Friday, January 20, 2012

Creating a basic Silverlight control–DateItem

When you first start working in Silverlight every control you use is something basic and sometimes you just want more then the basic controls.
The good people of Microsoft allowed us to create custom controls so we can do nearly anything in Silverlight to have great interaction with the user,
thus increasing user experience.
Today I’ll show you one of the first controls I ever made, It was for a small website that needed a nice way to show the date.
They kind of wanted a small calendar style icon.
The result was as following:
DateItem
In XAML I didn’t have to do more then(NL-BE date format):
<ctrl:DateItem Date="01/02/2012" />
To start of create a new “Silverlight Class Library” and call it for example “CustomControlLibrary.DateItem”:

Project 
Rename the Class1.cs file to DateItem.cs ( don’t forget to rename the class itself! ).
Add a new folder called “Themes” and add a new “Silverlight Resource Dictionary” to the folder. Rename that to “generic.xaml”.
This will automatically be recognized by Silverlight.
Open the generic.xaml file and paste the following code inside:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControlLibrary.DateItem">
</ResourceDictionary>
Make sure the xmls:local is referring to your own namespace, shouldn’t you have chosen to make it CustomControlLibrary.DateItem.
We have now the fundamentals for most, if not all custom controls.

  • a resourcedictionary
  • class file
We first want to create our layout of the control so I created the following code:
<Style TargetType="local:DateItem">
<
Setter Property="Template">
<
Setter.Value>
<
ControlTemplate TargetType="local:DateItem">
<
Border Width="50" Height="50" BorderBrush="#A0A0A0" BorderThickness="1">
<
Grid Margin="1">
<
Grid.RowDefinitions>
<
RowDefinition Height="auto" />
<
RowDefinition Height="*" />
<
RowDefinition Height="auto" />
</
Grid.RowDefinitions>
<
StackPanel HorizontalAlignment="Stretch" Margin="1,1" Grid.Row="0" Background="#DDE2E5">
<
StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<
TextBlock FontSize="9" x:Name="MonthPart" />
<
TextBlock FontSize="9" Margin="3,0,0,0" x:Name="YearPart" />
</
StackPanel>
</
StackPanel>
<
Viewbox Grid.Row="1" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<
TextBlock x:Name="DatePart"/>
</
Viewbox>
<
TextBlock Margin="0" HorizontalAlignment="Center" FontSize="9" Grid.Row="2" x:Name="DayOfTheWeekPart" />
</
Grid>
</
Border>
</
ControlTemplate>
</
Setter.Value>
</
Setter>
</
Style>
What I did here is create a “Style” that should be applied to the “DateItem” control, by overriding the template we can customize the look of our control.
We created a new “ControlTemplate”, with a border inside and a grid to arrange all the values. Very important: setting up the right names for your inner-controls.

This is because we will have to address them from the classfile. I created the following important controls:

  • a textblock (MonthPart)
  • a textblock (YearPart)
  • a textblock (DatePart)
  • a textblock (DayOfTheWeekPart)

Notice that I suffix them with “Part” this will allow easier recognition in the classfile. Talking of which, lets look at that.
The first thing I usually do is make the classfile “Blendable”, meaning it is easier to work with in Blend. I do this by adding the necessary class-attributes:



   1: [TemplatePart(Name = Parts.DatePART, Type = typeof(TextBlock))]
   2: [TemplatePart(Name = Parts.MonthPART, Type = typeof(TextBlock))]
   3: [TemplatePart(Name = Parts.YearPART, Type = typeof(TextBlock))]
   4: [TemplatePart(Name = Parts.DayOfTheWeekPART, Type = typeof(TextBlock))]
   5: public class DateItem : Control
   6: {
   7: }
Also notice I derived from the “Control” class.
By making an innerclass called Parts, it’s easier to work with the different parts. (It’s here that the naming in the resourcefile matters).


   1: public static class Parts
   2: {
   3:     public const string DatePART = "DatePart";
   4:     public const string MonthPART = "MonthPart";
   5:     public const string YearPART = "YearPart";
   6:     public const string DayOfTheWeekPART = "DayOfTheWeekPart";
   7: }
The next thing we need is a property on the object on which we can set our date, either manually or by databinding. This type of properties are dependency properties.


   1: public static readonly DependencyProperty DateProperty =
   2:             DependencyProperty.Register("Date", typeof(string), typeof(DateItem), new PropertyMetadata(DateTime.Now.ToShortDateString(),OnDateChanged));
   3:  
   4: private static void OnDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   5: {
   6:     ((DateItem) d).UpdateDate(e.NewValue.ToString());
   7: }
   8:  
   9: protected virtual void UpdateDate(string value)
  10: {
  11:     Debug.Assert(DateTime.TryParse(value, out _date), "Date couldn't be converted to datetime value");
  12:  
  13:  
  14:     _day.Text = string.Format("{0:dd}", _date);
  15:     _year.Text = string.Format("{0:yy}", _date);
  16:     _month.Text = string.Format("{0:MMM}", _date);
  17:     _dayOfTheWeek.Text = string.Format("{0:ddd}", _date);
  18: }
  19:  
  20: public string Date
  21: {
  22:     get
  23:     {
  24:         return (string)GetValue(DateProperty);
  25:     }
  26:     set
  27:     {
  28:         SetValue(DateProperty, value);
  29:     }
  30: }
There we go and we have a dependency property to get our date, with databinding and if the the “Date” property isn’t set, we set it to the current date.
Now to apply our resourcedictionary style template, we must tell the class to override its defaultstylekey, this can be done in the constructor of the class:


   1: public DateItem()
   2: {
   3:     DefaultStyleKey = typeof(DateItem);
   4: }
To link our style controls with the code and apply our date to the right controls, we have to get them out of the template as control objects and apply the date to them.
We do this by overriding the “OnApplyTemplate()” methode.
To support the object we create them as private fields first:


   1: private TextBlock _day;
   2: private TextBlock _month;
   3: private TextBlock _year;
   4: private TextBlock _dayOfTheWeek;
   5: private DateTime _date;


   1: public override void OnApplyTemplate()
   2: {
   3:     base.OnApplyTemplate();
   4:  
   5:     _day = GetTemplateChild(Parts.DatePART) as TextBlock;
   6:     _dayOfTheWeek = GetTemplateChild(Parts.DayOfTheWeekPART) as TextBlock;
   7:     _year = GetTemplateChild(Parts.YearPART) as TextBlock;
   8:     _month = GetTemplateChild(Parts.MonthPART) as TextBlock;
   9:  
  10:     Debug.Assert(_day != null, "Date textblock can not be null");
  11:     Debug.Assert(_year != null, "Year textblock can not be null");
  12:     Debug.Assert(_month != null, "Month textblock can not be null");
  13:     Debug.Assert(_dayOfTheWeek != null, "DayOfTheWeek textblock can not be null");
  14:     
  15:     UpdateDate(Date);
  16: }
In this methode we fetch every part and make an object of it, then for safety I assert those objects to check if they are not "null”. Then we call the “UpdateDate()” method to apply our date.
To learn more about the “out” parameter and optional parameters, I have a new blogpost ready to talk about those ;)
At this point we should have a ready to use DateItem!
Just built it, reference the project and you can use it in other projects.

Enjoy!

No comments:

Post a Comment