Detecting click anywhere on form (including controls) in WinForms - c#

A WinForms application, in which a certain UI control has to disappear 5 seconds after the user interacts with the window (clicking anywhere on the form including any control, or key presses).
Assuming an event is fired when this happens, the following is the event handler:
private async void userInteract(object sender, System.EventArgs e)
{
if (progressBarFinished)
{
await Task.Delay(5000);
statusIdle(); // this method hides a progress bar after 5 seconds. It is working.
}
}
In the form constructor, the event is subscribed to as follows:
// at the moment, it is not working for when a user clicks anywhere on the form
// it is working when a specific control click event occurs, like this one
progressBar.Click += userInteract;
Subscribing to the MouseClick and KeyPress events of the Form itself have been tried, but the event does not seem to fire then. It's only when specific control events are used, that it works, as stated above.

A form’s WndProc method processes messages sent to the form by the Windows operating system. This is an extremely important method that allows forms to move, resize, repaint, and perform other critical operations.
// Constants for decoding the Win32 message.
protected const int WM_MOUSEACTIVATE = 0x0021;
protected const int WM_LBUTTONDOWN = 0x201;
protected const int WM_RBUTTONDOWN = 0x204;
protected override void WndProc(ref Message m)
{
// Check the Message parameter to see if the message is WM_MOUSEACTIVATE indicating that a control was clicked.
if (m.Msg == WM_MOUSEACTIVATE)
{
int wparam = m.WParam.ToInt32();
if (wparam == WM_LBUTTONDOWN || wparam == WM_RBUTTONDOWN)
{
// TODO: Do something with the mouse event.
Console.WriteLine(m);
return;
}
}
base.WndProc(ref m);
}
Ref 1
Ref 2

Related

How to detect if the user clicked the X button in the leave event of a text box

I have a Leave Event for a TextEditor in which I perform a validation that an entry is required and display an error message.
Before I perform the validation, I check if the form is disposing, or the Cancel button was clicked. In that case I exit the leave event.
But if the user clicks the X button, these two checks do not capture that and the error message is displayed. I do not want the error message to display if the user clicks the X button. How can I achieve that?
private void TitleTextEditor_Leave(object sender, EventArgs e)
{
UltraTextEditor _currentControl = sender as UltraTextEditor;
if (this.CancelUButton.Focused || this.Disposing)
{
return;
}
if (_currentControl.Text.IsNullOrStringEmpty())
{
MessageBox.Show("Title is required.");
}
}
This is a cruddy problem if you want to suppress the validation error message you display. The only decent way to get ahead of it is by detecting the WM_CLOSE message before the Winforms code sees it and generates the Validating event on the control with the focus.
Paste this code to solve your problem:
protected override void WndProc(ref Message m) {
// Prevent validation on close:
if (m.Msg == 0x10) this.AutoValidate = AutoValidate.Disable;
base.WndProc(ref m);
}
Do consider that you are yelling too loud. The ErrorProvider component is a very decent way to display validation errors and be subtle about it. And nothing drastic goes wrong when the form validates itself on closure, you only have to do this:
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
e.Cancel = false;
}
In the FormClosingEventArgs you have a CloseReason property, you can probably use it.
How to find out if a control's Form has closed or not. I have listened to the VisibleChanged event in order to divine the Form because ParentChanged can happen before the control is added to a Form (e.g. if it is in a Panel). You might also want to unsubscribe from VisibleChanged events after the first one.
//put this at class level
bool _parentClosed;
//put this in controls constructor
//when control first becomes visible
this.VisibleChanged += (s1, a1) =>
{
//find parent Form (not the same as Parent)
Form form = this.FindForm();
//If we are on a Form
if (form != null)
//subscribe to it's closing event
form.Closing += (s2, a2) => { _parentClosed = true; };
else
throw new Exception("How did we become visible without being on a Form?");
};

How to detect when an MDIClient window has been scrolled

I need to update the position of a child window inside my System.Windows.Forms.MDIClient container when the user scrolls it by dragging the MDIClient's scrollbar thumb.
However I can't find an event that triggers when this happens.
Am I simply missing it, or do I need a workaround, possibly by talking direct to the scrollbar?
I've already tried handling MDIClient.Layout events, but they aren't being triggered by scrolling.
EDIT: I actually only need to know when the scrolling has stopped, in order to change my child window's position.
EDIT2: As a temporary workaround, I'm resetting the child window position on a timer every second, obviously not ideal, but better than nothing. Looks terrible though!
That's possible although a bit awkward. Winforms doesn't make it very easy to find the MdiClient window back and the class itself doesn't expose the Scroll event. That can be worked around, as always in Winforms, you have to sub-class the native MDI client window of your parent window so you can capture the WM_VSCROLL message. This code worked well, paste it into your parent form class:
void MdiClient_Scroll(object sender, ScrollEventArgs e) {
if (e.Type == ScrollEventType.EndScroll) {
// Do your stuff
//...
}
}
private MdiClientWrapper wrapper;
protected override void OnHandleCreated(EventArgs e) {
// Find the MdiClient and sub-class it so we can get the Scroll event
base.OnHandleCreated(e);
if (wrapper != null) wrapper.Scroll -= MdiClient_Scroll;
var client = this.Controls.OfType<MdiClient>().First();
wrapper = new MdiClientWrapper();
wrapper.AssignHandle(client.Handle);
wrapper.Scroll += MdiClient_Scroll;
}
private class MdiClientWrapper : NativeWindow {
public event ScrollEventHandler Scroll;
private int oldPos;
protected override void WndProc(ref Message m) {
if (m.Msg == 0x115) { // Trap WM_VSCROLL
var type = (ScrollEventType)(m.WParam.ToInt32() & 0xffff);
var pos = m.WParam.ToInt32() >> 16;
Scroll(this, new ScrollEventArgs(type, oldPos, pos));
oldPos = pos;
}
base.WndProc(ref m);
}
}

C# Detecting a Click in WndProc and calling a function after click happened

I am trying to detect a click and preform calling a function only after the click actually happened.
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message m) {
//Detect a Click
if (m.Msg == 0x210 && m.WParam.ToInt32() == 513){
lastClick = DateTime.Now;
clickedHappened();
Debug.Print("Click Detected!");
}
base.WndProc(ref m);
}
private void clickedHappened(){
MessageBox.Show("Click Already Happened");
}
I think that WndProc happens way before the actual click takes place.
I was wondering if there was a way to solve this with out using a timer? or sleep(400);
The only solution I can come up with is using a timer, but I want to get rid of some of my existing timers. It seems that the click actually happens 200 - 350 ms after it was detected in WndProc.
WndProc is short for Window Procedure, its the procedure that handles everything for the window, drawing, mouse capture, keyboard capture, resizing...
There are also several ways you can capture the mouse for varying results
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case 0x201:
//left button down
break;
case 0x202:
clickedHappened(); //left button up, ie. a click
break;
case 0x203:
//left button double click
break;
}
base.WndProc(ref m);
}
The thing is, windows forms in C# already handles all of these for you and from the WndProc, fires events there is no real need to handle WndProc yourself for this type of thing.
For a full list of mouse notification messages, see MSDN: Mouse Input Notifications and for a list of all wndproc messages, see MSDN System Defined Messages
Probably, you can handle WM_LBUTTONUP message that occured after click.
You can do it like this:
const int WM_LBUTTONUP = 0x202;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONUP)
{
clickedHappened();
}
base.WndProc(ref m);
}

How to handle Form caption right click

I'd like a context menu on the caption bar right click
any tips/samples pref in c# ?
UPDATE - for various reasons, right click on the form won't work because the form is not empty and the form is composited dynamically so....
You can do this by trapping the WM_NCRBUTTONDOWN notification that Windows sends when the user right-clicks the title bar. The control class does not have an event for it, you'll need to override WndProc(). Here's an example form, you'll need to add a ContextMenuStrip:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected void OnTitlebarClick(Point pos) {
contextMenuStrip1.Show(pos);
}
protected override void WndProc(ref Message m) {
const int WM_NCRBUTTONDOWN = 0xa4;
if (m.Msg == WM_NCRBUTTONDOWN) {
var pos = new Point(m.LParam.ToInt32());
OnTitlebarClick(pos);
return;
}
base.WndProc(ref m);
}
}
MSDN explains how to handle right-clicks on Windows Forms controls. Controls, including Forms, inherit the MouseClick event.
MouseEventArgs will tell you what button was clicked through the Button property. Have a look at the MouseButtons Enumeration.
if you handle the form mouse-click, you can then use the following code:
private void Dialog_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
this.Text = "new caption text";
}
}
But you'll have to make sure that you generate this event for the top-level control on a form. For instance if you have a group box on the form, it will receive the mouse-click events rather than the form itself, for the areas of the form that are under the group box.
There is already a menu managed by Windows when you right-click the titlebar.
Do you want to replace it completely?
If you want to add to it you will have to use the Win32 API and interop and you will have to subclass the form.
See the AppendMenu() function.
Basically you need to use p-invoke to do this. There is a really great example at Here
You can see from the example you will need to manually mimic the event handlers, but this is pretty straight forward.
You can override WndProc of the form and capture the WM_NCRBUTTONDOWN message:
protected override void WndProc(ref Message m)
{
const int WM_NCRBUTTONDOWN = 0xA4;
if (m.Msg == WM_NCRBUTTONDOWN)
{
MessageBox.Show("Caption right clicked!");
}
else
{
base.WndProc(ref m);
}
}
This code will suppress the window's context menu, however. You may not wish this. The WM_NCRBUTTONDOWN message will also be sent if you right click the window borders as well. You may not desire this either.

How do I add code to the exit button in a c# form?

I'm using Microsoft Visual C# 2008 Express.
In my main form, there's that X button to close the form at the top right. How do I add code to this button? In my menu, I have an "Exit" item and it has code that cleans up and closes my databases. How do I add the same code to this button if the user chooses that as a way to exit?
Thanks!
-Adeena
Using the FormClosing Event should catch any way of closing the form.
In the Design view for your form, within the Properties window, select the Events button and scroll down to the "FormClosed" and "FormClosing" events.
FormClosed is called after the form is closed.
FormClosing is called before the form is closed and also allows you to cancel the close, keeping the form open:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
}
If you want to ask the user "Are you sure you want do close this form?", then use FormClosing, where you can set Cancel = True and the form would remain open.
If you want to close some resource only when the form is definitely closed, then you use FormClosed event.
If you are in control of the whole code, then it kind of does not matter. But what you do not want to happen is to clean-up the resources using FormClosing when the the other handler of the event will keep the form open.
FormClosing/FormClosed lets you watch the event for that form which may coincide with the application exiting. However, there is another event you can wire up called Application.ApplicationExit.
In your Main method:
Application.ApplicationExit += Application_ApplicationExit;
...
private static void Application_ApplicationExit(object sender, EventArgs e) {
// do stuff when the application is truly exiting, regardless of the reason
}
Use the Closed-Event of your winform.
This code will capture the user clicking on the 'X' or using Alt-F4 on a form to allow you to do something. I had to use this because the I needed the action to call my closing event, and it wouldn't call it when using the FormClosing Event due to racing events.
/// <summary>
/// This code captures the 'Alt-F4' and the click to the 'X' on the ControlBox
/// and forces it to call MyClose() instead of Application.Exit() as it would have.
/// This fixes issues where the threads will stay alive after the application exits.
/// </summary>
public const int SC_CLOSE = 0xF060;
public const int WM_SYSCOMMAND = 0x0112;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_SYSCOMMAND && (int)m.WParam == SC_CLOSE)
MyClose();
base.WndProc(ref m);
}
double click the exit button in form'design then
simply call the Dispose() method
Form action method
//
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
if (e.CloseReason == CloseReason.WindowsShutDown) return;
switch (MessageBox.Show(this, "Are you sure you want to exit?", "Exit", MessageBoxButtons.YesNo))
{
case DialogResult.No:
e.Cancel = true;
break;
default:
break;
}
}
You can use form closing events choose or set closing event in properties window
You can add dialog conditions based on what task you want to perform before closing form
private void Form1_FormClosing(object sender,FormClosingEventArgs e)
{
Application.Exit();
//you can also use Application.ExitThread();
}

Categories