绑定到 UserControl 中 ComboBox 的 SelectedItem
Binding to SelectedItem of ComboBox in UserControl
我有一个由带有标签的组合框组成的用户控件。我希望使用此 ComboBox 的实例更新屏幕,并根据 SelectedItem 值在 StackPanel 中动态创建 UserControl。
我目前有一个带有此 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 事件。
感谢任何想法、更正和(建设性)批评。提前感谢您的帮助。
有几种方法可以解决这个问题。这是一种方法。这不一定是最好的方法,但很容易理解。
首先,用户控件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,该事件由代码底部的事件参数 class 和委托处理程序定义。我们的 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 的代码隐藏。这里我们定义了一个 public 属性 包含一个 MyComboBoxItem 类型的对象列表(在文件底部定义),我们用一些值初始化数组。
回想一下,我们将 MyUserControl 中的 ComboBox 的 ItemsSource 属性 设置为“{Binding MyComboBoxItems}”,所以问题是,MainWindow 中定义的 属性 是如何神奇地变为可用的我的用户控件?
在 WPF 中,如果未显式设置 DataContext 值,则从父控件继承它们,并且由于我们没有为控件指定数据上下文,MyUserControl 的实例继承父控件的 DataContext window.在构造函数中,我们将 MainWindow 数据上下文设置为引用自身,因此 MyComboBoxItems 列表可用于任何子控件(及其子控件等)。
通常我们会继续为名为 ItemsSource 的用户控件添加依赖项 属性,在用户控件中我们会将组合框的 ItemsSource 属性 绑定到依赖项 属性 而不是 MyComboxItems。
MainWindow.xaml 然后会将它的集合直接绑定到用户控件上的依赖项 属性。这有助于提高用户控件的可重用性,因为它不依赖于继承数据上下文中定义的特定属性。
最后,在用户控件的自定义事件的事件处理程序中,我们获取用户选择的值并创建一个填充有文本框的 UserControl(所有这些都设置了各种属性以使项目在视觉上很有趣),我们直接将它们添加到 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; }
}
}
最后,我会考虑使用绑定到 ObservableCollection 的 ItemsControl 或 ListBox,而不是将内容粘贴到 StackPanel 中。您可以为要显示的用户控件定义一个漂亮的数据模板,也可以定义一个 DataTemplateSelector 以根据数据项中的设置使用不同的用户控件。这将允许我简单地将对在选择更改处理程序中获得的 MyComboBoxItem 的引用添加到该集合,并且绑定机制将使用我定义的数据模板自动生成一个新项目并创建必要的可视元素来显示它。
因此,考虑到所有这些,这里是完成所有这些的更改。
首先,我们修改数据项以添加颜色 属性。我们将使用 属性 来确定我们如何显示所选项目:
public class MyComboBoxItem
{
public string Color { get; set; }
public string Text { get; set; }
}
现在我们在 MainWindow.xaml.cs 中实现 INotifyPropertyChanged,让 WPF 绑定引擎在我们更改属性时更新 UI。这是事件处理程序和辅助方法 OnPropertyChanged。
我们还修改了组合框初始值设定项以添加颜色值 属性。我们将留空以供娱乐。
然后我们添加一个新的 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 派生出来的。我还想根据颜色 属性 的值在列表中使用不同的用户控件。为此,我们为 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}" />
我有一个由带有标签的组合框组成的用户控件。我希望使用此 ComboBox 的实例更新屏幕,并根据 SelectedItem 值在 StackPanel 中动态创建 UserControl。
我目前有一个带有此 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 事件。
感谢任何想法、更正和(建设性)批评。提前感谢您的帮助。
有几种方法可以解决这个问题。这是一种方法。这不一定是最好的方法,但很容易理解。
首先,用户控件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,该事件由代码底部的事件参数 class 和委托处理程序定义。我们的 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 的代码隐藏。这里我们定义了一个 public 属性 包含一个 MyComboBoxItem 类型的对象列表(在文件底部定义),我们用一些值初始化数组。
回想一下,我们将 MyUserControl 中的 ComboBox 的 ItemsSource 属性 设置为“{Binding MyComboBoxItems}”,所以问题是,MainWindow 中定义的 属性 是如何神奇地变为可用的我的用户控件?
在 WPF 中,如果未显式设置 DataContext 值,则从父控件继承它们,并且由于我们没有为控件指定数据上下文,MyUserControl 的实例继承父控件的 DataContext window.在构造函数中,我们将 MainWindow 数据上下文设置为引用自身,因此 MyComboBoxItems 列表可用于任何子控件(及其子控件等)。
通常我们会继续为名为 ItemsSource 的用户控件添加依赖项 属性,在用户控件中我们会将组合框的 ItemsSource 属性 绑定到依赖项 属性 而不是 MyComboxItems。 MainWindow.xaml 然后会将它的集合直接绑定到用户控件上的依赖项 属性。这有助于提高用户控件的可重用性,因为它不依赖于继承数据上下文中定义的特定属性。
最后,在用户控件的自定义事件的事件处理程序中,我们获取用户选择的值并创建一个填充有文本框的 UserControl(所有这些都设置了各种属性以使项目在视觉上很有趣),我们直接将它们添加到 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; }
}
}
最后,我会考虑使用绑定到 ObservableCollection 的 ItemsControl 或 ListBox,而不是将内容粘贴到 StackPanel 中。您可以为要显示的用户控件定义一个漂亮的数据模板,也可以定义一个 DataTemplateSelector 以根据数据项中的设置使用不同的用户控件。这将允许我简单地将对在选择更改处理程序中获得的 MyComboBoxItem 的引用添加到该集合,并且绑定机制将使用我定义的数据模板自动生成一个新项目并创建必要的可视元素来显示它。
因此,考虑到所有这些,这里是完成所有这些的更改。
首先,我们修改数据项以添加颜色 属性。我们将使用 属性 来确定我们如何显示所选项目:
public class MyComboBoxItem
{
public string Color { get; set; }
public string Text { get; set; }
}
现在我们在 MainWindow.xaml.cs 中实现 INotifyPropertyChanged,让 WPF 绑定引擎在我们更改属性时更新 UI。这是事件处理程序和辅助方法 OnPropertyChanged。
我们还修改了组合框初始值设定项以添加颜色值 属性。我们将留空以供娱乐。
然后我们添加一个新的 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 派生出来的。我还想根据颜色 属性 的值在列表中使用不同的用户控件。为此,我们为 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}" />