Item Selection

User interfaces often display a list of items to a user in a control such as a ListBox and allow the user to select one of the items. Once he makes the selection, the user may be brought to another screen, another control may appear with detailed information about the selected item, or the selected item’s details may appear in a series of controls below the ListBox. Generally, this is a good design practice, as it is often impossible to effectively display all of the information about an item in a list-based control. For example, if an entity such as a Product has more than 50 properties, displaying all of those properties in a list-based control would be problematic and likely would not be very user-friendly. Though with Silverlight 2 and XAML, you can alleviate this somewhat by creating a DataTemplate that contains several rows of information within each row of the ListBox. However, even this solution has its limits.

One possible solution to this type of problem is to display a list of items that contains a summary of the items in a control such as a ListBox. Then when the user selects an item from the ListBox, the details of all of the properties for the Product are displayed in a series of controls. The code in Example 4-7 shows how to implement this type of scenario.

Example 4-7. Setting the DataContext for the selected product

C#
private void lstProducts_SelectionChanged(object sender,
        SelectionChangedEventArgs e)
{
    Product product = (Product) lstProducts.SelectedItem;
    ProductDetailsLayout.DataContext = product;
}
VB
Private Sub lstProducts_SelectionChanged(ByVal sender As Object, _
        ByVal e As SelectionChangedEventArgs)
    Dim product As Product = CType(lstProducts.SelectedItem, Product)
    ProductDetailsLayout.DataContext = product
End Sub

When a user selects an item from a ListBox, the SelectionChanged event is raised. You can write an event handler for this event that retrieves the selected item and sets the DataContext of a series of detailed controls that are bound to a DataContext that is expecting a product instance. For example, the following code shows the lstProducts_SelectionChanged event handler getting a reference to the lstProducts ListBox’s SelectedItem property. The SelectedItem contains either null or a Product instance. The Product instance is then set to the DataContext of a Grid layout control named ProductDetailsLayout.

Example 4-8 shows the XAML that contains the ListBox and the details controls that will present the Product information. The details controls are contained within the Grid layout control named ProductsDetailsLayout, shown in bold in Example 4-8. When the lstProducts_SelectionChanged event handler executes, it sets the DataContext for this Grid control to the selected product from the lstProducts ListBox. The controls contained within the ProductDetailsLayout Grid control inherit this DataContext, as none of their DataContext properties are set. This allows each of them to bind to the selected item in the ListBox and have the values displayed whenever a user selects a different item.

Example 4-8. XAML for product ListBox and details controls

<Grid x:Name="LayoutRoot" Margin="10,10,10,10">
    <Rectangle Fill="#FFDEE6FB" Grid.Column="0" Grid.Row="0"
        Grid.RowSpan="2" RadiusX="20" RadiusY="20" Opacity="0.8"
        StrokeThickness="0" />
    <StackPanel Grid.Column="0" Grid.Row="0">
        <ListBox x:Name="lstProducts" Height="88" HorizontalAlignment="Left"
            VerticalAlignment="Bottom" Margin="20,20,20,5" Width="440"
            Style="{StaticResource ListBoxStyle}"
            ItemTemplate="{StaticResource ProductTemplate}"
            ItemsSource="{Binding}"/>
        <StackPanel Orientation="Horizontal" x:Name="buttonPanel"
            HorizontalAlignment="Right" Margin="10,0,10,0" >
            <Button x:Name="btnAddProduct" Content="Add Product" Margin="10,5,10,5"
            HorizontalAlignment="Right" Height="30" Width="120"
                Style="{StaticResource ButtonStyle}"/>
            <Button x:Name="btnRemoveProduct" Content="Remove Product"
                Height="30" Width="120" Style="{StaticResource ButtonStyle}"/>
        </StackPanel>
        <Grid Margin="10,10,10,10" x:Name="ProductDetailsLayout">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="123"/>
                <ColumnDefinition Width="Auto" MinWidth="337"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
            </Grid.RowDefinitions>
            <TextBlock Text="Product Id" Grid.Row="0" Grid.Column="0"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>
            <TextBlock Text="Product Name" Grid.Row="1" Grid.Column="0"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>
            <TextBlock Text="Qty Per Unit" Grid.Row="2" Grid.Column="0"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>
            <TextBlock Text="Units in Stock" Grid.Row="3" Grid.Column="0"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>
            <TextBlock Text="Units on Order" Grid.Row="4" Grid.Column="0"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>
            <TextBlock Text="Reorder Level" Grid.Column="0"  Grid.Row="5"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>
            <TextBlock Text="Discontinued" Grid.Row="6" Grid.Column="0"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>
            <TextBlock Text="Discontinued Date" Grid.Row="7" Grid.Column="0"
            Margin="10,5,10,5" Style="{StaticResource TextBlockCaptionStyle}"/>

            <TextBlock Text="{Binding Path=ProductId, Mode=OneTime}" Grid.Row="0"
            Grid.Column="1" Margin="11,5,10,5"
            Style="{StaticResource TextBlockReadOnlyStyle}"
            x:Name="lbProductId" HorizontalAlignment="Left"/>
            <TextBox x:Name="tbProductName" Grid.Column="1" Grid.Row="1"
            Margin="10,5,10,5" HorizontalAlignment="Left"
            Height="30"   Width="240"
              Style="{StaticResource TextBoxStyle}"
            Text="{Binding Mode=TwoWay, Path=ProductName}"/>
            <TextBox x:Name="tbQtyPerUnit" Grid.Column="1" Grid.Row="2"
            Margin="10,5,10,5" HorizontalAlignment="Left"
            Height="30" Width="240"
              Style="{StaticResource TextBoxStyle}"
            Text="{Binding Mode=TwoWay, Path=QuantityPerUnit}"/>
            <StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
                <Slider HorizontalAlignment="Left" Margin="10,5,10,5"
                x:Name="sliUnitsInStock" VerticalAlignment="Stretch"
                LargeChange="10" Maximum="150" SmallChange="1" Width="240"
                   Value="{Binding Mode=TwoWay, Path=UnitsInStock}"
                Style="{StaticResource SliderStyle}" />
                <TextBlock Text="{Binding Path=UnitsInStock, Mode=OneWay}"
                Margin="5,5,10,5"
                Style="{StaticResource TextBlockReadOnlyStyle}"
                x:Name="lbUnitsInStock" HorizontalAlignment="Left"/>
            </StackPanel>
            <StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal">
                <Slider HorizontalAlignment="Left" Margin="10,5,10,5"
                x:Name="sliUnitsOnOrder" VerticalAlignment="Stretch"
                LargeChange="10" Maximum="150" SmallChange="1" Width="240"
                Value="{Binding Mode=TwoWay, Path=UnitsOnOrder}"
                Style="{StaticResource SliderStyle}" />
                <TextBlock Text="{Binding Path=UnitsOnOrder, Mode=OneWay}"
                Margin="5,5,10,5"
                Style="{StaticResource TextBlockReadOnlyStyle}"
                x:Name="lbUnitsOnOrder" HorizontalAlignment="Left"/>
            </StackPanel>
            <StackPanel Grid.Row="5" Grid.Column="1" Orientation="Horizontal">
                <Slider Margin="10,5,10,5" HorizontalAlignment="Left"
                Style="{StaticResource SliderStyle}"
                Value="{Binding Mode=TwoWay, Path=ReorderLevel}"
                VerticalAlignment="Stretch" x:Name="sliReorderLevel"
                LargeChange="10" Maximum="150" SmallChange="1" Width="240" />
                <TextBlock Text="{Binding Path=ReorderLevel, Mode=OneWay}"
                Margin="5,5,10,5"
                Style="{StaticResource TextBlockReadOnlyStyle}"
                x:Name="lbReorderLevel" HorizontalAlignment="Left"/>
            </StackPanel>
            <CheckBox x:Name="cbDiscontinued" Grid.Column="1" Grid.Row="6"
            Margin="10,5,10,5" HorizontalAlignment="Left" Height="30" Width="240"
              Style="{StaticResource CheckBoxStyle}"
            IsChecked="{Binding Mode=TwoWay, Path=Discontinued}"/>
            <TextBlock x:Name="lbDiscontinuedDate" Grid.Column="1" Grid.Row="7"
            Margin="10,5,10,5" HorizontalAlignment="Left" Height="13" Width="240"
              Style="{StaticResource TextBlockReadOnlyStyle}"
            Text="{Binding Mode=OneWay, Path=DiscontinuedDate}"
            VerticalAlignment="Center" />
        </Grid>
        <StackPanel Orientation="Horizontal" x:Name="bottomButtonPanel"
            Height="40" HorizontalAlignment="Right" Margin="10,0,10,0" >
            <Button x:Name="btnOK" Content="OK" Height="30" Width="120"
            Style="{StaticResource ButtonStyle}"/>
        </StackPanel>
    </StackPanel>
</Grid>

Using Different Binding Modes

Each details control in Example 4-8 uses a different binding mode. The ProductId property is the identifier for the Product entity and it will not be changed. This property value is displayed in a TextBlock, because it is read-only and the binding mode is set to OneTime. OneTime tells the target property that it need not listen for change notifications on this property, as the property will never change. Therefore, the only time this TextBlock’s value will be updated is when the DataContext is set.

The ProductName property is bound to a TextBox target control, because the user may decide to change this value. The binding mode is set to TwoWay for this and many other controls. TwoWay allows the user to change the value in the target control and have the new value sent automatically to the bound data source. It also tells the target control to listen for property change notifications so that it can update the contents of the TextBox if this event is raised for its bound property.

The UnitsInStock, ReorderLevel, and UnitsOnOrder properties are bound to two different controls. Each property is bound to a Slider and to a TextBlock control. The Slider is bound using the TwoWay binding mode to the corresponding property so that when the user moves the Slider left or right, the value is sent automatically back to the bound data source. The Slider control does not display a value, however, so a TextBlock has been placed to the right of each Slider control to display the Slider’s value. In Example 4-8, this is implemented entirely through declarative code using XAML. The TextBlock’s Text property and the Slider’s Value property are both bound to the same property of the Product data source. The Slider’s binding uses the TwoWay binding mode and the TextBlock uses the OneWay binding mode. The TextBlock cannot be updated, so it need not use the TwoWay binding mode. The TextBlock must listen for PropertyChanged events so that it knows when to display the updated value.

For example, when the user slides the Slider named sliReorderLevel (shown in bold in the middle of Example 4-8) to the right, increasing the value to 50, that value is sent to the Product instance, which calls the setter code for the ReorderLevel property, which raises the PropertyChanged event, which notifies anyone listening (in this case, the TextBlock named lbReorderLevel) and updates the target control’s value. The bindings shown in Example 4-8 demonstrate that all three types of binding modes can be valuable assets when used in the same Silverlight control. The presentation of the XAML in Example 4-8 appears in Figure 4-4. (Refer to Figure 4-5 in the next section for a look at the updated reorder level.)

ProductView Silverlight control

Figure 4-4. ProductView Silverlight control

Get Data-Driven Services with Silverlight 2 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.