How to capture mousemove events beneath child controls - c#

I am trying to handle a mouseclick event on a particular form that should fire if the mouse cursor falls between a set of coordinates - lets say a square.
I understand that if I had an empty form I could simply tie in to the mousemove event and off I go. But in reality there may be up to 10 different overlapping controls and in my test app the mousemove event only fires if the cursor is on the actual form itself and not if its over a child control.
Does anyone know how to handle this event when there are an unknown number of child controls at design time?
Is there an easy one-liner I can use?

try this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
AddMouseMoveHandler(this);
}
private void AddMouseMoveHandler(Control c)
{
c.MouseMove += MouseMoveHandler;
if(c.Controls.Count>0)
{
foreach (Control ct in c.Controls)
AddMouseMoveHandler(ct);
}
}
private void MouseMoveHandler(object sender, MouseEventArgs e)
{
lblXY.Text = string.Format("X: {0}, Y:{1}", e.X, e.Y);
}
}

I know this post is quite old, but it seems to me that the simplest method would be for the form to implement IMessageFilter. In the constructor (or in OnHandleCreated) you call
Application.AddMessageFilter(this);
and then you can catch the messages of all windows in your implementation of IMessageFilter.PreFilterMessage.
You'd likely need to use P/Invoke for the WIN32 IsChild method
[DllImport("user32.dll")]
public static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd);
along with the form's Handle property to ensure that you're handling the right messages.

imho there is a bit of a binary situation here : and there is no "one-liner." the only solution I can see is to get your controls that don't implement events into a .NET container that does.
When any control gets a click, the normal expected behavior is that it will become the Active Control of the Form (which can always be accessed by this.ActivceControl).
But, particulary if the control you clicked captures the mouse, something has got to raise an event since .NET does not implement event "bubbling" (as WPF does).
The usual way to deal with extending behavior of any object that is sealed or whatever is to write an extension method, and I have found writing extensions for Control quite easy, but I don't know if that will help you in this case. Unfortunately I am out of my home country right now, and do not have Visual Studio to play around with.
One strategy you can use to determine if a given Point on a Form falls within the bounds of any Control is to enumerate the areas (Bounds) of all controls on the Form via 'forall of the Forms Control.Collection (this.Controls). But, if you have overlapping Controls, you then have the issue of more than one control possibly containing a given point.
best, Bill

Why don't you just use the controls' mouseover event handlers?

I know I'm a bit late to the punch, but I was having troubles with this earlier today when using a Panel as a title bar. I had a label to display some text, a picturebox and a few buttons all nested within the Panel, but I needed to trap the MouseMove event regardless.
What I decided to do was implement a recursive method handler to do this, as I only had 1 level of nested controls, this may not scale overly well when you start approaching ridiculous levels of nesting.
Here's how I did it:
protected virtual void NestedControl_Mousemove(object sender, MouseEventArgs e)
{
Control current = sender as Control;
//you will need to edit this to identify the true parent of your top-level control. As I was writing a custom UserControl, "this" was my title-bar's parent.
if (current.Parent != this)
{
// Reconstruct the args to get a correct X/Y value.
// you can ignore this if you never need to get e.X/e.Y accurately.
MouseEventArgs newArgs = new MouseEventArgs
(
e.Button,
e.Clicks,
e.X + current.Location.X,
e.Y + current.Location.Y,
e.Delta
);
NestedControl_Mousemove(current.Parent, newArgs);
}
else
{
// My "true" MouseMove handler, called at last.
TitlebarMouseMove(current, e);
}
}
//helper method to basically just ensure all the child controls subscribe to the NestedControl_MouseMove event.
protected virtual void AddNestedMouseHandler(Control root, MouseEventHandler nestedHandler)
{
root.MouseMove += new MouseEventHandler(nestedHandler);
if (root.Controls.Count > 0)
foreach (Control c in root.Controls)
AddNestedMouseHandler(c, nestedHandler);
}
And then to set it up is relatively simple:
Define your "true" handler:
protected virtual void TitlebarMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
this.Text = string.Format("({0}, {1})", e.X, e.Y);
}
}
And then set up the controls event subscribers:
//pnlDisplay is my title bar panel.
AddNestedMouseHandler(pnlDisplay, NestedControl_Mousemove);
Relatively simple to use, and I can vouch for the fact it works :)

Related

BeginResize/EndResize Event for Control on WinForms Design Surface

TLDR: I would like to know how I can create a hook into a begin-resize and an end-resize event for a design-time control instance on the designer surface.
Detail: Specifically, I am working with a design surface produced by a BasicLoader in the System.Design and System.Component.Design .NET namespaces. Specifically, I'm working a design-time instance of the TableLayoutPanel. That control exposes a SizeChanged event and a Resize event--alas, both fire during the resize operation--that is, while the control is being resized--as well as when the resize operation is complete. I therefore have no way of know when the resize operation began and when it officially ended.
One way to tackle this would be to detect a mouse-down event along with a resize event--but it's unclear to me how I can detect a mouse-down event on any of the grab handles of a control being resized.
For the records, I revisited the BehaviorService and saw that it exposes BeginDrag, EndDrag, and Synchronize--I see nothing in that service that would help me with BeginResize/EndResize events.
So, ideally, I would like to subscribe to BeginResize/EndResize events for any designer instance of a Winform control, but I would be happy if the provided answer covered only my need to have these events attached to a designer instance of the TableLayoutPanel control...
Any thoughts?
When a resize starts, a designer transaction with a specific description starts and when the design ends, the transaction will be closed.
You can rely on TransactionOpened event of the IDesignerHost and check the TransactionDescription to see if it starts with "Resize", set a flag resizing to true. Then in TransactionClosed you can check if the flag is true, it means it's a resize end has happened.
Example
Here is a PoC to show how it works. Add the following control into a Windows Forms project and after building the project, drop an instance of MyControl on the form. Then if you try to resize the form, you will see the Resize started. and Resize ended. text on title-bar of the form:
Here is the code:
using System;
using System.ComponentModel.Design;
using System.Windows.Forms;
public class MyControl : Control
{
IDesignerHost host;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if(DesignMode)
{
host = (IDesignerHost)Site.GetService(typeof(IDesignerHost));
host.TransactionOpened += Host_TransactionOpened;
host.TransactionClosed += Host_TransactionClosed;
}
}
bool resizing = false;
private void Host_TransactionOpened(object sender, EventArgs e)
{
if (host?.TransactionDescription?.StartsWith("Resize") == true)
{
resizing = true;
((Control)host.RootComponent).Text = "Resize Started.";
}
}
private void Host_TransactionClosed(object sender,
DesignerTransactionCloseEventArgs e)
{
if (resizing)
{
resizing = false;
((Control)host.RootComponent).Text = "Resize ended.";
}
}
}
If you want to do some R&D before going with this solution, you may want to take a look at the following classes (mostly internal) in System.Design assembly: GrabHandleGlyph, ResizeBehavior.

Access method of an usercontrol from another usercontrol

On my form I have 2 UserControls (ButtonDiscount, ButtonAdvertisment) that inherit FadeControl. FadeControl inherits UserControl class and is used to do custom stuff on the controls like fade out.
My 2 UserControls each have just one button hence those two unimaginative names.
On click of that button on one of usercontrols, I need to access the method in FadeControl from the other UserControl. The other button does the opposite.
I've done the following, on the event click in the UserControl ButtonDiscount:
private void button1_Click(object sender, EventArgs e)
{
ButtonAdvertisment ba = (ButtonAdvertisment)this.Parent.Controls.Find("buttonAdvertisment1", true)[0];
ba.FadeOut(true);
}
It works like a charm, but I don't think this is the right way, is there another way to access the method from the parent class of the other UserControl?
I can't pass it thru a UserControl constructor, the designer breaks down every time.
You have 2 separate user controls which are not aware of each other, which is good and keeps your code loosely-coupled. Now what you are trying to do is to make them somehow know about each other and communicate. Making them aware of each other breaks the loose-coupling and is a bad design.
What I would suggest is creating a 3rd control which will hold the two together and will handle all the communication between them. Each of your original controls will have public events, to which the parent control can subscribe and handle appropriately.
Check mediator pattern for more info: http://en.wikipedia.org/wiki/Mediator_pattern
What you've done is fine - you could do it by exposing events that fired when you click the button in those controls, and then passing references to each other (subscribing to those, writing the code to fade 'this' control).
That might be a bit too much work for a simple solution, however.
What I would say about your solution is that if you were to change the name of control(s) then it stops working. You could instead do:
var ba = this.Parent.Controls.OfType<ButtonAdvertisement>().FirstOrDefault();
That way you're no longer tied to the control name - but the type of the control. You'll need a using System.Linq; in your code file for this to work. Of course, this relies on the fact that there is only ever one other instance of that control type in the parent.
If you're interested in the first solution I mentioned - then this code snippet should help demonstrate:
public class FadeControl {
public event EventHandler Clicked;
public void FadeOut(bool b){
}
public void AttachTo(FadeControl other){
//splitting this operation to a public and private allows us to
//initiate an attach publicly, but then recurse privately without
//causing a stack overflow
AttachToInternal(other);
other.AttachToInternal(this);
}
private void AttachToInternal(FadeControl other){
other.Clicked += Attached_Clicked;
}
protected virtual void Attached_Clicked(object sender, EventArgs e)
{
//fade me out
FadeOut(true);
}
// provides a way for the deriving class to raise the Clicked event
protected void OnButtonClicked(){
if(Clicked != null) Clicked(this, null);
}
}
public class ButtonDiscount : FadeControl {
Button _button;
//omitted: designer code
//this is your handler for the _button clicked event
private void _button_Clicked(object sender, EventArgs e){
//call the base class' OnButtonClicked method - to raise the event
OnButtonClicked();
//TODO: do work.
}
}
//omitted - code for the ButtonAdvertisement class
Once you have that done - in your form, assuming you have _buttonAdvertisement and _buttonDiscount members in your form and after they're initialised - you simply do:
_buttonAdvertisement.AttachTo(_buttonDiscount);
And that will immediately bind both controls to each other.
Note - in response to a comment below - I've made the event handler in FadeControl for another FadeControl's Clicked event protected and virtual - so you can override it.

Event handler firing for all controls instead of individually

I am having a rather odd problem with the Gecko Webbrowser control, I have created my own class which inherits off of the Gecko Webcontrol and within the constructor of this I have set an event:
class fooGeckoClass: Gecko.GeckoWebBrowser
{
public fooGeckoClass()
{
this.DomClick += new EventHandler<Gecko.GeckoDomEventArgs>(fooEventFunction);
}
private static void fooEventFunction(Object sender, Gecko.GeckoDomEventArgs e)
{
((Gecko.GeckoWebBrowser)sender).Navigate("www.foo.com");
}
}
I am using three of these controls in a manually created UserControl, the controls are loaded in dynamically at start up from a config file and added the the UserControl controls collection. When clicking on any of the three controls, all three will navigate to "www.foo.com" away from there original site. I had a look at:
e.StopPropagation();
Which specifies that it stops further propagation of events during an event flow, however it does also specify that it will handle all events in the current flow, I believe the events must have already been given to the controls before this has a chance to stop it as the the three controls will still fire the event. I also tried e.Handled = true to no avail. Has anyone encountered this problem before and have any kind of solution to make it only fire on the control that was clicked on?
EDIT:
It may be worth showing how the controls are added to the form seeing as this must be where the problem is occurring (it does not happen if the controls are just placed in a user control in a small test app).
private void fooUserControl_Load(object sender, EventArgs e)
{
if (!this.DesignMode)
{
for (int iControls = 0; iControls < geckObs.Count(); iControls ++)
{
fooGeckoClass geckControl = new fooGeckoClass();
this.Controls.Add(geckControl );
break;
}
}
}
Odd answer but I seem to have resolved the issue, DomClick was being called at first run, changing to DomMouseClick or DomMouseUp has completely resolved the issue. I assume DomClick must be an event unto itself as it also doesn't use the GeckoDomMouseEventArgs but the regular GeckoEventArgs.
EDIT:
To add to this, the site I was going to was actually calling DomClick when it had finished loading hence the reason it was being called at start up across all three browsers.

Visual Studio 2010 - C# UserControl event firing

I'm attempting to make a grid based dungeon system at the moment in Visual Studio 2010. I have a main user control which contains 64 other smaller user control objects, which I've called GridSquares, organised into an 8x8 grid. The idea behind the grid squares is to act as potential movement spaces within the 'dungeon'. The problem I have at the moment is that I need to be able to call a click event on the user controls (GridSquares) themselves, which have been placed on screen so I can retrieve their coordinate (name) for comparison. However the event does not work when I call it (through clicking).
I am aware that the events work when I place them within the usercontrol (GridSquare object) but I need the click even to work when the user control itself is clicked.
Given that all 64 objects placed are the same type I can not work within the GridSquare class as I require the name of the user control to be returned through the event.
I hope this makes sense but please ask if I need to explain further.
Many thanks, Liam
EDIT:
I'm not sure how much this will help or what code to display but the GridSpace controls have already been added to the 'dungeon' user control. Then within I add all 64 to a dictionary:
gridSpaces.Add(gs11.Name, gs11);
Where gs11 is the name of the GridSquare.
From here I tried creating event handlers for the individual user controls on the dungeon screen, which failed to call.
I think i get what your saying. Add this code to your user control:
public new event EventHandler Click {
Add {
base.Click += value;
foreach(Control i in Controls) {
i.Click+=value;
}
}
remove {
base.Click -= value;
foreach(Control i in Controls) {
i.Click -= value;
}
}
}
this will add the click event to everything in your user control, i hope i didnt make any errors, and that this helps
You can use the same handler for each GridSquare and use the sender parameter to decide which one was clicked:
protected void Page_Load(object sender, EventArgs e)
{
for (int i = 0; i < 64; i++)
{
GridSquare square = new GridSquare();
square.Click += new EventHandler(gridSquare_Click);
grid.Add(gridSquare);
}
}
void gridSquare_Click(object sender, EventArgs e)
{
GridSquare square = (GridSquare)sender;
// do something cool with the clicked square here
}

A way to monitor when a Control's screen location changes?

With WinForms, is there a way to be alerted to a control changing location with respect to the screen?
Say you have a Form with a button on it, and you would like to know when the button is moved from its current pixel location on the screen. If the button is moved to a different location on its parent Form you could obviously use the LocationChanged event, but if the Form is moved by the user, how do you know the button has visually moved?
In this simplified case the quick answer is to monitor the Form's LocationChanged and SizeChanged events, but there can be an arbitrary number of levels of nesting so monitoring those events for each parent up the chain to the primary form is not feasible. Using a timer to check if the location changed also seems like cheating (in a bad way).
Short version:
Given only an arbitrary Control object, is there a way to know when that Control's location changes on the screen, without knowledge of the control's parent hierarchy?
An illustration, by request:
Note that this "pinning" concept is an existing capability but it currently requires knowledge of the parent form and how the child control behaves; this is not the problem I am trying to solve. I would like to encapsulate this control tracking logic in an abstract Form that "pin-able" Forms can inherit from. Is there some message pump magic I can tap into to know when a control moves on the screen without having to deal with all the complicated parent tracking?
I'm not sure why you would say tracking the parent chain "is not feasible". Not only is it feasible, it's the right answer and the easy answer.
Just a quick hack at a solution:
private Control _anchorControl;
private List<Control> _parentChain = new List<Control>();
private void BuildChain()
{
foreach(var item in _parentChain)
{
item.LocationChanged -= ControlLocationChanged;
item.ParentChanged -= ControlParentChanged;
}
var current = _anchorControl;
while( current != null )
{
_parentChain.Add(current);
current = current.Parent;
}
foreach(var item in _parentChain)
{
item.LocationChanged += ControlLocationChanged;
item.ParentChanged += ControlParentChanged;
}
}
void ControlParentChanged(object sender, EventArgs e)
{
BuildChain();
ControlLocationChanged(sender, e);
}
void ControlLocationChanged(object sender, EventArgs e)
{
// Update Location of Form
if( _anchorControl.Parent != null )
{
var screenLoc = _anchorControl.Parent.PointToScreen(_anchorControl.Location);
UpdateFormLocation(screenLoc);
}
}

Categories