Привязка к выбранному элементу ComboBox в UserControl - PullRequest
0 голосов
/ 03 января 2019

У меня есть UserControl, состоящий из ComboBox с меткой.Я хочу обновить экран с экземпляром этого ComboBox и динамически создавать UserControls в StackPanel, основываясь на значении SelectedItem.

В настоящее время у меня есть экран с экземпляром этого ComboBox, и он привязывается следующим образом:

Пример псевдокода (удаление несвязанного кода):

<!-- MyComboBoxExample.xaml -->    
<ComboBox x:Name="myComboBox" SelectedValuePath="Key" DisplayMemberPath="Value" ItemsSource="{Binding MyBoxItems}/>
/* MyComboBoxExample.xaml.cs */

public static readonly DependencyProperty MyBoxItemsProperty = DependencyProperty.Register("MyBoxItems", typeof(Dictionary<string, string>),
    typeof(MyComboBoxExample), new PropertyMetadata(null));
<!-- MyScreen.xaml -->    
<local:MyComboBoxExample x:Name="MyComboBoxExampleInstance" MyBoxItems="{Binding Descriptions}"/>

Я новичок в WPF и привязке данных, поэтому не уверен, что это лучший способ реализовать это.По сути, на экране: когда изменяется выбор MyComboBoxExampleInstance, динамически устанавливайте элементы управления StackPanel на экране.Я не уверен, как правильно подключить событие SelectionChanged дочернего объекта UserControl.

Любые мысли, исправления и (конструктивная) критика приветствуются.Спасибо за любую помощь заранее.

1 Ответ

0 голосов
/ 05 января 2019

Есть несколько способов сделать это.Вот один из способов.Это не обязательно лучший способ, но его легко понять.

Во-первых, пользовательский элемент управления xaml.Обратите внимание на привязку свойства ItemsSource к пользовательскому элементу управления, который указывает MyComboBoxItems в качестве источника элементов.Подробнее об этом чуть ниже.

 <UserControl x:Class="WpfApp1.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
        <Grid>
            <ComboBox Height="Auto" ItemsSource="{Binding MyComboBoxItems}" SelectionChanged="OnSelectionChanged">
               <ComboBox.ItemTemplate>
                   <DataTemplate>
                       <TextBlock Text="{Binding Text}"/>
                   </DataTemplate>
               </ComboBox.ItemTemplate>
           </ComboBox>
        </Grid>
    </UserControl>

Теперь код, MyUserControl.xaml.cs.Мы предоставляем обработчик события с измененным выбором в выпадающем списке, который, в свою очередь, вызывает пользовательское событие MyComboBoxSelectionChanged, которое определяется классом аргумента события и обработчиком делегата в нижней части кода.Наш метод OnSelectionChanged просто перенаправляет событие изменения выбора через пользовательское событие, которое мы определили.

using System;
using System.Windows.Controls;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MyUserControl.xaml
    /// </summary>
    public partial class MyUserControl : UserControl
    {
        public event MyComboBoxSelectionChangedEventHandler MyComboBoxSelectionChanged;
        public MyUserControl()
        {
            InitializeComponent();
        }

        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {

            if (e.AddedItems.Count > 0)
            {
                MyComboBoxSelectionChanged?.Invoke(this,
                    new MyComboBoxSelectionChangedEventArgs() {MyComboBoxItem = e.AddedItems[0]});
            }
        }
    }

    public class MyComboBoxSelectionChangedEventArgs : EventArgs
    {
        public object MyComboBoxItem { get; set; }
    }

    public delegate void MyComboBoxSelectionChangedEventHandler(object sender, MyComboBoxSelectionChangedEventArgs e);

}

Теперь мы переходим к нашему MainWindow.xaml, где мы определяем экземпляр MyUserControl и устанавливаем обработчик для пользовательскогоСобытие мы определили.Мы также предоставляем StackPanel для размещения элементов, которые будут созданы при изменении события выбора.

<Window x:Class="WpfApp1.MainWindow"
        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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450"
        Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <local:MyUserControl Width="140" Height="32" DataContext="{Binding}"  Grid.Row="0" MyComboBoxSelectionChanged="OnSelectionChanged"></local:MyUserControl>

        <StackPanel Grid.Row="1" x:Name="MyUserControls"/>
    </Grid>

</Window>

Теперь код для MainWindow.xaml.Здесь мы определяем открытое свойство, содержащее список объектов типа MyComboBoxItem (определенный в нижней части файла), и мы инициализируем массив с некоторыми значениями.

Напомним, что мы установили свойство ItemsSource ComboBox внутри MyUserControl на "{Binding MyComboBoxItems}", поэтому вопрос заключается в том, как свойство, определенное в MainWindow, волшебным образом становится доступным в MyUserControl?

В WPF значения DataContext наследуются от родительских элементов управления, если они не установлены явно, и поскольку мы не указали контекст данных для элемента управления, экземпляр MyUserControl наследует DataContext родительского окна.В конструкторе мы устанавливаем контекст данных MainWindow для обращения к самому себе, поэтому список MyComboBoxItems доступен для любых дочерних элементов управления (и их дочерних элементов и т. Д.)

Обычно мы добавляем зависимость.свойство для пользовательского элемента управления с именем ItemsSource, а в пользовательском элементе управления мы привязываем свойство ItemsSource ComboBox к свойству зависимости, а не к MyComboxItems.MainWindow.xaml затем привязывает свою коллекцию непосредственно к свойству зависимостей в пользовательском элементе управления.Это помогает сделать пользовательский элемент управления более пригодным для повторного использования, поскольку он не будет зависеть от определенных свойств, определенных в унаследованном контексте данных.

Наконец, в обработчике события для пользовательского события пользовательского элемента управления мы получаем значение, выбранное с помощьюпользователь и создайте UserControl, заполненный текстовым полем (все с различными свойствами, установленными, чтобы сделать элементы визуально интересными), и мы напрямую добавим их в свойство Children в StackPanel.

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public List<MyComboBoxItem> MyComboBoxItems { get; set; } = new List<MyComboBoxItem>()
        {
            new MyComboBoxItem() {Text = "Item1"},
            new MyComboBoxItem() {Text = "Item2"},
            new MyComboBoxItem() {Text = "Item3"},

        };
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private void OnSelectionChanged(object sender, MyComboBoxSelectionChangedEventArgs e)
        {
            if (e.MyComboBoxItem is MyComboBoxItem item)
            {
                MyUserControls.Children.Add(
                new UserControl()
                {
                    Margin = new Thickness(2),
                    Background = new SolidColorBrush(Colors.LightGray),
                    Content = new TextBlock()
                    {
                        Margin = new Thickness(4),
                        VerticalAlignment = VerticalAlignment.Center,
                        HorizontalAlignment = HorizontalAlignment.Center,
                        FontSize = 48,
                        FontWeight = FontWeights.Bold,
                        Foreground = new SolidColorBrush(Colors.DarkGreen),
                        Text = item.Text
                    }
                });
            }
        }
    }

    public class MyComboBoxItem
    {
        public string Text { get; set; }
    }
}

Наконец, я 'рассмотреть возможность использования ItemsControl или ListBox, привязанного к ObservableCollection, а не вставлять вещи в StackPanel.Вы можете определить хороший шаблон данных для отображения пользовательского элемента управления и, возможно, DataTemplateSelector для использования различных пользовательских элементов управления на основе настроек в элементе данных.Это позволило бы мне просто добавить ссылку на MyComboBoxItem, полученную в обработчике изменения выбора, к этой коллекции, и механизм привязки автоматически сгенерирует новый элемент, используя определенный мной шаблон данных, и создаст необходимые визуальные элементы для его отображения.

Итак, учитывая все это, вот изменения, чтобы сделать все это.

Сначала мы изменим наш элемент данных, добавив свойство цвета.Мы будем использовать это свойство, чтобы определить способ отображения выбранного элемента:

public class MyComboBoxItem
{
    public string Color { get; set; }
    public string Text { get; set; }
}

Теперь мы реализуем INotifyPropertyChanged в MainWindow.xaml.cs, чтобы механизм привязки WPF обновлял пользовательский интерфейс при изменении свойств.Это обработчик событий и вспомогательный метод OnPropertyChanged.

Мы также модифицируем инициализатор поля со списком, чтобы добавить значение для свойства Color.Мы оставим пустым для удовольствия.

Затем мы добавляем новый ObservableCollect, "ActiveUserControls", чтобы сохранить MyComboBoxItem, полученный в событии выбора измененного поля со списком.Мы делаем это вместо создания пользовательских элементов управления на лету в коде.

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public List<MyComboBoxItem> MyComboBoxItems { get; set; } = new List<MyComboBoxItem>()
    {
        new MyComboBoxItem() {Text = "Item1", Color = "Red"},
        new MyComboBoxItem() {Text = "Item2", Color = "Green"},
        new MyComboBoxItem() {Text = "Item3"},
    };

    private ObservableCollection<MyComboBoxItem> _activeUserControls;
    public ObservableCollection<MyComboBoxItem> ActiveUserControls
    {
        get => _activeUserControls;
        set { _activeUserControls = value; OnPropertyChanged(); }
    }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void OnSelectionChanged(object sender, MyComboBoxSelectionChangedEventArgs e)
    {
        if (e.MyComboBoxItem is MyComboBoxItem item)
        {
            if (ActiveUserControls == null)
            {
                ActiveUserControls = new ObservableCollection<MyComboBoxItem>();
            }

            ActiveUserControls.Add(item);
        }
    }
}

Теперь давайте посмотрим на некоторые изменения, которые мы внесли в MyUserControl.Мы изменили поле со списком ItemsSource, чтобы оно указывало на свойство ItemsSource, определенное в MyUserControl, и мы также сопоставляем ItemTemplate со свойством ItemTemplate в MyUserControl.

<UserControl x:Class="WpfApp1.MyUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:WpfApp1"
         mc:Ignorable="d"
         d:DesignHeight="450"
         d:DesignWidth="800">
    <Grid>
        <ComboBox Height="Auto"
              ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyUserControl}}}"
              ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyUserControl}}}"
              SelectionChanged="OnSelectionChanged">

        </ComboBox>
    </Grid>
</UserControl>

Здесь мы определили эти новые свойства в MyUserControl..cs.

public partial class MyUserControl : UserControl
{
    public event MyComboBoxSelectionChangedEventHandler MyComboBoxSelectionChanged;
    public MyUserControl()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource",
            typeof(System.Collections.IEnumerable),
            typeof(MyUserControl),
            new PropertyMetadata(null));

    public System.Collections.IEnumerable ItemsSource
    {
        get => GetValue(ItemsSourceProperty) as IEnumerable;
        set => SetValue(ItemsSourceProperty, (IEnumerable)value);
    }

    public static readonly DependencyProperty ItemTemplateProperty =
        DependencyProperty.Register("ItemTemplate",
            typeof(DataTemplate),
            typeof(MyUserControl),
            new PropertyMetadata(null));

    public DataTemplate ItemTemplate
    {
        get => GetValue(ItemTemplateProperty) as DataTemplate;
        set => SetValue(ItemTemplateProperty, (DataTemplate)value);
    }

    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {

        if (e.AddedItems.Count > 0)
        {
            MyComboBoxSelectionChanged?.Invoke(this,
                new MyComboBoxSelectionChangedEventArgs() {MyComboBoxItem = e.AddedItems[0]});
        }
    }
}

Давайте посмотрим, как мы привязываемся к тем в MainWindow.xaml:

<local:MyUserControl Width="140"
                         Height="32"
                         Grid.Row="0"
                         MyComboBoxSelectionChanged="OnSelectionChanged"
                         ItemsSource="{Binding MyComboBoxItems}"
                         ItemTemplate="{StaticResource ComboBoxItemDataTemplate}" />

Так что теперь мы можем напрямую связать наши элементы и предоставить собственный шаблон данных для указаниякак в поле со списком должен отображаться элемент.

Наконец, я хочу заменить StackPanel на ItemsControl.Это как ListBox без прокрутки или поддержки выбора элементов.Фактически, ListBox является производным от ItemsControl.Я также хочу использовать другой пользовательский элемент управления в списке на основе значения свойства Color.Для этого мы определяем несколько шаблонов данных для каждого значения в MainWindow.Xaml:

    <DataTemplate x:Key="ComboBoxItemDataTemplate"
                  DataType="local:MyComboBoxItem">
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="4"
                       Text="{Binding Text}" />
            <TextBlock Margin="4"
                       Text="{Binding Color}" />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="GreenUserControlDataTemplate"
                  DataType="local:MyComboBoxItem">
        <local:GreenUserControl DataContext="{Binding}" />
    </DataTemplate>

    <DataTemplate x:Key="RedUserControlDataTemplate"
                  DataType="local:MyComboBoxItem">
        <local:RedUserControl DataContext="{Binding}" />
    </DataTemplate>

    <DataTemplate x:Key="UnspecifiedUserControlDataTemplate"
                  DataType="local:MyComboBoxItem">
        <TextBlock Margin="4"
                   Text="{Binding Text}" />
    </DataTemplate>

Вот RedUserControl.Зеленый цвет совпадает с другим цветом переднего плана.

<UserControl x:Class="WpfApp1.RedUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d"
             d:DesignHeight="450"
             d:DesignWidth="800">
    <Grid Background="LightGray"
          Margin="2">
        <TextBlock Margin="4"
                   Foreground="DarkRed"
                   TextWrapping="Wrap"
                   Text="{Binding Text}"
                   FontSize="24"
                   FontWeight="Bold" />
    </Grid>
</UserControl>

Теперь хитрость заключается в том, чтобы использовать правильный шаблон данных на основе значения цвета.Для этого мы создаем DataTemplateSelector.Это вызывается WPF для каждого отображаемого элемента.Мы можем исследовать объект контекста данных и выбрать, какой шаблон данных использовать:

public class UserControlDataTemplateSelector : DataTemplateSelector
{

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (container is FrameworkElement fe)
        {
            if (item is MyComboBoxItem cbItem)
            {
                if (cbItem.Color == "Red")
                {
                    return fe.FindResource("RedUserControlDataTemplate") as DataTemplate;
                }
                if (cbItem.Color == "Green")
                {
                    return fe.FindResource("GreenUserControlDataTemplate") as DataTemplate;
                }
                return fe.FindResource("UnspecifiedUserControlDataTemplate") as DataTemplate;
            }
        }
        return null;
    }
}

Мы создаем экземпляр нашего селектора шаблона данных в xaml в MainWindow.xaml:

<Window.Resources>
    <local:UserControlDataTemplateSelector x:Key="UserControlDataTemplateSelector" />
...

Наконецмы заменяем нашу панель стека элементом управления:

   <ItemsControl Grid.Row="1"
                  x:Name="MyUserControls"
                  ItemsSource="{Binding ActiveUserControls}"
                  ItemTemplateSelector="{StaticResource UserControlDataTemplateSelector}" />
...