WPF - Как связать ICollectionView с сетями данных, используя MVVM
Я новичок в WPF и по этой ссылке, чтобы использовать код первым методом, чтобы построить пример. И пример работает. 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, что и в оригинальном примере.
Итак, как связать товары в категориях со вторым сеткой данных? Как правильно реализовать это с помощью 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; }
}
3 ответа
Свяжите свойство SelectedItem главной DataGrid со свойством в 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)
Добавьте другое свойство, которое возвращает свойство продуктов выбранной категории.
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>
Простейшим способом является привязка DataContext подробного DataGrid к свойству SelectedItem главной DataGrid
<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>
С ICollectionView
было бы стыдно не использовать его функциональные возможности...
Сначала установите IsSynchronizedToCurrentItem="true"
на вас первый датагрид (категории).
Затем во второй DataGrid свяжите источник данных с ItemsSource="{Binding Categories.CurrentItem.Products}"
Где Категории - ваша модель представления ICollectionView
,
Эффект IsSynchToCurrentItem=true
является то, что вам не нужно держать свойство в вашей модели представления, чтобы отслеживать текущий элемент, потому что ICollectionView
делает это для вас.
Затем каждый раз, когда пользователь выбирает строку в сетке данных, текущий элемент будет меняться в модели представления (и на ICollectionView
чтобы уведомить об этом) и каждый раз, когда вы будете устанавливать текущий элемент в вашей модели представления, будет выбираться соответствующая строка.
В дополнение к этой функции ICollectionView позволяет сортировать, фильтровать и группировать, не касаясь вашей исходной коллекции, и, самое главное, позволяет программно изменять текущий элемент В МОДЕЛИ ВАС ПРОСМОТРА, БЕЗ ЗАПИСИ В КОНТРОЛЕ XAML (и, таким образом, выбранная строка в соответствующем элементе управления visual/XAML), используя такой метод, как 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>