Нет ничего плохого в связывании нескольких ComboBox
es и установке их Visibility
.С одной стороны, это значительно снижает сложность по сравнению с кодом из вашего поста.
Тем не менее, вы можете легко поменять контекст (не путать с DataContext
) на ItemsControl
путем введения дополнительной абстракции между моделью представления и представлением.
Вот как это работает:
- Создание объекта context с соответствующими свойствами
- Примените контекст к вашему
ItemsControl
- Позвольте де свойствам повторно привязать контекст изменилось
Ваша идея сборасвойства на единицу, безусловно, хорошие.Хотя реализация может быть лучше, и модель представления, и представление выглядят раздутыми.Вот для чего предназначен этот объект контекста, собирая и сохраняя состояние, когда вы меняете контексты назад и вперед.
Начиная с наших классов моделей.Давайте создадим код для интерфейса (даже если ItemsSource нетипизирован).
namespace WpfApp.Models
{
public interface IEntity
{
string Name { get; }
}
public class Dog : IEntity
{
public Dog(string breed, string name)
{
Breed = breed;
Name = name;
}
public string Breed { get; }
public string Name { get; }
}
public class Author : IEntity
{
public Author(string genre, string name)
{
Genre = genre;
Name = name;
}
public string Genre { get; }
public string Name { get; }
}
}
Далее, ViewModels, начиная с нашего контекста.
namespace WpfApp.ViewModels
{
public class ItemsContext : ViewModelBase
{
public ItemsContext(IEnumerable<IEntity> items)
{
if (items == null || !items.Any()) throw new ArgumentException(nameof(Items));
Items = new ObservableCollection<IEntity>(items);
SelectedItem = Items.First();
}
public ObservableCollection<IEntity> Items { get; }
private IEntity selectedItem;
public IEntity SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged();
}
}
public string DisplayMemberPath { get; set; }
}
}
Как уже было сказано,соответствующие свойства, с уведомлениями для SelectedItem
, ничего особенного.Мы сразу видим влияние на наши MainViewModel
.
namespace WpfApp.ViewModels
{
public class MainViewModel : ViewModelBase
{
private readonly ItemsContext _dogContext;
private readonly ItemsContext _authorContext;
public MainViewModel()
{
_dogContext = new ItemsContext(FetchDogs()) { DisplayMemberPath = nameof(Dog.Breed) };
_authorContext = new ItemsContext(FetchAuthors()) { DisplayMemberPath = nameof(Author.Genre) };
}
private ItemsContext selectedContext;
public ItemsContext SelectedContext
{
get { return selectedContext; }
set
{
selectedContext = value;
OnPropertyChanged();
}
}
private bool dogChecked;
public bool DogChecked
{
get { return dogChecked; }
set
{
dogChecked = value;
if(dogChecked) SelectedContext = _dogContext;
}
}
private bool authorChecked;
public bool AuthorChecked
{
get { return authorChecked; }
set
{
authorChecked = value;
if(authorChecked) SelectedContext = _authorContext;
}
}
private static IEnumerable<IEntity> FetchDogs() =>
new List<IEntity>
{
new Dog("Terrier", "Ralph"),
new Dog("Beagle", "Eddy"),
new Dog("Poodle", "Fifi")
};
private static IEnumerable<IEntity> FetchAuthors() =>
new List<IEntity>
{
new Author("SciFi", "Bradbury"),
new Author("RomCom", "James")
};
}
}
Два четко разделенных потока, каждый из которых управляет своим собственным контекстом.Понятно, что вы можете легко распространить это на любое количество контекстов, не мешая друг другу.Теперь, чтобы применить контекст к нашему ItemsControl
, у нас есть два варианта.Мы могли бы создать подкласс нашего Control
или использовать присоединенное свойство.Предпочтение композиции по наследованию, вот класс с AP.
namespace WpfApp.Extensions
{
public class Selector
{
public static ItemsContext GetContext(DependencyObject obj) => (ItemsContext)obj.GetValue(ContextProperty);
public static void SetContext(DependencyObject obj, ItemsContext value) => obj.SetValue(ContextProperty, value);
public static readonly DependencyProperty ContextProperty =
DependencyProperty.RegisterAttached("Context", typeof(ItemsContext), typeof(Selector), new PropertyMetadata(null, OnItemsContextChanged));
private static void OnItemsContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (System.Windows.Controls.Primitives.Selector)d;
var ctx = (ItemsContext)e.NewValue;
if (e.OldValue != null) // Clean up bindings from previous context, if any
{
BindingOperations.ClearBinding(selector, System.Windows.Controls.Primitives.Selector.SelectedItemProperty);
BindingOperations.ClearBinding(selector, ItemsControl.ItemsSourceProperty);
BindingOperations.ClearBinding(selector, ItemsControl.DisplayMemberPathProperty);
}
selector.SetBinding(System.Windows.Controls.Primitives.Selector.SelectedItemProperty, new Binding(nameof(ItemsContext.SelectedItem)) { Source = ctx, Mode = BindingMode.TwoWay });
selector.SetBinding(ItemsControl.ItemsSourceProperty, new Binding(nameof(ItemsContext.Items)) { Source = ctx });
selector.SetBinding(ItemsControl.DisplayMemberPathProperty, new Binding(nameof(ItemsContext.DisplayMemberPath)) { Source = ctx });
}
}
}
, который охватывает оба шага 2 и 3. Вы можете настроить это, как вам нравится.Например, мы сделали ItemsContext.DisplayMemberPath
пропуском без уведомления, поэтому вы можете просто установить значение напрямую, а не через привязку.
Наконец, представление, где все это объединяется.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp.ViewModels"
xmlns:ext="clr-namespace:WpfApp.Extensions"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Style x:Key="SelectorStyle" TargetType="{x:Type Selector}">
<Setter Property="Width" Value="150"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="0,20"/>
</Style>
</Window.Resources>
<StackPanel Margin="20">
<RadioButton GroupName="Entities" Content="Dogs" IsChecked="{Binding DogChecked}" />
<RadioButton GroupName="Entities" Content="Authors" IsChecked="{Binding AuthorChecked}" />
<ComboBox ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
<ListBox ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
<DataGrid ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
</StackPanel>
</Window>
Крутой особенностью Attached Property является то, что мы кодируем против абстрактного элемента управления Selector
, который является прямым потомком ItemsControl
.Таким образом, не изменяя наши нижние уровни, мы также можем делиться нашим контекстом с ListBox
и DataGrid
.