Программирование >>  Вывод графики 

1 ... 12 13 14 [ 15 ] 16 17 18 19


else

documentSize.Height = (int)(nLines*lineHeight) + 2*(int)margin; uint maxLineLength = 0;

foreach (TextLineInformation nextWord in documentLines)

uint tempLineLength = nextWord.Width; if (tempLineLength > maxLineLength) maxLineLength = tempLineLength;

maxLineLength += 2*margin; documentSize.Width = (int)maxLineLength;

AutoScrollMinSize = documentSize;

Сначала этот метод проверяет, есть ли данные, которые нужно отобразить. Если их нет, то мы прибегаем к хитрости и указываем жестко закодированный размер документа, который достаточен для отображения предупреждения <Empty document> (пустой документ) красного цвета. Если вы действительно хотите сделать это корректно, то нужно применить MeasureString(), чтобы определить действительный размер строки предупреждения.

Определив размер документа, мы сообщаем его экземпляру Form, устанавливая его свойство Form.AutoScrollMinSize. Когда мы это делаем, за кулисами происходит нечто интересное. В процессе установки значения этого свойства клиентская область объявляется недействительной и возбуждается событие Paint - по очень уважительной причине, связанной с тем, что изменение размера документа означает необходимость добавления или модификации линеек прокрутки, и вся клиентская область почти наверняка должна быть перерисована. Чем это интересно? Если вернуться к методу LoadFile(), то мы обнаружим, что вызов Invalidate() в этом методе на самом деле избыточен. Клиентская область в любом случае будет объявлена недействительной при установке размера документа. Явный вызов Invalidate() остается в LoadFile() для иллюстрации того, как следует поступать в общем случае. Но фактически в данном случае все повторные вызовы Invalidate() приводят только к избыточным запросам, дублирующим событие Paint. Однако это в свою очередь иллюстрирует то, как Invalidate() дает возможность Windows оптимизировать производительность. Второе событие Paint фактически не может быть возбуждено - Windows обнаружит, что в очереди уже есть событие Paint, и сравнит запрошенную недействительную область, чтобы проверить, нужно ли объединить ее с недействительной областью нового события. В данном случае оба события Paint объявляют недействительной всю клиентскую область окна, поэтому ничего не нужно делать, и Windows молча отбрасывает второй запрос Paint. Конечно, весь этот процесс потребует определенного процессорного времени, но это время совершенно незначительно по сравнению с тем, сколько требуется на перерисовку.

OnPaint()

Теперь, когда мы разобрались с тем, как CapsEditor загружает файл, самое время посмотреть, как будет выполняться его отображение:

protected override void OnPaint(PaintEventArgs e)

base.OnPaint(e); Graphics dc = e.Graphics;

int scrollPositionX = AutoScrollPosition.X; int scrollPositionY = AutoScrollPosition.Y; dc.TranslateTransform(scrollPositionX, scrollPositionY);



if (IdocumentHasData)

dc.DrawString( <Empty document> , emptyDocumentFont, emptyDocumentBrush, new Point(20,20)); base.OnPaint(e); return;

поиск строк, находящихся в области отсечения

int minLineInClipRegion = WorldYCoordinateToLineIndex(e.ClipRectangle.Top -

scrollPositionY); if (minLineInClipRegion == -1)

minLineInClipRegion = 0; int maxLineInClipRegion =

WorldYCoordinateToLineIndex(e.ClipRectangle.Bottom - scrollPositionY);

if (maxLineInClipRegion >= documentLines.Count maxLineInClipRegion == -1)

maxLineInClipRegion = documentLines.Count-1; TextLineInformation nextLine;

for (int i=minLineInClipRegion; i<=maxLineInClipRegion ; i++)

nextLine = (TextLineInformation)documentLines[i]; dc.DrawString(nextLine.Text, mainFont, mainBrush, LineIndexToWorldCoordinates(i));

В сердце переопределенного метода OnPaint() находится цикл, проходящий по все строкам документа, вызывая Graphics.DrawString() для рисования каждой из них. Весь прочий код в основном предназначен для оптимизации рисования - обычное хозяйство, занимающееся поиском того, что именно нуждается в перерисовке - чтобы не заставлять Graphics тупо перерисовывать все подряд.

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

При первом вызове метода WorldYCoordinateToLineIndex() ему передается значение координате! (e.ClipRectangle.Top - scrollPositionY). Это - вершина области отсечения, преобразованная к мировым координатам. Если возвращаемое значение будет равно -1, то мы в безопасности и предполагаем, что нужно начинать с начала документа (это тот случай, когда области отсечения находится внутри верхнего поля отступа).

Один раз сделав это, мы по существу повторяем этот процесс до нижней границы прямоугольника отсечения, пока не найдем последнюю строку, находящуюся внутри него. Индексы первой и последней строк сохраняются, соответственно, в minLineInClipRegion и maxLineInClipRegion, так что остается только запустить цикл for между этими значениями, чтобы выполнить все рисование. Внутри цикла рисования нужно выполнить приблизительно преобразование, обратное тому, что реализовано в WorldYCoordinateToLineIndex(). Имеется индекс строки текста, и необходимо определить, где она должна быть отображена. Это вычисляется достаточно просто, но оно помещено в оболочку другого метода - LineIndexToWorldCoordinates(), - который возвращает требуемые координаты левого верхнего угла элемента. Возвращенные координаты относятся к системе мировых координат, но это нормально, потому что мы уже вызвали TranslateTransform() для объекта Graphics, поэтому ему нужно передать именно мировые координаты, а не страничные, когда запрашивается отображение элементов.



Преобразования координат

В этом разделе рассматривается реализация вспомогательных методов примера CapsEditor, служащих для преобразования координат. Речь идет о методах Worl dYCoordinateToLineIndex() и LineIndexToWorldCoordinates(), которые упоминались в предыдущем разделе, наряду с парой других.

Первый метод, LineIndexToWorldCoordinates(), принимает индекс строки и вычисляет мировые координаты левого верхнего угла этой строки, используя известное значение отступа и высоту строки:

private Point LineIndexToWorldCoordinates(int index)

Point TopLeftCorner = new Point(

(int)margin, (int)(lineHeight*index + margin)); return TopLeftCorner;

Мы также используем метод, который выполняет примерно обратную операцию, в OnPaint(). WorldYCoordinateToLineIndex() вычисляет индекс строки, но принимает во внимание только вертикальную мировую координату. Это потому, что она используется для определения индекса, соответствующего верху и низу области отсечения:

private int WorldYCoordinateToLineIndex(int y)

if (y < margin) {

return -1;

return (int)((y-margin)/lineHeight);

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

В отличие от WorldYCoordinateToLineIndex(), этот метод принимает обе координаты - x и y. Он возвращает -1 , если по указанным координатам не обнаруживает строки:

private int WorldCoordinatesToLineIndex(Point position)

if (IdocumentHasData) {

return -1;

if (position.Y < margin position.X < margin) {

return -1;

int index = (int)(position.Y-margin)/(int)this.lineHeight;

если позиция за пределами документа

if (index >= documentLines.Count)

return -1;

проверить, что позиция по горизонтали находится в пределах строки TextLineInformation theLine =

(TextLineInformation)documentLines[index];



1 ... 12 13 14 [ 15 ] 16 17 18 19

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика