我想我理解你的困境。您希望运行余额显示特定交易对起始余额的影响,但该运行余额必须考虑到前面的交易。我认为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 < 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; }
}
}