Кнопка не обов'язково повинна мати стандартний зовнішній вигляд (хоча особисто я не знаходжу зовнішній вигляд стандартної кнопки нудним або "простацькі"). Однак для багатьох розробників і користувачів кнопки, що мають нестандартний вид, виглядають більш привабливими. Тому для додання якогось стилю інтерфейсу власних програм можна використовувати кнопки, що відображають певний бітмапами (bitmap - растрове зображення).
Windows має вбудовані механізми і API, що підтримують створення кнопок (а також і інших контролів), що мають нестандартний зовнішній вигляд. Спосіб відтворення зовнішнього вигляду контрола залежить від його стилю. В даному випадку, стиль, потрібний нам - це BS_OWNERDRAW. З його назви видно, що отрисовку виду кнопки виконує код користувача, поміщений в віконну (диалоговую) функцію вікна-власника контрола.
Розглянемо основні етапи відтворення контрола, що має стиль xx_OWNERDRAW.
- Батьківському вікну контрола приходить повідомлення WM_MEASUREITEM. в якому передається покажчик на структуру MEASUREITEMSTRUCT через параметр lParam. Оброблювач повідомлення повинен встановити значення полів itemWidth і itemHeight структури так, щоб вони містили ширину і висоту контрола відповідно. Якщо ми обробили повідомлення, обробник повинен повернути значення TRUE з віконної процедури. Це повідомлення приходить власнику один раз при створенні контрола.
- Кожен раз при необхідності перемалювати контрол його власнику приходить повідомлення WM_DRAWITEM. Параметр lParam повідомлення містить покажчик на структуру DRAWITEMSTRUCT. підготовлену системою. У завдання даного повідомлення входить надання контексту, в якому буде відбуватися отрисовка контрола. Хендл контексту супроводжує додаткова інформація про внутрішній стан контрола, необхідна (можливо) для зміни його зовнішнього вигляду, а також інформація про вид дії, виробленого в даний момент з контролом. Далі ми побачимо, яким чином ця інформація може бути використана для зміни зовнішнього вигляду кнопки. І, знову-таки, якщо ми обробляємо дане повідомлення, обробник зобов'язаний повернути з віконної процедури значення TRUE.
Оскільки ми реалізуємо, хоча і самостійно отрісовиваємих, але все ж кнопку, то було б непогано, якби вона мала поведінка звичайної кнопки - краю кнопки в нормальному стані повинні імітувати опуклий контрол, при натиснутому стані - втиснутий, при встановленому фокусі кнопка повинна мати на собі прямокутник, виконаний пунктирною лінією, і в неактивному стані кнопка повинна різко відрізнятися за кольором (або фону, або написи, або і того, і іншого).
Легко можна бачити, що при бажанні повністю реалізувати стандартну поведінку кнопки нам доведеться приготувати досить великий комплект растрових зображень:- опукла без фокусу і акселератора
- опукла з фокусом без акселератора
- опукла без фокусу з акселератором
- опукла з фокусом і акселератором
- втиснута без фокусу і акселератора
- втиснута з фокусом без акселератора
- втиснута без фокусу з акселератором
- втиснута з фокусом і акселератором
- неактивна
Ви, як розробник, має право прийняти рішення, наскільки точно ви будете слідувати цій методиці (я, наприклад, в демонстраційному додатку зупинився на варіанті без акселераторів :)).
Виконуючи зазначені вимоги, ми можемо підготувати п'ять бітмапами (опукла / втиснута з фокусом / без фокусу, неактивна), що реалізують зовнішній вигляд кожного з станів кнопки, і малювати в потрібний момент (ось де з'являється необхідність знати поточний стан кнопки) одне з них. В цьому випадку ми самі повністю контролюємо зовнішній вигляд кнопки в кожному з станів. Враження, яке ви справите на користувача, буде цілком залежати від вашого смаку і вміння створювати растрові зображення.
Що стосується коду, що реалізує необхідну логіку роботи, то він може виглядати приблизно так:
Як бачимо, нічого складного. Код розпадається на дві частини: у першій на основі відомостей про виконувані дії (itemAction) і поточний стан кнопки (itemState) проводиться вибір необхідного бітмапами, у другій частині відбувається відображення вибраного бітмапами в контекст кнопки.
Ранній варіант наведеного вище коду містив замість виклику DrawState () наступний фрагмент.
На мій погляд обидва варіанти рівноцінні з точки зору функціональності, але все ж у фрагменті з BitBlt () більше можливостей припуститися помилки, що приводить до витоку ресурсів.
Код обрамляється перевіркою на необхідний ідентифікатор контрола, оскільки в робочій програмі подібних контролів може бути кілька.
Уважний читач готовий поставити запитання про те, що на самому початку згадувалися не тільки механізми (реалізовані, як ми з'ясували, через повідомлення WM_MEASUREITEM і WM_DRAWITEM), але і API?
Дійсно, є кілька функцій, що полегшують надання стандартного виду OWNERDRAW-контроль. Розробник готує тільки основний бітмапами для кнопки, а для відтворення кордонів і станів кнопки (неактивний і в фокусі) користується функціями WinAPI - DrawEdge () (кордону контрола - "опуклий / втиснутий"), DrawState () (стан "активний / неактивний") і DrawFocusRect () (стан "у фокусі"). В такому випадку вищенаведений код набуде вигляду:
Виграш подібного підходу полягає в меншому використанні самостійно підготовлених ресурсів і меншому їх споживанні при роботі програми. До недоліків (і досить помітним, на мій погляд) можна віднести те, що відбувається втрата контролю над зовнішнім виглядом кнопки в різних її станах. Втім, робота цих згаданих функцій орієнтована на підтримку стандартного зовнішнього вигляду контролів, тому і результат не дуже виразний. На мій погляд, дана техніка більше підходить до виконання кнопок, що мають в основному стандартний зовнішній вигляд, але забезпечених невеликими зображеннями по сусідству з текстом кнопки.
Слід зауважити, що при необхідності можна (а іноді і потрібно) користуватися комбінацією наведених методик: припустимо, використовувати для відтворення чотири (або більше) бітмапами, але кордон малювати функцією DrawEdge ().