Summary lists are generally used as a means to find and drill down on a record to get more details and/or edit the record. There are a number of approaches you can take to implement this type of behavior, including the following”
DataGrid
to display the detailsWhich approach you choose really depends on the workflow you are implementing. Each has its own pros and cons, and your choice will largely depend upon the user experience design for your application. In this section, we will take a look at each of these approaches and how to implement them.
One simple way of displaying the details of a given item is to simply create a details view, and navigate to it using the Navigation Framework. How you can initiate this behavior isn't particularly straightforward, however. You have probably implemented this type of behavior in the past by having the user double-click an item in the list, but there are no DoubleClick
events in Silverlight. In fact, there's no RowClick
event on the DataGrid
either, nor is there an ItemClick
event on the ListBox
. So how can you enable the user to drill down on a record? Let's take a look at some approaches you might like to take.
Note If you still want to use a double-click behavior, you can implement it yourself using a behavior or a trigger. You can also find various double-click behaviors and triggers already created by other developers with a quick search on the Web. Behaviors are covered in Chapter 10.
If you are using a DataGrid
, then you can use a template column and add a control such as a Button
or HyperlinkButton
to it that the user can click to navigate to the details view. Alternatively, if you are using a ListBox
, then you can simply include one of these controls in your item template.
Unfortunately, the Navigation Framework doesn't allow you to pass complex objects between views. Therefore, in order to enable the details view to know which record it should load and display, you will need to pass a unique identifier to that view as a query string parameter. You could use the HyperlinkButton
control, which can be configured to automatically navigate to the details view with no code required; however, it unfortunately does not have the ability to automatically include the unique identifier of the record as a parameter in the URI to navigate to the details view. Your options in this case are to either handle the HyperlinkButton
's Click
event in the code-behind and build the URI to navigate to manually prior to initiating the navigation, or to bind the NavigateUri
property of the HyperlinkButton
to the property representing the unique identifier on the bound object and use a value converter to build the full URI. The value converter approach is probably the most appropriate choice, especially if you are using the MVVM design pattern; however, it does not apply if you are using a Button
control, because the Button
control has no NavigateUri
property. Therefore, we'll focus on the code-behind approach here instead, which you can use with either a HyperlinkButton
control or a Button
control.
The following XAML demonstrates the definition of a HyperlinkButton
control, which can be added to the cell template of a DataGrid
template column or to a ListBox
item template, that displays the name of the product as its text, has a Click
event handler, and binds the whole ProductSummary
object to its Tag
property, which it does by assigning {Binding}
to its property value, as follows:
<HyperlinkButton Content="{Binding Name}" Tag="{Binding}"
Click="NameButton_Click" />
Now, in the Click
event handler for the HyperlinkButton
, we can get the HyperlinkButton
that raised the event, which is passed into the event handler as the sender
parameter, and then get the ProductSummary
object corresponding to its summary list item/row from its Tag
property. The structure of this URI will depend on how your URI mapping has been configured. (See Chapter 3 for more information on URI mapping.) The following example demonstrates building a URI that navigates to the product details view, and passes it the ID of the product to display, which it can obtain from the query string and retrieve the corresponding details from the server for.
private void NameButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
HyperlinkButton button = sender as HyperlinkButton;
ProductSummary productSummary = button.Tag as ProductSummary;
Uri detailsUri = new Uri("ProductDetails/" + productSummary.ID.ToString(),
UriKind.Relative);
NavigationService.Navigate(detailsUri);
}
Note You should also handle the KeyDown
event of the control to capture the Enter key being pressed, and navigating to the details view corresponding to the selected item/row. This helps enable users to use the keyboard to navigate around the application instead of having to switch between using the keyboard and the mouse.
Silverlight has a neat class built in called ChildWindow
that enables you to display a modal pop-up window with content that you define. This makes it perfect for use in scenarios in which you want the user to be able to select a record in a summary list and display the related details in a pop-up window.
To implement a child window, add a new item to your project and select the Silverlight Child Window item template from the dialog. This will create a new XAML file (and corresponding code-behind class) inheriting from the ChildWindow
class. As shown in Figure 6-11, you have a nice window-type layout that you can style to suit your user interface design if necessary, already set up with OK/Cancel buttons, which you can add your own control layout to so that the user can view and edit the details of the selected record in the summary list.
After you have defined the layout of the child window, you need to be able to display it when the user clicks a record in the summary list. Using the approaches described in the previous section, instead of navigating to a new view you can simply handle the Click
event of the hyperlink or button defined for an item/row and display the child window. All that is required to display a child window is to instantiate the class and call the Show
method, like so:
ProductDetailsWindow window = new ProductDetailsWindow();
window.Show();
Note While the child window is displayed modally, unlike displaying modal windows in Windows Forms or WPF, the method returns immediately, instead of waiting for the window to be closed. If you need to perform an action, such as updating the summary list, after the window is closed, you will need to handle either the Closing
or Closed
event of the child window and put your logic to do so in there.
The modality of the child window is provided by applying a semitransparent Rectangle
control overlaying the application's user interface, positioning the child window control on top of this, and disabling the root visual. Therefore, any mouse clicks outside the child window will be ignored, effectively providing a modal behavior.
The source code for the ChildWindow
class can be found in the Silverlight Toolkit, so you can modify this modal behavior by modifying the source code if you wish. With slight modifications to the code, you can enable multiple child windows to be displayed simultaneously. Microsoft Silverlight program manager Tim Heuer has actually already done this with a control called the FloatableWindow
, which includes additional behavior such as the ability for it to be resized by the user. It is available on CodePlex at http://floatablewindow.codeplex.com
.
However, in this scenario, where we want to show the details of the selected record in the summary list, we need to pass in an identifier or an object so that it knows what data to load and display. Unlike the previous approach to navigating to a new view where we could only pass a simple string to the new view to tell it which record to load, we can actually pass complex objects into a child window by modifying the constructor of the class to accept a simple type or object with the required details as a parameter. For example, change the constructor, as follows:
public ProductDetailsWindow(ProductSummary productSummary)
{
InitializeComponent();
// Logic to load product data corresponding to
// the passed-in productSummary object can go here
}
You can now pass it the selected object in the summary list when instantiating the window, like so:
HyperlinkButton button = sender as HyperlinkButton;
ProductSummary productSummary = button.Tag as ProductSummary;
ProductDetailsWindow window = new ProductDetailsWindow(productSummary);
window.Show();
Figure 6-12 demonstrates displaying the details of a product in a child window.
The row details feature in the DataGrid
enables you to display additional data related to a row in the DataGrid
—useful when you only want to show limited details from the bound object in the main row, and display additional details in an area below it, as demonstrated in Figure 6-13. This is especially useful when you want to show additional details about a row without displaying those details in a pop-up window or navigating away from the summary list view.
This is achieved by assigning a data template defining the layout of the row details area to the RowDetailsTemplate
property of the DataGrid
. For example:
<sdk:DataGrid>
<sdk:DataGrid.RowDetailsTemplate>
<DataTemplate>
<!-- Row details layout goes here -->
</DataTemplate>
</sdk:DataGrid.RowDetailsTemplate>
</sdk:DataGrid>
You can assign a value to the RowDetailsVisibilityMode
property to specify whether the row details are displayed only when the row is selected (VisibleWhenSelected
), displayed for every row in the DataGrid
(Visible
), or never displayed (Collapsed
). You can use the RowDetailsVisibilityChanged
event to handle when the row details are shown/hidden, enabling you to implement behaviors such as loading related data from the server into the row details when the row is selected and the row details section is displayed.
By default, if the DataGrid
has columns extending beyond the width of the DataGrid
, its horizontal scrolling functionality will also allow horizontal scrolling of the row details area. However, you can freeze the row details so that they remain fixed in position regardless of any horizontal scrolling motion by setting the AreRowDetailsFrozen
property to True
.
The master/details view is a relatively common user interface pattern in which the summary list (acting as the master) occupies one part of the view, and when the user selects an item from the list, the complete record (acting as the details) are displayed in another part of the view for editing. For example, you might have a list of products (i.e., the master), and when the user selects a product from this list, details of the selected product will be displayed in a data entry form in another part of the view, where they can be edited.
This is actually quite easy to implement, thanks to the synchronization behavior provided by collection views, with no code actually required. Let's say you are using a DomainDataSource
control in your view that retrieves the list of products and displays them in a list, using either a DataGrid
or a ListBox
bound to the DomainDataSource
control. We'll simply bind a TextBox
in a manner to display the name of the selected product in the list and enable you to edit it.
TextBlock
control to your view and bind its DataContext
property to the DomainDataSource
's Data
property (as the DataGrid
/ListBox
does).Text
property to the Name
property of the bound object, like so:
<TextBlock DataContext="{Binding ElementName=productSummaryDDS, Path=Data}"
Text="{Binding Name}" />
Now run your project. When you select a product in the list, note how the text in the TextBlock
automatically changes to display the corresponding name of that product. Considering that the TextBlock
isn't even bound to the list, how does it know what item has been selected? This is because the DomainDataSource
control returns a DomainDataSourceView
from its Data
property, which the TextBox
control is binding to. In addition to filtering/sorting/grouping functions, which are enabled without the underlying collection needing to be modified, collection views also implement current record management functions, including properties such as CurrentItem
and CurrentPosition
, and methods such as MoveCurrentToNext
and MoveCurrentToPosition
. When a property on a control that accepts a value (rather than a collection) is bound to a collection view, the data binding engine will recognize that the binding source is a collection view and will assume that you don't actually want to bind to the collection view itself. It, therefore, drills down to the collection view's CurrentItem
property and binds to the given property on that instead.
Note Optionally, you can use "{Binding CurrentItem.Name}"
as the binding expression instead, with exactly the same results, but explicitly drilling down to the CurrentItem
property on the collection view isn't necessary unless you have a property on the object you want to bind to that has the same name as a property on the collection view object, such as Count
.
When you click on a new product in the list, the list will automatically update the CurrentItem
property on the collection view that it's bound to and set it to that of the selected item. In other words, by default, list controls automatically synchronize the CurrentItem
property of the collection view to which they are bound with their selected item. When the CurrentItem
property of the collection view is changed, any other controls in the view that are bound to it will have their binding source changed accordingly. The effect of this behavior is that those controls will display the details of the currently selected item in the list, effectively providing a master/details type behavior that your application can leverage.
Note Although most of the time you will want this synchronization behavior, sometimes you might want to have more control over the process. You will find that some of these controls, such as the ListBox
control, have an IsSynchronizedWithCurrentItem
property whose default value is null. You can't actually set this property to true (it will throw an exception if you try), as it depends on whether or not the binding source is a collection view for whether synchronization with the current item is possible. If the binding source is a collection view, current item synchronization will automatically be turned on, although you can explicitly turn it off if you want by setting the property to false. If you do so, you can control what item is the current item in the collection view manually (in code), using the record navigation methods (such as MoveCurrentToNext
and MoveCurrentToPosition
).