Visual Basic, .NET, ASP, VBScript
 

   
 

Родился я 07 ноября 1974 года. Как и все нормальные люди закончил среднюю школу, а потом (уже не как все) пошел учиться в институт. С выбором профессии я явно ошибся ;-) и поступил учиться в сельскохозяйственный институт на технолога сельскохозяйственного производства. Примерно на втором курсе я понял, что жестоко ошибся, но бросать учебу не стал и закончил учебу с дипломом с отличием. Не много думая сразу пошел на второе высшее образование в академию Государственной службы на факультет экономики и, через 3 года, стал дипломированным экономистом со специализацией финансовый менеджмент. Так у меня стало два диплома.
А как же программирование, спросите Вы. Это мое хобби, программирую на VB, пишу сайты на HTML и ASP (посмотрите внимательнее на этот сайт). Могу делать базы данных Access, да и вообще на все руки мастер ;-)).

 
     
   
 

Введение

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

 
     

   
   
     
  VBNet рекомендует