绑定模式为OneWay的WPF UserControl

Asp*_*pro 5 c# wpf xaml binding user-controls

我正在尝试制作具有可绑定属性的示例WPF用户控件(也许最好说“开发人员控件”)。我的代码包含以下文件:

----- MainWindow.xaml -----
<Window x:Class="Test_Binding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:testBinding="clr-namespace:Test_Binding"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <testBinding:MyLabelledTextBox x:Name="MLTB" LabelText="My custom control: MyLabelledTextBox" Text="{Binding StringData, Mode=OneWay}" />
    </StackPanel>
</Window>

----- MainWindow.xaml.cs -----
using System.Windows;

namespace Test_Binding
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.DataContext = new MyDataObject();
            this.InitializeComponent();
        }
    }
}

----- MyDataObject.cs -----
using System.Runtime.CompilerServices; // CallerMemberName
using System.ComponentModel; // INotifyPropertyChanged

namespace Test_Binding
{
    public class MyDataObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string stringData;
        public string StringData
        {
            get { return this.stringData; }
            set
            {
                if (value != this.stringData)
                {
                    this.stringData = value;
                    this.OnPropertyChanged();
                }
            }
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public MyDataObject()
        {
            System.Timers.Timer t = new System.Timers.Timer();
            t.Interval = 10000;
            t.Elapsed += t_Elapsed;
            t.Start();
        }

        private void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.StringData = ((this.StringData ?? string.Empty).Length >= 4 ? string.Empty : this.StringData + "*");
        }

    }
}

----- MyLabelledTextBox.xaml -----
<UserControl x:Class="Test_Binding.MyLabelledTextBox"
             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" 
             d:DesignHeight="300" d:DesignWidth="300">
  <StackPanel Background="Yellow">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*" />
            <ColumnDefinition Width="0.5*" />
        </Grid.ColumnDefinitions>   
        <Label x:Name="MLTBLabel" Grid.Row="0" Grid.Column="0" />
        <TextBox x:Name="MLTBTextBox" Grid.Row="0" Grid.Column="1" Background="Yellow" Text="{Binding Text, Mode=TwoWay}" />
    </Grid>
  </StackPanel>
</UserControl>

----- MyLabelledTextBox.xaml.cs -----
using System.Windows;
using System.Windows.Controls;

namespace Test_Binding
{
    /// <summary>
    /// Interaction logic for MyLabelledTextBox.xaml
    /// </summary>
    public partial class MyLabelledTextBox : UserControl
    {
        public static readonly DependencyProperty LabelTextProperty =
            DependencyProperty.Register("LabelText", typeof(string), typeof(MyLabelledTextBox),
            new PropertyMetadata(string.Empty, MyLabelledTextBox.LabelTextPropertyChanged));
        public string LabelText
        {
            get { return (string)this.GetValue(MyLabelledTextBox.LabelTextProperty); }
            set { this.SetValue(MyLabelledTextBox.LabelTextProperty, value); }
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(MyLabelledTextBox),
            new PropertyMetadata(string.Empty, MyLabelledTextBox.TextPropertyChanged));
        public string Text
        {
            get { return (string)this.GetValue(MyLabelledTextBox.TextProperty); }
            set { this.SetValue(MyLabelledTextBox.TextProperty, value); }
        }

        public MyLabelledTextBox()
        {
            this.InitializeComponent();

            this.MLTBLabel.DataContext = this;
            this.MLTBTextBox.DataContext = this;
            this.MLTBTextBox.TextChanged += new TextChangedEventHandler(this.MLTBTextBox_TextChanged);
        }

        private void MLTBTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            this.Text = this.MLTBTextBox.Text; // transfer changes from TextBox to bindable property (bindable property change notification will be fired)
        }

        private static void LabelTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MyLabelledTextBox)d).MLTBLabel.Content = (string)e.NewValue; // transfer changes from bindable property to Label
        }

        private static void TextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MyLabelledTextBox)d).MLTBTextBox.Text = (string)e.NewValue; // transfer changes from bindable property to TextBox
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

存在具有属性“ StringData”的“ MyDataObject”类的实例,可以使用计时器定期对其进行修改。我的用户控件绑定到其属性“ StringData”。如果将“ MainWindow.xaml”文件中的绑定设置为“ TwoWay”,则用户控件会不断更新,但是如果我使用“ OneWay”绑定,则将用户控件更新一次,然后再更新“ PropertyChanged” “ MyDataObject”类的实例的事件不会再次触发,因为它突然没有订阅者。

为什么“ OneWay”绑定在被调用一次后停止工作?哪些代码更改将允许“ TwoWay”和“ OneWay”绑定都继续起作用?

Con*_*ngo 0

在我看来,就像这一行:

this.StringData = ((this.StringData ?? string.Empty).Length >= 4 ? string.Empty : this.StringData + "*");
    }
Run Code Online (Sandbox Code Playgroud)

计时器第一次触发时this.StringData为空,因此“??” 在表达式中返回string.Empty. 然后它检查长度是否 >= 4。它不是,所以它this.StringData从 null 设置为string.Empty。由于属性仅在更改时更新,然后INotifyPropertyChanged触发一次。

第二次,我们从string.Empystring.Empty,所以INotifyPropertyChanged不会触发,因为没有变化。

本质上,计时器正在触发,但this.StringData现在卡在 上string.Empty,这意味着INotifyPropertyChanged忽略它。这是有道理的 - 如果属性实际上没有更改,为什么 WPF 运行时要大费周章地将更新从 C# 属性推送到 GUI?这只会减慢速度而没有任何好处。

如果您使用双向绑定,这一切都会改变。如果this.StringData将长度设置为 4 个字符或更多,那么它就会像赛马一样消失:它将执行代码,每 10 秒向其附加另一个“*”。

因此,如果您this.StringData在启动时设置为“****”,它将与 OneWay 或 TwoWay 绑定一起使用,并且您将观察到随着计时器触发,字符串的长度不断增加。

当然,如果设置为OneWay绑定,字符串只会不断地添加一个*,并且不会响应用户输入。我认为这是OneWay的简写OneWayFromSource,因此 C# 属性中的更改将被推送到 XAML,但 XAML 中的任何更改都不会被推送回 C#。