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; }
}
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>
最简单的方法是将DataGrid的DataContext绑定到主DataGrid的SelectedItem属性
<DataGrid x:Name="MasterGrid" 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>
<DataGrid DataContext="{Binding SelectedItem.Products, ElementName=MasterGrid}" 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>
With a ICollectionView
it would be a shame not to use its functionalities...
First, set the IsSynchronizedToCurrentItem="true"
on you first datagrid (categories).
Then in your second DataGrid, bind the DataSource with ItemsSource="{Binding Categories.CurrentItem.Products}"
Where Categories is your view model ICollectionView
.
The effect of IsSynchToCurrentItem=true
is that you don't need to hold a property in your viewmodel to keep track of your current item because the ICollectionView
does that for you.
Then each time the user select a row in the datagrid, the currentitem will change in the viewmodel (and there is an event on the ICollectionView
to notify so) et each time you will set the current item in your view model, the correponding row will be selected.
In addition to this feature, the ICollectionView enables you to sort, filter and group without touching to your source collection and most of all, it enables you to programatically change the current item IN YOU VIEW MODEL WITHOUT MESSING ARROUND WITH THE XAML CONTROL (and thus the selected row in the corresponding visual/XAML itemcontrol) by using the method such as MoveCurrentTo(object target)
, MovecurrentToFirst()
, and so on....
Your business C# model is fine, so your XAML would look like that :
<DataGrid Grid.ColumnSpan="2" IsSynchronizedToCurrentItem="true" 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" ItemsSource="{Binding Categories.CurrentItem.Products}" 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>
链接地址: http://www.djcxy.com/p/56154.html
上一篇: MVVM ICommand替代品