Ask Kathleen

Mastering Silverlight

Silverlight offers impressive talents and more than a few frustrations. From customizing your application UI to battling obscure bugs and error messages, this column helps with the transition to Silverlight tools.

Q: I'm trying to build a Silverlight application for my business, but many of the samples I find are either complicated or not relevant to a business application. We want the user to select a type of item to open, display a search screen and then edit the data, which may be a parent-child record. I see tools demoed that seem to remove a lot of development time, but I don't understand how to use these pieces together. Are these tools appropriate for real applications or are they just "demo ware?"

A: Over the years plenty of demo ware has been shown, but many pieces of the current Silverlight toolset can save considerable time. Part of the trick is building in enough flexibility so that you can use automated development techniques when and where they are appropriate. For example, many of your screens may be automatic during early development, but as your application nears release a number of screens may be customized. I'll show you how to combine navigation, child windows and the data form to create a useful UI, including a master/detail data display. This UI doesn't require any custom code, but you can add custom user controls when the default layout doesn't work. You or an artist can adjust the appearance because these controls are well designed for styling.

In order to focus on intelligent combinations of custom and automatic UI, I'll skip the data pipeline and assume a simple data object and a local service that manages operations on the data. I'll also skip menu creation and commanding. I'll show a navigation application with a hyperlink to access the edit page. When the user selects the hyperlink, a pseudo-modal search dialog window appears to allow future data filtering before the edit page fully displays. Both the search and edit displays are based on the DataForm control. The DataForm control was designed for use with RIA Services, but the example illustrates using it with another (mocked) pipeline. Peter Vogel provides an introduction to the DataForm in an article from last spring ("Silverlight 3 Enables Data-Driven App Dev," May 2009).

I'll also use the Managed Extensibility Framework (MEF) to provide flexibility in the resulting application (for more on MEF, see the "Working with MEF" AskKathleen column, April 2009). By composing key parts of the application, such as data layouts, you can use a generalized solution, except where custom behavior is required. This example was created in Visual Studio 2008 with Silverlight 3, the November release of the Silverlight Toolkit, and MEF Preview 8 (downloaded after the Nov. 11 bug fix).

I began by creating a Silverlight Navigation Application, which is available as a project type after the Silverlight Toolkit is installed. This creates a page with a navigation Frame you can use to move around in your application. If you're working within the browser, this control gives better interactivity. While this page looks pretty good out of the box, the real value is using it as a master page. You can add links, a treeview menu and other controls to manipulate the content area. These links will appear as browser navigation and support browser behavior like forward and back. You can also style the master page for a unique application appearance.

Adding a link to the page lets the user initiate navigation:

<HyperlinkButton  Style="{StaticResource LinkStyle}"
  NavigateUri="/Invoice" TargetName="ContentFrame" Content="invoice"/>

The TargetName points to a Silverlight Frame that manages URI navigation, including mapping. Mapping avoids hardcoded physical locations appearing throughout your application source code and displays abstract, rather than physical, URIs to the end user:

<navigation:Frame.UriMapper>
	<uriMapper:UriMapper>
      	<uriMapper:UriMapping Uri="" 
		MappedUri="/Views/Home.xaml"/>
      	<uriMapper:UriMapping Uri="/Invoice" 
MappedUri="/Views/GeneralForm.xaml?biz=Invoice"/>
          <uriMapper:UriMapping Uri="/{pageName}"
MappedUri="/Views/{pageName}.xaml"/>
          </uriMapper:UriMapper>
      </navigation:Frame.UriMapper>

The first entry maps the default content, the second explicit content, and the third all other content.

Once the user selects the hyperlink, navigation to the page is automatic. The page is a Silverlight Navigation Page created through the Add New Item dialog. The query parameter is available in the NavigateTo method of the target page:

   Protected Overrides Sub OnNavigatedTo(ByVal e As NavigationEventArgs)
      Dim argName = "biz"
      If NavigationContext.QueryString.ContainsKey(argName) Then
         _bizClassName = NavigationContext.QueryString(argName)

The target type from the biz argument can be used with a little bit of MEF to select a search window. A MEF factory can activate a custom search window if one is available, and otherwise use a default search. Whether default or custom, the search window needs to implement a custom interface (ISearchForm) and be a ChildWindow to allow the modal display. The search window is stored twice, in a field of each type:

_searchForm = _searchFactory. _
	GetItem(_bizClassName)
_searchChild = TryCast( _
	_searchForm, ChildWindow)
If _searchChild Is Nothing Then
    Throw New InvalidOperationException( _
	"Must be child window")
End If

Silverlight differs from WinForms because it doesn't fully block the thread when a modal dialog is displayed. This requires the dialog to follow an asynchronous pattern and fire an event when the user closes the dialog:

AddHandler _searchChild.Closed, _
  AddressOf Search_Closed
         _searchForm.Show(_bizClassName)
      End If
   End Sub

When the search window is closed, control returns to the Search_Closed event handler. This method checks the dialog result to determine how the user closed the window. If the user didn't cancel, another MEF factory attempts to find a custom edit user control. If a custom user control is found, it's displayed filling the content area of this page:

      If _searchChild.DialogResult Then
         Dim edit = TryCast(_editFactory.GetItem( _
	   _bizClassName), UserControl)
         If edit IsNot Nothing Then
            Me.dataContentBorder.Child = edit
         Else
            Me.dataContentBorder.Child = GetDataForm()
         End If
      Else
         Me.NavigationService.GoBack()
      End If

If a custom edit user control is not available, the GetDataForm method creates a standard DataForm user control and sets an event handler for the AutoGeneratingField event. An MEF factory creates the service to retrieve the data:

  Private Function GetDataForm() As UIElement
      Dim df = New DataForm()
      df.SetBinding(DataForm.ItemsSourceProperty, _
		New Data.Binding())
 AddHandler df.AutoGeneratingField, _ 
    AddressOf DataForm_AutoGeneratingField
 _bizService = _bizServiceFactory.GetItem( _
    _bizClassName)
 Dim items = _bizService.RetrieveList( _
    _searchForm.Filter)
 Me.DataContext = items
Return df
End Function 

That's almost the whole story. Figure 1 shows the data display at this point. Note that the data form inferred the correct user control for the Boolean and date types. However, the primary key is editable, the MoreInfo property should not be displayed to the user, and the line items don't appear. One way to fix these problems is to create a custom page, but fixes like these are likely to be needed by every entity type, negating the value of automated tools.


[Click on image for larger view.]
Figure 1. Hooking up a navigation application with a single link opening a general use data form creates a nice visual, but doesn't allow authorization or support detail in this master/detail display. You can improve the DataForm's automated display by applying attributes to your data model (the class holding your data), or by intercepting events.

Another approach is to add attributes to the data class. This works well for static customizations, such as blocking primary key editing. But attributes supply the same static information for all uses and are not useful for user specific authorization.

Catching the AutoGeneratingField event allows customization in response to the runtime environment such as canceling the creation of display fields. If your data service is designed to provide information such as authorization, you can request it to tune the display, in this case removing a field:

Private Sub DataForm_AutoGeneratingField( _
  ByVal sender As System.Object, 
  ByVal e As 
DataFormAutoGeneratingFieldEventArgs)
 If Not _bizService.CanReadProperty( _ 
       e.PropertyName) Then
      e.Cancel = True
      Return
End If

Intercepting display field generation allows other customizations, such as captions, descriptions and specific styling. You can also specify the type of control used for the display, such as supplying a DataGrid to display the list of invoice line items:

If IsEnumerableNotString(e.PropertyType) Then
         Dim dg As New DataGrid()
         dg.SetBinding(DataGrid.ItemsSourceProperty, _
 		 New Data.Binding(e.PropertyName))
         AddHandler dg.AutoGeneratingColumn, _ 
		  AddressOf DataGrid_AutoGeneratingColumn
         e.Field.Content = dg
      End If

Because a string is an enumerable, the IsEnumerableNotString function recognizes IEnumerable properties that are not strings to isolate that ugly code.

Similar handling of the AutoGeneratingColumn event can intercept DataGrid columns to fine-tune the child grid display. The challenge is getting the right service for the detail record, which is a different service than the master record. You can do this with a little bit of reflection to retrieve the target type name, followed by a call to the same factory that retrieved the master record's service:

	Dim targetTypeName = dg.ItemsSource. _
		GetType.GetGenericArguments(0).Name
 	Dim service = _bizServiceFactory. _
		GetItem(targetTypeName)

Figure 2 shows the data form after these adjustments.


[Click on image for larger view.]
Figure 2. The AutoGeneratingColumn event lets you intercept field creation and adjust many aspects of each in response to the runtime environment and in partnership with entity-specific code.

This is a good moment for the pitchman's call of: "Wait, wait ... there's more." The DataForm is part of the RIA Services support, and RIA Services rely on metadata provided in attributes on the data class. You can use attributes to set validation, display, read-only mode and other information:

  <Required()> _
   <StringLength(50)> _
   	<Display(Description:="The customer name",
 	   Name:="Name")> _
   Public Property CustomerName() As String

A rich array of attributes exists in the System.ComponentModel.DataAnnotations namespace. Required, Range, StringLength and RegularExpressions support automatic validation. Name provides the text for the Label. Order defines the order of display fields. If a Description is provided, in attributes or the AutoGeneratingField event, an information icon appears. When the user mouses over this, the description appears.

In addition to styling, a number of properties on the DataForm control aspects of the display, such as the location of the caption. Automatic creation of all aspects of your application probably won't be appropriate at release, but it jump-starts your development and lets you focus on customizations that provide real business benefit. With proper styling, your custom and automated screens will appear as an integrated whole giving you the best of both worlds, so you can focus on tasks that truly need your attention while retaining full flexibility.

Q: I have problem with the async behavior in Silverlight. All my WinForm applications are written in sync mode behavior, my conceptual model (logical model) is component-based and my Model View Presenter (MVP) works with data transfer object (DTO), which is generated by related components (logical model). With the aid of Windows Communication Foundation (WCF) I thought it would be very easy to force my components to act just like a Web service, just by adding some attributes. But the new programming model offered by Silverlight is in the async mode. Is there a way to use a synchronous model in Silverlight?

A: Synchronous communication happens when you call a function (a method that returns a value) and get a result. Async communication happens when you call a subroutine (a method that does not return a value) and either pass a callback delegate or hook to an event. The result appears in the event argument or callback method parameters. Silverlight requires that you use async communications when accessing a service. This avoids blocking your app for a potentially long operation.

The short answer to your question is no. You can't use synchronous communications from Silverlight, and you probably wouldn't want to. Silverlight mimics a browser, and a browser is async. Even if your Silverlight application runs out of the browser, it anticipates communications over an Internet style connection.

While communications on the Silverlight side are async, once you arrive at the server, further communications are generally synchronous. Many services are function calls and therefore synchronous in their own behavior, even though they are interacting with WCF, which is responding to the Silverlight client in an async manner.

An async pipeline can become a synchronous pipeline, but a synchronous pipeline cannot become an async pipeline. All pipelines that return a value are eventually synchronous because something returns a value. To preserve isolation, each step of the async pipeline communicates only with its neighbor and thus each step of the pipeline has its own callback method or event.

This information is relevant to your question because significant parts of your application are not forced into async mode by Silverlight. Server and client code that isn't related to communications can remain unchanged. For many applications, changing to sync communications is challenging because the same classes perform communications activities and non-communications activities.

Splitting these apart is likely to be one part of your solution. This additionally allows you to reuse code on Silverlight and your server by selecting "Add Existing Item," browsing to the item and then selecting "Add as Link" from the drop down list on the "Add" button.

Q: I'm receiving the following error when I try to compile a Silverlight application:

Unable to write to output file 
'C:\Users\...\Common\obj\Debug\Common.pdb': 
Unspecified error 	Common

It used to work and then suddenly I can't compile. I can manually delete the file, but when I recompile, both the file and the error reappear. Do you know what I should do to try to fix this?

A: There are actually quite a few reasons this error can occur; these reasons include not having proper directory rights or otherwise not having access to the file. In ASP.NET this can happen because of rights issues with the special log-on IDs. Because you can delete the file manually-and because it started suddenly when you made no change to machine rights-I think rights issues are unlikely.

Like many odd errors, rebooting Visual Studio or you machine sometimes works. It seems likely that you have just received my nomination for getting one of the worst error message of all time.

Check that you don't have a missing source code file, including a linked file you created with "Add Existing Item"/"Add as Link." Yes, that's right: If a file in the project is missing, Visual Studio complains about its capacity to overwrite the project's .PDB file. This problem has little to do with the .PDB file-although it probably occurs because the compiler ignores the missing file-but the tool creating debug information in the .PDB file does not.


[Click on image for larger view.]
Figure 3. A pipeline from a user interface element to the WCF service in Silverlight is async (as shown by the circular arrows) because the WCF service is async. Once the request arrives at the server, further calls are generally synchronous (as shown by the double headed arrow).

Look for a yellow warning symbol next to a file in Solution Explorer. This error will appear when a file is missing. You can fix the problem by requesting that the file be deleted or removed. It may feel strange to delete or remove a file that both you and Visual Studio know doesn't exist, but it this action will get you back in business.

This problem is more common in Silverlight, because you're likely to link files to share source code between .NET and Silverlight aspects of the application. If you rename the file where it actually resides, Visual Studio doesn't propagate the change and will break the link.

This problem is also more likely to waste a lot of time in projects organized into subdirectories because you probably wouldn't build while staring at a warning icon.

There are some reports of this situation occurring in relation to source code issues. This could be actual source code issues, or it could be source code issues that result in the directory and project file being out of date, which causes the .PDB generation to look for non-existent files.

Q: I'd like to copy parts of a dialog so I can reference it after my application closes, but it doesn't let me highlight and copy part of the text. Is there a way to do this?

A: If the dialog is an exception that occurs while you're debugging, you can "Copy to Clipboard" in the exception popup Visual Studio displays at the source code break point. For other types of dialogs, your best bet is Alt/PrintScreen and possibly pasting the image into Paint to crop, save or just display until you are done with it.

Q: I work with VB. Sometimes when I'm working with MEF, it doesn't do what I expect. When I rebuild the solution it starts working. Why is this?

A: When you initiate debugging, the compiler builds the projects it thinks it will be using. Because there are no hard references to your MEF assemblies, Visual Studio thinks you don't need some of your projects and they're not being built to disk. The background compiler does the full compile, except writing the results to disk, so you have the full set of errors giving the illusion of a full compile. VB programmers unaccustomed to working with MEF and XAML rarely do an explicit build because it's not necessary. When using MEF or XAML it's sometimes necessary to explicitly select Build Solution.

comments powered by Disqus

Featured

Subscribe on YouTube