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:
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”:
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:
<ResourceDictionaryMake sure the xmls:local is referring to your own namespace, shouldn’t you have chosen to make it CustomControlLibrary.DateItem.
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>
We have now the fundamentals for most, if not all custom controls.
- a resourcedictionary
- class file
<Style TargetType="local:DateItem">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.
<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>
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: }
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: }
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: }
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: }
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: }
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