Click here to Skip to main content
15,867,704 members
Articles / Desktop Programming / WPF

A WPF DateTimePicker That Works Like the One in Winforms

Rate me:
Please Sign up or sign in to vote.
4.63/5 (11 votes)
9 Sep 2011CPOL2 min read 132.1K   6.5K   28   26
A WPF DateTimePicker that Works like the one in Winforms

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.

XML
 <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.

VB.NET
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:

VB.NET
 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:

VB.NET
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) CyberDaDa
Sweden Sweden
Magnús Gudmundsson has been programming on and off since he got his first sinclair spectrum sometimes in the murky 80's.
Currently he is trying to start a company called CyberDada
http://www.cyberdada.com

Nowadays Magnús mostly programs in .Net. He is equally fluent in vb and c#, has done webforms, winforms, webservices, wpf, and then some Smile | :)

Comments and Discussions

 
PraiseThanks a lot Pin
Member 147060763-Jan-20 1:25
Member 147060763-Jan-20 1:25 
QuestionDatechanged event is not functioning? Pin
Murulimadhav130-May-19 22:56
Murulimadhav130-May-19 22:56 
QuestionBetter this than Qwertie one Pin
ninodago28-Oct-16 4:42
ninodago28-Oct-16 4:42 
GeneralReally Thankful Pin
Muhammad Mukarram17-Oct-15 18:31
Muhammad Mukarram17-Oct-15 18:31 
QuestionGetBindingExpression Pin
is.vlad18-Nov-14 6:50
is.vlad18-Nov-14 6:50 
AnswerRe: GetBindingExpression Pin
is.vlad18-Nov-14 7:42
is.vlad18-Nov-14 7:42 
GeneralRe: GetBindingExpression Pin
Magnus Gudmundsson18-Nov-14 9:44
professionalMagnus Gudmundsson18-Nov-14 9:44 
GeneralRe: GetBindingExpression Pin
is.vlad18-Nov-14 19:40
is.vlad18-Nov-14 19:40 
GeneralRe: GetBindingExpression Pin
is.vlad18-Nov-14 19:42
is.vlad18-Nov-14 19:42 
GeneralRe: GetBindingExpression Pin
is.vlad18-Nov-14 20:34
is.vlad18-Nov-14 20:34 
GeneralRe: GetBindingExpression Pin
Magnus Gudmundsson18-Nov-14 20:53
professionalMagnus Gudmundsson18-Nov-14 20:53 
GeneralRe: GetBindingExpression Pin
is.vlad19-Nov-14 1:06
is.vlad19-Nov-14 1:06 
QuestionCan't change DateFormat in DataGrid? Pin
Member 372081025-Jan-13 8:49
Member 372081025-Jan-13 8:49 
SuggestionGood example of WPF DateTimePicker In DataGrid Pin
JohnDetroit3-Jun-12 1:39
JohnDetroit3-Jun-12 1:39 
Questionits simple and useful Pin
musto2310-May-12 20:17
musto2310-May-12 20:17 
QuestionC# version + improvements [modified] Pin
Qwertie16-Aug-11 11:37
Qwertie16-Aug-11 11:37 
AnswerRe: C# version + improvements Pin
Magnus Gudmundsson16-Aug-11 19:27
professionalMagnus Gudmundsson16-Aug-11 19:27 
GeneralRe: C# version + improvements Pin
Qwertie17-Aug-11 4:28
Qwertie17-Aug-11 4:28 
GeneralRe: C# version + improvements Pin
Member 1131974118-Dec-14 0:17
Member 1131974118-Dec-14 0:17 
GeneralRe: C# version + improvements Pin
Qwertie18-Dec-14 6:37
Qwertie18-Dec-14 6:37 
QuestionDate format not accepted Pin
John Cserkuti4-Aug-11 3:45
John Cserkuti4-Aug-11 3:45 
AnswerRe: Date format not accepted Pin
Magnus Gudmundsson4-Aug-11 6:57
professionalMagnus Gudmundsson4-Aug-11 6:57 
Generalfunny I did one of these the other day Pin
Sacha Barber2-Dec-10 23:34
Sacha Barber2-Dec-10 23:34 
GeneralRe: funny I did one of these the other day Pin
Magnus Gudmundsson3-Dec-10 1:01
professionalMagnus Gudmundsson3-Dec-10 1:01 
Thanks Sacha.

I don't know if I should answer here, or in your post Smile | :)

At first glance, I notice a couple of differences though.
In my versions you can also change the time, and because the parsing is based on the
format string you specify, mine 'should work' in most cultures provided that the last element is two chars long... Smile | :)
see ugly const hack on top of the class.
Private Const FormatLengthOfLast As Integer = 2



furthermore I have added support for minimum and maximum date.
(the wpf datepicker has DisplayDateStart and DisplayDateEnd,
but as soon as you write a date in the textbox instead of picking it up from the calender,
it does not care about the values you have in those...)

Cheers
Magnús
GeneralRe: funny I did one of these the other day Pin
Sacha Barber3-Dec-10 21:19
Sacha Barber3-Dec-10 21:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.