|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionOne of the great things about WPF is that it separates a control's behaviour from its presentation. You can take any control, and by changing a template and some styles, you can make it look completely different. In this tutorial, we will go through the principles of this, and make ourselves a control template that turns a BackgroundA basic understanding of XAML and WPF is assumed, including knowledge of the different layout panels, Resources and Binding. Starting OutLet's get started by creating our tab control in XAML: <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<TabControl Name="monkey">
<TabItem Header="Mail" IsSelected="True">
<ListBox BorderThickness="0">
<ListBoxItem>Your mail here.</ListBoxItem>
</ListBox>
</TabItem>
<TabItem Header="Calendar" />
<TabItem Header="Tasks" />
</TabControl>
</Page>
As you can see, what we have is a very basic A Basic Control TemplateControl Templates are what make WPF so powerful. You can think of the control template as the entire "look" of a control. By replacing the default control template, we can completely change how it looks, while leaving the behaviour unchanged. Let's add a new one to our <Page.Resources>
<ControlTemplate x:Key="OutlookBar" TargetType="{x:Type TabControl}">
<ControlTemplate.Resources>
<SolidColorBrush x:Key="BorderBrush" Color="#6593CF" />
</ControlTemplate.Resources>
<Border BorderBrush="{StaticResource BorderBrush}" BorderThickness="1"
SnapsToDevicePixels="True" >
<DockPanel>
<StackPanel IsItemsHost="True" DockPanel.Dock="Bottom" />
<ContentPresenter Content="{TemplateBinding SelectedContent}" />
</DockPanel>
</Border>
</ControlTemplate>
</Page.Resources>
Now we can wire it up to the <TabControl Name="monkey" Template="{StaticResource OutlookBar}">
Now our
Let's look at the two key aspects of this template: <StackPanel IsItemsHost="True" DockPanel.Dock="Bottom" />
<ContentPresenter Content="{TemplateBinding SelectedContent}" />
We need to display the content of our selected tab in the content pane at the top of our Outlook Bar. Fortunately, there is a dependency property on the Adding Some StyleWe have perfected our layout, however our Outlook bar looks like a All a style does is set a group of properties on a control. They are designed so you can make a group of controls look or behave the same way, by just changing one property. Let's add some more resources that we can use for styling. <ControlTemplate.Resources>
<SolidColorBrush x:Key="CaptionBrush" Color= "#15428B" />
<SolidColorBrush x:Key="BorderBrush" Color="#6593CF" />
<LinearGradientBrush x:Key="LabelBrush" StartPoint="0, 0" EndPoint="0,1">
<GradientStop Color="#E3EFFF" Offset="0" />
<GradientStop Color="#AFD2FF" Offset="1" />
</LinearGradientBrush>
</ControlTemplate.Resources>
Now let's restyle our <Style TargetType="{x:Type TabItem}">
<Setter Property="Background" Value="{StaticResource ButtonNormalBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Background="{TemplateBinding Background}" MinHeight="32">
<Line Stroke="{StaticResource BorderBrush}" VerticalAlignment="Top"
Stretch="Fill" X2="1" SnapsToDevicePixels="True" />
<ContentPresenter Margin="5,0,5,0" TextBlock.FontFamily="Tahoma"
TextBlock.FontSize="8pt" TextBlock.FontWeight="Bold"
TextBlock.Foreground="{StaticResource CaptionBrush}"
Content="{TemplateBinding Header}" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
For our style, we specify a If we have a look at this point, the tab control will look something like this:
So at this point, our Pulling the TriggerYou can think of triggers as conditional styles. Let's have a look at a trigger: <Trigger Property="IsSelected" Value="False">
<Setter Property="TextElement.Foreground" Value="{StaticResource CaptionBrush}" />
</Trigger>
As you can see, this trigger sets the text colour whenever the You can set triggers to depend on more than one condition, using a <MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Background" Value="{StaticResource ButtonNormalBrush}" />
</MultiTrigger.Setters>
</MultiTrigger>
The conditions all need to be satisfied for a trigger to take effect, so for the one above, if the button is not selected and the mouse is not hovering over it, then the background brush is set. For our <ControlTemplate TargetType="{x:Type TabItem}">
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Background" Value="{StaticResource ButtonNormalBrush}" />
</MultiTrigger.Setters>
</MultiTrigger>
<!-- More Triggers here... -->
</ControlTemplate.Triggers>
<Grid Background="{TemplateBinding Background}"
MinHeight="32" SnapsToDevicePixels="True">
<Line Stroke="{StaticResource BorderBrush}"
VerticalAlignment="Top" Stretch="Fill" X2="1" SnapsToDevicePixels="True" />
<ContentPresenter Margin="5,0,5,0" TextBlock.FontFamily="Tahoma"
TextBlock.FontSize="8pt" TextBlock.FontWeight="Bold"
TextBlock.Foreground="{StaticResource CaptionBrush}"
Content="{TemplateBinding Header}" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
For clarity, I've not included all the triggers above. The remaining triggers are included in the source file. At this point, we're almost finished. All that's left is to add in the label that sits at the top, which turns out to be very straightforward: <Border BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" SnapsToDevicePixels="True" >
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" IsItemsHost="True" />
<!-- Top label -->
<Grid DockPanel.Dock="Top" MinHeight="28"
Background="{StaticResource ButtonNormalBrush}" SnapsToDevicePixels="True">
<TextBlock FontFamily="Tahoma" Foreground="{StaticResource CaptionBrush}"
VerticalAlignment="Center" Margin="5,0" FontSize="18" FontWeight="Bold" />
<Line Stroke="{StaticResource BorderBrush}"
VerticalAlignment="Bottom" X2="1" Stretch="Fill"/>
</Grid>
<ContentPresenter Content="{TemplateBinding SelectedContent}" />
</DockPanel>
</Border>
And with that, we're done! Where to Go From HereThe UI pedants amongst you (and I include myself in that grouping) will notice a few shortcomings in the model we've just done. I've included comments below and leave them as exercises for the interested reader.
Points of InterestShape Elements and the Layout SystemThe <Line Stroke="Navy" StrokeThickness="1" />
The layout engine was reserving the space for my line, but not drawing anything. I tried all sorts, until I read the MSDN help on shapes. In order to get any shape to use the whole space reserved for it in the layout engine, you need to set the Shapes use their own layout space, and then setting the <Line Stroke="Navy" StrokeThickness="1" Stretch="Fill" X2="1">
This drew a line between 0 and 1 in the line's layout space which the Snaps To Device PixelsYou'll notice that I have made heavy use of the History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||