c# - 通知/绑定(bind)父属性以计算子属性的总和

标签 c# wpf

我有两个类,一个用于ViewModel,一个用于Product。 Product 类有一个名为Line Total 的属性,ViewModel 类有一个名为Total Amount 的属性。 Product 类绑定(bind)到 DataGrid 和用户 插入随后自动更新行总计的数量。

这是 ViewModel 类:

public class ViewModel : INotifyPropertyChanged
{

    public ObservableCollection<Product> products { get; set; }// the children

    private decimal _TotalAmount; 
    public decimal TotalAmount // <=== has to hold sum of [products.LineTotal]
    {
        get
        {
            return totalAmount;
        }
        set
        {
            if (value != _TotalAmount)
            {
                _TotalAmount = value;
                onPropertyChanged(this, "TotalAmount");
            }
        }
    }

这是 Product 类的子类:

public class Product : INotifyPropertyChanged
    {
        private decimal _LineTotal;
        public decimal LineTotal
        {
            get
            {
                return _LineTotal;
            }
            set
            {
                if (value != _LineTotal)
                {
                    _LineTotal = value;
                    onPropertyChanged(this, "LineTotal");
                }

            }

        }
}

我的问题是:TotalAmount 如何计算所有 Products [Line Total] 的总和?子 Products 如何通知父 ViewModel 更新 TotalAmount

类似于:

foreach(var product in Products)
{
     TotalAmount += product.LineTotal;
}

最佳答案

实现此目的的一种方法是,每次用户编辑总计行以及每次向 ObservableCollection 添加或删除产品时重新计算总金额。

由于 Product 实现了 INotifyPropertyChanged 并在设置新行总数时引发 PropertyChanged 事件,ViewModel可以处理该事件并重新计算总金额。

ObservableCollection 有一个 CollectionChanged 事件,当一个项目被添加到其中或从中删除时会引发该事件,因此 ViewModel 也可以处理该事件并重新计算。 (如果产品只能更改而不能由用户添加/删除等,则这部分并不是真正必要的)。

您可以试试这个小程序,看看它是如何完成的:

代码隐藏

public partial class MainWindow : Window
{
    ViewModel vm = new ViewModel();

    public MainWindow()
    {
        InitializeComponent();

        vm.Products = new ObservableCollection<Product>
        {
            new Product { Name = "Product1", LineTotal = 10 },
            new Product { Name = "Product2", LineTotal = 20 },
            new Product { Name = "Product3", LineTotal = 15 }
        };

        this.DataContext = vm;
    }

    private void AddItem(object sender, RoutedEventArgs e)
    {
        vm.Products.Add(new Product { Name = "Added product", LineTotal = 50 });
    }

    private void RemoveItem(object sender, RoutedEventArgs e)
    {
        vm.Products.RemoveAt(0);
    }
}

public class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Product> _products;
    public ObservableCollection<Product> Products
    {
        get { return _products; }
        set
        {
            _products = value;

            // We need to know when the ObservableCollection has changed.
            // On added products: hook up eventhandlers to their PropertyChanged events.
            // On removed products: recalculate the total.
            _products.CollectionChanged += (sender, e) =>
            {
                if (e.NewItems != null)
                    AttachProductChangedEventHandler(e.NewItems.Cast<Product>());
                else if (e.OldItems != null)
                    CalculateTotalAmount();
            };

            AttachProductChangedEventHandler(_products);
        }
    }

    private void AttachProductChangedEventHandler(IEnumerable<Product> products)
    {
        // Attach eventhandler for each products PropertyChanged event.
        // When the LineTotal property has changed, recalculate the total.
        foreach (var p in products)
        {
            p.PropertyChanged += (sender, e) =>
            {
                if (e.PropertyName == "LineTotal")
                    CalculateTotalAmount();
            };
        }

        CalculateTotalAmount();
    }

    public void CalculateTotalAmount()
    {
        // Set TotalAmount property to the sum of all line totals.
        TotalAmount = Products.Sum(p => p.LineTotal);
    }

    private decimal _TotalAmount;
    public decimal TotalAmount
    {
        get { return _TotalAmount; }
        set
        {
            if (value != _TotalAmount)
            {
                _TotalAmount = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("TotalAmount"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class Product : INotifyPropertyChanged
{
    public string Name { get; set; }

    private decimal _LineTotal;
    public decimal LineTotal
    {
        get { return _LineTotal; }
        set
        {
            if (value != _LineTotal)
            {
                _LineTotal = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("LineTotal"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

XAML:

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}" />
                <DataGridTextColumn Binding="{Binding LineTotal}" />
            </DataGrid.Columns>
        </DataGrid>

        <Button Click="AddItem">Add item</Button>
        <Button Click="RemoveItem">Remove item</Button>

        <TextBlock>
            <Run>Total amount:</Run>
            <Run Text="{Binding TotalAmount}" />
        </TextBlock>
    </StackPanel>
</Window>

关于c# - 通知/绑定(bind)父属性以计算子属性的总和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12691978/

相关文章:

c# - 删除组合框中的重复项

C# 网络流压缩 - Sharpziplib、DotNetZip、gzipstream 在我的流上均出现错误

c# - 为什么 ObservableCollection 隐藏 INotifyPropertyChanged.PropertyChanged

c# - 当我将网格控件换成堆栈面板时,内容消失了

WPF:列表框和虚拟化

.net - 前台属性行为困惑

c# - 如何在 WPF 中显示另存为对话框?

c# - 从 SharePoint 中的 _layouts 文件夹获取文件

从给定字符串中提取 url 的 C# 正则表达式模式 - 不是完整的 html url,而是裸链接

c# - 找不到类型或命名空间名称(XAML 新增功能)