Nane
Nane

Reputation: 337

Propagate event to all controls

I'm using the following piece of code, for documentation, error handling and/or logging. It's saves an image of the UserControl or Form when I click it pressing Control+Alt+Shift:

  public Image GetImage()
  {
     Bitmap oBmp = new Bitmap(this.Width, this.Height);
     this.DrawToBitmap(oBmp, new Rectangle(0, 0, oBmp.Width, oBmp.Height));
     return (Image)oBmp;
  }

  protected override void OnMouseDown(MouseEventArgs e)
  {
     base.OnMouseDown(e);

     bool bControl = false;
     bool bShift = false;
     bool bAlt = false;

     bControl = (Control.ModifierKeys & Keys.Control) == Keys.Control;
     bShift = (Control.ModifierKeys & Keys.Shift) == Keys.Shift;
     bAlt = (Control.ModifierKeys & Keys.Alt) == Keys.Alt;

     if (bControl && bShift && bAlt)
     {
        GetImage().Save(this.Name.TimedLocalFileName("png"), ImageFormat.Png);
     }
  }

Right now, I'm coding it in every UserControl, in the base form and so. It's easy to do because I'm using a code Snippet. But it has obvious setbacks.

  1. The same piece of code in a lot of places (maintainability); and
  2. Works only when I click on the base control and not it's childs (if an UserControl has a Label, this doesn't works.

I've been for a few days analyzing GlobalHooks (mostly here: CodeProject, but my head is not helping me.

Any suggestion will be very much appreciated.

Note: TimedLocalFileName is an extension method that returns a String in format <ControlName>_<Culture>_<YYYYMMDD>_<HHMMSS>.<FileExtension>

Upvotes: 1

Views: 575

Answers (2)

Reza Aghaei
Reza Aghaei

Reputation: 125207

Create a base UserControl and name it BaseUserControl and derive all your user control from BaseUserControl then you can put all the logic inside the base user control.

Inside the BaseUserControl, using a recursive method, handle MouseDown event of all child controls and redirect them to OnMouseDown of this, like this post.

Override OnHanldeCrated and call that recursive method to wire up events.

Here is the base control:

using System;
using System.Drawing;
using System.Windows.Forms;
public class BaseUserControl : UserControl
{
    void WireMouseEvents(Control container)
    {
        foreach (Control c in container.Controls)
        {
            c.MouseDown += (s, e) =>
            {
                var p = PointToThis((Control)s, e.Location);
                OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, p.X, p.Y, e.Delta));
            };
            WireMouseEvents(c);
        };
    }
    Point PointToThis(Control c, Point p)
    {
        return PointToClient(c.PointToScreen(p));
    }
    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        if (Control.ModifierKeys == (Keys.Control | Keys.Alt | Keys.Shift))
            MessageBox.Show("Handled!");
        // Your custom logic
    }
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        WireMouseEvents(this);
    }
}

Upvotes: 3

Corentin Pane
Corentin Pane

Reputation: 4943

Answering issue 1

The same piece of code in a lot of places (maintainability)

It's not always applicable but if you find yourself using this behavior a lot, you could inherit from UserControl to create BitmapExportUserControl and put Image GetImage() and override void OnMouseDown(MouseEventArgs e) in this class instead, and make all your custom controls that need this behavior inherit from BitmapExportUserControl.

Another way could to perform the bitmap export from your Form itself, and have your Form subscribe to all the MouseDown events of all its children Control objects.

Answering issue 2

Works only when I click on the base control and not it's childs

As far as I know, there is no built-in "up" event propagation (or bubbling) in WinForms as there is in WPF for example. A solution could be to expose an event that can be raised by all UserControl in your application when there is a MouseDown event on them. Your code would become:

protected override void OnMouseDown(MouseEventArgs e)
{
    GlobalMouseDown.RaiseGlobalMouseDownEvent(this, e);
}

and you would have your main Form subscribe to this GlobalMouseDown.GlobalMouseDownEvent and perform the checks and bitmap export.

This is functionally equivalent to having a public method HandleMouseDown in some GlobalMouseDown class that would be called by all your UserControl MouseDownEventHandlers. The code in each UserControl would become:

protected override void OnMouseDown(MouseEventArgs e)
{
    GlobalMouseDown.HandleMouseDown(this, e);
}

and you would do your checks and bitmap export in this method.

Upvotes: 1

Related Questions