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;
}