Rick Barraza
Silverlight UX Development
Tuesday November 20, 2007
DD#01: Dynamically Attaching Controls in Silverlight
We’ll start with this assumption: You know the basics of XAML, you know how to at least get to the code behind file, and most importantly, you know where to look online for detailed help if you get stuck (‘cause these can go pretty fast ;)
Ready? Here’s what we’re building today:
Instructions: Click on the stage below to add a circle dynamically
Requires Silverlight 1.1
As a quick get your bearings for those of you coming from Flash trying to map up Silverlight to what you already know (Non flashers, ear muff it for a paragraph or so): We’re basically trying to figuring out the equivalent of attaching movieClips dynamically at run time and interacting with them in XAML/C#. Whereas in Flash you’re used to starting off with a single framed timeline and the ability to add ActionScript code to that initial frame, you start off here with a TestPage.xaml file (the visual definition of your stage in markup) and it’s code behind file, TestPage.xaml.cs (All my Sivlerlight 1.1 examples will use C# as the language of choice). Now in Flash, you create a movieClip, export it, and then attach it to the stage with movieClip.attachMovie(). These Flash movieClips come with their own stage and actionscript. In Silverlight, you can use User Controls in a similar way. Like Flash movieClips, Silverlight User Controls comes with their own XAML file (the visuals) and Code Behind file (the script).
So for the Silverlight example above, I created my stage like so:
<Canvas x:Name="parentCanvas"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="SilverlightDirtyDozen.Page;assembly=ClientBin/SilverlightDirtyDozen.dll"
Width="502"
Height="502"
>
<Canvas.Background>[…]</Canvas.Background>
<Rectangle Width="500" Height="500" Stroke="White" StrokeThickness="10" Canvas.Left="1" Canvas.Top="1">
<Rectangle.Fill>[…]</Rectangle.Fill>
</Rectangle>
<Canvas x:Name="circles" Width="480" Height="480" Canvas.Left="11" Canvas.Top="11" >
<Canvas.Clip>
<RectangleGeometry Rect="0, 0, 480, 480"/>
</Canvas.Clip>
</Canvas>
</Canvas>
The important element is the empty Canvas element toward the bottom named circles. This is where we will add our circle objects when the user clicks on the main stage.
The Rectangle isn’t important as far as what we’re doing here, it’s mainly aesthetic. I also added a clipping region to the empty circles canvas, purely for aesthetic reasons as well. A Clip object is a great, fast way to setup Opacity masks if you want to control render bounds.
Off of this main project in Visual Studio, I add a New Item > Silverlight User Control and name it DropCircle.xaml. I design it like so:
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="1" Height="1" >
<Ellipse x:Name="ellipse" Width="100" Height="100" Canvas.Left="-50" Canvas.Top="-50" Fill="#ccD5FCFC"
Stroke="#FFFFFF" StrokeThickness="2"/>
<Ellipse Width="6" Height="6" Canvas.Left="-3" Canvas.Top="-3" Fill="#FFFFFF"/>
<TextBlock x:Name="display" Width="100" Canvas.Top="6" Canvas.Left="0" Text="0,0" FontFamily="Arial"
FontWeight="Bold" FontSize="11" Foreground="#FFFFFF"/>
</Canvas>
Notice that for demonstration purposes, I left the Width and Height of the Use Control’s canvas to 1px, though the primary Ellipse object has a 100px diameter. I also cheated a little here and offset the Ellipse by setting its Left and Top properties to negative half its diameter. This will keep thing’s centered around the upper left corner of the Canvas. We’ll get to the TextBlock in a moment.
With just this much done, we can already get instances of DropCircle.xaml to added to the TestPage.xaml stage on left mouse button down by adding the following code to TestPage.xaml.cs:
namespace SilverlightDirtyDozen
{
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
this.MouseLeftButtonDown += new MouseEventHandler(Page_MouseLeftButtonDown);
}
void Page_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
DropCircle dc = new DropCircle();
dc.SetValue(Canvas.TopProperty, e.GetPosition(circles).Y);
dc.SetValue(Canvas.LeftProperty, e.GetPosition(circles).X);
circles.Children.Add(dc);
}
}
}
Since DropCircle and Page share the same namespace and are compiled into the same DLL, I can create new logical instances of DropCircle easily. However, that alone doesn’t visually display the object on the screen. I need to added it to some visual element already in the TestPage (including TestPage itself if I so wanted). In this case, I’m adding each DropCircle to my empty circles canvas I created earlier, so they’re all in one place. The reason for this will become more clear in a subsequent Dirty Dozen lesson.
Using the MouseEventArgs parameter e to get the position on the stage where the mouse clicked is a nice way to dynamically set the TopProperty and LeftProperty of our new object. But what if I also wanted to display that information inside the DropCircle itself? Here is where it gets a little more interesting.
The user control can easily be modified to expose various methods and properties, but the default User Control code needs a little fixing. Here’s how:
FrameworkElement lroot;
TextBlock display;
public DropCircle()
{
System.IO.Stream s = this.GetType().AssemblyGetManifestResourceStream(
"SilverlightDirtyDozen.DropCircle.xaml");
lroot = this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
display = lroot.FindName("display") as TextBlock;
}
You need to create a FrameworkElement as a self reference inside the user control if you want the class to be able to interacted with its own named XAML elements. Here, I created a FrameworkElement variable called lroot and had it catch a reference to itself at the point of creation. For any important xaml elements inside the UserControl I want it manipulate, I create variables of the appropriate type and create a reference to them by using lroot.FindName(). In this case, the TextBlock I named display in the XAML file is searched for by name off of the User Control and has a reference created for it and stored in a TextBlock variable I created also named display. I can now manipulate it through code.
Since I now have all the references I need, let’s keep adding to DropCircle.xaml.cs to create a function that will accept a string to display in the TextBlock:
public void setDisplay(string text)
{
display.Text = text;
display.SetValue(Canvas.LeftProperty, -display.ActualWidth/2);
}
Here, we have a public function that can accept a text string and will set and center the text inside by using the ActualWidth of the TextBlock once it’s Text property is changed.
Now, the last thing left to do is call this function from TestPage.xaml.cs when we add the DropDots. The final MouseLeftButton down event looks like this:
void Page_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
DropCircle dc = new DropCircle();
dc.SetValue(Canvas.TopProperty, e.GetPosition(circles).Y);
dc.SetValue(Canvas.LeftProperty, e.GetPosition(circles).X);
string location = e.GetPosition(circles).X.ToString() + " , " + e.GetPosition(circles).Y.ToString();
dc.setDisplay(location);
circles.Children.Add(dc);
}
That should do it. When TestPage.xaml is loaded, you can click on it and add a DropCircle dynamically and have it update its display programatically. This first lesson starts nice and easy, but it forms the initial step of many of the other tricks to come in building rich experiences in Silverlight.
But how do we get these dynamic User Controls to move both internally and as a collection? Dirty Dozen #02 will begin to address that with Dynamic Storyboards next.
The Silverlight Dirty Dozen - An Introduction
A couple years ago when I was teaching ActionScript programming, I came up with a series of lessons called the ActionScript Dirty Dozen. The general idea was thus. Most programming books take a very comprehensive, non biased approach to their subject matter (and well they should!). But many designers take a more holistic, intuitive approach to problem solving and pattern recognition. So once the basics are learned, there’s usually a gap of direction in terms of what to do next.
Now coding has the innate disposition toward precision; a cold, quantifiable efficiency. Design, on the other hand, has a this fuzzy, messy, highly subjective element to it, and many times the practitioners of either craft adopt a similar bent. The Dirty Dozen stems from the later, more designer focused inclination and takes a very personal and subjective approach to teaching user experience code. It takes the position: I may have hundreds of tools at my disposal, and those books may be a great reference for hundreds more, but the tips and tricks I used every day, the techniques that get grimy and gritty with use, are these twelve here...
So the next series of blog entries will present my current Dirty Dozen for Silverlight 1.1 projects. I’ll try and present these Dozen tips and tricks in growing order of complexity and often times they build upon each other.
Official Warning: This is very much a work in progress, if for no other reason than Silverlight 1.1 is still Alpha Bits and things can drastically change before the beta. I don’t know what the shelf life is on these, but right now they’re what I’m using all the time. The warning didn’t scare you away? Yeah, me neither. So let’s get cracking.