问题
I know, that I can write a function as an event for each component in a form to do something (e.g. when hovering with the mouse). Is there also a possibility to call an event function without linking the event to the specific component but for all of them in an application?
What I want to achieve is to display e.g. the handle (Control.Handle) or more information about any component by just hovering the mouse over it.
Thanks in advance.
回答1:
You can dynamycally set the event for all controls in the form, for example:
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);
}
}
You can show any information you need or show any popup box you want instead of a tooltip.
回答2:
You can also use IMessageFilter to see messages before they are dispatched to controls. This approach differs in that it does not require you to wire up a handler for each individual control, and would capture even dynamic controls added to the interface at run-time. It works across your entire application, including multiple forms:
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
}
}
回答3:
You can try the UI Automation way.
The UI Automation AutomationElement.FromPoint() method allows to determine which Control is under the Mouse Pointer at any time.
This method returns the AutomationElement object identified, along with many useful details. Other can be retrieved with other means, if necessary.
Check the AutomationElement and AutomationElement.Current details to see what properties are directly available.
Of course, you can also use use each AutomationElement's specific Patterns to interact with the UI Elements or retrieve other properties.
Using a Timer to call the AutomationElement.FromPoint()
method, the Window that hosts this procedure doesn't need to have focus: you can inspect UI Elements of any other Window, not necessarily belonging to the same Process.
This code requires a Project reference to:
► 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);
}
}
来源:https://stackoverflow.com/questions/62791731/mouseover-controls-and-display-its-handle