Windows Develop Bookmark and Share   
 index > Windows Forms General > Button Click Monitor and "Please wait..." messages. How to?
 

Button Click Monitor and "Please wait..." messages. How to?

Hello everybody.

What I want to do is simply show a message like "Please wait..." when a user clicks any button, in any form of my application, but only if the button's operation takes more than a'x' amount of time (I'm using 2s here as an example).

I have searched Patterns & Practices and MSDN for a solution for this problem but I couldn't find any, so I'm posting here for any suggestion or hint. In advance... Thank you very much Smile

Well,for thisjob get done,I thought in first creating a message filter that filters 'click' messages dispatched to my application.If the clicked control is a button,begin the code logic to start a timer.If Timer.Elapsed then show my message, and after the button's click event terminates, hide my message.

To do this, I began with a simple ClickFilter class, as follows:

Code Snippet

public sealed class ClickFilter : IMessageFilter

{

private Control _clickedControl;

public ClickFilter()

{

_clickedControl = null;

}

public bool PreFilterMessage(ref Message m)

{

if (m.Msg == 0x0201)

{

if (m.WParam.ToInt32() == 0x1)

{

_clickedControl = Control.FromHandle(m.HWnd);

if (_clickedControl is Button)

TimedSingleton.Instance.Attach(_clickedControl);

}

}

return false;

}

}

This is working perfectly well, since I get only left click messages (WParam = 0x1).

The tricky (and hard) part in my oppinion is the class TimedSingleton. This is a (theoretically) thread safe /multi-threaded singleton that manages click events from the whole application. This way, my solution only have one timer, one message and one monitor thread.

My implementation of this class is as follows:

Code Snippet

public class TimedSingleton : System.Object

{

#region Singleton Part

private static volatile TimedSingleton instance;

private static Object syncRoot = new Object();

public static TimedSingleton Instance

{

get

{

if (instance == null)

{

lock (syncRoot)

{

if (instance == null)

instance = new TimedSingleton();

}

}

return instance;

}

}

#endregion

private System.Threading.AutoResetEvent _flag;

private System.Timers.Timer _actionTimer;

private EventHandler _clickHandler;

private Form _waitForm;

private TimedSingleton()

{

// Creates thehandler

_clickHandler = new EventHandler(LastHandler);

//Createsthe flag

_flag = new AutoResetEvent(false);

// Creates the wait form

_waitForm = new frmWait();

_waitForm.TopMost = true;

_waitForm.UseWaitCursor = true;

// Creates the timer

_actionTimer = new System.Timers.Timer();

_actionTimer.Interval = 1000;

_actionTimer.Elapsed += new System.Timers.ElapsedEventHandler(ShowForm);

_actionTimer.Stop();

}

public void Attach(Control ctr)

{

lock (syncRoot)

{

if ((ctr == null) || (_actionTimer.Enabled))

return;

if (!_flag.Reset())

return;

ctr.Click += _clickHandler;

_actionTimer.Start();

}

}

private void LastHandler(object sender, EventArgs e)

{

try

{

_actionTimer.Stop();

((Control)sender).Click -= _clickHandler;

}

finally

{

_flag.Set();

}

}

private void ShowForm(object sender, System.Timers.ElapsedEventArgs e)

{

_actionTimer.Stop();

_waitForm.Show();

_waitForm.Refresh();

_flag.WaitOne();

_waitForm.Hide();

}

}

As the control's attached event handlers are executed in order, the LastHandlermethod is executed after all other handlers (since it was dinamically attached), meaning that the button have finished processing and it's returning to the form's message loop.

The reason of using another thread is simple: If the main thread is extremely busy (a very intensive calculation in progress for example), or blocked (an synchronous task is in progress), the message won't be shown, or even better, wont be drawn.

This is working fine for any button, in any form, except for:

  • If the button runs an modal form(sync op) for example, the message won't desappear because the Click event waits the modal form to close;
  • Using _flag.WaitOne(); blocks the message threadand soI cannot use an user-friendly animated image;
  • Althought the multi-click scenario was forseen in the Attach method, I'd appreciate some hints on this approach, since I'm not feeling entirely secure with the actual code.

..... and other glitches that this implementation may have.

Does anybody have a clue on howto solve mainly the first problem?

Thank you very much.

Margonar  Wednesday, July 04, 2007 1:11 PM

Hi Rodrigo Silva,

I think that multi-threading handling is a better technology for this requirment.The code below is amy example. This may make help.

Code Snippet

public class SampleButton : System.Windows.Forms.Button

{

private sealed class WaitForm : System.Windows.Forms.Form

{

private readonly StringFormat textFormat = new StringFormat();

public WaitForm()

{

this.textFormat.Alignment = StringAlignment.Center;

this.textFormat.LineAlignment = StringAlignment.Center;

}

protected override void OnPaint(PaintEventArgs e)

{

base.OnPaint(e);

e.Graphics.DrawString("Please wait...", this.Font, SystemBrushes.ControlText, this.ClientRectangle, this.textFormat);

}

protected override void OnHandleDestroyed(EventArgs e)

{

base.OnHandleDestroyed(e);

this.textFormat.Dispose();

}

}

private static readonly object EventButtonClick = new object();

private readonly WinTimer timer = new WinTimer();

private volatile bool busying;

private bool showWaitForm;

private WaitForm waitingForm;

public SampleButton()

{

this.timer.Tick += delegate

{

if (this.busying)

{

if (this.waitingForm == null)

{

this.waitingForm = new WaitForm();

this.waitingForm.ShowDialog(this.FindForm());

}

}

else

{

if (this.waitingForm != null)

{

this.timer.Stop();

this.waitingForm.Close();

this.waitingForm.Dispose();

this.waitingForm = null;

}

}

};

this.timer.Interval = 2000;

}

public bool ShowWaitForm

{

get { return this.showWaitForm; }

set { this.showWaitForm = value; }

}

public event EventHandler ButtonClick

{

add { this.Events.AddHandler(EventButtonClick, value); }

remove { this.Events.RemoveHandler(EventButtonClick, value); }

}

protected virtual void OnButtonClick(EventArgs e)

{

if (this.busying) return;

EventHandler handler = (EventHandler)this.Events[EventButtonClick];

if (handler != null)

{

this.busying = true;

if (this.showWaitForm)

{

this.timer.Start();

Thread thread = new Thread(new ThreadStart(delegate

{

try

{

handler(this, e);

}

finally

{

this.busying = false;

}

}));

thread.Start();

}

else

{

handler(this, e);

this.busying = false;

}

}

}

protected override void OnClick(EventArgs e)

{

base.OnClick(e);

OnButtonClick(e);

}

protected override void OnHandleDestroyed(EventArgs e)

{

base.OnHandleDestroyed(e);

this.timer.Dispose();

}

}

new System.EventHandler(this.longTimeButton_ButtonClick);

void longTimeButton_ButtonClick(object sender, EventArgs e)

{

Thread.Sleep(10000);

Hello Wei Zhou,

Thanks for your answer!

I must tell that I will analyse your code very carefully, but what I can see on a first glance is that you have created a new control with an especial event. That´s very cool, but one of my intentions is not to replace the existing buttons on my application, based on the fact that it will consume a lot of time and resources (this is a really big application).

I'll think more deeply in your solution, and post you anything shortly.

Thank you again.

Margonar  Friday, July 06, 2007 9:36 PM

Hi Rodrigo Silva

I think that not only when a button run a model form, but also when a button run a long time operation, will block the message. Is this a matter to you? If so,I think that multi-threading handlingcould bea considerable technology. The link below may help.

http://msdn2.microsoft.com/en-us/library/ms229730(VS.80).aspx

Regards

Wei Zhou

Wei Zhou - MSFT  Saturday, July 07, 2007 12:42 PM
I think what Wei is trying to say, and I would have to agree, is that using threading doesn't make sense in this case. If your program is busy executing code for so long that a "Please wait" message would be a useful diagnostic to the user, it is not going to see the mouse click event. Windows messages are only received and processed when your program goes idle. The odds that it would be idle to see the click event, then suddenly gets busy so you'd need the thread to handle the wait message display are quite small. Sorry if I got it wrong, I didn't read through your entire post.
nobugz  Saturday, July 07, 2007 9:39 PM

Hello guys! Thank you very much for your answers.

Well... I think that I need to explain better what I'm trying to do....

I have a huge application, and I know that the right thing to do is create a new control (as Wei did) to do this job. But there are a lot of buttonsin every form, and replacing them to do this job is totally out of question because of the amout of time required and impact on code.

Then, I have decided to monitor click messages from buttons, because this way I have one handler for all buttons.

This way, as my application is idle and a user clicks a button, my code receives the message, and my intentions are to attach an handler and start a timer. This running on an second thread, free of the activity. If the main thread gets busy by the button's click event, then the second thread shows de message and closes it, after the button's click handler exit.

I don't know other way of showing the message if the main thread is busy... Sad

What do you think?

Many thanks again!

Margonar  Monday, July 09, 2007 9:52 PM
Hmya, don't underestimate the power of an old-fashioned hourglass cursor to tell the user what's going on.
nobugz  Monday, July 09, 2007 10:11 PM

Hm well...

In thisI think thata simple hourglass cursor won't give the user the impression of a running app.

Does this "answer"means that doing this thru threads is impossible?

In many cases, and everyday, we can see applications that hangs and the cursor stays spinning as usual (wich by the way makes me even more angry Smile ). The new version of Hotmailshows something like what I'm trying to do... Maybe a good thing to be added to de MSP&P.

I'll keep trying to get somewhere with this approach, if anybody knows something that can help it will be of great use Wink

Many thanks, and best regards.

Margonar  Tuesday, July 10, 2007 12:29 PM

You can use google to search for other answers

Custom Search

More Threads

• autosave option using dot net
• Word document as Modal dialog
• WINFORMS
• How to make Popup form on the top????
• Slow c# mdi child form rendering
• Multithreaded application behaving abnormal.
• populate the datagrid on background thread.
• tablelayoutpanel, combobox, array
• C# - VB6 Interop and Message Filters
• Show() override