БлогNot. Visual C++: построение графиков с интерпретацией введённой пользователем функции

Помощь дата->рейтинг Поиск Почта RSS канал Статистика nickolay.info Домой

Visual C++: построение графиков с интерпретацией введённой пользователем функции

В принципе, вся нужная информация есть вот здесь, сделаем по ней законченный проект.

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

основная форма приложения
основная форма приложения

Текстовым полям можно ограничить максимальный размер вводимой строки (свойство MaxLength). Также panel1 расположена со свойством Dock=Top, а chart1 со свойством Dock=Fill. Это обеспечит нормальное взаимодействие компонент при изменении размеров окна. У самой формы выставлены Size и MinimumSize в значение 640; 400 - чтобы не "исчезали" кнопки при уменьшении окна.

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

Как альтернатива, можно формировать вещественные значения полей динамически в зависимости от текущего разделителя (например, по событию Load формы 1):

double x1=3.14;
this->x1->Text = x1.ToString();

В форму также добавлено глобальное свойство типа NumberFormatInfo

	public:
		System::Globalization::NumberFormatInfo ^ nfi;

которое проинициализировано в её конструкторе:

		Form1(void)
		{
			InitializeComponent();
			nfi = gcnew System::Globalization::NumberFormatInfo();
			nfi->NumberDecimalSeparator = "."; //"принудительная" точка разделителем целой и дробной части
			//
			//TODO: добавьте код конструктора
			//
		}

Основная работа выполняется по нажатию на кнопку OK (button1_Click). Сначала проверяем допустимость введённых данных с помощью пары служебных методов Parse (получить число) и Check (проверить правильность записи функции, попробовав получить её значение от 1-го аргумента). Потом метод Go делает цикл по нужным значениям аргумента, формируя диаграмму. Если возникает ошибка парсера, о ней выводится сообщение, но программа не завершается. Просто в данных не будет какой-то пары значений.

Парсер тот же, что по ссылке выше. Вот полный код фрагмента:

#pragma endregion

private: bool Parse (String ^s, double &a) {
 System::IFormatProvider ^ provider = System::Globalization::CultureInfo::GetCultureInfo("en-US");
 bool A = Double::TryParse(s,System::Globalization::NumberStyles::Number, provider,a);
 return A;
}

private: bool Check (String ^Str,double x1) {
 TParser *parser = new TParser();
 try {
  double x=x1;
  Str=Str->Replace("x",x.ToString(nfi));
  char *p = (char*) (Runtime::InteropServices::Marshal::StringToHGlobalAnsi (Str)).ToPointer ();
  parser->Compile(p);
  parser->Evaluate();
  return true;      
 }
 catch (...) {
  return false;
 }
}

private: void Go (String ^S,double x1, double x2, double dX) {
 TParser *parser = new TParser();
 using namespace System::Windows::Forms::DataVisualization::Charting;
 using namespace System::Collections::Generic; 
 using namespace System::Drawing::Drawing2D;
 using namespace System::Drawing;
 using namespace Runtime::InteropServices;
 Dictionary <double, double> f1 = gcnew Dictionary<double, double>();
 String ^ Str;
 double x;
 char *p;
 chart1->Series[0]->ChartType = SeriesChartType::Line;
 chart1->Series[0]->MarkerStyle = MarkerStyle::Circle;
 ArrayList points = gcnew ArrayList();
 
 try {
  for (x=x1; x<=x2; x+=dX) {
   Str=S->Replace("x",x.ToString(nfi));
   p = (char*) (Marshal::StringToHGlobalAnsi(Str)).ToPointer();
   parser->Compile(p);
   parser->Evaluate();
   f1.Add(x, parser->GetResult()); 
  }
 }
 catch(TError error) {
  System::String ^ str1 = gcnew System::String (error.error);
  System::String ^ str2 = gcnew System::String (error.pos.ToString());
  MessageBox::Show ("Ошибка " + str1 +  " в позиции строки разбора " + str2+" для x = "+x,
   "Ошибка парсера",MessageBoxButtons::OK);
  //return;
 }
 chart1->Series[0]->LegendText = S;
 chart1->Series[0]->Color = System::Drawing::Color::Green;
 chart1->Series[0]->BorderWidth = 2;
 chart1->Series[0]->Points->DataBindXY(f1.Keys, f1.Values);
}

private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
 double x1,x2,dX; String ^ s = textBox1->Text;
 this->chart1->Visible=false;
 if (Parse(this->x1->Text,x1)==false ||
	 Parse(this->x2->Text,x2)==false ||
	 Parse(this->dX->Text,dX)==false) {
  MessageBox::Show ("Введите числовые значения x1,x2,dx","Ошибка",MessageBoxButtons::OK);
  return;
 }
 if (x1>x2 || x1+dX>x2) {
  MessageBox::Show ("Введите x1<x1+dX<x2","Ошибка",MessageBoxButtons::OK);
  return;
 }
 this->chart1->Visible=true;
 if (!Check (s,x1)) {
  MessageBox::Show ("Введите верную функцию, не могу взять значение от 1-го аргумента",
   "Ошибка",MessageBoxButtons::OK);
  return;
 }
 Go (s, x1, x2, dX);
}

private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
 Form2 ^F2 = gcnew Form2;
 using namespace System::Windows::Forms::DataVisualization::Charting;
 for each (DataPoint ^p in chart1->Series[0]->Points) { 
  F2->Do (p->XValue, p->YValues[0],nfi);
 }
 F2->Show();
}

Единственная новая по отношению к статье мелочь -

Если национальные стандарты предполагают, что дробная часть вещественного числа отделяется от целой запятой, а не точкой, вместо оператора

Str=Str->Replace("x",x.ToString());

используйте конструкцию

System::Globalization::NumberFormatInfo ^ nfi = gcnew System::Globalization::NumberFormatInfo();
nfi->NumberDecimalSeparator = "."; 
//для C++ нужна "принудительная" точка разделителем целой и дробной части
//...
Str=S->Replace("x",x.ToString(nfi));

Добавим в проект вторую форму, куда можно будет выводить таблицы данных из диаграммы. Для этого обратимся к меню Проект - Добавить новый элемент - Форма Windows Forms и назовём её Form2. На вторую форму добавим DataGridView, поставим ему свойства Dock=Fill, ScrollBars=Vertical и подготовим 2 столбца для вывода значений X и Y:

вторая форма - вывод таблицы значений функции
вторая форма - вывод таблицы значений функции

У этой формы будет единственный публичный метод - принять пару значений (x,y) и добавить их в таблицу:

#pragma endregion

public: void Do (double x, double y, System::Globalization::NumberFormatInfo ^ nfi) {
 dataGridView1->Rows->Add(1);
 int i=dataGridView1->RowCount-1;
 dataGridView1->Rows[i]->Cells[0]->Value = Math::Round(x,3).ToString(nfi);
 dataGridView1->Rows[i]->Cells[1]->Value = Math::Round(y,3).ToString(nfi);
}

Такой код метода Do работает при установке свойства

dataGridView1->AllowUserToAddRows = false

так как при значении true в таблице есть "дополнительная" пустая строка, которая тоже участвует в нумерации.

А вызывать этот метод будет вторая кнопка tab с первой формы (функция button2_Click), при этом, сначала создастся новый экземпляр Form2, чтобы можно было сравнить несколько таблиц:

private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
 Form2 ^F2 = gcnew Form2;
 using namespace System::Windows::Forms::DataVisualization::Charting;
 for each (DataPoint ^p in chart1->Series[0]->Points) { 
  F2->Do (p->XValue, p->YValues[0]);
 }
 F2->Show();
}

Чтобы это сработало, заинклудьте заголовки второй формы в начале кода Form1.h:

#pragma once
#include "parser.h"
#include "Form2.h"
namespace Lab4 {
 //...

Разумеется, сам парсер тоже подлючён. Это весь проект, можно собирать. Вот пример работы программы:

пример работы программы
пример работы программы

Выражения в парсере пишутся "не совсем на C++", загляните в файл parser.cpp и увидите это, ещё лучше, можете модифицировать код парсера под свои нужды. Ну и ещё много что можно улучшить, а я выложу проект в текущем "образовательном" состоянии.

 Скачать этот проект Visual C++ в архиве .zip (21 Кб)

P.S. Для совместимости с Visual Studio 2015 достаточно сделать вот такой главный файл проекта Lab4.cpp:

// Lab4.cpp: главный файл проекта.
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "Form1.h"

using namespace Lab4;

[STAThreadAttribute]
int main(void)
{
	// Включение визуальных эффектов Windows XP до создания каких-либо элементов управления
	Application::EnableVisualStyles();
	Application::SetCompatibleTextRenderingDefault(false); 

	// Создание главного окна и его запуск
	Application::Run(gcnew Form1());
	return 0;
}

Самые очевидные улучшения:

Вот набросок чуть "улучшенного" проекта для Studio 2015:

 Скачать архив .zip с папкой этого проекта Visual Studio 2015 (21 Кб)


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

комментарии (1)

14.03.2015, 12:47; рейтинг: 16405

  свежие записипоиск по блогуоткомментироватьстатистика

Наверх Яндекс.Метрика
© PerS
вход