65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 模板 – 事件方面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (25投票s)

2014年4月2日

CPOL

3分钟阅读

viewsIcon

27653

downloadIcon

672

WPF 模板,是什么决定了事件的行为?

引言

WPF 允许我们使用模板来覆盖控件的可视化,在查看这些模板的事件方面时,是什么决定了事件的行为?

背景

WPF 允许我们使用各种方式来覆盖控件的可视化,我们可以覆盖 Control-Template, Data-Template 或创建自定义用户控件来结合这两种...

到目前为止,我遇到的文章大多详细讨论了模板的 UI 方面,但对事件的使用方式却关注较少。 在接下来的几行中,我想讨论 Control-TemplateData-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 命令被引发。

请注意:为了完全理解细微的语法差异,请从本文顶部的链接下载代码示例。

© . All rights reserved.