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>
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.)
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.