Статьи

Опыт создания "вырезанных" форм.

Часть 1

О вырезанных формах писалось уже неоднократно. Во многих публикациях предлагались различные варианты их создания. Однако, тема по-прежнему остается актуальной и по электронной почте продолжают приходить письма с просьбой осветить все возможные аспекты работы. Учитывая повторяемость вопросов, я создал пример, реализующий некоторые подходы для решения этой проблемы. Хочу оговориться сразу, что представленные коды - "не есть мое изобретение", я только попытался их упорядочить. И в конце этого короткого вступления хочется поблагодарить Ивана Шатрыкина, за оказанную помощь.

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

На второй форме не будем размещать ничего, а только установим параметры, чтобы не засорять пример дополнительными строками кода.
Name = frmDemonstration, AutoRedraw = True, BackColor = &H00000000& (черный цвет, для лучшей визуализации), BorderStyle = 0 (None), ScaleMode = 3 (Pixel - все API-функции работают только с пикселами).
Модуль будем использовать для объявления API-функций и глобальных функций.

Сразу же предусмотрим перемещение и закрытие демонстрационной формы.
Для перемещения объявим в модуле API-функции и пару констант к ним.

Public Const HTCAPTION = 2
Public Const WM_NCLBUTTONDOWN = &HA1
Public Declare Function ReleaseCapture Lib "user32" _
    () As Long
Public Declare Function SendMessage Lib "user32" _
    Alias "SendMessageA" _
    (ByVal hwnd As Long, _
    ByVal wMsg As Long, _
    ByVal wParam As Long, _
    lParam As Any) _
    As Long

А в форме напишем пару процедур, отвечающих за эти действия.

Private Sub Form_DblClick()
    Unload Me
End Sub

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, _
   
X As Single, Y As Single)
    
    Select Case Button
    Case vbLeftButton
        Call ReleaseCapture
        Call SendMessage(Me.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&)
    End Select
End Sub

Собственно говоря, это все коды для демонстрационной формы. Все остальные мы будем  оформлять в основной форме (с радиокнопками).
Маленькая функция, определяющая какая радиокнопка была выбрана, будет потом использоваться в процедуре нажатия кнопки для вывода демонстрационной формы.

Private Function FindOptionButton() As Integer
Dim i%
    For i = 0 To optSelect.Count - 1
        If optSelect(i).Value = True Then Exit For
    Next
    
    FindOptionButton = i
End Function

Прежде чем перейти к дальнейшему описанию кодов давайте уясним, как работает сам принцип "вырезания" формы. Вначале мы должны создать регион - массив точек, по которым и будет производиться "вырезание". Именно здесь и используются различные API-функции, определяющие итоговую фигуру. На них мы остановимся подробнее чуть ниже. После того как регион получен, с помощью API-функции SetWindowRgn (кстати, не забудьте внести ее в модуль) и производим непосредственное "вырезание" формы. В конце удаляем созданный нами регион, освобождая память.

Private Sub cmdPreview_Click()
Dim hRgn As Long

    With frmDemonstration
        hRgn = ...
        SetWindowRgn .hwnd, hRgn, True
        DeleteObject hRgn
    
        .Show vbModal
    End With
End Sub

Итак, настала пора заняться регионами. Указанные API-функции необходимо объявлять в модуле. Воспользуйтесь для их правильного написания Программой API Text Viewer (меню: Пуск\Программы\Microsoft Visual Studio 6.0\Microsoft Visual Studio 6.0 Tools\API Text Viewer).
Для начала "вырежем" обычный прямоугольник. Для этого используется функция
Public Declare Function CreateRectRgn Lib "gdi32" _
    (ByVal X1 As Long, _
    ByVal Y1 As Long, _
    ByVal X2 As Long, _
    ByVal Y2 As Long) _
    As Long

где X1 и Y1 координаты верхнего левого угла квадрата, а X2 и Y2 - нижнего правого. Внесем изменения в процедуру cmdPreview_Click, вырезав прямоугольник с размерами сторон в 2 раза меньше формы:

Private Sub cmdPreview_Click()
Dim hRgn As Long

    With frmDemonstration
    
    Select Case FindOptionButton
    Case 0
        hRgn = CreateRectRgn(.ScaleWidth * 0.25, .ScaleHeight * 0.25, _
            .ScaleWidth * 0.75, .ScaleHeight * 0.75)
    End Select
    
    SetWindowRgn .hwnd, hRgn, True
    DeleteObject hRgn
    
    .Show vbModal
    End With
End Sub

Запустим на проверку и получим "вырезанный" черный прямоугольник, который можно перемещать по экрану и закрыть двойным щелчком.

Так же легко как прямоугольник, мы можем получить и овальную форму. Для этого используется другая API-функция, но параметры те же, что и в предыдущей. В итоге получим овал, с размерами по основным осям, равным высоте и ширине формы:
Public Declare Function CreateEllipticRgn Lib "gdi32" _
    (ByVal X1 As Long, _
    ByVal Y1 As Long, _
    ByVal X2 As Long, _
    ByVal Y2 As Long) _
    As Long

и в форме:

    ...
    Select Case
FindOptionButton
    ...
    Case 3
        hRgn = CreateEllipticRgn(0, 0, .ScaleWidth, .ScaleHeight)
    End Select
    ...

Аналогичным образом можно получить прямоугольник с закругленными краями. Естественно, для этого используется своя API-функция:
Public Declare Function CreateRoundRectRgn Lib "gdi32" _
    (ByVal X1 As Long, _
    ByVal Y1 As Long, _
    ByVal X2 As Long, _
    ByVal Y2 As Long, _
    ByVal X3 As Long, _
    ByVal Y3 As Long) _
    As Long

Как Вы обратили, наверное, внимание, данная функция имеет два дополнительных параметра: X3 и Y3, которые отвечают за начало и конец закругления по горизонтальной и вертикальной осям левого верхнего угла. Остальные углы автоматически делаются симметричными этому.

    ...
    Select Case
FindOptionButton
    ...
    Case 2
        hRgn = CreateRoundRectRgn(0, 0, .ScaleWidth, .ScaleHeight, _
            .ScaleWidth / 3 * 2, .ScaleHeight / 5 * 3)
    End Select
    ...

Прямоугольник и овал можно получить так же и с помощью двух других API-функций. В ряде ситуаций ими бывает пользоваться удобней.
Public Declare Function CreateRectRgnIndirect Lib "gdi32" _
    (lpRect As RECT) _
    As Long
Public Declare Function CreateEllipticRgnIndirect Lib "gdi32" _
    (lpRect As RECT) _
    As Long

Здесь, в качестве параметра, используется структура RECT, которую необходимо объявить в разделе деклараций модуля. Кроме того, добавим еще одну API-функцию для более легкого получения переменной с этой структурой:
Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Public Declare Function SetRect Lib "user32" _
    (lpRect As RECT, _
    ByVal X1 As Long, _
    ByVal Y1 As Long, _
    ByVal X2 As Long, _
    ByVal Y2 As Long) _
    As Long

X1, Y1 - передаваемые координаты левого верхнего угла; X2, Y2 - передаваемые координаты правого нижнего угла; а lpRect - принимаемое значение этих координат в формате типа RECT.
Соответственно, в процедуре вывода демонстрационной формы внесем дополнения:

Private Sub cmdPreview_Click()
Dim hRgn As Long
Dim R As RECT

    With frmDemonstration
    
    Select Case FindOptionButton
    ...
    Case 1
        SetRect R, .ScaleWidth * 0.25, .ScaleHeight * 0.25, _
            .ScaleWidth * 0.75, .ScaleHeight * 0.75
        hRgn = CreateRectRgnIndirect(R)
    ...
    Case 4
        SetRect R, 0, 0, .ScaleWidth, .ScaleHeight
        hRgn = CreateEllipticRgnIndirect(R)
    ...
    End Select
    
    SetWindowRgn .hwnd, hRgn, True
    DeleteObject hRgn
    
    .Show vbModal
    End With
End Sub

В результате получим те же фигуры, но другим способом

На закуску, в Части 1, напишем коды работы с вырезанием формы неправильной (или, если больше нравится, многоугольной) конфигурации. Здесь так же используется своя API-функция
Public Declare Function CreatePolygonRgn Lib "gdi32.dll" _
    (lpPoint As POINTAPI, _
    ByVal nCount As Long, _
    ByVal nPolyFillMode As Long) _
    As Long

Естественно, прежде чем объявить эту функцию необходимо объявить тип POINTAPI, отвечающий за координаты каждой точки
Type POINTAPI
    X As Long
    Y As Long
End Type

nCount - определяет, сколько точек мы используем для вырезания данной фигуры, последний параметр - nPolyFillMode - определяет способ заполнения региона для каждого пиксела (в нашем случае этот параметр равен 0)- Для того чтобы создать регион неправильной формы вначале, в разделе деклараций формы, объявим массив с типом POINTAPI, и напишем маленькую процедуру заполнения этого массива значениями, учитывающими размеры формы, для получения шестиугольника:

Private Sub CreateRgnContur()
    With frmDemonstration
        rgnPts(1).X = .ScaleWidth * 0.3
        rgnPts(1).Y = .ScaleHeight * 0
        rgnPts(2).X = .ScaleWidth * 0.7
        rgnPts(2).Y = .ScaleHeight * 0
        rgnPts(3).X = .ScaleWidth * 1
        rgnPts(3).Y = .ScaleHeight * 0.5
        rgnPts(4).X = .ScaleWidth * 0.7
        rgnPts(4).Y = .ScaleHeight * 1
        rgnPts(5).X = .ScaleWidth * 0.3
        rgnPts(5).Y = .ScaleHeight * 1
        rgnPts(6).X = .ScaleWidth * 0
        rgnPts(6).Y = .ScaleHeight * 0.5
    End With
End Sub

И наконец, в основной процедуре:

Private Sub cmdPreview_Click()
Dim hRgn As Long
Dim R As RECT

    With frmDemonstration
    
    Select Case FindOptionButton
    ...
    Case 5
        Dim numAngle As Integer
        numAngle = 6
        ReDim rgnPts(1 To numAngle) As POINTAPI
        CreateRgnContur
        hRgn = CreatePolygonRgn(rgnPts(1), numAngle, 0)
    ...
    End Select
    
    SetWindowRgn .hwnd, hRgn, True
    DeleteObject hRgn
    
    .Show vbModal
    
    End With
End Sub

И еще пару слов об этом методе вырезания формы. Вроде бы те методы, которые будут описаны в Части 2 (работа с bmp-файлами), более эффектно выглядят на экране, но не торопитесь сбрасывать со счетов последний вариант. Во-первых, он работает значительно быстрее (точек-то меньше); и, во-вторых, данный метод позволяет масштабировать форму, что с картинками сделать значительно труднее без потери их качества. А кроме всего прочего, никто не мешает Вам с помощью графических методов раскрасить "вырезанную" форму. Из недостатков данного метода, конечно же, надо отметить сложность рассчетов координат каждой точки. Однако безвыходных ситуаций не бывает. В свое время я написал Add-In, упрощающий подсчет координат до простого кликанья по точкам на картинке. Если кому-то данный Add-In срочно необходим :) - можете скачать с моей странички (MapGraf). 


Назад

Продолжение статьи

Скачать пример

Hosted by uCoz