Free!  FoxPro to WPF screen converter
   Almost free!  One day of FREE VFP to WPF evaluation of your FoxPro app ONSITE
   Free!  Read the foreword to Les' new book (available here)...
Skip Navigation Links

Introduction to Windows Presentation Foundation

WPF replaced the WinForms designer several years ago; it's time to dig in.


Les Pinter

Login or register to download source code

Coding time: 15 minutes.   

Jump to:   Introduction to WPF   Adding Controls   Adding Data   Navigation and Filtering   Conclusion 

     


Introduction

In this article, we'll built a Windows Presentation Foundation application with a single form that returns a record from the NorthWind Customers table. A simple search mechanism will be used to search for customers.


Introduction to WPF
top

Windows Presentation Foundation (WPF) is Microsoft's replacement for the WinForms Designer that's been your home for lo these many years. It's been out for nearly three years, and yet many developers have hesitated to take the plunge.

Forms that you build in the WPF designer are saved in text files with the extension ".xaml". The text in these files is called XAML. XAML is the acronym for eXtended Application Markup Language. It derives from XML, so you would expect to see tag names between little brackets, and that's indeed what it looks like. Your XAML is compiled into BAML, which is embedded as a resource in your .dll or .exe. WPF then uses a vector graphics rendering engine to render your forms at run time.

As a result, things are now possible in your forms that were difficult or impossible in the WinForms environment. You can make your forms look like web pages, or like real works of art. Transitions, color application, fades, dissolves, resizing, and an almost limitless list of other capabilities are child's play to WPF. In fact, you could make a career out of designing forms, controls and buttons, without writing a line of code.

In fact, that's the point of XAML. The screen layout is in a file with the extension .xaml, but the code-behind is in a different file (or in the case of M-V-VM apps, in several different files). You can give the .xaml file to a graphic designer, and as long as they don't change the names of referenced program or data elements, the code will continue to work when they send it back. WPF truly "separates the implementation from the interface."

One new methodology that is increasingly viewed as "best practices" in the WPF community requires that you break each form into three parts:

  • A model class containing the structure of your table(s), with each column exposed as a public property;
  • A view class containing the screen (xaml) and, if any, its code-behind. I say this because the theoretical purists out there insist that the view's code-behind should be empty, period; and
  • A view-model class that links the data in the model to the view, and contains all code that will execute in the view.

This is called, naturally, Model-View-ViewModel, or M-V-VM. I personally think these should be called DataTable-Form-Linking_Code applications, because that more accurately characterizes what's happening, without requiring an explanation every time you say it.

<rant>NOTE: Using terminology that requires an explanation makes you look like an expert, so you might be able to get some mileage by calling it M-V-VM. Preachers and doctors use King James English and Latin, respectively, so why not put techno-babble to work for us?</rant>

The problem with M-V-VM is that it's unbelievably difficult to grasp on the first exposure. The complexity of the much-talked-about Model-View-ViewModel approach has scared off many developers. Does it really have to be so hard? No, it doesn't. It's a little arrogant to claim to be the only guy who's right. M-V-VM has many benefits, but it's absolutely not the only way to write WPF applications, as this article shows. <>Lately, I've seen an increase in the number of job postings that mention M-V-VM by acronym. However, In this article, we'll deal with WPF at the most basic level. Once you've mastered the basic mechanics of WPF forms, M-V-VM might not be quite so intimidating. However, we'll leave M-V-VM (or as I call it, D-F-LC) for a subsequent article.

Building the application

Let's create a WPF application. In Visual Studio 2008, select New, Project, and from the C# (or VB) templates (the source code for this article is available in both C# and VB) select WPF Application. I've put my new project in F:\Articles\601\CS, and used the default name WPFApplication1 (Fig. 1):


The project that's created by the WPF Application template appears in Fig. 2:


It contains two files that you may not have seen before: App.xaml and Window1.xaml.

App.xaml is the source of initial information that the equivalent of the Main program in your WPF application uses to launch your application. Double-click on App.xaml to open it, and you'll see this:

App.xaml:

<Application
    x:Class="WpfApplication1.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    <Application.Resources>

    </Application.Resources>
</Application>

The xmlns attribute defines namespaces, which are like using statements (Imports in Visual Basic). The two xmlns declarations shown above are in every XAML document. The first is the default WPF namespace, and contains all of the WPF classes, including the controls you'll use on your forms. The second is the XAML namespace, and allows you to refer to various XAML utilities and features. The second one ends in ":x", which is like the syntax that allows you to provide an abbreviated form for a using or imports statement: You can refer to elements of the XAML namespace by using the prefix "x:".

That little attribute "StartUri" is your tipoff that you're not in Kansas any more. XML is "tagged data". You can think of it as a way to present data as rows and columns in a text file. "StartupUri" is like a column name, and "Window1.xaml" is the data value in that cell; this means that when your application starts, Window1.xaml will be the startup form. There are many, many other attributes, which we'll introduce as the need arises.

Projects, forms, reports and everything else have attributes which in another environment might be stored in little data tables. XML and XAML files are text files; you can edit them with Notepad.

XML attributes are whatever you want them to be; however, XAML attributes correspond to objects in the .NET framework. For example, the tag <button> causes the environment to create a Button object. Window is one of several top-level elements that WPF can use; others include Application and Page,

XAML attributes are used to create objects and set their properties. In fact, everything you can do in XAML can be done in code. But programmers who write code charge $X per hour, while graphic designers can set properties for, say, half that. And now you know the rest of the story.

Window1.xaml

So let's have a look at Window1.xaml, our first screen. Window1 xaml appears in Fig. 3, below:


The circled controls let you modify the way the screen designer and xaml are displayed. I generally like to see the xaml, which is displayed if you click on the double down-arrows (the rightmost control).

As you can see in Fig. 3, the XAML for the form consists of a reference to the code-behind class for the window itself (x:class="Application1.Window1"), the two usual namespace declarations, a window caption ("Title"), and width and height values, You can change any of the last three and see the effect of your changes. I changed the value of Title to "C u s t o m e r s", and widened the window.

The code-behind

If you double-click on the code-behind file, Window1.xaml.cs, you'll see that it doesn't appear to do much. The default constructor,which runs when the class is instantiated, simply calls InitializeComponent:



Actually, it does quite a lot. InitializeComponent's code (not shown in VB) is generated at compile time; it calls the LoadComponent method, which loads and parses the BAML, renders the screen, creates and initializes any objects declared in the BAML, and attaches any referenced event handlers.

Back to the interface

XAML is more like web pages than it is like Windows Forms. The <grid>...</grid> tags bracket a grid, which has nothing to do with the DataGridView that it might remind you of. If you've built web pages, you undoubtedly remember the shock of discovering that controls on web pages don't have absolute positioning, i.e. the ability to place something 100 pixels down and 240 pixels from the left margin. Instead, you were forced to give objects padding, or to create a grid and place items within their own "<td>" tags.

In XAML, a grid is just a container for other controls. So if you put labels, textboxes and buttons in a grid, each might have a margin="l,t,r,b" specification to specify the distance in pixels from the left, top, right and bottom of its container. (I say "might" because you can also create rows and columns, then specify the row and column values for the cell containing each control.) Any way you look at it, it's probably more work than you expected.

NOTE: You're not limited to using a grid merely because the project template suggested it; The grid isn't the only container that you can use for your controls. Others are:

  • UniformGrid
  • Canvas
  • StackPanel
  • WrapPanel
  • DockPanel
  • TabControl
You can only have one of these at the top level, but you can nest them, e.g. you can place two StackPanels inside a two-column grid. So the possibilities are nearly endless. Each handles the layout of its contained controls in a slightly different fashion, and each has a best use. However, the grid is a reasonable starting point for many applications.


Adding Controls
top

WPF has a rich set of controls, including many of the ones you've used on WinForms forms. But the most exciting thing about WPF controls is they're extensible, almost to a ridiculous degree. You can add a checkbox inside a button, or a grid inside a checkbox. I don't know why you'd want to, but as I've explained to my inquisitive pre-adolescent nephew, who has chosen me as the go-to guy for indelicate questions, "anything you can imagine doing, a million others have thought of as well, and a few fools have tried." And that applies to WPF, too.

Press Ctrl+Alt+X to display the Toolbox. Drag two labels, then two textboxes, into the grid. Moving them changes the Margin property to four comma-delimited numbers representing the left, top, right and bottom margins respectively. Resize and reposition them, then change the labels' text to "ContactName:" and "Phone:", and you get something that looks like it might work (Fig. 4):


However, the degree of precision required to position them perfectly is annoying, and if there were fifty controls on the screen, you'd probably never get it quite right. And resizing the window wreaks havoc.

Instead, you can add row and column dividers, and then position elements within the cells defined by the row and column dividers, and the designer will do most of the work for you. Remove the labels and textboxes, then position the mouse pointer at the top center of the grid. The arrow will change to crosshairs. When you click, a ColumnDefinition will be positioned where the crosshairs were. Add a few RowDefinitions, then drag and drop labels and textboxes into the cells, and I think you'll see that it's a better approach. However, you'll have to "tweak" the XAML, as shown below:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="C u s t o m e r s" Height="300" Width="432">

    <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="30*" />
      <RowDefinition Height="30*" />
      <RowDefinition Height="210*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="55*" />
      <ColumnDefinition Width="353*" />
    </Grid.ColumnDefinitions>
    <Label Grid.Row="0" Grid.Column="0">ContactName:</Label>
    <Label Grid.Row="1" Grid.Column="0">Phone:</Label>
    <TextBox Grid.Row="0" Grid.Column="1" x:Name="txtContactName" Text="Les" />
    <TextBox Grid.Row="1" Grid.Column="1" x:Name="txtPhone" Text="333-1234" HorizontalAlignment="Left" Width="92" />
  </Grid>

</Window>

Note the Height specifications for the RowDefinitions are set to "30*", which is a hybrid of absolute size and a relative reference to the size of the grid itself - same deal for the ColumnDefinitions' Widths. If you change anything's size, everything else adjusts. That takes a little getting used to.

Note that I also had to manually specify the row and column indices of the labels and the textboxes; I also had to provide names for the two textbox controls if I want to be able to reference them in the codebehind; and making the phone textbox smaller required a HorizontalAlignment tag. That's why I keep the XAML window open. There are just lots of things that the designer can't do yet. Wait for version 2.

The result is shown in Fig. 5:



Adding Data
top

Many WPF presentations act as if cosmetics were everything. But if you're at Les Pinter's website, you know that data is everything. So how do you get data into your forms? There are about forty ways to do it, and the best ones are so complicated that you'd go crosseyed just reading about them. So we'll start with the simplest possible way, and work up to the real deal in a subsequent article.

For any type of data access in WPF, you'll need an object that exposes a public property for each column in your table. The statement Text="{Binding Path=PropertyName}" points the control to the named public property in the Window's DataContext. There are a number of objects that will function as a DataContext, and one of them is the lowly Dataset. So the simplest way to demonstrate the process is to use the Dataset Designer that's built into Visual Studio to build a dataset, then use a SQLDataAdapter to populate it. We'll use the Customers table from the NorthWind dataset that ships with SQL Server.

NOTE: There are many tools available to build datasets for you. Some of them, like IdeaBlade and StrataFrame, come with serious fringe benefits - automatic data saving, for example. But this is just a 15-minute intro to give you some insights into the mechanics. In the near future, I'll publish other articles about how to use other types of classes as a WPF DataContext.

In the Solution Explorer, right-click and add a Dataset. Call it dsCustomers. Right-click on the empty design surface and select Add, DataTable (Fig. 6):


By default it will be named DataTable1; Right-click on the name and change it to Customers (Fig. 7)


Right-click on the Table Name and add a Column named CompanyName, and another called Phone. Note that they default to string; if your columns were of a different type, you would use the Property Editor to select the appropriate datatype (Fig. 8);


Change the xaml for the two textboxes to this:

    <TextBox Grid.Row="0" Grid.Column="1" x:Name="txtName" Text="{Binding Path=ContactName}" />
    <TextBox Grid.Row="1" Grid.Column="1" x:Name="txtPhone" Text="{Binding Path=Phone}"
     HorizontalAlignment="Left" Width="100" />

You could assign an instance of the dsCustomers dataset as the DataContext for each of the TextBox controls in XAML; however, in WPF, if a DataContext is null, the binding mechanism "bubbles up" until it finds one in a higher-level container. So if we create and fill the dataset in the window's Load event as shown in Listing 2, the databinding process will find it at run time.

Finally, change the code-behind for the window to this:



The DataContext is where WPF Controls look for data. The "path=" statement in the Text="{DataBinding Path=ContactName}" clause tells WPF to use reflection to find the ContactName property and pull its value out of the DataContext. Binding mode is by default two-way, so when we get around to saving the data, the new or changed value on the screen is what will be saved back to the datasource.


Navigation and Filtering
top

There aren't many applications that don't need to give users some ability to search for a particular record. The approach I've used here is primitive, but it's a start.

Add a label, a textbox and a Button to the bottom of the window. The complete XML appears below:

Search for a Contact name starting with a particular string:
<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="C u s t o m e r s" Height="300" Width="432">

    <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="30*" />
      <RowDefinition Height="30*" />
      <RowDefinition Height="210*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="93*" />
      <ColumnDefinition Width="317*" />
    </Grid.ColumnDefinitions>
    <Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right">ContactName:</Label>
    <Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right">Phone:</Label>
    <TextBox Grid.Row="0" Grid.Column="1" x:Name="txtName" Text="{Binding Path=ContactName}" />
    <TextBox Grid.Row="1" Grid.Column="1" x:Name="txtPhone" Text="{Binding Path=Phone}"
    HorizontalAlignment="Left" Width="100" />

<!-- New stuff starts here -->

    <Label Grid.ColumnSpan="2" Grid.Row="2" Height="28" HorizontalAlignment="Left"
    Margin="12,0,0,10" Name="label1" VerticalAlignment="Bottom"
    Width="159">Find contacts starting with</Label>

    <TextBox Grid.Column="1" Grid.Row="2" Height="23" Margin="84,0,0,12"
    Name="textBox1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="88" />

    <Button Grid.Row="2" Height="23" Margin="0,0,12,12" Name="button1"
    VerticalAlignment="Bottom" Grid.Column="1" HorizontalAlignment="Right"
    Width="75" Click="button1_Click">_Search</Button>

<!-- End of new stuff -->

  </Grid>

</Window>

Next, change your code-behind so that the GetACustomer method that gets a record uses a SQL WHERE clause (Listing 3):



NOTE: The M-V-VM movement has as its major mantra the goal of completely banishing code-behind. This simple example doesn't even try to do so. (There are many other "best practices" that we aren't demonstrating here. For example, data access belongs in a data-access module, so that things like connection strings and even hard-coded references to a particular type of DataAdapter are not best practices.) However, I've read many 30-page articles on WPF that tried to dot all the I's and cross all the T's, and I simply can't inflict that on my readers. Master the basics here; you can apply all of the other programming paradigms later.

Run the form, and you'll see that you can search by contact name (Fig. 9). As an exercise, modify this app to return a list of all matches into a combo box, then use the SelectedIndexChanged event to return one of the filtered list.


Third-party controls

You're not limited to building your own controls; a vibrant third-party market has arisen, including these vendors:


Conclusion
top

WPF is absolutely what's next. It's not a beta; Microsoft released it in 2008, and it's been sitting there waiting to be discovered ever since. If you understand what's happening in this example, and can duplicate it from memory, it should take about 15 minutes to build. That's a good start on your next career.

One of these days this recession is going to end, and when it does, there will be pent-up demand. Users will see WPF and Silverlight forms and want the same thing. Take the time to master WPF, and you'll be there when the flood gates open.

See 'ya.

Les

qqq


Copyright(C) Pinter Consulting, 2012Tel: +1 (650) 464-6924
Automated conversion between C# and VB by Visible C#/Visible VB from Tangible Software Solutions