Опубликован: 10.03.2009 | Доступ: свободный | Студентов: 2295 / 280 | Оценка: 4.31 / 4.07 | Длительность: 09:23:00
Лекция 5:

Разработка полноценных Windows-приложений

4. Построение интерполяционного полинома

За построение и хранение интерполяционного полинома отвечает объект класса документа. Интерполяционный полином является объектом класса TPolinom. Это шаблонный класс, инкапсулирующий алгебраические полиномы. Этот класс вынесен в файл TPolinom.h, который необходимо скопировать в каталог с проектом и добавить в проект с помощью утилиты Solution Explorer (вызвать контекстное меню проекта в окне утилиты Solution Explorer \to Add \to 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.2. Задание узловых точек

то в результате получим соответствующий график интерполяционного полинома: рис. 5.3

График интерполяционного полинома

Рис. 5.3. График интерполяционного полинома