C# Winforms How to Understand Form object is moved - c#

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

Related

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 make an event span over multiple forms

The project I am working on needs a couple buttons on multiple forms, instead of doing the code shown below I was hoping it could be made global.
This is only one part of the project, all the code does is enlarge the button picture when the user hovers over it.
I've tried looking at classes, tags and attributes. I know classes can be made to use across multiple forms but I cant find out if they work with events.
private void btnEnter_MouseEnter(object sender, EventArgs e)
{
Button btn = (Button)sender;
btn.Size = new Size(299, 102);
}
private void btnLeave_MouseLeave(object sender, EventArgs e)
{
Button btn = (Button)sender;
btn.Size = new Size(289, 92);
}
You can create an inherited button. Add a new class then make sure you put : Button after the class name.
using System.Drawing;
using System.Windows.Forms;
namespace InheritedButton
{
public class ExpandButton : Button
{
public Size EnterSize { get; set; }
private Size _LeaveSize;
public Size LeaveSize
{
get
{
return (_LeaveSize);
}
set
{
_LeaveSize = value;
this.Size = LeaveSize;
}
}
public ExpandButton() : base()
{
}
protected override void OnMouseEnter(EventArgs e)
{
this.Size = EnterSize;
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
this.Size = LeaveSize;
base.OnMouseLeave(e);
}
}
}
Build your project and the new button will appear in the toolbox. Drop it onto a form/control and make sure you set the EnterSize and LeaveSize. EnterSize determines the size of the button when you mouse over and LeaveSize sets the initial size and sets the size of the button when you mouse out. You don't need to set the Size property, just set LeaveSize.
Any time you want to use the expanding/contracting button just use the inherited one instead.

Moving Frameless Windows Form via Mouse Drag

I'm new to C# and I've attempted many of the solutions here on StackExchange and I've had no luck in trying to set up a control or the entire form for that matter to left click and drag the entire window. I'm working with a frameless window in visual studio 12. The closest I've come to moving the window is moving a single control with the pastebin component(last response) from this-
How do I make mousedrag inside Panel move form window?
I could only get the panel itself to move with that component.
I've tried most of the approaches but I seem get lost where I am to customize it to my own needs. I've tried WndProc override but it didn't do anything when I attempted to move the form window.
I have two panels I want to be able to drag the window with DragPanel and DragPanel2.
Here is my most recent failed approach trying to use the whole form.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InsideMover _dragger = new InsideMover();
_dragger.ControlToMove = this.DragPanel;
}
private void close_Click(object sender, EventArgs e)
{
Close();
}
}
public class InsideMover : Component
{
/// <summary>
/// Required designer variable.
/// </summary>
private Container components = null;
public InsideMover(IContainer container)
{
///
/// Required for Windows.Forms Class Composition Designer support
///
container.Add(this);
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
public InsideMover()
{
///
/// Required for Windows.Forms Class Composition Designer support
///
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
public Control ControlToMove
{
set
{
if (_parent != null)
{
// odkvaci prijasnje evente
_parent.MouseDown -= new MouseEventHandler(_parent_MouseDown);
_parent.MouseMove -= new MouseEventHandler(_parent_MouseMove);
_parent.MouseUp -= new MouseEventHandler(_parent_MouseUp);
_parent.DoubleClick -= new EventHandler(_parent_DoubleClick);
}
_parent = value;
if (value != null)
{
// zakači se na evente od containera koji ti trebaju
_parent.MouseDown += new MouseEventHandler(_parent_MouseDown);
_parent.MouseMove += new MouseEventHandler(_parent_MouseMove);
_parent.MouseUp += new MouseEventHandler(_parent_MouseUp);
_parent.DoubleClick += new EventHandler(_parent_DoubleClick);
}
}
get
{
return _parent;
}
}
Control _parent;
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
int _lastMouseX;
int _lastMouseY;
bool _moving;
public void StartMouseDown(MouseEventArgs e)
{
_parent_MouseDown(null, e);
}
private void _parent_MouseDown(object sender, MouseEventArgs e)
{
_lastMouseX = e.X;
_lastMouseY = e.Y;
_moving = true;
}
private void _parent_MouseMove(object sender, MouseEventArgs e)
{
if (_moving)
{
Point newLocation = _parent.Location;
newLocation.X += e.X - _lastMouseX;
newLocation.Y += e.Y - _lastMouseY;
_parent.Location = newLocation;
}
}
private void _parent_MouseUp(object sender, MouseEventArgs e)
{
_moving = false;
}
private void _parent_DoubleClick(object sender, EventArgs e)
{
if (_parent is Form)
{
Form f = (Form)_parent;
if (f.WindowState == FormWindowState.Normal)
{
f.WindowState = FormWindowState.Maximized;
}
else
{
f.WindowState = FormWindowState.Normal;
}
}
}
}
}
How can I set the panels to left click drag the window?
I've tried all of the methods at the post above and the WndProc method here:
Drag borderless windows form by mouse
If you follow the answer in the link you've posted, it is using the mousemove event, whereas judging by the code you've posted, you're using the mousedown event. The mouse down event is only called once when you press a mouse button, it is not called again if you move the mouse while you keep pressing the button. Whereas the mousemove event is called whenever your pointer moves. So your best bet would be to change your mousedown event with the mousemove event i.e.
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
if that doesn't work, you can do something like this in the mousemove event, first create a Point 'prevpoint' and an offset point in the form.
Point prevpoint=new Point(0,0);
Point offset=new Point(0,0);
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
offset.X=e.X-prevpoint.X;
offset.Y=e.Y-prevpoint.Y;
prevpoint.X=e.X;
prevpoint.Y=e.Y;
this.Location = new Point(this.Location.X + offset.X, this.Location.Y + offset.Y);
}
}
I have not tested the above code but that will hopefully give you the basic idea.

Flashing ToolStripButton

I am using a ToolStrip with a number of ToolStripButtons.
What I would like is to be able to flash one of the buttons to get the user's attention.
For example, if they have made changes to information and need to click the Save button.
If this were a normal button I could do this using a Timer and periodically changing the BackColor however this doesn't work with a ToolStrip.
I could create a Renderer subclass and assign it to the ToolStrip but this appears to only get used in specific situations - i.e. it's event driven.
Does anyone have any ideas?
Well, just use a custom renderer so you can change the color of the button's background. With a timer that blinks it. Add a new class to your project and paste this code:
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Windows.Forms;
class BlinkingButtonRenderer : ToolStripProfessionalRenderer {
public BlinkingButtonRenderer(ToolStrip strip) {
this.strip = strip;
this.strip.Renderer = this;
this.strip.Disposed += new EventHandler(strip_Disposed);
this.blinkTimer = new Timer { Interval = 500 };
this.blinkTimer.Tick += delegate { blink = !blink; strip.Invalidate(); };
}
public void BlinkButton(ToolStripButton button, bool enable) {
if (!enable) blinkButtons.Remove(button);
else blinkButtons.Add(button);
blinkTimer.Enabled = blinkButtons.Count > 0;
strip.Invalidate();
}
protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) {
var btn = e.Item as ToolStripButton;
if (blink && btn != null && blinkButtons.Contains(btn)) {
Rectangle bounds = new Rectangle(Point.Empty, e.Item.Size);
e.Graphics.FillRectangle(Brushes.Black, bounds);
}
else base.OnRenderButtonBackground(e);
}
private void strip_Disposed(object sender, EventArgs e) {
blinkTimer.Dispose();
}
private List<ToolStripItem> blinkButtons = new List<ToolStripItem>();
private bool blink;
private Timer blinkTimer;
private ToolStrip strip;
}
Sample usage in a form with a Toolstrip containing a button:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
blinker = new BlinkingButtonRenderer(toolStrip1);
}
private void toolStripButton1_Click(object sender, EventArgs e) {
blink = !blink;
blinker.BlinkButton(toolStripButton1, blink);
}
private bool blink;
private BlinkingButtonRenderer blinker;
}

Handling a click event anywhere inside a panel in 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);
}
}

Categories

Resources