C# для профессионалов. Том II - Симон Робинсон
Шрифт:
Интервал:
Закладка:
Теперь посмотрим на более ранний снимок экрана — сразу после прокрутки изображения вниз. Мы замечаем, что здесь верхние две трети окна выглядят нормально. Это связано с тем, что они были нарисованы, когда приложение запускалось в первый раз. При прокрутке окна Windows не просит приложение перерисовать то, что уже было на экране. Система Windows достаточно разумна, чтобы самостоятельно определить, какие биты из изображаемых в данный момент на экране могут плавно переместиться, чтобы соответствовать текущему положению панели прокрутки. Это значительно более эффективный процесс, так как он может использовать некоторые аппаратные средства ускорения. Часть этого изображения экрана, которая выглядит неправильно, составляет нижнюю треть окна. Эта часть окна не была нарисована, когда приложение появилось на экране впервые, так как до начала прокрутки она находилась вне клиентской области. Значит, система Windows просит приложение ScrollShapes нарисовать эту область. Она инициирует событие Paint, передавая именно эту область в качестве прямоугольника вырезания. И именно это сделал метод OnPaint(). Такое довольно странное изображение экрана возникает в приложении, которое сделало в точности то, что ему было приказано.
Один из способов решения проблемы состоит в следующем. Мы в данный момент задаем координаты относительно верхнего левого угла начала "документа", а нам необходимо преобразовать их, чтобы задать относительно верхнего левого угла клиентской области. Рисунок должен это четко показать. На нем тонкие прямоугольники отмечают границы области экрана и всего документа (чтобы сделать рисунок понятнее, документ на самом деле расширен вниз и вправо за границы экрана, но это не изменяет рассуждения. Мы также предполагаем небольшую горизонтальную и вертикальную прокрутки). Толстые линии отмечают прямоугольник и эллипс, которые мы пытаемся нарисовать. Некоторая произвольно нарисованная точка Р будет служить в качестве примера. При вызове методов рисования мы предоставляем экземпляр объекта Graphics с вектором из точки В в точку Р, этот вектор представляет экземпляр Point. На самом деле нам нужен вектор из точки А в точку Р.
Проблема в том, что неизвестно, каким будет вектор из А в Р. Мы знаем, какой будет вектор из В в Р, это просто координаты Р относительно верхнего левого угла документа, позиция, где мы хотим нарисовать в документе точку Р. Мы знаем также, какой будет вектор из В в А — это величина, на которую была выполнена прокрутка; она хранится в свойстве класса Form с именем AutoScrollPosition. Однако мы не знаем вектор, направленный из А в Р. Чтобы найти искомый вектор, надо вычесть два вектора. Например, чтобы попасть из В в Р надо переместиться на 150 пикселей вправо и на 200 пикселей вниз, а чтобы попасть из B в А, необходимо переместиться на 10 пикселей вправо и на 57 пикселей вниз. Это означает, что для тою чтобы попасть из А в Р. необходимо переместиться на 140 (= 150 - 10) пикселей вправо и на 143 (= 200 - 57) пикселя вниз:
Однако все выполняется проще. Весь процесс был расписан подробно, чтобы показать, что происходит на самом деле, но класс Graphics на самом деле реализует метод, делающий эти вычисления. Он называется TranslateTransform. Ему передаются в качестве параметров горизонтальная и вертикальная координаты, которые указывают, где находится верхний левый угол клиентской области относительно верхнего левого угла документа (наше свойство AutoScrollPosition, которое определяет на рисунке вектор от В к А). Затем устройство Graphics с этого момента будет рассчитывать все координаты, принимая во внимание, где находится клиентская область относительно документа.
После всех этих объяснений осталось только добавить следующую строку кода в код рисования
dc.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
Фактически в нашем примере это происходит немного сложнее, так как мы по отдельности контролируем область вырезания, проверяя, нужно ли делать какое-либо рисование. Мы должны настроить эту проверку с учетом положения прокрутки, после чего весь код рисования для этого примера (загружаемый с web-сайта Wrox Press как ScrollShapes) выглядит таким образом:
protected override void OnPaint(PaintEventArgs e) {
Graphics dc = e.Graphics;
Size ScrollOffset = new Size(this.AutoScrollPosition);
if (e.ClipRectangle.Top + ScrollOffset.Width < 350 || e.ClipRectangle.Left + ScrollOffset.Height < 250) {
Rectangle RectangleArea =
new Rectangle(RectangleTopLeft + ScrollOffset, RectangleSize);
Rectangle EllipseArea =
new Rectangle(EllipseTopLeft + ScrollOffset, EllipseSize);
dc.DrawRectangle(BluePen, RectangleArea);
dc.DrawEllipse(RedPen, EllipseArea);
}
base.OnPaint();
}
Теперь код прокрутки работает прекрасно, мы можем, наконец, получить правильно прокрученный снимок экрана.
Координаты мировые, страницы и устройства
Различие в измерениях позиции относительно верхнего левого угла документа и относительно верхнего левого угла экрана является настолько значительным, что GDI+ имеет для них специальные названия.
□ Мировые координаты являются позицией точки, измеренной в пикселях от верхнего левого угла документа. Название отражает тот факт, что весь документ может рассматриваться как "мир" с точки зрения программы.
□ Координаты страницы являются позицией точки, измеренной в пикселях от верхнего левого угла клиентской области. Название идет от представления выводимой области как "страницы" выводимых данных.
Разработчики, знакомые с GDI, заметят, что мировые координаты соответствуют логическим координатам GDI. Координаты страницы соответствуют координатам устройства GDI. Эти разработчики должны также заметить, что способ кодирования преобразования между логическими координатами и координатами устройства в GDI+ изменился. В GDI преобразование осуществлялось через контекст устройства с помощью функций API Windows LPtoDP() и DPtoLP(). В GDI+ информацию, необходимую для выполнения преобразования, поддерживает объект Form.
GDI+ определяет также и третьи координаты, которые теперь называются координатами устройства. Координаты устройства аналогичны координатам страницы, за исключением того, что в качестве единиц измерения используются не пиксели, а другие единицы, которые пользователь может определить с помощью свойства Graphics.PageUnit. Возможные единицы измерения, помимо используемых по умолчанию пикселей, включают дюймы и миллиметры. Хотя свойство PageUnit в этой главе не будет использоваться, оно может быть полезно как способ обойти проблему различной плотности пикселей на устройствах. Например, 100 пикселей на большинстве мониторов будут занимать около дюйма. Однако лазерные принтеры могут иметь до тысяч dpi (точек на дюйм), что означает, что фигура в 100 пикселей шириной будут выглядеть значительно меньше при печати на таком лазерном принтере. Задавая единицы измерения, например, дюймы, и определяя, что фигура должна быть шириной в 1 дюйм, можно гарантировать, что фигура будет одного размера на различных устройствах.
Цвета
В этом разделе мы рассмотрим способы, с помощью которых можно определить цвет для рисования.
Цвета в GDI+ представлены экземплярами структуры System.Drawing.Color. Обычно после создания экземпляра такой структуры с ним почти ничего нельзя делать, только передавать в какой-либо вызываемый метод, требующий Color. Мы встречали эту структуру раньше, когда задавали фоновый цвет клиентской области окна в примерах. Свойство Form.BackColor в действительности возвращает экземпляр Color. Рассмотрим эту структуру более подробно. В частности, проверим несколько различных способов создания Color.
Значения красный-зеленый-синий (RGB)
Общее число цветов, которое можно изобразить на мониторе, огромно — более 16 млн. Точнее, оно равно 2 в 24-й степени, что составляет 16777216. Требуется некоторый способ индексирования этих цветов, чтобы можно было указать, какой цвет мы хотим использовать для данного пикселя.
Наиболее распространенный способ индексирования цветов состоит в разделении их на компоненты красного, зеленого и синего цветов. Эта идея основывается на принципе, что любой цвет, различаемый человеческим глазом, можно создать из некоторого количества красного, зеленого и синего. Эти цвета называются компонентами. На практике оказывается, что если разделить величину каждого компонента на 256 возможных интенсивностей, то это дает достаточно тонкую градацию, чтобы можно было вывести изображения, которые воспринимаются человеческим глазом как имеющие фотографическое качество. Поэтому мы определяем цвета, задавая значения этих компонентов на шкале от 0 до 255, где 0 означает, что компонент отсутствует, а 255 означает, что он имеет максимальную интенсивность.