XAML 绑定到自定义控件依赖项属性的WPF不会触发属性更改

wydwbb8l  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(128)

绑定到自定义控件上的依赖项属性时遇到问题。
这是我的自定义控件。它基本上是一个文本框,只允许数字输入,并公开了一个“Value”依赖属性,您应该能够绑定到该属性以获取和设置值。

NumberBox.xaml

<UserControl x:Class="CustomControls.NumberBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Name="root">
    
    <TextBox Text="{Binding ValueAsString, ElementName=root, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Right"/>
    
</UserControl>

字符串

NumberBox.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace CustomControls
{
    public partial class NumberBox : UserControl
    {
        public string ValueAsString
        {
            get { return (string)GetValue(ValueAsStringProperty); }
            set { SetValue(ValueAsStringProperty, value); }
        }

        public static readonly DependencyProperty ValueAsStringProperty =
            DependencyProperty.Register("ValueAsString", typeof(string), typeof(NumberBox), new PropertyMetadata("0", InputValidation));

        public double Value
        {
            get => (double)GetValue(ValueProperty);
            set => SetValue(ValueProperty, value);
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(NumberBox), new PropertyMetadata(0.0, ValueChanged));

        public NumberBox()
        {
            InitializeComponent();
        }

        private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is NumberBox box)
            {
                var input = box.Value.ToString();
                input = input.Replace(',', '.');
                input = RemoveDuplicateDecimalSymbols(input);
                input = RemoveLeadingZeros(input);
                box.ValueAsString = input;
            }
        }

        private static void InputValidation(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is NumberBox box)
            {
                var input = box.ValueAsString;

                input = input.Replace(',', '.');
                input = RemoveDuplicateDecimalSymbols(input);
                input = RemoveLeadingZeros(input);

                if (double.TryParse(input,
                                    System.Globalization.NumberStyles.Number,
                                    System.Globalization.CultureInfo.InvariantCulture,
                                    out double parsed))
                {
                    box.Value = parsed;
                }

                box.ValueAsString = input;
            }
        }

        private static string RemoveDuplicateDecimalSymbols(string input)
        {
            var split = input.Split('.');

            if (split.Length == 1)
                return input;

            var retval = string.Empty;

            for (int i = 0; i < split.Length; i++)
            {
                var part = split[i];
                retval += part;

                if (i == 0)
                    retval += ".";
            }

            return retval;
        }

        private static string RemoveLeadingZeros(string input)
        {
            string returnValue = string.Empty;
            bool allLeadingZerosRemoved = false;

            for (int i = 0; i < input.Length; i++)
            {
                char c = input[i];

                if (allLeadingZerosRemoved || c != '0')
                {
                    returnValue += c;

                    if (char.IsNumber(c) || (i < input.Length - 1 && input[input.Length - 1] == '.'))
                        allLeadingZerosRemoved = true;

                    continue;
                }

                if (c != '0')
                    returnValue += c;
            }

            return returnValue;
        }
    }
}


然后我还做了一个小的视图模型来简化我的用例。NumberBoxViewModel.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace CustomControls
{
    public class NumberBoxViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        private double someBoundValue;
        public double SomeBoundValue
        {
            get => someBoundValue;
            set
            {
                if (SetField(ref someBoundValue, value))
                {
                    int i = 0;      // For the sake of setting a breakpoint here
                }
            }
        }
    }
}


然后我在主窗口中这样使用它:MainWindow.xaml

<Window x:Class="CustomControls.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CustomControls"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
    <Grid x:Name="MainGrid">
        <local:NumberBox x:Name="Box" Value="{Binding SomeBoundValue}"/>
    </Grid>
    
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace CustomControls
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            NumberBoxViewModel viewModel = new NumberBoxViewModel();
            Box.DataContext = viewModel;
            //MainGrid.DataContext = viewModel;     // Does not work either
        }
    }
}


当我在文本框中输入一些东西时,我的断点在NumberBox.InputValidation & NumberBox.ValueChanged中命中。我绑定到“Value”的属性从未触发值更改(请参阅NumberBoxViewModel.SomeBoundValue的set属性)。
我是不是错过了什么愚蠢的东西?这里发生了什么。有人能解释一下属性和依赖属性之间的绑定是如何工作的吗?用户定义的依赖属性是否与内置属性(如TextBlock上的Text字段)有不同的行为?

flvlnr44

flvlnr441#

谢谢大家的回答。这个问题是通过调整依赖项属性定义来使用双向绑定来解决的。

private string ValueAsString
{
    get => (string)GetValue(ValueAsStringProperty);
    set => SetValue(ValueAsStringProperty, value);
}

private static readonly DependencyProperty ValueAsStringProperty =
    DependencyProperty.Register(nameof(ValueAsString),
                                typeof(string),
                                typeof(NumberBox),
                                new PropertyMetadata("0", InputValidation));

public double Value
{
    get => (double)GetValue(ValueProperty);
    set => SetValue(ValueProperty, value);
}

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(nameof(Value),
                                typeof(double),
                                typeof(NumberBox),
                                new FrameworkPropertyMetadata(0.0,
                                                              FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                              ValueChanged));

字符串

相关问题