想象一下类似DTO的课程:
class LineItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Description { get; set; }
private decimal m_Amount;
public decimal Amount
{
get { return m_Amount; }
set
{
if (m_Amount == value)
return;
m_Amount = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Amount"));
}
}
}
这样的绑定:
<ItemsControl ItemsSource="{Binding LineItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBox Text="{Binding Amount}"
DockPanel.Dock="Right" Width="50" />
<TextBlock Text="{Binding Description}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
这看起来像是:
现在,我想在底部总计。此外,我希望它在金额变化时更新。
这样的事情:
<TextBlock HorizontalAlignment="Right"
Text="{Binding LineItems,
Converter={StaticResource MyConverter}}" />
为此:
但是什么是MyConverter?而且,它甚至是一种正确的方法吗?
我的问题:
这不起作用,因为转换器仅在第一次绑定时被调用。我希望它反映用户的变化,我需要处理未知数量的LineItems。当然,我不是第一个打到这个的人。 有办法吗?
答案 0 :(得分:2)
您可以绑定到专门代表AverageAmount
(在“视图模型”上)的属性,并确保在PropertyChanged
上为您发送更改通知的每个LineItem
对于AverageAmount
属性,允许模型计算值以及重新获取新值的UI。 Maleak's example shows exactly that
但是,仔细考虑这样做的开销,我会看一下像BindableLinq或Obtics(或Continuous Linq)那样应该处理所有依赖关系分析和更改的内容通知。我们已经使用了BindableLinq并取得了巨大的成功,但是在这个时候,它并没有被启动它的人主动维护。
修改:
在不使用上面提到的那些库(删除处理集合和属性更改事件的管道)的情况下给出一个后端示例:
public class ItemListViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly ObservableCollection<ItemViewModel> _items = new ObservableCollection<ItemViewModel>();
public ItemListViewModel()
{
_items.CollectionChanged += OnItemsChanged;
}
public ICollection<ItemViewModel> Items { get { return _items; } }
private void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
e.NewItems.Cast<ItemViewModel>().ToList().ForEach(iv => iv.PropertyChanged += OnItemPropertyChanged);
break;
case NotifyCollectionChangedAction.Remove:
e.OldItems.Cast<ItemViewModel>().ToList().ForEach(iv => iv.PropertyChanged -= OnItemPropertyChanged);
break;
default:
throw new NotImplementedException();
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("AverageValue"));
}
}
public double AverageValue
{
get { return Items.Average(iv => iv.Value); }
}
}
public class ItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Family { get; set; }
private int m_Value;
public int Value
{
get { return m_Value; }
set
{
if (m_Value == value)
return;
m_Value = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
然后,XAML中的ItemsControl直接绑定到视图模型的Items属性,平均值绑定到AverageValue属性。它现在将处理所需的通知。
要在另一个级别添加分组,您必须引入另一个类“ItemGroupViewModel”,该类将监视父级的Items集合以进行更改。我会将属性更改侦听器添加到所有项目,然后如果他们更改其Family属性,则从本地Items集合添加/删除。如果他们更改了Value属性,则为AverageValue属性触发PropertyChanged。
注意:BindableLinq也支持分组操作。
答案 1 :(得分:1)
结果是这样的:
这是CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// used to force databinding to refresh
public int FakeProperty
{
get { return (int)GetValue(FakePropertyProperty); }
set { SetValue(FakePropertyProperty, value); }
}
public static readonly DependencyProperty FakePropertyProperty =
DependencyProperty.Register("FakeProperty",
typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));
private void TextBox_TextChanged(object sender,
System.Windows.Controls.TextChangedEventArgs e)
{
FakeProperty++;
}
}
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Family { get; set; }
private int m_Value;
public int Value
{
get { return m_Value; }
set
{
if (m_Value == value)
return;
m_Value = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
public class Items : ObservableCollection<Item>
{
public Items()
{
this.Add(new Item { Family = "One", Value = 1 });
this.Add(new Item { Family = "One", Value = 2 });
this.Add(new Item { Family = "Two", Value = 3 });
this.Add(new Item { Family = "Two", Value = 4 });
this.Add(new Item { Family = "Two", Value = 5 });
this.Add(new Item { Family = "Three", Value = 6 });
this.Add(new Item { Family = "Three", Value = 7 });
}
}
public class SumConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
var _Default = 0;
if (values == null || values.Length != 2)
return _Default;
var _Collection = values[0] as System.Collections.IEnumerable;
if (_Collection == null)
return _Default;
var _Items = _Collection.Cast<Item>();
if (_Items == null)
return _Default;
var _Sum = _Items.Sum(x => x.Value);
return _Sum;
}
public object[] ConvertBack(object value, Type[] targetTypes, obje
ct parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
这个XAML:
xmlns:sort="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:sys="clr-namespace:System;assembly=mscorlib" Name="This"
<Window.Resources>
<local:Items x:Key="MyData" />
<local:SumConverter x:Key="MyConverter" />
<CollectionViewSource x:Key="MyView" Source="{StaticResource MyData}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Family" />
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<sort:SortDescription PropertyName="Value" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding Source={StaticResource MyView}}"
Name="MyItemsControl">
<ItemsControl.Resources>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<!-- group header -->
<Border Padding="10,5,0,5" Margin="0,10,0,10"
Background="Gainsboro" CornerRadius="10">
<TextBlock FontWeight="Bold"
Text="{Binding Name}" />
</Border>
<!-- group items -->
<ItemsPresenter Margin="10,0,0,0"/>
<!-- group footer -->
<Border BorderBrush="Black"
BorderThickness="0,.5,0,0"
Margin="0,5,0,10">
<TextBlock Width="100"
HorizontalAlignment="Right"
TextAlignment="Right"
Padding="0,0,5,0">
<TextBlock.Text>
<MultiBinding
StringFormat="{}{0:C}"
Converter="{StaticResource MyConverter}">
<Binding Path="Items" />
<Binding Path="FakeProperty"
ElementName="This"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Resources>
<ItemsControl.GroupStyle>
<GroupStyle ContainerStyle="{x:Null}" />
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="ItemsPresenter">
<DockPanel>
<TextBox Text="{Binding Value, StringFormat={}{0:C},
UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged"
TextAlignment="Right" DockPanel.Dock="Right"
Width="100" />
<TextBlock Text="{Binding Value,
StringFormat={}Value is {0}}"
FontWeight="Bold" Foreground="DimGray" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- list footer -->
<Border BorderBrush="Black" BorderThickness="0,.5,0,0" Margin="0,5,0,10">
<TextBlock Width="100" HorizontalAlignment="Right" TextAlignment="Right"
Padding="0,0,5,0" FontWeight="Bold">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyConverter}"
StringFormat="{}{0:C}">
<Binding Path="ItemsSource" ElementName="MyItemsControl" />
<Binding Path="FakeProperty" ElementName="This"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</StackPanel>
弄清楚多么噩梦!