运行 加载任何表单时的事件
Run event when any Form loads
我正在尝试在我们的主要前端中创建一个表单人气竞赛。有很多物品不再使用,但事实证明很难详细了解哪些物品已使用,哪些物品已不再使用。
所以我想出了在加载表单时记录表单的想法,然后在一年左右的时间里,我将运行分组并了解使用了哪些表单,使用频率, 以及由谁。现在的问题是我不想在每个表单 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;
}
}
}
但是,我想知道是否有办法获取表单的名称而不是文本。由于这是访问 ALL windows 并且因此在托管 space 之外,我对此表示怀疑。尽管如此,它仍然有效,如果没有其他人这样做的话,我会 post 明天将其作为答案。
是的,这应该很容易。所有窗体和大多数用户控件都有事件挂钩,如 OnLoad、OnShow、OnClose()。如果您想更详细地了解您的用户正在使用哪些控件,您可以连接 OnClick()、OnMouseOver() 和大约一百个其他事件。
...并且您可以创建自己的自定义事件。
因此,通过选择窗体,然后选择属性(右键单击或 F4 键)来连接事件。在顶部的属性 window 中,您有一个看起来像闪电的 "show events" 按钮。单击它,然后从列表中选择要用于此日志记录的事件。
一个不太昂贵(也许)的解决方案可以是这样的:
创建一个新的 class MyBaseForm
,它继承自 System.Windows.Forms.Form
,并按照您需要的方式处理其加载事件。
现在是困难的部分:修改所有现有的表单 classes 以便它们继承自 MyBaseForm
而不是默认的 System.Windows.Forms.Form
;并确保对以后要添加到解决方案中的每个表单都执行相同的操作。
根本不是防弹的,很容易忘记为新形式修改基础 class and/or 而错过对现有形式的修改 class
不过你可以试一试
应用 IMessageFilter to the application to detect the 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. ");
}
}
我正在发布检测和记录表单 activity 所需的代码,用于测试或比较。
如图所示,这段代码只需要插入到Program.cs
文件中,在Main方法里面。
此过程记录每个新打开的表单的 Title/Caption 和表单的名称。
可以使用专用方法将其他元素添加到日志中。
当新的WindowPattern.WindowOpenedEvent事件检测到创建了新的Window时,将AutomationElement.ProcessId
与Application的ProcessId进行比较,判断新的Window是否属于应用程序。
然后解析 Application.OpenForms()
集合,使用 Form.AccessibleObject cast to Control.ControlAccessibleObject 将 AutomationElelement.NativeWindowHandle
与 Form.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 */ }
});
}
}
我正在尝试在我们的主要前端中创建一个表单人气竞赛。有很多物品不再使用,但事实证明很难详细了解哪些物品已使用,哪些物品已不再使用。
所以我想出了在加载表单时记录表单的想法,然后在一年左右的时间里,我将运行分组并了解使用了哪些表单,使用频率, 以及由谁。现在的问题是我不想在每个表单 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;
}
}
}
但是,我想知道是否有办法获取表单的名称而不是文本。由于这是访问 ALL windows 并且因此在托管 space 之外,我对此表示怀疑。尽管如此,它仍然有效,如果没有其他人这样做的话,我会 post 明天将其作为答案。
是的,这应该很容易。所有窗体和大多数用户控件都有事件挂钩,如 OnLoad、OnShow、OnClose()。如果您想更详细地了解您的用户正在使用哪些控件,您可以连接 OnClick()、OnMouseOver() 和大约一百个其他事件。
...并且您可以创建自己的自定义事件。
因此,通过选择窗体,然后选择属性(右键单击或 F4 键)来连接事件。在顶部的属性 window 中,您有一个看起来像闪电的 "show events" 按钮。单击它,然后从列表中选择要用于此日志记录的事件。
一个不太昂贵(也许)的解决方案可以是这样的:
创建一个新的 class MyBaseForm
,它继承自 System.Windows.Forms.Form
,并按照您需要的方式处理其加载事件。
现在是困难的部分:修改所有现有的表单 classes 以便它们继承自 MyBaseForm
而不是默认的 System.Windows.Forms.Form
;并确保对以后要添加到解决方案中的每个表单都执行相同的操作。
根本不是防弹的,很容易忘记为新形式修改基础 class and/or 而错过对现有形式的修改 class
不过你可以试一试
应用 IMessageFilter to the application to detect the 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. ");
}
}
我正在发布检测和记录表单 activity 所需的代码,用于测试或比较。
如图所示,这段代码只需要插入到Program.cs
文件中,在Main方法里面。
此过程记录每个新打开的表单的 Title/Caption 和表单的名称。
可以使用专用方法将其他元素添加到日志中。
当新的WindowPattern.WindowOpenedEvent事件检测到创建了新的Window时,将AutomationElement.ProcessId
与Application的ProcessId进行比较,判断新的Window是否属于应用程序。
然后解析 Application.OpenForms()
集合,使用 Form.AccessibleObject cast to Control.ControlAccessibleObject 将 AutomationElelement.NativeWindowHandle
与 Form.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 */ }
});
}
}