【问题标题】:Run event when any Form loads加载任何表单时运行事件
【发布时间】:2019-09-21 03:29:56
【问题描述】:

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

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

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

【问题讨论】:

  • 你可能想看看 Observer 设计模式
  • 您可以添加到Program.csAutomationEventHandler。当即将显示任何窗口(任何窗口)时会引发此事件。您可以确定此窗口是否属于当前进程(您的应用程序),如果是,则记录它。这里有一个使用 C# 的示例:Run the current application as Single Instance and show the previous instance(第二个代码部分)正在检测相反的情况,只需删除 !
  • @Cid 我创建了一个使用 IObserver 的类,但在打开表单时它不会触发。我以前从未使用过 IObserver,所以我可能对这个不太了解。
  • @Jimi 该代码似乎更多地用于阻止程序的第二个实例运行。我看不到如何在子表单加载时触发某些内容。
  • 不。该代码作为一个整体用于防止应用程序的第二个实例(以及更多)。代码的第二部分(在该代码中的表单构造函数中,在您的情况下需要移动到sub Main),仅检测窗口何时打开并确定它是否不是当前进程的一部分。这个逻辑当然可以反过来。如果您需要示例,请告诉我。

标签: c# winforms ui-automation


【解决方案1】:

是的,这应该很容易。所有表单和大多数用户控件都有事件挂钩,例如 OnLoad、OnShow、OnClose()。如果您想更详细地查看用户正在使用哪些控件,您可以连接 OnClick()、OnMouseOver() 和大约一百个其他事件。

...您可以创建自己的自定义事件。

因此,通过选择表单,然后选择属性(右键单击或 F4 键)来连接事件。在顶部的属性窗口中,您有一个看起来像闪电的“显示事件”按钮。单击它,然后从列表中选择要用于此日志记录的事件。

【讨论】:

  • 这就是我现在的做法。我想知道是否有办法在全球范围内这样做,这样我就不必在每个表单中添加一行。全局捕获错误是可能的,所以我认为可以看到全局加载表单。
  • 如果您的所有表单都继承自具有 OnLoad() 事件的父类,您可能可以这样做;这可能是一个全球性的解决方案。但听起来您是在尝试减少工作量,而不是增加工作量。
【解决方案2】:

一个不那么昂贵(也许)的解决方案可以是这样的:

创建一个新类MyBaseForm,它继承自System.Windows.Forms.Form,并以您需要的方式处理其加载事件。

现在最难的部分是:修改所有现有的表单类,使其继承自 MyBaseForm 而不是默认的 System.Windows.Forms.Form;并确保对以后添加到解决方案中的每个表单都执行相同的操作。

根本不是防弹的,很容易忘记为新表单修改基类和/或错过对现有表单类的修改

但你可以试一试

【讨论】:

  • 我在普通类中使用静态类来完成数据库工作。我的主要问题是试图让它在不触及每个表单代码的情况下工作。基本上我很懒^-^
  • @Kayot 看来您正在寻找一个事件“SomeFormIDontKnowWhich_Loaded”。据我所知,这样的事件是不存在的。如果您发现有用的东西,请告诉我。但实际上,我的建议是对所有现有表单进行非常小的修改,搜索“:表单”(即从表单继承的类)可以让您的喜欢变得非常容易
【解决方案3】:

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. ");
        }
    }

【讨论】:

    【解决方案4】:

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

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

    当一个新的WindowPattern.WindowOpenedEvent事件检测到创建了一个新的Window时,将AutomationElement.ProcessId与Application的ProcessId进行比较,判断新Window是否属于Application。

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

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

    【讨论】:

    • 这比我做的要好得多。我将其更改为使用数据库,但使用文本文件对没有数据库或希望保持简单的人有好处。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-26
    • 2011-07-21
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    • 2023-03-23
    相关资源
    最近更新 更多