Files
BeWoPlaner/Controls/Controls/TimePicker.cs
2016-06-27 01:45:38 +02:00

676 lines
24 KiB
C#

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
//AUS AvalonControlsLibrary
namespace BeWo.Controls
{
/// <summary>
/// Time Picker as a control that lets the user select a specific time
/// </summary>
[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
{
/// <summary>
/// Gets or sets the maximum time that can be selected
/// </summary>
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
}));
/// <summary>
/// Gets or sets the minimum time selected
/// </summary>
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
}));
/// <summary>
/// Backing store for the selected hour
/// </summary>
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);
}));
/// <summary>
/// Backing store for the selected minsutes
/// </summary>
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);
}));
/// <summary>
/// Backing store for the selected second
/// </summary>
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);
}));
/// <summary>
///
/// </summary>
public static readonly RoutedEvent SelectedTimeChangedEvent = EventManager.RegisterRoutedEvent("SelectedTimeChanged", RoutingStrategy.Bubble, typeof(TimeSelectedChangedEventHandler), typeof(TimePicker));
/// <summary>
/// Backing store for the selected timestamp
/// </summary>
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;
/// <summary>
/// Static constructor
/// </summary>
static TimePicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TimePicker), new FrameworkPropertyMetadata(typeof(TimePicker)));
}
/// <summary>
/// Default constructor
/// </summary>
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
/// <summary>
/// Gets or sets the maximum time that can be selected
/// </summary>
public TimeSpan MaxTime
{
get
{
return (TimeSpan)this.GetValue(MaxTimeProperty);
}
set
{
this.SetValue(MaxTimeProperty, value);
}
}
/// <summary>
/// Gets or sets the minimum time that can be selected
/// </summary>
public TimeSpan MinTime
{
get
{
return (TimeSpan)this.GetValue(MinTimeProperty);
}
set
{
this.SetValue(MinTimeProperty, value);
}
}
/// <summary>
/// Gets or sets the selected Hour
/// </summary>
public int SelectedHour
{
get
{
return (int)this.GetValue(SelectedHourProperty);
}
set
{
this.SetValue(SelectedHourProperty, value);
}
}
/// <summary>
/// Gets or sets the selected minutes
/// </summary>
public int SelectedMinute
{
get
{
return (int)this.GetValue(SelectedMinuteProperty);
}
set
{
this.SetValue(SelectedMinuteProperty, value);
}
}
/// <summary>
/// Gets or sets the selected second
/// </summary>
public int SelectedSecond
{
get
{
return (int)this.GetValue(SelectedSecondProperty);
}
set
{
this.SetValue(SelectedSecondProperty, value);
}
}
/// <summary>
/// Gets or sets the selected timestamp
/// </summary>
public TimeSpan SelectedTime
{
get
{
return (TimeSpan)this.GetValue(SelectedTimeProperty);
}
set
{
this.SetValue(SelectedTimeProperty, value);
}
}
/// <summary>
/// override to hook to the Control template elements
/// </summary>
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
}
/// <summary>
/// Delegate for the TimeSelectedChanged event
/// </summary>
/// <param name="sender">The object raising the event</param>
/// <param name="e">The routed event arguments</param>
public delegate void TimeSelectedChangedEventHandler(object sender, TimeSelectedChangedRoutedEventArgs e);
/// <summary>
/// Routed event arguments for the TimeSelectedChanged event
/// </summary>
public class TimeSelectedChangedRoutedEventArgs : RoutedEventArgs
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="routedEvent">The event that is raised </param>
public TimeSelectedChangedRoutedEventArgs(RoutedEvent routedEvent)
: base(routedEvent)
{
}
/// <summary>
/// Gets or sets the new time
/// </summary>
public TimeSpan NewTime { get; set; }
/// <summary>
/// Gets or sets the old time
/// </summary>
public TimeSpan OldTime { get; set; }
}
}