Keeping track of 'Active' tabpage with multiple TabControls
Hi,
I am building a sort of 'lightweight' Visual Studio, and I'm trying to implement the feature where you can drag tabs to different 'tab groups', viewing multiple tabs side by side.
I got nearly everything working, I show a ContextMenuStrip when you rightclick the tab headers, with the option of creating a new (horizontal or vertical) Tab Group. When that happens, a new TabControl is added dynamically and the selected tab is moved there. You can drag the tabs around inside a TabControl and even from one to another.
http://i36.tinypic.com/efexs4.png
The problem now is keeping track of the 'active' tab. Since I have multiple TabControls (not just two, there can be as many as you want theoretically), their SelectedTab properties are basically useless. How am I to determine which TabControl the user is currently working in? Obviously I will always have multiple selected tabs, but only one active tab, in which the user is currently typing, editing, whatever.
I need to know the active tab for obvious reasons: many menu and toolbar items act on the active tab for example.
A short explanation of my controls:
I have one TabGroupContainer control, which inherits UserControl.
I have a TabGroup control, which inherits TabControl.
I have a Tab control, which inherits TabPage.
The TabGroupContainer holds a collection of TabGroups (to which I dynamically add/remove TabGroups when required).
Now I want the TabGroupContainer control to have the ActiveTab property which returns the one and only active tab (out of possibly many selected tabs).
The only logical way is to keep track of the tab that was selected last, for every TabGroup.
At the moment, I am doing this:
- In the TabGroup (TabControl) class, I shadow the SelectedTab property (and return a Tab instead of a TabPage). More importantly, in the property setter, I raise a SelectedTabChanged event:
vb.net Code:
Public Shadows Property SelectedTab As Tab
Get
Return DirectCast(MyBase.SelectedTab, Tab)
End Get
Set(ByVal value As Tab)
If MyBase.SelectedTab IsNot value Then
MyBase.SelectedTab = value
Me.OnSelectedTabChanged(EventArgs.Empty) 'raises event
End If
End Set
End Property
- In the TabGroupContainer, when a new TabGroup is added, I use AddHandler twice to attach both the SelectedTabChanged and SelectedIndexChanged events to one method:
vb.net Code:
Public Function AddGroup() As TabGroup
Dim g As New TabGroup(Me, Me.TabPageType)
AddHandler g.SelectedIndexChanged, AddressOf TabGroup_SelectedTabPageChanged
AddHandler g.SelectedTabChanged, AddressOf TabGroup_SelectedTabPageChanged
Me.TabGroups.Add(g)
Me.LayoutGroups()
Return g
End Function
Private Sub TabGroup_SelectedTabPageChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim g = DirectCast(sender, TabGroup)
If g.SelectedTab IsNot Nothing Then Me.ActiveTab = DirectCast(g.SelectedTab, Tab)
End Sub
So this is where I set the ActiveTab property.
- In the ActiveTab property setter, I raise a custom ActiveTabChanged event, which I handle in the main form to update the form title.
Now, this seems to work as long as there are multiple tabs in a TabGroup. I can select different tabs, and even if there are multiple TabGroups, the active tab is always correct.
However, as soon as there is a TabGroup with just one Tab in it, then I cannot seem to make that tab active. I cannot seem to select it whatever I do. I can click on the tab header, but that doesn't seem to fire the SelectedIndexChanged event (and it shouldn't because there is only one tab, hence the selected index did not change), so my active tab isn't changed.
When I click in the text editor the selected tab isn't changed either.
This last problem can be overcome by a somewhat 'dirty' method: enter the Enter event of the text editor, and set the SelectedTab of the parent TabGroup to the current tab.
So, in the Tab class (which contains the text editor), I do this:
vb.net Code:
Private Sub TextEditor_Enter(ByVal sender As Object, ByVal e As EventArgs)
Dim g = DirectCast(Me.Parent, TabGroupLibrary.TabGroup)
g.SelectedTab = Me
End Sub
However, it still doesn't work. I soon figured out why: I am checking that the previously SelectedTab is not the same as the new value in the property setter of the (shadowed) SelectedTab property. I don't raise the event when they were the same, and in this case they were the same (as there is only one tab in the TabGroup!).
So, I took that check out, and now it seems to work when I click in the text editor.
So one problem remains: the active tab is not changed when you click on the tab header (and not on the text editor). I am clueless as to how I should handle this... I am once again sure there is a simple fix, but I cannot see it. I can click the tab header, and I'm sure behind the hood it responds to this click, but it never raises it SelectedIndexChanged event because the index didn't change but was set to the same value it already had.
I cannot override the SelectedIndex property (so I could skip this check and raise the event anyway even if the value didn't change), and the OnSelectedIndexChanged method is not called, so I cannot use that either...
Help?:ehh:
Re: Keeping track of 'Active' tabpage with multiple TabControls
Maybe you could simply use something like this in the SelectedTab getter:
Code:
Return DirectCast(TabGroupContainer1.ActiveControl, TabGroup).SelectedTab
Re: Keeping track of 'Active' tabpage with multiple TabControls
Hm, at first I thought that was the holy grail to my problem (lol), but I'm not sure if it will work. What does the ActiveControl return when I am doing something completely different, like navigating a menu, or if I'm busy in a different form (Find & Replace form?). If TabGroupContainer1.ActiveControl returns the TabGroup that was active last, that would probably work, but I'm not sure if it does. I know I could test this in a jiffy when I have time, but perhaps this is something to think about?
Re: Keeping track of 'Active' tabpage with multiple TabControls
Well, if the TabGroupContainer only contains TabGroups then the ActiveControl of it can't be anything but a TabGroup regardless if something else on the Form actually has the input focus at the time, or if the input focus is in another Form. So yes, it should work.
Re: Keeping track of 'Active' tabpage with multiple TabControls
So, I had a look at this, but it doesn't seem to work :(
Two reasons I found so far:
If I select the text editor (like, say I'm typing in it) and then try to retrieve the ActiveTab using your code, then the ActiveControl (of the TabGroupContainer!!!) is the TextEditor... Even though that control isn't even on the TabGroupContainer but on one of its tabs.
I suppose I could check if the type of the active control is a text editor, and if it is I take its parent instead, but what if I ever add some more controls? It would get really dirty very fast...
Reason 2: I can't find any way to notify the main form that the active tab has changed. As far as I know there isn't any 'ActiveControlChanged' event, so while I can retrieve the active control whenever I want, I cannot react as soon as it changes. Unless I run a timer which checks if the control has changed every few milliseconds (busy waiting), but I'm definitely not doing that.
Re: Keeping track of 'Active' tabpage with multiple TabControls
Using the method already suggested, you can check if the type is your tabpage, if not, check its parent, is that a tab page, if not check its... you get the idea. It would not matter what type you are currently on, only that you keep checking parents till you either find the tabpage you need or run out of parents!
Re: Keeping track of 'Active' tabpage with multiple TabControls
Well yeah but I don't really like that approach. And that doesn't solve my second issue. :(