Техніка програмування складних вікон у Visual Basic

Mного з Вас напевно бачили в Windows програмах вікна нестандартної форми (круглі, трикутні і т.д.) і ставили собі питання: як мені зробити таке вікно? Якщо прочитати документацію по Visual Basic, то можна зробити висновок, що стандартні засоби мови не надають такої можливості. А що ж робити, якщо дуже хочеться? Тоді слід згадати, що в розпорядженні програміста на VB є ще й Windows API, який повинен нам у цьому допомогти.

Теоретичні основи


Для початку давайте розберемося, як це можна зробити теоретично. З документації Windows видно, що кожне вікно в системі описується безліччю параметрів, з яких нас з Вами цікавить <видима область вікна>. Видима область вікна в системі, що створюється Visual Basic <за замовчуванням> має вигляд прямокутника, але, в принципі, ніщо не заважає змінити форму цієї області. Дана область вікна описується за допомогою спеціального об'єкта, який називається Region. Регіон можна представити у вигляді поверхні, обмеженої координатами, описуваними кутові точки цієї області. Простіше кажучи, можна описати область будь-якої форми, потім створити з неї, за допомогою спеціальних функцій, регіон і <прикріпити> його до нужому нам вікна.

Існує декілька функцій Windows API для створення регіонів, основними з яких є наступні:


Я не буду наводити докладний опис цих функцій, так як його можна знайти в описі Win32 API. Крім цих функцій існують ще кілька функцій для роботи з регіонами, але нам вони не потрібні.

Створення простих нестандартних вікон


Тепер, коли нам відомі основні функції, для створення регіонів, ми можемо застосувати отримані знання на практиці. Завантажте проект pTestRgn і уважно вивчіть його код. У цьому проет, для зміни форми вікна на овальну, використовується всього три рядки коду і три функції Win32 API. Спочатку за допомогою CreateEllipticRgn створюється регіон, потім він прикріплюється до вікна і, нарешті, завершальна фаза видалення, став непотрібним, створеного нами регіону. Якщо ж Ви не видаліть непотрібний Вам більше об'єкт, то Windows, створивши регіон для Вас буде зберігати його у своїх <надрах> і чекати подальших вказівок за його використання. Загалом, недобре <захаращувати> виділену пам'ять, і наздожене Вас кара небесна, і затягнеться небо хмарами синіми, і буде страшний суд над усіма невіруючими: Коротше код виглядає так:

Private Sub cmbCreateOval_Click()
Dim lRgn As Long
lRgn = CreateEllipticRgn (0, 0, Me.ScaleWidth / Screen.TwipsPerPixelX, _
Me.ScaleHeight / Screen.TwipsPerPixelY)
SetWindowRgn Me.hwnd, lRgn, True
DeleteObject lRgn
End Sub

Так само все просто, скажете Ви? Так, на перший погляд все дуже просто, але це тільки здається. Той приклад, який Ви тільки що бачили, майже не має практичного застосування в справжніх додатках Windows. Кому ж потрібно просто овальне вікно, яке до того ж жорстко задається на етапі програмування? А ось вікно, яке вільно могло б міняти свою форму цілком може знадобитися. Приклади? Будь ласка, WinAmp, Помічник в Microsoft Office та інші програми. Як же там все це реалізовано? Давайте розберемося з таким застосуванням регіонів.

Створення складних нестандартних вікон


Припустимо, що у нас є малюнок в BMP форматі, з якого потрібно зробити форму, а білий колір (наприклад) на ньому означає <порожнечу>. Як же зробити форму? Дуже просто, потрібно взяти все <не білі> пікселі на малюнку, створити з їх координат регіон і прикріпити його до потрібного нам вікна. Аналізувати пікселі можна GetPixel, ця функція за координатами повертає його колір. Давайте тепер напишемо такий алгоритм для аналізу BMP матриці. Я думаю, що такий алгоритм Вам відомий, і ми не будемо його докладно розбирати, зазначу тільки, що аналіз проводиться порядково і Pixel-і додаються в регіон не по одному, а групами порядково. Такий підхід сильно економить ресурси процесора, виграш в продуктивності досягає 100%.

Public Function lGetRegion (pic As PictureBox, lBackColor As Long) As Long
Dim lRgn As Long
Dim lSkinRgn As Long
Dim lStart As Long
Dim lX As Long
Dim lY As Long
Dim lHeight As Long
Dim lWidth As Long

"Створюємо порожній регіон, з якого почнемо роботу
lSkinRgn = CreateRectRgn(0, 0, 0, 0)
With pic
"Підрахуємо розміри малюнка в Pixel
lHeight = .Height / Screen.TwipsPerPixelY
lWidth = .Width / Screen.TwipsPerPixelX
For lX = 0 To lHeight – 1
lY = 0
Do While lY < lWidth
"Шукаємо потрібний Pixel
Do While lY <lWidth And GetPixel (. HDC, lY, lX) = lBackColor
lY = lY + 1
Loop
If lY < lWidth Then
lStart = lY
Do While lY <lWidth And GetPixel (. HDC, lY, lX) <> lBackColor
lY = lY + 1
Loop
If lY > lWidth Then lY = lWidth
"Потрібний Pixel знайдений, додамо його в регіон
lRgn = CreateRectRgn(lStart, lX, lY, lX + 1)
CombineRgn lSkinRgn, lSkinRgn, lRgn, RGN_OR
DeleteObject lRgn
End If
Loop
Next
End With
lGetRegion = lSkinRgn
End Function


Отже, для перевірки на практиці цього алгоритму завантажте приклад pTestRgnSkin і уважно вивчіть його код. У цьому проекті потрібний нам малюнок, для зручності, <зашитий> у файлі ресурсів, крім того проект запускається процедурою Main, в якій і відбуваються всі перетворення. Спочатку завантажується форма, потім у PictureBox з ресурсів завантажується потрібний нам малюнок, далі викликається функція, яка створює регіон і, нарешті, завершальний етап – прикріплення регіону до потрібного нам вікна. Для зручності тут же викликається функція, поміщаються вікно <поверх всіх>, щоб воно "не загубилося> у Вас на робочому столі Windows. Крім того, для нормальної роботи програми необхідно, щоб для PictureBox властивість AutoRedraw був встановлений в True, інакше нічого не вийде.

Sub Main()
Dim lRgn As Long
Load frmTestRgnSkin
frmTestRgnSkin.pic.Picture = LoadResPicture (101, vbResBitmap)
lRgn = lGetRegion(frmTestRgnSkin.pic, vbWhite)
SetWindowRgn frmTestRgnSkin.hWnd, lRgn, True
DeleteObject lRgn
frmTestRgnSkin.Show
SetFormPosition frmTestRgnSkin.hWnd, True
End Sub

Тепер можна запускати проект … О, знайоме обличчя, скажіть Ви, це ж <Ськрепиш> з Microsoft Office. Так, схожий, але не зовсім, <Ськрепиш> рухається, а цей ні. Що ж потрібно зробити, щоб це вікно динамічно змінювало свою форму по малюнку, псевдонімом в даний момент часу в PictureBox?

Динамічна зміна форми вікна


Існують програми в яких необхідно динамічно під час роботи змінювати форму вікна (наприклад анімований персонаж з Microsoft Office). Все це не дуже складно реалізувати, потрібно в подія PictureBox.Change додати наступний код:

lRgn = lGetRegion(frmTestRgnSkin.pic, vbWhite)
SetWindowRgn frmTestRgnSkin.hWnd, lRgn, True
DeleteObject lRgn
SetFormPosition frmTestRgnSkin.hWnd, True

У принципі все готово, залишилося тільки додати код для зміни картинки на формі, і <Ськрепиш> оживе. У нашому прикладі змінювати малюнок будемо в Timer циклічно, тобто анімація буде неперервна, так простіше. Отже, додамо на форму Timer і помістимо <в нього> невеликий код, який відповідає за зміни малюнка в PictureBox. Малюнків у файлі ресурсів десять штук, тому I повинно змінюватися від 101 до 110. Код зміни виглядає так:

Static i As Long
If i < 101 Then i = 101
If i > 110 Then i = 101
frmAnimateForm.pic.Picture = LoadResPicture(i, vbResBitmap)
i = i + 1

Готово, можна запускати проект, і якщо Ви щасливий володар Pentium III або Athlon, то Вам посміхнеться удача, так як <Ськрепиш> буде рухатися. Але якщо Ваш процесор Pentium II і нижче, то комп'ютер не зможе виконувати необхідні розрахунки за потрібне нам час, так як для плавної анімації необхідно (для нашого випадку) показувати близько 15 кадрів в секунду, а точніше кожні 80 мілісекунд по кадру і ще залишати час для інших завдань комп'ютера. Як ми бачимо наші алгоритми явно не тягнуть для таких завдань і призначені для <робіт> не вимагають таких швидких змін форми вікна, так як, наприклад на Celeron 333 один кадр формується близько 100 мілісекунд. Що ж робити?

Оптимізація алгоритму для швидкої анімації


Аналіз роботи алгоритму показує, що найбільші витрати часу припадають на функцію GetPixel. Це відбувається тому, що аналіз картинки йде безпосередньо на екрані. Єдиний шлях збільшення швидкодії алгоритму, це перенесення аналізу в пам'ять комп'ютера і використання при цьому Win 32 API. Такі алгоритми існують, але це тема окремої розмови, скажу тільки, що для оптимізації роботи алгоритм пишеться окремо для кожної глибини кольору і при застосуванні такої схеми швидкодія збільшується майже в чотири рази і дозволяє робити практично будь-яку анімацію.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*