如何在WPF数据网格中实现运行平衡列



我已经在谷歌上搜索了两天,找不到可以使用的答案。这是一个简单的"运行余额",在其他所有金融应用程序中都可以看到。我发现的东西要么是得到总和(最后是一个总和),要么是对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

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

我想我理解你的困境。您希望运行余额显示特定交易对起始余额的影响,但该运行余额必须考虑前一笔交易。我认为这篇文章很好地总结了(并非双关语)你想做什么,对吗?

将列与事务模型分开绑定到该计算将是有问题的。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; }
}
}

首先意识到这是表示逻辑,因此它属于视图。意识到我应该做的是重新排序ObservableCollection,并建立当时的运行总数(这让我走到了这里)。

但我仍然无法刷新ObservableCollection。如果我用一个新的(排序的)ObservableCollection替换它,它破坏了绑定逻辑。所以我找到了这个答案,最终找到了这个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;
}

最新更新