How to trigger an event when something is clicked 5 times? - c#

I know this is probably a stupid question with an easy answer, but I can't figure it out. I'm trying to trigger an event when a label is pressed 5 times. How could I go about doing this?

First of all, we don't know what label do you use - Windows Forms, Web Forms, WPF? For example, if you use Web Forms, this is completely different story with postbacks etc. WPF is also completely different "animal".
So, if this is a windows forms label or other control, just declare a class-variable/member and do something when it clicked 5 times and then reset it
public class Clickable
{
private int _counter = 0;
private void SomeControl_Click(object sender, EventArgs e)
{
_counter++;
if (_counter == 5)
{
// DO SOMETHING HERE
MySpecialMethod();
// And then reset counter so you can click 5 times again
_counter = 0;
}
}
}

I know this has been resolved, but I wanted to do the same thing recently and I'd like to share my implementation.
What I wanted was: do something when the user clicks 5 times within 2 seconds
Here's my helper class ClickStreakMachine.cs
using System;
namespace MyApp01;
/// <summary>
/// A helper class that invokes an action when the Click method is triggered enough times within a certain time window
/// </summary>
public class ClickStreakMachine
{
private readonly int _requiredClicks;
private readonly TimeSpan _maxClickSpacing;
private readonly Action _action;
private DateTime _lastClickedAt = DateTime.MinValue;
private int _clickStreak = 0;
/// <summary>
/// A helper class that invokes an action when the Click method is triggered enough times within a certain time window
/// </summary>
/// <param name="requiredClicks">Clicks required to trigger the action</param>
/// <param name="maxClickSpacing">Max distance between clicks</param>
/// <param name="action">Action to trigger when conditions are met</param>
public ClickStreakMachine(int requiredClicks, TimeSpan maxClickSpacing, Action action)
{
_requiredClicks = requiredClicks;
_maxClickSpacing = maxClickSpacing;
_action = action;
}
public void Click()
{
var now = DateTime.Now;
if (_clickStreak == 0)
{
//first click
_clickStreak++;
_lastClickedAt = now;
return;
}
//reset if clicked too late
if (now - _lastClickedAt > _maxClickSpacing)
{
_clickStreak = 1;
_lastClickedAt = now;
return;
}
//add
_clickStreak++;
_lastClickedAt = now;
//invoke action and reset streak if enough clicks
if (_clickStreak >= _requiredClicks)
{
_action?.Invoke();
_clickStreak = 0;
}
}
}
I'm using it like this:
using System.Windows.Controls;
namespace MyApp01;
public partial class MainWindow : Window
{
public MainWindow()
{
//setup the click streak machine so the user has to click 5 times (each click within 400ms of the last one) to trigger the DoSomething method
var clickStreakMachine = new ClickStreakMachine(5, TimeSpan.FromMilliseconds(400), () => DoSomething());
var label = new Label();
label.MouseLeftButtonDown += clickStreakMachine.Click();
}
}

Related

Can I have a Checked state on a ToolStripSplitButton in WinForms?

Is there a way to have a Checked state on a ToolStripSplitButton? I want to have a ToolStripButton with a Checked property to determine if an overlay is shown in my application's window. My users want to have direct interaction to control the opacity of the overlay, and to do that I imagine having a split button where the secondary button opens a slider bar to control the opacity. (That of course isn't standard functionality, but I'm confident of being able to put that together).
However, the ToolStripSplitButton doesn't have a Checked property, so I can't do that directly. I tried to have a standard ToolStripButton with a separate ToolStripSplitButton next to it, with the DropDownButtonWidth = 11, and Margin = -5, 1, 0, 2, so that it looks like it belongs to the ToolStripButton. It looks fine, but to complete the illusion of them being a single button, I need to get them to both to draw with the same VisualStyles.PushButtonState - so they both go Hot when the mouse is over either of them.
First I tried using the MouseEnter and MouseLeave events on both buttons to try to change the background color of the other button, but that had no effect. Then I tried to use the Paint event of each button to try to render them with the ButtonRenderer.DrawButton with a PushButtonState = Hot, but that didn't seem to be working either and was becoming very messy. It seems like there must be a better way!
Is there a simple, or at least a practical solution to this?
Edit: The effect I am after is something like this:
I suggest you to create your own UserControl and to use it as a ToolStripItem.
toolStripMenuItem.DropDownItems.Add(new ToolStripControlHost(new OverlayControl()));
Here is a simple example of the OverlayControl, which is deduced from your description (I suggest you to create a real UserControl with the Designer).
public class OverlayControl : UserControl
{
private readonly CheckBox _checkBox = new CheckBox();
private readonly TrackBar _trackBar = new TrackBar();
public ToolStripExtended()
{
_checkBox.Text = "Overlay";
BackColor = SystemColors.Window;
_checkBox.AutoSize = true;
_trackBar.AutoSize = true;
var flowLayoutPanel = new FlowLayoutPanel {AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink};
flowLayoutPanel.Controls.AddRange( new Control[] { _checkBox, _trackBar });
Controls.Add(flowLayoutPanel);
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
PerformLayout();
}
}
The solution that I came up with was to create my own button by inheriting from ToolStripSplitButton, add my own Checked property, and then manually draw over it when it is checked:
/// <summary>
/// ToolStripSplitCheckButton adds a Check property to a ToolStripSplitButton.
/// </summary>
public partial class ToolStripSplitCheckButton : ToolStripSplitButton
{
//==============================================================================
// Inner class: ToolBarButonSplitCheckButtonEventArgs
//==============================================================================
/// <summary>
/// The event args for the check button click event. To be able to use the OnCheckedChanged
/// event, we must also record a dummy button as well as this one (hack).
/// </summary>
public class ToolBarButonSplitCheckButtonEventArgs : ToolBarButtonClickEventArgs
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="split_button">The sender split check button</param>
public ToolBarButonSplitCheckButtonEventArgs(ToolStripSplitCheckButton split_button)
: base(new ToolBarButton("Dummy Button")) // Hack - Dummy Button is not used
{
SplitCheckButton = split_button;
}
/// <summary>
/// The ToolStripSplitCheckButton to be sent as an argument.
/// </summary>
public ToolStripSplitCheckButton SplitCheckButton { get; set; }
}
//==========================================================================
// Construction
public ToolStripSplitCheckButton()
{
m_checked = false;
m_mouse_over = false;
}
//==========================================================================
// Properties
/// <summary>
/// Indicates whether the button should toggle its Checked state on click.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the item should toggle its selected state when clicked."),
DefaultValue(true)]
public bool CheckOnClick { get; set; }
/// <summary>
/// Indictates the Checked state of the button.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the ToolStripSplitCheckButton is pressed in or not pressed in."),
DefaultValue(false)]
public bool Checked { get { return m_checked; } set { m_checked = value; } }
//==========================================================================
// Methods
/// <summary>
/// Toggle the click state on button click.
/// </summary>
protected override void OnButtonClick(EventArgs e)
{
if (CheckOnClick) {
m_checked = !m_checked;
// Raise the OnCheckStateChanged event when the button is clicked
if (OnCheckChanged != null) {
ToolBarButonSplitCheckButtonEventArgs args = new ToolBarButonSplitCheckButtonEventArgs(this);
OnCheckChanged(this, args);
}
}
base.OnButtonClick(e);
}
/// <summary>
/// On mouse enter, record that we are over the button.
/// </summary>
protected override void OnMouseEnter(EventArgs e)
{
m_mouse_over = true;
base.OnMouseEnter(e);
this.Invalidate();
}
/// <summary>
/// On mouse leave, record that we are no longer over the button.
/// </summary>
protected override void OnMouseLeave(EventArgs e)
{
m_mouse_over = false;
base.OnMouseLeave(e);
this.Invalidate();
}
/// <summary>
/// Paint the check highlight when required.
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (m_checked) {
// I can't get the check + mouse over to render properly, so just give the button a colour fill - Hack
if (m_mouse_over) {
using (Brush brush = new SolidBrush(Color.FromArgb(64, SystemColors.MenuHighlight))) {
e.Graphics.FillRectangle(brush, ButtonBounds);
}
}
ControlPaint.DrawBorder(
e.Graphics,
e.ClipRectangle, // To draw around the button + drop-down
//this.ButtonBounds, // To draw only around the button
SystemColors.MenuHighlight,
ButtonBorderStyle.Solid);
}
}
//==========================================================================
// Member Variables
// The delegate that acts as a signature for the function that is ultimately called
// when the OnCheckChanged event is triggered.
public delegate void SplitCheckButtonEventHandler(object source, EventArgs e);
public event SplitCheckButtonEventHandler OnCheckChanged;
private bool m_checked;
private bool m_mouse_over;
}
To use this, handle the OnCheckChanged event for this button.
This has a couple of clunky hacks, however. To be able to raise an OnCheckedChanged event for the check button, the event args have to include a reference to an unused dummy button in addition to the actual button. And even after experimenting with various renderers, I couldn't get the checked + mouse over render to be exactly the same as that for a normal check button, so I just draw a colour overlay, which is nearly right but not quite.

C# Windows Forms App: Separate GUI from Business Logic

I would like some advice on how to separate the UI and business logic in a simple C# Windows Forms Application.
Let's take this example:
The UI consists of a simple textbox and a button. The user enters a number between 0 and 9 and clicks the button. The program should add 10 to the number and update the text box with that value.
The business logic part should have no idea of the UI. How can this be accomplished?
Here's the empty Process class (Business Logic):
namespace addTen
{
class Process
{
public int AddTen(int num)
{
return num + 10;
}
}
}
The requirement is:
When the user clicks the button, somehow, the Process::AddTen gets invoked.
The Textbox must be updated with the return value of Process::AddTen.
I just don't know how to connect these two.
First, you need to change your class name. "Process" is name of a class in the Class Library and will likely cause confusion for anyone reading your code.
Let's assume, for the rest of this answer that you changed the class name to MyProcessor (still a bad name, but not a well-known, often-used class.)
Also, you're missing the code to check to ensure that the user input is, indeed, a number between 0 and 9. That's appropriate in the Form's code rather than the class code.
Assuming the TextBox is named textBox1 (The VS generated default for the first TextBox added to the form)
Further assuming the button's name is button1
In Visual Studio, double-click on the button to create the button click event handler, which will look like this:
protected void button1_Click(object sender, EventArgs e)
{
}
Within the event handler, add code so it looks like this:
protected void button1_Click(object sender, EventArgs e)
{
int safelyConvertedValue = -1;
if(!System.Int32.TryParse(textBox1.Text, out safelyConvertedValue))
{
// The input is not a valid Integer value at all.
MessageBox.Show("You need to enter a number between 1 an 9");
// Abort processing.
return;
}
// If you made it this far, the TryParse function should have set the value of the
// the variable named safelyConvertedValue to the value entered in the TextBox.
// However, it may still be out of the allowable range of 0-9)
if(safelyConvertedValue < 0 || safelyConvertedValue > 9)
{
// The input is not within the specified range.
MessageBox.Show("You need to enter a number between 1 an 9");
// Abort processing.
return;
}
MyProcessor p = new MyProcessor();
textBox1.Text = p.AddTen(safelyConvertedValue).ToString();
}
The class, with the access modifier set properly, should look like this:
namespace addTen
{
public class MyProcessor
{
public int AddTen(int num)
{
return num + 10;
}
}
}
You can create another class called "Process.cs" for example.
Methods that involve processing or data calculation you move there. In your case for example:
public class Process
{
public int AddTen(int num)
{
return num + 10;
}
}
Your UI click event will have a call to your "Process layer":
var myProcess = new Process();
//and then calculation
var initNumber = Convert.ToInt32(textBox.Text);
var calculatedValue = myProcess.AddTen(initNumber);
textBox.Text = calculatedValue.ToString();
This way your business logic, such as calculating is kept separately. If your UI changes you can still simply call myProcess.AddTen() method whether it's a web, Windows or a Mobile form.
Make your 'Process' class public (and as #DavidStratton says, change the name):
public class MyProcess
I would say you should parse your string value from TextBox.Text to an int:
private void button1_Click(object sender, EventArgs e)
{
MyProcess myProcess = new MyProcess();
string result = textBox1.Text;
int number;
if(int.TryParse(textBox1.Text, out number))
{
result = myProcess.AddTen(number).ToString();
}
textBox1.Text = result;
}
To separate the logic completely you can declare a base class that can contain the button and manage handlers. Your specific process can inherit the base class, and the logic can be set. Finally the form can declares an instance of the class and pass in the button.
It looks something like this:
class BaseProcessor
{
System.Windows.Forms.Button myButton;
public System.Windows.Forms.Button MyButton
{
get
{
return myButton;
}
set
{
myButton = value;
myButton.Click += new System.EventHandler(this.MyButton_Click);
}
}
public BaseProcessor()
{
}
public virtual void MyButton_Click(object sender, EventArgs e)
{
}
Then declare the process:
class MyProcess : BaseProcessor
{
public override void MyButton_Click(object sender, EventArgs e)
{
MessageBox.Show("This is my process");
}
}
Then inside the form, declare an instance of the process and attach the button:
public partial class Form1 : Form
{
MyProcess myProcess = null;
public Form1()
{
InitializeComponent();
myProcess = new MyProcess
{
MyButton = button1
};
}
}
Using this method, there is no business logic code in the form. The parent class is useful because events like clicking buttons are pretty common so its easier to declare them centrally, in my opinion.

implementing a progress bar to show work being done

I have one form Form1, and it contains two elements button and progress bar.
When I click on button it calls class.SomeFunction(), which then calls a few other functions from different classes to complete some work.
I would like to increase value of progress bar from inside class.SomeFunctin() and all other functions that SomeFunctino call
class #class
{
public static void SomeFunction(var something)
{
progressbar.value++;
class2.Function2(something);
}
}
class class2
{
public static void Function2(var something)
{
progressbar.value++;
}
}
How can this be done?
You really shouldn't have those functions update the progressbar-- it violates the single responsibility principle. You are better off using a backgroundworker or just update the progressbar from within your button_click event after each function call.
If you are doing something that takes so long you have to show a progress bar, then you should be doing it in a background thread and not the form. That will make the UI become unresponsive.
A Code Project BackgroundWorker Thread article has an example of a background thread that shows a progress bar in a WinForms app.
The easiest way for you to do this is simply to call an event that is handled in your form, and in the event handler have that increment the progress bar.
What you will first want to do is create a custom EventArgs.
public class ProgressEventArgs : EventArgs
{
public int Progress {get; set;}
public ProgressEventArgs(int progress)
{
Progress = progress;
}
}
Then in your classes that you want to increment the Progress bar you will want to raise this event.
class Class2
{
public event EventHandler<ProgressEventArgs> ProgressEvent;
public void Function2(var something)
{
OnRaiseProgressEvent(new ProgressEventArgs(1));
}
protected virtual void OnRaiseProgressEvent(ProgressEventArgs e)
{
// C# 6 and above:
// Raise event if event handler is set (i.e. not null)
ProgressEvent?.Invoke(this, e);
// end C# >=6 code
// C# 5 and earlier:
EventHandler<ProgressEventArgs> handler = ProgressEvent;
if(handler != null)
{
//this is what actually raises the event.
handler(this, e);
}
// end C# <=5 code
}
}
Then in your form you will want to subscribe to the event
public class YourForm
{
public YourForm
{
Class2 yourClass2Instance = new Class2();
yourClass2Instance.ProgressEvent += ProgressEventHandler;
}
private void ProgressEventHandler(object sender, ProgressEventArgs e)
{
progressbar.Value += e.Progress;
}
}
In the past I've used a simple menu bar-less form with a label and a Forms.ProgressBar on it using the following code in the form:
public partial class ProgressDialog : Form
{
//public delegate delSetProgress
private readonly int progressBarMax;
/// <summary>
/// Structure used for passing progress bar related parameters as a single variable.
/// </summary>
public struct ProgressBarParams
{
public int value;
public string message;
public ProgressBarParams(string Message, int Value)
{
message = Message;
value = Value;
}
}
/// <summary>
/// Constructs the progress bar dialog and sets the progress bar's maximum value to maxValue.
/// </summary>
/// <param name="maxValue">Value to set to progress bar's Maximum property.</param>
public ProgressDialog(int maxValue)
{
InitializeComponent();
progressBarMax = maxValue;
}
private void ProgressDialog_Load(object sender, EventArgs e)
{
progressBar.Maximum = progressBarMax;
}
/// <summary>
/// Public method to update the progressDialog
/// </summary>
/// <param name="inputParams">Values to update on the progressDialog</param>
public void SetProgress(ProgressBarParams inputParams)
{
lblMessage.Text = inputParams.message;
progressBar.setValue(inputParams.value);
Update();
}
/// <summary>
/// This method should be called when the operation represented by the ProgressDialog is
/// completed. It shows an "operation complete" message for a second and then closes the form.
/// </summary>
public void Finish()
{
lblMessage.Text = "Operation complete.";
progressBar.setValue(progressBar.Maximum);
Update();
System.Threading.Thread.Sleep(1000);
this.Close();
}
}
public static class MyExtensions
{
/// <summary>
/// Implements a hack to get around a stupid rendering problem with the .NET progress bar in some environments.
/// Sets the progress bar value property.
/// </summary>
/// <param name="proBar">Progress bar control to set the value on.</param>
/// <param name="value">Value to be set.</param>
public static void setValue(this ProgressBar proBar, int value)
{
if (value > 0)
{
proBar.Value = value;
proBar.Value = value - 1;
proBar.Value = value;
}
else
{
proBar.Value = value;
proBar.Value = value + 1;
proBar.Value = value;
}
}
}
Note the setValue extension method that uses a workaround to avoid an issue with some versions of Windows.
I then set it (and a splash screen) up with the following, where m_frmProgress is the progress bar form:
// Create splash screen/progress bar thread on the thread pool
ThreadPool.QueueUserWorkItem((x) =>
{
bool localDone = false;
m_frmSplash.Show();
m_frmProgress.Show();
// Set the progress bar form up slightly below the bottom of the splash screen
m_frmProgress.Location = new Point(m_frmProgress.Location.X, m_frmProgress.Location.Y + (m_frmSplash.Height / 2) + (m_frmProgress.Height / 2) + 10);
while (!localDone) // Keep iterating until the main thread tells us we're done
{
lock (splashScreenDoneLock)
{
localDone = splashScreenDone;
}
// I don't like this method of keeping the UI responsive, but as yet I have no better method
Application.DoEvents();
Thread.Sleep(500);
}
m_frmSplash.Close();
m_frmProgress.Close();
while (!m_frmProgress.IsDisposed || !m_frmSplash.IsDisposed) // While either splash form is not disposed (still active)
{
Thread.Sleep(100); // Keep waiting until the splash forms are gone
}
splashFormsDisposed.Set(); // Signal the main thread that the splash forms are gone so the main form can be shown
});
bool isSplashHandleCreated = false;
bool isProgressHandleCreated = false;
// Wait until both splash forms are created
while (!(isSplashHandleCreated && isProgressHandleCreated))
{
lock (m_frmSplash)
{
isSplashHandleCreated = m_frmSplash.IsHandleCreated;
}
lock (m_frmProgress)
{
isProgressHandleCreated = m_frmProgress.IsHandleCreated;
}
Thread.Sleep(500);
}
And invoke it like this:
m_frmProgress.Invoke(new Action<ProgressDialog.ProgressBarParams>(m_frmProgress.SetProgress), progressLevel);
It's not the most elegant method, but it gives you a cleanly update-able progress bar on a separate thread that will stay responsive while you're messing with it. I pretty much copy and pasted all that code from a working app, so it should work. On the flip side, I apologize if any of it is unclear.

OnPaint not getting called if form doesn't have focus

I have a user control with custom painting. Constructor sets styles correctly, from what I can tell. Basic code:
public partial class LineChart2 : UserControl
{
public LineChart2()
{
InitializeComponent();
//Set control styles to eliminate flicker on redraw and to redraw on resize
this.SetStyle(
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffer,
true);
SetDefaultValues();
}
protected override void OnPaint(PaintEventArgs e)
{
// breakpoint set here for verification
Paint~misc stuff(e.Graphics);
base.OnPaint(e);
}
private void UpdateGraph()
{
// this is called when the data that the control depends on changes
~update stuff();
this.Invalidate();
//this.Refresh();
}
}
The control is contained within a Panel on a standard WinForm.
I've tried both Invalidate and Refresh.
When using Invalidate(), the control will redraw properly as long as the form it is contained in has focus. Drawing is smooth. When I switch focus to another form, drawing ceases even though the events are still firing, and this.Invalidate() is still being called. The form is still fully visible on screen.
When using Refresh(), the control will redraw regardless of whether the form has focus, but the drawing constantly flickers, as if bypassing the double-buffering mechanism.
So how do I get the Invalidate message to properly invoke the OnPaint method regardless of focus?
Documentation says:
Calling the Invalidate method does not
force a synchronous paint; to force a
synchronous paint, call the Update
method after calling the Invalidate
method.
Have you tried calling Update after Invalidate?
You should not force the control do redraw (Update or Refresh) so often. The UI may get not responsive, others controls may not update, because you are giving all UI attention to the forced sync Refresh.
The right way is to draw only when UI is ready to do it. For that you need a render loop. The ApplicationLoopDoWork will be fired every time the UI is ready to draw something. The period depends on the machine speed and what is being redrawn.
The class is based on this post on Tom Miller's Blog.
Here is the class that I use to control that.
Make updates only on the ApplicationLoopDoWork call.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace Utilities.UI
{
/// <summary>
/// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible).
/// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
/// </summary>
public sealed class WinFormsAppIdleHandler
{
private readonly object _completedEventLock = new object();
private event EventHandler _applicationLoopDoWork;
//PRIVATE Constructor
private WinFormsAppIdleHandler()
{
Enabled = false;
SleepTime = 10;
}
/// <summary>
/// Singleton from:
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </summary>
private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler());
public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } }
private bool _enabled = false;
/// <summary>
/// Gets or sets if must fire ApplicationLoopDoWork event.
/// </summary>
public bool Enabled
{
get { return _enabled; }
set {
if (value)
Application.Idle += Application_Idle;
else
Application.Idle -= Application_Idle;
_enabled = value;
}
}
/// <summary>
/// Gets or sets the minimum time betwen ApplicationLoopDoWork fires.
/// </summary>
public int SleepTime { get; set; }
/// <summary>
/// Fires while the UI is free to work. Sleeps for "SleepTime" ms.
/// </summary>
public event EventHandler ApplicationLoopDoWork
{
//Reason of using locks:
//http://stackoverflow.com/questions/1037811/c-thread-safe-events
add
{
lock (_completedEventLock)
_applicationLoopDoWork += value;
}
remove
{
lock (_completedEventLock)
_applicationLoopDoWork -= value;
}
}
/// <summary>
///Application idle loop.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Application_Idle(object sender, EventArgs e)
{
//Try to update interface
while (Enabled && IsAppIdle())
{
OnApplicationIdleDoWork(EventArgs.Empty);
//Give a break to the processor... :)
//8 ms -> 125 Hz
//10 ms -> 100 Hz
Thread.Sleep(SleepTime);
}
}
private void OnApplicationIdleDoWork(EventArgs e)
{
var handler = _applicationLoopDoWork;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Gets if the app is idle.
/// </summary>
/// <returns></returns>
public static bool IsAppIdle()
{
bool isIdle = false;
try
{
Message msg;
isIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
catch (Exception e)
{
//Should never get here... I hope...
MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message);
}
return isIdle;
}
#region Unmanaged Get PeekMessage
// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
}
}
You also might try Invalidate(true) to trigger child controls to repaint as well.

OnMouseEnter for all controls on a form

I have OnMouseEnter and OnMouseLeave event handlers setup for my form. When the mouse moves over the form I want to set the opacity to 100% and when it moves away I want to set it to 25%. It works well, except when the mouse moves over one of the buttons on the form. The OnMouseLeave event fires and hides the form again. Is there a good way to handle this, without having to wire up OnMouseEnter for every control on the form?
EDIT: I'm going to leave this answer here, even though it can't be made to work reliably. The reason: to prevent somebody else from trying the same thing. See end of message for the reason it won't work.
You can do this fairly easily for the client rectangle by getting the cursor position and checking to see if it's within the Form's client area:
private void Form1_MouseLeave(object sender, EventArgs e)
{
Point clientPos = PointToClient(Cursor.Position);
if (!ClientRectangle.Contains(clientPos))
{
this.Opacity = 0.25;
}
}
This assumes that none of your child controls will be changing the opacity.
However, you'll find that it's a less than perfect solution, because when the mouse goes to the title bar, the Form goes to 0.25%. You could fix that by checking to see if the mouse position is within the window rect (using the Bounds property), but then your window will remain opaque if the mouse moves off the title bar and out of the window.
You have a similar problem when entering the title bar from outside.
I think you'll have to handle the WM_NCMOUSEENTER and WM_NCMOUSELEAVE messages in order to make this work reliably.
Why it can't work:
Even handling the non-client area notifications can fail. It's possible for the mouse to enter on a child control, which would prevent the Form from being notified.
I think it is impossible to do, without handling the MouseEnter and MouseLeave events of all the children, but you do not have to wire them manually.
Here is some code I copied & pasted from a project of mine. It does almost what you described here. I actually copied the idea and the framework from this site.
In the constructor I call the AttachMouseOnChildren() to attach the events.
The OnContainerEnter and OnContainerLeave are used to handle the mouse entering/leaving the form itself.
#region MouseEnter & Leave
private bool _childControlsAttached = false;
/// <summary>
/// Attach enter & leave events to child controls (recursive), this is needed for the ContainerEnter &
/// ContainerLeave methods.
/// </summary>
private void AttachMouseOnChildren() {
if (_childControlsAttached) {
return;
}
this.AttachMouseOnChildren(this.Controls);
_childControlsAttached = true;
}
/// <summary>
/// Attach the enter & leave events on a specific controls collection. The attachment
/// is recursive.
/// </summary>
/// <param name="controls">The collection of child controls</param>
private void AttachMouseOnChildren(System.Collections.IEnumerable controls) {
foreach (Control item in controls) {
item.MouseLeave += new EventHandler(item_MouseLeave);
item.MouseEnter += new EventHandler(item_MouseEnter);
this.AttachMouseOnChildren(item.Controls);
}
}
/// <summary>
/// Will be called by a MouseEnter event, with any of the controls within this
/// </summary>
void item_MouseEnter(object sender, EventArgs e) {
this.OnMouseEnter(e);
}
/// <summary>
/// Will be called by a MouseLeave event, with any of the controls within this
/// </summary>
void item_MouseLeave(object sender, EventArgs e) {
this.OnMouseLeave(e);
}
/// <summary>
/// Flag if the mouse is "entered" in this control, or any of its children
/// </summary>
private bool _containsMouse = false;
/// <summary>
/// Is called when the mouse entered the Form, or any of its children without entering
/// the form itself first.
/// </summary>
protected void OnContainerEnter(EventArgs e) {
// No longer transparent
this.Opacity = 1;
}
/// <summary>
/// Is called when the mouse leaves the form. When the mouse leaves the form via one of
/// its children, this will also call OnContainerLeave
/// </summary>
/// <param name="e"></param>
protected void OnContainerLeave(EventArgs e) {
this.Opacity = DEFAULT_OPACITY;
}
/// <summary>
/// <para>Is called when a MouseLeave occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerLeave should be called</para>
/// </summary>
protected override void OnMouseLeave(EventArgs e) {
Point clientMouse = PointToClient(Control.MousePosition);
if (!ClientRectangle.Contains(clientMouse)) {
this._containsMouse = false;
OnContainerLeave(e);
}
}
/// <summary>
/// <para>Is called when a MouseEnter occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerEnter should be called</para>
/// </summary>
protected override void OnMouseEnter(EventArgs e) {
if (!this._containsMouse) {
_containsMouse = true;
OnContainerEnter(e);
}
}
#endregion
I think one way to reliably handle the mouse events you're interested is to set up an IMessageFilter on your Application object from which you can intercept all mouse messages (WM_MOUSEMOVE etc ..) even if they are sent to child controls of the form.
Here's some demo code:
using System;
using System.Windows.Forms;
namespace Test
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
public static Form frm = null;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
frm = new Form1 {Opacity = 0.25};
frm.Controls.Add(new Button{Dock = DockStyle.Fill, Text = "Ok"});
Application.AddMessageFilter(new MouseMoveFilter());
Application.Run(frm);
}
}
public class MouseMoveFilter : IMessageFilter
{
#region IMessageFilter Members
private const int WM_MOUSELEAVE = 0x02A3;
private const int WM_NCMOUSEMOVE = 0x0A0;
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_NCMOUSELEAVE = 0x2A2;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_NCMOUSEMOVE:
case WM_MOUSEMOVE:
Program.frm.Opacity = 1;
break;
case WM_NCMOUSELEAVE:
case WM_MOUSELEAVE:
if (!Program.frm.Bounds.Contains(Control.MousePosition))
Program.frm.Opacity = 0.25;
break;
}
return false;
}
#endregion
}
}
Alternatively you can inherit from Form class and override PreProcessMessage() to accomplish the same thing ...

Categories