4

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.

Community
  • 1
  • 1
Nathan Tornquist
  • 6,468
  • 10
  • 47
  • 72
  • You want to use common code for multiple TextBox items so they behave the same? In this case you would most likely subclass TextBox and then use this custom class. For example, if it's a TextBox for a phone number, you could name it "PhoneNumberTextBox". – Trevor Elliott Jun 04 '12 at 16:00
  • LostFocus is not a DependencyProperty, it is an event, so you cannot bind values to it. When the XAML parser reads the text in the XAML attribute for LostFocus, it uses Reflection to find a method with the same name in the base class. To my knowledge, there is no way to override this behavior, nor would it be expedient to do so. Your best bet is to use one of the solutions presented below. – JDB Jun 05 '12 at 20:07
  • Yes you can ! The best answer => https://stackoverflow.com/a/29607336/2736742 – A. Morel Nov 09 '19 at 07:07

2 Answers2

2

You could create a dependency property and use it in the XAML like this:

ExpandedTextBoxEvents.Subscribe="True"

The dependency property would exist in the ExpandedTextBoxEvents class, and the Subscribe property could establish all the necessary event subscriptions when it gets set to True.

This would leave you with a separate class, pulled in by the XAML (or C# if you prefer) in one simple statement.

John Fisher
  • 22,355
  • 2
  • 39
  • 64
2

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}" />
JDB
  • 25,172
  • 5
  • 72
  • 123