Российский Государственный Технологический Университет им. К.Э. Циолковского
Опубликован: 02.03.2007 | Доступ: свободный | Студентов: 5677 / 802 | Оценка: 3.96 / 3.45 | Длительность: 27:04:00
ISBN: 978-5-9556-0086-4
Лекция 17:

GDI+

Битовая карта как поверхность для рисования

Приводимое ниже приложение демонстрирует технику рисования на невидимых виртуальных поверхностях – в буквальном смысле в оперативной памяти.

Приложение моделирует случайное блуждание множества однородных частиц. Несмотря на интенсивный вывод графической информации, удается избежать эффекта мигания, который возникает при непосредственной модификации внешнего вида элементов управления.

Демонстрация предварительно нарисованной в памяти картинки происходит при помощи элемента управления типа PictureBox, который благодаря свойству объекта Image обеспечивает быстрое отображение выводимой графической информации.

Итак, данное Windows-приложение включает объявление трех классов:

  • класса xPoint, объекты которого периодически изменяют собственное положение в пределах клиентской области окна;
  • класса cForm, который обладает свойствами сохранения постоянного соотношения собственных размеров и размеров клиентской области, и обеспечивает отображение множества блуждающих объектов – представителей класса xPoint ;
  • класса Program, который в соответствии с предопределенными алгоритмами обеспечивает создание и выполнение приложения.

Класс Program

Создается автоматически. Без комментариев.

using System;
 using System.Collections.Generic;
 using System.Windows.Forms;

 namespace cForm
 {
     static class Program
     {
         
         // The main entry point for the application.
         
         static void Main()
         {
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             Application.Run(new cForm());
         }
     }
 }

Класс xPoint

Объявление класса блуждающих объектов:

using System;
 using System.Collections.Generic;
 using System.Drawing;
 using System.Text;
 namespace cForm
 {
 class xPoint
 {
 // Собственный статический генератор случайных чисел.
 // Используется в конструкторе.
 public static Random rnd = new Random();

 // Цвет объекта.
 public Color xColor;

 // Текущая позиция. Относительные значения координат X и Y.
 public Point p;

 // Обеспечение перемещения. Текущие значения координат могут
 // изменяться как по X, так и по Y.
 public Point direction;

 // Счетчик циклов сохранения выбранного направления.
 // Объект на протяжении фиксированного интервала времени может 
 // сохранять ранее избранное направление движения.
 // В течение n.X "тиков" для X в течение n.Y "тиков" для Y.
 public Point n;

 public xPoint(int X, int Y, int Xn, int Yn)
 {
 n = new Point(rnd.Next(0, Xn), rnd.Next(0, Yn));
 direction = new Point(rnd.Next(-1, 2), rnd.Next(-1, 2));
 p = new Point(X, Y);
 xColor = 
   Color.FromArgb(150, rnd.Next(0, 256), rnd.Next(0, 256), rnd.Next(0, 256));
 }

 // Статический метод. Еще вопрос, где следовало размещать его 
 // объявление... Определение нового положения амебы. 
 // Просматривается ВЕСЬ список.
 // Ее перемещение ограничивается физическими размерами клиентской области
 // окна приложения и определяется по двум координатам (X и Y).
 // В каждый момент она может оставаться на месте, либо изменить
 //  свое положение на один "шаг" по каждой из осей координат
 // ("вверх" или "вниз" по оси Y и "вперед" или "назад" по оси X).
 public static void setNextPosition(cForm cf, xPoint[] pt)
 {
 int i;
 float xy;

 // Итак, определение текущей позиции объекта.
 // Просмотр массива...
 for (i = 0; i < pt.Length; i++)
 {
 // Сначала разбираемся со значением координаты X.
 // Вычисляем возможную позицию после перемещения объекта.
 xy = (float)((pt[i].p.X + pt[i].direction.X) * cf.rPointGet);
 // Вполне возможно, что это вполне подходящая позиция.
 // И надо всего лишь проверить две вещи:
 // 1. не выскочит ли объект за пределы клиентской области окна 
 // приложения,
 // 2. не настало ли время поменять направление движения.
 if (
 xy < 0 || xy > cf.ClientSize.Width 	// 1. 
 ||
 pt[i].n.X > cf.NX                	// 2.
 )
 {
 pt[i].n.X = 0; // Обнулили счетчик циклов сохранения выбранного направления.
 // Процедура изменения направления перемещения по оси X.
 // На ближайшую перспективу объект может переместиться
 // вперед:            pt[i].direction.X == 1,
 // назад:             pt[i].direction.X == –1,
 // остаться на месте: pt[i].direction.X == 0.
 // Главное — это не выйти за пределы клиентской области окна приложения.
     pt[i].direction.X = xPoint.rnd.Next(-1, 2);
     xy = (float)((pt[i].p.X + pt[i].direction.X) * cf.rPointGet);
     if (xy >= 0 && xy <= cf.ClientSize.Width)
     {
         // Направление выбрано, перемещение произведено.
         pt[i].p.X += pt[i].direction.X;
     }
     else
     {
         // Выбранное направление движения приводит к выходу объекта
         // за пределы клиентской области окна приложения.
         // На ближайшие cf.NX тактов объект остается неподвижен по оси X.  
         pt[i].direction.X = 0;
     }
 }
 else
 {
 // Осуществили очередное перемещение по оси X.
 pt[i].p.X += pt[i].direction.X;
 pt[i].n.X++;
 }

 xy = (float)((pt[i].p.Y + pt[i].direction.Y) * cf.rPointGet);
 // Вполне возможно, что это вполне подходящая позиция.
 // И надо всего лишь проверить две вещи:
 // 1. не выскочит ли объект за пределы клиентской области 
 // окна приложения,
 // 2. не настало ли время поменять направление движения.
 if (
 xy < 0 || xy > cf.ClientSize.Height // 1. 
 ||
 pt[i].n.Y > cf.NY                   // 2.
 )
 {
 pt[i].n.Y = 0; // Обнулили счетчик циклов сохранения выбранного направления.
 // Процедура изменения направления перемещения по оси Y.
 // На ближайшую перспективу объект может переместиться
 // вверх:             pt[i].direction.Y == 1,
 // вниз:              pt[i].direction.Y == -1,
 // остаться на месте: pt[i].direction.Y == 0.
 // Главное — это не выйти за пределы клиентской области окна приложения.
 pt[i].direction.Y = xPoint.rnd.Next(-1, 2);
 xy = (float)((pt[i].p.Y + pt[i].direction.Y) * cf.rPointGet);
 if (xy >= 0 && xy <= cf.ClientSize.Height)
 {
     // Направление выбрано, перемещение произведено.
     pt[i].p.Y += pt[i].direction.Y;
 }
 else
 {
     // Выбранное направление движения приводит к выходу объекта
     // за пределы клиентской области окна приложения.
     // На ближайшие cf.NY тактов объект остается неподвижен по оси Y.  
     pt[i].direction.Y = 0;
 }
 }
 else
 {
 // Осуществили очередное перемещение по оси Y.
 pt[i].p.Y += pt[i].direction.Y;
 pt[i].n.Y++;
 }
 }
 }
 }
 }
Листинг 17.1.

Класс cForm

Partial-класс. Содержит объявление класса формы приложения. Первая часть объявления в основном создается автоматически. Следует обратить внимание на настройку событий:

namespace cForm
 {
 partial class cForm
 {

 // Required designer variable.

 private System.ComponentModel.IContainer components = null;

 // Clean up any resources being used.
 // <param name="disposing">true if managed resources should be disposed;
 // otherwise, false.</param>
 protected override void Dispose(bool disposing)
 {
     if (disposing && (components != null))
     {
         components.Dispose();
     }
     base.Dispose(disposing);
 }

 #region Windows Form Designer generated code


 // Required method for Designer support - do not modify
 // the contents of this method with the code editor.

 private void InitializeComponent()
 {
     this.components = new System.ComponentModel.Container();
     this.mainPictureBox = new System.Windows.Forms.PictureBox();
     this.mainTimer = new System.Windows.Forms.Timer(this.components);
   ((System.ComponentModel.ISupportInitialize)(this.mainPictureBox)).
                         BeginInit();
     this.SuspendLayout();
     // 
     // mainPictureBox
     // 
     this.mainPictureBox.BackColor = System.Drawing.Color.White;
     this.mainPictureBox.BorderStyle =
                   System.Windows.Forms.BorderStyle.FixedSingle;
     this.mainPictureBox.Dock = System.Windows.Forms.DockStyle.Fill;
     this.mainPictureBox.Location = new System.Drawing.Point(0, 0);
     this.mainPictureBox.Name = "mainPictureBox";
     this.mainPictureBox.Size = new System.Drawing.Size(569, 538);
     this.mainPictureBox.TabIndex = 0;
     this.mainPictureBox.TabStop = false;
     // 
     // mainTimer
     // 
     this.mainTimer.Interval = 250;
     this.mainTimer.Tick += new System.EventHandler(this.mainTimer_Tick);
     // 
     // cForm
     // 
     this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
     this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
     this.ClientSize = new System.Drawing.Size(569, 538);
     this.Controls.Add(this.mainPictureBox);
     this.Name = "cForm";
     this.Text = "cForm";
 this.Paint +=
               new System.Windows.Forms.PaintEventHandler(this.bForm00_Paint);
 this.Layout +=
 new System.Windows.Forms.LayoutEventHandler(this.bForm00_Layout);
((System.ComponentModel.ISupportInitialize)(this.mainPictureBox)).EndInit();
 this.ResumeLayout(false);
 }

 #endregion

 private System.Windows.Forms.PictureBox mainPictureBox;

 // Определение неизменяемых характеристик рамки окна приложения. 
 private void GetBordersGabarits()
 {
     this.borderHeight = this.Height - this.ClientSize.Height;
     this.borderWidth = this.Width - this.ClientSize.Width;        
 }

 // Определение предельных габаритов окна приложения.
 private void SetMinMaxSize()
 {
  this.MinimumSize =
    new System.Drawing.Size(minWidth + borderWidth, minHeight + borderHeight);
  this.MaximumSize =
    new System.Drawing.Size(maxWidth + borderWidth, maxHeight + borderHeight);
 }
 }
 }
Листинг 17.2.

Вторая часть объявления. Здесь сосредоточены методы управления поведением популяции амеб, методы масштабирования внешнего вида приложения и отображения информации.

using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Data;
 using System.Drawing;
 using System.Text;
 using System.Windows.Forms;

 namespace cForm
 {
 public partial class cForm : Form
 {
 // Средства для отображения объектов на плоскости.
 Color backColor;
 Pen greedPen;
 Pen pen0;
 SolidBrush br;

 // Действующие лица. Массив объектов xPoint.
 xPoint[] pt;

 // Количество точек.
 const int nPT = 10000;

 // Синхронизаторы изменения направления перемещения объектов.
 public int NX, NY;

 // Предельные характеристики размеров клиентской области. 
 const int minHeight = 100;
 const int minWidth = 100;
 const int maxHeight = 950;
 const int maxWidth = 950;

 // Средство масштабирования. Всеобщая единица измерения.
 private double rPoint;

 // Абсолютные неизменяемые характеристики рамки окна приложения.
 int borderHeight;
 int borderWidth;

 // Область рисования. Изображение формируется в памяти.
 // На "поверхности" битовой карты
 // (объект - представитель класса Graphics, 
 // создается в результате выполнения метода FromImage),
 // средствами векторной графики (методы класса Graphics) 
 // рисуется картинка.
 // Изображение проецируется на поверхность элемента управления 
 // mainPictureBox 
 // путем модификации свойства Image этого элемента управления.
 private Bitmap bmp;

 // Таймер. Фактически представляет собой делегат,
 // который через фиксированные интервалы
 // времени активизирует соответствующее событие
 // (передает управление методу mainTimer_Tick).
 private System.Windows.Forms.Timer mainTimer;

 // Конструктор формы.
 public cForm()
 {
 int i;

 // Цвет фона картинки. Используется при перерисовке изображения.
 backColor = Color.FromArgb(255,0,0,0);

 // Средства для рисования.
 greedPen = new Pen(Color.FromArgb(50, 0, 0, 255), 1);
 pen0 = new Pen(Color.FromArgb(255,0,0,0),1);
 br = new SolidBrush(Color.FromArgb(0, 0, 0, 0));

 // Обязательно смотреть код начальной инициализации элементов формы!
 InitializeComponent();

 // Определение неизменяемых характеристик окна приложения (формы).
 GetBordersGabarits();

 // Начальная коррекция размеров клиентской области окна приложения.
 // "Прямой" вызов метода - обработчика события изменения размеров 
 // окна приложения.
 bForm00_Layout(null, null);

 // Определение предельных габаритов окна приложения. 
 SetMinMaxSize();

 // Значения синхронизаторов.
 // Неплохо приспособить специальные элементы управления,
 // которые позволяли бы изменять эти значения в интерактивном режиме!
 NX = 50;
 NY = 50;

 // Инициализация популяции.
 // Все располагаются в одном месте – в центре клиентской области окна.
 // Одинаковые значения двух последних параметров приводят к тому, что
 // члены популяции синхронно изменяют направление движения. 
 pt = new xPoint[nPT];
 for (i = 0; i < pt.Length; i++)
 {
 pt[i] = new xPoint(
   (int)(this.ClientSize.Width  / (2*rPoint)),
   (int)(this.ClientSize.Height / (2*rPoint)),
    0, //NX, // xPoint.rnd.Next(0, NX+1), // А такие значения нарушают 
    0  //NY  // xPoint.rnd.Next(0, NY+1) // синхронность поведения объектов.
    );
 }

 // Процесс пошел!
 mainTimer.Start();
 }

 // Определение относительной величины rPoint реализовано как свойство.
 // Замечательная особенность свойства!
 // Оно имеет явную спецификацию возвращаемого значения.
 // И точно такого же типа должен быть единственный параметр value!
 private Size rPointSet
 {            
 set
 { 
 Size s = value;
 rPoint = Math.Sqrt(2.0*s.Width*s.Height) * 0.002;            
 }        
 }

 // А вот перегрузить свойство нельзя! Невидимый параметр value 
 // и возвращаемое значение свойства должны быть одного типа. 
 // Именно поэтому МНЕ пришлось вводить пару свойств.
 public double rPointGet
 {
 get
 {
 return rPoint;
 }
 }        

 // Обработчик события перерисовки формы.
 // В списке параметров ссылка на объект - инициализатор события,
 // а также ссылка на объект – носитель информации об инкапсуляторе
 // данной поверхности рисования.
 // Способ получения этой информации простой – через свойство Graphics:
 //      Graphics gr = e.Graphics;
 // И знай рисуй себе!
 // Однако эта ссылка нам не понадобится, поскольку мы рисуем
 // не на ПОВЕРХНОСТИ ЭЛЕМЕНТА УПРАВЛЕНИЯ,
 // а в памяти, на ПОВЕРХНОСТИ БИТОВОЙ КАРТЫ. 
 private void bForm00_Paint(object sender, PaintEventArgs e)
 {
 xRedraw(); 
 }
               
 // Событие Layout возникает при удалении или добавлении дочерних
 // элементов управления,
 // изменении границ элемента управления и других изменениях, 
 // в том числе и изменении размеров формы, которые могут повлиять 
 // на макет элемента.
 // Рекомендуется использовать именно это событие,
 // а не события ..._Resize или ..._SizeChanged. Как сказано 
 // в руководстве, событие Layout возникает в ответ на события
 // ..._Resize и ..._SizeChanged,
 // а также в других ситуациях, когда может понадобиться применение макета.
 private void bForm00_Layout(object sender, LayoutEventArgs e)
 {
 // Использование сразу ДВУХ величин – длины и ширины клиентской 
 // области окна!
 // Вот оптимальное решение проблемы поддержки ПОСТОЯННОГО соотношения
 // характеристик клиентской области  
 // (среднее арифметическое для поддержки равенства сторон).
 // ПЛЮС поправка на размеры рамки окна приложения. 

 Size = new System.Drawing.Size
     (
     (ClientSize.Width + ClientSize.Height)/2 + borderWidth,
     (ClientSize.Width + ClientSize.Height)/2 + borderHeight
     );

 // После коррекции размеров окна – устанавливаем значение величины 
 // rPoint, которое производится через обращение к свойству rPointSet.   
 rPointSet = this.ClientSize;

 // Новая битовая карта под новый размер клиентской области.
 bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);

 // Объявляется недопустимой конкретная область элемента управления 
 // (здесь форма) и происходит отправка соответствующего сообщения
 // (Paint) элементу управления (форме). При этом значение 
 // параметра (true) 
 // объявляет недопустимыми назначенные элементу управления 
 // дочерние элементы.
 // Для каждого элемента автоматически формируется 
 // объект-представитель
 // класса Graphics, инкапсулирующий поверхность для рисования.
 // Для активизации процесса перерисовки соответствующего 
 // элемента управления требуется объявить метод – обработчик события
 // (в нашем случае это bForm00_Paint).
 this.Invalidate(false); 
 }

 // Обработчик события таймера:
 // всем амебам надлежит переопределить собственные координаты и
 // перерисовать картинку! Здесь также можно было бы объявить 
 // недействительной и требующей перерисовки поверхность формы.
 // Это просто: this.Invalidate(false);
 // Однако никто не помешает нам сделать все напрямую, через метод xRedraw.  
 private void mainTimer_Tick(object sender, EventArgs e)
 {
 xPoint.setNextPosition(this, pt);
 xRedraw();
 }

 // Перерисовка.
 private void xRedraw()
 {
 // Поверхность для рисования на битовой карте!
 Graphics gr = Graphics.FromImage(bmp);

 // Очистить поверхность и закрасить ее в ненавязчивый черный цвет!
 gr.Clear(backColor);

 // Нарисовать решетку!
 DrawGreed(gr);

 // Нарисовать объекты!
 foreach (xPoint xp in pt)
 {
 // Сообщили кисти цвет объекта... 
 br.Color = xp.xColor;
 // Закрасили объект.
 gr.FillEllipse(br,
                (float)((xp.p.X * rPoint) - rPoint),
                (float)((xp.p.Y * rPoint) - rPoint),
                (float)(2 * rPoint),
                (float)(2 * rPoint));
 // Сообщили перу цвет объекта... 
 pen0.Color = xp.xColor;
 // Нарисовали контур амебы...
 gr.DrawEllipse(pen0,
                (float)((xp.p.X * rPoint) - rPoint),
                (float)((xp.p.Y * rPoint) - rPoint),
                (float)(2 * rPoint),
                (float)(2 * rPoint));
 }

 // Рисование закончено. Подготовить объект-поверхность к удалению.
 gr.Dispose();

 // Отобразить картинку!
 // Это всего лишь модификация свойства Image элемента управления PictureBox. 
 this.mainPictureBox.Image = bmp;
 }

 // На поверхности битовой карты (параметр gr) рисуется координатная сетка. 
 private void DrawGreed(Graphics gr)
 {
 float i;
 float w = (float)(this.ClientSize.Width/25.0F);
 float h = (float)(this.ClientSize.Height/25.0F);
 float W = this.ClientSize.Width;
 float H = this.ClientSize.Height;

 for (i = h; i < H; i += h)
 {
 gr.DrawLine(this.greedPen, 0, i, W, i);
 }

 for (i = w; i < W; i += w)
 {
 gr.DrawLine(this.greedPen, i, 0, i, H);
 }
 }
 }
 }
Листинг 17.3.

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

kewezok kewezok
kewezok kewezok
Елена Шляхт
Елена Шляхт
Объясните плиз в чем отличие а++ от ++а
Почему результат разный?
int a=0, b=0;
Console.WriteLine(a++); //0
Console.WriteLine(++b); //1
a++;
++b;
Console.WriteLine(a); //2
Console.WriteLine(b); //2