An Outlook98 bar-like control

Download demo and code 142K

outbar.gif (7K)

This is another control which trys to reproduce an Outlook98-like bar. It try to have a more near look and fell with the original outlook bar, and support Folders, editing of items and folder text and small/large icon view modes.
I've written a small derived CSplitterWnd, named CGfxSplitterWnd, which tries to emulate the Outlook one - that means the different highlight of dragged splitterbar and the larger upper border. I won't discuss the CGfxSplitterWnd class here, beside saying that you can use it to sobstitute the normal CSplitterWnd "as is", that the upper border is sizeable with the variable m_upBorder (default 8) and the highlight line drawed at top is setted/removed by the bWhiteLine variable.

There are other articles here at codeguru explaining how to manage multiple views and things like that; here I will only explain how to use this control.

What's new ?

Since last release, I add the fSelHighlight (available in ModifyFlag and in Create) - which enable a dimmed highlight of the last pressed item - and the SetAnimSelHighlight function - which lets you enable a simple icon animation for the past pressed item. I also added a NM_FOLDERCHANGE notification for folder change.

1. The Outbar control

This control has not be derived from listbox or listctrl or any views, but from a generic CWnd object.
It supports the strange scoll bars used by outlook (I mean, only the arrows without the scrollbar), the cool highlight of items and folders, the dragging of items, the local editing of subitems and folders (thanks to Mario Contestabile and Zafir articles about this local editing and list control subitem editing).
It can be handled as any other standard control, even in dialogs.

2. Creating the outbar control

In the demo, I created an instance for the CGfxOutBarCtrl control in the mainframe header:
#include "GfxOutBarCtrl.h"
#include "GfxSplitterWnd.h"

class CMainFrame : public CFrameWnd
{
public:
	CGfxSplitterWnd	wndSplitter;
	CGfxOutBarCtrl	wndBar;

	CImageList		imaLarge, imaSmall;

As I told above, the CGfxSplitterWnd is a simply derived CSplitterWnd object used to have a closer look to outlook one; you can safely use a standard CSplitterWnd instead.
Then, in the OnCreateClient, I created the splitter and the outbar control:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) 
{
	if (!wndSplitter.CreateStatic(this, 1, 2)) return false;
	// Creation of the standard view
	if (!wndSplitter.CreateView(0, 1, pContext->m_pNewViewClass, CSize(0,0), pContext)) return false;

	// Here we create the outbar control; we give as id the parent splitter pane id here
	wndBar.Create(WS_CHILD|WS_VISIBLE, CRect(0,0,0,0), &wndSplitter, wndSplitter.IdFromRowCol(0, 0));
	// Tell the control to send message to this window (the mainframe) and not to its real parent (the splitter)
	wndBar.SetOwner(this);

	// Here we create the imagelists for the control
	imaLarge.Create(IDB_IMAGELIST, 32, 0, RGB(128,128,128));
	imaSmall.Create(IDB_SMALL_IMAGELIST, 16, 0, RGB(0,128,128));

	// and we link them to the control
	wndBar.SetImageList(&imaLarge, CGfxOutBarCtrl::fLargeIcon);
	wndBar.SetImageList(&imaSmall, CGfxOutBarCtrl::fSmallIcon);
	// Look at function reference for information about linking image list

	// Here we can add the folders to the control; we need at least one folder.
	// The numbers aside the text are an "lParam" value we can assign to each folder
	wndBar.AddFolder("Folder 1", 0);
	wndBar.AddFolder("Folder 2", 1);
	wndBar.AddFolder("Folder 3", 2);

	// Here we insert the items; syntax is folder, index, text, image, lParam value for item
	wndBar.InsertItem(0, 0, "Item 1", 0, 0);
	wndBar.InsertItem(0, 1, "Item 2", 1, 0);
	wndBar.InsertItem(0, 2, "Item 3", 2, 0);

	wndBar.InsertItem(1, 0, "Item 1", 0, 0);
	wndBar.InsertItem(1, 1, "A long description item which will probably be multilined unless you have a monstrous screen resolution", 1, 0);
	wndBar.InsertItem(1, 2, "Item 3", 2, 0);
	wndBar.InsertItem(1, 3, "Item 4", 3, 0);

	// Standard sizing for splitter
	CRect r;
	GetClientRect(&r);
	int w1 = r.Width()/5;
	int w2 = r.Width()/4;
	wndSplitter.SetColumnInfo( 0, w1, 0 );
	wndSplitter.SetColumnInfo( 1, w2, 0 );
	wndSplitter.RecalcLayout();

	return true;
}
The function used for creating the outbar control is this:
BOOL Create(DWORD dwStyle, const RECT& rect, CWnd * pParentWnd, UINT nID, const DWORD dwFlag = fDragItems|fEditGroups|fEditItems|fRemoveGroups|fRemoveItems|fAddGroups|fAnimation);
Where dwStyle, rect, pParentWnd and nID are the standard parameters, and dwFlag can have this values:
  • fSmallIcon - sets small icon mode (default is large)
  • fEditGroups - enable folders local editing (renaming)
  • fEditItems - enable items local editing (renaming)
  • fRemoveGroups - enable the "Remove" command for folders in context menu
  • fRemoveItems - enable the "Remove" command for items in context menu
  • fDragItems - enable item dragging to rearrange position
  • fAnimation - enable animation while changing folder selection

3. Handling its messages

The outbar sends to its parent (or to the window you specify with the SetOwner function - the mainframe in the example) a message, WM_OUTBAR_NOTIFY (defined in the gfxoutbarctrl.h) when user clicks an item, when end the edit of a folder or of a label and when user drags an item; this is the usual way to handle this message:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	//{{AFX_MSG_MAP(CMainFrame)
	...
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_OUTBAR_NOTIFY, OnOutbarNotify)
END_MESSAGE_MAP()

long CMainFrame::OnOutbarNotify(WPARAM wParam, LPARAM lParam)
{ switch (wParam) { case NM_FOLDERCHANGE: // cast the lParam to an integer to get the clicked folder return 0; case NM_OB_ITEMCLICK: // cast the lParam to an integer to get the clicked item return 0; case NM_OB_ONLABELENDEDIT: // cast the lParam to an OUTBAR_INFO * struct; it will contain info about the edited item // return 1 to do the change and 0 to cancel it return 1; case NM_OB_ONGROUPENDEDIT: // cast the lParam to an OUTBAR_INFO * struct; it will contain info about the edited folder // return 1 to do the change and 0 to cancel it return 1; case NM_OB_DRAGITEM: // cast the lParam to an OUTBAR_INFO * struct; it will contain info about the dragged items // return 1 to do the change and 0 to cancel it return 1; } return 0; }

4. What do I need to use the control ?

You simply need to include the "GfxOutBarCtrl.h" file in the parent window header.
The classes you need to import from the demo projects are:
  • CGfxOutBarCtrl
  • CGfxGroupEdit
  • CGfxPopupMenu
  • CGfxSplitterWnd (this only if you want it)
You also need to copy the 3 cursor resources (IDC_NODRAGGING, IDC_DRAGGING and IDC_HANDCUR).
An helper struct (OUTBAR_INFO) is defined in the CGfxOutBarCtrl files.
You'll also need to copy the two bitmap commands (for small and large icons command) from the demo toolbar resource "IDR_MAINFRAME" to your apps IDR_MAINFRAME (needed for bitmapped context menu).
You'll also need to define 4 command id:
#define ID_GFX_SMALLICON	32810
#define ID_GFX_LARGEICON	32811
#define ID_GFX_REMOVEITEM	32812
#define ID_GFX_RENAMEITEM	32813
You can use any id you wish; make sure the id of small and large icons commands are the same in the toolbar resource.

5. Bonus class: the CGfxPopupMenu, a CMenu derived for bitmapped popup menu

The outbar control uses bitmapped menu for the context menu with command to switch to small/large icon mode, editing and removing of items and folders. You can use the class for bitmapped menu in your too.
The class is CGfxPopupMenu, and is mainly derived from my CSpawnMenu class (that you can find in Menu section - owner draw menu 4); the difference is this class in CMenu derived.
Typical usage of this class is here (in response to a right mouse click, for example):
CGfxPopupMenu cMenu;
cMenu.LoadMenu(IDR_MYMENU);
cMenu.LoadToolBarResource(A_TOOLBAR_WITH_BITMAP);
cMenu.RemapMenu(&cMenu);
cMenu.EnableMenuItems(&cMenu, this);
cMenu.TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON, spt.x, spt.y, this);
cMenu.DestroyMenu();
Refer to the CSpawnMenu article for more info about this class.
Note that this class doesn't support shortcut keys. If you need this feature, use the CSpawnMenu class instead.

6. Localizing the control

The control use internally some text strings which are stored as #define and not as string resource.
You can find those defines at the beginning of the GfxOutBarCtrl.cpp file.

7. Function reference guide

Here follows a reference to the main outbar control member functions:

int AddFolder(const char * cFolderName, const DWORD exData);
Add a folder named "cFolderName" and assign the exData value to it.
int AddFolderBar(const char * pFolder, CWnd * pSon, const DWORD exData)
Add a folder with a CWnd child inside of it; you can insert a folder with a tree control (as in the sample) or things like that; "pFolder" is the name of the folder, "pSon" is a valid CWnd object pointer (the CWnd object must be created before inserting) and "exData" is the lParam of the folder.
BOOL Create(DWORD dwStyle, const RECT& rect, CWnd * pParentWnd, UINT nID, const DWORD dwFlag = fDragItems|fEditGroups|fEditItems|fRemoveGroups|fRemoveItems|fAddGroups|fAnimation);
Create the control; dwStyle is usually WS_CHILD|WS_VISIBLE, nId is the identifier of the control and dwFlag are the flags as described in the ModifyFlag function.
long GetAnimationTickCount()
Returns the current animation tick count.
DWORD GetFlag() const;
Return the currently setted flags.
CWnd * GetFolderChild(int iFolder)
Retrieve, if any, the CWnd object of the iFolder object; if iFolder is -1, the child of the currently selected folder is returned. If no CWnd object is linked with the folder, NULL is returned.
int GetFolderCount() const;
Return the total count of folders.
DWORD GetFolderData(int iFolder = -1)
Retrieve the lParam of the iFolder object; if iFolder is -1, the lParam of the currently selected folder is returned.
CImageList * GetFolderImageList(const int index, const bool bSmall) const;
Gets a pointer to the imagelist linked to the "index" folder; if bSmall is true, the small icon image list is returned, else the large one.
CImageList * GetImageList(CImageList * pImageList, int nImageList);
Gets the global image list; if nImageList is "CGfxOutBarCtrl::fSmallIcon", the small icon imagelist is returned; if nImageList is "CGfxOutBarCtrl::fLargeIcon", the large one is returned.
int GetItemCount() const;
Returns the item count for the currently selected folder.
DWORD GetItemData(const int index) const;
Gets the lParam value of the "index" item of the currently selected folder.
int GetItemImage(const int index) const;
Gets the image index in the imagelist for the "index" item of the currently selected folder.
CString GetItemText(const int index);
Return the text of the "index" item in the currently selected folder.
int GetSelFolder() const;
Return the currently selected folder.
int InsertItem(const int folder, const int index, const char * text, const int image, const DWORD exData = 0);
Insert an item at "index" position in the "folder" folder; "text" is the text of item (don't set it to null, don't use LPSTR_CALLBACK), "image" is the image index in the imagelist, exData is a value you can assign to the item (you can set and retrieve it using the GetItemData / SetItemData functions).
bool IsSmallIconView() const;
Return true if small icon view mode is set.
void ModifyFlag(const DWORD &dwRemove, const DWORD &dwAdd, const UINT redraw = 0);
You can add and remove this flags to the control:
  • fSmallIcon - sets small icon mode (default is large)
  • fEditGroups - enable folders local editing (renaming)
  • fEditItems - enable items local editing (renaming)
  • fRemoveGroups - enable the "Remove" command for folders in context menu
  • fRemoveItems - enable the "Remove" command for items in context menu
  • fDragItems - enable item dragging to rearrange position
  • fAnimation - enable animation while changing folder selection
  • fSelHighlight - enable dimmed highlight of last pressed item
The redraw flag has the same meaning as the CWnd::ModifyStyle() function.
void RemoveFolder(const int index);
Remove the "index" folder and its items.
void RemoveItem(const int index);
Remove the "index" item from of the currently selected folder.
void SetAnimationTickCount(const long value);
Set the tickcount (in milliseconds) between every animation frame in folder scrolling; if you set a value of -1 or minor no animation will be played. Animation also required the fAnimation flag.
void SetAnimSelHighlight(const int iTiming) const;
Use this to set an animation effect for last hitted item; iTiming will be the frame rate in milliseconds. Don't use this with the fSelHighlight flag.
CImageList * SetFolderImageList(const int folder, CImageList * pImageList, int nImageList);
Sets the image list for "folder" folder; If nImageList is "CGfxOutBarCtrl::fSmallIcon", the small icon imagelist is set; if nImageList is "CGfxOutBarCtrl::fLargeIcon", the large one is set.
void SetFolderText(const int index, const char * text);
Sets the text for the "index" folder label.
CImageList * SetImageList(CImageList * pImageList, int nImageList);
Set the imagelist. If nImageList is "CGfxOutBarCtrl::fSmallIcon", the small icon imagelist is set; if nImageList is "CGfxOutBarCtrl::fLargeIcon", the large one is set.
With this function, you set the main imagelist; you can link different imagelists to the folders using the SetFolderImageList function. If a folder has been linked to an imagelist with the SetFolderImageList function, it will own the linked imagelist; otherwise, it will use the use setted with this function.
void SetItemData(const int index, const DWORD dwData);
Sets the lParam value of the "index" item of the currently selected folder.
void SetItemImage(const int index, const int iImage);
Set the image index in the imagelist for the "index" item of the currently selected folder.
void SetItemText(const int index, const char * text);
Sets the text for the "index" item from of the currently selected folder.
void SetSelFolder(const int index);
Set the selection to the "index" folder.
void SetSmallIconView(const bool bSet);
Set large (bSet = false) or small (bSet = true) icon view mode.
void StartGroupEdit(const int index);
Start the local editing of the "index" folder.
void StartItemEdit(const int index);
Start the local editing of the "index" item from of the currently selected folder.

8. Enviroment note

This control have been build and tested under WinNT4.0 + SP3, with VC5.0 + SP3. There souldn't be trouble under Windows95, but probably the demo won't work well under WinNT3.51 (due to the modified splitter control - but the control probably will work).
I want to make a note here about the (in my opinion) very bad programming style of Microsoft in CSplitterWnd code; modifying it, I finded a lot of things like:
CRect(rectInside.right + afxData.bNotWin4, rectClient.top, rectClient.right, rectClient.top + m_cySplitter));
where bNotWin4 is a BOOL variable. Thanks, Microsoft, for writing such unreadable code and for teaching us that integer + boolean is a thing to do .. (irony .. don't do it in your code). And don't be surprised when MFC apps has strange trouble; with this kind of approach, it's obviously.
(for the gurus .. I know BOOL is internal handled as an integer and so this works, but it's not something that should be done and it surprised me that professional software as MFC is do a thing like that; try to do it in an university exam and you'll be throw away at speed light - with good reason).


That's all folks! Enjoy your stay here, support codeguru sending your code too and let me know if you liked this control.

Last updated: 4 August 1998



Comments

  • bug for CGfxOutBarCtrl::GetVisibleRange()

    Posted by wolcen@msn.com on 05/12/2006 04:45am

    GetVisibleRange(iFolder,first,last) if folder.GetItemCount() = 0 last return negative

    Reply
  • Displaying Chinese characters with this control

    Posted by vgandhi on 03/28/2005 01:55pm

    Hello everyone, I am having a very strange problem with this control. Usually if you are typing chinese or japanese you get a drop down box with all the different characters that you can type. This is the behavior with regular CEdit boxes and I have tried it out. Now say if you typed a letter in chinese you can select something on that box and hit enter and the focus stays on the box and you can select more characters to type. However in the case of this control when you press enter it thinks the entire item has been renamed and thus moves the focus out of the box. Would anyone happen to know why or what the reason might be? Please let me know. I would really appreciate your help. Thanks

    • Use _T() macro and _UNICODE

      Posted by redjes on 09/14/2005 05:45am

      Use All String in_T() macro and Build Use _UNICODE

      Reply
    Reply
  • everyone help me!!! ??

    Posted by fqy2000 on 08/19/2004 11:12pm

    I have try to create on the right pane an new splitter window with 2 views, like Outllok 2000 with preview window. But CGfxSplitterWnd works not correct as child of a parent splitter window. Can everyone help me!!!

    Reply
  • Not abel to see or Click any other application Implemented in Dialog

    Posted by Legacy on 11/13/2003 12:00am

    Originally posted by: amol

    When I start the application Using the OutBar(***Cool Bar***) ,It wont allow me to open(to see and work) other application and even it wont allow to go to other application. Other things works fine but. I need to open other application but it goes back to this Application. Help me out Its very urgent.

    Reply
  • Modalless dialog problem

    Posted by Legacy on 11/12/2003 12:00am

    Originally posted by: Donovon

    Cannot show Modalless dialog ! Any suggestions ?

    Reply
  • How can I change the folder's color?

    Posted by Legacy on 10/20/2003 12:00am

    Originally posted by: 龙星

    How can I change the folder's color?
    thanks very much/

    Reply
  • How to disable(hide) a Folder or a FolderBar?

    Posted by Legacy on 10/11/2003 12:00am

    Originally posted by: Yan Lee

    How to disable(hide) a Folder or a FolderBar?

    Reply
  • Great Control

    Posted by Legacy on 10/02/2003 12:00am

    Originally posted by: Vikrant

    This true,great,widely supported control is Just Great.
    Cheers :-)

    Reply
  • Control for first folder does not render until folder is changed (bug + fix)

    Posted by Legacy on 09/09/2003 12:00am

    Originally posted by: Devon Miller

    The Bug
    
    

    If you create the first folder using

    CGfxSplitterWnd::AddFolderBar(...)

    the folder will not render. You will simply see black where you should be seeing the window. If you switch to another folder, then switch back, the window will draw and work properly thereafter.

    The Fix

    In CGfxSplitterWnd::OnPaint(...) locate the if block that begins with this:

    if (!GetFolderChild())

    If you have applied the changes described in http://codeguru.earthweb.com/mfc/comments/24690.shtml, then the line will read:

    if (!GetFolderChild() && max > 0)

    In either case, change it to this:

    if (max > 0)
    {
    CWnd * pChild = GetFolderChild();
    if (!pChild) <= Formerly if (!GetFolderChild())
    {
    ...
    }
    else
    {
    // Instruct the window to show itself
    pChild->ShowWindow(SW_SHOW);
    }
    }

    Reply
  • release crashed !! help please!

    Posted by Legacy on 08/16/2003 12:00am

    Originally posted by: jianxinlee

    Hi~~~, all!!
    
    Firstly I create a controlbar, and create a modaless dialog in the controlbar(parent of the dialog), then i create the outlook bar in the modaless dialog(the dialog is parent of the bar), code as follow:

    // conflicts occurs???
    #define IDC_OUTLOOKBAR 1013
    int CMyDlg::OnCreate(...)
    {
    CDialog::OnCreate(...);
    DWORD dwf = CGfxOutBarCtrl::fRemoveGroups| CGfxOutBarCtrl::fRemoveItems|CGfxOutBarCtrl::fAddGroups;
    wndOutbar.Create(WS_CHILD|WS_VISIBLE, CRect(0,0,0,0), this, IDC_OUTLOOKBAR, dwf);
    wndOutbar.SetOwner(this);
    wndOutbar.AddFolderBar("TreeCtrl", &MyTreeCtrl);
    wndOutbar.AddFolderBar("ListCtrl", &MyListCtrl);
    ......
    }

    It works well in debug version, but in release, app crashed down. I made release debugable, and i find the crash point is:

    Line: ...STUDIO\VC98\MFC\SRC\WINCORE.CPP(289)

    CWnd* PASCAL CWnd::FromHandle(HWND hWnd)
    {
    CHandleMap* pMap = afxMapHWND(TRUE); //create map if not exist
    ASSERT(pMap != NULL);
    CWnd* pWnd = (CWnd*)pMap->FromHandle(hWnd);

    #ifndef _AFX_NO_OCC_SUPPORT

    /*my comment*/
    // crashed, pWnd invalid
    pWnd->AttachControlSite(pMap);
    #endif
    ......
    }

    Line: STUDIO\VC98\MFC\SRC\WINOCC.CPP(443)
    void COleControlContainer::AttachControlSite(CWnd* pWnd)
    {
    ASSERT(this != NULL);
    ASSERT(pWnd != NULL);

    // If a matching control site exists, it's an OLE control

    /*my comment*/
    // crashed, m_siteMap invalid
    COleControlSite* pSite = (COleControlSite*)m_siteMap.GetValueAt(pWnd->m_hWnd);
    if (pSite != NULL)
    {
    // detach any existing CWnd from this site (last one wins)
    CWnd* pOldCtrl = pSite->m_pWndCtrl;
    if (pOldCtrl != NULL && pOldCtrl->m_pCtrlSite == pSite)
    pOldCtrl->m_pCtrlSite = NULL;

    // now wire the site and CWnd together
    pWnd->m_pCtrlSite = pSite;
    pSite->m_pWndCtrl = pWnd;
    }
    }

    CWnd::FromHandle(HWND hWnd) was called in CWinThread::PreTranslateMsg(), and i found param hWnd value is exactly my outlookbar's HWND value.

    And at the time when crashes, the outlookbar window is visable indeed.

    i really don't know how to do now, give me your hand please!!!!! Many thanks.

    p.s. environment :
    Windows2000 sp3. visualC++6.0 sp5.

    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 …

  • Due to internal controls and regulations, the amount of long term archival data is increasing every year. Since magnetic tape does not need to be periodically operated or connected to a power source, there will be no data loss because of performance degradation due to the drive actuator. Read this white paper to learn about a series of tests that determined magnetic tape is a reliable long-term storage solution for up to 30 years.

Most Popular Programming Stories

More for Developers

RSS Feeds