-
-
Save qwertie/1150228 to your computer and use it in GitHub Desktop.
| <UserControl x:Class="DTPicker.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" | |
| xmlns:local="clr-namespace:DTPicker" | |
| xmlns:wpftc="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit" | |
| mc:Ignorable="d"><!--Uses Calendar in WPFToolkit.dll, | |
| see http://wpf.codeplex.com/releases/view/40535--> | |
| <UserControl.Resources> | |
| <ControlTemplate x:Key="IconButton" TargetType="{x:Type ToggleButton}"> | |
| <Border> | |
| <ContentPresenter /> | |
| </Border> | |
| </ControlTemplate> | |
| </UserControl.Resources> | |
| <Grid> | |
| <Grid.ColumnDefinitions> | |
| <ColumnDefinition Width="*"/> | |
| <ColumnDefinition Width="Auto"/> | |
| </Grid.ColumnDefinitions> | |
| <TextBox x:Name="DateDisplay" | |
| HorizontalAlignment="Stretch" | |
| VerticalContentAlignment="Center" | |
| Margin="0,0,0,0" | |
| MinHeight="{Binding ElementName=PopUpCalendarButton, Path=ActualHeight}" Text="yyyy-MM-dd HH:mm"> | |
| <TextBox.Style> | |
| <Style TargetType="TextBox"> | |
| <Style.Triggers> | |
| <DataTrigger Binding="{Binding DateTextIsWrong, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Value="True"> | |
| <Setter Property="Background" Value="LightGray" /> | |
| </DataTrigger> | |
| </Style.Triggers> | |
| </Style> | |
| </TextBox.Style> | |
| </TextBox> | |
| <ToggleButton Grid.Column="1" Template="{StaticResource IconButton}" | |
| MaxHeight="21" | |
| Margin="-1,0,0,0" | |
| Name="PopUpCalendarButton" | |
| IsChecked="False" | |
| IsHitTestVisible="{Binding ElementName=CalendarPopup, Path=IsOpen, Mode=OneWay, Converter={local:BoolInverter}}" > | |
| <Image Source="../Icons/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"> | |
| <wpftc:Calendar Margin="0,-1,0,0" x:Name="CalDisplay" ></wpftc:Calendar> | |
| </Popup> | |
| </Grid> | |
| </UserControl> |
| using System; | |
| using System.Globalization; | |
| using System.Windows; | |
| using System.Windows.Controls; | |
| using System.Windows.Data; | |
| using System.Windows.Input; | |
| using System.Windows.Markup; | |
| using System.Diagnostics; | |
| using System.Windows.Media; | |
| using System.Windows.Documents; | |
| using System.Windows.Shapes; | |
| namespace DTPicker | |
| { | |
| /// <summary> | |
| /// A WPF DateTimePicker control that allows the user to edit the date with a | |
| /// drop-down calendar, edit the date and time with the arrow keys, type a date | |
| /// by hand, or type a date componentwise. Based on a Visual Basic control by | |
| /// Magnus Gudmundsson: http://www.codeproject.com/KB/WPF/wpfDateTimePicker.aspx | |
| /// </summary> | |
| /// <remarks> | |
| /// Improvements over the VB version: | |
| /// <ul> | |
| /// <li>Bug fix: doesn't crash if the cursor is at the end of the textbox with | |
| /// no selection, and user presses the left arrow key.</li> | |
| /// <li>Bug fix: when the user clicks one of the punctuation marks in the | |
| /// textbox, the field to the right is selected (instead of something weird)</li> | |
| /// <li>Bug fix: user can enter a 4-digit year (in VB, it was cleared after the | |
| /// second digit)</li> | |
| /// <li>Bug fix: after typing a single-digit month, day or hour, the left/right | |
| /// arrow keys will select the adjacent field correctly.</li> | |
| /// <li>Bug fix: date formats that contain both "dd" and "ddd" are handled | |
| /// correctly, e.g. "ddd MM dd, yyyy HH:mm".</li> | |
| /// <li>Bug fix: any custom DateFormat is now permitted, not just formats that | |
| /// are understood by Visual Basic CDate(). However, you should not use | |
| /// one-digit fields in the DateFormat string because the code is designed | |
| /// for fixed-length dates. For example, you must use "MM" instead of "M" | |
| /// and "dd" instead of "d". Despite this limitation (which existed in the | |
| /// original VB version too), the user is allowed to input single digit | |
| /// values.</li> | |
| /// <li>Shift+Tab now selects the previous field instead of the next field</li> | |
| /// <li>Left/right arrow keys no longer let the TextBox lose keyboard focus</li> | |
| /// <li>XAML changed so that the control expands to fill the space it is given, | |
| /// because IMO the control looks strange if it changes width as the user is | |
| /// typing.</li> | |
| /// <li>After typing a new value for a field, the user can type the appropriate | |
| /// punctuation character to move to the next field. For example, given the | |
| /// default format "yyyy-MM-dd HH:mm", the user could click the month and | |
| /// type "2-28" to change the date to Febuary 28. When the user presses the | |
| /// "-" key, the day field will become selected automatically. "2/28" is | |
| /// also accepted.</li> | |
| /// <li>Free-form editing of the date is now permitted. The user can delete the | |
| /// entire date and re-enter it, even in an unexpected format. For example, | |
| /// although the date format may be "yyyy-MM-dd hh:mm tt", the control can | |
| /// accept a typed (or pasted) date in a different (standard) format such as | |
| /// "August 12, 2001 23:00". If the typed date cannot not be parsed, the | |
| /// date will revert to the original date (stored internally in | |
| /// SelectedDate) when the control loses focus.</li> | |
| /// <li>Added up and down arrows (WPF Adorners) that appear when you select a | |
| /// field of the date or time, allowing you to change the date or time | |
| /// incrementally with the mouse. This feature will not work automatically | |
| /// in all windows; you may need to wrap the DateTimePicker, or the entire | |
| /// contents of your window, in an <AdornerDecorator> object to allow | |
| /// the arrows to appear. See | |
| /// http://stackoverflow.com/questions/13389772/how-to-draw-wpf-adorners-on-top-of-everything-else | |
| /// </li></ul> | |
| /// Note: I removed support for "null" as a date value because I didn't need | |
| /// it for my application, so I didn't want to take responsibility for ensuring | |
| /// that it works correctly. | |
| /// <para/> | |
| /// The control switches to free-form edit mode when the user types something | |
| /// that makes the date invalid, such as an unexpected character or an invalid | |
| /// month number. The control reverts to normal "assisted" editing when the | |
| /// date becomes valid again, or when the control loses focus. | |
| /// <para/> | |
| /// A pleasant side-effect of the free-form input logic is that the user can now | |
| /// type non-numeric fields. For example, if the date format contains a MMM | |
| /// field, the user can select it and type "jul" to change the month to July. | |
| /// Editing with up/down arrow keys is also supported for the MMM and ddd fields, | |
| /// but not the t or tt fields (AM/PM). | |
| /// <para/> | |
| /// Although the date format can contain both "dd" and "ddd", please note that | |
| /// this currently thwarts user editing if they attempt to type any component | |
| /// (instead of using the arrow keys or calendar). The reason is that you have | |
| /// to update the day-of-month and day-of-week at the same time, otherwise | |
| /// parsing will tend to fail. For example, suppose the date is currently | |
| /// 2011-08-16 (Tue). If the user selects the day and types "19", DateTimePicker | |
| /// refuses to accept the new day because August 19, 2011 is not a Tuesday. | |
| /// Consequently, parsing fails unless the user manually changes "Tue" to "Fri" | |
| /// or deletes "(Tue)" from the end. | |
| /// <para/> | |
| /// License: The Code Project Open License (CPOL) | |
| /// http://www.codeproject.com/info/cpol10.aspx | |
| /// <para/> | |
| /// [2012-11] Changed (1) to change the SelectedDate immediately when text is | |
| /// typed instead of waiting for the text box to lose focus, and (2) not to | |
| /// update DateDisplay.Text when the SelectedDate changes and the text box has | |
| /// the focus. Instead, a trigger changes TextBox.Background to gray to indicate | |
| /// the discrepancy. | |
| /// [2012-11] Added up-down arrows | |
| /// </remarks> | |
| public partial class DateTimePicker : UserControl | |
| { | |
| private const int FormatLengthOfLast = 2; | |
| private enum Direction : int | |
| { | |
| Previous = -1, | |
| Next = 1 | |
| } | |
| TextBoxUpDownAdorner _upDownButtons; | |
| public DateTimePicker() | |
| { | |
| InitializeComponent(); | |
| CalDisplay.SelectedDatesChanged += CalDisplay_SelectedDatesChanged; | |
| DateDisplay.PreviewMouseUp += DateDisplay_PreviewMouseUp; | |
| DateDisplay.LostFocus += DateDisplay_LostFocus; | |
| DateDisplay.PreviewKeyDown += DateTimePicker_PreviewKeyDown; | |
| DateDisplay.TextChanged += new TextChangedEventHandler(DateDisplay_TextChanged); | |
| this.Loaded += (s, e) => | |
| { | |
| AdornerLayer adLayer = GetAdornerLayer(DateDisplay); | |
| if (adLayer != null) | |
| { | |
| adLayer.Add(_upDownButtons = new TextBoxUpDownAdorner(DateDisplay)); | |
| _upDownButtons.Click += (textBox, direction) => { OnUpDown(direction); }; | |
| } | |
| }; | |
| } | |
| static AdornerLayer GetAdornerLayer(FrameworkElement subject) | |
| { | |
| AdornerLayer layer = null; | |
| do { | |
| if ((layer = AdornerLayer.GetAdornerLayer(subject)) != null) | |
| break; | |
| } while ((subject = subject.Parent as FrameworkElement) != null); | |
| return layer; | |
| } | |
| #region "Properties" | |
| public DateTime SelectedDate | |
| { | |
| get { return (DateTime)GetValue(SelectedDateProperty); } | |
| set { SetValue(SelectedDateProperty, value); } | |
| } | |
| public string DateFormat | |
| { | |
| get { return Convert.ToString(GetValue(DateFormatProperty)); } | |
| set { SetValue(DateFormatProperty, value); } | |
| } | |
| public bool ShowCalendarButton | |
| { | |
| get { return PopUpCalendarButton.Visibility == Visibility.Visible; } | |
| set { PopUpCalendarButton.Visibility = (value ? Visibility.Visible : Visibility.Collapsed); } | |
| } | |
| public string _inputDateFormat; | |
| public string InputDateFormat() | |
| { | |
| if (_inputDateFormat == null) | |
| { | |
| string df = DateFormat; | |
| if (!df.Contains("MMM")) | |
| df = df.Replace("MM", "M"); | |
| if (!df.Contains("ddd")) | |
| df = df.Replace("dd", "d"); | |
| // Note: do not replace Replace("tt", "t") because a single "t" will not accept "AM" or "PM". | |
| _inputDateFormat = df.Replace("hh", "h").Replace("HH", "H").Replace("mm", "m").Replace("ss", "s"); | |
| } | |
| return _inputDateFormat; | |
| } | |
| public DateTime MinimumDate | |
| { | |
| get { return Convert.ToDateTime(GetValue(MinimumDateProperty)); } | |
| set { SetValue(MinimumDateProperty, value); } | |
| } | |
| public DateTime MaximumDate | |
| { | |
| get { return Convert.ToDateTime(GetValue(MaximumDateProperty)); } | |
| set { SetValue(MaximumDateProperty, value); } | |
| } | |
| #endregion | |
| #region "Events" | |
| public event RoutedEventHandler DateChanged | |
| { | |
| add { AddHandler(DateChangedEvent, value); } | |
| remove { RemoveHandler(DateChangedEvent, value); } | |
| } | |
| public static readonly RoutedEvent DateChangedEvent = EventManager.RegisterRoutedEvent("DateChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DateTimePicker)); | |
| public event RoutedEventHandler DateFormatChanged | |
| { | |
| add { this.AddHandler(DateFormatChangedEvent, value); } | |
| remove { this.RemoveHandler(DateFormatChangedEvent, value); } | |
| } | |
| public static readonly RoutedEvent DateFormatChangedEvent = EventManager.RegisterRoutedEvent("DateFormatChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DateTimePicker)); | |
| #endregion | |
| #region "DependencyProperties" | |
| public static readonly DependencyProperty DateFormatProperty = DependencyProperty.Register("DateFormat", typeof(string), typeof(DateTimePicker), new FrameworkPropertyMetadata("yyyy-MM-dd HH:mm", OnDateFormatChanged)); | |
| public static DependencyProperty MaximumDateProperty = DependencyProperty.Register("MaximumDate", typeof(DateTime), typeof(DateTimePicker), new FrameworkPropertyMetadata(Convert.ToDateTime("3000-01-01 00:00"), null, new CoerceValueCallback(CoerceMaxDate))); | |
| public static DependencyProperty MinimumDateProperty = DependencyProperty.Register("MinimumDate", typeof(DateTime), typeof(DateTimePicker), new FrameworkPropertyMetadata(Convert.ToDateTime("1900-01-01 00:00"), null, new CoerceValueCallback(CoerceMinDate))); | |
| public static readonly DependencyProperty SelectedDateProperty = DependencyProperty.Register("SelectedDate", | |
| typeof(DateTime), typeof(DateTimePicker), | |
| new FrameworkPropertyMetadata(DateTime.Now, | |
| new PropertyChangedCallback(OnSelectedDateChanged), | |
| new CoerceValueCallback(CoerceDate))); | |
| /// <summary>true when user is busy editing DateDisplay and the SelectedDate | |
| /// becomes different from the date shown on the text box.</summary> | |
| public static readonly DependencyProperty DateTextIsWrongProperty = DependencyProperty.Register("DateTextIsWrong", typeof(bool), typeof(DateTimePicker), new FrameworkPropertyMetadata(false)); | |
| protected bool DateTextIsWrong | |
| { | |
| get { return (bool)GetValue(DateTextIsWrongProperty); } | |
| set { SetValue(DateTextIsWrongProperty, value); } | |
| } | |
| #endregion | |
| #region "EventHandlers" | |
| bool _forceTextUpdateNow = true; | |
| private void CalDisplay_SelectedDatesChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) | |
| { | |
| PopUpCalendarButton.IsChecked = false; | |
| TimeSpan timeOfDay = TimeSpan.Zero; | |
| timeOfDay = SelectedDate.TimeOfDay; | |
| SelectedDate = CalDisplay.SelectedDate.Value.Date + timeOfDay; | |
| } | |
| private void DateDisplay_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) | |
| { | |
| if (DateDisplay.SelectionLength == 0) | |
| FocusOnDatePart(DateDisplay.SelectionStart); | |
| } | |
| bool IsDateInExpectedFormat() | |
| { | |
| return ParseDateText(false) != null; | |
| } | |
| DateTime? ParseDateText(bool flexible) | |
| { | |
| DateTime selectedDate; | |
| if (!DateTime.TryParseExact(DateDisplay.Text, InputDateFormat(), null, DateTimeStyles.AllowWhiteSpaces, out selectedDate)) | |
| if (!flexible || !DateTime.TryParse(DateDisplay.Text, out selectedDate)) | |
| return null; | |
| return selectedDate; | |
| } | |
| void ReformatDateText() | |
| { | |
| // Changes DateDisplay.Text to match the current DateFormat | |
| DateTime? date = ParseDateText(true); | |
| if (date != null) | |
| { | |
| string newText = date.Value.ToString(DateFormat); | |
| if (DateDisplay.Text != newText) | |
| DateDisplay.Text = newText; | |
| } | |
| } | |
| private void DateDisplay_LostFocus(object sender, System.Windows.RoutedEventArgs e) | |
| { | |
| DateDisplay.Text = SelectedDate.ToString(DateFormat); | |
| // When the user selects a field again, then the box loses focus, then | |
| // the user clicks the same field again, the selection is cleared, | |
| // causing the arrows not to appear. To fix, clear selection in advance. | |
| try { | |
| DateDisplay.SelectionLength = 0; | |
| } catch(NullReferenceException) { | |
| // Occurs during shutdown. Bug in WPF? Ain't documented, that's for sure. | |
| } | |
| } | |
| void DateDisplay_TextChanged(object sender, TextChangedEventArgs e) | |
| { | |
| DateTime? date = ParseDateText(true); | |
| if (date != null) | |
| SelectedDate = date.Value; | |
| } | |
| private void DateTimePicker_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) | |
| { | |
| int selstart = DateDisplay.SelectionStart; | |
| if (!IsDateInExpectedFormat()) | |
| return; | |
| switch (e.Key) | |
| { | |
| case Key.Up: | |
| OnUpDown(+1); | |
| break; | |
| case Key.Down: | |
| OnUpDown(-1); | |
| break; | |
| case Key.Left: | |
| if (Keyboard.Modifiers != ModifierKeys.None) | |
| return; | |
| SelectPosition(selstart, Direction.Previous); | |
| break; | |
| case Key.Right: | |
| if (Keyboard.Modifiers != ModifierKeys.None) | |
| return; | |
| SelectPosition(selstart, Direction.Next); | |
| break; | |
| case Key.Tab: | |
| var dir = Direction.Next; | |
| if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0) | |
| dir = Direction.Previous; | |
| e.Handled = SelectPosition(selstart, dir); | |
| break; | |
| default: | |
| char nextChar = '\0'; | |
| if (selstart < DateDisplay.Text.Length) | |
| nextChar = DateDisplay.Text[selstart]; | |
| if ((e.Key == Key.OemMinus || e.Key == Key.Subtract || e.Key == Key.OemQuestion || e.Key == Key.Divide) && | |
| (nextChar == '/' || nextChar == '-') || | |
| e.Key == Key.Space && nextChar == ' ' || | |
| e.Key == Key.OemSemicolon && nextChar == ':') | |
| SelectPosition(selstart, Direction.Next); | |
| else | |
| return; | |
| break; | |
| } | |
| e.Handled = true; | |
| } | |
| private void OnUpDown(int increment) | |
| { | |
| int selstart = DateDisplay.SelectionStart; | |
| _forceTextUpdateNow = true; | |
| SelectedDate = Increase(selstart, increment); | |
| FocusOnDatePart(selstart); | |
| } | |
| private static object CoerceDate(DependencyObject d, object value) | |
| { | |
| DateTimePicker me = (DateTimePicker)d; | |
| DateTime current = Convert.ToDateTime(value); | |
| if (current < me.MinimumDate) | |
| current = me.MinimumDate; | |
| if (current > me.MaximumDate) | |
| current = me.MaximumDate; | |
| return current; | |
| } | |
| private static object CoerceMinDate(DependencyObject d, object value) | |
| { | |
| DateTimePicker me = (DateTimePicker)d; | |
| DateTime current = Convert.ToDateTime(value); | |
| if (current >= me.MaximumDate) | |
| throw new ArgumentException("MinimumDate can not be equal to, or more than maximum date"); | |
| if (current > me.SelectedDate) | |
| me.SelectedDate = current; | |
| return current; | |
| } | |
| private static object CoerceMaxDate(DependencyObject d, object value) | |
| { | |
| DateTimePicker me = (DateTimePicker)d; | |
| DateTime current = Convert.ToDateTime(value); | |
| if (current <= me.MinimumDate) | |
| throw new ArgumentException("MaximimumDate can not be equal to, or less than MinimumDate"); | |
| if (current < me.SelectedDate) | |
| me.SelectedDate = current; | |
| return current; | |
| } | |
| public static void OnDateFormatChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) | |
| { | |
| var me = (DateTimePicker)obj; | |
| me._inputDateFormat = null; // will be recomputed on-demand | |
| me.DateDisplay.Text = me.SelectedDate.ToString(me.DateFormat); | |
| } | |
| public static void OnSelectedDateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) | |
| { | |
| var me = (DateTimePicker)obj; | |
| var date = (DateTime)args.NewValue; | |
| me.CalDisplay.SelectedDate = date; | |
| me.CalDisplay.DisplayDate = date; | |
| if (me.DateDisplay.IsFocused && !me._forceTextUpdateNow) | |
| { | |
| DateTime? oldDate = me.ParseDateText(true); | |
| if (oldDate != null) | |
| me.DateTextIsWrong = date != oldDate.Value; | |
| } | |
| else | |
| { | |
| me.DateTextIsWrong = false; | |
| me._forceTextUpdateNow = false; | |
| me.DateDisplay.Text = date.ToString(me.DateFormat); | |
| } | |
| } | |
| #endregion | |
| // Selects next or previous date value, depending on the incrementor value | |
| // Alternatively moves focus to previous control or the calender button | |
| private bool SelectPosition(int selstart, Direction direction) | |
| { | |
| selstart = CalcPosition(selstart, direction); | |
| if (selstart > -1) | |
| { | |
| return FocusOnDatePart(selstart); | |
| } | |
| else | |
| return false; | |
| } | |
| static char At(string s, int index) | |
| { | |
| if ((uint)index < (uint)s.Length) | |
| return s[index]; | |
| return '\0'; | |
| } | |
| // Gets location of next/previous date field, depending on the incrementor value. | |
| // Returns -1 if there is no next/previous field. | |
| private int CalcPosition(int selStart, Direction direction) | |
| { | |
| string df = DateFormat; | |
| if (selStart >= df.Length) | |
| selStart = df.Length - 1; | |
| char startChar = df[selStart]; | |
| int i = selStart; | |
| for (; ; ) | |
| { | |
| i += (int)direction; | |
| if ((uint)i >= (uint)df.Length) | |
| return -1; | |
| if (df[i] == startChar) | |
| continue; | |
| if (char.IsLetter(df[i])) | |
| break; | |
| startChar = '\0'; // to handle cases like "yyyy-MM-dd (ddd)" correctly | |
| } | |
| if (direction < 0) | |
| // move to the beginning of the field | |
| while (i > 0 && df[i - 1] == df[i]) | |
| i--; | |
| return i; | |
| } | |
| private bool FocusOnDatePart(int selStart) | |
| { | |
| ReformatDateText(); | |
| // Find beginning of field to select | |
| string df = DateFormat; | |
| if (selStart > df.Length - 1) | |
| selStart = df.Length - 1; | |
| char firstchar = df[selStart]; | |
| while (!char.IsLetter(firstchar) && selStart + 1 < df.Length) | |
| { | |
| selStart++; | |
| firstchar = df[selStart]; | |
| } | |
| while (selStart > 0 && df[selStart - 1] == firstchar) | |
| selStart--; | |
| int selLength = 1; | |
| while (selStart + selLength < df.Length && df[selStart + selLength] == firstchar) | |
| selLength++; | |
| // don't select AM/PM: we have no interface to change it. | |
| if (firstchar == 't') | |
| return false; | |
| DateDisplay.Focus(); | |
| DateDisplay.Select(selStart, selLength); | |
| return true; | |
| } | |
| private DateTime Increase(int selstart, int value) | |
| { | |
| DateTime retval = (ParseDateText(false) ?? SelectedDate); | |
| try | |
| { | |
| switch (DateFormat.Substring(selstart, 1)) | |
| { | |
| case "h": | |
| case "H": | |
| retval = retval.AddHours(value); | |
| break; | |
| case "y": | |
| retval = retval.AddYears(value); | |
| break; | |
| case "M": | |
| retval = retval.AddMonths(value); | |
| break; | |
| case "m": | |
| retval = retval.AddMinutes(value); | |
| break; | |
| case "d": | |
| retval = retval.AddDays(value); | |
| break; | |
| case "s": | |
| retval = retval.AddSeconds(value); | |
| break; | |
| } | |
| } | |
| catch (ArgumentException ex) | |
| { | |
| //Catch dates with year over 9999 etc, dont throw | |
| } | |
| return retval; | |
| } | |
| } | |
| // Adorners must subclass the abstract base class Adorner. | |
| public class TextBoxUpDownAdorner : Adorner | |
| { | |
| StreamGeometry _triangle = new StreamGeometry(); | |
| bool _shown; | |
| double _x, _top, _bottom; | |
| public Pen Outline = new Pen(new SolidColorBrush(Color.FromArgb(64,255,255,255)), 5); | |
| public Brush Fill = Brushes.Black; | |
| public TextBoxUpDownAdorner(TextBox adornedBox) : base(adornedBox) | |
| { | |
| _triangle = new StreamGeometry(); | |
| _triangle.FillRule = FillRule.Nonzero; | |
| using (StreamGeometryContext c = _triangle.Open()) | |
| { | |
| c.BeginFigure(new Point(-10, 0), true /* filled */, true /* closed */); | |
| c.LineTo(new Point(10, 0), true, false); | |
| c.LineTo(new Point(0, 15), true, false); | |
| } | |
| _triangle.Freeze(); | |
| MouseDown += (s, e) => { | |
| if (Click != null) | |
| { | |
| bool up = e.GetPosition(AdornedElement).Y < (_top + _bottom) / 2; | |
| Click((TextBox)AdornedElement, up ? 1 : -1); | |
| } | |
| }; | |
| adornedBox.LostFocus += RelevantEventOccurred; | |
| adornedBox.SelectionChanged += RelevantEventOccurred; | |
| } | |
| void RelevantEventOccurred(object sender, RoutedEventArgs e) | |
| { | |
| // In OnRender, GetRectFromCharacterIndex may return Infinity values, | |
| // so measure the location of the selection here instead. | |
| var box = AdornedElement as TextBox; | |
| if (box.IsFocused) { | |
| int start = box.SelectionStart, len = box.SelectionLength; | |
| if (_shown = len > 0) { | |
| var rect1 = box.GetRectFromCharacterIndex(start); | |
| var rect2 = box.GetRectFromCharacterIndex(start + len); | |
| _top = rect1.Top - 2; | |
| _bottom = rect1.Bottom + 2; | |
| _x = (rect1.Left + rect2.Left) / 2; | |
| } | |
| } else | |
| _shown = false; | |
| InvalidateVisual(); | |
| } | |
| public event Action<TextBox, int> Click; | |
| // A common way to implement an adorner's rendering behavior is to override the OnRender | |
| // method, which is called by the layout system as part of a rendering pass. | |
| protected override void OnRender(DrawingContext drawingContext) | |
| { | |
| if (_shown) | |
| { | |
| drawingContext.PushTransform(new TranslateTransform(_x, _top)); | |
| drawingContext.PushTransform(new ScaleTransform(1, -1)); | |
| drawingContext.DrawGeometry(Fill, Outline, _triangle); | |
| drawingContext.Pop(); | |
| drawingContext.Pop(); | |
| drawingContext.PushTransform(new TranslateTransform(_x, _bottom)); | |
| drawingContext.DrawGeometry(Fill, Outline, _triangle); | |
| drawingContext.Pop(); | |
| } | |
| } | |
| } | |
| public class BoolInverter : MarkupExtension, IValueConverter | |
| { | |
| public override object ProvideValue(IServiceProvider serviceProvider) | |
| { return this; } | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { return !(bool)value; } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { return !(bool)value; } | |
| } | |
| } |
@sbannikov it is my intention that people can use this in commercial projects at no charge, and modify it according to their needs. I haven't chosen any conditions, so you may proceed under the assumption that there aren't any.
Does this work with WPF net5 ( core)
I didn't test it on .net Core.
<datetimepicker:DateTimePicker Name="dateTimePicker" datetimepicker:DateTimePicker.DateChanged="" Grid.Row="13" Grid.Column="3" HorizontalAlignment="Left" Width="219" Margin="10,1,0,0">
</datetimepicker:DateTimePicker>
---> how do I get to access this in xaml.cs file ? am not able to access the control...
Hi, Its a nice DateTimePicker control by usercontrol. Its working very nicely using WPFToolkit(wpftoolkit.3.5.50211.1.nupkg). But Sir, one issue. The DateChanged event is not working. Any suggestions? Yours faithfully Mmkottari
Hi @qwertie,
Can i use this code in commercial project? Under which conditions? I'm really concerned about legal issues and don't want to make a wrong move. Thanks!