Using Dependency Properties to extend existing controls in WPF

This article shows how to code a simple dependency property class that will extend existing standard controls. It demostrates the usage of dependency properties.

So you have a user who requires some behaviour on your controls, but the controls are Microsoft's standard ones, for example the textbox.

You don
't want to subclass the control and then have to go around your xaml changing from one to another.

Well this is where dependency properties come into
view as they allow you to extend the properties and hence the behaviour of existing controls.

Recently someone was asked to change their form so that the enter key moved like the tab key and then they were asked to make the text get selected on focus, thus enabling quick data entry.

So lets start off by creating a simple dependency property to perform the enter as tab.

The easie
st way of changing the behaviour of the enter key is to handle the previewkeydown event and override the key code and change from enter to tab. We could do this by changing the form in the code behind, but this would clutter the code.

So the first thing we do is a add a new class that will contain the code handlers etc for the new property.

We need to register our new property and then deal with it's va
lue changing

Public Shared ReadOnly EnterAsTabProperty As DependencyProperty = DependencyProperty.RegisterAttached("EnterAsTab", GetType(Boolean), GetType(SmartText), New UIPropertyMetadata(False, AddressOf EnterAsTabChanged))

this line register a new property EnterAsTab and tells the system that when it's value changes to call the metho
d  EnterAsTabChanged

Note when you register a property like this there will have to be 2 methods to handle the property set/get, these must be named SetXXXX,GetXXXX, in our case we will do nothing here other tahn route though to the dependency object where out property will be stored. ( note this means that the property is sto
red not on our class, but on the target object itself )

So the job is nearly done, we just have to do the tricky wotk in
our changed event, ie. attach to the event and make sure we attach to the unloaded event so we can clean up afterwards.

AddHandler ue.Unloaded, AddressOf ue_Unloaded
AddHandler ue.PreviewKeyDown, AddressOf ue_PreviewKeyDown

A quick look at the previewkeydown and you'll see our target code

so a quick build and we can now go to our form xaml
add a namespace reference        
xmlns:my="clr-namespace:WpfApplication1DelMe"
and then we can add the new property to the grid control
    my:SmartText.EnterAsTab="True"

and there we go start up and the new behav
iour is added to the grid and it's contained text boxes.

So now we move onto the autoselect property, we follow the same method as above, but one
difference, here we want to be able to add to either a container ( eg grid ) or the text box level itself

But when you try and do this you'd like to iterate the textbox controls in the grid
.

How do we do that?
here we find that the controls both the the logical tree and visual tree haven't been buitl yet, so the usual mehtod of iteraiting controls fails.

So lets be sneaky and
hook the template loaded event after which we know the tree will have been build and then use the same method as above on the iterated tree

For i = 1 To VisualTreeHelper.GetChildrenCount(sender)

            Dim dobj As DependencyObject = VisualTreeHelper.GetChild(sender, i - 1)

            If TypeOf dobj Is TextBox Then
                Dim txt As TextBox = dobj
                AddHandler txt.Unloaded, AddressOf ue_AutoSelectUnloaded
                AddHandler txt.GotFocus, AddressOf ue_GotFocus
            End If

        Next

Now are job is done and we can add the new property at the level we want..

No doubt there are a few bits in the code that are wrong, but i think it's a good starting point and explains a simple use of dependency properties


Public Class SmartText

#Region "EnterAsTab"


    Public Shared Function GetEnterAsTab(ByVal obj As DependencyObject) As Boolean

        GetEnterAsTab = obj.GetValue(EnterAsTabProperty)

    End Function

    Public Shared Sub SetEnterAsTab(ByVal obj As DependencyObject, ByVal value As Boolean)

        obj.SetValue(EnterAsTabProperty, value)

    End Sub

    Public Shared Sub ue_PreviewKeyDown(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs)
        Dim ue As FrameworkElement

        ue = e.OriginalSource
        If (e.Key = Key.Enter) Then
            e.Handled = True
            ue.MoveFocus(New TraversalRequest(FocusNavigationDirection.Next))
        End If
    End Sub


    Private Shared Sub ue_Unloaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Dim ue As FrameworkElement

        ue = sender

        If (ue Is Nothing) Then Return

        RemoveHandler ue.Unloaded, AddressOf ue_Unloaded
        RemoveHandler ue.PreviewKeyDown, AddressOf ue_PreviewKeyDown
    End Sub


    Public Shared ReadOnly EnterAsTabProperty As DependencyProperty = DependencyProperty.RegisterAttached("EnterAsTab", GetType(Boolean), GetType(SmartText), New UIPropertyMetadata(False, AddressOf EnterAsTabChanged))

    Public Shared Sub EnterAsTabChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim ue As FrameworkElement
        ue = d
        If (ue Is Nothing) Then Return

        If (e.NewValue) Then
            AddHandler ue.Unloaded, AddressOf ue_Unloaded
            AddHandler ue.PreviewKeyDown, AddressOf ue_PreviewKeyDown
        Else
            RemoveHandler ue.PreviewKeyDown, AddressOf ue_PreviewKeyDown
        End If
    End Sub

#End Region


#Region "AutoSelect"


    Public Shared Function GetAutoSelect(ByVal obj As DependencyObject) As Boolean

        GetAutoSelect = obj.GetValue(AutoSelectProperty)

    End Function

    Public Shared Sub SetAutoSelect(ByVal obj As DependencyObject, ByVal value As Boolean)

        obj.SetValue(AutoSelectProperty, value)

    End Sub

    Public Shared Sub ue_GotFocus(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Dim ue As TextBox = sender

        ue.SelectAll()

    End Sub

    Private Shared Sub ue_AutoSelectContainerLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)

        Dim pa As FrameworkElement = sender
        RemoveHandler pa.Loaded, AddressOf ue_AutoSelectContainerLoaded

        For i = 1 To VisualTreeHelper.GetChildrenCount(sender)

            Dim dobj As DependencyObject = VisualTreeHelper.GetChild(sender, i - 1)

            If TypeOf dobj Is TextBox Then
                Dim txt As TextBox = dobj
                AddHandler txt.Unloaded, AddressOf ue_AutoSelectUnloaded
                AddHandler txt.GotFocus, AddressOf ue_GotFocus
            End If

        Next

    End Sub

    Private Shared Sub ue_AutoSelectUnloaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Dim ue As TextBox

        ue = sender

        If (ue Is Nothing) Then Return

        RemoveHandler ue.Unloaded, AddressOf ue_AutoSelectUnloaded
        RemoveHandler ue.GotFocus, AddressOf ue_GotFocus
    End Sub


    Public Shared ReadOnly AutoSelectProperty As DependencyProperty = DependencyProperty.RegisterAttached("AutoSelect", GetType(Boolean), GetType(SmartText), New UIPropertyMetadata(False, AddressOf AutoSelectChanged))

    Public Shared Sub AutoSelectChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)

        If Not TypeOf d Is TextBox Then

            If Not TypeOf d Is FrameworkElement Then Return

            Dim pa As FrameworkElement = d

            If (e.NewValue) Then
                AddHandler pa.Loaded, AddressOf ue_AutoSelectContainerLoaded
            Else
                RemoveHandler pa.Loaded, AddressOf ue_AutoSelectContainerLoaded
                For i = 0 To VisualTreeHelper.GetChildrenCount(pa)

                    Dim dobj As DependencyObject
                    dobj = VisualTreeHelper.GetChild(pa, i)

                    If TypeOf dobj Is TextBox Then
                        Dim txt As TextBox = dobj
                        RemoveHandler txt.GotFocus, AddressOf ue_GotFocus
                    End If

                Next
            End If

            Return

        Else

            Dim ue As TextBox = d

            If (e.NewValue) Then
                AddHandler ue.Unloaded, AddressOf ue_AutoSelectUnloaded
                AddHandler ue.GotFocus, AddressOf ue_GotFocus
            Else
                RemoveHandler ue.GotFocus, AddressOf ue_GotFocus
            End If
        End If

    End Sub

#End Region

    Private Shared Sub DumpLogicalTree(ByVal parent As Object, ByVal level As Integer)

        Dim doParent As DependencyObject
        Dim typeName As String = parent.GetType().Name
        Dim name As String = Nothing

        If (TypeOf parent Is DependencyObject) Then
            doParent = parent
            name = doParent.GetValue(FrameworkElement.NameProperty)
            Trace.WriteLine(String.Format("{0}: {1}", typeName, name))

            For Each child In LogicalTreeHelper.GetChildren(doParent)
                DumpLogicalTree(child, level + 1)
            Next
        Else
            name = parent.ToString()
            Trace.WriteLine(String.Format("{0}: {1}", typeName, name))
            Return

        End If



    End Sub

End Class

By pete rainbow   Popularity  (2679 Views)
Picture
Biography - pete rainbow
Windows Gui Developer with many years of experience from MFC days right through to WPF

Currently looking for any work in the London area