programming4us
programming4us
WEBSITE

Silverlight Recipes : Controls - Creating a Composite User Control

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
4/29/2013 7:48:22 PM

1. Problem

You need to compose a UI using existing controls and package it in a reusable format.

2. Solution

Use the Visual Studio 2010 Silverlight user control template to create a new class deriving from UserControl, and then add controls to UserControl to compose an UI.

3. How It Works

Silverlight offers two kinds of controls: user controls and custom controls. User controls are an effective way to package UI and related client-side processing logic tied to a specific business or application domain into a reusable unit that can then be consumed as a tag in XAML, similar to any other built-in primitive shape like Ellipse or Rectangle. There is excellent tool support for designing and implementing user controls both in Visual Studio and Expression Blend, making it the default choice for creating reusable user interface components with reasonable ease.

User controls allow you to create composite UIs by combining other custom or user controls. This ability makes them especially suitable for writing composite controls—in fact, most user controls that you end up writing will be composite controls.

Custom controls, on the other hand, are the more powerful controls in Silverlight. All the controls in the System.Windows.Controls namespace that come with the framework are built as custom controls; using them is typically the preferred way of implementing more general-purpose UI components that are not limited to one particular application or business domain. Custom controls also enable powerful features, such as control templates that allow radical customization of the control user interface. The recipes later in this chapter cover custom control development in greater detail.

The following sections review a few concepts critical to understanding how a control works. This information will increase your understanding of a user control and it will be good background for later recipes that discuss custom controls.

3.1. User Control Structure

Creating a user control is fairly easy if you are using Visual Studio 2010. With the Silverlight tools installed, Visual Studio offers you a template to add a new user control to a Silverlight project, through the Add New Item dialog box (see Figure 1).

Figure 1. Adding a new Silverlight user control to your project

Once you add a user control, you should see a XAML document coupled with a codebehind file defining the user control. User controls are defined as partial classes deriving from the UserControl type in the System.Windows.Controls namespace. Visual Studio generates one such class when you add a new user control. The following is such a class for a user control named PagedProductsGrid:

namespace CompositeControlLib
{
  public partial class PagedProductsGrid : UserControl
  {

    public PagedProductsGrid()
    {
      InitializeComponent();
    }

  }
}

In the generated XAML document for the user control, you should see some skeletal XAML initially generated by Visual Studio, as shown here:

<UserControl
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        x:Class="CompositeControlLib .PagedProductsGrid"
        d:DesignWidth="640" d:DesignHeight="480">
        <Grid x:Name="LayoutRoot"/>
    </UserControl>

					  

You will notice that the Visual Studio template adds a top-level Grid (conventionally named LayoutRoot) in the XAML. You can define the rest of the UI for the user control inside this Grid. Should you choose to, you can rename the Grid or even replace the Grid with some other container.

Note the x:Class attribute in the UserControl declaration in XAML. The value set here needs to be the namespace-qualified name of the partial class defined in the codebehind file. This mechanism allows the XAML declaration of the user control to be associated with the user control class at compile time.

3.2. XAML Loading

So how does the XAML for the user control get loaded at runtime? When you compile the user control project, a XAML parser generates some additional code to extend the user control partial class. This code is usually found in a file named <controlname>.g.i.cs inside the \obj\debug folder below your project's root folder. This generated code adds some startup functionality, which is encapsulated in a method named InitializeComponent(). You will find that the Visual Studio template already adds a call to InitializeComponent() to the constructor of your user control class. Listing 1 shows the generated code for a user control.

Listing 1. Visual Studio-generated startup code for a UserControl
namespace CompositeControlLib
{
  public partial class PagedProductsGrid : System.Windows.Controls.UserControl
  {
    internal System.Windows.Controls.Grid LayoutRoot;
    internal System.Windows.Controls.DataGrid dgProductPage;
    internal System.Windows.Controls.ListBox lbxPageNum;
    private bool _contentLoaded;

    /// <summary>
    /// InitializeComponent
    /// </summary>
    [System.Diagnostics.DebuggerNonUserCodeAttribute()]
    public void InitializeComponent()
    {
      if (_contentLoaded)
      {
        return;
      }
      _contentLoaded = true;
      System.Windows.Application.LoadComponent(
        this,
        new System.Uri("/CompositeControlLib;component/PagedProductsGrid.xaml",
          System.UriKind.Relative));
      this.LayoutRoot =
        ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
      this.dgProductPage =
        ((System.Windows.Controls.DataGrid)(this.FindName("dgProductPage")));
      this.lbxPageNum =
        ((System.Windows.Controls.ListBox)(this.FindName("lbxPageNum")));
    }
  }
}

					  

At the crux of this code is the LoadComponent() method, used at runtime to load the XAML included as a resource in the compiled assembly. Once the element tree defined in the XAML is formed, the FrameworkElement.FindName() is used to locate and store the instances for every named control in your XAML definition so that you can refer to them in your code.

3.3. Dependency Properties

Control types expose properties as a means to allow the control consumer (a developer or a designer) to get or set various attributes of a control instance. Since controls in Silverlight are also .NET classes, properties can be implemented using the standard CLR property syntax.

Silverlight provides an extension to the standard CLR property system by introducing a new concept called a dependency property. A dependency property provides additional functionality that cannot be implemented using standard CLR properties. Among other features, the extended functionality includes the following:

  • Data binding: Dependency properties can be data bound using either the XAML {Binding. . .} markup extension or the Binding class in code, thus allowing evaluation of its value at runtime.

  • Styles: Dependency properties can be set using setters in a style.

  • Resource referencing: A dependency property can be set to refer to a predefined resource defined in a resource dictionary using the {StaticResource. . .} markup extension in XAML.

  • Animations: For a property to be animated, it needs to be a dependency property. 

A dependency property is implemented in code as a public static member of type DependencyProperty, where the implementing type needs to derive from DependencyObject. Listing 2 shows a sample declaration of a dependency property named MaximumProperty, representing a double valued maximum for some range.

Listing 2. Sample DependencyProperty declaration
public static DependencyProperty MaximumProperty =
  DependencyProperty.Register("Maximum",
  typeof(double?),
  typeof(NumericUpdown),
  new PropertyMetadata(100,new PropertyChangedCallback(MaximumChangedCallback)));

public double? Maximum
{
  get
  {
    return (double?)GetValue(MaximumProperty);
  }
  set
  {
    SetValue(MaximumProperty, value);
  }
}

internal static void MaximumChangedCallback(DependencyObject Target,
DependencyPropertyChangedEventArgs e)

					  

{
  NumericUpdown target = Target as NumericUpdown;
  //other code to respond to the property change
}

The static method DependencyProperty.Register() is used to register the property with the Silverlight property system. The parameters to the method are a name for the property, the property data type, the containing type, and a PropertyMetadata instance. In the code above, note the string "Maximum" as the property name, double? as the data type for the property, and the property owner as a type named NumericUpdown. The PropertyMetadata parameter is constructed by passing in a default value for the property and a delegate to a static callback method that is invoked when the property value changes. Notice that the defaultValue parameter is of type object. Also note that the callback method is only required if you intend to take some action when the value of the dependency property changes. If the value change has no impact on your control's logic, PropertyMetadata has another constructor that only accepts the defaultValue parameter.

A conventional way of naming the dependency property is by concatenating the string "Property" to the property name. You are free to change that convention; however, it is to your benefit to stick with it. The framework and the Silverlight SDK follow the same convention, and developers around the world will soon get used to this convention to determine whether or not a property is a dependency property.

Although the dependency property is declared static, the Silverlight property system maintains and provides access to values of the property on a per-instance basis. The DependencyObject.GetValue() method accepts a dependency property and returns the value of the property for the instance of the declaring type within which GetValue() is invoked. The returned value is typed as Object, and you will need to cast it to the appropriate type before using it. SetValue() accepts a dependency property and a value and sets that value for the instance within which SetValue() is invoked. A CLR property wrapper of the same name, minus the "Property" extension (as shown in Listing 5-16) is typically provided as shorthand to using the GetValue()/SetValue() pair for manipulating the property in code.

The instance on which the property change happened is passed in as the first parameter to the static property change callback handler. This allows you to cast it appropriately, as shown in MaximumChangedCallback() in Listing 5-16, and then take action on that instance in response to the property value change. The second parameter of type DependencyPropertyChangedEventArgs exposes two useful properties: the OldValue property exposes the value of the property before the change, and the NewValue property exposes the changed value.

4. The Code

The code sample for this recipe builds a user control named PagedProductsGrid that displays Product data in a grid form, coupled with paging logic, where the consumer of the control gets to specify how many records to display per page and the control automatically adds a pager at the bottom that allows the user to navigate through pages.

Figure 2 shows the control in action. Also shown is the pager at the bottom, with the selected page in a solid blue rectangle.

Figure 2. Paged product data composite control

Listing 3 shows the XAML for the PagedProductsGrid user control.

Listing 3. XAML for PagedProductsGrid user control
<UserControl
  x:Class="Recipe5_8.PagedProductsGrid"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
  xmlns:data=
  "clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
  Width="700" Height="300">
  <UserControl.Resources>
    <!-- control template for Pager ListBoxItem -->
    <ControlTemplate TargetType="ListBoxItem" x:Key="ctLbxItemPageNum">
      <Grid>
        <vsm:VisualStateManager.VisualStateGroups>
          <vsm:VisualStateGroup x:Name="CommonStates">
            <vsm:VisualState x:Name="Normal">
              <Storyboard/>
            </vsm:VisualState>
            <vsm:VisualState x:Name="MouseOver">
              <Storyboard>
                <ColorAnimationUsingKeyFrames
                  BeginTime="00:00:00"
                  Duration="00:00:00.0010000"
                  Storyboard.TargetName="ContentBorder"
                  Storyboard.TargetProperty=
                  "(Border.BorderBrush).(SolidColorBrush.Color)">

					  

<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF091F88"/>
                </ColorAnimationUsingKeyFrames>
              </Storyboard>
            </vsm:VisualState>
          </vsm:VisualStateGroup>
          <vsm:VisualStateGroup x:Name="SelectionStates">
            <vsm:VisualState x:Name="Unselected">
              <Storyboard/>
            </vsm:VisualState>
            <vsm:VisualState x:Name="Selected">
              <Storyboard>
                <ColorAnimationUsingKeyFrames
                  BeginTime="00:00:00"
                  Duration="00:00:00.0010000"
                  Storyboard.TargetName="ContentBorder"
                  Storyboard.TargetProperty=
                  "(Border.Background).(SolidColorBrush.Color)">
                  <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF1279F5"/>
                </ColorAnimationUsingKeyFrames>
              </Storyboard>
            </vsm:VisualState>
            <vsm:VisualState x:Name="SelectedUnfocused">
              <Storyboard>
                <ColorAnimationUsingKeyFrames
                  BeginTime="00:00:00"
                  Duration="00:00:00.0010000"
                  Storyboard.TargetName="ContentBorder"
                  Storyboard.TargetProperty=
                  "(Border.Background).(SolidColorBrush.Color)">
                  <SplineColorKeyFrame KeyTime="00:00:00" Value="#FF1279F5"/>
                </ColorAnimationUsingKeyFrames>
              </Storyboard>
            </vsm:VisualState>
          </vsm:VisualStateGroup>
          <vsm:VisualStateGroup x:Name="FocusStates">
            <vsm:VisualState x:Name="Unfocused">
              <Storyboard/>
            </vsm:VisualState>
            <vsm:VisualState x:Name="Focused">
              <Storyboard/>
            </vsm:VisualState>
          </vsm:VisualStateGroup>
        </vsm:VisualStateManager.VisualStateGroups>

        <Border HorizontalAlignment="Left"

					  

VerticalAlignment="Top"
                Margin="5,5,5,5"
                Padding="5,5,5,5"
                BorderBrush="#00091F88"
                BorderThickness="2,2,2,2"
                Background="#001279F5"
                x:Name="ContentBorder">
          <ContentPresenter
            Content="{TemplateBinding Content}"
            ContentTemplate="{TemplateBinding ContentTemplate}"/>
        </Border>
      </Grid>
    </ControlTemplate>
    <!-- style applying the Pager ListBoxItem control template -->
    <Style x:Key="stylePageNum" TargetType="ListBoxItem">
      <Setter Property="Template" Value="{StaticResource ctLbxItemPageNum}" />
    </Style>
    <!-- Horizontal panel for the Pager ListBox -->
    <ItemsPanelTemplate x:Key="iptHorizontalPanel">
      <StackPanel Orientation="Horizontal"/>
    </ItemsPanelTemplate>

    <!--Control template for Pager ListBox-->
    <ControlTemplate x:Key="ctlbxPager" TargetType="ListBox">
      <Grid>
        <ItemsPresenter HorizontalAlignment="Left" VerticalAlignment="Top" />
      </Grid>
    </ControlTemplate>
  </UserControl.Resources>

  <Border Background="LightGray" BorderBrush="Black" BorderThickness="2,2,2,2">
    <Grid x:Name="LayoutRoot">
      <Grid.RowDefinitions>
        <RowDefinition Height="85*" />
        <RowDefinition Height="15*" />
      </Grid.RowDefinitions>
      <!-- data grid to display Products data -->
      <data:DataGrid x:Name="dgProductPage" AutoGenerateColumns="False"
                     Grid.Row="0"
                     SelectionChanged="dgProductPage_SelectionChanged">
        <data:DataGrid.Columns>
          <data:DataGridTextColumn Binding="{Binding ProductID}"
                                   Header="ID" />
          <data:DataGridTextColumn Binding="{Binding Name}"
                                   Header="Name"/>

					  

<data:DataGridTextColumn Binding="{Binding ProductNumber}"
                                   Header="Number"/>
          <data:DataGridTextColumn Binding="{Binding SellStartDate}"
                                   Header="Sell From"/>
          <data:DataGridTextColumn Binding="{Binding SellEndDate}"
                                   Header="Sell Till"/>
          <data:DataGridTextColumn Binding="{Binding Style}"
                                   Header="Style"/>
        </data:DataGrid.Columns>
      </data:DataGrid>
      <!-- Pager Listbox-->
      <ListBox x:Name="lbxPager" Grid.Row="1"
               HorizontalAlignment="Right" VerticalAlignment="Center"
               SelectionChanged="lbxPager_SelectionChanged"
               ItemsPanel="{StaticResource iptHorizontalPanel}"
               ItemContainerStyle="{StaticResource stylePageNum}"
               Template="{StaticResource ctlbxPager}">
      </ListBox>
    </Grid>
  </Border>
</UserControl>

The user control has two primary parts: a DataGrid named dgProductPage with columns bound to the Product data type, and a ListBox named lbxPager acting as a pager.

The first thing to note about the pager ListBox is that you replace its default ItemsPanel with a horizontal StackPanel so that the page numbers appear horizontally moving from left to right. This is done by defining a custom ItemsPanelTemplate, named iptHorizontalPanel, and associating that with the ItemsPanel property on the ListBox. Panel customization is discussed in greater detail in later recipes.

You apply a custom control template, named ctlbxPager, to the ListBox. It simplifies the ListBox significantly, just leaving an ItemsPresenter for displaying the items inside a Grid.

You also customize each ListBoxItem by applying a custom control template, named ctLbxItemPageNum, to the ListBoxItem. The template defines the ListBoxItem as a ContentPresenter within a Border and adds storyboards for the MouseOver, Selected, and SelectedUnfocused visual states (a solid blue rectangle around the page number to indicate the selected page and a blue border to indicate the one on which the mouse is hovering). A style named StylePageNum is used to associate this with the ListBox through its ItemContainerStyle property.

Again, the AdventureWorks WCF service delivers the data to the control. The following code shows the implementation of the AdventureWorks WCF service operation GetProductPage(), which returns a page of product data:

public List<Product> GetProductPage(int SkipCount, int TakeCount)
{
  ProductsDataContext dc = new ProductsDataContext();

  return (from Prod in dc.Products select Prod).Skip(SkipCount).
Take(TakeCount).ToList();
}

The SkipCount parameter to GetProductPage() indicates the number of rows to skip, and the TakeCount parameter indicates the number of rows to return after the skipping is done. LINQ exposes two handy operators, Skip and Take, that allow you to do just that on a collection of items.

Listing 4 shows the control codebehind.

Listing 4. Codebehind for the PagedProductsGrid Control
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Recipe5_8.AdvWorks;

namespace Recipe5_8
{
  public partial class PagedProductsGrid : UserControl
  {
    //WCF service proxy
    AdvWorksDataServiceClient client = new AdvWorksDataServiceClient();
    //raise an event when current record selection changes
    public event EventHandler<DataItemSelectionChangedEventArgs>
      DataItemSelectionChanged;
    //RecordsPerPage DP
    DependencyProperty RecordsPerPageProperty =
      DependencyProperty.Register("RecordsPerPage",
      typeof(int),
      typeof(PagedProductsGrid),
      new PropertyMetadata(20,
        new PropertyChangedCallback(
          PagedProductsGrid.RecordsPerPageChangedHandler)
        ));
    //CLR DP Wrapper
    public int RecordsPerPage
    {
      get
      {
        return (int)GetValue(RecordsPerPageProperty);
      }

    set
      {
        SetValue(RecordsPerPageProperty, value);
      }
    }
    //RecordPerPage DP value changed

					  

internal static void RecordsPerPageChangedHandler(DependencyObject sender,
      DependencyPropertyChangedEventArgs e)
    {
      PagedProductsGrid dg = sender as PagedProductsGrid;
      //call init data
      dg.InitData();
    }

    public PagedProductsGrid()
    {
      InitializeComponent();
    }

    internal void InitData()
    {
      //got data
      client.GetProductPageCompleted +=
        new EventHandler<GetProductPageCompletedEventArgs>(
          delegate(object Sender, GetProductPageCompletedEventArgs e)
          {
            //bind grid
            dgProductPage.ItemsSource = e.Result;
          });

      //got the count
      client.GetProductsCountCompleted +=
        new EventHandler<GetProductsCountCompletedEventArgs>(
          delegate(object Sender, GetProductsCountCompletedEventArgs e)
          {
            //set the pager control
            lbxPager.ItemsSource = new List<int>(Enumerable.Range(1,
              (int)Math.Ceiling(e.Result / RecordsPerPage)));
            //get the first page of data
            client.GetProductPageAsync(0, RecordsPerPage);
          });
      //get the product count
      client.GetProductsCountAsync();
    }
    //page selection changed
    private void lbxPager_SelectionChanged(object sender,
      SelectionChangedEventArgs e)
    {
      //get page number
      int PageNum = (int)(lbxPager.SelectedItem);
      //fetch that page - handler defined in InitData()

					  

client.GetProductPageAsync(RecordsPerPage * (PageNum − 1), RecordsPerPage);

    }
    //record selection changed
    private void dgProductPage_SelectionChanged(object sender, EventArgs e)
    {
      if (this.DataItemSelectionChanged != null)
      {
        //raise DataItemSelectionChanged
        this.DataItemSelectionChanged(this,
          new DataItemSelectionChangedEventArgs {
            CurrentItem = dgProductPage.SelectedItem as Product
          });
      }
    }
  }

  public class DataItemSelectionChangedEventArgs : EventArgs
  {
    public Product CurrentItem { get; internal set; }
  }
}

The InitData() function is used to load the data into the DataGrid. To facilitate paging, you first record the total number of Products available by calling the GetProductsCountAsync() service operation. In the callback handler for GetProductsCountAsync(), you set the lbxPager ListBox data to be a range of numbers, starting with 1 and ending at the maximum number of pages expected based on the record count retrieved earlier. You set the value of the RecordsPerPage property that the developer has set.

You then call the service operation GetProductPageAsync() with SkipCount set to 0 and TakeCount set to the value of RecordsPerPage. The retrieved Product data gets bound to the DataGrid dgProductPage as the first page of data.

If the value of RecordsPerPage changes at runtime, you reinitialize the grid by calling InitData() again in RecordsPerPageChangedHandler(). You also handle the navigation to a different page by handling the SelectionChanged event in lbxPager, where you retrieve the page requested and call GetProductsDataAsync() again, with SkipCount set to the product of RecordsPerPage times the number of pages before the current one selected and TakeCount again set to RecordsPerPage.

To demonstrate events from a user control, you also define and raise an event named DataItemSelectionChanged whenever the current row selection in a DataGrid changes. You handle a change in row selection in the internal DataGrid dgProductPage, and in that handler, you raise the event.

A custom event argument type of DataItemSelectionChangedEventArgs, also shown in Listing 4, is used to pass the actual Product instance bound to the current row to the event consumer.

To consume the user control in a page, you add a reference to the assembly containing the user control to your project. You then add a custom namespace declaration to dereference the types within the assembly in the page's XAML. Finally, you declare an instance of the control prefixed with the custom namespace in the XAML. Listing 5 shows the XAML for your consuming page.

NOTE

There is no strict requirement to implement your user control in a separate assembly from the application consuming it. We simply find it to be a best practice to follow, one that makes the control a lot more distributable and reusable.

Listing 5. XAML for the Test Page Hosting the User Control
<UserControl x:Class="Recipe5_8.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:composite=
"clr-namespace:Recipe5_8;assembly=Recipe5_8.ControlLib"
    >
  <Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <!-- user control declaration -->
    <composite:PagedProductsGrid x:Name="PagedGrid"
           RecordsPerPage="30"
           DataItemSelectionChanged="PagedGrid_DataItemSelectionChanged"
           Grid.Row="0"  HorizontalAlignment="Left"/>
    <!-- content control with a data template that gets bound to
    selected data passed through user control raised event -->
    <ContentControl x:Name="ProductCostInfo" Grid.Row="1" Margin="0,20,0,0">
      <ContentControl.ContentTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal">
            <TextBlock
              Text="The currently selected product has a list price of $ "/>
            <TextBlock Text="{Binding ListPrice}"
                       Margin="0,0,10,0"
                       Foreground="Blue"/>
            <TextBlock Text="and a standard cost of $ "/>
            <TextBlock Text="{Binding StandardCost}"
                       Foreground="Blue"/>
          </StackPanel>
        </DataTemplate>
      </ContentControl.ContentTemplate>
    </ContentControl>
  </Grid>
</UserControl>

					  

The custom namespace composite brings in the actual .NET namespace and the assembly reference into the XAML so that control can be referenced. You can then declare the control by prefixing its opening and closing tags with the namespace prefix. In Listing 5, you set the RecordsPerPage property to a value of 30 so that the control displays 30 records per page. If you refer to Listing 4, you will note a default value of 20 to RecordsPerPage in the PropertyMetadata constructor while registering the DependencyProperty. In the event you do not bind the RecordsPerPage property to some value in XAML, 20 will be the value applied as a default. To illustrate consuming the DataItemChanged event that you equipped the user control to raise, you also add a ContentControl named ProductCostInfo in your page with a data template that binds a couple of TextBlocks to the ListPrice and the StandardCost properties of a Product instance. You handle the DataItemChanged event, and in the handler, you bind the Product received through the event arguments to the ContentControl, as shown in the codebehind for the page in Listing 6.

Listing 6. Codebehind for the test page hosting the UserControl
using System.Windows.Controls;

namespace Recipe5_8
{
  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

    }

    private void PagedGrid_DataItemSelectionChanged(object sender,
      DataItemSelectionChangedEventArgs e)
    {
      if (e.CurrentItem != null)
        ProductCostInfo.Content = e.CurrentItem;
    }
  }
}

If you refer to Figure 2, you will see the resulting text on the page, right below the user control, showing the ListPrice and the StandardCost of the currently selected Product.

Other  
 
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
programming4us programming4us
programming4us
 
 
programming4us