【问题标题】:Update a label's text without needing to call the label's NotifyPropertyChanged from every property无需从每个属性调用标签的 NotifyPropertyChanged 即可更新标签的文本
【发布时间】:2019-08-04 19:13:02
【问题描述】:

我正在尝试实现一个 WPF 表单(显示员工数据),以便当任何字段发生更改时,头部标签 (FullnameTitle) 带有一个星号。例如。约翰·史密斯*

我正在使用 MVVM,其 ViewModel 基础还可以跟踪更改。我还选择将模型属性分开(即 NotifyPropertyChanged 在 ViewModel 中)

以下是我目前拥有的。我知道要更新头部标签,我需要在所有表单字段上调用 ​​NotifyPropertyChanged("FullnameTitle")

我的问题是,有没有更好的方法,不需要为每个领域都调用它? (我的完整员工表格有 50 多个字段)

窗口 XAML

<Label x:Name="HeadLabel" Content="{Binding FullnameTitle, FallbackValue='Employee Details'}" FontSize="28"/>
<StackPanel>
    <Label Content="Employee ID"/>
    <TextBox IsEnabled="False" Text="{Binding Employee.EmployeeId}"/>
    <Label Content="First name"/>
    <TextBox Text="{Binding Firstname, TargetNullValue=''}"/>
    <Label Content="Surname"/>
    <TextBox Text="{Binding Surname, TargetNullValue=''}"/>
    <Label Content="DOB"/>
    <DatePicker SelectedDate="{Binding Dob, TargetNullValue=''}"/>
    <Label Content="Age"/>
    <TextBox Text="{Binding Age, Mode=OneWay}" IsReadOnly="True"/>
</StackPanel>

视图模型

class EmployeeViewModel : ViewModelBase
{
    public EmployeeModel Employee { get; set; }

    public string FullnameTitle
    {
        get
        {
            return string.Format("{0} {1}{2}", Employee.Firstname, Employee.Surname, IsDirty ? "*" : "");
        }
    }

    public string Firstname
    {
        get
        {
            return Employee.Firstname;
        }
        set
        {
            Employee.Firstname = ApplyPropertyChange(Employee.Firstname, value, "Firstname");
        }
    }

    public string Surname
    {
        get
        {
            return Employee.Surname;
        }
        set
        {
            Employee.Surname = ApplyPropertyChange(Employee.Surname, value, "Surname");
        }
    }

    public DateTime? Dob
    {
        get
        {
            return Employee.Dob;
        }
        set
        {
            Employee.Dob = ApplyPropertyChange(Employee.Dob, value, "Dob");
            NotifyPropertyChanged("Age");
        }
    }

    public int Age
    {
        get
        {
            return Employee.Age;  // Age calculation done in EmployeeModel
        }
    }
}

ViewModelBase

abstract class ViewModelBase : INotifyPropertyChanged
{
    public Dictionary<string, object> Changes { get; private set; }
    public event PropertyChangedEventHandler PropertyChanged;

    public ViewModelBase()
    {
        Changes = new Dictionary<string, object>();
    }

    public bool IsDirty
    {
        get { return Changes.Count > 0; }
    }

    public T ApplyPropertyChange<T>(T field, T value, string caller)
    {
        // Only do this if the value changes
        if (field == null || !field.Equals(value))
        {
            string propertyName = caller;  // Property name

            field = value;                 // Set the value

            Changes[propertyName] = value; // Change tracking

            NotifyPropertyChanged(propertyName); // Notify change
        }

        return field;
    }

    public void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

【问题讨论】:

  • 只有在属性发生更改时才会引发事件,不是吗?你有什么问题?
  • 我想看看是否有比需要在每个绑定字段属性上添加 NotifyPropertyChanged("FullnameTitle") 更好的方法。而且由于 FullnameTitle 标签只需要在第一个表单的字段值更改后更新...有没有办法只更新一次标签,并且仍然不需要在每个绑定字段属性上使用 NotifyPropertyChanged("FullnameTitle")(包装在 if 语句中)跨度>

标签: c# wpf xaml mvvm


【解决方案1】:

使用事件。

在您的视图模型中订阅PropertyChanged

EmployeeViewModel.cs

class EmployeeViewModel : ViewModelBase
{
  public EmployeeViewModel()
  {
    this.PropertyChanged += (s, e) => NotifyPropertyChanged(nameof(this.FullnameTitle));
  }

  ...
}

但要小心。调用PropertyChanged 事件调用器NotifyPropertyChanged 以获得一个属性 本身引发PropertyChanged 事件将导致无限递归。

另一种更好的方法

使用由ViewModelBase.IsDirty触发的多重绑定IMultiValueConverter

IsDirtyToStringConverter.cs

class IsDirtyToStringConverter : IMultiValueConverter
{
  #region Implementation of IMultiValueConverter

  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  {
    if (values[0] is EmployeeViewModel viewModel && values[1] is bool modelIsDirty)
    {
      return string.Format("{0} {1}{2}", 
        viewModel.Firstname, 
        viewModel.Surname, 
        modelIsDirty 
          ? "*" 
          : string.Empty);
    }

    return Binding.DoNothing;
  }

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
  {
    throw new NotSupportedException();
  }

  #endregion
}

Window.xaml

<Window.Resources>
  <IsDirtyToStringConverter x:Keyy="IsDirtyToStringConverter" />
</Window.Resources>

<TextBlock x:Name="HeadLabel" FallbackValue='Employee Details'}" FontSize="28" >
  <TextBlock.Text>
    <MultiBinding Converter="{StaticResource IsDirtyToStringConverter}"
                  FallbackValue='Employee Details'>
       <Binding Path="." />           
       <Binding Path="IsDirty" />
     </MultiBinding>
   </TextBlock.Text>
</TextBlock>
<StackPanel>
  <Label Content="Employee ID"/>
  <TextBox IsEnabled="False" Text="{Binding Employee.EmployeeId}"/>
  <Label Content="First name"/>
  <TextBox Text="{Binding Firstname}"/>
  <Label Content="Surname"/>
  <TextBox Text="{Binding Surname}"/>
  <Label Content="DOB"/>
  <DatePicker SelectedDate="{Binding Dob}"/>
  <Label Content="Age"/>
  <TextBlock Text="{Binding Age}" />
</StackPanel>

【讨论】:

  • 我不太明白。我将其更改为按照您的建议进行。但是在编辑没有任何 NotifyPropertyChanged 的​​字段时标签不会更新,并且在调用 NotifyPropertyChanged("Age") 的 DOB 更新时引发 System.StackOverflow 异常
  • NotifyPropertyChanged 是必需的。你什么意思?绑定需要PropertyChanged 事件。如果您确定NotifyPropertyChanged("Age") 导致异常,请检查调用堆栈。此外,最好明确设置Age。让CalculateAge() 返回年龄并将其分配给Age 属性,该属性应该是一个简单的自动属性。它的阅读效果也更好。
  • 我使用多重绑定添加了一种更简洁的方法。标签绑定到IsDirty。当 IsDirty 在视图模型中更改时,标签将更新。您无需再为NotifyPropertyChanged 烦恼。但也要明确设置Age(使其成为自动属性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-23
  • 1970-01-01
  • 2019-11-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多