MULTIMEDIA

Leverage and Locate Controls and Classes on Silverlight 4

9/14/2010 9:45:47 AM
1. Problem

You want to add a custom class and a custom control to your application and access both the class and the control in the XAML, which is the markup similar to the ASPX page in ASP.NET. You also want to know how to dynamically find a control so that you can modify its properties.

2. Solution

To add a class, add a clr-namespace to the element in your Silverlight application via the xmlns attribute to make the custom class available to the Silverlight application. To add a control, first add a project reference to the assembly containing the custom control and then add a clr-namespace in a similar manner. To dynamically locate a control at runtime, use the FrameworkElement.FindName method.

3. How It Works

Most of the time, applications consist of more than one class and custom control. You can add a class to project by right-clicking a Silverlight project and selecting Add a Class. Classes can also be brought in through a separate project or assembly just as you would in any other .NET application by adding a reference to the assembly. To make the class or control available in XAML, you add an xmlns attribute to the root UserControl. Note that you add a using statement if the class is in a different namespace and you want to access the class in code.

Generally, in Silverlight applications much of the code is written in XAML, which is an XML markup language, so it takes an additional step to make the class or control available within the XAML markup. This step involves adding an xmlns namespace import statement to the element.

3.1. Add a Custom Control

To make a custom control available, the steps are similar to making a class available. You'll use a custom control from a separate solution titled SimpleControl that creates a simple control consisting of a TextBlock that displays the text Full Name: in front of the value set for the FullName property on the control.

3.2. Find a Control

Finding a control at runtime is often a necessary task. The abstract base class for controls in Silverlight is the DependencyObject class that represents objects participating in the Silverlight dependency property system. UIElement inherits from DependencyObject and represents objects that have visual appearance and that can perform basic input. FrameworkElement inherits from UIElement and provides common APIs for elements to participate in Silverlight layout, as well as APIs related to data binding, the object tree, and object lifetime.

One of the available members on FrameworkElement is FindName, which takes a string that contains the name of a control and returns either an object reference or null. The FindName method provides a convenient way of locating a control within the XAML visual tree without having to walk through the object tree.

In order for a control to be found, it must have its Name property set in code or via the x:Name property in XAML. XAML is hierarchical by nature, since it is an XML tree where there is a root element that contains child elements. After the XAML processor creates the object tree from markup, the x:Name attribute provides a reference to markup elements that is accessible in the codebehind file, such as in event handler code.

Names must be unique within an XAML namescope. The XAML , by default defined in MainPage.xaml as the MainPage class, is the most common namescope and is referred to as the root XAML namescope. Calling APIs that dynamically load XAML can define additional namescopes as well.

When XAML is added dynamically to the visual tree, the tree remains unified, but a new namescope will be created at the point where the dynamic XAML is attached. Templates and resources define their own namescopes independently of the containing page where the style or template is applied.

The reason for the detailed discussion regarding namescope is because FindName works within the constraint of namescopes. If you call FindName from the MainPage level to get a named object in the root XAML namescope, the call will succeed as usual. However, if you call FindName from the MainPage level, the method will not find the objects in the new discrete XAML namescope created by Load or within templates or resources. To find an element with FindName within newly created namescope, retain a reference to an object or UIElement within the namescope, and call FindName from the element that is within the new namescope in the XAML visual tree.

Since FindName is part of the visual control base class FrameworkElement, it is accessible in all visual controls and can be called just about anywhere. What is convenient about FindName is that if the XAML element has child elements, they are all searched recursively for the requested named element. FindName will search the current XAML namescope in both the up (parent) and down (children) direction within the visual object tree defined in XAML.

In this recipe, you will work with a class named Organization that you will add to the Silverlight application. The Organization class is just a fictitious class example with a few example data items. The Organization class is in the same Ch02_ProgrammingModel.Recipe2_1 namespace as the MainPage.xaml.cs file so you can access the Organization class directly without having to add a using statement. If the Organization class was in a separate assembly with a different namespace, you would need to add a reference to the other assembly and a using statement as you normally would to access a class within an application.

At the top of MainPage.xaml, you will notice namespace declarations within the element:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

The first statement imports the presentation framework namespace as the default namespace. The second declaration maps an additional XAML namespace, mapping it to the x: prefix. To access the Organization class within MainPage.xaml, you need to add an additional namespace declaration with a unique prefix by typing xmlns:data= in the tag. You use the prefix data because you want to data bind to the People collection in the Organization class. You can pick any prefix you want, but it helps to use something that makes sense for the application. Figure 1 shows the support in Visual Studio 2010 that lets you easily import the Ch02_ProgrammingModel.Recipe2_1 namespace.

Figure 1. The Visual Studio 2010 namespace import IntelliSense window

Selecting the first line in the pop-up IntelliSense window imports the correct namespace that allows you to access the Organization class within the Silverlight XAML markup, resulting in this namespace statement:

xmlns:data="clr-namespace:Ch02_ProgrammingModel.Recipe2_1"

You add a ListBox control to the XAML to help test your ability to access the Organization class. Let's use Microsoft Expression Blend 4 to set the ItemSourceListBox control. First, save the solution, and then open the solution in Blend so that it is open in both Expression Blend 4 and Visual Studio. Inside Expression Blend, open MainPage.xaml. Select the ListBox so that it is highlighted, and then enter Item in the Properties search box to bring the ItemSource to the top of the Properties window, as shown in Figure 2. property on the

Figure 2. The ListBox ItemSource property

Notice in Figure 2 that there is a small button highlighted by the mouse pointer hovering over it. Clicking this button provides access to the Advanced property options menu, shown in Figure 3.

Figure 3. The Advanced property options menu

Click the Data Binding option to open the Create Data Binding dialog shown in Figure 4. The astute reader will notice in Figure 2-4 that, in addition to Data Field and Explicit Data Context, Element Property is no longer grayed out as it was in Silverlight 2 and Expression Blend 2 SP1. In Silverlight 3 and now in Silverlight 4, it is possible for controls to data bind to values of other elements or controls.

Figure 4. The Create Data Binding dialog

For now, click the +CLR Object button to open the Define New Object Data Source dialog, shown in Figure 5.

Figure 5. The Define New Object Data Source dialog

Select Organization, and then click OK to create the OrganizationDS object. Select the OrganizationDS object in the Create Data Binding dialog and then expand the Organization object in the Fields pane on the right to display the People collection. Select the People collection, and click OK to set the ItemSource for the ListBox to the People collection. Save the solution in Expression Blend 4, and switch back to Visual Studio to view the generated XAML.

When you run the sample, the ListBox displays three items that contain the text Ch02_ProgrammingModel.Recipe2_1.Person, which is the type that is stored in the People collection.

Listing 1 shows the Organization class file.

Listing 1. Recipe 1's Organization Class File
using System.Collections.Generic;

namespace Ch02_ProgrammingModel.Recipe2_1
{
public class Organization
{
private List _people;
public List People
{
get
{
if (null == _people)
return Populate();
else
return _people;
}
}

private List Populate()
{
_people = new List
{ //C# 3.0 Object Initializers
new Person {FirstName="John",LastName="Smith", Age=20},
new Person{FirstName="Sean",LastName="Jones", Age=25},
new Person{FirstName="Kevin",LastName="Smith", Age=30}
};
return _people;
}
}

public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
}


Listing 2 shows the resulting code to add a custom class as well as the additional code discussed below regarding adding a custom control and how to find a control in XAML. (Please note the use of some layout controls, Grid and StackPanel, to help segment the three bits of functionality into areas separated by blue rectangles.)

Listing 2. The MainPage.xaml File
<UserControl x:Class="Ch02_ProgrammingModel.Recipe2_1.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:data="clr-namespace:Ch02_ProgrammingModel.Recipe2_1"
  xmlns:SC="clr-namespace:SimpleControl;assembly=SimpleControl"
 mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc=
"http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" >

  <UserControl.Resources>
    <data:Organization x:Key="OrganizationDS" d:IsDataSource="True"/>
  </UserControl.Resources><Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding
Source={StaticResource OrganizationDataSource}}">
    <StackPanel>
      <ListBox x:Name="PeopleListBox" ItemsSource="{Binding People}" />
      <Rectangle Fill="Navy" Height="10" Margin="2"></Rectangle>
      <SC:SimpleControl FullName="Rob Cameron and Jit Ghosh" FontSize="18" />
      <Rectangle Fill="Navy" Height="10" Margin="2"></Rectangle>
      <Grid Background="#FFD0D0D0" >
        <StackPanel Grid.RowSpan="2">
          <TextBlock x:Name="TextBlock1" Margin="4">TextBlock1</TextBlock>
          <TextBlock x:Name="TextBlock2" Margin="4">TextBlock2</TextBlock>
          <TextBlock x:Name="TextBlock3" Margin="4">TextBlock3</TextBlock>
          <TextBlock x:Name="TextBlock4" Margin="4">TextBlock4</TextBlock>
          <StackPanel >
                <TextBlock Margin="2" TextWrapping="Wrap" Text="Type the Name of a TextBlock
from the above list."></TextBlock>
                <TextBox x:Name="ControlName" KeyDown="ControlName_KeyDown"
                        Margin="2" Grid.Row="1" TextWrapping="Wrap"/>
                <Button Content="Click To Find the Name Entered." Margin="2"
                        Click="Button_Click"/>
          </StackPanel>
        </StackPanel>
      </Grid>
    </StackPanel>
  </Grid>
</UserControl>
Expression Blend 4 added a couple of xmlns statements to the element shown here:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/
markup-compatibility/2006" mc:Ignorable="d"

These namespaces are used by Expression Blend for code generation and can be removed if desired. However, you will need to edit the XAML in the code file as well if they use the mc: or d: namespace reference. Now, let's discuss the new resource added to the UserControl as part of configuring the data binding:




The resource uses the data: prefix you defined in Visual Studio to gain access to the Organization class and sets the x:Key property so that you can access the resource by name as OrganizationDS.

The other interesting markup change is the value configured on the ListBox's DataContext property:

DataContext="{Binding Mode=OneWay, Path=People,
Source={StaticResource OrganizationDS}}"

You can see that the DataContext is set to the People collection via the Path property on the Binding object, which is available by setting the Source property to the static resource OrganizationDS. As a result, the Listbox will display the list of people in the UI.

Now, let's learn how to make a custom control available in XAML, which is very similar to making a class available.

To make the custom control available, add a reference to the assembly in your project to the SimpleControl assembly, and then add an xmlns import to the element just like you did with the custom class:

xmlns:SC="clr-namespace:SimpleControl;assembly=SimpleControl"

Once the control's namespace is imported, the control can be added to the XAML in Visual Studio using the SC: namespace (isolated from Listing 2-2 here):


Figure 6 shows the UI with the Listbox displaying the Organization custom class and the SimpleControl custom control.

Figure 6. The Recipe 1 UI

The last bit of functionality related to adding a control is dynamically finding a control. In Listing 2-2, below the second rectangle in the XAML, there is a Grid control hosting a StackPanel with a few controls to implement the find logic. The user can enter a name of one of the TextBlock controls and click the Button to find the control name entered and scale its size a little bit if found. Listing 2-3 contains the codebehind file for this recipe where the programming logic exists to actually find the control.

Listing 3. The MainPage.xaml.cs File
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Ch02_ProgrammingModel.Recipe2_1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
TextBlock tb =
(TextBlock)LayoutRoot.FindName(ControlName.Text);
if (tb != null)
tb.FontSize = 20.0;
else
{
ControlName.Foreground = new SolidColorBrush(
Color.FromArgb(255, 200, 124, 124));
ControlName.Text = "Control not found! Please try again.";
}
}

private void ControlName_KeyDown(object sender, KeyEventArgs e)
{
ControlName.Foreground =
new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
}
}
}


There are two events in the codebehind file: one for clicking the button and another for the KeyDown for the TextBox. The Button_Click event tries to find a control with the name entered in the TextBox. If the entered value is valid and the control can be found, the FontSize is changed to 20 for the found TextBlock.

If the entered value is not valid, a message is put into the TextBox stating that the control was not found based on the entered value, and the font color is changed to a reddish color. The KeyDown event simply resets the font color for the TextBox back to black. We purposely did not use any of the great new animation features available in Silverlight and instead chose to have Windows Forms-like simple animation in the UI.

Figure 7 shows the initial layout of the UI but with the additional UX for finding the control functionality.

Figure 7. Recipe 1 UI final layout

Figure 8 shows the application when the correct value for the name of a TextBlock control is entered and the Button is clicked. TextBlock2 is entered for the value, and the font size is changed to 20, enlarging the text in TextBlock2.

Figure 8. Recipe 1's UI after entering a valid control name

Figure 9 shows the UI when an incorrect value is entered. The font color is changed, and an error message is put into the TextBox control.

Figure 9. Recipe 1's UI after entering an invalid control namee

 
Other  
 
Video
Top 10
Beginning Android 3 : The Input Method Framework - Fitting In
Windows 7 : Creating and Managing Groups
Windows 7 : Managing User Properties
Exchange Server 2010 : Unique database names
Exchange Server 2010 : Transaction log replay: The foundation for DAG replication
Exchange Server 2010 : Active Manager - Automatic database transitions & Best copy selection
Exchange Server 2010 : Breaking the link between database and server
iPhone 3D Programming : Drawing an FPS Counter (part 2) - Rendering the FPS Text
iPhone 3D Programming : Drawing an FPS Counter (part 1) - Generating a Glyphs Texture with Python
Mobile Application Security : Mobile Geolocation - Geolocation Methods & Geolocation Implementation
Most View
Programming with DirectX : Shading and Surfaces - Types of Textures
Creating Connections for Remote Access in Vista
Windows Server 2008 : Using Windows Server Update Services
WCF Services : Data Contract - Attributes
Windows Phone 7 Development : Using GeoCoordinateWatcher and the Bing Maps Control to Track Your Movements
Exploring the T-SQL Enhancements in SQL Server 2005 : The WAITFOR Command
Programming WCF : Queued Services - Transactions
Configuring Disk Quotas
Installing Windows Server 2008 R2 and Server Core : Upgrading to Windows Server 2008 R2
iPhone 3D Programming : Anti-Aliasing Tricks with Offscreen FBOs (part 2) - Jittering
iPhone Application Development : Working with Text, Keyboards, and Buttons (part 3) - Creating Styled Buttons
Management Tools in SQL Server 2008
Introducing Silverlight 2
Clustered Indexes in SQL Server 2008
Understanding Exchange Policy Enforcement Security : Creating Messaging Records Management Policies
Default Security Policy
Windows Phone 7 Development : Push Notifications - Implementing Cloud Service to Track Push Notifications
SQL Server 2005 Data Protection
Exchange Server 2007 : Configure the Client Access Server - Create and Apply ActiveSync Mailbox Policies
Building Android Apps: Going Native - Setting Up the Environment