You can use a general purpose Message handler/dispatcher:
- Make your Forms that need to provide control over Mouse events implement IMessageFilter, so these Forms will know beforehand what messages are sent to which child Control, what kind of event is generated and any other details that may be useful, such as the position of the Mouse pointer when the event is generated. Mouse related messages are generated in any case, no matter whether you click on a Control that usually cannot get Focus, as the Form itself, or a Panel etc.
- Your Forms also implement another Interface that just defines a public event that interested parties can subscribe to in order to receive fresh notifications about these events.
This also allows Control to detect whether the Parent Form is actually a Mouse Event notifier guy.
- When a Mouse event is notified (here, just the event generated when a
WM_LBUTTONDOWN message is received), subscribers of the event(s) can decide to act upon it. In your case, you can call the SelectNextControl() method of the Parent Form to set a different ActiveControl, only when your UserControl has the Focus and a MouseDown event is generate outside its bounds.
Call Application.AddMessageFilter() before the Form is constructed, passing the Form instance itself, since this Form implements the IMessageFilter interface.
Remove the message filter when the Form closes.
Form side:
add a message filter to capture a mouse down event (WM_LBUTTONDOWN) and rise an event to notify the subscribers of the location where the event is generated and which is the Control that will be affected (passing its Handle).
public partial class SomeForm : Form, IMessageFilter, IMouseHandler {
private const int WM_LBUTTONDOWN = 0x0201;
public event EventHandler<MouseDownEventArgs> MouseEvent;
public SomeForm() => InitializeComponent();
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN) {
var pos = MousePosition;
MouseEvent?.Invoke(this, new MouseDownEventArgs(m.HWnd, pos));
}
return false;
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!DesignMode) Application.AddMessageFilter(this);
}
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
if (!DesignMode) Application.RemoveMessageFilter(this);
}
}
UserControl part:
if the Parent Form implements IMouseHandler, subscribe to its MouseEvent. When the event is raised, verify that the UserControl is the current ActiveControl (it contains the Focus) and that the Mouse event is generated outside its bounds. If these conditions are met, call the Parent Form's SelectNextControl() method to move the Focus elsewhere.
The bool m_MouseEventSubscribed is there because a UserControl may regenerate its Handle more than once in its life-time.
public partial class MyUserControl : UserControl
{
private bool m_MouseEventSubscribed = false;
public MyUserControl() => InitializeComponent();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
var form = this.ParentForm;
if (form != null && form is IMouseHandler && !m_MouseEventSubscribed) {
m_MouseEventSubscribed = true;
((IMouseHandler)form).MouseEvent += (s, a) => {
if (this.ContainsFocus && !this.ClientRectangle.Contains(PointToClient(a.Position))) {
form.SelectNextControl(this, true, true, false, true);
}
};
}
}
}
IMouseHandler Interface:
public interface IMouseHandler
{
event EventHandler<MouseDownEventArgs> MouseEvent;
}
Custom EventArgs class:
using System;
using System.Drawing;
public class MouseDownEventArgs : EventArgs
{
public MouseDownEventArgs() { }
public MouseDownEventArgs(IntPtr hWnd, Point point)
{
this.ControlHandle = hWnd;
this.Position = point;
}
public IntPtr ControlHandle { get; }
public Point Position { get; }
}