WPF TextBox 未使用数据绑定、iNotifyPropertyChanged 和 PropertyChanged 触发器进行更新
WPF TextBox Not Updating with Data Binding, iNotifyPropertyChanged, and PropertyChanged Trigger
我遇到了一个过去两天未能解决的具有约束力的问题。我已经彻底浏览了 SO 上的大部分相关线程,但我仍然无法查明我的错误所在。
我遇到的问题是我程序中的一个文本框。它的目的是显示用户从文件浏览器中选择的文件。我已将它的文本 属性 绑定到一个名为 parameterFileSelected
的字符串,但文本框永远不会更新,即使调试似乎显示 iNotifyPropertyChanged 已被正确调用和执行。
如果我的代码有任何错误,请帮我看看下面的代码。
文本框是名为 GenerateReports 的 xaml 的一部分,此视图与 GenerateReportsViewModel 相关联,如下所示:
将数据上下文设置为 GenerateReportsViewModel 的代码
<Grid >
<Grid.DataContext>
<vm:GenerateReportsViewModel/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
....
文本框代码。我试过删除 Twoway 模式,将其更改为 Oneway 并删除该模式,但没有区别。
<TextBox Grid.Column="2" Grid.Row="1" Margin="5" Text="{Binding parameterFileSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
获取文件浏览器,然后将选择的文件结果传递给GenerateReportsViewModel,这是代码隐藏文件中的函数。 genviewmodel 在代码隐藏文件的开头初始化为 GenerateReportsViewModel genViewModel = new GenerateReportsViewModel();
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
DataContext = genViewModel;
genViewModel.updateParameterFileSelected(openFileDialog.FileName.ToString());
}
}
这是在 GenerateReportsViewModel 中调用的代码,用于更新文本框绑定到的 parameterFileSelected 字符串。
class GenerateReportsViewModel : ViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
这是视图模型附加到的 ViewModelBase。
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property != null)
{
if (property.Equals(value)) return;
}
OnPropertyChanged(propertyName);
property = value;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
编辑
应用 Kevin 的建议后的工作解决方案
为简单起见,Datacontext 设置在 XAML。
<Grid>
<Grid.DataContext>
<vm:GenerateReportsViewModel x:Name="generateReportsViewModel"/>
</Grid.DataContext>
然后,我直接从代码隐藏在视图模型中调用文本框绑定到的字符串。
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
generateReportsViewModel.parameterFileSelected = openFileDialog.FileName.ToString();
}
}
ViewModel 现在使用 Kevin 的 ViewModelBase:
public class GenerateReportsViewModel : ViewModelBase
{
public string parameterFileSelected
{
get { return this.GetValue<string>(); }
set { this.SetValue(value); }
}
}
感谢凯文提供的解决方案。现在我2天的问题解决了。
我发现我以前的 ViewModelBase 正在调用 iNotifyPropertyChanged,但不知何故,当更新视图时,该值却为 null。
我想了解为什么要在您的 viewModel 中使用 ref 关键字。我从 Classon 和 Baxter 的书中学到了一种创建 BaseViewModel 的好方法,您可以在下面找到该书。视图模型像您一样实现 INotifyPropertyChanged 。您对 [CallerMemberName] 所做的很棒,我们可以通过它引用我们的属性的方式真的很神奇。
视图模型使用字典来存储其属性。它使用了一个非常巧妙的技巧来查看字典键以查看我们是否包含 property.Otherwise 的字符串名称,我们将 return 一个默认的 T 值。
public class CommonBaseViewModel: INotifyPropertyChanged
{
private Dictionary<string, object> Values { get; set; }
protected CommonBaseViewModel()
{
this.Values = new Dictionary<string, object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected T GetValue<T>([CallerMemberName] string name=null)
{
if (this.Values.ContainsKey(name))
{
return (T)this.Values[name];
}
else
{
return default(T);
}
}
protected void SetValue(object value, [CallerMemberName] string name = null)
{
this.Values[name] = value;
//notify my property
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected void OnPropertyChanged([CallerMemberName] string name=null)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
至于您的 GenerateReportViewModel,使用我提供给您的通用视图模型,您的 class 将变为:
public class GenerateReportsViewModel : CommonViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
哦,之前我忘了,我不知道这是不是你的意图,但你的GenerateReportViewModel是私有的。这对您的代码有一些影响。不要忘记,默认情况下,classes 是私有的!
至于你的代码背后,即使它可能被认为是不好的做法,我建议你有一个你在初始化页面时构建的私有字段(OpenFileDialog _openFileDialog)。因为每次单击按钮时都这样做会消耗应用程序需要的更多数据。
//编辑
我检查了我的代码,似乎 属性 没有正确编程。
public class GenerateReportsViewModel:CommonViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
更多关于我关于构建页面和绑定视图模型的评论。创建页面时,您必须为该页面创建视图模型,然后将其绑定到数据上下文。
我不知道你在代码中做了什么,但我可以提供这个示例,例如
public GenerateReportView()
{
InitializeComponent();
//Some operations
var generateReportViewModel = new GenerateReportViewModel();
this.DataContext = generateReportViewModel;
}
我遇到了一个过去两天未能解决的具有约束力的问题。我已经彻底浏览了 SO 上的大部分相关线程,但我仍然无法查明我的错误所在。
我遇到的问题是我程序中的一个文本框。它的目的是显示用户从文件浏览器中选择的文件。我已将它的文本 属性 绑定到一个名为 parameterFileSelected
的字符串,但文本框永远不会更新,即使调试似乎显示 iNotifyPropertyChanged 已被正确调用和执行。
如果我的代码有任何错误,请帮我看看下面的代码。
文本框是名为 GenerateReports 的 xaml 的一部分,此视图与 GenerateReportsViewModel 相关联,如下所示:
将数据上下文设置为 GenerateReportsViewModel 的代码
<Grid >
<Grid.DataContext>
<vm:GenerateReportsViewModel/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
....
文本框代码。我试过删除 Twoway 模式,将其更改为 Oneway 并删除该模式,但没有区别。
<TextBox Grid.Column="2" Grid.Row="1" Margin="5" Text="{Binding parameterFileSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
获取文件浏览器,然后将选择的文件结果传递给GenerateReportsViewModel,这是代码隐藏文件中的函数。 genviewmodel 在代码隐藏文件的开头初始化为 GenerateReportsViewModel genViewModel = new GenerateReportsViewModel();
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
DataContext = genViewModel;
genViewModel.updateParameterFileSelected(openFileDialog.FileName.ToString());
}
}
这是在 GenerateReportsViewModel 中调用的代码,用于更新文本框绑定到的 parameterFileSelected 字符串。
class GenerateReportsViewModel : ViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
这是视图模型附加到的 ViewModelBase。
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property != null)
{
if (property.Equals(value)) return;
}
OnPropertyChanged(propertyName);
property = value;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
编辑 应用 Kevin 的建议后的工作解决方案
为简单起见,Datacontext 设置在 XAML。
<Grid>
<Grid.DataContext>
<vm:GenerateReportsViewModel x:Name="generateReportsViewModel"/>
</Grid.DataContext>
然后,我直接从代码隐藏在视图模型中调用文本框绑定到的字符串。
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
generateReportsViewModel.parameterFileSelected = openFileDialog.FileName.ToString();
}
}
ViewModel 现在使用 Kevin 的 ViewModelBase:
public class GenerateReportsViewModel : ViewModelBase
{
public string parameterFileSelected
{
get { return this.GetValue<string>(); }
set { this.SetValue(value); }
}
}
感谢凯文提供的解决方案。现在我2天的问题解决了。
我发现我以前的 ViewModelBase 正在调用 iNotifyPropertyChanged,但不知何故,当更新视图时,该值却为 null。
我想了解为什么要在您的 viewModel 中使用 ref 关键字。我从 Classon 和 Baxter 的书中学到了一种创建 BaseViewModel 的好方法,您可以在下面找到该书。视图模型像您一样实现 INotifyPropertyChanged 。您对 [CallerMemberName] 所做的很棒,我们可以通过它引用我们的属性的方式真的很神奇。
视图模型使用字典来存储其属性。它使用了一个非常巧妙的技巧来查看字典键以查看我们是否包含 property.Otherwise 的字符串名称,我们将 return 一个默认的 T 值。
public class CommonBaseViewModel: INotifyPropertyChanged
{
private Dictionary<string, object> Values { get; set; }
protected CommonBaseViewModel()
{
this.Values = new Dictionary<string, object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected T GetValue<T>([CallerMemberName] string name=null)
{
if (this.Values.ContainsKey(name))
{
return (T)this.Values[name];
}
else
{
return default(T);
}
}
protected void SetValue(object value, [CallerMemberName] string name = null)
{
this.Values[name] = value;
//notify my property
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected void OnPropertyChanged([CallerMemberName] string name=null)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
至于您的 GenerateReportViewModel,使用我提供给您的通用视图模型,您的 class 将变为:
public class GenerateReportsViewModel : CommonViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
哦,之前我忘了,我不知道这是不是你的意图,但你的GenerateReportViewModel是私有的。这对您的代码有一些影响。不要忘记,默认情况下,classes 是私有的!
至于你的代码背后,即使它可能被认为是不好的做法,我建议你有一个你在初始化页面时构建的私有字段(OpenFileDialog _openFileDialog)。因为每次单击按钮时都这样做会消耗应用程序需要的更多数据。
//编辑 我检查了我的代码,似乎 属性 没有正确编程。 public class GenerateReportsViewModel:CommonViewModelBase {
private string _parameterFileSelected;
public string parameterFileSelected
{
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
更多关于我关于构建页面和绑定视图模型的评论。创建页面时,您必须为该页面创建视图模型,然后将其绑定到数据上下文。 我不知道你在代码中做了什么,但我可以提供这个示例,例如
public GenerateReportView()
{
InitializeComponent();
//Some operations
var generateReportViewModel = new GenerateReportViewModel();
this.DataContext = generateReportViewModel;
}