vb.net - Agregar un evento a todos los formularios en un proyecto
winforms events (2)
Si quiero mostrar el tamaño de cada
Form
en mi Proyecto en el Título del
Form
, ¿cuál será el mejor enfoque?
No quiero poner manualmente un controlador de eventos en cada
Form
.
Quiero que el proceso sea automático.
Algo así como un evento
Load()
sobrecargado que agrega un controlador en el evento de cambio de tamaño.
Aquí hay un intento de implementar una solución de
Automation
para el problema.
El problema:
Adjunte uno o más controladores de eventos a cada
Form
existente en un proyecto de
WinForms
(o un subconjunto de ellos), sin editar / modificar el código existente de estas clases.
Una posible solución proviene de la clase de
Automation
, que proporciona medios para detectar cuándo se abre una nueva ventana e informa del evento a los suscriptores de su propio
Automation.AddAutomationEventHandler
, cuando el
EventId
de su
AutomationEvent
se establece en un patrón
WindowPattern
.
El miembro
AutomationElement
debe establecer en
AutomationElement.RootElement
y el miembro
Scope
en
TreeScope.SubTree
.
Automation
, para cada
AutomationElement
que genera
AutomationEvent
, informa:
-
Element.Name
(correspondiente al título de Windows)
- la
Process ID
- la
Window Handle
la
Window Handle
(como un valor entero)
Estos valores son suficientes para identificar una ventana que pertenece al proceso actual;
El manejador de la ventana permite identificar la instancia del
Form
abierto, probando la colección
Application.OpenForms()
.
Cuando se selecciona el formulario, se puede adjuntar un nuevo
Event Handler
a un
Event
de su elección.
Al expandir este concepto, es posible crear una Lista de Eventos predefinida y una Lista de Formularios para adjuntar estos eventos.
Posiblemente, con un archivo de clase para incluir en un proyecto cuando sea necesario.
Como nota, algunos eventos no serán significativos en este escenario, ya que la
Automation
informa la apertura de una ventana cuando ya se muestra, por lo tanto, los eventos
Load()
y
Shown()
pertenecen al pasado.
He probado esto con un par de eventos (
Form.Resize()
y
Form.Activate()
), pero en el código aquí estoy usando
.Resize()
para simplificar.
Esta es una representación gráfica del proceso.
Al iniciar la aplicación, el
.Resize()
eventos no se adjunta al evento
.Resize()
.
Es solo porque un campo
Boolean
está configurado en
False
.
Al hacer clic en un botón, el campo
Boolean
se establece en
True
, lo que permite el registro del controlador de eventos.
Cuando se registra el evento
.Resize()
, todos los
Window Title
Forms
informarán el tamaño actual de la ventana.
Entorno de prueba
:
Visual Studio 2017 pro 15.7.5
.Net FrameWork 4.7.1
Espacios de nombres importados:
System.Windows.Automation
Asambleas de referencia
:
UIAutomationClient
UIAutomationTypes
Código de
MainForm
:
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
Nota:
Este código anterior se coloca dentro del Formulario de inicio de la aplicación (para pruebas), pero es preferible tener un
Module
para incluir en el Proyecto cuando sea necesario, sin tocar el código actual.
Para que esto funcione, agregue un nuevo
Module
(denominado
Program
) que contenga un
Public Sub Main()
, y cambie las propiedades del Proyecto para iniciar la aplicación desde
Sub Main
lugar de un
Form
.
Elimine la marca de verificación en "Usar marco de aplicación" y elija "Sub Main" del Combo "Objeto de inicio".
Todo el código se puede transferir al proceso
Sub Main
con un par de modificaciones:
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
Puedes usar la automatización como se sugiere en @Jimi.
Puede usar My.Application.OpenForms para recorrer en iteración todos los formularios abiertos, pero no ayudará cuando se abra un nuevo formulario.
Puede crear alguna clase ReportSizeForm que herede System.Forms.Form. Y cambie la herencia de sus formularios de System.Windows.Forms.Form regular a su ReportSizeForm.