【问题标题】:How to implement a running balance column in WPF DataGrid?如何在 WPF DataGrid 中实现运行余额列?
【发布时间】:2018-12-20 20:54:41
【问题描述】:

我已经用谷歌搜索了两天,但找不到我可以使用的答案。 正如在其他所有金融应用程序中看到的那样,这是一个简单的“运行平衡”。我发现的东西要么得到总和(最后一个总和),要么对 PropertyChanged 做出反应(我的网格不可直接编辑),要么是答案的一半(“使用 CollectionView”,但不要说如何和我没看到)。

如何将 ObservableCollection 绑定到 DataGrid 并保持“运行平衡”作为计算列(不是模型的一部分),在其中一个列上进行排序后仍然存在?

(编辑)我正在寻找的示例

    Date    Payment    Deposit    Balance
09/01/2018     0.00    1500.00    1500.00
10/01/2018   100.00       0.00    1400.00
11/01/2018   234.00       0.00    1166.00
12/01/2018   345.00       0.00     821.00

...或者,在重新排序后...

    Date    Payment    Deposit    Balance
12/01/2018   345.00       0.00    -345.00
11/01/2018   234.00       0.00    -579.00
10/01/2018   100.00       0.00    -679.00
09/01/2018     0.00    1500.00     821.00

【问题讨论】:

  • 您是否尝试过使用DataTable 绑定您的DataGrid<DataGrid ItemsSource="{Binding MyTable}">
  • 您需要在某个地方放置总数。使用 balance 属性将每个模型包装在视图模型中。复制此代码bengribaudo.com/blog/2010/07/14/3/… 将一列绑定到余额。
  • @Andy 我已经找到那篇文章了。它无法生存。当 DataGrid 上的顺序发生变化时,它不会影响 DataContext 的顺序。

标签: c# wpf datagrid


【解决方案1】:

您可以在第一个旁边尝试单独的 DataGrid。

【讨论】:

  • 这与增加的噩梦有相同的问题,即让它们同时滚动到同一行。视觉呈现看起来很糟糕。
【解决方案2】:

我想我理解你的困境。您希望运行余额显示特定交易对起始余额的影响,但该运行余额必须考虑到前面的交易。我认为this article 很好地总结了(没有双关语)你想要做什么对吗?

将列绑定到与事务模型分开的此计算将是有问题的。 DataGrid 不是为绑定到多个数据源而设计的。这个想法是网格中的一行代表一个数据集。您也许可以使用排序事件获得创意,然后逐行读取当前值并以这种方式计算,但我认为这并不是最好的方法。

相反,您可以将运行余额作为模型的属性,但在事务加载到可观察集合时计算它。这适用于您的方案,因为您说您的用户不直接通过网格进行编辑。因此,您可以在将事务添加到 ObservableCollection 之前对其进行转换。

如果您要从数据库加载事务或从文件反序列化,只需将该属性标记为“未映射”或使用 AutoMapper 之类的东西将事务模型映射到事务 ViewModel。

虽然我使用后面的代码编写了这个示例,但它可以在 MVVM 中轻松完成,因为没有直接引用任何 ui 组件。

也许这样的事情会起作用?:

 <Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
      <RowDefinition Height="Auto"/>
      </Grid.RowDefinitions>
        <DataGrid x:Name="MyDataGrid" ItemsSource="{Binding Transactions}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Date" Binding="{Binding Date}"/>
                <DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
                <DataGridTextColumn Header="Running Balance" Binding="{Binding RunningBalance}"/>
            </DataGrid.Columns>
        </DataGrid>
        <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="5" >
            <Button x:Name="btnAddItem" Content="Add" Width="40" Height="30" Click="BtnAddItem_Click"/>
        </StackPanel>
    </Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        readonly Random _random;

        public MainWindow()
        {
            InitializeComponent();

            _random = new Random();

            DataContext = this;

            Transactions = new ObservableCollection<Transaction>();
            // add some transactions to the collection to get things started
            AddTransaction(new Transaction()
                {
                    Date = DateTime.Now.Subtract(TimeSpan.FromDays(5)),
                    Amount = -35.66M
                });

            AddTransaction(new Transaction()
                {
                    Date = DateTime.Now.Subtract(TimeSpan.FromDays(4)),
                    Amount = -22.00M
                });

            AddTransaction(new Transaction()
                {
                    Date = DateTime.Now.Subtract(TimeSpan.FromDays(3)),
                    Amount = -10.10M
                });
        }

        /// <summary>
        /// All transactions are added to the collection through this method so that the running balance
        /// can be calculated based on the previous transaction
        /// </summary>
        /// <param name="transaction"></param>
        void AddTransaction(Transaction transaction)
        {
            //find the preceding transaction
            var precedingTransaction = Transactions.Where(t => t.Date &lt; transaction.Date)
                .OrderByDescending(t => t.Date)
                .FirstOrDefault();

            if(precedingTransaction == null)
            {
                //This is the earliest transaction so calc based on starting balance
                transaction.RunningBalance = StartingBalance + transaction.Amount;
            } else
            {
                //this is not the earliest transaction so calc based on previous
                transaction.RunningBalance = precedingTransaction.RunningBalance + transaction.Amount;
            }

            //Add the transactions to the collection with the calculated balance
            Transactions.Add(transaction);
        }

        void BtnAddItem_Click(object sender, RoutedEventArgs e)
        {
            AddTransaction(new Transaction()
                {
                    Date = DateTime.Now,
                    //generate a random dollar amount
                    Amount = (decimal)-Math.Round(_random.Next(1, 100) + _random.NextDouble(), 2)
                });
        }

        public decimal StartingBalance => 345.00M;

        public ObservableCollection<Transaction> Transactions { get; set; }
    }

    public class Transaction
    {
        public decimal Amount { get; set; }

        public DateTime Date { get; set; }

        public decimal RunningBalance { get; set; }
    }
}

【讨论】:

  • 对不起,没有。这是我试图避免的确切情况。我可以获取交易清单,计算运行余额,然后再对其进行调整。我想要你文章中提到的“问题”。如果我对数据进行不同的排序,则运行余额应在排序后重新计算。
  • 您必须提供更多信息或示例。尝试使用其他名称而不是运行平衡,因为这显然不是您所追求的。
【解决方案3】:

首先意识到这是表示逻辑,所以它属于视图。意识到我应该做的是重新排序ObservableCollection并建立当时的运行总数(which led me here)。

但我仍然无法刷新 ObservableCollection。如果我用打破绑定逻辑的新(排序)ObservableCollection 替换它。所以我去了found this answer,最终把我带到了this GitHub

有了新的类,xaml.cs 变成了这样:

private void DataGrid_OnSorting(object sender, DataGridSortingEventArgs e)
{
    decimal runningTotal = 0.0M;
    //I have to maintain the sort order myself. If I let the control do it it will also resort the items again
    e.Column.SortDirection = e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;

    IEnumerable<RegisterEntry> tempList = RegisterList;

    switch (e.Column.Header.ToString())
    {
        case "Payment":
            tempList = e.Column.SortDirection == ListSortDirection.Ascending ? tempList.OrderBy(item => item.Payment) : tempList.OrderByDescending(item => item.Payment);
            break;
        case "Transaction":
            tempList = e.Column.SortDirection == ListSortDirection.Ascending ? tempList.OrderBy(item => item.TransactionDate) : tempList.OrderByDescending(item => item.TransactionDate);
            break;
        case "Payee":
            tempList = e.Column.SortDirection == ListSortDirection.Ascending ? tempList.OrderBy(item => item.itemPayee) : tempList.OrderByDescending(item => item.itemPayee);
            break;
    }

    tempList = tempList
        .Select(item => new RegisterEntry()
        {
            Id = item.Id,
            AccountId = item.AccountId,
            TransactionDate = item.TransactionDate,
            ClearanceDate = item.ClearanceDate,
            Flag = item.Flag,
            CheckNumber = item.CheckNumber,
            itemPayee = item.itemPayee,
            itemCategory = item.itemCategory,
            Memo = item.Memo,
            itemState = item.itemState,
            Payment = item.Payment,
            Deposit = item.Deposit,
            RunningBalance = (runningTotal += (item.Deposit - item.Payment))
        }).ToList();

    RegisterList.ReplaceRange(tempList);

    // Set the event as Handled so it doesn't resort the items.
    e.Handled = true;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多