Как создать свой элемент управления в VB.NET (часть 2)
Как и было обещано, теперь будем делать ЭУ, который совместит в себе свойства полосы прокрутки, ползунка и окошка с текущим числовым значением. Мене всегда этого окошка не хватало, приходилось всегда приделывать «Label» к ползунку и полосе прокрутки. Кроме этого, мне не нравилось, что у ползунка надо устанавливать свойство «Orientation», он, что сам не может догадаться об этом, когда его создают. Так как мы хотим создать совершенно новый ЭУ, проще будет наследовать сам «Control». Так, что будем создавать ЭУ почти с нуля.
Сначала определимся со свойствами.
- Хотя мы и планируем автоматически определять ориентацию, все равно такое свойство «Orientation» придется завести, но будет оно только для чтения.
- Нужно еще задавать размер окошка для индикации числового значения. Если оно будет равно нулю, то его просто не показываем. «WinRazmer». Окошки можно расположить слева или справа (при горизонтальном варианте), вверху или внизу (при вертикальном варианте). Поэтому будет логическое свойство «WinLocate».
- Две кнопки на концах полосы тоже будут иметь задаваемый размер «ButRazmer». Кроме этого я хочу завести логическое свойство «ButMinMax», которое будем определять, где у полосы минимум и максимум. Это особенно важно при вертикальном варианте, ноль не всегда внизу, иногда требуется и наоборот.
- Ползунок тоже захочет иметь свой размер «PolzRazmer» и еще «PolzStyle». Как Вы уже догадались, это будет перечисляемое свойство, принимающее значения: прямоугольник, овал, ромб, треугольник, стрелка и так далее.
- Размеры окошка, кнопок и ползунка можно задавать в пикселях, а можно в процентах от второго размера. В первом случае, при изменении высоты горизонтальной прокрутки, кнопка будет просто вытягиваться. Во втором, она будет еще и расширяться, пытаясь сохранить свои пропорции. Чтобы реализовать оба варианта добавим логическое свойство «RazmerStyle».
- Разумеется три числовых значения: минимальное, максимальное и текущее. «Minimum», «Maximum», «Value». Кроме этого два свойства, отвечающие за шаг, при щелчке на кнопках или полях «SmallChange» и «LargeChange».
- Пять градиентных заливок: окно, кнопки, ползунок, поле не заполненное и роле заполненное. Ну, пусть еще и для текста, уговорили.
- Для улучшения внешнего вида добавим обводящую рамку вокруг всего ЭУ. Поэтому просто добавим свойство «FrameColor».
- Теперь события, которые придется отловить: нажатие на ползунок и процесс его перетаскивания, нажатия на кнопки (для увеличения или уменьшения с малым шагом), нажатия на поля (большой шаг) и, наконец, щелчок по окошку для сброса в минимальное состояние. Можно добавить, что повторный щелчок устанавливает в максимум.
- Есть еще события, которые мы сгенерируем для программиста, который будет пользоваться нашим ЭУ (например, событие, когда «Value» достигает максимума). Об этом поговорим после создания графической части.
Теперь можно приступить к созданию ЭУ. Жмем «Add Class…» и создаем новый класс «NewTrackBar» и копируем с нашей «NewPanel.vb» практически все (лично я копирую все строки, а потом переименовываю название класса и убираю лишнее). С иконкой тоже можно поступить аналогичным образом: правой кнопкой на «NewPanel.bmp», жмем «Copy», потом правой кнопкой на «NewControl» и «Paste». Теперь переименовываем «Copy of NewPanel.bmp» в «NewTrackBar.bmp». Осталось заняться рисованием иконки, что не составит большого труда. Для тех, кому лень, может воспользоваться вот таким атрибутом:
<ToolboxBitmap(GetType(TrackBar))> _
Public Class NewTrackBar
Здесь мы в качестве иконки задаем стандартную иконку другого ЭУ. Можно вместо «TrackBar» поставить название класса любого другого ЭУ «Panel», «Label» и так далее. У этого атрибута есть еще два варианта написания, но я считаю, что они не стоят упоминания, незачем себе голову забивать тем, чем не придется пользоваться.
Написание свойств, несмотря на помощь самой студии, процесс все равно утомительный, поэтому я сел и написал свой макрос. Как только я познакомился с VS2003 и увидел, что можно писать макросы как в Worde, я сразу же нашлепал себе кнопок, которые вставляют часто встречающийся текст. Например:
Sub Impor()
DTE.ActiveDocument.Selection.Text = "Imports System."
End Sub
К сожалению возможности не такие как в VBA, я так и не смог сделать вызов формы. Немного помучавшись (целый день угрохал) я, наконец, добился своего.
Sub FunZF()
Dim s, w, t(7), pp, pt, pm As String
Dim p() As String = {"Boolean", "Integer", "Long", "String", "Image", "Color"}
t(0) = "' Property 11111() As 22222"
t(1) = " Get"
t(2) = " Return m11111"
t(3) = " End Get"
t(4) = " Set(ByVal Value As 22222)"
t(5) = " m11111 = Value : MyBase.Invalidate()"
t(6) = " End Set"
t(7) = "End Property"
Dim i%
For i = 0 To t.Length - 1 : w = w & t(i) & Chr(13) : Next i
s = InputBox("", " Название свойства ?", "")
w = w.Replace("11111", s)
For i = 0 To p.Length - 1
pt = pt & i & " - " & p(i) & vbCrLf
Next
pm = " Private m" & s & " As "
s = InputBox(pt, " Тип свойства ?", "") : pp = s
If InStr("0123456789", s.Substring(0, 1)) > 0 Then
i = System.Convert.ToInt16(s)
If i < p.Length Then pp = p(i)
Else : pm = pm & "New "
End If
' вставляем наверх внутреннюю переменную
Dim te As TextSelection = DTE.ActiveDocument.Selection
te.ClearBookmark() : te.SetBookmark()
te.StartOfDocument() : DTE.Find.FindWhat = "Private m" : DTE.Find.Execute()
te.StartOfLine(0) : te.Text = pm & pp : te.NewLine()
te.NextBookmark() : te.ClearBookmark()
' печатаем свойство
w = w.Replace("22222", pp)
DTE.ActiveDocument.Selection.Text = w : Call ss(8)
End Sub
Sub ss(ByVal ns As Integer)
Dim te As TextSelection = DTE.ActiveDocument.Selection
te.LineUp(False, ns) : te.StartOfLine(1)
DTE.ExecuteCommand("Edit.UncommentSelection") : te.LineDown(False, ns)
End Sub
Надеюсь, мне не надо объяснять, куда поместить данный код и как сделать кнопку для его вызова. Интересно, а среди Вас есть любители писать макросы для VS2003? Мне как-то не встречались на форуме вопросы на эту тему.
Для разминки добавим свойству «Gradient» еще пару «Sub»
Public Sub New(ByVal c1 As Int32, ByVal c2 As Int32, ByVal g As Int16)
Me.Color1 = Color.FromArgb(c1)
Me.Color2 = Color.FromArgb(c2)
Me.ColorN = g
End Sub
Public Sub Copy(ByVal kg As Gradient)
Me.Color1 = kg.Color1
Me.Color2 = kg.Color2
Me.ColorN = kg.ColorN
End Sub
Первая позволяет задавать градиент с помощью трех целых чисел, а вторая – делает копию с другого градиента. Позже вы оцените их полезность.
Добавим также еще одну функцию.
Overrides Function ToString() As String
Return mColor1.ToArgb & "," & mColor2.ToArgb & "," & Val(mColorN)
End Function
Это функция заменяет текст в странице свойств напротив имени градиента с совершенно не несущего никакой информации «NewControl.Gradient» на три числа записанных через запятую, которые и задают градиент.
Итак, перечисляемые типы созданы, внутренние переменные и свойства тоже. Для краткости приведу только самое начало.
Public Class NewTrackBar
Inherits Control
Enum Orientations
Горизонтально = 0
Вертикально = 1
End Enum
Enum PolzStyles
Прямоугольник = 0
Овал = 1
Ромб = 2
Треугольник = 3
Стрелка = 4
End Enum
Private WithEvents mGrad0Fon As New Gradient(24)
Private WithEvents mGrad1Fon As New Gradient(5)
Private WithEvents mGrad2Fon As New Gradient(18)
Private WithEvents mGrad3Fon As New Gradient(-7667573, -65281, 8)
Private WithEvents mGrad4Fon As New Gradient(-5658199, -8355712, 8)
Private WithEvents mGrad0Text As New Gradient(-16777216, -16777216, 0)
Private mOrientation As New Orientations
Private mLargeChange As Integer = 10
Private mSmallChange As Integer = 1
Private mValue As Integer = 25
Private mMaximum As Integer = 100
Private mMinimum As Integer = 0
Private mRazmerStyle As Boolean = False
Private mPolzStyle As New PolzStyles
Private mPolzRazmer As Integer = 12
Private mButMinMax As Boolean = False
Private mButRazmer As Integer = 16
Private mWinLocate As Boolean = False
Private mWinRazmer As Integer = 30
Private mGradSource As NewTrackBar = Nothing
Private mGradSTyle As Boolean = False
Private mFrameColor As Color = Color.DarkGray
Свойство «mGradSource» тоже скопировалось с «NewPanel.vb» и я не стал его убирать, пусть будет. Как Вы уже, наверное, заметили, я всем свойствам дал начальные значения. Должен же вновь создаваемый ЭУ как-то при рождении выглядеть. Откуда я взял такие хитрые числовые значения у градиента? Не угадали, не с потолка и не из пальца. Технология такая: на вновь созданном ЭУ я менял цвета его градиентов, а потом из окошка его свойства копировал в файл «NewTrackBar.vb». Теперь стало понятно, для чего были добавления в «Gradient.vb»? К сожалению, изменением начального внешнего вида Вы займетесь, когда ЭУ будет нарисован, а пока мы еще до этого не дошли.
Для начала определимся с ориентацией нашего ЭУ. Для этого переопределим событие «onResize». Для тех, кто забыл, повторим. В левом выпадающем списке выбираем «Overrides», а в правом событие «onResize». После чего в полученную заготовку вставляем следующий код. Я очень надеюсь, что все, кто читает данную статью, уже сами могут, без посторонней помощи его написать.
Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
mOrientation = Orientations.Горизонтально
If Height > Width Then mOrientation = Orientations.Вертикально
MyBase.Invalidate()
End Sub
Так как наш новый ЭУ намного сложнее чем прежний, то возможны ситуации, когда задание некоторых значений свойствам будут, мягко говоря, бессмысленными. Нельзя же задавать размер окошка или кнопок больше размера самого ЭУ. Поэтому наложим ограничения на задание этим свойствам. Для примера возьмем ползунок. Минимальная его ширина (или высота при вертикальном расположении) не может быть меньше, скажем 5 точек. Иначе за что его мышкой таскать? С другой стороны ширина не должна быть слишком большой иначе ползунок будет неприлично полный (хотя о вкусах не спорят). Поэтому сначала определим возможные границы допустимых значений, а потом проверим, входит ли новое значение в них. Если нет, то: «К сожалению, господа, но свободных мест нет».
Property PolzRazmer() As Integer
Get
Return mPolzRazmer
End Get
Set(ByVal Value As Integer)
Dim Rmin, Rmax As Integer
If mRazmerStyle Then
' размер в процентах
Rmax = 70 : Rmin = 500 / Height
If mOrientation = Orientations.Вертикально Then Rmin = 500 / Width
Else
' размер в пикселях
Rmin = 5 : Rmax = 0.7 * Height
If mOrientation = Orientations.Вертикально Then Rmax = 0.7 * Width
End If
If Rmax < Rmin Then Rmax = 3 * Rmin + 5
If (Value >= Rmin And Value <= Rmax) Or Value = 0 Then
mPolzRazmer = Value : MyBase.Invalidate()
Else
If DesignMode Then
MsgBox("Размер должен быть от " & Rmin & " до " & Rmax, _
MsgBoxStyle.Critical, "Ошибка")
End If
End If
End Set
End Property
Как вы уже догадались, сообщение об ошибке выдается только в режиме конструктора. В приведенном коде есть пара возможных деления на нуль, а это влечет за собой необходимость переопределить свойства «Width» и «Height» или надеяться, что нулевые размеры нашему ЭУ никто не задаст. Ограничения на окошко и кнопки попробуйте осилить самостоятельно.
Кое-кто, наверное, подумал, что раз уже есть свойства «Width» и «Height», то их можно проконтролировать прямо в «onResize» и если их значения будут нулевыми, то изменить их. Рискните и увидите, что Вас ожидает. Отрицательный опыт тоже бывает полезным.
Ну а мы перейдем к самому интересному. Будем рисовать наш ЭУ. Это уже не простая панель, тут все гораздо сложнее, а главное вариантов изображения несколько и все их надо учесть, никакой не пропустить. Для начала заведем массив прямоугольников, который будет хранить размер и положение каждого подэлемента нашего ЭУ: окошка, двух кнопок, двух полей и ползунка. Итого 6 штук. Потом мы их просто закрасим и все. Кроме этого, когда будем обрабатывать нажатие мышкой, легко можно определить, какому из прямоугольников принадлежит координата мышки. Это требует, чтобы массив был общим для всех и находился в том же блоке, что и наши внутренние переменные.
Для тех, кто уже чувствует в себе силы самому написать «OnPaint», предлагая сравнить свой вариант с моим.
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
If Width < 2 Or Height < 2 Then Exit Sub
Dim r0, r1, r2, r3, r4,i As Integer
Try
For i = 0 To 6
mRec(i) = New Rectangle(0, 0, Width - 1, Height - 1)
Next
Dim k, m As Single ' доля заполнения и масштаб для процентов
k = (mValue - mMinimum) / (mMaximum - mMinimum)
Dim nv As Integer = 0
If mOrientation = Orientations.Вертикально Then nv = 1
If mWinLocate Then nv = nv + 2
If mButMinMax Then nv = nv + 4
' имеем 8 вариантов расположения
Select Case nv
Case 0 ' горизонтально, окошко слева
m = 1 : If mRazmerStyle Then m = Height / 100
r0 = mWinRazmer * m : r3 = mPolzRazmer * m : r4 = mButRazmer * m
mRec(0).Width = r0 : mRec(3).Width = r3 : mRec(4).Width = r4 : mRec(5).Width = r4
mRec(4).X = r0 : mRec(5).X = Width - r4
r2 = (Width - r0 - 2 * r4) * k
r1 = Width - r0 - 2 * r4 - r2
mRec(1).Width = r1 : mRec(2).Width = r2
mRec(2).X = r0 + r4 : mRec(1).X = r0 + r4 + r2
mRec(3).X = mRec(1).X - r3 / 2
Case 1 ' вертикально, окошко вверху
m = 1 : If mRazmerStyle Then m = Width / 100
r0 = mWinRazmer * m : r3 = mPolzRazmer * m : r4 = mButRazmer * m
mRec(0).Height = r0 : mRec(3).Height = r3 : mRec(4).Height = r4 : mRec(5).Height = r4
mRec(4).Y = Height - r4 : mRec(5).Y = r0
r2 = (Height - r0 - 2 * r4) * k
r1 = Height - r0 - 2 * r4 - r2
mRec(1).Height = r1 : mRec(2).Height = r2
mRec(1).Y = r0 + r4 : mRec(2).Y = r0 + r4 + r1
mRec(3).Y = mRec(2).Y - r3 / 2
Case 2 ' горизонтально, окошко справа
Case 3 ' вертикально, окошко внизу
Case 4 ' тоже, что 0, но минимум справа
Case 5 ' тоже, что 1, но минимум вверху
Case 6 ' тоже, что 2, но минимум справа
Case 7 ' тоже, что 3, но минимум вверху
Case Else
End Select
Dim EU As NewTrackBar = Me
If mGradSTyle = True Then EU = mGradSource
If mWinRazmer > 0 Then
FillGrad(e.Graphics, EU.mGrad0Fon, mRec(0))
FillText(e.Graphics, EU.mGrad0Text, mRec(0), mValue, Font)
End If
FillGrad(e.Graphics, EU.mGrad1Fon, mRec(1))
FillGrad(e.Graphics, EU.mGrad2Fon, mRec(2))
FillGrad(e.Graphics, EU.mGrad4Fon, mRec(4))
FillGrad(e.Graphics, EU.mGrad4Fon, mRec(5))
If mPolzStyle = PolzStyles.Прямоугольник Then
FillGrad(e.Graphics, EU.mGrad3Fon, mRec(3))
Else : Dim nf As Integer = mPolzStyle
FillFigure(e.Graphics, EU.mGrad3Fon, mRec(3), nf)
End If
DrawFrame(e.Graphics, mFrameColor, 1, mRec(6))
Catch ex As Exception
End Try
Здесь я описал только два основных варианта нашего ЭУ. Несмотря на казалось бы головоломный текст программы, все довольно просто, надо просто внимательно переделать уже имеющиеся варианты (эти два и сами то друг от друга почти не отличаются). Для тех, кто решил ограничиться только двумя приведенными вариантами, надо убрать свойства «mWinLocate» и «mButMinMax». Кроме этого появилось две новые графические функции, которые, разумеется, расположены в общем модуле «ModulControl.vb». «FillText» - для градиентного текста, хотя для данного ЭУ оно не принципиально. Здесь мы вынуждены, ограничится только четырьмя вариантами заливки текста, поэтому все остальные значения градиента приравниваются к вертикальной заливке.
Public Sub FillText(ByVal pov As Graphics, ByVal mg As Gradient, ByVal r As Rectangle, _
ByVal t As String, ByVal ft As Font)
Dim n As LinearGradientMode = LinearGradientMode.Vertical
If mg.ColorN < 4 Then n = mg.ColorN
Try
Dim rt As New Rectangle
rt.Width = pov.MeasureString(t, ft).Width
rt.Height = pov.MeasureString(t, ft).Height
rt.X = r.X + (r.Width - rt.Width) / 2
rt.Y = r.Y + (r.Height - rt.Height) / 2
pov.DrawString(t, ft, New LinearGradientBrush(rt, mg.Color1, mg.Color2, n), rt.X, rt.Y)
Catch ex As Exception
End Try
End Sub
Обратите внимание на то, как вычисляется размер будущего текста и его центровка в заданном прямоугольнике.
Рисование фигур, дело намного более хлопотное и разных способов куча.
Public Sub FillFigure(ByVal pov As Graphics, ByVal mg As Gradient, ByVal r As Rectangle, ByVal nf As Integer)
pov.SmoothingMode = SmoothingMode.HighQuality ' качаство линий
Try
Select Case nf
Case 1 ' овал
Dim pat As New GraphicsPath
pat.AddEllipse(r)
Dim b As New PathGradientBrush(pat)
b.CenterPoint = New PointF(r.X + r.Width / 2, r.Y + r.Height / 2)
b.CenterColor = mg.Color2
Dim mc() As Color = {mg.Color1}
b.SurroundColors = mc
pov.FillPath(b, pat) : pov.DrawEllipse(New Pen(mg.Color1, 1), r)
b.Dispose() : pat.Dispose()
Case 2 ' ромб
Dim xy() As Single = {0.0, 0.5, 0.5, 0.0, 1.0, 0.5, 0.5, 1.0}
Figure(pov, mg, r, xy)
Case 3 ' треугольник
Dim xy() As Single = {0.0, 1.0, 0.5, 0.0, 1.0, 1.0}
If r.Width > r.Height Then
xy(2) = 0 : xy(5) = 0.5
End If
Figure(pov, mg, r, xy)
Case 4 ' стрелка
Dim xy() As Single = {0.5, 0.0, 1.0, 0.5, 0.7, 0.45, 0.7, 1.0, 0.3, 1.0, 0.3, 0.45, 0.0, 0.5}
If r.Width > r.Height Then
xy(0) = 1.0 : xy(1) = 0.5 : xy(2) = 0.5 : xy(3) = 1.0
xy(4) = 0.55 : xy(5) = 0.7 : xy(6) = 0.0 : xy(7) = 0.7
xy(8) = 0.0 : xy(9) = 0.3 : xy(10) = 0.55 : xy(11) = 0.3
xy(12) = 0.5 : xy(13) = 0.0
End If : Figure(pov, mg, r, xy)
Case Else
End Select
Catch ex As Exception
End Try
End Sub
Private Sub Figure(ByVal pov As Graphics, ByVal mg As Gradient, ByVal r As Rectangle, ByVal rr() As Single)
Try
Dim k As Int16 = rr.Length / 2 - 1
Dim mt(k) As Point
For i As Int16 = 0 To k
mt(i).X = r.X + r.Width * rr(i * 2)
mt(i).Y = r.Y + r.Height * rr(i * 2 + 1)
Next
Dim b As New PathGradientBrush(mt)
Dim mc() As Color = {mg.Color1, mg.Color2, mg.Color1}
Dim pos() As Single = {0.0F, 0.5F, 1.0F}
Dim cb As New ColorBlend
cb.Colors = mc : cb.Positions = pos
b.InterpolationColors = cb
pov.FillPolygon(b, mt) : b.Dispose()
pov.DrawPolygon(New Pen(mg.Color1, 1), mt)
Catch ex As Exception
End Try
End Sub
По использованию GDI+ можно писать отдельную статью. И не одну даже. Сам я еще плаваю в GDI+ по-собачьи. Заранее извиняюсь за возможную тяжеловесность кода. Например, поворот стрелки или треугольника можно было осуществить при помощи афинских преобразований. Теперь приступим к механизму, который будет управлять нашим ЭУ. Для начала определим событие, которое будет происходить при изменении значения «Value» и заведем три внутренние переменные, отвечающие за номер прямоугольника: 0 – окошко, 1 – заполненная область, 2 – не заполненная область, 3 – ползунок, 4 и 5 кнопки. Две другие – это координаты мышки (понадобятся, когда будем тащить мышкой ползунок).
Public Event ValueChanged(ByVal sender As Object, ByVal e As EventArgs)
Private mNom, xm, ym As Int16
Теперь переопределяем события для мышки: мышка нажимается, отжимается, движется и кликает. Первые два события выглядят совсем безобидно:
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
For i As Int16 = 0 To 5
If mRec(i).Contains(e.X, e.Y) Then mNom = i
Next
If mNom = 3 Then xm = e.X : ym = e.Y
End Sub
Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
Mnom = 6
End Sub
А вот движение мышки – совсем другой коленкор. Приходится учитывать ориентацию нашего ЭУ. А вот учет того, где находится минимум и максимум (свойство «ButMinMax») я специально опустил, что бы Вы самостоятельно могли дописать код.
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
If mNom = 3 And e.Button = MouseButtons.Left Then
Try
If mOrientation = Orientations.Горизонтально Then
Dim x0, x1, dx
x0 = Math.Min(mRec(1).X, mRec(2).X)
x1 = Math.Max(mRec(1).X + mRec(1).Width, mRec(2).X + mRec(2).Width)
dx = e.X : If dx < x0 Then dx = x0
If dx > x1 Then dx = x1
mValue = (dx - x0) / (x1 - x0) * (mMaximum - mMinimum) + mMinimum
Else
' вертикально
Dim y0, y1, dy As Int16
y0 = Math.Min(mRec(1).Y, mRec(2).Y)
y1 = Math.Max(mRec(1).Y + mRec(1).Height, mRec(2).Y + mRec(2).Height)
dy = e.Y : If dy < y0 Then dy = y0
If dy > y1 Then dy = y1
mValue = (y1 - dy) / (y1 - y0) * (mMaximum - mMinimum) + mMinimum
End If
RaiseEvent ValueChanged(Me, New System.EventArgs)
Me.Invalidate()
Catch ex As Exception
End Try
End If
End Sub
Наконец последнее событие. В зависимости по какому прямоугольнику произошло нажатие мышкой, происходит изменение значения ползунка. Тут происходит контроль за тем, чтобы не выйти за возможные границы значений и проверка того, что новое значение отличается от предыдущего. То есть при нажатии кнопки «уменьшить», когда значение и так минимально событие «ValueChanged» не будет происходить. Логически это справедливо.
Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
Dim nz As Integer = mValue ' начальное значение
Select Case mNom
Case 0
If mValue > mMinimum Then
mValue = mMinimum
Else : mValue = mMaximum
End If
Case 1 : mValue = mValue + mLargeChange
Case 2 : mValue = mValue - mLargeChange
Case 4 : mValue = mValue - mSmallChange
Case 5 : mValue = mValue + mSmallChange
Case Else
End Select
If mValue < mMinimum Then mValue = mMinimum
If mValue > mMaximum Then mValue = mMaximum
If nz <> mValue Then RaiseEvent ValueChanged(Me, New System.EventArgs)
MyBase.Invalidate()
End Sub
Теперь сделаем небольшую паузу и поработаем с нашим новым ЭУ на тестовой форме. Градиентных свойств много, редактировать их, даже если подключить форму, как это сделано в «NewPanel» тоже не выход. Сделаем такой редактор свойств, который бы мог редактировать у любого ЭУ сразу все градиентные свойства, включая и текстовый.
Для этого нам необходим еще один новый ЭУ типа «Label», чтобы был градиентный текст. Создадим его (я специально засек время и уложился за 6 минут).
Добавляем новый класс «NewLabel», копируем все полностью из «NewPanel». Затем с помощью поиска и замены заменяем «NewPanel» на «NewLabel». Здесь будьте внимательны, замена должна быть не во всем проекте, а только в файле «NewLabel.vb». Надо заменить наследование, то есть вместо «Panel» надо написать «Control». В дизайнере тоже надо изменить наследование с «ParentControlDesigner» на просто «ControlDesigner», так как надпись не является контейнером для других ЭУ в отличие от панели. Это касается и нашего ползунка. Добавляем новое свойство «Grad0Text», полный аналог уже имеющегося «Grad0Fon». И изменяем немного «OnPaint», который будет выглядеть так:
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
Try
Dim r As New Rectangle(0, 0, Width, Height)
Dim EU As NewLabel = Me
If mGradSTyle = True Then EU = mGradSource
FillGrad(e.Graphics, EU.Grad0Fon, r)
FillText(e.Graphics, EU.Grad0Text, r, Me.Text, Me.Font)
Dim ns As Integer = mFrameStyle
DrawFrame(e.Graphics, mFrameColor, ns, r)
Catch ex As Exception
End Try
End Sub
Осталось только задать начальное значение «Grad0Text», если только Вы не скопировали строку из «NewTrackBar.vb» и расширить обработку имеющегося события «mGrad0Fon_GradChanged», добавив еще одно событие через запятую (Handles mGrad0Fon.GradChanged, mGrad0Text.GradChanged). Вот и все!!!
Оставим без изменения нашу старую форму «GradEdit.vb» и сделаем более совершенную «GradEditM.vb». Кое-что со старой формы скопируем: кнопку и список. Вместо шести счетчиков создадим с теми же именами шесть наших «NewTrackBar». Вместо «NewPanel» будет «NewLabel» с тем же именем. Добавляем еще два списка: для различных градиентов «ListF» и «ListFT» список-переключатель «Фон-Текст».
Здесь я сразу привожу уже работающий редактор, с помощью которого редактируется наш ЭУ.
Для этого надо передать список всех градиентов, их названий, которые поместим в список и название ЭУ для полного комфорта. Лучше всего заякорить все элементы к краям формы, чтобы при ее растягивании увеличивалась только надпись-образец «NP».
Теперь рассмотрим код. В общем модуле заведем целый массив градиентов:
Public Mgr(19) As Gradient
Это значит, я рассчитываю, что у меня когда-нибудь будет ЭУ с десятью парами фон-текст. Далее при загрузке формы, через свойство «Tag» передается название ЭУ, список названий градиентов и список текстовых образцов. Он нужен для того, чтобы форма могла отличить просто фон от фона с текстом.
Dim kf As Integer ' количество различных фонов
Dim nt As Integer ' номер текущего фона в списке
Dim n2 As Integer ' номер в списке фон-текст
Dim tm(9) As String ' образцы текста
Private Sub GradEditM_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
Dim t0 As String = Convert.ToString(Me.Tag)
Dim t1() As String = t0.Split(";")
Me.Text = "Редактируем - " & t1(0)
Dim t2() As String = t1(1).Split(",")
Dim t3() As String = t1(2).Split(",")
For i As Int16 = 0 To t3.Length - 1 : tm(i) = t3(i) : Next
ListF.Items.Clear() : ListF.Items.AddRange(t2) : kf = t2.Length
nt = 0 : ListF.SelectedIndex = nt
n2 = 0 : ListFT.SelectedIndex = n2 : NovGrad()
End Sub
Следующая «Sub NovGrad» нужна при выборе нового градиента и задании ползункам значений цветов, а списку «ListN» – направления градиента. Кроме этого, если фон существует без текста, то автоматически происходит переход на редактирование фона. Ну, нельзя редактировать градиент текста, если его нет. Редактируемые градиенты в массиве расположены так: сначала все градиенты фона, потом соответствующие им градиенты текста. Надеюсь, у нас не будет ЭУ, у которого на одном фоне будет два разных градиента текста, иначе придется редактор переделывать.
Sub NovGrad()
NP.Grad0Fon.Copy(Mgr(nt))
NP.Grad0Text.Copy(Mgr(nt + kf))
NP.Text = tm(nt) : NP.Refresh()
If NP.Text = «» Then n2 = 0 : ListFT.SelectedIndex = 0
NR1.Value = Mgr(nt + kf * n2).Color1.R
NG1.Value = Mgr(nt + kf * n2).Color1.G
NB1.Value = Mgr(nt + kf * n2).Color1.B
NR2.Value = Mgr(nt + kf * n2).Color2.R
NG2.Value = Mgr(nt + kf * n2).Color2.G
NB2.Value = Mgr(nt + kf * n2).Color2.B
ListN.SelectedIndex = Mgr(nt + kf * n2).ColorN
End Sub
Далее у нас идет обработка ползунков и списка направлений градиента.
Private Sub NR1_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles NR1.ValueChanged, NG1.ValueChanged, NB1.ValueChanged, _
NR2.ValueChanged, NG2.ValueChanged, NB2.ValueChanged
Dim c1 = Color.FromArgb(NR1.Value, NG1.Value, NB1.Value)
Dim c2 = Color.FromArgb(NR2.Value, NG2.Value, NB2.Value)
If n2 = 0 Then
NP.Grad0Fon.Color1 = c1 : NP.Grad0Fon.Color2 = c2
Else
NP.Grad0Text.Color1 = c1 : NP.Grad0Text.Color2 = c2
End If : NP.Refresh()
End Sub
Private Sub ListN_SelectedIndexChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles ListN.SelectedIndexChanged
If n2 = 0 Then
NP.Grad0Fon.ColorN = ListN.SelectedIndex
Else
NP.Grad0Text.ColorN = ListN.SelectedIndex
End If : NP.Refresh()
End Sub
Тут все дело в том, что в данный момент редактируется, фон или текст. Следующие два элемента осуществляют переход от одного редактируемого градиента к другому.
Private Sub ListF_SelectedIndexChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles ListF.SelectedIndexChanged
nt = ListF.SelectedIndex : NovGrad()
End Sub
Private Sub ListFT_SelectedIndexChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles ListFT.SelectedIndexChanged
n2 = ListFT.SelectedIndex : NovGrad()
End Sub
Наконец внесение изменений в сам ЭУ.
Private Sub ButSave_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles ButSave.Click
Mgr(nt).Copy(NP.Grad0Fon)
If NP.Text <> "" Then Mgr(nt + kf).Copy(NP.Grad0Text)
End Sub
Здесь у нас в отличие от предыдущего редактора градиента, каждая пара градиентов фон-текст записывается в ЭУ отдельно и форма не закрывается. Теперь рассмотрим вызов формы из ЭУ. Разумеется, у нас уже есть свой дизайнер для нашего «NewTrackBar». Текст их практически не меняется и надо будет сделать один на целую группу разных ЭУ. Различие заключается в том, является ЭУ контейнером или нет. В самом же ЭУ добавляем такой код.
Sub GradEditorM()
If DesignMode Then
Dim fe As New GradEditM
Mgr(0) = Me.mGrad0Fon : Mgr(5) = Me.mGrad0Text
Mgr(1) = Me.mGrad1Fon : Mgr(6) = Me.mGrad0Text
Mgr(2) = Me.mGrad2Fon : Mgr(7) = Me.mGrad0Text
Mgr(3) = Me.mGrad3Fon : Mgr(8) = Me.mGrad0Text
Mgr(4) = Me.mGrad4Fon : Mgr(9) = Me.mGrad0Text
fe.Tag = "NewTrackBar;Окошко,Не заполненное поле, _
Заполненное поле,Ползунок,Кнопки;199,,,,,,"
fe.ShowDialog() : MyBase.Invalidate()
End If
End Sub
Казалось бы, элементам массива с номерами больше 5 можно ничего и не присваивать. Но я это делаю, чтобы обновить ссылки на всякий пожарный. Дело в том, что в NET это принципиальное отличие от VB6. И очень удобное, если конечно умеешь им пользоваться.
Свое обещание показать, как сделать свой редактор свойства я обязательно выполню на следующем ЭУ. Вообще-то это очень похоже на то, что делалось выше, только вызывается через кнопочку в странице свойств, расположенную рядом с самим свойством, как у фонта или цвета.
Впереди у нас еще другие интересные ЭУ: те, которые не располагаются на форме (типа таймер, ToolTip), ЭУ содержащие коллекции (наборы страниц, различные списки) и такие хитрющие бяки как меню. (Александр Воробьев mailto: Alexandr_post3@Rambler.ru) Продолжение следует…