Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / WPF

A Jump Start to the World of WPF

Rate me:
Please Sign up or sign in to vote.
4.12/5 (13 votes)
21 Jun 2008CPOL36 min read 50.7K   269   43   8
Basic concepts ranging from XAML layout, to C#->XAML communication and databinding.

CodeProjcet_CompletedWindow.jpg

Introduction

I am a very big newbie at CodeProject article submission. I do not understand it to be honest, and it took me hours to even get the horrible formatting that I did. I didn't see any WPF formatting, and my C# formatting came out like crap. If anyone knows any good articles on article submission, or a good article tool (perhaps an offline WinForm or something), I would greatly appreciate it.

Let me start by declaring that I am in no means an expert with WPF (yet). I am, however, an avid user of WPF, with a deep passion and desire to expand my knowledge. I just recently (a couple of weeks ago) picked up shop from the world of WinForms and moved over to WPF. For someone with no markup language experience (no XML, no HTML, etc...), the transfer from WinForms to WPF has been anything but easy. There are still many concepts I haven't attempted to grasp. I have, however, spent countless hours per day for the past couple of weeks surrounding myself in articles and tutorials with WPF. I have had the pleasure to (and not so pleasing) experience of viewing some great articles by users such as Bea the WPF Binding Queen, Josh Smith WPF Guru, and Sacha Barber. I've been following Josh and Sacha (early adopters of WPF on the CodeProject community) for a while, so I've enjoyed their articles without a doubt.

With that out of the way, the reason why I wrote this article is because I simply had to do a lot of digging around to understand some basic concepts of WPF that a lot of blogs out there fail to mention. Even MSDN made it difficult to follow along with their samples, as they "assumed" you understood the underlying concepts.

I'd like this page (along with Josh's: Great WPF Tutorial) to be one of the articles that mention the basic concepts for WPF that should be enough to get developers knee deep in their new found love [ WPF :) ].

Background

(Taking this from Josh Smith's article here (Great WPF Tutorial) since it contains the necessities for WPF development. I myself am using Visual C# 2008 Express w/ Vista.)

"In order to run a WPF application, you need the .NET Framework version 3.0, or greater. Windows Vista has the .NET Framework v3.0 installed by default, so you only need to install it if you have Windows XP SP2" - Josh Smith.

Simple XAML Layout

Let's begin!

I am going to start from the very beginning... Simply skip ahead to a topic that you think you may be interested in if the start is too simple for you.

I've created a new WPF Application called "JumpStartWPF". The IDE looks like this after creation (I've done nothing to it up to this point).

JumpStartWPF_Start.jpg

Take a look at the default XAML code here:

XML
<Window x:Class="JumpStartWPF.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>

Breaking this down line by line:

It starts off declaring a new Window and setting some properties. In this case, it's creating references to some default Microsoft namespaces (URLs), setting the Title text of the Window, and the Height and Width. Notice the first reference it makes though:

XML
x:Class="JumpStartWPF.Window1"

This markup is created for you by default, and points to the namespace/class of your main XAML Window and code-behind file. In this case, JumpStartWPF is the namespace we created, and Window1 is the default class name for WPF applications. If you were to change the namespace or class name in this markup, then our main XAML file (Window1.xaml) would not be able to find its code-behind file (Window1.xaml.cs) and would throw an error in the constructor of the code-behind file.

You may be wondering what code-behind means. In the WPF world, code-behind is used religiously as the physical code file behind each XAML object. In this case, our main Window's XAML file is Window1.xaml. Every XAML file needs a code-behind file to function, so ours is simply named Window1.xaml, and appended .cs (because I'm working in C#) to the code-behind file.

Next, you'll see the opening/closing tags for a Grid. It is basically a container for other controls (including other Panels), just like in the WinForms world, where GroupBox/DockPanel/etc. are commonly used as Containers.

There are many different ways to layout your UI with XAML. You may spend days or weeks deciding what you want your base Panel (Container) to be for an application. I myself have settled upon the Grid. The reason is I feel I have the most control over the Grid is, with Grid<code>, you can define rows/columns for your entire window and then place other panels within those newly defined rows/columns appropriately.

Let's do a simple, yet effective example.

XML
<Grid ShowGridLines="True">
  <Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition />
  </Grid.ColumnDefinitions>
</Grid>

OK, let's step through this.

First, I set the property "ShowGridLines" of the grid to "True". This is, in my opinion, really only useful for debugging/layout purposes... all it does is draw dotted lines at each column and row separation of the said [Grid] panel.

Next, I started defining some simple layout of the window. As you can already see, a lot of WPF concepts are very easy to understand (and a lot aren't, don't get me wrong), but the syntax is what throws some people off. In this case, you can clearly see that I am defining with two new Columns in my parent [Grid] panel. This starts out by declaring the Grid.ColumnDefinitions (and closing it), then, in between the ColumnDefinitions. I can start declaring all the columns I want my window to have. You'll see shortly how these come into play.

If you run this application now, you'll see a window with a dotted line down the middle. Pretty cool, eh? Yeah, I know... not really :) This is only the start though!

More Complex XAML Layout

Let's take what we've learned with defining columns, and expand upon that to create a look that looks like an American football field (thanks to ShowGridLines="True"). For time/simplicity's sake, I'm not going to draw each yard marker for rows. I'm simply going to draw the end zone rows, and the 50 yard line row. You can easily add more rows for getting the yard markers after you run through this quick example.

Now you might be able to guess that we're going to need to use a new concept in the Grid layout, which is Rows.

Now, if you know what an American football field looks like, you'd know that there's a horizontal line splitting the field in half (the 50 yard line). Well, I've never seen one with a vertical line which is what we have now, so let's adapt our current layout to show a horizontal line instead!

Just so you don't get lost, I've done one thing before moving on. I've set the window height to 500 and left the width at 300. American football fields are rectangular and not square, so this gives us a more desirable look.

Now, on with the layout.

So, you can literally replace the word "Column" with "Row" in the previous definitions and get the desired effect :)

XML
<Grid ShowGridLines="True">
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>
</Grid>

Run this now, and you'll see what we want! A horizontal line splitting the field in half.

Now, let's mess around with this some more and add some end zone horizontal lines.

You may be asking yourself, how do we tell the rows how much space to take up? The end zone lines need to only take a certain parentage of space. If we add another row like we have been doing, it will split up each row evenly spaced apart based on the window's height. Well, lucky for us, each RowDefinition has a Height property we can set.

There are a few ways to set the height of each row. You can set each row's height by pixel values, percentage values, or auto-fill values.

Pixel values are self explanatory, you can do something like:

XML
<Grid.RowDefinitions>
  <RowDefinition Height="50" />
  <RowDefinition Height="50" />
  <RowDefinition Height="50" />
</Grid.RowDefinitions>

Integral values written like this for width (Column) or height (Row) are parsed into Pixel format. The numbers used in this case are giving each row that amount of space. We've just declared 3 rows, and given them at least 50 pixels of space between them.

Now, you could play with this for a few minutes and probably figure out the precise amount of pixels to give each row to get two end zones... but we have another card up our sleeves.

I mentioned before that we could set the percentage values for each row or column. Well, I don't know about you, but I'd much rather define my row's height using percentages than hard coded pixel values. The markup for declaring a percentage value can be written in two ways. Just like in the numbers you're used to, 50% is written as either "50%", or "0.5". The same applies here, only you can replace the "%" symbol with "*".

Back to our example, let's apply this to our American football field.

Currently, the following gives us two rows, each given the same amount of space (represents a 50 yard line).

XML
<Grid.RowDefinitions>
  <RowDefinition />
  <RowDefinition />
</Grid.RowDefinitions>

Now, let's make end zones, giving each row a percentage. You can already guess we're going to need at least three rows for end zones. Each end zone itself is a row (2), and we need another row for the remaining space in the field (all 100 yards).

There are two ways to do this:

The old way (3 rows, each given equal space):

XML
<Grid.RowDefinitions>
  <RowDefinition />
  <RowDefinition />
  <RowDefinition />
</Grid.RowDefinitions>

The new way (3 rows, also each given equal space):

XML
<Grid.RowDefinitions>
  <RowDefinition Height="*" />
  <RowDefinition Height="*" />
  <RowDefinition Height="*" />
</Grid.RowDefinitions>

Notice how in this case, we set the height of each row to "*". This means we want the Row to automatically be given a Height to split it evenly based on other rows/window height. This is the same as putting no height at all, but it leads us to our next requirement, which is giving a percentage value.

If you think about this, we could bypass some hacky values for the end zone rows (1st/3rd rows), and just tell the center row (100 yard part of the field) how much space to take up.

XML
<Grid.RowDefinitions>
  <RowDefinition Height="*" />
  <RowDefinition Height="3*" />
  <RowDefinition Height="*" />
</Grid.RowDefinitions>

Now, you should start to see the true power of defining the layout of your window. We've just told the center row to take up 3 times more space than the rest of our rows, based on the window's height.

We've just told our Grid to create three rows, with the center one taking up 3 times more space than the rest, based on the height of the window.

If you run the application now, you'll see we have a center field and two end zones. But, where's our 50 yard line? Well, we'll need to split our center field into two rows to get that desired effect.

XML
<Grid.RowDefinitions>
  <RowDefinition Height="*" />
  <RowDefinition Height="3*" />
  <RowDefinition Height="3*" />
  <RowDefinition Height="*" />
</Grid.RowDefinitions>

So here, I told my Grid it has 4 rows. The center two rows are to take up 3 times each more than the outside two rows. This basically splits our field in half, and then gives the remaining space to the two outside rows (since they have no width set).

Now that you have the hang of it, let's quickly add some columns to set some sidelines in place.

XML
<Grid.RowDefinitions>
  <RowDefinition Height="*" />
  <RowDefinition Height="3*" />
  <RowDefinition Height="3*" />
  <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
     <ColumnDefinition />
     <ColumnDefinition Width="10*" />
     <ColumnDefinition />
  </Grid.ColumnDefinitions>

I simply told my Grid to create three columns, giving the center column 10 times more space than the outside columns.

And, there you have it! An American football field.

FootballGrid_End.jpg

Now that you have a Grid with rows and columns, how do you use it? The Grid panel offers the ability for its children objects to set which row/column of the grid they appear in. By default, all children of a grid appear in row 0 column 0, but we can set this.

The following examples don't apply to the American football theme. We are simply using our pre-existing layout to add some new controls. Our current Grid panel, in-fact, has more rows and columns than most basic applications, so it will be a good learning experience.

What's an application without a menu bar? Let's add one!

The markup for a menu system is very easy:

XML
<Menu>
</Menu>

If you run this, you may notice that it's nearly impossible to see. Well, the key word in menu system is "system" :) We haven't given any children to this Menu object. That, as well, is just as easy as declaring the Menu itself.

XML
<Menu>
  <MenuItem>
  <MenuItem />
  </MenuItem>
</Menu>

If you run this, you may notice that everything is being bunched up in the top left corner. Remember how I said, by default, all children of a Grid panel container are set in the 0th row and 0th column? We are seeing that in action now. And lucky for us, each child of the Grid has the option of explicitly setting where in the grid they want to appear. In our case, a Menu is typically spanned across the top of a Window. But this encompasses multiple columns... how do we achieve that?

Again, lucky for us, there is another property that children of a Grid panel can set, which tells the Grid that this child is going to span across 'x' amount of columns (or rows).

Let's see this in action.

First, I removed the ShowGridLines property of the parent Grid panel because it clunks up the UI :)

XML
<Menu VerticalAlignment="Top" Grid.ColumnSpan="3">
  <MenuItem>
  <MenuItem />
  </MenuItem>
</Menu>

So, you can see that I set two properties of the Menu object here. First, I told the menu to hug the top of whatever the parent container is. In this case, our parent is a Grid, and our default row is "0", so lucky for us, this just happens to be the top of the Window as well.

Next, I told the Menu how many columns to span across. We have three columns in our football field, so I simply set the Grid.ColumnSpan property to "3".

Let's add some text to these menu objects. We can use the Header content property for this.

XML
<Menu VerticalAlignment="Top" Grid.ColumnSpan="3"> 
  <MenuItem Header="File"> 
  <MenuItem Header="Exit"/> 
  </MenuItem> 
</Menu> 

Run this now, and you'll see the start of a real application! Soon, we're going to make that Exit button do something when Clicked, but in the mean time, let's add some other controls to our application and put to use all those columns and rows.

First, let's add some color to the border columns (left/right column) of our window. There are many controls that you can use for this... I'm going to use the Border control though.

So the basic concept is to place a Border in column 0, spanning all four rows of our field, and to replicate it over on column 3, spanning down that side of the field.

XML
<Border Grid.Column="0" Grid.RowSpan="4" />
<Border Grid.Column="3" Grid.RowSpan="4" />

The Border control is very powerful, and the capabilities of it are well beyond the scope of this article. Just know that I am purely using it so I can place a control where I need to add some color, and the Border control stood out in my mind.

So, just like most of our other controls, we can interact with the Border by setting its properties. You can probably guess that it has a Background property. So, let's set it.

XML
<Border Grid.Column="0" Grid.RowSpan="4" Background="DarkBlue" />
<Border Grid.Column="3" Grid.RowSpan="4" Background="DarkBlue" />

Notice that the top of the Borders are overlapping the Menu? If you have any graphics background, you will know that objects rendered last are drawn on top. In the case of our UI, we've told the Borders to be drawn after we declared our Menu.

To fix that, we just need to render the Menu after the Border.

XML
<Border Grid.Column="0" Grid.RowSpan="4" Background="DarkBlue" />
<Border Grid.Column="3" Grid.RowSpan="4" Background="DarkBlue" />
<Menu VerticalAlignment="Top" Grid.ColumnSpan="3">
<MenuItem Header="File">
<MenuItem Header="Exit"/>
</MenuItem>
</Menu>

Cool! Useless, but cool! :) Again, this article is for grasping the concepts of WPF by learning the syntax and some practical uses of its objects.

So anyways, let's add some interaction controls to this UI.

Just so you know where we're going... I eventually want this UI to have the Exit button working. Also, I want to give the user the ability to select/type in a color and the Border backgrounds change to that color.

Let's first add the controls that we'll need to give the user the ability to select a color. There are a few ways we can do this. We could present a ListBox/ComboBox full of preset colors, or we could give the user the ability to type in a color. The typing of the color is a little too complicated for this article.

So, let's begin by adding a Label to tell our user what this ComboBox will do, then let's add the ComboBox.

XML
<Label Content="Choose a color" Grid.Row="2" 
  Grid.Column="1" VerticalAlignment="Center" 
  HorizontalAlignment="Center" Margin="0,-50,0,0"/>

Let's examine this ugly piece of hardcoded code.

First, I declared a Label. I gave it the Content (Text) of "Choose a color". Then, the hacktastic-ness begins. First, I wanted it to appear somewhat in the middle of the UI, so I set it to the third row (second in a 0 based index), and the second column. By default, it's placed in the top left of the cell, so I then set the VerticalAlignment and HorizontalAlignment to Center. I then decided that I actually want my ComboBox directly in the middle of that cell, so I should move this Label up a little so it would be above my ComboBox of colors. To do that, I introduced a new property that nearly every control object has, which is called Margin.

There are a few ways to work with Margin. There's actually three :)

A Margin represents the amount of space in between the current control and its parent container's sides. So in my example, I put the Label in the center of the Cell, then I told it that I want its Margin to be moved 0 to the left side, negative 50 on the top, 0 on the right, and 0 on the bottom.

As you probably guessed, it's left, top, right, bottom when you use all four values in the Margin value. I wanted my Label to appear above the ComboBox, so I moved it negative 50 pixels on the Top coordinate (I could have alternatively done "0,0,0,50").

The two ways Margin can be used is by only setting one value. I.e., if I did:

XML
Margin="50"

then, it would apply a Margin of 50 pixels on all four sides of the control.

The last way to use Margin is to only set two of the values.

XML
Margin="0,50,0,0" <!--Applies a margin of 50 pixels on the top side of the control-->
Margin="50,0" <!--Applies a margin of 50 pixels on the left and right sides of the control-->
Margin="0,50" <!--Applies a margin of 50 pixels on the top and bottom sides of the control-->
Margin="50" <!--Applies a margin of 50 pixels on all sides of the control-->

So now, let's add our ComboBox.

XML
<ComboBox Grid.Row="2" Grid.Column="1" 
   Width="150" Height="25" SelectedIndex="0">
<ComboBoxItem Content="DarkRed" />
<ComboBoxItem Content="DarkGreen" />
<ComboBoxItem Content="DarkBlue" />
</ComboBox>

So first, we set our ComboBox to be in the same cell as our Label. We gave it a Width of 150 and Height of 25 (if we don't do this, then the ComboBox by default will encompass all the space in the cell). We also set the SelectedIndex to 0, so we are always showing something to the user rather than forcing them to click on it to see something. Also, this will play a part when we need to bind our Borders to the selected color in the ComboBox. Then, we gave it children. Just like our main Menu object, we can give children to objects of type ItemsControl, which ComboBox and ListBox are children of.

Now, take a look at that cell we've been adding our controls to. The XAML we've written for positioning them is rather ugly in my opinion. This is where I like to get creative. Up until now, you've only seen and used the Grid panel container. Well, I personally only use the Grid for the initial layout of the window. I don't like placing controls like we've been doing so far in a Grid. I prefer to add child Panels to the Grid and then add children to them.

We're now going to talk about my favorite panel, which is a StackPanel. The StackPanel is extremely simplistic, but very powerful and useful.

SolitaireExample.jpg

Consider the game Solitaire. Visually, it screams out layout (I only said that for the rhyme). Consider what we've learned so far. If you wanted to place collections of cards with a Grid in the layout they have, you'd have to either be crazy, or insane.

You'd have to have hard-coded margins to separate each card collection and a bunch of other time wasting tasks.

Insert, the StackPanel. When I look at that Solitaire picture, I see a Grid containing three rows (the third row is for the victory animation at the end) and two columns. The deck stack is in Row[0]Column[0]. The suit stack is in Row[0]Column[1]. The row stack is in Row[1] and spans across both columns. The victory animation is in Row[2].

So, how is the StackPanel used in Solitaire? Well, each of those stacks that I mentioned before would be a StackPanel. A StackPanel is literally a panel that stacks its children on top of each other (or next to each other depending on how you want it displayed).

Opposed to the grid, the elements within a StackPanel are tightly knit. In a Grid, all child elements overlap with each other, which is why we had to set margins to get it to appear somewhat friendly.

Let's put a StackPanel in the cell that our Label and ComboBox currently reside in; then, let's move the Label and ComboBox inside the StackPanel.

XML
<StackPanel Grid.Row="2" Grid.Column="1" 
   VerticalAlignment="Center" HorizontalAlignment="Center">
<Label Content="Choose a color" />
<ComboBox SelectedIndex="0">
<ComboBoxItem Content="DarkRed" />
<ComboBoxItem Content="DarkGreen" />
<ComboBoxItem Content="DarkBlue" />
</ComboBox>
</StackPanel>

Wow! That's much cleaner than setting all the margins and alignments on each control. Each element within the StackPanel inherits the Grid.Row, Grid.Column, and Vertical/Horizontal Alignment properties. So, we can immediately remove and let them implicitly be set. Something that you still may want to do is put a Margin on the top or bottom of each element to give it a little spacing, but I'll leave that off for this example.

Now, I'm going to move these up one row in the grid to make it more towards the top of the window.

XML
<StackPanel Grid.Row="1" Grid.Column="1" 
  VerticalAlignment="Center" HorizontalAlignment="Center"> 

Look how easy that was! We just moved our entire Panel collection up one cell just by changing one property. Before, when we were piggy backing the Grid panel, we would've had to change it for each control we wanted to move. Imagine if we had an entire UI flow of buttons and comboboxes, and how annoying it would be to manage if it was in a Grid.

The last topic I want to cover for the layout portion of this article is the Orientation property of a the StackPanel. By default, it is set to Vertical. This simply means that all elements within the StackPanel are stacked on top of one another. If we set the Orientation to Horizontal, then each element would stack side by side. A common usage for this would be something like displaying a user information form. Where you need to gather the Name/Email/City/State of a user, you would typically have a vertical StackPanel of horizontal StackPanels. Each horizontal StackPanel would be a Label such as "Name: " and a TextBox to the right of it where the user could enter this information. And, the vertical StackPanel would be containing all of those.

Simple XAML to XAML Element Binding

Moving on to a completely different topic, which is my favorite, Databinding. This, to me, is the heart and soul of what drives WPF. In WinForms, the only real binding that took place for me was binding collection controls to backend datasources etc. Well, in WPF, literally everything from the text on a content control, to the background of a panel, to the entire menu system, to the data in each collection, can be bound. So, what is binding? I'll give you a real world example.

Referring back to our current application (which will remain nameless due to its obscurity of meaning), we know that we want the Borders of the Window to change color to whatever is currently selected in the ComboBox. To you WinForms programmers, you would probably register to listen to the ComboBox's SelectedIndexChanged event, and would find out the text property of the SelectedIndex and cast it to a Color, and change the Border's Color to that color. Well, we can actually do this in WPF without ever touching any C#, and we're going to do it through binding.

So, here I'm going to introduce the concept of Element Binding. Again, there are many kinds of binding. We could do it all in C#, we could do part of it in XAML and the other part in C#, but I am going to do it all in XAML. The basic idea is we want the Background of the Border to spy on the SelectedItem.Content property of our ComboBox. Whatever the ComboBox says, we want the Border's background to be that color.

First, in order for any element to be referenced, we need to give it a name. I use the x:Name property of an element to give it a name.

XML
<ComboBox x:Name="ComboBoxColors" SelectedIndex="0" IsSynchronizedWithCurrentItem="True"> 

Here, I gave our ComboBox a name of ComboBoxColors. This is so other elements can reference this ComboBox by a unique name. You can name it whatever you want... This is the same as setting the Name property in C#.

You'll also notice that I set a property called IsSynchronizedWithCurrentItem to "True". This is to ensure that our ComboBox is always in sync with whatever item is currently displayed visually. Everything I see online has this set to True... I'm assuming it's defaulted to False, which doesn't make much sense, but anyways...

Now, we are going to do our first binding markup!

Go up to the Border elements and remove the Background property completely.

XML
<Border Grid.Column="0" Grid.RowSpan="4" />
<Border Grid.Column="3" Grid.RowSpan="4" />

Here's the basic layout for Element Binding. We need to declare the markup for a Binding, which is always surrounded by {}s (curly brackets). Within it, we need to tell what we are binding. There are a multitude of bindings, such as Binding to StaticResources, DynamicResources, and elements. Luckily for us, elements are the simplest to bind to. To bind to an element, we need to give the Binding markup an ElementName and a Path. The ElementName is the unique name (or x:Name) of the element we wish to bind to. Path is the property of that element we want to bind to at run-time. It uses Reflection at run-time to bind to the ElementName passed in, and then looks at the value of the Path of that object and binds to it.

Be careful!!! If you give an incorrect Path, you may have a hard time digging up the error because it doesn't throw an exception per-say. Instead, you'll get a message printed to the Output window of the Visual Studio, stating something like:

"System.Windows.Data Error: 35 : BindingExpression path error: 'sdf' property not found on 'object' ''ComboBoxItem' (Name='')'. BindingExpression:Path=SelectedItem.sdf; DataItem='ComboBox' (Name='ComboBoxColors'); target element is 'Border' (Name=''); target property is 'Background' (type 'Brush')"

You can obviously see that "sdf" is not a property of ComboBoxItem.

So, let's do the Binding, shall we?

XML
<Border Grid.Column="0" Grid.RowSpan="4" 
   Background="{Binding ElementName=ComboBoxColors, Path=SelectedItem.Content}" /> 
<Border Grid.Column="3" Grid.RowSpan="4" 
  Background="{Binding ElementName=ComboBoxColors, Path=SelectedItem.Content}" /> 

Voila! We set the Background property of each Border to the current Content of the SelectedItem of the ElementName, which in this case is our ComboBox.

Now, if you run the application and change the ComboBox, you'll see the Borders change. Notice how we still haven't written one smidgen of code in our code-behind yet... This is really a powerful language that separates business logic from the user-interface. Ideally, you would have a collection of Colors (or bind to the pre-defined enum of colors that is part of the mscorelib of the System namespace).

That completes our simple XAML to XAML element data-binding example.

BorderBound_Done.jpg

Binding XAML Defined Controls to Code-Behind

This example is going to be short, but simple. We're going to change our ComboBoxColors element to grab its data from C# instead of defining its children in XAML.

This is actually fairly easy. Let's take it one step at a time. First, we can remove the three ComboBoxItems from ComboBoxColors since we'll be defining these in the code-behind for the Window.

Now, we should have:

XML
<ComboBox x:Name="ComboBoxColors" SelectedIndex="0" 
  IsSynchronizedWithCurrentItem="True"> </ComboBox> 

or

XML
<ComboBox x:Name="ComboBoxColors" SelectedIndex="0" 
  IsSynchronizedWithCurrentItem="True" /> 

Now, let's create an enum that contains our colors.

C#
namespace
{
  JumpStartWPFpublic enum Colors 
  {
    DarkRed,
    DarkGreen,
    DarkBlue
  }
  /// <summary>
  /// Interaction logic for Window1.xaml
  /// </summary> public partial 
  class Window1 : Window 
  {
    {
      InitializeComponent();
    }
  }
}
public Window1() 

It took me forever to figure out, but you must create your enums to be outside of the class, and public. Just do it, you'll thank me later.

Now, we're going to set the ItemsSource of our ComboBox to all the values of Colors.

C#
namespace
{
  JumpStartWPFpublic enum Colors 
  {
    DarkRed,
    DarkGreen,
    DarkBlue
  }
  /// <summary> /// Interaction logic for Window1.xaml 
  /// </summary> public partial class Window1 : Window 
  {
    {
      InitializeComponent();
      ComboBoxColors.ItemsSource = 
    }
  }
}
public Window1()Enum.GetNames(typeof(Colors)); 

There are a few ways to bind a WPF element to the C# code-behind, this is the easiest for me. All this is doing is setting the Item collection ItemSource to all the values of the target enum. In this case, it's going to fill our ComboBox with DarkRed, DarkGreen, and DarkBlue at run-time. Notice how I put this code after InitializeComponent(). If you put it before, bad things happen. Again, you'll thank me later.

Now, if you run the application, you'll notice our ComboBox is filled with Items of type Colors (our enum) correctly. But wait... our Border's Backgrounds aren't displaying! Well, this is because our ComboBox is no longer containing a list of ContentControls. Now, we are physically binding our ComboBox's ItemsSource to a String[] (returned from Enum.GetNames()). Well, to Bind to a String, you simply need to point the Path of your target to "Text".

So, make this change, and voila, you should have working Borders that are bound to the ComboBox's SelectedItem, of type String.

XML
<Border Grid.Column="0" Grid.RowSpan="4" 
  Background="{Binding ElementName=ComboBoxColors, Path=Text}" /> 
<Border Grid.Column="3" Grid.RowSpan="4" 
  Background="{Binding ElementName=ComboBoxColors, Path=Text}" /> 

This completes our example of Binding a XAML defined element to C# code-behind. We successfully populated a ComboBox with a code-behind defined enum, and bound our Border's Background property to whatever was currently selected in the ComboBox.

Displaying Custom Content (Business Object C# Types) in XAML

Our next goal is to add a user input form that asks for first name, last name, and age. It stores each of these objects as type Person, into a collection of Persons of type People. Then, we'll have a custom list that displays all of this information.

The topics covered in this section will be:

  • XAML Resources
  • Displaying custom user created classes in XAML ItemsControls (I.e.: displaying all information of a type Person, into a single ListBox)
  • ObservableCollections and how WPF uses it
  • StaticResource Binding

Most tutorials I see out there fail to go into the details about how these classes are made. The design of custom classes in WPF is very important, and I'm going to document every step in here so you don't miss out on something.

First, for this, I'm going to create a class called People that is an ObservableCollection of type Person, which will contain properties for first name, last name, and age.

So, create a new class called Person.

Add public properties for FirstName, LastName, and Age (Int32) (I also make all classes that are going to be accessed in XAML public).

C#
namespace JumpStartWPF
{
    public class Person
    {
        public Person(String firstName, String lastName, Int32 age)
        {
            FirstName = firstName;
            LastName = lastName;
            Age = age;
        }
        public Int32 Age
        {
            get;
            set;
        }
        public String LastName
        {
            get;
            set;
        }
        public String FirstName
        {
            get;
            set;
        }
    }
}

Simple enough... Now, let's create a class that will be the List of type Person. Let's call it People.

Remember, our goal is to have it so whenever a user enters their information and clicks a Submit button, we are going to create a new Person and add it to our list of Persons, which we're calling People.

Now, how are we going to do this? There is a very nifty base class called ObservableCollection<Type>.

Basically, any class that derives from ObservableCollection<Type> automatically obtains the functionality of being a List of the type you declare in the ObservableCollection. So, WPF simply needs a reference to People, and it will automatically be updated whenever the People ObservableCollection receives a new object of type Person added to it.

ObservableCollection resides in the namespace:

C#
using System.Collections.ObjectModel;

namespace JumpStartWPF
{
    public class People : ObservableCollection<Person>
    {
        public People() { }
    }
}

Since People inherits from ObservableCollection, we automatically get the List that it contains.

For the first part of our example, I'm going to manually add some Persons since it's easier.

C#
public class People : ObservableCollection<Person>
{
    public People()
    {
    }
}

We call the parent method Add, which is part of ObservableCollection. It basically just adds a new object of type Person (which is what our ObservableCollection was expecting), to the List declared inside the parent ObservableCollection.

Alright, before I move on... we need to reorganize our elements in the window to give room for our next task. So, I've just made a lot of aesthetic changes, nothing too major.

I'm going to post the new code here...

XML
<Window x:Class="JumpStartWPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1" Height="500" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="3*" />
<RowDefinition Height="3*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="10*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border x:Name="BorderLeft" Grid.Column="0" Grid.RowSpan="4" 
  Background="{Binding ElementName=ComboBoxColors, Path=Text}" />
<Border Grid.Column="3" Grid.RowSpan="4" 
  Background="{Binding ElementName=ComboBoxColors, Path=Text}" />
<Menu VerticalAlignment="Top" Grid.ColumnSpan="3">
<MenuItem Header="File">
<MenuItem Header="Exit"/>
</MenuItem>
</Menu>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Top" 
  HorizontalAlignment="Center">
<Label Content="Choose a color" />
<ComboBox x:Name="ComboBoxColors" SelectedIndex="0" 
  IsSynchronizedWithCurrentItem="True">
</ComboBox>
</StackPanel>

<Border Grid.Row="1" Grid.Column="1" Margin="0,30" 
  HorizontalAlignment="Center" 
  VerticalAlignment="Bottom"
  BorderBrush="{Binding ElementName=BorderLeft, Path=Background}" 
  BorderThickness="1" 
CornerRadius="10">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="First Name: " Grid.Row="0" Grid.Column="0" 
  HorizontalAlignment="Right" 
  VerticalAlignment="Center" Margin="0,0,15,0"/>
<TextBlock Text="Last Name: " Grid.Row="1" Grid.Column="0" 
  HorizontalAlignment="Right" 
  VerticalAlignment="Center" Margin="0,0,15,0"/>
<TextBlock Text="Age: " Grid.Row="2" Grid.Column="0" 
  HorizontalAlignment="Right" 
  VerticalAlignment="Center" Margin="0,0,15,0"/>

<TextBox Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" 
  VerticalAlignment="Center" Width="90"/>
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" 
  VerticalAlignment="Center" Width="90" />
<TextBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" 
  VerticalAlignment="Center" Width="50"/>
</Grid>
</Border>

<Border Grid.Row="1" Grid.Column="1" 
  BorderBrush="{Binding ElementName=BorderLeft, 
  Path=Background}" 
  BorderThickness="1" CornerRadius="5" Margin="60,5" 
  HorizontalAlignment="Center" 
  VerticalAlignment="Bottom" Width="100">
<Button Margin="1" Click="Button_Click">
<Button.ContentTemplate>
<DataTemplate>
<ContentControl Content="Submit"/>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</Border>
</Grid>
</Window>

There are a couple of new concepts in all this markup that I've introduced. I'll go over them now.

First, even though I didn't want to, I ended up using a Grid for the layout of the User Information form. The stack panel was not agreeing with me today, so I setup a couple of columns and three rows. I also moved it all up into the second row of the entire window. This will give us room to display information in the other two rows.

Onto the new concepts.

First, you'll notice a Button element. The way I've displayed the Button gets to another powerful feature of WPF. All ContentControls and ItemsControls are customizable. As you can see here, the only Button aspect to this Button is the declaration of Button. Everything else from its border to its content is all custom.

I surrounded the Button with a Border only because I like the way Borders look, and I treat it like a Panel for my Button which is its child. I also bound the BorderBrush of this Border to the Background of the Border elements on the left and right of my Window. This is just a cool feature that I wanted to keep concurrent throughout the entire application. Then, I declare a button, but instead of setting the Content directly in the Button declaration, I tell the Button element that I want to go into more detail. Instead of setting the Content, I open up the Button.ContentTemplate tag. Within there, pretty much all you can do is define the DataTemplate. DataTemplates are a huge topic in WPF, and I could spend hours talking about it. All I'm going to say is, consider DataTemplates the makeup of an element. When you define the DataTemplate, you are literally re-teaching WPF how to draw this specific element. This is also how developers keep a theme to their applications. For instance, instead of continually setting all my Border's BorderBrush properties to bind to a target element, I would normally declare a DataTemplate for all Borders in my Window Resource, and say, whenever my Window comes across a Border, set its DataTemplate to this Resource and it will handle all the cosmetic changes I want it to display.

So, for this button's example, all I'm doing is teaching the button to do what it already knows how to do. I'm just adding a ContentControl (this is the base class control for all single item Content Controls... such as TextBlocks and Buttons and Labels). I only expanded on the DataTemplate so I could explain a little about its use. I will actually define a DataTemplate in a Window Resource later on for use.

Also note, I created a Click event for the Button. When you do this, if you have Intellisense hooked up, it will automatically create the event handler for whatever event you just registered. In this case, my code-behind was edited with this:

C#
private void Button_Click(object sender, RoutedEventArgs e)
{
}

Those were the only new concepts in my big cosmetic change. Now, we can get back to the goal. In-case you forgot, the goal we're trying to achieve is whenever you click Submit, it will add a new Person to our People ObservableCollection, and then we'll have some sort of ItemsControl in our Window that is bound to the People collection, so it will automatically pick up newly added Persons and display them.

Let's take this one step at a time. First, we know that if we want to add a new Person, we'll need to somehow gather the information that was entered into the TextBoxes. In order to get those TextBoxes' content, we'll need to give them unique names (just like we did with our first ComboBox a while back). Let's do that now.

XML
<TextBox x:Name="TextBoxFirstName" Grid.Row="0" Grid.Column="1" 
   HorizontalAlignment="Left" VerticalAlignment="Center" Width="90"/>
<TextBox x:Name="TextBoxLastName" Grid.Row="1" Grid.Column="1" 
   HorizontalAlignment="Left" VerticalAlignment="Center" Width="90" />
<TextBox x:Name="TextBoxAge" Grid.Row="2" Grid.Column="1" 
   HorizontalAlignment="Left" VerticalAlignment="Center" Width="50"/>

Simple enough. I just gave them obvious names so we can look them up in the C# side when adding a new Person.

Now, let's hook up our click to add a new Person to the People collection. I think our People collection should have a static access modifier so we don't continue creating new People collections each time we add a new Person to it from different sources. To do this, I simply created a static instance of the People class, and set it in the constructor to "this".

C#
public class People : ObservableCollection<Person>
{
  public static People Instance;
  public People()
  {
    Instance = this;
  }
}

Now, we can call this code from our Button click:

C#
private void Button_Click(object sender, RoutedEventArgs e)
{
    // For UI Simplicity Sake
    if (People.Instance.Count >= 5)
    {
        People.Instance.RemoveAt(0);
    }
    People.Instance.Add(new Person(TextBoxFirstName.Text, TextBoxLastName.Text, 
    Int32.Parse(TextBoxAge.Text)));
}

So, what I'm doing here is limiting the amount of Persons our People collection will maintain. If it gets beyond 5, we clear out the oldest person and move the second oldest one up. This is a first in first out collection (FIFO). Then, I get access to the current People instance by accessing the static accessor, and call the Add method which is part of the ObservableCollection base class, passing in our first name, last name, and age (converted to an int).

WPF provides error validation on elements through the use of another interface that can be implemented, called IDataErrorInfo. This is out of the scope of this article for now, but ideally, you'd want some sort of checking to be done on the input coming in from the user on the TextBoxes. Perhaps even some Converters, the possibilities are endless.

OK, now we need to create a visual collection of these People.

XML
<StackPanel Grid.Row="2" Grid.Column="1" HorizontalAlignment="Center" 
  VerticalAlignment="Center">
<Border BorderBrush="{Binding ElementName=BorderLeft, Path=Background}" 
  BorderThickness="1" CornerRadius="2">
<ItemsControl x:Name="PeopleItemsControl" 
  ItemsSource="{StaticResource PeopleCollection}" 
  ItemTemplate="{StaticResource PersonItemTemplate}" />
</Border>
</StackPanel>

So here, I created a vertical (by default) StackPanel. This is going to be the Container for each Person. Within it, I put a Border to go around the entire ItemsControl. I then put a generic ItemsControl. Remember, ItemsControl is simply a base class for a collection of Items. I usually use it when I want to really define what I want my collection to look like. In this case, I didn't want the user to be able to select an item, or have any highlight/actions on an item, so I didn't want to use a ListBox or a ComboBox. I just simply wanted to display the item.

Now you should notice a few new concepts in here. First, the ItemsSource. Remember how our last ComboBox was set in the code-behind? Well, I'm really just doing the same thing here, but this time, I'm setting it to a StaticResource in my XAML file. What's a Resource? Well, it's basically an object created in either C# or XAML, but is instantiated in XAML itself. (Note: It is possible to declare a resource in the code-behind and have it usable as a Static or Dynamic Resource in XAML, but for simplicity's sake, our Resource is defined in XAML).

Now, onto the syntax. Something that boggled my mind for a while was when to use and when not to use the Binding markup. I'm still not 100% comfortable with it, but I know that I don't use Binding markups when I am binding my collection controls to a Resource. So where is this resource I called PeopleCollection? Well... it's up in the Resources section.

XML
<Window.Resources>
  <local:People x:Key="PeopleCollection" />
</Window.Resources>

If you simply add this, it won't compile. local doesn't exist by default. You have to create it. local, in this case, is the name I gave my class in People.cs. Remember how that class is an ObservableCollection by proxy, so if I create an instance to that class, I am really also getting the entire collection that goes along with it. Here's how you add a Resource.

The top of the window contains a bunch of namespace declarations. Well, if you want to get access to your own types, you need to declare your namespace up here too.

The syntax is, "xmlns" followed by a colon ":", followed by an optional key for your namespace, followed by "clr-namespace", followed by a colon ":", followed by your namespace. From the beginning of "clr-namespace" to the end of your namespace, it is surrounded in quotes.

The optional key you can give your namespace is very important... it's how you reference the classes within that namespace when declaring a resource for your Window. I usually name the namespace that my main XAML window is contained in, "local".

XML
xmlns:local="clr-namespace:JumpStartWPF"

When you go back to your Window.Resources, you will notice that People should exist now. All Resources must have a key associated with them or some other type of associated key (such as a TargetType etc.). You can consider creating a Resource to your class the same as creating a new instance of your class. When this is called, it will actually create a new instance of our People class, and activate our static instance for use.

Now, back to our ItemsControl. There are two types of Resource bindings that you can do: StaticResource and DynamicResource. I've never used DynamicResource, but the way it works is if the Resource ever changes at run-time, all elements referencing that Resource will change too. StaticResource is a one time bind. DynamicResource is a little slower, since it can keep creating new references, while static lives on forever in the application lifetime.

Now on to one of our last topics.. the ItemTemplate property of our ItemsControl. Remember how we set our Submit button's DataTemplate a while back? The DataTemplate was telling our Button how to display itself. All we did was set the Content of the Button to Submit, but that's a lot of information about WPF to take in at once.

We're going to expand upon that now, and actually completely customize our ItemsControl and teach it how to display a Person.

You can already tell that I've bound to a StaticResource of PersonItemTemplate. ItemTemplates/ContentTemplates point to DataTemplates. So, you should be able to figure out that, in our Resources section, we have declared a DataTemplate with the key PersonItemTemplate.

Then, whenever our ItemsControl needs to draw a Person, it refers to that StaticResource to the DataTemplate of how to draw a Person.

XML
<DataTemplate x:Key="PersonItemTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="10">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Margin="5,0,0,0" Text="{Binding LastName}" HorizontalAlignment="Center" />
<TextBlock Text=", " HorizontalAlignment="Center" />
<TextBlock Margin="0,0,5,0" Text="{Binding FirstName}" HorizontalAlignment="Center" />
</StackPanel>
<TextBlock Margin="0,0,0,5" Text="{Binding Age}" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>

Remember, before we set a Border to surround the entire ItemsControl? Well now, I want an even more round Border (CornerRadius set to 10) to surround each individual Person. Within that, I have a vertical StackPanel that contains a horizontal StackPanel with a person's LastName, separated by a comma, and then a space and their FirstName. Then, below that StackPanel is a TextBlock displaying the Person's age. Notice the {Binding ...} markups for each TextBlock. At compile time, our DataTemplate here has absolutely no idea what a Person is. So how does it know what LastName, FirstName, and Age are? The answer is Reflection. At run-time, whenever it comes time to use this DataTemplate, the DataTemplate will inherit all properties of the calling control. In this case, our calling control is an ItemsTemplate with its ItemsSource set to type People, which is a collection of type Person, which has the properties FirstName, LastName, and Age. See how it all works? We're piggy backing on the glory of inheritance.

Well, we forgot one thing now, didn't we? Adding the Click event for the Exit MenuItem off the main menu. You should be able to easily do this now with your knowledge on the topics above.

The code is given below:

XML
<Menu VerticalAlignment="Top" Grid.ColumnSpan="3">
<MenuItem Header="File">
<MenuItem Header="Exit" Click="MenuItem_Click"/>
</MenuItem>
</Menu>

private void MenuItem_Click(object sender, RoutedEventArgs e)
{
    if (People.Instance != null)
    {
        People.Instance.Clear();
        People.Instance = null;
    }

    this.Close();
}

Thanks for reading! Again, I just started WPF about a week ago. Some of my information may be worded incorrectly, or there may be better methods of achieving the same tasks I did. All-in-all, I feel that this is a good introduction to WPF, and it offers some information that I felt was difficult to figure out on my own. Hope it helps!

CodeProjcet_CompletedWindow.jpg

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Electronic Arts
United States United States
I graduated from Fullsail University with a degree in Game Design and Development (Computer Science).

After college I got a job at Electronic Arts as a QA Tester. During the QA Testing job I off-tasked and wrote some tools to help the QA Department. This work was noticed and I interviewed for a job with an automation team and got the job.

My day in/out work consists of tasks such as:
Working with C#/ASP.NET/SQL/WPF/C++.

I have a deep passion for Managed languages, notably the .NET Framework.

I have a desire to learn WPF inside and out as much as some of the experts that have offered their wisdom and insight here on codeproject.

I was a Lead Programmer for a Half-Life 2 Mod named Goldeneye: Source (www.goldeneyesource.com)

Comments and Discussions

 
GeneralMy vote of 5 Pin
Leo569-May-11 2:38
Leo569-May-11 2:38 
GeneralCustom classes Pin
seblake8-Jan-10 14:07
seblake8-Jan-10 14:07 
GeneralRe: Custom classes Pin
mariocatch8-Jan-10 14:16
mariocatch8-Jan-10 14:16 
GeneralJust a shout out Pin
ddixon8-Oct-08 10:41
ddixon8-Oct-08 10:41 
GeneralRe: Just a shout out Pin
mariocatch8-Oct-08 10:49
mariocatch8-Oct-08 10:49 
GeneralIts compiling now.. Pin
Rajat Kaushal24-Jun-08 15:36
Rajat Kaushal24-Jun-08 15:36 
GeneralCode not compiling. Pin
Rajat Kaushal23-Jun-08 19:40
Rajat Kaushal23-Jun-08 19:40 
GeneralRe: Code not compiling. Pin
mariocatch24-Jun-08 3:49
mariocatch24-Jun-08 3:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.