CodeGuru: An Outlook98 bar-like control

来源:互联网 发布:韩进海运 知乎 编辑:程序博客网 时间:2024/06/07 09:12

Download demo and code 142K



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:
CGfxSplitterWndwndSplitter;
CGfxOutBarCtrlwndBar;

CImageListimaLarge, 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_SMALLICON32810
#define ID_GFX_LARGEICON32811
#define ID_GFX_REMOVEITEM32812
#define ID_GFX_RENAMEITEM32813

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).