This article is compatible with the latest version of Silverlight.
1. Introduction
I have been trying to get myself up to speed with the latest Microsoft technologies and more specially .NET 4,VS.NET 2008 and Silverlight/WPF. So rather than just ‘playing’ I have set myself a little application to write: a simple analog Silverlight clock.
It is extremely simple application that I have been able to complete the first version of it just for few days (working only in my spare time, if I had any). The target of that article is to introduce some basic concepts of programming in Silverlight, such as: drawing simple elements, using styles, making simple transformation, as well as introducing some of the most important and powerful approaches in object oriented programming.
Source code
2. Application Design
Before starting to implement my application I had some ideas in my mind for future development and growth of the application, so I decided to make a solid base - appropriate time to practice my OOP skills. After careful consideration I chose to separate my business logic from the user interface. Thus, in future I will be able to easily extend the application, without making any unnecessary changes. There are lots of well-known approaches and one of them is the classical Model – View – Presenter (MVP).
2.1 Introducing the Model - View - Presenter (MVP) pattern
There are tons of articles in the Internet about MVP. So, my target is not to reinvent the wheel but to represent the pattern in my words, to represent it in a way I understand it.
The MVP pattern is an OOP pattern which separates the User Interface (the view) from the business logic (the model). The pattern separates the responsibilities across four components, each one has only one responsibility, thus confirming the most basic principle of Single Responsibility (SRP) – ‘A class should have only one reason to change’. The SRP principle is one of the simples of the principles but one of the most difficult to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities is much of what software design is really about. That’s why the main target of MVP is confirming the Single – Responsibility principle.
-
The view is responsible only for rendering the UI elements
-
The model is responsible for business behaviors and state management
-
The presenter is responsible for interacting between the view and the model
-
The view interface is used to loosely couple the presenter from its view
The common workflow of the pattern is shown on the next diagram.
The user interacts with the User Interface. One makes an action and as a result the view fires events. The presenter handles them and updates the model.
When the model is updated, the view also has to be updated to reflect the changes. View updates can be handled in several ways. The Model – View – Presenters variants, Passive View and Supervising Controller, specify different approaches to implement view updates. For my application I have been using the first one. What are the differences?
In Passive View, the presenter updates the view to reflect changes in the model. The interaction with the model is handled exclusively by the presenter; the view is not aware of changes in the model.
In Supervising Controller, the view interacts directly with the model to perform simple data – binding that can be defined declaratively, without presenter intervention.
The next figure illustrates the logical view of the Passive View and Supervising Controller variants.
The key benefits of the MVP are obvious:
-
Loose Coupling – the presenter is intermediary between the view and the model
-
Clear separation of responsibilities (confirming the SRP principle)
-
Code reuse – separation of the view and model will increase the code reusability. In fact that is the main target (the heart) of object oriented programming
-
Flexibility and extendibility – your code is more adaptable to changes
The key drawbacks are:
Without appropriate design often the presenter becomes too complex
2.2 Silverlight Clock Design
The basic design of our clock is shown on the next figure.
2.2.1 Clock Model
First I will start with making one class and one interface – ClockData and IClockModel. Both types are in fact the hearts of the model component in the MVP pattern.
The ClockData class is used to transfer data from the model to the presenter and respectively to the view. The class diagram for the ClockData class can be shown on the next figure.
Note: For those who plan to use the ClockData object for data binding must implement the INotifyPropertyChanged interface.
The IClockModel is the interface for our model. In the classical aspect of the MVP pattern there is no additional interface which the model must implement, but in the future I plan to extend my application to something like stopwatch or countdown timer. So that part of the program will suffer frequent changes from now on, and it is better to depend on abstraction rather than on details.
public interface IClockModel
{
event EventHandler<ClockDataEventArgs> ClockChanging;
event EventHandler<ClockDataEventArgs> ClockChanged;
void SetClockInterval( TimeSpan timeSpan );
IClockModel Start();
IClockModel Continue();
IClockModel Stop();
ClockData ClockData { get; set; }
bool IsRunning { get; }
}
2.2.2 Clock View
As I already mentioned, the main target of the view interface is to loosely couple the presenter from its view. The interface design is shown on the next figure. It contains one method which responsibility is to update view. In our application I chose to use the passive view variation of the MVP, so the presenter must be responsible to update the view. That’s why I exposed in the interface the method Update whose exact objective is to update the view.
As you can guess the exposed events are absolutely useless yet, but in the future they will come into play.
2.2.3 Clock Presenter
The only responsibility of the presenter is to be a mediator between the view and the model. Its structure is shown on the next figure.
3. Application Implementation
Each clock and each one of its element is a graphical object. So we can use a grid for top layout root.
<Grid x:Name="LayoutRoot" Background="White">
<!-- ClockFace -->
<!-- Markers -->
<!-- Hands -->
</Grid>
3.1 Drawing the Clock Face
First, I will start with the clock background. I want a circle with a graded fill from top to bottom. I also want our clock to have a nice border for better feeling.
<Canvas x:Name="ClockFaceCanvas" Width="200" Height="200">
<Ellipse x:Name="ClockFaceEllipse" Style="{StaticResource ClockFace}"
Canvas.Left="0" Canvas.Top="0" Width="200" Height="200">
</Ellipse>
</Canvas>
Where the ClockFace is a style defined in the App.xaml file:
<Style x:Key="ClockFace" TargetType="Ellipse">
<Setter Property="StrokeThickness" Value="6"/>
<Setter Property="Fill">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Offset="0.0" Color="Black"/>
<GradientStop Offset="1.0" Color="White"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Stroke">
<Setter.Value>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="White" />
<GradientStop Offset="1.0" Color="Black" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
So we now have:
That is the easy bit done. Markers are the next.
3.2 Drawing the Clock Markers
There are two possible approaches for drawing the clock markers. The first one is to use the main idea of Silverlight/WPF: to include the graphical elements in the XAML, for the little markers around the edge that would be silly – dozens of nearly identical elements says “loop” to me and for that we need to code them. First I will define an additional canvas which will be the root of the markers:
<Canvas x:Name="MarkerCanvas" Width="200" Height="200"/>
Next, lets draw them programmatically. In fact, the markers are just rectangles; to position them I just put them at the top – center of the canvas and rotate around the center.
private void DrawMarkers()
{
MarkerCanvas.Children.Clear();
for ( int i = 0; i < 60; ++i )
{
Rectangle markerRectangle = new Rectangle();
if ( i % 5 == 0 )
{
markerRectangle.Width = 3;
markerRectangle.Height = 8;
markerRectangle.Fill = new SolidColorBrush( Colors.White );
markerRectangle.RenderTransform = this.CreateTransformGroup( i * 6,
-( markerRectangle.Width / 2 ),
-( markerRectangle.Height * 2 + 4.5 - ClockFaceEllipse.StrokeThickness / 2 - this.clockHeight / 2 ) );
}
else
{
markerRectangle.Width = 1;
markerRectangle.Height = 4;
markerRectangle.Fill = new SolidColorBrush( Colors.White );
markerRectangle.RenderTransform = this.CreateTransformGroup( i * 6,
-( markerRectangle.Width / 2 ),
-( markerRectangle.Height * 2 + 12.75 - ClockFaceEllipse.StrokeThickness / 2 - this.clockHeight / 2 ) );
}
MarkerCanvas.Children.Add( markerRectangle );
}
}
The CreateTransformGroup method is a help function that creates a transform group for each marker based on the angle that each marker must use to rotate them around the clock center.
private TransformGroup CreateTransformGroup( double angle, double firstTranslateXValue,
double firstTranslateYValue )
{
TransformGroup transformGroup = new TransformGroup();
TranslateTransform firstTranslate = new TranslateTransform();
firstTranslate.X = firstTranslateXValue;
firstTranslate.Y = firstTranslateYValue;
transformGroup.Children.Add( firstTranslate );
RotateTransform rotateTransform = new RotateTransform();
rotateTransform.Angle = angle;
transformGroup.Children.Add( rotateTransform );
TranslateTransform secondTranslate = new TranslateTransform();
secondTranslate.X = this.clockWidth / 2;
secondTranslate.Y = this.clockHeight / 2;
transformGroup.Children.Add( secondTranslate );
return transformGroup;
}
Now we have the basic background.
As you can see, everything which can be done in the XAML, can also be done with procedural code.
3.3 Drawing the Clock Hands
Next we should draw the clock hands. Each hand is a rectangle rounded off a little on the corners. Again two approaches come to my mind. The first is the tricky one: just to draw the clock hands in the XAML and bind the Angle property of their RotateTransform property element to a custom property in my business object. The reason I didn't use that approach is explained in the conclusion.The classical solution is just to draw the hands with procedural code:
<Canvas x:Name="ClockHandsCanvas" Width="200" Height="200"/>
private void DrawSecondsHand( ClockData clockData )
{
if ( secondsHand != null && ClockHandsCanvas.Children.Contains( secondsHand ) )
ClockHandsCanvas.Children.Remove( secondsHand );
secondsHand = this.CreateHand( this.secondsHandWidth, this.secondsHandHeight,
new SolidColorBrush( Colors.Black ), 0, 0, 0, null );
secondsHand.RenderTransform = this.CreateTransformGroup(
clockData.Seconds * 6,
-this.secondsHandWidth / 2,
-this.secondsHandHeight + 4.25 );
if ( ClockHandsCanvas.Children.Contains( secondsHand ) == false )
ClockHandsCanvas.Children.Add( secondsHand );
}
The other two functions for drawing the Minute and Hour hands are absolutely equivalent. And the result is:
3.4 Implementing the View
Our analog clock must implement the IView interface which offers one method and several events. The only thing that method must do is just to redraw the clock hands:
public void Update( ClockData clockData )
{
this.DrawSecondsHand( clockData );
this.DrawMinutesHand( clockData );
this.DrawHoursHand( clockData );
}
3.5 Implementing the Model
The clock model must implement the IClockModel interface. For better convenience I have created a base abstract class named ClockBase:
public abstract class ClockBase : IClockModel
{
private ClockData clockData;
protected bool isRunning;
protected DispatcherTimer timer;
public event EventHandler<ClockDataEventArgs> ClockChanging;
public event EventHandler<ClockDataEventArgs> ClockChanged;
public ClockBase()
{
clockData = new ClockData();
timer = new DispatcherTimer();
timer.Tick += new EventHandler( timer_Tick );
this.SetClockInterval( new TimeSpan( 0, 0, 0, 0, 1 ) );
}
public virtual void SetClockInterval( TimeSpan timeSpan )
{
timer.Interval = timeSpan;
}
public ClockData ClockData
{
get
{
return this.clockData;
}
set
{
this.OnClockChangind( new ClockDataEventArgs( this.clockData ) );
this.clockData = value;
this.OnClockChanged( new ClockDataEventArgs( this.clockData ) );
}
}
public bool IsRunning
{
get
{
return this.isRunning;
}
}
public abstract IClockModel Start();
public abstract IClockModel Stop();
public abstract IClockModel Continue();
protected abstract void UpdateClockData( DateTime dateTime );
protected virtual void OnClockChangind( ClockDataEventArgs e )
{
EventHandler<ClockDataEventArgs> temp = this.ClockChanging;
if ( temp != null )
temp( this, e );
}
protected virtual void OnClockChanged( ClockDataEventArgs e )
{
EventHandler<ClockDataEventArgs> temp = this.ClockChanged;
if ( temp != null )
temp( this, e );
}
private void timer_Tick( object sender, EventArgs e )
{
this.OnClockChangind( new ClockDataEventArgs( this.ClockData ) );
this.UpdateClockData( DateTime.Now );
this.OnClockChanged( new ClockDataEventArgs( this.ClockData ) );
}
The ClockModel class inherits the abstract base class and overrides the needed methods.
public class Clock : ClockBase
{
public static IClockModel Create()
{
return new Clock();
}
public override IClockModel Start()
{
if ( IsRunning )
return this;
timer.Start();
isRunning = true;
return this;
}
public override IClockModel Stop()
{
timer.Stop();
isRunning = false;
return this;
}
public override IClockModel Continue()
{
return this.Start();
}
protected override void UpdateClockData( DateTime dateTime )
{
this.ClockData.Update( dateTime );
}
}
3.6 Implementing the Presenter
The key point of out presenter are:
- It has references to the View and the Model. It is responsible for handling the events from the View and updating the Model.
- The callback is just a method. The purpose of the delegate mechanism is to chain the Update method of the View.
public class ClockPresenter
{
public Callback Response;
public ClockPresenter( IClockView clockView, IClockModel clockModel )
{
this.ClockView = clockView;
this.ClockModel = clockModel;
this.ClockView.StartClock += new EventHandler( ClockView_StartClock );
this.ClockView.StopClock += new EventHandler( ClockView_StopClock );
this.ClockModel.ClockChanged += new EventHandler<ClockDataEventArgs>( ClockModel_ClockChanged );
}
public IClockView ClockView
{
get;
set;
}
public IClockModel ClockModel
{
get;
set;
}
private void ClockView_StopClock( object sender, EventArgs e )
{
this.ClockModel.Stop();
}
private void ClockView_StartClock( object sender, EventArgs e )
{
this.ClockModel.Start();
}
private void ClockModel_ClockChanged( object sender, ClockDataEventArgs e )
{
Callback temp = this.Response;
if ( temp != null )
temp( e.ClockData );
}
}
3.7 Testing the Application
IClockModel clockModel = Clock.Create();
clockModel.ClockData = new ClockData().Update( DateTime.Now );
ClockPresenter presenter = new ClockPresenter( this.AnalogClockControl, clockModel );
presenter.Response += new Callback( this.AnalogClockControl.Update );
clockModel.Start();
4. Conclusion
4.1 Why not using the power of data binding?
The initial idea when start creating my first real Silverlight project was to draw all objects through the XAML, and to use procedural code for my business logic. The general purpose of XAML is a declarative programming language suitable for constructing .NET user interface, isn’t it?
Everything was perfect till the moment when I decided that the ClockData object should implement the INotifyPropertyChanged interface and to use something like this in XAML:
<Rectangle Width="8" Height="36" Fill="White" RadiusX="2" RadiusY="2">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Seconds}"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
When I started the application, an error occurred. After a serious investigation that took me few hours, I found the answer - Silverlight is not allowed to bind to an object which is not a FrameworkElement. While in WPF this is allowed. I think that is a lame limitation and it is too strange for me.