WPF ~ 绑定和 INotifyPropertyChanged 问题
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>
主要windowclass:
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 class
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);
}
}
}
}
首先你做错了很多事情。
你不应该使用 window 因为它有自己的数据上下文,你应该有一个你设置的视图模型。
您不应在视图中使用事件处理程序来操纵视图模型。您应该将按钮绑定到命令。
您的来源似乎是 "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);
}
}
}
并将此视图模型设置为您 window 的数据上下文。
this.DataContext = new MainWindowViewModel();
然后在您的 xaml 中,您应该这样绑定到视图模型
<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
(window)。您在 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
class,才能实际通知更改:
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>
您的代码存在一些问题。
首先,您永远不会将 "Source" 分配给数据上下文,因此您的第二个 TextBlock 无法找到 "HeaderText2".
的值
但是,如果您将 "Source" 分配给文本块数据上下文,那么我们可以获取 "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>
我们已经为您的第二个文本块命名,"TextBlock2" 并且还从您的绑定中删除了 "Datacontext" 部分。
然后我们将 "Source" 对象的创建从按钮事件移至 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";
}
不过还有一个细节。您的 class "Source" 确实实现了 "INotifyPropertyChanged",但它从未 "uses" 它。我的意思是,当您为 属性 "HeaderText2" 分配一个值时,您实际上从未 "notify" UI 某些东西随之改变,因此 UI 不会获取新值。考虑以下代码:
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set
{
headerText2 = value;
OnPropertyChanged("HeaderText2");
}
get
{
return headerText2;
}
}
string headerText2;
...
}
那么让我们来看看我们用 属性 "HeaderText2" 做了什么。每次 "HeaderText2" 被赋值时,它都会先将值保存在私有 属性 中(以便我们稍后可以从中读取)。但除此之外,我们还使用我们的属性名称调用 "OnPropertyChanged" 方法。该方法将依次检查是否有人 "listening" 到我们的 "PropertyChanged" 事件(并且由于我们在当前对象上有数据绑定,有人正在监听),并创建一个新事件。
现在我们已经为您的文本块分配了一个数据源,路径为 "HeaderText2",我们会在更新数据源 "HeaderText2" 时通知所有侦听器,我们正在更新 "HeaderText2"按钮点击事件。
编码愉快!
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>
主要windowclass:
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 class
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);
}
}
}
}
首先你做错了很多事情。
你不应该使用 window 因为它有自己的数据上下文,你应该有一个你设置的视图模型。
您不应在视图中使用事件处理程序来操纵视图模型。您应该将按钮绑定到命令。
您的来源似乎是 "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);
}
}
}
并将此视图模型设置为您 window 的数据上下文。
this.DataContext = new MainWindowViewModel();
然后在您的 xaml 中,您应该这样绑定到视图模型
<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
(window)。您在 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
class,才能实际通知更改:
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>
您的代码存在一些问题。 首先,您永远不会将 "Source" 分配给数据上下文,因此您的第二个 TextBlock 无法找到 "HeaderText2".
的值但是,如果您将 "Source" 分配给文本块数据上下文,那么我们可以获取 "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>
我们已经为您的第二个文本块命名,"TextBlock2" 并且还从您的绑定中删除了 "Datacontext" 部分。
然后我们将 "Source" 对象的创建从按钮事件移至 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";
}
不过还有一个细节。您的 class "Source" 确实实现了 "INotifyPropertyChanged",但它从未 "uses" 它。我的意思是,当您为 属性 "HeaderText2" 分配一个值时,您实际上从未 "notify" UI 某些东西随之改变,因此 UI 不会获取新值。考虑以下代码:
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set
{
headerText2 = value;
OnPropertyChanged("HeaderText2");
}
get
{
return headerText2;
}
}
string headerText2;
...
}
那么让我们来看看我们用 属性 "HeaderText2" 做了什么。每次 "HeaderText2" 被赋值时,它都会先将值保存在私有 属性 中(以便我们稍后可以从中读取)。但除此之外,我们还使用我们的属性名称调用 "OnPropertyChanged" 方法。该方法将依次检查是否有人 "listening" 到我们的 "PropertyChanged" 事件(并且由于我们在当前对象上有数据绑定,有人正在监听),并创建一个新事件。
现在我们已经为您的文本块分配了一个数据源,路径为 "HeaderText2",我们会在更新数据源 "HeaderText2" 时通知所有侦听器,我们正在更新 "HeaderText2"按钮点击事件。
编码愉快!