WPF Tutorial
WPF Tutorial
WPF Tutorial
Page 1 of 359
wpf-tutorial.com
Page 2 of 359
wpf-tutorial.com
wpf-tutorial.com
Page 4 of 359
wpf-tutorial.com
Page 5 of 359
wpf-tutorial.com
<Window x:Class="WpfApplication1.MainWindow"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
</Grid>
</Window>
This is the base XAML that Visual Studio creates for our window, all parts of it explained in the chapters on
XAML and "The Window". You can actually run the application now (select Debug -> Start debugging or
press F5) to see the empty window that our application currently consists of, but now it's time to get our
message on the screen. We'll do it by adding a TextBlock control to the Grid panel, with our
aforementioned message as the content:
<Window x:Class="WpfApplication1.MainWindow"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
Page 6 of 359
wpf-tutorial.com
<Grid>
<TextBlock HorizontalAlignment="Center" VerticalAlignment
="Center" FontSize="72">
Hello, WPF!
</TextBlock>
</Grid>
</Window>
Try running the application now (select Debug -> Start debugging or press F5) and see the beautiful result
of your hard work - your first WPF application:
You will notice that we used three different attributes on the TextBlock to get a custom alignment (in the
middle of the window), as well the FontSize property to get bigger text. All of these concepts will be treated
in later articles.
Congratulations on making it this far. Now go read the rest of the tutorial and soon you will master WPF!
Page 7 of 359
wpf-tutorial.com
1.3. XAML
1.3.1. What is XAML?
XAML, which stands for eXtensible Application Markup Language, is Microsoft's variant of XML for
describing a GUI. In previous GUI frameworks, like WinForms, a GUI was created in the same language
that you would use for interacting with the GUI, e.g. C# or VB.NET and usually maintained by the designer
(e.g. Visual Studio), but with XAML, Microsoft is going another way. Much like with HTML, you are able to
easily write and edit your GUI.
This is not really a XAML tutorial, but I will briefly tell you about how you use it, because it's such an
essential part of WPF. Whether you're creating a Window or a Page, it will consist of a XAML document
and a CodeBehind file, which together creates the Window/Page. The XAML file describes the interface
with all its elements, while the CodeBehind handles all the events and has access to manipulate with the
XAML controls.
In the next chapters, we will have a look at how XAML works and how you use it to create your interface.
Page 8 of 359
wpf-tutorial.com
<Button>
XAML tags has to be ended, either by writing the end tag or by putting a forward slash at the end of the
start tag:
<Button></Button>
Or
<Button />
A lot of controls allow you to put content between the start and end tags, which is then the content of the
control. For instance, the Button control allows you to specify the text shown on it between the start and
end tags:
<Button>A button</Button>
HTML is not case-sensitive, but XAML is, because the control name has to correspond to a type in the
.NET framework. The same goes for attribute names, which corresponds to the properties of the control.
Here's a button where we define a couple of properties by adding attributes to the tag:
<Button>
<Button.FontWeight>Bold</Button.FontWeight>
<Button.Content>A button</Button.Content>
</Button>
The result is exactly the same as above, so in this case, it's all about syntax and nothing else. However, a
lot of controls allow content other than text, for instance other controls. Here's an example where we have
text in different colors on the same button by using several TextBlock controls inside of the Button:
<Button>
Page 9 of 359
wpf-tutorial.com
<Button.FontWeight>Bold</Button.FontWeight>
<Button.Content>
<WrapPanel>
<TextBlock Foreground="Blue">Multi</TextBlock>
<TextBlock Foreground="Red">Color</TextBlock>
<TextBlock>Button</TextBlock>
</WrapPanel>
</Button.Content>
</Button>
The Content property only allows for a single child element, so we use a WrapPanel to contain the
differently colored blocks of text. Panels, like the WrapPanel, plays an important role in WPF and we will
discuss them in much more details later on - for now, just consider them as containers for other controls.
The exact same result can be accomplished with the following markup, which is simply another way of
writing the same:
<Button FontWeight="Bold">
<WrapPanel>
<TextBlock Foreground="Blue">Multi</TextBlock>
<TextBlock Foreground="Red">Color</TextBlock>
<TextBlock>Button</TextBlock>
</WrapPanel>
</Button>
1.3.2.1. Code vs. XAML
Hopefully the above examples show you that XAML is pretty easy to write, but with a lot of different ways of
doing it, and if you think that the above example is a lot of markup to get a button with text in different
colors, then try comparing it to doing the exact same thing in C#:
wpf-tutorial.com
pnl.Children.Add(txt);
txt = new TextBlock();
txt.Text = "Button";
pnl.Children.Add(txt);
btn.Content = pnl;
pnlMain.Children.Add(btn);
Of course the above example could be written less explicitly and using more syntactical sugar, but I think
the point still stands: XAML is pretty short and concise for describing interfaces.
Page 11 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.XAML.EventsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EventsSample" Height="300" Width="300">
<Grid Name="pnlMainGrid" MouseUp="pnlMainGrid_MouseUp" Background
="LightBlue">
</Grid>
</Window>
Notice how we have subscribed to the MouseUp event of the Grid by writing a method name. This method
needs to be defined in code-behind, using the correct event signature. In this case it should look like this:
wpf-tutorial.com
When you select <New Event Handler> Visual Studio will generate an appropriate event handler in
your Code-behind file. It will be named <control name>_<event name>, in our case
pnlMainGrid_MouseDown. Right-click in the event name and select Navigate to Event Handler and VS
will take you right to it.
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfTutorialSamples.XAML
{
public partial class EventsSample : Window
{
public EventsSample()
{
InitializeComponent();
pnlMainGrid.MouseUp += new
MouseButtonEventHandler(pnlMainGrid_MouseUp);
}
Page 13 of 359
wpf-tutorial.com
Simply press the [Tab] key twice to have Visual Studio generate the correct event handler for you, right
below the current method, ready for imeplentation. When you subscribe to the events like this, you don't
need to do it in XAML.
Page 14 of 359
wpf-tutorial.com
Page 15 of 359
wpf-tutorial.com
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
</Grid>
</Window>
The x:class attribute tells the XAML file which class to use, in this case Window1, which Visual Studio has
created for us as well. You will find it in the project tree in VS, as a child node of the XAML file. By default, it
looks something like this:
using System;
using System.Windows;
using System.Windows.Controls;
//more using statements
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
}
As you can see, the Window1 class is definied as partial, because it's being combined with your XAML file
in runtime to give you the full window. This is actually what the call to InitializeComponent() does, which is
why it's required to get a full functioning window up and running.
Page 16 of 359
wpf-tutorial.com
If we go back to the XAML file, you will notice a couple of other interesting attributes on the Window
element, like Title, which defines the title of the window (shown in the title bar) as well as the starting width
and height. There are also a couple of namespace definitions, which we will talk about in the XAML
chapters.
You will also notice that Visual Studio has created a Grid control for us inside the Window. The Grid is one
of the WPF panels, and while it could be any panel or control, the Window can only have ONE child control,
so a Panel, which in turn can contain multiple child controls, is usually a good choice. Later in this tutorial,
we will have a much closer look into the different types of panels that you can use, as they are very
important in WPF.
Page 17 of 359
wpf-tutorial.com
There are lots of other attributes though, so have a look for yourself and then move on to the next chapter.
Page 18 of 359
wpf-tutorial.com
<Application x:Class="WpfTutorialSamples.App"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
The main thing to notice here is the StartupUri property. This is actually the part that instructs which
Window or Page to start up when the application is launched. In this case, MainWindow.xaml will be
started, but if you would like to use another window as the starting point, you can simply change this.
In some situations, you want more control over how and when the first window is displayed. In that case,
you can remove the StartupUri property and value and then do it all from Code-Behind instead. This will be
demonstrated below.
using System;
Page 19 of 359
wpf-tutorial.com
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples
{
public partial class App : Application
{
}
}
You will see how this class extends the Application class, allowing us to do stuff on the application level.
For instance, you can subscribe to the Startup event, where you can manually create your starting window.
Here's an example:
<Application x:Class="WpfTutorialSamples.App"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
<Application.Resources></Application.Resources>
</Application>
Notice how the StartupUri has been replaced with a subscription to the Startup event (subscribing to events
through XAML is explained in another chapter). In Code-Behind, you can use the event like this:
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples
{
public partial class App : Application
{
private void Application_Startup(object sender,
StartupEventArgs e)
{
// Create the startup window
MainWindow wnd = new MainWindow();
// Do stuff here, e.g. to the window
wnd.Title = "Something else";
Page 20 of 359
wpf-tutorial.com
Page 21 of 359
wpf-tutorial.com
<Application x:Class="WpfTutorialSamples.App"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
<Application.Resources></Application.Resources>
</Application>
All we do here is to subscribe to the Startup event, replacing the StartupUri property. The event is then
implemented in App.xaml.cs:
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples
{
public partial class App : Application
{
private void Application_Startup(object sender,
StartupEventArgs e)
{
MainWindow wnd = new MainWindow();
if(e.Args.Length == 1)
Page 22 of 359
wpf-tutorial.com
Try running the application and you will see it respond to your parameter. Of course, the message isn't
terribly useful. Instead you might want to either pass it to the constructor of your main window or call a
public open method on it, like this:
using System;
using System.Collections.Generic;
using System.Windows;
Page 23 of 359
wpf-tutorial.com
namespace WpfTutorialSamples
{
public partial class App : Application
{
private void Application_Startup(object sender,
StartupEventArgs e)
{
MainWindow wnd = new MainWindow();
// The OpenFile() method is just an example of what you
could do with the
// parameter. The method should be declared on your MainWindow
class, where
// you could use a range of methods to process the passed file
path
if(e.Args.Length == 1)
wnd.OpenFile(e.Args[0]);
wnd.Show();
}
}
}
1.4.4.2. Command-line possibilities
In this example, we test if there is exactly one argument and if so, we use it as a filename. In a real world
example, you might collect several arguments and even use them for options, e.g. toggling a certain feature
on or off. You would do that by looping through the entire list of arguments passed while collecting the
information you need to proceed, but that's beyond the scope of this article.
Page 24 of 359
wpf-tutorial.com
1.4.5. Resources
WPF introduces a very handy concept: The ability to store data as a resource, either locally for a control,
locally for the entire window or globally for the entire application. The data can be pretty much whatever
you want, from actual information to a hierarchy of WPF controls. This allows you to place data in one place
and then use it from or several other places, which is very useful.
The concept is used a lot for styles and templates, which we'll discuss later on in this tutorial, but as it will
be illustrated in this chapter, you can use it for many other things as well. Allow me to demonstrate it with a
simple example:
<Window x:Class="WpfTutorialSamples.WPF_Application.ResourceSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ResourceSample" Height="150" Width="350">
<Window.Resources>
<sys:String x:Key="strHelloWorld">Hello, world!</sys:String>
</Window.Resources>
<StackPanel Margin="10">
<TextBlock Text="{StaticResource strHelloWorld}" FontSize="56"
/>
<TextBlock>Just another "<TextBlock Text="{StaticResource
strHelloWorld}" />" example, but with resources!</TextBlock>
</StackPanel>
</Window>
Resources are given a key, using the x:Key attribute, which allows you to reference it from other parts of
the application by using this key, in combination with the StaticResource markup extension. In this
example, I just store a simple string, which I then use from two different TextBlock controls.
Page 25 of 359
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.WPF_Application.ExtendedResourceSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ExtendedResourceSample" Height="160" Width="300"
Background="{DynamicResource WindowBackgroundBrush}">
<Window.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
<x:Array x:Key="ComboBoxItems" Type="sys:String">
<sys:String>Item #1</sys:String>
<sys:String>Item #2</sys:String>
<sys:String>Item #3</sys:String>
</x:Array>
<LinearGradientBrush x:Key="WindowBackgroundBrush">
<GradientStop Offset="0" Color="Silver"/>
<GradientStop Offset="1" Color="Gray"/>
</LinearGradientBrush>
</Window.Resources>
Page 26 of 359
wpf-tutorial.com
<StackPanel Margin="10">
<Label Content="{StaticResource ComboBoxTitle}" />
<ComboBox ItemsSource="{StaticResource ComboBoxItems}" />
</StackPanel>
</Window>
This time, we've added a couple of extra resources, so that our Window now contains a simple string, an
array of strings and a LinearGradientBrush. The string is used for the label, the array of strings is used as
items for the ComboBox control and the gradient brush is used as background for the entire window. So, as
you can see, pretty much anything can be stored as a resource.
<StackPanel Margin="10">
<StackPanel.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</StackPanel.Resources>
<Label Content="{StaticResource ComboBoxTitle}" />
</StackPanel>
In this case, we add the resource to the StackPanel and then use it from its child control, the Label. Other
controls inside of the StackPanel could have used it as well, just like children of these child controls would
have been able to access it. Controls outside of this particular StackPanel wouldn't have access to it,
though.
If you need the ability to access the resource from several windows, this is possible as well. The App.xaml
Page 27 of 359
wpf-tutorial.com
file can contain resources just like the window and any kind of WPF control, and when you store them in
App.xaml, they are globally accessible in all of windows and user controls of the project. It works exactly
the same way as when storing and using from a Window:
<Application x:Class="WpfTutorialSamples.App"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="WPF application/ExtendedResourceSample.xaml"
>
<Application.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</Application.Resources>
</Application>
Using it is also the same - WPF will automatically go up the scope, from the local control to the window
and then to App.xaml, to find a given resource:
<Application x:Class="WpfTutorialSamples.App"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="WPF
application/ResourcesFromCodeBehindSample.xaml">
<Application.Resources>
<sys:String x:Key="strApp">Hello, Application world!</
sys:String>
</Application.Resources>
</Application>
Page 28 of 359
wpf-tutorial.com
Window:
<Window x:Class
="WpfTutorialSamples.WPF_Application.ResourcesFromCodeBehindSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ResourcesFromCodeBehindSample" Height="175" Width="250"
>
<Window.Resources>
<sys:String x:Key="strWindow">Hello, Window world!</sys:String
>
</Window.Resources>
<DockPanel Margin="10" Name="pnlMain">
<DockPanel.Resources>
<sys:String x:Key="strPanel">Hello, Panel world!</
sys:String>
</DockPanel.Resources>
<WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Center"
Margin="10">
<Button Name="btnClickMe" Click="btnClickMe_Click">Click
me!</Button>
</WrapPanel>
<ListBox Name="lbResult" />
</DockPanel>
</Window>
Code-behind:
using System;
using System.Windows;
namespace WpfTutorialSamples.WPF_Application
{
public partial class ResourcesFromCodeBehindSample : Window
{
public ResourcesFromCodeBehindSample()
{
Page 29 of 359
wpf-tutorial.com
InitializeComponent();
}
private void btnClickMe_Click(object sender, RoutedEventArgs
e)
{
lbResult.Items.Add(pnlMain.FindResource("strPanel"
).ToString());
lbResult.Items.Add(this.FindResource("strWindow"
).ToString());
lbResult.Items.Add(Application.Current.FindResource(
"strApp").ToString());
}
}
}
So, as you can see, we store three different "Hello, world!" messages: One in App.xaml, one inside the
window, and one locally for the main panel. The interface consists of a button and a ListBox.
In Code-behind, we handle the click event of the button, in which we add each of the text strings to the
ListBox, as seen on the screenshot. We use the FindResource() method, which will return the resource as
an object (if found), and then we turn it into the string that we know it is by using the ToString() method.
Notice how we use the FindResource() method on different scopes - first on the panel, then on the window
and then on the current Application object. It makes sense to look for the resource where we know it is,
but as already mentioned, if a resource is not found, the search progresses up the hierarchy, so in
principal, we could have used the FindResource() method on the panel in all three cases, since it would
have continued up to the window and later on up to the application level, if not found.
The same is not true the other way around - the search doesn't navigate down the tree, so you can't start
looking for a resource on the application level, if it has been defined locally for the control or for the window.
Page 30 of 359
wpf-tutorial.com
Page 31 of 359
wpf-tutorial.com
In this case, the user would be forced to close your application, due to such a simple and easily avoided
error. So, if you know that things might go wrong, then you should use a try-catch block, like this:
wpf-tutorial.com
s.Trim();
}
catch(Exception ex)
{
MessageBox.Show("A handled exception just occurred: " +
ex.Message, "Exception Sample", MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
However, sometimes even the simplest code can throw an exception, and instead of wrapping every single
line of code with a try- catch block, WPF lets you handle all unhandled exceptions globally. This is done
through the DispatcherUnhandledException event on the Application class. If subscribed to, WPF will call
the subscribing method once an exception is thrown which is not handled in your own code. Here's a
complete example, based on the stuff we just went through:
<Window x:Class
="WpfTutorialSamples.WPF_Application.ExceptionHandlingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ExceptionHandlingSample" Height="200" Width="200">
<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment
="Center" Click="Button_Click">
Do something bad!
</Button>
</Grid>
</Window>
using System;
using System.Windows;
namespace WpfTutorialSamples.WPF_Application
{
public partial class ExceptionHandlingSample : Window
{
public ExceptionHandlingSample()
{
InitializeComponent();
}
Page 33 of 359
wpf-tutorial.com
<Application x:Class="WpfTutorialSamples.App"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DispatcherUnhandledException
="Application_DispatcherUnhandledException"
StartupUri="WPF
Application/ExceptionHandlingSample.xaml">
<Application.Resources>
</Application.Resources>
</Application>
using System;
using System.Windows;
namespace WpfTutorialSamples
{
public partial class App : Application
{
private void Application_DispatcherUnhandledException(object
sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs
Page 34 of 359
wpf-tutorial.com
e)
{
MessageBox.Show("An unhandled exception just occurred: "
+ e.Exception.Message, "Exception Sample", MessageBoxButton.OK,
MessageBoxImage.Warning);
e.Handled = true;
}
}
}
We handle the exception much like the local one, but with a slightly different text and image in the
message box. Also, notice that I set the e.Handled property to true. This tells WPF that we're done dealing
with this exception and nothing else should be done about it.
1.4.6.1. Summary
Exception handling is a very important part of any application and fortunately, WPF and .NET makes it
very easy to handle exceptions both locally and globally. You should handle exceptions locally when it
makes sense and only use the global handling as a fallback mechanism, since local handling allows you to
be more specific and deal with the problem in a more specialized way.
Page 35 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.TextBlockSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBlockSample" Height="100" Width="200">
<Grid>
<TextBlock>This is a TextBlock</TextBlock>
</Grid>
</Window>
That's as simple as it comes and if you have read the previous chapters of this tutorial, then there should
be nothing new here. The text between the TextBlock is simply a shortcut for setting the Text property of
the TextBlock.
For the next example, let's try a longer text to show how the TextBlock deals with that. I've also added a bit
of margin, to make it look just a bit better:
<Window x:Class="WpfTutorialSamples.Basic_controls.TextBlockSample"
Page 36 of 359
wpf-tutorial.com
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBlockSample" Height="100" Width="200">
<Grid>
<TextBlock Margin="10">This is a TextBlock control and it
comes with a very long text</TextBlock>
</Grid>
</Window>
<Window x:Class="WpfTutorialSamples.Basic_controls.TextBlockSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBlockSample" Height="200" Width="250">
<StackPanel>
<TextBlock Margin="10" Foreground="Red">
This is a TextBlock control<LineBreak />
with multiple lines of text.
</TextBlock>
<TextBlock Margin="10" TextTrimming="CharacterEllipsis"
Foreground="Green">
This is a TextBlock control with text that may not be
rendered completely, which will be indicated with an ellipsis.
</TextBlock>
<TextBlock Margin="10" TextWrapping="Wrap" Foreground="Blue">
This is a TextBlock control with automatically wrapped
text, using the TextWrapping property.
Page 37 of 359
wpf-tutorial.com
</TextBlock>
</StackPanel>
</Window>
So, we have three TextBlock controls, each with a different color (using the Foreground property) for an
easier overview. They all handle the fact that their text content is too long in different ways:
The red TextBlock uses a LineBreak tag to manually break the line at a designated location. This gives
you absolute control over where you want the text to break onto a new line, but it's not very flexible for most
situations. If the user makes the window bigger, the text will still wrap at the same position, even though
there may now be room enough to fit the entire text onto one line.
The green TextBlock uses the TextTrimming property with the value CharacterEllipsis to make the
TextBlock show an ellipsis (...) when it can't fit any more text into the control. This is a common way of
showing that there's more text, but not enough room to show it. This is great when you have text that might
be too long but you absolutely don't want it to use more than one line. As an alternative to
CharacterEllipsis you may use WordEllipsis, which will trim the text at the end of the last possible word
instead of the last possible character, preventing that a word is only shown in part.
The blue TextBlock uses the TextWrapping property with the value Wrap, to make the TextBlock wrap to
the next line whenever it can't fit anymore text into the previous line. Contrary to the first TextBlock, where
we manually define where to wrap the text, this happens completely automatic and even better: It's also
automatically adjusted as soon as the TextBlock get more or less space available. Try making the window
in the example bigger or smaller and you will see how the wrapping is updated to match the situation.
This was all about dealing with simple strings in the TextBlock. In the next chapter, we'll look into some of
the more advanced functionality of the TextBlock, which allows us to create text of various styles within the
TextBlock and much more.
Page 38 of 359
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Basic_controls.TextBlockInlineSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBlockInlineSample" Height="100" Width="300">
<Grid>
<TextBlock Margin="10" TextWrapping="Wrap">
TextBlock with <Bold>bold</Bold>, <Italic>italic</Italic>
and <Underline>underlined</Underline> text.
</TextBlock>
</Grid>
</Window>
Much like with HTML, you just surround your text with a Bold tag to get bold text and so on. This makes it
very easy to create and display diverse text in your applications.
All three of these tags are just child classes of the Span element, each setting a specific property on the
Span element to create the desired effect. For instance, the Bold tag just sets the FontWeight property on
the underlying Span element, the Italic element sets the FontStyle and so on.
Page 39 of 359
wpf-tutorial.com
1.5.2.2. LineBreak
Simply inserts a line break into the text. Please see the previous chapter for an example where we use the
LineBreak element.
1.5.2.3. Hyperlink
The Hyperlink element allows you to have links in your text. It's rendered with a style that suits your current
Windows theme, which will usually be some sort of underlined blue text with a red hover effect and a hand
mouse cursor. You can use the NavigateUri property to define the URL that you wish to navigate to. Here's
an example:
<Window x:Class
="WpfTutorialSamples.Basic_controls.TextBlockHyperlinkSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBlockHyperlinkSample" Height="100" Width="300">
<Grid>
<TextBlock Margin="10" TextWrapping="Wrap">
This text has a <Hyperlink RequestNavigate
="Hyperlink_RequestNavigate" NavigateUri="https://www.google.com">link</
Hyperlink> in it.
</TextBlock>
</Grid>
</Window>
The Hyperlink is also used inside of WPF Page's, where it can be used to navigate between pages. In that
case, you won't have to specifically handle the RequestNavigate event, like we do in the example, but for
launching external URL's from a regular WPF application, we need a bit of help from this event and the
Process class. We subscribe to the RequestNavigate event, which allows us to launch the linked URL in
the users default browser with a simple event handler like this one in the code behind file:
wpf-tutorial.com
}
1.5.2.4. Run
The Run element allows you to style a string using all the available properties of the Span element, but
while the Span element may contain other inline elements, a Run element may only contain plain text. This
makes the Span element more flexible and therefore the logical choice in most cases.
1.5.2.5. Span
The Span element doesn't have any specific rendering by default, but allows you to set almost any kind of
specific rendering, including font size, style and weight, background and foreground colors and so on. The
great thing about the Span element is that it allows for other inline elements inside of it, making it easy to
do even advanced combinations of text and style. In the following example, I have used many Span
elements to show you some of the many possibilities when using inline Span elements:
<Window x:Class="WpfTutorialSamples.Basic_controls.TextBlockSpanSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBlockSpanSample" Height="100" Width="300">
<Grid>
<TextBlock Margin="10" TextWrapping="Wrap">
This <Span FontWeight="Bold">is</Span> a
<Span Background="Silver" Foreground="Maroon">TextBlock</Span>
with <Span TextDecorations="Underline">several</Span>
<Span FontStyle="Italic">Span</Span> elements,
<Span Foreground="Blue">
using a <Bold>variety</Bold> of <Italic>styles</
Italic>
</Span>.
</TextBlock>
</Grid>
</Window>
So as you can see, if none of the other elements doesn't make sense in your situation or if you just want a
blank canvas when starting to format your text, the Span element is a great choice.
Page 41 of 359
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Basic_controls.TextBlockCodeBehindSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBlockCodeBehindSample" Height="100" Width="300">
<Grid></Grid>
</Window>
using
using
using
using
using
System;
System.Windows;
System.Windows.Controls;
System.Windows.Documents;
System.Windows.Media;
namespace WpfTutorialSamples.Basic_controls
{
public partial class TextBlockCodeBehindSample : Window
{
public TextBlockCodeBehindSample()
{
InitializeComponent();
TextBlock tb = new TextBlock();
tb.TextWrapping = TextWrapping.Wrap;
tb.Margin = new Thickness(10);
tb.Inlines.Add("An example on ");
tb.Inlines.Add(new Run("the TextBlock control ") {
FontWeight = FontWeights.Bold });
tb.Inlines.Add("using ");
tb.Inlines.Add(new Run("inline ") { FontStyle =
FontStyles.Italic });
tb.Inlines.Add(new Run("text formatting ") { Foreground =
Brushes.Blue });
tb.Inlines.Add("from ");
tb.Inlines.Add(new Run("Code-Behind") { TextDecorations =
Page 42 of 359
wpf-tutorial.com
TextDecorations.Underline });
tb.Inlines.Add(".");
this.Content = tb;
}
}
}
It's great to have the possibility, and it can be necessary to do it like this in some cases, but this example
will probably make you appreciate XAML even more.
Page 43 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.LabelControlSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LabelControlSample" Height="100" Width="200">
<Grid>
<Label Content="This is a Label control." />
</Grid>
</Window>
Another thing you might notice is the fact that the Label, by default, has a bit of padding, allowing the text to
be rendered a few pixels away from the top, left corner. This is not the case for the TextBlock control,
where you will have to specify it manually.
In a simple case like this, where the content is simply a string, the Label will actually create a TextBlock
internally and show your string in that.
Specify a border
Render other controls, e.g. an image
Use templated content through the ContentTemplate property
Use access keys to give focus to related controls
The last bullet point is actually one of the main reasons for using a Label over the TextBlock control.
Whenever you just want to render simple text, you should use the TextBlock control, since it's lighter and
performs better than the Label in most cases.
Page 44 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.LabelControlSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LabelControlSample" Height="180" Width="250">
<StackPanel Margin="10">
<Label Content="_Name:" Target="{Binding ElementName=txtName}"
/>
<TextBox Name="txtName" />
<Label Content="_Mail:" Target="{Binding ElementName=txtMail}"
/>
<TextBox Name="txtMail" />
</StackPanel>
</Window>
The screenshot shows our sample dialog as it looks when the Alt key is pressed. Try running it, holding
down the [Alt] key and then pressing N and M. You will see how focus is moved between the two textboxes.
So, there's several new concepts here. First of all, we define the access key by placing an underscore (_)
before the character. It doesn't have to be the first character, it can be before any of the characters in your
label content. The common practice is to use the first character that's not already used as an access key
for another control.
We use the Target property to connect the Label and the designated control. We use a standard WPF
binding for this, using the ElementName property, all of which we will describe later on in this tutorial. The
Page 45 of 359
wpf-tutorial.com
binding is based on the name of the control, so if you change this name, you will also have to remember to
change the binding.
<Window x:Class
="WpfTutorialSamples.Basic_controls.LabelControlAdvancedSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LabelControlAdvancedSample" Height="180" Width="250">
<StackPanel Margin="10">
<Label Target="{Binding ElementName=txtName}">
<StackPanel Orientation="Horizontal">
<Image Source
="http://cdn1.iconfinder.com/data/icons/fatcow/16/bullet_green.png" />
<AccessText Text="_Name:" />
</StackPanel>
</Label>
<TextBox Name="txtName" />
<Label Target="{Binding ElementName=txtMail}">
<StackPanel Orientation="Horizontal">
<Image Source
="http://cdn1.iconfinder.com/data/icons/fatcow/16/bullet_blue.png" />
<AccessText Text="_Mail:" />
</StackPanel>
</Label>
<TextBox Name="txtMail" />
</StackPanel>
</Window>
This is just an extended version of the previous example - instead of a simple text string, our Label will now
host both and image and a piece of text (inside the AccessText control, which allows us to still use an
access key for the label). Both controls are inside a horizontal StackPanel, since the Label, just like any
other ContentControl derivate, can only host one direct child control.
The Image control, described later in this tutorial, uses a remote image - this is ONLY for demonstrational
purposes and is NOT a good idea for most real life applications.
Page 46 of 359
wpf-tutorial.com
1.5.3.4. Summary
In most situations, the Label control does exactly what the name implies: It acts as a text label for another
control. This is the primary purpose of it. For most other cases, you should probably use a TextBlock
control or one of the other text containers that WPF offers.
Page 47 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.TextBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBoxSample" Height="80" Width="250">
<StackPanel Margin="10">
<TextBox />
</StackPanel>
</Window>
That's all you need to get a text field. I added the text after running the sample and before taking the
screenshot, but you can do it through markup as well, to pre-fill the textbox, using the Text property:
<Window x:Class="WpfTutorialSamples.Basic_controls.TextBoxSample"
xmlns
Page 48 of 359
wpf-tutorial.com
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBoxSample" Height="160" Width="280">
<Grid Margin="10">
<TextBox AcceptsReturn="True" TextWrapping="Wrap" />
</Grid>
</Window>
I have added two properties: The AcceptsReturn makes the TextBox into a multi-line control by allowing
the use of the Enter/Return key to go to the next line, and the TextWrapping property, which will make the
text wrap automatically when the end of a line is reached.
<Window x:Class="WpfTutorialSamples.Basic_controls.TextBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBoxSample" Height="160" Width="280">
<Grid Margin="10">
<TextBox AcceptsReturn="True" TextWrapping="Wrap"
SpellCheck.IsEnabled="True" Language="en-US" />
</Grid>
</Window>
We have used the previous, multi-line textbox example as the basis and then I have added two new
properties: The attached property from the SpellCheck class called IsEnabled, which simply enables spell
Page 49 of 359
wpf-tutorial.com
checking on the parent control, and the Language property, which instructs the spell checker which
language to use.
<Window x:Class
="WpfTutorialSamples.Basic_controls.TextBoxSelectionSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBoxSelectionSample" Height="150" Width="300">
<DockPanel Margin="10">
<TextBox SelectionChanged="TextBox_SelectionChanged"
DockPanel.Dock="Top" />
<TextBox Name="txtStatus" AcceptsReturn="True" TextWrapping
="Wrap" IsReadOnly="True" />
</DockPanel>
</Window>
The example consists of two TextBox controls: One for editing and one for outputting the current selection
status to. For this, we set the IsReadOnly property to true, to prevent editing of the status TextBox. We
subscribe the SelectionChanged event on the first TextBox, which we handle in the Code-behind:
using
using
using
using
System;
System.Text;
System.Windows;
System.Windows.Controls;
Page 50 of 359
wpf-tutorial.com
namespace WpfTutorialSamples.Basic_controls
{
public partial class TextBoxSelectionSample : Window
{
public TextBoxSelectionSample()
{
InitializeComponent();
}
private void TextBox_SelectionChanged(object sender,
RoutedEventArgs e)
{
TextBox textBox = sender as TextBox;
txtStatus.Text = "Selection starts at character #" +
textBox.SelectionStart + Environment.NewLine;
txtStatus.Text += "Selection is " +
textBox.SelectionLength + " character(s) long" + Environment.NewLine;
txtStatus.Text += "Selected text: '" +
textBox.SelectedText + "'";
}
}
}
wpf-tutorial.com
Page 52 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.CheckBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CheckBoxSample" Height="140" Width="250">
<StackPanel Margin="10">
<Label FontWeight="Bold">Application Options</Label>
<CheckBox>Enable feature ABC</CheckBox>
<CheckBox IsChecked="True">Enable feature XYZ</CheckBox>
<CheckBox>Enable feature WWW</CheckBox>
</StackPanel>
</Window>
As you can see, the CheckBox is very easy to use. On the second CheckBox, I use the IsChecked
property to have it checked by default, but other than that, no properties are needed to use it. The
IsChecked property should also be used from Code-behind if you want to check whether a certain
CheckBox is checked or not.
<Window x:Class="WpfTutorialSamples.Basic_controls.CheckBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Page 53 of 359
wpf-tutorial.com
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CheckBoxSample" Height="140" Width="250">
<StackPanel Margin="10">
<Label FontWeight="Bold">Application Options</Label>
<CheckBox>
<TextBlock>
Enable feature <Run Foreground="Green" FontWeight
="Bold">ABC</Run>
</TextBlock>
</CheckBox>
<CheckBox IsChecked="True">
<WrapPanel>
<TextBlock>
Enable feature <Run FontWeight="Bold">XYZ</Run>
</TextBlock>
<Image Source
="/WpfTutorialSamples;component/Images/question.png" Width="16" Height
="16" Margin="5,0" />
</WrapPanel>
</CheckBox>
<CheckBox>
<TextBlock>
Enable feature <Run Foreground="Blue"
TextDecorations="Underline" FontWeight="Bold">WWW</Run>
</TextBlock>
</CheckBox>
</StackPanel>
</Window>
As you can see from the sample markup, you can do pretty much whatever you want with the content. On
all three check boxes, I do something differently with the text, and on the middle one I even throw in an
Image control. By specifying a control as the content, instead of just text, we get much more control of the
appearance, and the cool thing is that no matter which part of the content you click on, it will activate the
Page 54 of 359
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Basic_controls.CheckBoxThreeStateSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CheckBoxThreeStateSample" Height="170" Width="300">
<StackPanel Margin="10">
<Label FontWeight="Bold">Application Options</Label>
<StackPanel Margin="10,5">
<CheckBox IsThreeState="True" Name="cbAllFeatures"
Checked="cbAllFeatures_CheckedChanged" Unchecked
="cbAllFeatures_CheckedChanged">Enable all</CheckBox>
<StackPanel Margin="20,5">
<CheckBox Name="cbFeatureAbc" Checked
="cbFeature_CheckedChanged" Unchecked="cbFeature_CheckedChanged">Enable
feature ABC</CheckBox>
<CheckBox Name="cbFeatureXyz" IsChecked="True"
Checked="cbFeature_CheckedChanged" Unchecked="cbFeature_CheckedChanged">
Enable feature XYZ</CheckBox>
<CheckBox Name="cbFeatureWww" Checked
="cbFeature_CheckedChanged" Unchecked="cbFeature_CheckedChanged">Enable
feature WWW</CheckBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Window>
using System;
using System.Windows;
Page 55 of 359
wpf-tutorial.com
namespace WpfTutorialSamples.Basic_controls
{
public partial class CheckBoxThreeStateSample : Window
{
public CheckBoxThreeStateSample()
{
InitializeComponent();
}
Page 56 of 359
wpf-tutorial.com
This example works from two different angles: If you check or uncheck the "Enable all" CheckBox, then all
of the child check boxes, each representing an application feature in our example, is either checked or
unchecked. It also works the other way around though, where checking or unchecking a child CheckBox
affects the "Enable all" CheckBox state: If they are all checked or unchecked, then the "Enable all"
CheckBox gets the same state - otherwise the value will be left with a null, which forces the CheckBox into
the indeterminate state.
All of this behavior can be seen on the screenshots above, and is achieved by subscribing to the Checked
and Unchecked events of the CheckBox controls. In a real world example, you would likely bind the values
instead, but this example shows the basics of using the IsThreeState property to create a "Toggle all"
effect.
Page 57 of 359
wpf-tutorial.com
Page 58 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.RadioButtonSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RadioButtonSample" Height="150" Width="250">
<StackPanel Margin="10">
<Label FontWeight="Bold">Are you ready?</Label>
<RadioButton>Yes</RadioButton>
<RadioButton>No</RadioButton>
<RadioButton IsChecked="True">Maybe</RadioButton>
</StackPanel>
</Window>
All we do is add a Label with a question, and then three radio buttons, each with a possible answer. We
define a default option by using the IsChecked property on the last RadioButton, which the user can
change simply by clicking on one of the other radio buttons. This is also the property you would want to
use from Code-behind to check if a RadioButton is checked or not.
<Window x:Class="WpfTutorialSamples.Basic_controls.RadioButtonSample"
xmlns
Page 59 of 359
wpf-tutorial.com
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RadioButtonSample" Height="230" Width="250">
<StackPanel Margin="10">
<Label FontWeight="Bold">Are you ready?</Label>
<RadioButton GroupName="ready">Yes</RadioButton>
<RadioButton GroupName="ready">No</RadioButton>
<RadioButton GroupName="ready" IsChecked="True">Maybe</
RadioButton>
<Label FontWeight="Bold">Male or female?</Label>
<RadioButton GroupName="sex">Male</RadioButton>
<RadioButton GroupName="sex">Female</RadioButton>
<RadioButton GroupName="sex" IsChecked="True">Not sure</
RadioButton>
</StackPanel>
</Window>
With the GroupName property set on each of the radio buttons, a selection can now be made for each of
the two groups. Without this, only one selection for all six radio buttons would be possible.
Page 60 of 359
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Basic_controls.RadioButtonCustomContentSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RadioButtonCustomContentSample" Height="150" Width
="250">
<StackPanel Margin="10">
<Label FontWeight="Bold">Are you ready?</Label>
<RadioButton>
<WrapPanel>
<Image Source
="/WpfTutorialSamples;component/Images/accept.png" Width="16" Height
="16" Margin="0,0,5,0" />
<TextBlock Text="Yes" Foreground="Green" />
</WrapPanel>
</RadioButton>
<RadioButton Margin="0,5">
<WrapPanel>
<Image Source
="/WpfTutorialSamples;component/Images/cancel.png" Width="16" Height
="16" Margin="0,0,5,0" />
<TextBlock Text="No" Foreground="Red" />
</WrapPanel>
</RadioButton>
<RadioButton IsChecked="True">
<WrapPanel>
<Image Source
="/WpfTutorialSamples;component/Images/question.png" Width="16" Height
="16" Margin="0,0,5,0" />
<TextBlock Text="Maybe" Foreground="Gray" />
</WrapPanel>
</RadioButton>
</StackPanel>
</Window>
Markup-wise, this example gets a bit heavy, but the concept is pretty simple. For each RadioButton, we
have a WrapPanel with an image and a piece of text inside of it. Since we now take control of the text using
a TextBlock control, this also allows us to format the text in any way we want to. For this example, I have
changed the text color to match the choice. An Image control (read more about those later) is used to
display an image for each choice.
Page 61 of 359
wpf-tutorial.com
Notice how you can click anywhere on the RadioButton, even on the image or the text, to toggle it on,
because we have specified it as content of the RadioButton. If you had placed it as a separate panel, next
to the RadioButton, the user would have to click directly on the round circle of the RadioButton to activate it,
which is less practical.
Page 62 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.PasswordBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PasswordBoxSample" Height="160" Width="300">
<StackPanel Margin="10">
<Label>Text:</Label>
<TextBox />
<Label>Password:</Label>
<PasswordBox />
</StackPanel>
</Window>
In the screenshot, I have entered the exact same text into the two text boxes, but in the password version,
the characters are replaced with dots. You can actually control which character is used instead of the real
characters, using the PasswordChar property:
Page 63 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Basic_controls.PasswordBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PasswordBoxSample" Height="160" Width="300">
<StackPanel Margin="10">
<Label>Text:</Label>
<TextBox />
<Label>Password:</Label>
<PasswordBox MaxLength="6" PasswordChar="X" />
</StackPanel>
</Window>
Notice how the characters are now X's instead, and that I was only allowed to enter 6 characters in the
box.
Page 64 of 359
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Control_concepts.ToolTipsSimpleSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ToolTipsSimpleSample" Height="150" Width="400">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Button ToolTip="Click here and something will happen!">Click
here!</Button>
</Grid>
</Window>
As you can see on the screenshots, this results in a floating box with the specified string, once the mouse
hovers over the button. This is what most UI frameworks offers - the display of a text string and nothing
more.
However, in WPF, the ToolTip property is actually not a string type, but instead an object type, meaning
that we can put whatever we want in there. This opens up for some pretty cool possibilities, where we can
Page 65 of 359
wpf-tutorial.com
provide the user with much richer and more helpful tooltips. For instance, consider this example and
compare it to the first one:
<Window x:Class
="WpfTutorialSamples.Control_concepts.ToolTipsAdvancedSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ToolTipsAdvancedSample" Height="200" Width="400"
UseLayoutRounding="True">
<DockPanel>
<ToolBar DockPanel.Dock="Top">
<Button ToolTip="Create a new file">
<Button.Content>
<Image Source
="/WpfTutorialSamples;component/Images/page_white.png" Width="16" Height
="16" />
</Button.Content>
</Button>
<Button>
<Button.Content>
<Image Source
="/WpfTutorialSamples;component/Images/folder.png" Width="16" Height
="16" />
</Button.Content>
<Button.ToolTip>
<StackPanel>
<TextBlock FontWeight="Bold" FontSize="14"
Margin="0,0,0,5">Open file</TextBlock>
<TextBlock>
Search your computer or local network
<LineBreak />
for a file and open it for editing.
</TextBlock>
<Border BorderBrush="Silver"
BorderThickness="0,1,0,0" Margin="0,8" />
<WrapPanel>
<Image Source
="/WpfTutorialSamples;component/Images/help.png" Margin="0,0,5,0" />
<TextBlock FontStyle="Italic">Press
F1 for more help</TextBlock>
Page 66 of 359
wpf-tutorial.com
</WrapPanel>
</StackPanel>
</Button.ToolTip>
</Button>
</ToolBar>
<TextBox>
Editor area...
</TextBox>
</DockPanel>
</Window>
Notice how this example uses a simple string tooltip for the first button and then a much more advanced
one for the second button. In the advanced case, we use a panel as the root control and then we're free to
add controls to that as we please. The result is pretty cool, with a header, a description text and a hint that
you can press F1 for more help, including a help icon.
wpf-tutorial.com
1.6.1.2. Summary
Tooltips can be a great help for the user, and in WPF, they are both easy to use and extremely flexible.
Combine the fact that you can completely control the design and content of the tooltip, with properties from
the ToolTipService class, to create more user friendly inline help in your applications.
Page 68 of 359
wpf-tutorial.com
As already mentioned in this tutorial, WPF does a lot more things on its own when compared to other UI
frameworks like WinForms, which will use the Windows API for many, many things. This is also clear when
it comes to the rendering of text - WinForms uses the GDI API from Windows, while WPF has its own text
rendering implementation, to better support animations as well as the device independent nature of WPF.
Unfortunately, this led to text being rendered a bit blurry, especially in small font sizes. This was a rather
big problem for WPF programmers for some time, but luckily, Microsoft made a lot of improvements in the
WPF text rendering engine in .NET framework version 4.0. This means that if you're using this version or
higher, your text should be almost as good as pixel perfect.
1.6.2.2. TextFormattingMode
Using the TextFormattingMode property, you get to decide which algorithm should be used when formatting
the text. You can choose between Ideal (the default value) and Display. You would normally want to leave
this property untouched, since the Ideal setting will be best for most situations, but in cases where you
need to render very small text, the Display setting can sometimes yield a better result. Here's an example
where you can see the difference (although it's very subtle):
<Window x:Class
="WpfTutorialSamples.Control_concepts.TextFormattingModeSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextFormattingModeSample" Height="200" Width="400">
<StackPanel Margin="10">
<Label TextOptions.TextFormattingMode="Ideal" FontSize="9">
TextFormattingMode.Ideal, small text</Label>
<Label TextOptions.TextFormattingMode="Display" FontSize="9">
Page 69 of 359
wpf-tutorial.com
1.6.2.3. TextRenderingMode
The TextRenderingMode property gives you control of which antialiasing algorithm is used when
rendering text. It has the biggest effect in combination with the Display setting for the
TextFormattingMode property, which we'll use in this example to illustrate the differences:
<Window x:Class
="WpfTutorialSamples.Control_concepts.TextRenderingModeSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextRenderingModeSample" Height="300" Width="400">
<StackPanel Margin="10" TextOptions.TextFormattingMode="Display">
<Label TextOptions.TextRenderingMode="Auto" FontSize="9">
TextRenderingMode.Auto, small text</Label>
<Label TextOptions.TextRenderingMode="Aliased" FontSize="9">
TextRenderingMode.Aliased, small text</Label>
<Label TextOptions.TextRenderingMode="ClearType" FontSize="9">
TextRenderingMode.ClearType, small text</Label>
<Label TextOptions.TextRenderingMode="Grayscale" FontSize="9">
TextRenderingMode.Grayscale, small text</Label>
<Label TextOptions.TextRenderingMode="Auto" FontSize="18">
Page 70 of 359
wpf-tutorial.com
As you can see, the resulting text differs quite a bit in how it looks and once again, you should mainly
change this in special circumstances.
Page 71 of 359
wpf-tutorial.com
1.7. Panels
1.7.1. Introduction to WPF panels
Panels are one of the most important control types of WPF. They act as containers for other controls and
control the layout of your windows/pages. Since a window can only contain ONE child control, a panel is
often used to divide up the space into areas, where each area can contain a control or another panel
(which is also a control, of course).
Panels come in several different flavors, with each of them having its own way of dealing with layout and
child controls. Picking the right panel is therefore essential to getting the behavior and layout you want, and
especially in the start of your WPF career, this can be a difficult job. The next section will describe each of
the panels shortly and give you an idea of when to use it. After that, move on to the next chapters, where
each of the panels will be described in detail.
1.7.1.1. Canvas
A simple panel, which mimics the WinForms way of doing things. It allows you to assign specific
coordinates to each of the child controls, giving you total control of the layout. This is not very flexible
though, because you have to manually move the child controls around and make sure that they align the
way you want them to. Use it (only) when you want complete control of the child control positions.
1.7.1.2. WrapPanel
The WrapPanel will position each of its child controls next to the other, horizontally (default) or vertically,
until there is no more room, where it will wrap to the next line and then continue. Use it when you want a
vertical or horizontal list controls that automatically wraps when there's no more room.
1.7.1.3. StackPanel
The StackPanel acts much like the WrapPanel, but instead of wrapping if the child controls take up too
much room, it simply expands itself, if possible. Just like with the WrapPanel, the orientation can be either
horizontal or vertical, but instead of adjusting the width or height of the child controls based on the largest
item, each item is stretched to take up the full width or height. Use the StackPanel when you want a list of
controls that takes up all the available room, without wrapping.
1.7.1.4. DockPanel
The DockPanel allows you to dock the child controls to the top, bottom, left or right. By default, the last
control, if not given a specific dock position, will fill the remaining space. You can achieve the same with the
Grid panel, but for the simpler situations, the DockPanel will be easier to use. Use the DockPanel whenever
you need to dock one or several controls to one of the sides, like for dividing up the window into specific
areas.
1.7.1.5. Grid
The Grid is probably the most complex of the panel types. A Grid can contain multiple rows and columns.
You define a height for each of the rows and a width for each of the columns, in either an absolute amount
Page 72 of 359
wpf-tutorial.com
of pixels, in a percentage of the available space or as auto, where the row or column will automatically
adjust its size depending on the content. Use the Grid when the other panels doesn't do the job, e.g. when
you need multiple columns and often in combination with the other panels.
1.7.1.6. UniformGrid
The UniformGrid is just like the Grid, with the possibility of multiple rows and columns, but with one
important difference: All rows and columns will have the same size! Use this when you need the Grid
behavior without the need to specify different sizes for the rows and columns.
Page 73 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.Canvas"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Canvas" Height="200" Width="200">
<Canvas>
<Button>Button 1</Button>
<Button>Button 2</Button>
</Canvas>
</Window>
As you can see, even though we have two buttons, they are both placed in the exact same place, so only
the last one is visible. The Canvas does absolutely nothing until you start giving coordinates to the child
controls. This is done using the Left, Right, Top and Bottom attached properties from the Canvas control.
These properties allow you to specify the position relative to the four edges of the Canvas. By default, they
are all set to NaN (Not a Number), which will make the Canvas place them in the upper left corner, but as
Page 74 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.Canvas"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Canvas" Height="200" Width="200">
<Canvas>
<Button Canvas.Left="10">Top left</Button>
<Button Canvas.Right="10">Top right</Button>
<Button Canvas.Left="10" Canvas.Bottom="10">Bottom left</
Button>
<Button Canvas.Right="10" Canvas.Bottom="10">Bottom right</
Button>
</Canvas>
</Window>
Notice how I only set the property or properties that I need. For the first two buttons, I only wish to specify
a value for the X axis, so I use the Left and Right properties to push the buttons towards the center, from
each direction.
For the bottom buttons, I use both Left/Right and Bottom to push them towards the center in both
directions. You will usually specify either a Top or a Bottom value and/or a Left or a Right value.
As mentioned, since the Canvas gives you complete control of positions, it won't really care whether or not
there's enough room for all your controls or if one is on top of another. This makes it a bad choice for pretty
much any kind of dialog design, but the Canvas is, as the name implies, great for at least one thing:
Painting. WPF has a bunch of controls that you can place inside a Canvas, to make nice illustrations.
Page 75 of 359
wpf-tutorial.com
1.7.2.1. Z-Index
In the next example, we'll use a couple of the shape related controls of WPF to illustrate another very
important concept when using the Canvas: Z-Index. Normally, if two controls within a Canvas overlaps, the
one defined last in the markup will take precedence and overlap the other(s). However, by using the
attached ZIndex property on the Panel class, this can easily be changed.
First, an example where we don't use z-index at all:
<Window x:Class="WpfTutorialSamples.Panels.CanvasZIndex"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CanvasZIndex" Height="275" Width="260">
<Canvas>
<Ellipse Fill="Gainsboro" Canvas.Left="25" Canvas.Top="25"
Width="200" Height="200" />
<Rectangle Fill="LightBlue" Canvas.Left="25" Canvas.Top="25"
Width="50" Height="50" />
<Rectangle Fill="LightCoral" Canvas.Left="50" Canvas.Top="50"
Width="50" Height="50" />
<Rectangle Fill="LightCyan" Canvas.Left="75" Canvas.Top="75"
Width="50" Height="50" />
</Canvas>
</Window>
Page 76 of 359
wpf-tutorial.com
Notice that because each of the rectangles are defined after the circle, they all overlap the circle, and each
of them will overlap the previously defined one. Let's try changing that:
<Window x:Class="WpfTutorialSamples.Panels.CanvasZIndex"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CanvasZIndex" Height="275" Width="260">
<Canvas>
<Ellipse Panel.ZIndex="2" Fill="Gainsboro" Canvas.Left="25"
Canvas.Top="25" Width="200" Height="200" />
<Rectangle Panel.ZIndex="3" Fill="LightBlue" Canvas.Left="25"
Canvas.Top="25" Width="50" Height="50" />
<Rectangle Panel.ZIndex="2" Fill="LightCoral" Canvas.Left="50"
Canvas.Top="50" Width="50" Height="50" />
<Rectangle Panel.ZIndex="4" Fill="LightCyan" Canvas.Left="75"
Canvas.Top="75" Width="50" Height="50" />
</Canvas>
</Window>
The default ZIndex value is 0, but we assign a new one to each of the shapes. The rule is that the element
with the higher z-index overlaps the ones with the lower values. If two values are identical, the last defined
element "wins". As you can see from the screenshot, changing the ZIndex property gives quite another
look.
Page 77 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.WrapPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WrapPanel" Height="300" Width="300">
<WrapPanel>
<Button>Test button 1</Button>
<Button>Test button 2</Button>
<Button>Test button 3</Button>
<Button Height="40">Test button 4</Button>
<Button>Test button 5</Button>
<Button>Test button 6</Button>
</WrapPanel>
</Window>
Notice how I set a specific height on one of the buttons in the second row. In the resulting screenshot, you
will see that this causes the entire row of buttons to have the same height instead of the height required, as
seen on the first row. You will also notice that the panel does exactly what the name implies: It wraps the
content when it can't fit any more of it in. In this case, the fourth button couldn't fit in on the first line, so it
Page 78 of 359
wpf-tutorial.com
All of this behavior is also true when you set the Orientation to Vertical. Here's the exact same example as
before, but with a Vertical WrapPanel:
<Window x:Class="WpfTutorialSamples.Panels.WrapPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WrapPanel" Height="120" Width="300">
<WrapPanel Orientation="Vertical">
<Button>Test button 1</Button>
<Button>Test button 2</Button>
<Button>Test button 3</Button>
<Button Width="140">Test button 4</Button>
<Button>Test button 5</Button>
<Button>Test button 6</Button>
</WrapPanel>
</Window>
You can see how the buttons go vertical instead of horizontal, before they wrap because they reach the
Page 79 of 359
wpf-tutorial.com
bottom of the window. In this case, I gave a wider width to the fourth button, and you will see that the
buttons in the same column also gets the same width, just like we saw with the button height in the
Horizontal example.
Please be aware that while the Horizontal WrapPanel will match the height in the same row and the Vertical
WrapPanel will match the width in the same column, height is not matched in a Vertical WrapPanel and
width is not matched in a Horizontal WrapPanel. Take a look in this example, which is the Vertical
WrapPanel but where the fourth button gets a custom width AND height:
Notice how button 5 only uses the width - it doesn't care about the height, although it causes the sixth
button to be pushed to a new column.
Page 80 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.StackPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StackPanel" Height="160" Width="300">
<StackPanel>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
<Button>Button 4</Button>
<Button>Button 5</Button>
<Button>Button 6</Button>
</StackPanel>
</Window>
The first thing you should notice is how the StackPanel doesn't really care whether or not there's enough
room for the content. It doesn't wrap the content in any way and it doesn't automatically provide you with
the ability to scroll (you can use a ScrollViewer control for that though - more on that in a later chapter).
You might also notice that the default orientation of the StackPanel is Vertical, unlike the WrapPanel where
the default orientation is Horizontal. But just like for the WrapPanel, this can easily be changed, using the
Orientation property:
<StackPanel Orientation="Horizontal">
Page 81 of 359
wpf-tutorial.com
Another thing you will likely notice is that the StackPanel stretches its child control by default. On a
vertically aligned StackPanel, like the one in the first example, all child controls get stretched horizontally.
On a horizontally aligned StackPanel, all child controls get stretched vertically, as seen above. The
StackPanel does this by setting the HorizontalAlignment or VerticalAlignment property on its child controls
to Stretch, but you can easily override this if you want to. Have a look at the next example, where we use
the same markup as we did in the previous example, but this time we assign values to the
VerticalAlignment property for all the child controls:
<Window x:Class="WpfTutorialSamples.Panels.StackPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StackPanel" Height="160" Width="300">
<StackPanel Orientation="Horizontal">
<Button VerticalAlignment="Top">Button 1</Button>
<Button VerticalAlignment="Center">Button 2</Button>
<Button VerticalAlignment="Bottom">Button 3</Button>
<Button VerticalAlignment="Bottom">Button 4</Button>
<Button VerticalAlignment="Center">Button 5</Button>
<Button VerticalAlignment="Top">Button 6</Button>
</StackPanel>
</Window>
Page 82 of 359
wpf-tutorial.com
We use the Top, Center and Bottom values to place the buttons in a nice pattern, just for kicks. The same
can of course be done for a vertically aligned StackPanel, where you would use the HorizontalAlignment on
the child controls:
<Window x:Class="WpfTutorialSamples.Panels.StackPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StackPanel" Height="160" Width="300">
<StackPanel Orientation="Vertical">
<Button HorizontalAlignment="Left">Button 1</Button>
<Button HorizontalAlignment="Center">Button 2</Button>
<Button HorizontalAlignment="Right">Button 3</Button>
<Button HorizontalAlignment="Right">Button 4</Button>
<Button HorizontalAlignment="Center">Button 5</Button>
<Button HorizontalAlignment="Left">Button 6</Button>
</StackPanel>
</Window>
As you can see, the controls still go from top to bottom, but instead of having the same width, each control
is aligned to the left, the right or center.
Page 83 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.DockPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DockPanel" Height="250" Width="250">
<DockPanel>
<Button DockPanel.Dock="Left">Left</Button>
<Button DockPanel.Dock="Top">Top</Button>
<Button DockPanel.Dock="Right">Right</Button>
<Button DockPanel.Dock="Bottom">Bottom</Button>
<Button>Center</Button>
</DockPanel>
</Window>
As already mentioned, we don't assign a dock position for the last child, because it automatically centers
the control, allowing it to fill the remaining space. You will also notice that the controls around the center
Page 84 of 359
wpf-tutorial.com
only takes up the amount of space that they need - everything else is left for the center position. That is
also why you will see the Right button take up a bit more space than the Left button - the extra character in
the text simply requires more pixels.
The last thing that you will likely notice, is how the space is divided. For instance, the Top button doesn't
get all of the top space, because the Left button takes a part of it. The DockPanel decides which control to
favor by looking at their position in the markup. In this case, the Left button gets precedence because it's
placed first in the markup. Fortunately, this also means that it's very easy to change, as we'll see in the next
example, where we have also evened out the space a bit by assigning widths/heights to the child controls:
<Window x:Class="WpfTutorialSamples.Panels.DockPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DockPanel" Height="250" Width="250">
<DockPanel>
<Button DockPanel.Dock="Top" Height="50">Top</Button>
<Button DockPanel.Dock="Bottom" Height="50">Bottom</Button>
<Button DockPanel.Dock="Left" Width="50">Left</Button>
<Button DockPanel.Dock="Right" Width="50">Right</Button>
<Button>Center</Button>
</DockPanel>
</Window>
The top and bottom controls now take precedence over the left and right controls, and they're all taking up
50 pixels in either height or width. If you make the window bigger or smaller, you will also see that this static
width/height remains the same no matter what - only the center area increases or decreases in size as you
resize the window.
Page 85 of 359
wpf-tutorial.com
1.7.5.1. LastChildFill
As already mentioned, the default behavior is that the last child of the DockPanel takes up the rest of the
space, but this can be disabled using the LastChildFill. Here's an example where we disable it, and at the
same time we'll show the ability to dock more than one control to the same side:
<Window x:Class="WpfTutorialSamples.Panels.DockPanel"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DockPanel" Height="300" Width="300">
<DockPanel LastChildFill="False">
<Button DockPanel.Dock="Top" Height="50">Top</Button>
<Button DockPanel.Dock="Bottom" Height="50">Bottom</Button>
<Button DockPanel.Dock="Left" Width="50">Left</Button>
<Button DockPanel.Dock="Left" Width="50">Left</Button>
<Button DockPanel.Dock="Right" Width="50">Right</Button>
<Button DockPanel.Dock="Right" Width="50">Right</Button>
</DockPanel>
</Window>
In this example, we dock two controls to the left and two controls to the right, and at the same time, we turn
off the LastChildFill property. This leaves us with empty space in the center, which may be preferable in
some cases.
Page 86 of 359
wpf-tutorial.com
Page 87 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.Grid"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Grid" Height="300" Width="300">
<Grid>
<Button>Button 1</Button>
<Button>Button 2</Button>
</Grid>
</Window>
As you can see, the last control gets the top position, which in this case means that you can't even see the
first button. Not terribly useful for most situations though, so let's try dividing the space, which is what the
grid does so well. We do that by using ColumnDefinitions and RowDefinitions. In the first example, we'll
stick to columns:
<Window x:Class="WpfTutorialSamples.Panels.Grid"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Grid" Height="300" Width="300">
Page 88 of 359
wpf-tutorial.com
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2</Button>
</Grid>
</Window>
In this example, we have simply divided the available space into two columns, which will share the space
equally, using a "star width" (this will be explained later). On the second button, I use a so-called Attached
property to place the button in the second column (0 is the first column, 1 is the second and so on). I could
have used this property on the first button as well, but it automatically gets assigned to the first column and
the first row, which is exactly what we want here.
As you can see, the controls take up all the available space, which is the default behavior when the grid
arranges its child controls. It does this by setting the HorizontalAlignment and VerticalAlignment on its child
controls to Stretch.
In some situations you may want them to only take up the space they need though and/or control how they
are placed in the Grid. The easiest way to do this is to set the HorizontalAlignment and VerticalAlignment
directly on the controls you wish to manipulate. Here's a modified version of the above example:
<Window x:Class="WpfTutorialSamples.Panels.Grid"
Page 89 of 359
wpf-tutorial.com
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Grid" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button VerticalAlignment="Top" HorizontalAlignment="Center">
Button 1</Button>
<Button Grid.Column="1" VerticalAlignment="Center"
HorizontalAlignment="Right">Button 2</Button>
</Grid>
</Window>
As you can see from the resulting screenshot, the first button is now placed in the top and centered. The
second button is placed in the middle, aligned to the right.
Page 90 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.TabularGrid"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TabularGrid" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2</Button>
<Button Grid.Column="2">Button 3</Button>
<Button Grid.Row="1">Button 4</Button>
<Button Grid.Column="1" Grid.Row="1">Button 5</Button>
<Button Grid.Column="2" Grid.Row="1">Button 6</Button>
<Button Grid.Row="2">Button 7</Button>
<Button Grid.Column="1" Grid.Row="2">Button 8</Button>
<Button Grid.Column="2" Grid.Row="2">Button 9</Button>
</Grid>
</Window>
A total of nine buttons, each placed in their own cell in a grid containing three rows and three columns. We
once again use a star based width, but this time we assign a number as well - the first row and the first
column has a width of 2*, which basically means that it uses twice the amount of space as the rows and
columns with a width of 1* (or just * - that's the same).
You will also notice that I use the Attached properties Grid.Row and Grid.Column to place the controls in
the grid, and once again you will notice that I have omitted these properties on the controls where I want to
use either the first row or the first column (or both). This is essentially the same as specifying a zero. This
saves a bit of typing, but you might prefer to assign them anyway for a better overview - that's totally up to
Page 91 of 359
wpf-tutorial.com
you!
Page 92 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.GridUnits"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridUnits" Height="200" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2 with long text</Button>
<Button Grid.Column="2">Button 3</Button>
</Grid>
</Window>
In this example, the first button has a star width, the second one has its width set to Auto and the last one
has a static width of 100 pixels.
The result can be seen on the screenshot, where the second button only takes exactly the amount of space
it needs to render its longer text, the third button takes exactly the 100 pixels it was promised and the first
button, with the variable width, takes the rest.
Page 93 of 359
wpf-tutorial.com
In a Grid where one or several columns (or rows) have a variable (star) width, they automatically get to
share the width/height not already used by the columns/rows which uses an absolute or Auto width/height.
This becomes more obvious when we resize the window:
On the first screenshot, you will see that the Grid reserves the space for the last two buttons, even though it
means that the first one doesn't get all the space it needs to render properly. On the second screenshot,
you will see the last two buttons keeping the exact same amount of space, leaving the surplus space to the
first button.
This can be a very useful technique when designing a wide range of dialogs. For instance, consider a
simple contact form where the user enters a name, an e-mail address and a comment. The first two fields
will usually have a fixed height, while the last one might as well take up as much space as possible, leaving
room to type a longer comment. In the next chapter, we will try building a contact form, using the grid and
rows and columns of different heights and widths.
Page 94 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.GridColRowSpan"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridColRowSpan" Height="110" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2</Button>
<Button Grid.Row="1" Grid.ColumnSpan="2">Button 3</Button>
</Grid>
</Window>
We just define two columns and two rows, all of them taking up their equal share of the place. The first two
buttons just use the columns normally, but with the third button, we make it take up two columns of space
on the second row, using the ColumnSpan attribute.
This is all so simple that we could have just used a combination of panels to achieve the same effect, but
for just slightly more advanced cases, this is really useful. Let's try something which better shows how
powerful this is:
Page 95 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.GridColRowSpanAdvanced"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridColRowSpanAdvanced" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.ColumnSpan="2">Button 1</Button>
<Button Grid.Column="3">Button 2</Button>
<Button Grid.Row="1">Button 3</Button>
<Button Grid.Column="1" Grid.Row="1" Grid.RowSpan="2"
Grid.ColumnSpan="2">Button 4</Button>
<Button Grid.Column="0" Grid.Row="2">Button 5</Button>
</Grid>
</Window>
Page 96 of 359
wpf-tutorial.com
With three columns and three rows we would normally have nine cells, but in this example, we use a
combination of row and column spanning to fill all the available space with just five buttons. As you can
see, a control can span either extra columns, extra rows or in the case of button 4: both.
So as you can see, spanning multiple columns and/or rows in a Grid is very easy. In a later article, we will
use the spanning, along with all the other Grid techniques in a more practical example.
Page 97 of 359
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.GridSplitterSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridSplitterSample" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock FontSize="55" HorizontalAlignment="Center"
VerticalAlignment="Center" TextWrapping="Wrap">Left side</TextBlock>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment
="Stretch" />
<TextBlock Grid.Column="2" FontSize="55" HorizontalAlignment
="Center" VerticalAlignment="Center" TextWrapping="Wrap">Right side</
TextBlock>
</Grid>
</Window>
Page 98 of 359
wpf-tutorial.com
As you can see, I've simply created a Grid with two equally wide columns, with a 5 pixel column in the
middle. Each of the sides are just a TextBlock control to illustrate the point. As you can see from the
screenshots, the GridSplitter is rendered as a dividing line between the two columns and as soon as the
mouse is over it, the cursor is changed to reflect that it can be resized.
wpf-tutorial.com
The GridSplitter is very easy to use and of course it supports horizontal splits as well. In fact, you hardly
have to change anything to make it work horizontally instead of vertically, as the next example will show:
<Window x:Class="WpfTutorialSamples.Panels.GridSplitterHorizontalSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridSplitterHorizontalSample" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="5" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock FontSize="55" HorizontalAlignment="Center"
VerticalAlignment="Center" TextWrapping="Wrap">Top</TextBlock>
<GridSplitter Grid.Row="1" Height="5" HorizontalAlignment
="Stretch" />
<TextBlock Grid.Row="2" FontSize="55" HorizontalAlignment
="Center" VerticalAlignment="Center" TextWrapping="Wrap">Bottom</
TextBlock>
</Grid>
</Window>
As you can see, I simply changed the columns into rows and on the GridSplitter, I defined a Height instead
of a Width. The GridSplitter figures out the rest on its own, but in case it doesn't, you can use the
ResizeDirection property on it to force it into either Rows or Columns mode.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Panels.GridContactForm"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridContactForm" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox>Name</TextBox>
<TextBox Grid.Row="1">E-mail</TextBox>
<TextBox Grid.Row="2" AcceptsReturn="True">Comment</TextBox>
</Grid>
</Window>
As you can see, the last TextBox simply takes up the remaining space, while the first two only takes up the
space they require. Try resizing the window and you will see the comment TextBox resize with it.
Page 101 of 359
wpf-tutorial.com
In this very simple example, there are no labels to designate what each of the fields are for. Instead, the
explanatory text is inside the TextBox, but this is not generally how a Windows dialog looks. Let's try
improving the look and usability a bit:
<Window x:Class="WpfTutorialSamples.Panels.GridContactFormTake2"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridContactFormTake2" Height="300" Width="300">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label>Name: </Label>
<TextBox Grid.Column="1" Margin="0,0,0,10" />
<Label Grid.Row="1">E-mail: </Label>
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" />
<Label Grid.Row="2">Comment: </Label>
<TextBox Grid.Row="2" Grid.Column="1" AcceptsReturn="True" />
</Grid>
</Window>
wpf-tutorial.com
But perhaps you're in a situation where the comment field is pretty self-explanatory? In that case, let's skip
the label and use ColumnSpan to get even more space for the comment TextBox:
So as you can see, the Grid is a very powerful panel. Hopefully you can use all of these techniques when
designing your own dialogs.
wpf-tutorial.com
1.8.1.1. Summary
In the next chapter, we'll look into a simple example where data binding is used and after that, we'll talk
some more about all the possibilities. The concept of data binding is included pretty early in this tutorial,
because it's such an integral part of using WPF, which you will see once you explore the rest of the
chapters, where it's used almost all of the time.
However, the more theoretical part of data binding might be too heavy if you just want to get started
building a simple WPF application. In that case I suggest that you have a look at the "Hello, bound world!"
article to get a glimpse of how data binding works, and then save the rest of the data binding articles for
later, when you're ready to get some more theory.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.DataBinding.HelloBoundWorldSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="HelloBoundWorldSample" Height="110" Width="280">
<StackPanel Margin="10">
<TextBox Name="txtValue" />
<WrapPanel Margin="0,10">
<TextBlock Text="Value: " FontWeight="Bold" />
<TextBlock Text="{Binding Path=Text,
ElementName=txtValue}" />
</WrapPanel>
</StackPanel>
</Window>
This simple example shows how we bind the value of the TextBlock to match the Text property of the
TextBox. As you can see from the screenshot, the TextBlock is automatically updated when you enter text
into the TextBox. In a non-bound world, this would require us to listen to an event on the TextBox and then
update the TextBlock each time the text changes, but with data binding, this connection can be established
just by using markup.
wpf-tutorial.com
This simply returns the current data context (more about that later). This can definitely be useful, but in the
most common situations, you would want to bind a property to another property on the data context. A
binding like that would look like this:
{Binding Path=NameOfProperty}
The Path notes the property that you want to bind to, however, since Path is the default property of a
binding, you may leave it out if you want to, like this:
{Binding NameOfProperty}
You will see many different examples, some of them where Path is explicitly defined and some where it's
left out. In the end it's really up to you though.
A binding has many other properties though, one of them being the ElementName which we use in our
example. This allows us to connect directly to another UI element as the source. Each property that we set
in the binding is separated by a comma:
{Binding Path=Text, ElementName=txtValue}
1.8.2.2. Summary
This was just a glimpse of all the binding possibilities of WPF. In the next chapters, we'll discover more of
them, to show you just how powerful data binding is.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.DataBinding.DataContextSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="130" Width="280">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Text="{Binding Title,
UpdateSourceTrigger=PropertyChanged}" Width="150" />
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="Window dimensions: " />
<TextBox Text="{Binding Width}" Width="50" />
<TextBlock Text=" x " />
<TextBox Text="{Binding Height}" Width="50" />
</WrapPanel>
</StackPanel>
</Window>
using System;
using System.Windows;
namespace WpfTutorialSamples.DataBinding
{
public partial class DataContextSample : Window
{
public DataContextSample()
{
InitializeComponent();
this.DataContext = this;
Page 107 of 359
wpf-tutorial.com
}
}
}
The Code-behind for this example only adds one line of interesting code: After the standard
InitalizeComponent() call, we assign the "this" reference to the DataContext, which basically just tells the
Window that we want itself to be the data context.
In the XAML, we use this fact to bind to several of the Window properties, including Title, Width and
Height. Since the window has a DataContext, which is passed down to the child controls, we don't have to
define a source on each of the bindings - we just use the values as if they were globally available.
Try running the example and resize the window - you will see that the dimension changes are immediately
reflected in the textboxes. You can also try writing a different title in the first textbox, but you might be
surprised to see that this change is not reflected immediately. Instead, you have to move the focus to
another control before the change is applied. Why? Well, that's the subject for the next chapter.
1.8.3.1. Summary
Using the DataContext property is like setting the basis of all bindings down through the hierarchy of
controls. This saves you the hassle of manually defining a source for each binding, and once you really
start using data bindings, you will definitely appreciate the time and typing saved.
However, this doesn't mean that you have to use the same DataContext for all controls within a Window.
Since each control has its own DataContext property, you can easily break the chain of inheritance and
override the DataContext with a new value. This allows you to do stuff like having a global DataContext on
the window and then a more local and specific DataContext on e.g. a panel holding a separate form or
something along those lines.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.DataBinding.DataContextSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="130" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title,
UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click
="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="Window dimensions: " />
<TextBox Text="{Binding Width,
UpdateSourceTrigger=LostFocus}" Width="50" />
<TextBlock Text=" x " />
<TextBox Text="{Binding Height,
UpdateSourceTrigger=PropertyChanged}" Width="50" />
</WrapPanel>
</StackPanel>
</Window>
using System;
Page 109 of 359
wpf-tutorial.com
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfTutorialSamples.DataBinding
{
public partial class DataContextSample : Window
{
public DataContextSample()
{
InitializeComponent();
this.DataContext = this;
}
private void btnUpdateSource_Click(object sender,
RoutedEventArgs e)
{
BindingExpression binding =
txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
}
As you can see, each of the three textboxes now uses a different UpdateSourceTrigger.
The first one is set to Explicit, which basically means that the source won't be updated unless you
manually do it. For that reason, I have added a button next to the TextBox, which will update the source
value on demand. In the Code-behind, you will find the Click handler, where we use a couple of lines of
code to get the binding from the destination control and then call the UpdateSource() method on it.
The second TextBox uses the LostFocus value, which is actually the default for a Text binding. It means
that the source value will be updated each time the destination control loses focus.
wpf-tutorial.com
The third and last TextBox uses the PropertyChanged value, which means that the source value will be
updated each time the bound property changes, which it does in this case as soon as the text changes.
Try running the example on your own machine and see how the three textboxes act completely different:
The first value doesn't update before you click the button, the second value isn't updated until you leave the
TextBox, while the third value updates automatically on each keystroke, text change etc.
1.8.4.1. Summary
The UpdateSourceTrigger property of a binding controls how and when a changed value is sent back to
the source. However, since WPF is pretty good at controlling this for you, the default value should suffice
for most cases, where you will get the best mix of a constantly updated UI and good performance.
For those situations where you need more control of the process, this property will definitely help though.
Just make sure that you don't update the source value more often than you actually need to. If you want the
full control, you can use the Explicit value and then do the updates manually, but this does take a bit of the
fun out of working with data bindings.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.DataBinding.ChangeNotificationSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ChangeNotificationSample" Height="150" Width="300">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add
user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click"
Margin="0,5">Change user</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">
Delete user</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.DataBinding
wpf-tutorial.com
{
public partial class ChangeNotificationSample : Window
{
private List<User> users = new List<User>();
public ChangeNotificationSample()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs
e)
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender,
RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
(lbUsers.SelectedItem as User).Name = "Random Name";
}
private void btnDeleteUser_Click(object sender,
RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
users.Remove(lbUsers.SelectedItem as User);
}
}
public class User
{
public string Name { get; set; }
}
}
wpf-tutorial.com
Try running it for yourself and watch how even though you add something to the list or change the name of
one of the users, nothing in the UI is updated. The example is pretty simple, with a User class that will
keep the name of the user, a ListBox to show them in and some buttons to manipulate both the list and its
contents. The ItemsSource of the list is assigned to a quick list of a couple of users that we create in the
window constructor. The problem is that none of the buttons seems to work. Let's fix that, in two easy
steps.
<Window x:Class
="WpfTutorialSamples.DataBinding.ChangeNotificationSample"
xmlns
wpf-tutorial.com
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ChangeNotificationSample" Height="135" Width="300">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add
user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click"
Margin="0,5">Change user</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">
Delete user</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
</Window>
using
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.ComponentModel;
System.Collections.ObjectModel;
namespace WpfTutorialSamples.DataBinding
{
public partial class ChangeNotificationSample : Window
{
private ObservableCollection<User> users = new
ObservableCollection<User>();
public ChangeNotificationSample()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs
e)
wpf-tutorial.com
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender,
RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
(lbUsers.SelectedItem as User).Name = "Random Name";
}
private void btnDeleteUser_Click(object sender,
RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
users.Remove(lbUsers.SelectedItem as User);
}
}
public class User : INotifyPropertyChanged
{
private string name;
public string Name {
get { return this.name; }
set
{
if(this.name != value)
{
this.name = value;
this.NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new
PropertyChangedEventArgs(propName));
wpf-tutorial.com
}
}
}
1.8.5.5. Summary
As you can see, implementing INotifyPropertyChanged is pretty easy, but it does create a bit of extra code
on your classes, and adds a bit of extra logic to your properties. This is the price you will have to pay if you
want to bind to your own classes and have the changes reflected in the UI immediately. Obviously you only
have to call NotifyPropertyChanged in the setter's of the properties that you bind to - the rest can remain
the way they are.
The ObservableCollection on the other hand is very easy to deal with - it simply requires you to use this
specific list type in those situations where you want changes to the source list reflected in a binding
destination.
wpf-tutorial.com
You have a numeric value but you want to show zero values in one way and positive
numbers in another way
You want to check a CheckBox based on a value, but the value is a string like "yes" or "no"
instead of a Boolean value
You have a file size in bytes but you wish to show it as bytes, kilobytes, megabytes or
gigabytes based on how big it is
These are some of the simple cases, but there are many more. For instance, you may want to check a
checkbox based on a Boolean value, but you want it reversed, so that the CheckBox is checked if the value
is false and not checked if the value is true. You can even use a converter to generate an image for an
ImageSource, based on the value, like a green sign for true or a red sign for false - the possibilities are
pretty much endless!
For cases like this, you can use a value converter. These small classes, which implement the
IValueConverter interface, will act like middlemen and translate a value between the source and the
destination. So, in any situation where you need to transform a value before it reaches its destination or
back to its source again, you likely need a converter.
<Window x:Class="WpfTutorialSamples.DataBinding.ConverterSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
wpf-tutorial.com
xmlns:local="clr-namespace:WpfTutorialSamples.DataBinding"
Title="ConverterSample" Height="140" Width="250">
<Window.Resources>
<local:YesNoToBooleanConverter x:Key="YesNoToBooleanConverter"
/>
</Window.Resources>
<StackPanel Margin="10">
<TextBox Name="txtValue" />
<WrapPanel Margin="0,10">
<TextBlock Text="Current value is: " />
<TextBlock Text="{Binding ElementName=txtValue,
Path=Text, Converter={StaticResource YesNoToBooleanConverter}}"></
TextBlock>
</WrapPanel>
<CheckBox IsChecked="{Binding ElementName=txtValue, Path=Text,
Converter={StaticResource YesNoToBooleanConverter}}" Content="Yes" />
</StackPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Data;
namespace WpfTutorialSamples.DataBinding
{
public partial class ConverterSample : Window
{
public ConverterSample()
{
InitializeComponent();
}
}
public class YesNoToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object
parameter, System.Globalization.CultureInfo culture)
{
switch(value.ToString().ToLower())
{
case "yes":
wpf-tutorial.com
case "oui":
return true;
case "no":
case "non":
return false;
}
return false;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if(value is bool)
{
if((bool)value == true)
return "yes";
else
return "no";
}
return "no";
}
}
}
wpf-tutorial.com
1.8.6.3. Code-behind
So, let's start from the back and then work our way through the example. We have implemented a
converter in the Code-behind file called YesNoToBooleanConverter. As advertised, it just implements the
two required methods, called Convert() and ConvertBack(). The Convert() methods assumes that it
receives a string as the input (the value parameter) and then converts it to a Boolean true or false value,
with a fallback value of false. For fun, I added the possibility to do this conversion from French words as
well.
The ConvertBack() method obviously does the opposite: It assumes an input value with a Boolean type
and then returns the English word "yes" or "no" in return, with a fallback value of "no".
You may wonder about the additional parameters that these two methods take, but they're not needed in
this example. We'll use them in one of the next chapters, where they will be explained.
1.8.6.4. XAML
In the XAML part of the program, we start off by declaring an instance of our converter as a resource for
the window. We then have a TextBox, a couple of TextBlocks and a CheckBox control and this is where the
interesting things are happening: We bind the value of the TextBox to the TextBlock and the CheckBox
control and using the Converter property and our own converter reference, we juggle the values back and
forth between a string and a Boolean value, depending on what's needed.
If you try to run this example, you will be able to change the value in two places: By writing "yes" in the
TextBox (or any other value, if you want false) or by checking the CheckBox. No matter what you do, the
change will be reflected in the other control as well as in the TextBlock.
1.8.6.5. Summary
This was an example of a simple value converter, made a bit longer than needed for illustrational
purposes. In the next chapter we'll look into a more advanced example, but before you go out and write
your own converter, you might want to check if WPF already includes one for the purpose. As of writing,
there are more than 20 built-in converters that you may take advantage of, but you need to know their
name. I found the following list which might come in handy for you:
http://stackoverflow.com/questions/505397/built-in-wpf-ivalueconverters
wpf-tutorial.com
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.DataBinding.StringFormatSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="StringFormatSample" Height="150" Width="250"
Name="wnd">
<StackPanel Margin="10">
<TextBlock Text="{Binding ElementName=wnd, Path=ActualWidth,
StringFormat=Window width: {0:#,#.0}}" />
<TextBlock Text="{Binding ElementName=wnd, Path=ActualHeight,
StringFormat=Window height: {0:C}}" />
<TextBlock Text="{Binding Source={x:Static
system:DateTime.Now}, StringFormat=Date: {0:dddd, MMMM dd}}" />
<TextBlock Text="{Binding Source={x:Static
system:DateTime.Now}, StringFormat=Time: {0:HH:mm}}" />
</StackPanel>
</Window>
wpf-tutorial.com
The first couple of TextBlock's gets their value by binding to the parent Window and getting its width and
height. Through the StringFormat property, the values are formatted. For the width, we specify a custom
formatting string and for the height, we ask it to use the currency format, just for fun. The value is saved as
a double type, so we can use all the same format specifiers as if we had called double.ToString(). You can
find a list of them here: http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx
Also notice how I can include custom text in the StringFormat - this allows you to pre/post-fix the bound
value with text as you please. When referencing the actual value inside the format string, we surround it by
a set of curly braces, which includes two values: A reference to the value we want to format (value number
0, which is the first possible value) and the format string, separated by a colon.
For the last two values, we simply bind to the current date (DateTime.Now) and the output it first as a date,
in a specific format, and then as the time (hours and minutes), again using our own, pre-defined format.
You can read more about DateTime formatting here: http://msdn.microsoft.com/enus/library/az4se3k1.aspx
<Window x:Class="WpfTutorialSamples.DataBinding.StringFormatSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="StringFormatSample" Height="150" Width="250"
Name="wnd">
<WrapPanel Margin="10">
<TextBlock Text="Width: " />
<TextBlock Text="{Binding ElementName=wnd, Path=ActualWidth,
StringFormat={}{0:#,#.0}}" />
</WrapPanel>
</Window>
1.8.7.2. Using a specific Culture
If you need to output a bound value in accordance with a specific culture, that's no problem. The Binding
will use the language specified for the parent element, or you can specify it directly for the binding, using
the ConverterCulture property. Here's an example:
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.DataBinding.StringFormatCultureSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="StringFormatCultureSample" Height="120" Width="300">
<StackPanel Margin="10">
<TextBlock Text="{Binding Source={x:Static
system:DateTime.Now}, ConverterCulture='de-DE', StringFormat=German
date: {0:D}}" />
<TextBlock Text="{Binding Source={x:Static
system:DateTime.Now}, ConverterCulture='en-US', StringFormat=American
date: {0:D}}" />
<TextBlock Text="{Binding Source={x:Static
system:DateTime.Now}, ConverterCulture='ja-JP', StringFormat=Japanese
date: {0:D}}" />
</StackPanel>
</Window>
It's pretty simple: By combining the StringFormat property, which uses the D specifier (Long date pattern)
and the ConverterCulture property, we can output the bound values in accordance with a specific culture.
Pretty nifty!
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.DataBinding.DataBindingDebuggingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBindingDebuggingSample" Height="100" Width="200">
<Grid Margin="10" Name="pnlMain">
<TextBlock Text="{Binding NonExistingProperty,
ElementName=pnlMain}" />
</Grid>
</Window>
1.8.8.1. The Output window
The first place you will want to look is the Visual Studio Output window. It should be at the bottom of your
Visual Studio window, or you can activate it by using the [Ctrl+Alt+O] shortcut. There will be loads of output
from the debugger, but somewhere you should find a line like this, when running the above example:
This might seem a bit overwhelming, mainly because no linebreaks are used in this long message, but the
important part is this:
'NonExistingProperty' property not found on 'object' ''Grid' (Name='pnlMain')'.
It tells you that you have tried to use a property called "NonExistingProperty" on an object of the type Grid,
with the name pnlMain. That's actually pretty concise and should help you correct the name of the property
or bind to the real object, if that's the problem.
wpf-tutorial.com
The above example was easy to fix, because it was clear to WPF what we were trying to do and why it
didn't work. Consider this next example though:
<Window x:Class
="WpfTutorialSamples.DataBinding.DataBindingDebuggingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBindingDebuggingSample" Height="100" Width="200">
<Grid Margin="10">
<TextBlock Text="{Binding Title}" />
</Grid>
</Window>
I'm trying to bind to the property "Title", but on which object? As stated in the article on data contexts, WPF
will use the DataContext property on the TextBlock here, which may be inherited down the control
hierarchy, but in this example, I forgot to assign a data context. This basically means that I'm trying to get a
property on a NULL object. WPF will gather that this might be a perfectly valid binding, but that the object
just hasn't been initialized yet, and therefore it won't complain about it. If you run this example and look in
the Output window, you won't see any binding errors.
However, for the cases where this is not the behavior that you're expecting, there is a way to force WPF
into telling you about all the binding problems it runs into. It can be done by setting the TraceLevel on the
PresentationTraceSources object, which can be found in the System.Diagnostics namespace:
<Window x:Class
="WpfTutorialSamples.DataBinding.DataBindingDebuggingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diag="clrnamespace:System.Diagnostics;assembly=WindowsBase"
Title="DataBindingDebuggingSample" Height="100" Width="200">
<Grid Margin="10">
<TextBlock Text="{Binding Title,
diag:PresentationTraceSources.TraceLevel=High}" />
</Grid>
</Window>
Notice that I have added a reference to the System.Diagnostics namespace in the top, and then used the
property on the binding. WPF will now give you loads of information about this specific binding in the
Output window:
wpf-tutorial.com
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.DataBinding.DataBindingDebuggingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:WpfTutorialSamples.DataBinding"
Title="DataBindingDebuggingSample" Name="wnd" Height="100"
Width="200">
<Window.Resources>
<self:DebugDummyConverter x:Key="DebugDummyConverter" />
</Window.Resources>
<Grid Margin="10">
<TextBlock Text="{Binding Title, ElementName=wnd,
Converter={StaticResource DebugDummyConverter}}" />
</Grid>
</Window>
using
using
using
using
System;
System.Windows;
System.Windows.Data;
System.Diagnostics;
wpf-tutorial.com
namespace WpfTutorialSamples.DataBinding
{
public partial class DataBindingDebuggingSample : Window
{
public DataBindingDebuggingSample()
{
InitializeComponent();
}
}
public class DebugDummyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object
parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
}
}
In the Code-behind file, we define a DebugDummyConverter. In the Convert() and ConvertBack()
methods, we call Debugger.Break(), which has the same effect as setting a breakpoint in Visual Studio, and
then return the value that was given to us untouched.
In the markup, we add a reference to our converter in the window resources and then we use it in our
binding. In a real world application, you should define the converter in a file of its own and then add the
reference to it in App.xaml, so that you may use it all over the application without having to create a new
reference to it in each window, but for this example, the above should do just fine.
If you run the example, you will see that the debugger breaks as soon as WPF tries to fetch the value for
the title of the window. You can now inspect the values given to the Convert() method, or even change
them before proceeding, using the standard debugging capabilities of Visual Studio.
If the debugger never breaks, it means that the converter is not used. This usually indicates that you have
Page 130 of 359
wpf-tutorial.com
an invalid binding expression, which can be diagnosed and fixed using the methods described in the start
of this article. The dummy-converter trick is only for testing valid binding expressions.
wpf-tutorial.com
1.9. Commands
1.9.1. Introduction to WPF Commands
In a previous chapter of this tutorial, we talked about how to handle events, e.g. when the user clicks on a
button or a menu item. In a modern user interface, it's typical for a function to be reachable from several
places though, invoked by different user actions.
For instance, if you have a typical interface with a main menu and a set of toolbars, an action like New or
Open might be available in the menu, on the toolbar, in a context menu (e.g. when right clicking in the main
application area) and from a keyboard shortcut like Ctrl+N and Ctrl+O.
Each of these actions needs to perform what is typically the exact same piece of code, so in a WinForms
application, you would have to define an event for each of them and then call a common function. With the
above example, that would lead to at least three event handlers and some code to handle the keyboard
shortcut. Not an ideal situation.
1.9.1.1. Commands
With WPF, Microsoft is trying to remedy that with a concept called commands. It allows you to define
actions in one place and then refer to them from all your user interface controls like menu items, toolbar
buttons and so on. WPF will also listen for keyboard shortcuts and pass them along to the proper
command, if any, making it the ideal way to offer keyboard shortcuts in an application.
Commands also solve another hassle when dealing with multiple entrances to the same function. In a
WinForms application, you would be responsible for writing code that could disable user interface elements
when the action was not available. For instance, if your application was able to use a clipboard command
like Cut, but only when text was selected, you would have to manually enable and disable the main menu
item, the toolbar button and the context menu item each time text selection changed.
With WPF commands, this is centralized. With one method you decide whether or not a given command
can be executed, and then WPF toggles all the subscribing interface elements on or off automatically. This
makes it so much easier to create a responsive and dynamic application!
wpf-tutorial.com
Command that it handles, as well as the actual event handlers for dealing with the Execute() and
CanExecute() events of the Command.
1.9.1.4. Summary
Commands help you to respond to a common action from several different sources, using a single event
handler. It also makes it a lot easier to enable and disable user interface elements based on the current
availability and state. This was all theory, but in the next chapters we'll discuss how commands are used
and how you define your own custom commands.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Commands.UsingCommandsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UsingCommandsSample" Height="100" Width="200">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed
="NewCommand_Executed" CanExecute="NewCommand_CanExecute" />
</Window.CommandBindings>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
>
<Button Command="ApplicationCommands.New">New</Button>
</StackPanel>
</Window>
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.Windows.Input;
namespace WpfTutorialSamples.Commands
{
public partial class UsingCommandsSample : Window
{
public UsingCommandsSample()
{
InitializeComponent();
}
private void NewCommand_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
wpf-tutorial.com
e.CanExecute = true;
}
private void NewCommand_Executed(object sender,
ExecutedRoutedEventArgs e)
{
MessageBox.Show("The New command was invoked");
}
}
}
We define a command binding on the Window, by adding it to its CommandBindings collection. We specify
that Command that we wish to use (the New command from the ApplicationCommands), as well as two
event handlers. The visual interface consists of a single button, which we attach the command to using the
Command property.
In Code-behind, we handle the two events. The CanExecute handler, which WPF will call when the
application is idle to see if the specific command is currently available, is very simple for this example, as
we want this particular command to be available all the time. This is done by setting the CanExecute
property of the event arguments to true.
The Executed handler simply shows a message box when the command is invoked. If you run the sample
and press the button, you will see this message. A thing to notice is that this command has a default
keyboard shortcut defined, which you get as an added bonus. Instead of clicking the button, you can try to
press Ctrl+N on your keyboard - the result is the same.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Commands.CommandCanExecuteSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CommandCanExecuteSample" Height="200" Width="250">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Cut" CanExecute
="CutCommand_CanExecute" Executed="CutCommand_Executed" />
<CommandBinding Command="ApplicationCommands.Paste" CanExecute
="PasteCommand_CanExecute" Executed="PasteCommand_Executed" />
</Window.CommandBindings>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="ApplicationCommands.Cut" Width="60">_Cut
</Button>
<Button Command="ApplicationCommands.Paste" Width="60"
Margin="3,0">_Paste</Button>
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
</Window>
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.Windows.Input;
namespace WpfTutorialSamples.Commands
{
public partial class CommandCanExecuteSample : Window
{
public CommandCanExecuteSample()
{
InitializeComponent();
}
private void CutCommand_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = (txtEditor != null) &&
(txtEditor.SelectionLength > 0);
wpf-tutorial.com
}
private void CutCommand_Executed(object sender,
ExecutedRoutedEventArgs e)
{
txtEditor.Cut();
}
private void PasteCommand_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = Clipboard.ContainsText();
}
private void PasteCommand_Executed(object sender,
ExecutedRoutedEventArgs e)
{
txtEditor.Paste();
}
}
}
So, we have this very simple interface with a couple of buttons and a TextBox control. The first button will
cut to the clipboard and the second one will paste from it.
In Code-behind, we have two events for each button: One that performs the actual action, which name
ends with _Executed, and then the CanExecute events. In each of them, you will see that I apply some
logic to decide whether or not the action can be executed and then assign it to the return value
CanExecute on the EventArgs.
wpf-tutorial.com
The cool thing about this is that you don't have to call these methods to have your buttons updated - WPF
does it automatically when the application has an idle moment, making sure that you interface remains
updated all the time.
<Window x:Class
="WpfTutorialSamples.Commands.CommandsWithCommandTargetSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CommandsWithCommandTargetSample" Height="200" Width
="250">
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="ApplicationCommands.Cut" CommandTarget="
{Binding ElementName=txtEditor}" Width="60">_Cut</Button>
<Button Command="ApplicationCommands.Paste" CommandTarget
="{Binding ElementName=txtEditor}" Width="60" Margin="3,0">_Paste</
Button>
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
</Window>
No Code-behind code needed for this example - WPF deals with all of it for us, but only because we want
to use these specific commands for this specific control. The TextBox does the work for us.
Notice how I use the CommandTarget properties on the buttons, to bind the commands to our TextBox
control. This is required in this particular example, because the WrapPanel doesn't handle focus the same
way e.g. a Toolbar or a Menu would, but it also makes pretty good sense to give the commands a target.
Page 138 of 359
wpf-tutorial.com
1.9.2.3. Summary
Dealing with commands is pretty straight forward, but does involve a bit extra markup and code. The
reward is especially obvious when you need to invoke the same action from multiple places though, or
when you use built-in commands that WPF can handle completely for you, as we saw in the last example.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Commands.CustomCommandSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:WpfTutorialSamples.Commands"
Title="CustomCommandSample" Height="150" Width="200">
<Window.CommandBindings>
<CommandBinding Command="self:CustomCommands.Exit" CanExecute
="ExitCommand_CanExecute" Executed="ExitCommand_Executed" />
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="File">
<MenuItem Command="self:CustomCommands.Exit" />
</MenuItem>
</Menu>
<StackPanel Grid.Row="1" HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button Command="self:CustomCommands.Exit">Exit</Button>
</StackPanel>
</Grid>
</Window>
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.Windows.Input;
wpf-tutorial.com
namespace WpfTutorialSamples.Commands
{
public partial class CustomCommandSample : Window
{
public CustomCommandSample()
{
InitializeComponent();
}
private void ExitCommand_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void ExitCommand_Executed(object sender,
ExecutedRoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
public static class CustomCommands
{
public static readonly RoutedUICommand Exit = new
RoutedUICommand
(
"Exit",
"Exit",
typeof(CustomCommands),
new InputGestureCollection()
{
new KeyGesture(Key.F4, ModifierKeys.Alt)
}
);
//Define more commands here, just like the one above
}
}
wpf-tutorial.com
In the markup, I've defined a very simple interface with a menu and a button, both of them using our new,
custom Exit command. This command is defined in Code-behind, in our own CustomCommands class,
and then referenced in the CommandBindings collection of the window, where we assign the events that it
should use to execute/check if it's allowed to execute.
All of this is just like the examples in the previous chapter, except for the fact that we're referencing the
command from our own code (using the "self" namespace defined in the top) instead of a built-in command.
In Code-behind, we respond to the two events for our command: One event just allows the command to
execute all the time, since that's usually true for an exit/quit command, and the other one calls the
Shutdown method that will terminate our application. All very simple.
As already explained, we implement our Exit command as a field on a static CustomCommands class.
There are several ways of defining and assigning properties on the commands, but I've chosen the more
compact approach (it would be even more compact if placed on the same line, but I've added line breaks
here for readability) where I assign all of it through the constructor. The parameters are the text/label of the
command, the name of the command, the owner type and then an InputGestureCollection, allowing me to
define a default shortcut for the command (Alt+F4).
1.9.3.1. Summary
Implementing custom WPF commands is almost as easy as consuming the built-in commands, and it
allows you to use commands for every purpose in your application. This makes it very easy to re-use
actions in several places, as shown in the example of this chapter.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.MenuSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MenuSample" Height="200" Width="200">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New" />
<MenuItem Header="_Open" />
<MenuItem Header="_Save" />
<Separator />
<MenuItem Header="_Exit" />
</MenuItem>
</Menu>
<TextBox AcceptsReturn="True" />
</DockPanel>
</Window>
As in most Windows applications, my menu is placed in the top of the window, but in keeping with the
enormous flexibility of WPF, you can actually place a Menu control wherever you like, and in any width or
height that you may desire.
I have defined a single top-level item, with 4 child items and a separator. I use the Header property to
define the label of the item, and you should notice the underscore before the first character of each label. It
Page 143 of 359
wpf-tutorial.com
tells WPF to use that character as the accelerator key, which means that the user can press the Alt key
followed by the given character, to activate the menu item. This works all the way from the top-level item
and down the hierarchy, meaning that in this example I could press Alt, then F and then N, to activate the
New item.
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.MenuIconCheckableSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MenuIconCheckableSample" Height="150" Width="300">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Exit" />
</MenuItem>
<MenuItem Header="_Tools">
<MenuItem Header="_Manage users">
<MenuItem.Icon>
<Image Source
="/WpfTutorialSamples;component/Images/user.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Show groups" IsCheckable="True"
Page 144 of 359
wpf-tutorial.com
IsChecked="True" />
</MenuItem>
</Menu>
<TextBox AcceptsReturn="True" />
</DockPanel>
</Window>
For this example I've created a secondary top-level item, where I've added two items: One with an icon
defined, using the Icon property with a standard Image control inside of it, and one where we use the
IsCheckable property to allow the user to check and uncheck the item. I even used the IsChecked
property to have it checked by default. From Code-behind, this is the same property that you can read to
know whether a given menu item is checked or not.
wpf-tutorial.com
category of articles here on the site, but for now, I can tell you that they have a couple of advantages when
used in WPF, especially in combination with a Menu or a Toolbar.
First of all, they ensure that you can have the same action on a toolbar, a menu and even a context menu,
without having to implement the same code in multiple places. They also make the handling of keyboard
shortcuts a whole lot easier, because unlike with WinForms, WPF is not listening for keyboard shortcuts
automatically if you assign them to e.g. a menu item - you will have to do that manually.
However, when using commands, WPF is all ears and will respond to keyboard shortcuts automatically.
The text (Header) of the menu item is also set automatically (although you can overwrite it if needed), and
so is the InputGestureText, which shows the user which keyboard shortcut can be used to invoke the
specific menu item. Let's jump straight to an example of combining the Menu with WPF commands:
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.MenuWithCommandsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MenuWithCommandsSample" Height="200" Width="300">
<Window.CommandBindings>
<CommandBinding Command="New" CanExecute
="NewCommand_CanExecute" Executed="NewCommand_Executed" />
</Window.CommandBindings>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="New" />
<Separator />
<MenuItem Header="_Exit" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Command="Cut" />
<MenuItem Command="Copy" />
<MenuItem Command="Paste" />
</MenuItem>
</Menu>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
</Window>
using System;
Page 146 of 359
wpf-tutorial.com
using System.Windows;
using System.Windows.Input;
namespace WpfTutorialSamples.Common_interface_controls
{
public partial class MenuWithCommandsSample : Window
{
public MenuWithCommandsSample()
{
InitializeComponent();
}
private void NewCommand_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void NewCommand_Executed(object sender,
ExecutedRoutedEventArgs e)
{
txtEditor.Text = "";
}
}
}
It might not be completely obvious, but by using commands, we just got a whole bunch of things for free:
Keyboard shortcuts, text and InputGestureText on the items and WPF automatically enables/disables the
items depending on the active control and its state. In this case, Cut and Copy are disabled because no
text is selected, but Paste is enabled, because my clipboard is not empty!
Page 147 of 359
wpf-tutorial.com
And because WPF knows how to handle certain commands in combination with certain controls, in this
case the Cut/Copy/Paste commands in combination with a text input control, we don't even have to handle
their Execute events - they work right out of the box! We do have to handle it for theNew command though,
since WPF has no way of guessing what we want it to do when the user activates it. This is done with the
CommandBindings of the Window, all explained in detail in the chapter on commands.
1.10.1.4. Summary
Working with the WPF Menu control is both easy and fast, making it simple to create even complex menu
hierarchies, and when combining it with WPF commands, you get so much functionality for free.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.ContextMenuSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ContextMenuSample" Height="250" Width="250">
<Grid>
<Button Content="Right-click me!" VerticalAlignment="Center"
HorizontalAlignment="Center">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Menu item 1" />
<MenuItem Header="Menu item 2" />
<Separator />
<MenuItem Header="Menu item 3" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</Grid>
</Window>
If you've already read the chapter on the regular menu, you will soon realize that the ContextMenu works
exactly the same way, and no wonder, since they both inherit the MenuBase class. Just like we saw in the
examples on using the regular Menu, you can of course add Click events to these items to handle when the
user clicks on them, but a more WPF-suitable way is to use Commands.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.ContextMenuWithCommandsSa
mple"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ContextMenuWithCommandsSample" Height="200" Width="250"
>
<StackPanel Margin="10">
<TextBox Text="Right-click here for context menu!">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="Cut">
<MenuItem.Icon>
<Image Source
="/WpfTutorialSamples;component/Images/cut.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="Copy">
<MenuItem.Icon>
<Image Source
="/WpfTutorialSamples;component/Images/copy.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="Paste">
Page 150 of 359
wpf-tutorial.com
<MenuItem.Icon>
<Image Source
="/WpfTutorialSamples;component/Images/paste.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</StackPanel>
</Window>
Try running the example and see for yourself how much functionality we get for free by assigning
commands to the items. Also notice how fairly simple it is to use icons on the menu items of the
ContextMenu.
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.ContextMenuManuallyInvoke
dSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ContextMenuManuallyInvokedSample" Height="250" Width
="250">
Page 151 of 359
wpf-tutorial.com
<Window.Resources>
<ContextMenu x:Key="cmButton">
<MenuItem Header="Menu item 1" />
<MenuItem Header="Menu item 2" />
<Separator />
<MenuItem Header="Menu item 3" />
</ContextMenu>
</Window.Resources>
<Grid>
<Button Content="Click me!" VerticalAlignment="Center"
HorizontalAlignment="Center" Click="Button_Click" />
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfTutorialSamples.Common_interface_controls
{
public partial class ContextMenuManuallyInvokedSample : Window
{
public ContextMenuManuallyInvokedSample()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ContextMenu cm = this.FindResource("cmButton") as
ContextMenu;
cm.PlacementTarget = sender as Button;
cm.IsOpen = true;
}
}
}
The first thing you should notice is that I've moved the ContextMenu away from the button. Instead, I've
added it as a resource of the Window, to make it available from all everywhere within the Window. This also
makes it a lot easier to find when we need to show it.
wpf-tutorial.com
The Button now has a Click event handler, which I handle in Code-behind. From there, I simply find the
ContextMenu instance within the window resources and then I do two things: I set it's PlacementTarget
property, which tells WPF which element it should calculate the position based on, and then I set the
IsOpen to true, to open the menu. That's all you need!
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.ToolbarSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ToolbarSample" Height="200" Width="300">
<Window.CommandBindings>
<CommandBinding Command="New" CanExecute
="CommonCommandBinding_CanExecute" />
<CommandBinding Command="Open" CanExecute
="CommonCommandBinding_CanExecute" />
<CommandBinding Command="Save" CanExecute
="CommonCommandBinding_CanExecute" />
</Window.CommandBindings>
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="New" Content="New" />
<Button Command="Open" Content="Open" />
<Button Command="Save" Content="Save" />
</ToolBar>
<ToolBar>
<Button Command="Cut" Content="Cut" />
<Button Command="Copy" Content="Copy" />
<Button Command="Paste" Content="Paste" />
</ToolBar>
</ToolBarTray>
<TextBox AcceptsReturn="True" />
</DockPanel>
</Window>
wpf-tutorial.com
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfTutorialSamples.Common_interface_controls
{
public partial class ToolbarSample : Window
{
public ToolbarSample()
{
InitializeComponent();
}
private void CommonCommandBinding_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
}
Notice how I use commands for all the buttons. We discussed this in the previous chapter and using
commands definitely gives us some advantages. Take a look at the Menu chapter, or the articles on
commands, for more information.
In this example, I add a ToolBarTray to the top of the screen, and inside of it, two ToolBar controls. Each
contains some buttons and we use commands to give them their behavior. In Code-behind, I make sure to
handle the CanExecute event of the first three buttons, since that's not done automatically by WPF,
contrary to the Cut, Copy and Paste commands, which WPF is capable of fully handling for us.
wpf-tutorial.com
Try running the example and place the cursor over the left part of one of the toolbars (the dotted area). If
you click and hold your left mouse button, you can now re-position the toolbar, e.g. below the other or even
make them switch place.
1.10.3.1. Images
While text on the toolbar buttons is perfectly okay, the normal approach is to have icons or at least a
combination of an icon and a piece of text. Because WPF uses regular Button controls, adding icons to the
toolbar items is very easy. Just have a look at this next example, where we do both:
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.ToolbarIconSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ToolbarIconSample" Height="200" Width="300">
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="Cut" ToolTip="Cut selection to
Windows Clipboard.">
<Image Source
="/WpfTutorialSamples;component/Images/cut.png" />
</Button>
<Button Command="Copy" ToolTip="Copy selection to
Windows Clipboard.">
<Image Source
="/WpfTutorialSamples;component/Images/copy.png" />
</Button>
<Button Command="Paste" ToolTip="Paste from Windows
Clipboard.">
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/paste.png" />
<TextBlock Margin="3,0,0,0">Paste</
TextBlock>
</StackPanel>
</Button>
</ToolBar>
</ToolBarTray>
<TextBox AcceptsReturn="True" />
</DockPanel>
Page 156 of 359
wpf-tutorial.com
</Window>
By specifying an Image control as the Content of the first two buttons, they will be icon based instead of
text based. On the third button, I combine an Image control and a TextBlock control inside of a
StackPanel, to achieve both icon and text on the button, a commonly used technique for buttons which are
extra important or with a less obvious icon.
Notice how I've used the ToolTip property on each of the buttons, to add an explanatory text. This is
especially important for those buttons with only an icon, because the purpose of the button might not be
clear from only looking at the icon. With the ToolTip property, the user can hover the mouse over the button
to get a description of what it does, as demonstrated on the screenshot.
1.10.3.2. Overflow
As already mentioned, a very good reason for using the ToolBar control instead of just a panel of buttons,
is the automatic overflow handling. It means that if there's no longer enough room to show all of the buttons
on the toolbar, WPF will put them in a menu accessible by clicking on the arrow to the right of the toolbar.
You can see how it works on this screenshot, which shows the first example, but with a smaller window,
thereby leaving less space for the toolbars:
wpf-tutorial.com
WPF even allows you to decide which items are suitable for overflow hiding and which should always be
visible. Usually, when designing a toolbar, some items are less important than the others and some of them
you might even want to have in the overflow menu all the time, no matter if there's space enough or not.
This is where the attached property ToolBar.OverflowMode comes into play. The default value is
IfNeeded, which simply means that a toolbar item is put in the overflow menu if there's not enough room for
it. You may use Always or Never instead, which does exactly what the names imply: Puts the item in the
overflow menu all the time or prevents the item from ever being moved to the overflow menu. Here's an
example on how to assign this property:
<ToolBar>
<Button Command="Cut" Content="Cut" ToolBar.OverflowMode="Always"
/>
<Button Command="Copy" Content="Copy" ToolBar.OverflowMode
="AsNeeded" />
<Button Command="Paste" Content="Paste" ToolBar.OverflowMode
="Never" />
</ToolBar>
1.10.3.3. Position
While the most common position for the toolbar is indeed in the top of the screen, toolbars can also be
found in the bottom of the application window or even on the sides. The WPF ToolBar of course supports
all of this, and while the bottom placed toolbar is merely a matter of docking to the bottom of the panel
instead of the top, a vertical toolbar requires the use of the Orientation property of the ToolBar tray. Allow
me to demonstrate with an example:
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.ToolbarPositionSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ToolbarPositionSample" Height="200" Width="300">
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="Cut" ToolTip="Cut selection to
Windows Clipboard.">
<Image Source
="/WpfTutorialSamples;component/Images/cut.png" />
</Button>
Page 158 of 359
wpf-tutorial.com
The trick here lies in the combination of the DockPanel.Dock property, that puts the ToolBarTray to the
right of the application, and the Orientation property, that changes the orientation from horizontal to
Page 159 of 359
wpf-tutorial.com
vertical. This makes it possible to place toolbars in pretty much any location that you might think of.
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.ToolbarCustomControlsSamp
le"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ToolbarCustomControlsSample" Height="200" Width="300">
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="Cut" ToolTip="Cut selection to
Windows Clipboard.">
<Image Source
="/WpfTutorialSamples;component/Images/cut.png" />
</Button>
<Button Command="Copy" ToolTip="Copy selection to
Windows Clipboard.">
Page 160 of 359
wpf-tutorial.com
<Image Source
="/WpfTutorialSamples;component/Images/copy.png" />
</Button>
<Button Command="Paste" ToolTip="Paste from Windows
Clipboard.">
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/paste.png" />
<TextBlock Margin="3,0,0,0">Paste</
TextBlock>
</StackPanel>
</Button>
<Separator />
<Label>Font size:</Label>
<ComboBox>
<ComboBoxItem>10</ComboBoxItem>
<ComboBoxItem IsSelected="True">12</
ComboBoxItem>
<ComboBoxItem>14</ComboBoxItem>
<ComboBoxItem>16</ComboBoxItem>
</ComboBox>
</ToolBar>
</ToolBarTray>
<TextBox AcceptsReturn="True" />
</DockPanel>
</Window>
1.10.3.5. Summary
Creating interfaces with toolbars is very easy in WPF, with the flexible ToolBar control. You can do things
Page 161 of 359
wpf-tutorial.com
that previously required 3rd party toolbar controls and you can even do it without much extra effort.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.StatusBarSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StatusBarSample" Height="150" Width="300">
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock Name="lblCursorPosition" />
</StatusBarItem>
</StatusBar>
<TextBox AcceptsReturn="True" Name="txtEditor"
SelectionChanged="txtEditor_SelectionChanged" />
</DockPanel>
</Window>
using System;
using System.Windows;
namespace WpfTutorialSamples.Common_interface_controls
{
public partial class StatusBarSample : Window
{
public StatusBarSample()
{
InitializeComponent();
}
private void txtEditor_SelectionChanged(object sender,
RoutedEventArgs e)
{
Page 163 of 359
wpf-tutorial.com
int row =
txtEditor.GetLineIndexFromCharacterIndex(txtEditor.CaretIndex);
int col = txtEditor.CaretIndex txtEditor.GetCharacterIndexFromLineIndex(row);
lblCursorPosition.Text = "Line " + (row + 1) + ", Char "
+ (col + 1);
}
}
}
It's all very simple - a TextBlock control that shows the current cursor position, just like in pretty much any
other application that allows you to edit text. In this very basic form, the StatusBar could just as easily have
been a panel with a set of controls on it, but the real advantage of the StatusBar comes when we need to
divide it into several areas of information.
<Window x:Class
="WpfTutorialSamples.Common_interface_controls.StatusBarAdvancedSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StatusBarAdvancedSample" Height="150" Width="400">
Page 164 of 359
wpf-tutorial.com
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Name="lblCursorPosition" />
</StatusBarItem>
<Separator Grid.Column="1" />
<StatusBarItem Grid.Column="2">
<TextBlock Text="c:\path\of\current\file.txt" />
</StatusBarItem>
<Separator Grid.Column="3" />
<StatusBarItem Grid.Column="4">
<ProgressBar Value="50" Width="90" Height="16" />
</StatusBarItem>
</StatusBar>
<TextBox AcceptsReturn="True" Name="txtEditor"
SelectionChanged="txtEditor_SelectionChanged" />
</DockPanel>
</Window>
using System;
using System.Windows;
namespace WpfTutorialSamples.Common_interface_controls
{
public partial class StatusBarAdvancedSample : Window
{
public StatusBarAdvancedSample()
{
wpf-tutorial.com
InitializeComponent();
}
private void txtEditor_SelectionChanged(object sender,
RoutedEventArgs e)
{
int row =
txtEditor.GetLineIndexFromCharacterIndex(txtEditor.CaretIndex);
int col = txtEditor.CaretIndex txtEditor.GetCharacterIndexFromLineIndex(row);
lblCursorPosition.Text = "Line " + (row + 1) + ", Char "
+ (col + 1);
}
}
}
As you can see, I've added a bit of sample information, like the fake filename in the middle column and the
progress bar to the right, showing a static value for now. You could easily make this work for real though,
and it gives a pretty good idea on what you can do with the StatusBar control.
1.10.4.2. Summary
Once again, WPF makes it easy to get standard Windows functionality, in this case the StatusBar,
integrated into your applications.
You can even place other controls than the ones used in these examples, like buttons, combo boxes and
so on, but please be aware that since the StatusBar doesn't apply any special rendering to these controls
when hosting them, it might not look as you would expect it to for controls in a status bar. This can be
handled with custom styling if you need it though, a subject discussed elsewhere in this tutorial.
wpf-tutorial.com
WPF doesn't come with a built-in Ribbon control, but Microsoft has released one that you can download
and use for free, as long as you promise to follow their implementation guide when using it. You can read
much more about it at MSDN, where you'll also find a download link for the Ribbon control.
1.10.5.1. Summary
You can download and use a Microsoft created Ribbon control, but it's not yet a part of the .NET
framework by default. Once it becomes an integrated part of the framework, we'll dig into it here at this
tutorial. In the meantime, if you're looking for a more complete Ribbon implementation, you might want to
look at some 3rd party alternatives - there are plenty of them, from some of the big WPF control vendors.
wpf-tutorial.com
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.FlowDocumentScrollViewerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FlowDocumentScrollViewerSample" Height="200" Width
="300">
<Grid>
<FlowDocumentScrollViewer>
<FlowDocument>
<Paragraph FontSize="36">Hello, world!</Paragraph>
<Paragraph FontStyle="Italic" TextAlignment="Left"
FontSize="14" Foreground="Gray">The ultimate programming greeting!</
Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
</Grid>
</Window>
Notice how easy it was to specify the text, using simple markup tags, in this case the Paragraph tag. Now
you might argue that this could have been achieved with a couple of TextBlock controls, and you would be
absolutely right, but even with an extremely basic example like this, you get a bit of added functionality for
Page 169 of 359
wpf-tutorial.com
free: You can select the text and copy it to the clipboard. It'll look like this:
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.FlowDocumentScrollViewerZoomSamp
le"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FlowDocumentScrollViewerZoomSample" Height="180" Width
="300">
<Grid>
<FlowDocumentScrollViewer IsToolBarVisible="True" Zoom="80"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<FlowDocument>
<Paragraph FontSize="36">Hello, world!</Paragraph>
<Paragraph FontStyle="Italic" TextAlignment="Left"
FontSize="14" Foreground="Gray">The ultimate programming greeting!</
Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
Page 170 of 359
wpf-tutorial.com
</Grid>
</Window>
Now the user can control the zoom level using the slider and the buttons in the toolbar below the
document. Notice also that we changed the default zoom level, using the Zoom property - it defines the
zoom level in percentages, so in this case, the text is zoomed out to 80% by default.
The last thing I changed in this example, in comparison to the first one, is the use of the
ScrollViewer.VerticalScrollBarVisibility property. By setting it to Auto, the scrollbars will be invisible until
the content actually goes beyond the available space, which is usually what you want.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.FlowDocumentTextAlignmentSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FlowDocumentTextAlignmentSample" Height="400" Width
="330">
<StackPanel>
<FlowDocumentScrollViewer
ScrollViewer.VerticalScrollBarVisibility="Auto">
<FlowDocument>
<Paragraph FontStyle="Italic" FontSize="14"
Foreground="Gray">
By setting the
<Bold>IsOptimalParagraphEnabled</Bold> property
to true,
you will allow WPF to look ahead on the lines
to come, before deciding
where to break. This will usually result in a
more pleasant reading
experience. It works especially well in
combination with the
<Bold>IsHyphenationEnabled</Bold> property.
</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
<FlowDocumentScrollViewer
ScrollViewer.VerticalScrollBarVisibility="Auto">
<FlowDocument IsOptimalParagraphEnabled="True"
IsHyphenationEnabled="True">
<Paragraph FontStyle="Italic" FontSize="14"
Foreground="Gray">
By setting the <Bold>IsOptimalParagraphEnabled
</Bold> property to true,
you will allow WPF to look ahead on the lines
to come, before deciding
where to break. This will usually result in a
more pleasant reading
experience. It works especially well in
Page 172 of 359
wpf-tutorial.com
IsOptimalParagraphEnabled is not enabled by default because it does require a bit more CPU power when
rendering the text, especially if the window is frequently resized. For most situations this shouldn't be a
problem though.
If you have a lot of FlowDocument instances in your application and you prefer this optimal rendering
method, you can enable it on all of your FlowDocument instances by specifying a global style that enables
it, in your App.xaml. Here's an example:
<Application x:Class="WpfTutorialSamples.App"
wpf-tutorial.com
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="Rich text
controls/FlowDocumentTextAlignmentSample.xaml">
<Application.Resources>
<Style TargetType="FlowDocument">
<Setter Property="IsOptimalParagraphEnabled" Value="True"
/>
<Setter Property="IsHyphenationEnabled" Value="True" />
</Style>
</Application.Resources>
</Application>
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.FlowDocumentPageViewerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FlowDocumentPageViewerSample" Height="300" Width="300">
<Grid>
<FlowDocumentPageViewer>
<FlowDocument>
<Paragraph>Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Fusce faucibus odio arcu, luctus vestibulum tortor
congue in. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Fusce nec lacinia neque. Donec malesuada, ligula non vestibulum cursus,
urna purus pellentesque orci, aliquet accumsan dui velit ac justo.
Phasellus sagittis ligula in leo dapibus, vel vestibulum felis mattis.
Fusce vitae auctor nibh. Ut sit amet fringilla turpis. Aenean tincidunt
feugiat sapien, quis scelerisque enim pretium commodo. Mauris fermentum
posuere nulla, vitae fermentum quam malesuada in. Cras ultrices bibendum
nulla eu mollis. Sed accumsan pretium magna, non sodales velit viverra
id. Sed eu elit sit amet sem ullamcorper rhoncus.</Paragraph>
<Paragraph>Nulla vitae suscipit tellus. Nunc sit
amet tortor fermentum, sollicitudin enim cursus, sagittis lacus.
Pellentesque tincidunt massa nisl, nec tempor nulla consequat a. Proin
pharetra neque vel dolor congue, at condimentum arcu varius. Sed vel
luctus enim. Curabitur eleifend dui et arcu faucibus, sit amet vulputate
libero suscipit. Vestibulum ultrices nisi id metus ultrices, eu
ultricies ligula rutrum. Phasellus rhoncus aliquam pretium. Quisque in
nunc erat. Etiam mollis turpis cursus, sagittis felis vel, dignissim
risus. Ut at est nec tellus lobortis venenatis. Fusce elit mi, gravida
sed tortor at, faucibus interdum felis. Phasellus porttitor dolor in
nunc pellentesque, eu hendrerit nulla porta. Vestibulum cursus placerat
Page 175 of 359
wpf-tutorial.com
Notice how the long text is cut off, and in the bottom, you can navigate between pages. This is not all that
the FlowDocumentPageViewer will do for you though - just look what happens when we make the window
wider:
Instead of just stretching the text indefinitely, the FlowDocumentPageViewer now divides your text up into
columns, to prevent lines from becoming too long. Besides looking good, this also increases the readability,
as texts with very long lines are harder to read. The page count is of course automatically adjusted,
bringing the amount of pages down from 5 to 2.
The FlowDocument class has a range of properties that will allow you to control how and when they are
used. Using them is simple, but a complete example goes beyond the scope of this tutorial. Instead, have a
look at this MSDN article, where several properties are used in a nice example: How to: Use
FlowDocument Column-Separating Attributes.
1.11.3.1. Searching
As you're about to see in the next chapter, the FlowDocumentReader wrapper supports searching right out
Page 176 of 359
wpf-tutorial.com
of the box, with search controls in the toolbar and everything. However, all of the three read-only
FlowDocument wrappers which will be discussed in this tutorial does in fact support searching, it just has to
be manually invoked for the first two (FlowDocumentScrollViewer and FlowDocumentPageViewer).
All three viewers support the Ctrl+F keyboard shortcut for initiating a search, but if you want this to be
accessible from e.g. a button as well, you just have to call the Find() method. Here's an example:
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.FlowDocumentSearchSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FlowDocumentSearchSample" Height="300" Width="580">
<DockPanel>
<WrapPanel DockPanel.Dock="Top">
<Button Name="btnSearch" Click="btnSearch_Click">Search</
Button>
</WrapPanel>
<FlowDocumentPageViewer Name="fdViewer">
<FlowDocument>
<Paragraph>Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Fusce faucibus odio arcu, luctus vestibulum tortor
congue in. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Fusce nec lacinia neque. Donec malesuada, ligula non vestibulum cursus,
Page 177 of 359
wpf-tutorial.com
wpf-tutorial.com
}
}
Simply press our dedicated Search button or the keyboard shortcut (Ctrl+F) and you have search
functionality in the FlowDocumentPageViewer. As mentioned, this works for both
FlowDocumentScrollViewer and FlowDocumentPageViewer (FlowDocumentPageReader has a search
button by default), but make sure that the search box has enough horizontal room on the toolbar otherwise you won't see it when you invoke the Find() command!
wpf-tutorial.com
This screenshot is taken in the page-based view, which is the default. You can switch between the view
modes using the buttons to the left of the zoom controls. In the left part of the toolbar, you have the controls
for searching through the document, as I have done here on the screenshot.
Here's the code that will give you the above result:
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.FlowDocumentReaderSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FlowDocumentReaderSample" Height="250" Width="550">
<Grid>
<FlowDocumentReader>
<FlowDocument>
wpf-tutorial.com
The FlowDocumentReader has a range of properties that can help you in controlling how it works. Here's
an incomplete list of some of the most important ones:
ViewingMode - controls the initial viewing mode. The default is Page, but you can change that into Scroll
or TwoPage , if you want another default view. This can still be changed by the user, unless specifically
disabled.
IsFindEnabled - gives you the ability to disable searching in the document. If disabled, the search button
will be removed from the toolbar.
wpf-tutorial.com
1.11.4.1. Summary
We've now been through all the choices for a read-only FlowDocument wrapper, and as you can probably
see, which one to choose really depends on the task at hand.
If you just want simple FlowDocument rendering with a scrollbar you should go with the
FlowDocumentScrollViewer - it's simple and is the least space and resource consuming of the three. If you
want a paged view, go with the FlowDocumentPageViewer, unless you want your user to be able to switch
between the modes and be able to quickly search, in which case you should use the
FlowDocumentReader.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.CodeBehindFlowDocumentSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CodeBehindFlowDocumentSample" Height="200" Width="300">
<Grid>
<FlowDocumentScrollViewer Name="fdViewer" />
</Grid>
</Window>
using
using
using
using
System;
System.Windows;
System.Windows.Documents;
System.Windows.Media;
namespace WpfTutorialSamples.Rich_text_controls
{
public partial class CodeBehindFlowDocumentSample : Window
{
public CodeBehindFlowDocumentSample()
{
InitializeComponent();
FlowDocument doc = new FlowDocument();
Paragraph p = new Paragraph(new Run("Hello, world!"));
p.FontSize = 36;
doc.Blocks.Add(p);
p = new Paragraph(new Run("The ultimate programming
Page 183 of 359
wpf-tutorial.com
greeting!"));
p.FontSize = 14;
p.FontStyle = FontStyles.Italic;
p.TextAlignment = TextAlignment.Left;
p.Foreground = Brushes.Gray;
doc.Blocks.Add(p);
fdViewer.Document = doc;
}
}
}
When compared to the small amount of XAML required to achieve the exact same thing, this is hardly
impressive:
<FlowDocument>
<Paragraph FontSize="36">Hello, world!</Paragraph>
<Paragraph FontStyle="Italic" TextAlignment="Left" FontSize="14"
Foreground="Gray">The ultimate programming greeting!</Paragraph>
</FlowDocument>
That's beside the point here though - sometimes it just makes more sense to handle stuff from Codebehind, and as you can see, it's definitely possible.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.ExtendedFlowDocumentSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ExtendedFlowDocumentSample" Height="550" Width="500">
<Grid>
<FlowDocumentScrollViewer>
<FlowDocument>
<Paragraph>
<Image Source="http://www.wpftutorial.com/images/logo.png" Width="90" Height="90" Margin="0,0,30,0"
/>
<Run FontSize="120">WPF</Run>
</Paragraph>
<Paragraph>
WPF, which stands for
<Bold>Windows Presentation Foundation</Bold>,
is Microsoft's latest approach to a GUI
framework, used with the .NET framework.
Some advantages include:
</Paragraph>
<List>
<ListItem>
<Paragraph>
It's newer and thereby more in tune
with current standards
</Paragraph>
</ListItem>
Page 185 of 359
wpf-tutorial.com
<ListItem>
<Paragraph>
Microsoft is using it for a lot of
new applications, e.g. Visual Studio
</Paragraph>
</ListItem>
<ListItem>
<Paragraph>
It's more flexible, so you can do
more things without having to write or buy new controls
</Paragraph>
</ListItem>
</List>
<Table CellSpacing="0">
<TableRowGroup>
<TableRow Background="Gainsboro"
FontWeight="Bold">
<TableCell></TableCell>
<TableCell>
<Paragraph TextAlignment="Right"
>WinForms</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right"
>WPF</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
<TableRowGroup>
<TableRow>
<TableCell Background="Gainsboro"
FontWeight="Bold">
<Paragraph>Lines of code</
Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right"
>1.718.000</Paragraph>
</TableCell>
wpf-tutorial.com
<TableCell>
<Paragraph TextAlignment="Right"
>1.542.000</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
<TableRowGroup>
<TableRow>
<TableCell Background="Gainsboro"
FontWeight="Bold">
<Paragraph>Developers</Paragraph
>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right"
>633.000</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right"
>981.000</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
<Paragraph Foreground="Silver" FontStyle="Italic">A
table of made up WinForms/WPF numbers</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
</Grid>
</Window>
I'm not going to go too much into details about each of the tags - hopefully they should make sense as
they are.
As you can see, including lists, images and tables are pretty easy, but in fact, you can include any WPF
control inside of your FlowDocument. Using the BlockUIContainer element you get access to all controls
that would otherwise only be available inside of a window. Here's an example:
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.BlockUIContainerSample"
xmlns
Page 187 of 359
wpf-tutorial.com
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clrnamespace:WpfTutorialSamples.Rich_text_controls"
Title="BlockUIContainerSample" Height="275" Width="300">
<Window.Resources>
<x:Array x:Key="UserArray" Type="{x:Type self:User}">
<self:User Name="John Doe" Age="42"/>
<self:User Name="Jane Doe" Age="36"/>
</x:Array>
Page 188 of 359
wpf-tutorial.com
</Window.Resources>
<Grid>
<FlowDocumentScrollViewer>
<FlowDocument>
<Paragraph FontSize="36" Margin="0">Users</Paragraph
>
<Paragraph FontStyle="Italic" TextAlignment="Left"
FontSize="14" Foreground="Gray">Here's a list of our users, inside our
FlowDocument, in a completely interactive ListView control!</Paragraph>
<BlockUIContainer>
<ListView BorderThickness="0" ItemsSource="
{StaticResource UserArray}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding Name}" Width="150" />
<GridViewColumn Header="Age"
DisplayMemberBinding="{Binding Age}" Width="75" />
</GridView>
</ListView.View>
</ListView>
</BlockUIContainer>
<Paragraph FontStyle="Italic" TextAlignment="Left"
FontSize="14" Foreground="Gray">More content can go here...</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
</Grid>
</Window>
Now we have a FlowDocument with a ListView inside of it, and as you can see from the screenshot, the
ListView works just like it normally would, including selections etc. Pretty cool!
1.11.6.1. Summary
By using the techniques described in the two examples of this article, pretty much anything is possible,
when creating FlowDocument documents. It's excellent for presenting visual information to the end-user, as
seen in many of the expensive reporting suites.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.RichTextBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RichTextBoxSample" Height="200" Width="300">
<Grid>
<RichTextBox Margin="10">
<FlowDocument>
<Paragraph FontSize="36">Hello, world!</Paragraph>
<Paragraph FontStyle="Italic" TextAlignment="Left"
FontSize="14" Foreground="Gray">Thanks to the RichTextBox control, this
FlowDocument is completely editable!</Paragraph>
</FlowDocument>
</RichTextBox>
Page 190 of 359
wpf-tutorial.com
</Grid>
</Window>
With this example, you can start editing your rich text content straight away. However, now that the content
is no longer read-only, it's obviously interesting how you can manipulate the text, as well as work with the
selection. We'll look into that right now.
Another interesting aspect is of course working with the various formatting possibilities - we'll look into that
in the next article, where we actually implement a small, but fully functional rich text editor.
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.RichTextBoxTextSelectionSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RichTextBoxTextSelectionSample" Height="300" Width
="400">
<DockPanel>
<WrapPanel DockPanel.Dock="Top">
<Button Name="btnGetText" Click="btnGetText_Click">Get
text</Button>
Page 191 of 359
wpf-tutorial.com
System;
System.Windows;
System.Windows.Controls;
System.Windows.Documents;
namespace WpfTutorialSamples.Rich_text_controls
{
public partial class RichTextBoxTextSelectionSample : Window
{
public RichTextBoxTextSelectionSample()
{
InitializeComponent();
}
private void btnGetText_Click(object sender, RoutedEventArgs
e)
{
TextRange textRange = new
TextRange(rtbEditor.Document.ContentStart,
rtbEditor.Document.ContentEnd);
MessageBox.Show(textRange.Text);
wpf-tutorial.com
}
private void btnSetText_Click(object sender, RoutedEventArgs
e)
{
TextRange textRange = new
TextRange(rtbEditor.Document.ContentStart,
rtbEditor.Document.ContentEnd);
textRange.Text = "Another world, another text!";
}
private void btnGetSelectedText_Click(object sender,
RoutedEventArgs e)
{
MessageBox.Show(rtbEditor.Selection.Text);
}
private void btnSetSelectedText_Click(object sender,
RoutedEventArgs e)
{
rtbEditor.Selection.Text = "[Replaced text]";
}
private void rtbEditor_SelectionChanged(object sender,
RoutedEventArgs e)
{
TextRange tempRange = new
TextRange(rtbEditor.Document.ContentStart, rtbEditor.Selection.Start);
txtStatus.Text = "Selection starts at character #" +
tempRange.Text.Length + Environment.NewLine;
txtStatus.Text += "Selection is " +
rtbEditor.Selection.Text.Length + " character(s) long" +
Environment.NewLine;
txtStatus.Text += "Selected text: '" +
rtbEditor.Selection.Text + "'";
}
}
}
As you can see, the markup consists of a panel of buttons, a RichTextBox and a TextBox in the bottom, to
show the current selection status. Each of the four available buttons will work with the RichTextBox by
Page 193 of 359
wpf-tutorial.com
wpf-tutorial.com
This is normal behavior for a text editor working in paragraphs, but depending on how and where you use
the RichTextBox, it might be confusing for your users that a single Enter press results in such a large
amount of space between the lines.
Fortunately, it's very easy to fix. The extra spaces comes from the fact that paragraphs have a default
margin bigger than zero, so fixing it is as simple as changing this property, which we can do with a style,
like this:
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.RichTextBoxParagraphSpacingSampl
e"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RichTextBoxParagraphSpacingSample" Height="150" Width
="300">
<Grid>
<RichTextBox Margin="10">
<RichTextBox.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0" />
</Style>
</RichTextBox.Resources>
</RichTextBox>
</Grid>
</Window>
Now the lines don't have extra space around them, and if you want, you can place the style in the window
or even in App.xaml, if you want it to work for more than just a single RichTextBox.
1.11.7.3. Summary
The RichTextBox is easy to use, has lots of features straight out of the box, and can easily be used if you
Page 195 of 359
wpf-tutorial.com
wish to create a fully featured rich text editor. In the next article, we'll have a look at doing just that! This will
also get us around important subjects such as loading and saving text from a RichTextBox and how to
affect formatting of text in the control.
wpf-tutorial.com
1.11.8.1. Interface
The interface consists of a ToolBar control with buttons and combo boxes on it. There are buttons for
loading and saving a document, buttons for controlling various font weight and style properties, and then
two combo boxes for controlling the font family and size.
Below the toolbar is the RichTextBox control, where all the editing will be done.
1.11.8.2. Commands
The first thing you might notice is the use of WPF Commands, which we've already discussed previously in
this article. We use the Open and Save commands from the ApplicationCommands class to load and save
the document, and we use the ToggleBold, ToggleItalic and ToggleUnderline commands from the
EditingCommands class for our style related buttons.
The advantage of using Commands is once again obvious, since the RichTextBox control already
Page 197 of 359
wpf-tutorial.com
implements the ToggleBold, ToggleItalic and ToggleUnderline commands. This means that we don't have
to write any code for them to work. Just hook them up to the designated button and it works:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed
="Open_Executed" />
<CommandBinding Command="ApplicationCommands.Save" Executed
="Save_Executed" />
</Window.CommandBindings>
I'll show you the implementation later in this article.
public RichTextEditorSample()
{
InitializeComponent();
cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f =>
f.Source);
cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12,
14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}
Once again, wPF makes it easy for us to get a list of possible font families, by using the
Page 198 of 359
wpf-tutorial.com
SystemFontFamilies property. Since the list of sizes is more of a suggestion, we make that ComboBox
control editable, so that the user may enter a custom size:
wpf-tutorial.com
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Rich_text_controls.RichTextEditorSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
wpf-tutorial.com
wpf-tutorial.com
System;
System.Linq;
System.Collections.Generic;
System.IO;
System.Windows;
System.Windows.Documents;
System.Windows.Input;
System.Windows.Media;
Microsoft.Win32;
System.Windows.Controls;
namespace WpfTutorialSamples.Rich_text_controls
{
public partial class RichTextEditorSample : Window
{
public RichTextEditorSample()
{
InitializeComponent();
cmbFontFamily.ItemsSource =
Fonts.SystemFontFamilies.OrderBy(f => f.Source);
cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10,
11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}
private void rtbEditor_SelectionChanged(object sender,
RoutedEventArgs e)
{
object temp =
rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
btnBold.IsChecked = (temp !=
DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
temp =
rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
wpf-tutorial.com
btnItalic.IsChecked = (temp !=
DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
temp =
rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
btnUnderline.IsChecked = (temp !=
DependencyProperty.UnsetValue) &&
(temp.Equals(TextDecorations.Underline));
temp =
rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
cmbFontFamily.SelectedItem = temp;
temp =
rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
cmbFontSize.Text = temp.ToString();
}
private void Open_Executed(object sender,
ExecutedRoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files
(*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName,
FileMode.Open);
TextRange range = new
TextRange(rtbEditor.Document.ContentStart,
rtbEditor.Document.ContentEnd);
range.Load(fileStream, DataFormats.Rtf);
}
}
private void Save_Executed(object sender,
ExecutedRoutedEventArgs e)
{
SaveFileDialog dlg = new SaveFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files
(*.*)|*.*";
if(dlg.ShowDialog() == true)
{
wpf-tutorial.com
1.11.8.7. Summary
As you can see, implementing a rich text editor in WPF is very simple, especially because of the excellent
RichTextBox control. If you want, you can easily extend this example with stuff like text alignment, colors,
lists and even tables.
Please be aware that while the above example should work just fine, there's absolutely no exception
handling or checking at all, to keep the amount of code to a minimum. There are several places which
could easily throw an exception, like the font size combo box, where one could cause an exception by
entering a non-numeric value. You should of course check all of this and handle possible exceptions if you
wish to expand on this example for your work.
Page 205 of 359
wpf-tutorial.com
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Misc_controls.BorderSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BorderSample" Height="170" Width="200">
<Grid Margin="10">
<Border Background="GhostWhite" BorderBrush="Gainsboro"
BorderThickness="1">
<StackPanel Margin="10">
<Button>Button 1</Button>
<Button Margin="0,10">Button 2</Button>
<Button>Button 3</Button>
</StackPanel>
</Border>
</Grid>
</Window>
The Border is completely lookless until you define either a background or a border brush and thickness, so
that's what I've done here, using the Background, BorderBrush and BorderThickness properties.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Misc_controls.BorderSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BorderSample" Height="175" Width="200">
<Grid Margin="10">
<Border Background="GhostWhite" BorderBrush="Silver"
BorderThickness="1" CornerRadius="8,8,3,3">
<StackPanel Margin="10">
<Button>Button 1</Button>
<Button Margin="0,10">Button 2</Button>
<Button>Button 3</Button>
</StackPanel>
</Border>
</Grid>
</Window>
All I've done is adding the CornerRadius property. It can be specified with a single value, which will be
used for all four corners, or like I did in the example here, where I specify separate values for the top right
and left followed by the bottom right and left.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Misc_controls.BorderSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BorderSample" Height="175" Width="200">
<Grid Margin="10">
<Border Background="GhostWhite" BorderBrush="DodgerBlue"
BorderThickness="1,3,1,5">
<StackPanel Margin="10">
<Button>Button 1</Button>
<Button Margin="0,10">Button 2</Button>
<Button>Button 3</Button>
</StackPanel>
</Border>
</Grid>
</Window>
<Window x:Class="WpfTutorialSamples.Misc_controls.BorderSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BorderSample" Height="175" Width="200">
<Grid Margin="10">
<Border BorderBrush="Navy" BorderThickness="1,3,1,5">
<Border.Background>
Page 209 of 359
wpf-tutorial.com
In this case, I've specified a LinearGradientBrush to be used for the background of the Border and then a
more fitting border color. The LinearGradientBrush might not have the most obvious syntax, so I will explain
that in a later chapter, including other brush types, but for now, you can try my example and change the
values to see the result.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Misc_controls.WebBrowserControlSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Page 211 of 359
wpf-tutorial.com
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WebBrowserControlSample" Height="300" Width="450">
<Window.CommandBindings>
<CommandBinding Command="NavigationCommands.BrowseBack"
CanExecute="BrowseBack_CanExecute" Executed="BrowseBack_Executed" />
<CommandBinding Command="NavigationCommands.BrowseForward"
CanExecute="BrowseForward_CanExecute" Executed="BrowseForward_Executed"
/>
<CommandBinding Command="NavigationCommands.GoToPage"
CanExecute="GoToPage_CanExecute" Executed="GoToPage_Executed" />
</Window.CommandBindings>
<DockPanel>
<ToolBar DockPanel.Dock="Top">
<Button Command="NavigationCommands.BrowseBack">
<Image Source
="/WpfTutorialSamples;component/Images/arrow_left.png" Width="16" Height
="16" />
</Button>
<Button Command="NavigationCommands.BrowseForward">
<Image Source
="/WpfTutorialSamples;component/Images/arrow_right.png" Width="16"
Height="16" />
</Button>
<Separator />
<TextBox Name="txtUrl" Width="300" KeyUp="txtUrl_KeyUp"
/>
<Button Command="NavigationCommands.GoToPage">
<Image Source
="/WpfTutorialSamples;component/Images/world_go.png" Width="16" Height
="16" />
</Button>
</ToolBar>
<WebBrowser Name="wbSample" Navigating="wbSample_Navigating"
></WebBrowser>
</DockPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Input;
wpf-tutorial.com
namespace WpfTutorialSamples.Misc_controls
{
public partial class WebBrowserControlSample : Window
{
public WebBrowserControlSample()
{
InitializeComponent();
wbSample.Navigate("http://www.wpf-tutorial.com");
}
private void txtUrl_KeyUp(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
wbSample.Navigate(txtUrl.Text);
}
private void wbSample_Navigating(object sender,
System.Windows.Navigation.NavigatingCancelEventArgs e)
{
txtUrl.Text = e.Uri.OriginalString;
}
private void BrowseBack_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = ((wbSample != null) &&
(wbSample.CanGoBack));
}
private void BrowseBack_Executed(object sender,
ExecutedRoutedEventArgs e)
{
wbSample.GoBack();
}
private void BrowseForward_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = ((wbSample != null) &&
(wbSample.CanGoForward));
}
wpf-tutorial.com
wpf-tutorial.com
The last part of the Code-behind is simple handling of our commands: Two for the back and forward
buttons, where we use the CanGoBack and CanGoForward to decide whether they can execute, and the
GoBack and GoForward methods to do the actual work. This is very standard when dealing with WPF
commands, as described in the commands section of this tutorial.
For the last command, we allow it to always execute and when it does, we use the Navigate() method once
again.
1.12.2.1. Summary
As you can see, hosting and using a complete webbrowser inside of your application becomes very easy
with the WebBrowser control. However, you should be aware that the WPF version of WebBrowser is a bit
limited when compared to the WinForms version, but for basic usage and navigation, it works fine.
If you wish to use the WinForms version instead, you may do so using the WindowsFormsHost, which is
explained elsewhere in this tutorial.
wpf-tutorial.com
WindowsFormsIntegration
System.Windows.Forms
In Visual Studio, this is done by right-clicking the "References" node in your project and selecting "Add
reference":
In the dialog that pops up, you should select "Assemblies" and then check the two assemblies that we
need to add:
wpf-tutorial.com
makes it easy to get and update the title of the window to match the title of the current webpage. We'll use
this as an excuse to test out the WinForms version right here in our WPF application:
<Window x:Class
="WpfTutorialSamples.Misc_controls.WindowsFormsHostSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clrnamespace:System.Windows.Forms;assembly=System.Windows.Forms"
Title="WindowsFormsHostSample" Height="350" Width="450">
<Grid>
<WindowsFormsHost Name="wfhSample">
<WindowsFormsHost.Child>
<wf:WebBrowser DocumentTitleChanged
="wbWinForms_DocumentTitleChanged" />
</WindowsFormsHost.Child>
</WindowsFormsHost>
</Grid>
Page 217 of 359
wpf-tutorial.com
</Window>
using System;
using System.Windows;
namespace WpfTutorialSamples.Misc_controls
{
public partial class WindowsFormsHostSample : Window
{
public WindowsFormsHostSample()
{
InitializeComponent();
(wfhSample.Child as
System.Windows.Forms.WebBrowser).Navigate("http://www.wpf-tutorial.com"
);
}
private void wbWinForms_DocumentTitleChanged(object sender,
EventArgs e)
{
this.Title = (sender as
System.Windows.Forms.WebBrowser).DocumentTitle;
}
}
}
Pay special attention to the line where we add the WinForms namespace to the window, so that we may
reference controls from it:
xmlns:wf="clrnamespace:System.Windows.Forms;assembly=System.Windows.Forms"
This will allow us to reference WinForms controls using the wf: prefix.
The WindowsFormsHost is fairly simple to use, as you can see. It has a Child property, in which you can
define a single WinForms control, much like the WPF Window only holds a single root control. If you need
more controls from WinForms inside of your WindowsFormsHost, you can use the Panel control from
WinForms or any of the other container controls.
The WinForms WebBrowser control is used by referencing the System.Windows.Forms assembly, using
the wf prefix, as explained above.
wpf-tutorial.com
In Code-behind, we do an initial call to Navigate, to have a visible webpage instead of the empty control on
startup. We then handle theDocumentTitleChanged event, in which we update the Title property of the
Window in accordance with the current DocumentTitle value of the WebBrowser control.
Congratulations, you now have a WPF application with a WinForms WebBrowser hosted inside of it.
1.12.3.2. Summary
As you can see, using WinForms controls inside of your WPF applications is pretty easy, but the question
remains: Is it a good idea?
In general, you may want to avoid it. There are a number of issues that may or may not affect your
application (a lot of them are described in this MSDN article: http://msdn.microsoft.com/enus/library/aa970911%28v=VS.100%29.aspx), but a more serious problem is that this kind of UI framework
mixing might not be supported in future versions of the .NET framework.
In the end though, the decision is up to you - do you really need the WinForms control or is there a WPF
alternative that might work just as well?
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Misc_controls.TabControlSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TabControlSample" Height="200" Width="250">
<Grid>
<TabControl>
<TabItem Header="General">
<Label Content="Content goes here..." />
</TabItem>
<TabItem Header="Security" />
<TabItem Header="Details" />
</TabControl>
</Grid>
</Window>
wpf-tutorial.com
As you can see, each tab is represented with a TabItem element, where the text shown on it is controlled
by the Header property. The TabItem element comes from the ContentControl class, which means that you
may define a single element inside of it that will be shown if the tab is active (like on the screenshot). I used
a Label in this example, but if you want to place more than one control inside of the tab, just use one of the
panels with child controls inside of it.
<Window x:Class
="WpfTutorialSamples.Misc_controls.TabControlWithCustomHeadersSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TabControlWithCustomHeadersSample" Height="200" Width
="250">
<Grid>
<Grid>
<TabControl>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_blue.png" />
<TextBlock Text="Blue" Foreground
="Blue" />
</StackPanel>
</TabItem.Header>
<Label Content="Content goes here..." />
</TabItem>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_red.png" />
<TextBlock Text="Red" Foreground
="Red" />
</StackPanel>
Page 221 of 359
wpf-tutorial.com
</TabItem.Header>
</TabItem>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_green.png" />
<TextBlock Text="Green" Foreground
="Green" />
</StackPanel>
</TabItem.Header>
</TabItem>
</TabControl>
</Grid>
</Grid>
</Window>
The amount of markup might be a bit overwhelming, but as you can probably see once you dig into it, it's
all very simple. Each of the tabs now has a TabControl.Header element, which contains a StackPanel,
which in turn contains an Image and a TextBlock control. This allows us to have an image on each of the
tabs as well as customize the color of the text (we could have made it bold, italic or another size as well).
<Window x:Class
Page 222 of 359
wpf-tutorial.com
="WpfTutorialSamples.Misc_controls.ControllingTheTabControlSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControllingTheTabControlSample" Height="300" Width
="350">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom"
Margin="2,5">
<Button Name="btnPreviousTab" Click
="btnPreviousTab_Click">Prev.</Button>
<Button Name="btnNextTab" Click="btnNextTab_Click">Next</
Button>
<Button Name="btnSelectedTab" Click
="btnSelectedTab_Click">Selected</Button>
</StackPanel>
<TabControl Name="tcSample">
<TabItem Header="General">
<Label Content="Content goes here..." />
</TabItem>
<TabItem Header="Security" />
<TabItem Header="Details" />
</TabControl>
</DockPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfTutorialSamples.Misc_controls
{
public partial class ControllingTheTabControlSample : Window
{
public ControllingTheTabControlSample()
{
InitializeComponent();
}
private void btnPreviousTab_Click(object sender,
wpf-tutorial.com
RoutedEventArgs e)
{
int newIndex = tcSample.SelectedIndex - 1;
if(newIndex < 0)
newIndex = tcSample.Items.Count - 1;
tcSample.SelectedIndex = newIndex;
}
private void btnNextTab_Click(object sender, RoutedEventArgs
e)
{
int newIndex = tcSample.SelectedIndex + 1;
if(newIndex >= tcSample.Items.Count)
newIndex = 0;
tcSample.SelectedIndex = newIndex;
}
private void btnSelectedTab_Click(object sender,
RoutedEventArgs e)
{
MessageBox.Show("Selected tab: " + (tcSample.SelectedItem
as TabItem).Header);
}
}
}
wpf-tutorial.com
As you can see, I've simply added a set of buttons in the lower part of the interface. The first two allows will
select the previous or next tab on the control, while the last one will display information about the currently
selected tab, as demonstrated on the screenshot.
The first two buttons uses the SelectedIndex property to determine where we are and then either
subtracts or adds one to that value, making sure that the new index doesn't fall below or above the amount
of available items. The third button uses the SelectedItem property to get a reference to the selected tab.
As you can see, I have to typecast it into the TabItem class to get a hold of the header property, since the
SelectedProperty is of the object type by default.
1.13.1.3. Summary
The TabControl is great when you need a clear separation in a dialog or when there's simply not enough
space for all the controls you want in it. In the next couple of chapters, we'll look into some of the
possibilites there are when using the TabControl for various purposes.
wpf-tutorial.com
However, using the TabStripPlacement property, we can very easily change this:
<Window x:Class
="WpfTutorialSamples.Misc_controls.TabStripPlacementSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TabStripPlacementSample" Height="200" Width="250">
<Grid>
<TabControl TabStripPlacement="Bottom">
<TabItem Header="General">
<Label Content="Content goes here..." />
</TabItem>
<TabItem Header="Security" />
<TabItem Header="Details" />
</TabControl>
</Grid>
</Window>
The TabStripPlacement can be set to Top, Bottom, Left and Right. However, if we set it to Left or Right, we
get a result like this:
wpf-tutorial.com
I personally would expect that the tabs to be rotated when placed on one of the sides, so that the tab text
becomes vertical instead of horizontal, but the WPF TabControl doesn't do this. Fortunately, we can
accomplish this behavior with a small hack:
<Window x:Class
="WpfTutorialSamples.Misc_controls.TabStripPlacementSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TabStripPlacementSample" Height="200" Width="250"
UseLayoutRounding="True">
<Grid>
<TabControl TabStripPlacement="Left">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
Page 227 of 359
wpf-tutorial.com
<DataTemplate>
<ContentPresenter Content="
{TemplateBinding Content}">
<
ContentPresenter.LayoutTransform>
<RotateTransform Angle
="270" />
</
ContentPresenter.LayoutTransform>
</ContentPresenter>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="3" />
</Style>
</TabControl.Resources>
<TabItem Header="General">
<Label Content="Content goes here..." />
</TabItem>
<TabItem Header="Security" />
<TabItem Header="Details" />
</TabControl>
</Grid>
</Window>
If you haven't yet read the chapters on templates or styles, this might seem a bit confusing, but what we do
is using a style targeted at the TabItem elements, where we override the HeaderTemplate and then apply a
rotate transform to the tabs. For tabs placed on the left side, we rotate 270 degrees - if placed on the right,
you should only rotate 90 degrees, to make it look correct.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Misc_controls.StyledTabItemsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyledTabItemsSample" Height="150" Width="250">
<Grid>
<TabControl Margin="10" BorderThickness="0" Background
="LightGray">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem"
>
<Grid Name="Panel">
<ContentPresenter x:Name
="ContentSite"
VerticalAlignment
="Center"
HorizontalAlignment
="Center"
ContentSource="Header"
Margin="10,2"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property
="IsSelected" Value="True">
<Setter TargetName
="Panel" Property="Background" Value="LightSkyBlue" />
</Trigger>
<Trigger Property
Page 229 of 359
wpf-tutorial.com
="IsSelected" Value="False">
<Setter TargetName
="Panel" Property="Background" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Header="General">
<Label Content="Content goes here..." />
</TabItem>
<TabItem Header="Security" />
<TabItem Header="Details" />
</TabControl>
</Grid>
</Window>
As you can see, this makes the TabControl looks a bit Windows 8'ish, with no borders and a less subtle
color to mark the selected tab and no background for the unselected tabs. All of this is accomplished by
changing the ControlTemplate, using a Style. By adding a ContentPresenter control, we specify where the
content of the TabItem should be placed. We also have a couple of triggers, which controls the background
color of the tabs based on the IsSelected property.
In case you want a less subtle look, it's as easy as changing the template. For instance, you might want a
border, but with round corners and a gradient background - no problem! Check out this next example,
where we accomplish just that:
<Window x:Class
="WpfTutorialSamples.Misc_controls.StyledTabItemsWithBorderSample"
xmlns
Page 230 of 359
wpf-tutorial.com
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyledTabItemsWithBorderSample" Height="150" Width
="250">
<Grid>
<TabControl Margin="10" BorderBrush="Gainsboro">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem"
>
<Border Name="Border"
BorderThickness="1,1,1,0" BorderBrush="Gainsboro" CornerRadius="4,4,0,0"
Margin="2,0">
<ContentPresenter x:Name
="ContentSite"
VerticalAlignment
="Center"
HorizontalAlignment
="Center"
ContentSource="Header"
Margin="10,2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property
="IsSelected" Value="True">
<Setter TargetName
="Border" Property="Background" Value="LightSkyBlue" />
</Trigger>
<Trigger Property
="IsSelected" Value="False">
<Setter TargetName
="Border" Property="Background" Value="GhostWhite" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
wpf-tutorial.com
<TabItem Header="General">
<Label Content="Content goes here..." />
</TabItem>
<TabItem Header="Security" />
<TabItem Header="Details" />
</TabControl>
</Grid>
</Window>
As you can see, I pretty much just added a Border control around the ContentPresenter to achieve this
changed look. Hopefully this should demonstrate just how easy it is to get custom styled tabs and how
many possibilities there are in this technique.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.ItemsControl.ItemsControlSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="ItemsControlSample" Height="150" Width="200">
<Grid Margin="10">
<ItemsControl>
<system:String>ItemsControl Item #1</system:String>
<system:String>ItemsControl Item #2</system:String>
<system:String>ItemsControl Item #3</system:String>
<system:String>ItemsControl Item #4</system:String>
<system:String>ItemsControl Item #5</system:String>
</ItemsControl>
</Grid>
</Window>
wpf-tutorial.com
As you can see, there is nothing that shows that we're using a control for repeating the items instead of
just manually adding e.g. 5 TextBlock controls - the ItemsControl is completely lookless by default. If you
click on one of the items, nothing happens, because there's no concept of selected item(s) or anything like
that.
<Window x:Class
="WpfTutorialSamples.ItemsControl.ItemsControlDataBindingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ItemsControlDataBindingSample" Height="150" Width="300"
>
<Grid Margin="10">
<ItemsControl Name="icTodoList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Title}" />
<ProgressBar Grid.Column="1" Minimum="0"
Maximum="100" Value="{Binding Completion}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
wpf-tutorial.com
using System;
using System.Windows;
using System.Collections.Generic;
namespace WpfTutorialSamples.ItemsControl
{
public partial class ItemsControlDataBindingSample : Window
{
public ItemsControlDataBindingSample()
{
InitializeComponent();
List<TodoItem> items = new List<TodoItem>();
items.Add(new TodoItem() { Title = "Complete this WPF
tutorial", Completion = 45 });
items.Add(new TodoItem() { Title = "Learn C#", Completion
= 80 });
items.Add(new TodoItem() { Title = "Wash the car",
Completion = 0 });
icTodoList.ItemsSource = items;
}
}
public class TodoItem
{
public string Title { get; set; }
public int Completion { get; set; }
}
}
The most important part of this example is the template that we specify inside of the ItemsControl, using a
Page 235 of 359
wpf-tutorial.com
DataTemplate tag inside of the ItemsControl.ItemTemplate. We add a Grid panel, to get two columns: In
the first we have a TextBlock, which will show the title of the TODO item, and in the second column we
have a ProgressBar control, which value we bind to the Completion property.
The template now represents a TodoItem, which we declare in the Code-behind file, where we also
instantiate a number of them and add them to a list. In the end, this list is assigned to the ItemsSource
property of our ItemsControl, which then does the rest of the job for us. Each item in the list is displayed by
using our template, as you can see from the resulting screenshot.
<Window x:Class
="WpfTutorialSamples.ItemsControl.ItemsControlPanelSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="ItemsControlPanelSample" Height="150" Width="250">
<Grid Margin="10">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Margin="0,0,5,5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<system:String>Item #1</system:String>
<system:String>Item #2</system:String>
<system:String>Item #3</system:String>
<system:String>Item #4</system:String>
<system:String>Item #5</system:String>
</ItemsControl>
</Grid>
Page 236 of 359
wpf-tutorial.com
</Window>
We specify that the ItemsControl should use a WrapPanel as its template by declaring one in the
ItemsPanelTemplate property and just for fun, we throw in an ItemTemplate that causes the strings to be
rendered as buttons. You can use any of the WPF panels, but some are more useful than others.
Another good example is the UniformGrid panel, where we can define a number of columns and then have
our items neatly shown in equally-wide columns:
<Window x:Class
="WpfTutorialSamples.ItemsControl.ItemsControlPanelSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="ItemsControlPanelSample" Height="150" Width="250">
<Grid Margin="10">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Margin="0,0,5,5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<system:String>Item #1</system:String>
<system:String>Item #2</system:String>
<system:String>Item #3</system:String>
<system:String>Item #4</system:String>
Page 237 of 359
wpf-tutorial.com
<system:String>Item #5</system:String>
</ItemsControl>
</Grid>
</Window>
WPF makes this very easy to solve though. There are a number of possible solutions, for instance you can
alter the template used by the ItemsControl to include a ScrollViewer control, but the easiest solution is to
simply throw a ScrollViewer around the ItemsControl. Here's an example:
<Window x:Class="WpfTutorialSamples.ItemsControl.ItemsControlSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="ItemsControlSample" Height="150" Width="200">
<Grid Margin="10">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
wpf-tutorial.com
<ItemsControl>
<system:String>ItemsControl
<system:String>ItemsControl
<system:String>ItemsControl
<system:String>ItemsControl
<system:String>ItemsControl
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
Item
Item
Item
Item
Item
#1</system:String>
#2</system:String>
#3</system:String>
#4</system:String>
#5</system:String>
I set the two visibility options to Auto, to make them only visible when needed. As you can see from the
screenshot, you can now scroll through the list of items.
1.14.1.5. Summary
The ItemsControl is great when you want full control of how your data is displayed, and when you don't
need any of your content to be selectable. If you want the user to be able to select items from the list, then
you're better off with one of the other controls, e.g. the ListBox or the ListView. They will be described in
upcoming chapters.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxSample" Height="120" Width="200">
<Grid Margin="10">
<ListBox>
<ListBoxItem>ListBox Item #1</ListBoxItem>
<ListBoxItem>ListBox Item #2</ListBoxItem>
<ListBoxItem>ListBox Item #3</ListBoxItem>
</ListBox>
</Grid>
</Window>
This is as simple as it gets: We declare a ListBox control, and inside of it, we declare three ListBoxItem's,
each with its own text. However, since the ListBoxItem is actually a ContentControl, we can define custom
content for it:
<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxSample" Height="120" Width="200">
<Grid Margin="10">
<ListBox>
Page 240 of 359
wpf-tutorial.com
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_blue.png" />
<TextBlock>ListBox Item #1</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_green.png" />
<TextBlock>ListBox Item #2</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_red.png" />
<TextBlock>ListBox Item #3</TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
</Grid>
</Window>
For each of the ListBoxItem's we now add a StackPanel, in which we add an Image and a TextBlock. This
gives us full control of the content as well as the text rendering, as you can see from the screenshot, where
different colors have been used for each of the numbers.
From the screenshot you might also notice another difference when comparing the ItemsControl to the
ListBox: By default, a border is shown around the control, making it look like an actual control instead of
just output.
wpf-tutorial.com
Manually defining items for the ListBox makes for a fine first example, but most of the times, your ListBox
controls will be filled with items from a data source using data binding. By default, if you bind a list of items
to the ListBox, their ToString() method will be used to represent each item. This is rarely what you want, but
fortunately, we can easily declare a template that will be used to render each item.
I have re-used the TODO based example from the ItemsControl article, where we build a cool TODO list
using a simple Code-behind class and, in this case, a ListBox control for the visual representation. Here's
the example:
<Window x:Class
="WpfTutorialSamples.ListBox_control.ListBoxDataBindingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxDataBindingSample" Height="150" Width="300">
<Grid Margin="10">
<ListBox Name="lbTodoList" HorizontalContentAlignment
="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Title}" />
<ProgressBar Grid.Column="1" Minimum="0"
Maximum="100" Value="{Binding Completion}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
using System;
using System.Windows;
using System.Collections.Generic;
namespace WpfTutorialSamples.ListBox_control
{
wpf-tutorial.com
All the magic happens in the ItemTemplate that we have defined for the ListBox. In there, we specify that
each ListBox item should consist of a Grid, divided into two columns, with a TextBlock showing the title in
the first and a ProgressBar showing the completion status in the second column. To get the values out, we
use some very simple data binding, which is all explained in the data binding part of this tutorial.
In the Code-behind file, we have declared a very simple TodoItem class to hold each of our TODO items.
In the constructor of the window, we initialize a list, add three TODO items to it and then assign it to the
ItemsSource of the ListBox. The combination of the ItemsSource and the ItemTemplate we specified in the
XAML part, this is all WPF need to render all of the items as a TODO list.
Page 243 of 359
wpf-tutorial.com
Please notice the HorizontalContentAlignment property that I set to Stretch on the ListBox. The default
content alignment for a ListBox item is Left, which means that each item only takes up as much horizontal
space as it needs. The result? Well, not quite what we want:
By using the Stretch alignment, each item is stretched to take up the full amount of available space, as you
can see from the previous screenshot.
<Window x:Class
="WpfTutorialSamples.ListBox_control.ListBoxSelectionSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxSelectionSample" Height="250" Width="450">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="0,0,0,5" />
</Style>
</StackPanel.Resources>
<TextBlock FontWeight="Bold" Margin="0,0,0,10">ListBox
selection</TextBlock>
<Button Name="btnShowSelectedItem" Click
="btnShowSelectedItem_Click">Show selected</Button>
Page 244 of 359
wpf-tutorial.com
wpf-tutorial.com
wpf-tutorial.com
}
public class TodoItem
{
public string Title { get; set; }
public int Completion { get; set; }
}
}
wpf-tutorial.com
As you can see, I have defined a range of buttons to the right of the ListBox, to either get or manipulate the
selection. I've also changed the SelectionMode to Extended, to allow for the selection of multiple items.
This can be done either programmatically, as I do in the example, or by the end-user, by holding down
[Ctrl] or [Shift] while clicking on the items.
For each of the buttons, I have defined a click handler in the Code-behind. Each action should be pretty
self-explanatory and the C# code used is fairly simple, but if you're still in doubt, try running the example on
your own machine and test out the various possibilities in the example.
1.14.2.3. Summary
The ListBox control is much like the ItemsControl and several of the same techniques can be used. The
ListBox does offer a bit more functionality when compared to the ItemsControl, especially the selection
handling. For even more functionality, like column headers, you should have a look at the ListView control,
which is given a very thorough description later on in this tutorial with several articles explaining all the
functionality.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.ComboBox_control.ComboBoxSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ComboBoxSample" Height="150" Width="200">
<StackPanel Margin="10">
<ComboBox>
<ComboBoxItem>ComboBox Item #1</ComboBoxItem>
<ComboBoxItem IsSelected="True">ComboBox Item #2</
ComboBoxItem>
<ComboBoxItem>ComboBox Item #3</ComboBoxItem>
</ComboBox>
</StackPanel>
</Window>
In the screenshot, I have activated the control by clicking it, causing the list of items to be displayed. As
you can see from the code, the ComboBox, in its simple form, is very easy to use. All I've done here is
manually add some items, making one of them the default selected item by setting the IsSelected property
on it.
<Window x:Class
="WpfTutorialSamples.ComboBox_control.ComboBoxCustomContentSample"
Page 249 of 359
wpf-tutorial.com
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ComboBoxCustomContentSample" Height="150" Width="200">
<StackPanel Margin="10">
<ComboBox>
<ComboBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_red.png" />
<TextBlock Foreground="Red">Red</TextBlock>
</StackPanel>
</ComboBoxItem>
<ComboBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_green.png" />
<TextBlock Foreground="Green">Green</TextBlock>
</StackPanel>
</ComboBoxItem>
<ComboBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_blue.png" />
<TextBlock Foreground="Blue">Blue</TextBlock>
</StackPanel>
</ComboBoxItem>
</ComboBox>
</StackPanel>
</Window>
For each of the ComboBoxItem's we now add a StackPanel, in which we add an Image and a TextBlock.
Page 250 of 359
wpf-tutorial.com
This gives us full control of the content as well as the text rendering, as you can see from the screenshot,
where both text color and image indicates a color value.
<Window x:Class
="WpfTutorialSamples.ComboBox_control.ComboBoxDataBindingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ComboBoxDataBindingSample" Height="200" Width="200">
<StackPanel Margin="10">
<ComboBox Name="cmbColors">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Name}" Width
="16" Height="16" Margin="0,2,5,2" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.Windows.Media;
namespace WpfTutorialSamples.ComboBox_control
{
public partial class ComboBoxDataBindingSample : Window
{
public ComboBoxDataBindingSample()
{
Page 251 of 359
wpf-tutorial.com
InitializeComponent();
cmbColors.ItemsSource = typeof(Colors).GetProperties();
}
}
}
It's actually quite simple: In the Code-behind, I obtain a list of all the colors using a Reflection based
approach with the Colors class. I assign it to the ItemsSource property of the ComboBox, which then
renders each color using the template I have defined in the XAML part.
Each item, as defined by the ItemTemplate, consists of a StackPanel with a Rectangle and a TextBlock,
each bound to the color value. This gives us a complete list of colors, with minimal effort - and it looks
pretty good too, right?
1.14.3.3. IsEditable
In the first examples, the user was only able to select from our list of items, but one of the cool things about
the ComboBox is that it supports the possibility of letting the user both select from a list of items or enter
their own value. This is extremely useful in situations where you want to help the user by giving them a predefined set of options, while still giving them the option to manually enter the desired value. This is all
controlled by the IsEditable property, which changes the behavior and look of the ComboBox quite a bit:
<Window x:Class
="WpfTutorialSamples.ComboBox_control.ComboBoxEditableSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ComboBoxEditableSample" Height="150" Width="200">
<StackPanel Margin="10">
<ComboBox IsEditable="True">
Page 252 of 359
wpf-tutorial.com
As you can see, I can enter a completely different value or pick one from the list. If picked from the list, it
simply overwrites the text of the ComboBox.
As a lovely little bonus, the ComboBox will automatically try to help the user select an existing value when
the user starts typing, as you can see from the next screenshot, where I just started typing "Co":
By default, the matching is not case-sensitive but you can make it so by setting the
IsTextSearchCaseSensitive to True. If you don't want this auto complete behavior at all, you can disable it
by setting the IsTextSearchEnabled to False.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.ComboBox_control.ComboBoxSelectionSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ComboBoxSelectionSample" Height="125" Width="250">
<StackPanel Margin="10">
<ComboBox Name="cmbColors" SelectionChanged
="cmbColors_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Name}" Width
="16" Height="16" Margin="0,2,5,2" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<WrapPanel Margin="15" HorizontalAlignment="Center">
<Button Name="btnPrevious" Click="btnPrevious_Click"
Width="55">Previous</Button>
<Button Name="btnNext" Click="btnNext_Click" Margin="5,0"
Width="55">Next</Button>
<Button Name="btnBlue" Click="btnBlue_Click" Width="55">
Blue</Button>
</WrapPanel>
</StackPanel>
</Window>
using
using
using
using
using
System;
System.Collections.Generic;
System.Reflection;
System.Windows;
System.Windows.Media;
namespace WpfTutorialSamples.ComboBox_control
{
Page 254 of 359
wpf-tutorial.com
The interesting part of this example is the three event handlers for our three buttons, as well as the
SelectionChanged event handler. In the first two, we select the previous or the next item by reading the
Page 255 of 359
wpf-tutorial.com
SelectedIndex property and then subtracting or adding one to it. Pretty simple and easy to work with.
In the third event handler, we use the SelectedItem to select a specific item based on the value. I do a bit
of extra work here (using .NET reflection), because the ComboBox is bound to a list of properties, each
being a color, instead of a simple list of colors, but basically it's all about giving the value contained by one
of the items to the SelectedItem property.
In the fourth and last event handler, I respond to the selected item being changed. When that happens, I
read the selected color (once again using Reflection, as described above) and then use the selected color
to create a new background brush for the Window. The effect can be seen on the screenshot.
If you're working with an editable ComboBox (IsEditable property set to true), you can read the Text
property to know the value the user has entered or selected.
wpf-tutorial.com
1.15.1.2. Summary
The ListView is a complex control, with lots of possibilities and especially in the WPF version, you get to
customize it almost endlessly if you want to. For that reason, we have dedicated an entire category to all
the ListView articles here on the site. Click on to the next article to get started.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewBasicSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewBasicSample" Height="200" Width="200">
<Grid>
<ListView Margin="10">
<ListViewItem>A ListView</ListViewItem>
<ListViewItem IsSelected="True">with several</
ListViewItem>
<ListViewItem>items</ListViewItem>
</ListView>
</Grid>
</Window>
This is pretty much as simple as it gets, using manually specified ListViewItem to fill the list and with
nothing but a text label representing each item - a bare minimum WPF ListView control.
wpf-tutorial.com
an image ID or key to a property. Instead, you take full control of it and specify the controls needed to
render both image and text in the ListViewItem. Here's an example:
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewBasicSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewBasicSample" Height="200" Width="200">
<Grid>
<ListView Margin="10">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_green.png" Margin
="0,0,5,0" />
<TextBlock>Green</TextBlock>
</StackPanel>
</ListViewItem>
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_blue.png" Margin="0,0,5,0"
/>
<TextBlock>Blue</TextBlock>
</StackPanel>
</ListViewItem>
<ListViewItem IsSelected="True">
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_red.png" Margin="0,0,5,0"
/>
<TextBlock>Red</TextBlock>
</StackPanel>
</ListViewItem>
</ListView>
</Grid>
</Window>
What we do here is very simple. Because the ListViewItem derives from the ContentControl class, we can
specify a WPF control as its content. In this case, we use a StackPanel, which has an Image and a
TextBlock as its child controls.
Page 259 of 359
wpf-tutorial.com
1.15.2.2. Summary
As you can see, building a ListView manually in XAML is very simple, but in most cases, your ListView data
will come from some sort of data source, which should be rendered in the ListView at runtime. We will look
into doing just that in the next chapter.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewDataBindingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewDataBindingSample" Height="300" Width="300">
<Grid>
<ListView Margin="10" Name="lvDataBinding"></ListView>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.ListView_control
{
public partial class ListViewDataBindingSample : Window
{
public ListViewDataBindingSample()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42 });
items.Add(new User() { Name = "Jane Doe", Age = 39 });
items.Add(new User() { Name = "Sammy Doe", Age = 13 });
lvDataBinding.ItemsSource = items;
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
wpf-tutorial.com
}
}
We populate a list of our own User objects, each user having a name and an age. The data binding
process happens automatically as soon as we assign the list to the ItemsSource property of the ListView,
but the result is a bit discouraging:
Each user is represented by their type name in the ListView. This is to be expected, because .NET doesn't
have a clue about how you want your data to be displayed, so it just calls the ToString() method on each
object and uses that to represent the item. We can use that to our advantage and override the ToString()
method, to get a more meaningful output. Try replacing the User class with this version:
This is a much more user friendly display and will do just fine in some cases, but relying on a simple string
Page 262 of 359
wpf-tutorial.com
is not that flexible. Perhaps you want a part of the text to be bold or another color? Perhaps you want an
image? Fortunately, WPF makes all of this very simple using templates.
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewItemTemplateSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewItemTemplateSample" Height="150" Width="350">
<Grid>
<ListView Margin="10" Name="lvDataBinding">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}"
FontWeight="Bold" />
<TextBlock Text=", " />
<TextBlock Text="Age: " />
<TextBlock Text="{Binding Age}" FontWeight
="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}"
TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
wpf-tutorial.com
namespace WpfTutorialSamples.ListView_control
{
public partial class ListViewItemTemplateSample : Window
{
public ListViewItemTemplateSample()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Mail
= "[email protected]" });
items.Add(new User() { Name = "Jane Doe", Age = 39, Mail
= "[email protected]" });
items.Add(new User() { Name = "Sammy Doe", Age = 13, Mail
= "[email protected]" });
lvDataBinding.ItemsSource = items;
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
}
}
We use a bunch of TextBlock controls to build each item, where we put part of the text in bold. For the email address, which we added to this example, we underline it, give it a blue color and change the mouse
cursor, to make it behave like a hyperlink.
1.15.3.2. Summary
Page 264 of 359
wpf-tutorial.com
Using an ItemTemplate and data binding, we produced a pretty cool ListView control. However, it still looks
a lot like a ListBox. A very common usage scenario for a ListView is to have columns, sometimes (e.g. in
WinForms) referred to as a details view. WPF comes with a built-in view class to handle this, which we will
talk about in the next chapter.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewGridViewSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewGridViewSample" Height="200" Width="400">
<Grid>
<ListView Margin="10" Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50"
DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="150"
DisplayMemberBinding="{Binding Mail}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.ListView_control
{
public partial class ListViewGridViewSample : Window
{
public ListViewGridViewSample()
{
Page 266 of 359
wpf-tutorial.com
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Mail
= "[email protected]" });
items.Add(new User() { Name = "Jane Doe", Age = 39, Mail
= "[email protected]" });
items.Add(new User() { Name = "Sammy Doe", Age = 7, Mail
= "[email protected]" });
lvUsers.ItemsSource = items;
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
}
}
So, we use the same User class as previously, for test data, which we then bind to the ListView. This is all
the same as we saw in previous chapters, but as you can see from the screenshot, the layout is very
different. This is the power of data binding - the same data, but presented in a completely different way, just
by changing the markup.
In the markup (XAML), we define a View for the ListView, using the ListView.View property. We set it to a
GridView, which is currently the only included view type in WPF (you can easily create your own though!).
The GridView is what gives us the column-based view that you see on the screenshot.
Page 267 of 359
wpf-tutorial.com
Inside of the GridView, we define three columns, one for each of the pieces of data that we wish to show.
The Header property is used to specify the text that we would like to show for the column and then we use
the DisplayMemberBinding property to bind the value to a property from our User class.
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewGridViewCellTemplateSample
"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewGridViewCellTemplateSample" Height="200" Width
="400">
<Grid>
<ListView Margin="10" Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50"
DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Mail}"
TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
Page 268 of 359
wpf-tutorial.com
</Window>
Please notice: The Code-behind code for this example is the same as the one used for the first example in
this article.
We specify a custom CellTemplate for the last column, where we would like to do some special formatting
for the e-mail addresses. For the other columns, where we just want basic text output, we stick with the
DisplayMemberBinding, simply because it requires way less markup.
wpf-tutorial.com
Let's try changing that to left aligned column names. Unfortunately, there are no direct properties on the
GridViewColumn to control this, but fortunately that doesn't mean that it can't be changed.
Using a Style, targeted at the GridViewColumHeader, which is the element used to show the header of a
GridViewColumn, we can change the HorizontalAlignment property. In this case it defaults to Center, but
we can change it to Left, to accomplish what we want:
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewGridViewSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewGridViewSample" Height="200" Width="400">
<Grid>
<ListView Margin="10" Name="lvUsers">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment"
Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
Page 270 of 359
wpf-tutorial.com
The part that does all the work for us, is the Style defined in the Resources of the ListView:
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewGridViewSample"
xmlns
Page 271 of 359
wpf-tutorial.com
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewGridViewSample" Height="200" Width="400">
<Window.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value
="Left" />
</Style>
</Window.Resources>
<Grid>
<ListView Margin="10" Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50"
DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="150"
DisplayMemberBinding="{Binding Mail}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
In case you want another alignment, e.g. right alignment, you just change the value of the style like this:
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewGroupSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewGroupSample" Height="300" Width="300">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50"
DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" FontSize
="14" Text="{Binding Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</Window>
using System;
Page 273 of 359
wpf-tutorial.com
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace WpfTutorialSamples.ListView_control
{
public partial class ListViewGroupSample : Window
{
public ListViewGroupSample()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Sex =
SexType.Male });
items.Add(new User() { Name = "Jane Doe", Age = 39, Sex =
SexType.Female });
items.Add(new User() { Name = "Sammy Doe", Age = 13, Sex
= SexType.Male });
lvUsers.ItemsSource = items;
CollectionView view =
(CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource)
;
PropertyGroupDescription groupDescription = new
PropertyGroupDescription("Sex");
view.GroupDescriptions.Add(groupDescription);
}
}
public enum SexType { Male, Female };
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
public SexType Sex { get; set; }
}
wpf-tutorial.com
In XAML, I have added a GroupStyle to the ListView, in which I define a template for the header of each
group. It consists of a TextBlock control, where I've used a slightly larger and bold text to show that it's a
group - as we'll see later on, this can of course be customized a lot more. The TextBlock Text property is
bound to a Name property, but please be aware that this is not the Name property on the data object
(in this case the User class). Instead, it is the name of the group, as assigned by WPF, based on the
property we use to divide the objects into groups.
In Code-behind, we do the same as we did before: We create a list and add some User objects to it and
then we bind the list to the ListView - nothing new there, except for the new Sex property that I've added,
which tells whether the user is male or female.
After assigning an ItemsSource, we use this to get a CollectionView that the ListView creates for us. This
specialized View instance contains a lot of possibilities, including the ability to group the items. We use this
by adding a so-called PropertyGroupDescription to the GroupDescriptions of the view. This basically tells
WPF to group by a specific property on the data objects, in this case the Sex property.
<Window x:Class
wpf-tutorial.com
="WpfTutorialSamples.ListView_control.ListViewCollapseExpandGroupSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewCollapseExpandGroupSample" Height="300" Width
="300">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50"
DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded
="True">
<Expander.Header>
<StackPanel
Orientation="Horizontal">
<
TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray"
FontSize="22" VerticalAlignment="Bottom" />
<
TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green"
FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment
="Bottom" />
<
TextBlock Text=" item(s)" FontSize="22" Foreground="Silver" FontStyle
="Italic" VerticalAlignment="Bottom" />
</StackPanel
>
wpf-tutorial.com
</Expander.Header
>
<ItemsPresenter
/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</Window>
The Code-behind is exactly the same as used in the first example - feel free to scroll up and grab it.
Now our groups look a bit more exciting, and they even include an expander button, that will toggle the
visibility of the group items when you click it (that's why the single female user is not visible on the
screenshot - I collapsed that particular group). By using the ItemCount property that the group exposes, we
can even show how many items each group currently consists of.
As you can see, it requires a bit more markup than we're used to, but this example also goes a bit beyond
what we usually do, so that seems fair. When you read through the code, you will quickly realize that many
of the lines are just common elements like style and template.
1.15.6.2. Summary
Adding grouping to the WPF ListView is very simple - all you need is a GroupStyle with a HeaderTemplate,
Page 277 of 359
wpf-tutorial.com
to tell the ListView how to render a group, and a few lines of Code-behind code to tell WPF which property
to group by. As you can see from the last example, the group is even very customizable, allowing you to
create some really cool views, without too much work.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewSortingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewSortingSample" Height="200" Width="300">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50"
DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
using
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Windows;
System.Windows.Data;
namespace WpfTutorialSamples.ListView_control
{
public partial class ListViewSortingSample : Window
{
public ListViewSortingSample()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42 });
Page 279 of 359
wpf-tutorial.com
The XAML looks just like a previous example, where we simply have a couple of columns for displaying
information about the user - nothing new here.
In the Code-behind, we once again create a list of User objects, which we then assign as the ItemsSource
of the ListView. Once we've done that, we use the ItemsSource property to get the CollectionView instance
that the ListView automatically creates for us and which we can use to manipulate how the ListView shows
our objects.
With the view object in our hand, we add a new SortDescription to it, specifying that we want our list sorted
Page 280 of 359
wpf-tutorial.com
by the Age property, in ascending order. As you can see from the screenshot, this works perfectly well - the
list is sorted by age, instead of being in the same order as the items were added.
CollectionView view =
(CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource)
;
view.SortDescriptions.Add(new SortDescription("Age",
ListSortDirection.Ascending));
view.SortDescriptions.Add(new SortDescription("Name",
ListSortDirection.Ascending));
Now the view will be sorted using age first, and when two identical values are found, the name will be used
as a secondary sorting parameter.
1.15.7.2. Summary
It's very easy to sort the contents of a ListView, as seen in the above examples, but so far, all the sorting is
decided by the programmer and not the end-user. In the next article I'll give you a how-to article showing
you how to let the user decide the sorting by clicking on the columns, as seen in Windows.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.ListView_control.ListViewColumnSortingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewColumnSortingSample" Height="200" Width="350">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
Page 282 of 359
wpf-tutorial.com
<GridView>
<GridViewColumn Width="120"
DisplayMemberBinding="{Binding Name}">
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Name"
Click="lvUsersColumnHeader_Click">Name</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="80" DisplayMemberBinding
="{Binding Age}">
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Age" Click
="lvUsersColumnHeader_Click">Age</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="80" DisplayMemberBinding
="{Binding Sex}">
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Sex" Click
="lvUsersColumnHeader_Click">Sex</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Notice how I have specified headers for each of the columns using an actual GridViewColumnHeader
element instead of just specifying a string. This is done so that I may set additional properties, in this case
the Tag property as well as the Click event.
The Tag property is used to hold the field name that will be used to sort by, if this particular column is
clicked. This is done in the lvUsersColumnHeader_Click event that each of the columns subscribes to.
That was the key concepts of the XAML. Besides that, we bind to our Code-behind properties Name, Age
and Sex, which we'll discuss now.
wpf-tutorial.com
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel;
System.Windows;
System.Windows.Controls;
System.Windows.Data;
System.Windows.Documents;
System.Windows.Media;
namespace WpfTutorialSamples.ListView_control
{
public partial class ListViewColumnSortingSample : Window
{
private GridViewColumnHeader listViewSortCol = null;
private SortAdorner listViewSortAdorner = null;
public ListViewColumnSortingSample()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Sex =
SexType.Male });
items.Add(new User() { Name = "Jane Doe", Age = 39, Sex =
SexType.Female });
items.Add(new User() { Name = "Sammy Doe", Age = 13, Sex
= SexType.Male });
items.Add(new User() { Name = "Donna Doe", Age = 13, Sex
= SexType.Female });
lvUsers.ItemsSource = items;
}
private void lvUsersColumnHeader_Click(object sender,
RoutedEventArgs e)
{
GridViewColumnHeader column = (sender as
GridViewColumnHeader);
string sortBy = column.Tag.ToString();
if(listViewSortCol != null)
{
wpf-tutorial.com
AdornerLayer.GetAdornerLayer(listViewSortCol).Remove(listViewSortAdorner
);
lvUsers.Items.SortDescriptions.Clear();
}
ListSortDirection newDir = ListSortDirection.Ascending;
if(listViewSortCol == column &&
listViewSortAdorner.Direction == newDir)
newDir = ListSortDirection.Descending;
listViewSortCol = column;
listViewSortAdorner = new SortAdorner(listViewSortCol,
newDir);
AdornerLayer.GetAdornerLayer(listViewSortCol).Add(listViewSortAdorner);
lvUsers.Items.SortDescriptions.Add(new
SortDescription(sortBy, newDir));
}
}
public enum SexType { Male, Female };
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
public SexType Sex { get; set; }
}
public class SortAdorner : Adorner
{
private static Geometry ascGeometry =
Geometry.Parse("M 0 4 L 3.5 0 L 7 4 Z");
private static Geometry descGeometry =
Geometry.Parse("M 0 0 L 3.5 4 L 7 0 Z");
wpf-tutorial.com
wpf-tutorial.com
1.15.8.3. Summary
Congratulations, you now have a fully sortable ListView with visual indication of sort column and direction.
In case you want to know more about some of the concepts used in this article, like data binding, geometry
or ListViews in general, then please check out some of the other articles, where each of the subjects are
covered in depth.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.ListView_control.FilteringSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FilteringSample" Height="200" Width="300">
<DockPanel Margin="10">
<TextBox DockPanel.Dock="Top" Margin="0,0,0,10" Name
="txtFilter" TextChanged="txtFilter_TextChanged" />
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50"
DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.Windows.Data;
namespace WpfTutorialSamples.ListView_control
{
public partial class FilteringSample : Window
{
public FilteringSample()
Page 288 of 359
wpf-tutorial.com
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42 });
items.Add(new User() { Name = "Jane Doe", Age = 39 });
items.Add(new User() { Name = "Sammy Doe", Age = 13 });
items.Add(new User() { Name = "Donna Doe", Age = 13 });
lvUsers.ItemsSource = items;
CollectionView view =
(CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource)
;
view.Filter = UserFilter;
}
private bool UserFilter(object item)
{
if(String.IsNullOrEmpty(txtFilter.Text))
return true;
else
return ((item as User).Name.IndexOf(txtFilter.Text,
StringComparison.OrdinalIgnoreCase) >= 0);
}
private void txtFilter_TextChanged(object sender,
System.Windows.Controls.TextChangedEventArgs e)
{
CollectionViewSource.GetDefaultView(lvUsers.ItemsSource).Refresh();
}
}
public enum SexType { Male, Female };
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
wpf-tutorial.com
The XAML part is pretty simple: We have a TextBox, where the user can enter a search string, and then a
ListView to show the result in.
In Code-behind, we start off by adding some User objects to the ListView, just like we did in previous
examples. The interesting part happens in the last two lines of the constructor, where we obtain a reference
to the CollectionView instance for the ListView and then assign a delegate to the Filter property. This
delegate points to the function called UserFilter, which we have implemented just below. It takes each item
as the first (and only) parameter and then returns a boolean value that indicates whether or not the given
item should be visible on the list.
In the UserFilter() method, we take a look at the TextBox control (txtFilter), to see if it contains any text - if
it does, we use it to check whether or not the name of the User (which is the property we have decided to
filter on) contains the entered string, and then return true or false depending on that. If the TextBox is
empty, we return true, because in that case we want all the items to be visible.
The txtFilter_TextChanged event is also important. Each time the text changes, we get a reference to the
View object of the ListView and then call the Refresh() method on it. This ensures that the Filter delegate is
called each time the user changes the value of the search/filter string text box.
1.15.9.1. Summary
This was a pretty simple implementation, but since you get access to each item, in this case of the User
class, you can do any sort of custom filtering that you like, since you have access to all of the data about
each of the items in the list. For instance, the above example could easily be changed to filter on age, by
looking at the Age property instead of the Name property, or you could modify it to look at more than one
property, e.g. to filter out users with an age below X AND a name that doesn't contain "Y".
Page 290 of 359
wpf-tutorial.com
wpf-tutorial.com
1.16.1.2. Summary
The WPF TreeView is indeed a complex control. In the first example, which we'll get into already in the
next chapter, it might seem simple, but once you dig deeper, you'll see the complexity. Fortunately, the
WPF TreeView control rewards you with great usability and flexibility. To show you all of them, we have
dedicated an entire category to all the TreeView articles. Click on to the next one to get started.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.TreeView_control.TreeViewSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TreeViewSample" Height="200" Width="250">
<Grid Margin="10">
<TreeView>
<TreeViewItem Header="Level 1" IsExpanded="True">
<TreeViewItem Header="Level 2.1" />
<TreeViewItem Header="Level 2.2" IsExpanded="True">
<TreeViewItem Header="Level 3.1" />
<TreeViewItem Header="Level 3.2" />
</TreeViewItem>
<TreeViewItem Header="Level 2.3" />
</TreeViewItem>
</TreeView>
</Grid>
</Window>
We simply declare the TreeViewItem objects directly in the XAML, in the same structure that we want to
display them in, where the first tag is a child of the TreeView control and its child objects are also child tags
to its parent object. To specify the text we want displayed for each node, we use theHeader property. By
default, a TreeViewItem is not expanded, but to show you the structure of the example, I have used the
IsExpanded property to expand the two parent items.
Page 293 of 359
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.TreeView_control.TreeViewCustomItemsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TreeViewCustomItemsSample" Height="200" Width="250">
<Grid Margin="10">
<TreeView>
<TreeViewItem IsExpanded="True">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/bullet_blue.png" />
<TextBlock Text="Level 1 (Blue)" />
</StackPanel>
</TreeViewItem.Header>
<TreeViewItem>
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Level 2.1"
Foreground="Blue" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem IsExpanded="True">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
Page 294 of 359
wpf-tutorial.com
<Image Source
="/WpfTutorialSamples;component/Images/bullet_green.png" />
<TextBlock Text="Level 2.2 (Green)"
Foreground="Blue" />
</StackPanel>
</TreeViewItem.Header>
<TreeViewItem>
<TreeViewItem.Header>
<TextBlock Text="Level 3.1"
Foreground="Green" />
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem>
<TreeViewItem.Header>
<TextBlock Text="Level 3.2"
Foreground="Green" />
</TreeViewItem.Header>
</TreeViewItem>
</TreeViewItem>
<TreeViewItem>
<TreeViewItem.Header>
<TextBlock Text="Level 2.3" Foreground
="Blue" />
</TreeViewItem.Header>
</TreeViewItem>
</TreeViewItem>
</TreeView>
</Grid>
</Window>
wpf-tutorial.com
I did a whole bunch of things here, just to show you the kind of flexibility you get: I colored the child items
and I added images and even buttons to the parent items. Because we're defining the entire thing with
simple markup, you can do almost anything, but as you can see from the example code, it does come with
a price: Huge amounts of XAML code, for a tree with just six nodes in total!
1.16.2.2. Summary
While it is entirely possible to define an entire TreeView just using markup, as we did in the above
examples, it's not the best approach in most situations, and while you could do it from Code-behind
instead, this would have resulted in even more lines of code. Once again the solution is data binding,
which we'll look into in the next chapters.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.TreeView_control.TreeViewDataBindingSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:WpfTutorialSamples.TreeView_control"
Title="TreeViewDataBindingSample" Height="150" Width="200">
<Grid Margin="10">
<TreeView Name="trvMenu">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type
self:MenuItem}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
using
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.IO;
System.Collections.ObjectModel;
namespace WpfTutorialSamples.TreeView_control
{
public partial class TreeViewDataBindingSample : Window
{
public TreeViewDataBindingSample()
{
Page 297 of 359
wpf-tutorial.com
InitializeComponent();
MenuItem root = new MenuItem() { Title = "Menu" };
MenuItem childItem1 = new MenuItem() { Title = "Child
item #1" };
childItem1.Items.Add(new MenuItem() { Title = "Child item
#1.1" });
childItem1.Items.Add(new MenuItem() { Title = "Child item
#1.2" });
root.Items.Add(childItem1);
root.Items.Add(new MenuItem() { Title = "Child item #2"
});
trvMenu.Items.Add(root);
}
}
public class MenuItem
{
public MenuItem()
{
this.Items = new ObservableCollection<MenuItem>();
}
public string Title { get; set; }
public ObservableCollection<MenuItem> Items { get; set; }
}
}
In the XAML markup, I have specified a HierarchicalDataTemplate for the ItemTemplate of the TreeView. I
instruct it to use the Items property for finding child items, by setting the ItemsSource property of the
template, and inside of it I define the actual template, which for now just consists of a TextBlock bound to
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.TreeView_control.TreeViewMultipleTemplatesSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:WpfTutorialSamples.TreeView_control"
Title="TreeViewMultipleTemplatesSample" Height="200" Width
="250">
<Grid Margin="10">
<TreeView Name="trvFamilies">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type
self:Family}" ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/group.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding Members.Count}"
Foreground="Blue" />
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:FamilyMember}">
<StackPanel Orientation="Horizontal">
<Image Source
Page 299 of 359
wpf-tutorial.com
System;
System.Collections.Generic;
System.Windows;
System.Collections.ObjectModel;
namespace WpfTutorialSamples.TreeView_control
{
public partial class TreeViewMultipleTemplatesSample : Window
{
public TreeViewMultipleTemplatesSample()
{
InitializeComponent();
List<Family> families = new List<Family>();
Family family1 = new Family() { Name =
family1.Members.Add(new FamilyMember()
Doe", Age = 42 });
family1.Members.Add(new FamilyMember()
Doe", Age = 39 });
family1.Members.Add(new FamilyMember()
Doe", Age = 13 });
families.Add(family1);
"The Doe's" };
{ Name = "John
{ Name = "Jane
{ Name = "Sammy
wpf-tutorial.com
wpf-tutorial.com
As mentioned, the two templates are declared as a part of the TreeView resources, allowing the TreeView
to select the appropriate template based on the data type that it's about to show. The template defined for
the Family type is a hierarchical template, using the Members property to show its family members.
The template defined for the FamilyMember type is a regular DataTemplate, since this type doesn't have
any child members. However, if we had wanted each FamilyMember to keep a collection of their children
and perhaps their children's children, then we would have used a hierarchical template instead.
In both templates, we use an image representing either a family or a family member, and then we show
some interesting data about it as well, like the amount of family members or the person's age.
In the code-behind, we simply create two Family instances, fill each of them with a set of members, and
then add each of the families to a list, which is then used as the items source for the TreeView.
1.16.3.3. Summary
Using data binding, the TreeView is very customizable and with the ability to specify multiple templates for
rendering different data types, the possibilities are almost endless.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.TreeView_control.TreeViewSelectionExpansionSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TreeViewSelectionExpansionSample" Height="200" Width
="300">
<DockPanel Margin="10">
<WrapPanel Margin="0,10,0,0" DockPanel.Dock="Bottom"
HorizontalAlignment="Center">
<Button Name="btnSelectNext" Click="btnSelectNext_Click"
Width="120">Select next</Button>
<Button Name="btnToggleExpansion" Click
="btnToggleExpansion_Click" Width="120" Margin="10,0,0,0">Toggle
expansion</Button>
</WrapPanel>
wpf-tutorial.com
<TreeView Name="trvPersons">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding
Children}">
<StackPanel Orientation="Horizontal">
<Image Source
="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" Margin
="0,0,4,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding
IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding
IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
</Window>
using
using
using
using
using
using
System;
System.Collections.Generic;
System.Windows;
System.Collections.ObjectModel;
System.ComponentModel;
System.Windows.Controls;
namespace WpfTutorialSamples.TreeView_control
{
public partial class TreeViewSelectionExpansionSample : Window
{
public TreeViewSelectionExpansionSample()
{
InitializeComponent();
List<Person> persons = new List<Person>();
Person person1 = new Person() { Name = "John Doe", Age =
wpf-tutorial.com
42 };
Person person2 = new Person() { Name = "Jane Doe", Age =
39 };
Person child1 = new Person() { Name = "Sammy Doe", Age =
13 };
person1.Children.Add(child1);
person2.Children.Add(child1);
person2.Children.Add(new Person() { Name = "Jenny Moe",
Age = 17 });
Person person3 = new Person() { Name = "Becky Toe", Age =
25 };
persons.Add(person1);
persons.Add(person2);
persons.Add(person3);
person2.IsExpanded = true;
person2.IsSelected = true;
trvPersons.ItemsSource = persons;
}
private void btnSelectNext_Click(object sender,
RoutedEventArgs e)
{
if(trvPersons.SelectedItem != null)
{
var list = (trvPersons.ItemsSource as List<Person>);
int curIndex = list.IndexOf(trvPersons.SelectedItem
as Person);
if(curIndex >= 0)
curIndex++;
if(curIndex >= list.Count)
curIndex = 0;
if(curIndex >= 0)
list[curIndex].IsSelected = true;
}
wpf-tutorial.com
}
private void btnToggleExpansion_Click(object sender,
RoutedEventArgs e)
{
if(trvPersons.SelectedItem != null)
(trvPersons.SelectedItem as Person).IsExpanded =
!(trvPersons.SelectedItem as Person).IsExpanded;
}
}
public class Person : TreeViewItemBase
{
public Person()
{
this.Children = new ObservableCollection<Person>();
}
public string Name { get; set; }
public int Age { get; set; }
public ObservableCollection<Person> Children { get; set; }
}
public class TreeViewItemBase : INotifyPropertyChanged
{
private bool isSelected;
public bool IsSelected
{
get { return this.isSelected; }
set
{
if(value != this.isSelected)
{
this.isSelected = value;
NotifyPropertyChanged("IsSelected");
}
wpf-tutorial.com
}
}
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
if(value != this.isExpanded)
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
}
wpf-tutorial.com
I'm sorry for the rather large amount of code in one place. In a real world solution, it would obviously be
spread out over multiple files instead and the data for the tree would likely come from an actual data
source, instead of being generated on the fly. Allow me to explain what happens in the example.
1.16.4.4. Summary
By creating and implementing a base class for the objects that you wish to use and manipulate within a
TreeView, and using the gained properties in the ItemContainerStyle, you make it a lot easier to work with
selections and expansion states. There are many solutions to tackle this problem with, and while this
should do the trick, you might be able to find a solution that fits your needs better. As always with
programming, it's all about using the right tool for the job at hand.
wpf-tutorial.com
You can now start expanding the nodes, and the application will automatically load the sub folders. If a
folder is empty, it will be shown as empty once you try to expand it, as it can be seen on the next
screenshot:
<Window x:Class="WpfTutorialSamples.TreeView_control.LazyLoadingSample"
Page 309 of 359
wpf-tutorial.com
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LazyLoadingSample" Height="300" Width="300">
<Grid>
<TreeView Name="trvStructure" TreeViewItem.Expanded
="TreeViewItem_Expanded" Margin="10" />
</Grid>
</Window>
using
using
using
using
System;
System.IO;
System.Windows;
System.Windows.Controls;
namespace WpfTutorialSamples.TreeView_control
{
public partial class LazyLoadingSample : Window
{
public LazyLoadingSample()
{
InitializeComponent();
DriveInfo[] drives = DriveInfo.GetDrives();
foreach(DriveInfo driveInfo in drives)
trvStructure.Items.Add(CreateTreeItem(driveInfo));
Page 310 of 359
wpf-tutorial.com
}
public void TreeViewItem_Expanded(object sender,
RoutedEventArgs e)
{
TreeViewItem item = e.Source as TreeViewItem;
if((item.Items.Count == 1) && (item.Items[0] is string))
{
item.Items.Clear();
DirectoryInfo expandedDir = null;
if(item.Tag is DriveInfo)
expandedDir = (item.Tag as
DriveInfo).RootDirectory;
if(item.Tag is DirectoryInfo)
expandedDir = (item.Tag as DirectoryInfo);
try
{
foreach(DirectoryInfo subDir in
expandedDir.GetDirectories())
item.Items.Add(CreateTreeItem(subDir));
}
catch { }
}
}
private TreeViewItem CreateTreeItem(object o)
{
TreeViewItem item = new TreeViewItem();
item.Header = o.ToString();
item.Tag = o;
item.Items.Add("Loading...");
return item;
}
}
}
The XAML is very simple and only one interesting detail is present: The way we subscribe to the
Expanded event of TreeViewItem's. Notice that this is indeed the TreeViewItem and not the TreeView itself,
but because the event bubbles up, we are able to just capture it in one place for the entire TreeView,
instead of having to subscribe to it for each item we add to the tree. This event gets called each time an
item is expanded, which we need to be aware of to load its child items on demand.
wpf-tutorial.com
In Code-behind, we start by adding each drive found on the computer to the TreeView control. We assign
the DriveInfoinstance to the Tag property, so that we can later retrieve it. Notice that we use a custom
method to create the TreeViewItem, called CreateTreeItem(), since we can use the exact same method
when we want to dynamically add a child folder later on. Notice in this method how we add a child item to
the Items collection, in the form of a string with the text "Loading...".
Next up is the TreeViewItem_Expanded event. As already mentioned, this event is raised each time a
TreeView item is expanded, so the first thing we do is to check whether this item has already been loaded,
by checking if the child items currently consists of only one item, which is a string - if so, we have found the
"Loading..." child item, which means that we should now load the actual contents and replace the
placeholder item with it.
We now use the items Tag property to get a reference to the DriveInfo or DirectoryInfo instance that the
current item represents, and then we get a list of child directories, which we add to the clicked item, once
again using the CreateTreeItem() method. Notice that the loop where we add each child folder is in a
try..catch block - this is important, because some paths might not be accessible, usually for security
reasons. You could grab the exception and use it to reflect this in the interface in one way or another.
1.16.5.1. Summary
By subscribing to the Expanded event, we can easily create a lazy-loaded TreeView, which can be a much
better solution than a statically created one in several situations.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.DataGrid_control.SimpleDataGridSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SimpleDataGridSample" Height="180" Width="300">
<Grid Margin="10">
<DataGrid Name="dgSimple"></DataGrid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.DataGrid_control
{
public partial class SimpleDataGridSample : Window
{
public SimpleDataGridSample()
{
InitializeComponent();
Page 313 of 359
wpf-tutorial.com
That's really all you need to start using the DataGrid. The source could just as easily have been a
database table/view or even an XML file - the DataGrid is not picky about where it gets its data from.
If you click inside one of the cells, you can see that you're allowed to edit each of the properties by default.
As a nice little bonus, you can try clicking one of the column headers - you will see that the DataGrid
supports sorting right out of the box!
The last and empty row will let you add to the data source, simply by filling out the cells.
Page 314 of 359
wpf-tutorial.com
1.17.1.2. Summary
As you can see, it's extremely easy to get started with the DataGrid, but it's also a highly customizable
control. In the next chapters, we'll look into all the cool stuff you can do with the DataGrid, so read on.
wpf-tutorial.com
DataGridTextColumn
DataGridCheckBoxColumn
DataGridComboBoxColumn
DataGridHyperlinkColumn
DataGridTemplateColumn
Especially the last one, the DataGridTemplateColumn, is interesting. It allows you to define any kind of
content, which opens up the opportunity to use custom controls, either from the WPF library or even your
own or 3rd party controls. Here's an example:
<Window x:Class
="WpfTutorialSamples.DataGrid_control.DataGridColumnsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataGridColumnsSample" Height="200" Width="300">
<Grid Margin="10">
wpf-tutorial.com
wpf-tutorial.com
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
}
In the markup, I have added the AutoGenerateColumns property on the DataGrid, which I have set to
false, to get control of the columns used. As you can see, I have left out the ID column, as I decided that I
didn't care for it for this example. For the Name property, I've used a simple text based column, so the most
interesting part of this example comes with the Birthday column, where I've used a
DataGridTemplateColumn with a DatePicker control inside of it. This allows the end-user to pick the date
from a calendar, instead of having to manually enter it, as you can see on the screenshot.
1.17.2.2. Summary
By turning off automatically generated columns using the AutoGenerateColumns property, you get full
control of which columns are shown and how their data should be viewed and edited. As seen by the
example of this article, this opens up for some pretty interesting possibilities, where you can completely
customize the editor and thereby enhance the end-user experience.
Page 318 of 359
wpf-tutorial.com
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.DataGrid_control.DataGridDetailsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataGridDetailsSample" Height="200" Width="400">
<Grid Margin="10">
<DataGrid Name="dgUsers" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding
Name}" />
<DataGridTextColumn Header="Birthday" Binding="
{Binding Birthday}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Details}" Margin="10"
/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.DataGrid_control
{
public partial class DataGridDetailsSample : Window
{
public DataGridDetailsSample()
{
InitializeComponent();
Page 320 of 359
wpf-tutorial.com
As you can see, I have expanded the example from previous chapters with a new property on the User
class: The Description property. It simply returns a bit of information about the user in question, for our
details row.
In the markup, I have defined a couple of columns and then I use the RowDetailsTemplate to specify a
template for the row details. As you can see, it works much like any other WPF template, where I use a
DataTemplate with one or several controls inside of it, along with a standard binding against a property on
the data source, in this case the Description property.
wpf-tutorial.com
As you can see from the resulting screenshot, or if you run the sample yourself, the details are now shown
below the selected row. As soon as you select another row, the details for that row will be shown and the
details for the previously selected row will be hidden.
If you set it to Collapsed, all details will be invisible all the time.
Page 322 of 359
wpf-tutorial.com
As you can see from the code listing, it's mostly about expanding the details template into using a panel,
which in turn can host more panels and/or controls. Using a Grid panel, we can get the tabular look of the
user data, and an Image control allows us to show a picture of the user (which you should preferably load
from a locale resource and not a remote one, like I do in the example - and sorry for being too lazy to find a
matching image of Jane and Sammy Doe).
<Window x:Class
="WpfTutorialSamples.DataGrid_control.DataGridDetailsSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataGridDetailsSample" Height="300" Width="300">
<Grid Margin="10">
<DataGrid Name="dgUsers" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding
Name}" />
<DataGridTextColumn Header="Birthday" Binding="
{Binding Birthday}" />
Page 323 of 359
wpf-tutorial.com
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DockPanel Background="GhostWhite">
<Image DockPanel.Dock="Left" Source="
{Binding ImageUrl}" Height="64" Margin="10" />
<Grid Margin="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="ID: " FontWeight
="Bold" />
<TextBlock Text="{Binding Id}"
Grid.Column="1" />
<TextBlock Text="Name: " FontWeight
="Bold" Grid.Row="1" />
<TextBlock Text="{Binding Name}"
Grid.Column="1" Grid.Row="1" />
<TextBlock Text="Birthday: "
FontWeight="Bold" Grid.Row="2" />
<TextBlock Text="{Binding Birthday,
StringFormat=d}" Grid.Column="1" Grid.Row="2" />
</Grid>
</DockPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
wpf-tutorial.com
using System.Windows;
namespace WpfTutorialSamples.DataGrid_control
{
public partial class DataGridDetailsSample : Window
{
public DataGridDetailsSample()
{
InitializeComponent();
List<User> users = new List<User>();
users.Add(new User() { Id = 1, Name = "John Doe",
Birthday = new DateTime(1971, 7, 23), ImageUrl = "http://www.wpftutorial.com/images/misc/john_doe.jpg" });
users.Add(new User() { Id = 2, Name = "Jane Doe",
Birthday = new DateTime(1974, 1, 17) });
users.Add(new User() { Id = 3, Name = "Sammy Doe",
Birthday = new DateTime(1991, 9, 2) });
dgUsers.ItemsSource = users;
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public string ImageUrl { get; set; }
}
}
1.17.3.3. Summary
Being able to show details for a DataGrid row is extremely useful, and with the WPF DataGrid it's both
easy and highly customizable, as you can see from the examples provided in this tutorial.
wpf-tutorial.com
1.18. Styles
1.18.1. Introduction to WPF styles
If you come from the world of developing for the web, using HTML and CSS, you'll quickly realize that
XAML is much like HTML: Using tags, you define a structural layout of your application. You can even
make your elements look a certain way, using inline properties like Foreground, FontSize and so on, just
like you can locally style your HTML tags.
But what happens when you want to use the exact same font size and color on three different TextBlock
controls? You can copy/paste the desired properties to each of them, but what happens when three
controls becomes 50 controls, spread out over several windows? And what happens when you realize that
the font size should be 14 instead of 12?
WPF introduces styling, which is to XAML what CSS is to HTML. Using styles, you can group a set of
properties and assign them to specific controls or all controls of a specific type, and just like in CSS, a style
can inherit from another style.
<Window x:Class="WpfTutorialSamples.Styles.SimpleStyleSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SimpleStyleSample" Height="200" Width="250">
<StackPanel Margin="10">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="24" />
</Style>
</StackPanel.Resources>
<TextBlock>Header 1</TextBlock>
<TextBlock>Header 2</TextBlock>
<TextBlock Foreground="Blue">Header 3</TextBlock>
</StackPanel>
</Window>
wpf-tutorial.com
For the resources of my StackPanel, I define a Style. I use the TargetType property to tell WPF that this
style should be applied towards ALL TextBlock controls within the scope (the StackPanel), and then I add
two Setter elements to the style. The Setter elements are used to set specific properties for the target
controls, in this case Foreground and FontSize properties. The Property property tells WPF which
property we want to target, and the Value property defines the desired value.
Notice that the last TextBlock is blue instead of gray. I did that to show you that while a control might get
styling from a designated style, you are completely free to override this locally on the control - values
defined directly on the control will always take precedence over style values.
1.18.1.2. Summary
WPF styles make it very easy to create a specific look and then use it for several controls, and while this
first example was very local, I will show you how to create global styles in the next chapters.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Styles.ControlSpecificStyleSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControlSpecificStyleSample" Height="100" Width="300">
<Grid Margin="10">
<TextBlock Text="Style test">
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.FontSize" Value
="36" />
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
In this example, the style only affects this specific TextBlock control, so why bother? Well, in this case, it
makes no sense at all. I could have replaced all that extra markup with a single FontSize property on the
TextBlock control, but as we'll see later, styles can do a bit more than just set properties, for instance, style
triggers could make the above example useful in a real life application. However, most of the styles you'll
define will likely be in a higher scope.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Styles.SimpleStyleSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SimpleStyleSample" Height="200" Width="250">
<StackPanel Margin="10">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="24" />
</Style>
</StackPanel.Resources>
<TextBlock>Header 1</TextBlock>
<TextBlock>Header 2</TextBlock>
<TextBlock Foreground="Blue">Header 3</TextBlock>
</StackPanel>
</Window>
This is great for the more local styling needs. For instance, it would make perfect sense to do this in a
dialog where you simply needed a set of controls to look the same, instead of setting the individual
properties on each of them.
wpf-tutorial.com
The next step up in the scope hierarchy is to define the style(s) within the Window resources. This is done
in exactly the same way as above for the StackPanel, but it's useful in those situations where you want a
specific style to apply to all controls within a window (or a UserControl for that matter) and not just locally
within a specific control. Here's a modified example:
<Window x:Class="WpfTutorialSamples.Styles.WindowWideStyleSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowWideStyleSample" Height="200" Width="300">
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="24" />
</Style>
</Window.Resources>
<StackPanel Margin="10">
<TextBlock>Header 1</TextBlock>
<TextBlock>Header 2</TextBlock>
<TextBlock Foreground="Blue">Header 3</TextBlock>
</StackPanel>
</Window>
As you can see, the result is exactly the same, but it does mean that you could have controls placed
everywhere within the window and the style would still apply.
wpf-tutorial.com
<Application x:Class="WpfTutorialSamples.App"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Styles/WindowWideStyleSample.xaml">
<Application.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="24" />
</Style>
</Application.Resources>
</Application>
Window
<Window x:Class="WpfTutorialSamples.Styles.WindowWideStyleSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ApplicationWideStyleSample" Height="200" Width="300">
<StackPanel Margin="10">
<TextBlock>Header 1</TextBlock>
<TextBlock>Header 2</TextBlock>
<TextBlock Foreground="Blue">Header 3</TextBlock>
</StackPanel>
</Window>
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Styles.ExplicitStyleSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ExplicitStyleSample" Height="150" Width="300">
<Window.Resources>
<Style x:Key="HeaderStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="24" />
</Style>
</Window.Resources>
<StackPanel Margin="10">
<TextBlock>Header 1</TextBlock>
<TextBlock Style="{StaticResource HeaderStyle}">Header 2</
TextBlock>
<TextBlock>Header 3</TextBlock>
</StackPanel>
</Window>
Notice how even though the TargetType is set to TextBlock, and the style is defined for the entire window,
only the TextBlock in the middle, where I explicitly reference the HeaderStyle style, uses the style. This
allows you to define styles that target a specific control type, but only use it in the places where you need it.
Page 332 of 359
wpf-tutorial.com
1.18.2.6. Summary
WPF styling allows you to easily re-use a certain look for your controls all over the application. Using the
x:Key property, you can decide whether a style should be explicitly referenced to take effect, or if it should
target all controls no matter what.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Styles.StyleTriggersSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleTriggersSample" Height="100" Width="300">
<Grid>
<TextBlock Text="Hello, styled world!" FontSize="28"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Blue"></
Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value
="True">
<Setter Property="Foreground" Value
="Red" />
<Setter Property="TextDecorations"
Value="Underline" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
wpf-tutorial.com
In this style, we set the Foreground property to blue, to make it look like a hyperlink. We then add a
trigger, which listens to theIsMouseOver property - once this property changes to True, we apply two
setters: We change the Foreground to red and then we make it underlined. This is a great example on
how easy it is to use triggers to apply design changes, completely without any code-behind code.
We define a local style for this specific TextBlock, but as shown in the previous articles, the style could
have been globally defined as well, if we wanted it to apply to all TextBlock controls in the application.
<Window x:Class="WpfTutorialSamples.Styles.StyleDataTriggerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleDataTriggerSample" Height="200" Width="200">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
>
<CheckBox Name="cbSample" Content="Hello, world?" />
<TextBlock HorizontalAlignment="Center" Margin="0,20,0,0"
FontSize="48">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No" />
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding
ElementName=cbSample, Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Yes!"
/>
<Setter Property="Foreground" Value
="Green" />
Page 335 of 359
wpf-tutorial.com
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Window>
In this example, we have a CheckBox and a TextBlock. Using a DataTrigger, we bind the TextBlock to
the IsChecked property of the CheckBox. We then supply a default style, where the text is "No" and the
foreground color is red, and then, using a DataTrigger, we supply a style for when the IsChecked property
of the CheckBox is changed to True, in which case we make it green with a text saying "Yes!" (as seen on
the screenshot).
<Window x:Class="WpfTutorialSamples.Styles.StyleEventTriggerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleEventTriggerSample" Height="100" Width="300">
<Grid>
<TextBlock Name="lblStyled" Text="Hello, styled world!"
FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
Page 336 of 359
wpf-tutorial.com
<Style TargetType="TextBlock">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.300" Storyboard.TargetProperty="FontSize" To="28" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.800" Storyboard.TargetProperty="FontSize" To="18" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
The markup might look a bit overwhelming, but if you run this sample and look at the result, you'll see that
we've actually accomplished a pretty cool animation, going both ways, in ~20 lines of XAML. As you can
see, I use an EventTrigger to subscribe to two events: MouseEnter and MouseLeave. When the mouse
enters, I make a smooth and animated transition to a FontSize of 28 pixels in 300 milliseconds. When the
mouse leaves, I change the FontSize back to 18 pixels but I do it a bit slower, just because it looks kind of
cool.
wpf-tutorial.com
1.18.3.4. Summary
WPF styles make it easy to get a consistent look, and with triggers, this look becomes dynamic. Styles are
great in your application, but they're even better when used in control templates etc. You can read more
about that elsewhere in this tutorial.
In the next article, we'll look at multi triggers, which allow us to apply styles based on multiple properties.
wpf-tutorial.com
1.18.4.1. MultiTrigger
<Window x:Class="WpfTutorialSamples.Styles.StyleMultiTriggerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleMultiTriggerSample" Height="100" Width="250">
<Grid>
<TextBox VerticalAlignment="Center" HorizontalAlignment
="Center" Text="Hover and focus here" Width="150">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property
="IsKeyboardFocused" Value="True" />
<Condition Property
="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Background"
Value="LightGreen" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</Window>
wpf-tutorial.com
In this example, we use a trigger to change the background color of the TextBox once it has keyboard
focus AND the mouse cursor is over it, as seen on the screenshot. This trigger has two conditions, but we
could easily have added more if needed. In the Setters section, we define the properties we wish to change
when all the conditions are met - in this case, just the one (background color).
1.18.4.2. MultiDataTrigger
Just like a regular DataTrigger, the MultiDataTrigger is cool because it uses bindings to monitor a property.
This means that you can use all of the cool WPF binding techniques, including binding to the property of
another control etc. Let me show you how easy it is:
<Window x:Class="WpfTutorialSamples.Styles.StyleMultiDataTriggerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleMultiDataTriggerSample" Height="150" Width="200">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
>
<CheckBox Name="cbSampleYes" Content="Yes" />
<CheckBox Name="cbSampleSure" Content="I'm sure" />
<TextBlock HorizontalAlignment="Center" Margin="0,20,0,0"
FontSize="28">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Unverified" />
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding
ElementName=cbSampleYes, Path=IsChecked}" Value="True" />
<Condition Binding="{Binding
ElementName=cbSampleSure, Path=IsChecked}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Text" Value
="Verified" />
Page 340 of 359
wpf-tutorial.com
In this example, I've re-created the example we used with the regular DataTrigger, but instead of binding to
just one property, I bind to the same property (IsChecked) but on two different controls. This allows us to
trigger the style only once both checkboxes are checked - if you remove a check from either one of them,
the default style will be applied instead.
1.18.4.3. Summary
As you can see, multi triggers are pretty much just as easy to use as regular triggers and they can be
extremely useful, especially when developing your own controls.
wpf-tutorial.com
<Window x:Class="WpfTutorialSamples.Styles.StyleTriggerEnterExitActions"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleTriggerEnterExitActions" Height="200" Width="200"
UseLayoutRounding="True">
<Grid>
<Border Background="LightGreen" Width="100" Height="100"
BorderBrush="Green">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value
="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Duration="0:0:0.400" To="3" Storyboard.TargetProperty="BorderThickness"
/>
<DoubleAnimation
Duration="0:0:0.300" To="125" Storyboard.TargetProperty="Height" />
<DoubleAnimation
Duration="0:0:0.300" To="125" Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Duration="0:0:0.250" To="0" Storyboard.TargetProperty="BorderThickness"
wpf-tutorial.com
/>
<DoubleAnimation
Duration="0:0:0.150" To="100" Storyboard.TargetProperty="Height" />
<DoubleAnimation
Duration="0:0:0.150" To="100" Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</Grid>
</Window>
In this example, we have a green square. It has a trigger that fires once the mouse is over, in which case it
fires of several animations, all defined in the EnterActions part of the trigger. In there, we animate the
Page 343 of 359
wpf-tutorial.com
thickness of the border from its default 0 to a thickness of 3, and then we animate the width and height from
100 to 125. This all happens simultaneously, because they are a part of the same StoryBoard, and even at
slightly different speeds, since we have full control of how long each animation should run.
We use the ExitActions to reverse the changes we made, with animations that goes back to the default
values. We run the reversing animations slightly faster, because we can and because it looks cool.
The two states are represented on the two screenshots, but to fully appreciate the effect, you should try
running the example on your own machine, using the source code above.
1.18.5.1. Summary
Using animations with style triggers is very easy, and while we haven't fully explored all you can do with
WPF animations yet, the example used above should give you an idea on just how flexible both animations
and styles are.
wpf-tutorial.com
1.19. Misc.
1.19.1. The DispatcherTimer
In WinForms, there's a control called the Timer, which can perform an action repeatedly within a given
interval. WPF has this possibility as well, but instead of an invisible control, we have the DispatcherTimer
control. It does pretty much the same thing, but instead of dropping it on your form, you create and use it
exclusively from your Code-behind code.
The DispatcherTimer class works by specifying an interval and then subscribing to the Tick event that will
occur each time this interval is met. The DispatcherTimer is not started before you call the Start() method
or set the IsEnabled property to true.
Let's try a simple example where we use a DispatcherTimer to create a digital clock:
<Window x:Class="WpfTutorialSamples.Misc.DispatcherTimerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DispatcherTimerSample" Height="150" Width="250">
<Grid>
<Label Name="lblTime" FontSize="48" HorizontalAlignment
="Center" VerticalAlignment="Center" />
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Threading;
namespace WpfTutorialSamples.Misc
{
public partial class DispatcherTimerSample : Window
{
public DispatcherTimerSample()
{
InitializeComponent();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
wpf-tutorial.com
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
lblTime.Content = DateTime.Now.ToLongTimeString();
}
}
}
The XAML part is extremely simple - it's merely a centered label with a large font size, used to display the
current time.
Code-behind is where the magic happens in this example. In the constructor of the window, we create a
DispatcherTimer instance. We set the Interval property to one second, subscribe to the Tick event and
then we start the timer. In the Tick event, we simply update the label to show the current time.
Of course, the DispatcherTimer can work at smaller or much bigger intervals. For instance, you might only
want something to happen every 30 seconds or 5 minutes - just use the TimeSpan.From* methods, like
FromSeconds or FromMinutes, or create a new TimeSpan instance that completely fits your needs.
To show what the DispatcherTimer is capable of, let's try updating more frequently... A lot more frequently!
using System;
using System.Windows;
using System.Windows.Threading;
namespace WpfTutorialSamples.Misc
{
public partial class DispatcherTimerSample : Window
{
public DispatcherTimerSample()
Page 346 of 359
wpf-tutorial.com
{
InitializeComponent();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1);
timer.Tick += timer_Tick;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
lblTime.Content = DateTime.Now.ToString("HH:mm:ss.fff");
}
}
}
As you can see, we now ask the DispatcherTimer to fire every millisecond! In the Tick event, we use a
custom time format string to show the milliseconds in the label as well. Now you have something that could
easily be used as a stopwatch - just add a couple of buttons to the Window and then have them call the
Stop(), Start() and Restart() methods on the timer.
1.19.1.1. Summary
There are many situations where you would need something in your application to occur at a given
interval, and using the DispatcherTimer, it's quite easy to accomplish. Just be aware that if you do
something complicated in your Tick event, it shouldn't run too often, like in the last example where the timer
ticks each millisecond - that will put a heavy strain on the computer running your application.
Also be aware that the DispatcherTimer is not 100% precise in all situations. The tick operations are
placed on the Dispatcher queue, so if the computer is under a lot of pressure, your operation might be
delayed. The .NET framework promises that the Tick event will never occur too early, but can't promise that
it won't be slightly delayed. However, for most use cases, the DispatcherTimer is more than precise
enough.
wpf-tutorial.com
If you need your timer to have a higher priority in the queue, you can set the DispatcherPriority by sending
one of the values along on the DispatcherTimer priority. More information about it can be found on this
MSDN article.
wpf-tutorial.com
wpf-tutorial.com
understand how and what it does, so you don't accidentally do something wrong - as already stated, errors
in multi-threading can lead to some nasty problems.
<Window x:Class="WpfTutorialSamples.Misc.BackgroundWorkerSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BackgroundWorkerSample" Height="300" Width="375">
<DockPanel Margin="10">
<DockPanel DockPanel.Dock="Top">
<Button Name="btnDoSynchronousCalculation" Click
="btnDoSynchronousCalculation_Click" DockPanel.Dock="Left"
HorizontalAlignment="Left">Synchronous (same thread)</Button>
<Button Name="btnDoAsynchronousCalculation" Click
="btnDoAsynchronousCalculation_Click" DockPanel.Dock="Right"
HorizontalAlignment="Right">Asynchronous (worker thread)</Button>
</DockPanel>
<ProgressBar DockPanel.Dock="Bottom" Height="18" Name
="pbCalculationProgress" />
<ListBox Name="lbResults" Margin="0,10" />
</DockPanel>
</Window>
using System;
using System.ComponentModel;
using System.Windows;
namespace WpfTutorialSamples.Misc
Page 350 of 359
wpf-tutorial.com
{
public partial class BackgroundWorkerSample : Window
{
public BackgroundWorkerSample()
{
InitializeComponent();
}
private void btnDoSynchronousCalculation_Click(object sender,
RoutedEventArgs e)
{
int max = 10000;
pbCalculationProgress.Value = 0;
lbResults.Items.Clear();
int result = 0;
for(int i = 0; i < max; i++)
{
if(i % 42 == 0)
{
lbResults.Items.Add(i);
result++;
}
System.Threading.Thread.Sleep(1);
pbCalculationProgress.Value = Convert.ToInt32(((
double)i / max) * 100);
}
MessageBox.Show("Numbers between 0 and 10000 divisible by
7: " + result);
}
private void btnDoAsynchronousCalculation_Click(object sender,
RoutedEventArgs e)
{
pbCalculationProgress.Value = 0;
lbResults.Items.Clear();
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
wpf-tutorial.com
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync(10000);
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
int max = (int)e.Argument;
int result = 0;
for(int i = 0; i < max; i++)
{
int progressPercentage = Convert.ToInt32(((double)i
/ max) * 100);
if(i % 42 == 0)
{
result++;
(sender as
BackgroundWorker).ReportProgress(progressPercentage, i);
}
else
(sender as
BackgroundWorker).ReportProgress(progressPercentage);
System.Threading.Thread.Sleep(1);
}
e.Result = result;
}
void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
pbCalculationProgress.Value = e.ProgressPercentage;
if(e.UserState != null)
lbResults.Items.Add(e.UserState);
}
void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Numbers between 0 and 10000 divisible by
7: " + e.Result);
}
wpf-tutorial.com
}
}
The XAML part consists of a couple of buttons, one for running the process synchronously (on the UI
thread) and one for running it asynchronously (on a background thread), a ListBox control for showing all
the calculated numbers and then a ProgressBar control in the bottom of the window to show... well, the
progress!
In Code-behind, we start off with the synchronous event handler. As mentioned, it loops from 0 to 10.000
with a small delay in each iteration, and if the number is divisible with the number 7, then we add it to the
list. In each iteration we also update the ProgressBar, and once we're all done, we show a message to the
user about how many numbers were found.
If you run the application and press the first button, it will look like this, no matter how far you are in the
process:
No items on the list and, no progress on the ProgressBar, and the button hasn't even been released, which
proves that there hasn't been a single update to the UI ever since the mouse was pressed down over the
button.
Pressing the second button instead will use the BackgroundWorker approach. As you can see from the
code, we do pretty much the same, but in a slightly different way. All the hard work is now placed in the
DoWork event, which the worker calls after you run the RunWorkerAsync() method. This method takes
input from your application which can be used by the worker, as we'll talk about later.
Page 353 of 359
wpf-tutorial.com
As already mentioned, we're not allowed to update the UI from the DoWork event. Instead, we call the
ReportProgress method on the worker. If the current number is divisible with 7, we include it to be added
on the list - otherwise we only report the current progress percentage, so that the ProgressBar may be
updated.
Once all the numbers have been tested, we assign the result to the e.Result property. This will then be
carried to the RunWorkerCompleted event, where we show it to the user. This might seem a bit
cumbersome, instead of just showing it to the user as soon as the work is done, but once again, it ensures
that we don't communicate with the UI from the DoWork event, which is not allowed.
The result is, as you can see, much more user friendly:
The window no longer hangs, the button is clicked but not suppressed, the list of possible numbers is
updated on the fly and the ProgressBar is going steadily up - the interface just got a whole lot more
responsive.
wpf-tutorial.com
This is accomplished by using a more complex type, in many cases a struct or even a class which you
create yourself and pass along. By doing this, the possibilities are pretty much endless and you can
transport as much and complex data as you want between your BackgroundWorker and your
application/UI.
The same is actually true for the ReportProgress method. Its secondary argument is called userState and
is an object type, meaning that you can pass whatever you want to the ProgressChanged method.
1.19.2.4. Summary
The BackgroundWorker is an excellent tool when you want multi-threading in your application, mainly
because it's so easy to use. In this chapter we looked at one of the things made very easy by the
BackgroundWorker, which is progress reporting, but support for cancelling the running task is very handy
as well. We'll look into that in the next chapter.
wpf-tutorial.com
<Window x:Class
="WpfTutorialSamples.Misc.BackgroundWorkerCancellationSample"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BackgroundWorkerCancellationSample" Height="120" Width
="200">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
>
<TextBlock Name="lblStatus" HorizontalAlignment="Center"
Margin="0,10" FontWeight="Bold">Not running...</TextBlock>
<WrapPanel>
<Button Name="btnStart" Width="60" Margin="10,0" Click
="btnStart_Click">Start</Button>
<Button Name="btnCancel" Width="60" Click
="btnCancel_Click">Cancel</Button>
</WrapPanel>
</StackPanel>
</Window>
using
using
using
using
System;
System.ComponentModel;
System.Windows;
System.Windows.Media;
namespace WpfTutorialSamples.Misc
{
public partial class BackgroundWorkerCancellationSample : Window
Page 356 of 359
wpf-tutorial.com
{
private BackgroundWorker worker = null;
public BackgroundWorkerCancellationSample()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
worker.CancelAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i <= 100; i++)
{
if(worker.CancellationPending == true)
{
e.Cancel = true;
return;
}
worker.ReportProgress(i);
System.Threading.Thread.Sleep(250);
}
e.Result = 42;
}
void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
wpf-tutorial.com
{
lblStatus.Text = "Working... (" + e.ProgressPercentage +
"%)";
}
void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
{
lblStatus.Foreground = Brushes.Red;
lblStatus.Text = "Cancelled by user...";
}
else
{
lblStatus.Foreground = Brushes.Green;
lblStatus.Text = "Done... Calc result: " + e.Result;
}
}
}
}
So, the XAML is very fundamental - just a label for showing the current status and then a couple of buttons
for starting and cancelling the worker.
In Code-behind, we start off by creating the BackgroundWorker instance. Pay special attention to the
WorkerSupportsCancellation and WorkerReportsProgress properties which we set to true - without
that, an exception will be thrown if we try to use these features.
The cancel button simply calls the CancelAsync() method - this will signal to the worker that the user
would like to cancel the running process by setting the CancellationPending property to true, but that is all
you can do from the UI thread - the rest will have to be done from inside the DoWork event.
In the DoWork event, we count to 100 with a 250 millisecond delay on each iteration. This gives us a nice
Page 358 of 359
wpf-tutorial.com
and lengthy task, for which we can report progress and still have time to hit that Cancel button.
Notice how I check the CancellationPending property on each iteration - if the worker is cancelled, this
property will be true and I will have the opportunity to jump out of the loop. I set the Cancel property on the
event arguments, to signal that the process was cancelled - this value is used in the
RunWorkerCompleted event to see if the worker actually completed or if it was cancelled.
In the RunWorkerCompleted event, I simply check if the worker was cancelled or not and then update the
status label accordingly.
1.19.3.1. Summary
So to sum up, adding cancellation support to your BackgroundWorker is easy - just make sure that you set
WorkerSupportsCancellation property to true and check the CancellationPending property while
performing the work. Then, when you want to cancel the task, you just have to call the CancelAsync()
method on the worker and you're done.
wpf-tutorial.com