Windows Presentation Foundation (WPF)
The Windows Presentation Foundation is Microsoft’s next generation UI framework to create applications with a rich user experience. It is part of the .NET framework 3.0 and higher.
WPF combines application UIs, 2D graphics, 3D graphics, documents and multimedia into one single framework. Its vector based rendering engine uses hardware acceleration of modern graphic cards. This makes the UI faster, scalable and resolution independent.
Separation of Appearance and Behavior
WPF separates the appearance of a user interface from its behavior. The appearance is generally specified in the Extensible Application Markup Language (XAML), the behavior is implemented in a managed programming language like C# or Visual Basic. The two parts are tied together by data binding, events and commands. The separation of appearance and behavior brings the following benefits:
Controls in WPF are extremely composable. You can define almost any type of controls as content of another. Although this flexibility sounds horrible to designers, it’s a very powerful feature if you use it appropriate. Put an image into a button to create an image button, or put a list of videos into a combo box to choose a video file.
Because of the strict separation of appearance and behavior you can easily change the look of a control. The concept of styles let you skin controls almost like CSS in HTML. Templates let you replace the entire appearance of a control.
The following example shows a default WPF button and a customized button.
Resolution independence
All measures in WPF are logical units - not pixels. A logical unit is a 1/96 of an inch. If you increase the resolution of your screen, the user interface stays the same size - if just gets crispier. Since WPF builds on a vector based rendering engine it's incredibly easy to build scalable user interfaces.
Today XAML is used to create user interfaces in WPF, Silver light, declare workflows in WF and for electronic paper in the XPS standard.
(Example:
WPF has some built-in markup extensions, but you can write your own, by deriving from
The first is http://schemas.microsoft.com/winfx/2006/xaml/presentation.
It is mapped to all wpf controls in
The second is http://schemas.microsoft.com/winfx/2006/xaml
It is mapped to
The mapping between an XML namespace and a CLR namespace is done by the
Open Visual Studio 2008 and choose "File", "New", "Project..." in the main menu. Choose "WPF Application" as project type.
Choose a folder for your project and give it a name. Then press "OK"
Visual Studio creates the project and automatically adds some files to the solution. A Window1.xaml and an App.xaml. The structure looks quite similar to WinForms, except that the
Open the
Select the Button and switch to the event view in the properties window (click on the little yellow lightning icon). Double-click on the "Click" event to create a method in the code behind that is called, when the user clicks on the button.
Note: If you do not find a yellow lightning icon, you need to install the Service Pack 1 for Visual Studio on your machine. Alternatively you can double click on the button in the designer to achieve the same result.
Visual Studio automatically creates a method in the code-behind file that gets called when the button is clicked.
Development Tools
Microsoft provides two development tools for WPF applications. One is Visual Studio, made for developers and the other is Expression Blend made for designers. While Visual Studio is good in code and XAML editing, it has a rare support for all the graphical stuff like gradients, template editing, animation, etc. This is the point where Expression Blend comes in. Blend covers the graphical part very well but it has (still) rare support for code and XAML editing.
So the conclusion is that you will need both of them.
Layout of Controls
Layout of controls is critical to applications usability. Arranging controls based on fixed pixel coordinates may work for a limited environment, but as soon as you want to use it on different screen resolutions or with different font size it will fail. WPF provides a rich set built-in layout panels that help you to avoid the common pitfalls.
These are the five most popular layout panels of WPF:
The StackPanel can also be oriented horizontally.
Child elements are assigned to rows and columns using the Grid.Row and Grid.Column attached properties. The following XAML uses a grid to arrange two labels, two textboxes, and a button.
The UniformGrid is useful for simple scenarios, but for the most amount of control, you are better off using the Grid.
Styles
Styles are a means to define a common look and feel to controls. They are similar to CSS style sheets for web pages, but are defined using XAML syntax. Styles are typically defined in the Resources section of a control, panel, or the parent window. They can also be collected into what are known as Resource Dictionaries, and hosted in external files.
To implement a style, follow these steps:
For example, here is a demo which defines two styles as part of a Canvas' resources collection. The style changes the default display of buttons to be 25x100, with a black background and white text. The named style is derived from the first style (style inheritance) and sets the background of the button to Dark Blue instead. The third button references the named style via the Style attribute.
Triggers
When applying a style to a control, all properties defined in setter elements are unconditionally applied to the control. If that is not the desired behavior, we can use triggers.
Triggers allow us to define several Setter elements in a style. But, contrary to the previous situation, not all these Setter elements will be applied to the controls using the particular style. The trigger includes a condition, and when that condition is true, the Setters defined in the trigger will be applied. When the condition is false, the Setters are ignored.
There are three types of triggers in WPF:
In WPF, it’s not necessary to write scripting code. Instead, property triggers can help us create the same compelling effects. Property triggers are, like all triggers defined inside a style, but when applied to a certain object, they fire when a certain property of that object itself changes. This way, one or more properties of the object can be changed.
Let’s take a look at some code examples.
<Style TargetType="{x: Type Button}">
<Setter Property="Button.Background" Value="AliceBlue" />
...
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Button.Background" Value="Yellow" />
</Trigger>
</Style.Triggers>
There are 2 important things to note. First, we did not have to write any code, it’s all handled by WPF. Secondly, there is no Setter to counteract the first one. When the condition is no longer valid, WPF reverts the control back to its previous state.
We are not limited in the number of triggers included in a Style, and each trigger can have as many Setter elements as we want, like in the next sample.
<Style TargetType="{x: Type Button}">
<Setter Property="Button.Background" Value="AliceBlue" />
<Setter Property="Button.Opacity" Value="0.5" /> ...
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Button.Opacity" Value="1"></Setter>
<Setter Property="Button.Background" Value="Green"></Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Button.Background" Value="Yellow"></Setter>
</Trigger>
</Style.Triggers>
So far we’ve had more than one trigger inside a style, but each of them only had one condition to check. What happens when the trigger should only fire when 2 or more conditions are true? For example: only change the background of a button when the mouse is over it AND the button is enabled.
For this purpose, we have so-called multi-condition property triggers.
Here’s some sample code using a multi-condition trigger.
<Page.Resources>
<Style TargetType="{x: Type Button}">
<Setter Property="Button.Background" Value="AliceBlue" />
...
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsEnabled" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Button.Background" Value="Yellow" />
</MultiTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
<StackPanel>
<Button x: Name="WelcomeButton” Content="Welcome user!" ></Button>
</StackPanel>
We used a MultiTrigger element here, in which we specify both the conditions that have to be checked. If needed, more can be added in an analogue manner. One or more normal Setters are applied when all of these conditions are true.
As can be seen, property triggers are a very easy-to-use way to change our application’s interface based on one or more conditions and they help increase the user experience with effects which in the past, cost a lot more effort to create.
Data triggers
As we’ve seen, property triggers are used to check on WPF dependency properties, like the IsMouseOver property. However, there will be times when we have to check the value of a property of a non-visual .NET object, like a self-created User object for example. For this purpose, data triggers come in handy.
In the following sample, we’ll be creating and filling a list box. Each item represents a user, an instance of the User class. Based on the value of the Role property, the user will receive another color. To accomplish this, I’ll use a style with a data trigger that fires whenever the specified condition is true.
Let’s create a User class, with 2 string properties: name and role.
namespace Demo {
public class User {
private string name;
private string role;
public User(string name, string role) {
this.name = name;
this.role = role;
}
public string Name {
get {return name ;}
set {name = value;}
}
public string Role {
get { return role; }
set { role = value; }
}
}
}
Note the namespace Demo; we’ll need it later on.
Within the same namespace, let’s create a class Users.
public class Users : ObservableCollection<User>
{
public Users()
{
this.Add(new User("Gill Cleeren", "Admin"));
this.Add(new User("Steve Smith", "Contributor"));
this.Add(new User("John Miller", "User"));
}
}
This class inherits from ObservableCollection, which represents a dynamic data collection. ObservableCollection provides methods to notify when items are added to or removed from the collection. Since we provide User as type parameter, only User instances can be added to the collection.
Now, let’s get back to the XAML-code. As said earlier, we want to display the users in a list box.
<Page x: Class="Demo.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1"
Xmlns: clr="clr-namespace: Demo"
>
<Page.Resources>
<clr:Users x:Key="myUsers" />
<DataTemplate DataType="{x:Type clr:User}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
...
</Page.Resources>
<StackPanel>
<ListBox Width="200"
ItemsSource="{Binding Source={StaticResource myUsers}}" />
</StackPanel>
</Page>
Since we’ll be working with the Users collection in the XAML code, it has to be made available in that context. Therefore, we have to create a mapping between a CLR namespace and a namespace in WPF, using the following syntax.
Within the resources of the Page in the XAML code, we can now use the clr-prefix to have WPF create an instance of the Users class, using the default constructor. The value myUsers, specified in the x:Key attribute, can be used to access the instance.
The DataTemplate is used to tell WPF how it should display a non-visual object like a User. In this case, a TextBlock containing the name will be used.
In the StackPanel, I declare a ListBox. This ListBox will bind its ItemsSource to the Users instance, myUsers, which is a collection of User instances.
Now, let’s add the trigger functionality.
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Role}" Value="Admin">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
Here a data trigger is used. The condition whether the trigger should fire is based on the value of the Role property of the specified User object: if it has the value “Admin”, the trigger’s condition will become true, and the property declared within the Setter will be applied.
The TargetType of the Style is ListBoxItem. Whenever the Role property of the User instance to which a ListBoxItem is bound, has the value “Admin”, the foreground will be set to red.
And this is the result, with the first user colored red:
Event triggers
To finish our trigger-tour, we have to make a stop at the event triggers. Event triggers are used to watch for events to happen, like a click-event or a mouse-over event. They are used in combination with animation: whenever the event is raised, the event trigger fires and starts an animation action.
In the sample code, we’ll use an event trigger to react to the mouse-over event for a button.
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="200" />
<Setter Property="Height" Value="100" />
<Setter Property="Margin" Value="20" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Style.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="300" Duration="0:0:3"
Storyboard.TargetProperty="(Button.Width)" />
<DoubleAnimation To="200" Duration="0:0:3"
Storyboard.TargetProperty="(Button.Height)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:3"
Storyboard.TargetProperty="(Button.Width)" />
<DoubleAnimation Duration="0:0:3"
Storyboard.TargetProperty="(Button.Height)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<Button x:Name="GrowButton" Content="Hello MSDN" />
</StackPanel>
The style now includes 2 event triggers. The first one will react to the mouse starting to hover over the Button; the second one will react to the mouse leaving the button surface.
When either one of the events is raised, the actions specified for the trigger that reacts to the event, will be executed.
The result is a button that expands both in width and height when hovering over it.
Note
that we also have to include code that will return the button to its
original state here, which wasn’t necessary for the other types of
triggers. This is due to the fact that event triggers have no concept of
termination of state, so the property won’t be changed back to the
state it was in before the trigger fired.
As you can see, using triggers is fairly easy and they make your applications much more interactive, without having to write much code.
The Windows Presentation Foundation is Microsoft’s next generation UI framework to create applications with a rich user experience. It is part of the .NET framework 3.0 and higher.
WPF combines application UIs, 2D graphics, 3D graphics, documents and multimedia into one single framework. Its vector based rendering engine uses hardware acceleration of modern graphic cards. This makes the UI faster, scalable and resolution independent.
Separation of Appearance and Behavior
WPF separates the appearance of a user interface from its behavior. The appearance is generally specified in the Extensible Application Markup Language (XAML), the behavior is implemented in a managed programming language like C# or Visual Basic. The two parts are tied together by data binding, events and commands. The separation of appearance and behavior brings the following benefits:
- Appearance and behavior are loosely coupled
- Designers and developers can work on separate models.
- Graphical design tools can work on simple XML documents instead of parsing code.
Controls in WPF are extremely composable. You can define almost any type of controls as content of another. Although this flexibility sounds horrible to designers, it’s a very powerful feature if you use it appropriate. Put an image into a button to create an image button, or put a list of videos into a combo box to choose a video file.
<Button>
<StackPanel Orientation="Horizontal">
<Image Source="speaker.png" Stretch="Uniform"/>
<TextBlock Text="Play Sound" />
</StackPanel>
</Button>
Highly customizableBecause of the strict separation of appearance and behavior you can easily change the look of a control. The concept of styles let you skin controls almost like CSS in HTML. Templates let you replace the entire appearance of a control.
The following example shows a default WPF button and a customized button.
Resolution independence
All measures in WPF are logical units - not pixels. A logical unit is a 1/96 of an inch. If you increase the resolution of your screen, the user interface stays the same size - if just gets crispier. Since WPF builds on a vector based rendering engine it's incredibly easy to build scalable user interfaces.
Introduction to XAML
XAML stands for Extensible Application Markup Language. It’s a simple language based on XML to create and initialize .NET objects with hierarchical relations. Although it was originally invented for WPF it can be used to create any kind of object trees.Today XAML is used to create user interfaces in WPF, Silver light, declare workflows in WF and for electronic paper in the XPS standard.
Advantages of XAML
All you can do in XAML can also be done in code. XAML is just another way to create and initialize objects. You can use WPF without using XAML. It's up to you if you want to declare it in XAML or write it in code. Declare your UI in XAML has some advantages:- XAML code is short and clear to read
- Separation of designer code and logic
- Graphical design tools like Expression Blend require XAML as source.
- The separation of XAML and UI logic allows it to clearly separate the roles of designer and developer.
XAML vs. Code
As an example we build a simple StackPanel with a textblock and a button in XAML and compare it to the same code in C#.
<StackPanel>
<TextBlock Margin="20">Welcome to the World of XAML</TextBlock>
<Button Margin="10" HorizontalAlignment="Right">OK</Button>
</StackPanel>
The same expressed in C# will look like this:
// Create the StackPanel
StackPanel stackPanel = new StackPanel();
this.Content = stackPanel;
// Create the TextBlock
TextBlock textBlock = new TextBlock();
textBlock.Margin = new Thickness(10);
textBlock.Text = "Welcome to the World of XAML";
stackPanel.Children.Add (textBlock);
// Create the Button
Button button = new Button ();
button.Margin= new Thickness (20);
button.Content = "OK";
stackPanel.Children.Add(button);
As you can see is the XAML version much shorter and clearer to read. And that's the power of XAMLs expressiveness.Properties as Elements
Properties are normally written inline as known from XML<Button Content="OK" />
.
But what if we want to put a more complex object as content like an
image that has properties itself or maybe a whole grid panel? .To do
that we can use the property element syntax. This allows us to extract
the property as an own child element.
<Button>
<Button.Content>
<Image Source="Images/OK.png" Width="50" Height="50" />
</Button.Content>
</Button>
Implicit Type conversion
A very powerful construct of WPF are implicit type converters. They do their work silently in the background. When you declare a Border Brush, the word "Blue" is only a string. The implicitBrush Converter
makes a System.Windows.Media.Brushes.Blue
out of it. The same regards to the border thickness that is being converted implicit into a Thickness
object. WPF includes a lot of type converters for built-in classes, but
you can also write type converters for your own classes.
<Border BorderBrush="Blue" BorderThickness="0, 10">
</Border>
Markup Extensions
Markup extensions are dynamic placeholders for attribute values in XAML. They resolve the value of a property at runtime. Markup extensions are surrounded by curly braces(Example:
Background="{StaticResourceNormalBackgroundBrush}"
).WPF has some built-in markup extensions, but you can write your own, by deriving from
Markup Extension
. These are the built-in markup extensions:
· Binding
To bind the values of two properties together.
To bind the values of two properties together.
· Static Resource
One time lookup of a resource entry
One time lookup of a resource entry
· Dynamic Resource
Auto updating lookup of a resource entry
Auto updating lookup of a resource entry
· Template Binding
To bind a property of a control template to a dependency property of the control
To bind a property of a control template to a dependency property of the control
· x: Static
Resolve the value of a static property.
Resolve the value of a static property.
· x:Null
Return
The
first identifier within a pair of curly braces is the name of the
extension. All preceding identifiers are named parameters in the form of
Property=Value. The following example shows a label whose Return
null
Content
is bound to the Text
of the textbox. When you type a text into the text box, the text
property changes and the binding markup extension automatically updates
the content of the label.
<TextBox x:Name="textBox"/>
<Label Content="{Binding Text, ElementName=textBox}"/>
Namespaces
At the beginning of every XAML file you need to include two namespaces.The first is http://schemas.microsoft.com/winfx/2006/xaml/presentation.
It is mapped to all wpf controls in
System.Windows.Controls
. The second is http://schemas.microsoft.com/winfx/2006/xaml
It is mapped to
System.Windows.Markup
that defines the XAML keywords.The mapping between an XML namespace and a CLR namespace is done by the
XmlnsDefinition
attribute at assembly level. You can also directly include a CLR namespace in XAML by using the clr-namespace:
prefix.
<Window xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”>
</Window>Create simple WPF application
Open Visual Studio 2008 and choose "File", "New", "Project..." in the main menu. Choose "WPF Application" as project type.
Choose a folder for your project and give it a name. Then press "OK"
Visual Studio creates the project and automatically adds some files to the solution. A Window1.xaml and an App.xaml. The structure looks quite similar to WinForms, except that the
Window1.designer.cs
file is no longer code but it's now declared in XAML as Window1.xaml
Open the
Window1.xaml
file in the WPF designer and drag a Button and a TextBox from the toolbox to the WindowSelect the Button and switch to the event view in the properties window (click on the little yellow lightning icon). Double-click on the "Click" event to create a method in the code behind that is called, when the user clicks on the button.
Note: If you do not find a yellow lightning icon, you need to install the Service Pack 1 for Visual Studio on your machine. Alternatively you can double click on the button in the designer to achieve the same result.
Visual Studio automatically creates a method in the code-behind file that gets called when the button is clicked.
private void button1_Click(object sender, RoutedEventArgs e)
{
textBox1.Text = "Hello WPF!";
}
The textbox has automatically become assigned the name textBox1
by the WPF designer. Set text Text to "Hello WPF!" when the button gets
clicked and we are done! Start the application by hit [F5] on your
keyboard.Development Tools
Microsoft provides two development tools for WPF applications. One is Visual Studio, made for developers and the other is Expression Blend made for designers. While Visual Studio is good in code and XAML editing, it has a rare support for all the graphical stuff like gradients, template editing, animation, etc. This is the point where Expression Blend comes in. Blend covers the graphical part very well but it has (still) rare support for code and XAML editing.
So the conclusion is that you will need both of them.
Layout of Controls
Layout of controls is critical to applications usability. Arranging controls based on fixed pixel coordinates may work for a limited environment, but as soon as you want to use it on different screen resolutions or with different font size it will fail. WPF provides a rich set built-in layout panels that help you to avoid the common pitfalls.
These are the five most popular layout panels of WPF:
· Canvas - for specific (X,Y) positioning
· Stack Panel - for stacking elements horizontally or vertically
· Wrap Panel - automatically handles wrapping elements to a new row as needed
· Dock Panel - for familiar docking functionality
· Grid - for a row and column based layout
· Uniform Grid - a specialized form of Grid where all cells are the same size
· Each
one is described below, along with a screenshot. I've placed each panel
within a group box (not shown in the XAML) in order to demonstrate the
boundaries of the panel.
Canvas
The Canvas panel is used for absolute positioning by specifying pixels relative to the edges of the canvas, In the example below, notice the Canvas.Left and Canvas.Top attached properties on the Textboxes and the and Button.<Canvas Width="Auto" Height="Auto">
<TextBox Width="159" Height="26" Text="Name" Canvas.Left="36" Canvas.Top="12"/>
<TextBox Width="159" Height="26" Text="Password" Canvas.Left="36" Canvas.Top="53"/>
<Button Width="159" Height="23" Content="Submit" Canvas.Left="36" Canvas.Top="101"/>
</Canvas>
StackPanel
The StackPanel, as the name implies, arranges content either horizontally or vertically. Vertical is the default, but this can be changed using the Orientation property. Content is automatically stretched based on the orientation (see screenshot below), and this can be controlled by changing the HorizontalAlignment or VerticalAlignment properties.<StackPanel Width="Auto" Height="Auto">
<Button Content="Button" />
<Button Content="Button" HorizontalAlignment="Left" />
<Button Content="Button" HorizontalAlignment="Center" />
<Button Content="Button" HorizontalAlignment="Right" />
</StackPanel>
The StackPanel can also be oriented horizontally.
<StackPanel Width="Auto" Height="Auto" Orientation="Horizontal">
<Button Content="Button" />
<Button Content="Button" VerticalAlignment="Top" />
<Button Content="Button" VerticalAlignment="Center" />
<Button Content="Button" VerticalAlignment="Bottom" />
</StackPanel>
WrapPanel
The WrapPanel is similar to the StackPanel in that it arranges item sequentially. The WrapPanel will wrap content based on the available space in the panel. Like the StackPanel, the Orientation property controls the primary direction of the layout.<WrapPanel Width="Auto" Height="Auto">
<Button Content="Button"/><Button Content="Button"/>
<Button Content="Button"/><Button Content="Button"/>
<Button Content="Button"/><Button Content="Button"/>
<Button Content="Button"/><Button Content="Button"/>
</WrapPanel>
DockPanel
The DockPanel is used to anchor elements to the edges of the container, and is a good choice to set up the overall structure of the application UI. Elements are docked using the DockPanel.Dock attached property. The order that elements are docked determines the layout. Here is an example that sets up a typical navigation system: a top menu, a bottom status bar, left and right navigational or informational panes, and a center area that fills the remaining space.<DockPanel HorizontalAlignment="Stretch" Margin="0, 0, 0, 0" Width="Auto">
<! -- DOCKED ON BOTTOM TO FILL ENTIRE HORIZONTAL SPACE -->
<StatusBar Width="Auto" Height="25" Background="#FF451B1B" DockPanel.Dock="Bottom">
<TextBlock Width="Auto" Height="Auto" Foreground="#FFFEFEFE" Text="Bottom Content"/>
</StatusBar>
<! -- DOCKED ON TOP TO FILL ENTIRE HORIZONTAL SPACE -->
<Menu Width="Auto" Height="25" DockPanel.Dock="Top">
<MenuItem Header="Top Content"/>
</Menu>
<! -- DOCKED TO LEFT AND RIGHT TO SIMULATE NAVIGATION PANES -->
<GroupBox DockPanel.Dock="Left" Width="100" Height="Auto" Header="Left Content"/>
<GroupBox DockPanel.Dock="Right" Width="100" Height="Auto" Header="Right Content" />
<! -- FINAL ELEMENT FILLS REMAINING SPACE -->
<Canvas Width="Auto" Height="Auto">
<TextBlock Width="Auto" Height="Auto" Text="Center Content" TextWrapping="Wrap"
Canvas.Left="85" Canvas.Top="51"/>
</Canvas></DockPanel>
Grid
The Grid is used to create a table-style layout of rows and columns. If you've ever done any web programming, you'll understand the versatility of a table in organizing the layout. Columns and rows are defined using two property elements: Grid.ColumnDefinitions and Grid.RowDefinitions. The Grid supports column and row spanning, and either absolute, proportional, or auto sizing.Child elements are assigned to rows and columns using the Grid.Row and Grid.Column attached properties. The following XAML uses a grid to arrange two labels, two textboxes, and a button.
<Grid Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" />
<TextBox Grid.Row="0" Grid.Column="1" VerticalAlignment="Top" Height="25" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Password:" />
<TextBox Grid.Row="1" Grid.Column="1" VerticalAlignment="Top" Height="25" />
<Button Grid.ColumnSpan="2" Grid.Row="2" HorizontalAlignment="Center"
VerticalAlignment="Center" Width="100" Height="25" Content="Submit" />
</Grid>
UniformGrid
The UniformGrid is a limited version of the grid where all rows and columns are the same size and where a cell can only hold one control (determined by the grid). Because it's unnecessary to declare each row and column, the UniformGrid contains two properties, Rows and Columns, for setting the number of rows and columns. Controls are added to the grid in the order that they are declared.The UniformGrid is useful for simple scenarios, but for the most amount of control, you are better off using the Grid.
<UniformGrid Rows="5" Columns="1">
<TextBlock Text="Name:" />
<TextBox VerticalAlignment="Top" />
<TextBlock Text="Password:" />
<TextBox VerticalAlignment="Top" />
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Content="Submit" />
</UniformGrid>
Styles
Styles are a means to define a common look and feel to controls. They are similar to CSS style sheets for web pages, but are defined using XAML syntax. Styles are typically defined in the Resources section of a control, panel, or the parent window. They can also be collected into what are known as Resource Dictionaries, and hosted in external files.
To implement a style, follow these steps:
1. Create a <style> within a Resources collection and give it a key
2. Create <setter> elements within the style to set properties to specific values
3. Use the style from an element using the Style attribute
For example, here is a demo which defines two styles as part of a Canvas' resources collection. The style changes the default display of buttons to be 25x100, with a black background and white text. The named style is derived from the first style (style inheritance) and sets the background of the button to Dark Blue instead. The third button references the named style via the Style attribute.
<Canvas>
<Canvas. Resources>
<! -- No x: Key specified, so this becomes the default for buttons -->
<Style TargetType="{x: Type Button}">
<Setter Property="Background" Value="Black" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Height" Value="25" />
<Setter Property="Width" Value="100" />
</Style>
<! -- A style with a key must be referenced explicitly using the Style attribute -->
<! -- Note also that the BasedOn attribute can be used for style inheritance -->
<Style x: Key="SpecificStyle" TargetType="{x: Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="DarkBlue" />
</Style>
</Canvas.Resources>
<Button Canvas.Left="10" Canvas.Top="10">Button 1</Button>
<Button Canvas.Left="10" Canvas.Top="44">Button 2</Button>
<Button Style="{StaticResource SpecificStyle}" Canvas.Left="10" Canvas.Top="77">Button 3</Button>
</Canvas>
Effects of TargetType and Key
When creating a style, including or omitting the TargetType and Key have the following effects:
· TargetType only - style is automatically applied as the default for all elements of that type within scope
· Key
only - style can be applied to different elements. Properties not on
the element are ignored. The Setter properties must include "Control."
(Eg. Property="Control.Background")
· Key and TargetType - style can be applied to the target type via explicit use of the Style attribute on the element
Triggers
When applying a style to a control, all properties defined in setter elements are unconditionally applied to the control. If that is not the desired behavior, we can use triggers.
Triggers allow us to define several Setter elements in a style. But, contrary to the previous situation, not all these Setter elements will be applied to the controls using the particular style. The trigger includes a condition, and when that condition is true, the Setters defined in the trigger will be applied. When the condition is false, the Setters are ignored.
There are three types of triggers in WPF:
1. Property Triggers - run when the value of a dependency property changes
2. Data Triggers - run when the value of any .NET property changes, using data binding
3. Event Triggers - run when a routed event occurs
Property triggers
Modern applications make intensive use of interactive UI elements: highlighting buttons when the mouse hovers over them, rollover images. Most of the time, these effects are achieved through the use of some scripting code, like JavaScript in the case of web applications.In WPF, it’s not necessary to write scripting code. Instead, property triggers can help us create the same compelling effects. Property triggers are, like all triggers defined inside a style, but when applied to a certain object, they fire when a certain property of that object itself changes. This way, one or more properties of the object can be changed.
Let’s take a look at some code examples.
<Style TargetType="{x: Type Button}">
<Setter Property="Button.Background" Value="AliceBlue" />
...
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Button.Background" Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>In this style, we have added the Style.Triggers element, which contains one Setter element, which is not applied by default to the control. The property being checked is the IsMouseOver, and when true, the trigger fires and properties defined in its Setters are applied. In this case, this results in a button that changes color whenever the mouse hovers over it.
There are 2 important things to note. First, we did not have to write any code, it’s all handled by WPF. Secondly, there is no Setter to counteract the first one. When the condition is no longer valid, WPF reverts the control back to its previous state.
We are not limited in the number of triggers included in a Style, and each trigger can have as many Setter elements as we want, like in the next sample.
<Style TargetType="{x: Type Button}">
<Setter Property="Button.Background" Value="AliceBlue" />
<Setter Property="Button.Opacity" Value="0.5" /> ...
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Button.Opacity" Value="1"></Setter>
<Setter Property="Button.Background" Value="Green"></Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Button.Background" Value="Yellow"></Setter>
</Trigger>
</Style.Triggers>
</Style>Here, we now have 2 triggers, one of which contains 2 Setter elements. The conditions of both triggers are checked and when true, the properties are applied to the control.
So far we’ve had more than one trigger inside a style, but each of them only had one condition to check. What happens when the trigger should only fire when 2 or more conditions are true? For example: only change the background of a button when the mouse is over it AND the button is enabled.
For this purpose, we have so-called multi-condition property triggers.
Here’s some sample code using a multi-condition trigger.
<Page.Resources>
<Style TargetType="{x: Type Button}">
<Setter Property="Button.Background" Value="AliceBlue" />
...
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsEnabled" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Button.Background" Value="Yellow" />
</MultiTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
<StackPanel>
<Button x: Name="WelcomeButton” Content="Welcome user!" ></Button>
</StackPanel>
We used a MultiTrigger element here, in which we specify both the conditions that have to be checked. If needed, more can be added in an analogue manner. One or more normal Setters are applied when all of these conditions are true.
As can be seen, property triggers are a very easy-to-use way to change our application’s interface based on one or more conditions and they help increase the user experience with effects which in the past, cost a lot more effort to create.
Data triggers
As we’ve seen, property triggers are used to check on WPF dependency properties, like the IsMouseOver property. However, there will be times when we have to check the value of a property of a non-visual .NET object, like a self-created User object for example. For this purpose, data triggers come in handy.
In the following sample, we’ll be creating and filling a list box. Each item represents a user, an instance of the User class. Based on the value of the Role property, the user will receive another color. To accomplish this, I’ll use a style with a data trigger that fires whenever the specified condition is true.
Let’s create a User class, with 2 string properties: name and role.
namespace Demo {
public class User {
private string name;
private string role;
public User(string name, string role) {
this.name = name;
this.role = role;
}
public string Name {
get {return name ;}
set {name = value;}
}
public string Role {
get { return role; }
set { role = value; }
}
}
}
Note the namespace Demo; we’ll need it later on.
Within the same namespace, let’s create a class Users.
public class Users : ObservableCollection<User>
{
public Users()
{
this.Add(new User("Gill Cleeren", "Admin"));
this.Add(new User("Steve Smith", "Contributor"));
this.Add(new User("John Miller", "User"));
}
}
This class inherits from ObservableCollection, which represents a dynamic data collection. ObservableCollection provides methods to notify when items are added to or removed from the collection. Since we provide User as type parameter, only User instances can be added to the collection.
Now, let’s get back to the XAML-code. As said earlier, we want to display the users in a list box.
<Page x: Class="Demo.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1"
Xmlns: clr="clr-namespace: Demo"
>
<Page.Resources>
<clr:Users x:Key="myUsers" />
<DataTemplate DataType="{x:Type clr:User}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
...
</Page.Resources>
<StackPanel>
<ListBox Width="200"
ItemsSource="{Binding Source={StaticResource myUsers}}" />
</StackPanel>
</Page>
Since we’ll be working with the Users collection in the XAML code, it has to be made available in that context. Therefore, we have to create a mapping between a CLR namespace and a namespace in WPF, using the following syntax.
Xmlns: clr="clr-namespace: Demo"
The namespace Demo is mapped to a namespace in WPF, which is called clr. Note that the name clr is not obligatory. Within the resources of the Page in the XAML code, we can now use the clr-prefix to have WPF create an instance of the Users class, using the default constructor. The value myUsers, specified in the x:Key attribute, can be used to access the instance.
The DataTemplate is used to tell WPF how it should display a non-visual object like a User. In this case, a TextBlock containing the name will be used.
In the StackPanel, I declare a ListBox. This ListBox will bind its ItemsSource to the Users instance, myUsers, which is a collection of User instances.
Now, let’s add the trigger functionality.
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Role}" Value="Admin">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
Here a data trigger is used. The condition whether the trigger should fire is based on the value of the Role property of the specified User object: if it has the value “Admin”, the trigger’s condition will become true, and the property declared within the Setter will be applied.
The TargetType of the Style is ListBoxItem. Whenever the Role property of the User instance to which a ListBoxItem is bound, has the value “Admin”, the foreground will be set to red.
And this is the result, with the first user colored red:
Event triggers
To finish our trigger-tour, we have to make a stop at the event triggers. Event triggers are used to watch for events to happen, like a click-event or a mouse-over event. They are used in combination with animation: whenever the event is raised, the event trigger fires and starts an animation action.
In the sample code, we’ll use an event trigger to react to the mouse-over event for a button.
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="200" />
<Setter Property="Height" Value="100" />
<Setter Property="Margin" Value="20" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Style.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="300" Duration="0:0:3"
Storyboard.TargetProperty="(Button.Width)" />
<DoubleAnimation To="200" Duration="0:0:3"
Storyboard.TargetProperty="(Button.Height)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:3"
Storyboard.TargetProperty="(Button.Width)" />
<DoubleAnimation Duration="0:0:3"
Storyboard.TargetProperty="(Button.Height)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<Button x:Name="GrowButton" Content="Hello MSDN" />
</StackPanel>
The style now includes 2 event triggers. The first one will react to the mouse starting to hover over the Button; the second one will react to the mouse leaving the button surface.
When either one of the events is raised, the actions specified for the trigger that reacts to the event, will be executed.
The result is a button that expands both in width and height when hovering over it.
As you can see, using triggers is fairly easy and they make your applications much more interactive, without having to write much code.
No comments:
Post a Comment