Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Microsoft Blend Style Incrementing TextBox

0.00/5 (No votes)
24 Jun 2007 1  
An article on how to extend the WPF TextBox control to function like the Microsoft Blend TextBox that changes value by using the mouse. As a bonus feature, a very cool data binding tooltip is included.

Introduction

This article describes how to extend the WPF TextBox control to function like the Microsoft Blend TextBox that increments and decrements the value by using the mouse. As a bonus feature, a very cool data binding tooltip is included.

Background

As part of a Blend presentation, I included a WPF Take Home Pay Calculator program. (The demo only calculates North Carolina state tax for 2007, and is 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 form 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!

Desired Feature of TextBox: To Function Like the Microsoft Blend TextBox

  • Increment or decrement the numeric value of a TextBox by allowing the user to click on the TextBox and drag up, down, right, or left and have the value change.
  • If the user enters a non-numeric value, just return the TextBox to its previous value after the user leaves the TextBox.
  • Extra credit: Display a tooltip to the user explaining how the feature works and how much the value will change based on the direction they drag their mouse.

Extending the TextBox

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 missed something or didn't do it the WPF way, please post a comment.

Properties

I added two new properties: XIncrementValue and YIncrementValue to the extended TextBox.

  • XIncrementValue is how much the value will be incremented or decremented when the user drags the mouse right or left.
  • YIncrementValue is 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

Trapping the Mouse Down to Begin the Increment - Decrement

I have declared 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

Trapping the MouseMove, Incrementing, Decrementing the Value

Sanity checks:

  • Verify that we are in an incrementing or decrementing operation. We know this by checking if the _objMouseIncrementor object has been instantiated or not.
  • Check if the developer set the XIncrementValue and YIncrementValue property values at design or run-time.
  • Check to make sure the value in the TextBox will parse to a number.

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.

The last order of business is to 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 User Releases the Mouse Button

When the OnMouseUp event code runs, it sets _objMouseIncrementor = Nothing. This takes the TextBox control out of the mouse increment mode.

Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Input.MouseButtonEventArgs)
    MyBase.OnMouseUp(e)
    _objMouseIncrementor = Nothing
    _strOriginalTextValue = Nothing
End Sub

Allowing the User to Select the Text in the TextBox with the Mouse

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 this 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

Handling Text Entry in Our Numeric TextBox

Please keep in mind, there are better ways to restrict the allowed characters in a TextBox than the code below. 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

The ToolTip!

Most of the code below is standard XAML markup.

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.

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.

Screenshot - CoolTooltip.png

<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>

Closing

Hope this article can help someone learn a little more about WPF.

History

  • 24 June 2007: Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here