WPF - 如何使用 MVVM 将 ICollectionView 绑定到数据网格
WPF - How to bind ICollectionView to datagrids using MVVM
我是 WPF 的新手,按照这个 link 使用代码优先方法构建示例。这个例子有效。
https://msdn.microsoft.com/en-us/data/jj574514.aspx
现在,我正在尝试更改它以遵循 MVVM。
这是主窗口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>
这是 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();
}
}
我不知道如何将第二个详细信息数据网格绑定到 ViewModel。我想要和原来例子一样的Master-Details显示功能
那么如何将Categories中的Products绑定到第二个datagrid呢?使用 MVVM 实现它的正确方法是什么?
更多信息:
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; }
}
最简单的方法是将细节 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>
将主 DataGrid 的 SelectedItem 属性 绑定到 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");
}
}
(这需要您的视图模型实现 INotifyPropertyChanged。OnPropertyChanged 方法调用 PropertyChanged 事件处理程序)
添加另一个 属性 returns 所选类别的产品 属性
public ObservableCollection<Product> SelectedCategoryProducts
{
get
{
if (_selectedCategory == null) return null;
return _selectedCategory.Products;
}
}
将详细信息 DataGrid 绑定到视图模型中的 SelectedCategoryProducts 属性
<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>
有了 ICollectionView
不使用它的功能将是一种耻辱...
首先,在您的第一个数据网格(类别)上设置 IsSynchronizedToCurrentItem="true"
。
然后在您的第二个 DataGrid 中,将 DataSource 与 ItemsSource="{Binding Categories.CurrentItem.Products}"
绑定,其中 Categories 是您的视图模型 ICollectionView
。
IsSynchToCurrentItem=true
的效果是您不需要在您的视图模型中保持 属性 来跟踪您当前的项目,因为 ICollectionView
会为您做到这一点。
然后每次用户 select 数据网格中的一行时,当前项目将在视图模型中更改(并且 ICollectionView
上有一个事件来通知)等每次你都会在您的视图模型中设置当前项目,相应的行将被 selected.
除此功能外,ICollectionView 使您能够在不接触源集合的情况下进行排序、过滤和分组,最重要的是,它使您能够以编程方式更改当前项目在您的视图模型中通过使用 MoveCurrentTo(object target)
、MovecurrentToFirst()
,等等....
您的业务 C# 模型很好,所以您的 XAML 看起来像这样:
<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>
我是 WPF 的新手,按照这个 link 使用代码优先方法构建示例。这个例子有效。 https://msdn.microsoft.com/en-us/data/jj574514.aspx
现在,我正在尝试更改它以遵循 MVVM。
这是主窗口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>
这是 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();
}
}
我不知道如何将第二个详细信息数据网格绑定到 ViewModel。我想要和原来例子一样的Master-Details显示功能
那么如何将Categories中的Products绑定到第二个datagrid呢?使用 MVVM 实现它的正确方法是什么?
更多信息:
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; }
}
最简单的方法是将细节 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>
将主 DataGrid 的 SelectedItem 属性 绑定到 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");
}
}
(这需要您的视图模型实现 INotifyPropertyChanged。OnPropertyChanged 方法调用 PropertyChanged 事件处理程序)
添加另一个 属性 returns 所选类别的产品 属性
public ObservableCollection<Product> SelectedCategoryProducts
{
get
{
if (_selectedCategory == null) return null;
return _selectedCategory.Products;
}
}
将详细信息 DataGrid 绑定到视图模型中的 SelectedCategoryProducts 属性
<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>
有了 ICollectionView
不使用它的功能将是一种耻辱...
首先,在您的第一个数据网格(类别)上设置 IsSynchronizedToCurrentItem="true"
。
然后在您的第二个 DataGrid 中,将 DataSource 与 ItemsSource="{Binding Categories.CurrentItem.Products}"
绑定,其中 Categories 是您的视图模型 ICollectionView
。
IsSynchToCurrentItem=true
的效果是您不需要在您的视图模型中保持 属性 来跟踪您当前的项目,因为 ICollectionView
会为您做到这一点。
然后每次用户 select 数据网格中的一行时,当前项目将在视图模型中更改(并且 ICollectionView
上有一个事件来通知)等每次你都会在您的视图模型中设置当前项目,相应的行将被 selected.
除此功能外,ICollectionView 使您能够在不接触源集合的情况下进行排序、过滤和分组,最重要的是,它使您能够以编程方式更改当前项目在您的视图模型中通过使用 MoveCurrentTo(object target)
、MovecurrentToFirst()
,等等....
您的业务 C# 模型很好,所以您的 XAML 看起来像这样:
<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>