C# 如何在ItemsControl组中绑定小计

C# 如何在ItemsControl组中绑定小计,c#,wpf,xaml,data-binding,C#,Wpf,Xaml,Data Binding,想象一下这个DTO类: class LineItem : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public string Description { get; set; } private decimal m_Amount; public decimal Amount { get { return m_Amou

想象一下这个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}}" />

这看起来像:

现在,我希望底部有一个总数。此外,我希望它在金额变化时更新

大概是这样的:

<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?而且,这是一种正确的方法吗

我的问题:


这不起作用,因为转换器仅在第一次绑定时才被调用。我希望它反映用户的更改,并且我需要处理未知数量的行项目。当然,我不是第一个这样做的人有办法吗?

您可以改为绑定到一个专门表示
平均金额的属性(在“视图模型”上)并确保在
PropertyChanged
上为每个
LineItem
发送
AverageAmount
属性的更改通知,以允许模型计算值,并让UI重新获取新值

然而,在仔细考虑了这样做的开销之后,我将研究类似于or(or)的东西,它应该处理所有的依赖性分析和更改通知。我们使用BindableLinq取得了巨大的成功,尽管在这个时候,启动它的人并没有积极地维护它

编辑:

举一个不使用我上面提到的库的后端示例(它删除了处理集合和属性更改事件的管道):

公共类ItemListViewModel:INotifyPropertyChanged
{
公共事件属性更改事件处理程序属性更改;
私有只读ObservableCollection _items=新ObservableCollection();
public ItemListViewModel()
{
_items.CollectionChanged+=已更改;
}
公共ICollection项{get{return\u Items;}}
私有void OnItemChanged(对象发送方,NotifyCollectionChangedEventArgs e)
{
开关(电动)
{
案例NotifyCollectionChangedAction。添加:
e、 NewItems.Cast().ToList().ForEach(iv=>iv.PropertyChanged+=OnItemPropertyChanged);
打破
案例NotifyCollectionChangedAction。删除:
e、 OldItems.Cast().ToList().ForEach(iv=>iv.PropertyChanged-=OnItemPropertyChanged);
打破
违约:
抛出新的NotImplementedException();
}
}
私有void OnItemPropertyChanged(对象发送方,PropertyChangedEventArgs e)
{
如果(例如,PropertyName==“值”)
{
if(PropertyChanged!=null)
PropertyChanged(即新PropertyChangedEventArgs(“AverageValue”);
}
}
公共双平均值
{
获取{returnitems.Average(iv=>iv.Value);}
}
}
公共类ItemViewModel:INotifyPropertyChanged
{
公共事件属性更改事件处理程序属性更改;
公共字符串族{get;set;}
私人国际货币单位价值;
公共整数值
{
获取{返回m_值;}
设置
{
如果(m_值==值)
返回;
m_值=值;
if(PropertyChanged!=null)
PropertyChanged(即新PropertyChangedEventArgs(“价值”);
}
}
}
然后,XAML中的ItemsControl直接绑定到视图模型的Items属性,average值绑定到AverageValue属性。它现在将处理所需的通知

要在另一个级别添加分组,您必须引入另一个类“ItemGroupViewModel”,该类将监视父项集合的更改。我会将属性更改侦听器添加到所有项目中,如果它们更改了其族属性,则从本地项目集合中添加/删除。如果他们更改了Value属性,则为AverageValue属性触发PropertyChanged


注意:BindableLinq也支持分组操作。

结果如下:

<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}}" />

这是政务司司长:

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();
    }
}
公共部分类主窗口:窗口
{
公共主窗口()
{
初始化组件();
}
//用于强制数据绑定刷新
公共伪造财产
{
获取{return(int)GetValue(FakePropertyProperty);}
set{SetValue(FakePropertyProperty,value);}
}
公共静态只读从属属性FakeProperty属性属性=
DependencyProperty.Register(“FakeProperty”,
typeof(int)、typeof(MainWindow)、新的UIPropertyMetadata(0);
私有void TextBox\u TextChanged(对象发送方,
System.Windows.Controls.textchangedventargs(e)
{
伪属性++;
}
}
公共类项目:INotifyPropertyChanged
{
公共事件属性更改事件处理程序属性更改;
公共字符串族{get;set;}
私人国际货币单位价值;
公共整数值
{
获取{返回m_值;}
设置
{
如果(m_值==值)
返回;
m_值=值;
if(PropertyChanged!=null)
PropertyChanged(即新PropertyChangedEventArgs(“价值”);
}
}
}
公共类项目:ObservableCollection
{
公共物品()
{
添加(新项{Family=“One”,Value=1});
添加(新项{Family=“One”,值=2});
添加(新项{Family=“Two”,Value=3});
添加(新项{Family=“Two”,值=4});
添加(新项{Family=“Two”,值=5});
添加(新项{Family=“Three”,Value=6});
添加(新项{Family=“Three”,Value=7});
}
}
公共类SumConverter:IMultiValueConverter
{
公共对象转换(对象[]值,类型targetType,
对象参数,System.Globalization.CultureInfo(区域性)