[英]WPF UserControl with Binding Mode=OneWay
我正在嘗試使用可綁定屬性制作一個示例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”綁定,則用戶控件會更新一次,然后“PropertyChanged” “MyDataObject”類實例的事件不會再次觸發,因為它突然沒有訂閱者。
為什么“OneWay”綁定在被調用一次后停止工作? 什么代碼更改將允許“TwoWay”和“OneWay”綁定繼續工作?
首先。
this.MLTBLabel.DataContext = this;
this.MLTBTextBox.DataContext = this;
Noooooooooooooooo!
決不。 永遠。 永遠。 從代碼隱藏中設置DataContext
。 一旦執行此操作,您就會失去從父控件綁定到用戶控件的依賴項屬性的神奇美感。 換句話說,就是不要這樣做。
這是你應該做的:
為UserControl
提供x:Name
。
<UserControl ...
x:Name="usr">
將UserControl的依賴屬性綁定到元素,如下所示:
<TextBlock Text="{Binding MyDependencyProperty, ElementName=usr}" ... />
將UserControl的DataContext屬性綁定到元素,如下所示:
<TextBlock Text="{Binding MyDataContextProperty}"/>
使用此方法將允許您在MainWindow
設置UserControl
的DataContext
,但仍然能夠綁定到UserControl中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)。
貫穿我之前提到的。 有很多東西需要學習,但這是一個開始。
它看起來像它的這一行:
this.StringData = ((this.StringData ?? string.Empty).Length >= 4 ? string.Empty : this.StringData + "*");
}
計時器第一次觸發時, this.StringData
為空,所以'??' 在表達式中返回string.Empty
。 然后它檢查長度是否> = 4.它不是,所以它將this.StringData
從null設置為string.Empty
。 由於屬性僅在更改時更新,因此INotifyPropertyChanged
觸發一次。
第二次,我們從string.Empy
轉到string.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#中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.