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

IdeaBlade DevForce Simplifies WPF Apps

Let IdeaBlade DevForce manage your data, and do only the fun stuff


Les Pinter

Login or register to download source code

Coding time: 15 minutes.   

Jump to:   Introduction to WPF   Building the Application   Adding Controls   Adding Data   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 (Fig. 1). A simple search mechanism will be used for navigation.


For this exercise, you'll need to go to the IdeaBlade download page and download a copy of DevForce WinClient Express Edition, which is free. And, if you already know WPF, you can jump straight to  Adding Data .


Introduction to WPF
top

Windows Presentation Foundation (WPF) is Microsoft's replacement for the WinForms Designer. It was released nearly three years ago, but many developers have hesitated to get started.

Undoubtedly, part of the reason is the fact that there's a way to use OnPropertyChanged methods to handle the wiring up of a screen, so that little or no code appears in the codebehind. It's called Model-View-ViewModel, and it's often presented as "the only way that good developers write WPF applications." It's so daunting that many developers have stayed away from WPF entirely.

I like M-V-VM, but if I spend 90% of every WPF article explaining M-V-VM, you'll just walk away dizzy, and you'll never get up to speed with WPF and XAML. So, this article shows how to build a WPF window and use IdeaBlade DevForce to return data, without venturing into M-V-VM. Once you master page layout and DataContexts, you can go on to M-V-VM. But for now, sit back and relax. This article will introduce you to WPF as gently as possible.

<rant>(Galileo had to publicly support the church's terra-centric view of the solar system in order to avoid career suicide, and this little obéissance is apparently de rigeur these days. You make up your own mind.) </rant>

So what is WPF?

WPF application is one of the project types that you can select as a New Project in VS2008/VS2010. It adds a number of references needed by WPF applications, and creates two files with the .xaml extension. One of them, App.xaml, controls application startup; the other, Window1, is a placeholder for your first form.

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, the acronym for eXtended Application Markup Language. It derives from XML, so you would expect to see tag names between little brackets in the XAML source, and that's indeed what you'll find.

However, they're not the same thing. XML tags are anything you want them to be; XAML tags correspond to the .NET framework classes. Within the XAML window of the WPF designer, as you type, something like IntelliSense shows you which tags are available. At run time, the XAML is used to instantiate .NET objects and set their properties, raise their events and call their methods. You could do everything in code that you do in XAML. But isn't filling in properties in the Properties Sheet even better than IntelliSense?

Once you've built your form, 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. WPF's vector graphics let you do things 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.

That's one of the selling points 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."

IdeaBlade DevForce

DevForce is an Object-Relational Mapping tool from IdeaBlade of Emeryville, California, near my old stomping grounds, the San Francisco Bay Area. It builds on the Entity-Framework tools built into Visual Studio, extending the ADO.NET Entity-Data model to make data management deliciously easy.

You could use a DataSet, which exposes the columns in each table as properties, to bind to WPF TextBoxes and other controls. However, IdeaBlade DomainModels are like DataSets on steroids. They don't need a DataAdapter to load and save their data, or a DataView to permit filtering. They're built on ADO.NET Entity Data Models (stored in .edmx files), and leverage Entity-Framework, Microsoft's latest technology for data access and manipulation. And they use LINQ, and it's about time you got into LINQ.

In this article, I'll build an Entity Data Model (which takes about 30 seconds), then use it as input to the DevForce Object-Relational Mapper tool (that takes another 30 seconds) to create a data access class. The result is that the little WPF application that you see in Fig. 1, above, requires three lines of code to retrieve a record from the NorthWind Customers table. I'll also use Styles, which resemble CSS, to show off some of WPF's layout capabilities.


Building the application
top

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\602\CS, and used the default name WPFApplication1 (Fig. 2):


The project created by the WPF Application template appears in Fig. 3:


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, "xmlns", is the default WPF namespace, and contains all of the WPF classes, including the controls you'll use on your forms. The second, "xmlns:x", is the XAML namespace, and allows you to refer to various XAML utilities and features. The ":x" suffix is analogous to the syntax that allows you to provide an abbreviated form for a using or imports statement to jumpstart IntelliSense in your code; it gives the prefix "x:" the same capability while editing XAML.

The "Application" tag will cause the loader to create an application object; the StartUri attribute sets the StartUri property of the application object. In the generated app.xaml code, StartUri is set to "Window1.xaml", so when your application starts, Window1.xaml will be the startup form. Projects, forms and reports use the names of other objects and their attributes. XAML files are text files; you can edit them with Notepad.

Window1.xaml

Let's have a look at Window1.xaml, our first screen, Window1.xaml, appears in Fig. 4, below:


The circled controls let you modify the way the screen designer and its associated xaml are displayed. I generally like to see the xaml also, 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="WpfApplication1.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. Here, I changed the value of Title to "C u s t o m e r s".

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, one of seven top-level containers available in xaml. This grid 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 (You can do something similar with CSS, but it's a workaround.) Instead, you were forced to give objects padding, or to create a grid and place items within their own "<td>" tags.

A XAML grid is more like an HTML Table; it's a container for other controls. However, it doesn't have to have rows and columns (although doing so can be useful, as we'll see). You can simply put labels, textboxes and buttons inside a grid, each with a margin="l,t,r,b" specification to specify the distance in pixels from the left, top, right and bottom of the grid. (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.

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. WPF controls are 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. 5):


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 by positioning the mouse cursor within the left margin of the Grid and clicking. Unless you have really fine motor control, you'll then need to precisely set the height of the RowDefinitions in the XAML window. After you add the RowDefinitions, you can drag and drop labels and textboxes into the cells. I think you'll see that it's a better way to position elements like labels and textboxes.

<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" />
    <TextBox Grid.Row="1" Grid.Column="1" x:Name="txtPhone" HorizontalAlignment="Left" Width="92" />

  </Grid>

</Window>

Note that I also had to manually specify the row and column indices of the labels and the textboxes; I also have 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. 6:


Designing with Style

Now, let's add some color. You could add properties to each object, but there's a better way: Styles..

Before we explore styles, there are a few changes we need to make. First, enclose the <grid>..</grid> tags within a pair of StackPanel tags:

<StackPanel>
  <Grid>
...
  </Grid>
</StackPanel>

Next, add a Label between the <StackPanel> and the <Grid> tags to provide a heading:

<StackPanel>
<Label FontSize="24" Background="Green" Height="48" Foreground="White" Content="Customer lookup" />
<Grid..

Third, expand the grid downward to fill the window, and add a background color:

<Grid Height="279" Background="GreenYellow">

Those are some of the ways you can set visual attributes in XAML to affect the appearance of the screen. However, you can use styles to affect the appearance of specific controls in a way that's analogous to Cascading Style Sheets in web pages.

Styles are specified in a section called <Window.Resources> just after the <Window> tag, or alternately in an external Resource Dictionary. We'll do them inline here just so that we're only dealing with one issue at a time.

Styles use (among other things) Setters to set property values, and Triggers to specify property changes upon raising particular events. Below, I'm setting the BorderThickness and BorderBrush (color) of my textboxes, just as I might do with Cascading Style Sheets in a web page. When a textbox gets focus, the BorderThickness and BorderBrush property values are changed to notify the user visually that the focus has changed. You could accomplish the same thing in code, of course, but isn't this simpler?

Add the following to the top of your xaml, just after the <window> tag:

<Window.Resources>

  <Style TargetType="{x:Type TextBox}">
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="BorderBrush" Value="Gray" />
    <Setter Property="Margin" Value="3,3,3,3" />
    <Style.Triggers>
      <Trigger Property="IsFocused" Value="true">
        <Setter Property="BorderBrush" Value="Green" />
        <Setter Property="BorderThickness" Value="3" />
      </Trigger>
    </Style.Triggers>
  </Style>

</Window.Resources>

Press F5 to run the application, and you'll see that (1) the two textboxes have acquired a slightly different look, and (2) when a textbox gets the focus, its border changes just enough to make it easier for the user to see which textbox has the focus (Fig. 7):


Next, we'll need the infrastructure for a customer search feature. Add the following XAML just before the closing </Grid> tag:

  <Label Name="label1" Grid.ColumnSpan="2" Grid.Row="2"
  HorizontalAlignment="Left" VerticalAlignment="Top"
  Margin="1,5,0,0"  Height="28" Width="152">Find contacts starting with</Label>

  <TextBox Name="textBox1" Grid.Row="2" Grid.ColumnSpan="2"
  VerticalAlignment="Top" HorizontalAlignment="Left"
  Height="27" Width="147" Margin="6,28,0,0" />

  <Button Name="button1" Grid.Row="2" Grid.ColumnSpan="2"
  Margin="6,57,0,0" Height="23" Width="147"
  VerticalAlignment="Top" HorizontalAlignment="Left"
  Click="button1_Click" Content="_Show matching records" />

  <ListBox Name="listBox1" Grid.Column="1" Grid.Row="2" Margin="83,15,6,4"
  Background="SandyBrown" MouseDoubleClick="listBox1_MouseDoubleClick" />

</Grid>

Note that you can use Grid.ColumnSpan="2" in a way that's analogous to ColSpan in a web page table's <td> to treat two adjacent columns as a single column, thus permitting the display of content beyond the starting column.

So far, what you've done looks like Fig. 8 when you press F5:


One of the nice effects that you can add to a listbox is to increase the size of the currently selected item. You can do so in code by doubling the SelectedItem's FontSize upon selection, then returning it to its original value upon de-selection. But in WPF, all you have to do is add a ListBoxItem style trigger that hooks up the IsSelected event. Add the following XAML in the Window.Resources section just under the Style that targeted TextBoxes:

  <Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="Margin" Value="1,1,1,1" />
    <Style.Triggers>
      <Trigger Property="IsSelected" Value="true">
        <Setter Property="FontSize" Value="22" />
        <Setter Property="Background" Value="Red" />
      </Trigger>
    </Style.Triggers>
  </Style>

This trigger doubles the size of the selected item in the ListBox. You won't be able to see it until we add some data, however; that's what's next.


Adding Data
top

For any type of data access in WPF, you'll need to supply an object that exposes a public property for each column in your table and assign it to the DataContext. Each databound object - textbox, combobox, etc - will contain a clause like Text="{Binding Path=PropertyName}" which points the control to the named public property in the DataContext.

Although you could supply the datacontext to each control, thanks to "bubbling", WPF will search up through the container hierarchy (in this case the grid within the window) until it finds it. So we can assign the data object to the Window's DataContext, and the textboxes will find their data and pull out the value of the public property named in the Path= clause.

There are a number of objects that can function as a DataContext, and one of them is the IdeaBlade DomainModel. To get one, you add an ADO.NET Entity Data Model to the project, then use it as input to the IdeaBlade DevForce Object Mapper, which creates a class stored in a file with the extension .ibedmx. This is the class that provides the DataContext that WPF needs. The whole process takes less than a minute for a single table, and probably less than two minutes for a hundred tables. It's stupid fast.

Adding the Entity-Data Model

We'll use the Customers table from the NorthWind dataset that ships with SQL Server. In the Solution Explorer, right-click on the Project, select Add Item, and select an ADO.NET Entity Data Model (Fig. 9). By default it will be named Model1.


Select Generate from Database (Fig. 10). This will ultimately (in about 3 more screens) present you with a list of the tables in your database so that you can select which one(s) to include in the model (the .edmx file):


Select the connection to connect to your SQL database, or create a new one (Fig. 11):


Finally, select the NorthWind Customers table (Fig. 12) and save the model. This will create a file called Model1.edmx, displayed in a graphical designer, but the code-behind is where the heavy lifting is done.


Generating the IdeaBlade DomainModel

Next, you'll need to generate the IdeaBlade DomainModel, which is the class that contains the Public properties corresponding to columns in the Customers table, as well as code to retrieve and store your data. From the Tools menu pad, select DevForce Object Mapper (Fig.13):


From the menu, select Model, Add Entity Model (Fig. 14):


Select the Model1.edmx file, which is the only Entity Data model in the project (Fig. 15):


You can expand the treeview to see the columns in the customer table that will be exposed as public properties in the generated DomainModel. You can also set oneway or twoway binding, change the display name, pick a strategy for generating new primary key values, and a whole lot more. IdeaBlade DevForce encapsulates many years of effort by some of the smartest people in the .NET world, and you'll be amazed at its depth. This article just scratches the surface.

Saving the model generates the ibedmx file containing the class that will manage your data (Fig. 16):


As seen in Fig. 17, the generated IdeaBlade DomainModel class has been added to the project:


Binding the data

In WPF forms, the Text property is bound to a public property in the DataContext by way of the syntax Text="{Binding Path=ColumnName}". 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 Customers object 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 DomainModel object when the form is loaded, as shown in Listing 2, the databinding process will find it at run time. Add the following three lines of code after the InitializeComponent() line in the window's constructor (C#), or add the same three lines to the window's Loaded event in VB: '



WPF uses reflection to find the ContactName property and pull its value out of the DataContext, so it's a little slow in the overall scheme of things. 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.


Filtering/Searching
top

Listing 3 shows the code that I wrote to support the search functionality implied by the screen's design. Since IdeaBlade models can be queried using LINQ, that's the technology you'll see here. It takes some getting used to, but once you use it, you'll like it. It gives you IntelliSense with SQL queries, and that's something we've waited a long time for.

The screen initially loads all of the customers into the listbox by calling ShowMatches() with a null string as the parameter. If the user filters the list by supplying an initial letter or letters and clicking the Show matching records button, the list is redisplayed showing only matching ContactNames. Double-clicking on a name loads the selected name into the DataContext, thus forcing the redisplay of the selected name and phone number. The Boolean LoadList is used to suppress reloading the list when double-clicking is the reason for the call to ShowMatches().



Run the form, and you'll see that you can search by the first letter or letters of contact name (Fig. 18). If multiple names appear in the ListBox, you can double-click to display the associated ContactName and Phone:


If you download the source code for this article, you'll have to double-click on the Model1.edmx file in the project, then right-click on the table in the Model Designer and select Update Model From Database in order to and associate it with the NorthWind database on your SQL Server, then re-run the IdeaBlade Object Mapper generation step (or, if you prefer, create a new table with ContactName and Phone columns, load some data into it, erase the Model1.edmx, generate a new Model1.edmx from your table, and re-run the IdeaBlade Object Mapper to re-generate the ibedmx file.) It just takes a few seconds.


Conclusion
top

WPF and IdeaBlade DevForce are fabulous tools for developing the next generation of applications. The beauty of vector graphics, and the power of the IdeaBlade DomainModel classes, make this a winning combination. Download and install the free IdeaBlade Devforce ORM tool using the link at the top of this article, then download and run the source code in either VB or C#.

See 'ya.

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