Як зробити нестандартну кнопку на основі бітмапами

Кнопка не обов'язково повинна мати стандартний зовнішній вигляд (хоча особисто я не знаходжу зовнішній вигляд стандартної кнопки нудним або "простацькі"). Однак для багатьох розробників і користувачів кнопки, що мають нестандартний вид, виглядають більш привабливими. Тому для додання якогось стилю інтерфейсу власних програм можна використовувати кнопки, що відображають певний бітмапами (bitmap - растрове зображення).

Windows має вбудовані механізми і API, що підтримують створення кнопок (а також і інших контролів), що мають нестандартний зовнішній вигляд. Спосіб відтворення зовнішнього вигляду контрола залежить від його стилю. В даному випадку, стиль, потрібний нам - це BS_OWNERDRAW. З його назви видно, що отрисовку виду кнопки виконує код користувача, поміщений в віконну (диалоговую) функцію вікна-власника контрола.

Розглянемо основні етапи відтворення контрола, що має стиль xx_OWNERDRAW.

  1. Батьківському вікну контрола приходить повідомлення WM_MEASUREITEM. в якому передається покажчик на структуру MEASUREITEMSTRUCT через параметр lParam. Оброблювач повідомлення повинен встановити значення полів itemWidth і itemHeight структури так, щоб вони містили ширину і висоту контрола відповідно. Якщо ми обробили повідомлення, обробник повинен повернути значення TRUE з віконної процедури. Це повідомлення приходить власнику один раз при створенні контрола.
  2. Кожен раз при необхідності перемалювати контрол його власнику приходить повідомлення WM_DRAWITEM. Параметр lParam повідомлення містить покажчик на структуру DRAWITEMSTRUCT. підготовлену системою. У завдання даного повідомлення входить надання контексту, в якому буде відбуватися отрисовка контрола. Хендл контексту супроводжує додаткова інформація про внутрішній стан контрола, необхідна (можливо) для зміни його зовнішнього вигляду, а також інформація про вид дії, виробленого в даний момент з контролом. Далі ми побачимо, яким чином ця інформація може бути використана для зміни зовнішнього вигляду кнопки. І, знову-таки, якщо ми обробляємо дане повідомлення, обробник зобов'язаний повернути з віконної процедури значення TRUE.

Оскільки ми реалізуємо, хоча і самостійно отрісовиваємих, але все ж кнопку, то було б непогано, якби вона мала поведінка звичайної кнопки - краю кнопки в нормальному стані повинні імітувати опуклий контрол, при натиснутому стані - втиснутий, при встановленому фокусі кнопка повинна мати на собі прямокутник, виконаний пунктирною лінією, і в неактивному стані кнопка повинна різко відрізнятися за кольором (або фону, або написи, або і того, і іншого).

Легко можна бачити, що при бажанні повністю реалізувати стандартну поведінку кнопки нам доведеться приготувати досить великий комплект растрових зображень:
  • опукла без фокусу і акселератора
  • опукла з фокусом без акселератора
  • опукла без фокусу з акселератором
  • опукла з фокусом і акселератором
  • втиснута без фокусу і акселератора
  • втиснута з фокусом без акселератора
  • втиснута без фокусу з акселератором
  • втиснута з фокусом і акселератором
  • неактивна

Ви, як розробник, має право прийняти рішення, наскільки точно ви будете слідувати цій методиці (я, наприклад, в демонстраційному додатку зупинився на варіанті без акселераторів :)).

Виконуючи зазначені вимоги, ми можемо підготувати п'ять бітмапами (опукла / втиснута з фокусом / без фокусу, неактивна), що реалізують зовнішній вигляд кожного з станів кнопки, і малювати в потрібний момент (ось де з'являється необхідність знати поточний стан кнопки) одне з них. В цьому випадку ми самі повністю контролюємо зовнішній вигляд кнопки в кожному з станів. Враження, яке ви справите на користувача, буде цілком залежати від вашого смаку і вміння створювати растрові зображення.

Що стосується коду, що реалізує необхідну логіку роботи, то він може виглядати приблизно так:

Як бачимо, нічого складного. Код розпадається на дві частини: у першій на основі відомостей про виконувані дії (itemAction) і поточний стан кнопки (itemState) проводиться вибір необхідного бітмапами, у другій частині відбувається відображення вибраного бітмапами в контекст кнопки.

Ранній варіант наведеного вище коду містив замість виклику DrawState () наступний фрагмент.

На мій погляд обидва варіанти рівноцінні з точки зору функціональності, але все ж у фрагменті з BitBlt () більше можливостей припуститися помилки, що приводить до витоку ресурсів.

Код обрамляється перевіркою на необхідний ідентифікатор контрола, оскільки в робочій програмі подібних контролів може бути кілька.

Уважний читач готовий поставити запитання про те, що на самому початку згадувалися не тільки механізми (реалізовані, як ми з'ясували, через повідомлення WM_MEASUREITEM і WM_DRAWITEM), але і API?

Дійсно, є кілька функцій, що полегшують надання стандартного виду OWNERDRAW-контроль. Розробник готує тільки основний бітмапами для кнопки, а для відтворення кордонів і станів кнопки (неактивний і в фокусі) користується функціями WinAPI - DrawEdge () (кордону контрола - "опуклий / втиснутий"), DrawState () (стан "активний / неактивний") і DrawFocusRect () (стан "у фокусі"). В такому випадку вищенаведений код набуде вигляду:

Виграш подібного підходу полягає в меншому використанні самостійно підготовлених ресурсів і меншому їх споживанні при роботі програми. До недоліків (і досить помітним, на мій погляд) можна віднести те, що відбувається втрата контролю над зовнішнім виглядом кнопки в різних її станах. Втім, робота цих згаданих функцій орієнтована на підтримку стандартного зовнішнього вигляду контролів, тому і результат не дуже виразний. На мій погляд, дана техніка більше підходить до виконання кнопок, що мають в основному стандартний зовнішній вигляд, але забезпечених невеликими зображеннями по сусідству з текстом кнопки.

Слід зауважити, що при необхідності можна (а іноді і потрібно) користуватися комбінацією наведених методик: припустимо, використовувати для відтворення чотири (або більше) бітмапами, але кордон малювати функцією DrawEdge ().

Схожі статті