Reputation: 65
I want my UserControl (at the middle-top in the image) to lose focus when I click outside of it, anywhere.
I have tried this:
private void Form1_Click(object sender, EventArgs e)
{
ActiveControl = null;
}
It only works when I click the Form itself, the TrackBar and the MediaPlayer. It doesn't work when I click other controls, even the FlowLayoutPanel.
What can I do about it?
Upvotes: 4
Views: 2092
Reputation: 32298
You can use a general purpose Message handler/dispatcher:
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; }
}
Upvotes: 2
Reputation: 2872
Eric,
To remove the focus from an item in your custom control, you will need some other focusable control to pass the focus to. I would do something like this:
private void StealFocus() => lbl_SomeLabel.Focus();
And then drop StealFocus()
in your form's click event handler:
private void Form1_Click(object sender, EventArgs e) => StealFocus();
and any other control the user might click that doesn't execute a command.
Note that you cannot set focus to the Form. Container controls like Form and Panel will pass the Focus on to their first child control. Which could be the custom control you wanted to remove focus from.
Upvotes: 4
Reputation: 4671
When you click on controls, the click event is eaten by them and doesn't go to the form itself. To notify the form that one of its controls was clicked, use something like this:
private void control_Click(object sender, EventArgs e)
{
// you control click code here
this.OnClick(new EventArgs());
}
Upvotes: 1