| Казахстан |
Разработка полноценных 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

