【问题标题】:Add an event to all Forms in a Project将事件添加到项目中的所有表单
【发布时间】:2018-12-31 15:49:54
【问题描述】:

如果我想在 Form 的标题中显示我的项目中每个 Form 的大小,最好的方法是什么?
我不想在每个 Form 中手动放置一个事件处理程序。
我希望该过程是自动的。
类似于重载的 Load() 事件,它在调整大小事件上添加处理程序。

【问题讨论】:

  • 您可以为此使用Automation。当打开一个窗口(任何窗口)时,它的 Automation.AddAutomationEventHandlerWindowPattern.WindowOpenedEvent 会引发。当您检测到它被激活时,您可以更改表单标题。不过,自动化处理起来有点棘手。
  • 所有表单都继承自的自定义基类将是最佳解决方案。您可以让基类修改 Shown 事件中的文本。
  • @Visual Vincent 我已经搜索过这件事,但实际上我一无所获。为这个“问题”添加几个解决方案可能会很有趣。我可以在 VB.Net 中测试自动化模式。
  • @Jimi :这是一个简单的类继承问题,真的没有什么特别的:)。所有表单都继承自System.Windows.Forms.Form,您只需将它们更改为继承YourBaseForm
  • @Visual Vincent:继承不是一种选择。我无法更改数百个表单。

标签: vb.net winforms events ui-automation


【解决方案1】:

这是一个尝试实现Automation 解决问题的方法。

问题:
将一个或多个事件处理程序附加到项目中的每个现有表单(或其子集),而无需编辑/修改这些类的现有代码。

一个可能的解决方案来自 UIAutomation,当它的AutomationEventEventId 设置为@ 987654324@图案。
AutomationElement 成员必须设置为 AutomationElement.RootElementScope 成员必须设置为 TreeScope.SubTree

Automation,对于每个引发AutomationEventAutomationElement,报告:

  • Element.Name(对应 Windows 标题)
  • Process ID
  • Window Handle(作为整数值)

这些值足以识别一个属于当前进程的Window; Window 句柄允许识别打开的Form 实例,测试Application.OpenForms() 集合。

选中表单后,可以将新的Event Handler 附加到所选的Event

通过扩展此概念,可以创建预定义的事件列表和表单列表以将这些事件附加到。
可能,需要时将类文件包含在项目中。

请注意,在这种情况下,某些事件将没有意义,因为Automation 会在窗口已经显示时报告它的打开,因此Load()Shown() 事件属于过去。


我已经用几个事件(Form.Resize()Form.Activate())对此进行了测试,但在此处的代码中,为了简单起见,我只使用了 .Resize()

这是过程的图形表示。
启动应用程序时,事件处理程序未附加到 .Resize() 事件。
这只是因为 Boolean 字段设置为 False
单击按钮,Boolean 字段设置为True,启用事件处理程序的注册。
.Resize()事件注册后,所有Forms的Title都会报告Window的当前大小。

测试环境
Visual Studio 2017 pro 15.7.5
.Net FrameWork 4.7.1

导入的命名空间:
System.Windows.Automation

参考程序集
UIAutomationClient
UIAutomationTypes

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

注意:
之前的代码放置在应用程序的启动表单中(用于测试),但最好在需要时将模块包含在项目中,而无需触及当前代码。

要使其正常工作,请添加一个包含Public Sub Main() 的新模块(名为Program),并更改项目属性以从Sub Main() 而不是表单启动应用程序。
去掉 Use Application Framework 上的复选标记,然后从 Startup object 组合中选择 Sub Main

所有代码都可以通过一些修改转移到Sub Main proc:

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

【讨论】:

  • 你肯定是一个解决方法的人;)。 -- “当 Forms 实例没有被正式释放时,一些事件处理程序可能不会被取消订阅” - 你不能通过订阅FormClosed event 来手动取消订阅吗?
  • @Visual Vincent 好吧,当谈到 VB(.Net) 默认实例时,我对会发生什么、何时发生以及由谁执行有未解决的疑问。我认为应该使用此功能(在您的代码中,您忘记了对所谓已死表单的匿名引用,并且插入了一个健康的NullRef,您将获得它的一个新实例),为爱好者留下一个向后兼容的选项。
  • 谢谢,这正是我所需要的。我昨天尝试了自动化,但我无法掌握它。
  • @mvaculisteanu 请注意,我从未在VB.Net 中为WinForms 实现此功能。所以这是一种实验(请参阅我写给@Visual Vincent 的内容)。它成功地在几个用不同语言编写的程序中运行。如果我发现有什么值得注意的地方,我会通知你。
  • 我需要这个来测试而不是生产。如果出现任何问题,无需担心。 ;)
【解决方案2】:

您可以按照@Jimi 的建议使用自动化。

您可以使用 My.Application.OpenForms 遍历所有打开的表单,但在打开新表单时无济于事。

您可以创建一些继承 System.Forms.Form 的 ReportSizeForm 类。并将表单的继承从常规 System.Windows.Forms.Form 更改为 ReportSizeForm。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多