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

问题描述 投票:0回答:1

我在绑定到自定义控件上的依赖属性时遇到问题。

这是我的自定义控件。它基本上是一个文本框,只允许数字输入并公开一个“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 上的文本字段)具有不同的行为吗?

c# wpf xaml binding dependency-properties
1个回答
0
投票

谢谢大家的回答。通过调整依赖属性定义以使用 to-way 绑定来解决该问题。

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

public static readonly DependencyProperty ValueAsStringProperty =
    DependencyProperty.Register("ValueAsString", 
                                typeof(string), 
                                typeof(NumberBox), 
                                new FrameworkPropertyMetadata("0",
                                                              FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                              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 FrameworkPropertyMetadata(0.0,
                                                              FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                              ValueChanged));
© www.soinside.com 2019 - 2024. All rights reserved.