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.
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
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.
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.
For now, click the +CLR Object button to open the Define New Object Data Source dialog, shown in Figure 5.
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.
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 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 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.