Introduction
When I started moving from Winforms to WPF, there was one control I really missed, and that was the DateTimePicker
, as implemented in Winforms. WPF has the datepicker
, but what I wanted was a control where you can easily tab from year to month to day, and use the arrow keys to change values.
Note: Qwertie has made an excellent C# version of this control, ironing out the worst bugs in the process. :) You can check out his version at https://gist.github.com/1150228.
Background
The code plays around with some of the fundamental techniques required when building a user control in WPF.
Using the Code
To use the code, just compile the DTPicker
and add a reference to the DLL in your project. (or include the DTPicker
project as a subproject to your project. The solution contains two projects; DTPicker
, where the usercontrol
resides, and TestDTPicker
, a small WPF project to test the DateTimePicker
in.
XAML
The XAML for the control is pretty straight forward.
<UserControl x:Class="DateTimePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:DTPicker="clr-namespace:DTPicker">
<UserControl.Resources>
<ControlTemplate x:Key="IconButton"
TargetType="{x:Type ToggleButton}">
<Border>
<ContentPresenter />
</Border>
</ControlTemplate>
<DTPicker:BoolInverterConverter x:Key="BoolInverterConverter" />
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="DateDisplay"
VerticalContentAlignment="Center"
Margin="0,0,0,0"
MinHeight="{Binding ElementName=PopUpCalendarButton,
Path=ActualHeight}" >2001-01-01 12:30</TextBox>
<ToggleButton Template="{StaticResource IconButton}"
MaxHeight="21"
Margin="-1,0,0,0"
Name="PopUpCalendarButton"
IsChecked="False"
IsHitTestVisible="{Binding ElementName=CalendarPopup,
Path=IsOpen, Mode=OneWay, Converter={StaticResource BoolInverterConverter}}" >
<Image Source="Calendar.Icon.bmp" Stretch="
None" HorizontalAlignment="Left" />
</ToggleButton>
<Popup IsOpen="{Binding Path=IsChecked, ElementName=PopUpCalendarButton}"
x:Name="CalendarPopup" Margin="0,-7,0,0"
PopupAnimation="Fade"
StaysOpen="False">
<Calendar Margin="0,-1,0,0"
x:Name="CalDisplay" ></Calendar>
</Popup>
</StackPanel>
</UserControl>
Code
There are four main variables in our control:
SelectedDate
- The selected date :)
DateFormat
- How this date should be displayed (yyyy-MM-hh etc.)
MinimumDate
- The minimum date value that can be selected
MaximumDate
- The maximum date value that can be selected
The interesting thing is that the values of SelectedDate
, MinimumDate
and MaximumDate
are all interconnected. So when declaring the Dependency properties for these, I have to include a coerce callback function.
Public Shared ReadOnly SelectedDateProperty As DependencyProperty = _
DependencyProperty.Register("SelectedDate", _
GetType(Nullable(Of Date)),
GetType(DateTimePicker), _
New FrameworkPropertyMetadata(Date.Now,
New PropertyChangedCallback(AddressOf OnSelectedDateChanged),
New CoerceValueCallback(AddressOf CoerceDate)))
Here is the CoerceDate
function:
Private Shared Function CoerceDate(ByVal d As DependencyObject,
ByVal value As Object) As Object
Dim dtpicker As DateTimePicker = CType(d, DateTimePicker)
Dim current As Date = CDate(value)
If current < dtpicker.MinimumDate Then
current = dtpicker.MinimumDate
End If
If current > dtpicker.MaximumDate Then
current = dtpicker.MaximumDate
End If
Return current
End Function
Points of Interest
One thing that was hard to figure out was how to move the focus away from my control. It turned out to be an easy oneliner:
Me.MoveFocus(New TraversalRequest(FocusNavigationDirection.Previous))
Things That Can Be Done Better
The image of the calendar is encapsulated in the control. Some would probably like to expose the image as a property. Also although SelectedDate
is a nullable, I should really write more code to display a default text when there is no SelectedDate
.
History
- 2010-12-02: First attempt to write the article
- 2011-09-08: Added a note about Qwerties version