Wednesday 13 May 2009

Which control lost the focus?

Sometimes underneath all the abstraction that is the .Net framework, they take away stuff that you really want to know. My requirement was essentially that, for a load of textboxes on a Windows Form, when the user clicks on the one-and-only command-button I needed to know which control/textbox had the focus before the button was clicked.
Clearly I could manually code things in the OnLostFocus/OnLeave methods, or listen for LostFocus or Leave events on each of the controls, but that seemed like plenty of work and plenty ugly. (After all, I'm only interested when the user clicks on the button and the button's GetFocus / Enter events fire, so this functionality should belong to the button, right?)

The "Win32" event which the .Net framework is massaging into its own events does provide the control that's losing focus, apparently. And you can override the framework method, WndProc, that's way up the stack there somewhere.

Anyway, I'm working in vb.net at the moment so I've come up with something like what's below...
(I haven't necessarily worked out all the foibles, but it's certainly doing the basic job. I also haven't bothered synchronising access to the PreviousControl property - can't see when that's going to cause me an issue, but YMMV. BTW, Google tells me Microsoft Access had a PreviousControl property... hence the name.)

Public Class ButtonThatTracksPreviousFocus
Inherits Button

''' <summary>
''' This PreviousControl property holds the last control that was known to have focus before this button
''' received the focus. Note that where this button has the focus on an inactive window, when the parent
''' window activates the PreviousControl will end up being the last control to have focus on the
''' other form, it seems.
''' </summary>
''' <remarks></remarks>
Private _previousControl As Control

Public Property PreviousControl() As Control
Get
Return _previousControl
End Get
Set(ByVal value As Control)
_previousControl = value
End Set
End Property


Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)

' First check to see if this message is related to our gaining focus. If it is, make a note of the
' window / control that's losing it.

If m.Msg = 7 Then 'This is the WM_SETFOCUS message (which tells us in WParam the "window" that's losing focus)
Try
' We don't expect errors, but in the event that we get them we can simply assume we couldn't
' find the control and otherwise ignore.
If Not IntPtr.Zero.Equals(m.WParam) Then
Dim ctrl As Control = Control.FromHandle(m.WParam)
If Not ctrl Is Nothing AndAlso Not (ctrl.Equals(Me)) Then
' Store a reference to the "lost-focus" control. Note that this could actually be on
' another form so client-code may need to take account of this!
Me.PreviousControl = ctrl
End If
End If
Catch
Me.PreviousControl = Nothing
End Try


End If

'Regardless, normal message processing is carried out...
MyBase.WndProc(m)

End Sub

End Class