I am sure that many of you have read about Silverlight, the latest offering from Microsoft promising the ability to deliver rich interactive applications and media. Silverlight 1.1 Alpha has just been released and with this in mind, I decided to download all the tools required to start using Silverlight. To be honest, I have not spent much time looking into Silverlight 1.0; I just couldn't bring myself to start coding heavily in JavaScript. Version 1.1 gives the ability to perform the 'behind the scenes' coding in languages such as C# and VB.NET (plus others).
Having downloaded Silverlight 1.1, Visual Studio 2008 Beta 2 and Expression Blend, I started looking at development. One thing that becomes apparent very quickly is the lack of any form of controls, this is after all an Alpha release and I am sure this will change very soon. You can download the Silverlight 1.1 SDK which does provide controls such as a button, scrollbar, list box, etc. Ever the developer, I decided to have a go at implementing my own simple button control and this article is the result of my development work so far.
The Visual Studio solution file accompanying this article was produced using Visual Studio 2008 Beta 2. I am using the Standard Edition, which weighs in at around 700+ Mb as an ISO file. It is possible to use Visual Studio 2005 with a little tweaking of a solution file, information on how to do this is readily available on the net.
The Button Control
It is fair to say that this is a very simple control; it's not a particularly pretty looking button. The sole purpose of the exercise was to see how complex implementing your own control was. The button has a rollover effect and animates upon being clicked, this to me is good enough for a simple button.
Figure 1 - No roll over effect
Figure 2 - Roll over effect (notice the shade of blue around the edge)
There are two elements to this button, the XAML which defines the look of the button (rectangles and colours, etc) and the 'code behind' the XAML. I used Expression Blend to produce the XAML and then tweaked it by hand. I am not going to show the whole XAML for the button within the article as there is a fair amount of it, but I will be explaining the pertinent parts of it.
The XAML
I generated the layout of the button first, this obviously gave me something to work with when adding functionality, such as events, etc. Below is the XAML for defining the button rectangle.
<Rectangle Width="180" Height="34" RadiusX="5" RadiusY="5"
StrokeThickness="1" Stroke="#FF0E0E0E" x:Name="ButtonRectangle" >
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,0" StartPoint="0,1">
<GradientStop Color="#00909194" Offset="0"/>
<GradientStop Color="#FF979391" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
This defines the button outline and gradient fills the rectangle gray. The edges of the rectangle are also rounded.
The next thing we need for our button is text. Text is displayed using a <TextBlock>. The XAML for this is as follows:
<TextBlock x:Name="ButtonText" Canvas.Left="8" Canvas.Top="5"
Foreground="#FF101010" Text="Button" FontFamily="Arial" FontWeight="Normal"
FontSize="12" />
In this version of the button, I have hardcoded the font type and size, but ideally this would be set using properties. The layout of the text on the button surface is performed in code. When the button is clicked, to simulate the button looking like it has been pressed, the X and Y position is adjusted. To adjust the position, I am applying a transform. The XAML for this is as follows:
<Canvas.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="buttonPress" X="0" Y="0" />
</TransformGroup>
</Canvas.RenderTransform>
As it stands, this transform is doing nothing as X and Y are set to 0. These values are changed programmatically to simulate the press, more on this later.
The Roll over effect
As you saw in figure 2 above, when the mouse pointer is over the button, the edges of the button glow blue. This is very simple to achieve using two rectangles similar to the button surface. Each rectangle is of a slightly smaller size. The two rectangles are defined within their own Canvas. This time the opacity is set as essentially the rectangles are placed over the top of our text. You will notice in the Canvas tag, the Visibility is set to collapsed. We only want the glow to be visible when the mouse pointer is over the button. The glow visibility is changed within code.
Our XAML for the glow effect is:
<Canvas x:Name="MouseOver" Canvas.Top="0" Canvas.Left="0" Width="180"
Height="34" Visibility="Collapsed">
<Rectangle Width="178" Height="32" Stroke="#FFB0DAEE" Canvas.Top="1"
Canvas.Left="1" Opacity=".5" x:Name="MouseOverRectangle1" RadiusX="5"
RadiusY="5" >
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,0" StartPoint="0,1">
<GradientStop Color="#00909194" Offset="0"/>
<GradientStop Color="#FF979391" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Width="176" Height="30" Stroke="#FFB0DAEE" Canvas.Top="2"
Canvas.Left="2" Opacity=".5" x:Name="MouseOverRectangle2" RadiusX="5"
RadiusY="5" >
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,0" StartPoint="0,1">
<GradientStop Color="#00909194" Offset="0"/>
<GradientStop Color="#FF979391" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>
As you can see, the rectangles are almost exactly the same as the button surface. The complete XAML for the button can be found in the MyControl project in MyButton.xaml.
The control code
Now we come on to the C# code for the button. Anyone at ease with writing web custom controls, etc will feel quite at home with coding Silverlight controls. All controls inherit from Control. To ease the creation of a control, within your Visual Studio solution, create a new Silverlight class and then to that project, right click on the project and select Add => New Item and then select Silverlight User Control. This generates boilerplate XAML and a class enabling you to add your code.
As with all controls, we will be needing properties; our properties will be to set the width and height of the control and also the text appearing on the button. To make the button worthwhile, we will also need to be able to respond to a click event, more on this a little later. Now let's get on with some code.
Within the class, I have defined some private variables and one public event. Our private variables allow us to address individual elements within the XAML. The first two lines of code within the constructor are generated for us by Visual Studio, these lines of code basically load in the XAML for the control from the assembly. The second line of code that defines actualControl allows us to reference the control and to set references to each of the elements in our XAML. For example, doing the following:
buttonRectangle = actualControl.FindName("ButtonRectangle") as Rectangle;
Will allow us to access the attributes of our button surface.
The rest of the code sets up event handlers for handling the mouse events for our button. A call to UpdateButton is then made. let's take a look at UpdateButton.
buttonRectangle = actualControl.FindName("ButtonRectangle") as Rectangle;
mouseOverRectangle1 = actualControl.FindName("MouseOverRectangle1") as Rectangle;
mouseOverRectangle2 = actualControl.FindName("MouseOverRectangle2") as Rectangle;
mouseOverCanvas = actualControl.FindName("MouseOver") as Canvas;
TextBlock btnText = actualControl.FindName("ButtonText") as TextBlock;
buttonRectangle.Width = _width;
buttonRectangle.Height = _height;
mouseOverRectangle1.Width = _width - 2;
mouseOverRectangle1.Height = _height - 2;
mouseOverRectangle2.Width = _width - 4;
mouseOverRectangle2.Height = _height - 4;
btnText.Text = _buttonText;
double left = (buttonRectangle.Width - btnText.ActualWidth) / 2;
double top = (buttonRectangle.Height - btnText.ActualHeight) / 2;
btnText.SetValue<double>(Canvas.TopProperty, top);
btnText.SetValue<double>(Canvas.LeftProperty, left);
The first 5 lines of code assign references to individual elements defined within the XAML. After this, we are then setting the width and height of the button surface rectangle and the 2 mouse over rectangles, also not forgetting setting the text we want displayed on the button. We then perform some simple maths to centre the text onto the button surface.
A note on inherited Properties
This class implements the properties Width and Height, so to use these in our control should be a simple matter of doing the following:
public new double Width
{
get { . . . }
set { . . . }
}
And in reality it is, but there is a bug in the current Alpha release. If you create an instance of your control in XAML and set the width, it will not be set. If you set the width in code, it works fine, when we come to write code to test our control, I will show you exactly the impact this has. To overcome the problem of setting the buttons text within XAML, I have created the property ButtonText which will allow the buttons text to be defined. Note that within the setter for all the properties, the UpdateButton method is being called; this is because different attributes of the button are being set and we want this reflected in the rendered button.
Mouse events
At the beginning of the article, I mentioned that the control declares a public event called Click. The reason I have done this is because I want to emulate the way controls work in general and 99% of controls have a click event. There is also another reason for this. Within Silverlight, there are two events that are fired when a mouse button is clicked, these are MouseLeftButtonDown and MouseLeftButtonUp. Neither of these two events, on their own, gives the desired click behaviour. What I want to achieve with the Click event is to check if the mouse pointer is over the button when the left button is released. Two other mouse events come into play here, these being MouseEnter and MouseLeave. Let's take a look at what each of these events is doing.
MouseEnter
Within the code, there is a Boolean variable called isPressed. When the left mouse button is pressed this is set to true. When the mouse pointer first enters the button surface we set this variable to false. As we are within the button surface, we want to make the button glow; basically we want to switch on our glow effect. To do this, we simply make the MouseOver canvas visible, by setting the Visibility attribute to Visibility.Visible. That is all that happens within this event.
MouseLeave
We set isPressed to false. Even if the left mouse button is being held down, if we leave the surface of the button, we do not want the Click event to fire when the button is released, as we are not over the button. We set the MouseOver canvas to Visibility.Collapsed, which hides the canvas. Remember at the beginning of the article we declared a transform within the XMAL which would enable us to animate the button? Well when the mouse leaves the button surface, we want to remove this animation; we simply set the X and Y values of the transform to 0, thus setting the button back to its original position. Even if the button hasn't been pressed and it's still in its original position, we may as well execute this code.
MouseLeftButtonDown
We set isPressed to true, as the mouse button has been pressed. We also increment the X and Y position of the button surface by 2, simulating the button being pressed.
MouseLeftButtonUp
This event holds the key to a correct click event. First we remove the transform on the button as we want it to revert back to its original position. We then check if the button isPressed and the Click event has an event handler 'wired up' to it. If these two conditions are true, we execute the click. We then set isPressed to false.
Testing the control
Within the Visual Studio solution file supplied with this article, there is the project for the actual button and also a project that allows us to test the control. This project was created using the Silverlight Project template. In order to use our new control, there are a couple of things we need to do. First we need to add a reference to our button control; within our test application, we need to right click on the References tree node and when the Add Reference dialog pops up, select the Project tab and select our button project from the list.
When the project was created, some boilerplate XAML was created for the project. This XAML needs to be amended in order to support our control. We need to add an xmlns tag to our XAML to reference the assembly of our control. This takes the form of:
xmlns:MyControls="clr-namespace:MyButton;assembly=ClientBin/MyButton.dll"
With the addition of the above line, the XAML should now look like:
<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="MyControl.Page;assembly=ClientBin/MyControl.dll"
xmlns:MyControls="clr-namespace:MyButton;assembly=ClientBin/MyButton.dll"
Width="640"
Height="480"
Background="White">
</Canvas>
We can now add an instance of our button to the above XAML, the mark-up for our button is:
<MyControls:Button x:Name="Button1" Canvas.Left="20" Canvas.Top="200" />
This mark-up is placed between the Canvas tag. As with our control, there is an associated C# file where we can manipulate our control programmatically. Within code, we will set the text to be displayed on the button and set its width and height. We will also wire up an event to respond to clicking the button.
Our source code for this is as follows:
Canvas cc = o as Canvas;
MyButton.Button btn = (MyButton.Button)cc.FindName("Button1");
btn.ButtonText = "Press Me!!";
btn.Width = 140;
btn.Height = 35;
btn.Click += delegate(object sender, EventArgs ea)
{
btn.ButtonText = "Button Pressed!";
};
There shouldn't be a great many surprises in the code above. Basically we are setting the text, width and height of the button and wiring-up the Click event. I have wired-up the event the easy way, but it could also have been done as follows:
btn.Click += new EventHandler(btn_Click);
. . .
private void btn_Click(object sender, EventArgs ea)
{
. . .
}
I did mention the problem with adding inherited properties to the XAML for the button. As an example, the code above sets the width to 140, if I added Width="140" to the XAML, the button would not have rendered correctly. I have blogged about this and an entry can be read on my blog with a link to where I found the information. I am sure this problem will be ironed out in future releases.