Лекция 10 Линии, кривые, заливка областей
• Какие преимущества дает наследование от класса Form в сравнении с созданием экземпляра этого класса? Хотя большинство методов и свойств Form определены как public, некоторые важные свойства определены как protected. • Эти защищенные методы и свойства доступны только наследникам Form. Один из защищенных методов, наследуемых Form от Control. — On. Paint. • Однако он вам нужен не для того, чтобы его вызывать, но чтобы переопределить — тогда вам не потребуется подключать обработчик события Paint. • У метода On. Paint один параметр — объект типа Paint. Event. Args. Можно из этого параметра получить объект Graphics точно так же, как и в обработчике события Paint.
using System; using System, Drawing; using System. Windows. Forms; class Hello. World: Form { public static void Main() { Application. Run(new Hello. World()); } public Hello. World() { Text = "Hello World"; Back. Color = Color. White; }
protected override void On. Paint(Paint. Event. Args pea) { Graphics grfx = pea. Graphics; grfx. Draw. String("Hello, Windows Forms!", Font, Brushes. Black, 0, 0); } } • Обратите внимание, что в On. Paint ничего не стоит перед Font. Методу On. Paint не нужен параметр obj. Sender, так как форма, для которой вызывается On. Paint, всегда определяется как this.
• При создании экземпляра Control или любого класса, происходящего от Control, например Form, можно установить обработчик события Paint. • Для этого сначала нужно определить статический метод с такими же типами параметров и возвращаемого значения, что и у делегата Paint. Event. Handler. • static void My. Paint. Handler(object obj. Sender, Paint. Event. Args pea) • { • // Код, выполняющий прорисовку • }
• Затем нужно установить обработчик этого события для конкретного объекта, например для form, при помощи кода: form. Paint += new Paint. Event. Handler(My. Paint. Handler); • Однако в классе, происходящем от Control, не нужно (хотя и можно) устанавливать обработчик события Paint. Можно просто переопределить метод On. Paint: protected override void On. Paint(Paint. Event. Args pea) { // Код, выполняющий прорисовку
• Цвета в Windows Forms основаны на модели ARGB (alpha-red-green-blue). Цвета определяются однобайтовыми значениями красного, зеленого и синего. Альфа-канал определяет прозрачность цвета. Значения alpha лежат в диапазоне от 0 для прозрачного и до 0 x. FF для непрозрачного. • Структура Color имеет один конструктор по умолчанию, который можно использовать так: • Color color = new Color(); • Однако он создает пустой цвет (прозрачный черный) и невозможно изменить свойства этого цвета. Вместо этого вы будете получать объекты типа Color, используя статические методы или свойства, определенные в Color для этих целей.
• Для создания цвета, основанного на красном, зеленом, синем и альфа-канале, можно задействовать показанные ниже статические методы Color. From. Argb, каждый из которых возвращает объект Color. • • • Статические методы Color. From. Argb(int r, int g, int b) Color. From. Argb(int a, Color color) Color. From. Argb(int argb. Packed)
using System; using System. Drawing; using System. Windows. Forms; class Random. Clear: Form { public static void Main() { Application. Run(new Random. Clear()); } public Random. Clear() { Text = "Random Clear“; }
protected override void On. Paint(Paint. Event. Args pea) { Graphics grfx = pea. Graphics; rand = new Random(); grfx. Clear(Color, From. Argb(rand. Next(256), rand. Next(256))); } }
• Если запустить эту программу и поэкспериментировать, изменяя размер формы, то при увеличении размера формы, неокрашенные полосы в правой и нижней части формы будут изображаться другим цветом. Каждый новый цвет отображается новым вызовом On. Paint. • Казалось бы, метод Clear должен очищать всю клиентскую область, но вообще -то его действие ограничивается областью, ставшей недействительной. (если уменьшить размер формы, цвета не изменятся, потому что нет части клиентской область, которая стала недействительной. ) • Такое поведение не всегда желательно. Может оказаться, что программа должна делать недействительной всю клиентскую область при изменении размера клиентской области.
• Для этого нужно переопределить метод On. Resize и поместить в него вызов Invalidate(). • Другое решение — присвоить значение true свойству Resize. Redraw в конструкторе формы: • Resize. Redraw = true; • Если Resize. Redraw равно true, всякий раз, когда размер клиентской области меняется, клиентская область становится недействительной.
• Функции рисования, являются методами класса Graphics и начинаются с префикса Draw или Fill. • Методы Draw рисуют прямые и кривые; мегоды Fill служат для заливки областей (границы которых, естественно, определяются прямыми и кривыми). • Первым аргументом всех рассматриваемых методов Draw является объект Реn, а первым аргументом методов Fill — объект Brush.
• Для рисования нужен объект типа Graphics. Но конструктор Graphics не является открытым, поэтому просто создать объект Graphics, как это показано ниже, не получится: • Graphics grfx = new Graphics(); • // He будет работать! • Кроме того, класс Graphics является изолированным (sealed), т. е. вы не можете создавать собственные классы, производные от Graphics: • class My. Graphics: Graphics // He будет работать!
Cпособы получения объектов Graphics • При переопределении метода On. Paint или установке обработчика события Paint в любом классе, производном от Control (например, Form), вы получаете объект Graphics как свойство класса Paint. Event. Args.
• Для рисования на элементе управления или форме не только во время исполнения метода On. Paint или обработки события Paint, но и в другие моменты времени можно вызывать метод Create. Graphics этого элемента управления. • Классы иногда вызывают метод Create. Graphics в своих конструкторах, чтобы получить нужные данные или выполнить инициализацию. Для элемента управления или формы совершенно обычно рисовать что-то во время событий клавиатуры, мыши или таймера. • Важно, чтобы программа использовала объект Graphics только во время получающего события (т. е. объект Graphics не должен храниться в поле класса). По завершении использования объекта Graphics программа также должна вызвать его метод Dispose.
• У некоторых элементов управления (меню, полей со списком, комбинированных полей и строк состояния) есть свойство Owner. Draw, позволяющее программе динамически рисовать на этом элементе управления. • События Measure. Item и Drawltem предоставляют объекты типа Measureltem. Event. Args и Drawltem. Event. Args, обладающие объектами Graphics, которые могут быть использованы обработчиком этого события; • Чтобы рисовать по битовой карте, надо получить специальный объект Graphics, вызвав статический метод Graphicsfromlmage
• Если нужно получить информацию из объекта Graphics, связанного с принтером, не распечатывая никаких данных, можно использовать метод Create. Measurement. Graphics класса Printer. Settings • При взаимодействии с кодом Win 32 получить объект Graphics позволяют статические методы Graphicsfrom. Hwnd к Graphics from. Hdc.
• Чтобы нарисовать любую линию, надо задать объект Реn (перо). • Тип инструмента определяет как минимум цвет и ширину линии. Эти и другие атрибуты линии находятся в ведении класса Реn. • • Создать перо некоторого цвета можно так: • Pen pen = new Pen(color); • где color — объект типа Color. • Кроме того, есть конструкторы Реп, включающие толщину пера как аргумент: • Pen(Color color); • Pen(Color color, float f. Width);
Прямые линии • Для рисования одиночных прямых служит метод Draw. Line класса Graphics. Есть четыре перегруженных версии Draw. Line, но все они требуют один и тот же набор аргументов: координаты начальной и конечной точек линии, а также перо, которым она рисуется: void Draw. Line(Pen pen, int x 1, int y 1, int x 2, int y 2) void Draw. Line(Pen pen, float x 1, float y 1, float x 2, float y 2) void Draw. Line(Pen pen, Point point 1, Point point 2) void Draw. Line(Pen pen, Point. F point 1, Point. F point 2)
• Можно указывать координаты как четырьмя значениями типа int или float, так и парой структур Point или Point. F. • Метод Draw. Line чертит линию от первой точки до второй, включая вторую точку в состав линии (этим он несколько отличается от Win 32 GDI, который проводит линию между двумя точками, не включая конечную точку в состав линии). • Пример: • grfx. Draw. Line(pen, 0, 0, 5, 5); • Эта команда закрашивает черным цветом 6 пикселов с координатами (0, 0), (1, 1), (2, 2), (3, 3), (4, 4) и (5, 5).
Порядок указания начальной и конечной точек линии не имеет значения, поэтому вызов: • grfx. Draw. Line(pen, 5, 5, 0, 0); даст тот же результат. Вызов: • grfx. Draw. Line(pen, 2, 2, 3, 3); рисует 2 пиксела с координатами (2, 2) и (3, 3) Вызов: • grfx. Draw. Line(pen, 3, 3); не рисует ничего.
• Определить ширину и высоту клиентской области позволяет свойство Client. Size формы. • Число пикселов по горизонтали определяется значением Client. Size. Width; Пикселы могут нумероваться от 0 до Client. Size. Width - 1. • Аналогично пикселы клиентской области по вертикали нумеруются от 0 до Client. Size. Height - 1.
using System; using System. Drawing; using System. Windows. Forms; class XMarks. The. Spot : Form { public static void Main() { Application. Run(new XMarks. The. Spot()); } public XMarks. The. Spot() { Text = "X Marks The Spot"; Back. Color = System. Colors. Window; Fore. Color = System. Colors. Window. Text; . Resize. Redraw= true; }
protected override void On. Paint(Paint. Event. Args pea) { Graphics grfx = pea. Graphics; Pen pen = new Pen( Fore. Color); grfx. Draw. Line(pen, 0, 0, Client. Size. Width - 1, Client. Size. Height - 1); grfx. Draw. Line(pen, 0, Client. Slze. Height - 1, Client. Size. Width - 1, 0); } }
• Первый вызов Draw. Line рисует линию от верхнего левого до нижнего правого пиксела клиентской области. • Второй вызов Draw. Line ведет линию из нижнего левого пиксела с координатами (0, Client. Size. Height - 1) и доводит ее до верхнего правого пиксела с координатами (Client. Size. Width - 1, 0).
• В некоторых средах программирования графики используется текущая позиция, что можно считать странным: ведь чтобы нарисовать одну линию, приходится вызывать дне функции. Однако текущая позиция серьезно помогает при рисовании нескольких соединенных друг с другом линий, позволяя задавать при вызове каждой функции координаты лишь одной точки. • GDI+ не столь экономичен. Например, чтобы нарисовать прямоугольник по периметру клиентской области программы, приходится четырежды вызывать Draw. Line.
• • grfx. Drawl_ine(pen, 0, 0, сх - 1, 0); grfx. Draw. Line(per, сх - 1, 0, сх - 1, су - 1); grfx. Draw. Line(pen, сх - 1, су - 1, 0, су - 1); grfx. Draw. Line(pen, 0, су - 1, 0, 0); • Заметьте, конечная точка каждого предыдущего вызова должна повториться как начальная точка следующего. • В силу этих и некоторых других причин (которые мы обсудим чуть позже) в класс Graphics включен метод рисования нескольких соединенных линий, в совокупности обычно называемых ломаной. Есть две версии Draw. Lines (обратите внимание на множественное число в названии метода):
• void Draw. Lines(Pen pen, Point[] apt) • void Draw. Lines(Pen pen, Point. F[] aptf) • Эти методы требуют массив целочисленных координат (Point) или координат с плавающей точкой (Point. F). • Следующий пример демонстрирует использование метода Draw. Lines для очерчивания клиентской области:
using System; using System. Drawing; using System. Windows. Forms; class Boxing. The. Client: Form { public new static void Main(); { Application, Run(new Boxing. The. Client()); } public Boxing. The. Client() { Text = "Boxing the Client"; }
protected override void On. Paint(Paint. Event. Args pea) { Do. Page(pea. Graphics, Fore. Color, Client. Size. Width, Client. Size. Height) ; } protected void Do. Page (Graphics grfx, Color clr, int cx, int cy) { Point[] apt = { new Point(0, 0), new Point(cx - 1, cy - 1), new Point(0, 0) }; grfx. Draw. Lines(new Pen( clr), apt); } }
• Массив структур Point может быть определен прямо в методе Draw. Lines, grfx. Draw. Lines(new Pen(clr), new Point[] { new Point(0, 0), new Point(cx - 1, cy - 1), new Point(0, 0) });
• Особенность метода Draw. Lines — большая производительность. • Метод Draw. Lines создан не для рисования прямых, его настоящее назначение — кривые. • Особенность Draw. Lines в том, что он способен нарисовать множество очень коротких отдельных линий, позволяя изобразить любую кривую, которая может быть задана математически, Можно легко передавать сотни и даже тысячи структур Point или Point. F при единственном вызове метода Draw. Lines — он создан именно для этого. • Даже если передать Draw. Lines миллион структур Point или Point. F, их визуализация займет всего пару секунд. • Сколько потребуется точек, чтобы нарисовать кривую? Максимальная гладкость кривой достигается, если число ее точек по крайней мере равно числу пикселов.
protected void Do. Page( Graphics grfx, Color clr, int cx, int cy) { Point. F aptf = new Point. F[cx]; for (int i = 0; i < cx; i++) { aptf[i]. X = i; aptf[i]. Y = cy / 2 * (1 -(float)Math. Sin(i * 2 * Math. PI / (cx 1))); } grfx. Draw. Lines(new Pen(clr), aptf); }
Рисование эллипса protected void Do. Page( Graphics grfx, Color clr, int cx, int cy) { int i. Num =2 * (cx+cy); Point. F aptf = new Point. F[i. Num]; for (int i = 0; i < i. Num; 1++) { double d. Ang = i * 2 * Math. PI / (i. Num - 1); aptf[i]. X = (cx - 1) / 2 f * (1 + (float)Math. Cos(d. Ang)); aptf[i}, Y = (cy - 1) / 2 f * (1 + (float)Math. Sin(d. Ang)); } grfx. Draw. Lines(new Pen(clr), aptf); }
• Поскольку центр эллипса находится на пересечении прямых, делящих экран надвое по ширине и высоте, а ширина и высота эллипса равны ширине и высоте области экрана, то число точек массива определяется как число точек, достаточное для изображения прямоугольника, периметр которого равен периметру экрана,
protected void Do. Page( Graphics grfx, Color clr, int cx, int cy) { const int i. Num. Revs = 20; int i. Num. Points = i. Num. Revs * 2 * (cx + cy); Point. F[] aptf = new Point. F[i. Num. Points]; float f. Angle, f. Scale; for (int i = 0; i < i. Num. Points; i++) { f. Angle = (float) (i * 2 * Math. PI /(i. Num. Points / i. Num. Revs)); f. Scale = 1 - (float)i / i. Num. Points; aptf[i]. X = (float)(cx / 2 * (1 + f. Scale * Math. Cos(f. Angle))); aptf[i]. Y = (float )(cy / 2 * (1 + f. Scale * Matn. Sin(f. Angle))); } grfx. Draw. Lines(new Pen(clr), aptf); }