A life spent making mistakes is not only more honorable, but more useful than a life spent doing nothing.
- George Bernard Shaw
Introduction
After part 1, I decided to really concentrate on how to make the Calendar
control better and more “professional”. Let's assume for a minute that I wanted to sell this control. If I buy a control, I expect decent design time support, and I want the control to be stylable. My first attempt fell short!!! Let's try and make it better…
Item Generation
In the previous version of the Calendar
, all the CalendarLedgerItem
and CalendarTimeslotItem
were created manually! This is a big no, no…
The CalendarLedger
is responsible for creating all the CalendarLedgerItem
s. Calling PopulateLedger()
will dynamically create all the ledger items!
private void PopulateLedger()
{
if (_ledgerItems != null)
{
for (int i = 0; i < 24; i++)
{
CalendarLedgerItem item = new CalendarLedgerItem();
item.TimeslotA = i.ToString();
item.TimeslotB = "00";
item.SetBinding(CalendarLedgerItem.StyleProperty,
GetOwnerBinding("CalendarLedgerItemStyle"));
_ledgerItems.Children.Add(item);
}
}
}
The CalendarDay
is also responsible for creating the CalendarTimeslotItems
(by calling PopulateDay()
).
Style, Style, Style
The Calendar
currently exposes styles for the CalendarLedgerItem
, CalendarTimeslotItem
, and CalendarAppointmentItem
.
All the styles are exposed as DependencyProperty
s:
public static readonly DependencyProperty CalendarTimeslotItemStyleProperty =
DependencyProperty.Register("CalendarTimeslotItemStyle",
typeof(Style), typeof(Calendar));
public Style CalendarTimeslotItemStyle
{
get { return (Style)GetValue(CalendarTimeslotItemStyleProperty); }
set { SetValue(CalendarTimeslotItemStyleProperty, value); }
}
All that is now left to do is bind the StyleProperty
of the CalendarLedgerItem
, CalendarTimeslotItem
, and CalendarAppointmentItem
to these DependencyProperty
s!
timeslot.SetBinding(CalendarTimeslotItem.StyleProperty,
GetOwnerBinding("CalendarTimeslotItemStyle"));
Namespaces
By adding the following attribute...
[assembly: XmlnsDefinition(http:
"RudiGrobler.Controls")]
... it is now very easy to reference my controls without needing to remember all the namespaces:
xmlns:rg=”http://schemas.rudigrobler.com/wpf/2008”
Design Time Support
Visual Studio and Expression Blend design time support is currently a hot topic!
<>
The Calendar
control is actually composed of some primitives like the CalendarLedger
, CalendarDay
, etc. I do not want these primitives to show up in my toolbox! I only want my Calendar
control to be “selectable”.
To remove controls from the toolbox, add the following attribute:
[ToolboxBrowsable(false)]
Also notice the “cool” custom icon I now have! This is achieved by adding an embedded resource with a specific name (Calendar.Icon.bmp). The icon can also be specified by using the ThumbnailAttribute
!
In Expression Blend, the property grid gets divided into categories!
[Category("Calendar")]
Each category also gets subdivided into a “Normal” section and an expander that has some “Advanced” properties.
To place a property in the “Advanced” expander:
[EditorBrowsable(EditorBrowsableState.Advanced)]
Or to place it in the “Normal” section:
[EditorBrowsable(EditorBrowsableState.Always)]
Named Parts
A common practice in designing reusable controls is to use named parts! In my Calendar
control, I needed access to the CalendarDay
control. To get access to this control, I started by giving it a name:
<calendarledger x:name="PART_Ledger" />
The next step is to override the Calendar
's OnApplyTemplate
:
CalendarDay _day;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_day = GetTemplateChild(ElementDay) as CalendarDay;
if (_day != null)
{
_day.Owner = this;
}
}
In OnApplyTemplate
, I try and locate the named part (by using GetTemplateChild()
).
Now, I have full access to the CalendarDay
control!!! The only other “thing” to notice here is that I also set the Owner
of my CalendarDay
. This allows my Calendar
day to set bindings on its Owner
:
public Calendar Owner { get; set; }
private BindingBase GetOwnerBinding(string propertyName)
{
Binding result = new Binding(propertyName);
result.Source = this.Owner;
return result;
}
A best practice in using named parts is to always start the name with PART_
, and also to add the following attribute to your control:
[TemplatePart(Name = CalendarLedger.ElementLedgerItems, Type = typeof(StackPanel))]
This attribute makes it easy to determine what type a named part should be!
Summary
That is it for part 2!!!
- Rudi Grobler
History
- 11th November, 2008: Initial post.