Этот метод просто выполняется на каждой прочитанной строке и использует метод
Graphics.MeasureString
для определения и сохранения значения величины горизонтального пространства экрана, которое требуется строке. Мы сохраняем значение, так как
MeasureString
является весьма интенсивным с вычислительной точки зрения. Так как в нашем примере
CapsEditor
не слишком легко определить высоту и положение каждого элемента, то этот метод почти наверняка будет реализован, чтобы вычислить все эти величины.
Теперь, когда мы знаем размер каждого элемента на экране и можем вычислить приблизительное его положение, определим реальный размер документа. Высота, по существу, равна числу строк, умноженному на высоту
каждой строки. Ширину необходимо определить просмотром всех строк, чтобы выявить самую длинную и взять ширину этой строки. Для высоты и ширины также желательно допустить небольшие поля вокруг выводимого документа, чтобы приложение выглядело более привлекательно. (Нежелательно, чтобы текст прикасался к одному из углов клиентской области.) Вот метод, который вычисляет размер документа:
foreach (TextLineInformation nextWord in documentLines) {
uint tempLineLength = nextWord.Width + 2*margin;
if (tempLineLength > maxLineLength) maxLineLength = tempLineLength;
}
documentSize.Width = (int)maxLineLength;
}
this.AutoScrollMinSize = documentSize;
}
Этот метод сначала проверяет, есть ли данные для вывода. Если данных нет, мы слегка схитрим и зададим жестко кодированный размер документа такой величины, чтобы хватило места для выведения большими красными буквами предупреждения <Empty Document>. В противном случае необходимо воспользоваться методом
MeasureString
для определения реального размера документа.
После этого размер документа сообщается экземпляру класса Form, задавая свойство
Form.AutoScrollMinSize
. Когда это сделано, за сценой происходит кое-что интересное. В процессе задания этого свойства клиентская область становится недействительной и инициируется событие
Paint
в связи с тем, что изменение размера документа означает необходимость добавить или изменить панели прокрутки, а также, что вся клиентская область почти наверняка будет перерисована. Это в полной мере иллюстрирует то, что было сказано ранее об использовании метода
Form.Invalidate
. Если вернуться назад к коду
LoadFile
, то станет понятно, что вызов метода
Invalidate
в этом методе является на самом деле излишним. Клиентская область будет объявлена недействительной в любом случае, когда задается размер документа. Явный вызов метода
Invalidate
в реализации метода
LoadFile
оставлен для иллюстрации. Фактически в этом случае все, что будет делать вызванный метод
Invalidate
, является ненужным запросом повторного события
Paint
. Однако это в свою очередь подтверждает, что
Invalidate
дает Windows возможность оптимизировать производительность. Второе событие Paint не будет фактически инициировано: Windows увидит, что в очереди уже находится событие
Paint
, и сравнит запрошенные недействительные области, чтобы попробовать объединить их. В этом случае оба события Paint будут определять всю клиентскую область, поэтому ничего не нужно делать, и Windows спокойно удалит второй запрос
Paint
. Конечно, это действие займет какое-то процессорное время, но оно будет ничтожным по сравнению с тем, сколько времени потребуется для реального выполнения рисования.
OnPaint
Итак, мы увидели, как
CapsEditor
загружает файл. Теперь пришло время посмотреть, как выполняется рисование:
находится цикл, который перебирает все строки документа, вызывая метод
Graphics.DrawString
для рисования каждой из них. Остальная часть этого кода связана в основном с оптимизацией рисования — обычный материал для определения, что действительно необходимо нарисовать вместо необдуманного приказания экземпляру Graphics перерисовать все.
Мы начинаем с проверки, имеются ли в документе какие-либо данные. Если данных нет, мы выводим краткое сообщение, говорящее об этом вызываем реализацию
OnPaint
из базового класса и выходим. Если имеются данные, то мы начинаем проверять прямоугольник вырезания. Способ, которым это делается, состоит в вызове другого написанного нами метода
WorldYCoordinateToLineIndex
. Мы рассмотрим этот метод позже, но по сути он получает заданную у-позицию относительно верха документа и определяет, какая строка документа будет выводиться в этой точке.
При первом вызове метода
WorldYCoordinateToLineIndex
ему передается значение координаты
е.ClipRectangle.Top - scrollPositionY
. Это верх области вырезания, преобразованный в мировые координаты. Если возвращаемое значение будет -1, мы предположим, что нам нужно начать с начала документа (если верх области вырезания находится наверху граничного поля).
После того, как все это будет сделано, мы практически повторяем тот же процесс для низа прямоугольника вырезания, чтобы определить последнюю строку документа, которая находится внутри области вырезания. Индексы первой и последней строки хранятся соответственно в
minLineInClipRegion
и
maxLineInClipRegion
, поэтому мы можем просто выполнить цикл
for
между этими значениями, чтобы реализовать рисование. Внутри цикла рисования мы должны сделать приблизительно обратное преобразование для преобразования, выполненного методом
WorldYCoordinateToLineIndex
. Задан индекс строки текста и нужно проверить, где она должна быть нарисована. Это вычисление является вполне простым, но мы поместили его в другой метод
LineIndexToWorldCoordinates
, который возвращает требуемые координаты верхнего левого угла элемента. Возвращаемые координаты являются мировыми координатами, и это хорошо, так как мы уже вызвали метод
TranslateTransform
на объекте Graphics, поэтому нам нужно передать ему при запросе вывода элемента мировые координаты, а не координаты страницы.