Search code examples
c#wpfeventsevent-handling

WPF Event Handler in Another Class


I have built a series of event handlers for some custom WPF controls. The event handles format the text displayed when the user enters or leaves a textbox based on the type of data contained (Phone number, zip code, monetary value, etc.)

Right now I have all of the events locally in the C# code directly attached to the xaml. Because I have developed a could controls, this means that the logic is repeated a lot, and if I want to change the program-wide functionality I would have to make changes everywhere the event code is located.

I am sure there is a way to put all of my event handlers in a single class. Can anyone help point me in the correct direction?

I saw this article: Event Handler located in different class than MainWindow But I'm not sure if it directly relates to what I'm doing. I would rather make small changes to the existing logic that I have, as it works, then rewrite everything into commands.

I would essentially like to something like this if possible:

LostFocus="ExpandedTextBoxEvents.TextBox_LostFocus"

It is easy enough to do something like this:

private void TextBoxCurrencyGotFocus(object sender, RoutedEventArgs e)
{
    ExpandedTextBoxEvents.TextBoxCurrencyGotFocus(sender, e);
}
private void TextBoxCurrencyLostFocus(object sender, RoutedEventArgs e)
{
    ExpandedTextBoxEvents.TextBoxCurrencyLostFocus(sender, e);
}

But that is less elegant.


Solution

  • Attached Properties are one approach to what you want. I use them often.

    Attached property code:

    Public Class TextBoxFormatter
    
        ' ------------------------------------------------------
        ' Define the FormatType attached property
    
        Public Shared ReadOnly FormatTypeProperty As DependencyProperty = _
            DependencyProperty.RegisterAttached( _
                name:="FormatType", _
                propertyType:=GetType(TextBoxFormatterType), _
                ownerType:=GetType(TextBoxFormatter), _
                defaultMetadata:=New FrameworkPropertyMetadata( _
                    defaultValue:=TextBoxFormatterType.None, _
                    PropertyChangedCallback:=New PropertyChangedCallback(AddressOf FormatTypePropertyChanged) _
                ) _
            )
        ' ------------------------------------------------------
        ' Define the "getter" and "setter" for FormatType
        Public Shared Function GetFormatType(ByVal target As DependencyObject) As TextBoxFormatterType
            target.GetValue(FormatTypeProperty)
        End Function
        Public Shared Sub SetFormatType(ByVal target As DependencyObject, ByVal value As TextBoxFormatterType)
            target.SetValue(FormatTypeProperty, value)
        End Sub
    
        ' ------------------------------------------------------
        ' Define the FormatType "PropertyChanged" event handler
    
        Private Shared Sub FormatTypePropertyChanged(ByVal target As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
            If CType(e.NewValue, TextBoxFormatterType) = TextBoxFormatterType.None Then
                UnregisterFormatTypeControl(target)
            Else
                RegisterFormatTypeControl(target)
            End If
        End Sub
    
        ' ------------------------------------------------------
        ' Define the a collection of event listerns for the
        ' FormatType "PropertyChanged" event
    
        Private Shared _registeredFormatTypeControlDelegates As New Dictionary(Of TextBox, RoutedEventHandler)
    
        ' ------------------------------------------------------
        ' Register a control as an event listener
        ' (also, attach to the control's LostFocus event)
    
        Private Shared Sub RegisterFormatTypeControl(ByVal candidate As DependencyObject)
            Dim l_control = TryCast(candidate, TextBox)
            If l_control IsNot Nothing Then
                Dim l_handler = New RoutedEventHandler(AddressOf FormatTypeControl_LostFocus)
                _registeredFormatTypeControlDelegates.Add(l_control, l_handler)
                l_control.AddHandler(TextBox.LostFocusEvent, l_handler)
            End If
        End Sub
    
        ' ------------------------------------------------------
        ' Unregister a control as an event listener
        ' (also, unattach from the control's LostFocus event)
    
        Private Shared Sub UnregisterFormatTypeControl(ByVal candidate As DependencyObject)
            Dim l_control = TryCast(candidate, TextBox)
            If l_control IsNot Nothing AndAlso _registeredFormatTypeControlDelegates.ContainsKey(l_control) Then
                Dim l_handler = _registeredFormatTypeControlDelegates(l_control)
                l_control.RemoveHandler(TextBox.LostFocusEvent, l_handler)
                _registeredFormatTypeControlDelegates.Remove(l_control)
            End If
        End Sub
    
        ' ------------------------------------------------------
        ' On the control's LostFocus event, apply the format
        ' (You could apply the format based on another event,
        ' just be careful if using the TextChanged event - it
        ' will fire whether the user has changed the text or
        ' your code has changed the text - could introduce an
        ' infinite loop)
    
        Private Shared Sub FormatTypeControl_LostFocus(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim l_textBox = TryCast(e.Source, TextBox)
            If l_textBox IsNot Nothing Then
    
                Dim l_formatType = CType(l_textBox.GetValue(FormatTypeProperty), TextBoxFormatterType)
                Select Case l_formatType
    
                    Case TextBoxFormatterType.SocialSecurityNumber
                        ' Apply the format to the l_textBox
                        ' (What do you want a social security number to look like?)
    
                    Case TextBoxFormatterType.ZipCode
                        ' Apply the format to the l_textBox
                        ' (What do you want a zip code to look like?)
    
                    Case TextBoxFormatterType.Etc
                        ' Apply the format to the l_textBox
    
                End Select
    
            End If
        End Sub
    
    End Class
    
    Public Enum TextBoxFormatterType
        None
        ZipCode
        SocialSecurityNumber
        Etc
    End Enum
    

    The above looks a bit cluttered, but once the code is written (once), you can use it over and over again in your UI:

    <Window x:Class="Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:this="clr-namespace:WpfApplication1"
            Title="Window1">
      <Grid>
    
        <StackPanel>
    
          <TextBox this:TextBoxFormatter.FormatType="SocialSecurityNumber" />
          <TextBox this:TextBoxFormatter.FormatType="ZipCode" />
          <TextBox this:TextBoxFormatter.FormatType="None" />
    
        </StackPanel>
    
      </Grid>
    </Window>
    

    One advantage to this approach is that the "FormatType" becomes a dependency property, meaning you can bind values to it at run-time (rather than just harcoding, as in the example above). For example:

    <TextBox this:TextBoxFormatter.FormatType="{Binding ViewModel.DesiredFormat}" />