| « Поставить закладку » « Сделать стартовой » | |||
|
|||
|
Статьи:: Программирование на Visual Basic 7 .NET) :: Глава 8. Формы Windows, графический вывод и печать
Глава 8. Формы Windows, графический вывод и печать
Глава №8. Формы Windows, графический вывод и печать Почти во всех
материалах по .NET-программированию, публикуемых в журналах и Интернете,
основное внимание уделяется web-службам, использованию броузера как платформы
программирования, ASP .NET и другим темам, так или иначе связанным с Web.
Многочисленные усовершенствования в области графического
Windows-программирования на стороне клиента удостаиваются разве что кратких
упоминаний. Как бы странно это ни звучало для продукта Microsoft, о
программировании графических приложений в Visual Studio было сказано слишком
мало хорошего; а ведь в этой области появились многие новшества, которых
программисты VB ждали в течение нескольких лет! Бесспорно,
программирование на платформе броузера играет все более важную роль, но и
традиционные клиентские Windows-приложения умирать не собираются. В этой главе
мы попытаемся исправить это упущение и познакомим читателя с основными
принципами программирования, необходимого для построения графических приложений
в VB .NET.
Мы не будем тратить время на пространные рассуждения о технологиях ускоренной разработки приложений (RAD, Rapid Application Development) в IDE [В VB .NET, как и в прежних версиях VB, поддерживается визуальное построение пользовательского интерфейса, а новые свойства (такие, как Anchor и Dock) значительно упрощают его разработку.]и на описания свойств, методов и событий различных элементов — для нормального изложения этого материала понадобилась бы книга вдвое большего объема. Вместо этого мы взглянем на происходящее с точки зрения программиста. Надеемся, читатель досконально разберется в том, как программируются графические приложения в .NET, и в дальнейшем сможет свободно пользоваться документацией или найдет дополнительную информацию в книгах, посвященных графическому программированию. После общих
сведений о формах и элементах мы познакомимся с общими принципами графического
программирования в VB .NET. В этой области новый язык несколько отличается от
прежних версий VB (например, из него исчезли знакомые функции-примитивы Сircle и
Line). Далее рассматривается тема печати, которая в .NET представляет собой
частный случай графического вывода. Хотя новые возможности печати неизмеримо
шире тех, которые поддерживались в прежних версиях VB, знакомый объект Pri nter
теперь не поддерживается, и поддержка печати в программах теперь требует
дополнительных усилий. Для работы с
формами в прежних версиях VB использовался механизм Ruby Forms. Он представлял
собой версию программы Ruby, написанной Аланом Купером (Alan Cooper); в
результате объединения Ruby с QuickBasic появился VB11. Таким
образом, разработка графических приложений в прежних версиях VB зависела от
механизма форм, работа которого была практически полностью скрыта от
программиста. Стартовая форма, словно по волшебству, появлялась на экране, а
элементы размещались на форме программистом на стадии конструирования [В VB6
элементы могли добавляться и во время работы программы, но данная возможность
почти не использовалась из-за крайне неудобного механизма перехвата событий в этих
элементах.]. В программах
С/С++ процесс ручного создания окон и элементов на формах был делом в лучшем
случае непростым, и в этой области VB обладал несомненными преимуществами.
Впрочем, у этого волшебства была и оборотная сторона — механизм Ruby Forms был
недостаточно гибким. Он плохо расширялся, и программисту приходилось мириться со
всеми недочетами в его реализации. Возможности использования форм и элементов VB
были ограничены; в любых нестандартных ситуациях программисту приходилось широко
использовать функции API и перехватывать стандартные сообщения Windows в
уродливых конструкциях с субклассированием [Некоторые пакеты независимых фирм
(например, Desa ware Spy Works) упрощали процесс субклас-сирования, но задача
все равно оставалась непростой.]. Даже такая простая задача, как создание
списка с ускоренным поиском, требовала вызова функции API; стандартная операция
включения новых строк в список усложнялась тем, что свойство Items списка было
доступно только для чтения (к счастью, в VB .NET эти проблемы решаются
элементарно [Например, чтобы быстро заполнить список, достаточно присвоить
коллекцию свойству DataSource.]). Более того,
многие программисты не понимали, чем же в действительности были формы VB —
классами или экземплярами классов? В каком-то смысле и тем и другим. В
результате программа выглядела весьма странно: Forml.Show ' Я -
экземпляр Dim newForm As New
Forml ' А теперь я - класс newForm.Show Программисты
предпочитают работать с логичными и последовательными моделями, а с точки зрения
ООП механизм форм в ранних версиях часто выглядел как нагромождение искусственных и
несогласованных «заплат». В VB .NET ситуация полностью изменилась. Здесь формы
являются экземплярами класса Windows. Forms. Form, и поведение форм может
изменяться посредством наследования точно так же, как и поведение любых других
классов .NET Framework. Например, код специализированной формы может начинаться
со следующего заголовка: Public Class
MyForm Inherits
System.Windows.Forms.Form После этого
форма наделяется новыми возможностями, для чего программист либо переопределяет
члены родительского класса Form, либо добавляет в него новые члены, как для
любого другого класса (см. главу 5). В прежних
версиях VB элементы даже не считались полноценными классами, поэтому команды
вроде следующей были недопустимыми: Dim myButton As New
CommandButton Перед нами
классический пример логической связи «является частным случаем», идеально
подходящий для наследования, однако программист не мог определить новый вариант
кнопки как производный от базового класса стандартной кнопки. Возможности
создания нестандартных элементов, появившиеся в VB5, были основаны на включении
и делегировании, причем в этом процессе использовалась одна из самых
непредсказуемых программ-мастеров (wizards) — не говоря уже о том, что элементы,
построенные в VB5 и 6, несколько отличались от элементов, построенных на других
языках (например, C++ и Delphi). В .NET элементы
и формы являются классами. Как будет показано ниже в этой главе, VB .NET
позволяет определить новый класс текстового поля, начинающийся с заголовка
следующего вида: Public Class
PositivelntegerTextBox Inherits
System.Windows.Forms.TextBox Далее
программист переопределяет или добавляет методы, как в любом другом классе,
причем полученный элемент ничем не отличается от элементов, написанных на С# или
управляемом C++. Короче говоря, в
предыдущих версиях VB графические приложения строились на весьма шаткой и
ненадежной основе, которая к тому же не была в полной мере
объектно-ориентированной. Переход к единой программной модели в рамках
объектно-ориентированной версии VB потребовал коренного пересмотра этой
структуры. Так появилось новое пространство имен Windows.Forms. Программисты VB6
легко привыкают к особенностям работы с формами и элементами в VS .NET IDE. В
вашем распоряжении оказывается пара новых (притом весьма полезных) инструментов,
кратко описанных далее, однако общие принципы работы с панелью элементов
(toolbox) почти не изменились. Для читателей,
никогда не работавших в старой версии VB IDE, мы кратко опишем процесс включения
нового элемента в окно формы.
Рис. 8.1.
Маркеры изменения размеров элемента Существует и
другой способ размещения элементов на форме:
Если элемент не
имеет пользовательского интерфейса (к этой категории относятся, например,
таймеры), дважды щелкните на нем. Элемент размещается в специальной области под
формой и не мешает размещению других элементов.
Позицию и
размеры элементов, размещенных на форме, можно изменить командами меню Format.
Некоторые команды меню Format (в частности, команды подменю Align) работают
только с группами элементов. Группу можно выделить несколькими способами;
например, щелкните на первом элементе группы, а затем, удерживая нажатой клавишу
Shift, последовательно щелкайте на всех остальных элементах, включаемых в
группу. Рядом со всеми выделенными элементами появляются маркеры изменения
размеров, но лишь у одного элемента они будут темными, как у кнопки на рис.
8.2. Элемент с
темными маркерами используется в качестве эталона при выполнении команд меню
Format — например, Format > Make Same Width > Width. При ручном
масштабировании все элементы в группе изменяются пропорционально независимо от
того, какой элемент выбран эталонным.
Рис. 8.2.
Объединение элементов в группы Разместив группу
элементов в нужном месте, выполните команду Format > Lock Controls —
элементы фиксируются в текущей позиции, что предотвращает их случайное
перемещение. В отличие от предыдущих версий VB фиксация положения выполняется
для отдельных групп и не распространяется на новые элементы, размещаемые на
форме.
Сохранение пропорций: свойства Anchor и Dock В прежних
версиях VB6 случайное изменение размеров формы приводило к тому, что все
старания по размещению элементов на стадии конструирования шли прахом. Чтобы
решить эту проблему, программисту приходилось либо самостоятельно
программировать масштабирование формы, либо тратиться на специальный элемент
управления. При использовании класса Windows. Forms проблема исчезает, поскольку
в каждый видимый элемент добавляются два чрезвычайно полезных свойства:
Anchor и Dock. Значение
свойства Anchor задается в окне свойств при помощи небольшого мини-редактора,
показанного на рис. 8.3. Свойство Anchor
привязывает элемент к одному или нескольким краям формы. В этом случае
расстояние между элементом и краем остается постоянным при любых изменениях
размеров контейнера. Элемент, привязанный к противоположным краям контейнера,
автоматически масштабируется с изменением размеров контейнера. Ниже приведен
пример кода, сгенерированного при установке свойства Anchor с привязкой элемента
по левому и правому краю: Me.Button1.Anchor = (System.Windows.Forms.AnchorStyles.Left _ Or
System.Windows.Forms.AnchorStyles.Right) Свойство Dock
обладает значительно большей гибкостью по сравнению со старым свойством Al ign,
которое оно заменяет. Элемент, пристыкованный к краю формы, остается совмещенным
с этим краем при любых изменениях размеров формы. Значение свойства Dock
задается в мини-редакторе, показанном на рис. 8.4.
Рис. 8.3.
Мини-редактор для свойства Anchor
Рис. 8.4.
Мини-редактор для свойства Dock Если щелкнуть в
центральной области, свойство Dock принимает значение Fill, при котором элемент
распространяется по площади контейнера (если это возможно) и занимает такое
положение при любых изменениях размеров контейнера. Заполнение контейнера не
поддерживается для некоторых элементов — например, для однострочных текстовых
полей, всегда имеющих определенную высоту в зависимости от используемого шрифта.
При задании свойства Dock генерируется код следующего вида: Me.Buttonl.Dock =
System.Windows.Forms.DockStyle.Bottom При стыковке
нескольких элементов с одним краем контейнера первый элемент совмещается с
краем, а остальные располагаются как можно ближе к нему, не перекрывая
предыдущие элементы, как показано на рис. 8.5. Поскольку и
стыковка, и привязка выполняются по отношению к контейнеру, вы можете сначала
стыковать/привязать групповые поля (GroupBox) или панели к краям формы, а затем
проделать то же самое с элементами внутри этих контейнеров. Как правило,
необходимость в программировании изменения размеров при этом пропадает.
Например, в процессе построения формы, показанной на рис. 8.6, три групповых
поля были пристыкованы к левому краю формы, после чего три кнопки были
пристыкованы к нижнему краю первого группового поля.
Рис. 8.5. Стыковка элементов с краем формы
Рис. 8.6.
Будущий калькулятор Изменение
порядка перебора элементов (tab order) в прежних версиях VB было делом
утомительным и неприятным, которое несколько упрощалось только специальной
надстройкой (add-in). В VB .NET существует команда View > Tab Order, которая
делает эту задачу элементарной. Все, что от вас потребуется, — ввести нужную
позицию элемента в небольшом поле, которое при выполнении команды View > Tab
Order появляется рядом с элементом (рис. 8.7). Для элементов, находящихся внутри
контейнера, позиция задается в формате «х.у». Например, если групповому полю в
порядке перебора была присвоена позиция 3, то расположенные внутри него элементы
будут иметь номера 3.0, 3.1 и т. д. (чтобы отключить режим ввода порядка
перебора, снова выполните команду Tab Order).
Рис. 8.7.
Команда Tab Order В главе 1 был
приведен довольно сложный код Windows-приложения, автоматически сгенерированный
IDE. Приложение выполняло простейшую функцию — оно реагировало на нажатие
кнопки. Тогда же мы пообещали, что вскоре вы поймете, как работает эта
программа. Настало время вернуться к старому примеру (последовательность
действий при построении этого приложения описана в главе 1). Прежде всего
обратите внимание на то, что при построении приложений на базе форм Windows VS
.NET IDE автоматически включает в решение ссылки на сборки System.Drawing и
System.Windows.Forms; в этом нетрудно убедиться при помощи Object Browser (рис.
8.8). Сборка System.Drawing состоит из одного пространства имен, классы которого
предназначены для изменения размеров и позиционирования форм и элементов. Кроме
того, эта сборка используется при выводе или размещении на форме графических
изображений. Сборка System.Windows.Forms тоже состоит из одного пространства
имен и содержит классы всех элементов управления, а также класс Wi ndows. Forms.
Form, представляющий экземпляры форм.
Рис. 8.8.
Простейшее Windows-приложение в программе Object
Browser 1 Public Class
Forml 2 Inherits
System.Windows.Forms.Form 3 4 IRegion "Windows
Form designer generated code " 5 6 Public Sub
New() 7
MyBase.New() 8 9 ' Вызов необходим
для работы дизайнера форм Windows 10
InitializeComponent() 11 12 ' Дальнейшая
инициализация выполняется 13 ' после вызова
InitializeComponent() 14 End
Sub 15 16 ' Форма
переопределяет Dispose для очистки списка компонентов. 17 Protected
Overloads Overrides Sub Dispose(ByVal disposing As Boolean) 18 If disposing
Then 19 If Not
(components Is Nothing)Then 20
components.Dispose() 21 End
If 22 End
If 23
MyBase.Dispose(disposing) 24 End
Sub 25 Friend
WithEvents Buttonl As System.Windows.Forms.Button 26 27 ' Необходимо для
работы дизайнера форм Windows 28 Private
components As System.ComponentModel.Container 29 30 ' ВНИМАНИЕ:
следующий фрагмент необходим для дизайнера форм Windows 31 ' Для его
модификации следует использовать дизайнер форм. 32 ' Не изменяйте
его в редакторе! 33 <System.Diagnostics.DebuggerStepThrough()Private Sub InitializeComponent() 34 Me.Buttonl = New
System.Windows.Forms.Button() 35
Me.SuspendLayout() 36. 37
'Button1 38 39 Me.Buttonl.Anchor = ((System.Wi ndows.Forms.AnchorStyles.Bottom Or _ System.Windows.Forms.AnchorStyles.Left) Or _ System.Windows.Forms.AnchorStyles.Right) 40
Me.Buttonl.Location = New System.Drawing.Point(46.216) 41 Me.Buttonl.Name
= "Buttonl" 42 Me.Buttonl.Size
= New System.Drawing.Size(200.48) 43
Me.Buttonl.Tablndex = 0 44 Me.Buttonl.Text
= "Click me!" 45' 46
'Form1 47' 48
Me.AutoScaleBaseSize = New System.Drawing.Size(6,15) 49 Me.ClientSize =
New System.Drawing.Size(292,268) 50
Me.Controls.AddRangetNew System.Wi
ndows.Forms.Control(){Me.Buttonl}) 51 Me.Name
="Form1" 52 Me.Text = "First
Windows Application" 53
Me.ResumeLayouUFalse) 54 55 End
Sub 56 57 #End Region
58 59 Private Sub Buttonl_Click(ByVal sender As System.Object.ByVal e As _ System.EventArgs)Handles Buttonl.Click 60 MsgBox("Thanks
for clicking!") 61 End
Sub 62 End
Class Из строк 1 и 2
видно, что перед нами открытый (Public) класс, производный от класса Form
пространства Имен System.Windows. Forms. Знак # в строке 4 отмечает
на-чалорегиона в автоматически сгенерированном коде. Помните, что при
любых операциях в окне свойств или перетаскивании элементов в дизайнере
генерируется программный код. По мере накопления опыта вы научитесь
просматривать этот код и редактировать его. В строках 6 и 7
начинается конструктор класса. Как обычно, в начале конструктора производного
класса вызывается конструктор базового класса (за дополнительной информацией по
этой теме обращайтесь к разделу «Иерархия классов пространства имен
System.Windows.Forms» этой главы). В строке 10
вызывается метод Initial izeComponent, определяемый в строках 33-35. Этот метод
генерируется автоматически и преобразует в программный код все ваши действия на
стадии конструирования и в окне свойств. Например, кнопка объявляется в строке
25; позже ее экземпляр создается в строке 34, а размещение кнопки на форме
происходит в строке 50. Также обратите внимание на то, как в строках 44 и 52
программа задает свойство Text кнопки и формы в соответствии с данными,
введенными в окне свойств.
В строках 40 и
42 методы пространства имен System. Drawl ng задают позицию и размеры кнопки, а
в строках 48-49 аналогичные действия выполняются для формы. По умолчанию
единицей измерения в графических операциях являются пикселы (!), а не твипы
(twips).
В строках 17-24
реализуется версия метода Dispose, необходимого для приложений на базе форм
Windows. Формат этого метода несколько отличается от простого метода Dispose,
описанного в главе 5; в данном примере освобождаются все ресурсы, используемые
всеми элементами формы, а также самой формой. В строке 25
используется уже знакомый синтаксис (см. главу 6), указывающий на то, что кнопка
является источником событий. Непосредственное подключение обработчика происходит
в строках 59-61 с ключевым словом Handles. Синтаксис всех автоматически
сгенерированных событий элементов и форм в .NET аналогичен приведенному в строке
59: Sub ObjectName_eventname(ByVal sender As Object.Byval e As EventArgs)_ Handles
ObjectName.eventname В параметре
sender передается ссылка на элемент, инициировавший событие. В параметре
EventArgs инкапсулируется описание события (при таких событиях, как KeyPress или
MouseDown, используются объекты классов, производных от EventArgs, — такие, как
KeyEventArgs и MouseEventArgs; в них передается дополнительная
информация). Приложение на
базе форм Windows можно запустить из процедуры Sub Main, являющейся точкой входа
вашего приложениях. При этом вызывается метод Application.Run, которому
передается имя формы: Pubic Sub
Main() Dim myForm As New
Forml() Application.Run(myForm) End Sub
(Замаскированный
вызов Application.Run присутствует везде, где форма используется в качестве
стартового объекта. Интересная подробность: этот вызов обязателен в С#, но не в
VB .NET.)
Ограниченный
объем книги не позволяет нам рассмотреть все свойства класса Form, но мы хотим
выделить существенные различия между поведением форм в прежних версиях VB и VB
.NET. Одно из самых принципиальных изменений связано с использованием шрифтов.
Вместо старых шрифтовых свойств (например, FontBold) используется класс Font
пространства имен System.Drawing, самый распространенный конструктор которого
выглядит следующим образом: Sub New(ByVal family As FontFamily.ByVal emSize As Single._ ByVal style As
FontStyle) Термин
«семейство шрифтов» (font family) хорошо знаком пользователям Word. Семейством
называется группа шрифтов (Times New Roman, Courier New, Arial и т. д.),
объединенных сходным графическим стилем, но обладающих разным кеглем и
атрибутами начертания (курсив, жирный шрифт и т. д.). Например,
поскольку оператор Or объединяет значения отдельных битов, следующая команда
присваивает переменной MyFont объект полужирного курсивного шрифта Arial с
кеглем 12 пунктов: myFont =New System.Drawing.Font("Arial". 12._ FontStyle.Bold Or
FontStyle.Italic) Следующая
процедура события Button 1_C1ick изменяет шрифт при нажатии
кнопки: Private Sub
Buttonl_Click(ByVal sender As System.Object. _ ByVal e As
System.EventArgs) Handles Buttonl.Click Dim myFont As
System.Drawing.Font myFont = New
System.Drawing.Font("Arial".12. FontStyle.Bold Or
FontStyle.Italic) Me.Font = myFont
End Sub На рис. 8.9
показано, как выглядит новый шрифт на кнопке.
Рис. 8.9.
Кнопка с текстом, оформленным полужирным курсивным
шрифтом
В сочетании со
свойствами Anchor и Dock часто используются свойства MinimumSize и MaximumSize,
определяющие соответственно минимальные и максимальные размеры формы. Значения
этих свойств представляют собой объекты Size. Например, следующая команда запрещает
уменьшать форму до размеров, меньших размеров кнопки: Me.MimmumSize =New
Size(Buttonl.Size) Поскольку
свойство MinimumSize управляет изменением свойства Size, в заданные размеры
включается размер заголовка окна. Таким образом, после выполнения
предыдущей команды в уменьшенном окне почти не останется места для кнопки. Лучше
воспользоваться командой вида Me.MinimumSize =
New Size(Buttonl.Size.Width * 2, Button1.Size.Height * 2)
Новое свойство
ClientSi ze возвращает информацию о клиентской области формы (области, не
включающей заголовок и рамку). Свойство Bounds предназначено для чтения/записи
структуры Rectangle, содержащей ширину и высоту формы и позицию ее левого
верхнего угла.
Многие свойства,
методы и события форм отличаются от своих прототипов из VB6. Важнейшие изменения
перечислены в табл. 8.1. Таблица
8.1. Изменения в свойствах, методах и событиях
форм
Элементы меню и новый редактор меню Visual
Studio Хотя визуальное
конструирование форм в книге почти не рассматривается, мы просто не могли не
упомянуть новый редактор меню. Программисты VB уже давно ждали чего-то
подобного. Впрочем, при всем удобстве нового редактора вы извлечете из него
максимум пользы лишь при полном понимании кода, сгенерированного
IDE. Построить меню в
редакторе меню (Menu Editor) несложно. Сначала с панели элементов на форму
перетаскивается элемент MainMenu; на форме появляется заготовка меню (рис.
8.10). Теперь можно
переходить к вводу команд меню. При вводе очередной команды в редакторе
появляются поля для ввода следующей команды или команд, находящихся на других
уровнях иерархии меню (рис. 8.11). Чтобы отредактировать команду меню, созданную
ранее, достаточно щелкнуть на ней (для изменения существующих меню щелчок
делается на элементе MainMenu на панели под формой). Порядок команд меню
изменяется операциями вырезания/вставки (эти операции работают даже в главной
строке меню). Как и в прежних версиях VB, перед клавишами ускоренного вызова
ставится префикс &. На рис. 8.11 показано меню с клавишей ускоренного вызова
и разделительной чертой. Прежде чем
переходить к коду, сгенерированному для меню на рис. 8.11, необходимо знать, что
меню формы инкапсулируется в классе System.Windows.Forms.MainMenu. Объект
MainMenu выполняет функции контейнера для экземпляров Menu Item. Для организации
подменю в экземплярах Menultem определяется свойство Menultems; значение этого
свойства представляет собой коллекцию класса Menu.MenuItemCollection, содержащую
другие объекты Menultem. Что касается
кода, соответствующего рис. 8.11, то он начинается с объявления команд меню.
Имена, принятые по умолчанию, были заменены более содержательными: вместо
Menulteml используется имя mnuFile и т. д.
Рис. 8.10.
Начало построения меню в редакторе меню
Рис. 8.11.
Построение меню в новом редакторе меню Visual
Studio Friend WithEvents MainMenul As System.Windows.Forms.MainMenu Friend WithEvents mnuFile As System.Windows.Forms.Menultem Friend WithEvents mnuEdit As System.Windows.Forms.MenuItem Friend WithEvents mnuHelp As System.Windows.Forms.Menultem Friend WithEvents mnuOpen As System.Windows.Forms.Menultem Friend WithEvents mnuSave As System.Windows.Forms.Menultem Friend WithEvents
mnuExit As System.Windows.Forms.Menultem
Экземпляр
главного меню создается командой, которую IDE включает в метод InitializeComponent: Me. MainMenul =New
System. Windows. Forms. MainMenu () Следующий
фрагмент создает в методе Initial! zeComponent объекты отдельных команд меню: Me.mnuFile = New
System. Windows. Forms. MenuItem() Me.mnuNew = New
System. Windows. Forms. MenuItem() Me.mnuOpen = New
System. Windows. Forms. MenuItem() Как видно из
этого фрагмента, меню File верхнего уровня тоже представлено экземпляром класса Menultem. Объекты
команд включаются в экземпляр Mai nMenu вызовом метода AddRange класса
Menultems. В следующем примере метод AddRange включает в меню сразу три
команды: Me . Mai nMenul .
Menultems . AddRange( New System . Windows . Forms . Menultem( ) {Me.mnuFile.
Me.mnuEdit, Me.mnuHelp}) Поскольку у объектов отдельных команд тоже имеется свойство Menultems, автоматически сгенерированный код включения команд выглядит практически так же: Me . mnuFile . Menu
Items . AddRange ( New System . Windows . Forms . Menul tem( )_ {Me.mnuNew.
Me.mnuOpen. Me.mnuExit. Me.mnuSep}) На последнем шаге IDE подключает события Click для команд меню. Сгенерированный код выглядит так: Private Sub
mnuExit_Click(ByVal sender As System. Object, _ ByVal e As System.
EventArgs)Handles mnuExit. Click В прежних
версиях VB создавать контекстные меню было неудобно. В .NET контекстное меню
представляется экземпляром класса ContextMenu и редактируется в визуальном
режиме. Чтобы связать контекстное меню с элементом или формой, достаточно задать
значение свойства ContextMenu этого элемента или формы. По стандартам Windows
контекстные меню вызываются щелчком правой кнопки мыши; в .NET это происходит
автоматически благодаря наследованию. Вам не придется программировать событие
MouseDown — поддержка контекстных меню реализована в классе Control ,
производными от которого являются формы и элементы. При перетаскивании на форму
элемента ContextMenu IDE генерирует почти такой же код, как для команд главного
меню: Friend WithEvents
ContextMenul As System.Windows.Forms.ContextMenu Me.ContextMenul =New System,Windows.Forms.ContextMenu() Так же
вызывается метод AddRange: Me.ContextMenul.MenuIterns.AddRange(New System.Windows.Forms.MenuItem()
{Me.MenuIteml}) Чтобы быстро
задать значение свойства Name команд контекстного меню, перейдите в режим
редактирования имен щелчком правой кнопкой мыши.
В прежних
версиях VB при программировании приложений с интерфейсом MDI (Multiple Document
Interface) родительская форма MDI выбиралась на стадии конструирования. В .NET
эта задача решается иначе — свойству IsMdiContainer формы задается значение
True. Программист создает дочерние формы MDI на стадии конструирования или
выполнения, а затем заносит в их свойство Mdi Parent ссылку на форму со
свойством I sMdi Conta i пег, равным True. Таким образом, в программах VB .NET
можно сделать то, что было практически нереально в предыдущих версиях VB, —
изменять связи MDI во время работы программы. Кроме того, приложение может
содержать несколько родительских форм MDI; в VB6 такая возможность не
поддерживалась. Рассмотрим
пример. Создайте приложение со следующей процедурой Forml_Load: Private Sub Forml_Load(ByVal sender As System.Object._ ByVal e As
System.EventArgs) Handles MyBase.Load Me.Text = "I'm an
MDI Parent" Me.IsMdiContainer -
True Dim MyChild As New
System.Windows.Forms.Form() MyChiId.MdiParent =
Me MyChild.Show() MyChild.Text ="MDI
Child" End Sub Примерный вид
окна показан на рис. 8.12. Конечно, это
весьма жалкое подобие приложения MDI. В приложениях MDI обычно присутствует меню
Window, позволяющее расположить открытые дочерние окна «черепицей» или
«мозаикой», а также активизировать любое дочернее окно. Меню Window реализуется
в родительском окне, причем его код может быть достаточно
простым: Public Sub
InitializeMenu() Dim mnuWindow As New MenuItem("&Window") MainMenu1.MenuIterns.Add(mnuWindow) mnuWindow.MenuItems.AddCNew Menultem _ ("&Cascade", AddressOf WindowCascade_Clicked)) mnuWindow.MenuItems.Add(New Menultem ("Tile &Horizontal", AddressOf WindowTileHoriz_C1icked)) mnuWindow.MenuItems.Add(New Menultem _ ("Tile &Vertical". AddressOf WindowTileVert_Clicked)) mnuWindow.MdiList = True End
Sub Protected Sub WindowCascade_Clicked(ByVal Sender As Object.
ByValeAs System.EventArgs) Me.LayoutMdi(MdiLayout.Cascade) End
Sub Protected Sub WindowTileHoriz_Clicked(ByVal Sender As Object._ ByVal e As
System.EventArgs) Me.LayoutMdi(MdiLayout.TileHorizonta1) End
Sub Protected Sub WindowTileVert_Clicked(ByVal Sender As Object, ByVal e As
System.EventArgs) Me.LayoutMdi(MdiLayout.TileVertica1) End Sub Примерный вид
окна, полученного при вызове InitializeMenu для формы из предыдущего примера,
показан на рис. 8.13.
Рис. 8.12.
Простейшее приложение MDI
Рис. 8.13.
Типичное меню Window в приложении MDI Чтобы оповестить
родительскую форму об активизации дочернего окна, следует зарегистрировать
обработчик для события mdi ChildActivate. Активизируемое окно определяется при
помощи свойства ActiveMdiChild класса Form. Например, в следующем фрагменте
создается обработчик для вывода в строке состояния (элемент StatusBar)
родительской формы содержимого заголовка дочернего окна: AddHandler
Me.MdiChi1dActivate.AddressOf Me.MdiChi1dActivated Остается лишь определить
следующую процедуру: Protected Sub
MdiChi1dActivated(sender As object, e As System.EventArgs) If (Me.ActiveMdiChild <> Nothing) Then statusBarl.Text Me.ActiveMdiChild.Text End If End Sub Диалоговые формы и диалоговые окна В .NET Framework
включена поддержка стандартных диалоговых окон; соответствующие элементы
расположены в нижней части панели элементов (рис. 8. 14).
Рис. 8.14.
Стандартные диалоговые окна на панели элементов Элементы
диалоговых окон обладают различными свойствами, управляющими их поведением.
Например, у диалоговых окон открытия/сохранения файлов имеется свойство Filter.
В этом разделе рассматриваются лишь основные принципы использования этих
полезных элементов. Полная информация об их свойствах приведена в электронной
документации .NET.
Классы всех
стандартных диалоговых окон являются производными от абстрактного класса
CommonDialog. Важнейший метод этого класса ShowDialog предназначен для
отображения диалогового окна. По возвращаемому значению этого метода можно
узнать, какую кнопку нажал пользователь в диалоговом окне — ОК или Cancel. Метод
ShowDialog имеет следующий синтаксис: Public Function ShowDialogO As
DialogResult При проверке
возвращаемое значение сравнивается с константами DialogResult .ОК и
DialogResult.Cancel. При
использовании диалогового окна выбора цвета (Col orDialog) программа обычно
запрашивает свойство Color и назначает его свойству ForeColor или BackColor
элемента или формы. Например, приведенная ниже процедура изменяет фоновый цвет
формы при нажатии на кнопку: Private Sub btnCo1or_Click(ByVal sender As System.Object. _ ByVale As System.EventArgs)Handles btnColor.Click Dim myDialog As New ColorDialog() Dim Temp As Color = btnColor.BackColor If myDialog.ShowDialog() = DialogResult.OK Then Me.BackColor = myDialog.Color btnColor.BackColor = Temp End If End Sub На экране
появляется стандартное диалоговое окно, в котором выбирается нужный
цвет. Диалоговое окно
выбора шрифта (FontDialog) хорошо знакомо всем пользователям текстовых
редакторов Windows. В свойстве Font объекта диалогового окна возвращается объект
Font, обычно назначаемый свойству Font элемента или формы. В следующем примере
предполагается, что на форме находится текстовое поле TextBoxl и кнопка
Buttonl: Private Sub Buttonl_Click_l(ByVal sender As System.Object. _ ByVal e As
System.EventArgs) Handles Buttonl.Click Dim myDialog As New
FontDialog() If myDialog.ShowDialog() = DialogResult.OK Then TextBoxl.Font myDialog.Font End If End Sub (обратите
внимание, как однострочное текстовое поле автоматически подстраивается под
размеры нового шрифта). Абстрактный
класс FileDialog является базовым для двух специализированных
подклассов:
Рассмотрим
использование этих диалоговых окон на простом примере. Элемент RichTextbox .NET,
как и его аналоги из предыдущих версий VB, поддерживает методы LoadFilе и
SaveFile для быстрого открытия и сохранения файлов. Чтобы пример стал более
реалистичным, свойство Filter диалогового окна будет ограничивать вывод файлов
.txt и .rtf (в следующем фрагменте эта строка выделяется жирным шрифтом). Также
обратите внимание на то, как при вызове метода LoadFi 1е указывается текстовый
формат файла (при загрузке файлов RTF второй параметр указывать не
обязательно): Private Sub mnuOpen_Click(ByVal sender As System.Object,_ ByVal e As System.EventArgs)Handles mnuOpen.Click Dim myDialog As New OpenFileDialog() myDialog.Filter = "text (*.txt),RTF (*.rtf)|*.txt:*rtf" If
myDialog.ShowDialog =DialogResult.OK Then ' При загрузке текстовых файлов во втором параметре ' необходимо передавать признак типа файла. Dim Temp As String = myDialog.FileName.Tollpper If
Temp.EndsWith("TXT") Then RichTextBoxl.LoadFi1e(myDialog.FileName, Ri
chTextBoxStreamType.Plai nText) Else Ri chTextBoxl.
LoadFiletmyDialog.File_Name. Ri ChTextBoxStreamType.RichText) End
If End If End
Sub Чтобы вывести
собственное диалоговое окно, создайте форму, задайте ее свойствам ControlBox,
MinimizeBox и MaximizeBox значение False, а свойству Modal — значение True.
Форму следует выводить методом ShowDialog в режиме модального диалогового окна.
Если при этом задать свойство TopMost равным True, диалоговое окно будет
располагаться поверх всех окон на экране (и вам уже не придется использовать
функцию API SetWindowPos). Однако поведение
стандартных кнопок несколько изменилось по сравнению с VB6. Свойства Default и
Cancel не поддерживаются, поэтому соответствующие элементы-кнопки назначаются
свойствам AcceptButton и Cancel Button: Me.AcceptButton =
btnOK Me.Cancel Button = btnCancel После вызова
ShowDialog программа может узнать, какая кнопка была нажата на форме, при помощи
свойства Dial ogResult кнопки или самой формы (нажатие кнопки с заданным
свойством DialogResul t приводит к автоматическому закрытию формы, на которой
эта кнопка находится). Размещение элементов на форме во время
выполнения До выхода VB6
существовал лишь один способ размещения элементов на форме во время выполнения
программы — массивы элементов. В VB6 появился более удобный режим динамического
добавления элементов, но и в этом случае не обходилось без проблем с обработкой
событий для добавленных элементов. В VB .NET этот процесс подвергся существенным
изменениям. Например, создайте новое приложение Windows и включите в него
следующий фрагмент: 1 Private Sub
Forml_Load(ByVal sender As System.Object,ByVal e As _ 2 System.EventArgs)
Handles MyBase.Load 3 Dim newButton As
New System.Windows.Forms.Button() 4 ' Задать свойства
newButton 5 With
newButton 6 .Visible
=True 7 .Size =New
Size(l00.l00) 8 ' .Text ="I'm a
new button" 9 ' Обычно здесь
задаются и другие свойства 10 End
With 11
Me.Controls.Add(newButton) 12 AddHandler
newButton.Click.AddressOf Me.newButton_Click 13 End
Sub 14 Public Sub
newButton_Click(ByVal sender As _ 15
System.Object.ByVal e As System.EventArgs) 16 MsgBox("You
clicked on my new button") 17 End
Sub В строке З
создается новая кнопка, а в строках 5-10 удобная сокращенная запись With
используется для задания ряда свойств объекта newButton. Только в строке 11
новая кнопка размещается на форме. Строка 12 снова демонстрирует замечательную
гибкость механизма обработки событий .NET: код, содержащийся в строках 14-17, назначается обработчиком
события для кнопки. Возможный результат выполнения программы показан на рис.
8.15.
Рис. 8.15.
Создание кнопки во время выполнения программы Прежде всего
следует сказать, что «визуальное наследование», часто упоминаемое в рекламных
материалах по VB .NET, существует лишь в больном воображении специалистов по
маркетингу. На самом деле речь идет о том, что формы, созданные в программе,
могут использоваться как основа для определения новых форм посредством
наследования. Конечно, это весьма удобно и полезно, но ничего принципиально
нового в таком наследовании нет. Класс формы, производный от Windows. Forms.
Form и дополненный специализированными свойствами, методами и событиями, в
дальнейшем может использоваться в качестве базового для определения новых
классов.[История с «визуальным наследованием» как нельзя лучше демонстрирует
тупость специалистов по рекламе. Возможно, эффектный термин поразит
некомпетентноге менеджера, но у программистов он лишь вызывает
раздражение.] Предположим, вы
хотите создать для своей организации окно-заставку (splash screen), которое
отдельные подразделения будут дополнять своими данными. Базовая форма создается
следующим образом:
Предположим, вы
хотите преобразовать стандартное приложение Windows в библиотеку классов, чтобы
откомпилировать его в DLL вместо ЕХЕ-файла. Проще всего это делается
так:
После построения
библиотеки DLL остается лишь включить ссылку на нее в решение, после чего классы
DLL используются в программе наравне с остальными классами. При выполнении
команды Project > Add Inherited Form можно поручить IDE включить в проект
весь необходимый код, для чего достаточно ответить на несколько вопросов в
диалоговых окнах. С другой стороны, возня с диалоговыми окнами выглядит немного
глупо, поскольку после включения ссылки на DLL в проект остается лишь
привести первую строку приложения к следующему виду: Public Class
Form1 Inherits
SplashScreenBase. Form1 Все остальное за
вас сделает механизм наследования! Этот способ особенно хорош тем, что работа
дизайнера автоматически синхронизируется с унаследованной формой. Построение нестандартных элементов на базе
наследования Наследование
применяется при построении не только новых форм, но и новых элементов. Допустим,
вы хотите создать текстовое поле, предназначенное для ввода только целых чисел.
Как обычно, все начинается с объявления нового класса: Public Class
PositivelntegerTextBox Inherits System .
Windows . Forms . TextBox Остается лишь
запрограммировать поле на нужное поведение. В оставшейся части этого раздела
будет показано, как класс Positi velntegerTexBox наполняется специализированными
свойствами, событиями и методами.
Итак, создайте новую библиотеку классов и включите в решение ссылку на сборку Windows.Forms.dll.
Начнем с
переопределения событий базового класса. Например, событие OnChange поможет
предотвратить ввод в поле всего, что не является целым числом. Следующий
обработчик разрешает вводить или вставлять из буфера только положительные целые
числа: 1 Public Class
PositivelntegerTextBox 2 Inherits
System.Windows.Forms.TextBox 3 Protected
Overrides Sub OnTextChanged(ByVal e As EventArgs) 4
MyBase.OnTextChanged(e) 5 If Not
(IsNumeric(Me.Text))Then 6 Me.Text -
String.Empty 7
Else 8 Dim temp As
Decimal 9 temp =
CType(Me.Text.Decimal) 10 If temp -
Math.Round(temp.0) <> 0 Then 11 Me.Text =
String.Empty 12 End
If 13 End
If 14 End
Sub 15 End
Class В строках 1 и 2
объявляется специализированная версия обычного текстового поля. Поскольку
при специализации на базе наследования сохраняются все члены базового класса,
которые не подверглись явным изменениям, вам не придется прибегать к услугам
программы-мастера (как в VB6), чтобы решить вопрос с неизменными свойствами —
например, с основным цветом. При переопределении событий в производном классе
обычно вызывается обработчик базового класса, как в строке 4. Необходимость этой
строки связана с тем, что мы не программируем обработку этого события от начала
и до конца, а хотим воспользоваться унаследованными аспектами поведения базового
класса. В строках 5-6 предотвращается ввод нечисловых данных типа 32Skiddoo. В
строках 9-12 из текстового поля удаляются дробные числа; при помощи встроенной
функции Round программа убеждается в том, что округленное число совпадает с
исходным. Следует заметить, что простое уничтожение введенного текста выглядит
несколько жестоко по отношению к пользователю. В более изощренной программе
следовало бы сохранить предыдущий текст, чтобы восстановить его при
необходимости. В этом случае одна ошибка ввода не будет приводить к полной
потере введенных данных. На этой стадии
приведенный выше фрагмент можно откомпилировать в DLL и получить вполне
работоспособный элемент. Чтобы протестировать элемент после компиляции, создайте
новое приложение Windows и поместите элемент на панель элементов:
Элемент
помещается на вкладку .NET Framework Components (рис. 8.16).
Рис. 8.16.
Нестандартный элемент на вкладке .NET Framework
Components
Рис. 8.17.
Нестандартный элемент на панели элементов Нестандартный
элемент размещается в нижней части панели (рис. 8.17). Дважды щелкните в строке
Poslti velntegerTextBox, и элемент появится на форме. Обратите внимание: наш
простейший элемент обладает всеми свойствами обычных текстовых полей и
особенностями поведения, присущими текстовым полям в режиме конструирования. Все
это было автоматически унаследовано от класса текстовых полей Windows. Forms.
TextBox без малейших усилий с вашей стороны.
Добавить
поддержку нового события в элемент ничуть не сложнее, чем включить обработчик
события в класс (эта тема рассматривалась в главе 6). Предположим, каждый раз,
когда пользователь вводит нечто отличное от положительного целого числа, элемент
должен инициировать событие BadDataEntered. Новый код класса выделен жирным
шрифтом: Public Class PositivelntegerTextBox Inherits System.Windows.Forms.TextBox Public Event BadDataEntered(ByVal Sender As Object, _ ByVal e As
EventArgs) Protected Overrides Sub OnTextChanged(ByVal e As EventArgs) MyBase.OnTextChanged(e) If Not
(IsNumeric(Me.Text)) Then Me.Text = String.Empty RaiseEvent
BadDataEntered(Me. New System.EventArgs()) Else Dim temp As Decimal temp = CType(Me.Text,Decimal) If temp = Math.Round(temp.0) <> 0 Then Me.Text =
String.Empty RaiseEvent BadDataEntered(Me, New System.EventArgs()) End If End If End Sub End
Class В элементах VB
существует понятие события по умолчанию (default event). Событие по
умолчанию срабатывает автоматически при двойном щелчке на экземпляре элемента в
дизайнере форм. Событие по умолчанию задается при помощи специального атрибута.
Атрибуты соответствуют экземплярам класса System.Attribute; атрибут Def aul
tEvent входит в пространство имен System. ComponentModel. В программе атрибуты
заключаются в угловые скобки, а при установке атрибута Defaul tEvent указывается
имя события в кавычках. Чтобы назначить событие BadDataEntered событием по
умолчанию для данного элемента, приведите начало класса к следующему
виду: Imports System.ComponentModel <DefaultEvent("BadDataEntered")> Public Class
_ PositivelntegerTextBox Inherits
System.Windows.Forms.TextBox На следующем
шаге класс будет дополнен свойствами MinValue и MaxValue. Программная реализация
этих свойств выглядит весьма прямолинейно. При создании экземпляра переменным
присваиваются значения 1 и максимальное значение типа Long соответственно. В
дальнейшем необходимо проследить за тем, чтобы свойство MinValue не оказалось
меньше 1, а свойство MaxVal ue не превышало предельной величины: Private m_Min As Long = 1 Private m_Max As Long = Long.MaxValue Public Property
MinValue()As Long Get Return m_Min End
Get SetCByVal Value As Long) m_Min = Math.Maxd,Value) End Set End
Property Public Property
MaxValue()As Long Get Return m_Max End
Get Set(ByVal Value As Long) m_Max =Math.Min(m_Min.Value) End Set End
Property Если включить
этот код в проект и откомпилировать его, свойство будет поддерживаться
элементом, но не будет отображаться в окне свойств. Проблема решается установкой
атрибута Browsable для имени свойства: <Browsable(True)> Public Property MinValue Примерный вид
окна свойств при установке атрибута Browsable для свойств MinValue и MaxValue
показан на рис. 8.18.
Рис. 8.18.
Свойства MaxValue и MinValue в окне свойств Как видите,
нестандартное свойство отображается в окне свойств, а свойства MaxValue и
MinValue выводятся с начальными значениями, которые им были присвоены. Еще
интереснее то, что окно свойств отвергает любые попытки изменения свойств
способом, запрещенным в программе (например, присваивание MinValue значения,
меньшего 1). Чтобы свойство
не отображалось в окне свойств, достаточно установить для него атрибут
<Browsable(False)>. Запрет может устанавливаться как для новых, так и для
переопределяемых свойств. Например, следующий фрагмент убирает стандартное
свойство Text специализированного текстового поля из окна
свойств: <Browsable(False)> Public Overrides Property Text() As
String Get Return MyBase.Text End Get Set(ByVal
Value As String) MyBase.Text = Value End Set End
Property Как видно из
рис. 8.19, свойство Text перестает отображаться в окне свойств.
Рис. 8.19.
При установке атрибута Browsable свойство Text не
отображается Иерархия пространства имен
System.Windows.Forms Рассмотрев
простейшие возможности использования пространства имен Windows . Forms на
конкретных примерах, мы переходим к иерархии классов, показанной на рис.
8.20. Диаграмма
выглядит весьма устрашающе, но мы уделим основное внимание главной линии
наследования: System.ComponentModel .Component > Control Из рис. 8.20
видно, что классы форм и элементов являются производными от класса Control.
Например, генеалогия класса Form выглядит так: ScrollableControl >ContainerControl >
Form
Рис. 8.20.
Иерархия классов пространства имен Windows.Forms Текстовое поле
входит в другую ветвь: Control > TextBoxBase > RichTextBox >
TextBox Иерархия
основных разновидностей кнопочных элементов выглядит так: Control > ButtonBase >
Button >
CheckBox >
RadioButton Класс Control
вмещает в себя наибольшее количество функций (более 300) и обладает наибольшими
функциональными возможностями. Классы форм и элементов объявляются производными
от членов класса Control.
Класс Component
является базовым классом для всех компонентов пространства имен System. Wi
ndows. Forms. В нем собраны члены, необходимые для реализации включения (в
частности, размещения элементов на форме или дочерних окон MDI в родительском
окне MDI). В основном они реализованы в виде методов интерфейса IComponent.
Поскольку класс Component также занимается освобождением ресурсов, расходуемых
при визуальной разработке, он реализует IDisposable. По |