Оценка 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-кнопок.
Оценка 45 Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|