【问题标题】:WPF Databinding with ObservableCollection to LabelWPF Databinding with ObservableCollection to Label
【发布时间】:2025-12-22 18:55:11
【问题描述】:

我有一个 ObservableCollection,我需要绑定到 2 个标签,第一个显示集合中的项目数,第二个显示值的总和。

第一个标签绑定到集合计数属性,第二个标签直接绑定到 ObservableCollection,使用转换器计算所有项目的总数

XAML 看起来像这样

<Grid>
    <ListBox Name="itemList" ItemsSource="{Binding DataList}"/>
    <Label Name="lblcount" Content="{Binding DataList.Count}" />
    <Label Name="lblTotal" Content="{Binding DataList, Converter={StaticResource calculateTotalConvertor}" />
</Grid>

我的虚拟机有一个这样的集合

    ObservableCollection<int> data = new ObservableCollection<int>();

    public ObservableCollection<int> DataList
    {
        get { return data; }
        set { data = value; }
    }

我的转换器代码是

public class CalculateTotalConvertor : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<int> collection = value as ObservableCollection<int>;

        return collection.Sum();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

问题在于在 DataList、ListView 中添加新项目,并且显示项目计数的标签已更新,但“lblTotal”未随总计数更新。

基本上如何强制您的绑定在 ObservableCollection 更改时进行评估?它如何直接用于 ListView 或 DataGrid 而不是用于 label ?

我知道这个问题可以通过在 VM 中创建一个属性来解决,以便在集合更新时显示总数并引发属性更改,但是还有比这更好的解决方案吗?

当然,这是我实际问题的简化形式,我无法访问 ViewModel 和集合,它是第三方控件。我正在创建一个包装用户控件,并与视图与其内部集合进行相对绑定。

【问题讨论】:

  • 你试过set { data = value; RaisePropertyChanged("Data"); } 吗?
  • 老实说,我会提供一个完全符合您要求的属性,并处理 VM 中的逻辑。为此使用 Converter 有点奇怪,我尽量避免使用它。想一想,转换应该将一个值转换为另一个值,您正在将一个列表转换为一个数字。如果初始值被更新,则再次调用转换器,在您的情况下,它保持不变,只是它的内部状态发生了变化。为此使用您的虚拟机,为视图提供和预处理数据是视图模型的主要目的。
  • @dowhilefor 我同意你的看法...我想知道 DataGrid 如何在集合更改时更新以及我是否可以在这里做同样的事情。我的实际用例有很多不同和复杂。我已经编辑了问题。
  • @AndreyGordeev RaisePropertychanged 不会有帮助,因为集合属性本身没有改变。

标签: wpf xaml data-binding


【解决方案1】:

其他答案正确解释了为什么它没有更新。要强制它更新,您可以将转换器更改为IMultiValueConverter

public class CalculateTotalConvertor : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<int> collection = values.FirstOrDefault() as ObservableCollection<int>;

        return collection.Sum();
    }

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

然后将您的绑定更改为MultiBinding,这也会拉入计数:

<Label Name="lblTotal">
    <Label.Content>
        <MultiBinding Converter="{StaticResource calculateTotalConvertor}">
            <Binding Path="DataList"/>
            <Binding Path="DataList.Count"/>
        </MultiBinding>
    </Label.Content>
</Label>

现在第二个绑定会在添加或删除项目时通知绑定需要更新,但您可以忽略计数值而不使用它。

【讨论】:

    【解决方案2】:

    它没有更新,因为它绑定到DataListDataList 没有改变,计数标签更新因为它绑定到DataList.Count,当一个项目添加到列表中时更新。

    我能想到的更新Sum 标签的唯一方法是通知 UI DataList 已更改,但这会导致 ListBox 重新绑定列表,并且性能会贵很多不仅仅是在你的模型上有一个属性更新Sum

    所以我认为最好的选择是使用模型上的属性来计算总和,使用 ObservableCollections CollectionChangedEvent 或在将项目添加到列表的逻辑中

    【讨论】:

      【解决方案3】:

      它适用于ListViewDataGrid,因为它们是ItemsControls,它们侦听ObservableCollectionCollectionChangedEvent,当通过添加或删除项目更改集合本身时会引发此问题。

      另一方面,Label 是一个只监听PropertyChangedEventContentControl。由于插入后的 DataList 与之前的 ObservableCollection 相同,因此不会引发任何事件。

      刚刚看到您的编辑:

      如果您要创建包装控件,请为第 3 方控件命名,并从您的控件后面的代码中连接到其内部集合的 CollectionChangedEvent。这样您仍然可以将更新通知推送到包装视图。

      使用额外的属性,它将为您节省一些转换器上的代码。从后面的代码:

      public partial class MainWindow : Window, INotifyPropertyChanged
      {
          ObservableCollection<int> _list = new ObservableCollection<int>();
          int _sum = 0;
          Random rnd = new Random();
      
          public MainWindow()
          {
              DataList = new ObservableCollection<int>();
              DataList.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(DataList_CollectionChanged);
              DataContext = this;
              InitializeComponent();
          }
      
          void DataList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
          {
              switch (e.Action)
              {
                  case NotifyCollectionChangedAction.Add:
                      foreach (object number in e.NewItems)
                          _sum += (int)number;
                      break;
                  case NotifyCollectionChangedAction.Remove:
                      foreach (object number in e.OldItems)
                          _sum -= (int)number;
                      break;
              }
              OnNotifyPropertyChanged("Sum");
          }
      
          public int Sum { get { return _sum; } }
          public ObservableCollection<int> DataList { get; set; }
      
          private void Add_Btn_Click(object sender, RoutedEventArgs e)
          {
              DataList.Add(rnd.Next(0, 256));
          }
      
          private void Remove_Btn_Click(object sender, RoutedEventArgs e)
          {
              if (DataList.Count == 0)
                  return;
      
              DataList.RemoveAt(DataList.Count - 1);
          }
      
          public event PropertyChangedEventHandler PropertyChanged;
      
          void OnNotifyPropertyChanged(string property)
          {
              if (PropertyChanged == null)
                  return;
      
              PropertyChanged(this, new PropertyChangedEventArgs(property));
          }
      }
      

      【讨论】: