к оглавлению   к 4GL - визуальному программированию

Формы Windows, графический вывод и печать

Почти во всех материалах по .NET-программированию, публикуемых в журналах и Интернете, основное внимание уделяется web-службам, использованию броузера как платформы программирования, ASP .NET и другим темам, так или иначе связанным с Web. Многочисленные усовершенствования в области графического Windows-программирования на стороне клиента удостаиваются разве что кратких упоминаний. Как бы странно это ни звучало для продукта Microsoft, о программировании графических приложений в Visual Studio было сказано слишком мало хорошего; а ведь в этой области появились многие новшества, которых программисты VB ждали в течение нескольких лет!

Бесспорно, программирование на платформе броузера играет все более важную роль, но и традиционные клиентские Windows-приложения умирать не собираются. В этой главе мы попытаемся исправить это упущение и познакомим читателя с основными принципами программирования, необходимого для построения графических приложений в VB .NET.

Visual Studio IDE в отличие от предыдущих версий 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, мы кратко опишем процесс включения нового элемента в окно формы.

  1. Дважды щелкните на элементе или перетащите его с панели элементов на форму.
  2. Расположите элемент в нужной позиции (щелкните внутри элемента и перетащите его мышью).
  3. Измените размеры элемента при помощи маленьких квадратных маркеров, показанных на рис. 8.1 (при необходимости можно выполнить более точную настройку размеров при помощи комбинации Shift+клавиши со стрелками).

Рис. 8.1. Маркеры изменения размеров элемента

Существует и другой способ размещения элементов на форме:

  1. На панели элементов щелкните на кнопке элемента, размещаемого на форме.
  2. Расположите курсор над формой (в отличие от прежних версий VB внешний вид курсора теперь изменяется в зависимости от выбранного элемента).
  3. Щелкните в точке, где должен находиться левый верхний угол элемента, и перетащите курсор в позицию правого нижнего угла (при необходимости размеры элемента можно изменить при помощи комбинации Shift+клавиши со стрелками).

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

В процессе построения формы в IDE прежде всего необходимо помнить о том, что IDE автоматически генерирует довольно большой объем кода. В отличие от прежних версий VB, все ваши действия в дизайнере форм и в окне свойств воспроизводятся в виде сгенерированного кода, который можно просмотреть (а при необходимости и отредактировать).

Позицию и размеры элементов, размещенных на форме, можно изменить командами меню Format. Некоторые команды меню Format (в частности, команды подменю Align) работают только с группами элементов. Группу можно выделить несколькими способами; например, щелкните на первом элементе группы, а затем, удерживая нажатой клавишу Shift, последовательно щелкайте на всех остальных элементах, включаемых в группу. Рядом со всеми выделенными элементами появляются маркеры изменения размеров, но лишь у одного элемента они будут темными, как у кнопки на рис. 8.2.

Элемент с темными маркерами используется в качестве эталона при выполнении команд меню Format — например, Format > Make Same Width > Width. При ручном масштабировании все элементы в группе изменяются пропорционально независимо от того, какой элемент выбран эталонным.

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

Рис. 8.2. Объединение элементов в группы

Разместив группу элементов в нужном месте, выполните команду Format > Lock Controls — элементы фиксируются в текущей позиции, что предотвращает их случайное перемещение. В отличие от предыдущих версий VB фиксация положения выполняется для отдельных групп и не распространяется на новые элементы, размещаемые на форме.

Если несколько элементов должны действовать как единое целое (например, набор переключателей), разместите их внутри элемента GroupBox вместо элемента Frame. Усовершенствованный элемент Panel позволяет использовать полосы прокрутки, которые также могут использоваться при группировке элементов. Кстати говоря, теперь на элементы GroupBox и Panel можно перетаскивать существующие элементы.

Сохранение пропорций: свойства 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

Изменение порядка перебора элементов (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 кнопки и формы в соответствии с данными, введенными в окне свойств.

Свойство Text в VB .NET заменяет свойство Caption, использовавшееся в предыдущих версиях VB.

В строках 40 и 42 методы пространства имен System. Drawl ng задают позицию и размеры кнопки, а в строках 48-49 аналогичные действия выполняются для формы. По умолчанию единицей измерения в графических операциях являются пикселы (!), а не твипы (twips).

Хотя старые свойства Left, Top, Width и Height по-прежнему поддерживаются, теперь удобнее работать со свойствами Location и Size. Свойство Location задает или возвращает структуру Point с информацией о точке. Свойство Size использует структуру System.Drawing.Size для хранения информации о прямоугольной области. Поскольку каждый из этих классов просто инкапсулирует два целых числа, в них предусмотрены свойства для задания отдельных компонентов (например, свойства Width для объекта Size и свойства Y для свойства Point).

В строках 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.)

Вызов MsgBox в строке 60 характерен для программистов с большим стажем работы на VB. В пространство имен Windows.Forms входит класс MessageBox, по своим возможностям значительно превосходящий команду MsgBox, но старые привычки берут свое. Обратитесь к документации по этому классу, если вы предпочитаете работать с ним.

Свойства форм

Ограниченный объем книги не позволяет нам рассмотреть все свойства класса 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. Кнопка с текстом, оформленным полужирным курсивным шрифтом

При изменении свойства Font формы новый шрифт автоматически используется для вывода свойства Text всех элементов, расположенных на форме. Исключение составляют элементы, которым были назначены собственные шрифты.

В сочетании со свойствами 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)

При изменении свойства MaximumSize часто используется класс System.Windows. Forms.Screen, предназначенный для работы с экранами (с поддержкой нескольких мониторов). Этот класс также используется при изменении свойств DesktopBounds и DesktopLocation.

Новое свойство ClientSi ze возвращает информацию о клиентской области формы (области, не включающей заголовок и рамку). Свойство Bounds предназначено для чтения/записи структуры Rectangle, содержащей ширину и высоту формы и позицию ее левого верхнего угла.

Класс Rectangle пространства имен System.Drawing содержит немало полезных мето-дов; подробное описание этой вспомогательной структуры данных приведено в документации. Мы часто используем метод Inflate, предназначенный для увеличения прямоугольников с заданным приращением.

Многие свойства, методы и события форм отличаются от своих прототипов из VB6. Важнейшие изменения перечислены в табл. 8.1.

Таблица 8.1. Изменения в свойствах, методах и событиях форм

Старый элемент формы

Новый элемент формы

Activate/Deactivate (события)

Переименованы в Activated/Deactivated

Container (свойство)

Переименовано в Parent

DblClick (событие)

Переименовано в DoubleClick

hWnd (свойство)

Переименовано в Handle

MouseCursor (свойство)

Переименовано в Cursor и возвращает экземпляр класса Cursor

Parent (свойство)

Заменено методом FindForm

Picture (свойство)

Заменено свойством Backgroundlmage

SetFocus (метод)

Переименован в Focus

Startup (свойство)

Заменено свойством StartPosition

ToolTip (свойство)

Заменено элементом ToolTip, который связывается с элементами через свойство ToolTip элемента

Unload (команда) Unload (событие)

Заменена методом Close

Заменено событием hosing (также существует новое событие Closed, инициируемое после закрытия формы)

ZOrder (метод)

Заменен методами BriflgToFront и SendToBack

 

Элементы меню и новый редактор меню 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

Чтобы быстро изменить свойство Name команд меню, щелкните на команде правой кнопкой мыши и выберите в контекстном меню команду Edit Names (завершив редактирование, выйдите из этого режима при помощи контекстного меню).

Экземпляр главного меню создается командой, которую 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 команд контекстного меню, перейдите в режим редактирования имен щелчком правой кнопкой мыши.

Поскольку обычные и контекстные меню управляются на программном уровне, ваше приложение может построить меню с нуля на стадии выполнения программы!

Формы MDI

В прежних версиях 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.

ColorDialog

При использовании диалогового окна выбора цвета (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

Диалоговое окно выбора шрифта (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

Абстрактный класс 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), которое отдельные подразделения будут дополнять своими данными. Базовая форма создается следующим образом:

  1. Выполните команду File > New > Project.
  2. Выберите тип приложения Windows Application, введите в поле Name строку SplashScreeriBase и нажмите кнопку ОК.

Предположим, вы хотите преобразовать стандартное приложение Windows в библиотеку классов, чтобы откомпилировать его в DLL вместо ЕХЕ-файла. Проще всего это делается так:

  1. Щелкните правой кнопкой мыши в строке SplashScreenBase окна решения и выберите в контекстном меню команду Properties.
  2. Выберите в раскрывающемся списке Output Type строку Class Library (вместо Windows Application). Нажмите кнопку ОК.
  3. Сконструируйте форму, разместите на ней нужные элементы, реализуйте свойства, методы и события.
  4. Откомпилируйте программу.

После построения библиотеки DLL остается лишь включить ссылку на нее в решение, после чего классы DLL используются в программе наравне с остальными классами. При выполнении команды Project > Add Inherited Form можно поручить IDE включить в проект весь необходимый код, для чего достаточно ответить на несколько вопросов в диалоговых окнах. С другой стороны, возня с диалоговыми окнами выглядит немного глупо, поскольку после включения ссылки на DLL в проект остается лишь привести первую строку приложения к следующему виду:

Public Class Form1

Inherits SplashScreenBase. Form1

Все остальное за вас сделает механизм наследования! Этот способ особенно хорош тем, что работа дизайнера автоматически синхронизируется с унаследованной формой.

Построение нестандартных элементов на базе наследования

Наследование применяется при построении не только новых форм, но и новых элементов. Допустим, вы хотите создать текстовое поле, предназначенное для ввода только целых чисел. Как обычно, все начинается с объявления нового класса:

Public Class PositivelntegerTextBox

Inherits System . Windows . Forms . TextBox

Остается лишь запрограммировать поле на нужное поведение. В оставшейся части этого раздела будет показано, как класс Positi velntegerTexBox наполняется специализированными свойствами, событиями и методами.

Чтобы сократить объем этого примера, мы ограничимся упрощенной версией элемента. В частности, проблемы лицензирования и безопасности вообще не рассматриваются — за дополнительной информацией обращайтесь к более серьезным книгам, посвященным программированию элементов в .NET.

Итак, создайте новую библиотеку классов и включите в решение ссылку на сборку Windows.Forms.dll.


Мы начинаем работу с библиотеки классов, а не с проекта типа User Control, потому что он лучше подходит для элементов, написанных «на пустом месте». Если вы захотите построить элемент, содержащий несколько других элементов, выберите в диалоговом окне New Project тип Windows Controls Library — в вашем распоряжении окажется контейнер, предназначенный для построения сложного элемента посредством включения.

Переопределение события

Начнем с переопределения событий базового класса. Например, событие 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 и поместите элемент на панель элементов:

  1. Выполните команду Tools > Customize Toolbox (или нажмите Ctrl+T).
  2. Перейдите на вкладку .NET Framework Components.
  3. Нажмите кнопку Browse и выберите DLL нужнопа^лемента (библиотека находится в подкаталоге \bin основного каталога решения).

Элемент помещается на вкладку .NET Framework Components (рис. 8.16).

Рис. 8.16. Нестандартный элемент на вкладке .NET Framework Components

Рис. 8.17. Нестандартный элемент на панели элементов

Нестандартный элемент размещается в нижней части панели (рис. 8.17). Дважды щелкните в строке Poslti velntegerTextBox, и элемент появится на форме. Обратите внимание: наш простейший элемент обладает всеми свойствами обычных текстовых полей и особенностями поведения, присущими текстовым полям в режиме конструирования. Все это было автоматически унаследовано от класса текстовых полей Windows. Forms. TextBox без малейших усилий с вашей стороны.

Элемент, помещенный на панель элементов, остается на ней и может использоваться в будущих проектах. Чтобы удалить элемент с панели, щелкните на нем правой кнопкой мыши и выберите команду Delete.

Добавление новых событий

Добавить поддержку нового события в элемент ничуть не сложнее, чем включить обработчик события в класс (эта тема рассматривалась в главе 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.

Данная глава дает лишь начальное представление о Windows.Forms. Чтобы овладеть возможностями этого пространства имен на профессиональном уровне, необходимо тщательно изучить электронную документацию и, возможно, купить более специализированную книгу.

Класс Component является базовым классом для всех компонентов пространства имен System. Wi ndows. Forms. В нем собраны члены, необходимые для реализации включения (в частности, размещения элементов на форме или дочерних окон MDI в родительском окне MDI). В основном они реализованы в виде методов интерфейса IComponent. Поскольку класс Component также занимается освобождением ресурсов, расходуемых при визуальной разработке, он реализует IDisposable.

Поскольку класс Control является базовым для элементов и форм, он поддерживает многие операции пользовательского интерфейса, в том числе обработку событий клавиатуры и мыши. Кроме того, он определяет цвет, позицию и размер формы или элемента.

Использование средств базового класса Control (и в частности, обработка ключевых событий) рассматривается в следующем разделе. Тем не менее сначала мы продолжим путь к формам в иерархии классов System. Windows. Forms. Поскольку среди предков класса Form числится ScrollableControl, формы VB обладают полезной особенностью, которую оценит каждый, кто пытался реализовать форму с прокруткой в VB6:

При выходе за грашцы текущего содержимого форма автоматически прокручивается. Взгляните на рис. 8.21 — на нем изображена форма, позволяющая прокручивать очень большую фрактальную картинку (более 5 Мбайт в запакованном виде!) с базовым множеством Мандельброта. За информацией о множестве Мандельброта обращайтесь по адресу www.olympus.net/personal/dewey/mandelbrot.html.

Прокрутку форм можно протестировать на любом изображении достаточно больших размеров:

  1. Разместите на форме графическое поле и задайте его свойству SizeMode значение AutoSize.
  2. Найдите большой графический файл.

Рис. 8.21. Прокрутка изображения базового множество Мандельброта

  1. Приведите процедуру Form_Load к следующему виду:

1 Private Sub Forml_Load(ByVal sender As System.Object,_

ByVal e As System.EventArgs)Handles MyBase.Load

2 Me.AutoScroll = True

3 Me.VScroll = True

4 Me.HScroll = True

5 Dim aBigBox As Rectangle = Rectangle.Inflate _

6 (Me.ClientRectangle. 6. 6)

7 PictureBoxl.Bounds = aBigBox

8 ' Предполагается, что графический файл находится в подкаталоге \bin

9 PictureBoxl.Image = Image.FromFile("Mandelbrot Set.bmp")

10 End Sub

Главное место в этом листинге занимает строка 2, в которой свойству AutoScrol 1, определяемому в классе Scrollabl e Control, задается значение True. Без этого прокрутка работать не будет. Строки 3 и 4 указывают на то, что в окне должны отображаться обе полосы прокрутки (вертикальная и горизонтальная). В строке 5 общий метод класса System.Drawing.Rectangle создает прямоугольный объект, в 6 раз больший клиентской области формы. Строка 7 приводит графическое поле к заданному размеру, присваивая прямоугольник свойству Bounds, присутствующему во всех элементах. Как указано в комментарии, строка 9 предполагает, что файл с растровым изображением находится в подкаталоге \bin каталога решения — в реальной программе для определения местонахождения файла следовало бы вызвать диалоговое окно Open File.

Средства базового класса Control

Класс Control содержит более 300 членов, и описать их все (или хотя бы большую часть) в одной главе просто невозможно. Следовательно, вам все равно придется обратиться к электронной документации [Кстати говоря, пакет форм Windows автоматически опознает операции с колесом мыши и обеспечивает прокрутку формы/элемента там, где это имеет смысл. Таким образом, в большинстве случаев вам не придется использовать новое событие Control.MouseWheel.]. Однако события клавиатуры и события проверки, занимающие важное место в работе элементов, несколько отличаются от своих прототипов из VB6, и при работе с ними следует помнить о некоторых нюансах.

События клавиатуры

Три события клавиатуры инициируются в тех же ситуациях, что и их аналоги из прежних версий VB.

По аналогии с VB6 событие KeyDown инициируется раньше события KeyPress, которое, в свою очередь, предшествует KeyUp. Событие KeyPress использует объект KeyPressEventArgs, в свойстве KeyChar которого передается информация о нажатой клавише. Например, следующий фрагмент выводит окно сообщения в том случае, если введенный символ не является цифрой:

Private Sub TextBoxl_KeyPress(ByVal sender As Object._

ByVal e As System.Windows.Forms.KeyPressEventArgs)

Handles TextBoxl.KeyPress

If e.KeyChar < "0" Or e.KeyChar > "9" Then

MsgBox("only digits allowed")

End If

End Sub

События KeyDown и KeyUp, как и в VB6, могут использоваться для проверки клавиш-модификаторов (таких, как Ctrl и Alt). Класс KeyEventArgs, передаваемый этим событиям, обладает несколько большими возможностями, чем класс KeyPressEventArgs события KeyPress. Класс KeyEventArgs содержит свойство KeyData, в котором при помощи перечисляемого типа Key передается полная информация о комбинациях клавиш и о состоянии клавиш-модификаторов в момент нажатия. Свойства Modi f i ers и Shi ft позволяют узнать об одновременном нажатии трех клавиш (Alt+ +Shift+другая клавиша). Например, следующая команда проверяет, была ли нажата клавиша-модификатор Alt: If e.Modifiers =Keys.Alt Then

Если вас не интересуют такие мелочи, как различия между левой и правой клавишей Shift, удобнее воспользоваться свойствами Control Shift и Alt класса KeyEventArgs.

К сожалению, значения свойств KeyChar и KeyData нельзя сбросить, поскольку они доступны только для чтения [Возможно, этот недочет будет исправлен в окончательной версии.]. Впрочем, введенный символ можно «поглотить» и тем самым предотвратить его появление в элементе; для этого свойству Handled объекта события задается значение True. Пример:

If e.KeyChar < "0" Or e.KeyChar >"9" Then

e.Handled = True

End If

В результате неверный символ не появится в текстовой поле.

События проверки

В .NET вместо одного события проверки существуют целых два: Validating и Validated. Событие Validating инициируется перед утратой фокуса элементом. Например, следующий фрагмент гарантирует, что пользователь не оставит текстовое поле пустым:

Public Sub TextBoxl_Validating(ByVa1 sender As Object.

ByVal e As System.ComponentModel.CancelEventArgs)

Handles TextBoxl.Validating

' Если текстовое поле не содержит символов, отменить передачу фокуса

If TextBoxl.Text.Trim = String.Empty Then e.Cancel = True

End Sub

Команда e.Cancel = True отменяет передачу фокуса от текстового поля другому элементу, если в поле нет ни одного символа.

С другой стороны, событие Val idated инициируется после утраты фокуса элементом, но до передачи его другому элементу. Таким образом, в обработчике события Validated можно обновить состояние других элементов формы.

Если свойство CausesValidation элемента равно False, события Validating и Validated не инициируются.

Графика: GDI+

Графическое программирование в .NET Framework полностью отличается от всего, что было реализовано в прежних версиях VB. Знакомые графические команды (частично позаимствованные еще из QuickBasic) исчезли. Из числа принципиальных изменений также следует обратить внимание на отсутствие свойства AutoRedraw или его аналогов. В прежних версиях VB свойство AutoRedraw, равное True, избавляло программиста от необходимости программировать процедуру события Pal nt для того, чтобы обеспечить восстановление графического изображения в элементе.

Программирование графики в VB .NET основано на концепции графического контекста — отдаленного родственника контекстов устройств Windows GDI. Любопытная подробность: новая система называется GDI+, хотя с GDI она имеет очень мало общего.

У программистов с опытом работы в GDI переход на GDI+ часто вызывает шок, поскольку графический вывод в .NET происходит без сохранения состояния. Иначе говоря, каждая графическая команда должна содержать полную информацию о выполняемой операции. Скажем, если вы использовали черную кисть в первой строке программы и хотите снова воспользоваться ею во второй строке, необходимо указать графической системе, что операция должна выполняться черной кистью. GDI+ «не помнит» об операциях, выполнявшихся ранее.

Классы GDI+ находятся в пространствах имен System.Drawing, System.Drawing. Drawing2D, System. Drawing. Imagi ng и System. Drawing. Text [Каждое из этих пространств имен заслуживает отдельной книги, и здесь мы ограничимся лишь кратким упоминанием.]. Эти пространства имен входят в сборку System.Drawing, ссылка на которую создается автоматически при выборе типа приложения Windows Application в диалоговом окне New Project.

Большая часть графического вывода в GDI+ осуществляется переопределением процедуры [Это не событие, хотя в конечном счете перерисовка и приводит к |ызову события OnPaint базового класса Form.]OnPaint формы или элемента. Процедура OnPaint играет столь же важную роль, как и в прежних версиях VB: она обеспечивает восстановление изображения при временном скрытии или свертывании формы. Сигнатура этой важной процедуры выглядит следующим образом: Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)

Вывод осуществляется на графической поверхности GDI+, представленной экземпляром класса Graphics. Процедура OnPaint класса Form инкапсулирует такую поверхность в виде значения свойства e.Graphics.

Хотя любая форма или элемент (в том числе и PictureBox) с поддержкой вывода позволяет получить доступ к своему графическому содержимому при помощи вызова ControlName.CreateGraphics, будьте очень внимательны, если это происходит за пределами процедуры Paint. Между выводом в графическом контексте, полученным вызовом e.Graphics в процедуре OnPaint и написанием кода, использующего CreateGraphics, существуют тонкие различия. Мы столкнулись с этой проблемой при создании программы вывода всех шрифтов (см. ниже).

Простейший вывод

Рассмотрим очень простой пример графического вывода. Следующая программа выводит растровый файл sample.bmp (находящийся в каталоге \bin решения) в левом верхнем углу формы:

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)

MyBase.OnPaint(e)

Dim g As Graphics

g = e.Graphics()

g.Draw!mage(New Bitmap("sample.bmp"). 0. 0)

g.Dispose()

End Sub

Обратите внимание на вызов Dispose в строке, выделенной жирным шрифтом. Поскольку сборщик мусора не освобождает графические контексты, привыкайте делать это самостоятельно в конце процедуры Paint.

Напоминаем: если объект поддерживает метод Dispose, этот метод следует вызвать по завершении работы с объектом.

Следующий этап — рисование линий, прямоугольников и других фигур. Перед операциями такого рода следует получить объект пера, который является экземпляром класса System.Drawing.Pen. Самый распространенный конструктор класса Реп имеет следующий синтаксис:

Public Sub New(Color.Single)

Первый параметр определяет цвет пера (и входит в перечисляемый тип System. DrawingColor), а второй определяет толщину пера (другие конструкторы также позволяют задать кисть для заполнения внутренней части объекта). Например, чтобы нарисовать прямоугольник, вы определяете его размеры и вызываете g. DrawRectangle. Результат выполнения следующей программы показан на рис. 8.22:

Protected Overrides Sub OnPaint(ByVa1 e As PaintEventArgs)

MyBase.OnPaint(e)

Dim g As Graphics

g = e.Graphics()

Dim myPen As New PerKColor,Purple. 6)

Dim aRectangle As New Rectangle(Me.ClientRectangle.Width \4,_

Me.ClientRectangle.Height \ 4. . Me.ClientRectangle.Height \2,_

Me.ClientRectangle.Width \ 2))

g.DrawRectangle(myPen,aRectangle)

g.Dispose()

End Sub

Рис. 8.22. Результат вызова DrawRectangle: прямоугольник в рамке толщиной 6 пикселов

Вывод текста

Метод DrawString объекта Graphics предназначен для вывода текста. При вызове этого метода задается объект шрифта, цвет, кисть и начальная точка вывода. Например, следующий фрагмент выводит текст «Hello World» — в современных книгах по программированию это превратилось в традицию. При выводе используется текущий шрифт формы, текст выводится фиолетовой кистью на белом фоне:

Protected Overrides Sub OnPaint(ByVal e As _

System.Wi ndows.Forms.PaintEventArgs)

MyBase.OnPaint(e)

Dim g As Graphics = e.Graphics

Dim theColor As Color = Color.Purple

Dim theFont As New Font("Arial", 22._

FontStyle.Bold Or FontStyle.Italic)

Me.BackColor = Col or.White

g.DrawString("Hello World!". theFont.New SolidBrush(theColor). 0, 0)

g.Dispose()

End Sub

Рис. 8.23. Вывод текста «Hello World!» средствами GDI+

В GDI+ полностью поддерживается кодировка Unicode, что позволяет выводить текст на любом языке.

Пример: вывод всех шрифтов в системе

Для демонстрации вывода текста была написана программа, которая воспроизводит в графическом поле все установленные шрифты с указанием имен (попутно мы столкнулись с проблемой, описанной в конце раздела). Программа состоит из нестандартного элемента и формы с прокруткой (рис. 8.24).

Рис. 8.24. Вывод списка установленных шрифтов в форме с прокруткой

Список системных шрифтов возвращается методом InstalledFontCollection пространства имен System.Drawing.Text. Этот метод возвращает объект класса Sys-tem.Drawing.Text.FontCollection. Объекты, входящие в коллекцию FontCollection, представляют не шрифты, а семейства шрифтов (такие, как Arial или Courier). Вы должны выбрать из семейства нужный шрифт. Некоторые шрифты не имеют обычного начертания, поскольку их семейство состоит только из курсивных, узких, тонких и других модифицированных начертаний. Следовательно, в программе должен присутствовать фрагмент вида

For Each aFontFamily In TheFonts.Families

If aFontFamily.IsStyleAvailable(FontStyle.Regular) Then

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

Private Sub Form1_Load(ByVal sender As System.Object._

ByVal e As System. EventArgs)Hand.les MyBase.Load

Me.VScroll = True

Me.HScroll = True

Me.AutoScroll = True

FontPictureBoxl.Left = 0

FontPictureBoxl.Top = 0

End Sub

Ниже приведен полный код специализированного графического поля:

1 Public Class FontPictureBox

2 Inherits System.Windows.Forms.PictureBox

3 Protected Overrides Sub OnPaint(ByVal pe As _

System.Windows.Forms.PaintEventArgs)

4 ' Всегда вызывайте Mybase.OnPaint!

5 MyBase.OnPaint(pe)

6 DisplayFonts(pe.Graphics)

7 End Sub

8 Private Sub OisplayFonts(ByVal g As Graphics)

9 ' HE РАБОТАЕТ: Dim g As Graphics = Me.CreateGraphics()

10 Dim aFontFamily As FontFamily

11 Dim curx.curY As Single

12 Dim TheFonts As System.Drawing.Text.FontCollection

13 Dim tempFont As Font

14 Dim spacing As Integer = 2 ' Вывод с интервалом 2 пиксела

15 TheFonts =New System.Drawing.Text.InstalledFontCollection()

16 For Each aFontFamily In TheFonts.Families

17 Me.Height += 2

18 If aFontFamily.IsStyleAvailable(FontStyle.Regular) Then

19 tempFont =New Font(aFontFamily. 14. FontStyle.Regular)

20 Elself aFontFamily.IsStyleAvailable(FontStyle.Bold) Then

21 tempFont =New Font(aFontFamily, 14. FontStyle.Bold)

22 Elself aFontFamily.IsStyleAvailable(FontStyle.Italic) Then

23 tempFont =New Font(aFontFamily, 14. FontStyle.Italic)

24 End If

25 g.DrawString("Thls is displayed in " & aFontFamily.Name._

26 tempFont. Brushes.Black, curx, curY)

27 DimtheSizeAs SizeF = g.MeasureString("This text is displayed in "_

28 & aFontFamily.Name. tempFont)

29 curY = curY + theSize.Height + spacing

30 Me.Height = Me.Height + CIntCtheSize.Height) + spacing

31 Me.Width = Math.Max(CInt(theSize.Width). Me.Width)

32 Next

33 End Sub

34 End Class

Обратите внимание: в строке 6 функции Displayfonts, определяемой в строках 8-33, передается текущий графический контекст — вместо того, чтобы создавать новый графический контекст вызовом Me.CreateGraphics(). В исходной версии эта вспомогательная процедура получала собственный контекст вместо использования контекста ре.Graphics, переданного в объекте PaintEventArgs. По каким-то загадочным причинам такое решение не работало. Закомментированный вызов Me. CreateGraphics0 остался в строке 9; при желании снимите комментарий и посмотрите, к чему это приведет.

При выводе текста необходимо знать высоту каждой строки. Высота вычисляется
в строках 27 и 28 очень полезной функцией MeasureString:

Public Function MeasureString(String.Font) As SizeF


Функция возвращает объект класса Si zeF — разновидность структуры Si ze, в которой вместо типа Integer используется тип Single. Поскольку класс SizeF содержит вещественные числа, в строках 30 и 31 преобразование осуществляется функцией CInt. В строке 30 происходит наращивание высоты графического поля, а строка 31 гарантирует, что ширина поля позволяет вместить самую длинную из выводимых строк. Проверка осуществляется методом Мах класса Math.

Печать

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

Чтобы обойти некоторые ограничения, действующие в GDI+, мы будем предполагать, что изображение задается свойством Image, а не прямым копированием в графическое поле.
Печать в конечном счете сводится к выводу информации в графическом контексте, но вместо экранного контекста используется контекст, ассоциированный с принтером или окном предварительного просмотра печати.

Как при выводе на принтер, так и при использовании поддержки предварительного просмотра (Print Preview) в .NET работа всегда начинается с создания объекта класса System. Drawl ng. Pri nti ng. Pri ntDocument. Для получения этого объекта можно применить один из следующих способов:

Объявление объекта PrintDocument с ключевым словом WithEvents играет важней^ шую роль для понимания автоматически сгенерированного кода печати. Дело в том, что при вызове метода Print для экземпляра класса Pri ntDocument .NET инициирует по крайней мере три события:

Минимальная поддержка печати в программе требует программирования как минимум события PrintPage.

При необходимости можно запрограммировать обработчики и для двух других событий, но фактическая печать выполняется именно в обработчике PrintPage. Два других события обычно используются для оповещения о начале и завершении печати.

Во втором параметре события Pri ntPage передается объект PagePri ntEventArgs. В этом объекте хранится много полезных данных, в том числе:

В следующем простом примере при нажатии кнопки вызывается метод Print класса PrintDocument:

Private Sub Buttonl_Click(ByVal sender As System.Object,_

ByVal e As System.EventArgs)

Handles Buttonl.Click

PrintDocumentl.Print()

End Sub

Метод Print вызывает событие PrintPage, поэтому на следующем этапе следует запрограммировать обработчик события PrintDocumentl_PrintPage, в котором и происходит непосредственная печать. Если обработчик был сгенерирован с помощью дизайнера, в заголовок автоматически включается соответствующая секция Handles:

1 Private Sub Pri ntDocumentl_PrintPage(

ByVal sender As System.Object. ByVal e As

System.Drawing.Printing.PrintPageEventArgs)

Handles PrintDocument1.PrintPage

2 Dim g As Graphics

3 g = e.Graphics

4 g.DrawImageCPictureBoxl.Image. 0. 0)

5 g.Dispose()

6 e.HasMorePages = False

7 End Sub

При выполнении этого фрагмента изображение печатается на принтере по умолчанию (о том, как сменить принтер, будет рассказано в следующем разделе). Хотя наш пример относительно прост, каждая строка в нем играет важную роль и заслуживает объяснения. В строке 1 событие связывается с процедурой, имеющей правильную сигнатуру, не требуя от вас прямого использования делегата (см. главу 6).

В строке 3 мы получаем объект Graphics, представляющий поверхность вывода текущего принтера. Строка 4 выводит изображение начиная с левого верхнего угла. Вывод происходит на принтере, с которым связан графический контекст. Присутствие вызова D1 spose в строке 5 связано с тем, что графические контексты (как было сказано выше) не освобождаются сборщиком мусора. Строка 6 сообщает об отсутствии дальнейших страниц для печати.

Многостраничный вывод

Процесс многостраничной печати основан на небольшой хитрости: если процедура обработки события Pri ntPage задает свойству HasMorePages объекта Pri ntPageEventArgs значение True, то объект PrintDocument узнает о наличии дополнительных страниц для печати и автоматически инициирует заново событие PagePri n't.

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

  1. Прочитать строку из поля или из файла.
  2. Не превышает ли длина строки предельное допустимое значение? Если превышает, разбить на несколько фрагментов.
  3. Помещаются ли эти фрагменты на текущей странице?
  4. Если помещаются, напечатать их функцией DrawString начиная с текущей позиции. Если не помещаются, напечатать сколько удастся, начать новую страницу и вывести на ней остальные фрагменты.
  5. Повторить процесс до тех пор, пока не будут обработаны все строки текстового поля или файла.

Однако проверка возможности размещения новой строки на странице не имеет ничего общего с печатью; в ней используются различные метрики, вычисляемые на основании ширины и высоты текстовой строки. В свою очередь, эти параметры зависят от семейства и размера используемого шрифта. К счастью, вы можете положиться на такие методы, как MeasureStri ng; эти методы используют метрики, связанные с текущим контекстом устройства.

О классе PrintPageEventArgs

Объект PrintPageEventArgs содержит два ReadOnly-свойства, при помощи которых можно получить информацию о размерах страницы. Значения обоих свойств задаются в сотых долях дюйма.

В свойстве PageSettings объекта PrintPageEventArgs хранится дополнительная информация. В табл. 8.2 перечислены важнейшие свойства класса PageSetti ngs (большинство принтеров позволяет читать эти свойства, но не все принтеры поддерживают запись).

Таблица 8.2. Свойства класса PageSettings

Свойство

Описание

Bounds Возвращает размеры страницы с учетом возможной альбомной ориентации печати. Свойство доступно только для чтения
Color Признак печати страницы в цвете. Логическое свойство, доступное для чтения и записи
Landscape Ориентация страницы. Логическое свойство, доступное для чтения и записи
Margins Размеры полей (по умолчанию равны 1 дюйму). Свойство доступно для чтения и записи
PaperSize Размеры бумаги. Свойство доступно для чтения и записи
PaperSource Источник бумаги. Свойство доступно для чтения и записи
PrinterResolution Разрешение принтера. Некоторые принтеры поддерживают нестандартные разрешения, другие позволяют выбирать только между черновой и качественной печатью. Свойство доступно для чтения и записи
PrinterSettings Настройка принтера для данной страницы. Свойство доступно для чтения и записи

Например, многие принтеры не позволяют печатать ближе, чем в 0,5 дюйма от края бумаги. В этом случае минимальные поля задаются следующей командой:

е.PageSettings.Margins = New System.Drawing.Printing.Margins(50.50.50.50)

Следующий фрагмент начинает печать с левого верхнего края области, ограниченной полями:

Dim g As Graphics

g = e.Graphics

g.DrawImage(PictureBoxl.Image, e.MarginBounds.Left, e.MarginBounds.Top)

Элемент PrintDialog и конфигурация печати

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

Private Sub Buttonl_Click(ByVal sender As System.Object,_

ByVal e As System.EventArgs)Handles Buttonl.Click

Dim PhntDialogl As New PrintDialog()

' Следующая строка необходима, потому что информация

' объекта PrinterSettings нужна объекту PrintDialog перед выводом

PrintDialogl.Document = PrintDocumentl

If PrintDialogl.ShowDialog() = DialogResult.OK Then

PrintDocumentl. PrintO

End If

End Sub

Выделенная строка сообщает экземпляру PrintOialog, что связанный с ним документ должен быть экземпляром PrintDocumentl (предполагается, что этот объект был создан ранее). Эта строка необходима, поскольку элемент PrintDialog должен получить некоторые параметры печати (в виде объекта Pri ntSetti ngs) перед выводом окна. Чтобы передать ему эту информацию, проще всего назначить объект PrintDocument свойству Document.

Самостоятельное программирование печати

Во многих случаях вполне достаточно элемента Pri ntDialog и автоматически сгенерированного кода, но время от времени требуется реализовать нестандартные возможности печати. Для этого необходимо написать процедуру со следующей сигнатурой:

Private Sub ProcedureToDoThePrinting(ByVal Sender As Object,_

ByVal e As System.Drawing.Printing.PrintPageEventArgs)

Затем процедура при помощи делегата подключается к событию PrintPage класса PrintDocument. Например, для вызова объекта aPrintDocument класса PrintDocument с приведенной выше процедурой aPri ntDocument_PrintPage используется команда следующего вида:

AddHandler aPrintDocument.PrintPage, AddressOf Me.aPrintDocument_PrintPage

В следующем фрагменте показано, как выглядит возможная реализация печати в обработчике команды меню Print:

Private Sub mnuPrint_Click(ByVal sender As System.Object,_

ByVal e As System.EventArgs)Handles mnuPrint.Click

Dim aPrintDocument As New PrintDocument()

AddHandler aPrintDocument.PrintPage.

AddressOf Me.aPrintDocument_PrintPage

aPrintDocument.Print()

End Sub

Рис. 8.25. Режим предварительного просмотра

Предварительный просмотр

Печать простейших документов в VB .NET требует несколько больших усилий, чем в VB6, зато режим предварительного просмотра реализуется гораздо проще. Для этого от вас потребуется лишь назначить объект PrintDocument свойству Document экземпляра PrintPrevl ewDialog. Так, следующий фрагмент почти полностью совпадает с кодом, приведенным выше, однако он реализует предварительный просмотр вместо вывода на принтер. Результаты его выполнения показаны на рис. 8.25:

Private Sub btnPreview_Click(ByVal sender As System.Object.

ByVal e As System.EventArgs) Handles btnPreview.Click

Dim PrintPreviewDialogl As New PrlntPreviewDialog()

PrintPreviewDlalogl.Document = PrintDocumentl

If PrintPreviewDialogl.ShowDialog() = DialogResult.OK Then

PrintDocumentl.Print()

End If

End Sub

к оглавлению   к 4GL - визуальному программированию

Знаете ли Вы, что, когда некоторые исследователи, пытающиеся примирить релятивизм и эфирную физику, говорят, например, о том, что космос состоит на 70% из "физического вакуума", а на 30% - из вещества и поля, то они впадают в фундаментальное логическое противоречие. Это противоречие заключается в следующем.

Вещество и поле не есть что-то отдельное от эфира, также как и человеческое тело не есть что-то отдельное от атомов и молекул его составляющих. Оно и есть эти атомы и молекулы, собранные в определенном порядке. Также и вещество не есть что-то отдельное от элементарных частиц, а оно состоит из них как базовой материи. Также и элементарные частицы состоят из частиц эфира как базовой материи нижнего уровня. Таким образом, всё, что есть во вселенной - это есть эфир. Эфира 100%. Из него состоят элементарные частицы, а из них всё остальное. Подробнее читайте в FAQ по эфирной физике.

НОВОСТИ ФОРУМА

Форум Рыцари теории эфира


Рыцари теории эфира
 10.11.2021 - 12:37: ПЕРСОНАЛИИ - Personalias -> WHO IS WHO - КТО ЕСТЬ КТО - Карим_Хайдаров.
10.11.2021 - 12:36: СОВЕСТЬ - Conscience -> РАСЧЕЛОВЕЧИВАНИЕ ЧЕЛОВЕКА. КОМУ ЭТО НАДО? - Карим_Хайдаров.
10.11.2021 - 12:36: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от д.м.н. Александра Алексеевича Редько - Карим_Хайдаров.
10.11.2021 - 12:35: ЭКОЛОГИЯ - Ecology -> Биологическая безопасность населения - Карим_Хайдаров.
10.11.2021 - 12:34: ВОЙНА, ПОЛИТИКА И НАУКА - War, Politics and Science -> Проблема государственного терроризма - Карим_Хайдаров.
10.11.2021 - 12:34: ВОЙНА, ПОЛИТИКА И НАУКА - War, Politics and Science -> ПРАВОСУДИЯ.НЕТ - Карим_Хайдаров.
10.11.2021 - 12:34: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Вадима Глогера, США - Карим_Хайдаров.
10.11.2021 - 09:18: НОВЫЕ ТЕХНОЛОГИИ - New Technologies -> Волновая генетика Петра Гаряева, 5G-контроль и управление - Карим_Хайдаров.
10.11.2021 - 09:18: ЭКОЛОГИЯ - Ecology -> ЭКОЛОГИЯ ДЛЯ ВСЕХ - Карим_Хайдаров.
10.11.2021 - 09:16: ЭКОЛОГИЯ - Ecology -> ПРОБЛЕМЫ МЕДИЦИНЫ - Карим_Хайдаров.
10.11.2021 - 09:15: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Екатерины Коваленко - Карим_Хайдаров.
10.11.2021 - 09:13: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Вильгельма Варкентина - Карим_Хайдаров.
Bourabai Research - Технологии XXI века Bourabai Research Institution