Click here to Skip to main content
15,890,043 members
Articles / Desktop Programming / WPF

Silverlight 2.0 Components Development

Rate me:
Please Sign up or sign in to vote.
3.62/5 (7 votes)
24 Apr 2008CPOL7 min read 44K   608   23   7
This article has been initiated by some knowledge that we wanted to share based on our Silverlight experiences from one of the projects that we made.

Creating Components using WPF

Silverlight is a perspective platform for web solutions. Microsoft has been actively developing it, trying to move Macromedia Flash aside from its current position. In the internet, there is plenty of data containing examples based on Silverlight, which indeed look impressive. However, unfortunately it is really hard to say anything specific on effort involved as well as possible pitfalls. As an object for our experiments, we have selected a control for setting a value in a specific numeric range. What we have finally achieved, you will know a bit later as well as you will learn about the difficulties and pitfalls.

So, let’s formulate the task. The control should provide for:

  1. Selecting a value from a possible numeric range
  2. Displaying the selected value
  3. Displaying the range of possible numeric values
  4. Catching graphics design.

As for example, the control may look like this:

Main Classes

We should have to be able to define a current value for which the property Value would be responsible. Also, we should know a maximal and minimal value and increment - MinValue, MaxValue, Step. It would be nice to have an option to regulate the positions where the scale begins and ends which will be set by angles - StartAngle, SweepAngle. To set up the frame, scale and other items, we will be using a template.

First, we create the basic class Indicator with minimum required set of features: MinValue, MaxValue, Step. We inherit it from the class Control. Then we create the class CircularIndicator as a successor of the class Indicator and define the following properties in it: StartAngle, SweepAngle, PointerAngle. PointerAngle will be used for binding. Here we will also define the handler for the mouse events; we should react to the mouse clicks, movements to change the position of the pointer.

Here we should point out that in Silverlight 2.0, it is not feasible to define the following methods as OnMouseEnter, OnMouseMove, etc. Instead, we have to subscribe for the corresponding events, which is somewhat unusual, however not causing any inconvenience.

The formula to calculate PointerAngle is relatively simple:

C#
StartAngle + Value * SweepAngle / (MaxValue - MinValue)

That is practically all, so we have to only add events for changing the properties. But, there is one trick here. The thing is, that unlike WPF, the Dependency property in Silverlight 2.0 allows pointing at only 4 parameters which is obviously not enough. An option to set metadata is missing or, for example, callback function, which would be nice to have in our case, to check properties (CoerceValueCallback).

Creating a Panel for Scale Representation

Let’s move to the visual part. And the first one which we come across is the way to display the scale. It is clear that a panel can be created, 15 rectangles placed in there as hairlines set at an angle. However, this is not an attractive solution. It would be more easy and elegant to define a class successor of the class Panel, let’s call it CircularElementPanel, and through it define the number, elements template, initial and final angles and the circle radius. This would allow creating the elements automatically. Obviously, the class will fit for creating captions for scales as well, which only require adding StartValue and EndValue. Nevertheless, it is not that easy since we should also get bound to something in the template to display a value.

We have done it this way:

First we created class DataField.

C#
public class DataField
{
private object value = null;

public object ElementValue
{
get { return this.value; }
set { this.value = value; }
} 
public DataField(object value)
{
this.value = value;
}
}  

When creating a separate element, the object DataField will be assigned to its property DataContext. A sample code is provided below:

C#
private static void RecreateElements(CircularElementsPanel panel)
{
DataTemplate elementTemplate = panel.ElementsTemplate;
if (elementTemplate == null) return; 
double value = panel.StartValue;
double valueStep = (panel.EndValue - panel.StartValue) / (panel.ElementsCount - 1); 
panel.Children.Clear(); 
for (int i = 0; i < panel.ElementsCount; i++)
{
UIElement element = (UIElement)panel.ElementsTemplate.LoadContent(); 
if (element is FrameworkElement) 
{
((FrameworkElement)element).DataContext = new DataField(value + i * valueStep);
}
panel.Children.Add(element);
}
} 

Then in the template of the element, a bind can be created as shown below:

XML
<c:CircularElementsPanel.ElementsTemplate>
   <DataTemplate>
       <TextBlock Width="50? TextAlignment="Center" Text="{Binding ElementValue}"/>
   </DataTemplate>
</c:CircularElementsPanel.ElementsTemplate>

Will these elements be positioned along the circle? In order to achieve such an effect, we need to predefine methods ArrangeOverride and MeasureOverride on our panel. We have simplified it a bit – use CodeProject PolarPanel where these methods are predefined and inherited from it. Despite the fact that it has been written for WPF there’re no issues with it, the only thing that needs reworking is property registration. However, we should point out that the panel contains two more Attachable properties – angle and radius. The Radius will be set as the most possible (considering the final panel size), and the angle will be defined in the method RecreateElements. Considering the latest changes, the function looks like this:

C#
private static void RecreateElements(CircularElementsPanel panel)
{
DataTemplate elementTemplate = panel.ElementsTemplate; 
if (elementTemplate == null) return; 
double angle = panel.StartAngle;
double angleStep = panel.SweepAngle / (panel.ElementsCount - 1); 
double value = panel.StartValue;
double valueStep = (panel.EndValue - panel.StartValue) / (panel.ElementsCount - 1); 
panel.Children.Clear(); 
for (int i = 0; i < panel.ElementsCount; i++)
{
UIElement element = (UIElement)panel.ElementsTemplate.LoadContent();
SetRadius(element, panel.ElementsRadius);
SetAngle(element, angle + i * angleStep);
if (element is FrameworkElement)
{    
((FrameworkElement)element).DataContext = new DataField(value + i * valueStep);
}
panel.Children.Add(element);
}    
} 

And finally, let’s create another class – CircularGauge or Gauge. In this class, there’s only one property – an angle the knob is turned at. When changing the property, we apply RotateTransform.

Creating Control Template

Below is the full template CircularIndicator. This should help in resolving issues described further.

XML
<ControlTemplate x:Key="Indicator" TargetType ="w:CircularIndicator">
            <Grid x:Name="LayoutRoot" Margin="0?>
                <Rectangle RadiusX="5? RadiusY="5? Margin="0? Stroke="Black">
                    <Rectangle.Fill>
                        <LinearGradientBrush StartPoint="0,0? EndPoint="0,1?>
                            <LinearGradientBrush.GradientStops>
                                <GradientStop Color="Lavender" Offset="0?/>
                                <GradientStop Color="Gray" Offset="0.1?/>
                                <GradientStop Color="LightGray" Offset="0.5?/>
                                <GradientStop Color="Gray" Offset="0.9?/>
                                <GradientStop Color="Lavender" Offset="1?/>
                            </LinearGradientBrush.GradientStops>
                        </LinearGradientBrush>
                    </Rectangle.Fill>
                </Rectangle>
                <c:CircularElementsPanel x:Name="ScaleLabels"
StartValue="{TemplateBinding MinValue}" 
EndValue="{TemplateBinding MaxValue}" Margin="50?
StartAngle="{TemplateBinding StartAngle}"
SweepAngle="{TemplateBinding SweepAngle}"
ElementsCount="{TemplateBinding ValuesCount}"
RotateElements="False">
                    <c:CircularElementsPanel.ElementsTemplate>
                        <DataTemplate>
                            <TextBlock Width="50? TextAlignment="Center"
Text="{Binding ElementValue}"/>
                        </DataTemplate>
                    </c:CircularElementsPanel.ElementsTemplate>
                </c:CircularElementsPanel>
                <c:CircularElementsPanel StartValue="{TemplateBinding MinValue}"
EndValue="{TemplateBinding MaxValue}"
Margin="80? StartAngle="{TemplateBinding StartAngle}"
SweepAngle="{TemplateBinding SweepAngle}"
ElementsCount="{TemplateBinding ValuesCount}"
RotateElements="True">
                    <c:CircularElementsPanel.ElementsTemplate>
                        <DataTemplate>
                            <Rectangle Fill="Black" Width="5? Height="2?/>
                        </DataTemplate>
                    </c:CircularElementsPanel.ElementsTemplate>
                </c:CircularElementsPanel>
                <w:CircularGauge Margin="90? Template="{StaticResource Gauge}"
Angle="{TemplateBinding PointerAngle}"/> 
</Grid>
</ControlTemplate> 

First we can see is the background layer with gradient fill, further are the scale and its captions and then Gauge. Please notice the number of scale marks we bind to the property ValuesCount. It can be easily calculated:

C#
((MaxValue - MinValue) / Step) + 1;

Assignment of Binding's Expressions

It would be nice to bind the number of captions and scale marks with ValuesCount, however, there’s no such possibility. Honestly, we still cannot believe that hours that we have spent on searching for a solution to set expressions to bind type a*b have not lead us to any result.

The second item that we would like to draw your attention to is Gauge. What is it for, you may ask. In fact, its template consists of two ellipses. Why not creating RotateTransform with a value of an angle bound to PointerAngle property value in the template? However, we somehow cannot do this. Though the expression Angle = “{TemplateBinding PointerAngle}” does not cause an error at compilation, it does not provide for any positive result too. Why? That’s still a mystery.

Therefore, it is the reason for creating the class CircularGauge. It is also worth noticing that in WPF, we can easily solve this task using the property RelativeSource from Binding, however in Silverlight 2.0 it is missing. We would like to hope that it would show up in later versions since without it, anything substantial is hard to be created.

Also, the presence of RelativeSource would allow for bypassing the issue of setting an expression for binding. The Converter could have been passed parameterized by some object. The object would define the operation and one of the operands; the second operand would have been defined by the bind field. Unfortunately, there is no such property as Converter in the TemplateBinding, therefore the issue cannot be resolved by using it.

Animation

And finally, the third aspect, which is animation. If in WPF we just can create a set of triggers in the control template, let’s say on mouse pointing, and from there starting animation (Storyboard) however here is another approach. With the help of the attribute TemplatePart, we define the name and type of the elements which should be included into the control template of the class. Here, we can set the name Storyboard used at animation and the name of the element where in the resources the Storyboard is stored.

C#
[TemplatePart(Name = "RootElement",
    Type = typeof(FrameworkElement))] [TemplatePart(Name = "Normal State",
    Type = typeof(Storyboard))] 

Further, in the constructor, we subscribe to the events MouseEnter and MouseLeave and create the following handlers:

C#
void CircularGauge_MouseLeave(object sender, MouseEventArgs e)
{
    FrameworkElement panel = this.GetTemplateChild("RootElement") as FrameworkElement;
    (panel.Resources["MouseOver State"] as Storyboard).Stop();
}
void CircularGauge_MouseEnter(object sender, MouseEventArgs e)
{
FrameworkElement panel = this.GetTemplateChild("RootElement") as FrameworkElement;
(panel.Resources["MouseOver State"] as Storyboard).Begin();
}

As a result, when we point at the control with the mouse, animation starts, and when we take the mouse off, animation stops. Animation in our example is implemented on the Gauge. The Storyboard then looks this way:

XML
<Storyboard x:Key=’MouseOver State’>
<ColorAnimation Storyboard.TargetName=’Stop’ Storyboard.TargetProperty=’Color’
    To=’White’ Duration="0:0:0.5? AutoReverse=’False’/>
</Storyboard>

Summary

It should also be noticed that despite all of the glitches and difficulties when developing in Silverlight, this could definitely be considered as a huge step forward in interactive, user-friendly and good-looking web interface. Of course, the technology is only growing, however the developer’s instruments are still not yet debugged (to almost all errors the environment responds with one and the same message all the time, often falls into infinity cycles), there are no or almost absent custom options from WPF (triggers in templates, Bindings, animation). However, it has become easier to create management elements for Web, besides Silverlight technology allows for drawing a distinctive line between programming and graphics design.

History

  • 25th April, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Enterra Inc.
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralI will be grateful if you can added sample Pin
DVsevolod26-Apr-08 0:05
DVsevolod26-Apr-08 0:05 
GeneralRe: I will be grateful if you can added sample Pin
Enterra27-Apr-08 19:12
Enterra27-Apr-08 19:12 
GeneralRe: I will be grateful if you can added sample Pin
Sean Ewington28-Apr-08 5:50
staffSean Ewington28-Apr-08 5:50 
GeneralNo source code Pin
leppie24-Apr-08 23:49
leppie24-Apr-08 23:49 
GeneralRe: No source code Pin
Enterra27-Apr-08 19:13
Enterra27-Apr-08 19:13 
GeneralNo Section Headings Pin
#realJSOP24-Apr-08 23:34
mve#realJSOP24-Apr-08 23:34 
GeneralRe: No Section Headings Pin
Enterra27-Apr-08 19:13
Enterra27-Apr-08 19:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.