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

Simpler parent-child forms with StrataFrame

See what Strataframe data-aware controls can do to simplify this common task


Les Pinter

Login or register to download source code

Coding time: 20 minutes.

 Jump to:   Setting up the project   Adding the Business Objects   Data Retrieval Methods   Grids   ListBoxes   ListViews 

     


Introduction

There are few WinForms applications that don't need to show a list of records that are linked to a key field in another list of records - what's commonly called a parent-child pair of lists. The child table may or may not be identified in the database as dependent upon the parent table, but it really doesn't matter: There is no automatic way to display these pairs. You just have to code it.

However, there are several ways to do so. Some require almost no code; others, depending on how much you avail yourself of the controls' designers, can take anywhere between a little and a lot. You decide which one is the best fit.

One of the reasons that I like StrataFrame is that it comes with a ton of controls that have been modified to interact intelligently with StrataFrame business objects - those little table classes that not only expose the tables' columns as properties, but also add other nice features that make dealing with data a whole lot easier than .NET's "out-of-the-box" experience. Parent-child forms nicely showcase some of those features. So that's the topic of today's exercise.


Setting up the project
 Top 

As always, we start with a StrataFrame Windows Application project. You'll have to choose the language, but in this article we'll include the code for both C# and VB. The starting project appears below:


I'll first add a StrataFrame MainForm:


You'll need to change the initially displayed form from Form1 to your new MainForm. StrataFrame uses the InitApplication method of startup program called AppMain in VB and program.cs in C#, which tells the application which is the starting form in the InitApplication method. In program.cs, it's around line 139:

    private static void InitApplication(InitializingApplicationEventArgs e)
      { e.Forms.Add(typeof(ParentChildForms.Form1)); }

In AppMain.vb it's near line 119:

    Private Shared Sub InitApplication(ByVal e As InitializingApplicationEventArgs)
      e.Forms.Add(GetType(ParentChildDemo.Form1))

By default, it's set to Form1. Change it to the form you want to see when the app starts - MainForm if you're using a MainForm with a menu, or frmGrids if you just want to see the Grids form. In Fig. 3 I've changed the C# version:


Note: In VB, the project Properties sheet lets you set the startup object; don't do it, because it will bypass the Main routine in Program.vb that initializes the StrataFrame framework.


Now, just to familiarize yourself with another neat StrataFrame feature, drag and drop a DefaultApplicationTheme from the StrataFrame toolbox onto the form, select it, open the Properties Sheet by pressing F4, and pick "CoolBreeze" from the list of available Themes. You'll see the results in the forms we're going to do today.


Adding the business objects
 Top 

StrataFrame uses Business Objects (table classes) that it generates directly from your datasource. We'll use the Customers and Orders tables from the StrataFrameSample database that installs with StrataFrame. Right-click on your project and select Add, New Item, then pick SF Business Object from the available items. Be sure to change the name to CustomersBO before you click Add (Fig. 5)


You've added an empty class; we'll configure it shortly. Add another SF Business Object named OrdersBO in the same way.

Now, from the StrataFrame menu pad in the IDE menu, select Business Object Mapper. Since the project isn't yet pointed at a data source, the little red "x" over the Project name and the business objects below it indicate that they're not yet configured. Not a problem: Select the project name from the Available Projects tree and click on the Configure Project link, which becomes enabled when you select the project name (Fig. 6).


In the Choose Project dialog that appears, click the New Button. Then, from the Project Properties screen that appears (Fig. 7), fill in a project name and description. Leave the Database Deployment Toolkit radiobutton selection in the Default Structure Settings group as it is; this is an external file used in the IDE where StrataFrame stores project information. You can also store it in a database, but we generally won't do so. Be sure to select the new project (on the Choose Project dialog page) after you create it.


You should, however, build and store your SQL connection string information by selecting the little button with the three dots just to the right of the SQL Server Connection string textbox in the Default Connection Strings group (circled in Fig. 7, above) to bring up the Database Connection Wizard (Fig. 8)


Once you've configured the project, highlight CustomersBO and click on the Configure Business Object link to produce the screen seen in Fig. 9:


Now select the SQL Server radio button and click the Select Source button: You'll be able to select the StrataFrameSample database and pick the Customers table (Fig. 10). Repeat the process for the OrdersBO business object, selecting the Orders table.


When you return from selecting tables and fields for the two business objects, you'll see that the little red "x"s have disappeared. You can double-click on any row in the Business Object Mapper to select other options for each field (for example, to replace a NULL value in a column with a blank or zero) (Fig, 11). However, for the moment we don't need to do anything else.


Select Rebuild All, and close the BOM dialog.


Data Retrieval Methods
 Top 

You might think that a Business Object knows how to return data; it doesn't. You have to add one line of code in each one. DoubleClick on the CustomersBO and select Click here to switch to code view. Expand the Data Retrieval Methods region and type this in:



<rant>Okay, it's two lines of code in C#, if you like to use extra lines. If you like to see those goofy little squiggly things on a line by themselves, it's, like, a dozen lines of code. I published a magazine for 10 years, and I hate wasted vertical space; I'd scrunch it down to one line, but I'd probably lose half of you C# guys...</rant>

The code for the Orders table filters the orders to the currently-selected customer in the top grid, so it's a little different. Double-click on OrdersBO, select Code View, and add the following:



Rebuild the project, and you're ready to build some forms using StrataFrame to get data.


Grids
 Top 

The simplest way to display records from two related tables is with a pair of DataGridViews. Fig. 1 shows a scenario that's pretty common: A list of customers and the orders that each customer has placed. Right-click on the project, select Add, New Item, and select a SF Standard Form, giving it the name frmGrids. Add two DataGridViews, position one above the other (Fig. 12). I've named them dgvCustomers and dgvOrders to make the code easier to follow.


Next you'll need to drop the two business objects (CustomersBO and OrdersBO) on the form and Rebuild your project. To do this, select Tools, Options, Windows Forms Designer from the IDE menu and make sure that AutoToolboxPopulate is set to True (Fig. 13). You can then use Ctrl+Alt+X to open the Toolbox, and you'll see your two business objects at the top of the Toolbox (Fig. 14). If you don't see them, you didn't Rebuild your project.





Drag and drop one of each onto the form. Almost done.

StrataFrame list controls know how to databind to SF business objects. However, the WinForms DataGridView is about the only WinForms control that doesn't have a StrataFrame counterpart. But it doesn't matter: If you add two BusinessBindingSource objects (highlighted in Fig. 14, above) from the StrataFrame Controls & Components Toolbox, rename them CustomersBS and OrdersBS respectively, and set their DataSources to CustomersBO and OrdersBO respectively, you've taken care of databinding. Set the DataSource property of the top DataGridView to CustomersBS and the DataSource property of the bottom DataGridView to OrdersBS and you'll see that it already knows the column headings for the two tables (Fig. 15).


Code

Now we need some code. Here's the complete code for frmGrids:



If you don't see the grid form, verify that the startup form is frmGrids, as shown in Fig. 3, above.

There are a number of refinements that you can make to enhance the appearance of the grid:

  • Set the AllowUserToAddRows and AllowUserToDeleteRows properties to False and ReadOnly to True
  • Fix the column headings
  • Set the AlternatingRowsDefaultCellStyle BackColor property to some other color, e.g. (128, 255, 255)
  • Set the AutoSizeColumnsMode to, say, DisplayedCells
  • Format any datetime columns to exclude the time (what was Microsoft thinking?)
  • Anchor both grids at the Top, Left and Right so that they resize when the form is widened (requires setting the form's FormBorderStyle to Sizable)

To test the form from MainForm, add a MenuItem captioned "Parent-Child Grids" and the following code to test it:

C#: { GridForm fg = new GridForm(); fg.ShowDialog();}
VB: Dim fg As New GridForm(): fg.ShowDialog()

The result is shown in Fig. 16:


However, for many users, the grid is just too ugly. The obvious choice for the next step up is the ListBox. It's easy, but there are some shortcomings in the aesthetics department. We'll see what it takes below, then see an even better alternative.


ListBoxes
 Top 

The ListBox is an easy control to use. StrataFrame has one that's data-aware, i.e. it plays well with SF business objects. So we won't need to add BindingSources for the two business objects. But there's a little more coding.

Add a StrataFrame Standard Form and name it frmListBox. On it, put two SF GroupBoxes (because they look nice) with a SF ListBox inside each group box, naming them lstCustomers and lstOrders. Finally, drop both of our favorite business objects on the form (Fig. 17):


When you set PopulationType to Business Object and then click on the button with the three little dots, you're presented with a screen that offers a number of options (Fig. 18).


Select Method to Execute to populate the upper listbox. The Fill() data retrieval method that we wrote for the CustomersBO business object (see above) will do nicely.


Use the little green icon to add to the Display Member list. You can have any number of columns from the business object; their sequence numbers, shown in squiggly braces, are used to determine their display in the Display Member Format String. I used a string that will display last name, comma, first name. Finally, I designated cust_pk (the customer ID) as the value member. We'll use that in populating the second list.


That should do it for the Customers ListBox. For the Orders ListBox, set PopulationType to BusinessObject, select OrdersBO.fill(System.Int32) (because as you recall we use the CustomerID (CustomerBO1.cust_pk) to select matching orders), and fill in the Display Members (note the use of format strings to pad the order ID to 6 characters, and to suppress the goofy time component of the datetime field or_Created) as shown in Fig. 21:


Code

The code for a listbox form is a little more involved. The reason is that we're going to use the second listbox's Requery() method, which doesn't have parameters. So, to tell the OrdersBO1.fill(nCustID) method what value to use for nCustID, we'll pass it as a parameter in the ListBox2_ListPopulating event, whose mission in life is to pass parameters to events like Requery(). Here's the code:



Finally, change the font for the two listboxes to Courier New, get the columns to line up. The resulting form is shown in Fig. 22: Not bad, but not good. If the P/O column is empty, things aren't going to line up, even with the monospaced font.

To test the form from MainForm, add a MenuItem captioned "Parent-Child ListBoxes" and the following code to test it:

C#: { ListBoxForm flb = new ListBoxForm(); flb.ShowDialog(); }
VB: Dim flb As New ListBoxForm(): flb.ShowDialog()



So what's our last resort? The ListView,


ListViews
 Top 

ListViews are a little more demanding, but I think you'll find that they deliver the degree of control that you need.

In this next form we'll display the customer in last name, first name format, together with the daytime telephone number, in the first listview, and the order number, P/O number and ship date in the second listview.

The order in which you do things is important. You'll first add the business objects, then add the listviews and the groupboxes that they live in; then you'll define the number of columns in each listview and specify their column headings; next, you'll assign the appropriate business object to each listview; finally, you'll select the method of loading the data into each listbox.

First, add a new StrataFrame Standard Form named frmListView, changing the form's Text property to "Orders by customer." Add the CustomersBO and OrdersBO business objects and rebuild the application. Then, put two GroupBoxes on the form, changing their Text captions to Customers and Orders respectively. Drop a ListView on each one, naming them lstCustomers and lstOrders. Add two Columns to the first and three columns to the second, with column headings as shown in Fig. 23:


Next, set the BusinessObject property of the two listviews to CustomersBO and OrdersBO. IMPORTANT: Before you do anything else, change the AutoNavigateToSelectedRecord property of lstCustomers to True and the PopulateOnFormLoad property of lstOrders from FormLoad to Manual. The Customer listview is synchronized automatically, but we'll have to code the loading of the orders listview explicitly.

Now for something completely unfamiliar: StrataFrame controls have their own ways of loading data, and they specify it using the PopulationDataSource property. This property has its own very special property editor, as seen in Fig. 24:


Recall that we defined the Columns for the ListView, setting only their column headings. The PopulationDataSource Property Editor lets us build the strings that are to be displayed in each of the previously defined columns. As you add Display Fields, they're assigned sequence numbers. Then, under "Columns", you can use the format "{0,"C"}, where 0 is the sequence number and "C" is an optional string format code. See Visual Studio help on string format codes ("MM/dd/yyyy" for a date, "C" for currency, etc).

For the Customers listview, I've specified the fill method that we wrote for the Customers business object. That will initially load the control. But how do we move to the "current" record when users move up or down in the listbox, Notice that the "Tag" property at the bottom of the editor screen is set to "cust_pk". You might have noticed that Tag is described as a "user" property; well, that's us. When you select AutoNavigateToSelectedRecord, the control's Tag property is used to look up the matching record.

The Orders ListView columns need to be formatted, and as we did above, we'll use the PopulationDataSource Property Editor to specify how the columns are to be formatted. We'll also pick the fill(System.Int32) method that we wrote for the OrdersBO object, which will need the CustomerID (CustomersBO1.cust_pk) as a parameter. For the Orders listview, The method call needs to know where it will get the Int32 value from; we'll pass it the value in the lstOrders_ListPopulating event, as you'll see in the code below.


Code

Now for the code: In the form's Load event, we'll load the customers object and call Requery() on both listviews. Then, when the selected record in CustomersBO changes, we'll requery the Orders listview. Since the OrdersBO fill method needs to know which CustomerID to use, and we'll pass that value to its ListPopulatingEventArgs parameter, as shown below:




To test the form from MainForm, add a MenuItem captioned "Parent-Child ListViews" and the following code to test it:

C#: { ListViewForm lvf = new ListViewForm(); lvf.ShowDialog(); }
VB: Dim lvf As New ListViewForm(): lvf.ShowDialog()

The result is shown in Fig. 26:


There are many, many variations on this theme. If you open the Method to Execute dropdown, you'll see a whole raft of available choices. Some of them allow you to query the default table of the business object, which since we loaded it in the form's LOAD event, doesn't have to be loaded again; this avoids a round trip to SQL Server each time you call it, which can be very efficient. We'll explore their uses in a future article.

Easter Egg

You might have noticed the color schemes of the ListBoxes and ListViews. Go back to MainForm and change the Theme of the defaultApplicationTheme1 object in the tray below the form to, say, SandDunes. That's another little freebie from StrataFrame.


Conclusion
 Top 

Consider how long your last parent-child form took to do, and how much simpler it would have been using these techniques. Working through all three of these examples should take no more than 20 minutes. So crank up VS2008, download StrataFrame if you haven't already done so, and take it from the top. This won't take long.

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