C# для профессионалов. Том II - Симон Робинсон
Шрифт:
Интервал:
Закладка:
Вследствие этого запрос экземпляра Graphics выполнит некоторое рисование вне недействительной области, что почти наверняка будет бесполезным использованием процессорного времени и замедлит работу приложения. В хорошо спроектированном приложении код активно помогает контексту устройства, выполняя несколько простых проверок, чтобы убедиться, что обычная работа по рисованию действительно нужна, прежде чем вызывать подходящие методы экземпляра Graphics. В этом разделе мы создадим новый пример DrawShapesWithClipping, изменяя для этого пример DisplayShapes. В коде OnPaint() будет сделана простая проверка достоверности, что недействительная область пересекла область рисования, и только в этом случае вызовутся методы рисования.
Сначала необходимо получить данные области вырезания. Для этого используется дополнительное свойство PaintEventArgs. Свойство, называемое ClipRectangle, содержит координаты предназначенной для перерисовывания области, помещенные в экземпляр структуры System.Drawing.Rectangle. Rectangle является достаточно простой структурой, она содержит 4 представляющих интерес свойства: Top, Bottom, Left и Right. Они соответственно содержат вертикальные координаты верха и низа прямоугольника и горизонтальные координаты левого и правого краев.
Затем надо решить, какой тест будет использоваться для рисования. Здесь будет использован простой тест. Отметим, что прямоугольник и эллипс полностью содержатся внутри прямоугольника, который простирается в клиентской области от точки (0, 0) до точки (80, 130), в действительности до точки (82, 132), так как мы знаем, что линии могут отклоняться примерно на пиксель вне этой области. Поэтому будем проверять, что верхний левый угол области вырезания находится внутри этого прямоугольника. Если это так, то выполняется рисование. Если нет, то ничего не делается. Код выглядит следующим образом:
protected override void OnPaint(PaintEventArgs e) {
Graphics dc = e.Graphics;
if (e.ClipRectangle.Tор < 132 && e.ClipRectangle.Left < 82) {
Pen BluePen = new Pen(Color. Blue, 3);
dc.DrawRectangle(BluePen, 0, 0, 50, 50);
Pen RedPen = new Pen(Color.Red, 2);
dc.DrawEllipse(RedPen, 0, 50, 80, 60);
}
base.OnPaint(e);
}
Заметим, что изображение получится точно таким же, как и раньше, но производительность повысится благодаря раннему выявлению некоторых случаев, когда ничего не должно рисоваться. Отметим также, что мы выбрали достаточно примитивный тест необходимости рисования, более точный тест мог бы проверять по отдельности, нужно ли рисовать прямоугольник или эллипс, или оба объекта. Здесь существует некоторое балансирование. Можно сделать проверку в OnPaint() более сложной, в этом случае повысится производительность, но код OnPaint() при этом усложнится и потребует больше работы для своего создания. Однако почти всегда стоит провести некоторую проверку просто потому, что это помогает понять общую картину (например, в нашем примере мы узнали дополнительно, что рисунок никогда не выходит за пределы прямоугольника (0, 0) на (82, 132)). Экземпляр Graphics не имеет этого знания, он слепо следует командам рисования. Такое дополнительное знание означает, что имеются более полезные или эффективные проверки, чем те. что мог бы делать экземпляр объекта Graphics.
Измерение координат и областей
В последнем примере мы встретили базовую структуру Rectangle, которая используется для представления координат прямоугольника. GDI+ в действительности использует несколько аналогичных структур для представления координат или областей. Мы рассмотрим основные структуры, определенные в пространстве имен System.Drawing:
Структура Основные открытые свойства struct Point X, Y struct PointF X, Y struct Size Width, Height struct SizeF Width, Height struct Rectangle Left, Right, Top, Bottom, Width, Height, X, Y, Location, Size struct RectangleF Left, Right, Top, Bottom, Width, Height, X, Y, Location, SizeОтметим, что многие эти объекты имеют ряд других свойств, методов или перезагруженных операторов, не перечисленных здесь. Рассмотрим только самые важные.
Point и PointF
Рассмотрим сначала Point (точка) Эта структура концептуально является простейшей и математически полностью эквивалентна двумерному вектору. Она содержит два открытых целых свойства, которые представляют горизонтальное и вертикальное смещение от определенного места (возможно, на экране). Посмотрите на рисунок:
Чтобы перейти из точки А в точку В, необходимо сместиться на 20 единиц вправо и на 10 единиц вниз, помеченных как X и Y на рисунке, так как это обычное обозначение. Можно было бы создать структуру Point, которая представляет это, следующим образом:
Point АВ = new Point(20, 10);
Console.WriteLine("Moved {0} across, {1} down", AB.X, AB.Y);
X и Y являются свойствами чтения-записи, а значит, можно также задать значения в Point следующим образом:
Point АВ = new Point();
AB.X = 20;
АВ.Y = 10;
Console.WriteLine("Moved (0) across, (1) down", AB.X, AB.Y);
Отметим, что хотя обычно горизонтальные и вертикальные координаты обозначаются как координаты х и у (буквы нижнего регистра), соответствующие свойства Point обозначаются X и Y (буквами верхнего регистра), так как обычное соглашение в C# для открытых свойств требует, чтобы их имена начинались с букв верхнего регистра.
PointF по сути идентична Point, за исключением того, что X и Y имеют тип float вместо int. PointF используется, когда координаты не обязательно являются целыми значениями. Для этих структур определено преобразование типов, поэтому можно неявно преобразовывать из Point в PointF и явно из PointF в Point (последнее преобразование явное в связи с риском ошибок округления):
PointF ABFloat = new PointF(20.5F, 10.9F);
Point AB = (Point)ABFloat;
PointF ABFloat2 = AB;
Одно последнее замечание о координатах. В нашем обсуждении Point и PointF сознательно присутствует неопределенность в отношении единиц измерения. Можно говорить о 20 пикселях вправо и 10 пикселях вниз или о 20 дюймах, или 20 милях. Интерпретация координат полностью принадлежит разработчику.
По умолчанию GDI+ будет представлять единицы измерения как пиксели на экране (или принтере, в зависимости от графического устройства), именно таким образом методы объекта Graphics будут представлять любые координаты, которые передаются им в качестве параметров. Например, точка Point(20, 10) представляет 20 пикселей вправо по экрану и 10 пикселей вниз. Обычно эти пиксели измеряются от верхнего левого угла клиентской области окна, как было до сих пор в рассмотренных примерах. Но это не всегда так, в некоторых ситуациях может потребоваться нарисовать относительно верхнего левого угла всего окна (включая границу) или даже относительно верхнего левого угла экрана. В большинстве случаев, однако, если документация не говорит обратное, можно предполагать, что речь идет о пикселях относительно верхнего левого угла клиентской области.
Мы вернемся к рассмотрению этого вопроса после изучения прокрутки экрана, когда речь пойдет об использовании трех различных координатных систем: мировых, страницы и устройства.
Size и SizeF
Подобно Point и PointF размеры выступают в двух вариантах. Структура Size предназначена для работы с целыми значениями, SizeF — для значений с плавающей точкой. В остальном Size и SizeF идентичны. Мы сосредоточимся здесь на структуре Size.
Во многом Size аналогична структуре Point. Она имеет два целых свойства, которые представляют горизонтальное и вертикальное расстояния, основное различие состоит в том, что вместо X и Y эти свойства называются Width и Height. Можно представить предыдущую диаграмму с помощью кода:
Size АВ = new Size(20, 10);
Console.WriteLine("Moved {0} across, {1} down", AB.Width, AB.Height);
Строго говоря структура Size математически представляет то же, что и Point, но концептуально она предназначена для использования немного другим образом. Point применяется, если говорится о местоположении объекта, a Size — когда речь идет о размере чего-то.
В качестве примера рассмотрим нарисованный ранее прямоугольник с координатой вверху слева (0, 0) и размером (50, 50):