我在绑定到自定义控件上的依赖属性时遇到问题。
这是我的自定义控件。它基本上是一个文本框,只允许数字输入并公开一个“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 上的文本字段)具有不同的行为吗?
谢谢大家的回答。通过调整依赖属性定义以使用 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));