Mouse capture for custom context or drop-down menus

F

Fardreamer

Hello,

I'm implementing an undo/redo drop-down menu similar to the undo/redo
menus in VS.NET 2003. The idea is that when the drop-down arrow is
pressed, a floating ListBox control appears under the pressed button,
and disappears when the user chooses an action to undo or clicks
anywhere else in the form - meaning that when the ListBox is open, I
need to handle every MouseDown event on the form.

I tried attaching an event handler to the MouseDown event of the
ListBox and then setting ListBox.Capture = true, but unfortunately that
didn't do the trick (why I couldn't figure out). Mouse events were
raised for every control on the form that handles them, and my
ListBox.MouseDown handler was only called when the mouse was over the
list box - i.e., capture wasn't working. Next I tried P/invoking
SetCapture and ReleaseCapture, but with the same result (unless the
mouse button was still pressed, in which case ALL system mouse events
were captured - far from ideal).

After lots of searching I found a message on this group saying that
only in a MouseLeave event handler will Control.Capture = true actually
make any difference - that turned out to be true and I finally got
capture to work. The only problem was that since the ListBox first
appears when the cursor is still on the drop-down button, capture
wasn't set until the cursor was moved over and out of the ListBox.

I finally solved it by some ugly cursor position fiddling. I
(programmatically) move the cursor position over the ListBox and then
move it back. I'm sharing this here in case someone gets stuck on a
similar problem. Also, if anyone has a better solution I'd love to see
it.


// Called from the toolbar's ButtonDropDown event handler
private void OpenHistoryList(ToolBarButton button)
{
// Get the entry list associated with this history button
HistoryEntryList entryList = (HistoryEntryList) button.Tag;

// Add the list to the form
ListBox listbox = new ListBox();
this.Controls.Add(listbox);

// Retrieve sizes from the configuration file
int width =
int.Parse(ConfigurationSettings.AppSettings["History.Width"]);
int height =
int.Parse(ConfigurationSettings.AppSettings["History.Height"]);

// Set the list coordinates
listbox.Location = new Point(button.Rectangle.X, button.Rectangle.Y +
button.Rectangle.Height);
listbox.Width = width;
listbox.Height = height;

// Add the action descriptions to the list
foreach(HistoryEntry entry in entryList)
{
listbox.Items.Add(entry.Description);
}

listbox.BringToFront();
listbox.Focus();
listbox.MouseDown += new MouseEventHandler(CloseHistoryList);
listbox.MouseLeave += new EventHandler(CaptureHistoryList);

// Fiddle with mouse position to generate a MouseLeave event
automatically
Point currentPos = Cursor.Position;
Rectangle listCoords =
listbox.RectangleToScreen(listbox.ClientRectangle);
Cursor.Position = new Point(listCoords.Left+1, listCoords.Top+1);
Application.DoEvents();
Cursor.Position = currentPos;
}

private void CloseHistoryList(object sender, MouseEventArgs e)
{
// Get the listbox's coordinates
ListBox listbox = (ListBox) sender;
Rectangle listCoords =
listbox.RectangleToScreen(listbox.ClientRectangle);

// Check whether the click was within the listbox's bounds
if (
e.X < listCoords.X || e.X > listCoords.X + listCoords.Width ||
e.Y< listCoords.Y || e.Y > listCoords.Y + listCoords.Height
)
{
// Remove the listbox from the form, release mouse capture and
dispose of the listbox
this.Controls.Remove(listbox);
listbox.Capture = false;
listbox.Dispose();
}

}

private void CaptureHistoryList(object sender, EventArgs e)
{
ListBox listbox = (ListBox) sender;
listbox.Capture = true;
}

Hope that's useful

-Doron
 
G

Guest

Hi,

I'm curious why a regular ComboBox wasn't up to scratch in this particular
situation?

You could consider using a mouse hook if you're not put off by a bit of
p/invoking. That has worked well for us in similar circumstances. I can put
together a small code sample if you're interested.

Regards,
Matt Garven

Fardreamer said:
Hello,

I'm implementing an undo/redo drop-down menu similar to the undo/redo
menus in VS.NET 2003. The idea is that when the drop-down arrow is
pressed, a floating ListBox control appears under the pressed button,
and disappears when the user chooses an action to undo or clicks
anywhere else in the form - meaning that when the ListBox is open, I
need to handle every MouseDown event on the form.

I tried attaching an event handler to the MouseDown event of the
ListBox and then setting ListBox.Capture = true, but unfortunately that
didn't do the trick (why I couldn't figure out). Mouse events were
raised for every control on the form that handles them, and my
ListBox.MouseDown handler was only called when the mouse was over the
list box - i.e., capture wasn't working. Next I tried P/invoking
SetCapture and ReleaseCapture, but with the same result (unless the
mouse button was still pressed, in which case ALL system mouse events
were captured - far from ideal).

After lots of searching I found a message on this group saying that
only in a MouseLeave event handler will Control.Capture = true actually
make any difference - that turned out to be true and I finally got
capture to work. The only problem was that since the ListBox first
appears when the cursor is still on the drop-down button, capture
wasn't set until the cursor was moved over and out of the ListBox.

I finally solved it by some ugly cursor position fiddling. I
(programmatically) move the cursor position over the ListBox and then
move it back. I'm sharing this here in case someone gets stuck on a
similar problem. Also, if anyone has a better solution I'd love to see
it.


// Called from the toolbar's ButtonDropDown event handler
private void OpenHistoryList(ToolBarButton button)
{
// Get the entry list associated with this history button
HistoryEntryList entryList = (HistoryEntryList) button.Tag;

// Add the list to the form
ListBox listbox = new ListBox();
this.Controls.Add(listbox);

// Retrieve sizes from the configuration file
int width =
int.Parse(ConfigurationSettings.AppSettings["History.Width"]);
int height =
int.Parse(ConfigurationSettings.AppSettings["History.Height"]);

// Set the list coordinates
listbox.Location = new Point(button.Rectangle.X, button.Rectangle.Y +
button.Rectangle.Height);
listbox.Width = width;
listbox.Height = height;

// Add the action descriptions to the list
foreach(HistoryEntry entry in entryList)
{
listbox.Items.Add(entry.Description);
}

listbox.BringToFront();
listbox.Focus();
listbox.MouseDown += new MouseEventHandler(CloseHistoryList);
listbox.MouseLeave += new EventHandler(CaptureHistoryList);

// Fiddle with mouse position to generate a MouseLeave event
automatically
Point currentPos = Cursor.Position;
Rectangle listCoords =
listbox.RectangleToScreen(listbox.ClientRectangle);
Cursor.Position = new Point(listCoords.Left+1, listCoords.Top+1);
Application.DoEvents();
Cursor.Position = currentPos;
}

private void CloseHistoryList(object sender, MouseEventArgs e)
{
// Get the listbox's coordinates
ListBox listbox = (ListBox) sender;
Rectangle listCoords =
listbox.RectangleToScreen(listbox.ClientRectangle);

// Check whether the click was within the listbox's bounds
if (
e.X < listCoords.X || e.X > listCoords.X + listCoords.Width ||
e.Y< listCoords.Y || e.Y > listCoords.Y + listCoords.Height
)
{
// Remove the listbox from the form, release mouse capture and
dispose of the listbox
this.Controls.Remove(listbox);
listbox.Capture = false;
listbox.Dispose();
}

}

private void CaptureHistoryList(object sender, EventArgs e)
{
ListBox listbox = (ListBox) sender;
listbox.Capture = true;
}

Hope that's useful

-Doron
 
F

Fardreamer

Hi Matt, thanks for your reply.

I'm not aware of any functionality that allows you to invoke only the
drop-down part of a ComboBox... did I miss something? Also, I have no
idea what a mouse hook is, but I'd be interested to see a code sample.

BTW, the condition in CloseHistoryList is incorrect. The conversion to
screen coordinates is unnecessary. The correct expression is

e.X < 0 || e.X > listbox.Width || e.Y< 0 || e.Y > listbox.Height
 
G

Guest

Right, I didn't realise you wanted just the drop down part.

Information on Windows Hooks are available here (specifically on Mouse Hooks
which is what you want):
http://support.microsoft.com/default.aspx?scid=kb;EN-US;318804

You can intercept all mouse events before they're passed to your
application. What the article doesn't tell you is that returning 1 from the
MouseHookProc method in the sample will prevent the message being passed on
to your application.

Let me know if I can clarify anything further or if you need assistance
adapting the sample to your application. Hope this helps.

Regards,
Matt Garven
 
F

Fardreamer

Thanks Matt, that looks like a perfect solution.I'm surprised .NET
doesn't supply this basic functionality out-of-the-box, considering it
has such robust user control abilities. I haven't tried it yet though,
but judging by the example it seems pretty straightforward. I'll post
here if it works out for me (or if if I have any more questions).

Thanks again.
 

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