This article discusses a framework that can be used to implement Visual Studio.NET-style Tear Off panes. The framework uses the commonly used MFC classes to achieve the functionality. This article is the third in the series.
In Part I, we developed a framework for the inserting, moving, hiding, and unhiding of panes using the public methods of a XTearOffPaneManager class. In Part II, we extended the class to add support for tear off tabs. In this article, let us focus on how we can implement the autohide feature for the tabs.
- Auto hide enabling of tabs.
- Notification of events.
- Bug fixes and enhancements.
We'll be devoting time on the first one here.
Refer to Figure 2. The tab marked in red is a tab that can be set to autohide. Usually, this is realized by clicking on the pin when the tab is in the docked state. On clicking, the tab (and all the tabs in that pane) will be hidden and shown only on demand when the mouse hovers on the tabs in the dockbar, as shown in Figure 3. The following are the requirements:
- Ability for the pane to provide the user the capability to autohide the tabs through the User interface.
- Ability for the frame window to show the autohidden tabs in a tab control docked to one of the frame's edge. This should not interfere with the functioning of the control bars in the frame such as statusbar, toolbar, and so forth.
- Ability to slide out/slide in the tabs individually when clicked with mouse or on hovering mouse over a tab for a certain period of time.
- Ability to un-autohide all tabs when the pin is pushed in the autohide state.
Let us look at the requirements one by one...
Ability for the pane to provide the user the capability to autohide the tabs through the User interface
This one is the simplest of all the requirements. The XTearOffPaneView class (which is the class which hosts tabs) already implements its own NCPAINTing (non-client painting) for drawing the caption and the close button. Hence, we just need to add some more spice into this. For this, we introduce some new styles, one of them being TPVS_DISABLE_AUTOHIDE. This is an extended style that can be set by using SetExtendedStyle method. If this style is set, the pane will not show any autohide pin button (floating panes do not set this style). The XTearOffPaneView class also traps NCMOUSE move messages to send a custom hittest code of XTEAROFF_HTAUTOHIDE when the mouse is on the pin. This will be needed for performing any action when the pin is pushed.
Ability for the frame window to show the autohidden tabs in a tab control docked to one of the frame's edge
This is slightly tricky. We somehow need to make these tabs appear docked to one of the edges. To do this, we rely on MFC's control bar handling and docking. First, we introduce a new CFrameWnd derived class, called XTearOffFrameWnd. This will form the base class for the application's main frame window. This class creates four dockbars docked to each edge of the frame. The dockbars are of the XTearOffDockBar class. This class creates the tab control and has methods to add tabs, remove tabs, and trap notifications from the tab control for sliding out or in tabs. The frame class has one method, EnableAutohide, that can be used to enable autohide on the frame's edges.
Ability to slide out/slide in the tabs individually
In addition to creating the dockbars for each edge, the XTearOffFrameWnd class also creates one frame window each for each edge. This frame window is of the XTearOffAutoHideFrame class. This class holds all the logic for sliding in, sliding out, and displaying the tab. The XTearOffAutoHideFrame class is pretty simple. It hosts a XTearOffPaneView object. When there is a request to insert a tab to an edge, the autohideframe class inserts the tab to the paneview object. Now, the XTearOffPaneView class has an extended style called TPVS_HIDE_TABS. If set, the tab windows will be shown, but not the tab control. XTearOffAutoHideFrame makes use of this style.
Ability to un-autohide all tabs when the pin is pushed in the autohide state
The design used for achieving this is like this: a tab can be hidden or not hidden. When I say hidden, it means that it is not always visible. In other words, it has no visiblity in the UI at all or it is visible but as an autohide one; it's visible only on demand. When it is not hidden, it is actually seen all the time, either in the main frame as a part of a pane or in a floating frame window. Having said this, when a pin button is pushed on a tab that is in a not-hidden state, we do two things. First, we add the tab to a hidden tab array in panemanager. Additionally, we ask the frame to insert a tab. The frame will then add it to the dockbar (which in turn adds a tab) and also to the autohide frame. When the pin is pushed on a autohide tab, the reverse has to be done. The tab has to be removed from the dockbar and autohide frame, and then the panemanager is instructed to show the tab. The pane manager, using the tab ID and pane ID info will then show it in the appropriate pane window.
How It Works
When the pin button is pushed on a visible, docked tab, the steps followed are:
- the XTearOffPaneView class traps the WM_NCLBUTTONDOWN and checks for the hittest code. If it is XTEAROFF_HTAUTOHIDE, it does the following. It loops through all the tabs, and for each tab, it calls the panemanager's method InsertTabToHiddenPool with bAutoHide set to TRUE. What the panemanager does is this: It first adds the tab to the hidden tab array and if the bAutoHide is TRUE, it gets the align flags for the pane and asks the main frame window to insert the tab. A point to be noted is the align flags. We know that a tab is contained within a pane. Now, when the tab is made auto hidden, we need to know to which edge of the frame to add it to: left, top, right, or bottom. The GetAlignFlagsForPane method of XTearOffPaneManager class helps decide this.
- When the main frame is asked to insert an item, it does two things. It adds it to the corresponding edge's dockbar and also to the corresponding edge's autohideframe window.
- The autohide frame window then adds it to its internal XTearOffPaneView object.
- The XTearOffPaneView, after auto-hiding all the tabs, will then request the panemanager to show the tab ID that was active when the autohide pin was pressed.
- XTearOffPaneManager's ShowTab method will ask the main frame window to show the tab.
- The frame window will loop through all its dockbars and check which of them hosts this tab ID. Once found, it asks the corresponding frame window to show the tab.
- The autohideframe window will then start a timer. In its timer function it implements the logic for sliding in and out the tab window.
When the pin button is pushed on a autohide frame, the steps followed are:
- the XTearOffPaneView class traps the WM_NCLBUTTONDOWN and checks for the hittest code. If it is XTEAROFF_HTAUTOHIDE, it does the following: It loops through all the tabs, and for each tab, it calls the panemanager's method InsertTabToHiddenPool with bAutoHide set to FALSE. What the panemanager does is this: It first adds the tab to the hidden tab array and if the bAutoHide is FALSE, and then asks the frame to remove the tab.
- When the main frame is asked to remove an item, it instructs the dockbar to remove it.
- The XTearOffPaneView also instructs the panemanager to ShowTab for each tab. Thus, in effect, the tabs will be added to whichever panes they came from.
About the Included Demo
I have included a demo application demonstrating the use of the methods exposed by XTearOffPaneManager class and the implementation of autohide capabilities. It is an SDI application. In addition to the initial layout, you can add your own panes, show/hide them, and execute each of the XTearOffPaneManager's methods by using the Pane Manager -> Manage panes menu. A dialog is presented with a list of three methods and an execute button for each. On pressing execute, the corresponding method is called with the parameters shown in the GUI. This can be used as a unit test tool. The code PaneManipulationPage class will show a typical way of calling these APIs.
In the next article, I wish to address some of these issues:
- Notification of events.
- Currently, the framewnd class can be used only if you use CFrameWnd as the baseclass. I'd like to extend it to make the framework usable with CMDIFrameWnd and CMDIChildWnd classes also.
- Allow grouping of autohide tabs; in other words, when an tab is pinned, only the tabs in the group will be un-autohidden and not the rest.
For the sake of documenting, here are some bugs/enhancements that I have noted down, but haven't attempted to address, just because of time contraints and an eagerness to have the core functionality up and running.
- Problem with floating panes. In some cases, when there are multiple floating frame windows, a floating frame window doesn't come to the top when clicked and activated.
- At times, when a pane is dragged and dropped, the resulting pane size is kind of constricted.
- Currently, it is not possible to make a non-tab hosting pane floating.
- When a framewindow is no longer needed—for example, all panes have been moved to some other frame—the window is still lying around. Need to optimize this.
- During the drag operation, the drag display is not exactly WYSIWYG when the drag is happening over the caption bar of a tab hosting pane.
- It is not possible to drag and drop a tab within the same pane, in effect, repositioning the tab within the same pane.
- Panes with tabs appearing at the top, basically a XTearOffPaneView derivative with a tab control at top rather than in the bottom.
- Use a registered message instead of a (WM_USER + x) message I use right now for WM_CLOSE_FRAME.