How to pass messages from a child user-control to the parent - c#

This is a Windows Forms / .Net C# question.
I have a borderless windows whose transparency key and background color make it completely transparent. Inside the window are a couple of user controls.
I want to be able to move the window. I know how to do this on the parent window, but my problem is that the child controls are the only thing visible and thus the only thing click-able.
The question is: how can I pass certain messages up to the Parent so the Parent can move when the right mouse button is down and the mouse is moving on any one of the child controls?
Or maybe you can suggest another way?
Thanks for the help.
Mark

You can achieve your goal even without SendMessage using System.Windows.Forms.Message class. If you have done dragging I guess you are familiar with WM_NCLBUTTONDOWN message. Send it to you parent from your control's MouseDown event.
Here is an example for moving the form clicking on control label1. Note the first line where sender is used to release the capture from clicked control. This way you can set this handler to all controls intended to move your form.
This is complete code to move the form. Nothing else is needed.
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
private void label1_MouseDown(object sender, MouseEventArgs e)
{
(sender as Control).Capture = false;
Message msg = Message.Create(Handle, WM_NCLBUTTONDOWN, (IntPtr)HT_CAPTION, IntPtr.Zero);
base.WndProc(ref msg);
}
Hope this helps.

I think the easiest way is to add this event to your child controls:
/// <summary>
/// The event that you will throw when the mouse hover the control while being clicked
/// </summary>
public event EventHandler MouseRightClickedAndHoverChildControl;
After, all the parent have to do is to subscribe to those events and make the operations to move the Parent:
ChildControl.MouseRightClickedAndHoverChildControl += OnMouseHoverChildControl;
private void OnMouseHoverChildControl(object sender, EventArgs e)
{
//do foo...
}

You need to call the SendMessage API function to send mouse messages to your parent control.
It would probably be easiest to do this by overriding your control's WndProc method.

I had exactly this question... but came up with a different answer.
If you have the Message in your WndProc, you can just change the handle to your Parent's handle and then pass it along.
I needed to do this in our derived TextBox... TextBox eats scroll wheel events even when its ScrollBars are set to None. I wanted those to propagate on up to the Form. So, I simply put this inside the WndProc for my derived TextBox:
case 0x020A: // WM_MOUSEWHEEL
case 0x020E: // WM_MOUSEHWHEEL
if (this.ScrollBars == ScrollBars.None && this.Parent != null)
m.HWnd = this.Parent.Handle; // forward this to your parent
base.WndProc(ref m);
break;
default:
base.WndProc(ref m);
break;

Related

C# WindowsForms - Hide control after clicking outside of it

C# WindowsForms - Hide control after clicking outside of it
I have a picturebox (f.e. picturebox1) which is not visible as default. When I click a button (let's say button1) the picturebox1 will show up. Now -> I need the picturebox1 to become hidden again when I click outside of it (on form itself or any other control). It works the same as a contextmenu would work.
I have no idea how to do it since any "Click_Outside" event doesn't exist. Is there any simple way to do this? Thanks.
Here is a simple solution, albeit not one that is totally easy to fully understand as it does involve catching the WndProc event and using a few constants from the Windows inderds..:
This is the obvious part:
private void button1_Click(object sender, EventArgs e)
{
pictureBox1.Show();
}
Unfortunately we can't use the pictureBox1.LostFocus event to hide the Picturebox.
That is because only some controls can actually receive focus when clicking them; a Button or other interactive controls like a ListBox, a CheckBox etc can, too.
But a Panel, a PictureBox and also the Form itself can't receive focus this way. So we need a more global solution.
As ever so often the solution comes form the depths of the Windows message system:
const int WM_PARENTNOTIFY = 0x210;
const int WM_LBUTTONDOWN = 0x201;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN || (m.Msg == WM_PARENTNOTIFY &&
(int)m.WParam == WM_LBUTTONDOWN))
if (!pictureBox1.ClientRectangle.Contains(
pictureBox1.PointToClient(Cursor.Position)))
pictureBox1.Hide();
base.WndProc(ref m);
}
Note that we need to make sure that you can still clcik on the PictureBox itself; so we check if the mouse is inside its ClientRectangle..
Simply add this to the form code and every click outside the PictureBox will hide it.
Use the LostFocus event of the control (in your case, a PictureBox control)
As you said, the ClickOutside doesn't exist, so you have few choices:
Loop through all the controls of your form (Form.Controls) and add a click event that hides the PictureBox excluding your "Show" button.
You can intercept the mouse click message at the source like in this example: intercept
The easiest way is this:
Copy and paste the following method anywhere in your code:
private void mouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && pictureBox1.Visible == true)
pictureBox1.Visible = false;
}
then inside your form_Load Event copy and paste the following code:
foreach (Control ctrl in this.Controls)
if (ctrl is GroupBox || ctrl is .....)
ctrl.MouseClick += mouseClick;
of course you should repeat this loop for every groupBox within another groupBox and replace the dots with textbox, button, combobox, label, ... according to what controls you have

Is there any way to detect a mouseclick outside a user control?

I'm creating a custom dropdown box, and I want to register when the mouse is clicked outside the dropdown box, in order to hide it. Is it possible to detect a click outside a control? or should I make some mechanism on the containing form and check for mouseclick when any dropdownbox is open?
So I finally understand that you only want it to close when the user clicks outside of it. In that case, the Leave event should work just fine... For some reason, I got the impression you wanted it to close whenever they moved the mouse outside of your custom dropdown. The Leave event is raised whenever your control loses the focus, and if the user clicks on something else, it will certainly lose focus as the thing they clicked on gains the focus.
The documentation also says that this event cascades up and down the control chain as necessary:
The Enter and Leave events are hierarchical and will cascade up and down the parent chain until the appropriate control is reached. For example, assume you have a Form with two GroupBox controls, and each GroupBox control has one TextBox control. When the caret is moved from one TextBox to the other, the Leave event is raised for the TextBox and GroupBox, and the Enter event is raised for the other GroupBox and TextBox.
Overriding your UserControl's OnLeave method is the best way to handle this:
protected override void OnLeave(EventArgs e)
{
// Call the base class
base.OnLeave(e);
// When this control loses the focus, close it
this.Hide();
}
And then for testing purposes, I created a form that shows the drop-down UserControl on command:
public partial class Form1 : Form
{
private UserControl1 customDropDown;
public Form1()
{
InitializeComponent();
// Create the user control
customDropDown = new UserControl1();
// Add it to the form's Controls collection
Controls.Add(customDropDown);
customDropDown.Hide();
}
private void button1_Click(object sender, EventArgs e)
{
// Display the user control
customDropDown.Show();
customDropDown.BringToFront(); // display in front of other controls
customDropDown.Select(); // make sure it gets the focus
}
}
Everything works perfectly with the above code, except for one thing: if the user clicks on a blank area of the form, the UserControl doesn't close. Hmm, why not? Well, because the form itself doesn't want the focus. Only controls can get the focus, and we didn't click on a control. And because nothing else stole the focus, the Leave event never got raised, meaning that the UserControl didn't know it was supposed to close itself.
If you need the UserControl to close itself when the user clicks on a blank area in the form, you need some special case handling for that. Since you say that you're only concerned about clicks, you can just handle the Click event for the form, and set the focus to a different control:
protected override void OnClick(EventArgs e)
{
// Call the base class
base.OnClick(e);
// See if our custom drop-down is visible
if (customDropDown.Visible)
{
// Set the focus to a different control on the form,
// which will force the drop-down to close
this.SelectNextControl(customDropDown, true, true, true, true);
}
}
Yes, this last part feels like a hack. The better solution, as others have mentioned, is to use the SetCapture function to instruct Windows to capture the mouse over your UserControl's window. The control's Capture property provides an even simpler way to do the same thing.
Technically, you'll need to p/invoke SetCapture() in order to receive click events that happen outside of your control.
But in your case, handling the Leave event, as #Martin suggests, should be sufficient.
EDIT: While looking for an usage example for SetCapture(), I came across the Control.Capture property, of which I was not aware. Using that property means you won't have to p/invoke anything, which is always a good thing in my book.
So, you'll have to set Capture to true when showing the dropdown, then determine if the mouse pointer lies inside the control in your click event handler and, if it doesn't, set Capture to false and close the dropdown.
UPDATE:
You can also use the Control.Focused property to determine if the control has got or lost focus when using a keyboard or mouse instead of using the Capture with the same example provided in the MSDN Capture page.
Handle the Form's MouseDown event, or override the Form's OnMouseDown
method:
enter code here
And then:
protected override void OnMouseDown(MouseEventArgs e)
{
if (!theListBox.Bounds.Contains(e.Location))
{
theListBox.Visible = false;
}
}
The Contains method old System.Drawing.Rectangle can be used to indicate if
a point is contained inside a rectangle. The Bounds property of a Control is
the outer Rectangle defined by the edges of the Control. The Location
property of the MouseEventArgs is the Point relative to the Control which
received the MouseDown event. The Bounds property of a Control in a Form is
relative to the Form.
You are probably looking for the leave event:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.leave.aspx
Leave occurs when the input focus leaves the control.
I just wanted to share this. It is probably not a good way of doing it that way, but looks like it works for drop down panel that closes on fake "MouseLeave", I tried to hide it on Panel MouseLeave but it does not work because moving from panel to button leaves the panel because the button is not the panel itself. Probably there is better way of doing this but I am sharing this because I used about 7 hours figuring out how to get it to work. Thanks to #FTheGodfather
But it works only if the mouse moves on the form. If there is a panel this will not work.
private void click_to_show_Panel_button_MouseDown(object sender, MouseEventArgs e)
{
item_panel1.Visible = true; //Menu Panel
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (!item_panel1.Bounds.Contains(e.Location))
{
item_panel1.Visible = false; // Menu panel
}
}
I've done this myself, and this is how I did it.
When the drop down is opened, register a click event on the control's parent form:
this.Form.Click += new EventHandler(CloseDropDown);
But this only takes you half the way. You probably want your drop down to close also when the current window gets deactivated. The most reliable way of detecting this has for me been through a timer that checks which window is currently active:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
and
var timer = new Timer();
timer.Interval = 100;
timer.Tick += (sender, args) =>
{
IntPtr f = GetForegroundWindow();
if (this.Form == null || f != this.Form.Handle)
{
CloseDropDown();
}
};
You should of course only let the timer run when the drop down is visible. Also, there's probably a few other events on the parent form you'd want to register when the drop down is opened:
this.Form.LocationChanged += new EventHandler(CloseDropDown);
this.Form.SizeChanged += new EventHandler(CloseDropDown);
Just don't forget to unregister all these events in the CloseDropDown method :)
EDIT:
I forgot, you should also register the Leave event on you control to see if another control gets activated/clicked:
this.Leave += new EventHandler(CloseDropDown);
I think I've got it now, this should cover all bases. Let me know if I'm missing something.
If you have Form, you can simply use Deactivate event just like this :
protected override void OnDeactivate(EventArgs e)
{
this.Dispose();
}

About treeview control

I have used the mouse down event of the treeview control. And I want to set the selected node as the node on which the mouse down event has happened. How can this problem be resolved?
The MouseDown event is fired before the node is selected. Try handling the AfterSelect event instead. If e.Action is set to TreeViewAction.ByMouse then the event was raised by the mouse.
A couple of options come to mind here. Both might well be overkill, but they still solve the problem.
Handle the MouseDown event and use the HitTest method to determine which node the user clicked on. If they clicked on a valid node, manually set the focus to that node via the SelectedNode property.
private void myTreeView_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
myTreeView.SelectedNode = myTreeView.HitTest(e.Location).Node;
}
}
The bazooka-style solution is to override the WndProc method and listen for WM_RBUTTONDOWN messages. I've done this in my own extended version of the TreeView control because it allows me to fix some really minor stuff that normal, non-obsessive people probably wouldn't notice. I go into excruciating detail in my answer here.
Basically, you're doing the same thing as the code above does, but at a lower level, which stops the native control from pulling some shenanigans with the focus. I don't remember if they actually apply here (hence the potential overkill), but I'm too lazy to fire up Visual Studio to see for sure.
public class FixedTreeView : System.Windows.Forms.TreeView
{
protected override void WndProc(ref System.Windows.Forms.Message m)
{
const int WM_RBUTTONDOWN = 0x204;
if (m.Msg == WM_RBUTTONDOWN)
{
Point mousePos = this.PointToClient(Control.MousePosition);
this.SelectedNode = this.GetNodeAt(mousePos);
}
base.WndProc(ref m);
}
}
The first method should work just fine for you. Try that before breaking out bigger weapons.

Event bubbling - Mousehover event for child controls

I have a winform with some controls on it.
While the cursor is enters the form (form boundaries, this includes form's content) I want to trigger specific task.
When cursor leavs the form, i want to trigger another task.
Obviously seeting these events on form won't work: (because, for instance MouseLeave will bw triggered when i move from form itself to other control)
this.MouseEnter += (sender, e) =>
{
//do stuff
};
this.MouseLeave += (sender, e) =>
{
//do stuff
};
I laso tried IMessageFilter interface as appears in similar questions, but none gives me the needed result!
The problem is to detect when mouse leave the form completely.
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_MOUSEMOVE: //or other messages
bool z = myForm.Bounds.Contains(Cursor.Position); //This didn't help
}
eturn false;
}
There is also problem with detection within the form content (when i move between controls).
What am I missing?
update: i don't want to use timers!
You could get the current x,y coordinates of the pointer and see if they are within the form. If they aren't, then perform the off-form task.
I pulled a trick like this once to keep tabs on all forms' ancestors:
Forms have a ControlAdded event. You could hook yourself into that (probably before InitializeForm. Inside that event handler can hook yourself into the ControlAdded and MouseXXX events of all children and grandchildren. You could send all the events of the same type to the same handler.
With that methodology, I suppose that the easiest solution will then be to watch the MouseLeave and MouseEnter events of all ancestors and then check the mouse cursor position to see if it's in the form's screen rectangle (your event handlers will be mashed full of events firing off as your mouse moves around the form). Anyway, then you'll have it solved with zero timers!

WinForms equivalent of WPF's IsHitTestVisible

I have a button override that has a Label as a child. I have MouseEnter and Leave events attached to the button control.
When the mouse enters the label the button's events are nullified (which is natural). My question is how can I disable the label's hit testing without actually disabling the label.
I want it to retain it's color and I want it to be able to change colors (MouseEnter on button for example), but when the mouse is over the label, the hit test to be considered on the button.
P.S: I know I can add Mouse Enter and Leave on the Label and handle those cases but I want the control to be self sufficient such that if the parameters change outside of it (the colors on mouse enter and leave), the control will still function properly.
Ran across this question while looking for other information and don't believe the accepted answer is really correct.
You can extend the label and alter the hittest response in WndProc. Something along these lines:
public class HTTransparentLabel : Label
{
private const int WM_NCHITTEST = 0x84;
private const int HTTRANSPARENT = -1;
protected override void WndProc(ref Message message)
{
if ( message.Msg == (int)WM_NCHITTEST )
message.Result = (IntPtr)HTTRANSPARENT;
else
base.WndProc( ref message );
}
}
The short answer is that you cannot. Both the button and the label are in fact windows, so when the mouse leave one for the other, mouseenter and mouseleave events are generated.
The real question is, why do you need a label on a button?

Categories

Resources