GetChildAtPoint method is returning the wrong control - c#

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);

Related

Can't get children of DevExpress combobox via native UI API for Windows 7

I work at process of automating of testing GUI of one windows desktop application. In general, GUI of one was developed with WPF and DevExpress controls were used such as combobox (in fact, it's panel with edit and button inside).
So I have faced with such problem: when I has gotten panel (combobox), I can't get edit or button controls inside of panel. I use Inspect.exe to get info about UI controls and I can get and navigate to panel as well as edit and button controls inside. I know, there are a lot of information on this theme and I know that Inspect.exe uses native UI API, so I has rewritten my code using native API, but problem still rises.
Code below is how I try to retrieve controls inside of wrapped panel (combobox):
public void findButtondWithUnmanagedApi(AutomationElement win){
/* win - current window where I try to find my target controls.
* win is instance of AutomationElement (Yes, I know AutomationElement is managed code, but I use it only for getting pointer to my windows and will reduced futher)
*/
IntPtr hwnd = (IntPtr)win.Current.NativeWindowHandle;
interop.UIAutomationCore.IUIAutomation automation = new interop.UIAutomationCore.CUIAutomation();
interop.UIAutomationCore.IUIAutomationElement element = automation.ElementFromHandle(hwnd);
interop.UIAutomationCore.IUIAutomationTreeWalker walker = automation.RawViewWalker;
interop.UIAutomationCore.IUIAutomationElement child = walker.GetFirstChildElement(element);
int level = 0;
/*
* Scaning all controlls on the form to find target panel which represents a combobox
*/
while (child != null) {
string name = child.CurrentName;
string aid = child.CurrentAutomationId != null ? child.CurrentAutomationId : "null";
string type = child.CurrentLocalizedControlType;
log.TraceInformation("child: {0}, {1}, {2}, {3}", name, type, aid, level);
// Some controls have value of the property "Name" and don't have value of AutomationId
if (name == "PanelControlFromTo" || name == "panelControlSystem")
{
level++;
child = walker.GetFirstChildElement(child);
continue;
}
/*
* lookUpEditSystem - is value of AutomationId property of target panel (combobox from DevExpress)
* If target panel has found then stop scanning of other element
*/
if (child.CurrentAutomationId == "lookUpEditSystem"){
break;
}
child = walker.GetNextSiblingElement(child);
}
interop.UIAutomationCore.IUIAutomationElement comboboxChild = walker.GetFirstChildElement(child);
log.TraceInformation("Combobox Child: {0}", comboboxChild == null ? null: comboboxChild.CurrentName);
log.Flush();
}
In log I see the result:
TestLog Information: 0 : Combobox Child:
In Inspect.exe I see:
Info about combobox in Inspect.exe
As you can see Inspect shows, that "lookUpEditeSystem" panel has two children. Btw, each of these children has value "IsWindowPatternAvailable" property equal to false.
I has spent 3 day trying to solve this problem and nothing.
Any help is appreciated.

Allow a ListBox to overlap a TableLayoutPanel (C# .NET)

I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.

When is Control.Visible = true turns out to be false?

I have a C# WinForms project that's very wizard like in its functionality. The individual steps live on a class called StepPanel, which inherits from the Panel control, within the form and those panels are organized in an array.
What I've run into is that when UpdateUI() is called and walks the array, adjusts the wizards step title text for the current step, it makes sure that all of the inactive steps are hidden, and ensures that the active step is visible, in the right spot, and is the right size.
Here's the code:
private void UpdateUI()
{
// If the StepIndex equals the array length, that's our cue
// to exit.
if (StepIndex == Steps.Length)
{
Application.Exit();
return;
}
for (var xx = 0; xx < Steps.Length; xx++)
{
if (xx == StepIndex)
{
if (!String.IsNullOrEmpty(Steps[xx].Title))
{
LabelStepTitle.ForeColor = SystemColors.ControlText;
LabelStepTitle.Text = Steps[xx].Title;
}
else
{
LabelStepTitle.ForeColor = Color.Red;
LabelStepTitle.Text =
Resources.UiWarning_StepTitleNotSet;
}
}
else
{
Steps[xx].Visible = false;
}
}
Steps[StepIndex].Top = 50;
Steps[StepIndex].Left = 168;
Steps[StepIndex].Width = 414;
Steps[StepIndex].Height = 281;
Steps[StepIndex].Visible = true;
SetNavigationButtonState(true);
}
When everything is said and done, Steps[StepIndex].Visible == false.
I'm still perplexed by this behavior because I was working less than 30 minutes ago.
If you set a parent/container control to Visible = false then setting any child controls to Visible = true will have no effect what so ever. The Visible property of the child control will still be false.
I don't know if it's what happens in this case since I don't know the structure of the controls but it seems to be a likely scenario.
To solve this you need to first set the parent/contianer control to Visible = true and THEN the child control(s).
if (xx == StepIndex)
Is only going to be true and the end of the loop unless I am missing something.
There are several possibilities. When you attach a debugger on the line:
SetNavigationButtonState(true);
does Steps[StepIndex].Visible == true? If so, then make sure that StepIndex is actually the index you expected (not off by 1, and not reflecting the "previous" step). If you verify that the correct step is set to true, then you must be updating it somewhere else.
if Steps[StepIndex].Visible == false right after you set it to true, then either the getter on the Visible property is returning based on some calculation or an event was triggered that is changing it back to false.
HTH.
I encountered same issue using MDIForm in VB.net and Sani Singh Huttunen explanation is correct for me.
I post this answer to give more explanations and also a specific solution or a work around.
When I click on a specific menu to load a new MDI Child form, my program excute following code
Dim frm As New FrmPaiement
frm.MdiParent = Me
Call frm.NewRecord()
Call ReorganizeControlTopPositions(frm.DataPanel)
frm.Show()
where FrmPaiement is a Form class that originally contains a lot of controls and DataPanel is central panel that contains all data TextBox, CheckBox, ComboBox and DateBox controls.
But these time, some controls are hidden in frm.NewRecord() function.
ReorganizeControlTopPosition() function is called to reduce gaps between remainding visible controls
For information, VB.Net code of ReorganizeControlTopPositions() is following
Public Sub ReorganizeControlTopPositions(ctlContainer As Panel)
'Put all controls in a List(Of Control) and sort it on Top position
For Each ctl As Control In ctlContainer.Controls
lstControls.Add(ctl)
Next
lstControls.Sort(Function(x, y) x.Top.CompareTo(y.Top))
'Reduce gaps between 2 visibles controls
Dim iLastTop As Integer = -1
Dim nInvisible = 0
Dim iLastControlTop As Integer = 0
For Each ctl In lstControls
If nInvisible > 0 Then
If ctl.Visible Then
If ctl.Top = iLastControlTop Then
ctl.Top = iLastTop
Else
iLastControlTop = ctl.Top
ctl.Top = iLastTop + 32
End If
End If
End If
If ctl.Visible Then
iLastTop = ctl.Top
Else
nInvisible += 1
End If
Next
End Sub
EXPLANATION OF ERROR
Since ReorganizeControlTopPositions() function is called before frm.Show() function, MDI Child is hidden and in debug mode, ctl.Visible contains always False !
If initialisation code if changed so that ReorganizeControlTopPositions() is called after frm.Show() call, program runs correctly and ctl.Visible contains "correct" value.
Dim frm As New FrmPaiement
frm.MdiParent = Me
Call frm.NewRecord()
frm.Show()
Call ReorganizeControlTopPositions(frm.DataPanel)
The only problem is that MDI Form is shortly (very quicly) displaying controls with gaps and a micro secund later without gaps.
The problem is linked to Microsoft implementation of Visible property.
Setting Visible property seems to change visible value of control but getting Visible value return only True if Visible property of control is True and also all containers containing this control are visibles !
What is written on Control.Visible property on learn.microsoft.com is extremly confuse !!!
Return True if the control and all its child controls are displayed; otherwise, false. The default is true.
The correct definition sould be
Return True if the control and all its PARENT controls are displayed; otherwise, false. The default is true.

How do I determine visibility of a control?

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;
}
}

How to get control under mouse cursor? [duplicate]

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

Categories

Resources