На этом шаге мы рассмотрим создание приложения для построения графика введенной пользователем функции.
Создадим приложение, которое строит графики с интерпретацией введенной пользователем функции.
Структура основной программы показана на рисунке 1, компоненты в panel1 перечислены по порядку в форме слева направо, что обеспечивает и нормальный порядок обхода полей по клавише табуляции.
Рис.1. Форма в режиме проектирования
Текстовым полям можно ограничить максимальный размер вводимой строки (свойство MaxLength). Также panel1 расположена со свойством Dock=Top, а chart1 со свойством Dock=Fill. Это обеспечит нормальное взаимодействие компонент при изменении размеров окна. У самой формы выставлены Size и MinimumSize в значение 640; 400 - чтобы не "исчезали" кнопки при уменьшении окна.
В форму также добавлено глобальное свойство типа NumberFormatInfo:
public:
System::Globalization::NumberFormatInfo ^ nfi;
Form1(void) { InitializeComponent(); nfi = gcnew System::Globalization::NumberFormatInfo(); //"принудительная" точка разделителем целой и дробной части nfi->NumberDecimalSeparator = "."; // //TODO: добавьте код конструктора // }
Основная работа выполняется по нажатию на кнопку OK (button1_Click). Сначала проверяем допустимость введённых данных с помощью пары служебных методов Parse (получить число) и Check (проверить правильность записи функции, попробовав получить её значение от 1-го аргумента). Потом метод Go делает цикл по нужным значениям аргумента, формируя диаграмму. Если возникает ошибка парсера, о ней выводится сообщение, но программа не завершается. Просто в данных не будет какой-то пары значений.
Парсер был взят отсюда: класс parser.cpp от Chaos Master. Вот полный код фрагмента:
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(); }
Добавим в проект вторую форму, куда можно будет выводить таблицы данных из диаграммы. Для этого обратимся к меню Проект | Добавить новый элемент | Форма Windows Forms и назовём её Form2. На вторую форму добавим DataGridView, поставим ему свойства Dock=Fill, ScrollBars=Vertical и подготовим 2 столбца для вывода значений X и Y:
Рис.2. Вторая форма в режиме редактирования
У этой формы будет единственный публичный метод - принять пару значений (x,y) и добавить их в таблицу:
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
А вызывать этот метод будет вторая кнопка 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], nfi); } F2->Show(); }
Чтобы это сработало, не забудьте подключить заголовки в начале кода Form1.h:
#include "parser.h" #include "Form2.h"
Также нужно добавить в приложение сам парсер. Для этого поместите файлы parser.cpp и parser.h в папку с кодом (туда, где находится файл Form1.h). Выполним пункт меню Проект | Существующий элемент... и добавим файл parser.cpp (он добавится. в раздел "Файлы исходного кода" Обозревателя решений).
Теперь в поле ввода можно писать любые допустимые парсером выражения с переменной x, например, cos(x)+1, текущее значение переменной x из программы подставится в выражение и его результат динамически подсчитается.
Результат выполнения приложения приведен на рисунке 3.
Рис.3. Результат работы приложения
На следующем шаге мы закончим изучение этого вопроса.