БлогNot. C#: строим диаграммы и выполняем анимацию в приложениях Windows Forms

C#: строим диаграммы и выполняем анимацию в приложениях Windows Forms

Для непосредственного простейшего построения графика или диаграммы можно использовать следующие арифметические соображения.

1. Узнав с помощью соответствующих свойств размеры графической канвы в пикселях и определив значение xstep - шаг по оси X, соответствующий одному пикселю на экране, мы сможем обеспечить масштабирование графика по оси X. Для масштабирования по оси Y на первом этапе пересчета требуется также определить максимальное и минимальное значения f(x) на интервале построения [a, b] при изменении значения x с шагом xstep.

2. Второй этап связан с непосредственным пересчётом значений (x, f(x)) в экранные координаты (cx, cy). Для решения этой задачи воспользуемся формулой, согласно которой значение x, принадлежащее интервалу [a, b], можно линейно преобразовать в значение y, принадлежащее интервалу [c, d]:

Преобразовать x из интервала значений [a,b] в y из интервала значений  [c,d]
Преобразовать x из интервала значений [a,b] в y из интервала значений [c,d]

Эта формула позволит получить коэффициенты преобразования величин (x, f(x)) к экранным координатам. Дополнительно придется учесть то, что экранная ось Y проведена сверху вниз.

Схема "ручного" преобразования координат к экранным
Схема "ручного" преобразования координат к экранным

Проект Lab6_1. Создав приложение Windows Forms, пропишем в классе формы необходимые данные:

  private delegate double Function (double x); //делегат для подключения функции
  private double func (double x) { return Math.Sin (x); } //сама функция
  private double x1 = -2 * Math.PI, x2 = 2 * Math.PI;
   //границы, в реальности границы по x введены откуда-то извне

и построим график на канве формы, например, по её событию Activated:

   this.ClientSize = new Size (640, 480); //размер клиентской части формы
   double x, xstep, y, y1, y2, ystep;
   int pw = this.ClientSize.Width, ph = this.ClientSize.Height; //размеры полотна
   Function  f = func; //отображаемая функция
   xstep = ( x2 - x1 ) / pw; //шаг по x = 1 пикселу
   y1 = y2 = f (x1);
   for (x = x1; x <= x2; x += xstep) { //ищем макс. и мин. по y
    y = f (x); 
    if (y > y2) y2 = y; 
    if (y < y1) y1 = y;
   }
   ystep = ( y2 - y1 ) / ph; //шаг по y также = 1 пикселу (искажение пропорций)
   double xcoeff = pw / ( x2 - x1 ), ycoeff = ph / ( y2 - y1 );
    //коэффициенты пересчета физических координат в пиксельные
   Graphics  g = this.CreateGraphics(); //контекст картинки
   int mx = (int) ( -x1 * xcoeff ), my = (int) ( -y1 * ycoeff ); //начало координат
   g.DrawLine (Pens.Red, mx, 0, mx, ph); 
   g.DrawLine (Pens.Red, 0, my, pw, my); //оси
   System.Collections.Generic.List <PointF>  
    Points = new System.Collections.Generic.List <PointF> (); //коллекция точек
   x = x1;
   while (x <= x2) { 
    y = f (x); 
    Points.Add (new PointF (
     (float) ( ( x - x1 ) * xcoeff ), (float) ( ( y2 - y ) * ycoeff ))); 
    x += xstep; 
   }
   g.DrawLines (Pens.Green, Points.ToArray ()); //отрисовка

При небольшой модификации можно добиться и пропорционального масштабирования графика по осям (используя один коэффициент пересчёта вместо xcoeff и ycoeff).

Разумеется, такой график очень примитивен и нужен только для иллюстрации. В реальности существует компонента Chart (вкладка Данные), разработанная для поддержки 2D- и частично 3D-диаграмм и графиков.

 Скачать пример Lab6_1 в архиве .zip с проектом C# Visual Studio 2019 (11 Кб)

Проект Lab6_2. Добавим на пустую форму компоненту Chart со свойством Dock = Fill и выведем туда программно сгенерированные данные (команда меню Файл - График 1).

   Dictionary <double, double> f1 = new Dictionary <double, double> ();
    //коллекция "ключ"-"значение" для пар "x" - "f(x)"
   double x0 = 0, xmax = Math.PI, dx = Math.PI / 100.0; //границы графика по оси x и шаг
   f1.Clear ();
   chart1.Series [0].ChartType = SeriesChartType.Line; //тип диаграммы
   chart1.Series [0].MarkerStyle = MarkerStyle.Circle; //тип маркеров
   for (double x = x0; x <= xmax; x += dx) { //заполнение коллекции
    f1.Add ((int) ( x * 100 ) / 100.0, ( Math.Sin (x) ));
   }
   chart1.Series [0].Points.DataBindXY (f1.Keys, f1.Values);
    //привязать точки коллекции к ряду данных номер 0
   chart1.Series [0].LegendText = "Функция 1"; //настроить легенду
   chart1.Series [0].Color = System.Drawing.Color.Green;
   chart1.Series [0].BorderWidth = 2;

Для работы примера требуются следующие подключенные пространства имён:

using System.Windows.Forms.DataVisualization.Charting;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;

Для команды меню Файл - График 2 создадим и выведем красивый Chart программно:

   //Общие свойства
   Chart myChart = new Chart ();
   myChart.Parent = this;
   myChart.Left = 10; myChart.Top = 10;
   myChart.Width = ( this.ClientSize.Width - 10 );
   myChart.Height = ( this.ClientSize.Height - 20 );
   myChart.BringToFront (); //или chart1.Visible = false;
   //Сразу 2 важных вещи:
   //1. Анонимная "стрелочная" функция-делегат для обработки события
   //2. По клику диаграмма 2 будет "сама себя" прятать на задний фон
   myChart.Click += (sendr, args) => { myChart.SendToBack (); };
   //Область в которой будет построен график (их может быть несколько)
   ChartArea myChartArea = new ChartArea ();
   myChartArea.Name = "myChartArea";
   myChart.ChartAreas.Add (myChartArea);
   //График (их может быть несколько)
   Series  mySeries1 = new Series ();
   mySeries1.ChartType = SeriesChartType.Column;
   mySeries1.ChartArea = "myChartArea";
   myChart.Series.Add (mySeries1);
   //Исходные данные для графика
   double [] yval1 = { 10, 6, 4, 6, 3 };
   String [] xval = { "Январь", "Февраль", "Март", "Апрель", "Май" };
   mySeries1.Points.DataBindXY (xval, yval1);
   //фон градиентом
   myChart.BackColor = System.Drawing.Color.MistyRose;
   myChart.BackGradientStyle = GradientStyle.DiagonalLeft;
   //границы в современном стиле
   myChart.BorderSkin.SkinStyle = BorderSkinStyle.Sunken;
   myChart.BorderSkin.PageColor = this.BackColor;
   //линии сетки покажем разными цветами
   myChartArea.AxisX.MajorGrid.LineColor = System.Drawing.SystemColors.ControlDark;
   myChartArea.AxisY.MajorGrid.LineColor = System.Drawing.SystemColors.ControlLight;
   //добавим второй график
   Series  mySeries2 = new Series ();
   mySeries2.ChartType = SeriesChartType.Point;
   mySeries2.ChartArea = "myChartArea";
   myChart.Series.Add (mySeries2);
   double [] yval2 = { 4, 7, 3, 5, 5 };
   mySeries2.Points.DataBindXY (xval, yval2);

Для построения таблицы значений по данным первого графика в конец обработчика пункта меню Файл - График 1 добавлены строки:

   //Создадим новую форму и выведем туда данные 1-го ряда в виде таблицы
   Form form = new Form (); //Создали форму
   form.Size = new Size (640, 480);
   DataGridView dataGridView1 = new DataGridView (); //Создали таблицу
   dataGridView1.Dock = DockStyle.Fill; //Растянули
   dataGridView1.AllowUserToAddRows = false; //Запретили пользователю добавлять строки
   dataGridView1.Columns.Add ("X", "X");
   dataGridView1.Columns.Add ("Y", "Y"); //Добавили 2 столбца
   foreach (DataPoint p in chart1.Series [0].Points) {
    //Пробегаем по всем точкам графика
    dataGridView1.Rows.Add (1); //Добавляем строку в таблицу
    int i = dataGridView1.RowCount - 1;
    dataGridView1.Rows [i].Cells [0].Value = p.XValue.ToString (); //Добавляем значение X
    double yp = p.YValues [0]; //Y является массивом, поэтому берём элемент
    dataGridView1.Rows [i].Cells [1].Value = Math.Round (yp, 3).ToString ();
   }
   form.Controls.Add (dataGridView1); //Добавили таблицу на форму
   form.Show (); //Показали форму

 Скачать пример Lab6_2 в архиве .zip с проектом C# Visual Studio 2019 (13 Кб)

Проект Lab6_3. На основе готового парсера выражений реализуем программу- "графопостроитель", умеющую строить графики введённой пользователем функции одной переменной в заданных пределах.

Ввод данных будем выполнять через стандартное меню MenuStrip, вот его структура:

  • Аргумент: пункты "От x1", "С шагом dx", "До x2", в каждый из которых вложен элемент ToolStripTextBox (с именами x1, dx, x2 соответственно);
  • Функция: пункты "Функция", "Переменная", в каждый из которых вложен элемент ToolStripTextBox (с именами funcexp, funcvar соответственно);
  • Построить: вложенных пунктов нет.

Нам понадобится дополнительный пакет интерпретации формул AngouriMath, установить этот и другие пакеты можно так:

  • Нажмем в Обозревателе решений правой кнопкой на названии проекта и в контекстном меню выберем пункт "Управление пакетами NuGet...";
  • В появившейся вкладке выберем "Обзор", отметим "Включить предварительные версии", введём в текстовое поле AngouriMath и нажмём Enter;
  • Когда пакет найден, выбираем его (лучше последнюю стабильную версию) и нажимаем справа "Установить". Все нужные файлы и зависимые пакеты будут установлены во вложенную папку проекта packages, а в основной папке будет создан файл packages.config.

Для применения пакета добавим в начале файла Form1.cs директиву

using AngouriMath;

Документацию по пакету можно найти на его странице.

Заметим, что пакеты будут также скопированы в служебную папку packages Visual Studio и в дальнейшем могут быть восстановлены при разворачивании проекта (если не удалён файл packages.config).

В классе формы подключим дополнительные стандартные пространства имён

using System.Windows.Forms.DataVisualization.Charting;
using System.Text.RegularExpressions;

и опишем нужные данные:

  public Series mySeriesOfPoint;
  public Chart myChart;
  private bool check = true;

В конструкторе формы создадим и программно настроим компоненту Chart:

  public Form1 () {
   InitializeComponent ();
   
   myChart = new Chart (); //Создаем элемент Chart
   myChart.Parent = this; //Помещаем его на форму и растягиваем на все окно
   myChart.Dock = DockStyle.Fill;
   //Добавляем в Chart область для рисования графиков, их может быть
   //много, поэтому даем ей имя
   myChart.ChartAreas.Add (new ChartArea ("Math functions"));
   //Создаем и настраиваем набор точек для рисования графика, 
   //не забыв указать имя области на которой хотим отобразить этот
   //набор точек.
   mySeriesOfPoint = new Series ("Default");
   mySeriesOfPoint.ChartType = SeriesChartType.Line;
   mySeriesOfPoint.ChartArea = "Math functions";
  }

Основываясь на документации к установленному пакету и данных из меню "Функция", напишем метод Polynom, интерпретирующий значение введённого в поле "Функция" выражения для аргумента x и возвращающий вычисленное значение f(x):

  bool Polynom (double x, out double y) {
   string pattern = @"[а-я]"; //можно включить другие "запрещённые" символы ввода
   Entity exp; //выражение
   Regex regex = new Regex (pattern, RegexOptions.IgnoreCase); 
    //регулярное выражение для проверки
   if (!regex.IsMatch (funcexp.Text.Trim ())) {
    try {
     exp = funcexp.Text;
    }
    catch (AngouriMath.Core.Exceptions.ParseException) {
     MessageBox.Show ("Ошибка интерпретации функции, проверьте её запись");
     y = 0;  return false;
    }
   }
   else {
    MessageBox.Show ("Недопустимые символы в функции, проверьте её запись");
    y = 0; return false;
   }
   AngouriMath.Core.FastExpression func;
   try {
    func = exp.Compile (funcvar.Text.Trim());
    check = true;
   }
   catch (System.Collections.Generic.KeyNotFoundException) {
    MessageBox.Show ("Неверный аргумент функции или функция указана неверно");
    Entity errorExp = "x";
    func = errorExp.Compile ("x");
    check = false;
   }
   y = func.Substitute (x).Real;
   return true;
  }

Команда "Построить" будет собирать данные из меню "Аргумент" и, если они допустимы, выполнять организацию основного цикла:

  private void построитьToolStripMenuItem_Click (object sender, EventArgs e) {
   mySeriesOfPoint.Points.Clear ();
   myChart.Series.Clear ();
   double from, to, foot, f;
   try {
    from = double.Parse (x1.Text);
    foot = double.Parse (dx.Text);
    to = double.Parse (x2.Text);
   }
   catch (Exception) {
    MessageBox.Show ("Введите числовые значения в меню Аргумент!");
    return;
   }
   if (from + foot >= to) {
    MessageBox.Show ("Введите корректно: x1 + dx < x2");
    return;
   }
   for (double x = from; x <= to; x += foot) {
    check = Polynom (x, out f);
    if (!check) {
     MessageBox.Show ("Ошибка интерпретации, построение прервано!");
     break;
    }
    mySeriesOfPoint.Points.AddXY (x, f); //Добавляем точку в набор
   }
   myChart.Series.Add (mySeriesOfPoint); //Добавляем созданный набор точек в Chart
   check = true;
  }

Теперь в поле ввода "Функция" можно писать любые допустимые парсером выражения с аргументом x (или другим, который мы поменяли командой Функция - Переменная), например, cos(x), текущее значение переменной x из программы подставится в выражение и его результат динамически посчитается.

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

  private void Form1_Load (object sender, EventArgs e) {
   построитьToolStripMenuItem_Click (this, e); //Строим график с данными по умолчанию
  }

Замечание. Напомним, что в различных национальных стандартах могут применяться разные разделители для элементов списка, целой и дробной части числа и т.п. Для обработки таких ситуаций могут пригодиться как объекты пространства имён Globalization, так и простые замены нужных разделителей в интерпретируемых данных, например:

   String Str = "1,2345";
   Str = Str.Replace(",",".");
   MessageBox.Show (Str.ToString()); //1.2345

Следует также помнить, что для ввода чисел согласно установленной в системе локали, в общем случае предпочтительнее компонента NumericUpDown.

 Скачать пример Lab6_3 в архиве .zip с проектом C# Visual Studio 2019 (14 Кб)

Приложение Lab6_3 в работе с открытым меню ввода интерпретируемой функции
Приложение Lab6_3 в работе с открытым меню ввода интерпретируемой функции

Теперь рассмотрим принципы простой анимации в программах Windows Forms.

Высокоуровневые методы библиотеки .NET едва ли подойдут для написания насыщенных быстрым движением и графикой мультимедийных или игровых приложений - для этого есть DirectX и OpenGL. Тем не менее, иногда бывает нужно куда-то передвинуть или перетащить картинку, "оживить" своё приложение небольшой покадровой анимацией и т.п. В качестве примера покажем несколько типовых действий по реализации анимации в .NET на C#.

Общий подход к анимации следующий: на форму добавляется или программно создаётся компонента Timer. Её единственное событие Tick выполняется через заданный в миллисекундах интервал времени Interval. Обработчик события Tick отслеживает, нужна ли перерисовка каких-либо объектов на форме и, при необходимости, вызывает соответствующие методы. Самый простой способ - выполнить метод Invalidate формы, отправляющий сообщение методу Paint, отвечающему за перерисовку.

Проект Lab6_4. Картинка следует за курсором мыши.

Создав решение Windows Forms на C++, проиллюстрируем добавление в проекту файла ресурсов и загрузку оттуда картинки.

  • В верхнем меню выбрать Проект - Добавить компонент - Файл ресурсов;
  • Выполнить двойной клик по Resuorses1.resx в Обозревателе решений, выбрать Добавить ресурс - Добавить существующий файл, выбрать в окне диалога рисунок butterfly.png из папки с исходным кодом проекта, нажать Открыть;
  • Выбрать добавленный рисунок, в его свойствах установить Persisitence = "Внедрено в RESX-файл";

Теперь из кода рисунок можно загрузить строкой вида

Img = (Image) Resource1.butterfly;

, где Resource1 - имя файла ресурсов, а buttefly - свойство Name добавленного ресурса изображения.

Опишем в классе формы необходимые объекты:

  Image Img; //изображение
  Point imgPoint, mousePoint; //координаты картинки и цели
  bool mouseMode; //следим ли за мышью
  Timer timer1; //таймер

Дадим им начальные значения в обработчике события Load формы:

   this.DoubleBuffered = true;
    //включаем двойную буферизацию, чтобы не мерцало
   Img = (Image) Resource1.butterfly;
    //загружаем картинку из ресурсов
   imgPoint = new Point (0, 0);
   mousePoint = new Point ();
   mouseMode = true;
   timer1 = new Timer ();
   timer1.Interval = 5; //вызываем каждые 5 мс
   timer1.Enabled = true;
   timer1.Tick += new EventHandler (timer1_Tick);

Обратите внимание, что для формы включена двойная буферизация графики - это позволит избежать мерцания картинки при её движении. При относительно медленном процессе отрисовки объектов непосредственно на канве, где они отображаются, часто возникает эффект мерцания изображения. Избежать его позволяет двойная буферизация, идея которой состоит в следующем: объект рисуется на невидимой канве, а затем законченный объект быстрым копированием из одной области оперативной памяти в другую помещается на канву отображения. В библиотеку .NET двойная буферизация уже встроена, для её использования достаточно установить в значение true свойство DoubleBuffered формы.

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

Вот сам этот метод, добавленный в класс формы плюс вспомогательный метод MoveImg, отвечающий за изменение координат:

  private void timer1_Tick (Object sender, EventArgs e) {
   //добавили этот метод "вручную", а не из конструктора
   if (mouseMode) {
    MoveImg (mousePoint);
    this.Invalidate (); //перерисовать после смещения!
   }
  }
  private void MoveImg (Point p) {
   //добавили этот метод "вручную", а не из конструктора
   if (imgPoint.X < p.X) imgPoint.X++;
   if (imgPoint.X > p.X) imgPoint.X--;
   if (imgPoint.Y < p.Y) imgPoint.Y++;
   if (imgPoint.Y > p.Y) imgPoint.Y--;
  }

Непосредственно заниматься отрисовкой будет обработчик события Paint, который мы создадим из стандартного окна "Свойства":

  private void Form1_Paint (object sender, PaintEventArgs e) {
   if (Img != null) {
    e.Graphics.DrawImage (Img, imgPoint);
   }
  }

По событию MouseDown будем переключать режим движения картинки, то есть, кликнем раз - движение за курсором прекратится, а следующий клик возобновит движение и т.д.:

  private void Form1_MouseDown (object sender, MouseEventArgs e) {
   mouseMode = !mouseMode;
  }

Наконец, обработчик события перемещения мыши будет следить за тем, куда нужно смещаться картинке. Обратите внимание на то, какой должна быть "цель" движения, чтобы картинка останавливалась "прямо под курсором":

  private void Form1_MouseMove (object sender, MouseEventArgs e) {
   if (Img != null) {
    mousePoint = new Point (e.Location.X - Img.Width / 2,
                            e.Location.Y - Img.Height / 2);
   }
  }

Это всё, приложение можно собирать.

 Скачать пример Lab6_4 в архиве .zip с проектом C# Visual Studio 2019 (154 Кб)

Проект Lab6_5. Движение картинки мышью.

Так как у Image нет свойства Location, будем перемещать добавленный на форму PictureBox. Ему автоматически присвоится имя pictureBox1.

Создадим файл ресурсов точно так же, как в предыдущем проекте, и добавим туда рисунок butterfly.png.

Опишем в классе формы необходимые данные:

  Image  Img;     //картинка
  bool i_mouse;   //флажок, показывающий, нажата ли кнопка мыши для перетаскивания
  int pozx, pozy; //координаты курсора мыши на картинке
  PictureBox pictureBox1; //контейнер для рисунка

В обработчике события Load формы настроим наш PictureBox:

  private void Form1_Load (object sender, EventArgs e) {
   i_mouse = false;
   pictureBox1 = new PictureBox ();
   pictureBox1.Location = new Point (0, 0);
   pictureBox1.AutoSize = true;
   pictureBox1.Image = (Image) Resource1.butterfly;
   pozx = pictureBox1.Width / 2;
   pozy = pictureBox1.Height / 2;
  }

Для перемещения объекта мышью понадобится совместная обработка нескольких событий от мыши для компоненты pictureBox1.

По нажатию кнопки мыши будем включать режим перетаскивания картинки, а по отпусканию - выключать:

  private void pictureBox1_MouseDown (object sender, MouseEventArgs e) {
   i_mouse = true;
  }
  private void pictureBox1_MouseUp (object sender, MouseEventArgs e) {
   i_mouse = false;
  }

Обработчик события MouseMove выполнит основную работу:

  private void pictureBox1_MouseMove (object sender, MouseEventArgs e) {
   int mouseX = e.Location.X, mouseY = e.Location.Y;
   if (i_mouse == true) {
    if (pictureBox1.Left < 0) { pictureBox1.Left = 0; return; }
    else if (pictureBox1.Left + pictureBox1.Width > this.ClientRectangle.Width ) {
     pictureBox1.Left = this.ClientRectangle.Width - pictureBox1.Width; return;
    }
    if (pictureBox1.Top < 0) { pictureBox1.Top = 0; return; }
    else if (pictureBox1.Top + pictureBox1.Height > this.ClientRectangle.Height) {
     pictureBox1.Top = this.ClientRectangle.Height - pictureBox1.Height; return;
    }
    if (Math.Abs (mouseX - pozx) >= 5 || Math.Abs (mouseY - pozy) >= 5) {
     pictureBox1.Left += mouseX - pictureBox1.Width / 2;
     pictureBox1.Top += mouseY - pictureBox1.Height / 2;
     pozx = mouseX; pozy = mouseY;
    }
   }
  }

"Допуск" в 5 пикселей позволяет картинке быть не слишком чувствительной к смещению мыши.

Аналогично можно реализовать перетаскивание группы объектов, например, поместив их в контейнер System.Collections.Generic.List и программно назначив всем картинкам списка одни и те же обработчики событий мыши. Обработчики событий смогут различать, от какой именно картинки "пришло" событие, например, с помощью следующего кода:

   String s = ((PictureBox) sender).Name.ToString ();
    //вернёт имя компоненты, например, "pictureBox1"

 Скачать пример Lab6_5 в архиве .zip с проектом C# Visual Studio 2019 (154 Кб)

Проект Lab6_6. Картинка двигается по форме нажатием клавиш со стрелками.

Для формы создан файл ресурсов с рисунком, как в проектах Lab6_4 и Lab6_5.

Начнём как и в прошлый раз, в пустом проекте Windows Forms к классу формы добавлены следующие поля:

  Image img;      //картинка
  Point imgPoint; //точка вывода картинки
  Keys  keyCode;  //направление движения (код клавиши)
  Timer timer1;   //таймер

В обработчике события формы Load они проинициализированы и создаётся таймер, которому программно назначается обработчик его события:

  private void Form1_Load (object sender, EventArgs e) {
   this.DoubleBuffered = true;
   Img = (Image) Resource1.butterfly;
   imgPoint = new Point (0, 0);
   timer1 = Timer ();
   timer1.Interval = 5;
   timer1.Enabled = true;
   timer1.Tick += new EventHandler (timer1_Tick);
  }
 }
 private void timer1_Tick (Object sender, EventArgs e) {
  //добавили этот метод "вручную", а не из конструктора
  this.Invalidate (); //запросить перерисовку
 }

На отрисовку теперь будем менять координаты картинки и затем уже обновлять форму:

  private void Form1_Paint (object sender, PaintEventArgs e) {
   switch (keyCode) {
   case Keys.Right:
    if (imgPoint.X + Img.Width < this.ClientSize.Width) imgPoint.X++;
    break;
   case Keys.Left:
    if (imgPoint.X > 0) imgPoint.X--;
   break;
   case Keys.Up:
    if (imgPoint.Y > 0) imgPoint.Y--;
   break;
   case Keys.Down:
    if (imgPoint.Y + Img.Height < this.ClientSize.Height) imgPoint.Y++;
   break;
   }
   e.Graphics.DrawImage (Img, imgPoint);
  }

По нажатию клавиш со стрелками будем запоминать, что нажато, в поле keyCode, чтобы при следующей отрисовке поменять координаты картинки на экране:

  private void Form1_KeyDown (object sender, KeyEventArgs e) {
   switch (e.KeyCode) {
   case Keys.Right:
   case Keys.Left:
   case Keys.Up:
   case Keys.Down:
    keyCode = e.KeyCode;
   break;
   //другие клавиши игнорируются
   }
  }

Разрешённые клавиши можно нажимать и "во время движения" объекта, он изменит своё направление, если это возможно.

 Скачать пример Lab6_6 в архиве .zip с проектом C# Visual Studio 2019 (154 Кб)

Проект Lab6_7. Анимация на основе ImageList и таймера.

Как и в предыдущих проектах, добавим файл ресурсов с изображениями 1.png, ..., 6.png из папки проекта, обозначающими фазы движения. Им будут назначены имена _1, ..., _6.

Опишем в классе формы необходимые данные:

  int index, count;       //номер картинки и число картинок
  ImageList imageList1;   //компонента для списка картинок
  PictureBox pictureBox1; //компонента для вывода одной картинки
  Timer timer1;           //таймер

Запрограммируем событие Load загрузки формы на заполнение списка картинками и настройку PictureBox и запуск таймера:

  private void Form1_Load (object sender, EventArgs e) {
   imageList1 = new ImageList ();
   index = 0;
   count = 6;
   for (int i = 1; i <= count; i++) {
    String name = "_" + i;
    imageList1.Images.Add ((Image) Resource1.ResourceManager.GetObject (name));
   }
   pictureBox1 = new PictureBox ();
   pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
   pictureBox1.Size = new Size (imageList1.Images[0].Width*4, imageList1.Images[0].Height*4);
    //предполагаем, что все картинки одинаковы по размеру
   pictureBox1.Image = imageList1.Images[index];
   this.Location = new Point (0, 0);
   this.Controls.Add (pictureBox1);
   timer1 = new Timer ();
   timer1.Interval = 100;
   timer1.Enabled = true;
   timer1.Tick += (sendr, args) => {
    index++;
    if (index == count) index = 0;
    pictureBox1.Image = imageList1.Images[index];
   };
  }

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

 Скачать пример Lab6_7 в архиве .zip с проектом C# Visual Studio 2019 (46 Кб)

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

05.04.2023, 19:33 [2095 просмотров]


теги: время c# программирование графика учебное

К этой статье пока нет комментариев, Ваш будет первым