Introduction
I had a TabControl
holding User
details, and wanted a way to provide edit facilities. I'd already added a "Add New User" tab, but I didn't want to clutter the interface with an "Edit" button - it's not a frequently used facility, so it would waste useful space. So I thought that a double click on the tab would be a good way to "hide" it in plain sight.
The problem is that the Tab isn't part of the TabPage
:
The red outline above shows the TabPage
area, which passes DoubleClick
, etc. to the TabPage
control.
The red outline above shows the Tab
itself, which is not part of the TabPage
and does not pass clicks of any type to the TabPage
control.
Background
Double click on a tab doesn't propagate to the tab page under normal circumstances, because the tab is not a part of the TabPage
- it's a part of the TabControl
which contains all the pages. This isn't unsurmountable - you could derive a MyTabPage
class from TabP
age, add an event and a mechanism to signal it which is available to the outside world - but that's rather nasty and doesn't fit the event model too well at all.
What I wanted was a way to signal an existing event within an existing .NET class from outside: DoubleClick
.
Fortunately, it's not too difficult - you can do it with Reflection.
Using the Code
Handle the TabControl MouseDoubleClick
event (because you need the MouseEventArgs
to get the click position) and find which tab is being clicked, and trigger the appropriate instance event.
private void tabWith_MouseDoubleClick(object sender, MouseEventArgs e)
{
TabControl tc = sender as TabControl;
if (tc != null)
{
for (int i = 0; i < tc.TabCount; ++i)
{
if (tc.GetTabRect(i).Contains(e.Location))
{
TabPage tp = tc.TabPages[i];
MethodInfo onDoubleClick = tp.GetType().GetMethod
("OnDoubleClick", BindingFlags.NonPublic | BindingFlags.Instance);
onDoubleClick.Invoke(tp, new object[] { null });
break;
}
}
}
}
Loop through all tab pages, and check if the double click occurred within its tab area.
When you find it, get the appropriate TabPage
, and use that via Reflection to get the method that triggers DoubleClick
within the class. Reflection allows you to "play" with private
methods...
Use Invoke to signal the event so that the normal handler is called.
You could do this with a fixed class, rather than the actual instance:
MethodInfo onDoubleClick = typeof(TabPage).GetMethod("OnDoubleClick",
BindingFlags.NonPublic | BindingFlags.Instance);
But... I'd rather use the actual instance in case it's a derived class which has "masked" the method.
Notice that I'm handing a null
as the EventArgs
parameter to the handler when I "force" the event:
onDoubleClick.Invoke(tp, new object[] { null });
This allows the handler to detect the difference between a "real" body-of-the-page double click and a "tab" double click without having to set an invalid location. (The Tab itself is not a part of the "body" of the tab page, it's part of the TabControl
that holds all TabPages
, so the coordinates of the actual click would be misleading if passed through.)
If you want to pass a dummy location or similar instead, create a new MouseEventArgs
and pass that through instead of null
:
MouseEventArgs clickEventArgs =
new MouseEventArgs(e.Button, e.Clicks, int.MinValue, int.MinValue, e.Delta);
onDoubleClick.Invoke(tp, new object[] { clickEventArgs });
Also note that the TabPage DoubleClick
event handler expects an EventArgs
, not a MouseEventArgs
, so to access any dummy location, you will have to cast the argument in the handler.
Other Information
The download shows it working, and adds some "tracing" statements so you can tell what has happened.
The same technique will work for middle button "click to close", tab right click, and so on - the world is the mollusc of your choice!
History
- 2015-11-22 First release version