□ Наконец, код для выполнения рисования будет, вероятно, одной из наиболее сложных частей кода приложения, особенно если у приложения достаточно развитый интерфейс пользователя. Те, кому придется модифицировать этот код через пару лет, будут благодарить разработчика за сохранение кода рисования в одном месте и за максимально возможную простоту — чего добиться гораздо легче, если код не разбросан по разными частям программы.
Из этого можно сделать вывод, что хорошая практика состоит в поддержании всего кода рисования в процедуре
OnPaint
или в других методах, вызываемых из этого метода. Не создавайте в коде множества других мест, которые вызывают методы для реализации странных фрагментов рисования, все-таки аспекты
создания программы должны быть сбалансированы относительно различных рассмотрений. Если, предположим, требуется заменить только один символ или фигуру на экране или добавить акцент к букве и совершенно точно известно, что это не повлияет ни на какие другие изображения, то можно не пользоваться методом
Invalidate
, а написать просто отдельную процедуру рисования.
В очень сложном приложении можно даже написать целый класс, который отвечает за рисование на экране. Несколько лет назад, когда MFC были стандартной технологией для приложений с интенсивным использованием GDI, MFC следовали этой модели с помощью класса C++ с именем
С<Имя_приложения>View
, который отвечал за это. Однако даже в таком случае этот класс имел функцию-член
OnDraw
, которая была создана, чтобы быть точкой входа для большинства запросов рисования.
Вычисление размеров объектов и размера документа
Мы возвращаемся теперь к примеру
CapsEditor
и разбираем методы
CalculateLineWidths
и
CalculateDocumentSize
, которые вызываются из метода
LoadFile
:
private void CalculateLineWidths {
Graphics dc = this.CreateGraphics;
foreach (TextLineInformation nextLine in documentLines) {
Этот метод просто выполняется на каждой прочитанной строке и использует метод
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
загружает файл. Теперь пришло время посмотреть, как выполняется рисование: