自定义UserControl的WPF数据绑定问题

本文关键字:数据绑定 问题 WPF UserControl 自定义 | 更新日期: 2023-09-27 18:26:34

我在UserControl的DependencyProperty和TextBlock的Dependency Property上使用DataBinding。我将这些绑定到属性TestValue。首先,我用TestValue="第一次设置TestValue!"初始化TestValue真管用同时更新UserControl和TextBlock
然后,通过DispatcherTimer,我每秒向TestValue写入一个随机值,这不起作用。TextBlock得到更新,但UserControl没有。

这是我的主窗口代码:

//MainWindow.cs
public partial class MainWindow : Window
{
    private MainWindowViewModel viewModel;
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this.viewModel = new MainWindowViewModel()
        {
            TestValue = "TestValue set first time!",
        };
    }
}
//MainWindow.xaml
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local ="clr-namespace:RhinoTouchUIwpf;assembly="
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="RhinoTouchUIwpf.MainWindow"
    SizeToContent="WidthAndHeight">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
    </Grid.ColumnDefinitions>
    <local:UserControl1 OperationName="{Binding TestValue, Mode=OneWay}" Grid.Row="0"/>
    <TextBlock Text="{Binding TestValue, Mode=OneWay}" FontSize="15" Grid.Row="1" />
</Grid>

这是UserControl的代码:

//UserControl1.cs
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }
    public static DependencyProperty MyDependencyProperty =DependencyProperty.Register("OperationName", typeof(String), typeof(UserControl1), new UIPropertyMetadata(null, MyDependencyPropertyChangedHandler));
    private static void MyDependencyPropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        ((UserControl1)sender).OperationName = (String)e.NewValue;
        ((UserControl1)sender).NumberLabel.Text = (String)e.NewValue;
    }
    public String OperationName
    {
        get { return (string)(this.GetValue(MyDependencyProperty)); }
        set { this.SetValue(MyDependencyProperty, value); }
    }
}
//UserControl1.xaml
<UserControl
         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" 
         xmlns:local="clr-namespace:RhinoTouchUIwpf" x:Class="RhinoTouchUIwpf.UserControl1" 
         mc:Ignorable="d">
<Border BorderThickness="1" BorderBrush="Blue" x:Name="ControlBorder">
    <Grid Background="AliceBlue">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock x:Name="NumberLabel" FontSize="20"  Text="0" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Border>

这是MainWindowViewModel的代码,DispatcherTimer每秒都会更改它的属性。

public class MainWindowViewModel : ViewModelBase
{
    DispatcherTimer dispatcherTimer; 
    public MainWindowViewModel()
    {
        dispatcherTimer = new DispatcherTimer();
        dispatcherTimer.Tick += Dispatcher_Tick;
        dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
        dispatcherTimer.IsEnabled = true;
    }
    void Dispatcher_Tick(object sender, EventArgs e)
    {
        this.TestValue = "" + DateTime.Now.Millisecond;
    }
    private String _TestValue;
    public String TestValue
    {
        get { return _TestValue; }
        set
        {
            _TestValue = value;
            OnPropertyChanged("TestValue");
        }
    }
    private static readonly MainWindowViewModel NullInstance = null;
}
public abstract class ViewModelBase : System.ComponentModel.INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this,
                new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

TextBlock的文本每秒都会更改,而UserControl1的文本则不会
我做错了什么?

自定义UserControl的WPF数据绑定问题

您遇到的问题是PropertyChangedCallback:中的这行代码

((UserControl1)sender).OperationName = (String)e.NewValue;

这里的问题是,事件处理程序将通知您对同一属性的新更改。当您输入此功能时,它来自:

set { this.SetValue(MyDependencyProperty, value); }

有效地进入了一个无限循环(依赖属性系统检测到并停止对该函数的进一步调用)。删除该行将解决您的问题。

另一方面,如果您在XAML中定义PropertyChangedCallback,则可以完全去掉它:

// On your UserControl element, add a x:Name="uc"
<TextBlock x:Name="NumberLabel" FontSize="20"  
           Text="{Binding OperationName, ElementName=uc, FallbackValue=0}" 
           Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center" 
           VerticalAlignment="Center"/>

这是完整的UserControl1.xaml

<UserControl x:Class="WpfApplication1.UserControl1"
             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" x:Name="uc"
             d:DesignHeight="300" d:DesignWidth="300">
    <Border BorderThickness="1" BorderBrush="Blue" x:Name="ControlBorder">
        <Grid Background="AliceBlue">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <TextBlock x:Name="NumberLabel" FontSize="20"  
                        Text="{Binding OperationName, ElementName=uc, FallbackValue=0}" 
                        Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center" 
                        VerticalAlignment="Center"/>
        </Grid>
    </Border>
</UserControl>