Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / MFC

Transparent Menu

Rate me:
Please Sign up or sign in to vote.
4.44/5 (22 votes)
8 Jun 20047 min read 279.9K   4.4K   82   43
An article on creating transparent menus for your applications

Sample Image

Introduction

Mac OS has nice transparent menus, which uses hardware acceleration. It is almost impossible to achieve that kind of effect in MS Windows, but one can surely create menus, which are looking like that of Mac. The topic of this article is not how to create Mac like menus, but discuss some of the options available for a Windows developer to create nice owner drawn menus. Here in this article, I present four different approaches, to implement a transparent menu.

  • Using WM_INITMENUPOPUP message
  • Using Owner Draw method
  • Superclassing menu class
  • Using Hooks

All versions of Windows from Win2K allow you to set transparency level for windows. Since Windows provides no API which returns window HANDLE of a menu, each of the above mentioned methods tries to get a handle to menu and set transparency.

Setting Transparency for a Normal Window

Windows 2000 and above provides a function to set window transparency

C++
BOOL SetLayeredWindowAttributes(HWND hwnd,COLORREF crKey,BYTE bAlpha,DWORD dwFlags)

This function can be used in the following manner to set transparency for a window given its HWND.

C++
//Setting transparency for a normal window
DWORD dwStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
//WS_EX_LAYERED Flag must be specified with extended styles
SetWindowLong(hWnd, GWL_EXSTYLE, dwStyle|WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 150, LWA_ALPHA); 
  //150 represent transparency level

The transparency level can vary from 0 to 255. Zero means completely transparent (invisible) and 255 corresponds to fully opaque.

Making of a Transparent Menu

There occurs some complication in the case of menus. In addition to transparency feature MS has also provided Menu animations, which will interfere with transparency settings, if you apply the above code to menu window. So, as a workaround, we will temporarily disable menu animations with the following code:

C++
//Disable menu animation
OSVERSIONINFO ovi;
ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
BOOL bRet = GetVersionEx(&ovi);
// Toggle menu fading/animation mode (Windows2K or later)
BOOL bMenuFading = FALSE;
BOOL bMenuAnim = FALSE;
if( ovi.dwMajorVersion >= 5 ){
  SystemParametersInfo(SPI_GETMENUFADE, 0, &bMenuFading, 0);
  SystemParametersInfo(SPI_GETMENUANIMATION, 0, &bMenuAnim, 0);
  SystemParametersInfo(SPI_SETMENUFADE, 0, 
    (LPVOID) FALSE, SPIF_SENDWININICHANGE);
  SystemParametersInfo(SPI_SETMENUANIMATION, 0, 
    (LPVOID) FALSE, SPIF_SENDWININICHANGE);
}

//Transparency code goes here...

if( ovi.dwMajorVersion >= 5 ){
  SystemParametersInfo(SPI_SETMENUFADE, 0, 
    (LPVOID) bMenuFading, SPIF_SENDWININICHANGE);
  SystemParametersInfo(SPI_SETMENUANIMATION, 0, 
    (LPVOID) bMenuAnim, SPIF_SENDWININICHANGE);
}

I will be omitting the above code fragment in this article hereafter, for the sake of simplicity. But it should be there for proper working.

1. Handling WM_INITMENUPOPUP

In this method, we will be handling WM_INITMENUPOPUP message. From MSDN, "The WM_INITMENUPOPUP message is sent when a drop-down menu or submenu is about to become active. This allows an application to modify the menu before it is displayed, without changing the entire menu". What is done is that, when WM_INITMENUPOPUP is generated, the menu will already be created. So we will be using FindWindow() API to get HWND of the menu. Then we will be modifying menu, to suit our purpose. Class name for menu is #32768. Here is the code:

C++
//Main window message handler procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 
  WPARAM wParam, LPARAM lParam)
{
  switch (message) 
  {
    case WM_INITMENUPOPUP:
    {
      HWND hMenuWnd = FindWindow(_T("#32768"), NULL);
      if (IsWindow(hMenuWnd)) {
        DWORD dwStyle;
         dwStyle = GetWindowLong(hMenuWnd, GWL_EXSTYLE);
   
        dwStyle = SetWindowLong(hMenuWnd, GWL_EXSTYLE, 
          dwStyle|WS_EX_LAYERED);
        SetLayeredWindowAttributes(hMenuWnd, 0, 150, LWA_ALPHA);
      }
    }
    break;

    case WM_CONTEXTMENU:
    {
      HMENU hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDC_TMENU1));
      if (IsMenu(hMenu)) {
        DWORD dwPos = ::GetMessagePos();
        POINT pt = {LOWORD(dwPos), HIWORD(dwPos)};
        HMENU hSubMenu = GetSubMenu(hMenu, 0);
        TrackPopupMenuEx(hSubMenu, TPM_LEFTALIGN | 
          TPM_RIGHTBUTTON | TPM_NOANIMATION,
          pt.x, pt.y, hWnd, NULL);
      }
    }
    break;
//Other handlers go here...
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

Note that this technique only works with top-level windows, so unfortunately you cannot apply it to individual controls. During my testing, I got WM_INITMENUPOPUP for all menus, but SetWindowLong() failed for all menus except Context Menu, even popup menus inside a context menu also failed. A screen shot is given below:

So its application is limited to context menu like purpose. Another point worth noting is that menu animations can be turned off without using SystemParametersInfo(). This is because TrackPopupMenu() / TrackPopupMenuEx() can take a parameter TPM_NOANIMATION which will prevent menu animations. You can use this method effectively in applications that require only simple context menus and button menus. Attached project tMenu1 illustrates this method.

2. Owner-Drawn Menu

If you have an owner-drawn menu, then this method is for you. When we receive WM_DRAWITEM message, we will also be receiving HDC to menu. So we can obtain handle of the menu window using WindowFromDC() API. This call will not always return a valid handle, because of naughty menu animations. Windows provides a temporary DC for menu animations. But once we get a valid DC, we can perform all sorts of operations we require on that menu window. So you need to set MF_OWNERDRAW style set to each menu item.

C++
LRESULT CALLBACK WndProc(HWND hWnd, 
   UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
    case WM_MEASUREITEM:
    {
      LPMEASUREITEMSTRUCT lpms = (LPMEASUREITEMSTRUCT)lParam;
      lpms->itemWidth = 80;
      lpms->itemHeight=40;
    }
    return 0;
    case WM_DRAWITEM:
    {
      LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam;
      HWND hMenuWnd = WindowFromDC(lpdis->hDC);
      if (IsWindow(hMenuWnd)) {
        DWORD dwStyle =0;
        dwStyle = GetWindowLong(hMenuWnd, GWL_EXSTYLE);
        SetWindowLong(hMenuWnd, GWL_EXSTYLE, dwStyle|WS_EX_LAYERED);
        SetLayeredWindowAttributes(hMenuWnd, 0, 150, LWA_ALPHA);
      }
      //TODO:Rest of the drawing stuff...
    }
    return 0;
    case WM_CONTEXTMENU:
    {
      HMENU hMenu = CreateMenu();
      HMENU hMenuPopup = CreatePopupMenu();
      AppendMenu(hMenuPopup, MF_STRING|MF_OWNERDRAW, 
          ID_FILE_NEW, (LPCTSTR)"&Pub");
      AppendMenu(hMenuPopup, MF_STRING|MF_OWNERDRAW, 
          ID_FILE_OPEN, (LPCTSTR)"&Club");
      AppendMenu(hMenu, MF_POPUP|MF_OWNERDRAW, 
          (UINT)hMenuPopup, (LPCTSTR)"&labamba0");
      DWORD dwPos = ::GetMessagePos();
      POINT pt = {LOWORD(dwPos), HIWORD(dwPos)};
      TrackPopupMenuEx(hMenuPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON 
          |TPM_NOANIMATION, pt.x, pt.y, hWnd, NULL);
      DestroyMenu(hMenu);
    }

    return 0;
//Rest of message handler
  }//switch
}

Here also some glitches occur, which are of course caused by menu animations. You have to hover mouse pointer over menu to make it transparent. To overcome this bad effect, you should specify TPM_NOANIMATION with TrackPopupMenuEx() flags. But it still has got a bit of problem with popup menus. Here's a screenshot:

This method can be very easily integrated to owner-drawn menus. It can give nice transparent effect without too much tedious work. Attached project tMenu0 illustrates this method.

3. Superclassing Menu

This is one of those rare methods. I haven't seen an instance where a menu class is superclassed. In this method, we obtain information about menu class using GetClassInfo() / GetClassInfoEx() and replace its window procedure with our one and re-register the menu class with the same name. Hence, all the menus created afterwards will be using our window procedure. The class name for menu is #32768. The following code illustrates how it is done.

C++
//Superclassing menu

LRESULT CALLBACK MenuWndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    LPTSTR lpCmdLine,int nCmdShow)
{
  WNDCLASS wc;

  GetClassInfo(NULL, _T("#32768"), &wc);
  wc.hInstance = hInstance;
  wpOldMenuProc = wc.lpfnWndProc;
  wc.lpfnWndProc = (WNDPROC)MenuWndProc;
  SetLastError(0);
  RegisterClass(&wc);

  //Rest of the stuff goes here...
  return 0;
}

Now MenuWndProc can be written as follows:

C++
//Message Handler for Menu
LRESULT CALLBACK MenuWndProc(HWND hWnd, UINT uMsg, 
  WPARAM wParam, LPARAM lParam)
{
switch(uMsg) {
case WM_CREATE:
  {
  LRESULT lRet = CallWindowProc(wpOldMenuProc, 
    hWnd, uMsg, wParam, lParam);
  DWORD dwStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
  dwStyle &= ~WS_EX_WINDOWEDGE;
  dwStyle &= ~WS_EX_DLGMODALFRAME;
  SetWindowLong(hWnd, GWL_EXSTYLE, dwStyle|WS_EX_LAYERED);
  SetLayeredWindowAttributes(hWnd, 0, 180, LWA_ALPHA);
  return lRet;
  }
default:
  return CallWindowProc(wpOldMenuProc, hWnd, uMsg, wParam, lParam);
}//switch
return 0;
}

This method works great for all kind of menus. It is not limited to context menus. It can also handle Popup menus. But as usual, the "Fade Effect" animation style can cause some problems. It sometimes causes interference with menus. Attached sample project tMenu2 illustrates this method.

4. Using Windows Hooks

Hooks represent one of those powerful abilities of windows. Virtually everything inside Windows can be monitored using Hooks. As MSDN says, they are powerful and should be used with care, else they can mess things up. By "hooking", you tell Windows about a function, filter function also called hook procedure, that will be called every time an event you're interested in occurs. There are two types of them: local and remote hooks. We are interested in local hooks, as they will trap events occurring in local thread.

When you create a hook, Windows creates a data structure in memory, containing information about the hook, and adds it to a linked list of existing hooks. New hook is added in front of old hooks. When an event occurs, if you install a local hook, the filter function in your process is called so it's rather straightforward. One can install Hooks with SetWindowsHookEx() function. You can uninstall a hook by calling UnhookWindowsHookEx() which accepts only one parameter, the handle of the hook you want to uninstall. When an event occurs, Windows calls HookProcedure.

We will be using WH_CALLWNDPROC which is initiated when a SendMessage() is called. Then we will check if the window is a menu window by checking its class name. If it is so, we will proceed with our transparency operation. The following code illustrates this:

C++
//Main window Procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 
  WPARAM wParam, LPARAM lParam)
{
  int wmId, wmEvent;
  static HWND    hWndEdit;
  static HHOOK  hHook;
  switch (message) 
  {
  case WM_CREATE:
    hHook = SetWindowsHookEx(WH_CALLWNDPROC, HookCallWndProc, 
       0,GetWindowThreadProcessId(hWnd,0));
    hWndEdit = CreateWindowEx(WS_EX_CLIENTEDGE, _T("EDIT"),NULL,
       WS_CHILD | WS_VISIBLE | ES_MULTILINE | WS_HSCROLL | WS_VSCROLL,
       0,0,0,0,hWnd,(HMENU)NULL,hInst,NULL;
    SendMessage(hWndEdit, WM_SETFONT, (
      WPARAM)GetStockObject(SYSTEM_FIXED_FONT), MAKELPARAM(TRUE, 0));
    break;
  case WM_DESTROY:
    if(hHook!=0)
      UnhookWindowsHookEx(hHook);
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

LRESULT CALLBACK HookCallWndProc(int nCode, 
  WPARAM wParam, LPARAM lParam)
{
  CWPSTRUCT cwps;
  LONG lRet;
  if( nCode == HC_ACTION ){
    CopyMemory(&cwps, (LPVOID)lParam, sizeof(CWPSTRUCT));
  switch(cwps.message){

  case WM_CREATE:
    {

      CHAR szClass[128];
    GetClassName(cwps.hwnd, szClass, 127);
    if(_tcscmp(szClass, _T("#32768"))==0){
      HWND hMenuWnd = cwps.hwnd;
      DWORD dwStyle =0;
      dwStyle = GetWindowLong(hMenuWnd, GWL_EXSTYLE);
      SetWindowLong(hMenuWnd, GWL_EXSTYLE, dwStyle|WS_EX_LAYERED);

      SetLayeredWindowAttributes(hMenuWnd, 0, 220, LWA_ALPHA);
    }
    }
    break;
   }//switch
  }//if
  return CallNextHookEx((HHOOK)WH_CALLWNDPROC, nCode, wParam, lParam);
}

The hooks are chained in a linked list with the most recently installed hook at the head of the list. When an event occurs, Windows will call only the first hook in the chain. It's your hook procedure's responsibility to call the next hook in the chain. You can choose not to call the next hook but you'd better know what you're doing. Most of the time, it's a good practice to call the next procedure so other hooks can have a shot at the event. You can call the next hook by calling CallNextHookEx()

As you have seen, it is a slightly difficult technique. But it is very useful in the sense that very window can be caught in the HookWindow Procedure. For more information on Windows Hooks, you can visit Iczelion's Win32 Assembly Homepage, although it is in assembly language it is pretty useful. This method is illustrated in the attached tMenu3 sample project. The fist screenshot applies to the last two techniques.

Which One to Choose...

The answer to this question entirely depends upon your application. If you have simple applications, like one that is in a dialog box, then you can chose any of the first two methods. You can choose any of the last two methods to get support for every menu item in your application. If you use hooks, you should be very careful and must unhook when it is not needed. By using appropriate transparency level, you can have your applications' nice looking transparent menus. This is important, since too much transparency can prevent users from seeing the menu texts. I recommend a value between 220 and 245.

Concluding Words...

When this article was first posted, it only included hooks-method. But the readers suggested many more methods. I have included all those suggestions, and have provided samples for them also. I suppose that this article has something worthwhile. Thank you all for your valuable suggestions and they are welcome.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Exciton Technologies LLP
India India
https://exciton.net

Comments and Discussions

 
Generalget rid of menu animations Pin
seddi12-Aug-14 13:33
seddi12-Aug-14 13:33 
QuestionNeat Pin
KevinSW27-Jul-14 17:43
KevinSW27-Jul-14 17:43 
Questionbroken link Pin
JJMatthews22-Aug-12 22:55
JJMatthews22-Aug-12 22:55 
GeneralMy vote of 5 Pin
JJMatthews22-Aug-12 22:37
JJMatthews22-Aug-12 22:37 
GeneralWorks as suggested. Pin
parandor9-Jun-11 6:36
parandor9-Jun-11 6:36 
GeneralSuperclassing System MENU #32768 and other system provided classes Pin
manurocks11-Jun-10 7:55
manurocks11-Jun-10 7:55 
GeneralWM_ENTERIDLE [modified] Pin
cantstandya1-Jun-10 11:37
cantstandya1-Jun-10 11:37 
Generalplacing custom button in another application's title bar. Pin
Member 41401198-Dec-08 21:12
Member 41401198-Dec-08 21:12 
Generaltransparecy over video Pin
Saiz21-Jun-07 2:22
Saiz21-Jun-07 2:22 
GeneralProblem with Owner-Drawn method Pin
AnandChavali27-Oct-06 0:50
AnandChavali27-Oct-06 0:50 
GeneralRe: Problem with Owner-Drawn method Pin
AnandChavali13-Nov-06 19:50
AnandChavali13-Nov-06 19:50 
General#32768 Pin
Sarath C12-Oct-06 19:00
Sarath C12-Oct-06 19:00 
GeneralRe: #32768 Pin
AnandChavali13-Nov-06 19:45
AnandChavali13-Nov-06 19:45 
Questionwhen i use right mouse button click caption or edit,show a button on taskbar,why? Pin
user884830-Aug-06 17:39
user884830-Aug-06 17:39 
Generalwhy SetLayeredWindowAttributes can't used in 8 bits(256) Pin
Eureka Jim11-Aug-04 12:01
Eureka Jim11-Aug-04 12:01 
GeneralImpressive Work but bug Pin
ChetnaBABY29-Apr-04 18:56
ChetnaBABY29-Apr-04 18:56 
General( 255 / 100 ) * ( 100 - 0 ) = 255 Pin
.:floyd:.6-Feb-04 15:32
.:floyd:.6-Feb-04 15:32 
GeneralAdding items to this popup menu Pin
Parag Paithankar5-Nov-03 22:08
Parag Paithankar5-Nov-03 22:08 
GeneralVery convincing... Pin
dandy7220-Aug-02 2:24
dandy7220-Aug-02 2:24 
GeneralRe: Very convincing... Pin
.:floyd:.4-Feb-04 9:04
.:floyd:.4-Feb-04 9:04 
GeneralThis could be done without hooks, i guess Pin
10-Jul-02 10:27
suss10-Jul-02 10:27 
GeneralDoesn't work on Windows Me Pin
rushfn18-Jun-02 9:17
rushfn18-Jun-02 9:17 
GeneralRe: Doesn't work on Windows Me Pin
Derick Cyril Thomas21-Mar-03 2:36
Derick Cyril Thomas21-Mar-03 2:36 
GeneralRe: DB on Win 2k Pin
11-May-02 17:31
suss11-May-02 17:31 
GeneralIt doesn't work on Win98. Pin
1-May-02 12:48
suss1-May-02 12:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.