using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; //AUS AvalonControlsLibrary namespace BeWo.Controls { /// /// Time Picker as a control that lets the user select a specific time /// [TemplatePart(Name = "PART_Hours", Type = typeof(TextBox))] [TemplatePart(Name = "PART_Minutes", Type = typeof(TextBox))] [TemplatePart(Name = "PART_Seconds", Type = typeof(TextBox))] [TemplatePart(Name = "PART_IncreaseTime", Type = typeof(ButtonBase))] [TemplatePart(Name = "PART_DecrementTime", Type = typeof(ButtonBase))] public class TimePicker : Control { /// /// Gets or sets the maximum time that can be selected /// public static readonly DependencyProperty MaxTimeProperty = DependencyProperty.Register( "MaxTime", typeof(TimeSpan), typeof(TimePicker), new UIPropertyMetadata( TimeSpan.MaxValue, delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TimePicker picker = (TimePicker)sender; picker.HourMaxValue = picker.MaxTime.Hours; picker.MinuteMaxValue = picker.MaxTime.Minutes; picker.SecondMaxValue = picker.MaxTime.Seconds; picker.CoerceValue(SelectedTimeProperty); // make sure to update the time if appropiate })); /// /// Gets or sets the minimum time selected /// public static readonly DependencyProperty MinTimeProperty = DependencyProperty.Register( "MinTime", typeof(TimeSpan), typeof(TimePicker), new UIPropertyMetadata( TimeSpan.MinValue, delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TimePicker picker = (TimePicker)sender; picker.HourMinValue = picker.MinTime.Hours; picker.MinuteMinValue = picker.MinTime.Minutes; picker.SecondMinValue = picker.MinTime.Seconds; picker.CoerceValue(SelectedTimeProperty); // make sure to update the time if appropiate })); /// /// Backing store for the selected hour /// public static readonly DependencyProperty SelectedHourProperty = DependencyProperty.Register( "SelectedHour", typeof(int), typeof(TimePicker), new UIPropertyMetadata( 0, delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TimePicker timePicker = (TimePicker)sender; // validate the hour set int hour = MathUtil.ValidateNumber(timePicker.SelectedHour, timePicker.HourMinValue, timePicker.HourMaxValue); if (hour != timePicker.SelectedHour) { timePicker.SelectedHour = hour; } // set the new timespan SetNewTime(timePicker); })); /// /// Backing store for the selected minsutes /// public static readonly DependencyProperty SelectedMinuteProperty = DependencyProperty.Register( "SelectedMinute", typeof(int), typeof(TimePicker), new UIPropertyMetadata( 0, delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TimePicker timePicker = (TimePicker)sender; // validate the minute set int min = MathUtil.ValidateNumber(timePicker.SelectedMinute, timePicker.MinuteMinValue, timePicker.MinuteMaxValue); if (min != timePicker.SelectedMinute) { timePicker.SelectedMinute = min; } // set the new timespan SetNewTime(timePicker); })); /// /// Backing store for the selected second /// public static readonly DependencyProperty SelectedSecondProperty = DependencyProperty.Register( "SelectedSecond", typeof(int), typeof(TimePicker), new UIPropertyMetadata( 0, delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TimePicker timePicker = (TimePicker)sender; // validate the minute set int sec = MathUtil.ValidateNumber(timePicker.SelectedSecond, timePicker.SecondMinValue, timePicker.SecondMaxValue); if (sec != timePicker.SelectedSecond) { timePicker.SelectedSecond = sec; } // set the new timespan SetNewTime(timePicker); })); /// /// /// public static readonly RoutedEvent SelectedTimeChangedEvent = EventManager.RegisterRoutedEvent("SelectedTimeChanged", RoutingStrategy.Bubble, typeof(TimeSelectedChangedEventHandler), typeof(TimePicker)); /// /// Backing store for the selected timestamp /// public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register("SelectedTime", typeof(TimeSpan), typeof(TimePicker), new UIPropertyMetadata(new TimeSpan(0, 0, 0), SelectedTimePropertyChanged, ForceValidSelectedTime)); private int HourMaxValue = 23; private int HourMinValue; private int MinuteMaxValue = 59; private int MinuteMinValue; private int SecondMaxValue = 59; private int SecondMinValue; private TextBox currentlySelectedTextBox; // data memebers to store the textboxes for hours, minutes and seconds private TextBox hours; private bool isUpdatingTime; private TextBox minutes; private TextBox seconds; /// /// Static constructor /// static TimePicker() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TimePicker), new FrameworkPropertyMetadata(typeof(TimePicker))); } /// /// Default constructor /// public TimePicker() { this.SelectedTime = DateTime.Now.TimeOfDay; } public event TimeSelectedChangedEventHandler SelectedTimeChanged { add { this.AddHandler(SelectedTimeChangedEvent, value); } remove { this.RemoveHandler(SelectedTimeChangedEvent, value); } } // the textbox that is selected /// /// Gets or sets the maximum time that can be selected /// public TimeSpan MaxTime { get { return (TimeSpan)this.GetValue(MaxTimeProperty); } set { this.SetValue(MaxTimeProperty, value); } } /// /// Gets or sets the minimum time that can be selected /// public TimeSpan MinTime { get { return (TimeSpan)this.GetValue(MinTimeProperty); } set { this.SetValue(MinTimeProperty, value); } } /// /// Gets or sets the selected Hour /// public int SelectedHour { get { return (int)this.GetValue(SelectedHourProperty); } set { this.SetValue(SelectedHourProperty, value); } } /// /// Gets or sets the selected minutes /// public int SelectedMinute { get { return (int)this.GetValue(SelectedMinuteProperty); } set { this.SetValue(SelectedMinuteProperty, value); } } /// /// Gets or sets the selected second /// public int SelectedSecond { get { return (int)this.GetValue(SelectedSecondProperty); } set { this.SetValue(SelectedSecondProperty, value); } } /// /// Gets or sets the selected timestamp /// public TimeSpan SelectedTime { get { return (TimeSpan)this.GetValue(SelectedTimeProperty); } set { this.SetValue(SelectedTimeProperty, value); } } /// /// override to hook to the Control template elements /// public override void OnApplyTemplate() { // get the hours textbox and hook the events to it this.hours = this.GetTemplateChild("PART_Hours") as TextBox; this.hours.PreviewTextInput += this.HoursTextChanged; this.hours.KeyUp += this.HoursKeyUp; this.hours.GotFocus += this.TextGotFocus; this.hours.GotMouseCapture += this.TextGotFocus; // get the minutes textbox and hook the events to it this.minutes = this.GetTemplateChild("PART_Minutes") as TextBox; this.minutes.PreviewTextInput += this.MinutesTextChanged; this.minutes.KeyUp += this.MinutesKeyUp; this.minutes.GotFocus += this.TextGotFocus; this.minutes.GotMouseCapture += this.TextGotFocus; // get the seconds textbox and hook the events to it this.seconds = this.GetTemplateChild("PART_Seconds") as TextBox; this.seconds.PreviewTextInput += this.SecondsTextChanged; this.seconds.KeyUp += this.SecondsKeyUp; this.seconds.GotFocus += this.TextGotFocus; this.seconds.GotMouseCapture += this.TextGotFocus; // Get the increase button and hook to the click event ButtonBase increaseButton = this.GetTemplateChild("PART_IncreaseTime") as ButtonBase; increaseButton.Click += this.IncreaseTime; // Get the decrease button and hook to the click event ButtonBase decrementButton = this.GetTemplateChild("PART_DecrementTime") as ButtonBase; decrementButton.Click += this.DecrementTime; } private static void AdjustCarretIndexOrMoveToNeighbour(TextBox current, TextBox neighbour) { // if the current is near the end move to neighbour if (current.CaretIndex == 1 && neighbour != null) { neighbour.Focus(); } // if the carrot is in the first index move the caret one index else if (current.CaretIndex == 0) { current.CaretIndex++; } } private static string AdjustText(TextBox textBox, string newText) { // replace the new text with the old text if there are already 2 char in the textbox if (textBox.Text.Length == 2) { if (textBox.CaretIndex == 0) { return newText + textBox.Text[1]; } else { return textBox.Text[0] + newText; } } else { return textBox.CaretIndex == 0 ? newText + textBox.Text // if the carrot is in front the text append the new text infront : textBox.Text + newText; // else put it in behind the existing text } } private static object ForceValidSelectedTime(DependencyObject sender, object value) { TimePicker picker = (TimePicker)sender; TimeSpan time = (TimeSpan)value; if (time < picker.MinTime) { return picker.MinTime; } if (time > picker.MaxTime) { return picker.MaxTime; } return time; } private static void SelectedTimePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TimePicker timePicker = (TimePicker)sender; TimeSpan newTime = (TimeSpan)e.NewValue; TimeSpan oldTime = (TimeSpan)e.OldValue; if (!timePicker.isUpdatingTime) { timePicker.BeginUpdateSelectedTime(); // signal that the selected time is being updated if (timePicker.SelectedHour != newTime.Hours) { timePicker.SelectedHour = newTime.Hours; } if (timePicker.SelectedMinute != newTime.Minutes) { timePicker.SelectedMinute = newTime.Minutes; } if (timePicker.SelectedSecond != newTime.Seconds) { timePicker.SelectedSecond = newTime.Seconds; } timePicker.EndUpdateSelectedTime(); // signal that the selected time has been updated timePicker.OnTimeSelectedChanged(timePicker.SelectedTime, oldTime); } } private static void SetNewTime(TimePicker timePicker) { if (!timePicker.isUpdatingTime) { TimeSpan newTime = new TimeSpan(timePicker.SelectedHour, timePicker.SelectedMinute, timePicker.SelectedSecond); // check if the time is the same if (timePicker.SelectedTime != newTime) { timePicker.SelectedTime = newTime; } } } private static void TrimSelectedText(TextBox textBox) { if (textBox.SelectionLength > 0) { textBox.Text = textBox.Text.Remove(textBox.SelectionStart, textBox.SelectionLength); } } private static void TryFocusNeighbourControl(TextBox currentControl, TextBox leftControl, TextBox rightControl, Key keyPressed) { if (keyPressed == Key.Left && leftControl != null && currentControl.CaretIndex == 0) { leftControl.Focus(); } else if (keyPressed == Key.Right && rightControl != null && // if the caret index is the same as the length of the text and the user clicks right key it means that he wants to go to the next textbox currentControl.CaretIndex == currentControl.Text.Length) { rightControl.Focus(); } } private void BeginUpdateSelectedTime() { this.isUpdatingTime = true; } private void DecrementTime(object sender, RoutedEventArgs e) { this.IncrementDecrementTime(false); } private void EndUpdateSelectedTime() { this.isUpdatingTime = false; } private void HoursKeyUp(object sender, KeyEventArgs e) { // focus the next control TryFocusNeighbourControl(this.hours, null, this.minutes, e.Key); if (!IncrementDecrementTime(e.Key)) { this.ValidateAndSetHour(this.hours.Text); } } // event handler for the textboxes (hours, minutes, seconds) // handle the preview event so that we validate the text before it is set in the textbox's text // event handler for the Hour TextBox private void HoursTextChanged(object sender, TextCompositionEventArgs e) { // delete the text that is highlight(selected) TrimSelectedText(this.hours); // Adjust the text according to the carrot index string newText = AdjustText(this.hours, e.Text); // validates that the hour is correct if not set a valid value (0 or 24) int hourNum = this.ValidateAndSetHour(newText); // moves the carrot index or focus the neighbour AdjustCarretIndexOrMoveToNeighbour(this.hours, this.minutes); // handle the event so that it does not set the text, since we do it manually e.Handled = true; } private void IncreaseTime(object sender, RoutedEventArgs e) { this.IncrementDecrementTime(true); } // event handler for the Minute TextBox // increments/decrement the selected time accordingly to the selected control private bool IncrementDecrementTime(Key selectedKey) { if (selectedKey == Key.Up) { this.IncrementDecrementTime(true); } else if (selectedKey == Key.Down) { this.IncrementDecrementTime(false); } else { return false; } return true; } private void IncrementDecrementTime(bool increment) { // check if hour is selected if yes set it if (this.hours == this.currentlySelectedTextBox) { this.SelectedHour = MathUtil.IncrementDecrementNumber(this.hours.Text, this.HourMinValue, this.HourMaxValue, increment); } // check if minute is selected if yes set it else { // if (minutes == currentlySelectedTextBox) this.SelectedMinute = MathUtil.IncrementDecrementNumber(this.minutes.Text, this.MinuteMinValue, this.MinuteMaxValue, increment); } // if non of the above are selected assume that the seconds is selected // else // SelectedMinute = MathUtil.IncrementDecrementNumber(minutes.Text, SecondMinValue, SecondMaxValue, increment); } private void MinutesKeyUp(object sender, KeyEventArgs e) { // focus the next control TryFocusNeighbourControl(this.minutes, this.hours, this.seconds, e.Key); if (!IncrementDecrementTime(e.Key)) { this.ValidateAndSetMinute(this.minutes.Text); } } private void MinutesTextChanged(object sender, TextCompositionEventArgs e) { // delete the text that is highlight(selected) TrimSelectedText(this.minutes); // Adjust the text according to the carrot index string newText = AdjustText(this.minutes, e.Text); // validates that the minute is correct if not set a valid value (0 or 59) int minNum = this.ValidateAndSetMinute(newText); // moves the carrot index or focus the neighbour AdjustCarretIndexOrMoveToNeighbour(this.minutes, this.seconds); // handle the event so that it does not set the text, since we do it manually e.Handled = true; } private void OnTimeSelectedChanged(TimeSpan newTime, TimeSpan oldTime) { TimeSelectedChangedRoutedEventArgs args = new TimeSelectedChangedRoutedEventArgs(SelectedTimeChangedEvent); args.NewTime = newTime; args.OldTime = oldTime; this.RaiseEvent(args); } private void SecondsKeyUp(object sender, KeyEventArgs e) { // focus the next control TryFocusNeighbourControl(this.seconds, this.minutes, null, e.Key); if (!IncrementDecrementTime(e.Key)) { this.ValidateAndSetSeconds(this.seconds.Text); } } private void SecondsTextChanged(object sender, TextCompositionEventArgs e) { // delete the text that is highlight(selected) TrimSelectedText(this.seconds); // Adjust the text according to the carrot index string newText = AdjustText(this.seconds, e.Text); // validates that the second is correct if not set a valid value (0 or 59) int secNum = this.ValidateAndSetSeconds(newText); // moves the carrot index or focus the neighbour AdjustCarretIndexOrMoveToNeighbour(this.seconds, null); // handle the event so that it does not set the text, since we do it manually e.Handled = true; } private void TextGotFocus(object sender, RoutedEventArgs e) { TextBox selectedBox = (TextBox)sender; // set the currently selected textbox. // This field is used to check which entity(hour/minute/second) to increment/decrement when user clicks the buttuns this.currentlySelectedTextBox = selectedBox; // highlight all code so that it is easier to the user to enter new info in the text box selectedBox.SelectAll(); } // event handler for the decrease button click // validates the hour passed as text and sets it to the SelectedHour property private int ValidateAndSetHour(string text) { int hourNum = MathUtil.ValidateNumber(text, this.HourMinValue, this.HourMaxValue); this.SelectedHour = hourNum; return hourNum; } // validates the minute passed as text and sets it to the SelectedMinute property private int ValidateAndSetMinute(string text) { int minNum = MathUtil.ValidateNumber(text, this.MinuteMinValue, this.MinuteMaxValue); this.SelectedMinute = minNum; return minNum; } // validates the second passed as text and sets it to the SelectedSecond property private int ValidateAndSetSeconds(string text) { int secNum = MathUtil.ValidateNumber(text, this.SecondMinValue, this.SecondMaxValue); this.SelectedSecond = secNum; return secNum; } // focuses the left/right control accordingly to the key passed. Pass null if there is not a neighbour control } /// /// Delegate for the TimeSelectedChanged event /// /// The object raising the event /// The routed event arguments public delegate void TimeSelectedChangedEventHandler(object sender, TimeSelectedChangedRoutedEventArgs e); /// /// Routed event arguments for the TimeSelectedChanged event /// public class TimeSelectedChangedRoutedEventArgs : RoutedEventArgs { /// /// Constructor /// /// The event that is raised public TimeSelectedChangedRoutedEventArgs(RoutedEvent routedEvent) : base(routedEvent) { } /// /// Gets or sets the new time /// public TimeSpan NewTime { get; set; } /// /// Gets or sets the old time /// public TimeSpan OldTime { get; set; } } }