Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

Behaviors to create double or TimeSpan Value TextBox for WPF Masked TextBox

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
26 Oct 2016CPOL4 min read 13.7K   369   10   8
Presents a concept for creating a masked TextBox, and has the implementation for a TimeSpan and double value

Introduction

I have used and application for a long time that has a TextBox that use used to specify a time on a music track, and have been very unhappy with it. I finally got around to seeing what I could implement. I work primarily in WPF for I figured that I should do it in this technology. I could implement it as a control, but decided to do it as a behavior. This does have the advantage in that all the styling for TextBox controls would work for this control, and could possibly be used in other ways. I have recently added a control for double values.

Notes

This is work in progress, and will change with time to add additional features that are not in the initial version. In particular I plan to handle pasting, but that is not in this version. There have been some improvements in the TimeSpan behavior. The double behavior is a cleaner design. It is probably the easier template to use for creating new behaviors.

The DoubleTextBoxBehavior has a format DependencyProperty that should be set. It is only somewhat similar to the format strings that are used elsewhere in Visual Studio and this is on purpose. Currently the right most period or comma is used for the decimal point. Any zero character will be used for the number regardless of location, and the right most plus or minus (dash) will be used for the sign. Any other charaters will be displayed as in the format string, which gives a lot of flexibility. If a plus sign is used that positive numbers will have a sign, otherwise only negative numbers. Using the minus key at any position will make the number positive, and the plus key will make it positive. Using the up and down arrow keys when the cursor is on the sigh position will change from positive to negative. However, if the specified minimum values is above zero or the maximum value is below zero, sign will not change.  And if sign is changed then the number will be immediately adjusted to remain within the specified minimum and maximum. 

Design

The design has a single class, but the class has two very different parts implemented as the static and instance code. Quite often with behaviors I will only implement the static part and use only static event handlers. I felt in this case it would be better to separate the code.

The static part is as follows:

C#
                public static readonly DependencyProperty MaxTimeProperty =
                        DependencyProperty.RegisterAttached("MaxTime", typeof(string),
                                typeof(TimeSpanTextBoxBehaviour), new UIPropertyMetadata(null, OnMaxTimeChanged));

                public static string GetMaxTime(Control o)
                {
                        return (string)o.GetValue(MaxTimeProperty);
                }

                public static void SetMaxTime(Control o, string value)
                {
                        o.SetValue(MaxTimeProperty, value);
                }

                private static void OnMaxTimeChanged(DependencyObject dependencyObject,
                        DependencyPropertyChangedEventArgs e)
                {
                        var timeTextBoxBehaviour = GetTimeTextBoxBehaviour(dependencyObject);
                        var timeString = (string)e.NewValue;
                        var timeSpan = TimeSpanParse(timeString, false);
                        timeTextBoxBehaviour.MaxTimeSpanChanged(timeSpan);
                }

                public static readonly DependencyProperty ValueProperty =
                        DependencyProperty.RegisterAttached("Value", typeof(TimeSpan),
                                typeof(TimeSpanTextBoxBehaviour), new FrameworkPropertyMetadata(TimeSpan.Zero, 
                                        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));

                public static TimeSpan GetValue(Control o)
                {
                        return (TimeSpan)o.GetValue(ValueProperty);
                }

                public static void SetValue(Control o, TimeSpan value)
                {
                        o.SetValue(ValueProperty, value);
                }

                public static readonly DependencyProperty TimeFormatProperty =
                        DependencyProperty.RegisterAttached("TimeFormat", typeof(TimerFormats),
                                typeof(TimerFormats), new UIPropertyMetadata(TimerFormats.Seconds10Ths, OnTimeFormatChanged));

                public static TimerFormats GetTimeFormat(Control o)
                {
                        return (TimerFormats)o.GetValue(TimeFormatProperty);
                }

                public static void SetTimeFormat(Control o, TimerFormats timeFormat)
                {
                        o.SetValue(TimeFormatProperty, timeFormat);
                }

                private static void OnTimeFormatChanged(DependencyObject dependencyObject,
                        DependencyPropertyChangedEventArgs e)
                {
                        var timeTextBoxBehaviour = GetTimeTextBoxBehaviour(dependencyObject);
                        timeTextBoxBehaviour.TimeFormatChanged((TimerFormats)e.NewValue);
                }

                public static TimeSpanTextBoxBehaviour GetTimeTextBoxBehaviour(object textBox)
                {
                        var castTextBox = (TextBox)textBox;
                        var control = GetTimeTextBoxBehaviour(castTextBox);
                        if (control == null)
                        {
                                control = new TimeSpanTextBoxBehaviour(castTextBox);
                                SetTimeTextBoxBehaviour(castTextBox, control);
                        }
                        return control;
                }

                private static void OnValueChanged(DependencyObject dependencyObject,
                        DependencyPropertyChangedEventArgs e)
                {
                        var timeTextBoxBehaviour = GetTimeTextBoxBehaviour(dependencyObject);
                        var timeSpan = (TimeSpan)e.NewValue;
                        timeTextBoxBehaviour.UpdateText(timeSpan);
                }

                /// <summary>
                /// This is the private DependencyProperty where the instance to handle the time behaviour is kept
                /// </summary>
                private static readonly DependencyProperty TimeTextBoxBehaviourProperty =
                        DependencyProperty.RegisterAttached("TimeTextBoxBehaviour", typeof(TimeSpanTextBoxBehaviour),
                                typeof(TimeSpanTextBoxBehaviour), new UIPropertyMetadata(null));

                private static TimeSpanTextBoxBehaviour GetTimeTextBoxBehaviour(TextBox o)
                {
                        return (TimeSpanTextBoxBehaviour)o.GetValue(TimeTextBoxBehaviourProperty);
                }

                private static void SetTimeTextBoxBehaviour(TextBox o, TimeSpanTextBoxBehaviour value)
                {
                        o.SetValue(TimeTextBoxBehaviourProperty, value);
                }
                #endregion

Modification

The timer is a more complex case. If the data to provide the format for is something like a Social Security Number, it could be a lot simpler. As long as the "-"'s should be in the saved social security number, the ValueProperty would not be required. 

Input Errors

I have not done anything about reporting errors to the user, in particular is an invalid key is pressed. Only the number keys and a few other keys are supported, and the code just ignores the input. Quite often a beep is programmed on invalid input, but this can be objectionable to some users. I have thought of possibly coming up with a an interface to allow binding to a command that will handle these input issues, Interested in feedback.

History

  • 2016/10/26: Initial version
  • 2016/11/03: Fix of some issues with arrow key up down behavior and cursor selection, and added time format flexibility.
  • 2016/11/04: Updated behavior to allow a larger digit than normally allowed if all positions left of the digit are zeroes (i.e., if you have "0:00:00" and enter a 9 in the 10's of seconds then the value will be "0:01:30:00".
  • 2016/12/13: Fix small bug when enter a too large value in the first position, and also support entering a too large value in the first position and that value is moved to the first allowed position.
  • 2017/09/15: Fixed Value DependencyProperty so defaults to Mode=TwoWay. Also fix for when cursor is at end of text.
  • 2020/07/25: Serious error in the original version that would not bind from the ViewModel property. This means that no feedback. Also the changes to make it two way were not in the DependencyProperty definition. Changed the example to bind through a ViewModel instead of direclty.
  • 2020/07/26: Problem found caused by rounding error fixed when incrementing with 10ths of seconds, and probably caused issues anytime there were fractons of a second being displayed.
  • 2020/08/07: Improved TimeSpan behavior, and also included a behavior for double values.
  • 2020/08/09: Fix a small bug that prevented using arrow key to move to right most position.
  • 2020/08/15: Bug fix

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
BugI also cannot download file Pin
Medtronic WPF Developer5-Apr-18 14:42
Medtronic WPF Developer5-Apr-18 14:42 
AnswerRe: I also cannot download file Pin
Clifford Nelson16-Apr-18 10:25
Clifford Nelson16-Apr-18 10:25 
AnswerRe: I also cannot download file Pin
Medtronic WPF Developer19-Apr-18 1:54
Medtronic WPF Developer19-Apr-18 1:54 
AnswerRe: I also cannot download file Pin
Clifford Nelson19-Apr-18 1:55
Clifford Nelson19-Apr-18 1:55 
QuestionCan't download file Pin
Max Lambert27-Oct-16 2:25
Max Lambert27-Oct-16 2:25 
AnswerRe: Can't download file Pin
Clifford Nelson27-Oct-16 6:33
Clifford Nelson27-Oct-16 6:33 
AnswerRe: Can't download file Pin
Clifford Nelson31-Oct-16 4:41
Clifford Nelson31-Oct-16 4:41 
AnswerRe: Can't download file Pin
Clifford Nelson16-Apr-18 10:24
Clifford Nelson16-Apr-18 10:24 
I have uploaded the file again...somebody else had the problem.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.