This article describes how to extend the WPF TextBox control to function like the Microsoft Blend TextBox that increment and decrement their value by using the mouse. As a bonus feature, a very cool data binding tooltip is included.
As part of a Blend presentation, I included a WPF Take Home Pay Calculator program. (Demo only calculates North Carolina State tax for 2007 not a full blown program). As a demo feature, I wanted TextBoxes that the user could change the value by using their mouse. You can download that presentation and code at my blog here : Get The Code From : Grain Of Sand
Obviously, developers wouldn't want every TextBox on their forms to have this feature! However, you may have an application that this fits. This is more of a you can do this article.
Also included in the .xaml is a resource that renders the ToolTip and data binds back to the properties of the control that spawned the tooltip.
I must give credit to Tom Mathews for his demo code on ToolTip data binding. You can view his code here : Binding a ToolTip in XAML.
I spent a good deal of time trying different things and almost gave up before I found Tom's post. Thank you Tom!
Like a good number of developers, I have authored all my own ASP.NET controls and WinForm controls. I have not extended any WPF controls so far, so this is my first. If I miss something or didn't do it the WPF way please post a comment.
Added two new properties XIncrementValue and YIncrementValue to the extended TextBox.
XIncrementValue is the how much the value will be incremented or decremented when the user drags the mouse right or left.
YIncrementValue is the how much the value will be incremented or decremented when the user drags the mouse up or down.
Public Property XIncrementValue() As Integer
Get
Return _intXIncrementValue
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New ArgumentOutOfRangeException("XIncrementValue", "Must be equal to or greater than 0.")
End If
_intXIncrementValue = Value
End Set
End Property
Public Property YIncrementValue() As Integer
Get
Return _intYIncrementValue
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New ArgumentOutOfRangeException("YIncrementValue", "Must be equal to or greater than 0.")
End If
_intYIncrementValue = Value
End Set
End Property
I have declare a module level variable _objMouseIncrementor of Type MouseIncrementor. When the user first clicks the TextBox a new MouseIncrementor object is fired up and stored.
Notice that we are storing the location of the mouse and setting the initial state of the MouseIncrementor to MouseIncrementor.MouseDirections.None. This is important because until the user starts dragging right, left, up or down, the TextBox control does not know how to change the value of the TextBox.
The MouseIncrementor class is a simple container for the data we need to know in order to make the calculations and also to know if the control is in Mouse Increment mode. It keeps track of the mouse movement direction and the Point of the mouse.
Private _objMouseIncrementor As MouseIncrementor
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Input.MouseButtonEventArgs)
MyBase.OnMouseDown(e)
_objMouseIncrementor = New MouseIncrementor(e.GetPosition(Me), MouseIncrementor.MouseDirections.None)
End Sub
A simple class for storing the state of the mouse increment, decrement operation.
Class MouseIncrementor
Private _enumMouseDirection As MouseDirections = MouseDirections.None
Private _objPoint As Point
Enum MouseDirections
LeftRight
None
UpDown
End Enum
Public Property MouseDirection() As MouseDirections
Get
Return _enumMouseDirection
End Get
Set(ByVal Value As MouseDirections)
_enumMouseDirection = Value
End Set
End Property
Public Property Point() As Point
Get
Return _objPoint
End Get
Set(ByVal Value As Point)
_objPoint = Value
End Set
End Property
Public Sub New(ByVal objPoint As Point, _
ByVal enumMouseDirection As MouseDirections)
_objPoint = objPoint
_enumMouseDirection = enumMouseDirection
End Sub
End Class
Sanity Checks
_objMouseIncrementor object has been instantiated or not.XIncrementValue and YIncrementValue property values at design or run-time.We need to determine if this is the first time the OnMouseMove sub has run since the _objMouseIncrementor object was instantiated. We know this by checking the _objMouseIncrementor.MouseDirection property. If its currently set to None then by checking the delta of the X and Y movements and testing for the higher value we can set the _objMouseIncrementor.MouseDirection to either LeftRight or UpDown so that the next time OnMouseMove is called, we know how to calculate the new TextBox value.
Now that we know which direction the mouse is moving and the delta from the last position, we can quickly calculate the new value of the TextBox.
Last order of business to the actually set the TextBox value and to record the current position of the mouse.
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Input.MouseEventArgs)
MyBase.OnMouseMove(e)
If _objMouseIncrementor Is Nothing Then
'nothing to do here
Exit Sub
End If
If _intXIncrementValue = 0 AndAlso _intYIncrementValue = 0 Then
'nothing to do here
Exit Sub
End If
Dim dblValue As Double
If Double.TryParse(Me.Text, dblValue) = False Then
'since we can't parse the value, we are out of here, i.e. user put text in our number box
_objMouseIncrementor = Nothing
Exit Sub
End If
Dim intDeltaX As Double = _objMouseIncrementor.Point.X - e.GetPosition(Me).X
Dim intDeltaY As Double = _objMouseIncrementor.Point.Y - e.GetPosition(Me).Y
If _objMouseIncrementor.MouseDirection = MouseIncrementor.MouseDirections.None Then
'this is our first time here, so we need to record if we are tracking x or y movements
If Math.Abs(intDeltaX) > Math.Abs(intDeltaY) Then
_objMouseIncrementor.MouseDirection = MouseIncrementor.MouseDirections.LeftRight
Else
_objMouseIncrementor.MouseDirection = MouseIncrementor.MouseDirections.UpDown
End If
End If
If _objMouseIncrementor.MouseDirection = MouseIncrementor.MouseDirections.LeftRight Then
If intDeltaX > 0 Then
dblValue -= _intXIncrementValue
ElseIf intDeltaX < 0 Then
dblValue += _intXIncrementValue
End If
Else
If intDeltaY > 0 Then
dblValue += _intYIncrementValue
Else
dblValue -= _intYIncrementValue
End If
End If
Me.Text = dblValue.ToString
_objMouseIncrementor.Point = e.GetPosition(Me)
End Sub
When the OnMouseUp event code runs it sets _objMouseIncrementor = Nothing. This takes the TextBox control out of mouse increment mode.
Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Input.MouseButtonEventArgs)
MyBase.OnMouseUp(e)
_objMouseIncrementor = Nothing
_strOriginalTextValue = Nothing
End Sub
With all this mouse movement and mouse down trapping going on, we can't forget to allow the user to double click the TextBox to select the text. Easily accomplish by the following event handler.
Protected Overrides Sub OnMouseDoubleClick(ByVal e As System.Windows.Input.MouseButtonEventArgs)
MyBase.OnMouseDoubleClick(e)
_objMouseIncrementor = Nothing
Me.SelectAll()
End Sub
Please keep in mind, there are better ways to restrict the allowed characters in a TextBox than the below code. We are trying to emulate the Microsoft Blend TextBox, so let us just do it like Blend. Below is the simple code that accomplishes this.
The only comment for this code is the _strOriginalTextValue variable that gets set in the OnGotFocus event. This value allows us to restore the TextBox value if the user enters a non numeric value.
Protected Overrides Sub OnLostFocus(ByVal e As System.Windows.RoutedEventArgs)
MyBase.OnLostFocus(e)
'this simulates the functionality of the Blend textbox.
'it allows the end user to enter non numeric characters in the textbox and simply resets the original value rather an catching mistakes at keypress time.
Dim dblValue As Double
If Double.TryParse(MyBase.Text, dblValue) = True Then
MyBase.Text = dblValue.ToString
Else
If String.IsNullOrEmpty(_strOriginalTextValue) Then
_strOriginalTextValue = "0"
End If
MyBase.Text = _strOriginalTextValue
End If
_strOriginalTextValue = Nothing
_objMouseIncrementor = Nothing
End Sub
Most of the below code is standard xaml mark up.
However, the cool part of this ToolTip is the data binding to properties from the control this ToolTip belongs to.
Since a ToolTip is really a new Window, you can't data bind by trying to set its source to an element on the Window like we normally would by setting the ElementName property. Instead you must set a DataContext for the ToolTip. It took me forever to find out how to do this. I finally found this in Tom Mathews article that I mentioned at the start of this article that explains this.
The magic is all in the statement, DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}". This establishes the DataContext of the ToolTip as the control. Now we have full access to all of the control's properties. This code is slightly different from Tom's but is also performing a different function.
Since we now have a DataContext, just data bind to properties on the control that opened this ToolTip. Example : Text="{Binding Path=XIncrementValue}"
Below the two bold values, 100 and 50 are the YIncrementValue and XIncrementValue properties of the TextBox.
<Window.Resources> <ToolTip x:Key="toolTip4TextBoxWithMouseIncrementing" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}"> <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Width="409" Height="Auto" Background="#FFDEE5F0"> <StackPanel.Resources> <LinearGradientBrush x:Key="CoolToolTipGradientBrush" EndPoint="1,0.5" StartPoint="0,0.5"> <GradientStop Color="#FF0024A5" Offset="0"/> <GradientStop Color="#FF148CAB" Offset="1"/> <GradientStop Color="#FF14A7AE" Offset="0.242"/> <GradientStop Color="#FF1676AA" Offset="0.542"/> <GradientStop Color="#FE09E4CE" Offset="0.836"/> </LinearGradientBrush> </StackPanel.Resources> <TextBlock FontWeight="Bold" Foreground="White" Text="Using The Incrementing TextBox" Background="{DynamicResource CoolToolTipGradientBrush}" Margin="0,0,0,10" Padding="0,3,0,3" TextAlignment="Center"/> <TextBlock TextWrapping="WrapWithOverflow" Margin="5,0,5,0"> You can increment or decrement the value of this textbox by simply clicking the mouse, hold and drag up or down, right or left. Release when happy with the value.</TextBlock> <Line Stroke="#FF0069D7" StrokeThickness="2" Margin="0,5,0,5" HorizontalAlignment="Stretch" X2="409"/> <StackPanel Orientation="Horizontal" Margin="0,0,0,0"> <TextBlock Margin="5,0,5,0">When moving the mouse up(+) or down(-) the value will change by :</TextBlock> <TextBlock FontWeight="Bold" Text="{Binding Path=YIncrementValue}"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,5,0,0"> <TextBlock Margin="5,0,5,0">When moving the mouse right(+) or left(-) the value will change by :</TextBlock> <TextBlock FontWeight="Bold" Text="{Binding Path=XIncrementValue}"/> </StackPanel> <TextBlock Text="WPF is so cool!" HorizontalAlignment="Stretch" Background="{DynamicResource CoolToolTipGradientBrush}" Foreground="#FFFFFFFF" TextAlignment="Center" Margin="0,5,0,0" Padding="0,3,0,3"/> </StackPanel> </ToolTip> </Window.Resources>
Hope this article can help someone learn a little more about WPF.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||