0

So, I've got a DataGrid which shows a list of payments.

<DataGrid x:Name="dataGrid" ItemsSource="{Binding Payments}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="PaymentDate" Binding="{Binding PaymentDate, StringFormat=\{0:d\}}" />
        <DataGridTextColumn Header="Amount" Binding="{Binding Amount, StringFormat=\{0:N\}}" />
        <DataGridTextColumn Header="Comment" Binding="{Binding Comment}" />
        <DataGridTextColumn Binding="{Binding EventCode}" Header="Event Code"/>
        <DataGridTextColumn Binding="{Binding DueDate, StringFormat=\{0:d\}}" Header="DueDate"/>
    </DataGrid.Columns>
</DataGrid>

This DataGrid is bound to an ObservableCollection of Payment objects.

public class Payment
{
    public Guid ID { get; set; }

    public DateTime PaymentDate { get; set; }

    public decimal Amount { get; set; }

    public string Comment { get; set; }

    public string EventCode { get; set; }

    public DateTime? DueDate { get; set; }

    List<Booking> Bookings
    {
        get { ...magic that retrieves booking info... }
    }
}

As you can see, each payment has a property which is a list of Booking objects which show how each payment was allocated.

The Booking object is pretty simple.

public class Booking
{
    public string EventCode { get; set; }

    public decimal Amount { get; set; }

    public DateTime? BookingDate { get; set; }

    public string Designation { get; set; }

    public string Comment { get; set; }
}

And I have a second DataGrid which should show a list of Booking objects for a selected Payment.

<DataGrid ItemsSource="{Binding SelectedItem.Bookings, ElementName=dataGrid}" AutoGenerateColumns="True" />

What I expected was that whenever I selected a payment item in DataGrid 1, DataGrid 2 would be populated with the details for how that payment was allocated. What I got, however, was an empty details DataGrid.

I know I could tie the SelectedItem property to a property in my ViewModel and notify my View every time that property is changed, but it seems like DataGrid 2 should know that the SelectedItem property of DataGrid 1 has been changed automatically. Am I asking too much, or am I just doing it wrong?

Troy Frazier
  • 391
  • 3
  • 13
  • 1
    What you've described should be fine, but devil is in the details. Try this and see what you see at runtime in the VS Output pane, when you change the selection in the master grid: `ItemsSource="{Binding SelectedItem.Bookings, ElementName=dataGrid, PresentationTraceSources.TraceLevel=High}"` – 15ee8f99-57ff-4f92-890c-b56153 May 30 '17 at 14:56
  • Did you double check that the Bookings List isnt empty for the selectedItem? cant see an error with the given code – Rand Random May 30 '17 at 14:58
  • Should work provided that both DataGrids reside in the same naming scope. Make sure that the Bookings collection of the selected Payment has been populated. – mm8 May 30 '17 at 14:58
  • You could instead of ElmentName try to use x:Reference https://stackoverflow.com/questions/19244111/what-is-the-difference-between-xreference-and-elementname – Rand Random May 30 '17 at 15:00
  • 1
    Ohhhhh, I'm such a dunce. I didn't make my Bookings property public. Thanks for the extra sets of eyes, guys. I've never used `PresentationTraceSources.TraceLevel=High` before. Thanks for the new tool. – Troy Frazier May 30 '17 at 15:08

2 Answers2

0

There are three common ways to manage parent-child selection from a list in WPF

  1. Use the "/" syntax in the child grid's binding
  2. Specifically add a SelectedItem property in the viewmodel, set it in the parent grid and read it in the child grid
  3. Declaratively bind the parent grid to a CollectionViewSource and set the data source of the child grid when the CurrentChanged event of the CollectionViewSource fires.

Option 2 is the most common. Personally, I prefer using 3 because it gives me the ability to MoveFirst/Last/Next/Previous in my viewmodel.

You seem to be looking for 1, which is the simplest way but also the least powerful. All you need to do is change your binding in your child grid to

<DataGrid ItemsSource="{Binding Payments/Bookings}" AutoGenerateColumns="True" />

Notice I've removed the ElementName reference, which was wrong; you're binding to your DataContext, not a property on the parent grid.

Barracoder
  • 3,696
  • 2
  • 28
  • 31
  • 1
    Why is binding directly to the SelectedItem of the master DataGrid wrong? – Troy Frazier May 30 '17 at 15:16
  • Wrong was the incorrect word to use. A better way to phrase it would've been "bad practice". Binding to properties on other controls breaks if you rename the source control or try to use your child control in any other context. Also, you're now dependent on the implementation of your source control, which may fire the SelectedItem property any number of times as it refreshes. And most importantly, binding to a property element is much harder to unit test. – Barracoder May 30 '17 at 15:24
0

Make sure the property of Bookings is a public one.

You might also want to use UpdateSourceTrigger=PropertyChanged in your SelectedItem binding, as in:

ItemsSource="{Binding SelectedItem.Bookings, ElementName=dataGrid, UpdateSourceTrigger=PropertyChanged}"

If this does not help, try using Snoop to make sure the data is actually in Bookings- if yes, and it does not show, either go with a property inside VM and implementing INotifyPropetyChanged in your data class, or update the property of datagrid2 via datagrid1's SelectionChanged EventHandler

Rachey
  • 211
  • 2
  • 9
  • This was the problem. My Bookings property wasn't set to public. – Troy Frazier May 30 '17 at 15:22
  • Retracted upvote due to the preposterous suggestion to add `UpdateSourceTrigger=PropertyChanged` to the binding. You must not know what it means if you suggested that; it's obviously a no-op. And if you don't know what it does, don't make any confident claims about it having any magic curative effects on people's bindings. – 15ee8f99-57ff-4f92-890c-b56153 May 30 '17 at 17:45