Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF Datagrid Binding Doesn't Update Until Clicking Row Header

I have a datagrid that is bound to a List in the view model. The grid's contents don't update until I click on the row header. Clicking in various cells doesn't affect it. I have to click on the header.

This is the datagrid in the XAML:

<DataGrid x:Name="TransactionDetailsGrid" Grid.Row="1" AutoGenerateColumns="False" SelectionMode="Extended" IsReadOnly="True" HeadersVisibility="Column"
                  ItemsSource="{Binding TransactionDetailList}" SelectedItem="{Binding SelectedTransactionDetail}" GridLinesVisibility="None">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Path=Account.AccountNumber}" Header="Account No." HeaderStyle="{StaticResource DataGridHeaderStyleCenter}" Width="120" />
        <DataGridTextColumn Binding="{Binding Path=Account.AccountName}" Header="Account Name" HeaderStyle="{StaticResource DataGridHeaderStyleCenter}" Width="*" />
        <DataGridTextColumn Binding="{Binding Path=Amount}"  Header="Amount" HeaderStyle="{StaticResource DataGridHeaderStyleCenter}" Width="120" />
    </DataGrid.Columns>
</DataGrid>

And this is from the view model:

public List<TransactionDetail> TransactionDetailList
{
    get { return this._transactionDetailList; }

    set
    {
        this._transactionDetailList = value;
        RaisePropertyChanged("TransactionDetailList");                
    }
}

This is the edit of one of the items, in the view model:

private void AddTransactionDetail()
{
    TransactionDetailViewModel viewModel = new TransactionDetailViewModel();

    MainWindowViewModel.ViewLoader.ShowDialog(viewModel);

    if (viewModel.TransactionDetail != null)
    {
        this.TransactionDetailList.Add(viewModel.TransactionDetail);
        RaisePropertyChanged("TransactionDetailList");
    }
}

After this runs, I can put a breakpoint on the getter of TransactionDetailList, and the collection has the item in it. However, the datagrid is empty. If I click on the header row, the item shows up in the grid.

I have the same issue when doing an edit.

I've done this successfully before, so I'm not sure what's different here. Am I missing something obvious? Why won't the grid show its contents until I click on the header row?

I just noticed something interesting. When I click on the grid header, the breakpoint in the TransactionDetailList getter doesn't get hit, but the data still shows up. So, it's like the grid has the info, it's just not showing it until the header is clicked.

After changing to use ObservableCollection, it worked. But now I'm having the same problem with the edit (grid doesn't update until clicking the header):

private void EditTransactionDetail()
{
    TransactionDetailViewModel viewModel = new TransactionDetailViewModel(this.SelectedTransactionDetail);

    MainWindowViewModel.ViewLoader.ShowDialog(new TransactionDetailViewModel(this.SelectedTransactionDetail));

    RaisePropertyChanged("TransactionDetailList");
}

Does my entity need to implement INotifyPropertyChanged? If I change the collection, and call RaisePropertyChanged, shouldn't that cause an update to the grid?

like image 420
Bob Horn Avatar asked Jun 24 '11 00:06

Bob Horn


2 Answers

The problem is that when you add or remove an item from the collection, the setter isn't called. This means INotifyPropertyChanged isn't called, and the view has no way of knowing it needs to be refreshed.

WPF solves this by also supporting the INotifyCollectionChanged interface.

Try using an ObservableCollection<TransactionDetail> instead of a List<TransactionDetail>. ObservableCollection<T> is built in, and implements INotifyCollectionChanged, so you won't have to do much to your code.

Also make sure that you continue to implement INotifyPropertyChanged on your view model as you already have. This way the view will be notified when you replace the entire collection (when the setter is called).

public ObservableCollection<TransactionDetail> TransactionDetailList
{
    get { return this._transactionDetailList; }

    set
    {
        this._transactionDetailList = value;
        RaisePropertyChanged("TransactionDetailList");                
    }
}

See:

  • http://msdn.microsoft.com/en-us/library/ms668604.aspx
  • http://msdn.microsoft.com/en-us/library/ms748365.aspx (although this isn't using MVVM style)

Edit:

Also, you should implement INotifyPropertyChanged on TransactionDetail too. If you can't, wrap it in a class that does implement INotifyPropertyChanged.

If you don't implement it on TransactionDetail, than changes you make that don't impact the list, but do impact properties on a TransactionDetail instance, won't show in the UI until you somehow refresh the whole list.

If you tried to fix this by calling RaisePropertyChanged on the list property, then your UI thinks the entire list (and hence the whole set of UI objects) needs to be thrown out and updated. This will kill your performance and make your app sluggish.

like image 146
Merlyn Morgan-Graham Avatar answered Nov 20 '22 10:11

Merlyn Morgan-Graham


If you want you grid to update the moment you change the IEnumerable list it is bound to, make it a type of list that implements INotifyCollectionChanged. A built in data type that has this is the ObservableCollection (also here). So if you make your property look like this:

public ObservableCollection<TransactionDetail> TransactionDetailList
{
    get { return this._transactionDetailList; }

    set
    {
        this._transactionDetailList = value;
        RaisePropertyChanged("TransactionDetailList");                
    }
}

your grid will automatically pick up any items you add or remove from the list.
Effectively what you are doing here is notifying when the list has changed (i.e. the list reference), but you are not notifying when the contents of the list have changed.

like image 2
slugster Avatar answered Nov 20 '22 08:11

slugster