MonthCalendar, when changing month + showing an msgbox, it loops..

G

Guest

Well, make a new windows-application...
Put in a MonthCalendar, and put in this:

Public Class Form1

Private Sub MonthCalendar1_DateChanged(ByVal sender As System.Object,
ByVal e As System.Windows.Forms.DateRangeEventArgs) Handles
MonthCalendar1.DateChanged

MsgBox("problem")

End Sub

End Class

Then start the program... See my problem?
Any way to fix this? :)
 
P

Peter Duniho

Private Sub MonthCalendar1_DateChanged(ByVal sender As System.Object,
ByVal e As System.Windows.Forms.DateRangeEventArgs) Handles
MonthCalendar1.DateChanged

MsgBox("problem")

End Sub

Then start the program... See my problem?
Any way to fix this? :)

I'm going to take a wild guess and make the assumption that calling
MsgBox() causes the DateChanged event to be raised again, which causes you
to call MsgBox(), etc.

For future reference, you should skip the theatrics and actually
_describe_ the problem you're having in your post. You have no way of
knowing for sure that someone else will see the same behavior that you
are, and so you could get an answer to a completely different question
from the one you thought you were asking.

All that said, I see at least a couple of options:

* Don't call MsgBox() from within the event handler. Use
BeginInvoke() or similar mechanism to cause the message box to be
displayed outside of the DateChanged event handling call stack.

* Set a flag before you call MsgBox() the first time. Don't call
MsgBox() if the flag is set.

Also, not being a VB user, I don't know what MsgBox() maps to, but on the
outside chance that it's _not_ exactly the same as using the
Forms.MessageBox class, you might try that instead. It's not really clear
to me why displaying a message box would cause the DateChanged event to be
raised, and maybe the Forms.MessageBox class avoids that behavior somehow.

Pete
 
G

Guest

Well, the thing that happens is that:

When the mouse is still clicked, the messagebox shows... Then, more and more
messageboxes show up, because apparently, the mouse hasent released the
"scrolling-button" (which changes the month, which like you said, raises the
event again and again)

I got a fix, and that was to execute the msgbox in a backgroundworker :)

Ill try your way with begininvoke, though, ive never used it before, but ill
give it a go :)

Thanks for the help..
 
P

Peter Duniho

Well, the thing that happens is that:

When the mouse is still clicked, the messagebox shows... Then, more and
more
messageboxes show up, because apparently, the mouse hasent released the
"scrolling-button" (which changes the month, which like you said, raises
the
event again and again)

For the record, you never mentioned that part of the problem in your
original post. You just said to "start the program".

I didn't bother trying to run your code because, well...to do so seemed
pointless. But you certainly left out some important detail there.
I got a fix, and that was to execute the msgbox in a backgroundworker :)

How does that fix the behavior? If the user continues to drag and change
the month, I would expect that you would simply keep executing new message
boxes in a background worker.
Ill try your way with begininvoke, though, ive never used it before, but
ill
give it a go :)

Given your newest information, I'm not sure BeginInvoke() would help, just
as I'm not clear on why running the message box in a background worker
helps. From your description, the user really is doing something that
causes the event to be raised, and every time the event is raised, you
display a message box.

Until you stop doing that, it doesn't seem to me that it will matter how
you display the message box. It's not the act of displaying the message
box that causes the event to be raised again, so changing how you display
the message box isn't going to fix the problem.

You need to set a flag while the message box is displayed, and not show it
again until the message box has been dismissed.

Personally, I think that wouldn't really be all that great a solution
either. IMHO, the _correct_ user interface is to not show the dialog
until the user has released the mouse button. For that, you will need to
set a flag in the DateChanged event that is then checked later in the
MouseUp event handler, and only then display the message box.

It's pretty bad UI to be popping up dialog boxes when the mouse button is
still down.

Pete
 
G

Guest

Well, i really dont know how it works when the messagebox is in a
backgroundworker, but it does work.. It does solve the problem...

Only weird thing, the event is raised twice for some intriguing reason :p
Anyway, i just put in a check on the worker status, and it exits the sub if
its not finished, so the problem is solved...

Although, explain some more about the mouseup event, it sounds less messy ^^
 
P

Peter Duniho

[...]
Although, explain some more about the mouseup event, it sounds less
messy ^^

Well, it turns out I don't have a great suggestion there after all.

I did a little checking on the MonthCalendar control, and found it to have
a couple of problems. One is that you don't reliably get a DateChanged
event when clicking and dragging within the control. It seems that
generally, you have to drag within the same week to get the event (and to
change the date...to be clear, it seems to be that the date itself isn't
changing...when the date does change, the event does seem to be raised).

This may be explaining why you do not always get a new message box
instance when dragging. Not all dragging causes the date to change.

It also has some pretty serious redraw issues when dragging. Yuck!

The more problematic issue is that the very first time that clicking and
dragging occurs within the control, the DateChanged event is raised before
the MouseDown event. Oops. Via the standard event-handling mechanism,
you don't find out early enough about the mouse interaction to avoid doing
anything in the event handler itself. So your only options are to bypass
the standard event handling mechanism, or to defer _everything_. I prefer
the latter (I think it's a little cleaner, and I try to avoid using the
WndProc override whenever possible), but here's an example of both, just
in case:

To bypass the event handling mechanism, you have to subclass the
MonthCalendar control, so that you have direct access to the WndProc. I
suppose if you wanted, you could expose new events driven by what's going
on in the WndProc, but given that you have to subclass anyway, I figure
it's simplest to just put everything in the subclassed control.

(sorry, C#...if I can try to read your VB code, you can try to read my C#
code :) )...

public partial class MyMonthCalendar1 : MonthCalendar
{
public MyMonthCalendar1()
{
InitializeComponent();
}

private bool _fDateChanged;
private bool _fMouseDown;

protected override void OnDateChanged(DateRangeEventArgs drevent)
{
base.OnDateChanged(drevent);
if (_fMouseDown)
{
// Only defer the display of the message box if the mouse
is captured
// (Capture is true when the control has "captured" the
mouse, in response
// to the user clicking in the control)
_fDateChanged = true;
}
else
{
// Otherwise, just go ahead and show the message box (if,
for example,
// the value changed programmatically)
_ShowMessage();
}
}

void _ShowMessage()
{
// do your actual message box here
MessageBox.Show("Here's a message");
}

const int WM_LBUTTONDOWN = 0x0201;
const int WM_LBUTTONUP = 0x0202;

protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDOWN:
_fMouseDown = true;
break;
case WM_LBUTTONUP:
if (_fDateChanged)
{
// Still bad form to put modal behavior in event
handlers, so use
// BeginInvoke to make sure that the
_ShowMessage() method isn't
// actually run until back at the main event loop
BeginInvoke(new MethodInvoker(_ShowMessage));
_fDateChanged = false;
}
_fMouseDown = false;
break;
}
base.WndProc(ref m);
}
}

Here's another alternative, which involved deferring any work until after
all of the current event processing is done (essentially, queue up a
request to show the dialog in the form's message queue, so that by the
time that request is actually executed, you've had time to observe the
mouse-down event). Note that the code may call BeginInvoke() multiple
times while clicking and dragging the mouse, but none of those should
result in the message-box being shown; only the one that's invoked from
the mouse-up handler will do that, since by the time it gets processed,
the _fMouseDown flag will have been reset:

public partial class Form1 : Form
{
private bool _fDateChanged;
private bool _fMouseDown;

public Form1()
{
InitializeComponent();
}

private void myMonthCalendar11_MouseDown(object sender,
MouseEventArgs e)
{
_fMouseDown = true;
}

private void myMonthCalendar11_MouseUp(object sender,
MouseEventArgs e)
{
if (_fDateChanged)
{
BeginInvoke(new MethodInvoker(_ShowMessage));
_fDateChanged = false;
}
_fMouseDown = false;
}

private void _ShowMessage()
{
if (!_fMouseDown)
{
MessageBox.Show("A message");
_fDateChanged = false;
}
}

private void myMonthCalendar11_DateChanged(object sender,
DateRangeEventArgs e)
{
_fDateChanged = true;

// A slight optimization here would be to only call
BeginInvoke() if
// !_fMouseDown; I suspect any performance advantage would be
minimal though
BeginInvoke(new MethodInvoker(_ShowMessage));
}
}

Hope that helps.

Pete
 
G

Guest

Well, it would probably work, but ive decided to change that part of my
program, so that i wont need to display the messagebox...

Thanks for all the help anyway :)

Peter Duniho said:
[...]
Although, explain some more about the mouseup event, it sounds less
messy ^^

Well, it turns out I don't have a great suggestion there after all.

I did a little checking on the MonthCalendar control, and found it to have
a couple of problems. One is that you don't reliably get a DateChanged
event when clicking and dragging within the control. It seems that
generally, you have to drag within the same week to get the event (and to
change the date...to be clear, it seems to be that the date itself isn't
changing...when the date does change, the event does seem to be raised).

This may be explaining why you do not always get a new message box
instance when dragging. Not all dragging causes the date to change.

It also has some pretty serious redraw issues when dragging. Yuck!

The more problematic issue is that the very first time that clicking and
dragging occurs within the control, the DateChanged event is raised before
the MouseDown event. Oops. Via the standard event-handling mechanism,
you don't find out early enough about the mouse interaction to avoid doing
anything in the event handler itself. So your only options are to bypass
the standard event handling mechanism, or to defer _everything_. I prefer
the latter (I think it's a little cleaner, and I try to avoid using the
WndProc override whenever possible), but here's an example of both, just
in case:

To bypass the event handling mechanism, you have to subclass the
MonthCalendar control, so that you have direct access to the WndProc. I
suppose if you wanted, you could expose new events driven by what's going
on in the WndProc, but given that you have to subclass anyway, I figure
it's simplest to just put everything in the subclassed control.

(sorry, C#...if I can try to read your VB code, you can try to read my C#
code :) )...

public partial class MyMonthCalendar1 : MonthCalendar
{
public MyMonthCalendar1()
{
InitializeComponent();
}

private bool _fDateChanged;
private bool _fMouseDown;

protected override void OnDateChanged(DateRangeEventArgs drevent)
{
base.OnDateChanged(drevent);
if (_fMouseDown)
{
// Only defer the display of the message box if the mouse
is captured
// (Capture is true when the control has "captured" the
mouse, in response
// to the user clicking in the control)
_fDateChanged = true;
}
else
{
// Otherwise, just go ahead and show the message box (if,
for example,
// the value changed programmatically)
_ShowMessage();
}
}

void _ShowMessage()
{
// do your actual message box here
MessageBox.Show("Here's a message");
}

const int WM_LBUTTONDOWN = 0x0201;
const int WM_LBUTTONUP = 0x0202;

protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDOWN:
_fMouseDown = true;
break;
case WM_LBUTTONUP:
if (_fDateChanged)
{
// Still bad form to put modal behavior in event
handlers, so use
// BeginInvoke to make sure that the
_ShowMessage() method isn't
// actually run until back at the main event loop
BeginInvoke(new MethodInvoker(_ShowMessage));
_fDateChanged = false;
}
_fMouseDown = false;
break;
}
base.WndProc(ref m);
}
}

Here's another alternative, which involved deferring any work until after
all of the current event processing is done (essentially, queue up a
request to show the dialog in the form's message queue, so that by the
time that request is actually executed, you've had time to observe the
mouse-down event). Note that the code may call BeginInvoke() multiple
times while clicking and dragging the mouse, but none of those should
result in the message-box being shown; only the one that's invoked from
the mouse-up handler will do that, since by the time it gets processed,
the _fMouseDown flag will have been reset:

public partial class Form1 : Form
{
private bool _fDateChanged;
private bool _fMouseDown;

public Form1()
{
InitializeComponent();
}

private void myMonthCalendar11_MouseDown(object sender,
MouseEventArgs e)
{
_fMouseDown = true;
}

private void myMonthCalendar11_MouseUp(object sender,
MouseEventArgs e)
{
if (_fDateChanged)
{
BeginInvoke(new MethodInvoker(_ShowMessage));
_fDateChanged = false;
}
_fMouseDown = false;
}

private void _ShowMessage()
{
if (!_fMouseDown)
{
MessageBox.Show("A message");
_fDateChanged = false;
}
}

private void myMonthCalendar11_DateChanged(object sender,
DateRangeEventArgs e)
{
_fDateChanged = true;

// A slight optimization here would be to only call
BeginInvoke() if
// !_fMouseDown; I suspect any performance advantage would be
minimal though
BeginInvoke(new MethodInvoker(_ShowMessage));
}
}

Hope that helps.

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top