绑定到UserControl中ComboBox的SelectedItem

时间:2019-01-03 19:59:02

标签: c# wpf xaml data-binding wpf-controls

我有一个由带有标签的ComboBox组成的UserControl。我希望使用此ComboBox的实例更新屏幕,并根据SelectedItem值在StackPanel中动态创建UserControls。

我目前有一个包含此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的控件。我不确定如何正确连接到UserControl的子对象的SelectionChanged事件。

任何想法,更正和(建设性)批评都值得赞赏。感谢您的任何提前帮助。

1 个答案:

答案 0 :(得分:0)

有几种方法可以解决此问题。这是一种方法。这不一定是最好的方法,但很容易理解。

首先,用户控件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类型的对象的列表(在文件底部定义),并使用一些值初始化该数组。

回想一下,我们将MyUserControl中的ComboBox的ItemsSource属性设置为“ {Binding MyComboBoxItems}”,所以问题是,如何在MainWindow中定义的属性在MyUserControl中神奇地可用?

在WPF中,如果未明确设置DataContext值,则它们将从父控件继承,并且由于我们没有为控件指定数据上下文,因此MyUserControl的实例继承了父窗口的DataContext。在构造函数中,我们将MainWindow数据上下文设置为引用自身,因此MyComboBoxItems列表可用于任何子控件(及其子控件,等等)。

通常,我们将继续为用户控件添加一个名为ItemsSource的依赖项属性,并在用户控件中将ComboBox的ItemsSource属性绑定到该依赖项属性而不是MyComboxItems。  然后MainWindow.xaml将其集合直接绑定到用户控件上的依赖项属性。这有助于使用户控件更加可重用,因为它不依赖于继承数据上下文中定义的特定属性。

最后,在用户控件的自定义事件的事件处理程序中,我们获取用户选择的值,并创建一个由文本框填充的UserControl(所有控件都设置了各种属性,使这些项在视觉上变得有趣),然后直接添加它们到StackPanel的Children属性。

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; }
    }
}

最后,我考虑使用绑定到ObservableCollection的ItemsControl或ListBox,而不是将其粘贴到StackPanel中。您可以根据数据项中的设置定义一个不错的数据模板供用户控件显示,也可以定义一个DataTemplateSelector来使用不同的用户控件。这将使我可以简单地将对在选择更改处理程序中获得的MyComboBoxItem的引用添加到该集合,并且绑定机制将使用我定义的数据模板自动生成一个新项目,并创建必要的可视元素来显示它。

因此,考虑到所有这些,这里是进行所有更改的更改。

首先,我们修改数据项以添加color属性。我们将使用该属性来确定如何显示所选项目:

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

现在,我们在MainWindow.xaml.cs中实现INotifyPropertyChanged,以使WPF绑定引擎在更改属性时更新UI。这是事件处理程序和帮助程序方法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来指向MyUserControl中定义的属性ItemsSource,并且还将ItemTemplate映射到MyUserControl中的ItemTemplate属性。

<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}" />

因此,现在我们可以直接绑定项目并提供我们自己的数据模板,以指定组合框应如何显示项目。

最后,我想用ItemsControl代替StackPanel。这就像一个没有滚动或项目选择支持的列表框。实际上,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;
    }
}

我们在MainWindow.xaml中的xaml中创建数据模板选择器的实例:

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

最后,我们将堆栈面板替换为Items控件:

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