This question already has answers here:
Is there a quick way to get the control that's under the mouse?
(2 answers)
Closed 9 years ago.
I have form with few buttons and I want to know what button is under cursor now.
P.S. Maybe it's duplicate, but I can't find answer to this question.
Have a look at GetChildAtPoint. You will have to do some extra work if the controls are contained in a container, see Control.PointToClient.
Maybe GetChildAtPoint and PointToClient is the first idea for most people. I also used it first. But, GetChildAtPoint doesn't work properly with invisible or overlapped controls. Here's a well-working code and it manages those situations.
using System.Drawing;
using System.Windows.Forms;
public static Control FindControlAtPoint(Control container, Point pos)
{
Control child;
foreach (Control c in container.Controls)
{
if (c.Visible && c.Bounds.Contains(pos))
{
child = FindControlAtPoint(c, new Point(pos.X - c.Left, pos.Y - c.Top));
if (child == null) return c;
else return child;
}
}
return null;
}
public static Control FindControlAtCursor(Form form)
{
Point pos = Cursor.Position;
if (form.Bounds.Contains(pos))
return FindControlAtPoint(form, form.PointToClient(pos));
return null;
}
This will give you the control right under the cursor.
// This getYoungestChildUnderMouse(Control) method will recursively navigate a
// control tree and return the deepest non-container control found under the cursor.
// It will return null if there is no control under the mouse (the mouse is off the
// form, or in an empty area of the form).
// For example, this statement would output the name of the control under the mouse
// pointer (assuming it is in some method of Windows.Form class):
//
// Console.Writeline(ControlNavigatorHelper.getYoungestChildUnderMouseControl(this).Name);
public class ControlNavigationHelper
{
public static Control getYoungestChildUnderMouse(Control topControl)
{
return ControlNavigationHelper.getYoungestChildAtDesktopPoint(topControl, System.Windows.Forms.Cursor.Position);
}
private static Control getYoungestChildAtDesktopPoint(Control topControl, System.Drawing.Point desktopPoint)
{
Control foundControl = topControl.GetChildAtPoint(topControl.PointToClient(desktopPoint));
if ((foundControl != null) && (foundControl.HasChildren))
return getYoungestChildAtDesktopPoint(foundControl, desktopPoint);
else
return foundControl;
}
}
You could do it a number of ways:
Listen to the MouseEnter event of your form's controls. The "sender" parameter will tell you what control raised the event.
Obtain the cursor position using System.Windows.Forms.Cursor.Location and map it to your form's coordinates using Form.PointToClient(). You can then pass the point to Form.GetChildAtPoint() to find the control under that point.
Andrew
What about defining an on-Mouse-over event in each button
which assigns the sender button to a public variable of a button type
Related
The Setting:
I have a RichTextBox containing a hyperink and a DropDownButton somewhere else in my UI. Now when I click the button's DropDown open and afterwards click somewhere else on my UI, the DropDown is implemented to close, and check if it still owns the keyboardfocus so it can set its ToggleButton to focused again after the DropDown collapsed as intended.
The Problem:
When clicking inside my RichTextBox I will face an InvalidOperationException caused by my method to check focus ownership. The call to VisualTreeHelper.GetParent(potentialSubControl) works fine for all elements that are part of the VisualTree. Apparently the focused Hyperlink (returned by FocusManager.GetFocusedElement()) is not part of the VisualTree and therefore is invalid input to GetParent(). Well, how can I find the parent (either logical parent or visual parent) of a hyperlink within my RichTextBox?
My method for determining focus ownership:
// inside DropDownButton.cs
protected override void OnLostFocus( RoutedEventArgs e )
{
base.OnLostFocus( e );
if (CloseOnLostFocus && !DropDown.IsFocused()) CloseDropDown();
}
// inside static class ControlExtensions.cs
public static bool IsFocused( this UIElement control )
{
DependencyObject parent;
for (DependencyObject potentialSubControl =
FocusManager.GetFocusedElement() as DependencyObject;
potentialSubControl != null; potentialSubControl = parent)
{
if (object.ReferenceEquals( potentialSubControl, control )) return true;
try { parent = VisualTreeHelper.GetParent(potentialSubControl); }
catch (InvalidOperationException)
{
// can happen when potentialSubControl is technically
// not part of the visualTree
// for example when FocusManager.GetFocusedElement()
// returned a focused hyperlink (System.Windows.Documents.Hyperlink)
// from within a text area
parent = null;
}
if (parent == null) {
FrameworkElement element = potentialSubControl as FrameworkElement;
if (element != null) parent = element.Parent;
}
}
return false;
}
[Edit]
One potential idea to solve the issue: since Hyperlink is a DependencyObject I could try to access its inheritance context and find other DependencyObjects higher up in the tree and test them for being FrameworkElements. But I struggle to find any information about inheritance context in Silverlight.
Probably this question has already an answer here but I was not able to find it..
I have a tabControl with a flowlayoutpanel in each tab page where I can add controls at run time. I can rearrange them, move them across tab pages.. How can I select multiple controls to be able to move them around using ctrl key + mouse click?
This is my drag event so far:
private void control_DragDrop(object sender, DragEventArgs e)
{
Control target = new Control();
target.Parent = sender as Control;
if (target != null)
{
int targetIndex = FindCSTIndex(target.Parent);
if (targetIndex != -1)
{
string cst_ctrl = typeof(CustomControl).FullName;
if (e.Data.GetDataPresent(cst_ctrl))
{
Button source = new Button();
source.Parent = e.Data.GetData(cst_ctrl) as CustomControl;
if (targetIndex != -1)
fl_panel = (FlowLayoutPanel)tabControl1.SelectedTab.Controls[0];
if (source.Parent.Parent.Name == target.Parent.Parent.Parent.Name)
{
this.fl_panel.Controls.SetChildIndex(source.Parent, targetIndex);
}
else
{
target.Parent.Parent.Parent.Controls.Add(source.Parent);
this.fl_panel.Controls.SetChildIndex(source.Parent, targetIndex);
}
}
}
}
}
private int FindCSTIndex(Control cst_ctr)
{
fl_panel = (FlowLayoutPanel)tabControl1.SelectedTab.Controls[0];
for (int i = 0; i < this.fl_panel.Controls.Count; i++)
{
CustomControl target = this.fl_panel.Controls[i] as CustomControl;
if (cst_ctr.Parent == target)
return i;
}
return -1;
}
This is not an easy, nor a common task. But surely doable and depending on preconditions could become trivial without need to spend multi-man-year effort on it ^^.
You have many options:
controls support selection;
container control support children controls selection;
overlay.
Handling selection is pretty easy: have a dictionary (or a control property, possibly using Tag) to store if control is selected or not, show selection somehow, when control is Ctrl-clicked invert selection. You can even provide Shift-key selection.
As #Hans Passant commented, you can use overlay window (invisible window on top of everything) to draw selection reticle there as well as handle selection and dragging itself. Or it could be a custom control with property IsSelected, setting which will draw something (border?) to indicate selection.
Easiest option would be to create SelectionPanel control, which can host any other controls inside, has IsSelected indication and is draggable. When children is added subscribe to MouseUp/MouseDown events or you can only allow to drag if special area of SelectionPanel is clicked. To example, you could have option Enable dragging in your software, when set all SelectionPanels will display special area (header?) which you can drag or Ctrl-click.
My form hierarchy is something like this:
Form -> TableLayoutOne -> TableLayoutTwo -> Panel -> ListBox
In the MouseMove event of the ListBox, I have code like this:
Point cursosPosition2 = PointToClient(new Point(Cursor.Position.X, Cursor.Position.Y));
Control crp = this.GetChildAtPoint(cursosPosition2);
if (crp != null)
MessageBox.Show(crp.Name);
The MessageBox is showing me "TableLayoutOne", but I expect it to show me "ListBox". Where in my code am I going wrong? Thanks.
The GetChildFromPoint() method uses the native ChildWindowFromPointEx() method, whose documentation states:
Determines which, if any, of the child windows belonging to the
specified parent window contains the specified point. The function can
ignore invisible, disabled, and transparent child windows. The search
is restricted to immediate child windows. Grandchildren and deeper
descendants are not searched.
Note the bolded text: the method can't get what you want.
In theory you could call GetChildFromPoint() on the returned control until you got null:
Control crp = this.GetChildAtPoint(cursosPosition2);
Control lastCrp = crp;
while (crp != null)
{
lastCrp = crp;
crp = crp.GetChildAtPoint(cursorPosition2);
}
And then you'd know that lastCrp was the lowest descendant at that position.
a better code can be written as following:
Public Control FindControlAtScreenPosition(Form form, Point p)
{
if (!form.Bounds.Contains(p)) return null; //not inside the form
Control c = form, c1 = null;
while (c != null)
{
c1 = c;
c = c.GetChildAtPoint(c.PointToClient(p), GetChildAtPointSkip.Invisible | GetChildAtPointSkip.Transparent); //,GetChildAtPointSkip.Invisible
}
return c1;
}
The usage is as here:
Control c = FindControlAtScreenPosition(this, Cursor.Position);
I have a TabControl that contains several tabs. Each tab has one UserControl on it. I would like to check the visibility of a control x on UserControl A from UserControl B. I figured that doing x.Visible from UserControl B would be good enough. As it turns out, it was displaying false in the debugger even though I set it explicitly to true and it was never changed. Then I read on MSDN for Control.Visible that:
Even if Visible is set to true, the control might not be visible to the user if it is obscured behind other controls.
So much to my surprise, that will not work. Now I'm wondering how I can tell if the control x is visible from a different UserControl. I would like to avoid using a boolean if possible. Has anyone run into this and found a solution?
Note: It also appears that Control.IsAccessible is false in this situation.
Unfortunately the control doesn't provide anything public that will allow you to check this.
One possibility would be to set something in the controls 'Tag' property. The tag’s purpose is to associate user data with the control. So it can be anything not just a boolean.
Here is the Tag property doc
If you really want the brute force way, you can use Reflection, basically calling GetState(2):
public static bool WouldBeVisible(Control ctl)
{
// Returns true if the control would be visible if container is visible
MethodInfo mi = ctl.GetType().GetMethod("GetState", BindingFlags.Instance | BindingFlags.NonPublic);
if (mi == null) return ctl.Visible;
return (bool)(mi.Invoke(ctl, new object[] { 2 }));
}
Please try this:
bool ControlIsReallyVisible(Control C)
{
if (C.Parent == null) return C.Visible;
else return (C.Visible && ControlIsReallyVisible(C.Parent));
}
I'm using this code not only checking all the ancestors visible and also who is the root control. Checking a root is needed when the control is not added on the Mainform.
public static class StratoControlExtension
{
public static bool TruelyVisible(this Control control, Control expected_root)
{
if (control.Parent == null) { return control == expected_root && control.Visible; }
return control.Parent.TruelyVisible(expected_root) && control.Visible;
}
}
Is it possible to know who got the focus in a lost focus event?
Compact Framework does not have an ActiveControl, so I don't know how to tell who got the focus.
This is the solution that ended up working:
public System.Windows.Forms.Control FindFocusedControl()
{
return FindFocusedControl(this);
}
public static System.Windows.Forms.Control FindFocusedControl(System.Windows.Forms.Control container)
{
foreach (System.Windows.Forms.Control childControl in container.Controls)
{
if (childControl.Focused)
{
return childControl;
}
}
foreach (System.Windows.Forms.Control childControl in container.Controls)
{
System.Windows.Forms.Control maybeFocusedControl = FindFocusedControl(childControl);
if (maybeFocusedControl != null)
{
return maybeFocusedControl;
}
}
return null; // Couldn't find any, darn!
}
One option would be to interop the GetFocus API
[DllImport("coredll.dll, EntryPoint="GetFocus")]
public extern static IntPtr GetFocus();
This will give you the handle to the window that currently has input focus, you can then recursively iterate the control tree to find the control with that handle.
Using the corell.dll looks like a good idea.
Another possible way is to create GotFocus event handlers for all the controls on your form Then create a class level variable that updates with the name of the control that has the current focus.
No. first comes the LostFocus-event of one control then comes the GotFocus-event of the next control. as long as you can not figure out which control the user uses in the next moment, it is not possible.
whereas if the compact framework control does have a TabIndex-property it could be predicted only if the user uses the tab-key.
Edit:
OK You posted the solution and it works fine I must admit: the simple "No" is wrong
+1
This is a shorter code for the Vaccano's answer, using Linq
private static Control FindFocusedControl(Control container)
{
foreach (Control childControl in container.Controls.Cast<Control>().Where(childControl => childControl.Focused)) return childControl;
return (from Control childControl in container.Controls select FindFocusedControl(childControl)).FirstOrDefault(maybeFocusedControl => maybeFocusedControl != null);
}
Exactly the same (in high-level, abstraction).