【发布时间】:2020-07-08 09:31:36
【问题描述】:
我知道,我可以为表单中的每个组件编写一个函数作为事件来执行某些操作(例如,当鼠标悬停时)。 是否也有可能调用事件函数而不将事件链接到特定组件,而是针对应用程序中的所有组件?
我想要实现的是显示例如只需将鼠标悬停在任何组件上即可获得句柄 (Control.Handle) 或有关任何组件的更多信息。
提前致谢。
【问题讨论】:
标签: c# winforms mouseevent
我知道,我可以为表单中的每个组件编写一个函数作为事件来执行某些操作(例如,当鼠标悬停时)。 是否也有可能调用事件函数而不将事件链接到特定组件,而是针对应用程序中的所有组件?
我想要实现的是显示例如只需将鼠标悬停在任何组件上即可获得句柄 (Control.Handle) 或有关任何组件的更多信息。
提前致谢。
【问题讨论】:
标签: c# winforms mouseevent
您可以为表单中的所有控件动态设置事件,例如:
private ToolTip ToolTip = new ToolTip();
public FormTest()
{
InitializeComponent();
AddMouseHoverEvents(this, true, ShowControlInfo);
ToolTip.ShowAlways = true;
}
private void ShowControlInfo(object sender, EventArgs e)
{
var control = sender as Control;
if ( control == null ) return;
ToolTip.SetToolTip(control, control.Handle.ToString());
}
private void AddMouseHoverEvents(Control control, bool recurse, EventHandler action)
{
if ( !recurse )
Controls.OfType<Control>().ToList().ForEach(item => item.MouseHover += action);
else
foreach ( Control item in control.Controls )
{
item.MouseHover += action;
if ( item.Controls.Count > 0 )
AddMouseHoverEvents(item, recurse, action);
}
}
private void RemoveMouseHoverEvents(Control control, bool recurse, EventHandler action)
{
ToolTip.RemoveAll();
if ( !recurse )
Controls.OfType<Control>().ToList().ForEach(item => item.MouseHover -= action);
else
foreach ( Control item in control.Controls )
{
item.MouseHover -= action;
if ( item.Controls.Count > 0 )
RemoveMouseHoverEvents(item, recurse, action);
}
}
您可以显示您需要的任何信息或显示任何您想要的弹出框,而不是工具提示。
【讨论】:
您还可以使用IMessageFilter 在消息被分派到控件之前查看它们。这种方法的不同之处在于它不需要您为每个单独的控件连接一个处理程序,并且甚至可以捕获在运行时添加到界面的动态控件。它适用于您的整个应用程序,包括多种形式:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MyFilter mf = new MyFilter();
private void Form1_Load(object sender, EventArgs e)
{
mf.Target += Mf_Target;
Application.AddMessageFilter(mf);
}
private void Mf_Target(Point pt, IntPtr target)
{
label1.Text = $"({pt.X}, {pt.Y}) : {target.ToString()}";
}
}
public class MyFilter : IMessageFilter
{
private Point? lastPoint;
private IntPtr lastWindow = IntPtr.Zero;
private const int WM_MOUSEMOVE = 0x0200;
public event dlgTarget Target;
public delegate void dlgTarget(Point pt, IntPtr target);
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_MOUSEMOVE:
Point curPos = Cursor.Position;
if (!lastPoint.HasValue || !lastPoint.Value.Equals(curPos))
{
lastPoint = curPos;
if (lastWindow.Equals(IntPtr.Zero) || !lastWindow.Equals(m.HWnd))
{
lastWindow = m.HWnd;
}
Target?.Invoke(lastPoint.Value, lastWindow);
}
break;
}
return false; // allow normal dispatching of messages
}
}
【讨论】:
您可以尝试 UI 自动化方式。
UI 自动化AutomationElement.FromPoint() 方法允许随时确定鼠标指针下的控件。
此方法返回标识的 AutomationElement 对象以及许多有用的详细信息。如有必要,可以通过其他方式检索其他。
查看AutomationElement 和AutomationElement.Current 详细信息以查看哪些属性可直接使用。
当然,您也可以使用每个 AutomationElement 的 specific Patterns 与 UI 元素交互或检索 other properties。
使用 Timer 调用 AutomationElement.FromPoint() 方法,承载此过程的 Window 不需要具有焦点:您可以检查任何其他 Window 的 UI 元素,不一定属于同一进程。
此代码需要项目引用:
► UIAutoamtionClient
► UIAutomationTypes
► WindowBase
using System.Diagnostics;
using System.Windows.Automation;
using System.Windows.Forms;
using wPoint = System.Windows.Point;
public partial class frmUIExplorer : Form
{
private Timer locationTimer = null;
private AutomationElement uiaElement = null;
public frmUIExplorer()
{
InitializeComponent();
if (this.components == null) this.components = new Container();
locationTimer = new Timer() { Interval = 200 };
this.components.Add(locationTimer);
locationTimer.Tick += OnTimerTick;
}
private void OnTimerTick(object sender, EventArgs e)
{
var pt = MousePosition;
try {
var currentElement = AutomationElement.FromPoint(new wPoint(pt.X, pt.Y));
if (currentElement.Equals(uiaElement)) return;
uiaElement = currentElement;
Console.WriteLine($"Control Type: {currentElement.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")}");
// FrameworkId returns, e.g., Win32, WinForm, WPF, DirectUI etc.
Console.WriteLine($"Framework: {currentElement.Current.FrameworkId}");
Console.WriteLine($"Handle: 0x{currentElement.Current.NativeWindowHandle.ToString("X2")}");
Console.WriteLine($"Class Name: {currentElement.Current.ClassName}");
Console.WriteLine($"Bounds: {currentElement.Current.BoundingRectangle}");
if (currentElement.TryGetClickablePoint(out wPoint p)) {
Console.WriteLine($"Clickable Point: {p}");
}
Console.WriteLine($"Process Id: {currentElement.Current.ProcessId}");
if (currentElement.Current.ProcessId > 4) {
using (var proc = Process.GetProcessById(currentElement.Current.ProcessId)) {
Console.WriteLine($"Process Name: {proc.ProcessName}");
Console.WriteLine($"Process Main Window: {proc.MainWindowHandle}");
Console.WriteLine($"Process Window Title: {proc.MainWindowTitle}");
}
}
}
// An exception may be thrown when an UI Element become unavailable
// for some reason. This is expected, nothing to do here.
catch (Exception ex) { Console.WriteLine($"Exception: {ex.Message}"); };
}
protected override void OnShown(EventArgs e) => locationTimer.Start();
protected override void OnFormClosing(FormClosingEventArgs e)
{
locationTimer.Stop();
locationTimer.Tick -= OnTimerTick;
uiaElement = null;
base.OnFormClosing(e);
}
}
【讨论】: