Visual Studio.NET-Style Tear Off Panes - Part III

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.

Contents

Introduction

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.

Figure 1.

Goals

As has been our custom, we shall now look at what goals we set for ourselves in the end of Part II.

  • Auto hide enabling of tabs.
  • Notification of events.
  • Bug fixes and enhancements.

We'll be devoting time on the first one here.

Autohide Tabs

Figure 2.

Figure 3.

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.

Design

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.

What's Next

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.


Downloads

Comments

  • Great work. One doubt...

    Posted by fermisoft on 09/28/2006 05:41am

    Hi, First of all my sincere appreciations and Thanks to your great work. Please find time to answer my doubt. The Close and Auto-Hide controls on the Pane view are actually not controls, but some drawings on the NC area. I would like to extend this to show a focus on those two buttons, on mouse hover. In addition, I prefer to handle the commands on button click rather than mouse button down. So, I beleive it is better to implement them as flat buttons instead of drawings. What is your opinion? Regards, Karthik karthik.murugan@hp.com

    • OK

      Posted by fermisoft on 10/06/2006 05:10am

      You are right. Thanks for your time.

      Reply
    • Thanks.

      Posted by kirants on 09/28/2006 11:50pm

      Thanks for your nice words. As for your question, you sure can do that. Only thing you want to ensure is that the buttons do not have WS_TABSTOP style. Basically, I don't think you would want the buttons to receive keyboard focus, if I am right.

      Reply
    Reply
  • Flicker problem?

    Posted by LucianoLiu on 04/20/2006 09:11am

    When I set the Pin to auto hide the tabs,if my mouse moves on a tab,it will slide out.But the tab slide out flickeringly.How to solve this problem? While in Visual Studio.NET,the tabs slide out smoothly. What's the reason?

    Reply
  • Doubt with rootpane....

    Posted by B4rret on 02/02/2005 01:16pm

    Hello
    
    I am trying to use this framework and in my first tests I4ve seen that when a only pane is inserted in the rootPane, only  fills half rootpane.
    The other half is like "empty"... 
    Later, when I insert a second pane in the rootpane, I thought that this new pane will fill the rest of the rootpane, but not. Again it fills only the half of the empty space.
    By example 
    
    XTearOffPaneView* pRootPane = (XTearOffPaneView*)m_oPaneManager.InsertRootPane(GetActiveView(),1);
    pRootPane->SetPaneManager(&m_oPaneManager);
    CEditView * v1 = (CEditView*)m_oPaneManager.InsertPane(1,XTearOffInsertAtTop,RUNTIME_CLASS(CEditView),2);
    GetActiveDocument()->AddView(v1);
    CEditView * v2 = (CEditView*)m_oPaneManager.InsertPane(1,XTearOffInsertAtBottom,RUNTIME_CLASS(CEditView),3);
    GetActiveDocument()->AddView(v2);
    
    I thought that this code create a window like this:
    -----------
    |   v1    |
    -----------
    |   v2    |
    -----------
    
    
    but really it creates:
    
    -----------
    |   v1    |
    -----------
    |  empty  |
    -----------
    |   v2    |
    -----------
    
    It is like the rootpane don4t realize that it haves a pane within... 
    Is this behavior correct?  
    Wouldn4t be more logic that the first pane inserted, fills the complete rootpane?
    
    Thanks in advance and sorry for the english ^_^U

    • Per design..

      Posted by kirants on 02/02/2005 01:24pm

      Thanks for posting your concern here.. Let me now explain..
      
      This is per design. Note that the grey area you see is the root pane , and as the name implies is a PANE itself. So, you have 1 pane when you do a InsertRootPane(). When you do 2 other calls to InsertPane, you get 2 more. So , you end up with 3 total. 
      
      I understand it is pretty tricky and confusing at times.. 
      
      From what you want. It seems like , you need to do this:
      1. pRootPane = InsertRootPane(). 
      2. InsertTab to the root pane as pRootPane->InsertTab passing RUNTIMECLASS of CEditView
      3. InsertPane to top or bottom of root pane.. 
      
      Does that help ?
      
      Also, there is a Part IV here if you may want to see .. 
      
      Part IV
      

      Reply
    Reply
  • Perfect ...

    Posted by NoHero on 12/28/2004 02:14pm

    Very good, I haven't found anything better yet. :thumb:

    Reply
  • Just what I needed

    Posted by alanjhd on 10/27/2004 08:22am

    Thanks a lot, just what I was looking for.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • CentreCorp is a fully integrated and diversified property management and real estate service company, specializing in the "shopping center" segment, and is one of the premier retail service providers in North America. Company executives travel a great deal, carrying a number of traveling laptops with critical current business data, and no easy way to back up to the network outside the office. Read this case study to learn how CentreCorp implemented a suite of business continuity services that included …

Most Popular Programming Stories

More for Developers

RSS Feeds