Apple Juice Apple Juice - 4 months ago 101
C# Question

WPF - How to bind ICollectionView to datagrids using MVVM

I am new to WPF and following this link to use code first method to build the example. And the example works.
https://msdn.microsoft.com/en-us/data/jj574514.aspx

Now, I am trying to change it to follow MVVM.

Here is the MainWindow XAML

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPFwithEFSampleCodeFirst" mc:Ignorable="d" x:Class="WPFwithEFSampleCodeFirst.MainWindow"
Title="MainWindow" Height="352.134" Width="517.53" Loaded="Window_Loaded">

<Grid Margin="0,0,0,-3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0*"/>
<ColumnDefinition Width="77*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<Button Content="Save" Grid.Column="2" HorizontalAlignment="Left" Margin="41,167,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<DataGrid Grid.ColumnSpan="2" ItemsSource="{Binding Categories}" AutoGenerateColumns="False" HorizontalAlignment="Left" Margin="32,10,0,0" VerticalAlignment="Top" Height="124" Width="330" >
<DataGrid.Columns>
<DataGridTextColumn Width="SizeToHeader" Header="Category Id" Binding="{Binding Path = CategoryId}"/>
<DataGridTextColumn Width="SizeToHeader" Header="Name" Binding="{Binding Path = Name}"/>
</DataGrid.Columns>
</DataGrid>

<DataGrid Grid.ColumnSpan="2" AutoGenerateColumns="False" HorizontalAlignment="Left" Margin="32,153,0,0" VerticalAlignment="Top" Height="146" Width="330">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding CategoryId}" Header="Category Id" Width="SizeToHeader"/>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="SizeToHeader"/>
<DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id" Width="SizeToHeader"/>
</DataGrid.Columns>
</DataGrid>
</Grid>




Here is the MainWindowViewModel

class MainWindowViewModel
{
private ICollectionView _categoryView;

public ICollectionView Categories
{
get { return _categoryView; }
}

ProductContext context = new ProductContext();

public MainWindowViewModel()
{
IList<Category> categories = GetCategories();
_categoryView = CollectionViewSource.GetDefaultView(categories);

}

public IList<Category> GetCategories()
{
return context.Categories.ToList();
}
}


I don't know how to binding the second details datagrid to ViewModel. I would like to have the same Master-Details display function as the original example.

So how to bind Products in Categories to the second datagrid? What is the right way to implement it using MVVM?




More info:

public class Category
{
public Category()
{
this.Products = new ObservableCollection<Product>();
}

public int CategoryId { get; set; }
public string Name { get; set; }

public virtual ObservableCollection<Product> Products { get; private set; }
}

public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }

public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}

public class ProductContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}

Answer

Bind the master DataGrid's SelectedItem property to a property in the ViewModel

<DataGrid SelectedItem="{Binding SelectedCategory}" Grid.ColumnSpan="2" ItemsSource="{Binding Categories}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn  Width="SizeToHeader" Header="Category Id" Binding="{Binding Path = CategoryId}"/>
        <DataGridTextColumn  Width="SizeToHeader" Header="Name" Binding="{Binding Path = Name}"/>
    </DataGrid.Columns>
</DataGrid>

MainWindowViewModel

private Category _selectedCategory;

public Category SelectedCategory
{
    get { return _selectedCategory; }
    set
    {
        _selectedCategory = value;
        OnPropertyChanged("SelectedCategory");
        OnPropertyChanged("SelectedCategoryProducts");
    }
}

(this requires your view model to implement INotifyPropertyChanged. The OnPropertyChanged method invokes the PropertyChanged event handler)

Add another property that returns the selected category's products property

public ObservableCollection<Product> SelectedCategoryProducts
{
    get
    {
        if (_selectedCategory == null) return null;

        return _selectedCategory.Products;
    }
}

Bind the details DataGrid to the SelectedCategoryProducts property in the view model

<DataGrid ItemsSource="{Binding SelectedCategoryProducts}" Grid.ColumnSpan="2" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn  Binding="{Binding CategoryId}" Header="Category Id" Width="SizeToHeader"/>
        <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="SizeToHeader"/>
        <DataGridTextColumn  Binding="{Binding ProductId}" Header="Product Id" Width="SizeToHeader"/>
    </DataGrid.Columns>
</DataGrid>
Comments