【问题标题】:With MVVM how I can update a Label from many visual component or options?使用 MVVM 如何从许多可视组件或选项中更新标签?
【发布时间】:2012-07-18 08:32:02
【问题描述】:

我试图了解每次使用 WPF (XAML) 修改窗口上的选项时如何调用 Resfresh() 或 Work() 方法。我已经问了这个问题,但我还不够清楚。所以我再举一个更好的例子。

我想知道如何从许多视觉组件中更新标签。假设我们有 10 个标签为 0 到 9 的复选框,如果它们被选中,我想做它们的总和。

在经典 Winform 中,我将创建一个事件处理程序 OnClick() 并在每个 CheckBox 状态更改时调用该事件。 OnClick 调用 Refresh() 全局方法。刷新评估是否检查了每个 CheckBox 并在需要时将它们相加。在 Refresh() 方法结束时,我将 Label Text 属性设置为我的总和。

如何使用 XAML 和数据绑定来做到这一点?

<CheckBox Content="0" Name="checkBox0" ... IsChecked="{Binding Number0}" />
<CheckBox Content="1" Name="checkBox1" ... IsChecked="{Binding Number1}" />
<CheckBox Content="2" Name="checkBox2" ... IsChecked="{Binding Number2}" />
<CheckBox Content="3" Name="checkBox3" ... IsChecked="{Binding Number3}" />
<CheckBox Content="4" Name="checkBox4" ... IsChecked="{Binding Number4}" />
...
<Label Name="label1" ... Content="{Binding Sum}"/>

在我的 ViewModel 中,每个复选框都有一个数据绑定属性,而 Sum 有一个属性

private bool number0;
public bool Number0
{
    get { return number0; }
    set
    {
        number0 = value;
        NotifyPropertyChanged("Number0");
        // Should I notify something else here or call a refresh method?
        // I would like to create something like a global NotifyPropertyChanged("Number")
        // But how can I handle "Number" ???
    }
}

// Same for numer 1 to 9 ...

private bool sum;
public bool Sum
{
    get { return sum; }
    set
    {
        sum = value;
        NotifyPropertyChanged("Sum");
    }
}

private void Refresh() // or Work()
{ 
    int result = 0;
    if (Number0)
        result = result + 0; // Could be more complex that just addition
    if (Number1)
        result = result + 1; // Could be more complex that just addition
    // Same until 9 ...
    Sum = result.ToString(); 
}

我的问题是我应该如何以及何时调用此 Refresh 方法?

【问题讨论】:

    标签: c# .net xaml mvvm


    【解决方案1】:

    您的设计中有几处错误。这是我会做的:

    不要使用Number0...9,而是使用BindingList&lt;bool&gt; Numbers

    然后在 XAML 中,显示如下复选框:

    <ItemsControl ItemsSource="{Binding Numbers}">
       <ItemsControl.ItemTemplate>
          <DataTemplate>
             <CheckBox IsChecked="{Binding} Content="Name the checkbox here" />
          </DataTemplate>
       </ItemsControl.ItemTemplate>
    </ItemsControl>
    
    <Label Content="{Binding Numbers, Converter={StaticResource NumbersToSumConverter}}" />
    

    NumbersToSumConverterIValueConverter,例如:

    public NumbersToSumConverter : IValueConverter
    {
       public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
       {
          var numbers = value as BindingList<bool>();
          //Do your sum here.
       }
    
       public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
       {
          throw new NotImplementedException();
       }
    }
    

    这是一个如何在 MVVM 中完成的示例,如果您需要在 BindingList&lt;T&gt; 中存储的不仅仅是布尔值,
    创建一个实现 INotifyPropertyChanged 的新类,并根据需要添加任意数量的属性(确保在其设置器中引发 PropertyChanged)。
    然后将其用作BindingList&lt;T&gt; 的类型。

    希望这会有所帮助,

    巴布。

    【讨论】:

    • 如果我理解的话,我的想法是将我的标签绑定到一个唯一的对象(这里是一个 BindingList),其中包含可能影响我的标签结果的所有属性。
    • @DranDane 是的,这就是它的精髓。也不要为每个布尔值创建一个属性,将它们放在一个集合中(这里是 BindingList)并通过 ItemsControl 显示它们。
    • 你绝对不应该在你的 ViewModel 中有一个控件集合。制作字符串和 POCO 的集合,并将控件的内容绑定到该集合。
    • 所以制作一个 BindingList 并使用它。我会在我的答案中添加必要的东西。
    • WPF 中的 BindingList 是一个超级 ObservableCollection:它会在其子属性发生更改时通知更改。
    【解决方案2】:

    尝试类似 -

    private bool number0;
        public bool Number0
        {
            get { return number0; }
            set
            {
                number0 = value;
                NotifyPropertyChanged("Number0");
                NotifyPropertyChanged("Sum");
            }
        }
    
        public bool Sum
        {
            get { return this.EvaluateSum(); }
        }
    
        private bool EvaluateSum() 
        {
            int result = 0; 
            if (Number0) 
                result = result + 0; // Could be more complex that just addition 
            if (Number1) 
                result = result + 1; // Could be more complex that just addition 
            // Same until 9 ... 
    
            return result.ToString(); 
        }
    

    注意:这未经测试。

    如果您不喜欢上述内容,则可以进行以下更改:

    声明新类

    public class SomeClass: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private bool number0;
        public bool Number0
        {
            get { return number0; }
            set
            {
                number0 = value;
                this.NotifyPropertyChanged("Number0");
            }
        }
    
        private bool sum;
        public bool Sum
        {
            get { return sum; }
            set
            {
                sum = value;
                this.NotifyPropertyChanged("Sum");
            }
        }
    
        protected void NotifyPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
            if ((propertyChanged != null))
            {
                propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

    注意:相应更改Binding

    关于 SomeClass 的属性变化:

    void SomeClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName.Contains("Number"))
            {
                (sender as SomeClass).Sum = EvaluateSum(); // put or call the sum logic
            }
        }
    

    【讨论】:

    • @Baboon 我想我同意你的看法。我可以说如果我添加的 NotifyPropertyChanged("Sum") 与我当前的属性(此处为“Number0”)不对应,我做错了吗?
    • 在 WPF 和 MVVM 方面的经验告诉我,除了提高 PropertyChanged 之外,您不应该进一步破解 setter,否则您会后悔的。
    【解决方案3】:

    就我个人而言,我会按照您的建议在每个 Number setter 中调用 Refresh()

    我不热衷于在其他属性的设置器中直接引发PropertyChanged 通知。如果由于Number* 属性之一的更改而导致其他一些属性更改,将来会发生什么?您还必须检查每个 setter 并为该属性引发 PropertyChanged 事件。它违反了single reponsibility principleNumber*setter 在那里设置属性,不用担心其他属性改变的结果。

    使用专用的Refresh() 函数,该函数负责更新其他属性,并且它们会像往常一样引发自己的更改事件。

    是的,在 setter 中调用 Refresh() 并不好,但“正确”的做法是让您的视图模型订阅自己的 PropertyChanged 事件并在事件处理程序中调用 Refresh()。这增加了 IMO 不必要的复杂性,并且在每个 setter 中调用 Refresh() 实际上是相同的逻辑。

    【讨论】:

    • 您描述的“正确”方式根本不正确。我非常讨厌有人建议在任何地方筹集和订阅 PropertyChanged:这不是一个好习惯,而且它不会强制执行 MVVM 设计。事实上,这是糟糕的设计并导致内存泄漏。
    • @GazTheDestroyer 如果我在 Refresh() 中执行的工作应该更改一个属性,该属性将再次调用 Refresh(),该属性将更改另一个属性,该属性将再次更改 Refresh(),该属性将更改另一个属性,该属性将再次更改 Refresh() 将更改另一个将更改的属性...
    • @狒狒。我同意也许“适当”是一个坏词。我同意这将是一个坏主意,因此为什么我说它会过于复杂。我试图传达这样一个事实,即计算新状态的工作和属性设置器的工作是两个不同的事情,在理想世界中它们是分离的。正如我所说,直接调用 Refresh() 将是我选择的解决方案。
    • @DranDane。可观察对象上的大多数属性设置器都会检查该值,并且仅在它不同时才引发。在这种情况下,您的示例不会导致无限调用。
    猜你喜欢
    • 2018-08-16
    • 2018-06-08
    • 2012-08-09
    • 1970-01-01
    • 1970-01-01
    • 2018-07-27
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多