1

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>

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Nane
  • 318
  • 1
  • 8
  • 18
  • You could expose and generate a public event when a MouseDown is raised (and all the expected keys are pressed). You can have a specialized class subscribe to these events (with a single handler) and use the `sender` object to determine which control raised the event and act accordingly. Btw, the `Control.DrawToBitmap()` method should actually paint the UC'c internal controls and their content (except RichTextBox controls, a *special* case). – Jimi Oct 19 '19 at 15:45
  • If not (for some reason), you can try [something like this](https://stackoverflow.com/a/57257205/7444103). – Jimi Oct 19 '19 at 15:47
  • Create a `UserControl` as base of all other user controls. In the base user control redirect `MouseDown` event of all child controls to the control, like [this post](https://stackoverflow.com/a/50886066/3110834). Then handle click event of the control and run your custom logic. – Reza Aghaei Oct 19 '19 at 16:03

2 Answers2

2

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);
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • This seems to work like a charm! Let me test it a little more and will mark it as the answer. Thank you very very much – Nane Oct 19 '19 at 16:45
  • No problem. Take your time on testing it and let me know if you find any issue. I think it's addressing both issues which is mentioned in the question. – Reza Aghaei Oct 19 '19 at 16:50
  • I seems like depending on the order of invocation of all ``OnHandleCreated`` handlers, you might subscribe several times to the same `MouseDown` event, no? For example, if a control ``A`` contains ``B`` which contains ``C``, and if you call ``WireMouseEvents`` on ``A`` then ``B`` then ``C`` you 'll end up subscribing way too many times. – Corentin Pane Oct 19 '19 at 16:51
  • @CorentinPane `WireMouseEvents` is called just in the base control so in all normal scenarios I cannot see any problem. If you are talking about cases that a derived user control is hosting another derived user control, then the problem can be fixed easily in the foreach loop by checking type of the control. – Reza Aghaei Oct 19 '19 at 17:03
  • I also couldn't find any problem using `OnHandleCreated`. Just in case that you had any doubt about it, as an alternative implement `ISupportInitialize` interface and call the wire function id `EndInit`. – Reza Aghaei Oct 19 '19 at 17:05
  • 1
    @RezaAghaei. This piece of code is gold! At least for me. I have it now in only 3 places for any solution: the BaseUserControl, my BaseForm and the solution form because it does something different. I cannot thank you enough. When you are in Uruguay, beers on me. Thank you very much – Nane Oct 20 '19 at 00:29
1

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.

Corentin Pane
  • 4,794
  • 1
  • 12
  • 29
  • This make total sense. I'm trying other answer and see what happens. Anyway, thank you sou much – Nane Oct 19 '19 at 16:46