Сообщений 4 Оценка 45 Оценить |
Демонстрационный проект bmpbtn (33.1KB)
Кнопка не обязательно должна иметь стандартный внешний вид (хотя лично я не нахожу внешний вид стандартной кнопки скучным или "простецким"). Однако для многих разработчиков и пользователей кнопки, имеющие нестандартный вид, выглядят более привлекательными. Поэтому для придания некоего стиля интерфейсу собственных программ можно использовать кнопки, отображающие некий битмап (bitmap - растровое изображение).
ПРИМЕЧАНИЕ Кроме эффектов изображения можно использовать еще и эффекты формы - к примеру, круглая или овальная кнопка также достаточно оригинальны внешне, - но данная статья не рассматривает технику создания кнопок, имеющих форму, отличную от прямоугольной. |
Windows имеет встроенные механизмы и API, поддерживающие создание кнопок (а также и других контролов), имеющих нестандартный внешний вид. Способ отрисовки внешнего вида контрола зависит от его стиля. В данном случае, стиль, нужный нам - это BS_OWNERDRAW. Из его названия видно, что отрисовку вида кнопки выполняет код пользователя, помещенный в оконную (диалоговую) функцию окна-владельца контрола.
Рассмотрим основные этапы отрисовки контрола, имеющего стиль xx_OWNERDRAW.
Это - теория. Практика же показывает, что сообщение WM_MEASUREITEM в кнопку со стилем BS_OWNERDRAW не приходит. Удивлены? Мы - группа авторов сайта, обсуждавших этот эффект - тоже были удивлены. Информация по данному вопросу, приводимая в различных выпусках MSDN, противоречива, однако последние версии говорят, что подобное поведение системы считается нормой. Чтобы проверить, какие же сообщения все таки приходят в кнопку, в диалог демонстрационного проекта включен листбокс, в который выводится информация о приходящих сообщениях. |
Поскольку мы реализуем, хотя и самостоятельно отрисовываемую, но все же кнопку, то было бы неплохо, если бы она имела поведение обычной кнопки - края кнопки в нормальном состоянии должны имитировать выпуклый контрол, при нажатом состоянии - вдавленный, при установленном фокусе кнопка должна иметь на себе прямоугольник, выполненный пунктирной линией, и в неактивном состоянии кнопка должна резко отличаться по цвету (либо фона, либо надписи, либо и того, и другого).
Если вы хотите, чтобы ваша кнопка в точности повторяла поведение стандартных кнопок, то вам предстоит потрудиться. Для начала изучите собственно стандартные кнопки - как они ведут себя в реальной жизни. Причем имейте ввиду, что наиболее сложное их поведение наблюдается в Windows 2000. Если с момента создания окна диалога ни разу не была нажата клавиша TAB, то невзирая на то, что ваша кнопка получила фокус ввода, рамка фокуса не должна выводиться - об этом говорит установленный флаг ODS_NOFOCUSRECT, причем он может приходить вместе с флагом ODS_FOCUS ( счастливые обладатели Windows 2000 могут в этом убедиться, запустив демонстрационную программу, нажав кнопку при свернутом диалоге и затем, развернув диалог, понаблюдать за флагами в сообщениях ). Разумеется, приоритет флага запрета отрисовки фокуса выше, и пунктирный прямоугольник не рисуется. После первого нажатия TAB этот флаг перестает устанавливаться (в Windows 9x и Windows NT он вообще отсутствует). Практически та же картина наблюдается в Windows 2000 с подчеркиванием символа акселерации - до первого нажатия кнопки ALT кнопка получает флаг ODS_NOACCEL, что означает подавление подчеркивания акселератора. Легко можно видеть, что при желании полностью реализовать стандартное поведение кнопки нам придется приготовить достаточно большой комплект растровых изображений:
Вы, как разработчик, вправе принять решение, насколько точно вы будете следовать данной методике (я, к примеру, в демонстрационном приложении остановился на варианте без акселераторов :)). |
Выполняя указанные требования, мы можем подготовить пять битмапов (выпуклая/вдавленная с фокусом/без фокуса, неактивная), реализующих внешний вид каждого из состояний кнопки, и отрисовывать в нужный момент (вот где появляется необходимость знать текущее состояние кнопки) одно из них. В этом случае мы сами полностью контролируем внешний вид кнопки в каждом из состояний. Впечатление, которое вы произведете на пользователя, будет целиком зависеть от вашего вкуса и умения создавать растровые изображения.
Что касается кода, реализующего необходимую логику работы, то он может выглядеть примерно так:
BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { static HBITMAP hBm[BM_COUNT] = { NULL, NULL, NULL, NULL, NULL }; . . . case WM_INITDIALOG: . . . LoadBitmaps(hBm); . . . case WM_DRAWITEM: return DrawFreeStyleBtn( (LPDRAWITEMSTRUCT) lParam, hBm ); . . . } BOOL DrawFreeStyleBtn(LPDRAWITEMSTRUCT pis, HBITMAP* phBm) { if(IDC_BMPBTN == pis->CtlID) { HBITMAP hbm = phBm[BM_UP]; // по-умолчанию выпуклая без фокуса switch(pis->itemAction) { case ODA_SELECT: if( pis->itemState & ODS_SELECTED ) { if( pis->itemState & ODS_FOCUS #if(_WIN32_WINNT >= 0x0500) && !(pis->itemState & ODS_NOFOCUSRECT) // только W2000 #endif ) hbm = phBm[BM_DOWN_FOCUS]; // вдавленная с фокусом else hbm = phBm[BM_DOWN]; // вдавленная без фокуса } break; case ODA_DRAWENTIRE: if( pis->itemState & ODS_DISABLED ) hbm = phBm[BM_DISABLE]; // неактивная break; case ODA_FOCUS: if( pis->itemState & ODS_FOCUS ) hbm = phBm[BM_FOCUS]; // выпуклая с фокусом break; } // отрисуем внешний вид кнопки DrawState( pis->hDC, NULL, NULL, (LONG)hbm, 0, 0, 0, 0, 0, DST_BITMAP | DSS_NORMAL ); return TRUE; } return FALSE; } |
Как видим, ничего сложного. Код распадается на две части: в первой на основе сведений о выполняемых действиях (itemAction) и текущем состоянии кнопки (itemState) производится выбор необходимого битмапа, во второй части происходит вывод выбранного битмапа в контекст кнопки.
Ранний вариант приведенного выше кода содержал вместо вызова DrawState()
следующий фрагмент.
|
Внимательный читатель готов задать вопрос о том, что в самом начале упоминались не только механизмы (реализованные, как мы выяснили, через сообщения WM_MEASUREITEM и WM_DRAWITEM), но и API?
Действительно, имеется несколько функций, облегчающих придание стандартного вида OWNERDRAW-контролам. Разработчик готовит только основной битмап для кнопки, а для отрисовки границ и состояний кнопки (неактивное и в фокусе) пользуется функциями WinAPI - DrawEdge() (границы контрола - "выпуклый/вдавленный"), DrawState() (состояние "активный/неактивный") и DrawFocusRect() (состояние "в фокусе"). В таком случае вышеприведенный код примет вид:
BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { static HBITMAP hBm = NULL; . . . case WM_INITDIALOG: . . . LoadBitmap(&hBm); . . . case WM_DRAWITEM: return DrawClassicStyleBtn( (LPDRAWITEMSTRUCT) lParam, hBm ); . . . } BOOL DrawClassicStyleBtn(LPDRAWITEMSTRUCT pis, HBITMAP hBm, int deflate = 4) { UINT uState = DSS_NORMAL; // активная UINT uEdge = EDGE_RAISED; // выпуклая int x = 0, y = 0; BOOL bFocus = FALSE; // без фокуса RECT rFocus; if(IDC_BMPBTN == pis->CtlID) { switch(pis->itemAction) { case ODA_SELECT: if(pis->itemState & ODS_SELECTED) { x += 1; // смещение битмапа вниз-право y += 1; // создает эффект нажатия кнопки uEdge = EDGE_SUNKEN; // вдавленная граница } case ODA_DRAWENTIRE: if(pis->itemState & ODS_DISABLED) { uState = DSS_DISABLED; // неактивная } case ODA_FOCUS: if(pis->itemState & ODS_FOCUS) { memcpy(&rFocus, &pis->rcItem, sizeof(RECT)); rFocus.left += deflate; rFocus.top += deflate; rFocus.right -= deflate; rFocus.bottom -= deflate; bFocus = TRUE; // кнопка получила фокус #if(_WIN32_WINNT >= 0x0500) if( pis->itemState &ODS_NOFOCUSRECT ) // только W2000 bFocus = FALSE; #endif } break; } // отрисовка битмапа DrawState( pis->hDC, NULL, NULL, (LONG)hBm, 0, x, y, 0, 0, DST_BITMAP | uState); // отрисовка границы DrawEdge(pis->hDC, &pis->rcItem, uEdge, BF_RECT); // отрисовка прямоугольника фокуса if( bFocus ) DrawFocusRect(pis->hDC, &rFocus); return TRUE; } return FALSE; } |
Выигрыш подобного подхода состоит в меньшем использовании самостоятельно подготавливаемых ресурсов и меньшем их потреблении при работе программы. К недостаткам (и весьма заметным, на мой взгляд) можно отнести то, что происходит потеря контроля над внешним видом кнопки в различных ее состояниях. Впрочем, работа этих упомянутых функций ориентирована на поддержание стандартного внешнего вида контролов, поэтому и результат не очень выразителен. На мой взгляд, данная техника больше подходит к выполнению кнопок, имеющих в основном стандартный внешний вид, но снабженных небольшими изображениями по соседству с текстом кнопки.
Следует заметить, что при необходимости можно (а иногда и нужно) пользоваться комбинацией приведенных методик: предположим, использовать для отрисовки четыре (или больше) битмапа, но границу рисовать функцией DrawEdge(). |
Автор благодарит Александра Шаргина за информацию об обнаружении эффекта отсутствия WM_MEASUREITEM для ownerdrawn-кнопок.
Сообщений 4 Оценка 45 Оценить |