Handling a click event anywhere inside a panel in C# - c#

I have a panel in my Form with a click event handler. I also have some other controls inside the panel (label, other panels, etc.). I want the click event to register if you click anywhere inside the panel. The click event works as long as I don't click on any of the controls inside the panel but I want to fire the event no matter where you click inside the panel. Is this possible without adding the same click event to all of the controls inside the panel?

Technically it is possible, although it is very ugly. You need to catch the message before it is sent to the control that was clicked. Which you can do with IMessageFilter, you can sniff an input message that was removed from the message queue before it is dispatched. Like this:
using System;
using System.Drawing;
using System.Windows.Forms;
class MyPanel : Panel, IMessageFilter {
public MyPanel() {
Application.AddMessageFilter(this);
}
protected override void Dispose(bool disposing) {
if (disposing) Application.RemoveMessageFilter(this);
base.Dispose(disposing);
}
public bool PreFilterMessage(ref Message m) {
if (m.HWnd == this.Handle) {
if (m.Msg == 0x201) { // Trap WM_LBUTTONDOWN
Point pos = new Point(m.LParam.ToInt32());
// Do something with this, return true if the control shouldn't see it
//...
// return true
}
}
return false;
}
}

I needed the exact same functionality today, so this is tested and works:
1: Create a subclasser which can snatch your mouseclick:
internal class MessageSnatcher : NativeWindow
{
public event EventHandler LeftMouseClickOccured = delegate{};
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_PARENTNOTIFY = 0x210;
private readonly Control _control;
public MessageSnatcher(Control control)
{
if (control.Handle != IntPtr.Zero)
AssignHandle(control.Handle);
else
control.HandleCreated += OnHandleCreated;
control.HandleDestroyed += OnHandleDestroyed;
_control = control;
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PARENTNOTIFY)
{
if (m.WParam.ToInt64() == WM_LBUTTONDOWN)
LeftMouseClickOccured(this, EventArgs.Empty);
}
base.WndProc(ref m);
}
private void OnHandleCreated(object sender, EventArgs e)
{
AssignHandle(_control.Handle);
}
private void OnHandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();
}
}
2: Initialize the snatcher to hook into the panel WndProc:
private MessageSnatcher _snatcher;
public Form1()
{
InitializeComponent();
_snatcher = new MessageSnatcher(this.panel1);
}
3: The Message snatcher will get the WM_PARENTNOTIFY if you click on a child control.

You used to be able to override the OnBubbleEvent method on the control. In WPF the mechanism is called Routed events : http://weblogs.asp.net/vblasberg/archive/2010/03/30/wpf-routed-events-bubbling-several-layers-up.aspx

Bit late to the party, But What I did was to map all the click event of all the controls inside of the panel to panel click event. I know its nasty approach. but hey!!

a simple solution:
every control inside the usercontrol gets the same click-event "ControlClick". The usercontrol event click works with any controls inside.
private void ControlClick(Object sender, EventArgs e)
{
if (sender is UC_Vorgang uC_vorgang)
{
uC_vorgang.OnClick(e);
}
else
{
ControlClick(((Control)sender).Parent, e);
}
}

Related

C# Winforms How to Understand Form object is moved

I want to trigger an event only when the form is moved via the mouse click over the title bar. I could not find proper event for this.
Currently I implemented a move event for my forms in winform. I only want the move event to be triggered when the user drags the form via clicking on the title bar. However, this event is triggered also when form is tried to be resized by mouse or minimized/maximized. How can I disable this? I only want to trigger an event only when the form is moved. I am trying to implement my own floating forms and I want to catch this specific event to change the MDiParent of the form.
You can check if the form is resized and the Form's FormWindowState is changed in the Move event, if not so, you can decide that Form is moving using the title bar.
To do so, you should cache both the window state and size each time when Move event is triggered.
Note: The Move event is triggered even when you change the location of the form via the Location property, not only using the title bar. So, the event FromDragged will be triggered in the case. This is a "false positive".
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WhenDraggingUsingCaptionBar
{
public partial class CustomForm : Form
{
public CustomForm()
{
InitializeComponent();
FormDragged += Form1_FormDragged;
}
private void Form1_FormDragged(object sender, EventArgs e)
{
MessageBox.Show("Test");
}
public event EventHandler FormDragged;
private Size _cachedSize = new Size(0, 0);
private FormWindowState _cachedState = FormWindowState.Normal;
private void Form1_Move(object sender, EventArgs e)
{
if (_cachedSize == Size && _cachedState == WindowState)
if (FormDragged != null)
FormDragged(this, new EventArgs());
_cachedSize = Size;
_cachedState = WindowState;
}
}
}
As an addition, there is a low-level solution using Win32 API. This solution eliminates the "false-positive" issues in the approach above.
You can handle the WM_NCLBUTTONDOWN message. This message is sent when you down the mouse left button in the non-client area of the window. When we get the message, we set a variable to true. We also catch another message WM_EXITSIZEMOVE to understand when the dragging the window is stopped and set the variable to false.
If the variable is set to true when the move event is triggered, we can say the window is being dragged using the title bar.
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
namespace WhenDraggingUsingCaptionBar
{
public partial class CustomForm : Form
{
public CustomForm()
{
InitializeComponent();
FormDragged += Form1_FormDragged;
}
private void Form1_FormDragged(object sender, EventArgs e)
{
Debug.WriteLine("{1}: Move:{0}", _ncbuttonDown, DateTime.Now);
}
public event EventHandler FormDragged;
private const int WM_NCLBUTTONDOWN = 0x00A1;
private const int WM_EXITSIZEMOVE = 0x0232;
private bool _ncbuttonDown = false;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCLBUTTONDOWN)
_ncbuttonDown = true;
else if (m.Msg == WM_EXITSIZEMOVE)
_ncbuttonDown = false;
base.WndProc(ref m);
}
private void CustomForm_Move(object sender, EventArgs e)
{
if (_ncbuttonDown)
if (FormDragged != null)
FormDragged(this, new EventArgs());
}
}
}

How can I make a Control lose focus clicking anywhere outside of it?

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?
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.
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; }
}
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());
}

how to redirect events at a usercontrol

I've a user control, pretty simple just a label and a picturebox, they are docked that you can't even see the underlying
most of the events never occurs (click, hover and others)
i think (i could be so wrong), it's because the events won't be triggered as they are being clicked either on the label or the picturebox and not on the background
i need somehow to redirect these events (i.e create that events in the usercontrol and somehow redirect them to my code and with my code i mean the form that uses them) or any other way to capture those events
i've seen like 5 topics about the same problem and none were solved, any workaround ?
You need to redirect your Click event of your label and pictureBox to the OnClick event of your user control using this code:
public UserControl1()
{
InitializeComponent();
this.label1.Click += myClickEvent;
this.pictureBox1.Click += myClickEvent;
}
private void myClickEvent(object sender, EventArgs e)
{
this.OnClick(e);
}
If you can Inherit the Label and PictureBox, Inherit them and override WndProc and do the following.
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)WindowsMessage.WM_NCHITTEST)
{
m.Result = (IntPtr)(-1);//Transparent
return;
}
base.WndProc(ref m);
}
If not, Use this
public partial class MyUserControl : UserControl
{
private TransparentWindow label;
private TransparentWindow pic;
public MyUserControl()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
label = new TransparentWindow(label1);
pic = new TransparentWindow(pictureBox1);
}
}
class TransparentWindow : NativeWindow
{
public TransparentWindow(Control control)
{
this.AssignHandle(control.Handle);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)WindowsMessage.WM_NCHITTEST)
{
m.Result = (IntPtr)(-1);//Transparent
return;
}
base.WndProc(ref m);
}
}
WindowMessage enumeration can be found here
Related question
The answer from Matin Lotfaliee is definetly correct, but in my case, where i want to forward an event from one control to 'OtherControl', you may prefer one-liners:
this.OtherControl.Click += (s, e) => { this.OnClick(e); };

added Scroll event to ListView, and mouse wheel moves it, but scroll event doesn't file

I have added this
public class MyListView : ListView
{
public event EventHandler<EventArgs> Scrolled;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
const int wm_vscroll = 0x115;
if (m.Msg == wm_vscroll && Scrolled != null)
{
Scrolled(this, new EventArgs());
}
}
}
and when i scroll the mouse wheel it scroll the list perfectly, my scroll event doesn't fire.
I have tried hooking MouseWheel, but the scroll happens after the mousewheel event returns to windows, but still doesn't call Scroll event.
EDIT:-
I have added an OnMouseWheel event that calls my update code, but this is called before the visible area is scrolled, so my update code misses some parts.
I want the mousewheel event to scroll the visible area then call the onScroll event,
OR
for the onscroll event to get called as a by product of the mousewheel scrolling the visible area
Trapping WM_MOUSEWHEEL worked for me:
public class MyListView : ListView
{
private const int WM_MOUSEWHEEL = 0x20a;
public event EventHandler<EventArgs> Scrolled;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_MOUSEWHEEL && Scrolled != null)
{
Scrolled(this, new EventArgs());
}
}
}
default listview's visual tree have ScrollViewer control.
ScrollViewer override OnMouseWheel and set e.handled = true,
if you want to handle this event, you must use EventManager.RegisterClassHandler(Type classType, RoutedEvent routedEvent,Delegate handler,true);
true to invoke this class handler even if arguments of the routed event have been marked as handled;
try it:
class CustomListView: ListView
{
public CustomListView()
{
EventManager.RegisterClassHandler(typeof(CustomListView), MouseWheelEvent, new RoutedEventHandler(OnMouseWheel), true);
}
internal static void OnMouseWheel(object sender, RoutedEventArgs e)
{
//Do something you want
}
}

Handling scroll event on listview in c#

I have a listview that generates thumbnail using a backgroundworker. When the listview is being scrolled i want to pause the backgroundworker and get the current value of the scrolled area, when the user stopped scrolling the listview, resume the backgroundworker starting from the item according to the value of the scrolled area.
Is it possible to handle scroll event of a listview? if yes how? if not then what is a good alternative according to what i described above?
You'll have to add support to the ListView class so you can be notified about scroll events. Add a new class to your project and paste the code below. Compile. Drop the new listview control from the top of the toolbox onto your form. Implement a handler for the new Scroll event.
using System;
using System.Windows.Forms;
class MyListView : ListView {
public event ScrollEventHandler Scroll;
protected virtual void OnScroll(ScrollEventArgs e) {
ScrollEventHandler handler = this.Scroll;
if (handler != null) handler(this, e);
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == 0x115) { // Trap WM_VSCROLL
OnScroll(new ScrollEventArgs((ScrollEventType)(m.WParam.ToInt32() & 0xffff), 0));
}
}
}
Beware that the scroll position (ScrollEventArgs.NewValue) isn't meaningful, it depends on the number of items in the ListView. I forced it to 0. Following your requirements, you want to watch for the ScrollEventType.EndScroll notification to know when the user stopped scrolling. Anything else helps you detect that the user started scrolling. For example:
ScrollEventType mLastScroll = ScrollEventType.EndScroll;
private void myListView1_Scroll(object sender, ScrollEventArgs e) {
if (e.Type == ScrollEventType.EndScroll) scrollEnded();
else if (mLastScroll == ScrollEventType.EndScroll) scrollStarted();
mLastScroll = e.Type;
}
Based upon the post that #Adriaan Stander posted my class for raising scroll events is below.
internal class ControlScrollListener : NativeWindow, IDisposable
{
public event ControlScrolledEventHandler ControlScrolled;
public delegate void ControlScrolledEventHandler(object sender, EventArgs e);
private const uint WM_HSCROLL = 0x114;
private const uint WM_VSCROLL = 0x115;
private readonly Control _control;
public ControlScrollListener(Control control)
{
_control = control;
AssignHandle(control.Handle);
}
protected bool Disposed { get; set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed) return;
if (disposing)
{
// Free other managed objects that implement IDisposable only
}
// release any unmanaged objects
// set the object references to null
ReleaseHandle();
Disposed = true;
}
protected override void WndProc(ref Message m)
{
HandleControlScrollMessages(m);
base.WndProc(ref m);
}
private void HandleControlScrollMessages(Message m)
{
if (m.Msg == WM_HSCROLL | m.Msg == WM_VSCROLL)
{
if (ControlScrolled != null)
{
ControlScrolled(_control, new EventArgs());
}
}
}
}
Use it like so...
Declare a field:
private ControlScrollListener _processListViewScrollListener;
Instantiate it with the controls which you need to know issrolling:
_processListViewScrollListener = new ControlScrollListener(ProcessesListView);
Wire in a handler:
_processListViewScrollListener.ControlScrolled += ProcessListViewScrollListener_ControlScrolled;
Handler the event:
void ProcessListViewScrollListener_ControlScrolled(object sender, EventArgs e)
{
// do what you need to do
}
The event args in the event raised could be tweaked to contain more useful information. I just needed to know my control had been scrolled!
See this post ListView Scroll Event
Use the native window class to listen
for the scroll messages on the
listbox. Will work with any control.
Catching the scroll event now is easily done in .net 4.
Catch the Loaded event from your ListView (m_ListView) and do this:
if (VisualTreeHelper.GetChildrenCount(m_ListView) != 0)
{
Decorator border = VisualTreeHelper.GetChild(m_ListView, 0) as Decorator;
ScrollViewer sv = border.Child as ScrollViewer;
sv.ScrollChanged += ScrollViewer_ScrollChanged;
}
then, implement your ScrollViewer_ScrollChanged function:
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
...
}

Categories

Resources