WPF: Time Control 12 Hour Format

ByMel

WPF: Time Control 12 Hour Format

Time Control 12 Hour Format
Time Control 12 Hour Format

I’ve spent a bit of time scouring the internet for a WPF Time Control that supports 12-hour format. There are several examples out there that are in 24-hour format, but I haven’t seen any for 12-hour format, so I wrote one. I began with some code located here and then modified it extensively to work for my situation. I didn’t need to display seconds, however it would be pretty easy to add them.

This control supports a wide range of keyboard input. You can enter the value for Hour, Minute, and Part of Day by either using the up/down arrow keys or by typing in numbers and letters. If you have any questions/suggestions/praise/opinions please feel free to leave a comment!

XAML:

<UserControl x:Class="CGS.TimeControl"
             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"
             xmlns:local="clr-namespace:CGS"
             Height="Auto" Width="Auto" x:Name="UserControl">
    <UserControl.Resources>
        <local:MinuteSecondToStringConverter x:Key="minuteSecondConverter" />
    </UserControl.Resources>
 
    <Border BorderBrush="Black" BorderThickness="1" CornerRadius="1">
        <Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" Margin="2" Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.2*"/>
                <ColumnDefinition Width="0.05*"/>
                <ColumnDefinition Width="0.2*"/>
                <ColumnDefinition Width="0.05*"/>
                <ColumnDefinition Width="0.2*"/>
            </Grid.ColumnDefinitions>
 
            <Grid x:Name="hour" Focusable="True" KeyDown="Down" GotFocus="Grid_GotFocus" LostFocus="Grid_LostFocus" MouseDown="Grid_MouseDown">
                <TextBlock TextWrapping="Wrap" Text="{Binding Path=Hours, ElementName=UserControl, Mode=Default}"
                 TextAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ElementName=UserControl, Path=FontSize}"/>
            </Grid>
 
            <Grid  Grid.Column="1">
                <TextBlock  x:Name="sep1" TextWrapping="Wrap" VerticalAlignment="Center" Background="{x:Null}"
                  FontSize="{Binding ElementName=UserControl, Path=FontSize}" Text=":" TextAlignment="Center"/>
            </Grid>
 
            <Grid  Grid.Column="2" x:Name="min" Focusable="True" KeyDown="Down" GotFocus="Grid_GotFocus" LostFocus="Grid_LostFocus" MouseDown="Grid_MouseDown">
                <TextBlock  TextWrapping="Wrap" Text="{Binding Path=Minutes, ElementName=UserControl, Mode=Default, Converter={StaticResource minuteSecondConverter}}"
                  TextAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ElementName=UserControl, Path=FontSize}"/>
            </Grid>
 
            <Grid  Grid.Column="4" Name="half" Focusable="True" KeyDown="Down" GotFocus="Grid_GotFocus" LostFocus="Grid_LostFocus" MouseDown="Grid_MouseDown">                
                <TextBlock TextWrapping="Wrap" Text="{Binding Path=DayHalf, ElementName=UserControl, Mode=Default}"
                 TextAlignment="Center" VerticalAlignment="Center" FontSize="{Binding ElementName=UserControl, Path=FontSize}"/>
            </Grid>
 
        </Grid>
    </Border>
</UserControl>

Code:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Data;
 
namespace CGS
{
    /// <summary>
    /// Interaction logic for TimeControl.xaml
    /// </summary>
    public partial class TimeControl : UserControl
    {
        const string amText = "am";
        const string pmText = "pm";
 
        static SolidColorBrush brBlue = new SolidColorBrush(Colors.LightBlue);
        static SolidColorBrush brWhite = new SolidColorBrush(Colors.White);
 
        DateTime _lastKeyDown;
 
        public TimeControl()
        {
            InitializeComponent();
 
            _lastKeyDown = DateTime.Now;
        }
 
        public TimeSpan Value
        {
            get { return (TimeSpan)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(TimeSpan), typeof(TimeControl),
        new UIPropertyMetadata(DateTime.Now.TimeOfDay, new PropertyChangedCallback(OnValueChanged)));
 
        private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            TimeControl control = obj as TimeControl;
 
            TimeSpan newTime = ((TimeSpan)e.NewValue);
 
            int timehours = newTime.Hours;
            int hours = timehours % 12;
            hours = (hours > 0) ? hours : 12;
 
            control._hours = newTime.Hours;
            control.Hours = hours;
            control.Minutes = ((TimeSpan)e.NewValue).Minutes;
            control.DayHalf = ((timehours - 12) >= 0) ? pmText : amText;
 
        }
 
        private int _hours;
        public int Hours
        {
            get { return (int)GetValue(HoursProperty); }
            set { SetValue(HoursProperty, value); }
        }
        public static readonly DependencyProperty HoursProperty =
        DependencyProperty.Register("Hours", typeof(int), typeof(TimeControl),
        new UIPropertyMetadata(0));
 
        public int Minutes
        {
            get { return (int)GetValue(MinutesProperty); }
            set { SetValue(MinutesProperty, value); }
        }
        public static readonly DependencyProperty MinutesProperty =
        DependencyProperty.Register("Minutes", typeof(int), typeof(TimeControl),
        new UIPropertyMetadata(0));
 
 
        public string DayHalf
        {
            get { return (string)GetValue(DayHalfProperty); }
            set { SetValue(DayHalfProperty, value); }
        }
        public static readonly DependencyProperty DayHalfProperty =
        DependencyProperty.Register("DayHalf", typeof(string), typeof(TimeControl),
        new UIPropertyMetadata(amText));
 
        private void Down(object sender, KeyEventArgs args)
        {
            bool updateValue = false;
 
            if (args.Key == Key.Up || args.Key == Key.Down)
            {
                switch (((Grid)sender).Name)
                {
                    case "min":
                        if (args.Key == Key.Up)
                            if (this.Minutes + 1 > 59)
                            {
                                this.Minutes = 0;
                                goto case "hour";
                            }
                            else
                            {
                                this.Minutes++;
                            }
                        if (args.Key == Key.Down)
                            if (this.Minutes - 1 < 0)
                            {
                                this.Minutes = 59;
                                goto case "hour";
                            }
                            else
                            {
                                this.Minutes--;
                            }
                        break;
 
                    case "hour":
                        if (args.Key == Key.Up)
                            this._hours = (_hours + 1 > 23) ? 0 : _hours + 1;
                        if (args.Key == Key.Down)
                            this._hours = (_hours - 1 < 0) ? 23 : _hours - 1;
                        break;
 
                    case "half":
                        this.DayHalf = (this.DayHalf == amText) ? pmText : amText;
 
                        int timeHours = this.Hours;
                        timeHours = (timeHours == 12) ? 0 : timeHours;
                        timeHours += (this.DayHalf == amText) ? 0 : 12;
 
                        _hours = timeHours;
                        break;
                }
 
                updateValue = true;
 
                args.Handled = true;
            }
            else if ((args.Key >= Key.D0 && args.Key <= Key.D9) || (args.Key >= Key.NumPad0 && args.Key <= Key.NumPad9))
            {
                int keyValue = (int)args.Key;
                int number = 0;
 
                number = keyValue - ((args.Key >= Key.D0 && args.Key <= Key.D9) ?
                                        (int)Key.D0 :
                                        (int)Key.NumPad0
                                    );
 
                bool attemptAdd = (DateTime.Now - _lastKeyDown).TotalSeconds < 1.5;
 
                switch (((Grid)sender).Name)
                {
                    case "min":
                        if (attemptAdd)
                        {
                            number += this.Minutes * 10;
 
                            if (number < 0 || number >= 60)
                            {
                                number -= this.Minutes * 10;
                            }
                        }
 
                        this.Minutes = number;
                        break;
 
                    case "hour":
                        if (attemptAdd)
                        {
                            number += this.Hours * 10;
 
                            if (number < 0 || number >= 13)
                            {
                                number -= this.Hours * 10;
                            }
                        }
 
                        number = (number == 12) ? 0 : number;
                        number += (this.DayHalf == amText) ? 0 : 12;
 
                        _hours = number;
                        break;
 
                    default:
                        break;
                }
 
                updateValue = true;
 
                args.Handled = true;
            }
            else if (args.Key == Key.A || args.Key == Key.P)
            {
                if (((Grid)sender).Name == "half")
                {
                    this.DayHalf = (args.Key == Key.A) ? amText : pmText;
 
                    updateValue = true; 
                }
            }
 
            if (updateValue)
            {
                this.Value = new TimeSpan(_hours, this.Minutes, 0);
            }
 
            _lastKeyDown = DateTime.Now;
        }
 
        private void Grid_GotFocus(object sender, RoutedEventArgs e)
        {
            var grd = sender as Grid;
 
            grd.Background = brBlue;
        }
 
        private void Grid_LostFocus(object sender, RoutedEventArgs e)
        {
            var grd = sender as Grid;
 
            grd.Background = brWhite;
        }
 
        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var grd = sender as Grid;
 
            grd.Focus();
        }
    }
 
 
    public class MinuteSecondToStringConverter : IValueConverter
    {
        #region IValueConverter Members
 
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
            {
                if (value is int)
                {
                    return ((int)value).ToString("00");
                }
            }
 
            return string.Empty;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
            {
                if (value is string)
                {
                    int number;
                    if (int.TryParse(value as string, out number))
                    {
                        return number;
                    }
                }
            }
 
            return 0;
        }
 
        #endregion
    }
}

And here’s an example of how to use it:

<cgs:TimeControl x:Name="timeControl" Margin="15,0,0,0" Height="25" Width="70" xmlns:cgs="clr-namespace:CGS" />
// Set the time of the control
this.timeControl.Value = DateTime.Now.TimeOfDay;
 
// Get the time from the control
TimeSpan time = this.timeControl.Value;

About the author

Mel administrator

21 Comments so far

Zubair AhmedPosted on10:49 pm - May 21, 2012

Thanks for great article.I have one issue when i select i am not able to get am or pm from time control for example if set time 7 pm it says only 7 can u please help me out in this to get time with am and pm
 
Thanks

    MelPosted on10:43 am - May 22, 2012

    Hey Zubair,

    Are you attempting to get the selected time value as a TimeSpan object from the control? The TimeSpan class will return hours in 24-hour format, so if you select 7:00 pm in the control the resulting TimeSpan will have hours of 19. Does that make sense?

MelPosted on9:03 am - Jan 4, 2012

Thanks for the feedback, you’re all very welcome!

sureshkumarPosted on11:44 pm - Jan 3, 2012

Thanks a lot for sharing the code, grate work

niloofarPosted on2:22 am - Sep 29, 2011

hi
thanks a lot
i used your idea and i made a new usercontrol for date and time.
but when load form , i set a value for uc but don’t show  value.
 

manjeetPosted on7:13 am - Feb 27, 2011

I used it, n its really works nice.

thanks for sharing..

manjeet

Brenda HatchPosted on2:53 pm - Feb 17, 2011

I must be going crazy, but it’s not letting me type in any of the TextBlocks. Or doesn’t let me use the arrow keys either.
Is there something I’m missing that you added later?
Thanks!

    MelPosted on9:08 am - Jan 4, 2012

    Hi Brenda,

    Sorry for the (extremely) late reply! That’s strange that it doesn’t work for you, the code above should accept keyboard input as described. If you’d like to send me some example project illustrating what you’re experiencing I’ll happily try to help you get it working!

    Thanks 

BatsIhorPosted on4:58 am - Dec 7, 2010

Tnx, it’s help me too, but I change it to use 24 hours format

yoelPosted on6:19 am - Nov 19, 2010

Hi, very nice contorl. thanks.
i have a question, why did you put each of the textblocks inside a seperate grid of their own?

    MelPosted on10:07 am - Nov 19, 2010

    Thanks Yoel!

    To be honest that was a carry-over from the original control I modified. I don’t think you necessarily need to do it this way. You could put the events directly on the TextBlocks themselves.

    Although, by using the Grids it gives you a greater ability to add additional UI components such as borders with special background colors to each section of the time editor. Just a thought. 🙂

BinjiePosted on2:38 am - Nov 11, 2010

Thanks for sharing the code Mel. Great work!
I couldn’t get it to work at first. With a little addition it then worked for me. Here is the change:
private void GridMouseDown(object sender, MouseButtonEventArgs e)
{
var grd = sender as Grid;

if (grd != null)
{
grd.Focus();
e.Handled = true;
}
}
Note that line reads: e.Handled = true;
Without it the grid will gain focus and lose it straight away. So you never get to set the value of the time using key strokes.

    MelPosted on10:14 am - Nov 11, 2010

    Thanks for the comment Binjie!

    That’s interesting that it wouldn’t keep focus for you originally. My guess is it has to do with how you’re using the control, such as the container it’s in.

    But I’m glad you got it to work! 😀

ShishirPosted on1:04 am - Sep 30, 2010

Exactly what I needed. Thanks a zillion. You saved me tons of time! Where would the world be without guys like you. Good work mate. works like a charm.

TamerPosted on6:49 am - Sep 29, 2010

Thanks, its very helpful and easy to use

nainaPosted on5:20 pm - Aug 12, 2010

You have done a great work!
This time control was a big help to my work.
Thanks thanks thanks!!

Yao KangPosted on1:25 am - Jul 26, 2010

Why the control only can right click to change the value?

    MelPosted on9:23 am - Jul 26, 2010

    Hey Yao,

    I’m not sure what you mean? You should be able to edit the value once the control has focus either by typing the time in digits or using the arrow keys to change the time.

JaredPosted on11:53 am - Apr 18, 2010

Looks pretty good so far Mel!  Not sloppy like some  other timepicker offerings I’ve seen on the web.  Is there any license associated with this code or making derivative works from this code?  I’d like to use this for a project!

    MelPosted on2:17 pm - Apr 18, 2010

    Thanks Jared!

    Like the rest of the content on my site it’s free to use and modify for any purpose, private or commercial.

    I think I’ll add a more official declaration of that to help clarify things.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.