我正在尝试制作一个具有可绑定属性的示例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
}
}
}有一个“MyDataObject”类的实例,其属性为“StringData”,它使用计时器定期修改。我的用户控件被绑定到它的属性“StringData”。如果“MainWindow.xaml”文件中的绑定设置为“TwoWay”,则用户控件将不断更新,但如果我使用“OneWay”绑定,则用户控件将更新一次,然后“MyDataObject”类的实例的“PropertyChanged”事件不会再次激发,因为它突然没有订阅者。
为什么“OneWay”绑定被调用一次后就停止工作了?什么代码更改可以让“TwoWay”和“OneWay”绑定保持工作?
发布于 2015-06-01 17:19:32
首先。
this.MLTBLabel.DataContext = this;
this.MLTBTextBox.DataContext = this;不要,不要!
绝不可能。永远不会。永远不会。从代码隐藏设置DataContext。一旦你这样做了,你就失去了从父控件绑定到用户控件的依赖属性的神奇美感。换句话说,就是不要这样做。
下面是你应该做的:
给你的UserControl一个x:Name。
<UserControl ...
x:Name="usr">将UserControl的依赖属性绑定到元素,如下所示:
<TextBlock Text="{Binding MyDependencyProperty, ElementName=usr}" ... />将用户控件的DataContext属性绑定到元素,如下所示:
<TextBlock Text="{Binding MyDataContextProperty}"/>使用此方法将允许您在MainWindow中设置UserControl的DataContext,但仍然可以绑定到UserControl中的用户控件的依赖属性。如果在代码隐藏中设置UserControl的DataContext,将无法绑定到依赖项属性。
现在,来看看你的实际问题。
所有这些:
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
}忘了它吧。看起来你是想绕过我之前说过的那些坏事。
你应该绑定到你的依赖属性:
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Text, ElementName=usr}"/>您遇到的另一个问题是,在您的MainWindow中,您正在使用UserControl上的绑定。
Text="{Binding StringData, Mode=OneWay}"现在,因为您已经在代码隐藏中设置了DataContext。这实际上是在说:
从当前控件的DataContext绑定到StringData。
在您的情况下,这是一个与您的MainWindow DataContext完全不同的绑定。(就像您在UserControl中显式设置了DataContext一样)。
浏览一下我前面提到的内容。有很多东西需要学习,但这只是一个开始。
发布于 2015-06-01 02:10:32
在我看来,它是这样的:
this.StringData = ((this.StringData ?? string.Empty).Length >= 4 ? string.Empty : this.StringData + "*");
}第一次触发计时器时,this.StringData为null,因此“??”在表达式中返回string.Empty。然后,它会检查长度是否为>= 4。如果不是,则将this.StringData从null设置为string.Empty。由于属性只在更改时更新,因此INotifyPropertyChanged将触发一次。
第二次,我们从string.Empy转到string.Empty,所以INotifyPropertyChanged不会触发,因为没有变化。
从本质上讲,计时器正在触发,但this.StringData现在停留在string.Empty上,这意味着INotifyPropertyChanged会忽略它。这是有道理的-如果属性实际上没有改变,为什么WPF运行时要麻烦地将更新从C#属性推送到图形用户界面呢?这只会让事情变慢,而不会有任何收获。
如果你使用双向绑定,这一切都会改变。如果this.StringData的长度设置为4个字符或更多,那么它就像一匹赛马:它将每隔10秒执行一次代码,在它后面追加一个"*“。
因此,如果您在启动时将this.StringData设置为“*”,它将与OneWay或TwoWay绑定一起工作,并且您将看到字符串的长度随着计时器的触发而增加。
当然,如果设置为OneWay binding,字符串将只会连续添加一个*,并且不会响应用户输入。我认为OneWay是OneWayFromSource的缩写,因此C#属性中的更改将被推送到XAML中,但是来自XAML的任何更改都不会被推送回C#中。
https://stackoverflow.com/questions/30560390
复制相似问题