WPF: Time Control 12 Hour Format

March 15th, 2010 by Mel Leave a reply »
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;
Advertisement

21 comments

  1. Zubair Ahmed says:

    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

    • Mel says:

      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?

  2. Mel says:

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

  3. sureshkumar says:

    Thanks a lot for sharing the code, grate work

  4. niloofar says:

    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.
     

  5. manjeet says:

    I used it, n its really works nice.

    thanks for sharing..

    manjeet

  6. Brenda Hatch says:

    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!

    • Mel says:

      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 

  7. BatsIhor says:

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

  8. yoel says:

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

    • Mel says:

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

  9. Binjie says:

    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.

    • Mel says:

      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! 😀

  10. Shishir says:

    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.

  11. Tamer says:

    Thanks, its very helpful and easy to use

  12. naina says:

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

  13. Yao Kang says:

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

    • Mel says:

      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.

  14. Jared says:

    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!

    • Mel says:

      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