Reputation: 167
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.
Upvotes: 5
Views: 2239
Reputation: 32253
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:
Element.Name
(corresponding to the Windows Title)Process ID
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
Upvotes: 6
Reputation: 1083
You can use Automation as @Jimi suggested.
You can use My.Application.OpenForms to iterate throught all opened forms, but it will not help when new form is opened.
You can create some ReportSizeForm class that inherits System.Forms.Form. And change inheritance of your forms from regular System.Windows.Forms.Form to your ReportSizeForm.
Upvotes: 0