简体   繁体   English

具有绑定模式的WPF UserControl = OneWay

[英]WPF UserControl with Binding Mode=OneWay

I am trying to make a sample WPF user control (maybe it would be better to say “developer control”) with bindable properties. 我正在尝试使用可绑定属性制作一个示例WPF用户控件(可能更好地说“开发人员控件”)。 My code consists of these files: 我的代码包含以下文件:

----- 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
        }
    }
}

There is an instance of the “MyDataObject” class with the property “StringData”, which is modified periodically using a timer. 有一个“MyDataObject”类的实例,其属性为“StringData”,使用计时器定期修改。 My user control is bound to its property “StringData”. 我的用户控件绑定到其属性“StringData”。 If the binding in the “MainWindow.xaml” file is set as “TwoWay”, the user control keeps being updated, but if I use the “OneWay” binding, then the user control is updated a single time and then the “PropertyChanged” event of the instance of the “MyDataObject” class does not fire again, because suddenly it has no subscriber. 如果“MainWindow.xaml”文件中的绑定设置为“TwoWay”,则用户控件会不断更新,但如果我使用“OneWay”绑定,则用户控件会更新一次,然后“PropertyChanged” “MyDataObject”类实例的事件不会再次触发,因为它突然没有订阅者。

Why does the “OneWay” binding stop working after being invoked once ? 为什么“OneWay”绑定在被调用一次后停止工作? What code change would allow both the “TwoWay” and “OneWay” bindings to keep working ? 什么代码更改将允许“TwoWay”和“OneWay”绑定继续工作?

Firstly. 首先。

this.MLTBLabel.DataContext = this;
this.MLTBTextBox.DataContext = this;

Noooooooooooooooo! Noooooooooooooooo!

Never. 决不。 Ever. 永远。 Ever. 永远。 Set your DataContext from code-behind. 从代码隐藏中设置DataContext As soon as you do this, you lose the magical beauty of binding to your user control's dependency properties from your parent control. 一旦执行此操作,您就会失去从父控件绑定到用户控件的依赖项属性的神奇美感。 In other words, just don't do it. 换句话说,就是不要这样做。

Here's what you should do: 这是你应该做的:

Give your UserControl an x:Name . UserControl提供x:Name

<UserControl ...
    x:Name="usr">

Bind your UserControl's Dependency Properties to your elements, like this: 将UserControl的依赖属性绑定到元素,如下所示:

<TextBlock Text="{Binding MyDependencyProperty, ElementName=usr}" ... />

Bind your UserControl's DataContext properties to your elements, like this: 将UserControl的DataContext属性绑定到元素,如下所示:

<TextBlock Text="{Binding MyDataContextProperty}"/>

Using this method will allow you to set the DataContext of your UserControl in the MainWindow , but still be able to bind to the UserControl's dependency properties within the UserControl. 使用此方法将允许您在MainWindow设置UserControlDataContext ,但仍然能够绑定到UserControl中UserControl的依赖项属性。 If you set the DataContext of your UserControl in the code-behind, you will not be able to bind to your Dependency Properties. 如果在代码隐藏中设置UserControl的DataContext,则无法绑定到依赖项属性。

Now, onto your actual problem. 现在,解决你的实际问题。

All of this: 所有这些:

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
    }

Forget about it. 忘掉它。 Looks like you're trying to get round the wrongdoings that I spoke about earlier. 看起来你正试图解决我之前谈到的错误行为。

You should be binding to your dependency properties instead: 您应该绑定到您的依赖项属性:

<Label Grid.Row="0" Grid.Column="0" Text="{Binding Text, ElementName=usr}"/>

Another issue that you have is that in your MainWindow , you are using a binding on your UserControl . 您遇到的另一个问题是,在您的MainWindow ,您正在使用UserControl上的绑定。

Text="{Binding StringData, Mode=OneWay}"

Now, as you have already set your DataContext in code-behind. 现在,因为您已经在代码隐藏中设置了DataContext What this is effectively saying is: 这有效地说的是:

Bind to StringData from the DataContext of the current control. 从当前控件的DataContext绑定到StringData。

Which in your case, is a totally different binding from your MainWindow DataContext. 在您的情况下,与您的MainWindow DataContext完全不同。 (As you have explicitly set the DataContext in your UserControl). (因为您在UserControl中明确设置了DataContext)。

Run through what I mentioned earlier. 贯穿我之前提到的。 There's a lot to learn, but it's a start. 有很多东西需要学习,但这是一个开始。

It looks to me like its this line: 它看起来像它的这一行:

this.StringData = ((this.StringData ?? string.Empty).Length >= 4 ? string.Empty : this.StringData + "*");
    }

The first time the timer fires, this.StringData is null so the '??' 计时器第一次触发时, this.StringData为空,所以'??' in the expression returns string.Empty . 在表达式中返回string.Empty It then checks if the length is >= 4. It isnt, so it sets this.StringData from null to string.Empty . 然后它检查长度是否> = 4.它不是,所以它将this.StringData从null设置为string.Empty As properties only update on a change, then INotifyPropertyChanged fires once. 由于属性仅在更改时更新,因此INotifyPropertyChanged触发一次。

The second time, we go from string.Empy to string.Empty , so INotifyPropertyChanged does not fire, as there is no change. 第二次,我们从string.Empy转到string.Empty ,因此INotifyPropertyChanged不会触发,因为没有变化。

Essentially, the timer is firing, but this.StringData is now stuck on string.Empty , which means INotifyPropertyChanged ignores it. 本质上,计时器正在触发,但是this.StringData现在停留在string.Empty ,这意味着INotifyPropertyChanged忽略它。 This makes sense - why should the WPF runtime go to the trouble of pushing an update from the C# property to the GUI, if the property hasn't actually changed? 这是有道理的 - 如果该属性实际上没有改变,为什么WPF运行时会遇到将更新从C#属性推送到GUI的麻烦? This would just slow things down for no gain. 这只会让事情变得缓慢而无益。

This all changes if you use two way binding. 如果您使用双向绑定,这一切都会改变。 If this.StringData ever gets set to a length of 4 characters or more, then its away like a racehorse: it will execute the code to append another "*" to it every 10 seconds. 如果this.StringData被设置为4个字符或更长的长度,那么它就像赛马一样:它会执行代码,每隔10秒向它附加一个“*”。

Thus, if you set this.StringData to "****" on startup, it will work with OneWay or TwoWay binding, and you will observe the string increasing in length as the timer fires. 因此,如果在启动时将this.StringData设置为“****”,它将与OneWay或TwoWay绑定一起使用,并且当计时器触发时,您将观察到字符串的长度增加。

Of course, if set to OneWay binding, the string will just have a * added to it continuously, and it won't respond to user input. 当然,如果设置为OneWay绑定,则字符串将连续添加*,并且不会响应用户输入。 I think of OneWay as shorthand for OneWayFromSource , so the changes in the C# property will be pushed into the XAML, but any changes from the XAML will not get pushed back into the C#. 我认为OneWayOneWayFromSource简写,因此C#属性的更改将被推送到XAML中,但是来自XAML的任何更改都不会被推回到C#中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM