БлогNot. Visual C++/CLI: рисуем простой график функции в PictureBox

Visual C++/CLI: рисуем простой график функции в PictureBox

В этом примере мы хотим нарисовать простой график на компоненте PictureBox "по точкам", а не с использованием готовой компоненты Chart.

Создав проект Windows Forms в любой версии Studio 2012 и выше, придадим окну формы удобный размер и перетащим туда компоненту TableLayoutPanel, установим ей свойство Dock=Fill, затем удалим последнюю строку так, чтобы осталось 2 столбца, первому столбцу зададим относительную ширину 100%, а второму - абсолютную ширину 100 пикселов (как выполнять такие действия, показано в этой заметке).

В правую ячейку TableLayoutPanel перетащим кнопку Button и привяжем её ко всем краям формы, кроме нижнего (свойство Anchor = Top, Left, Right, свойство Dock=Top).

В левую ячейку перетащим PictureBox и также установим ему Dock=Fill. Внешний вид полученной формы показан на рисунке:

Вид формы приложения
Вид формы приложения

Так как экранный шаг в пикселах в условии не задан, установим его в переменной unit. Для порядка шаблон отрисовываемой функции double f(double x) опишем как делегат соответствующего типа. Весь этот код можно поместить после директивы #pragma endregion в файле MyForm.h:

#pragma endregion
 public: int unit = 50; //шаг в пискелах
 public: delegate double DelegatePtr(double); //тип функции для рисования

 private:  double f(double x) { //конкретная функция, которую рисуем
  double y=Math::Cos(Math::Sqrt(x));
  return Double::IsNaN(y)?0:y;
 }

Нам придётся запрограммировать только щелчок по кнопке, после двойного щелчка по ней в конструкторе создастся обработчик события
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
}

в который мы и поместим весь показанный ниже код с комментариями.

private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
  int pW = pictureBox1->Width, pH = pictureBox1->Height;
  Bitmap ^img = gcnew Bitmap(pW, pH); 
   //создаем поверхность для рисования (изображение) с размером элемента управления PictureBox
  Graphics ^g = Graphics::FromImage(img); //создаем устройство для рисования на поверхности
  //рисуем сетку:
  for (int i = 0; i < pW; i += unit) g->DrawLine(Pens::Blue, i, 0, i, pH);
  for (int i = 0; i < pH; i += unit)  g->DrawLine(Pens::Blue, 0, i, pW, i);
  //находим середину и рисуем линии осей:
  int mX = int(pW / 2 - pW / 2 % unit);
  int mY = int(pH / 2 - pH / 2 % unit);
  g->DrawLine(Pens::Red, mX, 0, mX, pH);
  g->DrawLine(Pens::Red, 0, mY, pW, mY);
  g->ScaleTransform(1, -1); //переворачиваем ось Y для удобства восприятия
  g->TranslateTransform((float)mX, -(float)mY); //смещаем нулевую координату на пересечение осей
  //рисуем график:
  DelegatePtr^ f = gcnew DelegatePtr(this, &MyForm::f);
  double x1 = -1., x2 = 10., s = 0.25; //границы рисования
  double x = x1;
  double y;
  System::Collections::Generic::List<PointF> ^Points = 
   gcnew System::Collections::Generic::List<PointF>(); //коллекция точек графика
  while (x < x2) {
   y = f(x);
   Points->Add(PointF(x*unit, y*unit));
    //добавляем точку в коллекцию. Полученные координаты сразу переводим в экранные единицы
   x += s;
  }
  g->DrawLines(Pens::Green, Points->ToArray()); //рисование линий графика
  delete g; //освобождение ресурсов устройства рисования
  this->pictureBox1->Image = img; //присвоение и отображение изображения в PictureBox
}

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

 Скачать этот проект Visual Studio 2015 в архиве .zip, развернуть, не создавая новой папки (7 Кб)

Можно подойти к задаче и по-другому, заранее задав интервал изменения переменной по оси X, а шаг по ней (как и по оси Y) рассчитывая так, чтобы он был равен одному пикселу. Тогда при той же самой форме пользовательская часть кода примет следующий вид (для простоты исключено рисование линий сетки).

#pragma endregion 
 /* простой график на PictureBox, шаг по x = 1 пикселу */

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

 private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
  int pw = pictureBox1->Width, ph = pictureBox1->Height; //размеры полотна
  Function ^f = gcnew Function(this, &MyForm::f); //рисуемая функция
  xstep = (x2 - x1) / pw; //шаг по x = 1 пикселу
  y1 = y2 = f(x1);
  for (double x = x1; x <= x2; x += xstep) { //ищем макс. и мин. по y
   double 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);
   //коэффициенты пересчета физических координат в пиксельные
  Bitmap ^img = gcnew Bitmap(pw,ph); //картинка для рисования на ней
  Graphics ^g = Graphics::FromImage (img); //контекст картинки
  int mx = - x1 * xcoeff, my = - y1 * ycoeff; //начало координат
  g->DrawLine(Pens::Red,mx,0,mx,ph);g->DrawLine(Pens::Red,0,my,pw,my); //оси
  System::Collections::Generic::List <PointF> ^Points = 
   gcnew System::Collections::Generic::List <PointF>(); //коллекция точек
  double x = x1, y;
  while (x <= x2) { y = f(x); Points->Add(PointF((x-x1)*xcoeff,(y2-y)*ycoeff)); x += xstep;  }
  g->DrawLines(Pens::Green,Points->ToArray()); //отрисовка
  this->pictureBox1->Image = img; //назначили картинку полотну
 }
Вид формы приложения (2)
Вид формы приложения (2)

10.11.2017, 13:12 [20039 просмотров]


теги: учебное графика c++/cli

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