![](/img/trans.png)
[英]INotifyPropertyChanged in singleton and binding to the WPF element
[英]WPF ~ Trouble with Binding & INotifyPropertyChanged
WPF n00bie在这里,试图使他的UI正常工作。
所以我做了这个测试例子。 绑定到HeaderText1的文本块在应用程序启动时会正确更改,但是绑定到HeaderText2的文本块在单击按钮后不会更新。
我究竟做错了什么? 提前致谢!!
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=DataContext.HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=DataContext.HeaderText2}"></TextBlock>
</StackPanel>
</Window>
主窗口类:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DataBinding
{
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
public DataContextSample()
{
HeaderText = "YES";
InitializeComponent();
this.DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
Source source = new Source();
source.HeaderText2 = "YES2";
}
}
}
还有INotifyPropertyChanged类
using System.ComponentModel;
namespace DataBinding
{
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set; get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
}
首先,您做错了很多事情。
您不应该使用窗口,因为它是自己的数据上下文,应该设置一个视图模型。
您不应在视图中使用事件处理程序来操纵视图模型。 您应该将按钮绑定到命令。
您的源代码似乎是“ viewmodel”,请考虑将其重命名为MainWindowViewModel(为清楚起见),然后执行此操作。
public class MainWindowViewModel : INotifyPropertyChanged
{
private string headerText;
private string headerText2;
private ICommand updateHeaderText2;
public string HeaderText
{
set
{
return this.headerText;
}
get
{
this.headerText = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText");
}
}
public string HeaderText2
{
set
{
return this.headerText2;
}
get
{
this.headerText2 = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText2");
}
}
public ICommand UpdateHeaderText2
{
get
{
// Google some implementation for ICommand and add the MyCommand class to your solution.
return new MyCommand (() => this.HeaderText2 = "YES2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
并将此视图模型设置为窗口的datacontext。
this.DataContext = new MainWindowViewModel();
然后在您的xaml中,您应该这样绑定到viewmodel
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<!-- Not sure what this binding is? -->
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Command="{Binding UpdateHeaderText2}" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding HeaderText}"></TextBlock>
<TextBlock Text="{Binding HeaderText2}"></TextBlock>
</StackPanel>
</Window>
您将DataContext
设置this
(窗口)。 您在DataContext
没有名为HeaderText2
的属性,因此第二个绑定将不起作用。
我会这样做(无需过多更改您的代码,实际上我会使用适当的MVVM方法):
public partial class DataContextSample : Window
{
public Source Source { get; set; }
public string HeaderText { set; get; }
public MainWindow()
{
InitializeComponent();
HeaderText = "YES";
Source = new Source { HeaderText2 = "YES" };
DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
Source.HeaderText2 = "YES2";
}
}
我添加了一个名为Source
的新属性,该属性的类型为Source
。 在构造函数中将其初始HeaderText2
设置为相同的"YES"
,然后单击按钮将其更改为"YES2"
。
您还必须更改Source
类,以实际通知更改:
public class Source : INotifyPropertyChanged
{
private string _headerText2;
public string HeaderText2
{
get { return _headerText2; }
set
{
_headerText2 = value;
OnPropertyChanged("HeaderText2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
然后在您的XAML中:
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=Source.HeaderText2}"></TextBlock>
</StackPanel>
好的,您的代码存在一些问题。 首先,您永远不会将“源”分配给数据上下文,因此第二个TextBlock无法找到“ HeaderText2”的值。
但是,如果您将“源”分配给文本块数据上下文,那么我们可以获取“ HeaderText2”的值。 考虑下面的代码
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Name="TextBlock2" Text="{Binding Path=HeaderText2}"></TextBlock>
</StackPanel>
</Window>
我们为您的第二个Textblock命名为“ TextBlock2”,并从绑定中删除了“ Datacontext”部分。
然后,我们将“源”对象的创建从按钮事件移到了Windows构造函数中(当我们要做的只是更新属性时,无需每次单击按钮都创建一个新对象)
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
private Source source { get; set; }
public DataContextSample()
{
...
source = new Source();
TextBlock2.DataContext = source;
...
}
...
}
然后在您的按钮单击事件中,我们为您的数据绑定属性分配一个值“ YES2”。
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
...
source.HeaderText2 = "YES2";
}
但是,还有更多细节。 您的类“ Source”确实实现了“ INotifyPropertyChanged”,但从未“使用”它。 我的意思是,当您为属性“ HeaderText2”分配值时,您实际上从未“通知” UI发生了某些更改,因此UI不会获取新值。 考虑下面的代码:
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set
{
headerText2 = value;
OnPropertyChanged("HeaderText2");
}
get
{
return headerText2;
}
}
string headerText2;
...
}
因此,让我们看一下对属性“ HeaderText2”所做的工作。 每当“ HeaderText2”获得分配的值时,它将首先将该值保存在privat属性中(以便我们以后可以从中读取)。 但是除此之外,我们还使用属性名称调用“ OnPropertyChanged”方法。 该方法将依次检查是否有人“监听”了我们的“ PropertyChanged”事件(并且由于我们对当前对象进行了数据绑定,因此有人正在监听),并创建了一个新事件。
现在,我们已经为您的文本块分配了一个带有“ HeaderText2”路径的数据源,当我们在数据源上更新“ HeaderText2”并且在按钮click事件上更新“ HeaderText2”时,我们将通知所有侦听器。
编码愉快!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.