0
votes

Question summary: Is there a way in XAML to ensure that my DataGrid component is fully loaded before it initiates a binding on the SelectedIndex property?


My ViewModel is set up like this. I'm using MVVM-light to notify the view of changes. I pass the new model to SetData() whenever the it gets updated from the server.

public class MyViewModel : ViewModelBase
{
    public void SetData(DataModel model)
    {                
        Data = model.Data; //Array of 75 DataObjects
        DataIndex = model.Index; //int between 0 and 74
    }

    // Array to Bind to Datagrid ItemsSource
    public DataObject[] Data 
    {
        get { return _data; }
        private set
        {
            if (_data!= value)
            {
                _data= value;
                RaisePropertyChanged("Data");
            }
        }
    }
    private DataObject[] _data;

    // Int to Bind to Datagrid SelectedIndex
    public int DataIndex
    {
        get { return _index; }
        private set
        {
            if (_index != value)
            {
                _index = value;
                RaisePropertyChanged("DataIndex");
            }
        }
    }
    private int _index;
}

The view looks like this:

<Application.Resources>
    <ResourceDictionary>
        <core:ViewModelLocator x:Key="Locator" />
    </ResourceDictionary>
</Application.Resources>

<DataGrid ItemsSource="{Binding MyViewModel.Data, Source={StaticResource Locator}}"
          SelectedIndex="{Binding MyViewModel.DataIndex, Source={StaticResource Locator}, Mode=OneWay}"
          AutoGenerateColumns="True"/>

My issue is that none of the rows are selected on my DataGrid. All the data shows up in the grid correctly but the row does not get selected. I've checked the properties and confirmed that the Array length is 75 and DataIndex is an int between 0 and 74.


It seems the reason is because the DataGrid hasn't finished loading when the binding is set. I can prove this by initializing the binding after the component is loaded. In this case, everything works as expected and my selected item is displayed correctly:

<DataGrid x:Name="MyDataGrid" Loaded="OnDataGridLoaded"
          ItemsSource="{Binding MyViewModel.Data, Source={StaticResource Locator}}"
          AutoGenerateColumns="True"/>

private void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
    Binding b = new Binding("DataIndex");
    b.Source = Locator.MyViewModel.Data;
    MyDataGrid.SetBinding(DataGrid.SelectedIndexProperty, b);
}

I'd prefer not to have to do it like this because, you know, code-behind. So is there a way to fix this using only XAML? Here is what I've tried so far (none of which worked for me):

  • Binding SelectedIndex to an int property on my ViewModel (as shown above)
  • Binding SelectedItem to a DataObject property on my ViewModel (same result)
  • Binding SelectedValue and SelectedPath to a property of my DataObject (This actually worked, for the first instance only. The problem is I have multiple instances of this Datagrid component, and for some reason this only works on the first instance)
  • Binding to an ObservableCollection instead of an Array (tried all 3 of the above methods with an ObservableCollection and got the same results for each)
  • Delaying the change notification by wrapping it in a call to Dispatcher.Invoke. This doesn't help because the component is not immediately in view.
  • Creating the binding in XAML and then updating the target in the Loaded function. MyDataGrid.GetBindingExpression(DataGrid.SelectedIndexProperty).UpdateTarget();
1
@Clemens Thanks, noted. This code is a condensed summary of my actual program, in which the DataGrid's source is a separate object than the UserControl's DataContext. But regardless, all good points. - Karmacon
why don't you call setData on Window.Loaded event for first time. - esiprogrammer
@EdPlunkett Sorry, I forgot to include that in this code. But yes, I have set the binding to one way - Karmacon
@esiprogrammer I'm not sure what you mean, but let me clarify. I have multiple instances of the component, all of which are not in view by default. Once a user presses a button, the component is created and the grid is populated with the data. In other words, the data is already stored well in advance before the it is displayed in the component. - Karmacon
I guess the problem might be setting DataContext of the view. that's why ur provided code-behind is working. where do you set DataContext ? - esiprogrammer

1 Answers

0
votes

My original question was missing a piece of info that was causing the issue. It seems there's a WPF bug when the binding source is a static link. If I move the DataIndex property into the component's DataContext, then it will work correctly.

I'm not going to do this however, because the data is shared between multiple instances. I don't need to have multiple instances of the data, only the component. Therefor, I will just right it off as a Microsoft bug, and use the code-behind work around.

I will leave the question open tho, in case anyone has a solution for this bug.