Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Create Dynamic XAML Forms with the Presentation Model Pattern : Page 2

Harness this powerful user interface pattern to XAML forms to simplify capturing unbounded sets of data.

A Concrete Example
To create a dynamic UI backed by a Presentation Model, you will define a ListView that uses DataTemplates to bind elements in a collection to ListViewItems. After those components are working together, adding and removing line items becomes a simple matter of calling Presentation Model methods from UI event handlers. Before all this can come together though, you need to introduce the Presentation Model into the XAML UI.

ExpenseSheetWindow is the XAML UI for Expenses.NET. The corresponding Presentation Model is ExpenseSheetWindowModel. You create an ObjectDataProvider in the root Window element's Resources block, and then bind it to the Window's DataContext. Now the Presentation Model and its properties are available to all controls:

public class ExpenseSheetWindowModel : INotifyPropertyChanged { private ExpenseSheet currentExpenseSheet; public event PropertyChangedEventHandler PropertyChanged; public ExpenseSheet CurrentExpenseSheet { get { return currentExpenseSheet; } } private void NotifyPropertyChanged(string info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } }

Here's the basic XAML code:

<Window x:Class= "ComFrame.Expenses.Presentation.ExpenseSheetWindow" xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:my="clr-namespace:ComFrame.Expenses.Presentation" Title="Expenses" SizeToContent="WidthAndHeight" Background="White" Name="rootWindow"> <!-- Window Resources --> <Window.Resources> <!-- Backing presentation model --> <ObjectDataProvider x:Key="presentationModel" ObjectType="{x:Type my:ExpenseSheetWindowModel}" /> </Window.Resources> <Window.DataContext> <Binding Source="{StaticResource presentationModel}"/> </Window.DataContext> </Window>

Now you'll create the ListView that displays the line items. You might be wondering what the benefit is of using a ListView over its parent, ItemsControl—after all, ListView derives from Selector, a class that provides functionality you don't need (for example, you don't want any of the line items to highlight when selected). But ListView also has a specialty view called a GridView that arranges controls in a grid, with column headers over each column that users can rearrange as desired. That adds a nice look and feel for Expenses.NET. While you could dig into the internals of the ItemsControl class to try to make it work with GridView, it's easier to use a ListView and suppress its selection functionality:

<ListView Name="lineItemListView" Height="300"> <ListView.View> <GridView AllowsColumnReorder="True"> <GridViewColumn Header="Date"/> <GridViewColumn Header="Description"/> <GridViewColumn Header="Amount"/> <GridViewColumn Header="Actions"/> </GridView> </ListView.View> </ListView>

With the ListView defined, you need to hook it up to the ExpenseSheetWindowModel by setting its ItemsSource property to a collection nested within the Presentation Model. ExpenseSheetWindowModel maintains a reference to the current ExpenseSheet for a given weekly period. Recall that an ExpenseSheet contains an ObservableCollection of ExpenseLineItems; this will be your source property into the binding:

<ListView Name="lineItemListView" ItemsSource="{Binding Path=CurrentExpenseSheet.LineItems}" Height="300">

If you launch Expenses.NET at this point, you'll notice it comes up with an empty ListView. But you want it to display one initial ExpenseLineItem so users have a place to start entering expenses. The ObjectDataProvider you defined implicitly calls ExpenseSheetWindowModel's default constructor. This is a good place to initialize an empty line item:

public ExpenseSheetWindowModel() { currentExpenseSheet = new ExpenseSheet(DateTime.Now); CurrentExpenseSheet.LineItems.Insert(0, new ExpenseLineItem()); }

Interestingly enough, launching the application again still yields what appears to be an empty ListView. Don't be fooled though—the apparently empty ListView contains an empty ExpenseLineItem. The default presentation of the ListViewItem renders the output of the ExpenseLineItem's ToString method in a TextBlock. Because the implementation returns the description property (which is initially set to null), the ListViewItem isn't visible. However, you can see something is in fact there by clicking on the space in which it should appear, and watching it highlight.

To hold the data for each ExpenseLineItem, you're going to replace each ListViewItem's default, read-only TextBlock by using DataTemplates to substitute editable controls. A DataTemplate lets you take advantage of WPF's rich content model by applying a user-defined presentation to each item in a collection as it's rendered. The first three DataTemplates are each bound to a property of the ExpenseLineItem implicitly passed in from the ListView's ItemsSource. The last DataTemplate contains two buttons to add or remove ExpenseLineItems. You'll wire these up later to events in the XAML code-behind:

<ListView.Resources> <DataTemplate x:Key="startDateTemplate"> <ComboBox MinWidth="200" Margin="3,0,3,0" SelectedItem="{Binding Path=StartDate}"/> </DataTemplate> <DataTemplate x:Key="descriptionTemplate"> <TextBox Width="350" Text="{Binding Description}" Margin="3,0,3,0"/> </DataTemplate> <DataTemplate x:Key="amountTemplate"> <TextBox Width="50" Text="{Binding Amount}" Margin="3,0,3,0"/> </DataTemplate> <DataTemplate x:Key="actionsTemplate"> <StackPanel Orientation="Horizontal"> <Button Margin="6,0,0,0" Click="addButton_Click"> <Image Source="/Images/add.png"/> </Button> <Button Margin="3,0,0,0" Click="deleteButton_Click"> <Image Source="/Images/delete.png"/> </Button> </StackPanel> </DataTemplate> </ListView.Resources>

These four DataTemplates correspond to the four GridViewColumns you defined earlier. You wire the DataTemplates and GridViewColumns together by setting each GridViewColumn's CellTemplate property:

<GridView AllowsColumnReorder="True"> <GridViewColumn Header="Date" CellTemplate="{StaticResource startDateTemplate}"/> <GridViewColumn Header="Description" CellTemplate="{StaticResource descriptionTemplate}"/> <GridViewColumn Header="Amount" CellTemplate="{StaticResource amountTemplate}"/> <GridViewColumn Header="Actions" CellTemplate="{StaticResource actionsTemplate}"/> </GridView>

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date