Tuesday 1 January 2013

WPF Restricting Text Entry to Numeric Characters, the MVVM Way

I needed to Limit several TextBoxes in my WPF MVVM application to only allow Numeric Characters.

I stumbles upon a very useful post by Jermey Likeness which described a method using Attached Behaviours, which worked well for me;

http://csharperimage.jeremylikness.com/2009/10/silverlight-behaviors-and-triggers_07.html

I modified this code slightly to allow for Decimal point control also.

The VB.net Code is as follows;

Imports System.Collections.ObjectModel
 
Namespace Behaviours
 
    Public Class clsTextBoxFilters
 
#Region "Variables"
 
        Private Shared ReadOnly _controlKeys As List(Of Key) = New List(Of Key) From {Key.Back,
                                                                                           Key.CapsLock,
                                                                                            Key.LeftCtrl,
                                                                                            Key.RightCtrl,
                                                                                            Key.Down,
                                                                                            Key.[End],
                                                                                            Key.Enter,
                                                                                            Key.Escape,
                                                                                            Key.Home,
                                                                                            Key.Insert,
                                                                                            Key.Left,
                                                                                            Key.PageDown,
                                                                                            Key.PageUp,
                                                                                            Key.Right,
                                                                                            Key.LeftShift,
                                                                                            Key.RightShift,
                                                                                            Key.Tab,
                                                                                            Key.Up,
                                                                                            Key.Decimal,
                                                                                            Key.OemPeriod}
 
#End Region
 
#Region "Helper Functions"
 
        ''' <summary>
        ''' Check if the key is a Numeric
        ''' </summary>
        ''' <param name="key__1"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Shared Function _IsDigit(key__1 As Key) As Boolean
            Dim shiftKey As Boolean = (Keyboard.Modifiers And ModifierKeys.Shift) <> 0
            Dim retVal As Boolean
            If key__1 >= Key.D0 AndAlso key__1 <= Key.D9 AndAlso Not shiftKey Then
                retVal = True
            Else
                retVal = key__1 >= Key.NumPad0 AndAlso key__1 <= Key.NumPad9
            End If
            Return retVal
        End Function
 
#End Region
 
#Region "Getters and Setters"
 
        ''' <summary>
        ''' Get the value of the Numeric Filter Dependancy Property
        ''' </summary>
        ''' <param name="src"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function GetIsPositiveNumericFilter(src As DependencyObject) As Boolean
            Return CBool(src.GetValue(IsPositiveNumericFilterProperty))
        End Function
 
        ''' <summary>
        ''' Set the Value of the Numeric Filter Dependancy Property
        ''' </summary>
        ''' <param name="src"></param>
        ''' <param name="value"></param>
        ''' <remarks></remarks>
        Public Shared Sub SetIsPositiveNumericFilter(src As DependencyObject, value As Boolean)
            src.SetValue(IsPositiveNumericFilterProperty, value)
        End Sub
 
        ''' <summary>
        ''' Get the Value of the IsIntegerOnly Dependancy Property
        ''' </summary>
        ''' <param name="src"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function GetIsIntegerOnly(src As DependencyObject) As Boolean
            Return CBool(src.GetValue(IsIntegerOnlyProperty))
        End Function
 
        ''' <summary>
        ''' Sets the Value of the IsIntegerOnly Dependancy Property
        ''' </summary>
        ''' <param name="src"></param>
        ''' <param name="value"></param>
        ''' <remarks></remarks>
        Public Shared Sub SetIsIntegerOnly(src As DependencyObject, value As Boolean)
            src.SetValue(IsIntegerOnlyProperty, value)
        End Sub
 
#End Region
 
#Region "Dependancy Properties"
 
        Public Shared IsPositiveNumericFilterProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsPositiveNumericFilter", GetType(Boolean), GetType(clsTextBoxFilters), New PropertyMetadata(False, AddressOf IsPositiveNumericFilterChanged))
        Public Shared IsIntegerOnlyProperty As DependencyProperty = DependencyProperty.Register("IsIntegerOnly", GetType(Boolean), GetType(clsTextBoxFilters), Nothing)
 
#End Region
 
#Region "Dependancy Property Change Handlers"
 
        ''' <summary>
        ''' Determines if the Behaviour is active, adds the KeyDown event Handler
        ''' </summary>
        ''' <param name="src"></param>
        ''' <param name="args"></param>
        ''' <remarks></remarks>
        Public Shared Sub IsPositiveNumericFilterChanged(src As DependencyObject, args As DependencyPropertyChangedEventArgs)
            If src IsNot Nothing AndAlso TypeOf src Is TextBox Then
                Dim textBox As TextBox = TryCast(src, TextBox)
 
                If CBool(args.NewValue) Then
                    AddHandler textBox.KeyDown, AddressOf _TextBoxPositiveNumericKeyDown
                End If
            End If
        End Sub
 
#End Region
 
#Region "Event Handlers"
 
        ''' <summary>
        ''' KeyDown event... Check if the Key is valid... If so allow, if not then ignore the key
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        ''' <remarks></remarks>
        Private Shared Sub _TextBoxPositiveNumericKeyDown(sender As Object, e As KeyEventArgs)
            Dim handled As Boolean = True
 
            Dim txtSender As TextBox = DirectCast(sender, TextBox)
 
            If e.Key = Key.OemPeriod Then                   'Is the current key a Decimal Point?
 
                If txtSender.GetValue(IsIntegerOnlyProperty) = True Then                    'Is this an Integer Only TextBox?
 
                    handled = True
 
                ElseIf InStr(txtSender.Text, ".") = 0 And Len(txtSender.Text) > 0 Then      'We only want 1 decimal point, and it can't be the first digit!
 
                    handled = False
 
                Else                                                                        'Otherwise ignore the keypress
 
                    handled = True
 
                End If
 
            ElseIf _controlKeys.Contains(e.Key) OrElse _IsDigit(e.Key) Then                 'Is the Key a numeric or an allowed key?
 
                handled = False
 
            End If
 
            e.Handled = handled
 
        End Sub
 
#End Region
 
    End Class
 
End Namespace

The XAML to add to the UserControl Header Namespaces is;



xmlns:Behaviors="clr-namespace:CriticalPath_WPF_MVVM_EF.Behaviours"

The XAML to add to the TextBox Is;



Behaviors:clsTextBoxFilters.IsPositiveNumericFilter="True" Behaviors:clsTextBoxFilters.IsIntegerOnly="True"

Very handy!

No comments:

Post a Comment