Разработка полноценных Windows-приложений
4. Построение интерполяционного полинома
За построение и хранение интерполяционного полинома отвечает объект класса документа. Интерполяционный полином является объектом класса TPolinom. Это шаблонный класс, инкапсулирующий алгебраические полиномы. Этот класс вынесен в файл TPolinom.h, который необходимо скопировать в каталог с проектом и добавить в проект с помощью утилиты Solution Explorer (вызвать контекстное меню проекта в окне утилиты Solution Explorer Add Existing item… и далее указать имя файла). Полный код класса приведен в приложении. Функция, объявленная в классе документа bool BuildPolinom(void) , строит полином по введенным узлам. Функция построения полинома:
bool CGpDoc::BuildPolinom(void) { InterPol = 0; if(InitPoints.size() <= 1) { AfxMessageBox("Error. Number of points is not enough."); return false; } else { for(size_t i = 0; i < InitPoints.size(); i++) { TPolinom <double> Lk(1); for(size_t j = 0; j < InitPoints.size(); j++) { if(i == j) continue; else { double initTp[] = {-InitPoints[j].x,1}; TPolinom <double> Tp(2, initTp); Lk = Lk*Tp; } } if(Lk(InitPoints[i].x)) InterPol = InterPol + Lk*(InitPoints[i].y/Lk(InitPoints[i].x)); else { AfxMessageBox("Incorrect initial points. Error."); return false; } } } return true; }
Построение происходит при нажатии на пункт меню Build and draw polinom или на соответствующую кнопку на панели инструментов. Обработчик будет разобран в следующем пункте.
5 Вывод данных
Выведем в окно приложения график полинома (в клиентскую область окна), коэффициенты полинома (в отдельное диалоговое окно), координаты курсора мыши (в строку состояния). Также следует добавить возможность сохранения изображения клиентской области окна в файл. За вывод информации отвечает класс вида и класс основного окна рамки. До того как будет отображен график полинома, необходимо нарисовать оси системы координат и отметить узлы интерполирования на плоскости. За это отвечают функции void DrawAxis(CDC* pDC) и void DrawInitPoints(CDC* pDC) ; Функция для рисования осей координат:
void CGpView::DrawAxis(CDC* pDC) { //Получаем клиентский прямоугольник CRect cr; GetClientRect(&cr); CFont *oldFont = pDC->SelectObject(&axisFont); //Загрузка в контекст усройства шрифта для подписи осей CPoint VisibleCenter = LogToSys(0,0); //Сохраняем системные координаты логического центра //Рисование оси Y pDC->MoveTo(VisibleCenter.x,cr.bottom); pDC->LineTo(VisibleCenter.x,0); //Рисование стрелки на конце pDC->LineTo(VisibleCenter.x-3,7); pDC->MoveTo(VisibleCenter.x,0); pDC->LineTo(VisibleCenter.x+3,7); //Рисование оси X pDC->MoveTo(0,VisibleCenter.y); pDC->LineTo(cr.right,VisibleCenter.y); //Рисование стрелки на конце pDC->LineTo(cr.right-7,VisibleCenter.y-3); pDC->MoveTo(cr.right,VisibleCenter.y); pDC->LineTo(cr.right-7,VisibleCenter.y+3); pDC->TextOutA(cr.right-7,VisibleCenter.y-19,"x",1); //Обозначение оси X pDC->TextOutA(VisibleCenter.x+7,0,"y",1); // Обозначение оси Y pDC->TextOutA(VisibleCenter.x+2,VisibleCenter.y-14,"0",1); // Обозначение начала координат //Разбиение в соответствии с масштабным коэффициентом for(int i = VisibleCenter.x; i < cr.right; i += ScaleXY) { pDC->MoveTo(i,VisibleCenter.y - 2); pDC->LineTo(i,VisibleCenter.y); } for(int i = VisibleCenter.x; i > 0; i -= ScaleXY) { pDC->MoveTo(i,VisibleCenter.y + 2); pDC->LineTo(i,VisibleCenter.y); } for(int i = VisibleCenter.y; i < cr.bottom; i += ScaleXY) { pDC->MoveTo(VisibleCenter.x + 2,i); pDC->LineTo(VisibleCenter.x,i); } for(int i = VisibleCenter.y; i > 0; i -= ScaleXY) { pDC->MoveTo(VisibleCenter.x - 2,i); pDC->LineTo(VisibleCenter.x,i); } pDC->SelectObject(oldFont); //Возврат шрифта по умолчанию в контекст усройства }
Функция для рисования узлов интерполирования:
void CGpView::DrawInitPoints(CDC* pDC) { //Получение указателя на документ CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; //Перебор всех узлов в векторе for(size_t i = 0; i < pDoc->InitPoints.size(); i++) { //Расстановка их на плоскости CPoint help = LogToSys(pDoc->InitPoints[i].x,pDoc->InitPoints[i].y); pDC->SetPixel(help,RGB(255,0,0)); pDC->SetPixel(help.x+1,help.y,RGB(255,0,0)); pDC->SetPixel(help.x-1,help.y,RGB(255,0,0)); pDC->SetPixel(help.x,help.y+1,RGB(255,0,0)); pDC->SetPixel(help.x,help.y-1,RGB(255,0,0)); } }
Функция для рисования графика полинома:
void CGpView::DrawPolinom(CDC* pDC) { //Получение указателя на документ CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; if(pDoc->pol_ready) //Если полином построен, то... { CPen *oldPen = pDC->SelectObject(&polPen); //Загрузка в контекст пера для рисования полинома //Получение клиентского прямоугольника CRect cr; GetClientRect(&cr); //Создание вспомогательных переменных double XStart = SysToLog(CPoint(0,0)).x; //Логическая координата X левого края клиентского прямоугольника double XEnd = SysToLog(CPoint(cr.right,0)).x; //Логическая координата X правого края клиентского прямоугольника double step = abs(XEnd-XStart)/static_cast<double>(cr.right); //Шаг для последовательного вычисления значений полинома pDC->MoveTo(LogToSys(XStart,pDoc->InterPol(XStart))); //Устанавка фокуса в начальное положение for(double i = XStart + step; i < XEnd; i += step) //Пока в пределах клиентского прямоугольника pDC->LineTo(LogToSys(i,pDoc->InterPol(i))); //Рисование линии к следующей точке pDC->SelectObject(oldPen); //Возврат стандартного пера в контекст усройства } else return; //Если полином не построен, то выход из функции }
Эти три функции обеспечивают вывод графической информации. Все они имеют один и тот же прототип, но различные имена. Каждая функция принимает в качестве аргумента указатель на контекст устройства CDC* pDC (таким образом, основная задача графического вывода разбивается на 3 более простых). Остается только последовательно вызвать их в методе класса вида OnDraw(CDC* pDC) . Функция OnDraw:
void CGpView::OnDraw(CDC* pDC) { DrawAxis(pDC); DrawInitPoints(pDC); DrawPolinom(pDC); }
Для запуска механизма построения и отображения полинома, необходимо обработать выбор соответствующего пункта меню и нажатие на кнопку на панели инструментов. Обработчик пункта меню Build and draw polynom и связанной с ним кнопки на панели инструментов (определен в классе документа):
void CGpDoc::OnToolsBuilddrawpolinom() { InterPol = 0; //Стираем старый полином if(BuildPolinom()) pol_ready = true;//Если полином построен удачно, то else pol_ready = false; //Устанавливаем флаг UpdateAllViews(NULL); //Сигнализируем виду о том, что документ изменился }
Далее необходимо осуществить вывод координат курсора в строку состояния. Для этого отредактируем класс основного окна рамки. Прототип класса основного окна рамки:
// MainFrm.h : interface of the CMainFrame class // #pragma once class CMainFrame : public CFrameWnd { protected: // create from serialization only CMainFrame(); DECLARE_DYNCREATE(CMainFrame) // Attributes public: // Operations public: // Overrides public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif public: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; // Generated message map functions protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); DECLARE_MESSAGE_MAP() public: afx_msg void OnViewStatusBar(); afx_msg void OnUpdateViewStatusBar(CCmdUI *pCmdUI); };
Жирным шрифтом отмечены изменения. Объекты члены m_wndStatusBar и m_wndToolBar необходимо объявить с модификатором public. В реализации класса основного окна рамки необходимо изменить состав массива indicators[] и функцию OnCreate(LPCREATESTRUCT lpCreateStruct) . Массив indicators:
static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_SEPARATOR, };
Функция инициализации основного окна рамки:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar\n"); return -1; // fail to create } if (!m_wndStatusBar.Create(this,WS_CHILD|WS_VISIBLE|CBRS_BOTTOM,ID_INFO_STATUS_BAR) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create } // TODO: Delete these three lines if you don't want the toolbar to be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); return 0; }
Также необходимо создать в ресурсах идентификатор с именем ID_INFO_STATUS_BAR для новой строки состояния и добавить обработчики для пункта меню Status Bar. Обработчик пункта меню Status Bar:
void CMainFrame::OnViewStatusBar() { m_wndStatusBar.ShowWindow(!(m_wndStatusBar.GetStyle() & WS_VISIBLE)); RecalcLayout(); }
Обработчик изменения внешнего вида пункта меню Status Bar:
void CMainFrame::OnUpdateViewStatusBar(CCmdUI *pCmdUI) { pCmdUI->SetCheck(m_wndStatusBar.GetStyle() & WS_VISIBLE); }
Вывод коэффициентов полинома организован с помощью диалогового окна. Создадим в ресурсах прообраз диалогового окна с расширенным текстовым полем RichEdit. Создадим класс на его основе. Прототип класса диалога для вывода коэффициентов интерполяционного полинома:
#pragma once // CShowkoefsDlg dialog class CShowkoefsDlg : public CDialog { DECLARE_DYNAMIC(CShowkoefsDlg) public: CShowkoefsDlg(CWnd* pParent = NULL); // standard constructor virtual ~CShowkoefsDlg(); // Dialog Data enum { IDD = IDD_SHOWKOEFS_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support DECLARE_MESSAGE_MAP() public: CString InitStr; public: // virtual BOOL OnInitDialog(); };
Переменная связана с текстовым полем, в нее будут записываться коэффициенты полинома. Чтобы использовать в программе элемент управления RichEdit, необходимо вызвать в методе InitInstance() класса приложения функцию AfxInitRichEdit(). Для вывода диалогового окна на экран, необходимо создать пункт меню Show polynomial coefficients и соответствующую кнопку на панели управления, и обработать нажатие на них. Обработчик пункта меню Show polynomial coefficients и связанной с ним кнопки на панели управления:
void CGpView::OnToolsShowpolinomialkoefficients() { CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; CShowkoefsDlg dlg; vector<CString> help = pDoc->InterPol.GetTPolinomString(); for(size_t i = 0; i < help.size(); i++) { CString t; t.Format("x^%d: ",i); dlg.InitStr += t + help[i] + CString("\n"); } dlg.DoModal(); }
Обработчик изменения внешнего вида пункта меню Mark initial points и связанной с ним кнопки на панели инструментов:
void CGpView::OnUpdateToolsShowpolinomialkoefficients(CCmdUI *pCmdUI) { CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; pCmdUI->Enable(pDoc->pol_ready); }
Для сохранения клиентской области (графика полинома) в файл, создадим обработчик OnFileSaveAs() в классе вида. Используем переменную imgOriginal типа CImage как вспомогательную для сохранения. Обработчик сохранения графика полинома:
void CGpView::OnFileSaveAs() { CRect clRect; //Переменная для сохранения размеров клиентской области CString strFilter; //Строка со списком поддерживаемых форматов CString strFileName; //Строка с путем и именем файла CString strExtension; //Строка расширения файла в который происходит сохранение strFilter = "Bitmap image|*.bmp|JPEG image|*.jpg|GIF image|*.gif|PNG image|*.png||"; CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter); //Диалог для получения пути и имени файла SetCapture(); //Захват мыши HCURSOR hcurs = SetCursor(LoadCursor(NULL,IDC_WAIT)); //Установка курсора в виде часов imgOriginal.Destroy(); //Очистка переменной imgOriginal GetClientRect(&clRect); //Получение размеров клиентского прямоугольника imgOriginal.Create(clRect.right,clRect.bottom,24); //Инициализация изображения в соответствии с размерами клиентского прямоугольника CDC* sourceDC = GetDC(); //Получение указателя на используемый контест устройства for(int i = 0; i < clRect.right; i++) //Копирование изображения из контекста в переменную imgOriginal for(int j = 0; j < clRect.bottom; j++) imgOriginal.SetPixel(i,j,sourceDC->GetPixel(i,j)); SetCursor(hcurs); //Возвращение предыдущего курсора ReleaseCapture(); //Освобождение мыши if (dlg.DoModal() == IDOK) //Если нажата кнопка OK ... { strFileName = dlg.m_ofn.lpstrFile; //Запись пути и имени файла в строку if (dlg.m_ofn.nFileExtension == 0) //Если имя правильное { switch (dlg.m_ofn.nFilterIndex) //В соответствии с указанным в диалоге расширением инициализация строки расширения { case 1: strExtension = "bmp"; break; case 2: strExtension = "jpg"; break; case 3: strExtension = "gif"; break; case 4: strExtension = "png"; break; default: break; } strFileName = strFileName + '.' + strExtension; //Инициализация полной строки с именем и расширением для сохранения } } else return; //Иначе выход из функции HRESULT hResult = imgOriginal.Save(strFileName); //Сохранение изображения if (FAILED(hResult)) //Если ошибка { CString fmt; //Строка с кодом ошибки fmt.Format("Save image failed:\n%x - %s", hResult, _com_error(hResult).ErrorMessage()); AfxMessageBox(fmt); //Вывод сообщения об ошибке return; } }Листинг 5.2.
Данная функция выполняет сохранение изображения клиентской области окна в файл в виде растрового изображения. Для сохранения используется метод Save(…) класса CImage. Для работы с этим классом необходимо подключить заголовочный файл atlimage.h к программе.
Все основные части программы написаны. Перед компиляцией добавим в файл stdafx.h строки:
#include <comdef.h> #include <atlimage.h> #include <vector> #include <cmath> #include <atlimage.h> #include "DoublePoint.h"
После строки
#include <afxdisp.h> // MFC Automation classes
И строку
using namespace std;
в конец файла. Добавим строки в файл GpView.cpp
#include "AddPointDlg.h" #include "ShowkoefsDlg.h"
После строки
#include "GpView.h"
Добавим строку в файл GpDoc.h
#include "TPolinom.h"
После строки
#pragma once
Скомпилируем и запустим приложение. Если взять в качестве узлов следующие точки (указаны на рисунке): рис. 5.2
то в результате получим соответствующий график интерполяционного полинома: рис. 5.3