4 Вопрос: Запустить событие при загрузке любой формы

вопрос создан в Thu, May 2, 2019 12:00 AM

Я пытаюсь создать Конкурс популярности для форм в нашем основном интерфейсе. Есть много предметов, которые больше не используются, но получение сведений о том, какие из них больше не используются, оказывается трудным.

Итак, мне пришла в голову идея регистрировать форму, когда она загружается, а затем через год или около того я буду руководить группой и узнавать, какие формы используются, как часто и кем. Теперь проблема в том, что я не хочу добавлять строку в каждый блок InitializeComponent форм. Вместо этого я хотел бы поместить это в файл Program.cs и кое-как перехватить все загрузки форм, чтобы я мог их регистрировать.

Возможно ли это?

Изменить

Используя комментарий @ Jimi, я смог придумать следующее.

using CrashReporterDotNET;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;

namespace Linnabary
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //This keeps the user from opening multiple copies of the program
            string[] clArgs = Environment.GetCommandLineArgs();
            if (PriorProcess() != null && clArgs.Count() == 1)
            {
                MessageBox.Show("Another instance of the WOTC-FE application is already running.");
                return;
            }

            //Error Reporting Engine Setup
            Application.ThreadException += ApplicationThreadException;
            AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;


            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            //This is the SyncFusion License Key.
            Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("<Removed>");

            //Popularity Contest
            Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent,
                         AutomationElement.RootElement, TreeScope.Subtree, (UIElm, evt) =>
                          {
                              try
                              {
                                  AutomationElement element = UIElm as AutomationElement;
                                  string AppText = element.Current.Name;
                                  if (element.Current.ProcessId == Process.GetCurrentProcess().Id)
                                  {
                                      Classes.Common.PopularityContest(AppText);
                                  }
                              }
                              catch (Exception)
                              {
                                  //throw;
                              }
                          });


            Application.Run(new Forms.frmMain());
        }

        private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
        {
            ReportCrash((Exception)unhandledExceptionEventArgs.ExceptionObject);
            Environment.Exit(0);
        }

        private static void ApplicationThreadException(object sender, ThreadExceptionEventArgs e)
        {
            ReportCrash(e.Exception);
        }

        public static void ReportCrash(Exception exception, string developerMessage = "")
        {
            var reportCrash = new ReportCrash("<Removed>")
            {
                CaptureScreen = true,
                DeveloperMessage = Environment.UserName,
                ToEmail = "<Removed>"
            };
            reportCrash.Send(exception);
        }

        public static Process PriorProcess()
        {
            Process curr = Process.GetCurrentProcess();
            Process[] procs = Process.GetProcessesByName(curr.ProcessName);
            foreach (Process p in procs)
            {
                if ((p.Id != curr.Id) && (p.MainModule.FileName == curr.MainModule.FileName))
                {
                    return p;
                }
            }
            return null;
        }
    }
}

Однако мне интересно, есть ли способ получить имя формы вместо ее текста. Так как он обращается ко ВСЕМ окнам и поэтому находится за пределами управляемого пространства, я сомневаюсь в этом. Тем не менее, это работает, и я опубликую это как ответ завтра, если никто не сделает этого.

    
1
  1. Возможно, вы захотите взглянуть на шаблон дизайна Observer
    2019-05-02 15: 08: 57Z
  2. Вы можете добавить к Program.cs AutomationEventHandler . Это событие возникает, когда должно быть показано любое окно (любое окно). Вы можете определить, принадлежит ли это окно текущему процессу (вашему приложению) и, если оно есть, зарегистрировать его. Вот пример работы в C #: Запустите текущее приложение в качестве единственного экземпляра и покажите предыдущий экземпляр (второй раздел кода), который обнаруживает обратное, просто удалите !.
    2019-05-02 15: 23: 22Z
  3. @ Cid Я создал класс, который использует IObserver < FormCollection > но он не срабатывает при открытии формы. Я никогда не использовал IObserver раньше, поэтому я, вероятно, не в этом разбираюсь.
    2019-05-02 15: 27: 34Z
  4. @ Jimi Этот код больше подходит для предотвращения запуска второго экземпляра программы. Я не вижу, как я могу что-то вызвать при загрузке формы.
    2019-05-02 15: 38: 00Z
  5. Нет. Этот код в целом используется для предотвращения второго экземпляра приложения (и чего-то большего). Вторая часть кода (которая находится внутри конструктора формы в этом коде и должна быть перемещена в sub Main в вашем случае), только определяет, когда открыто окно, и определяет, не является ли она частью текущего процесса. Эта логика , конечно, может быть изменена на противоположную. Если вам нужен пример, дайте мне знать.
    2019-05-02 15: 41: 04Z
4 ответа                              4                         

Я публикую код, необходимый для обнаружения и регистрации активности форм, для тестирования или сравнения.
Как показано, этот код необходимо вставить только в файл Program.cs внутри метода Main .

Эта процедура регистрирует каждое новое открытое название /заголовок формы и имя формы.
Другие элементы могут быть добавлены в журнал, возможно, с помощью специального метода.

Когда новый WindowPattern.WindowOpenedEvent обнаруживает, что новое окно создано, AutomationElement.ProcessId сравнивается с ProcessId приложения, чтобы определить, принадлежит ли новое окно к приложению.

Затем выполняется анализ коллекции Application.OpenForms() с использованием Form.AccessibleObject приведен к Control.ControlAccessibleObject для сравнения AutomationElelement.NativeWindowHandle со свойством Form.Handle, чтобы избежать вызова потока пользовательского интерфейса для получения дескриптора формы (который может генерировать исключения или блокировки потока, так как формы только загружаются в это время).

using System.Diagnostics;
using System.IO;
using System.Security.Permissions;
using System.Windows.Automation;
using System.Windows.Forms;

static class Program
{
    [STAThread]
    [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
    static void Main(string[] args)
    {
        Automation.AddAutomationEventHandler(
            WindowPattern.WindowOpenedEvent, AutomationElement.RootElement,
            TreeScope.Subtree, (uiElm, evt) =>
            {
                AutomationElement element = uiElm as AutomationElement;
                if (element == null) return;
                try 
                {
                    if (element.Current.ProcessId == Process.GetCurrentProcess().Id)
                    {
                        IntPtr elmHandle = (IntPtr)element.Current.NativeWindowHandle;
                        Control form = Application.OpenForms.OfType<Control>()
                            .Where(f => (f.AccessibilityObject as Control.ControlAccessibleObject).Handle == elmHandle)
                            .FirstOrDefault();

                        string log = $"Name: {form?.Name ?? element.Current.AutomationId} " +
                                     $"Form title: {element.Current.Name}{Environment.NewLine}";
                        File.AppendAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "formLogger.txt"), log);
                    }
                }
                catch (ElementNotAvailableException) { /* May happen when Debugging => ignore or log */ }
            });
    }
}
    
2
2019-05-02 21: 55: 27Z
  1. Это работает лучше, чем то, что я делал. Я изменил это, чтобы использовать базу данных, но использование текстового файла хорошо для людей, у которых нет базы данных или которые хотят быть проще.
    2019-05-07 14: 55: 12Z

Да, это должно быть легко. Существуют перехватчики событий, такие как OnLoad, OnShow, OnClose () для всех форм и большинства пользовательских элементов управления. Если вы хотите увидеть на более детальном уровне, какие элементы управления используются вашими пользователями, вы можете подключить OnClick (), OnMouseOver () и около сотни других событий.

... и вы можете создавать свои собственные события.

Итак, подключите события, выбрав форму, а затем свойства (щелкните правой кнопкой мыши или нажмите клавишу F4). В окне свойств в верхней части у вас есть кнопка «Показать события», которая выглядит как молния. Нажмите на это, а затем выберите из списка событие, которое вы хотите использовать для этой регистрации.

 введите описание изображения здесь

    
0
2019-05-02 15: 19: 02Z
  1. Вот как я делаю это прямо сейчас. Мне было интересно, есть ли способ сделать это глобально, чтобы мне не пришлось добавлять строки для каждой формы. Можно ловить ошибки глобально, поэтому я подумал, что можно было бы увидеть, как формы загружаются глобально.
    2019-05-02 15: 39: 36Z
  2. Если все ваши формы унаследованы от родительского класса, у которого было событие OnLoad (), вы могли бы сделать это таким образом; это может быть глобальным решением. Но, похоже, вы пытаетесь уменьшить свою нагрузку, а не добавить к ней.
    2019-05-02 17: 32: 53Z

Не очень дорогое (может быть) решение может быть таким:

Создайте новый класс MyBaseForm, который наследуется от System.Windows.Forms.Form, и обработайте его событие загрузки так, как вам нужно.

Теперь сложная часть: измените все существующие классы форм, чтобы они наследовали от MyBaseForm, а не от значения по умолчанию System.Windows.Forms.Form; и убедитесь, что вы делаете то же самое для каждой будущей формы, которую вы добавите в свое решение.

Не является пуленепробиваемым, может быть легко забыть изменить базовый класс для новой формы и /или пропустить модификацию для существующего класса формы

Но вы можете попробовать это

    
0
2019-05-02 15: 33: 36Z
  1. Я использую статический класс в общем классе для работы с базой данных. Моя основная проблема - попытаться заставить его работать, не затрагивая каждый код формы. В основном я ленивый ^ - ^
    2019-05-02 15: 41: 05Z
  2. @ Кайот, кажется, вы ищете событие "SomeFormIDontKnowWhich_Loaded". Насколько я знаю, такого события не существует. Дайте мне знать, если найдете что-то полезное. Но на самом деле, то, что я предлагаю, - это очень небольшая модификация всех существующих форм, поиск «: Form» (то есть классов, наследуемых от формы) может сделать ваш подобный довольно легким
    2019-05-02 17: 46: 22Z

Применение IMessageFilter к приложение для обнаружения сообщения WM_Create с последующим определением, если целевой дескриптор, принадлежащий Form, был бы идеальным решением с минимальным ударом по производительности. К сожалению, это сообщение не передается в фильтр. В качестве альтернативы я выбрал сообщение WM_Paint, чтобы уменьшить влияние на производительность. Следующий код фильтра создает словарь имен типов форм и количество форм с таким именем. Событие Form.Closed не является надежным при всех условиях закрытия, но событие Disposed выглядит надежным.

internal class FormCreationFilter : IMessageFilter
{
    private List<Form> trackedForms = new List<Form>();
    internal Dictionary<string, Int32> formCounter = new Dictionary<string, Int32>(); // FormName, CloseCount

    public bool PreFilterMessage(ref Message m)
    {
        // Ideally we would trap the WM_Create, butthe message is not routed through
        // the message filter mechanism.  It is sent directly to the window.
        // Therefore use WM_Paint as a surrgogate filter to prevent the lookup logic 
        // from running on each message.
        const Int32 WM_Paint = 0xF;
        if (m.Msg == WM_Paint)
        {
            Form f = Control.FromChildHandle(m.HWnd) as Form;
            if (f != null && !(trackedForms.Contains(f)))
            {
                trackedForms.Add(f);
                f.Disposed += IncrementFormDisposed;
            }
        }
        return false;
    }

    private void IncrementFormDisposed(object sender, EventArgs e)
    {
        Form f = sender as Form;
        if (f != null)
        {
            string name = f.GetType().Name;
            if (formCounter.ContainsKey(name))
            {
                formCounter[name] += 1;
            }
            else
            {
                formCounter[name] = 1;
            }
            f.Disposed -= IncrementFormDisposed;
            trackedForms.Remove(f);
        }
    }
}

Создайте экземпляр и установите фильтр, подобный следующему примеру. Цикл foreach показан для демонстрации доступа к счетчику.

    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        FormCreationFilter mf = new FormCreationFilter();
        Application.AddMessageFilter(mf);

        Application.Run(new Form1());
        Application.RemoveMessageFilter(mf);

        foreach (KeyValuePair<string, Int32> kvp in mf.formCounter)
        {
            Debug.Print($"{kvp.Key} opened {kvp.Value} times. ");
        }
    }
    
0
2019-05-02 19: 44: 01Z
источник размещен Вот