首页 > 解决方案 > 加载任何表单时运行事件

问题描述

我正在尝试在我们的主要前端创建表单人气竞赛。有许多物品不再使用,但要了解哪些物品已使用和哪些不再使用的详细信息被证明是困难的。

所以我想出了在表单加载时记录表单的想法,然后在一年左右的时间里我会运行一个小组,了解使用哪些表单、使用频率以及由谁使用。现在的问题是我不想在每个表单 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;
        }
    }
}

但是,我想知道是否有办法获取表单的名称而不是文本。由于这是访问所有窗口,因此在托管空间之外,我对此表示怀疑。尽管如此,它仍然有效,如果没有其他人这样做,我会在明天发布这个作为答案。

标签: c#winformsui-automation

解决方案


我发布了检测和记录表单活动所需的代码,用于测试或比较原因。
如图所示,此代码只需要插入到Program.cs文件中的Main方法中。

此过程记录每个新打开的表单的标题/标题和表单的名称。
其他元素可以添加到日志中,可能使用专用方法。

当新的WindowPattern.WindowOpenedEvent事件检测到创建了新的 Window 时,AutomationElement.ProcessId会与 Application 的 ProcessId 进行比较,以确定新的 Window 是否属于 Application。

然后Application.OpenForms()解析集合,使用转换为Control.ControlAccessibleObject的Form.AccessibleObject与属性进行比较,以避免调用 UI 线程来获取 Form 的句柄(这可能会生成异常或线程锁,因为 Forms 只是当时加载)。 AutomationElelement.NativeWindowHandleForm.Handle

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

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>()
                            .FirstOrDefault(f => (f.AccessibilityObject as Control.ControlAccessibleObject).Handle == elmHandle);

                        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 */ }
            });
    }
}

推荐阅读