我有一个我在过去两天无法弄清楚的具有约束力的问题。我已经完全了解了SO上的大多数相关主题,但我仍然无法确定我的错误所在。
我遇到的问题是我的程序中有一个文本框。它的目的是显示用户从文件浏览器中选择的文件。我已将它的text属性绑定到一个名为parameterFileSelected
的字符串,但文本框永远不会更新,即使调试似乎显示iNotifyPropertyChanged被调用并正确执行。
如果我的代码中有任何错误,请帮助我查看下面的代码。
文本框是名为GenerateReports的xaml的一部分,该视图与GenerateReportsViewModel绑定如下:
将datacontext设置为GenerateReportsViewModel的代码
<Grid >
<Grid.DataContext>
<vm:GenerateReportsViewModel/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
....
TextBox的代码。我已经尝试删除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;
}
}
这是viewmodel所附加的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));
}
}
}
修改 应用凯文的建议后的工作解决方案
为简单起见,Datacontext是在XAML中设置的。
<Grid>
<Grid.DataContext>
<vm:GenerateReportsViewModel x:Name="generateReportsViewModel"/>
</Grid.DataContext>
然后,我直接从后面的代码调用文本框绑定的字符串,在viewmodel中。
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); }
}
}
谢谢Kevin的解决方案。现在我的2天问题已经解决了。
我发现我之前的ViewModelBase正在调用iNotifyPropertyChanged,但不知何故,当View更新时,值为null。
答案 0 :(得分:1)
我试图理解为什么在viewModel中使用ref关键字。我从Classon和Baxter书中学到了一种很好的方法来创建BaseViewModel,你可以在下面找到它。视图模型像你一样实现了INotifyPropertyChanged。您使用[CallerMemberName]所做的事情很棒,因为它可以引用我们的属性,这真的很神奇。
视图模型使用字典来存储其属性。它使用一个非常巧妙的技巧来查看字典键,看看我们是否包含属性的字符串名称。否则,我们将返回一个默认的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,使用我提供给您的公共视图模型,您的课程将成为:
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是私有的。这对您的代码有一些影响。不要忘记,通过defaut,课程是私人的!
至于你的代码背后,即使它可能被认为是不好的做法,我建议你有一个私有字段(OpenFileDialog _openFileDialog),你在初始化页面时构建。因为每次单击按钮时都会使用您需要应用程序的更多数据。
<强> //修改 我查看了我的代码,似乎该属性没有正确编程。 公共类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;
}