Data-Centric Coding in Silverlight Applications

Focus on the data, not the controls, to build cleaner, better code

Dan Wahlin

October 29, 2009

7 Min Read
ITPro Today logo in a gray background | ITPro Today

Silverlight 101

Data-Centric Coding in Silverlight Applications

Focus on the data, not the controls, to build cleaner, bettercode

By Dan Wahlin

If you build ASP.NET applications, you're used toworking with controls a lot. Nearly everything you do requires accessingcontrol IDs. If you need to retrieve data entered by an end user, you need toreference the controls that contain the data. That's just the way we do it inASP.NET, and if you've been writing ASP.NET applications very long, it's howyou're conditioned to think.

Silverlight changes the game quite a bit because ofthe way you can bind data to controls. You don't have to name your controls inmany situations, since ultimately you care about accessing the data rather thanthe control that contains the data. Granted, if you need to perform ananimation or change a control's style, you'll need to access the control directlyby its name, but when it comes to accessing data, there's an easier way.

Silverlight provides two-way bindings that allow adata object to be bound to a control in a unique way. If the user changes thedata, the source object is automatically updated without additional code onyour part. I covered the different types of bindings available in Silverlightin my previous article, "SilverlightData Binding," but here's a quick example of a two-way binding definedin XAML:

Style="{StaticResourceTimeSheetTextBoxStyle}" />


This example binds the DataContext (the main object bound to the overall form)object's MondayQuantity property to a TextBox control. The Mode is set toTwoWay rather than the default OneWay binding, which means that any changes tothe TextBox are automatically moved back to the MondayQuantity property in thesource object. As a result, you don't need to access the TextBox to get thevalue entered, since the source object that was originally bound to the controlcontains up-to-date data. This takes some time to get used to, but it's nice toleverage once you know about it.

My company is currently working on an applicationthat uses a Silverlight DataGrid control with TextBox controls in each row. Asa user changes quantity or hour values, the totals need to be updated.Figure 1 shows a simplified version of the DataGrid that's available with thisarticle's sample code. Let's look at different solutions that can be used toupdate the totals values, starting with the traditional control-orientedapproach.

Figure 1: Using the DataGrid control to capture quantity and hours.

Control-Oriented Approach

One solution that can be used to update the totalsis to iterate through the target row and locate each TextBox to get the values.That's the control-oriented approach that would normally be used in ASP.NETapplications. The TextBox controls are defined in DataGrid cell templates, asshown in Figure 2.

To update TextBlock controls that track totals atthe end of each row (red area in Figure 1) as a user changes hours and quantityvalues, you can use the code shown in Figure 3. This code iterates through eachcolumn in a given DataGrid row and locates TextBox controls.

This code starts by first locating the selecteditem (the selected row) and converting it back to a TimeSheetViewRow. TheTimeSheetViewRow class (see Figure 4) is bound to each row and contains theweekly hours and quantity values displayed and captured by the DataGrid. Oncethe appropriate item is found, the code iterates through all the controls inthe row, locates TextBox controls, and uses their values to update the totals.Once all the TextBox controls are found, the last column in the DataGrid islocated and the TextBlock controls in it are updated with the hours andquantity totals.

While this technique works fine, it's definitelynot the easiest way to total each day's hours and quantity. Also, it would haveto be re-written if the TextBox controls' container changes from a StackPanelto something else. Let's look at a more flexible, data-oriented approach.

Data-Oriented Approach

Since the TextBoxes in each row all have TwoWaybindings defined (refer to Figure 2), the TimeSheetViewRow object that was originallybound is automatically updated as TextBox values change. As a result, you cangrab the selected item (which represents the bound object) from the DataGrid,then total up the property values of the TimeSheetViewRow object.

Once the totals are calculated, the appropriatequantity and hours total properties can be updated on the source object, whichautomatically updates the grid TextBlock controls bound to those properties.It's important to note that the TimeSheetViewRow class implements INotifyPropertyChanged(refer to Figure 4), so that it can notify Silverlight as property valueschange and the data can be rebound to the TextBox and TextBlock controls. Thecontrol-oriented approach shown earlier can be simplified to the followingcode, which could be called as each TextBox loses focus:

_ViewModel.UpdateRowTotals((TimeSheetViewRow)
TimeSheetDataGrid.SelectedItem);

The UpdateRowTotals method that performs the actualcalculations is located in a ViewModel class named TimeSheetViewModel. The ViewModelclass contains all the properties that are bound to the UI (called the View)that the end user sees. Think of it as the client-side container for the datathat's bound to Silverlight controls. You can bind the ViewModel class throughcode (LayoutRoot.DataContext = YourViewModelInstance) or declaratively in XAML,as Figure 5 demonstrates.

The code in Figure 5 references the ViewModelnamespace, defines the ViewModel class as a resource, binds it to the layoutroot's DataContext, and binds the DataGrid to a TimeSheetViewRows propertyexposed through the ViewModel instance. A lot can be done without actuallywriting C# or Visual Basic code.

Figure 6 shows the UpdateRowTotals method that'slocated in the TimeSheetViewModel class. Looking through the code, you'll seethat it relies on reflection to simplify the calculation process since eachproperty has a set naming convention (e.g., MondayHours, MondayQuantity,TuesdayHours, TuesdayQuantity). The code could have explicitly added all theTimeSheetViewRow properties together if desired, using MondayHours +TuesdayHours + WednesdayHours type of syntax, but the "real world"solution I'm building called for more flexibility. Notice that no controls arereferenced at all in the code.

As the HoursTotal and QuantityTotal properties areupdated at the end of the UpdateRowTotals method, the corresponding TextBlockcontrols that they're bound to in the DataGrid will be updated automatically.It's a nice way to code since you don't have to be so concerned about controlsand can focus on the actual data.

Focus on Data for Better Code

You can see that with Silverlight you can focus onworking with data as opposed to working with controls by using the built-insupport for TwoWay binding. Controls are there to present the data to the enduser and allow the user to change values. By focusing less on controls, you canoften reduce the amount of code that has to be written. It takes a littlegetting used to, especially if you're familiar with the ASP.NET control-centricapproach, but once the concept clicks, it really changes how you think aboutwriting data-oriented code.

Dan Wahlin,a Microsoft MVP for ASP.NET and XML web services, founded the XML for ASP.NETDevelopers website and The Wahlin Group (http://www.TheWahlinGroup.com), whichspecializes in .NET, Silverlight, and SharePoint consulting and trainingsolutions.

Source code accompanying this article isavailable for download.

Figure 2:Defining TextBox controls in a DataGrid cell template

HeaderStyle="{StaticResource TimeSheetDayHeaderStyle}"> Text="{Binding MondayQuantity, Mode=TwoWay}" LostFocus="TextBox_LostFocus" Style="{StaticResource TimeSheetTextBoxStyle}" /> Text="{Binding MondayHours, Mode=TwoWay}" LostFocus="TextBox_LostFocus" Style="{StaticResource TimeSheetTextBoxStyle}" />

Figure 3:Using the control-oriented approach to accessing data in controls

//Get the DataGrid's selected item

TimeSheetViewRow dataContext =(TimeSheetViewRow)TimeSheetDataGrid.SelectedItem;

decimal totalQty = 0;

decimal totalHours = 0;

//Loop through all the columns for the selected row

for (int i = 2; i { StackPanel sp =(StackPanel)TimeSheetDataGrid.Columns[i].GetCellContent(dataContext); foreach (UIElement elemin sp.Children) { //Find the TextBoxcontrols if (elem isTextBox) { TextBox tb =(TextBox)elem; if (!String.IsNullOrEmpty(tb.Text)) { decimal val= decimal.Parse(tb.Text); if(tb.Tag.ToString() == "Quantity") totalQty += val; if(tb.Tag.ToString() == "Hours") totalHours += val; } } }}//Find totals TextBlocks and update themStackPanel totalsSP =(StackPanel)TimeSheetDataGrid.Columns[TimeSheetDataGrid.Columns.Last() .DisplayIndex].GetCellContent(dataContext);((TextBlock)totalsSP.FindName("TotalQuantityTextBlock")).Text= totalQty.ToString();((TextBlock)totalsSP.FindName("TotalHoursTextBlock")).Text= totalHours.ToString();Figure 4: TheTimeSheetViewRow class bound to each row of the DataGridpublic partial class TimeSheetViewRow : INotifyPropertyChanged{ private decimalHoursTotalField; private decimalQuantityTotalField; private decimal?FridayHoursField; private decimal?FridayQuantityField; private decimal?MondayHoursField; private decimal?MondayQuantityField; private decimal?SaturdayHoursField; private decimal?SaturdayQuantityField; private decimal?SundayHoursField; private decimal?SundayQuantityField; private decimal?ThursdayHoursField; private decimal?ThursdayQuantityField; private decimal?TuesdayHoursField; private decimal?TuesdayQuantityField; private decimal?WednesdayHoursField; private decimal?WednesdayQuantityField; public decimal?MondayHours { get { returnthis.MondayHoursField; } set { if((this.MondayHoursField.Equals(value) != true)) { this.MondayHoursField = value; this.RaisePropertyChanged("MondayHours"); } } } public decimal?MondayQuantity { get { returnthis.MondayQuantityField; } set { if((this.MondayQuantityField.Equals(value) != true)) { this.MondayQuantityField = value; this.RaisePropertyChanged("MondayQuantity"); } } } //Other Propertiesommitted for the sake of brevity public eventPropertyChangedEventHandler PropertyChanged; protected voidRaisePropertyChanged(string propertyName) { PropertyChangedEventHandler propertyChanged = this.PropertyChanged; if((propertyChanged != null)) { propertyChanged(this, newPropertyChangedEventArgs(propertyName)); } }}Figure 5:Declaratively binding a ViewModel class to a view using XAML xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:data="clr-namespace:System.Windows.Controls; assembly=System.Windows.Controls.Data" xmlns:viewModel="clr-namespace:SilverlightDataBindingOptions.ViewModel"> ItemsSource="{Binding TimeSheetView.TimeSheetViewRows}"> Figure 6: Adata-centric approach to updating the totals in the DataGridpublic void UpdateRowTotals(TimeSheetViewRow row){ decimal qty = 0M; decimal hours = 0M; Type t =typeof(TimeSheetViewRow); //Iterate throughMonday Sunday property values foreach (PropertyInfoprop in t.GetProperties()) { object val =prop.GetValue(row, null); decimal decimalVal= (val==null) ?0.00M:decimal.Parse(val.ToString()); if(prop.Name.EndsWith("Hours")) hours += decimalVal; if(prop.Name.EndsWith("Quantity")) qty += decimalVal; } row.HoursTotal = hours; row.QuantityTotal =qty;}

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like