Introduction
I searched many times on the Internet for an easy way to implement an AppBar (desktop application toolbar) but without success. Microsoft provides some WinAPI shell functions like SHAppBarMessage
(read here about it) and an old sample application (AppBar.exe). I tried it out but that was not what I expected because Shell AppBars reorganize the desktop to fit the toolbar, while I wanted a sliding bar that doesn't disturb other application windows or the desktop icons.
So, I have developed a single MFC object, CAppBarMngr
, to allow almost any application to become a sliding AppBar, with minimal changes to the application's source code. Since it is necessary to respond to mouse movements outside the application window, I had to implement a global mouse hook, which requires to generate a DLL, but you don't have to deal with the DLL internals, just to distribute with your software.
Overall Design
There is just one class: CAppBarMngr
, which will be responsible for sliding the application's main frame from the left or right edge of the desktop screen. As I mentioned earlier, a global hook is needed to respond appropriately to the mouse cursor position (i.e. if the side edge has been reached).
Traditionally, a global hook is implemented as a DLL that sends messages to an application's window through its HWND
handler (hook DLLs can't be MFC enabled), which requires several modifications in the application's code for receiving and managing the hook messages. In order to avoid that, however, I have tried a different approach: to send messages to a secondary thread, as explained below.
CAppBarMngr Class
This class is derived from CWinThread
, so it is capable of receiving messages sent using WinAPI PostThreadMessage()
function, by implementing the PreTranslateMessage()
event. Also this class has some other responsibilities: to be a wrapper for the hook DLL, and to handle window movements (sliding).
CAppBarMngr
has just one public
member: the Init()
function. It will be used to link the manager with the managed window. It receives three arguments as described in the source code:
int CAppBarMngr::Init(HWND _hWnd, int _width, bool _left)
How to Use It
This is all you have to do to implement AppBar into your application:
- Insert the AppBarMngr.cpp and AppBarMngr.h files into your MFC project.
- Put
#include "AppBarMngr.h"
into your main file (where main window is created).
- Create your main window, usually into
yourapp::InitInstance()
.
- Create a thread with an MFC
::AfxBeginThread()
function as shown below, saving a pointer to it.
- Call the
Init
method specifying window's HWND
handler, desired width and edge.
- Verify if
Init
has returned successfully.
Here is the code portion from the demo application:
BOOL CAppBarDemoApp::InitInstance()
{
CMainFrame* pFrame = new CMainFrame;
m_pMainWnd = pFrame;
pFrame->Create(NULL, "AppBarDemo", WS_POPUP);
pFrame->ModifyStyleEx(WS_EX_APPWINDOW|WS_EX_CLIENTEDGE, WS_EX_TOOLWINDOW);
pFrame->ShowWindow(SW_HIDE);
pFrame->UpdateWindow();
CAppBarMngr *appbar =
(CAppBarMngr *)::AfxBeginThread(RUNTIME_CLASS(CAppBarMngr));
int result = appbar->Init(pFrame->m_hWnd, 150, false);
if (result==APPBARHOOK_SUCCESS)
return TRUE;
else if (result==APPBARHOOK_DLLERROR)
::AfxMessageBox("Error loading AppBarHook.dll");
return FALSE;
}
That's all you have to do. Also, don't forget to distribute the hook DLL (AppBarHook.dll) with your executable application; it must reside in the same directory to work properly.
Single Instance Control for Free
As it makes no sense to run two copies of the same AppBar program, the Init()
function is a notification that the hook has already been used (by returning APPBARHOOK_ALREADYHOOKED
), so, you can use it to avoid a second instance to run. Notice the last lines in the example above, if hook has been already used, then it returns FALSE
to close the application.
The Hook Project
I had to develop a project to implement the global mouse hook DLL. It has just one file: AppBarHook.cpp. I don't want to make a hook tutorial here because there are several great articles here at CodeProject. So, I will just give you some of the details.
The usual hook technique saves a windows handler (HWND
) of the receiving window for hook messages. I have used a Thread ID instead, that's why CAppBarMngr
is a thread object. So, messages are passed using WinAPI ::SendThreadMessage()
instead of the ::SendMessage()
function.
The hook will detect mouse events of possible interest to CAppBarMngr
. I say possible because the hook doesn't know about the window state and position, it knows only about the managed edge and window width. The MFC class will do the rest of the work.
The Hook DLL exports only one function: SetHook()
, which creates the global mouse hook and saves the desired width and edge. It is called by CAppBarMngr
.
About the Demo Application
I have created a simple demo application project for testing purpose. It has a simple CFrameWnd
derived object without frame, caption, menu or border. I have tested this with other standard MFC windows without any problem.
The demo source is a Visual C++ 6.0 project, but you will be able to open and automatically convert to a newer Visual C++ version.
If you find any bug in this application, please don't care about it. It is just for demo purposes and it is not the intention of this article. I will not describe its internals here for the same reason.
.NET Version
I have been requested several times for a .NET version of this control. I have started working on it. I will leave a message in the forum below when it is ready.
History
- May 3, 2005 - First edition
- July 9, 2008 - Added multi-monitor support