11.18.4.5 - Пример "собственных" пунктов меню

Пример в этой теме использует в меню "собственные" пункты. Пункты меню выбирают конкретные атрибуты шрифта, а прикладная программа показывает на экране каждый пункт меню, используя шрифт, который имеет соответствующий атрибут. Например, пункт меню Italic (Курсив) отображается в курсивном шрифте. Название меню Character (Шрифт) в строке меню открывает меню.

Строка меню и раскрывающееся меню определены вначале расширенным ресурсом шаблона меню. Поскольку шаблон меню не может определять "собственные" пункты, меню вначале содержит четыре текстовых пункта меню со следующими строками: "Regular" ("Обычный"), "Bold" ("Полужирный"), "Italic" ("Курсив") и "UnderlineWM_CREATE. Когда процедура получает сообщение WM_CREATE, она вызывает определяемую программой функцию OnCreate, которая выполняет следующие шаги для каждого пункта меню:

  1. Распределяет в памяти определяемую программой структуру MYITEM.
  2. Получает текст пункта меню и сохраняет его в определяемой программой структуре MYITEM.
  3. Создает шрифт, использованный, чтобы показать на экране пункт меню и сохраняет дескриптор в определяемой программой структуре MYITEM.
  4. Изменяет тип пункта меню на MFT_OWNERDRAW и сохраняет указатель на определяемую программой структуру MYITEM, как на данные пункта.

Поскольку указатель на каждую определяемую программой структуру MYITEM сохраняется как данные пункта, он переходит в оконную процедуру вместе с сообщениями WM_MEASUREITEM и WM_DRAWITEM для соответствующего пункта меню. Указатель содержится в члене itemData и структуры MEASUREITEMSTRUCT и структуры DRAWITEMSTRUCT.

Сообщение WM_MEASUREITEM отправляется для каждого "собственного" пункта меню, когда он впервые отображается на экране. Приложение обрабатывает это сообщение, выбирая шрифт для пункта меню в контекст устройства, а затем устанавливает пространство, требуемое, чтобы показать на экране текст пункта меню этим шрифтом. Шрифт и текст пункта меню оба определены структурой пункта меню MYITEM (структура, определенная прикладной программой). Приложение устанавливает размер текста при помощи использования функции GetTextExtentPoint32.

Оконная процедура обрабатывает сообщение WM_DRAWITEM, показывая на экране текст пункта меню в соответствующем шрифте. Шрифт и текст пункта меню оба определены структурой пункта меню MYITEM. Прикладная программа выбирает текст и цвета фона, соответствующие состоянию пункта меню.

Оконная процедура обрабатывает сообщение WM_DESTROY, чтобы уничтожить шрифты и освободить память. Прикладная программа удаляет шрифт и освобождает определяемую программой структуру MYITEM для каждого пункта меню.

Ниже следуют необходимые части заголовочного файла прикладной программы.

// Идентификаторы пункта меню для меню Character (Шрифт)

#define IDM_CHARACTER 10
#define IDM_REGULAR   11
#define IDM_BOLD      12
#define IDM_ITALIC    13
#define IDM_UNDERLINE 14

// Структура, связанная с пунктами меню

typedef struct tagMYITEM {
	HFONT hfont;
	int cchItemText;
	char szItemText[1];
} MYITEM;

#define CCH_MAXITEMTEXT 256

Ниже следуют необходимые части оконной процедуры прикладной программы и связанных функций.

LRESULT CALLBACK MainWindowProc(
	HWND hwnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam
	)
{
	switch (uMsg) {
		case WM_CREATE:
			if (!OnCreate(hwnd))
				return -1;
			break;

		case WM_DESTROY:
			OnDestroy(hwnd);
			PostQuitMessage(0);
			break;

		case WM_MEASUREITEM:
			OnMeasureItem(hwnd, (LPMEASUREITEMSTRUCT) lParam);

			return TRUE;

		case WM_DRAWITEM:
			OnDrawItem(hwnd, (LPDRAWITEMSTRUCT) lParam);
			return TRUE;

		.
		. // Дополнительная обработка сообщения идет здесь.
		.

		default:
			return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
	return 0;
}

BOOL WINAPI OnCreate(HWND hwnd)
{
	HMENU hmenuBar = GetMenu(hwnd);
	HMENU hmenuPopup;
	MENUITEMINFO mii;
	UINT uID;

	MYITEM *pMyItem;

	// Получим дескриптор выскакивающего меню.

	mii.fMask = MIIM_SUBMENU;	 <>// чтобы получить информацию
	GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii);
	hmenuPopup = mii.hSubMenu;

	// Модифицируем каждый пункт меню. Предположим, что 
	// IDs ( идентификаторы) от IDM_REGULAR до 
	// IDM_UNDERLINE - последовательные числа

	for (uID = IDM_REGULAR; uID <= IDM_UNDERLINE; uID++) {

		// Распределим в памяти структуру пункта, 
		// оставляя пространство для строки плюс для 
		// символов CCH_MAXITEMTEXT.

		pMyItem = (MYITEM *) LocalAlloc(LMEM_FIXED,
				sizeof(MYITEM) + CCH_MAXITEMTEXT);

		// Сохраним текст пункта в структуре пункта

		mii.fMask = MIIM_TYPE;
		mii.dwTypeData = pMyItem->szItemText;
		mii.cch = CCH_MAXITEMTEXT;
		GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);
		pMyItem->cchItemText = mii.cch;

		// Перераспределим память для структуры для 
		// минимально требуемого размера.

		pMyItem = (MYITEM *) LocalReAlloc(pMyItem,
				sizeof(MYITEM) + mii.cch, LMEM_MOVEABLE);

		// Создадим шрифт, который используем для прорисовки пункта.

		pMyItem->hfont = CreateMenuItemFont(uID);

		// Заменим пункт на "собственный" пункт и сохраним
		// адрес структуры пункта как данные пункта.

		mii.fMask = MIIM_TYPE | MIIM_DATA;
		mii.fType = MFT_OWNERDRAW;
		mii.dwItemData = (DWORD) pMyItem;

		SetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);
	}
	return TRUE;
}

HFONT CreateMenuItemFont(UINT uID)
{
	LOGFONT lf;

	ZeroMemory(&lf, sizeof(lf));
	lf.lfHeight = 20;
	lstrcpy(lf.lfFaceName, "Times New Roman");

	switch (uID) {
		case IDM_BOLD:
			lf.lfWeight = FW_HEAVY;
			break;

		case IDM_ITALIC:
			lf.lfItalic = TRUE;
			break;

		case IDM_UNDERLINE:

			lf.lfUnderline = TRUE;
			break;
	}
	return CreateFontIndirect(&lf);
}

VOID WINAPI OnDestroy(HWND hwnd)
{
	HMENU hmenuBar = GetMenu(hwnd);
	HMENU hmenuPopup;
	MENUITEMINFO mii;
	UINT uID;
	MYITEM *pMyItem;

	// Получим дескриптор меню.

	mii.fMask = MIIM_SUBMENU; // чтобы получить информацию
	GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii);
	hmenuPopup = mii.hSubMenu;

	// Освободим ресурсы, связанные с каждым пунктом меню.

	for (uID = IDM_REGULAR; uID <= IDM_UNDERLINE; uID++) {

		// Получим данные пункта.

		mii.fMask = MIIM_DATA;
		GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);
		pMyItem = (MYITEM *) mii.dwItemData;

		// Уничтожим шрифт и освободим структуру пункта

		DeleteObject(pMyItem->hfont);
		LocalFree(pMyItem);
	}
}

VOID WINAPI OnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpmis)
{
	MYITEM *pMyItem = (MYITEM *) lpmis->itemData;
	HDC hdc = GetDC(hwnd);
	HFONT hfntOld = SelectObject(hdc, pMyItem->hfont);
	SIZE size;
	GetTextExtentPoint32(hdc, pMyItem->szItemText, pMyItem->cchItemText, &size);
	lpmis->itemWidth = size.cx;
	lpmis->itemHeight = size.cy;
	SelectObject(hdc, hfntOld);
	ReleaseDC(hwnd, hdc);
}

VOID WINAPI OnDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpdis)
{
	MYITEM *pMyItem = (MYITEM *) lpdis->itemData;
	COLORREF clrPrevText, clrPrevBkgnd;
	HFONT hfntPrev;
	int x, y;
	// Установим соответствующие цвета шрифта и фона.
	if (lpdis->itemState & ODS_SELECTED) {
		clrPrevText = SetTextColor(lpdis->hDC,
				GetSysColor(COLOR_HIGHLIGHTTEXT));
		clrPrevBkgnd = SetBkColor(lpdis->hDC,
				GetSysColor(COLOR_HIGHLIGHT));
	}
	else {
		clrPrevText = SetTextColor(lpdis->hDC,
				GetSysColor(COLOR_MENUTEXT));
		clrPrevBkgnd = SetBkColor(lpdis->hDC,

				GetSysColor(COLOR_MENU));
	}

	// Решим, где нарисовать и оставить пространство для "галочки".
	x = lpdis->rcItem.left;
	y = lpdis->rcItem.top;
	x += LOWORD(GetMenuCheckMarkDimensions());

	// Выберем шрифт и прорисуем текст.

	hfntPrev = SelectObject(lpdis->hDC, pMyItem->hfont);
	ExtTextOut(lpdis->hDC, x, y, ETO_OPAQUE,
			&lpdis->rcItem, pMyItem->szItemText,
			pMyItem->cchItemText, NULL);


	// Восстанавливаем первоначальный шрифт и цвет.

	SelectObject(lpdis->hDC, hfntPrev);
	SetTextColor(lpdis->hDC, clrPrevText);
	SetBkColor(lpdis->hDC, clrPrevBkgnd);
}

Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Владимир Соковиков - 07.10.2002