Добавить событие во все формы в проекте

Если я хочу отображать размер каждого Form в моем проекте в заголовке Form, что будет лучшим подходом?
Я не хочу вручную помещать обработчик событий в каждый Form.
Я хочу, чтобы процесс быть автоматическим.
Что-то вроде перегруженного события Load(), которое добавляет обработчик события изменения размера.


person mvaculisteanu    schedule 24.07.2018    source источник
comment
Вы можете использовать Automation для этого. Его Automation.AddAutomationEventHandler с WindowPattern.WindowOpenedEvent возникает при открытии окна (любого окна). Вы можете изменить заголовок формы, когда обнаружите, что она была активирована. Однако автоматизация немного деликатна в обращении.   -  person Jimi    schedule 24.07.2018
comment
Лучшим решением будет собственный базовый класс, от которого наследуются все ваши формы. Вы можете заставить базовый класс изменить текст, скажем, в событии Shown.   -  person Visual Vincent    schedule 24.07.2018
comment
@Visual Vincent Я искал по этому вопросу и ничего не нашел по этому поводу. Было бы интересно добавить пару решений этой проблемы. Я мог бы протестировать шаблон автоматизации в VB.Net.   -  person Jimi    schedule 24.07.2018
comment
@Jimi Джими: это вопрос простого наследования классов, на самом деле ничего особенного :). Все формы уже наследуются от System.Windows.Forms.Form, вам просто нужно изменить их, чтобы вместо этого наследовать YourBaseForm.   -  person Visual Vincent    schedule 24.07.2018
comment
@Visual Vincent: Наследование не вариант. Я не могу изменить сотни форм.   -  person mvaculisteanu    schedule 24.07.2018
comment
@Visual Vincent Да, но поскольку один из параметров OP требует, чтобы вы не изменяли / не редактировали существующие формы (можно также использовать шаблоны при создании пользовательского интерфейса с нуля), я подумал, что было бы интересно иметь ссылку, если вам нужен какой-то глобальный обработчик, который позволяет добавлять/удалять обработчики событий в Windows верхнего уровня на лету и изменять их поведение/представление во время выполнения , используя разные инструменты. На самом деле, я хотел бы услышать, что другие думают о реализации автоматизации в VB.Net (если кто-то еще тестировал ее и это плохая новость, было бы неплохо узнать об этом).   -  person Jimi    schedule 24.07.2018
comment
@mvaculisteanu: вы можете использовать приложение, такое как Notepad ++, для поиска во всех ваших файлах дизайнера и замены Inherits System.Windows.Forms.Form на Inherits YourBaseForm одним щелчком мыши. Visual Studio также поддерживает это через поле поиска (CTRL + F в редакторе кода), но не позволяет фильтровать файлы для поиска.   -  person Visual Vincent    schedule 24.07.2018
comment
@Jimi: В вопросе ОП не указано, что он не хочет изменять существующие формы (хотя теперь он заявил об этом в комментарии), только то, что он не хочет вручную помещать обработчик событий в каждую. Пост также противоречит сам себе: Я не хочу вручную помещать обработчик событий в каждую форму (...) Что-то вроде перегруженного события загрузки, которое добавляет обработчик на событие изменения размера — если он хочет, чтобы событие загрузки применялось ко всем формам, наследование — самый безопасный способ. || У вас есть интересная мысль, и я не говорю, что это невозможно, просто обязательно тщательно ее проверьте.   -  person Visual Vincent    schedule 24.07.2018
comment
@Visual Vincent: я имел в виду некий процесс, который автоматически добавляет обработчик во время выполнения. Я не хочу/не могу позволить себе изменить определение формы.   -  person mvaculisteanu    schedule 24.07.2018
comment
Дайте ему тест-драйв. Похоже, это работает, но могут быть некоторые причуды с экземплярами VB.Net по умолчанию. Когда экземпляры Forms не размещены формально, некоторые обработчики событий могут быть неправильно отменены (я думаю).   -  person Jimi    schedule 24.07.2018
comment
@VisualVincent Не будут ли перезаписаны какие-либо изменения в файлах конструктора, когда пользователь в следующий раз откроет конструктор?   -  person Zev Spitz    schedule 27.07.2018
comment
@ZevSpitz Нет, не все. Наследование, например, сохраняется.   -  person Visual Vincent    schedule 27.07.2018


Ответы (2)


Вот попытка реализовать Автоматизация решения проблемы.

Проблема:
Прикрепите один или несколько обработчиков событий к каждому существующему Form в проекте WinForms (или к их подмножеству), не редактируя/модифицируя существующий код этих классов.

Возможное решение исходит от класса Automation, который предоставляет средства для обнаружения открытия нового окна и сообщает об этом событии своим собственным подписчикам Automation.AddAutomationEventHandler, when the EventId of its AutomationEvent имеет значение WindowPattern.
Шаблон AutomationElement должно быть задано значение AutomationElement.RootElement и член Scope в TreeScope.SubTree.

Automation, для каждого AutomationElement, вызывающего AutomationEvent, сообщает:
- Element.Name (соответствует заголовку Windows)
- Process ID
- Window Handle (в виде целого числа)

Этих значений вполне достаточно, чтобы идентифицировать окно, принадлежащее текущему процессу; дескриптор окна позволяет идентифицировать открытый экземпляр Form, тестируя коллекция Application.OpenForms() .

Когда форма выделена, новый Event Handler может быть присоединен к выбранному Event.

Расширяя эту концепцию, можно создать предопределенный список событий и список форм для прикрепления этих событий.
Возможно, с файлом класса для включения в проект при необходимости.

Обратите внимание, что некоторые события не будут иметь значения в этом сценарии, поскольку Automation сообщает об открытии окна, когда оно уже показано, поэтому события Load() и Shown() относятся к прошлому.


Я проверил это с парой событий (Form.Resize() и Form.Activate()), но в этом коде для простоты я использую только .Resize().

Это графическое представление процесса.
При запуске приложения обработчик событий не привязан к событию .Resize().
Это просто потому, что для поля Boolean задано значение False.
При нажатии кнопки Boolean в поле установлено значение True, что позволяет зарегистрировать обработчик событий.
Когда зарегистрировано событие .Resize(), все Forms Window Title будут сообщать о текущем размере окна.

Глобальные обработчики

Тестовая среда:
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

Примечание.
Этот предыдущий код помещается в стартовую форму приложения (для тестирования), но может быть предпочтительнее иметь Module для включения в проект при необходимости, не затрагивая текущий код.

Чтобы это заработало, добавьте новый Module (с именем Program), который содержит Public Sub Main(), и измените свойства проекта, чтобы приложение запускалось с Sub Main вместо Form.
Снимите флажок "Использовать Application Framework" и выберите " Sub Main" из комбо "Startup object".

Весь код можно перенести в прок Sub Main с парой модификаций:

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
person Jimi    schedule 24.07.2018
comment
Вы, конечно, парень для обходных путей;). -- Если экземпляры Forms формально не удалены, некоторые обработчики событий могут не быть отменены - Нельзя ли просто отказаться от подписки на них вручную, подписавшись на файл FormClosed событие как Что ж? - person Visual Vincent; 25.07.2018
comment
@Visual Vincent Что ж, когда дело доходит до экземпляров VB (.Net) по умолчанию, у меня есть неразрешенные сомнения относительно того, что произойдет, когда и чьими руками. Я думаю, что эта функция должна быть удалена (в вашем коде вы забываете анонимную ссылку на предположительно мертвую форму, и вместо здоровой NullRef вы получаете ее новый экземпляр), оставляя возможность обратной совместимости для поклонников. - person Jimi; 25.07.2018
comment
Спасибо, это именно то, что мне было нужно. Я вчера попробовал автоматизацию, но не смог ее освоить. - person mvaculisteanu; 25.07.2018
comment
@mvaculisteanu Имейте в виду, что я никогда не реализовывал это в VB.Net для WinForms. Так что это своего рода эксперимент (посмотрите, что я написал @Visual Vincent). Он успешно работает в паре программ, написанных на разных языках. Если я найду что-то примечательное, я сообщу вам. - person Jimi; 25.07.2018
comment
Мне это нужно для тестирования, а не для производства. Нет необходимости беспокоиться в случае любой икоты. ;) - person mvaculisteanu; 25.07.2018
comment
@mvaculisteanu Я добавил примечание об использовании ModuleSub Main) для включения в проект вместо редактирования формы запуска. Может быть полезно. - person Jimi; 25.07.2018
comment
Я хотел бы проголосовать за это несколько раз. Это был лучший учебник для меня, чем все остальное, что я нашел в другом месте (для несвязанной задачи, которая требовала много нестандартного мышления). Этот и другие хорошо написанные ответы, как и причина того, что SO так хорош: понятные ответы, которые помогают не только текущему пользователю, но и каждому человеку, которому он понадобится со временем. Удивительно. Когда скайнет захватит власть, держу пари, он пощадит тебя. - person laancelot; 16.12.2020

Вы можете использовать автоматизацию, как предложил @Jimi.

Вы можете использовать My.Application.OpenForms для перебора всех открытых форм, но это не поможет при открытии новой формы.

Вы можете создать некоторый класс ReportSizeForm, который наследует System.Forms.Form. И измените наследование ваших форм с обычного System.Windows.Forms.Form на ваш ReportSizeForm.

person PavlinII    schedule 24.07.2018