WPF 模板 – 事件方面






4.69/5 (25投票s)
WPF 模板,是什么决定了事件的行为?
- 下载 ControlTemplateVsDataTemplate (无 EXE) - 33.2 KB
- 下载 ControlTemplateVsDataTemplate - 69.5 KB
- 下载示例代码 (无 EXE) - 40.2 KB
- 下载示例代码 - 120.6 KB
引言
WPF 允许我们使用模板来覆盖控件的可视化,在查看这些模板的事件方面时,是什么决定了事件的行为?
背景
WPF 允许我们使用各种方式来覆盖控件的可视化,我们可以覆盖 Control-Template, Data-Template 或创建自定义用户控件来结合这两种...
到目前为止,我遇到的文章大多详细讨论了模板的 UI 方面,但对事件的使用方式却关注较少。 在接下来的几行中,我想讨论 Control-Template 和 Data-Template 之间的事件逻辑差异。
Control-Template 与 Data-Template
虽然 Control-Templates 使用其逻辑树表示来处理事件,但 Data-Templates 使用其可视树表示来处理事件。
例如,Control-Template
<CheckBox Grid.Column="0" Grid.Row="1" x:Name="checkbox"
Unchecked="CheckBox_Unchecked" Checked="CheckBox_Checked">
<CheckBox.Template>
<ControlTemplate TargetType="CheckBox">
<StackPanel Orientation="Vertical">
<Border Margin="5" BorderThickness="1" BorderBrush="Black"
Width="20" Height="20" Background="Black"/>
<ContentPresenter/>
</StackPanel>
</ControlTemplate>
</CheckBox.Template>
</CheckBox>
后台代码如下:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
MessageBox.Show(checkbox.IsChecked.ToString());
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
MessageBox.Show(checkbox.IsChecked.ToString());
}
正如我们所看到的,尽管模板是 Border
位于 StackPanel
内部,但控件事件的行为是 Check-Box

现在,让我们看看 Data-Template 的事件行为
<ListBox Grid.Column="1" Grid.Row="1"
ItemsSource="{Binding Items}" Height="129" HorizontalAlignment="Left" Name="listBox1"
VerticalAlignment="Top" Width="145" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="2" Text="{Binding Path=Name}" />
<Button Margin="2" Width="40" Command="{Binding Path=Command}"
CommandParameter="{Binding Path=CommandParam}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
我们可以看到一个简单的 ListBox
,其 Data-Template 为 TextBlock
+ Button
Data-Template 代表某个类
public class Class1
{
public string Name { get; set; }
public ICommand Command { get; set; }
public object CommandParam { get; set; }
}
单击 ListBox
中的按钮会给我们按钮事件
区别就在这里:在覆盖 Checkbox
时,Control-Template 保留了 Check-Box 的事件行为,而覆盖 ListBox
Data-Template(即 ListBoxItem
的模板)则使 ListBoxItem
的事件行为与 Data-Template 相同,而不是 ListBoxItem
! 也就是说,事件现在由 Visual-Tree 而不是 Logical tree 触发!
当然,这有点令人困惑…一个模板覆盖了事件 (Data-Template),而另一个模板没有 (Control-Template)。
但这并不是唯一的区别;Control-Template 覆盖控件的可视化,而 Data-Template 覆盖 ItemsControl
内对象列表的可视化。
那么,如果我们想覆盖控件的可视化和事件行为,我们可以做什么呢? 例如,创建一个带有两个圆形视觉效果的按钮,但只有一个会触发点击事件
用户控件
最简单的方法就是亲力亲为,即使用用户控件
<UserControl x:Class="WpfApplication9.UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="100">
<Grid>
<WrapPanel Orientation="Horizontal">
<Ellipse Fill="Black" Width="50" Height="50" MouseDown="Ellipse_MouseDown"
/>
<Ellipse Fill="Black" Width="50" Height="50"/>
</WrapPanel>
</Grid>
</UserControl>
后台代码如下:
private void Ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Click");
}
在这里,您可以使用您喜欢的控件、您喜欢的 Control-Templates 甚至您喜欢的 Data-Templates 来控制事件的行为。
摘要
总而言之,让我们看看如何以 3 种不同的方式将事件连接到 UI 元素
UI |
Control-Template |
Data-Template |
用户控件 |
事件逻辑 |
通过逻辑树 |
通过可视树 |
通过您的逻辑 |
覆盖了... 的可视化 |
Control |
ItemsControl 中的对象列表。 |
通过您的逻辑 |
进一步讨论
为了便于讨论,让我们尝试用另一种方式理解这种差异
如果没有 Data-Template 呢? 让我们尝试使用自定义列表框的 Control-Template 来实现上面的 Data-Template 示例
首先,让我们创建一个包含 ListBox
的用户控件
<UserControl x:Class="ListBoxWithItemsControlTemplate.CustomListBox"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ListBox x:Name="listbox"/>
</Grid>
</UserControl>
用户控件可以获取一个项目列表(例如 Class1
项目),对于每个项目,我们创建一个带有 Control-Template 的 ListBoxItem,并将其添加到内部 ListBox
中
public void UpdateItems(IEnumerable items)
{
foreach (var item in items)
{
//Default Content is item.ToString():
var lbItem = new ListBoxItem() { Content = item.ToString()};
if (ListBoxItemTemplate != null)
lbItem.Template = ListBoxItemTemplate;
this.listbox.Items.Add(lbItem);
}
}
我们还需要将 ItemsSource
和 ListBoxItemTemplate
公开为依赖属性,这样我们就可以在 MainWindow
XAML 中使用它们了
/// <summary>
/// Interaction logic for CutomListBox.xaml
/// </summary>
public partial class CustomListBox : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustomListBox), new
FrameworkPropertyMetadata(null, ItemsSourcePropertyChanged));
public static readonly DependencyProperty ListBoxItemTemplateProperty =
DependencyProperty.Register("ListBoxItemTemplate", typeof(ControlTemplate), typeof(CustomListBox), new
FrameworkPropertyMetadata(null));
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)base.GetValue(ItemsSourceProperty);
}
set
{
base.SetValue(ItemsSourceProperty, value);
UpdateItems(value);
}
}
private static void ItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listbox = d as CustomListBox;
if (listbox != null)
{
listbox.ItemsSource = e.NewValue as IEnumerable;
}
}
public ControlTemplate ListBoxItemTemplate
{
get
{
return (ControlTemplate)base.GetValue(ListBoxItemTemplateProperty);
}
set
{
base.SetValue(ListBoxItemTemplateProperty, value);
}
}
public CustomListBox()
{
InitializeComponent();
}
public void UpdateItems(IEnumerable items)
{
...
}
}
让我们看看 MainWindow
XAML 中的控件
<my:CustomListBox Width="140"
ItemsSource="{Binding Items}" x:Name="customListBox1">
<my:CustomListBox.ListBoxItemTemplate>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="2" Width="40"
Background="AliceBlue" Text="{TemplateBinding Content}" />
<Button Margin="2" Width="40"
Command="{TemplateBinding Tag}"
CommandParameter="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</my:CustomListBox.ListBoxItemTemplate>
</my:CustomListBox>
我们将 ItemSource
绑定到一些 Class1
对象列表,并为列表中的每个 ListBoxItem
创建 Control-Template。
请注意:为了简化示例,我们将项目的命令存储在 ListBoxItem
标签中。
现在,让我们注意与 Data-Template 相比的变化
单击 Template 按钮时,会在 ListBoxItem
& Template Button 命令上引发 "MouseDown
" 事件
当我们使用 Data-Template 时,只有 Template Button 命令被引发。
请注意:为了完全理解细微的语法差异,请从本文顶部的链接下载代码示例。