Question

I'm developing a WPF application with .NET Framework 4 and MVVM Light Toolkit I created a custom user control which only contains a DataGrid:

<UserControl
    x:Class="PadacEtl.Matcher.Views.LaraDataGrid"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    mc:Ignorable="d" 
    d:DesignHeight="300" d:DesignWidth="300"
    DataContext="{Binding}">

    <DataGrid ItemsSource="{Binding}" SelectionChanged="SelectionChanged">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Value" Binding="{Binding Model.Value}" />
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

This control defines a dependency property SelectedItems:

public partial class CustomDataGrid : UserControl
{
    public IEnumerable<ItemViewModel> SelectedItems
    {
        get { return (IEnumerable<ItemViewModel>)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(IEnumerable<ItemViewModel>),
            typeof(CustomDataGrid), new PropertyMetadata(new List<ItemViewModel>()));

    public CustomDataGrid()
    {
        InitializeComponent();
    }

    private void SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var dataGrid = sender as DataGrid;
        SelectedItems = dataGrid.SelectedItems.Cast<ItemViewModel>();
    }
}

Finally, this custom user control is used in a view, defined as follow:

<Window x:Class="Project.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:uc="clr-namespace:Project.Views"
    Title="Project"
    Height="700" Width="1050"
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}">

    <Window.Resources>
        <ResourceDictionary Source="Styles.xaml" />
    </Window.Resources>

    <Grid>
        <uc:CustomDataGrid DataContext="{Binding Items}"
            SelectedItems="{Binding SelectedItems}" />
    </Grid>
</Window>

With the corresponding ViewModel:

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<ItemViewModel> Items { get; private set; }

    private IEnumerable<ItemViewModel> selectedItems = new List<ItemViewModel>();
    public IEnumerable<ItemViewModel> SelectedItems
    {
        get { return selectedItems; }
        set
        {
            if (value != selectedItems)
            {
                selectedItems = value;
                RaisePropertyChanged(() => SelectedItems);
             }
        }
    }

    public MainViewModel()
    {
        //Something useful to feed Items
    }
}

My problem is: When I select one or more rows from my CustomDataGrid, SelectedItems from MainViewModel is not updated. I think I didn't wired something well but I don't find what.

Any idea?

Was it helpful?

Solution

You have to have a two-way binding on your SelectedItems property. Either you do that explicitly in the binding expression like this:

<uc:CustomDataGrid ... SelectedItems="{Binding SelectedItems, Mode=TwoWay}"/>

or you set the FrameworkPropertyMetadataOptions.BindsTwoWayByDefault flags in the dependency property declaration:

public static readonly DependencyProperty SelectedItemsProperty =
    DependencyProperty.Register(
        "SelectedItems",
        typeof(IEnumerable<ItemViewModel>),
        typeof(CustomDataGrid),
        new FrameworkPropertyMetadata(
            null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

Note also that it is bad practice to set the default property value to a new collection instance, as that collection would be used as default value for all "instances" of the property. In other words, the default value of the SelectedItems property on two instances of CustomDataGrid would be the same collection object. If you add items to the one, the items would also be contained in the other. You have to set the default value in the constructor of the control.


Edit: After taking a second look at your UserControl and how you bind its properties, I realized that it can't work the way you designed it. Setting the DataContext as done in your binding declaration

<uc:CustomDataGrid DataContext="{Binding Items}"
    SelectedItems="{Binding SelectedItems}"/>

would require to explicitly set the binding source object of the SelectedItems binding, perhaps like this

SelectedItems="{Binding SelectedItems, Source={StaticResource myViewModelInstance}}"

Instead of doing that, your control should have a bindable Items or ItemsSource in addition to the SelectedItems property. You could then simply write your bindings like this:

<uc:CustomDataGrid DataContext="{StaticResource myViewModelInstance}"
    ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}"/>

OTHER TIPS

Change the List to observablecollection, because observablecollection implements INotifyCollectionChanged and INotifyPropertyChanged where as list doesn't do so

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top