Разделы
Главная

Windows API
Реестр
MS Access
Visual Basic
Download
Форум
Гостевая книга
Ссылки

E-mail


Партнерская программа WWW.PORTA.RU (on-line магазин портативной аудио, видео, фототехники). Самая щедрая, самая честная, самая популярная. Приглашаем всех веб-мастеров к участию!



Submitter.ru - Free promoting
Visual Basic

вернуться назад Техника программирования сложных окон в Visual Basic

   Автор: Шатрыкин Иван, VBNet

   Введение

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

   Теоретические основы

   Для начала давайте разберемся, как это можно сделать теоретически. Из документации Windows видно, что каждое окно в системе описывается множеством параметров, из которых нас с Вами интересует "видимая область окна". Видимая область окна в системе, создаваемое Visual Basic "по умолчанию" имеет вид прямоугольника, но, в принципе, ничто не мешает изменить форму этой области. Данная область окна описывается с помощью специального объекта, который называется Region. Регион можно представить в виде поверхности, ограниченной координатами, описываемыми угловые точки этой области. Проще говоря, можно описать область любой формы, затем создать из неё, с помощью специальных функций, регион и "прикрепить" его к нужному нам окну.
   Существует несколько функций Windows API для создания регионов, основными из которых являются следующие:

CombineRgn             Комбинирует два региона между собой
CreateEllipticRgn      Создает регион в виде эллипса или окружности
CreatePolygonRgn       Создает регион в виде многоугольника
CreateRectRgn          Создает прямоугольный регион
CreateRoundRectRgn     Создает регион со скругленными краями из прямоугольной области
SetWindowRgn           Прикрепляет регион к указанному окну

   Я не буду приводить подробное описание этих функций, так как его можно найти в описании 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. Такие алгоритмы существуют, но это тема отдельного разговора, скажу только, что для оптимизации работы алгоритм пишется отдельно для каждой глубины цвета и при применении такой схемы быстродействие увеличивается почти в четыре раза и позволяет делать практически любую анимацию.