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

Table Maintenance with DevForce Classic and WPF

How to build your next WPF table maintenance screen in 30 minutes or less


Les Pinter

Login or register to download source code

Coding time: 30 minutes.

Jump to:   The WPF project   Using styles   The forms   Devforce does data   The code   Conclusion 

     


Introduction

This is the first in a two-part series on building M-V-VM (Model-View-ViewModel) applications with Windows Presentation Foundation (WPF). In this part, we'll develop the basic application UI with XAML (eXtended Application Markup Language), including the Add, Edit, Delete and Search capabilities that any such application needs. Having finalized the design and built the basic scaffolding, the transition to MVVM should be relatively easy - at least within the realm of what is considered easy in the MVVM world.

WPF is Microsoft's replacement for the WinForms designer. It can be used to give your existing applications a face-lift, changing boring forms into drop-dead gorgeous screens. Increasingly, users will expect the experience of using an executable to visually resemble a web application. Since XAML is also used in Silverlight applications, and Silverlight is Microsoft's replacement for ASP.NET, XAML is clearly the future of interface design.

In many articles that deal with table maintenance, the mechanics of connecting to data tend to overshadow the whatever else you're trying to accomplish. For that reason, I've chosen to use IdeaBlade DevForce to provide the data access layer (DAL). It's minimally invasive, requires only a few lines of code on the part of the developer, and provides the Model in Model-View-ViewModel that we'll build in the follow-up to this article.

Building this application should take you about half an hour. Once you master the techniques described in this article, if your application needs a dozen forms like this one, a day should be more than enough to get 'er done.


Background
top

Table maintenance screens have a very simple mission; adding, editing and deleting records in a single table. The screen shown below is typical of such applications:

The sample application
Fig. 1: The sample application

Steve Martin had a comic routine that went like this: "How do you get a million dollars in real estate? First, get a million dollars; then..."

In articles about building WPF applications, the discussion of data management often takes a similar approach: "First, build a data model class; then.... They assume away the hardest part of the job. How do you retrieve a record? And how about adding, deleting, and saving changes?

Visual Studio 2008 added Entity Framework support in the form of the ADO.NET Entity Data Model. You select a data source (typically SQL Server, but other sources are also supported), select your tables, views and stored procedures, and the wizard writes what can be hundreds of thousands of lines of code containing almost what you need for WPF. For example, the generated model class has a public property for each table column's value. That's what textboxes and other data controls bind to via the DataContext and Text="{Binding Path=PropertyName}" tags.

But what's lacking is the important part. You get a public property with a getter and setter for each table's columns, but you don't get the OnPropertyChanged() call in the setter that notifies the rest of the application of changed values. And you don't get the ability to load and save records from the database or undo changes. Entity framework provides the models, but not the muscle.

That's why I use a tool like IdeaBlade DevForce. It takes the ADO.NET Entity Data Model generated by Visual Studio 2008 (or 2010) and adds in the missing pieces. It actually uses the edmx file(s) generated by the ADO.NET Entity Data Model template as input to its code generation wizard.

NOTE: In Visual Studio 2010, which should be release by Summer of 2010, IdeaBlade Devforce no longer requires you generate the Entity Model.


The WPF project
top

First, we'll need a WPF project. From the IDE menu, select File, New, Project, then select either VB or C#, and from the Windows templates group select WPF Application, supplying the project name Styles (Fig. 1):


After a few seconds you'll have a project that looks like Fig. 2 in the Solution Explorer:


The generated Window contains a grid, which is one of seven containers you can use. Note that this isn't a grid as in DataGridView; it's just a container. We'll replace it with a stackpanel containing a layout that I use for many screens of this type:

  • A label for the form title;
  • A grid containing the fields in the table and their identifying labels, and
  • A grid containing the buttons needed to perform the reqired actions.


Using Styles
top

Before you build the maintenance form, you should create styles for your screen controls. The app.xaml file shown in Listing 1 will give you a good start. You can then make cosmetic changes here, and they'll be propagated throughout your application.

Styles can be created within a control, in a container for other controls, in a window or page, or at the application level. Generally, App.xaml is the place to put your styles. They'll be applied to all forms in the application, and you'll always know where to find them.


What does app.xaml do?

The styles located in app.xaml function like a Cascading Style Sheet in a web page. The TargetType= tag determines which type of control will be affected by the style. For example, the grid and stackpanel background color will be set to LightGray. Buttons will be 18 points tall and 50 points wide, using the Courier New typeface, and will be aligned at the top left of their container.

Triggers are conditionally executed when the named property is set to the named value, e.g. when the IsMouseOver property of a TextBox is set to the value True, the background color changes to Yellow.

The style trigger for the ListBoxItem simply changes the appearance of the currently-selected ListBoxItem by doubling its FontSize and changes the background color to yellow.

There are many, many other tags available to use in styles; I've seen styles a hundred lines long. Fortunately, you can ship your forms and styles to a professional XAML designer; there aren't many yet, but there will be - I'd recommend the St. Petersburg (Russia, not Florida) yellow pages. Let programmers focus on code. Changes in the XAML won't break the app as long as names of DataContext and Path targets don't change.


The forms
top

The best way to lay out a form of this type is usually to use a StackPanel with a label to identify the table being edited, a grid of the values in a single record, and another grid containing the buttons needed for the Find, Add, Save, Undo, Delete and Close buttons, as shown at the beginning of this article.

Fig. 3 shows an implementation of this layout for the NorthWind Customers table:


I've added a column divider and seven row dividers by clicking on the grid borders, first at the top middle to add a ColumnDefinition, then along the left margin seven times to add the RowDefinitions. I manually corrected the Width and Height values in the XAML. These rows and columns allow you to use Grid.Row and Grid.Column values to position labels and textboxes within the grid. The Labels style in app.xaml right-justifies the labels in the grid cells.

Listing 2 shows the XAML used to create this page:


How it works

The title at the top of the form is provided by a label control with a fontsize of 24 points, a foreground color of white and a background color of green. The Times New Roman font is a good choice for a form title. The Label.Effect tag allows the addition of visual effects; one of them is the dropshadow effect seen in the sample screen.

I manually adjusted the RowDefinition heights until they looked about right, and then did the same thing with the ColumnDefinition widths. This gave me a grid that provides a well-proportioned container for the labels and textboxes, each of which has a Grid.Row and Grid.Column value that positions the corresponding control inside a particular cell of the grid.

Finally, the second grid at the bottom of the stackpanel has two rows and two columns, which exist solely because I want to position the instructions for the Find button directly above the button itself. The Add...Exit buttons have a Margin property because they're all located inside a single cell, and there's no other way to position the buttons at different distances from the left margin.

Note the use of XAML to attach event handlers to buttons. Under the hood, the hookup of event handlers happens exactly as it would if you coded it explicitly in C#; you just no longer have to write the code.

The Search form

My mechanism for retrieving a particular customer consists of letting the user type in the first few letters of the company name and clicking the Find button, which presents matching records in the MatchesWindow dialog form shown in Fig. 4 for the purpose of selecting one of them. The form stores either the record selected by the user or null in the search form's public SelectedCustomer field depending on whether they click Select or Cancel. The calling form then retrieves the stored value and closes the search form.


We'll look at the code that launches this form, as well as the code for the Click event of each of the other buttons, shortly. But first, it's time to build the data management mechanism. This should take about one minute.


DevForce does data
top

IdeaBlade DevForce has been around for a long time, and has been at the leading edge in providing what Visual Studio didn't include. In this section, you'll see how the Model in Model-View-ViewModel can be built in literally under a minute. If you're already familiar with DevForce DomainModel generation, you can skip this and go straight to the code

If you haven't done so already, you can download the latest version of DevForce Universal Express Edition, which can be used to build either WPF or Silverlight applications with up to 10 tables. It's free.

NB: If you need to support more than 10 tables, WinClient Professional is $995; the Universal version, which also supports Silverlight, is $500 more. (Prices with tech support bundled cost more, as you might expect.) But there's no time limit on the free version, and it's the full product in every respect except for the limitation on the number of tables. IdeaBlade does a lot more than just provide a data model - the ability to disconnect your app from the Internet and then continue to work with your data, for one thing - but for the purposes of this article, the data access is all we'll talk about.

The data for our application will come from the Customer table in the NorthWindIB database that's created when you install IdeaBlade, by way of an IdeaBlade DomainModel, which takes just seconds to add to your project. In the Solution Explorer, select your WPF project, then right-click and select Add, New Item, ADO.NET Entity Data Model (Fig. 5):


We'll generate the model from a table in the NorthWindIB database that installs with IdeaBlade DevForce (Fig. 6):


If you haven't yet added a connection to the NorthWindIB database, here's your chance; otherwise, select it here (Fig. 7):


For this exercise, all we need is the Customer table (Fig. 8):


The generated class is code, displayed in a visual designer that you can use to tweak your model considerably. However, for this exercise, you don't have to do anything.


Now, select DevForce Object Mapper from the Tools menu in the IDE. The IdeaBlade wizard dialog shown in Fig. 10 will appear. Select Model, Add Entity Model from the Object Mapper menu:


Select the Model1.edmx file, which is the ADO.NET model generated a moment ago, and which is the only model in the project (Fig. 11):


In Fig. 12, DevForce has read the edmx model file and is paused to allow the developer to set some of the available options prior to generating the DomainModel class. There are lots of them, and they affect how the generated model will interact with WPF via DataBinding. However, the defaults are usually correct, so you generally don't have to do anything.


Select File, Save to generate the ibedmx file where the IdeaBlade DevForce DomainModel generated code will be stored (Fig. 13): Close the dialog and, if you're so inclined, take a look at the IBAddEditDelete.Model1.Designer.cs file. However, it's not necessary. You just need to know how to use it, and that's real easy.


Those two steps are all you need to create the data access layer that your application will use.

NOTE: The ibedmx file and its associated code file provide the Model in Model-View-ViewModel. In the follow-up article to this one, you'll see how the IdeaBlade DevForce model is all you need to make the move to M-V-VM.


The code
top

The code in the Customers window is shown below in Listing 3. I haven't included any data validation, or code to enable and disable buttons as applicable. Validation is a separate topic, and one for which IdeaBlade provides a "Verification Engine" that does a great job of managing this important task. And I've used TextBoxes for all controls, rather than the ComboBoxes that would be more appropriate for lists like Region and Country. Finally, I didn't include any error handling, and if you delete a customer with orders, you'll definitely get an error message. So create a new customer before you test the Delete button...<g>


How it works:

Besides the first three usings, you'll need one for the IdeaBlade.EntityModel that's added to the project references when you select Save in the DevForce Object Mapper, and one for your generated DomainModel. Of course, you don't need them if you don't mind prefixing the objects that use them, but I prefer more concise code. The three field declarations at the top of the class come from them.

IdeaBlade objects: _mgr, _customerQuery and _currentCustomer

The way that IdeaBlade's generated classes are used is a little idiosyncratic. A DomainManager object (_mgr) instantiated from the generated EntityModel class performs several functions that you might have expected to be located elsewhere. In addition, you'll create two objects from the DomainModel class (_customerQuery and _currentCustomer); part of what you need for some tasks comes from one of these objects, and the rest comes from the other. So, pay close attention to how they're used.

_mgr does several important things:

  • It provides the mechanism for creating the _customerQuery object;
  • It creates a new customer record via the CreateEntity method; and
  • It exposes the SaveChanges and RejectChanges methods.

_customerQuery is an IdeaBlade construct that uses LINQ to get your data. It contains both the LINQ capabilities and a collection of customer records returned by the LINQ query.

_currentCustomer is also a container for a customer record. It doesn't have the query capability that _CustomerQuery does; it just stores a record. However, it contains the machinery needed to add or delete a record, via its EntityAspect. That wasn't intuitively obvious to me, but that's just how it works.

Supporting cast

ResetCurrentCustomer contains three lines of code that are called from two different places; so the code was just refactored into a single routine. Essentially, it nulls the DataContext if you either haven't yet retrieved a record, or if you just deleted the current record. That's why, in the Delete Click event handler, after calling _currentCustomer.EntityAspect.Delete(), you call SaveAll(), which calls _mgr.SaveChanges() and then ResetCurrentCustomer().

Event handlers

The Add button's Click event handler is the most convoluted. You first create an empty Customer record using the _mgr.CreateEntity() method, then add it to _currentCustomer using the EntityAspect.AddToManager() method. This is needed because as mentioned above, _mgr contains the SaveChanges() and RejectChanges methods, so it has to know what's out there. Note also that the assignment of the new record's GUID key is done manually here. It can also be assigned in the generated Customer class, but this is the most explicit approach.

The first line in the Find Click event handler (_customersQuery = _mgr.Customer;) instantiates _customersQuery, which can use LINQ to find Customer records whose CompanyName starts with the letter or letters the user entered and pass them as a list to the MatchesWindow's ListBox. The customer selected by the user is retrieved from the MatchesWindow object where it was stored when the user clicked Select, and is then assigned to the window's DataContext.

The lookup window

In MatchesWindow, we show the CompanyNames that matched the LINQ Where() parameters, then let the user select one of them. The selected customer entity is stored in the SelectedCustomer field, which is assigned as the Window's DataContext after returning from the MatchesWindow modal form (Listing 4):


Since the list of matching customer company names was built and assigned to the listbox in the calling form, all this form has to do is to assign the selected customer record to SelectedCustomer if the Select button is clicked, or assign null to SelectedCustomer if the user clicks Cancel. (In either case, the MatchesWindow form is then hidden.) The calling then form assigns SelectedCustomer to its DataContext and closes the MatchesWindow form.


Conclusion
top

I hope you're pleasantly surprised at how little it took to build this WPF table maintenance application. Window1 has about 90 lines of code, although if you remove the blank lines separating routines, it's closer to 70. The SearchForm contains another 18 lines. If you don't count method headers and closing curly braces, the total amount of code in this article is about 50 lines. He who codes least codes best.

Sign up to download the code at the top of this article. Be sure to change the servername in app.config to that of your own server. Run it, then read the code until you understand it and can reproduce it from memory. I don't know of any other way to master a new technology.

In the follow-up M-V-VM article, we'll move this code out of the screen's codebehind to a ViewModel, adding command objects and databinding that completely manage the application. I've used traditional codebehind here because it's easier to see what's required, since in the process of moving code to delegates, it sometimes looks more complex than it really is. But the MVVM version uses essentially the same code, moved to the ViewModel and repackaged as delegates. The enabling and disabling of buttons in M-V-VM is handled in a unique way by command objects, so I made no attempt to do so here.

Acknowledgements

I would like to thank Ward Bell, VP of Marketing at IdeaBlade, for his invaluable comments and suggestions. Ward is a bottomless repository of knowledge about software development, and it is in large part due to his insights that DevForce has become the robust, multifaceted tool that it is for application development.

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