.net 加载任何窗体时运行事件

093gszye  于 2023-02-10  发布在  .NET
关注(0)|答案(4)|浏览(172)

我正在尝试在我们的主前端创建一个流行度竞赛表单。有许多项目不再使用,但获得哪些使用和哪些不再使用的详细信息被证明是困难的。
所以我想到了在窗体加载时记录它的想法,然后在一年左右的时间里,我将运行一个组,并了解哪些窗体被使用,使用频率和使用者。现在的问题是,我不想在每个窗体的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;
        }
    }
}

然而,我想知道是否有一种方法可以获得表单的名称,而不是它的文本。因为这是访问所有窗口,因此是在托管空间之外,我怀疑它。尽管如此,它的工作,我会张贴这作为一个答案,明天如果没有人这样做。

kwvwclae

kwvwclae1#

为了测试或比较的原因,我发布了检测和记录表单活动所需的代码。
如图所示,此代码只需插入到**Program.cs文件中的Main**方法内。
本程序记录每个新打开的表格的标题/题注和表格名称。
可以使用专用方法将其他元素添加到日志中。
当新WindowPattern.WindowOpenedEvent事件检测到创建了新窗口时,将AutomationElement.ProcessId与应用程序的ProcessId进行比较,以确定新窗口是否属于应用程序。
然后解析Application.OpenForms()集合,使用Form.AccessibleObjectControl.ControlAccessibleObject的强制转换来比较AutomationElelement.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 */ }
            });
    }
}
suzh9iv8

suzh9iv82#

是的,这应该很简单。所有表单和大多数用户控件都有OnLoad、OnShow、OnClose()等事件挂钩。如果你想在更细的层次上看到你的用户正在使用哪些控件,你可以挂钩OnClick()、OnMouseOver()和大约一百个其他事件。
...您可以创建自己的自定义事件。
因此,通过选择表单,然后选择属性(右键单击或F4键)来连接事件。在顶部的属性窗口中,您有一个“show events”按钮,看起来像一个 lightning 。单击该按钮,然后从列表中选择您想要用于此日志记录的事件。

9rygscc1

9rygscc13#

一个不那么昂贵(也许)的解决方案可以是这样的:
创建一个新的类MyBaseForm,它继承自System.Windows.Forms.Form,并以您需要的方式处理其load事件。
现在是最难的部分:修改所有现有的表单类,使它们继承自MyBaseForm,而不是默认的System.Windows.Forms.Form;并确保对将来要添加到解决方案中的每个窗体执行相同的操作。
完全不是防弹的,很容易忘记修改新窗体的基类和/或错过对现有窗体类的修改
但你可以给予一试

lbsnaicq

lbsnaicq4#

IMessageFilter应用于应用程序以检测WM_Create消息,然后确定目标句柄是否属于Form,这将是性能影响最小的理想解决方案。遗憾的是,该消息不会传递到筛选器。作为替代方案,我已选择WM_Paint消息以降低性能影响。以下筛选器代码创建窗体类型名称的字典和Form 's。Form.Closed Event并非在所有关闭条件下都可靠,但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. ");
        }
    }

相关问题