Date: 12 September
1997
Author: Mark Caroli, University of Mannheim, Germany
This article discusses the use of context menus. Context menus are the menus which appear when the right mouse button is clicked in a window' s client area.
The text is divided into three chapters. The first one focusses on different ways of creating an instance of the TPopupMenu class, which contains the context menu. Chapter two introduces two alternatives for prompting the context menu on the screen. Finally, you can see how to display different context menus for one window. A download sample is available.
The first thing that has to be done when you want to use context menus is the creation of an TPopupMenu instance. This chapter introduces several alternatives for accomplishing this task.
Let us assume that you have defined the following menu in the resource workshop
IDM_SIMPLECONTEXTMENU
BEGIN
MENUITEM "Menuitem 1",CM_MENUITEM1
MENUITEM "Menuitem 2",CM_MENUITEM2
END
Unfortunenatly a TPopupMenu object cannot be created directly from a resource script. This is only valid for it's base class TMenu. In addition to this there is a TPopupMenu constructor that supports TMenu instances. So all we have to do is:
The corresponding code looks like this:
//create a TMenu instance from the resource script
TMenu Menu (*GetApplication(),IDM_SIMPLECONTEXTMENU);
//use the result to create a TPopupMenu
TPopupMenu *pPopupMenu = new TPopupMenu (Menu);
//assign the context menu
AssignContextMenu (pPopupMenu);
OWL also supports the use of submenus of the main window's menu as context menu. Therefore we have to retrieve the handle of the main window. It is used to retrieve the main menu itself. The TMenu::GetSubMenu() requires the index of the submenu you wish to extract and returns its handle. From the handle another TMenu instance holding the submenu's items is created. This one is used in the same manner as above to get the TPopupMenu object. These steps are illustrated in the code below:
//get the handle of the frame window owning the main menu
HWND HFrame = *GetApplication()->GetMainWindow();
//get the main menu
TMenu* pMenu = new TMenu(HFrame);
//extract the submenu, that should become the context menu
TMenu* pContextMenu = new TMenu(pMenu->GetSubMenu(1));
//create the conext menu
TPopupMenu* pPopupMenu = new TPopupMenu(*pContextMenu);
The previously mentioned alternatives are based on the assumption that the context menu is known at design time. Sometimes this is not the case. The context menu has to be generated at runtime. With the default constructor you can create an empty TPopupMenu object. Menuitems can be added with the AppendMenu(...) function. The meaning of the parameters is explained in the BWCC online help. The following code adds two menuitems to an empty popupmenu:
//create an empty popup menu
TPopupMenu* pPopupMenu = new TPopupMenu();
//insert some items
pPopupMenu->AppendMenu (MF_ENABLED,CM_MENUITEM3,"Menuitem 3");
pPopupMenu->AppendMenu (MF_ENABLED,CM_MENUITEM4,"Menuitem 4");
There exist two ways to tell your window to popup a context menu when the right mouse button is pressed.
You can either call the TWindow::AssignContextMenu(...) method in the window' s SetupWindow function. It requires a pointer to a TPopupMenu instance.
Or you can use the WM_RBUTTONDOWN message to display the context menu. In order to achieve that, we, first of all, need to declare the response table entry and a handler for this message.
class TCombinedWindow : public TWindow
{
//other methods
protected:
//handler for right
mouse button
void EvRButtonDown(uint modKeys, TPoint& point);
//other methods
DECLARE_RESPONSE_TABLE(TCombinedWindow);
};
DEFINE_RESPONSE_TABLE (TCombinedWindow,TWindow)
EV_WM_RBUTTONDOWN,
END_RESPONSE_TABLE;
The the handler's implementation
The next code snippet shows a template implementation:
void TCombinedWindow::EvRButtonDown(uint modKeys, TPoint& point)
{
TWindow::EvRButtonDown(modKeys, point);
// create a TPopupMenu instance first (see chapter 1)
TPopupMenu *pPopupMenu = ...
// get the main window which
will receive the menu messages
HWND HFrame = *GetApplication()->GetMainWindow();
ClientToScreen(point);
// invoke the menu
pPopupMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point, 0, HFrame, 0);
delete pPopupMenu;
}
No matter which alternative you choose a response table entry and a handler for each menuitem must be defined. If you forget these, the menuitems will appear grayed, when the context menu is activated. The article "Responding To Dynamically Created Controls And Menuitems" shows how to respond to menuitems without having defined these things. It also can be seen from the sample application.
Let us say that you have a window where you want to popup different context menus. Which menu is displayed depends on the current position of the mouse cursor. This problem sounds very difficult, but for rectangular regions it is quite easy to solve. For each context menu to be shown, you declare a TRect instance indicating the areas where the context menue is valid. In the EvRButtonDown handler you receive the coordinates of the point where the click took place. Checking in which rectangle the point lies leads to the solution. Below a template function is shown:
void TCombinedWindow::EvRButtonDown(uint modKeys, TPoint& point)
{
TWindow::EvRButtonDown(modKeys, point);
if p(Rect1->Contains(point))
{
//popup context menu no 1
}
else
if (pRect2->Contains(point))
{
//popup context menu no
2
}
else
if (pRect3->Contains (point))
{
//popup context
menu no 3
}
else
//popup context
menu no 4
}
This solution does not work properly, if
the rectangles intersect.