Визуальное проектирование приложений C#
Глава 10. Графический интерфейс GDI+. Отслеживание состояния клавиш мыши Отслеживание перемещения курсора мыши Идентификатор окна Handle и объект Graphics Рисование в окне элемента управления Рисование в окнах приложений Microsoft Windows Пример обработки события Paint Перерисовка окон элементов управления Методы и свойства класса Graphics Рисование геометрических фигур Растровые и векторные изображения Рисование загруженного изображения Единицы измерения размера шрифта
Глава 10. Графический интерфейс GDI+ Мы изучили многочисленные аспекты визуального создания приложений C# с графическим интерфейсом, однако так и не познакомились непосредственно с реализацией самого графического интерфейса библиотеки классов Microsoft .NET Framework. Действительно, все приложения, рассмотренные ранее в предыдущих главах этой книги, не осуществляли непосредственный вывод графических изображений или текста в окна приложения. Мы абстрагировались от деталей графического интерфейса, возложив все операции по рисованию в окнах на такие элементы управления, как текстовые поля, поля редактирования, списки, деревья и пр. Вместе с тем рано или поздно наступает момент, когда набор стандартных элементов управления перестает удовлетворять разработчика приложений. Иногда возникают задачи, требующие функций и методов, позволяющих программе рисовать в окнах приложения произвольные графические изображения или отображать текст нестандартным способом. Типичный пример — построение всякого рода графиков и диаграмм, а также создание анимационных изображений. Графический интерфейс приложений C#, как и других приложений, предназначенных для работы в рамках Microsoft .NET Framework, состоит из набора классов. Эти классы инкапсулируют поведение объектов и инструментов, предназначенных для рисования. Однако прежде чем приступить к описанию этих классов, нам необходимо познакомиться с основными понятиями интерфейса графических устройств. Заметим, что графический интерфейс GDI+ настолько обширный, что ему можно посвятить отдельную книгу или даже несколько книг. Поэтому в рамках одной главы мы изложим только наиболее существенные аспекты применения GDI+. Тех из Вас, кому будет интересно познакомиться с возможностями GDI+ глубже, мы адресуем к [10]. Если Вы когда-либо создавали программы для операционной системы MS-DOS и ее аналогов, то помните, что такие программы могут работать в текстовом или графическом режиме. Использование текстового режима обычно не вызывает затруднений. Программы выводят текстовые строки на экран консоли, обращаясь к системным функциям ОС или к функциям базовой системы ввода-вывода BIOS. Что же касается графического режима, то его использование приводило к необходимости выполнять в программах прямое обращение к аппаратуре видеоадаптера. С учетом того обстоятельства, что компьютеры могут быть оснащены видеоадаптерами самого разного типа, создание графических программ MS-DOS, способных работать на любых компьютерах, превращалось в непростую задачу. В самом деле, прежде чем выводить что-то на экран монитора в графическом режиме, программа должна была определить тип видеоадаптера и его возможности, переключить видеоадаптер в нужный видеорежим, а затем выполнять вывод изображения с учетом особенностей этого видеорежима. Всех, кого интересуют подробности программирования видеоадаптеров на уровне аппаратуры, мы адресуем к нашей книге [7], посвященной этому вопросу. При создании ОС Microsoft Windows компания Microsoft избавила программистов от необходимости учитывать аппаратные особенности видеоадаптеров, переложив эту задачу на драйверы видеоадаптеров. Эти драйверы создаются разработчиками видеоадаптеров и наилучшим образом реализуют возможности аппаратуры. Что же касается приложений, то для них в составе ОС Microsoft Windows был предусмотрен набор системных функций, реализующих интерфейс графических устройств (Graphics Device Interface, GDI). Применительно к приложениям Microsoft Windows мы рассмотрели этот интерфейс в [8]. Что же касается этой книги, то здесь мы расскажем о работе с усовершенствованным интерфейсом GDI+, доступным приложениям Microsoft .NET Framework. Интерфейс графических устройств GDI, как это можно предположить из названия, предназначен для взаимодействия приложений Microsoft Windows с графическими устройствами, такими как видеоадаптер, принтер или плоттер. Когда приложения обращаются к GDI для выполнения операции вывода графического изображения, они работают не с реальными (физическими) устройствами вывода, а с логическими устройствами. Приложения Microsoft Windows не определяют тип видеоадаптера (EGA, VGA, SVGA и т.п.), а работают с логическим видеоадаптером, имеющим феноменальные характеристики: способность отображать практически любой цвет, имеющим огромное разрешение и т. д. Выполняя запрос приложения, GDI обращается к драйверу соответствующего устройства вывода, работающему, в свою очередь, непосредственно с физическим устройством вывода. В процессе выполнения запроса GDI (или драйвер) учитывает ограниченные возможности видеоадаптера и его аппаратные особенности, делая необходимые приближения. Например, приложение может указать для цвета линии любой из примерно 16 млн. цветов, однако не всякое устройство обладает таким цветовым разрешением (ограничения на количество одновременно отображаемых цветов присутствуют, например, в карманных компьютерах). В зависимости от типа физического устройства, используемого для вывода, GDI может выбрать для отображения цвет, наиболее соответствующий запрошенному цвету, и допустимый для устройства. Например, если устройство вывода монохромное, вместо различных цветов могут использоваться градации серого цвета. Поэтому приложение может запросить для вывода любой цвет, но для рисования будет использован только такой, который может использовать данное физическое устройство. Такая ситуация, когда приложение запрашивает у ОС Microsoft Windows одно, а получает другое, возникает не только при работе с цветом. Приложение может запросить для вывода шрифт, описав его характеристики. Интерфейс GDI подберет для вывода наиболее подходящий (с его точки зрения) шрифт, соответствующий описанию, и предоставит его приложению. На первый взгляд, этот подход обескураживает, особенно тех программистов, кто имеет опыт создания графических программ для MS-DOS и привык получать от системы точно то, что требуется. Однако такой подход удобен для обеспечения аппаратной независимости. Составляя программы для MS-DOS, Вы работали с видеоадаптерами, указывая конкретные цвета и загружая в его память конкретные шрифты из отдельных файлов. Поэтому программы MS-DOS были крепко «привязаны» к аппаратуре. Для использования новых возможностей требовалось вносить изменения в программы. Приложения Microsoft Windows способны работать в неизменном виде на любом оборудовании, лишь бы был соответствующий драйвер. Чем лучше используемая аппаратура, чем большими возможностями она обладает, тем ближе будут параметры полученного шрифта и цвета соответствовать запрошенным. Поэтому даже если сейчас в Вашем распоряжении есть только видеоадаптер с ограниченным количеством отображаемых цветов, при разработке приложений Microsoft Windows Вы можете не ограничивать себя дюжиной цветов. Ваше приложение должно быть сделано так, чтобы оно могло использовать любой цвет. Со временем, когда у Вас появится современный видеоадаптер, окно Вашего приложения засветится всеми цветами радуги, причем для этого не придется вносить никаких изменений в само приложение. С точки зрения приложений, интерфейс GDI состоит из контекста отображения и инструментов, предназначенных для рисования. Контекст отображения можно сравнить с листом бумаги, на котором приложение рисует то или иное графическое изображение, а также пишет текст. Инструменты для рисования — это перья, кисти (а также шрифты и даже целые графические изображения), с помощью которых создается изображение. Кроме контекста отображения и инструментов для рисования, приложениям доступны десятки функций программного интерфейса GDI, предназначенные для работы с контекстом отображения и инструментами. Что же касается приложений Microsoft .NET Framework, то они реализуют возможности интерфейса GDI+ с помощью набора соответствующих классов и интерфейсов. В терминах ОС Microsoft Windows контекст отображения (display context) представляет собой структуру данных, описывающую устройство отображения. В этой структуре хранятся различные характеристики устройства и набор инструментов для рисования, выбранный по умолчанию. Приложение может выбирать в контекст отображения различные инструменты (например, перья различной толщины и цвета, с различными «наконечниками»). Поэтому если Вам надо нарисовать линию красного или зеленого цвета, перед выполнением операции следует выбрать в контекст отображения соответствующее перо. Заметим, что функции рисования GDI, входящие в программный интерфейс Win32 API, не имеют параметров, указывающих цвет или толщину линии. Такие параметры хранятся в контексте отображения. Приложение может создать контекст отображения не только для окна приложения, но и для любого другого графического устройства вывода, например, для принтера. В последнем случае оно может рисовать на принтере различные изображения, используя те же функции, что и для рисования в окне приложения. Можно создать контекст отображения для метафайла. Метафайл — это обычный файл или файл в памяти, в котором хранятся последовательности команд интерфейса GDI. Приложение может выполнять графический вывод в метафайл как в обычное устройство вывода, а затем «проигрывать» метафайл на реальном устройстве вывода. Контекст устройства в терминах ОС Microsoft Windows выступает в роли связующего звена между приложением и драйвером устройства (рис. 10-1) и представляет собой структуру данных размером примерно 800 байт. Эта структура данных содержит информацию о том, как нужно выполнять операции вывода на данном устройстве (цвет и толщину линии, тип системы координат и т. д.). Рис. 10-1. Вывод данных через контекст устройства Если приложение получает или создает контекст для устройства отображения, такой контекст называется контекстом отображения (display context). Поэтому когда, например, приложение получает контекст для отображения в одном из своих окон, такой контекст называется контекстом отображения. Если же ему требуется выполнять операцию вывода для устройства (для принтера или для экрана дисплея), приложение должно получить или создать контекст устройства (device context). Следует понимать, что контексты устройства и отображения содержат описания одних и тех же характеристик и имеют одинаковую структуру. Название контекста определяется только тем, относится ли контекст к окну отображения или устройству вывода. Класс Graphics Концепция графического интерфейса GDI+ несколько отличается от концепции «классического» графического интерфейса GDI, с которым привыкли иметь дело разработчики приложений Microsoft Windows. Те из Вас, кто создавал приложения Java с графическим интерфейсом на базе классов Abstract Window Toolkit (AWT), найдут, что эти классы имеют много общего с классами GDI+. Прежде всего, это касается класса Graphics, реализующего в себе как свойства контекста отображения, так и инструменты, предназначенные для рисования в этом контексте. Для того чтобы приложение могло что-нибудь нарисовать в окне, оно должно, прежде всего, получить или создать для этого окна объект класса Graphics. Далее, пользуясь свойствами и методами этого объекта, приложение может рисовать в окне различные фигуры или текстовые строки. Приложение GraphicsApp Проще всего объяснить назначение и принципы использования класса Graphics на конкретном примере. Давайте создадим приложение GraphicsApp, в окне которого можно рисовать мышью (рис. 10-2). Рис. 10-2. Рисунок в окне приложения GraphicsApp Создайте проект приложения GraphicsApp при помощи мастера проектов, а затем установите белый цвет фона для формы этого приложения Form1. Изменить цвет фона можно, отредактировав свойство BackColor. Отслеживание состояния клавиш мыши Нам нужно сделать так, чтобы пользователь мог рисовать в окне при нажатой левой клавише мыши. Поэтому нам придется отслеживать в нашем приложении события, связанные с нажатием и отжатием этой клавиши. Кроме того, необходимо отслеживать перемещения курсора мыши. Текущее состояние мыши будет храниться в поле doDraw типа bool. Вам нужно добавить это поле в класс Form1, проинициализировав его следующим образом: bool doDraw = false; Создайте два обработчика событий MouseDown и MouseUp для формы Form1: private
void Form1_MouseDown(object sender, Чтобы создать эти обработчики событий, откройте окно визуального проектирования формы, а затем на вкладке событий щелкните дважды события MouseDown и MouseUp. Мы показали это окно на рис. 10-3, добавив обработчик события MouseMove, необходимый для отслеживания перемещений мыши. Рис. 10-3. Добавление обработчиков событий Когда пользователь нажимает левую клавишу мыши, управление передается обработчику событий Form1_MouseDown. Этот обработчик записывает в поле doDraw значение true, отмечая таким способом тот факт, что пользователь приступил к процедуре рисования. Нарисовав линию, пользователь отпускает левую клавишу мыши. При этом управление передается обработчику событий Form1_MouseUp, записывающему в поле doDraw значение false. Это означает завершение процедуры рисования. Отслеживание перемещения курсора мыши Для того чтобы наделить наше приложение возможностью рисования, добавьте обработчик события Form1_MouseMove: private void Form1_MouseMove(object sender, Этот обработчик будет получать управление при всяком перемещении курсора мыши, причем свойства e.X и e.Y будут содержать новые координаты курсора. Мы воспользуемся этим обстоятельством, нарисовав в месте нового расположения курсора квадрат, с шириной стороны в один пиксел. На экране такой квадрат будет выглядеть как точка. Рисование должно выполняться только в том случае, если в поле doDraw хранится значение true. Идентификатор окна Handle и объект Graphics Как мы уже говорили, прежде чем мы сможем что-нибудь нарисовать в окне нашего приложения, мы должны получить для этого окна объект класса Graphics. В случае приложения GraphicsApp нам нужно получить такой объект для окна формы Form1. Как это сделать? Те из Вас, кто создавал приложения для ОС Microsoft Windows, знают, что каждое окно имеет свой идентификатор (handle). Зная идентификатор окна, можно легко получить связанный с этим окном контекст отображения. Приложения Microsoft .NET Framework могут получить идентификатор формы или любого другого элемента управления при помощи свойства Handle. В частности, наше приложение получает идентификатор окна формы Form1 с помощью свойства this.Handle. Зная идентификатор окна, с помощью метода Graphics.FromHwnd нетрудно получить нужный нам объект класса Graphics: Graphics g = Graphics.FromHwnd(this.Handle); Позже Вы узнаете и о других способах получения этого объекта. Для того чтобы рисовать, художнику нужна кисть. Программист, создающий приложение GDI+, тоже нуждается в инструментах для рисования. Мы создадим кисть как объект класса SolidBrush: SolidBrush redBrush = new SolidBrush(Color.Red); С помощью этой кисти можно рисовать замкнутые геометрические фигуры, закрашенные заданным цветом. Через единственный параметр мы передаем конструктору класса SolidBrush цвет кисти Color.Red. Таким образом, мы будем рисовать кистью красного цвета. Позже мы познакомимся с перьями класса Pen, а также с другими кистями. В классе Graphics имеется множество различных методов, предназначенных для рисования самых разных геометрических фигур, таких как линии, прямоугольники, овалы и окружности, многоугольники, кривые Безье и т.д. Но вот чего в этом классе нет, так это метода, с помощью которого можно было бы нарисовать одну единственную точку. Заметим, однако, что вместо точки мы можем нарисовать закрашенный квадрат с шириной стороны, равным 1 пикселу. Эта задача выполняется при помощи метода FillRectangle: g.FillRectangle(redBrush, e.X, e.Y, 1, 1); Обратите внимание на то, что метод FillRectangle вызывается для объекта g класса Graphics, созданного нами для окна формы Form1. Поэтому квадрат будет нарисован в окне этой формы. В качестве первого параметра методу FillRectangle передается кисть redBrush, которую нужно использовать для рисования. Кисть нужна и для других методов класса Graphics, предназначенных для рисования геометрических фигур. Второй и третий параметры метода FillRectangle задают координаты, в которых будет нарисован квадрат. Начало системы координат при этом находится в левом верхнем углу окна, для которого был получен объект Graphics. В нашем случае это левый верхний угол внутренней области окна формы Form1. Ось X в этой системе координат, принятой по умолчанию, направлена слева направо, а ось Y — сверху вниз (рис. 10-4). Рис. 10-4. Система координат по умолчанию И, наконец, последние два параметра метода FillRectangle задают, соответственно, ширину и высоту прямоугольника. В процессе перемещения мыши, при нажатой левой клавише, происходит многократный вызов обработчика событий Form1_MouseMove. Как результат, в окне нашего приложения появляется рисунок, состоящий из отдельных точек (рис. 10-2). Рисование в окне элемента управления В приложении GraphicsApp мы получили контекст отображения (в виде объекта класса Graphics) для всего окна формы Form1. Однако формы реальных приложений создаются с использованием различных элементов управления, о которых мы рассказывали в предыдущих главах нашей книги. Что же касается непосредственного рисования, то оно чаще всего выполняется не во всем окне приложения, а только в его некоторой части. На рис. 10-5 мы показали главное окно приложения GraphicsApp1, внутри которого находится элемент управления класса Panel. Рис. 10-5. Рисование в окне элемента управления Panel С помощью мыши пользователь может рисовать внутри окна панели Panel, однако остальная часть главного окна приложения для рисования недоступна. Создайте проект приложения GraphicsApp1 и перетащите из панели Toolbox системы Microsoft Visual Studio в форму Form1 значок элемента управления Panel. Далее создайте в классе Form1 поле doDraw и обработчики событий, создаваемых мышью. Обработчики должны создаваться для объекта Panel, а не для формы Form1, как это было в предыдущем приложении: bool doDraw = false; Этот код почти аналогичен коду, который мы использовали в предыдущем приложении GraphicsApp, однако в обработчиках событий имеются важные отличия. Прежде всего, мы создали обработчики событий для панели panel1, а не для формы Form1. В результате они будут получать управление только в том случае, когда пользователь щелкает левую кнопку мыши и перемещает ее курсор внутри панели. Второе важное отличие в методе panel1_MouseMove. Этот метод получает контекст отображения не для всего окна формы, а только для окна панели panel1. С этой целью он передает методу Graphics.FromHwnd идентификатор окна панели, извлеченный из свойства panel1.Handle: Graphics g = Graphics.FromHwnd(panel1.Handle); В результате последующая операция рисования будет выполнена в системе координат, связанной с окном панели, а не с окном формы. Третье отличие от предыдущего приложения не соль существенно. Вместо метода FillRectangle для рисования мы применили метод FillEllipse: g.FillEllipse(redBrush,e.X, e.Y, 10, 10); Назначение параметров метода FillEllipse, предназначенного для рисования закрашенных эллипсов, аналогично назначению параметров метода FillRectangle. При этом два последних параметра задают, соответственно, ширину и высоту прямоугольной области, занимаемой эллипсом. Событие Paint Проверяя работу приложения GraphicsApp1, описанного в предыдущем разделе этой главы, Вы наверняка заметите одну неприятную особенность — при изменении размеров окна часть нарисованного изображения или все изображение пропадает. Аналогичная неприятность происходит и в том случае, когда окно приложения GraphicsApp1 перекрывается окном другого приложения. В чем здесь проблема? Она в том, что в приложении GraphicsApp1 не реализована техника рисования в окне, применяемая во всех стандартных приложениях Microsoft Windows. По своей логике способ рисования программ Microsoft Windows в корне отличается от способа, к которому привыкли разработчики, создававшие программы для ОС MS-DOS. Рисование в окнах приложений Microsoft Windows Известно, что приложения Microsoft Windows не могут выводить текст или графику ни с помощью стандартных функций библиотеки компилятора, например таких, как printf, cprintf или putc. Не помогут и прерывания BIOS, так как приложениям Microsoft Windows запрещено их использовать (во всяком случае, для вывода на экран). Все эти функции ориентированы на консольный вывод в одно-единственное окно, предоставленное в полное распоряжение программе MS-DOS. В ОС Microsoft Windows параллельно работающие приложения должны совместно использовать один общий экран монитора. Для этого они создают перекрывающиеся и перемещаемые окна, в которые и выполняют вывод текста или графических изображений. ОС Microsoft Windows берет на себя все проблемы, связанные с возможным перекрытием или перемещением окон, так что правильно спроектированные приложения не должны специально заботиться о восстановлении содержимого окна после его перекрытия другим окном. Способ, которым приложение Microsoft Windows выводит что-либо в свои окна, коренным образом отличается от способа, используемого в программах MS-DOS. Программа MS-DOS формирует изображение на экране «рассредоточенным» образом, то есть в любом месте программы могут вызываться функции, которые выводят что-либо на экран. Например, сразу после запуска программа может нарисовать на экране диалоговую панель, а затем в любой момент времени и, что самое главное, из любого места программы модифицировать ее. Приложения Microsoft Windows также могут выводить в созданные ими окна текст или графические изображения в любой момент времени и из любого места. Именно так поступает наше приложение GraphicsApp1. Однако обычно разработчики поступают по-другому. Сообщение WM_PAINT Прежде чем приступить к описанию способов рисования в окнах, применяемых приложениями .NET Frameworks, расскажем о том, как это делают «классические» приложения Microsoft Windows, составленные на языках программирования C или C++. Это поможет Вам глубже разобраться в сути проблемы. При необходимости Вы найдете более подробную информацию об этом в [4]. ОС Microsoft Windows следит за перемещением и изменением размера окон и при необходимости извещает приложения, о том, что им следует перерисовать содержимое окна. Для извещения в очередь приложения записывается сообщение с идентификатором WM_PAINT. Получив такое сообщение, функция окна должна выполнить перерисовку всего окна или его части, в зависимости от дополнительных данных, полученных вместе с сообщением WM_PAINT. Напомним, что функция окна выполняет обработку всех сообщений, поступающих в окно приложения Microsoft Windows. Для облегчения работы по отображению содержимого окна весь вывод в окно обычно выполняют в одном месте приложения — при обработке сообщения WM_PAINT в функции окна. Приложение должно быть сделано таким образом, чтобы в любой момент времени при поступлении сообщения WM_PAINT функция окна могла перерисовать все окно или любую его часть, заданную своими координатами. Последнее нетрудно сделать, если приложение будет хранить где-нибудь в памяти свое текущее состояние, пользуясь которым функция окна сможет перерисовать окно в любой момент времени. Здесь не имеется в виду, что приложение должно хранить образ окна в виде графического изображения и восстанавливать его при необходимости, хотя это и можно сделать. Приложение должно хранить информацию, на основании которой оно может в любой момент времени перерисовать окно. Например, если приложение выводит на экран дамп оперативной памяти, оно должно хранить информацию о начальном адресе отображаемого участка памяти и размере этого участка. При получении сообщения WM_PAINT приложение должно определить, какой участок окна необходимо перерисовать и какому диапазону адресов дампа памяти этот участок соответствует. Затем приложение должно заново вывести участок дампа памяти в окно, опрашивая соответствующие адреса и выполняя преобразование байт памяти в символьные, шестнадцатеричные или другие используемые для вывода дампа числа. Сообщение WM_PAINT передается функции окна, если стала видна область окна, скрытая раньше другими окнами, если пользователь изменил размер окна или выполнил операцию прокрутки изображения в окне. Приложение может передать функции окна сообщение WM_PAINT явным образом, вызывая функции программного интерфейса Win32 API, такие как UpdateWindow, InvalidateRect или InvalidateRgn. Иногда ОС Microsoft Windows может сама восстановить содержимое окна, не посылая сообщение WM_PAINT. Например, при перемещении курсора мыши или значка свернутого приложения ОС восстанавливает содержимое окна. Если же ОС не может восстановить окно, функция окна получает от ОС сообщение WM_PAINT и перерисовывает окно самостоятельно. Перед тем как записать сообщение WM_PAINT в очередь приложения, ОС посылает функции окна сообщение WM_ERASEBKGND. Если функция окна не обрабатывает сообщение WM_ERASEBKGND, передавая его функции DefWindowProc, последняя в ответ на это сообщение закрашивает внутреннюю область окна с использованием кисти, указанной в классе окна (при регистрации класса окна). Поэтому, если функция окна нарисует что-либо в окне во время обработки других сообщений, отличных от WM_PAINT, после прихода первого же сообщения WM_PAINT нарисованное изображение будет закрашено. Заметим, что изображение, нарисованное в окне приложения GraphicsApp1 пропадает именно по этой причине. Что же делать в том случае, когда по логике работы приложения требуется изменить содержимое окна не во время обработки сообщения WM_PAINT, а в любом другом месте приложения? В этом случае приложение должно сообщить ОС Microsoft Windows, что необходимо перерисовать часть окна или все окно. При этом в очередь приложения будет записано сообщение WM_PAINT, обработка которого приведет к нужному результату. Можно указать ОС, что был изменен прямоугольный участок окна или область произвольной формы, например эллипс или многоугольник. Для этого предназначены функции InvalidateRect и InvalidateRgn программного интерфейса Win32 API. Пример обработки события Paint Для форм класса System.Windows.Forms предусмотрен удобный объектно-ориентированный способ, позволяющий приложению при необходимости перерисовывать окно формы в любой момент времени. Когда вся клиентская область окна формы или часть этой области требует перерисовки, форме передается событие Paint. Все, что требуется от программиста, это создать обработчик данного события, наполнив его необходимой функциональностью. Для наглядной демонстрации методики обработки события Paint мы подготовим простейшее приложение PaintApp, рисующее в своем окне текстовую строку и геометрические фигуры. Прежде всего, создайте проект приложения PaintApp, пользуясь мастером проектов системы разработки Microsoft Visual Studio .NET. Далее выделите в окне дизайнера форму Form1 и откройте вкладку событий для этой формы, показанную на рис. 10-6. Рис. 10-6. Добавление обработчика события Paint Отыщите на вкладке строку события Paint и щелкните ее дважды левой клавишей мыши. В результате будет создан обработчик события Form1_Paint (как это видно на рис. 10‑6). Этот обработчик будет получать управление всякий раз, когда по тем или иным причинам возникнет необходимость в перерисовке содержимого окна нашего приложения. Вот в каком виде будет создан обработчик события Paint: private void Form1_Paint(object
sender, Обработчику Form1_Paint передаются два параметра. Через первый параметр передается ссылка на объект, вызвавший событие. В нашем случае это будет ссылка на форму Form1. Что же касается второго параметра, то через него передается ссылка на объект класса PaintEventArgs. Этот объект имеет два свойства, доступных только для чтения — Graphics и ClipRectangle. Класс Graphics Вам уже знаком — он представляет собой контекст отображения, необходимый для рисования текста и геометрических фигур. Мы работали с этим классом в приложениях GraphicsApp и GraphicsApp1, рассмотренных ранее в этой главе. Обработчик события Paint получает контекст отображения через свои параметры, поэтому программисту не нужно определять его специальным образом. Через свойство ClipRectangle передаются границы области, которую должен перерисовать обработчик события Paint. Эти границы передаются в виде объекта класса Rectangle. Свойства этого класса Left, Right, Width и Height, наряду с другими свойствами, позволяют определить расположение и размеры области. Заметим, что в простейших случаях обработчик события Paint может игнорировать свойство ClipRectangle, перерисовывая содержимое окна полностью. Однако процесс перерисовки содержимого окна можно заметно ускорить, если перерисовывать не все окно, а только область, описанную свойством ClipRectangle. Ускорение будет особенно заметным, если в окне нарисовано много текста и геометрических фигур. Итак, давайте отредактируем исходный текст приложения PaintApp таким образом, чтобы в его окне была нарисована текстовая строка, прямоугольник и эллипс. Прежде всего, создайте в классе Form1 поле text класса string, в котором будет храниться отображаемая текстовая строка: public string text; Добавьте также в конструктор класса Form1 строку инициализации упомянутого поля text: public Form1() И, наконец, измените исходный текст обработчика события Form1_Paint следующим образом: private void Form1_Paint(object
sender, Здесь в теле обработчика Form1_Paint мы определили локальную переменную g класса Graphics, предназначенную для хранения контекста отображения. Эта переменная инициализируется при помощи значения, полученного из свойства Graphics первого параметра обработчика Form1_Paint: Graphics g = e.Graphics; Получив контекст отображения, наш обработчик события Paint может рисовать в соответствующем окне все, что угодно. Вначале мы закрашиваем окно белым цветом, вызывая для этого метод Clear, определенный в классе Graphics: g.Clear(Color.White); Таким способом мы можем закрасить фон, цвет которого задан для формы в свойстве BackColor. Далее мы вызываем методы DrawString, DrawRectangle и DrawEllipse, также определенные в классе Graphics: g.DrawString(text, new
Font("Helvetica", 15), Brushes.Black, 0, 0); Первый из них рисует текстовую строку в верхней части окна, а два других — прямоугольник и эллипс, соответственно (рис. 10-7). Рис. 10-7. Окно приложения PaintApp Запустив приложение PaintApp, Вы сможете убедиться в том, что нарисованное изображение не пропадает при изменении размеров окна. Оно заново перерисовывается и в том случае, если окно приложения PaintApp оказывается временно перекрыто окном другого приложения. Перерисовка окон элементов управления На данном этапе у читателя может возникнуть вопрос — а почему мы заговорили об обработке события Paint только в 10 главе нашей книги? Почему эта проблема не вставала перед нами раньше, при создании приложений с элементами управления, такими как кнопки, текстовые поля, списки и деревья? Дело в том, что до сих пор мы не предпринимали никаких попыток непосредственного рисования в окнах форм или в окнах элементов управления. Все, что мы делали, — это размещение элементов управления в окне формы и создание обработчиков событий, создаваемых этими элементами управления при манипуляциях пользователя с клавиатурой и мышью. Однако сами по себе использованные нами ранее элементы управления выполняют обработку события Paint, перерисовывая при необходимости свои окна. И программисту нет никакой необходимости вмешиваться в этот процесс, если только ему не нужно наделить тот или иной элемент управления нестандартным поведением. Обработка сообщения Paint скрыта внутри классов элементов управления, поэтому Вы можете создавать достаточно сложные приложения, даже не зная о существовании события Paint. Но как только Вам потребуется что-то нарисовать или написать в окне приложения или в окне элемента управления, тут не обойтись без обработки события Paint, а также без знаний приемов рисования с применением контекста отображения Graphics. Методы и свойства класса Graphics В предыдущих разделах этой главы Вы познакомились с некоторыми методами, определенными в классе Graphics и предназначенными для рисования текста и геометрических фигур. Теперь мы расскажем Вам о методах и свойствах класса Graphics более подробно. Рисование геометрических фигур Имена большого количества методов, определенных в классе Graphics, начинается с префикса Draw* и Fill*. Первые из них предназначены для рисования текста, линий и не закрашенных фигур (таких, например, как прямоугольные рамки), а вторые — для рисования закрашенных геометрических фигур. Мы рассмотрим применение только самых важных из этих методов, а полную информацию Вы найдете в документации. Метод DrawLine, как это нетрудно догадаться из его названия, рисует линию, соединяющую две точки с заданными координатами. Ниже мы привели прототипы различных перегруженных версий этого метода: public void DrawLine(Pen, Point, Point); Первый параметр задает инструмент для рисования линии — перо. Перья создаются как объекты класса Pen, например: Pen p = new Pen(Brushes.Black,2); Здесь мы создали черное перо толщиной 2 пиксела. Создавая перо, Вы можете выбрать его цвет, толщину и тип линии, а также другие атрибуты. Подробнее об этом мы расскажем ниже в разделе «Инструменты для рисования». Остальные параметры перегруженных методов DrawLine задают координаты соединяемых точек. Эти координаты могут быть заданы как объекты класса Point и PointF, а также в виде целых чисел и чисел с плавающей десятичной точкой. В классах Point и PointF определены свойства X и Y, задающие, соответственно, координаты точки по горизонтальной и вертикальной оси. При этом в классе Point эти свойства имеют целочисленные значения, а в классе PointF — значения с плавающей десятичной точкой. Третий и четвертый вариант метода DrawLine позволяет задавать координаты соединяемых точек в виде двух пар чисел. Первая пара определяет координаты первой точки по горизонтальной и вертикальной оси, а вторая — координаты второй точки по этим же осям. Разница между третьим и четвертым методом заключается в использовании координат различных типов (целочисленных int и с плавающей десятичной точкой float). Чтобы испытать метод DrawLine в работе, создайте приложение DrawLineApp (аналогично тому, как Вы создавали предыдущее приложение). В этом приложении создайте следующий обработчик события Paint: private void Form1_Paint(object sender, Здесь мы вызываем метод DrawLine в цикле, рисуя 50 горизонтальных линий (рис. 10‑8). Рис. 10-8. Горизонтальные линии в окне приложения DrawLineApp Вызвав один раз метод DrawLines, можно нарисовать сразу несколько прямых линий, соединенных между собой. Иными словами, метод DrawLines позволяет соединить между собой несколько точек. Координаты этих точек по горизонтальной и вертикальной оси передаются методу через массив класса Point или PointF: public void DrawLines(Pen, Point[]); Для демонстрации возможностей метода DrawLines мы подготовили приложение DrawLinesApp. В классе Form1 этого приложения мы создаем кисть pen для рисования линий, а также массив точек points, которые нужно соединить линиями: Pen pen = new Pen(Color.Black, 2); Обратите внимание, что координаты точек по горизонтальной оси зависят от того, является ли значение переменной цикла i четным или нечетным. Рисование линий выполняется за один вызов метода DrawLines во время обработки события Paint: private void Form1_Paint(object
sender, На рис. 10-9 мы показали результат работы этого приложения. Рис. 10-9. Линии в окне приложения DrawLinesApp Как видите, внешний вид линий оставляет желать лучшего — вместо прямых линий мы получили ломаные линии! Можно ли избавиться от этой неприятности? Да, можно, и для этого нужно настроить один из параметров контекста отображения, а именно параметр SmoothingMode. Этот параметр задает режим сглаживания при отображении линий. Включите в исходный текст программы пространство имен System.Drawing.Drawing2D, в котором определено перечисление SmoothingMode: using System.Drawing.Drawing2D; Это пространство имен будет использоваться практически во всех примерах приложений этой главы, так что не забывайте его включать. Далее измените обработчик Form1_Paint следующим образом: private void Form1_Paint(object
sender, Константа SmoothingMode.HighQuality, определенная в перечислении SmoothingMode, задает высококачественное сглаживание при отображении линии. Как видно на рис. 10-10, результат получился вполне удовлетворительный.
Рис. 10-10. Сглаживание линий В таблице 10-1 мы перечислили возможные значения перечисления SmoothingMode. Таблица 10-1. Значения перечисления SmoothingMode
В общем случае использование сглаживания повышает качество изображения, однако приводит к уменьшению скорости рисования. Метод DrawRectangle позволяет рисовать прямоугольники, заданные координатой верхнего левого угла, а также шириной и высотой. В библиотеке классов .NET Frameworks имеется три перегруженных варианта этого метода: public void DrawRectangle(Pen,
Rectangle); В качестве первого параметра этим методам передается перо класса Pen. Остальные параметры задают расположение и размеры прямоугольника. Класс Rectangle используется для описания расположения и размеров прямоугольной области. Свойства X и Y этого класса задают координаты верхнего левого угла прямоугольной области, соответственно, по горизонтальной и вертикальной оси координат. Свойства Width и Height, хранят, соответственно, ширину и высоту прямоугольной области. В классе Rectangle определены и другие свойства, а также методы. Подробное описание этого класса Вы найдете в документации. Варианты метода DrawRectangle с пятью параметрами позволяют задавать расположение и размеры прямоугольника в виде целых чисел, а также в виде числе с плавающей десятичной точкой. Второй и третий параметр задает расположение верхнего левого угла по горизонтальной и вертикальной оси координат, соответственно, а четвертый и пятый — ширину и высоту прямоугольника. Пример использования DrawRectangle мы уже приводили выше в разделе «Пример обработки события Paint», когда рассказывали о приложении PaintApp. Набор прямоугольников За один вызов метода DrawRectangles программа может нарисовать сразу несколько прямоугольников. Существует два перегруженных варианта этого метода: public void DrawRectangles(Pen, Rectangle[]); Первый из этих методов получает в качестве второго параметра ссылку на массив объектов класса Rectangle, описывающих размеры и расположение прямоугольных областей. Второй метод использует для этого объекты класса RectangleF, свойства X, Y, Width и Height которого задают расположение и размеры прямоугольника в виде чисел с плавающей десятичной точкой. Для демонстрации способа применения метода DrawRectangles мы подготовили приложение DrawRectanglesApp. В классе Form1 этого приложения мы определили перо myPen и массив вложенных друг в друга прямоугольников myRectsArray: Pen myPen = new Pen(Color.Black, 2); Метод DrawRectangles вызывается в теле обработчика события Paint, исходный текст которого приведен ниже: private void Form1_Paint(object
sender, В результате работы метода Form1_Paint в окне приложения появляется наш набор прямоугольников (рис. 10-11). Рис. 10-11. Рисование прямоугольников Метод DrawPolygon поможет Вам в тех случаях, когда нужно нарисовать многоугольник, заданный своими вершинами. Предусмотрено два варианта этого метода: public void DrawPolygon(Pen, Point[]); В первом случае методу DrawPolygon через второй параметр передается массив точек класса Point, в котором координаты точек заданы целыми числами, а во втором — массив класса PointF, где координаты соединяемых точек задаются в виде числе с плавающей десятичной точкой. Для демонстрации возможностей метода DrawPolygon мы создали приложение DrawPolygonApp. В нем мы определили перо myPen и массив точек myPoints: Pen myPen = new Pen(Color.Black, 2); Обработчик события Form1_Paint соединяет эти точки вместе, вызывая метод DrawPolygon: private void Form1_Paint(object
sender, На рис. 10-12 мы показали результат работы нашего приложения.
Рис. 10-12. Рисование многоугольника Метод DrawEllipse рисует эллипс, вписанный в прямоугольную область, расположение и размеры которой передаются ему в качестве параметров. Предусмотрено четыре перегруженных варианта метода DrawEllipse: public void DrawEllipse(Pen, Rectangle); Как видите, эти методы отличаются только способом, при помощи которого описывается расположение и размеры прямоугольной области, в которую вписан эллипс. Вы можете задавать расположение и размеры этой области в виде рассмотренных ранее объектов класса Rectangle, RectangleF, а также в виде целых чисел или числе с плавающей десятичной точкой. Пример использования метода DrawEllipse мы привели в разделе «Пример обработки события Paint», когда рассказывали о приложении PaintApp. При помощи метода DrawArc программа может нарисовать сегмент эллипса. Сегмент задается при помощи координат прямоугольной области, в которую вписан эллипс, а также двух углов, отсчитываемых в направлении против часовой стрелки. Первый угол Angle1 задает расположение одного конца сегмента, а второй Angle2 — расположение другого конца сегмента (рис. 10-13). Предусмотрено четыре перегруженных варианта метода DrawArc: public void DrawArc(Pen, Rectangle, float, float); Первый параметр метода DrawArc определяет перо, с помощью которой будет нарисован сегмент. Последние два параметра задают углы Angle1 и Angle2 в соответствии с рис. 10-13. Расположение и размеры прямоугольной области передаются методу DrawArc аналогично тому, как это делается для рассмотренного выше метода DrawEllipse.
Рис. 10-13. Углы и прямоугольник, задающие сегмент эллипса Для рисования сегмента эллипса мы создали приложение DrawArcApp. Вся работа по рисованию выполняется внутри обработчика события Form1_Paint: private void Form1_Paint(object
sender, Здесь мы использовали вариант метода DrawArc, допускающий указание расположения и размеров прямоугольной области, а также углов в виде целых чисел. Результат работы приложения DrawArcApp показан на рис. 10-14.
Рис. 10-14. Рисование сегмента эллипса Из институтского курса математики Вам, скорее всего, известно понятие сплайна (spline). Сплайн представляет собой кривую линию, соединяющую между собой несколько точек. Кривая Безье, представляющая собой одну из разновидностей сплайна, задается четырьмя точками. Две из них — начальная и конечная, а две другие — управляющие. Кривая Безье проходит через начальную и конечную точки, а управляющие точки задают изгибы кривой линии. Те из Вас, кто когда-либо работал с векторными графическими редакторами, например, с редактором Corel Draw, знают о существовании кривых Безье и управляющих точек. Для рисования кривых Безье имеются два перегруженных набора методов DrawBezier и DrawBeziers: public void DrawBezier(Pen, Point, Point, Point, Point); public void DrawBeziers(Pen, Point[]); Во всех этих методах первый параметр задает перо, которая будет использована для рисования. Остальные параметры задают координаты начальной, конечной и управляющих точек. Что касается метода DrawBeziers, то он позволяет задавать координаты точек в виде массивов, что может быть удобно в некоторых случаях. В приложении DrawBezierApp мы рисуем кривую Безье в обработчике события Paint: private void Form1_Paint(object sender, Здесь мы создаем начальную и конечную точки startPt и endPt, через которые проходит наша кривая, а также управляющие точки control1Pt и control2Pt. Координаты всех точек передаются методу DrawBeziers через массив myBezierPoints. Управляющие точки изгибают линию, как бы притягивая ее к себе (рис. 10-15). Рис. 10-15. Рисование кривой Безье В отличие от только что рассмотренных кривых линий Безье, линии канонического или обычного сплайна (cardinal spline) проходит через все заданные точки. Для рисования обычных сплайнов предусмотрены методы DrawCurve и DrawClosedCurve. Первый из этих методов рисует незамкнутую кривую линию (открытый сплайн), а второй — замкнутую (закрытый сплайн). В простейшем случае методам передается перо и массив соединяемых точек: public void DrawCurve(Pen, Point[]); public void DrawCurveClosed(Pen, Point[]); Существуют версии методов, позволяющие дополнительно задать так называемую жесткость (tension) сплайна. Жесткость задается в виде третьего дополнительного параметра: public void DrawCurve(Pen, Point[], float); public void DrawClosedCurve(Pen, Point[], float, FillMode); По умолчанию значение жесткости равно 0,5. При увеличении этого параметра увеличиваются изгибы кривой линии. При жесткости большей 1 или меньшей 0 кривая может превратиться в петлю. Методу DrawClosedCurve дополнительно задается параметр типа FillMode. Если значение этого параметра равно FillMode.Alternate, при рисовании самопересекающихся замкнутых сплайнов будут чередоваться закрашенные и не закрашенные области. Если же значение этого параметра равно FillMode.Winding, большинство замкнутых областей будет закрашено. В приложении DrawClosedCurveApp мы рисуем обычный сплайн, используя точки с теми же координатами, что и в предыдущем примере: using System.Drawing.Drawing2D; Результат показан на рис. 10-16. Рис. 10-16. Рисование канонического сплайна Для рисования замкнутого сегмента эллипса (pie) Вы можете воспользоваться методом DrawPie. Имеется 4 перегруженных варианта этого метода: public void DrawPie(Pen, Rectangle,
float, float); В качестве первого параметра методу нужно передать перо для рисования. Последние два параметра определяют углы, ограничивающие сегмент эллипса. Эти углы используются таким же образом, как и при рисовании незамкнутого сегмента эллипса методом DrawArc, рассмотренным выше. Остальные параметра задают расположение и размеры прямоугольника, в который вписывается сегмент эллипса. Для демонстрации возможностей метода DrawPie мы подготовили приложение DrawPieApp. Вот как выглядит обработчик события Form1_Paint этого приложения: private void Form1_Paint(object sender, Здесь через переменные xPos, yPos, width и height передаются координаты левого верхнего угла и размеры прямоугольной области, в которую вписывается сегмент эллипса. Переменные startAngle и endAngle задают углы, ограничивающие сегмент. Для большей наглядности мы рисуем не только сегмент эллипса, но и прямоугольник, в который этот сегмент вписан. Для рисования прямоугольника мы используем линию толщиной 1 пиксел, а для рисования сегмента — два пиксела (рис. 10-17). Рис. 10-17. Рисование сегмента эллипса В классе Graphics определен ряд методов, предназначенных для рисования закрашенных фигур. Имена некоторых из этих методов, имеющих префикс Fill, мы перечислили в табл. 10‑2. Таблица 10-2. Методы для рисования закрашенных фигур
Есть два отличия методов с префиксом Fill от одноименных методов с префиксом Draw. Прежде всего, методы с префиксом Fill рисуют закрашенные фигуры, а методы с префиксом Draw — не закрашенные. Кроме этого, в качестве первого параметра методам с префиксом Fill передается не перо класса Pen, а кисть класса Brush. Пример использования метода FillEllipse мы приводили ранее в разделе «Рисование в окне элемента управления» этой главы. Вот еще один пример: SolidBrush redBrush = new SolidBrush(Color.Red); Здесь мы вначале создаем кисть красного цвета как объект класса SolidBrush. Эта кисть затем передается методу FillEllipse в качестве первого параметра. Остальные параметры метода FillEllipse задают расположение и размеры прямоугольника, в который будет вписан эллипс. Рассказывая в [8] о том, как устроена графическая подсистема в ОС Microsoft Windows, и как ей пользоваться, мы отметили, что рисование графических изображений с использованием одного только программного интерфейса Win32 API — не простая задача. Решая ее, программисту приходится учитывать многочисленные детали, имеющие отношение к цветовому разрешению и режимам видеоадаптера. Кроме того, часто приходится работать напрямую с внутренними форматами файлов графических изображений, таких как BMP, GIF, JPEG, PNG и т.д. Графическая подсистема GDI+, реализованная на платформе Microsoft .NET Frameworks, содержит мощные и удобные средства работы с графикой и графическими изображениями, исключающие для программиста необходимость разбираться с внутренними форматами файлов графических изображений, а также задумываться о таких вещах, как палитры [8]. Однако прежде чем мы займемся рисованием, необходимо сделать маленькое отступление и рассказать о ресурсах приложения. Если Вы раньше создавали приложения Microsoft Windows, то знаете, что формат загрузочного модуля таких приложений сложнее формата загрузочного модуля программ MS-DOS. Кроме выполняемого кода и констант в загрузочном модуле приложения Microsoft Windows находятся дополнительные данные — ресурсы. Что такое ресурсы? Приложение Microsoft Windows может хранить в виде ресурсов текстовые строки, значки, курсоры, графические изображения, меню, диалоговые окна, произвольные массивы данных и т. д. Физически ресурсы находятся внутри exe-файла приложения. Они могут загружаться в оперативную память автоматически при запуске приложения или по запросу приложения (явному или неявному). Такой механизм обеспечивает экономное использование оперативной памяти, так как все редко используемые данные можно хранить на диске и загружать в память только при необходимости. Например, приложение может иметь сложную систему диагностики ошибочных ситуаций, содержащую различные диалоговые окна, массивы сообщений об ошибках и т. п. Когда приложение работает без ошибок (что, очевидно, является нормальным явлением) ненужные диагностические ресурсы спокойно лежат на диске, не перегружая оперативную память. Но как только возникает ошибка, и приложение вызовет функцию обработки ошибки, эти ресурсы будут автоматически загружены. Ресурсы можно редактировать без повторной трансляции программного проекта. Это означает, что Вы сможете легко перевести все сообщения и тексты меню приложения на другой национальный язык, отредактировать графические изображения или любые другие ресурсы, даже не имея в своем распоряжении исходные тексты. В приложениях Microsoft .NET Framework тоже используется концепция ресурсов. В частности, ресурсы таких приложений могут содержать значки и графические изображения. Если приложение должно рисовать в своих окнах значки или графические изображения, целесообразно включить их в ресурсы приложения. В этом случае данные из файлов, содержащих значки или изображения, будут переписаны в файл сборки приложения. Таким образом, Вы сможете поставлять приложение как единый загрузочный файл, не «комплектуя» его дополнительно файлами графических изображений. В этом разделе мы опишем приложение DrawIconApp, предназначенное для демонстрации способов рисования значков, хранящихся в ресурсах приложения. Создайте приложение DrawIconApp обычным образом, пользуясь мастером проектов. Затем перепишите в каталог проекта любой файл значка, взяв его, например, из каталога значков системы Microsoft Visual Studio .NET. Далее включите значок в проект приложения, щелкнув название проекта правой клавишей мыши в окне Solution Explorer и выбрав затем строку контекстного меню Add Existing Item. В результате всех этих действий файл значка появится в списке файлов Вашего проекта. Щелкните этот файл левой клавишей мыши, а затем установите значение свойства Build Action для файла значка равным Embedded Resource (рис. 10-18). Рис. 10-18. Включение значка в ресурсы приложения В результате выполнения этих действий файл значка (в нашем случае это был файл FACE02.ICO) будет добавлен в ресурсы приложения. Создайте обработчик события Form1_MouseDown, получающий управление, когда пользователь щелкает левой клавишей мыши окно приложения: private void Form1_MouseDown(object sender, Этот обработчик получает контекст отображения с помощью метода Graphics.FromHwnd, а затем создает объект класса Icon, представляющий значок. Для создания этого объекта мы использовали конструктор с двумя параметрами. Через первый параметр конструктору класса Icon мы передаем ссылку на объект-сборку, в котором хранится наш ресурс — значок из файла FACE02.ICO. Мы можем получить такую ссылку при помощи метода GetType. Что же касается второго параметра, то через него мы передаем имя ресурса, которое в нашем случае будет совпадать с именем файла значка. Функция DrawIcon рисует значок myIcon в точке, где был курсор мыши в момент щелчка (рис. 10-19). Рис. 10-19. Использование метода DrawIcon Всего существует два перегруженных метода DrawIcon: public void DrawIcon(Icon, Rectangle); В первом варианте метода параметр типа Rectangle задает прямоугольную область, внутри которой будет нарисован значок. При этом значок будет масштабирован таким образом, чтобы занять собой всю заданную область. Независимо от значка myIcon, фрагмент кода, приведенный ниже, рисует в левом верхнем углу значок с размерами 100x100 пикселов: g.DrawIcon(myIcon, new Rectangle(0, 0, 100, 100)); Если исходный значок меньше указанных размеров, при рисовании он будет растянут, а если больше — сжат. Для рисования значков можно также использовать метод DrawIconUnstretched: public void DrawIconUnstretched(Icon, Rectangle); Этот метод рисует значок внутри области, заданной с помощью второго параметра. В отличие от метода DrawIcon, метод DrawIconUnstretched не растягивает и не сжимает значок, а рисует его в точном соответствии с оригинальными размерами значка. Разумеется, Вам не обязательно включать значок в проект приложения и добавлять его к ресурсам. Вы можете воспользоваться вариантом конструктора класса Icon, допускающим указание в своем единственном параметре имени или пути к файлу значка. Если указать только имя значка, то при запуске приложения файл значка должен будет находиться в том же каталоге, что и exe-файл самого приложения. Растровые и векторные изображения В ОС Microsoft Windows используются два формата изображений — аппаратно-зависимый DDB (Device-Dependent Bitmap) и аппаратно-независимый DIB (Device-Independent Bitmap). Согласно определению, данному в документации, изображение DDB есть набор битов в оперативной памяти, который может быть отображен на устройстве вывода (например, выведен на экран видеомонитора или распечатан на принтере). Внутренняя структура изображения DDB жестко привязана к аппаратным особенностям устройства вывода. Поэтому представление изображения DDB в оперативной памяти полностью зависит от устройства вывода. Иногда такие изображения называют растровыми, подчеркивая тот факт, что их можно рассматривать как совокупность строк растра (горизонтальных линий развертки). Если бы в Microsoft Windows можно было работать только с изображениями DDB, было бы необходимо иметь отдельные наборы изображений для каждого типа видеоадаптера и каждого видеорежима, что, очевидно, крайне неудобно. Аппаратно-независимое изображение DIB содержит описание цвета пикселов изображения, которое не зависит от особенностей устройства отображения. ОС Microsoft Windows после соответствующего преобразования может нарисовать такое изображение на любом устройстве вывода. Несмотря на некоторое замедление процесса вывода по сравнению с выводом изображений DDB, универсальность изображений DIB делает их весьма привлекательными для хранения изображений. При создании обычных приложений Microsoft Windows, работающих файлами изображений, программисту следовало учитывать, что существует множество форматов таких файлов. Файлы изображений могут содержать таблицу цветов или цветовую палитру, могут быть сжаты с использованием тех или иных алгоритмов. Приложение должно также анализировать структуру графических файлов с целью обнаружения возможных ошибок, так как отображение содержимого неправильного файла может создать полный хаос на экране. При необходимости Вы найдете в [8] полную информацию о форматах DDB и DIB. Кроме растровых изображений используются так называемые векторные изображения. Если растровые изображения содержат описание цвета каждого пиксела, векторные изображения состоят из описаний отдельных графических объектов, из которых состоит изображение. Это могут быть линии, окружности и т. п. Некоторые графические редакторы, например, Corel Draw, Microsoft Draw, Microsoft Visio, для внешнего представления изображения используют векторный формат. Сравнивая эти форматы, отметим, что каждый из них имеет свои преимущества и свои недостатки. Растровые изображения, как правило, выводятся на экран быстрее, так как их внутренняя структура аналогична (до некоторой степени) структуре видеопамяти. К недостаткам растровых изображений можно отнести большой объем памяти, требующийся для их хранения (которых доходит до десятков и сотен Мбайт), невозможность масштабирования без потери качества изображения, а также сложность выделения и изменения отдельных объектов изображения. Векторные изображения состоят из описаний отдельных элементов, поэтому они легко масштабируются. С помощью такого графического редактора, как, например, Corel Draw, Вы без особого труда сможете выделить отдельный элемент изображения и изменить его внешний вид. Следует отметить, что некоторые устройства вывода, такие как плоттер (графопостроитель), способен работать только с векторными изображениями, так как с помощью пера можно рисовать только линии. В операционной системе Microsoft Windows и многих офисных приложениях широко используется такой векторный формат изображений, как метафайл WMF (Windows Metafile). Как мы уже говорили, графический интерфейс GDI+, реализованный компанией Microsoft на платформе Microsoft .NET Frameworks, значительно облегчает работу с изображениями, как растровыми, так и векторными. В табл. 10-4 мы привели полный список форматов графических файлов, с которыми могут работать приложения GDI+. Эти форматы определены в виде свойств статического класса ImageFormat. Таблица 10-4. Доступные форматы графических файлов
Как видите, список довольно внушительный и включает в себя практически все наиболее распространенные форматы файлов. Все эти форматы, за исключением WMF и EMF, являются растровыми. Использование класса Image Для рисования изображений, загруженных из файлов с форматами, перечисленными в табл. 10‑4, поможет класс Image. В этом разделе мы создадим приложение ImageViewApp, способное показывать содержимое графических файлов, значки которых перетаскиваются в окно приложения из папок ОС Microsoft Windows методом буксировки. Вы сможете убедиться, что отображение графических изображений в программах C# действительно не вызывает никаких затруднений. Попутно мы расскажем об использовании буксировки в приложениях Microsoft .NET Frameworks. Если Вы когда-либо сталкивались с необходимостью писать обычные приложения Microsoft Windows, способные обрабатывать файлы или другие объекты, значки которых перетаскиваются пользователем в окно приложения методом буксировки, то знаете, насколько это непросто. Фактически при этом необходимо реализовать в своем приложении OLE-сервер, а эта задача доступна только опытным и очень опытным программистам, владеющим всеми тонкостями модели компонентного объекта (COM, Component Object Model), реализованной в ОС Microsoft Windows. К счастью, при создании приложений Microsoft .NET Framework эта задача решается очень и очень просто. Мы покажем ее решение на примере упомянутого выше приложения ImageViewApp, предназначенного для просмотра содержимого файлов графических изображений. Нам необходимо сделать так, чтобы пользователи могли перетаскивать значки изображений в окно нашего приложения из папок ОС Microsoft Windows для просмотра. Итак, с помощью мастера проектов создайте приложение ImageViewApp. Для того чтобы главное окно нашего приложения могло принимать объекты, перетаскиваемые методом буксировки, установите значение свойства AllowDrop равным True, как это показано на рис. 10-20.
Рис. 10-20. Включение режима AllowDrop Заметим, что данное свойство предусмотрено не только у окна формы, но и у окон различных элементов управления, таких, например, как текстовые поля, текстовые редакторы, списки, панели и др. Далее нам необходимо предусмотреть обработку как минимум двух событий. Это события DragOver и DragDrop. Событие DragOver возникает, когда пользователь при перемещении курсора над поверхностью клиентской области окна. Обработчик этого события должен сообщить вызывающей программе, сможет ли он принять данные, передаваемые методом буксировки. Событие DragDrop возникает после завершения буксировки, когда пользователь отпускает кнопку над поверхностью окна, принимающего объект буксировки. Обработчик этого события должен принять и обработать данные. В нашем случае обработка будет заключаться в отображении содержимого графического файла. Помимо этих событий возникают еще события DragEnter и DragLeave. Первое из них позволяет отслеживать момент входа курсора мыши в область принимающего окна, а второе — когда курсор покидает эту область. Для обработки события DragOver мы подготовили метод Form1_DragOver, который Вам нужно добавить в приложение ImageViewApp: private void Form1_DragOver(object
sender, Обработчику события DragOver передается объект класса DragEventArgs. Анализируя свойства этого объекта, Ваша программа может принять решение о том, сможет ли она обработать объект, который пользователь отбуксировал в окно приложения. Свойство KeyState объекта DragEventArgs несет в себе информацию о том, какой клавишей мыши осуществляется буксировка, и нажаты ли при этом клавиши Shift, Control и Alt. Это свойство представляет собой набор флажков, перечисленных в табл. 10-5. Таблица 10-5. Флажки свойства KeyState
В свойствах X и Y объекта DragEventArgs передаются, соответственно, горизонтальные и вертикальные координаты курсора мыши (в экранных координатах). Анализируя эти свойства, программа может принять решение о возможности выполнения операции буксировки в те или иные области окна. Анализируя свойство Data объекта DragEventArgs с помощью методов GetFormats и GetDataPresent, приложение может определить тип передаваемых ему данных. Описание этих методов Вы найдете в документации. Свойство AllowedEffects содержит информацию о действиях, которые программа может предпринять с передаваемыми ей данными. Это одна из следующих констант, описанных в табл. 10-6. Таблица 10-6. Константы перечисления AllowedEffects
После анализа содержимого перечисленных выше свойств объекта DragEventArgs программа должна установить значение свойства Effect, разрешив или запретив выполнение той или иной операции. При этом следует использовать константы, перечисленные в табл. 10-6. В приложении ImageViewApp мы разрешаем копирование буксируемых данных, записывая в свойство Effect константу DragDropEffects.Copy: e.Effect = DragDropEffects.Copy; Теперь займемся обработкой события DragDrop. Вот соответствующий обработчик: Image img; Если пользователь буксирует в окно приложения файл, то формат данных, извлеченных методом GetData из свойства Data, будет DataFormats.FileDrop. В качестве данных будет выступать массив полных путей к файлам (пользователь может перетаскивать сразу несколько файлов). Если же пользователь перетаскивает в окно приложения выделенный фрагмент текста, формат данных будет DataFormats.StringFormat. Нас интересует только один файл, т.к. приложение ImageViewApp может показывать за один раз только одно изображение. Для загрузки изображения в память мы используем очень мощный метод Image.FromFile, передавая ему самый первый путь к файлу, извлеченный из нулевой ячейки массива files: Image img; Этот метод загрузит изображение файла любого типа из набора, перечисленного в табл. 10-4. После того как изображение будет загружено, наше приложение сообщает о необходимости перерисовки его окна, вызывая для этого метод Invalidate: Invalidate(); В результате будет создано событие Paint, обработчик которого займется рисованием загруженного изображения в окне нашей программы. Так как при рисовании изображения мы используем масштабирование, это же событие мы будем создавать и при изменении размеров окна нашего приложения: private void Form1_Resize(object
sender, System.EventArgs e) Изменение размеров окна приводит к генерации события Resize, так что Вам остается только предусмотреть для него обработчик Form1_Resize. Рисование загруженного изображения И, наконец, вот код обработчика события Form1_Paint, на который возложена работа по рисованию загруженного изображения: private void Form1_Paint(object sender, Как видите, большая часть программного кода выполняет масштабирование, а для рисования мы вызываем один из перегруженных методов Graphics.DrawImage. Получив управление, метод Form1_Paint проверяет, загрузил ли пользователь какое-либо изображение для рисования. Если загрузил, то метод Form1_Paint определяет границы прямоугольной области, занимаемой клиентской областью окна нашего приложения: RectangleF winRect = new RectangleF(0,
0, Именно в этой области и будет нарисовано изображение, файл которого пользователь отбуксировал мышью. Обычно программы, предназначенные для просмотра изображений, умеют масштабировать их таким образом, чтобы изображение полностью помещалось в окне приложения. Чтобы реализовать необходимую логику масштабирования, нам нужно знать некоторые атрибуты загруженного изображения. Эти атрибуты доступны приложению как свойства класса Image. В частности, свойства Width и Height содержат размеры загруженного изображения в дюймах. Чтобы пересчитать эти значения в пикселы для отображения на экране, нам потребуются свойства HorizontalResolution и VerticalResolution. Первое из них содержит количество пискелов в пересчет на один дюйм изображения по горизонтали, а второе — по вертикали. Таким образом, чтобы получить размеры изображения в пикселах, нам нужно воспользоваться следующим выражением: SizeF size = new SizeF(img.Width /
img.HorizontalResolution, Размеры изображения получаются в виде объекта класса SizeF, в котором определены свойства Width и Height. Зная размеры окна winRect.Width и winRect.Height, в котором мы будем рисовать изображение, а также размеры изображения size.Width и size.Height, мы вычисляем масштаб, необходимый для того, чтобы изображение полностью поместилось в окне: float scale = Math.Min(winRect.Width /
size.Width, Здесь с помощью метода Math.Min мы выбираем наименьшее среди отношений ширины и высоты окна к ширине и высоте изображения. Полученный масштаб используется для вычисления новых размеров изображения: size.Width *= scale; И, наконец, последнее действие — рисование изображения: e.Graphics.DrawImage(img, Через первый параметр методу Graphics.DrawImage передается изображение, загруженное при выполнении операции буксировки. Второй и третий параметры задают координаты верхнего левого угла прямоугольной области, в которой будет нарисовано изображение, а четвертый и пятый — размеры этой области. При рисовании будет выполнено масштабирование. Расположение прямоугольной области выбирается таким образом, чтобы изображение было отцентрировано в окне приложения при любых размерах изображения. Результат работы приложения ImageViewApp показан на рис. 10-21. Рис. 10-21. Просмотр изображений в окне приложения PictureViewer Заметим, что помимо метода DrawImage, в классе Graphics определен метод DrawImageUnscaled. Этот метод аналогичен методу DrawImage, но отличается от него тем, что при рисовании не выполняет масштабирование изображения. Еще один важный метод, определенный в классе Graphics, это метод DrawString. С помощью этого метода приложения могут рисовать в своих окнах текстовые строки. Напомним, что ранее мы уже пользовались методом DrawString в приложении PaintApp, описанном в разделе «Событие Paint» этой главы. Мы вызвали этот метод в обработчике события Form1_Paint, как это показано ниже: public string text; В качестве первого параметра методу DrawString передается текстовая строка, которую нужно нарисовать. Второй параметр задает шрифт. О шрифтах мы расскажем ниже, в разделе «Инструменты для рисования». С помощью третьего параметра задается кисть, с применением которой будет нарисован текст. И, наконец, два последних параметра определяют координаты точки, в которой начнется рисование текста. В классе Graphics определено несколько перегруженных вариантов метода DrawString: public void DrawString(string, Font,
Brush, PointF); Параметр типа PointF задает расположение точки вывода текста. В последних двух вариантах метода DrawString расположение этой точки задается при помощи пары чисел формата float. Если задан параметр типа RectangleF, то текст будет нарисован внутри области, размеры и расположение которой заданы этим параметром. В том случае, если текст выйдет за границы области, то он будет обрезан. И, наконец, параметр типа StringFormat позволяет выполнить форматирование текста. Описание этой возможности Вы найдете в документации. Все методы класса Graphics, предназначенные для рисования фигур или текста, получают через один из параметров перо класса Pen или кисть класса Brush, с помощью которых и выполняется рисование. Метод DrawString, кроме этого, получает еще и шрифт, применяемый для рисования текста. В этом разделе нашей книги мы познакомимся с перечисленными выше инструментами рисования, применяемые в системе GDI+ и доступными приложениям C# с графическим интерфейсом. Перья используются для рисования линий и простейших геометрических фигур и создаются как объекты класса Pen. Вот соответствующие конструкторы: public Pen(Color); Первый из этих конструкторов создает перо заданного цвета. Цвет задается при помощи объекта класса Color. Второй конструктор позволяет дополнительно задать толщину пера. Третий и четвертый конструктор создают перо на основе кисти, причем в четвертом конструкторе можно указать толщину создаваемого пера. О кистях мы расскажем чуть позже в этой главе. После того как перо создано, программа может определить его атрибуты при помощи свойств класса Pen. Некоторые из этих свойств перечислены в табл. 10-7. Таблица 10-7. Свойства пера
Как видите, предусмотрены многочисленные возможности для создания самых разных перьев. Устанавливая значение свойства Color и Width, приложение может изменить, соответственно, цвет и ширину линии, рисуемой пером. Если надо нарисовать пунктирную или штрих-пунктирную линию, приложение должно задать необходимое значение для свойства DashStyle. При этом допускается изменять вид точек и тире пунктирных и штрих-пунктирных линий (свойство DashCup), задавать расстояние от начала линии до начала штриха (свойство DashOffset) или даже вовсе задать произвольный вид для штрихов и разделяющих эти штрихи пробелов (свойство DashPattern). При необходимости изменить внешний вид концов линий используйте свойства StartCup и EndCup, задающие стиль концов линий. Свойство LineCap определяет форму концов линий. Если вас интересует стык между двумя различными линиями, то стиль этого стыка задается свойством LineJoin, а предельная толщина стыка — стилем MiterLimit. В приложении PenApp мы покажем способы изменения толщины линии, стиля пунктирной и штрих-пунктирной линии, а также стиля концов линий. Ниже мы привели исходный текст обработчика события Form1_Paint, рисующий линии различных типов и стилей: using System.Drawing.Drawing2D; В начале своей работы метод Form1_Paint получает ссылку на объект класса Graphics, т.е. контекст отображения: Graphics g=e.Graphics; Используя полученный контекст отображения, метод Form1_Paint закрашивает окно приложения белым цветом, а затем рисует черную линию толщиной 1 пиксел: g.Clear(Color.White); Эта техника использовалась нами ранее в приложениях, описанных в этой главе. А вот дальше начинается новое — мы изменяем свойство Width созданного ранее пера myPen, и снова рисуем линию с небольшим смещением по вертикали: y += 15; После этого мы рисуем подобным образом еще несколько линий увеличивающейся толщины. На следующем этапе наша программа изменяет значение свойства DashStyle, последовательно присваивая ему значения DashStyle.Dash, DashStyle.DashDot, DashStyle.DashDotDot и DashStyle.Dot: y += 15; В результате приложение нарисует в своем окне четыре пунктирные и штрих-пунктирные линии различного типа. Финальная часть обработчика события Form1_Paint показывает возможность изменения стиля концов линий. Сначала мы устанавливаем значение свойства DashStyle равным DashStyle.Solid, отменяя рисование пунктирных и штрих-пунктирных линий. Далее мы четыре раза устанавливаем различные свойства StartCap и EndCap, снабжая линии наконечниками различных стилей: y += 15; Результат выполнения этих операций представлен на рис. 10-22.
Рис. 10-22. Рисование линий различными перьями На рис. 1.3. показаны геометрические фигуры, нарисованные с использованием различных перьев, сплошных и пунктирных. Разумеется, что с помощью подобных перьев можно рисовать не только прямые линии, но и любые геометрические фигуры. Внутренняя область окна и замкнутых геометрических фигур может быть закрашена при помощи кисти. В приложениях Microsoft .NET Frameworks кисти создаются на базе классов, производных от абстрактного класса Brush. Это следующие классы: · Brushes · SolidBrush; · HatchBrush; · TextureBrush; · LinearGradientBrush; · PathGradientBrush Простейшие из кистей — это кисти Brushes и SolidBrush, предназначенные для сплошной закраски фигур. Эти кисти создается при помощи конструктора с одним параметром, задающим цвет в виде объекта класса Color. В начале этой главы мы рассказывали о приложении PaintApp, в котором кисть класса Brushes применялась для создания кисти, с помощью которой приложение рисовало прямоугольник и эллипс. Кроме этого, кисть черного цвета создавалась и для рисования текста: private void Form1_Paint(object
sender, Кисти типа HatchBrush При помощи класса HatchBrush можно создать прямоугольную кисть заданного стиля, с заданным цветом изображения и фона. Для создания кистей этого типа предусмотрено два конструктора: public HatchBrush(HatchStyle, Color); Первый из этих конструкторов позволяет создать кисть заданного стиля и цвета, а второй дополнительно позволяет указать цвет фона. В табл. 10-8 мы перечислили различные стили кисти HatchBrush, представляющие собой константы перечисления HatchStyle. Таблица 10-8. Стили кисти типа HatchBrush
Для того чтобы продемонстрировать использование кистей класса HatchBrush, мы подготовили приложение HatchBrushApp. Вот исходный текст обработчика событий Form1_Paint этого приложения, в котором выполняются существенные для нас операции: using System.Drawing.Drawing2D; Как видите, мы здесь последовательно создаем три различных кисти, а затем используем их для закраски внутренних областей прямоугольника и эллипсов. Результат работы приложения HatchBrushApp показан на рис. 10-23.
Рис. 10-23. Использование кистей класса HatchBrush Кисти типа TextureBrush Если Вас не устраивает ни одна из кистей, перечисленных в табл. 10-8, то Вы можете создать собственную кисть на базе класса TextureBrush, в виде произвольного изображения. Такая кисть, называемая текстурной, может иметь любой внешний вид и любой цвет. Для создания кисти класса TextureBrush Ваше приложение может воспользоваться одним из следующих конструкторов: public TextureBrush(Image); Самому простому из этих конструкторов нужно передать изображение, загруженное из ресурсов приложения или из внешнего файла (с помощью рассмотренного ранее метода Image.FromFile). Структуры Rectangle и RectangleF позволяют задать границы прямоугольной области, ограничивающие изображение кисти. С помощью констант перечисления WrapMode программа может задать способ размещения текстуры по горизонтали и вертикали. Эти константы приведены в табл. 10-9. Таблица 10-9. Константы перечисления WrapMode
И, наконец, параметр ImageAttributes позволяет задать различные атрибуты изображения, такие как количество цветов и способ рисования. Описание этого параметра и класса ImageAttributes Вы найдете в электронной справочной документации системы Microsoft Visual Studio .NET. Закраску с помощью текстурной кисти мы демонстрируем в приложении TextureBrushApp. Ниже мы привели исходный текст обработчика события Form1_Paint этого приложения, в котором происходит все самое интересное: private void Form1_Paint(object
sender, После получения контекста отображения и очистки поверхности окна мы создаем новое изображение класса Image, загружая его из ресурсов приложения: Image myBrushImage = new Bitmap(GetType(),"L_RED.GIF"); Обратите внимание, что мы создаем объект класса Bitmap, а полученную в результате ссылку присваиваем объекту класса Image. Предполагается, что перед трансляцией приложения Вы скопировали файл текстуры L_RED.GIF в ресурсы приложения, а также установили значение свойства Build Action для файла изображения равным Embedded Resource. Результат закраски прямоугольной области текстурной кистью показан на рис. 10-24.
Рис. 10-24. Закрашивание прямоугольника кистью типа TextureBrush Приложениям GDI+ доступен еще один вид кистей — так называемая градиентная кисть. Линейная градиентная кисть LinearGradientBrush позволяет задать в кисти переход от одного цвета к другому. Кисти с множественным градиентом PathGradientBrush позволяют задать внутри кисти область, которая будет закрашена с использованием цветового градиента. В нашей книге из-за недостатка места мы рассмотрим только один вариант использования линейной градиентной кисти. Рассмотрим обработчик события Form1_Paint приложения LinearGradientApp, специально созданного нами для демонстрации возможностей закрашивания при помощи линейной градиентной кисти: private void Form1_Paint(object
sender, Прямоугольная область задает пространство, в котором происходит изменение цвета. Конструктору класса LinearGradientBrush передаются координаты этой области, значения двух цветов, а также режим градиентного закрашивания LinearGradientMode. Возможные значения перечисления LinearGradientMode мы привели в табл. 10-10. Таблица 10-10. Константы перечисления LinearGradientMode
На рис. 10-25 мы показали пример градиентного закрашивания внутренней области прямоугольника в приложении LinearGradientApp.
Рис. 10-25. Закрашивание прямоугольника линейной градиентной кистью типа LinearGradientBrush Для того чтобы рисовать текст, используются шрифты. ОС Microsoft Windows может работать с растровыми, векторными и масштабируемыми шрифтами. Кроме этого, приложения Microsoft Windows могут использовать шрифты, встроенные в устройство вывода (обычно это принтерные шрифты). Растровые шрифты содержат образы всех символов в виде растровых изображений. При этом для каждого размера шрифта необходимо иметь свой набор символов. Кроме того, различные устройства вывода имеют разное соотношение горизонтальных и вертикальных размеров пиксела, что приводит к необходимости хранить отдельные наборы образов символов не только для разных размеров шрифта, но и для разного соотношения размеров пиксела физического устройства отображения. Растровые шрифты плохо поддаются масштабированию, так как при этом наклонные линии контура символа принимают зазубренный вид. Векторные шрифты хранятся в виде набора векторов, описывающих отдельные сегменты и линии контура символа, поэтому они легко масштабируются. Однако их внешний вид далек от идеального. Как правило, векторные шрифты используются для вывода текста на векторные устройства, такие, как плоттер. В состав ОС Microsoft Windows входит не очень большое количество шрифтов, однако при необходимости Вы можете приобрести дополнительные шрифты как отдельно, так и в составе различного программного обеспечения. Например, вместе с графическим редактором Corel Draw поставляются сотни различных шрифтов. Помимо обычных шрифтов существуют символьные или декоративные шрифты, содержащие вместо букв различные значки. Шрифты TrueType Масштабируемые шрифты TrueType впервые появились в Microsoft Windows версии 3.1 и сильно повлияли на рост популярности этой ОС. Шрифты TrueType поддаются масштабированию без существенных искажений внешнего вида. Рис. 10-26 иллюстрирует ухудшение внешнего вида растрового и векторного шрифтов при увеличенном размере символов. Внешний вид масштабируемого шрифта не ухудшился. Масштабируемые шрифты TrueType не только сохраняют свое начертание при произвольном изменении высоты букв, но и обладают другими достоинствами. Отметим, например, возможность вывода строк текста, расположенных под любым углом относительно горизонтальной оси. Растровые и векторные шрифты позволяют располагать строки текста только в горизонтальном направлении, что может создать определенные трудности, например, при необходимости надписать название улицы на карте города. Еще одно преимущество масштабируемых шрифтов TrueType связано с тем, что Вы можете встроить такой шрифт непосредственно в документ. Зачем это может понадобиться? Дело в том, что стандартный набор шрифтов TrueType, поставляемых в составе ОС Microsoft Windows, не всегда удовлетворяет пользователей. Поэтому они приобретают дополнительные шрифты у независимых разработчиков. Однако использование нестандартных шрифтов может привести к проблемам при необходимости переноса документа из одного компьютера в другие, так как там нужного шрифта может не оказаться. Вы, конечно, можете просто скопировать нужный шрифт и перенести его вместе с документом, однако такая процедура может быть запрещена по условию лицензионного соглашения с разработчиками шрифта. Рис. 10-26. Растровый, векторный и масштабируемый шрифты Проблему переноса документа на другой компьютер с сохранением прав разработчиков шрифта можно решить, используя шрифты, встроенные в документ. Пользователь может, например, подготовить документ в текстовом процессоре Microsoft Word и встроить в него все использованные шрифты. При переносе такого документа на другой компьютер эти шрифты можно будет использовать для просмотра и, возможно, редактирования этого (и только этого) документа. Возможность редактирования с использованием встроенного шрифта определяется разработчиком шрифта. Шрифт TrueType состоит из изображений (рисунков) отдельных символов — глифов (glyph). Для внутреннего представления глифа в файле шрифта TrueType используются описания контуров, причем один глиф может содержать несколько таких контуров (рис. 10-27).
Рис. 10-27. Рисунки символов Глифы могут иметь различный внешний вид (typeface). ОС Microsoft Windows классифицирует шрифты по типам, или семействам (font family). Эти типы называются Modern, Roman, Swiss, Script, Decorative. Шрифты семейства Modern имеют одинаковую ширину букв. Шрифты семейства Roman содержат буквы различной ширины, имеющие засечки. Семейство Swiss отличается тем, что при переменной ширине букв они не имеют засечек. Буквы в шрифтах семейства Script как бы написаны от руки. Семейство Decorative содержит глифы в виде небольших картинок (значков). В следующей таблице мы привели примеры шрифтов различных семейств. Приложения Microsoft Windows могут заказывать шрифт, ссылаясь на название соответствующего семейства, однако в зависимости от состава имеющихся шрифтов ОС Microsoft Windows может предоставить приложению не тот шрифт, какой бы Вам хотелось. Другая важная характеристика шрифта — это размер символов. Для описания вертикального размера символов шрифта используются несколько параметров (рис. 10-28). Рис. 10-28. Параметры вертикального размера шрифта Отсчет всех размеров выполняется от так называемой базовой линии (base line) шрифта. Для размеров используются логические единицы, которые зависят от режима отображения, установленного в контексте устройства. На рис. 10-28 общая высота символов отмечена как Height. Эта высота складывается из двух компонентов — Ascent и Descent. Компонент Ascent представляет собой высоту от базовой линии с учетом таких элементов, как тильда в букве «Й». Компонент Descent определяет пространство, занимаемое символами ниже базовой линии. Сумма Ascent и Descent в точности равна Height. Величина InternalLeading определяет размер выступающих элементов символов и может быть равна нулю. Величина ExternalLeading определяет минимальный межстрочный интервал, рекомендуемый разработчиком шрифта. Ваше приложение может игнорировать межстрочный интервал, однако в этом случае строки будут соприкасаться друг с другом, что не всегда приемлемо. Как видите, с размерами символов здесь далеко не все так просто, как хотелось бы! Растровые шрифты, которые относятся к одному семейству, но имеют разные размеры букв, хранятся в отдельных файлах. В то же время благодаря возможности масштабирования шрифтов TrueType для них нет необходимости в отдельном хранении глифов различных размеров. Графический интерфейс GDI может выполнять масштабирование растровых шрифтов, увеличивая (но не уменьшая) размер букв. Результат такого масштабирования при большом размере букв обычно неудовлетворительный, так как на наклонных линиях контура букв образуются зазубрины (рис. 10-26). Что же касается GDI+, то он работает только с масштабируемыми шрифтами, к которым относятся шрифты TrueType. Векторные шрифты легко поддаются масштабированию, поэтому для хранения шрифта одного семейства, но разного размера, можно использовать один файл. Вы знаете, что шрифты могут иметь нормальное (normal), жирное (bold) или наклонное (italic) начертание. В табл. 10-11 мы привели примеры различных начертаний шрифтов. В табл. 10-12 Вы найдете примеры начертаний шрифтов, доступные приложениям GDI+. Таблица 10-11. Примеры начертаний шрифтов
Графический интерфейс GDI получает жирное и наклонное начертание растровых шрифтов из нормального при помощи соответствующих алгоритмов утолщения и наклона шрифта. Такие алгоритмы могут быть использованы и для масштабируемых шрифтов TrueType, однако лучших результатов можно достигнуть при использовании отдельных файлов шрифтов TrueType для нормального, жирного и наклонного начертания. Еще один часто используемый атрибут оформления строк
текста — подчеркивание. Иногда используется шрифт с Шрифты OpenType Операционная система Microsoft Windows 2000 и Microsoft Windows ХР способны работать с шрифтами OpenType, созданными совместно компаниями Adobe и Microsoft. Шрифты OpenType сочетают в себе достоинства шрифтов TrueType, а также шрифтов Type1, разработанных компанией Adobe и широко применяемых в издательском деле. На рис. 10-29 мы показали содержимое папки шрифтов Fonts, которую можно найти в окне управляющей панели Control Panel. Рис. 10-29. Шрифты в ОС Microsoft Windows 2000 В этой папке векторные и растровые шрифты обозначены буквой A, шрифты TrueType — буквами TT, а шрифты OpenType — буквой O. Чтобы просмотреть образцы текста, оформленные тем или иным шрифтом, достаточно дважды щелкнуть название шрифта в папке Fonts. Результат такого просмотра для шрифта Comic Sans MS показан на рис. 10-30. Рис. 10-30. Просмотр шрифта Comic Sans MS Щелкнув кнопку Print, расположенную в верхнем правом углу окна просмотра, Вы сможете распечатать образец и посмотреть, как этот шрифт будет выглядеть в бумажном документе. Прежде чем нарисовать текстовую строку, приложение должно выбрать шрифт, создав объект класса Font. В приложении PaintApp мы выбирали шрифт для рисования текста методом DrawString: private void Form1_Paint(object
sender, Помимо шрифта, методу DrawString необходимо передать кисть для рисования текста, а также координаты точки, в которой этот текст должен быть нарисован. Существуют и другие перегруженные варианты метода DrawString, причем для каждого из них необходимо указать шрифт. В классе Font существует довольно много конструкторов, с помощью которых можно подобрать любой нужный Вам шрифт: public Font(string, float); Первому конструктору нужно передать название шрифта (точнее говоря, название гарнитуры шрифта), а также высоту символов в пунктах (в одном дюйме содержится 72 пункта): public Font(string, float); Выбирая название гарнитуры шрифта, учитывайте, что можно указывать только шрифты TrueType и OpenType. Если указанный Вами шрифт не установлен на компьютере пользователя, ОС Microsoft Windows заменит его другим шрифтом, наиболее подходящим с ее «точки зрения». Лучше всего, если программа позволит пользователю выбирать шрифт для отображения текста из числа установленных в системе шрифтов, тогда с отображением текста будет меньше проблем. Последний из конструкторов позволяет создать шрифт на основе другого шрифта, изменив его стиль FontStyle. Конструкторы, у которых имеется параметр типа byte, позволяют задавать номер набора символов в терминах GDI. И, наконец, последний параметр типа bool позволяет создавать шрифты с вертикальным расположением строк символов. Параметр типа FontStyle задает тип шрифта. Возможные значения констант перечисления FontStyle и их описания мы привели в табл. 10-12. Таблица 10-12. Константы перечисления FontStyle
Единицы измерения размера шрифта Параметр конструктора Font типа GraphicsUnit дает Вам возможность указывать размеры шрифта не только в пунктах, но и в других единицах измерения. В табл. 10-13 мы привели константы перечисления GraphicsUnit с кратким описанием. Таблица 10-13. Константы перечисления GraphicsUnit
Семейство шрифта FontFamily С помощью конструкторов класса Font, принимающих ссылку на объект класса FontFamily, можно выбрать шрифт из группы шрифтов, имеющий похожий дизайн и лишь немного отличающихся в стиле. Вот конструкторы класса FontFamily: public FontFamily(string); Первый конструктор позволяет задать имя семейства, а второй — выбрать шрифт из числа шрифтов, установленных приложением. Приложение FontApp Для демонстрации способов создания шрифтов класса Font с использованием конструкторов различного типа мы создали приложение FontApp. Вот исходный текст обработчика событий Form1_Paint этого приложения: private void Form1_Paint(object sender, Сначала мы создаем шрифт, указывая его имя и размер: Font f1 = new Font("Helvetica", 10); Этим конструктором мы уже пользовались ранее в нашей книге. Следующий конструктор выбирает не какой-либо конкретный шрифт, а любой шрифт семейства Courier: Font f2 = new Font(new FontFamily("Courier"), 10); Такие шрифты являются моноширинными, т.е. ширина всех символов шрифта одинаковая. Моноширинные шрифты обычно используются для оформления программных листингов. При создании шрифта можно задать его стиль, как мы это сделали в следующем примере: Font f3 = new Font("Times New
Roman", 10, Здесь создается жирный перечеркнутый шрифт. Последний конструктор создает для нас шрифт, указывая его высоту не в пунктах, а в миллиметрах: Font f4 = new Font("Helvetica", 10, GraphicsUnit.Millimeter); Результат работы нашей программы Вы можете увидеть на рис. 10-31.
Рис. 10-31. Окно приложения FontApp |