Introduction
WPF
enables us to override visualization of controls using templates, looking on
the event aspect of these templates, what is determining the events
behavior?
Background
WPF
enables us various ways to override the visualization of controls, we can
override the Control-Template, Data-Template or create a custom user control
combining these two...
Most
articles I have encountered so far, dealt in detail with the UI aspect of the
templates, but less attention was given to the way in which events are used. In the
following lines, I want to discuss the difference in the event logic between Control-Template to Data-Template.
Control-Template Vs
Data-Template
While
Control-Templates handle events using its logical tree representation,
Data-Templates handle events using its visual tree representation.
For
example, 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>
The code behind is as follows:
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());
}
As
we can see, although the template is a Border
inside a StackPanel
, the control event
behavior is of Check-Box:
Now,
let's see the event behavior of 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>
We
can see a simple ListBox
with Data-Template of TextBlock
+ Button
:
And
the Data-Template represents some class:
public class Class1
{
public string Name { get; set; }
public ICommand Command { get; set; }
public object CommandParam { get; set; }
}
Clicking
on the button in the ListBox
gives us the button event:
Here lies the big difference: while overriding Checkbox
, the Control-Template keeps the
Check-Box event behavior, overriding the ListBox
Data-Template (i.e., the
template of the ListBoxItem
) makes the event behavior of the ListBoxItem
to be
as the Data-Template and not the ListBoxItem
! That is to say, the events are now triggered by the
Visual-Tree and not the Logical tree!
Of
course, this is somewhat confusing… one template overrides the events (Data-Template)
and one template does not (Control-Template).
But
this is not the only difference; while Control-Template overrides the
visualization of a control while Data-Template overrides the visualization of a list
of objects inside an ItemsControl
.
So
what can we do if we want to override the visualization of a control & the
event behavior? For example, creating a button with two visual circles but only
one gets the click event:
User-Control
The
simplest way to do it is to take matters into your hand, i.e. use a User-Control:
<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>
The code behind is as follows:
private void Ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Click");
}
Here,
you can control the behavior of your events using the controls you like, the
Control-Templates you like and even the Data-Templates you like.
Summary
To
sum things up, let's see how to connect events to UI elements in 3 different
ways:
UI:
|
Control-Template
|
Data-Template
|
User-Control
|
Event
logic:
|
By
Logical Tree
|
By
Visual Tree
|
By
your logic
|
Overrides
visualization of:
|
Control
|
List of objects
inside an ItemsControl.
|
By your logic
|
Farther Discussion
For the sake of discussion, let's try to understand the
difference in another way:
What if there was not such a thing as Data-Template? Let's
try to implement the Data-Template example from above using Control-Template of
a custom list-box:
First, let's create a User-Control containing a 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>
The User-Control can get a list of items (Class1
items for example), for each Item we are creating a ListBoxItem with Control-Template and add it to the inner ListBox
:
public void UpdateItems(IEnumerable items)
{
foreach (var item in items)
{
var lbItem = new ListBoxItem() { Content = item.ToString()};
if (ListBoxItemTemplate != null)
lbItem.Template = ListBoxItemTemplate;
this.listbox.Items.Add(lbItem);
}
}
We also need to expose the ItemsSource
&
ListBoxItemTemplate
as Dependency Properties so we can use them at the
MainWindow
XAML:
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)
{
...
}
}
Let's look at the control on the 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>
We
are binding the ItemSource
to some list of Class1
objects, and creating a Control-Template
for each ListBoxItem
in the list.
Please
Notice: To simplify the example, we store the item's command in the
ListBoxItem
tag.
Now,
let's notice the change comparing to Data-Template:
Each
click on the Template button raises a "MouseDown
" event at the ListBoxItem
& Template Button command:
When
we've used the Data-Template, only the Template Button Command was raised.
Please
Notice: In order to fully understand the small syntax nuance, please
download the code sample from the links at the top of this article.