[an error occurred while processing this directive]
[an error occurred while processing this directive]


Создание нестандартных окон

В Visual Basic очень легко создавать простые виндосовские окна! Но ведь они такие прямоугольные серые и некрасивые... К сожалению в VB нет интегрированных методов решения этой проблемы и нам приходится прибегать к помощи API-функций. В этой статье я покажу почти все возможности использования регионов: регионы любой формы, регионы с загруглёнными углами и комбинирование регионов! Итак, начнём!

[Окна любых форм]

1.Конечно же нужно создать стандартный EXE-проект. Теперь создайте единственный на форме объект Image. Назовём его stern.

2.Так, начнём с создания регионов любой формы. Мне кажется красивее будет если на форме будет рисунок, вокруг которого мы и "отрежем" лишнее. Поэтому запустим Paint, аттрибуты рисунка установим 249 по высоте и 313 по ширине. Теперь нарисуем самый угловатый рисунок - звёздочку (как у октябрят :)) Кто очень талантливый может посерёдке нарисовать вoждя мирового пролетариата - на регионы это никак не повлияет :)))!

3.Сохраните рисунок. Теперь объекту image по имени stern присвоим этот рисунок. 

4.Присвоили? Теперь сдвиньте его подальше вниз за пределы формы, чтобы он не мешался, а размеры формы установите равными: Width = 4710, Height = 3780. В свойстве BorderStyle установите флажок 0-None. Свойство AutoRedraw установите равным True. Готово! Теперь приступим к созданию!

5.Сделайте двойной клик по форме и впишите в раздел General Declarations:

Private Declare Function SetWindowRgn Lib "user32" (ByVal hwnd _
As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean) As Long

Private Declare Function CreatePolygonRgn Lib "gdi32" (lpPoint _
As POINTAPI, ByVal nCount As Long, ByVal nPolyFillMode As Long) As Long

Private Type POINTAPI
    X As Long
    Y As Long
End Type

Dim P(10) As POINTAPI

Теперь объясню зачем это. Первая API-функция SetWindowRgn так сказать накладывает созданный регион на окно. Те части которые остались за пределами региона исчезают. Знаете с чем это можно сравнить? Есть такие специальные жестянные формочки для выпечки печения. Они все в форме всяких цветков, звёздочек, колокольчиков и т.д. Так вот я видел пару раз как ими работают: лежит, например, на столе кусок теста и человек просто ложит эти острые формочки на него и выдавлевает формы этих самых цветков, звёздочек, колокольчиков!!! По такому же принципу и взамодействует окно с регионом. Наша же задача - это именно создание "формочки" для формы ;) Передаваемые значения это:

hwnd - идентификатор окна, на котором нужно "выдавить" форму

hRgn - "формочка" для окна

bRedraw - перерисовывать ли окно после "выдавления"?

Для того, чтобы создать регион-"формочку" нам нужна вторая вторая API-функция CreatePolygonRgn. Состоит регион из n-ого количества точек, которое Вы и должны задать! Т.е., например, что бы описать звезду, нам нужны все её точки, как внутрение, так и наружние. Как их всех найти мы рассмотрим позже, а сейчас посмотрим на параметры:

lpPoint - первая точка региона. 

nCount - количество точек

nPolyFillMode - описание метода заливки полигона

Каждая точка на форме, как известно, имеет свои координаты - по иксу и по игреку. Поэтому каждая точка должна содержаться в переменной-ящичке POINTAPI. Всего точек у звезды десять (5 внутренних и пять наружних), но регион это замкнутая форма, поэтому какaя-то точка будет подсчитана дважды, как первая и как последняя. Итак нам нужны одиннадцать точек и каждая имеет свои координаты. Для этого-то мы и объявили массив P по типу POINTAPI. В скобках стоит десять, но, т.к. счёт идёт у Бэйсика с нуля, то и значений в нём одиннадцать.

6.А теперь в событие Form_Load впишем:

Picture = stern

Строка выглядит немного странновато, да. Зато короче уже не придумаешь. Это значит:

Me.Picture = stern.Picture

Умный VB определил, что свойству формы Picture ничего не нужно, как такого же вида свойство. Это можно делать со строковыми свойствами:

Label1 = "ABCVB"

Но это не относится к теме.
Теперь при загрузки формы на ней будет появляться звезда.

7.А вот теперь нам надо найти все точки, вокруг которых будет описан регион. Снова кликаем на форме и в окне Code в раздел General Declarations временно объявляем переменную-счётчик m, которая будет подсчитывать и подставлять точки региона:

Dim m As Integer

Теперь выбираем событие Form_MouseDown и вписываем:

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
Debug.Print "P(" & m & ").X=" & X / Screen.TwipsPerPixelX  _
& ":" & "P(" & m & ").Y=" & Y / Screen.TwipsPerPixelY
m = m + 1
End Sub

Что делает эта процедура? Нет, сначала объясню, что должны сделать Вы. Вам нужно запустить проект и клинуть на каждой из одиннадцати кнопок звёздочки, а процедура Mouse_Down будет в свою очередь генерировать код присвоения каждой точкe её координат в пикселах. Код потом можно будет прямо вставить в программу. Появляться будут строки примерно следующего содержания:

P(0).X = 132: P(0).Y = 6
P(1).X = 173: P(1).Y = 64
P(2).X = 303: P(2).Y = 71
P(3).X = 213: P(3).Y = 123
P(4).X = 291: P(4).Y = 241
P(5).X = 157: P(5).Y = 154
P(6).X = 5: P(6).Y = 239
P(7).X = 78: P(7).Y = 103
P(8).X = 10: P(8).Y = 58
P(9).X = 100: P(9).Y = 60

Так было у меня. Теперь вырежте этот код в событие Form_Load и уже вручную допишите код первой-последней точки с координатами точно, как у первой точки:

P(10).X = 132: P(10).Y = 6

8.Теперь строку Dim m As Integer и событие Form_MouseDown удалите вообще - они нам больше не нужны - координаты точек у нас есть. Тепeрь осталось всего-то создать регион и выдавить его на форме! В событие Form_Load допишем под присвоением координaт точек:

Dim Rgn As Long

Эта переменная будет содержать регион.

Теперь чуть ниже впишем строку создания региона:

Rgn = CreatePolygonRgn(P(0), 10, 0)

и ещё чуть ниже выдавливем форму:

Call SetWindowRgn(hwnd, Rgn, True)

Готово! Теперь просто запускаем. Любуемся! По этому ёе принципу можно создавать окна немысленных форм!!! Конечно, чем больше задано точек, тем красивее и точнее будет форма. Так же можно создавать точки во время передвижения мыши по форме. Точек будет конечно же много, но Вам ведь нужно просто их переставить в другое окно и всё! Сделать это надо так:

В разделе General Declarations объявляем переменную

Dim IfMove As Boolean
Dim m As Integer
 

Теперь вставьте три следующих события чуть ниже:

Private Sub Form_MouseUp(Button As Integer, Shift As _
 Integer, X As Single, Y As Single)
     IfMove = False
End Sub

Private Sub Form_MouseDown(Button As Integer, Shift _
 As Integer, X As Single, Y As Single)
  IfMove = True
End Sub

Private Sub Form_MouseMove(Button As Integer, Shift As _
   Integer, X As Single, Y As Single)
  If IfMove Then
Debug.Print "P(" & m & ").X=" & X / Screen.TwipsPerPixelX _
    & ":" & "P(" & m & ").Y=" & Y / Screen.TwipsPerPixelY
   m = m + 1
   End If
End Sub

А теперь последняя проблемка - передвижение формы по экрану!

Объявим переменную, проверяющую двигается ли мышь и две переменные для значений положения мышки во время нажатия и добавим три процедуры:

Dim IfMove As Boolean
Dim X1 As Integer
Dim Y1 As Integer

Private Sub Form_MouseDown(Button As Integer, Shift _
 As Integer, X As Single, Y As Single)
  IfMove = True
  X1 = X
  Y1 = Y
 End Sub

Private Sub Form_MouseUp(Button As Integer, Shift As _
 Integer, X As Single, Y As Single)
     IfMove = False
End Sub

Private Sub Form_MouseMove(Button As Integer, Shift As _
  Integer, X As Single, Y As Single)
  If IfMove Then
  Move Left + X - X1, Top + Y - Y1
  End If
End Sub

А вот второй способ осуществим при помощи API функции SendMessage. В раздел General Declarations допишем следующие API-функции:

Private 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 Declare Sub ReleaseCapture Lib "user32" ()

Добавим процедуру передвижения иышки:

Private Sub Form_MouseMove(Button As Integer, Shift As _
   Integer, X As Single, Y As Single)
'если была нажата левая кнопка вызываем процедуры
  If Button = 1 Then
    Call ReleaseCapture
    Call SendMessage(hwnd, &HA1, 2, 0&)
   End If
End Sub

[Окна c закругленными углами]

А вот это ещё проще! А всё-таки тоже необычно - часто Вы встречаете окна с закругленными углами??? 

1.В раздел General Declarations допишем следующую API-функцию:

Private 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

Её параметры:

X1,Y1 - координаты верхней левой точки прямоугольника, углы которого надо закруглить

X2,Y2 - координаты нижней правой точки

X3 - ширина овала, применяемого для закругления углов

Y3 - его высота

2.Теперь попробуем создать такой. В событии Form_Load закомментируйте строку Picture = stern, чтобы картинка больше не появлялась. А под строкой

Dim Rgn As Long

объявите ещё одну переменную для региона:

Dim Rgn2 As Long

Теперь создадим второй регион:

Rgn2 = CreateRoundRectRgn(0, 0, Width / Screen.TwipsPerPixelX, _
Height / Screen.TwipsPerPixelY, 50, 50)

В качестве аргументов мы передаём координаты формы в пикселах а высоту и ширину овала задаём одинаковой, тем самым углы будут именно закруглены, а не "заовалены" :).

Теперь чуть ниже вставьте эту строку, окраски формы в красный цвет:

BackColor = QBColor(12)

И строку

Call SetWindowRgn(hwnd, Rgn, True)

исправьте на 

Call SetWindowRgn(hwnd, Rgn2, True)

Запускайте! Потрясающе, неправда ли !? Но вот если бы была ещё рамка, было бы просто неотразимо. Но ведь это тоже осуществимо! Правда совсем не такая, как у стандарного окна, но всё-таки!

3.Для этого можно применить API-функцию FrameRgn. Итак, под все остальные API-функции допишем ещё одну:

Private Declare Function FrameRgn Lib "gdi32" (ByVal hDC As Long, _
ByVal hRgn As Long, ByVal hBrush As Long, ByVal nWidth As Long, _
ByVal nHeight As Long) As Long

Передаваемые параметры:

hDC - handle Device Context (я не понимаю что это, но что-то на подобии hWnd - может идентификатор какой?...)

hRgn - регион, который надо "обрамить"

hBrush - что это за "щётка" не понимаю тоже, но её надо прежде тоже создать (!) скорее всего это вид рисования рамки.

nWidth, nHeight - толшина рамки по высоте и ширине

И, как я уже сказал, нам нужна ещё одна API-функция для создания "щётки" (дописываем под первую):

Private Declare Function CreateSolidBrush Lib "gdi32" _
(ByVal crColor As Long) As Long

Внимание! Для FrameRgn тоже нужен регион! Ведь рамку можно нарисовать и на квадратном окне и выглядеть она будет в зависимости от региона.

Теперь в событие Form_Load перед строкой

Call SetWindowRgn ...

вставьте строку

Call FrameRgn(hDC, Rgn, CreateSolidBrush(QBColor(0)), 3, 3)

Запускайте!

[Комбинирование регионов]

А ещё регионы можно комбинировать! Нужно это правда не так часто но, когда вам нужна форма с одной стороны полукруглая, а с другой выперающие углы, штыри и т.д., то без комбинирования регионов Вам уже не обойтись! Прежде чем начать введите в Genral Declarations:

Private Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn _
As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal _
nCombineMode As Long) As Long

Параметры:

hDestRgn - регион, которому возвращается полученный результат из двух регионов

hSrcRgn1 - первый регион

hSrcRgn2 - второй регион

nCombineMode - методы комбинирования
 

Сами методы (есть ещё несколько, но эти самые основные):

'отображаются только те точки, которые принадлежат 
'одному И второму региону
Const RGN_AND = 1 

'отображаются только те точки, которые принадлежат 
'к хотябы одному региону
Const RGN_OR = 2

'отображаются только те точки, которые принадлежат 
'ТОЛЬКО одному из двух регионов
Const RGN_XOR = 3

'отображается разница регионов, "остатки" одного от другого
Const RGN_DIFF = 4

Так, теперь можно было бы скомбинировать регионы и для эффективности они должны быть разные по размерам, т.к. звезда полностью умещается в форме с закругленными углами. Поэтому исправьте строку:

Rgn2 = CreateRoundRectRgn(0, 0, Width / Screen.TwipsPerPixelX, _
Height / Screen.TwipsPerPixelY, 50, 50)

на

Rgn2 = CreateRoundRectRgn(0, 0, (Width / Screen.TwipsPerPixelX) - 100, _
(Height / Screen.TwipsPerPixelY) - 100, 50, 50)

Т.е. регион был уменьшен на сто никселов.

Всё, осталось теперь только поместить следующую строку в тоже событие Form_Load, но перед строкой Call SetWindowRgn...:

Call CombineRgn(Rgn2, Rgn, Rgn2, RGN_OR)

В этом случае регион, в который будет помещён результат мы взяли формы с круглыми углами и метод комбинирования выбрали RGN_OR, т.е. будут отображены все точки, которые принадлежат к хотябы одному из регионов. А создание круглых и овальных регионов я уже рассматривал в примере изменения контуров формы и это ещё проще. Теперь пробуйте, эксперементируйте наслаждайтессссь... :)

Ну, - как говорил Алексей Булдаков в "Осебенностях национальной охоты", - всё, что знал, рассказал!


[an error occurred while processing this directive]


[an error occurred while processing this directive]