I have a WPF application wherein a Window has a master/child relationship. When a user selects a Buyer from a ListView, a DataGrid is bound with related Items they have purchased. In the GridView, there is a button on the left where a Payment for this individual item can be made. However, I want to disable/enable the button based on the state of the Item, whether or not an existing Payment is associated with it. Using Galasoft MVVM Light, RelayCommand throughout the application. I have the Pay button working, but ONLY when clicked (row selected). This works fine when bound to a command in this way:
<DataGrid ..
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
IsSynchronizedWithCurrentItem="True"
.. >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button ...
Command="{Binding Path=DataContext.PayItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
CommandParameter="{Binding SelectedItem, Mode=OneWay, ElementName=itemsPurchasedGrid}"
>Pay</Button>
...
// in ctor
PayItemCommand = new RelayCommand<Item>(PayItem, IsItemUnPaid);
// in body
private void PayItem(Item item)
{
PaymentMode = PaymentMode.Item;
Messenger.Default.Send<BuyerViewModel>(this, "LaunchPayWindow");
}
// CanExecute impl
private bool IsItemUnPaid(Item item)
{
// code to determine if item is already paid
}
So, when I click the button, it correctly passes the SelectedItem to the RelayCommand delegate. However, the CanExecute implementation: IsItemUnPaid(Item item) never gets the item, because it is not (yet) selected. I tried: IsSynchronizedWithCurrentItem="True", which always passes the first Item, but not any others. Is there a way to get a handle to the Item while it is binding to the DataGrid to disable/enable it? I'm sure there are ugly ways in the codebehind during binding, but is there an MVVM way to check the current item and toggle that button? Many thanks!
P.S. I wrote a simple converter which works, but is that really the best way?
public class PayItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is Item item))
return true;
// if you find any payments for this item...
var payments = (item.Payments != null && item.Payments.Any(p => p.ItemId.HasValue && p.ItemId.Value == item.Id))
? item.Payments.Where(p => p.ItemId.HasValue && p.ItemId.Value == item.Id).ToArray()
: null;
// if you find payments for this item, are the sum of the payments less than the total of the item TotalAmount, it is said to be unpaid
return (payments != null && payments.Length > 0)
? payments.Sum(p => p.Amount) < item.TotalAmount
: true;
}