Add an event to all Forms in a Project
If I want to display the size of every Form
in my Project in the Form
's Title what will be the best approach?
I don't want to manually put a event handler in every Form
.
I want the process to be automatic.
Something like a overloaded Load()
event that adds a handler on the resize event.
Solution 1:
Here is an attempt to implement an Automation solution to the problem.
The problem:
Attach one or more Event Handlers to each existing Form in a Project (or a subset of them), without editing/modifying these classes existing code.
A possible solution comes from UIAutomation, which provides means to detect when a new Window is opened and reports the event to the subscribers of its own Automation.AddAutomationEventHandler, when the EventId
of its AutomationEvent is set to a WindowPattern pattern.
The AutomationElement member must be set to AutomationElement.RootElement and the Scope
member to TreeScope.SubTree.
Automation
, for each AutomationElement
that raises the AutomationEvent
, reports:
- the
Element.Name
(corresponding to the Windows Title) - the
Process ID
- the
Window Handle
(as an Integer value)
These values are quite enough to identify a Window that belongs to the current process; the Window handle allows to identify the opened Form
instance, testing the Application.OpenForms() collection.
When the Form is singled out, a new Event Handler
can be attached to an Event
of choice.
By expanding this concept, it's possible to create a predefined List of Events and a List of Forms to attach these events to.
Possibly, with a class file to include in a Project when required.
As a note, some events will not be meaningful in this scenario, because the Automation
reports the opening of a Window when it is already shown, thus the Load()
and Shown()
events belong to the past.
I've tested this with a couple of events (Form.Resize()
and Form.Activate()
), but in the code here I'm using just .Resize()
for simplicity.
This is a graphics representation of the process.
Starting the application, the Event Handler is not attached to the .Resize()
event.
It's just because a Boolean
fields is set to False
.
Clicking a Button, the Boolean
field is set to True
, enabling the registration of the Event Handler.
When the .Resize()
event is registered, all Forms' Title will report the current size of the Window.
Test environment:Visual Studio 2017 pro 15.7.5
.Net FrameWork 4.7.1
Imported Namespaces:System.Windows.Automation
Reference Assemblies:UIAutomationClient
UIAutomationTypes
MainForm
code:
Imports System.Diagnostics
Imports System.Windows
Imports System.Windows.Automation
Public Class MainForm
Friend GlobalHandlerEnabled As Boolean = False
Protected Friend FormsHandler As List(Of Form) = New List(Of Form)
Protected Friend ResizeHandler As EventHandler
Public Sub New()
InitializeComponent()
ResizeHandler =
Sub(obj, args)
Dim CurrentForm As Form = TryCast(obj, Form)
CurrentForm.Text = CurrentForm.Text.Split({" ("}, StringSplitOptions.None)(0) &
$" ({CurrentForm.Width}, {CurrentForm.Height})"
End Sub
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent,
AutomationElement.RootElement,
TreeScope.Subtree,
Sub(UIElm, evt)
If Not GlobalHandlerEnabled Then Return
Dim element As AutomationElement = TryCast(UIElm, AutomationElement)
If element Is Nothing Then Return
Dim NativeHandle As IntPtr = CType(element.Current.NativeWindowHandle, IntPtr)
Dim ProcessId As Integer = element.Current.ProcessId
If ProcessId = Process.GetCurrentProcess().Id Then
Dim CurrentForm As Form = Nothing
Invoke(New MethodInvoker(
Sub()
CurrentForm = Application.OpenForms.
OfType(Of Form)().
FirstOrDefault(Function(f) f.Handle = NativeHandle)
End Sub))
If CurrentForm IsNot Nothing Then
Dim FormName As String = FormsHandler.FirstOrDefault(Function(f) f?.Name = CurrentForm.Name)?.Name
If Not String.IsNullOrEmpty(FormName) Then
RemoveHandler CurrentForm.Resize, ResizeHandler
FormsHandler.Remove(FormsHandler.Where(Function(fn) fn.Name = FormName).First())
End If
Invoke(New MethodInvoker(
Sub()
CurrentForm.Text = CurrentForm.Text & $" ({CurrentForm.Width}, {CurrentForm.Height})"
End Sub))
AddHandler CurrentForm.Resize, ResizeHandler
FormsHandler.Add(CurrentForm)
End If
End If
End Sub)
End Sub
Private Sub btnOpenForm_Click(sender As Object, e As EventArgs) Handles btnOpenForm.Click
Form2.Show(Me)
End Sub
Private Sub btnEnableHandlers_Click(sender As Object, e As EventArgs) Handles btnEnableHandlers.Click
GlobalHandlerEnabled = True
Me.Hide()
Me.Show()
End Sub
Private Sub btnDisableHandlers_Click(sender As Object, e As EventArgs) Handles btnDisableHandlers.Click
GlobalHandlerEnabled = False
If FormsHandler IsNot Nothing Then
For Each Item As Form In FormsHandler
RemoveHandler Item.Resize, ResizeHandler
Item = Nothing
Next
End If
FormsHandler = New List(Of Form)
Me.Text = Me.Text.Split({" ("}, StringSplitOptions.RemoveEmptyEntries)(0)
End Sub
End Class
Note:
This previous code is placed inside the app Starting Form (for testing), but it might be preferable to have a Module to include in the Project when needed, without touching the current code.
To get this to work, add a new Module (named Program
) which contains a Public Sub Main()
, and change the Project properties to start the application from Sub Main()
instead of a Form.
Remove the check mark on Use Application Framework
and choose Sub Main
from the Startup object
Combo.
All the code can be transferred to the Sub Main
proc with a couple of modifications:
Imports System
Imports System.Diagnostics
Imports System.Windows
Imports System.Windows.Forms
Imports System.Windows.Automation
Module Program
Friend GlobalHandlerEnabled As Boolean = True
Friend FormsHandler As List(Of Form) = New List(Of Form)
Friend ResizeHandler As EventHandler
Public Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
Dim MyMainForm As MainForm = New MainForm()
ResizeHandler =
Sub(obj, args)
Dim CurrentForm As Form = TryCast(obj, Form)
CurrentForm.Text = CurrentForm.Text.Split({" ("}, StringSplitOptions.None)(0) &
$" ({CurrentForm.Width}, {CurrentForm.Height})"
End Sub
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent,
AutomationElement.RootElement,
TreeScope.Subtree,
Sub(UIElm, evt)
If Not GlobalHandlerEnabled Then Return
Dim element As AutomationElement = TryCast(UIElm, AutomationElement)
If element Is Nothing Then Return
Dim NativeHandle As IntPtr = CType(element.Current.NativeWindowHandle, IntPtr)
Dim ProcessId As Integer = element.Current.ProcessId
If ProcessId = Process.GetCurrentProcess().Id Then
Dim CurrentForm As Form = Nothing
If Not MyMainForm.IsHandleCreated Then Return
MyMainForm.Invoke(New MethodInvoker(
Sub()
CurrentForm = Application.OpenForms.
OfType(Of Form)().
FirstOrDefault(Function(f) f.Handle = NativeHandle)
End Sub))
If CurrentForm IsNot Nothing Then
Dim FormName As String = FormsHandler.FirstOrDefault(Function(f) f?.Name = CurrentForm.Name)?.Name
If Not String.IsNullOrEmpty(FormName) Then
RemoveHandler CurrentForm.Resize, ResizeHandler
FormsHandler.Remove(FormsHandler.Where(Function(fn) fn.Name = FormName).First())
End If
AddHandler CurrentForm.Resize, ResizeHandler
FormsHandler.Add(CurrentForm)
CurrentForm.Invoke(New MethodInvoker(
Sub()
CurrentForm.Text = CurrentForm.Text & $" ({CurrentForm.Width}, {CurrentForm.Height})"
End Sub))
End If
End If
End Sub)
Application.Run(MyMainForm)
End Sub
End Module