|
Программирование >> Вывод графики
торые еще с 4 Мбайт видеопамяти. Понятно, что было бы непрактично для Windows управлять пользовательским интерфейсом подобным образом. В тот момент, когда любая часть окна скрывается, скрытые пиксели теряются, потому что Windows освобождает память, которую они занимали. Однако она запоминает, что часть окна была скрыта, и когда обнаруживает, что эта часть окна опять открыта, то запрашивает у приложения, владеющего окном, перерисовку его содержимого. Имеется несколько исключений из этого правила - обычно, когда речь идет о сокрытии очень небольших частей окон на короткое время (хорошим примером может служить выбор элемента главного меню, когда из него выпадает подменю, временно накрывая часть окна). Обычно же можно ожидать, что если часть окна была скрыта, то ваше приложение должно перерисовать его. Именно это является причиной проблемы с нашим примером приложения. Мы поместили код рисования в конструктор Form1, который вызывается только один раз при запуске приложения, и мы не можем вызвать этот конструктор снова, чтобы перерисовать наши фигуры, когда это понадобится позднее. При работе с элементами управления Windows Forms нет необходимости знать что-либо о том, как ими решается эта задача. Дело в том, что стандартные элементы управления достаточно сложны, и они знают, как им следует перерисовывать себя в любой момент, когда Windows попросит их об этом. Это объясняет, почему программируя элементы управления, нам вообще не приходится беспокоиться о реальном процессе из рисования. Если же мы берем на себя ответственность за отображение экрана своего приложения, то надо также позаботиться о том, чтобы приложение корректно отвечало на запросы Windows, касающиеся перерисовки всех частей его окна. В следующем разделе мы внесем необходимые изменения, чтобы сделать это. Рисование контуров с использованием OnPaint() Не беспокойтесь, если предыдущие объяснения создали у вас впечатление, что рисование собственного пользовательского интерфейса выглядит ужасающе сложной задачей. Заставить приложение перерисовать себя, когда это необходимо, на самом деле достаточно просто. Windows уведомляет приложение о том, что требуется некоторая перерисовка, возбуждая событие Paint. Интересно, что класс Form уже имеет реализованный обработчик этого события, поэтому нам не придется добавлять его самостоятельно. Обработчик Form1 события Paint содержится в виртуальном методе OnPaint() , которому передается единственный параметр PaintEventArgs. Это значит, что все, что нам необходимо сделать - это переопределить OnPaint() , чтобы он выполнял требуемое нам рисование. Хотя в данном примере мы переопределим OnPaint() , но с тем же успехом можно было бы достичь того же результата, просто добавив еще один собственный обработчик события Paint (скажем, метод Form1 Paint() ) - точно так же, как это делается с любым событием Windows Forms. Этот второй подход, возможно, более удобен, поскольку новый обработчик события можно добавить в окне свойств Visual Studio 2008, что избавляет от необходимости вводить код вручную. Однако вариант с переопределением OnPaint() обеспечивает дополнительную гибкость, позволяя управлять тем, когда произойдет обработка события базовым классом окна, к тому же такой подход рекомендован в документации. В данном разделе создадим новое Windows-приложение под названием DrwShapes. Как и ранее, установим белый цвет фона, используя окно свойств. Кроме того, изменим текст окна на DrawShapes Sample. После этого добавим следующий фрагмент к сгенерированному коду класса Form1. protected override void OnPaint( PaintEventArgs e ) base.OnPaint(e); Graphics dc = e.Graphics; 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); Обратите внимание, что метод OnPaint() объявлен как protected, поскольку обычно он используется внутри класса, поэтому нет причин любому коду вне класса знать о его существовании. PaintEventArgs - это наследник класса EventArgs, обычно используемого для передачи информации о событиях. PaintEventArgs имеет два дополнительных свойства, более важное из которых - это экземпляр Graphics, уже подготовленный и оптимизированный для рисования нужной области окна. Это значит, что нам не придется в методе OnPaint() вызывать CreateGraphics(), чтобы получить DC - нам его уже передали. Второе дополнительное свойство мы вскоре также рассмотрим; оно содержит более детализированную информацию о том, какая область окна действительно нуждается в перерисовке. В нашей реализации OnPaint() первым делом мы получаем ссылку на объект Graphics из PaintEventArgs, затем рисуем наши фигуры точно так, как делали это раньше. И в конце вызываем метод OnPaint() базового класса. Этот шаг важен. Мы переопределили OnPaint() для выполнения нашего собственного рисования, но, возможно, что Windows может иметь некоторую дополнительную работу, которую необходимо выполнить в процессе рисования - вся эта работа будет выполнена в методе OnPaint() одного из базовых классов .NET. В этом примере, если убрать врезов OnPaint() базового класса, то мы не увидим никакого особого эффекта. Однако не следует поддаваться соблазну отказаться от этого вызова. Это может нарушить нормальную работу Windows, и результаты могут быть непредсказуемсми. Метод OnPaint() также вызывается при первоначальном запуске приложения, когда наше окно впервые отображается на экране. В вязи с этим нет необходимости дублировать код рисования в конструкторе. Рис. 33.2. Результат выполнения примера DrawShapes при перекрытии формы другим окном Запуск этого кода изначально даст тот же результат, что и предыдущий пример, за исключением того, что теперь наше приложение ведет себя правильно при минимизации окна или сокрытии его части. Использование области отсечения Пример приложения DrawShapes из предыдущего раздела иллюстрирует главные принципы, касающиеся рисования в окнах, хотя он и не слишком эффективен. Причина связана с попыткой рисовать все в окне, независимо от того, насколько много в действительности элементов должно быть перерисовано. На рис. 33.2 показан результат выполнения примера DrawShapes с последующим открытием другого окна и перемещением его над формой DrawShapes так, что часть его перекрывается. Пока все хорошо. Однако когда мы сдвинем перекрывшее окно так, что окно DrawShapes снова будет полностью видимо, Windows, как обычно, пошлет нашей форме событие Paint, запрашивая ее перерисовку. Прямоугольник и эллипс находятся в левом верхнем углу клиентской области, а потому оставались видимыми все время. Таким образом, на самом деле не было необходимости их все время перерисовывать вдобавок к перерисовке белого фона окна. Однако Windows не знает об этом, а потому думает, что должна сгенерировать событие Paint, что приводит к вызову нашей реализации OnPaint() . И эта реализация выполняет совершенно излишнюю перерисовку прямоугольника и эллипса. На самом деле в этом случае фигуры не будет перерисованы благодаря контексту устройства. Windows предварительно инициализирует контекст устройства информацией об области, которую в действительности необходимо перерисовать. Во времена GDI область, помеченная для перерисовки, называлась недействительной областью (invalidated region), но в GDI+ эта терминология в значительной мере изменена и теперь эта область называется областью отсечения (clipping region). Контекст устройства распознает эту область. Таким образом, он пресекает любые попытки рисовать вне его, и не передает соответствующие команды рисования видеокарте. Звучит хорошо, но все же здесь остается потенциальная опасность снижения производительности. Невозможно предсказать, сколько обработки должен выполнить контекст устройства, прежде чем он обнаружит то, что рисование происходит за пределами недействительной области. В некоторых случаях этот объем может оказаться довольно большим, поскольку вычисление того, какие пиксели должны быть изменены и в какой цвет, может быть достаточно ресурсоемким (хотя в хороших видеокартах для этого предусмотрено аппаратное ускорение). В результате всего этого запрос к экземпляру Graphics выполнить какое-то рисование вне недействительной области почти наверняка приведет к излишней трате процессорного времени и замедлит приложение. В хорошо спроектированном приложении код должен помогать контексту устройства, выполняя несколько простых проверок, чтобы убедиться, что не будет предпринято излишних попыток вызова методов экземпляра Graphics. В этом разделе мы закодируем новый пример DrawShapesWithClipping, модифицировав предыдущий DisplayShapes. В коде OnPaint() будут выполнены простые проверки, чтобы убедиться, что недействительная область пересекается с областью, в которой нужно рисовать, и только в случае, если это так, рисование будет выполнено. Во-первых, нам нужно получить информацию об области отсечения. Для этого нам пригодится новое свойство ClipRectangle класса PaintEventArgs. Свойство ClipRectangle содержит координаты перерисовываемой области, оформленные в виде экземпляра структуры System.Drawing.Rectangle. Это достаточно простая структура - она содержит четыре свойства, которые нас интересуют: Top, Bottom,
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |