В данном параграфе на примере написания приложения "
Пусть у нас есть следующая таблица:
$$\begin{tabular}{|r||r||r||r|} \hline X x_1 ... x_n\\ \hline Y y_1 ... y_n\\ \hline \end{tabular} $$ $$\{x_1,...,x_n,y_1,...,y_n\} $$Требуется построить полином y=f(x), принимающий значения согласно таблице. Интерполяционный полином - полином, принимающий значения y в точках x согласно указанной таблице. Значения x называются
Существуют разные алгоритмы построения интерполяционного полинома, мы воспользуемся методом Лагранжа. Данный метод дает аналитическое представление интерполяционного полинома в следующем виде:
$$f(x)=\sum_{j=1}^n \frac{y_j \cdot W_n(x)}{(x-x_j) \cdot W_n^l(x_j)} $$ $$W_n(x)=(x-x_1) \cdot ... \cdot (x-x_n) $$Для построения интерполяционного полинома напишем Windows приложение с графическим интерфейсом, удовлетворяющим следующим требованиям:
Согласно требованиям, наша программа должна осуществлять полноценный графический вывод информации на экран. При этом возникает проблема аппаратно-независимого вывода изображения. Каждое Windows окно имеет собственную стандартную (аппаратную) систему координат.
Стандартная (аппаратная) система координат - левая система координат с центром в левом верхнем углу окна. Ось X направлена вправо, а ось Y вниз. Единица измерения - 1 пиксель.
Стандартные координаты аппаратно зависимы. А это значит, что на разных мониторах и принтерах изображения размером n?n пикселей будут иметь различные размеры, в зависимости от размеров пикселей поддерживаемых устройством вывода. Аппаратная зависимость координат также не позволяет выводить на экран изображения, размеры которых превосходят размеры клиентской области окна. Для преодоления аппаратной зависимости необходимо выполнить преобразование координат.
Принципы построения собственных функций преобразования координат
Чтобы реализовать аппаратно независимый вывод графики, можно ввести 2 логические системы координат: видимую и невидимую и организовать переход от одной к другой. Видимая система координат - основная, она и определяет логические координаты точек. Невидимая система координат - вводится для реализации точки наблюдения. рис 5.1
(рис 5.1) Экранная система координатГеометрический смысл преобразования. Переход от стандартной системы координат к видимой логической выполняется для преодоления проблемы несоответствия размеров пикселей на разных устройствах. А переход от видимой логической системы координат к невидимой системе координат необходим, если требуется отобразить ту часть изображения, которая не помещается в окне.
Математическая реализация. Пусть (a,b) - координаты центра видимой логической системы координат в стандартной системе координат, а (c,d) - координаты точки наблюдения в видимой логической системе координат. Тогда формула для выполнения нужного перехода:
S - коэффициент масштабирования (он выбирается в зависимости от характеристик монитора). Теперь, пользуясь этой формулой, можно, зная логические координаты, переходить к системным координатам, что необходимо для рисования. А так же, изменяя координаты точки наблюдения, отображать невидимые ранее части образа.
Нетрудно получить и обратное выражение логических координат через системные координаты. Это нужно, чтобы выводить информацию о положении на плоскости.
Этапы разработки
Создадим с помощью мастера MFC стандартное
1. Функции перехода от логических координат к системным координатам и наоборот
Логические координаты на плоскости представляют собой упорядоченную пару вещественных чисел. Для их программной реализации следует ввести структуру SDPoint. Вынесем ее в новый заголовочный файл (например, DoublePoint.h )
struct SDPoint
{
double x;
double y;
};
Далее приведен полный исходный код класса вида CGpView программы. Пояснения ко всем введенным переменным, функциям и обработчикам приведены в пунктах, при реализации которых использовались эти элементы. В рамках данного пункта рассмотрим функции перехода от системных координат к логическим координатам, выделенные жирным шрифтом.
// GpView.h : interface of the CGpView class
#pragma once
class CGpView : public CView
{
protected: // create from serialization only
CGpView();
DECLARE_DYNCREATE(CGpView)
// Attributes
public:
CGpDoc* GetDocument() const;
// Operations
public:
// Overrides
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT cs);
protected:
// Implementation
public:
virtual ~CGpView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext dc) const;
#endif
protected:
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
private: //Добавляем необходимые переменные
int ScaleXY; //Масштабный коэффициент (сколько пикселей в логической единице)
SDPoint CameraPoint; //Логические координаты точки наблюдения
bool parallel_shift; //флаг для входа(выхода) в(из) режим(а) параллельного переноса
bool move_camera; //флаг для активации параллельного переноса
CPoint MousePosPoint; //Системные координаты курсора мыши
CFont axisFont; //Шрифт для подписи осей
CPen polPen; //Перо для рисования графика полинома
bool mark_points; //Флаг для входа(выхода) в(из) режим(а) визуального
редактирования узлов интерполирования
CImage imgOriginal; //Переменная для сохранения изображения клиентской области окна (графика)
public: //Необходимые функции
SDPoint SysToLog(CPoint SysPoint); //Функция перехода от системных к логическим координатам
CPoint LogToSys(double xl, double yl); //Функция перехода от логических коодинат к системным
void DrawAxis(CDC* pDC); //Функция для прорисовки осей
void DrawPolinom(CDC* pDC); //Функция для прорисовки полинома
void DrawInitPoints(CDC* pDC); //Функция для прорисовки узлов интерполяции
public: //Обработчики (добавлены с помощью мастера)
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnToolsParallelshift();
afx_msg void OnUpdateToolsParallelshift(CCmdUI *pCmdUI);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg void OnToolsAddpoint();
afx_msg void OnToolsMarkinitialpoints();
afx_msg void OnUpdateToolsMarkinitialpoints(CCmdUI *pCmdUI);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
afx_msg void OnToolsShowpolinomialkoefficients();
afx_msg void OnUpdateToolsShowpolinomialkoefficients(CCmdUI *pCmdUI);
afx_msg void OnFileSaveAs();
};
#ifndef _DEBUG // debug version in GpView.cpp
inline CGpDoc* CGpView::GetDocument() const
{ return reinterpret_cast<CGpDoc*>(m_pDocument); }
#endif
Функция перехода от системных координат к логическим координатам:
SDPoint CGpView::SysToLog(CPoint SysPoint)
{
CRect cr;
GetClientRect(cr);
SDPoint Res;
Res.x = (static_cast<double>(SysPoint.x) - static_cast<double>(cr.right)/2)/ScaleXY - CameraPoint.x;
Res.y = (static_cast<double>(cr.bottom)/2 - static_cast<double>(SysPoint.y))/ScaleXY - CameraPoint.y;
return Res;
}
Функция перехода от логических координат к системным координатам:
CPoint CGpView::LogToSys(double xl, double yl)
{
CRect cr;
GetClientRect(cr);
CPoint Res;
Res.x = static_cast<int>((xl + CameraPoint.x)*ScaleXY + cr.right/2);
Res.y = static_cast<int>(-(yl + CameraPoint.y)*ScaleXY + cr.bottom/2);
return Res;
}
CameraPoint - переменная для хранения координат точки наблюдения типа SDPoint.
Функция GetClientRect(cr) используется для получения клиентского прямоугольника. ScaleXY - переменная типа int, которая хранит масштаб (число пикселей в логической единице). Ее значение может быть задано произвольно, а может быть связано с характеристиками монитора с помощью соответствующих функций (например, int GetDeviceCaps(. Это основные функции, которые обеспечивают правильную работу программы в дальнейшем.
2. Параллельный перенос и масштабирование плоскости
Создадим пункт меню Tools (инструменты). В нем создадим подпункт Parallel Shift (параллельный перенос) и связанную с ним кнопку на панели инструментов, которые переводят программу в режим ожидания параллельного переноса или выводят ее из него. Если программа находится в режиме ожидания параллельного переноса, то нажатие левой кнопки активирует его и программа начинает обрабатывать событие WM_MOUSEMOVE (движение мыши). В соответствии с перемещением курсора, перемещается точка наблюдения. Отпускание левой кнопки мыши деактивирует перенос и возвращает программу в режим ожидания. В данном пункте необходимо добавить обработчики движения мыши, нажатия левой кнопки мыши и ее отпускания.
Обработчик движения мыши (важные для этого пункта строки выделены жирным шрифтом):
void CGpView::OnMouseMove(UINT nFlags, CPoint point)
{
CRect cr; //Переменная для хранения размеров клиентского прямоугольника
CString str; //Строка для записи и вывода координат курсора
GetClientRect(cr); //Получение размеров клиентского прямоугольника
CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Указатель на
основную рамку (необходимо для обращения к строке состояния)
CStatusBar *pStatus = pFrame->m_wndStatusBar; //Указатель на строку состояния
(будет отображать координаты курсора)
if(cr.PtInRect(point)) //Если курсор в клиентском прямоугольнике то...
{
SetCapture(); //Захватываем мышь
if(parallel_shift) //Если в режиме ожидания параллельного переноса то...
if(move_camera) //Если перенос актвирован
{
//Сдвигается точка наблюдения на разность между текущим и предыдущим положениями курсора
CameraPoint.x += (static_cast<double>(point.x) - static_cast<double>(MousePosPoint.x))/ScaleXY;
CameraPoint.y -= (static_cast<double>(point.y) - static_cast<double>(MousePosPoint.y))/ScaleXY;
Invalidate(); //Обновление клиентсой области
MousePosPoint = point; //Сохранение положения курсора
}
else SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Если перенос
не активирован то устанавливается нужный курсор
if(mark_points) SetCursor(LoadCursor(NULL,IDC_CROSS)); //Если в режиме визуального редактирования
то устанавливается соответствующий курсор
}
else //Если курсор вне клиентского прямоугольника то...
{
ReleaseCapture(); //Освобождается мышь
move_camera = false; //Деактивируется перенос
}
if(pStatus) //Если указатель на строку состояния верный то...
{
str.Format("x = %f", SysToLog(point).x); //Запись во вспомогательную строку координаты x
pStatus->SetPaneText(0,str); //Вывод в строку состояния
str.Format("y = %f", SysToLog(point).y); //Запись во вспомогательную строку координаты y
pStatus->SetPaneText(1,str); //Вывод в строку соостояния
}
CView::OnMouseMove(nFlags, point);
}
MousePosPoint - это переменная типа CPoint, которая служит для хранения координат курсора и объявлена в прототипе класса CGpView с модификатором private. С тем же модификатором объявлены переменные parallel_shift и move_camera типа bool. Первая переменная сигнализирует, нажата ли кнопка параллельного переноса, а вторая - активирован ли перенос (нажата или отпущена левая кнопка мыши). Функция SetCapture() перенаправляет все сообщения мыши на обработку окну, которое ее вызвало. Функция ReleaseCapture() восстанавливает стандартный путь сообщений мыши в операционной системе. Функция SetCursor(...) меняет изображение курсора. Функция LoadCursorA(...), член класса приложения, загружает курсор из ресурсов приложения, вызвавшего ее, и возвращает его описатель, в качестве
входного параметра требует его идентификатор. Функция AfxGetApp() возвращает указатель на экземпляр приложения.
Обработчик нажатия левой кнопки мыши:
void CGpView::OnLButtonDown(UINT nFlags, CPoint point) //Обработчик нажатия левой кнопки мыши
{
CRect cr; //Переменная для хранения размеров клиентского прямоугольника
GetClientRect(cr); //Получение размеров клиентского прямоугольника
if(parallel_shift cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник...
{
MousePosPoint = point; //Сохранение координат нажатия
move_camera = true; //Активация переноса
SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_FIST)); //Устанавливается соответствующий курсор
}
CView::OnLButtonDown(nFlags, point);
}
Обработчик отпускания левой кнопки мыши:
void CGpView::OnLButtonUp(UINT nFlags, CPoint point) //Обработчик отпускания левой кнопки мыши
{
CRect cr; //Переменная для хранения размеров клиентского прямоугольника
GetClientRect(cr); //Получение размеров клиентского прямоугольника
if(parallel_shift cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник ...
{
move_camera = false; //Деактивация переноса
SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Устанавливается соответствующий курсор
}
CView::OnLButtonUp(nFlags, point);
}
Для корректной работы программы создадим и отредактируем соответствующие обработчики для кнопки Parallel Shift. В рамках данного пункта важны строки, выделенные жирным шрифтом. Обработчик пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов:
void CGpView::OnToolsParallelshift()
{
mark_points = false; //Если в режиме визуального редактирования узлов то выход из него
parallel_shift = !parallel_shift; //Вход или выход в режим ожидания переноса
}
Обработчик изменения внешнего вида пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов:
void CGpView::OnUpdateToolsParallelshift(CCmdUI *pCmdUI) //Обрабатывает изменение внешнего вида кнопки (пункта меню)
{
pCmdUI->SetCheck(parallel_shift); //Смена статуса (активна или не активна) в зависимости от флага
}
Для масштабирования следует обрабатывать прокрутку колеса мыши. В зависимости от направления вращения будет меняться масштабный коэффициент. Обработчик колесика мыши:
BOOL CGpView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
CString str("Updating coordinates. Please move cursor."); //Информативная строка
CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Получение указатель на основную рамку
CStatusBar *pStatus = pFrame->m_wndStatusBar; //Получение указатель на строку состояния
if(zDelta > 0) {if(ScaleXY < 200) ScaleXY += 5;} //Если вращение вперед, то увеличивается массштабный коэффициент
else if(ScaleXY > 40) ScaleXY -= 5; //Если вращение назад, то уменьшается масштабный коэффициент
Invalidate(); //Перерисовываем
if(pStatus) //Если указатель на строку состояния истина, то
{
//Вывод информативной строки в строку состояния
pStatus->SetPaneText(0,str);
pStatus->SetPaneText(1,str);
}
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
3. Ввод начальных данных
Рассмотрим два способа ввода начальных данных: ввод точных данных с клавиатуры и визуальное редактирование таблицы узлов посредством мыши. Первый подход реализован с помощью создания и подключения к приложению диалогового окна с необходимыми элементами управления, а второй с помощью обработки двойного нажатия левой кнопки мыши. Также необходимо организовать хранение информации об узлах. Ввод данных с клавиатуры. Создадим ресурс диалогового окна с помощью редактора ресурсов. В диалоговом окне должны присутствовать 2 текстовых поля ввода (координаты x и y), 4 кнопки (Add, Delete, Ok, Cancel) и список для отображения таблицы узлов. После создания ресурса, добавим класс диалогового окна на его базе. Создадим подпункт меню Add/Delete Initial Points в пункте Tools и аналогичную кнопку на панели управления, при нажатии на которые будет появляться диалоговое окно.
Прототип класса диалогового окна для ввода узлов:
#pragma once
#include "afxwin.h"
// CAddPointDlg dialog
class CAddPointDlg : public CDialog
{
DECLARE_DYNAMIC(CAddPointDlg)
public:
CAddPointDlg(CWnd* pParent = NULL); // standard constructor
virtual ~CAddPointDlg();
// Dialog Data
enum { IDD = IDD_ADDPOINT_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public: //переменные связанные с элементами управления добавлены с помощью мастера
CListBox PointList; //Переменная связанная со списком
double ValX; //Переменная связанная с первым текстовым полем
double ValY; //Переменная связанная со вторым текстовым полем
vector<SDPoint> ListedPointsVec; //Вектор узлов интерполирования (добавлена вручную)
public: //Обработчики и переопределенные виртуальные функции добавлены с помощью мастера
afx_msg void OnBnClickedButtonAdd(); //Обработчик кнопки Add
virtual BOOL OnInitDialog();
afx_msg void OnBnClickedButtonDelete(); //Обработчик кнопки Delete
};
Обработчик кнопки Add на диалоговом окне:
void CAddPointDlg::OnBnClickedButtonAdd()
{
CString str; //Вспомогательная строка для вывода узла в список
SDPoint temp; //Вспомогательная точка
UpdateData(true); //Обмен данными
temp.x = ValX; //Инициализация вспомогательной точки
temp.y = ValY;
for(size_t i = 0; i < ListedPointsVec.size(); i++) //Перебор всех узлов интерполяции
if(ListedPointsVec[i].x == ValX ListedPointsVec[i].y == ValY) //Если один из узлов совпал с вводимым то...
return; //выход из функции
ListedPointsVec.push_back(temp); //Добавление нового узла в вектор (проверка сделана, одинаковых нет)
str.Format("(%f;%f)",ValX,ValY); //Формирование строки для добавления в список
PointList.AddString(str); //Добавление строки в конец списка
}
Функция для инициализации переменных диалогового окна:
BOOL CAddPointDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CString str; //Вспомогательная строка для формирования списка
for(size_t i = 0; i < ListedPointsVec.size(); i++) //Перебор по всему вектору узлов
{
str.Format("(%f;%f)",ListedPointsVec[i].x,ListedPointsVec[i].y); //Формирование строки
PointList.AddString(str); //Добавление ее в список
}
return TRUE;
}
Обработчик кнопки Delete на диалоговом окне:
void CAddPointDlg::OnBnClickedButtonDelete()
{
int n = PointList.GetCurSel(); //Получение номера выделенной строки списка
if(n == LB_ERR) return; //Если ничего не выделено - выход
if(n == ListedPointsVec.size()) //Если выделена последняя строка, то...
ListedPointsVec.pop_back(); //удаление последнего элемента из таблицы
else //иначе...
{
for(size_t i = n; i < ListedPointsVec.size()-1; i++) //перебор узлов начиная с n-ого и до конца
{
//Смещение узлов в векторе
ListedPointsVec[i].x = ListedPointsVec[i+1].x;
ListedPointsVec[i].y = ListedPointsVec[i+1].y;
}
ListedPointsVec.pop_back(); //Удаление последнего элемента
}
PointList.DeleteString(n); //Удаление ненужней строки списка
}
Для корректной работы последней функции, в списке должна быть отключена сортировка. Когда диалоговый класс реализован, необходимо подключить его к программе. Для этого используем соответствующий пункт меню и кнопку на панели управления - Add/Delete Initial Points. Также необходимо передать введенные данные в основное хранилище информации (объект класса документа). Обработчик пункта меню Add/Delete Initial Points и связанной с ним кнопки на панели инструментов:
void CGpView::OnToolsAddpoint()
{
CAddPointDlg dlg; //Создаем экземпляр класса диалогового
//Стандартная процедура получения указателя на документ и его проверки на //корректность
CGpDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
pol_ready = false; //Сигнализируем о том что построенный полином устарел (сейчас будет меняться таблица)
Invalidate(); //Перерисовываем
dlg.ListedPointsVec = pDoc->InitPoints; //переписываем копию узлов в диалоговую переменную
switch(dlg.DoModal()) //Ждем реультата работы диалога
{
case -1: AfxMessageBox("AddPoiintDialog creation error"); return; //Если диалог не запустился
case IDOK: pDoc->InitPoints = dlg.ListedPointsVec; Invalidate(); break; //Если нажали Ok то переписываем
новую таблицу в документ и перерисовываем
case IDCANCEL: break; //Если Cancel ничего не делаем
}
}
Этот обработчик осуществляет связь между механизмом изменения (диалоговое окно) узлов и механизмом их хранения (объект документа). Прежде чем переходить к реализации визуального редактирования, рассмотрим механизм хранения узлов и интерполяционного полинома. Объект документа полностью соответствует требованиям хранилища необходимых программе данных. Прототип класса документа:
// GpDoc.h : interface of the CGpDoc class
//
#pragma once
#include "TPolinom.h"
class CGpDoc : public CDocument
{
protected: // create from serialization only
CGpDoc();
DECLARE_DYNCREATE(CGpDoc)
// Attributes
public:
// Operations
public:
// Overrides
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive ar);
// Implementation
public:
virtual ~CGpDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext dc) const;
#endif
protected:
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
public: //Необходимые переменные
vector<SDPoint> InitPoints; //Таблица узлов реализована с помощью вектора
вещественных точек плоскости
TPolinom<double> InterPol; //Интерполяционный полином
bool pol_ready; //Флаг сигнализирующий о том что полином построен
public:
bool BuildPolinom(void); //Функция для построения полинома
afx_msg void OnToolsBuilddrawpolinom();
};
Важным для данного пункта является объект InitPoints класса vector SDPoint . В него записываются и хранятся на протяжении работы программы узлы интерполирования.
Визуальное редактирование таблицы узлов. Создадим подпункт Mark initial points в меню и соответствующую кнопку, которые будут переводить программу в режим ожидания редактирования (двойного клика по левой кнопке мыши) и выводить из него. Добавим обработчик сообщения WM_LBTNDBLCLICK
Обработчик нажатия пункта меню Mark initial points и связанной с ним кнопки на панели инструментов:
void CGpView::OnToolsMarkinitialpoints()
{
parallel_shift = false; //Если в режиме ожидания переноса то выход из него
mark_points = !mark_points; //Вход или выход в(из) режима визуального редактирования узлов
}
Обработчик изменения внешнего вида пункта меню Mark initial points и связанной с ним кнопки на панели инструментов:
void CGpView::OnUpdateToolsMarkinitialpoints(CCmdUI *pCmdUI)//Обрабатываем изменение внешнего вида кнопки (пункта меню)
{
pCmdUI->SetCheck(mark_points); //Смена статуса (активна или не активна) в зависимости от флага
}
Обработчик двойного клика по левой кнопке мыши:
void CGpView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
if(mark_points) //Если в режиме визуального редактирования, то...
{
//Получение указателя на документ
CGpDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
SDPoint help = SysToLog(point); //Вспомогательная переменная для проверки (вводится ли уже существующий узел)
for(size_t i = 0; i < pDoc->InitPoints.size(); i++) //Перебор по всем узлам и проверка...
if(help.x == pDoc->InitPoints[i].x help.y == pDoc->InitPoints[i].y) //Совпадает с вводимым хотя бы оди узел таблицы
return; //Если да, то выход из функции без добавления вводимого узла в таблицу
pDoc->InitPoints.push_back(help); //Добавление вводимого узла в таблицу (проверка сделана, повторов нет)
Invalidate(); //Обновление клиентской области окна
}
CView::OnLButtonDblClk(nFlags, point);
}
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( и void DrawInitPoints( ;
Функция для рисования осей координат:
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; //Если полином не построен, то выход из функции
}
Эти три функции обеспечивают вывод графической информации. Все они имеют один и тот же прототип, но различные имена. Каждая функция принимает в качестве аргумента указатель на контекст устройства (таким образом, основная задача графического вывода разбивается на 3 более простых). Остается только последовательно вызвать их в методе класса вида OnDraw(.
Функция OnDraw:
void CGpView::OnDraw(CDC* pDC)
{
DrawAxis(pDC);
DrawInitPoints(pDC);
DrawPolinom(pDC);
}
Для запуска механизма построения и отображения полинома, необходимо обработать выбор соответствующего пункта меню и нажатие на кнопку на панели инструментов.
Обработчик пункта меню Build and draw
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 для новой строки состояния и добавить обработчики для пункта меню
void CMainFrame::OnViewStatusBar()
{
m_wndStatusBar.ShowWindow(!(m_wndStatusBar.GetStyle() WS_VISIBLE));
RecalcLayout();
}
Обработчик изменения внешнего вида пункта меню
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
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;
}
}
Данная функция выполняет сохранение изображения клиентской области окна в файл в виде растрового изображения. Для сохранения используется метод 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) График интерполяционного полинома
В данном параграфе, на примере написания приложения "Шифр Виженера", рассматриваются следующие вопросы: работа с классом CRichEdit, ввод и форматирование текста, сохранение форматированного текста в файл, работа с модальными диалоговыми окнами, работа с таймером и элементом управления "индикатор хода процесса".
Пусть у нас есть текст. Все буквы текста принадлежат алфавиту A=(a1,...,an), где ai - i -ый символ алфавита. Требуется зашифровать этот текст по алгоритму Виженера.
Шифр Виженера - это шифр простой замены, с секретным ключом.
Шифрование происходит по формуле xi->ci=xi+ei(mod k)(mod n), а дешифрование по формуле ci->xi=ci+ei(mod k)(mod n), где n - число символов в алфавите, k - число символов в секретном слове (ключе), x - i -ый символ открытого текста, c - i -ый символ шифрованного текста.
Задача. Написать приложение для реализации шифрования текста по алгоритму Вижинера.В приложении должна быть:
В качестве алфавита в программе будем использовать наиболее распространенные печатные символы ASCII кодировки. Для работы с текстом будем использовать специализированный класс CRichEditCtrl.
MFC предоставляет два основных средства редактирования текста. Это - обычное поле ввода (Edit Control) и поле ввода с форматированием (Rich Edit Control). Ими можно воспользоваться как элементами управления в диалоговых окнах, а можно создавать на их основе окна представлений, как в стандартных текстовых редакторах Windows: Notepad и WordPad. Такую гибкость обеспечивают классы CEditView и CRichEditView.
Класс CEditView
В основе этого класса - элемент управления Windows поле ввода. Мастер MFC Application Wizard позволяет наследовать ваш собственный класс "вида" от CEditView. При работе с объектами класса CEditView доступны все методы классов CView и CEdit.Множественное наследование здесь не применяется. Размер текста обрабатываемого данным классом по умолчанию ограничен - не более 1048575 символов, его можно изменить, отправив сообщение EM_LIMITTEXT. Класс CEditView имеет следующие ограничения:
CEditView не поддерживает WYSIWYG редактирование.
В случае, когда стоит выбор между читаемостью текста на дисплее и соответствию размеров при печати, CEditView отобразит текст в соответствии с первым критерием.CEditView может отображать текст только в одном формате. Нет специализированной поддержки шрифтов.Класс CRichEditView
Этот класс базируется на элементе управления "поле ввода с форматированием". Элемент управления "поле ввода с форматированием" - окно, в которое пользователь может вводить и редактировать текст. Данный элемент управления позволяет работать с форматированным текстом и встроенными OLE объектами, предоставляет методы для управления параметрами шрифтов и абзацев, поддерживает большие объемы текста. Класс CRichEditView предназначен для совместного использования с классом CRichEditDoc и CRichEdiCtrl, что позволяет создавать полноценные контейнерные приложения ActiveX.
Класс CRichEditCtrl
Этот класс представляет собой оболочку для элемента управления "поле ввода с форматированием". В нем переопределены обработчики сообщений клавиатуры и мыши для ввода и редактирования текстовой информации. Также он предоставляет множество методов для работы с текстом. Рассмотрим их подробнее.
1. Методы для создания объектов CRichEditCtrl
Метод virtual BOOL Create(DWORD dwStyle, const RECT rect, CWnd* pParentWnd, UINT создает окно элемента управления "поле ввода с форматированием" и связывает его с классом CRichEditCtrl. Параметр dwStyle задает набор флагов, определяющих стиль окна, rect задает оконный прямоугольник, pParentWnd - указатель на родительское окно (если метод Create(…) вызывается в диалоговом окне, то параметр pParentWnd должен иметь значение NULL ), - идентификатор окна.
Если поле ввода с форматированием используется в диалоговом окне, то в функции InitInstance класса приложения необходимо вызвать функцию AfxInitReichEdit() для того, чтобы загрузить необходимые библиотеки элементов управления.
2 Методы управления строками
Метод int GetLine(int nIndex, LPTSTR lpszBuffer, int nMaxLength) const копирует строку, номер которой указан в параметре nIndex в буфер lpszBuffer длиной в nMaxLength символов. Скопированная строка не содержит в конце символа окончания строки.
Метод int GetLineCount() const возвращает количество строк в текстовом поле.
Метод int LineLength(int nLine = -1) const возвращает длину строки в байтах, номер которой указан в параметре nLine, по умолчанию возвращает длину строки, содержащей каретку.
3. Методы выделения текста
Метод void Clear() удаляет выделенный текст.
Метод void GetSel(long nStartChar, long nEndChar) const записывает границы выделенного текста в переменные nStartChar (начало) и nEndChar (конец).
Метод CString GetSelText( ) const возвращает объект-строку с выделенным текстом.
Метод void SetSel(long nStartChar, long nEndChar) выделяет текст, начало выделения передается в параметре nStartSel, конец - в параметре nEndSel.
4. Методы форматирования текста
Метод DWORD GetDefaultCharFormat(CHARFORMAT cf) const возвращает в параметр cf формат символов, принятый текстовым полем по умолчанию.
Метод DWORD GetSelectionCharFormat(CHARFORMAT cf) const возвращает в параметр cf формат выделенного текста.
Метод BOOL SetDefaultCharFormat(CHARFORMAT cf) устанавливает формат символов, который текстовое поле будет использовать по умолчанию. Параметр cf определяет устанавливаемый формат.
Метод BOOL SetSelectionCharFormat(CHARFORMAT cf) устанавливает формат выделенных в текстовом поле символов, указанный в параметре cf.
Все вышеуказанные методы требуют в качестве параметра ссылку на объект структуры CHARFORMAT.Поля данной структуры подробнее рассмотрены далее.
5 Методы работы с потоками
Метод StreamIn(int nFormat, EDITSTREAM es) предназначен для чтения данных из потока. Параметр nFormat определяет формат читаемых данных, может принимать значения: SF_TEXT и SF_RTF. В первом случае прочтенные данные воспринимаются как неформатированный текст, во втором - как форматированный. Параметр es определяет поток, из которого следует читать информацию, имеет тип EDITSTREAM.
Метод StreamOut(int nFormat, EDITSTREAM es) предназначен для записи данных в поток. Имеет такой же набор аргументов, как и предыдущий метод.
Закончим на этом рассмотрение специализированных методов класса CRichEditCtrl для работы с текстом. Их применение будет продемонстрировано ниже в примере "Шифр Виженера".
Этапы разработки приложения
Создадим с помощью мастера MFC Application Wizard
1. Настройка каркаса MFC для ввода и хранения текстовой информации
Основным классом для хранения и отображения данных в случае, когда отключена поддержка каркаса "документ - вид", является класс CChildView, наследник класса CWnd. Мы воспользуемся специализированным классом CRichEditCtrl для работы с текстом. Этот класс предоставляет множество методов для форматирования, редактирования, отображения и сохранения текстовой информации.
Добавим в прототип класса CChildView следующую строку:
public: CRichEditCtrl m_rich; //Переменная для управления текстовым полем с форматированием
m_rich - переменная, через которую будем осуществлять взаимодействие с текстовым полем типа CRichEditCtrl. Далее, для инициализации введенной переменной, обработаем сообщения WM_CREATE и WM_SIZE.
Обработчик сообщения WM_CREATE:
int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
CRect cr(0,0,0,0); //Создадим прямоугольник
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
m_rich.Create(ES_AUTOVSCROLL|ES_MULTILINE|ES_WANTRETURN|WS_CHILD|WS_VISIBLE|WS_VSCROLL,cr,this,1);
//Инициализируем переменную для управления текстовым полем
return 0;
}
При создании окна конструируется поле ввода нулевых размеров, т.к. на данном этапе выполнения программы длина и ширина клиентской области еще неизвестны. Для создания объекта класса CRichEditCtrl используется метод Create(…). Первый аргумент метода - набор флагов, определяющих стиль, второй - прямоугольник с размерами, третий - указатель на окно родителя, четвертый - идентификатор окна элемента управления.
Обработчик WM_SIZE:
void CChildView::OnSize(UINT nType, int cx, int cy)
{
CRect cr; //Создание переменной для получения размеров клиентского прямоугольника
CView::OnSize(nType, cx, cy);
GetClientRect(cr); //Получение размеров клиентского прямоугольника
m_rich.SetWindowPos(wndTop,0,0,cr.right-cr.left,cr.bottom-cr.top,SWP_SHOWWINDOW);
//Изменение размеров поля ввода
}
Функция GetClientRect(cr) используется для получения клиентского прямоугольника. Функция SetWindowPos(wndTop,0,0,cr.right-cr.left,cr.bottom-cr.top,SWP_SHOWWINDOW) устанавливает новое положение и размеры окна (в данном случае окна элемента управления Rich Edit). В параметрах указаны: указатель на окно предшественник (окно, поверх которого будет располагаться окно, вызвавшее функцию SetWindowPos(…)), координаты левого верхнего угла окна, длина и ширина окна, флаг со специальными параметрами отображения. Если скомпилировать и запустить приложение на данном этапе, то появится окно с растянутым на всю клиентскую область полем ввода, которое обрабатывает все сообщения клавиатуры. Добавим возможность считывания текстовой информации из файла в это поле ввода. Добавим элемент главного меню File $$\to$$ Open… и обработаем нажатие на него.
Добавим следующую строку в прототип класса CChildView:
protected: static DWORD CALLBACK FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb); //Функция обратного вызова для чтения данных из файла
Определим данную функцию в файле ChildView.cpp:
DWORD CALLBACK CChildView::FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb)
{
CFile *pFile = (CFile*) dwCookie;
*pcb = pFile->Read(pbBuf,cb);
return 0;
}
Обработчик пункта меню File $$\to$$ Open…
void CChildView::OnFileOpen()
{
CString strFilter; //строка с фильтром форматов
strFilter = "Text file|*.txt|Rich text format file|*.rtf||";
CFileDialog dlg(TRUE,NULL,NULL,OFN_FILEMUSTEXIST,strFilter); //Создание диалога для открытия файла
if(dlg.DoModal() == IDOK) //Если выбран файл и нажата кнопка Ok...
{
EDITSTREAM es; //Создаем структуру потока
CFile InFile(dlg.GetFileName(),CFile::modeRead); //Открываем файл с указанным именем для чтения
es.dwCookie = (DWORD) InFile; //Определяем поток
es.pfnCallback = FileStreamInCallback;
switch(dlg.m_ofn.nFilterIndex) //В зависимости от того, какой формат выбран, читаем текст из файла
{
case 1: m_Rich.StreamIn(SF_TEXT,es); break;
case 2: m_Rich.StreamIn(SF_RTF,es); break;
default: break;
}
AfxGetMainWnd()->SetWindowTextA(dlg.GetFileName()); //Выводим в заголовок окна название открытого файла
}
}
Для чтения текстовой информации из файла используется специализированный метод StreamIn(SF_TEXT,es) класса CRichEditCtrl. В качестве первого аргумента методу передается флаг, который определяет формат данных для чтения. Вторым аргументом является объект структуры EDITSTREAM. Поле dwCookie этой структуры определяет первый параметр, передаваемый в функцию чтения (обычно это идентификатор некоторого объекта, из которого следует читать данные). В поле pfnCallback записывается адрес функции чтения, которая определяет способ работы с данными и источником (она имеет строго определенный прототип). В данном случае мы сами создали функцию чтения данных: DWORD CALLBACK FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb) . Первый параметр - идентификатор источника, второй - указатель на буфер, третий - число байтов, которое необходимо считать, четвертый - указатель на переменную, в которую записывается число прочтенных байтов. Метод StreamIn(…) повторно вызывает функцию чтения, пока не произойдет одно из следующих событий:
Теперь, когда стало возможным чтение данных из файла и их ввод с клавиатуры, реализуем простейшие операции работы с буфером обмена (вырезать, копировать, вставить и т.п.). Для этого будем обрабатывать соответствующие пункты главного меню. Обработчик пункта меню Edit $$\to$$ Copy:
void CChildView::OnEditCopy()
{
m_Rich.Copy();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Copy:
void CChildView::OnUpdateEditCopy(CCmdUI *pCmdUI)
{
LONG nStartSel, nEndSel; //Переменные для хранения начальной и конечной позиции выделенного текста
m_Rich.GetSel(nStartSel,nEndSel); //Получение начальной и конечной позиции выделенного текста
pCmdUI->Enable(nStartSel != nEndSel); //Если текст выделен, то можно скопировать
}
Метод GetSel(nStartSel,nEndSel) записывает позиции начала и конца выделенного текста в соответствующие переменные, переданные ему в качестве параметров.
Обработчик пункта меню Edit $$\to$$ Cut:
void CChildView::OnEditCut()
{
m_Rich.Cut();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Cut:
void CChildView::OnUpdateEditCut(CCmdUI *pCmdUI)
{
LONG nStartSel, nEndSel;
m_Rich.GetSel(nStartSel,nEndSel);
pCmdUI->Enable(nStartSel != nEndSel);
}
Код данного обработчика совпадает с кодом обработчика изменения внешнего вида элемента Edit $$\to$$ Copy. Обработчик пункта меню Edit $$\to$$ Paste:
void CChildView::OnEditPaste()
{
m_Rich.Paste();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Paste: void CChildView::OnUpdateEditPaste(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_Rich.CanPaste());
}
В данном обработчике используется метод CanPaste() класса CRichEditCtrl, который возвращает значение TRUE, если данные, скопированные в буфер, поддерживаются элементом управления, и FALSE в любом другом случае.
Обработчик пункта меню Edit $$\to$$ Undo:
void CChildView::OnEditUndo()
{
m_Rich.Undo();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Undo:
void CChildView::OnUpdateEditUndo(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_Rich.CanUndo());
}
Данные обработчики полностью реализуют необходимые функции работы с буфером обмена. Добавим возможность вызова контекстного меню с элементами, соответствующими пунктам меню Edit. Для этого обработаем сообщение WM_CONTEXTMENU.
Обработчик сообщения WM_CONTEXTMENU:
void CChildView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
CMenu Menu; //Создаем объект меню
Menu.LoadMenuA(IDR_MAINFRAME); //Загружаем ресурс меню
Menu.GetSubMenu(1)->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this); //Вызываем меню
}
Метод LoadMenuA(IDR_MAINFRAME) загружает из ресурсов приложения меню, идентификатор которого передан в качестве параметра. Метод GetSubMenu(1) возвращает указатель на подменю. Метод TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON, раскрывает контекстное меню в точке point.
2. Форматирование текста
Текстовое поле Rich Edit предназначено для работы с данными в формате rtf. То есть, существует возможность работы со шрифтами (изменение цвета, размера символов, их выделение, подчеркивание и т.п.). Реализуем некоторые функции работы со шрифтами в нашей программе. Для этого создадим пункт меню Format, а в нем подпункт Format $$\to$$ Font…, который будем обрабатывать. Обработчик пункта меню Format $$\to$$ Font…
void CChildView::OnFormatFont()
{
LONG nStartSel, nEndSel; //Переменные для хранения начала и конца выделения
CHARFORMAT cf_old, cf_new; //Переменные для хранения формата символов
m_Rich.GetSel(nStartSel,nEndSel); //Получение начала и конца выделения
if(nStartSel == nEndSel) m_Rich.GetDefaultCharFormat(cf_old); //Если ничего не выделено,
то сохраняем формат по умолчанию
else m_Rich.GetSelectionCharFormat(cf_old); //иначе сохраняем формат выделенного текста
CFontDialog fdlg(cf_old); //На основе полученного формата создаем диалог работы со шрифтом
if(fdlg.DoModal() == IDOK) //Если пользователь нажал Ok
{ //Заполняем поля объекта структуры CHARFORMAT
cf_new.cbSize = sizeof(CHARFORMAT);
cf_new.dwMask = CFM_BOLD|CFM_COLOR|CFM_FACE|CFM_ITALIC|CFM_SIZE|CFM_UNDERLINE|CFM_CHARSET|CFM_STRIKEOUT;
cf_new.dwEffects = (fdlg.m_lf.lfItalic ? CFE_ITALIC : 0)|(fdlg.m_lf.lfUnderline ? CFE_UNDERLINE : 0)|
(fdlg.m_lf.lfWeight > 400 ? CFE_BOLD : 0)|(fdlg.m_lf.lfStrikeOut ? CFE_STRIKEOUT : 0);
cf_new.yHeight = -MulDiv(fdlg.m_lf.lfHeight,1440,GetDC()->GetDeviceCaps(LOGPIXELSY));
cf_new.bPitchAndFamily = fdlg.m_lf.lfPitchAndFamily;
cf_new.bCharSet = fdlg.m_lf.lfCharSet;
cf_new.crTextColor = fdlg.m_cf.rgbColors;
if(nStartSel == nEndSel)
{
m_Rich.SetFocus();//Возвращаем фокус текстовому полю
m_Rich.SetDefaultCharFormat(cf_new); //Если текст не был выделен, то обновляем шрифт по умолчанию
}
else //иначе
{
m_Rich.SetFocus(); //Возвращаем фокус текстовому полю
m_Rich.SetSel(nStartSel,nEndSel); //Выделяем форматируемый текст
m_Rich.SetSelectionCharFormat(cf_new); //Обновляем формат выделенного текста
}
}
}
Для получения текущего формата символов и установки нового формата используются методы:
cf_oldcf_oldНаибольший интерес представляет собой структура CHARFORMAT, объектами которой являются переменные cf_old и cf_new. Рассмотрим поля данной структуры.
UINT cbSize - размер заданной структуры в байтах.DWORD dwMask - поле, содержащее информацию о доступных для изменения атрибутов шрифта.DWORD dwEffects - параметры шрифта (жирный, подчеркнутый, курсив и т.п.).LONG yHeight - высота символа в твипах (1/1440 дюйма).BYTE bPitchAndFamily - определяет семейство шрифта и расстояние между символами.BYTE bCharSet - определяет набор символов (алфавит).COLORREF crTextColor - цвет символов.Возможные значения полей данной структуры представлены в MSDN Library. Остановимся подробнее только на высоте символа. В программе она задается строкой cf_new.yHeight = -MulDiv(fdlg.m_lf.lfHeight,1440,GetDC()->GetDeviceCaps(LOGPIXELSY)). Функция MulDiv умножает два 32-битных значения (первый и второй аргумент), а затем делит получившееся 64-битное значение на третий аргумент и округляет результат до целых. Значение fdlg.m_lf.lfHeight определяет высоту символа в логических единицах дисплея, а GetDeviceCaps(LOGPIXELSY) определяет число логических единиц в дюйме. Таким образом, по формуле
рис 5.4
(рис 5.4) Формула переходамы переходим к нужным единицам измерения. Поле m_lf класса CFontDialog имеет тип LOGFONT и является GDI структурой, которая определяет параметры шрифта в логических
единицах дисплея.
3. Шифрование и дешифрование текста
Создадим ресурс диалога для шифрования с идентификатором IDD_CIPHER_DIALOG. Добавим два текстовых поля (Edit Control), два флажка-переключателя (
(рис 5.5) Шаблон диалогаПервое текстовое поле предназначено для ввода ключа шифрования, свойство Password должно иметь значение true. В групповое поле объединены элементы управления, отвечающие за вывод информации. Кнопка открывает диалог для указания пути к файлу в который будет сохранен шифротекст. Путь к файлу выводится во второе текстовое поле. Защитим его от изменения, установив свойство Read Only в значение true. Первый флажок-переключатель сигнализирует о необходимости записи конечного результата в файл. Второй-о необходимости вывода результата в основное текстовое поле программы.
Добавим в приложение класс диалога, созданный на основе данного ресурса. Добавим также необходимые переменные.
Класс CCipherDialog:
#pragma once
// CCipherDialog dialog
class CCipherDialog : public CDialog
{
DECLARE_DYNAMIC(CCipherDialog)
public:
CCipherDialog(CWnd* pParent = NULL); // standard constructor
virtual ~CCipherDialog();
// Dialog Data
enum { IDD = IDD_CIPHER_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public:
BOOL m_bCiphType; //Флаг указывает на то,какое действие необходимо
выполнить (шифрование/дешифрование)
CString m_sFilePathStr; //Переменная, связанная с текстовым полем,
в которое выводится путь файла для сохранения
CString m_sBufStr; //Переменная буфер
BOOL m_bToFile; //Переменная, связанная с первым флажком переключателем
BOOL m_bDisplay; //Переменная, связанная со вторым флажком переключателем
CString m_sKeyStr; //Переменная, связанная с первым текстовым полем,
в которое вводится ключ
int table[161]; //Таблица алфавита
int m_nTimer; //Счетчик таймера
int m_nCount; //Переменная индикатор хода процесса шифрования/дешифрования
bool m_bFinProc; //Флаг, сигнализирующий о том, прошел ли процесс до конца
public:
afx_msg void OnBnClickedButtonBrowse();
afx_msg void OnTimer(UINT_PTR nIDEvent);
protected:
virtual void OnCancel();
virtual void OnOK();
};
Конструктор класса CCipherDialog:
CCipherDialog::CCipherDialog(CWnd* pParent /*=NULL*/)
: CDialog(CCipherDialog::IDD, pParent) //Инициализация переменных
, m_bCiphType(FALSE)
, m_sFilePathStr(_T(""))
, m_sBufStr(_T(""))
, m_bToFile(FALSE)
, m_bDisplay(FALSE)
, m_sKeyStr(_T(""))
, m_nTimer(0)
, m_nCount(0)
, m_bFinProc(true)
{
for(int i = 0; i < 95; i++) //Создание таблицы для шифрования символов
table[i] = i + 32; //Используются ASCII коды только печатных символов
for(int i = 95; i < 159; i++)
table[i] = i - 159;
table[159] = -88;
table[160] = -72;
}
Функция DoDataExchenge:
void CCipherDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_FILEPATH, m_sFilePathStr);
DDX_Check(pDX, IDC_CHECK_TOFILE, m_bToFile);
DDX_Check(pDX, IDC_CHECK_TOCLIENT, m_bDisplay);
DDX_Text(pDX, IDC_EDIT_KEY, m_sKeyStr);
}
Функция DoDataExchenge используется для создания связей между переменными и элементами управления. При добавлении переменных с помощью мастера, ее код генерируется автоматически.
Для связи диалога с основным окном программы создадим пункт меню Cipher и в нем два подпункта Cipher $$\to$$ CCipherDialog добавим в начало файла ChildView.h строку
#include "CipherDialog.h"
Обработчик пункта меню Cipher $$\to$$
void CChildView::OnCipherEncipher()
{
CCipherDialog dlg; //Создание диалога шифрования/дешифрования
dlg.m_bCiphType = TRUE; //Инициализация флага индикатора действия
(будем производить шифрование)
m_Rich.GetWindowTextA(dlg.m_sBufStr); //Копирование текста из текстового поля
в строковую переменную - буфер диалога
if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
if(dlg.m_bToFile) //Если установлен флаг сохранения результата шифрования/дешифрования
{
int nBuf; //количество символов в тексте
char *dinBuf; //буфер с текстом
if(dlg.m_sFilePathStr != "") //Если верно указан путь файла
{
CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записиси
nBuf = dlg.m_sBufStr.GetLength() + 1; //Записываем количество символов текста в переменную
dinBuf = new char [nBuf]; //Инициализируем буфер
strcpy_s(dinBuf,nBuf,dlg.m_sBufStr); //Копируем данные из диалога в буфер
OutFile.Write(dinBuf,nBuf); //Записываем данные из буфера в файл
}
else AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено");
//Если неверно указан путь к файлу,то сообщаем об ошибке
}
if(dlg.m_bDisplay) //Если установлен флаг вывода результатов на экран
{
m_Rich.Clear(); //Очищаем текстовое поле
m_Rich.SetWindowTextA(dlg.m_sBufStr); //Записываем результат шифрования/дешифрования в текстовое поле
}
}
}
В данном пункте важен код, выделенный жирным шрифтом, остальное относится к выводу результатов шифрования/дешифрования на экран или их сохранения в файл. Для получения текста из элемента управления Rich Edit, используется метод GetWindowText(CString), аргументом ему передается строка, в которую будет записан текст. Для записи текста в текстовое поле, используется метод SetWindowText(CString).
Обработчик пункта меню Cipher $$\to$$
void CChildView::OnCipherDecipher()
{
CCipherDialog dlg;
m_Rich.GetWindowTextA(dlg.m_sBufStr);
if(dlg.DoModal() == IDOK)
{
if(dlg.m_bToFile)
{
int nBuf;
char *dinBuf;
if(dlg.m_sFilePathStr != "")
{
CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite);
nBuf = dlg.m_sBufStr.GetLength() + 1;
dinBuf = new char [nBuf];
strcpy_s(dinBuf,nBuf,dlg.m_sBufStr);
OutFile.Write(dinBuf,nBuf);
}
else
AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено");
}
if(dlg.m_bDisplay)
{
m_Rich.Clear();
m_Rich.SetWindowTextA(dlg.m_sBufStr);
}
}
}
Также как и в предыдущем обработчике строки, важные в данном пункте, выделены жирным шрифтом. Единственное отличие данного обработчика от предыдущего состоит в том, что мы не инициализируем переменную dlg.m_bCiphType. Это говорит о том, что будет происходить дешифрование.
Теперь реализуем алгоритм шифрования. Переопределим виртуальную функцию OnOk() класса CCipherDialog.
Переопределенный метод OnOk:
void CCipherDialog::OnOK()
{
MSG message; //Структура сообщение
UpdateData(true); //Обновляем данные
GetDlgItem(IDOK)->EnableWindow(FALSE); //Отключаем возможность повторного нажатия
кнопки Ok пока происходит шифрование
int nKeyLength = m_sKeyStr.GetLength(); //Инициализация переменной для хранения длины ключа
if(nKeyLength) //Если ключ введен
{
m_nTimer = (int)SetTimer(1,100,NULL); //Пускаем таймер с интервалом 100 милисекунд
CString tempstr; //Вспомогательная строка
for(m_nCount = 0; m_nCount < m_sBufStr.GetLength(); m_nCount++) //Пока не конец открытого текста
{
int buf_ascii = int(m_sBufStr[m_nCount]); //Шифруем или дешифруем по алгоритму
if(buf_ascii == 10 || buf_ascii == 13 || buf_ascii == 9) //Табуляции и переносы на новую строку не шифруем
{
tempstr += m_sBufStr[m_nCount];
continue;
}
int nTextChar = std::find(table,table+160,int(m_sBufStr[m_nCount])) - table;
int nKeyChar = std::find(table,table+160,int(m_sKeyStr[m_nCount % nKeyLength])) - table;
if(m_bCiphType) tempstr += char(table[(nTextChar + nKeyChar) % 161]);
else tempstr += char(table[(nTextChar + 161 - nKeyChar) % 161]);
if(::PeekMessageA(message,NULL,0,0,PM_REMOVE)) //При этом на каждой иттерации
цикла обрабатываем сообщения
{
::TranslateMessage(message);
::DispatchMessageA(message);
}
}
if(m_bFinProc) //Если процесс полностью завершен
{
m_sBufStr = tempstr; //Записываем все полученные данные в буфер
KillTimer(m_nTimer); //Сбрасывем таймер
CDialog::OnOK(); //Закрываем диалог
}
else //Иначе
{
m_bFinProc = true; //Сбрасываем флаг завершения процедуры в начальное положение
m_nCount = 0; //Сбрасываем счетчик символов
CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH); //Ининциализируем переменную
для управления индикатором хода процесса
pBar->SetPos(0); //Устанавливаем индикатор в начальное состояние
KillTimer(m_nTimer); //Сбрасываем таймер
}
}
else //Если ключ не введен
{
GetDlgItem(IDOK)->EnableWindow(TRUE); //Разблокируем кнопку Ok
AfxMessageBox("Введите ключ шифрования"); //Просим ввести ключ
}
}
Обратим внимание на использовании в данном методе функций: PeekMessageA(message,NULL,0,0,PM_REMOVE), TranslateMessage(message) и DispatchMessageA(message). Они позволяют обрабатывать сообщения на каждой иттерации цикла, создавая тем самым впечатление параллельности вычислений и реакции на действия пользователя. Функция SetTimer(1,100,NULL) запускает WM_TIMER.Первый аргумент функции - целое число, идентифицируещее таймер, третий - указатель на функцию, которая должна обрабатывать сообщение WM_TIMER. Если он равен NULL, то сообщение WM_TIMER посылается в общую очередь сообщений программы. Функция std::find(table,table+160,int(m_sBufStr[m_nCount])) - это алгоритм CCipherDialog обработчик сообщения WM_TIMER, для того чтобы выводить информацию о ходе шифрования в индикатор состояния.
Обработчик сообщения WM_TIMER:
void CCipherDialog::OnTimer(UINT_PTR nIDEvent)
{
CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH);
//Инициализируем переменную для управления индикатором процесса
if(m_sBufStr.GetLength()) //Если буфер не пуст
pBar->SetPos(m_nCount*100/m_sBufStr.GetLength()); //Устанавливаем позицию индикатора
процесса в соответствии с текущей позицией шифрования/дешифрования
CDialog::OnTimer(nIDEvent);
}
Функция GetDlgItem(IDC_PROGRESS_CIPH) возвращает указатель на объект элемента управления диалога, идентификатор которого передается ей в качестве аргумента.
Также необходимо переопределить функцию OnCancel() для данного диалога. Она прерывает процесс шифрования/дешифрования, если процесс начался, но не закрывает диалог. И закрывает диалог если процесс не запущен.
Переопределенный метод OnCancel():
void CCipherDialog::OnCancel()
{
if(m_nCount) //Если идет процесс, то
{
GetDlgItem(IDOK)->EnableWindow(TRUE); //разблокируем кнопку Ok
m_nCount = m_sBufStr.GetLength(); //завершаем цикл шифрования, присваивая переменной счетчику конечное значение
m_bFinProc = false; //Указываем на то, что шифрование/дешифрование было прервано
}
else Cdialog::OnCancel(); //Иначе закрываем диалог
}
Осталось обработать нажатие на кнопку Browse… Она должна открывать диалог для указания пути к файлу, в который будет выводиться результат шифрования/дешифрования. Обработчик кнопки Browse…
void CcipherDialog::OnBnClickedButtonBrowse()
{
Cstring strFilter; //Строка с поддерживаемыми форматами
Cstring strFileName; //Строка с путем и именем файла
Cstring strExtension; //Строка с расширением файла
UpdateData(true);//Обмен данными между элементами управления и переменными
strFilter = "Text file|*.txt||"; //Инициализация строки поддерживаемыми форматами
CfileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter);
//Создание диалога
if (dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
strFileName = dlg.m_ofn.lpstrFile; //Определяем путь и имя файла в который будем сохранять
if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввел расширение, то
{
switch (dlg.m_ofn.nFilterIndex) //В зависимости от того, какой из поддерживаемых форматов выбран
{
case 1: strExtension = "txt"; break; //Инициализируем строку с расширением
default: break;
}
strFileName = strFileName + '.' + strExtension; //Окончательно определяем строку
с именем путем и расширением
}
m_sFilePathStr = strFileName; //Записывае готовую строку с полным именем
в пременную член, связанную с текстовым полем
UpdateData(false); //Обновляем данные в элементах управления
в соответствии со связанными с ними переменными
}
}
4. Вывод результатов и их сохранение
Описание вывода результатов частично затронуто в предыдущем пункте. Теперь рассмотрим вывод форматированного текста в файл. Для этого создадим пункт меню File $$\to$$ Save as…, и обработаем нажатие на него. Но прежде добавим в файл ChildView.h строку:
protected:
static DWORD CALLBACK FileStreamOutCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb);
//Функция обратного вызова для записи данных в файл
и определим объявленный метод в файле ChildView.cpp:
DWORD CALLBACK CChildView::FileStreamOutCallback(DWORD dwCookie,
LPBYTE pbBuf, LONG cb, LONG *pcb)
{
CFile *pFile = (CFile*) dwCookie;
pFile->Write(pbBuf,cb);
*pcb = cb;
return 0;
}
Обработчик пункта меню File $$\to$$ Save as…
void CChildView::OnFileSaveas()
{
CString strFilter; //Строка для поддерживаемых форматов данных
CString strFileName; //Строка для имени файла
CString strExtension; //Строка для расширения файла
strFilter = "Text file|*.txt|Rich text format file|*.rtf||"; //Инициализация строки поддерживаемыми форматами
CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_EXPLORER,strFilter);
//Создание диалога сохранения
if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
strFileName = dlg.m_ofn.lpstrFile; //Инициализируем строку с именем файла
if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввел расширение
{
switch (dlg.m_ofn.nFilterIndex) //В зависисмости от выбранного формата
{
case 1: strExtension = "txt"; break; //Инициализируем строку
case 2: strExtension = "rtf"; break; //с расширением
default: break;
}
strFileName = strFileName + '.' + strExtension; //Окончательно записываем путь имя и расширение файла
}
EDITSTREAM es; //Создаем структуру потока
CFile OutFile(strFileName,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записи
es.dwCookie = (DWORD) OutFile; //Определяем поле структуры, указывающее куда записывать данные
es.pfnCallback = FileStreamOutCallback; //Задаем функцию записи
switch(dlg.m_ofn.nFilterIndex) //В зависимости от выбранного формата
{
case 1: m_Rich.StreamOut(SF_TEXT,es); break; //Записываем данные
case 2: m_Rich.StreamOut(SF_RTF,es); break; //в файл
default: break;
}
}
}
Процедура записи данных в файл похожа на процедуру чтения. Мы пользуемся методом StreamOut(int nFormat, EDITSTREAM es) класса CRichEditCtrl, который требует аргументами формат записываемых данных и структуру потока. Структура потока EDITSTREAM определяется заданием пункта назначения и функции записи.
Добавим строку:
#include <algorithm>
в конец файла stdafx.h.
Скомпилируем и запустим приложение. В результате, в соответствии с открытым текстовым документом, получается следующее:
рис 5.6рис 5.7рис 5.8

(рис 5.7) Открытый текст в формате rtf (рис 5.6) Диалог шифрования
(рис 5.8) Зашифрованный текст (ключ шифрования "крипто")
В данном параграфе, на примере создания приложения "Метод наименьших квадратов (МНК) ", рассматриваются следующие вопросы: диалог в качестве основного окна приложения, элемент управления CListCtrl, элемент управления CRichEditCtrl, класс CArray, работа с матрицами.
Пусть у нас есть следующая таблица:
$$\begin{tabular}{|r||r||r||r|} \hline X x_1 ... x_n\\ \hline Y y_1 ... y_n\\ \hline \end{tabular} $$ $$\{x_1,...,x_n,y_1,...,y_n\} $$Требуется построить полином y=f(x) заданной степени k: k меньше n-1 такой, что сумма квадратов расстояний от точек таблицы до графика полинома была наименьшей. Для поиска коэффициентов полинома составим следующий функционал:
и поставим задачу минимизации этого функционала F -> min. Полином запишем в виде
тогда необходимое условие минимума запишется в виде:
$$\left\{ \begin{array}{l} \frac{\parial F}{\partial a_0}=\sum_{i=1}^n(y_i-a_0-a_1{x_i}-...-a_k{x_i^k})=0\\ \frac{\parial F}{\partial a_1}=\sum_{i=1}^n(y_i-a_0-a_1{x_i}-...-a_k{x_i^k})(x_i)=0\\ \vdots\\ \frac{\parial F}{\partial a_k}=\sum_{i=1}^n(y_i-a_0-a_1{x_i}-...-a_k{x_i^k})(x_i^k)=0 \end{array}$$ $$\left( \begin{array}{ccc} 1 \frac{\sum_{i=1}^n{x_i}}{n} \ldots \frac{\sum_{i=1}^n{x_i^k}}{n}\\ \frac{\sum_{i=1}^n{x_i}}{n} \frac{\sum_{i=1}^n{x_i^2}}{n} \ldots \frac{\sum_{i=1}^n{x_i^k+1}}{n}\\ \vdots\\ \frac{\sum_{i=1}^n{x_i^k}}{n} \frac{\sum_{i=1}^n{x_i^{k+1}}}{n} \ldots \frac{\sum_{i=1}^n{x_i^2k}}{n} \end{array} \right) \cdot \left( \begin{array}{c} a_0\\ a_1\\ \vdots\\ a_k \end{array}\right)= \left( \begin{array}{c} \frac{\sum_{i=1}^n{y_i}}{n}\\ \frac{\sum_{i=1}^n{y_i\cdot x_i}}{n}\\ \vdots\\ \frac{\sum_{i=1}^n{y_i \cdot x_i^k}}{n} \end{array}\right)$$Данная система имеет единственное решение, если k<n-1. Значит, существует единственный полином доставляющий минимум этому функционалу, с коэффициентами представимыми решением указанной системы. Напишем программу для нахождения коэффициентов полинома.
Этапы разработки
Создадим с помощью мастера MFC приложение "Regr" на основе диалогового окна. Для этого установим Application type в значение Dialog based. Снимем флажок Use Unicode libraries.
1. Создание ресурса диалогового окна
Создадим диалоговое окно с тремя текстовыми полями для ввода данных в таблицу и указания степени аппроксимации, четырьмя кнопками(Add, Delete, Start Approximation, Exit), списковым представлением для отображения таблицы точек и текстовым полем RichEdit для вывода строки результата. Шаблон диалога приведен на рисунке: рис 5.9
(рис 5.9) Создание диалогового окнаДля всех элементов управления типа поле ввода, установим свойство Right Align Text в значение true. Для элемента управления типа поле ввода, расположенного напротив надписи Approximation degree: установим свойство Number в значение true. Для элемента управления типа поле ввода с форматированием, установим свойство Auto HScroll в значение true, свойство Align Text в значение center, свойство в значение true, свойство Read Only в значение true. Для элемента управления типа списковое представление, установим свойство Border в значение false (убираем рамку для того, чтобы проще было получить размеры клиентской области элемента управления), свойство Single Selection в значение true, свойство View в значение Report (будем выводить таблицу).
2. Ввод и редактирование начальных данных
Начальные данные представляют собой точки плоскости. Как и в примере "RegrDlg.h, содержащий прототип класса основного диалога, следующий код:
class SDPoint
{
public:
double x;
double y;
SDPoint():x(0),y(0){} //Конструктор по умолчанию
SDPoint(double a, double b):x(a),y(b){} //Конструктор с параметрами
};
после строки
#pragma once
Теперь, создадим в классе диалога массив объектов класса SDPoint, который будет хранить начальные данные.
Прототип класса CRegrDlg:
// CRegrDlg dialog
class CRegrDlg : public CDialog
{
// Construction
public:
CRegrDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_REGR_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
//Обработчики событий добавлены с помощью мастера
public:
afx_msg void OnBnClickedButtonAdd();
afx_msg void OnBnClickedButtonDel();
afx_msg void OnBnClickedButtonStart();
public:
matrix<double> mRightPart; //Вектор - правая часть системы
matrix<double> mNodeMatrix; //Матрица системы
CArray<SDPoint,SDPoint> mInitArr; //Массив точек
//Следующие переменные добавлены с помощью утилиты ClassView и связаны с элементами управления
double mX; //Координата X точки связана с первым текстовым полем
double mY; //Координата Y связана со вторым текстовым полем
int mNum; //Степень аппроксимации связана с третьим текстовым полем
CListCtrl mListCtrl; //Переменная для управления списковым представлением
};
Жирным шрифтом выделена переменная, которую необходимо добавить в данном пункте. CArray - это шаблонный класс, инкапсулирующий динамические массивы в MFC. В качестве первого аргумента шаблону передается тип хранимых в массиве объектов (в данном случае SDPoint ), вторым аргументом передается тип объектов, использующихся для получения доступа к данным массива, обычно это ссылка на тип, указанный в первом аргументе (в данном случае SDPoint ). Для ввода данных в диалоге была создана кнопка Add и два текстовых поля. Добавим в диалоговый класс связанные с текстовыми полями переменные mX, mY, mNum и mListCtrl с помощью мастера. Прежде чем обрабатывать нажатие на кнопку Add, настроим списковое представление. Добавим следующие строки в функцию OnInitDialog():
BOOL CRegrDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
//Настраиваем Элемент управления "списковое представление"
CRect cr;
mListCtrl.GetWindowRect(cr); //Получение размеров окна элемента управления
mListCtrl.SetExtendedStyle(LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT|LVS_EX_ONECLICKACTIVATE);
//Задаем необходимые стили
mListCtrl.InsertColumn(0,_T("X"),LVCFMT_LEFT,(cr.right-cr.left)/2); //Добавляем колонку
mListCtrl.InsertColumn(1,_T("Y"),LVCFMT_RIGHT,(cr.right-cr.left)/2); //Добавляем еще одну
//Настраиваем самую левую колонку
LVCOLUMN lvm; //Структура для хранения информации о колонке
lvm.mask = LVCF_FMT; //Инициализация полей структуры
lvm.fmt = LVCFMT_RIGHT;
mListCtrl.SetColumn(0,lvm); //Задание необходимого стиля колонки
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
return TRUE; // return TRUE unless you set the focus to a control
}
Жирным шрифтом выделен добавленный код. А теперь добавим обработчик кнопки Add и Delete. Обработчик нажатия на кнопку Add:
void CRegrDlg::OnBnClickedButtonAdd()
{
CString str;
UpdateData(true); //Обмен данными
for(int i = 0; i < mInitArr.GetSize(); i++) //Перебираем все точки в массиве
if(mInitArr[i].x == mX mInitArr[i].y == mY) return; //Если существует такая же, то выходим
mInitArr.Add(SDPoint(mX,mY)); //Проверка сделана, повторов нет, добавляем точку в массив
str.Format("%f",mX); //Форматирум строку для вывода в таблицу
mListCtrl.InsertItem((int)mInitArr.GetSize()-1,str); //Добавляем строку в таблицу
str.Format("%f",mY); //Повторяем для следующей колонки
mListCtrl.SetItemText((int)mInitArr.GetSize()-1,1,str);
}
Обработчик нажатия на кнопку Delete:
void CRegrDlg::OnBnClickedButtonDel()
{
int n = mListCtrl.GetSelectionMark(); //Получаем номер выделенной строки списка
if(n < 0) return; //Если ничего не выделено,то выходим
mInitArr.RemoveAt(n); //Удаляем точку из массива
mListCtrl.DeleteItem(n); //Удаляем строку из списка
}
3. Построение системы линейных уравнений и ее решение
Для построения системы линейных уравнений будем использовать шаблонный класс матриц matrix<type>. Этот класс выделен в отдельный заголовочный файл matrix.h. Его необходимо скопировать в каталог с проектом и добавить в проект с помощью утилиты Solution Explorer (вызвать контекстное меню проекта в окне утилиты Solution Explorer $$\to$$ Add $$\to$$ Existing item… и далее указать имя файла). Полный код класса приведен в приложении. Напишем функции для поиска обратной матрицы. Введем их в файле RegrDlg.cpp после строк.
#include "stdafx.h" #include "Regr.h" #include "RegrDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif
Функции для поиска обратной матрицы:
//Функции для решения СЛАУ методом окаймления
matrix<double> bordering (matrix<double> u, matrix<double> v,
matrix<double> A, matrix<double> a)
{
matrix<double> b = a - u*A*v;
b.setcell(1/b.getcell(0,0),0,0);
matrix<double> s = -u*A*b.getcell(0,0);
matrix<double> w = -A*v*b.getcell(0,0);
matrix<double> B = A - A*v*s;
B.addcolend(w);
s.addcolend(b);
B.addrowend(s);
return B;
}
matrix>double> getreverse (matrix<double> M)
{
matrix<double> Res;
if(M.getcolsize() == M.getrowsize())
{
Res = M.getminor(0,0,0,0);
Res.setcell(1/Res.getcell(0,0),0,0);
for(uni i = 1; i < M.getcolsize(); i++)
{
matrix<double> v = M.getminor(i,0,i,i-1);
matrix<double> u = M.getminor(0,i,i-1,i);
matrix<double> a = M.getminor(i,i,i,i);
Res = bordering(u,v,Res,a);
}
}
return Res;
}
Для формирования и решения системы мы создали кнопку Start approximation. Обработаем нажатие на нее. Обработчик нажатия на кнопку Start approximation:
void CRegrDlg::OnBnClickedButtonStart()
{
UpdateData(true);
CRichEditCtrl *pRich = (CRichEditCtrl *) GetDlgItem(IDC_RICHEDIT_RES); //Переменная для управления RichEditom
if(mNum >= mInitArr.GetSize()) //Если данные не соответствуют
{
pRich->SetWindowTextA(CString("Number of points mismatches approximation degree")); //Выводим предупреждение
return; //Выходим
}
mNodeMatrix.setsize(mNum + 1,mNum + 1); //Задаем размерность матрицы для поиска коэффициентов
int n = (int) mInitArr.GetSize();
for(int i = 0; i < mNum + 1; i++) //Идем по строкам матрицы
for(int j = 0; j < mNum + 1; j++) //Идем по столбцам матрицы
{
double elem = 0; //вспомогательная переменная
for(int k = 0; k < n; k++) //Идем по точкам таблицы
elem += pow(mInitArr[k].x,(double)i+j); //Формируем элемент матрицы
mNodeMatrix.setcell(elem/n,j,i); //Инициализируем элемент матрицы
}
mRightPart.setsize(1,mNum + 1); //Задаем размерностьправой части системы
for(int i = 0; i < mNum + 1; i++) //Идем по строкам правой части
{
double elem = 0; //Вспомогательный элемент
for(int k = 0; k < n; k++) //Идем по точкам таблицы
elem += mInitArr[k].y*pow(mInitArr[k].x,(double)i); //Формируем элемент правой части
mRightPart.setcell(elem/n,0,i); //Инициализируем элемент правой части
}
matrix<double> Solution(::getreverse(mNodeMatrix)*mRightPart); //Решаем уравнение
CString str; //Строка для вывода результатов
str.Format("f(x) = %f",Solution.getcell(0,0));
//Формируем строку полинома
for(uni i = 1; i < Solution.getrowsize(); i++)
{
CString temp;
temp.Format(" + %f*x^%d",Solution.getcell(0,i),i);
str += temp;
}
pRich->SetWindowTextA(str); //Выводим ее
}
4. Вывод информации
Вывод осуществляется в двух направлениях: вывод таблицы узлов и вывод аналитической формулы полинома. За вывод узлов отвечает элемент управления списковое представление, код вывода точек в таблице представлен в обработчиках кнопок Add и Delete. За вывод формулы полинома отвечает элемент управления текстовое поле с форматированием, код вывода формулы приведен в обработчике кнопик Start approximation. Для завершения программы добавим строку:
#include "matrix.h"
в файл RegrDlg.h перед описанием класса SDPoint. Скомпилируем и запустим приложение. В результате в соответствии с введенными данными должно получиться следующее: рис 5.10
(рис 5.10) Форма исполняемой программы
В данном параграфе на примере написания приложения "
Пусть у нас есть следующая таблица:
$$\begin{tabular}{|r||r||r||r|} \hline X x_1 ... x_n\\ \hline Y y_1 ... y_n\\ \hline \end{tabular} $$ $$\{x_1,...,x_n,y_1,...,y_n\} $$Требуется построить полином y=f(x), принимающий значения согласно таблице. Интерполяционный полином - полином, принимающий значения y в точках x согласно указанной таблице. Значения x называются
Существуют разные алгоритмы построения интерполяционного полинома, мы воспользуемся методом Лагранжа. Данный метод дает аналитическое представление интерполяционного полинома в следующем виде:
$$f(x)=\sum_{j=1}^n \frac{y_j \cdot W_n(x)}{(x-x_j) \cdot W_n^l(x_j)} $$ $$W_n(x)=(x-x_1) \cdot ... \cdot (x-x_n) $$Для построения интерполяционного полинома напишем Windows приложение с графическим интерфейсом, удовлетворяющим следующим требованиям:
Согласно требованиям, наша программа должна осуществлять полноценный графический вывод информации на экран. При этом возникает проблема аппаратно-независимого вывода изображения. Каждое Windows окно имеет собственную стандартную (аппаратную) систему координат.
Стандартная (аппаратная) система координат - левая система координат с центром в левом верхнем углу окна. Ось X направлена вправо, а ось Y вниз. Единица измерения - 1 пиксель.
Стандартные координаты аппаратно зависимы. А это значит, что на разных мониторах и принтерах изображения размером n?n пикселей будут иметь различные размеры, в зависимости от размеров пикселей поддерживаемых устройством вывода. Аппаратная зависимость координат также не позволяет выводить на экран изображения, размеры которых превосходят размеры клиентской области окна. Для преодоления аппаратной зависимости необходимо выполнить преобразование координат.
Принципы построения собственных функций преобразования координат
Чтобы реализовать аппаратно независимый вывод графики, можно ввести 2 логические системы координат: видимую и невидимую и организовать переход от одной к другой. Видимая система координат - основная, она и определяет логические координаты точек. Невидимая система координат - вводится для реализации точки наблюдения. рис 5.1
(рис 5.1) Экранная система координатГеометрический смысл преобразования. Переход от стандартной системы координат к видимой логической выполняется для преодоления проблемы несоответствия размеров пикселей на разных устройствах. А переход от видимой логической системы координат к невидимой системе координат необходим, если требуется отобразить ту часть изображения, которая не помещается в окне.
Математическая реализация. Пусть (a,b) - координаты центра видимой логической системы координат в стандартной системе координат, а (c,d) - координаты точки наблюдения в видимой логической системе координат. Тогда формула для выполнения нужного перехода:
S - коэффициент масштабирования (он выбирается в зависимости от характеристик монитора). Теперь, пользуясь этой формулой, можно, зная логические координаты, переходить к системным координатам, что необходимо для рисования. А так же, изменяя координаты точки наблюдения, отображать невидимые ранее части образа.
Нетрудно получить и обратное выражение логических координат через системные координаты. Это нужно, чтобы выводить информацию о положении на плоскости.
Этапы разработки
Создадим с помощью мастера MFC стандартное
1. Функции перехода от логических координат к системным координатам и наоборот
Логические координаты на плоскости представляют собой упорядоченную пару вещественных чисел. Для их программной реализации следует ввести структуру SDPoint. Вынесем ее в новый заголовочный файл (например, DoublePoint.h )
struct SDPoint
{
double x;
double y;
};
Далее приведен полный исходный код класса вида CGpView программы. Пояснения ко всем введенным переменным, функциям и обработчикам приведены в пунктах, при реализации которых использовались эти элементы. В рамках данного пункта рассмотрим функции перехода от системных координат к логическим координатам, выделенные жирным шрифтом.
// GpView.h : interface of the CGpView class
#pragma once
class CGpView : public CView
{
protected: // create from serialization only
CGpView();
DECLARE_DYNCREATE(CGpView)
// Attributes
public:
CGpDoc* GetDocument() const;
// Operations
public:
// Overrides
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT cs);
protected:
// Implementation
public:
virtual ~CGpView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext dc) const;
#endif
protected:
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
private: //Добавляем необходимые переменные
int ScaleXY; //Масштабный коэффициент (сколько пикселей в логической единице)
SDPoint CameraPoint; //Логические координаты точки наблюдения
bool parallel_shift; //флаг для входа(выхода) в(из) режим(а) параллельного переноса
bool move_camera; //флаг для активации параллельного переноса
CPoint MousePosPoint; //Системные координаты курсора мыши
CFont axisFont; //Шрифт для подписи осей
CPen polPen; //Перо для рисования графика полинома
bool mark_points; //Флаг для входа(выхода) в(из) режим(а) визуального
редактирования узлов интерполирования
CImage imgOriginal; //Переменная для сохранения изображения клиентской области окна (графика)
public: //Необходимые функции
SDPoint SysToLog(CPoint SysPoint); //Функция перехода от системных к логическим координатам
CPoint LogToSys(double xl, double yl); //Функция перехода от логических коодинат к системным
void DrawAxis(CDC* pDC); //Функция для прорисовки осей
void DrawPolinom(CDC* pDC); //Функция для прорисовки полинома
void DrawInitPoints(CDC* pDC); //Функция для прорисовки узлов интерполяции
public: //Обработчики (добавлены с помощью мастера)
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnToolsParallelshift();
afx_msg void OnUpdateToolsParallelshift(CCmdUI *pCmdUI);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg void OnToolsAddpoint();
afx_msg void OnToolsMarkinitialpoints();
afx_msg void OnUpdateToolsMarkinitialpoints(CCmdUI *pCmdUI);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
afx_msg void OnToolsShowpolinomialkoefficients();
afx_msg void OnUpdateToolsShowpolinomialkoefficients(CCmdUI *pCmdUI);
afx_msg void OnFileSaveAs();
};
#ifndef _DEBUG // debug version in GpView.cpp
inline CGpDoc* CGpView::GetDocument() const
{ return reinterpret_cast<CGpDoc*>(m_pDocument); }
#endif
Функция перехода от системных координат к логическим координатам:
SDPoint CGpView::SysToLog(CPoint SysPoint)
{
CRect cr;
GetClientRect(cr);
SDPoint Res;
Res.x = (static_cast<double>(SysPoint.x) - static_cast<double>(cr.right)/2)/ScaleXY - CameraPoint.x;
Res.y = (static_cast<double>(cr.bottom)/2 - static_cast<double>(SysPoint.y))/ScaleXY - CameraPoint.y;
return Res;
}
Функция перехода от логических координат к системным координатам:
CPoint CGpView::LogToSys(double xl, double yl)
{
CRect cr;
GetClientRect(cr);
CPoint Res;
Res.x = static_cast<int>((xl + CameraPoint.x)*ScaleXY + cr.right/2);
Res.y = static_cast<int>(-(yl + CameraPoint.y)*ScaleXY + cr.bottom/2);
return Res;
}
CameraPoint - переменная для хранения координат точки наблюдения типа SDPoint.
Функция GetClientRect(cr) используется для получения клиентского прямоугольника. ScaleXY - переменная типа int, которая хранит масштаб (число пикселей в логической единице). Ее значение может быть задано произвольно, а может быть связано с характеристиками монитора с помощью соответствующих функций (например, int GetDeviceCaps(. Это основные функции, которые обеспечивают правильную работу программы в дальнейшем.
2. Параллельный перенос и масштабирование плоскости
Создадим пункт меню Tools (инструменты). В нем создадим подпункт Parallel Shift (параллельный перенос) и связанную с ним кнопку на панели инструментов, которые переводят программу в режим ожидания параллельного переноса или выводят ее из него. Если программа находится в режиме ожидания параллельного переноса, то нажатие левой кнопки активирует его и программа начинает обрабатывать событие WM_MOUSEMOVE (движение мыши). В соответствии с перемещением курсора, перемещается точка наблюдения. Отпускание левой кнопки мыши деактивирует перенос и возвращает программу в режим ожидания. В данном пункте необходимо добавить обработчики движения мыши, нажатия левой кнопки мыши и ее отпускания.
Обработчик движения мыши (важные для этого пункта строки выделены жирным шрифтом):
void CGpView::OnMouseMove(UINT nFlags, CPoint point)
{
CRect cr; //Переменная для хранения размеров клиентского прямоугольника
CString str; //Строка для записи и вывода координат курсора
GetClientRect(cr); //Получение размеров клиентского прямоугольника
CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Указатель на
основную рамку (необходимо для обращения к строке состояния)
CStatusBar *pStatus = pFrame->m_wndStatusBar; //Указатель на строку состояния
(будет отображать координаты курсора)
if(cr.PtInRect(point)) //Если курсор в клиентском прямоугольнике то...
{
SetCapture(); //Захватываем мышь
if(parallel_shift) //Если в режиме ожидания параллельного переноса то...
if(move_camera) //Если перенос актвирован
{
//Сдвигается точка наблюдения на разность между текущим и предыдущим положениями курсора
CameraPoint.x += (static_cast<double>(point.x) - static_cast<double>(MousePosPoint.x))/ScaleXY;
CameraPoint.y -= (static_cast<double>(point.y) - static_cast<double>(MousePosPoint.y))/ScaleXY;
Invalidate(); //Обновление клиентсой области
MousePosPoint = point; //Сохранение положения курсора
}
else SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Если перенос
не активирован то устанавливается нужный курсор
if(mark_points) SetCursor(LoadCursor(NULL,IDC_CROSS)); //Если в режиме визуального редактирования
то устанавливается соответствующий курсор
}
else //Если курсор вне клиентского прямоугольника то...
{
ReleaseCapture(); //Освобождается мышь
move_camera = false; //Деактивируется перенос
}
if(pStatus) //Если указатель на строку состояния верный то...
{
str.Format("x = %f", SysToLog(point).x); //Запись во вспомогательную строку координаты x
pStatus->SetPaneText(0,str); //Вывод в строку состояния
str.Format("y = %f", SysToLog(point).y); //Запись во вспомогательную строку координаты y
pStatus->SetPaneText(1,str); //Вывод в строку соостояния
}
CView::OnMouseMove(nFlags, point);
}
MousePosPoint - это переменная типа CPoint, которая служит для хранения координат курсора и объявлена в прототипе класса CGpView с модификатором private. С тем же модификатором объявлены переменные parallel_shift и move_camera типа bool. Первая переменная сигнализирует, нажата ли кнопка параллельного переноса, а вторая - активирован ли перенос (нажата или отпущена левая кнопка мыши). Функция SetCapture() перенаправляет все сообщения мыши на обработку окну, которое ее вызвало. Функция ReleaseCapture() восстанавливает стандартный путь сообщений мыши в операционной системе. Функция SetCursor(...) меняет изображение курсора. Функция LoadCursorA(...), член класса приложения, загружает курсор из ресурсов приложения, вызвавшего ее, и возвращает его описатель, в качестве
входного параметра требует его идентификатор. Функция AfxGetApp() возвращает указатель на экземпляр приложения.
Обработчик нажатия левой кнопки мыши:
void CGpView::OnLButtonDown(UINT nFlags, CPoint point) //Обработчик нажатия левой кнопки мыши
{
CRect cr; //Переменная для хранения размеров клиентского прямоугольника
GetClientRect(cr); //Получение размеров клиентского прямоугольника
if(parallel_shift cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник...
{
MousePosPoint = point; //Сохранение координат нажатия
move_camera = true; //Активация переноса
SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_FIST)); //Устанавливается соответствующий курсор
}
CView::OnLButtonDown(nFlags, point);
}
Обработчик отпускания левой кнопки мыши:
void CGpView::OnLButtonUp(UINT nFlags, CPoint point) //Обработчик отпускания левой кнопки мыши
{
CRect cr; //Переменная для хранения размеров клиентского прямоугольника
GetClientRect(cr); //Получение размеров клиентского прямоугольника
if(parallel_shift cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник ...
{
move_camera = false; //Деактивация переноса
SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Устанавливается соответствующий курсор
}
CView::OnLButtonUp(nFlags, point);
}
Для корректной работы программы создадим и отредактируем соответствующие обработчики для кнопки Parallel Shift. В рамках данного пункта важны строки, выделенные жирным шрифтом. Обработчик пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов:
void CGpView::OnToolsParallelshift()
{
mark_points = false; //Если в режиме визуального редактирования узлов то выход из него
parallel_shift = !parallel_shift; //Вход или выход в режим ожидания переноса
}
Обработчик изменения внешнего вида пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов:
void CGpView::OnUpdateToolsParallelshift(CCmdUI *pCmdUI) //Обрабатывает изменение внешнего вида кнопки (пункта меню)
{
pCmdUI->SetCheck(parallel_shift); //Смена статуса (активна или не активна) в зависимости от флага
}
Для масштабирования следует обрабатывать прокрутку колеса мыши. В зависимости от направления вращения будет меняться масштабный коэффициент. Обработчик колесика мыши:
BOOL CGpView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
CString str("Updating coordinates. Please move cursor."); //Информативная строка
CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Получение указатель на основную рамку
CStatusBar *pStatus = pFrame->m_wndStatusBar; //Получение указатель на строку состояния
if(zDelta > 0) {if(ScaleXY < 200) ScaleXY += 5;} //Если вращение вперед, то увеличивается массштабный коэффициент
else if(ScaleXY > 40) ScaleXY -= 5; //Если вращение назад, то уменьшается масштабный коэффициент
Invalidate(); //Перерисовываем
if(pStatus) //Если указатель на строку состояния истина, то
{
//Вывод информативной строки в строку состояния
pStatus->SetPaneText(0,str);
pStatus->SetPaneText(1,str);
}
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
3. Ввод начальных данных
Рассмотрим два способа ввода начальных данных: ввод точных данных с клавиатуры и визуальное редактирование таблицы узлов посредством мыши. Первый подход реализован с помощью создания и подключения к приложению диалогового окна с необходимыми элементами управления, а второй с помощью обработки двойного нажатия левой кнопки мыши. Также необходимо организовать хранение информации об узлах. Ввод данных с клавиатуры. Создадим ресурс диалогового окна с помощью редактора ресурсов. В диалоговом окне должны присутствовать 2 текстовых поля ввода (координаты x и y), 4 кнопки (Add, Delete, Ok, Cancel) и список для отображения таблицы узлов. После создания ресурса, добавим класс диалогового окна на его базе. Создадим подпункт меню Add/Delete Initial Points в пункте Tools и аналогичную кнопку на панели управления, при нажатии на которые будет появляться диалоговое окно.
Прототип класса диалогового окна для ввода узлов:
#pragma once
#include "afxwin.h"
// CAddPointDlg dialog
class CAddPointDlg : public CDialog
{
DECLARE_DYNAMIC(CAddPointDlg)
public:
CAddPointDlg(CWnd* pParent = NULL); // standard constructor
virtual ~CAddPointDlg();
// Dialog Data
enum { IDD = IDD_ADDPOINT_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public: //переменные связанные с элементами управления добавлены с помощью мастера
CListBox PointList; //Переменная связанная со списком
double ValX; //Переменная связанная с первым текстовым полем
double ValY; //Переменная связанная со вторым текстовым полем
vector<SDPoint> ListedPointsVec; //Вектор узлов интерполирования (добавлена вручную)
public: //Обработчики и переопределенные виртуальные функции добавлены с помощью мастера
afx_msg void OnBnClickedButtonAdd(); //Обработчик кнопки Add
virtual BOOL OnInitDialog();
afx_msg void OnBnClickedButtonDelete(); //Обработчик кнопки Delete
};
Обработчик кнопки Add на диалоговом окне:
void CAddPointDlg::OnBnClickedButtonAdd()
{
CString str; //Вспомогательная строка для вывода узла в список
SDPoint temp; //Вспомогательная точка
UpdateData(true); //Обмен данными
temp.x = ValX; //Инициализация вспомогательной точки
temp.y = ValY;
for(size_t i = 0; i < ListedPointsVec.size(); i++) //Перебор всех узлов интерполяции
if(ListedPointsVec[i].x == ValX ListedPointsVec[i].y == ValY) //Если один из узлов совпал с вводимым то...
return; //выход из функции
ListedPointsVec.push_back(temp); //Добавление нового узла в вектор (проверка сделана, одинаковых нет)
str.Format("(%f;%f)",ValX,ValY); //Формирование строки для добавления в список
PointList.AddString(str); //Добавление строки в конец списка
}
Функция для инициализации переменных диалогового окна:
BOOL CAddPointDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CString str; //Вспомогательная строка для формирования списка
for(size_t i = 0; i < ListedPointsVec.size(); i++) //Перебор по всему вектору узлов
{
str.Format("(%f;%f)",ListedPointsVec[i].x,ListedPointsVec[i].y); //Формирование строки
PointList.AddString(str); //Добавление ее в список
}
return TRUE;
}
Обработчик кнопки Delete на диалоговом окне:
void CAddPointDlg::OnBnClickedButtonDelete()
{
int n = PointList.GetCurSel(); //Получение номера выделенной строки списка
if(n == LB_ERR) return; //Если ничего не выделено - выход
if(n == ListedPointsVec.size()) //Если выделена последняя строка, то...
ListedPointsVec.pop_back(); //удаление последнего элемента из таблицы
else //иначе...
{
for(size_t i = n; i < ListedPointsVec.size()-1; i++) //перебор узлов начиная с n-ого и до конца
{
//Смещение узлов в векторе
ListedPointsVec[i].x = ListedPointsVec[i+1].x;
ListedPointsVec[i].y = ListedPointsVec[i+1].y;
}
ListedPointsVec.pop_back(); //Удаление последнего элемента
}
PointList.DeleteString(n); //Удаление ненужней строки списка
}
Для корректной работы последней функции, в списке должна быть отключена сортировка. Когда диалоговый класс реализован, необходимо подключить его к программе. Для этого используем соответствующий пункт меню и кнопку на панели управления - Add/Delete Initial Points. Также необходимо передать введенные данные в основное хранилище информации (объект класса документа). Обработчик пункта меню Add/Delete Initial Points и связанной с ним кнопки на панели инструментов:
void CGpView::OnToolsAddpoint()
{
CAddPointDlg dlg; //Создаем экземпляр класса диалогового
//Стандартная процедура получения указателя на документ и его проверки на //корректность
CGpDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
pol_ready = false; //Сигнализируем о том что построенный полином устарел (сейчас будет меняться таблица)
Invalidate(); //Перерисовываем
dlg.ListedPointsVec = pDoc->InitPoints; //переписываем копию узлов в диалоговую переменную
switch(dlg.DoModal()) //Ждем реультата работы диалога
{
case -1: AfxMessageBox("AddPoiintDialog creation error"); return; //Если диалог не запустился
case IDOK: pDoc->InitPoints = dlg.ListedPointsVec; Invalidate(); break; //Если нажали Ok то переписываем
новую таблицу в документ и перерисовываем
case IDCANCEL: break; //Если Cancel ничего не делаем
}
}
Этот обработчик осуществляет связь между механизмом изменения (диалоговое окно) узлов и механизмом их хранения (объект документа). Прежде чем переходить к реализации визуального редактирования, рассмотрим механизм хранения узлов и интерполяционного полинома. Объект документа полностью соответствует требованиям хранилища необходимых программе данных. Прототип класса документа:
// GpDoc.h : interface of the CGpDoc class
//
#pragma once
#include "TPolinom.h"
class CGpDoc : public CDocument
{
protected: // create from serialization only
CGpDoc();
DECLARE_DYNCREATE(CGpDoc)
// Attributes
public:
// Operations
public:
// Overrides
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive ar);
// Implementation
public:
virtual ~CGpDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext dc) const;
#endif
protected:
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
public: //Необходимые переменные
vector<SDPoint> InitPoints; //Таблица узлов реализована с помощью вектора
вещественных точек плоскости
TPolinom<double> InterPol; //Интерполяционный полином
bool pol_ready; //Флаг сигнализирующий о том что полином построен
public:
bool BuildPolinom(void); //Функция для построения полинома
afx_msg void OnToolsBuilddrawpolinom();
};
Важным для данного пункта является объект InitPoints класса vector SDPoint . В него записываются и хранятся на протяжении работы программы узлы интерполирования.
Визуальное редактирование таблицы узлов. Создадим подпункт Mark initial points в меню и соответствующую кнопку, которые будут переводить программу в режим ожидания редактирования (двойного клика по левой кнопке мыши) и выводить из него. Добавим обработчик сообщения WM_LBTNDBLCLICK
Обработчик нажатия пункта меню Mark initial points и связанной с ним кнопки на панели инструментов:
void CGpView::OnToolsMarkinitialpoints()
{
parallel_shift = false; //Если в режиме ожидания переноса то выход из него
mark_points = !mark_points; //Вход или выход в(из) режима визуального редактирования узлов
}
Обработчик изменения внешнего вида пункта меню Mark initial points и связанной с ним кнопки на панели инструментов:
void CGpView::OnUpdateToolsMarkinitialpoints(CCmdUI *pCmdUI)//Обрабатываем изменение внешнего вида кнопки (пункта меню)
{
pCmdUI->SetCheck(mark_points); //Смена статуса (активна или не активна) в зависимости от флага
}
Обработчик двойного клика по левой кнопке мыши:
void CGpView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
if(mark_points) //Если в режиме визуального редактирования, то...
{
//Получение указателя на документ
CGpDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
SDPoint help = SysToLog(point); //Вспомогательная переменная для проверки (вводится ли уже существующий узел)
for(size_t i = 0; i < pDoc->InitPoints.size(); i++) //Перебор по всем узлам и проверка...
if(help.x == pDoc->InitPoints[i].x help.y == pDoc->InitPoints[i].y) //Совпадает с вводимым хотя бы оди узел таблицы
return; //Если да, то выход из функции без добавления вводимого узла в таблицу
pDoc->InitPoints.push_back(help); //Добавление вводимого узла в таблицу (проверка сделана, повторов нет)
Invalidate(); //Обновление клиентской области окна
}
CView::OnLButtonDblClk(nFlags, point);
}
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( и void DrawInitPoints( ;
Функция для рисования осей координат:
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; //Если полином не построен, то выход из функции
}
Эти три функции обеспечивают вывод графической информации. Все они имеют один и тот же прототип, но различные имена. Каждая функция принимает в качестве аргумента указатель на контекст устройства (таким образом, основная задача графического вывода разбивается на 3 более простых). Остается только последовательно вызвать их в методе класса вида OnDraw(.
Функция OnDraw:
void CGpView::OnDraw(CDC* pDC)
{
DrawAxis(pDC);
DrawInitPoints(pDC);
DrawPolinom(pDC);
}
Для запуска механизма построения и отображения полинома, необходимо обработать выбор соответствующего пункта меню и нажатие на кнопку на панели инструментов.
Обработчик пункта меню Build and draw
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 для новой строки состояния и добавить обработчики для пункта меню
void CMainFrame::OnViewStatusBar()
{
m_wndStatusBar.ShowWindow(!(m_wndStatusBar.GetStyle() WS_VISIBLE));
RecalcLayout();
}
Обработчик изменения внешнего вида пункта меню
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
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;
}
}
Данная функция выполняет сохранение изображения клиентской области окна в файл в виде растрового изображения. Для сохранения используется метод 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) График интерполяционного полинома
В данном параграфе, на примере написания приложения "Шифр Виженера", рассматриваются следующие вопросы: работа с классом CRichEdit, ввод и форматирование текста, сохранение форматированного текста в файл, работа с модальными диалоговыми окнами, работа с таймером и элементом управления "индикатор хода процесса".
Пусть у нас есть текст. Все буквы текста принадлежат алфавиту A=(a1,...,an), где ai - i -ый символ алфавита. Требуется зашифровать этот текст по алгоритму Виженера.
Шифр Виженера - это шифр простой замены, с секретным ключом.
Шифрование происходит по формуле xi->ci=xi+ei(mod k)(mod n), а дешифрование по формуле ci->xi=ci+ei(mod k)(mod n), где n - число символов в алфавите, k - число символов в секретном слове (ключе), x - i -ый символ открытого текста, c - i -ый символ шифрованного текста.
Задача. Написать приложение для реализации шифрования текста по алгоритму Вижинера.В приложении должна быть:
В качестве алфавита в программе будем использовать наиболее распространенные печатные символы ASCII кодировки. Для работы с текстом будем использовать специализированный класс CRichEditCtrl.
MFC предоставляет два основных средства редактирования текста. Это - обычное поле ввода (Edit Control) и поле ввода с форматированием (Rich Edit Control). Ими можно воспользоваться как элементами управления в диалоговых окнах, а можно создавать на их основе окна представлений, как в стандартных текстовых редакторах Windows: Notepad и WordPad. Такую гибкость обеспечивают классы CEditView и CRichEditView.
Класс CEditView
В основе этого класса - элемент управления Windows поле ввода. Мастер MFC Application Wizard позволяет наследовать ваш собственный класс "вида" от CEditView. При работе с объектами класса CEditView доступны все методы классов CView и CEdit.Множественное наследование здесь не применяется. Размер текста обрабатываемого данным классом по умолчанию ограничен - не более 1048575 символов, его можно изменить, отправив сообщение EM_LIMITTEXT. Класс CEditView имеет следующие ограничения:
CEditView не поддерживает WYSIWYG редактирование.
В случае, когда стоит выбор между читаемостью текста на дисплее и соответствию размеров при печати, CEditView отобразит текст в соответствии с первым критерием.CEditView может отображать текст только в одном формате. Нет специализированной поддержки шрифтов.Класс CRichEditView
Этот класс базируется на элементе управления "поле ввода с форматированием". Элемент управления "поле ввода с форматированием" - окно, в которое пользователь может вводить и редактировать текст. Данный элемент управления позволяет работать с форматированным текстом и встроенными OLE объектами, предоставляет методы для управления параметрами шрифтов и абзацев, поддерживает большие объемы текста. Класс CRichEditView предназначен для совместного использования с классом CRichEditDoc и CRichEdiCtrl, что позволяет создавать полноценные контейнерные приложения ActiveX.
Класс CRichEditCtrl
Этот класс представляет собой оболочку для элемента управления "поле ввода с форматированием". В нем переопределены обработчики сообщений клавиатуры и мыши для ввода и редактирования текстовой информации. Также он предоставляет множество методов для работы с текстом. Рассмотрим их подробнее.
1. Методы для создания объектов CRichEditCtrl
Метод virtual BOOL Create(DWORD dwStyle, const RECT rect, CWnd* pParentWnd, UINT создает окно элемента управления "поле ввода с форматированием" и связывает его с классом CRichEditCtrl. Параметр dwStyle задает набор флагов, определяющих стиль окна, rect задает оконный прямоугольник, pParentWnd - указатель на родительское окно (если метод Create(…) вызывается в диалоговом окне, то параметр pParentWnd должен иметь значение NULL ), - идентификатор окна.
Если поле ввода с форматированием используется в диалоговом окне, то в функции InitInstance класса приложения необходимо вызвать функцию AfxInitReichEdit() для того, чтобы загрузить необходимые библиотеки элементов управления.
2 Методы управления строками
Метод int GetLine(int nIndex, LPTSTR lpszBuffer, int nMaxLength) const копирует строку, номер которой указан в параметре nIndex в буфер lpszBuffer длиной в nMaxLength символов. Скопированная строка не содержит в конце символа окончания строки.
Метод int GetLineCount() const возвращает количество строк в текстовом поле.
Метод int LineLength(int nLine = -1) const возвращает длину строки в байтах, номер которой указан в параметре nLine, по умолчанию возвращает длину строки, содержащей каретку.
3. Методы выделения текста
Метод void Clear() удаляет выделенный текст.
Метод void GetSel(long nStartChar, long nEndChar) const записывает границы выделенного текста в переменные nStartChar (начало) и nEndChar (конец).
Метод CString GetSelText( ) const возвращает объект-строку с выделенным текстом.
Метод void SetSel(long nStartChar, long nEndChar) выделяет текст, начало выделения передается в параметре nStartSel, конец - в параметре nEndSel.
4. Методы форматирования текста
Метод DWORD GetDefaultCharFormat(CHARFORMAT cf) const возвращает в параметр cf формат символов, принятый текстовым полем по умолчанию.
Метод DWORD GetSelectionCharFormat(CHARFORMAT cf) const возвращает в параметр cf формат выделенного текста.
Метод BOOL SetDefaultCharFormat(CHARFORMAT cf) устанавливает формат символов, который текстовое поле будет использовать по умолчанию. Параметр cf определяет устанавливаемый формат.
Метод BOOL SetSelectionCharFormat(CHARFORMAT cf) устанавливает формат выделенных в текстовом поле символов, указанный в параметре cf.
Все вышеуказанные методы требуют в качестве параметра ссылку на объект структуры CHARFORMAT.Поля данной структуры подробнее рассмотрены далее.
5 Методы работы с потоками
Метод StreamIn(int nFormat, EDITSTREAM es) предназначен для чтения данных из потока. Параметр nFormat определяет формат читаемых данных, может принимать значения: SF_TEXT и SF_RTF. В первом случае прочтенные данные воспринимаются как неформатированный текст, во втором - как форматированный. Параметр es определяет поток, из которого следует читать информацию, имеет тип EDITSTREAM.
Метод StreamOut(int nFormat, EDITSTREAM es) предназначен для записи данных в поток. Имеет такой же набор аргументов, как и предыдущий метод.
Закончим на этом рассмотрение специализированных методов класса CRichEditCtrl для работы с текстом. Их применение будет продемонстрировано ниже в примере "Шифр Виженера".
Этапы разработки приложения
Создадим с помощью мастера MFC Application Wizard
1. Настройка каркаса MFC для ввода и хранения текстовой информации
Основным классом для хранения и отображения данных в случае, когда отключена поддержка каркаса "документ - вид", является класс CChildView, наследник класса CWnd. Мы воспользуемся специализированным классом CRichEditCtrl для работы с текстом. Этот класс предоставляет множество методов для форматирования, редактирования, отображения и сохранения текстовой информации.
Добавим в прототип класса CChildView следующую строку:
public: CRichEditCtrl m_rich; //Переменная для управления текстовым полем с форматированием
m_rich - переменная, через которую будем осуществлять взаимодействие с текстовым полем типа CRichEditCtrl. Далее, для инициализации введенной переменной, обработаем сообщения WM_CREATE и WM_SIZE.
Обработчик сообщения WM_CREATE:
int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
CRect cr(0,0,0,0); //Создадим прямоугольник
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
m_rich.Create(ES_AUTOVSCROLL|ES_MULTILINE|ES_WANTRETURN|WS_CHILD|WS_VISIBLE|WS_VSCROLL,cr,this,1);
//Инициализируем переменную для управления текстовым полем
return 0;
}
При создании окна конструируется поле ввода нулевых размеров, т.к. на данном этапе выполнения программы длина и ширина клиентской области еще неизвестны. Для создания объекта класса CRichEditCtrl используется метод Create(…). Первый аргумент метода - набор флагов, определяющих стиль, второй - прямоугольник с размерами, третий - указатель на окно родителя, четвертый - идентификатор окна элемента управления.
Обработчик WM_SIZE:
void CChildView::OnSize(UINT nType, int cx, int cy)
{
CRect cr; //Создание переменной для получения размеров клиентского прямоугольника
CView::OnSize(nType, cx, cy);
GetClientRect(cr); //Получение размеров клиентского прямоугольника
m_rich.SetWindowPos(wndTop,0,0,cr.right-cr.left,cr.bottom-cr.top,SWP_SHOWWINDOW);
//Изменение размеров поля ввода
}
Функция GetClientRect(cr) используется для получения клиентского прямоугольника. Функция SetWindowPos(wndTop,0,0,cr.right-cr.left,cr.bottom-cr.top,SWP_SHOWWINDOW) устанавливает новое положение и размеры окна (в данном случае окна элемента управления Rich Edit). В параметрах указаны: указатель на окно предшественник (окно, поверх которого будет располагаться окно, вызвавшее функцию SetWindowPos(…)), координаты левого верхнего угла окна, длина и ширина окна, флаг со специальными параметрами отображения. Если скомпилировать и запустить приложение на данном этапе, то появится окно с растянутым на всю клиентскую область полем ввода, которое обрабатывает все сообщения клавиатуры. Добавим возможность считывания текстовой информации из файла в это поле ввода. Добавим элемент главного меню File $$\to$$ Open… и обработаем нажатие на него.
Добавим следующую строку в прототип класса CChildView:
protected: static DWORD CALLBACK FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb); //Функция обратного вызова для чтения данных из файла
Определим данную функцию в файле ChildView.cpp:
DWORD CALLBACK CChildView::FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb)
{
CFile *pFile = (CFile*) dwCookie;
*pcb = pFile->Read(pbBuf,cb);
return 0;
}
Обработчик пункта меню File $$\to$$ Open…
void CChildView::OnFileOpen()
{
CString strFilter; //строка с фильтром форматов
strFilter = "Text file|*.txt|Rich text format file|*.rtf||";
CFileDialog dlg(TRUE,NULL,NULL,OFN_FILEMUSTEXIST,strFilter); //Создание диалога для открытия файла
if(dlg.DoModal() == IDOK) //Если выбран файл и нажата кнопка Ok...
{
EDITSTREAM es; //Создаем структуру потока
CFile InFile(dlg.GetFileName(),CFile::modeRead); //Открываем файл с указанным именем для чтения
es.dwCookie = (DWORD) InFile; //Определяем поток
es.pfnCallback = FileStreamInCallback;
switch(dlg.m_ofn.nFilterIndex) //В зависимости от того, какой формат выбран, читаем текст из файла
{
case 1: m_Rich.StreamIn(SF_TEXT,es); break;
case 2: m_Rich.StreamIn(SF_RTF,es); break;
default: break;
}
AfxGetMainWnd()->SetWindowTextA(dlg.GetFileName()); //Выводим в заголовок окна название открытого файла
}
}
Для чтения текстовой информации из файла используется специализированный метод StreamIn(SF_TEXT,es) класса CRichEditCtrl. В качестве первого аргумента методу передается флаг, который определяет формат данных для чтения. Вторым аргументом является объект структуры EDITSTREAM. Поле dwCookie этой структуры определяет первый параметр, передаваемый в функцию чтения (обычно это идентификатор некоторого объекта, из которого следует читать данные). В поле pfnCallback записывается адрес функции чтения, которая определяет способ работы с данными и источником (она имеет строго определенный прототип). В данном случае мы сами создали функцию чтения данных: DWORD CALLBACK FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb) . Первый параметр - идентификатор источника, второй - указатель на буфер, третий - число байтов, которое необходимо считать, четвертый - указатель на переменную, в которую записывается число прочтенных байтов. Метод StreamIn(…) повторно вызывает функцию чтения, пока не произойдет одно из следующих событий:
Теперь, когда стало возможным чтение данных из файла и их ввод с клавиатуры, реализуем простейшие операции работы с буфером обмена (вырезать, копировать, вставить и т.п.). Для этого будем обрабатывать соответствующие пункты главного меню. Обработчик пункта меню Edit $$\to$$ Copy:
void CChildView::OnEditCopy()
{
m_Rich.Copy();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Copy:
void CChildView::OnUpdateEditCopy(CCmdUI *pCmdUI)
{
LONG nStartSel, nEndSel; //Переменные для хранения начальной и конечной позиции выделенного текста
m_Rich.GetSel(nStartSel,nEndSel); //Получение начальной и конечной позиции выделенного текста
pCmdUI->Enable(nStartSel != nEndSel); //Если текст выделен, то можно скопировать
}
Метод GetSel(nStartSel,nEndSel) записывает позиции начала и конца выделенного текста в соответствующие переменные, переданные ему в качестве параметров.
Обработчик пункта меню Edit $$\to$$ Cut:
void CChildView::OnEditCut()
{
m_Rich.Cut();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Cut:
void CChildView::OnUpdateEditCut(CCmdUI *pCmdUI)
{
LONG nStartSel, nEndSel;
m_Rich.GetSel(nStartSel,nEndSel);
pCmdUI->Enable(nStartSel != nEndSel);
}
Код данного обработчика совпадает с кодом обработчика изменения внешнего вида элемента Edit $$\to$$ Copy. Обработчик пункта меню Edit $$\to$$ Paste:
void CChildView::OnEditPaste()
{
m_Rich.Paste();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Paste: void CChildView::OnUpdateEditPaste(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_Rich.CanPaste());
}
В данном обработчике используется метод CanPaste() класса CRichEditCtrl, который возвращает значение TRUE, если данные, скопированные в буфер, поддерживаются элементом управления, и FALSE в любом другом случае.
Обработчик пункта меню Edit $$\to$$ Undo:
void CChildView::OnEditUndo()
{
m_Rich.Undo();
}
Обработчик изменения внешнего вида пункта меню Edit $$\to$$ Undo:
void CChildView::OnUpdateEditUndo(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_Rich.CanUndo());
}
Данные обработчики полностью реализуют необходимые функции работы с буфером обмена. Добавим возможность вызова контекстного меню с элементами, соответствующими пунктам меню Edit. Для этого обработаем сообщение WM_CONTEXTMENU.
Обработчик сообщения WM_CONTEXTMENU:
void CChildView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
CMenu Menu; //Создаем объект меню
Menu.LoadMenuA(IDR_MAINFRAME); //Загружаем ресурс меню
Menu.GetSubMenu(1)->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this); //Вызываем меню
}
Метод LoadMenuA(IDR_MAINFRAME) загружает из ресурсов приложения меню, идентификатор которого передан в качестве параметра. Метод GetSubMenu(1) возвращает указатель на подменю. Метод TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON, раскрывает контекстное меню в точке point.
2. Форматирование текста
Текстовое поле Rich Edit предназначено для работы с данными в формате rtf. То есть, существует возможность работы со шрифтами (изменение цвета, размера символов, их выделение, подчеркивание и т.п.). Реализуем некоторые функции работы со шрифтами в нашей программе. Для этого создадим пункт меню Format, а в нем подпункт Format $$\to$$ Font…, который будем обрабатывать. Обработчик пункта меню Format $$\to$$ Font…
void CChildView::OnFormatFont()
{
LONG nStartSel, nEndSel; //Переменные для хранения начала и конца выделения
CHARFORMAT cf_old, cf_new; //Переменные для хранения формата символов
m_Rich.GetSel(nStartSel,nEndSel); //Получение начала и конца выделения
if(nStartSel == nEndSel) m_Rich.GetDefaultCharFormat(cf_old); //Если ничего не выделено,
то сохраняем формат по умолчанию
else m_Rich.GetSelectionCharFormat(cf_old); //иначе сохраняем формат выделенного текста
CFontDialog fdlg(cf_old); //На основе полученного формата создаем диалог работы со шрифтом
if(fdlg.DoModal() == IDOK) //Если пользователь нажал Ok
{ //Заполняем поля объекта структуры CHARFORMAT
cf_new.cbSize = sizeof(CHARFORMAT);
cf_new.dwMask = CFM_BOLD|CFM_COLOR|CFM_FACE|CFM_ITALIC|CFM_SIZE|CFM_UNDERLINE|CFM_CHARSET|CFM_STRIKEOUT;
cf_new.dwEffects = (fdlg.m_lf.lfItalic ? CFE_ITALIC : 0)|(fdlg.m_lf.lfUnderline ? CFE_UNDERLINE : 0)|
(fdlg.m_lf.lfWeight > 400 ? CFE_BOLD : 0)|(fdlg.m_lf.lfStrikeOut ? CFE_STRIKEOUT : 0);
cf_new.yHeight = -MulDiv(fdlg.m_lf.lfHeight,1440,GetDC()->GetDeviceCaps(LOGPIXELSY));
cf_new.bPitchAndFamily = fdlg.m_lf.lfPitchAndFamily;
cf_new.bCharSet = fdlg.m_lf.lfCharSet;
cf_new.crTextColor = fdlg.m_cf.rgbColors;
if(nStartSel == nEndSel)
{
m_Rich.SetFocus();//Возвращаем фокус текстовому полю
m_Rich.SetDefaultCharFormat(cf_new); //Если текст не был выделен, то обновляем шрифт по умолчанию
}
else //иначе
{
m_Rich.SetFocus(); //Возвращаем фокус текстовому полю
m_Rich.SetSel(nStartSel,nEndSel); //Выделяем форматируемый текст
m_Rich.SetSelectionCharFormat(cf_new); //Обновляем формат выделенного текста
}
}
}
Для получения текущего формата символов и установки нового формата используются методы:
cf_oldcf_oldНаибольший интерес представляет собой структура CHARFORMAT, объектами которой являются переменные cf_old и cf_new. Рассмотрим поля данной структуры.
UINT cbSize - размер заданной структуры в байтах.DWORD dwMask - поле, содержащее информацию о доступных для изменения атрибутов шрифта.DWORD dwEffects - параметры шрифта (жирный, подчеркнутый, курсив и т.п.).LONG yHeight - высота символа в твипах (1/1440 дюйма).BYTE bPitchAndFamily - определяет семейство шрифта и расстояние между символами.BYTE bCharSet - определяет набор символов (алфавит).COLORREF crTextColor - цвет символов.Возможные значения полей данной структуры представлены в MSDN Library. Остановимся подробнее только на высоте символа. В программе она задается строкой cf_new.yHeight = -MulDiv(fdlg.m_lf.lfHeight,1440,GetDC()->GetDeviceCaps(LOGPIXELSY)). Функция MulDiv умножает два 32-битных значения (первый и второй аргумент), а затем делит получившееся 64-битное значение на третий аргумент и округляет результат до целых. Значение fdlg.m_lf.lfHeight определяет высоту символа в логических единицах дисплея, а GetDeviceCaps(LOGPIXELSY) определяет число логических единиц в дюйме. Таким образом, по формуле
рис 5.4
(рис 5.4) Формула переходамы переходим к нужным единицам измерения. Поле m_lf класса CFontDialog имеет тип LOGFONT и является GDI структурой, которая определяет параметры шрифта в логических
единицах дисплея.
3. Шифрование и дешифрование текста
Создадим ресурс диалога для шифрования с идентификатором IDD_CIPHER_DIALOG. Добавим два текстовых поля (Edit Control), два флажка-переключателя (
(рис 5.5) Шаблон диалогаПервое текстовое поле предназначено для ввода ключа шифрования, свойство Password должно иметь значение true. В групповое поле объединены элементы управления, отвечающие за вывод информации. Кнопка открывает диалог для указания пути к файлу в который будет сохранен шифротекст. Путь к файлу выводится во второе текстовое поле. Защитим его от изменения, установив свойство Read Only в значение true. Первый флажок-переключатель сигнализирует о необходимости записи конечного результата в файл. Второй-о необходимости вывода результата в основное текстовое поле программы.
Добавим в приложение класс диалога, созданный на основе данного ресурса. Добавим также необходимые переменные.
Класс CCipherDialog:
#pragma once
// CCipherDialog dialog
class CCipherDialog : public CDialog
{
DECLARE_DYNAMIC(CCipherDialog)
public:
CCipherDialog(CWnd* pParent = NULL); // standard constructor
virtual ~CCipherDialog();
// Dialog Data
enum { IDD = IDD_CIPHER_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public:
BOOL m_bCiphType; //Флаг указывает на то,какое действие необходимо
выполнить (шифрование/дешифрование)
CString m_sFilePathStr; //Переменная, связанная с текстовым полем,
в которое выводится путь файла для сохранения
CString m_sBufStr; //Переменная буфер
BOOL m_bToFile; //Переменная, связанная с первым флажком переключателем
BOOL m_bDisplay; //Переменная, связанная со вторым флажком переключателем
CString m_sKeyStr; //Переменная, связанная с первым текстовым полем,
в которое вводится ключ
int table[161]; //Таблица алфавита
int m_nTimer; //Счетчик таймера
int m_nCount; //Переменная индикатор хода процесса шифрования/дешифрования
bool m_bFinProc; //Флаг, сигнализирующий о том, прошел ли процесс до конца
public:
afx_msg void OnBnClickedButtonBrowse();
afx_msg void OnTimer(UINT_PTR nIDEvent);
protected:
virtual void OnCancel();
virtual void OnOK();
};
Конструктор класса CCipherDialog:
CCipherDialog::CCipherDialog(CWnd* pParent /*=NULL*/)
: CDialog(CCipherDialog::IDD, pParent) //Инициализация переменных
, m_bCiphType(FALSE)
, m_sFilePathStr(_T(""))
, m_sBufStr(_T(""))
, m_bToFile(FALSE)
, m_bDisplay(FALSE)
, m_sKeyStr(_T(""))
, m_nTimer(0)
, m_nCount(0)
, m_bFinProc(true)
{
for(int i = 0; i < 95; i++) //Создание таблицы для шифрования символов
table[i] = i + 32; //Используются ASCII коды только печатных символов
for(int i = 95; i < 159; i++)
table[i] = i - 159;
table[159] = -88;
table[160] = -72;
}
Функция DoDataExchenge:
void CCipherDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_FILEPATH, m_sFilePathStr);
DDX_Check(pDX, IDC_CHECK_TOFILE, m_bToFile);
DDX_Check(pDX, IDC_CHECK_TOCLIENT, m_bDisplay);
DDX_Text(pDX, IDC_EDIT_KEY, m_sKeyStr);
}
Функция DoDataExchenge используется для создания связей между переменными и элементами управления. При добавлении переменных с помощью мастера, ее код генерируется автоматически.
Для связи диалога с основным окном программы создадим пункт меню Cipher и в нем два подпункта Cipher $$\to$$ CCipherDialog добавим в начало файла ChildView.h строку
#include "CipherDialog.h"
Обработчик пункта меню Cipher $$\to$$
void CChildView::OnCipherEncipher()
{
CCipherDialog dlg; //Создание диалога шифрования/дешифрования
dlg.m_bCiphType = TRUE; //Инициализация флага индикатора действия
(будем производить шифрование)
m_Rich.GetWindowTextA(dlg.m_sBufStr); //Копирование текста из текстового поля
в строковую переменную - буфер диалога
if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
if(dlg.m_bToFile) //Если установлен флаг сохранения результата шифрования/дешифрования
{
int nBuf; //количество символов в тексте
char *dinBuf; //буфер с текстом
if(dlg.m_sFilePathStr != "") //Если верно указан путь файла
{
CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записиси
nBuf = dlg.m_sBufStr.GetLength() + 1; //Записываем количество символов текста в переменную
dinBuf = new char [nBuf]; //Инициализируем буфер
strcpy_s(dinBuf,nBuf,dlg.m_sBufStr); //Копируем данные из диалога в буфер
OutFile.Write(dinBuf,nBuf); //Записываем данные из буфера в файл
}
else AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено");
//Если неверно указан путь к файлу,то сообщаем об ошибке
}
if(dlg.m_bDisplay) //Если установлен флаг вывода результатов на экран
{
m_Rich.Clear(); //Очищаем текстовое поле
m_Rich.SetWindowTextA(dlg.m_sBufStr); //Записываем результат шифрования/дешифрования в текстовое поле
}
}
}
В данном пункте важен код, выделенный жирным шрифтом, остальное относится к выводу результатов шифрования/дешифрования на экран или их сохранения в файл. Для получения текста из элемента управления Rich Edit, используется метод GetWindowText(CString), аргументом ему передается строка, в которую будет записан текст. Для записи текста в текстовое поле, используется метод SetWindowText(CString).
Обработчик пункта меню Cipher $$\to$$
void CChildView::OnCipherDecipher()
{
CCipherDialog dlg;
m_Rich.GetWindowTextA(dlg.m_sBufStr);
if(dlg.DoModal() == IDOK)
{
if(dlg.m_bToFile)
{
int nBuf;
char *dinBuf;
if(dlg.m_sFilePathStr != "")
{
CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite);
nBuf = dlg.m_sBufStr.GetLength() + 1;
dinBuf = new char [nBuf];
strcpy_s(dinBuf,nBuf,dlg.m_sBufStr);
OutFile.Write(dinBuf,nBuf);
}
else
AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено");
}
if(dlg.m_bDisplay)
{
m_Rich.Clear();
m_Rich.SetWindowTextA(dlg.m_sBufStr);
}
}
}
Также как и в предыдущем обработчике строки, важные в данном пункте, выделены жирным шрифтом. Единственное отличие данного обработчика от предыдущего состоит в том, что мы не инициализируем переменную dlg.m_bCiphType. Это говорит о том, что будет происходить дешифрование.
Теперь реализуем алгоритм шифрования. Переопределим виртуальную функцию OnOk() класса CCipherDialog.
Переопределенный метод OnOk:
void CCipherDialog::OnOK()
{
MSG message; //Структура сообщение
UpdateData(true); //Обновляем данные
GetDlgItem(IDOK)->EnableWindow(FALSE); //Отключаем возможность повторного нажатия
кнопки Ok пока происходит шифрование
int nKeyLength = m_sKeyStr.GetLength(); //Инициализация переменной для хранения длины ключа
if(nKeyLength) //Если ключ введен
{
m_nTimer = (int)SetTimer(1,100,NULL); //Пускаем таймер с интервалом 100 милисекунд
CString tempstr; //Вспомогательная строка
for(m_nCount = 0; m_nCount < m_sBufStr.GetLength(); m_nCount++) //Пока не конец открытого текста
{
int buf_ascii = int(m_sBufStr[m_nCount]); //Шифруем или дешифруем по алгоритму
if(buf_ascii == 10 || buf_ascii == 13 || buf_ascii == 9) //Табуляции и переносы на новую строку не шифруем
{
tempstr += m_sBufStr[m_nCount];
continue;
}
int nTextChar = std::find(table,table+160,int(m_sBufStr[m_nCount])) - table;
int nKeyChar = std::find(table,table+160,int(m_sKeyStr[m_nCount % nKeyLength])) - table;
if(m_bCiphType) tempstr += char(table[(nTextChar + nKeyChar) % 161]);
else tempstr += char(table[(nTextChar + 161 - nKeyChar) % 161]);
if(::PeekMessageA(message,NULL,0,0,PM_REMOVE)) //При этом на каждой иттерации
цикла обрабатываем сообщения
{
::TranslateMessage(message);
::DispatchMessageA(message);
}
}
if(m_bFinProc) //Если процесс полностью завершен
{
m_sBufStr = tempstr; //Записываем все полученные данные в буфер
KillTimer(m_nTimer); //Сбрасывем таймер
CDialog::OnOK(); //Закрываем диалог
}
else //Иначе
{
m_bFinProc = true; //Сбрасываем флаг завершения процедуры в начальное положение
m_nCount = 0; //Сбрасываем счетчик символов
CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH); //Ининциализируем переменную
для управления индикатором хода процесса
pBar->SetPos(0); //Устанавливаем индикатор в начальное состояние
KillTimer(m_nTimer); //Сбрасываем таймер
}
}
else //Если ключ не введен
{
GetDlgItem(IDOK)->EnableWindow(TRUE); //Разблокируем кнопку Ok
AfxMessageBox("Введите ключ шифрования"); //Просим ввести ключ
}
}
Обратим внимание на использовании в данном методе функций: PeekMessageA(message,NULL,0,0,PM_REMOVE), TranslateMessage(message) и DispatchMessageA(message). Они позволяют обрабатывать сообщения на каждой иттерации цикла, создавая тем самым впечатление параллельности вычислений и реакции на действия пользователя. Функция SetTimer(1,100,NULL) запускает WM_TIMER.Первый аргумент функции - целое число, идентифицируещее таймер, третий - указатель на функцию, которая должна обрабатывать сообщение WM_TIMER. Если он равен NULL, то сообщение WM_TIMER посылается в общую очередь сообщений программы. Функция std::find(table,table+160,int(m_sBufStr[m_nCount])) - это алгоритм CCipherDialog обработчик сообщения WM_TIMER, для того чтобы выводить информацию о ходе шифрования в индикатор состояния.
Обработчик сообщения WM_TIMER:
void CCipherDialog::OnTimer(UINT_PTR nIDEvent)
{
CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH);
//Инициализируем переменную для управления индикатором процесса
if(m_sBufStr.GetLength()) //Если буфер не пуст
pBar->SetPos(m_nCount*100/m_sBufStr.GetLength()); //Устанавливаем позицию индикатора
процесса в соответствии с текущей позицией шифрования/дешифрования
CDialog::OnTimer(nIDEvent);
}
Функция GetDlgItem(IDC_PROGRESS_CIPH) возвращает указатель на объект элемента управления диалога, идентификатор которого передается ей в качестве аргумента.
Также необходимо переопределить функцию OnCancel() для данного диалога. Она прерывает процесс шифрования/дешифрования, если процесс начался, но не закрывает диалог. И закрывает диалог если процесс не запущен.
Переопределенный метод OnCancel():
void CCipherDialog::OnCancel()
{
if(m_nCount) //Если идет процесс, то
{
GetDlgItem(IDOK)->EnableWindow(TRUE); //разблокируем кнопку Ok
m_nCount = m_sBufStr.GetLength(); //завершаем цикл шифрования, присваивая переменной счетчику конечное значение
m_bFinProc = false; //Указываем на то, что шифрование/дешифрование было прервано
}
else Cdialog::OnCancel(); //Иначе закрываем диалог
}
Осталось обработать нажатие на кнопку Browse… Она должна открывать диалог для указания пути к файлу, в который будет выводиться результат шифрования/дешифрования. Обработчик кнопки Browse…
void CcipherDialog::OnBnClickedButtonBrowse()
{
Cstring strFilter; //Строка с поддерживаемыми форматами
Cstring strFileName; //Строка с путем и именем файла
Cstring strExtension; //Строка с расширением файла
UpdateData(true);//Обмен данными между элементами управления и переменными
strFilter = "Text file|*.txt||"; //Инициализация строки поддерживаемыми форматами
CfileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter);
//Создание диалога
if (dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
strFileName = dlg.m_ofn.lpstrFile; //Определяем путь и имя файла в который будем сохранять
if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввел расширение, то
{
switch (dlg.m_ofn.nFilterIndex) //В зависимости от того, какой из поддерживаемых форматов выбран
{
case 1: strExtension = "txt"; break; //Инициализируем строку с расширением
default: break;
}
strFileName = strFileName + '.' + strExtension; //Окончательно определяем строку
с именем путем и расширением
}
m_sFilePathStr = strFileName; //Записывае готовую строку с полным именем
в пременную член, связанную с текстовым полем
UpdateData(false); //Обновляем данные в элементах управления
в соответствии со связанными с ними переменными
}
}
4. Вывод результатов и их сохранение
Описание вывода результатов частично затронуто в предыдущем пункте. Теперь рассмотрим вывод форматированного текста в файл. Для этого создадим пункт меню File $$\to$$ Save as…, и обработаем нажатие на него. Но прежде добавим в файл ChildView.h строку:
protected:
static DWORD CALLBACK FileStreamOutCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb);
//Функция обратного вызова для записи данных в файл
и определим объявленный метод в файле ChildView.cpp:
DWORD CALLBACK CChildView::FileStreamOutCallback(DWORD dwCookie,
LPBYTE pbBuf, LONG cb, LONG *pcb)
{
CFile *pFile = (CFile*) dwCookie;
pFile->Write(pbBuf,cb);
*pcb = cb;
return 0;
}
Обработчик пункта меню File $$\to$$ Save as…
void CChildView::OnFileSaveas()
{
CString strFilter; //Строка для поддерживаемых форматов данных
CString strFileName; //Строка для имени файла
CString strExtension; //Строка для расширения файла
strFilter = "Text file|*.txt|Rich text format file|*.rtf||"; //Инициализация строки поддерживаемыми форматами
CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_EXPLORER,strFilter);
//Создание диалога сохранения
if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok
{
strFileName = dlg.m_ofn.lpstrFile; //Инициализируем строку с именем файла
if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввел расширение
{
switch (dlg.m_ofn.nFilterIndex) //В зависисмости от выбранного формата
{
case 1: strExtension = "txt"; break; //Инициализируем строку
case 2: strExtension = "rtf"; break; //с расширением
default: break;
}
strFileName = strFileName + '.' + strExtension; //Окончательно записываем путь имя и расширение файла
}
EDITSTREAM es; //Создаем структуру потока
CFile OutFile(strFileName,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записи
es.dwCookie = (DWORD) OutFile; //Определяем поле структуры, указывающее куда записывать данные
es.pfnCallback = FileStreamOutCallback; //Задаем функцию записи
switch(dlg.m_ofn.nFilterIndex) //В зависимости от выбранного формата
{
case 1: m_Rich.StreamOut(SF_TEXT,es); break; //Записываем данные
case 2: m_Rich.StreamOut(SF_RTF,es); break; //в файл
default: break;
}
}
}
Процедура записи данных в файл похожа на процедуру чтения. Мы пользуемся методом StreamOut(int nFormat, EDITSTREAM es) класса CRichEditCtrl, который требует аргументами формат записываемых данных и структуру потока. Структура потока EDITSTREAM определяется заданием пункта назначения и функции записи.
Добавим строку:
#include <algorithm>
в конец файла stdafx.h.
Скомпилируем и запустим приложение. В результате, в соответствии с открытым текстовым документом, получается следующее:
рис 5.6рис 5.7рис 5.8

(рис 5.7) Открытый текст в формате rtf (рис 5.6) Диалог шифрования
(рис 5.8) Зашифрованный текст (ключ шифрования "крипто")
В данном параграфе, на примере создания приложения "Метод наименьших квадратов (МНК) ", рассматриваются следующие вопросы: диалог в качестве основного окна приложения, элемент управления CListCtrl, элемент управления CRichEditCtrl, класс CArray, работа с матрицами.
Пусть у нас есть следующая таблица:
$$\begin{tabular}{|r||r||r||r|} \hline X x_1 ... x_n\\ \hline Y y_1 ... y_n\\ \hline \end{tabular} $$ $$\{x_1,...,x_n,y_1,...,y_n\} $$Требуется построить полином y=f(x) заданной степени k: k меньше n-1 такой, что сумма квадратов расстояний от точек таблицы до графика полинома была наименьшей. Для поиска коэффициентов полинома составим следующий функционал:
и поставим задачу минимизации этого функционала F -> min. Полином запишем в виде
тогда необходимое условие минимума запишется в виде:
$$\left\{ \begin{array}{l} \frac{\parial F}{\partial a_0}=\sum_{i=1}^n(y_i-a_0-a_1{x_i}-...-a_k{x_i^k})=0\\ \frac{\parial F}{\partial a_1}=\sum_{i=1}^n(y_i-a_0-a_1{x_i}-...-a_k{x_i^k})(x_i)=0\\ \vdots\\ \frac{\parial F}{\partial a_k}=\sum_{i=1}^n(y_i-a_0-a_1{x_i}-...-a_k{x_i^k})(x_i^k)=0 \end{array}$$ $$\left( \begin{array}{ccc} 1 \frac{\sum_{i=1}^n{x_i}}{n} \ldots \frac{\sum_{i=1}^n{x_i^k}}{n}\\ \frac{\sum_{i=1}^n{x_i}}{n} \frac{\sum_{i=1}^n{x_i^2}}{n} \ldots \frac{\sum_{i=1}^n{x_i^k+1}}{n}\\ \vdots\\ \frac{\sum_{i=1}^n{x_i^k}}{n} \frac{\sum_{i=1}^n{x_i^{k+1}}}{n} \ldots \frac{\sum_{i=1}^n{x_i^2k}}{n} \end{array} \right) \cdot \left( \begin{array}{c} a_0\\ a_1\\ \vdots\\ a_k \end{array}\right)= \left( \begin{array}{c} \frac{\sum_{i=1}^n{y_i}}{n}\\ \frac{\sum_{i=1}^n{y_i\cdot x_i}}{n}\\ \vdots\\ \frac{\sum_{i=1}^n{y_i \cdot x_i^k}}{n} \end{array}\right)$$Данная система имеет единственное решение, если k<n-1. Значит, существует единственный полином доставляющий минимум этому функционалу, с коэффициентами представимыми решением указанной системы. Напишем программу для нахождения коэффициентов полинома.
Этапы разработки
Создадим с помощью мастера MFC приложение "Regr" на основе диалогового окна. Для этого установим Application type в значение Dialog based. Снимем флажок Use Unicode libraries.
1. Создание ресурса диалогового окна
Создадим диалоговое окно с тремя текстовыми полями для ввода данных в таблицу и указания степени аппроксимации, четырьмя кнопками(Add, Delete, Start Approximation, Exit), списковым представлением для отображения таблицы точек и текстовым полем RichEdit для вывода строки результата. Шаблон диалога приведен на рисунке: рис 5.9
(рис 5.9) Создание диалогового окнаДля всех элементов управления типа поле ввода, установим свойство Right Align Text в значение true. Для элемента управления типа поле ввода, расположенного напротив надписи Approximation degree: установим свойство Number в значение true. Для элемента управления типа поле ввода с форматированием, установим свойство Auto HScroll в значение true, свойство Align Text в значение center, свойство в значение true, свойство Read Only в значение true. Для элемента управления типа списковое представление, установим свойство Border в значение false (убираем рамку для того, чтобы проще было получить размеры клиентской области элемента управления), свойство Single Selection в значение true, свойство View в значение Report (будем выводить таблицу).
2. Ввод и редактирование начальных данных
Начальные данные представляют собой точки плоскости. Как и в примере "RegrDlg.h, содержащий прототип класса основного диалога, следующий код:
class SDPoint
{
public:
double x;
double y;
SDPoint():x(0),y(0){} //Конструктор по умолчанию
SDPoint(double a, double b):x(a),y(b){} //Конструктор с параметрами
};
после строки
#pragma once
Теперь, создадим в классе диалога массив объектов класса SDPoint, который будет хранить начальные данные.
Прототип класса CRegrDlg:
// CRegrDlg dialog
class CRegrDlg : public CDialog
{
// Construction
public:
CRegrDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_REGR_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
//Обработчики событий добавлены с помощью мастера
public:
afx_msg void OnBnClickedButtonAdd();
afx_msg void OnBnClickedButtonDel();
afx_msg void OnBnClickedButtonStart();
public:
matrix<double> mRightPart; //Вектор - правая часть системы
matrix<double> mNodeMatrix; //Матрица системы
CArray<SDPoint,SDPoint> mInitArr; //Массив точек
//Следующие переменные добавлены с помощью утилиты ClassView и связаны с элементами управления
double mX; //Координата X точки связана с первым текстовым полем
double mY; //Координата Y связана со вторым текстовым полем
int mNum; //Степень аппроксимации связана с третьим текстовым полем
CListCtrl mListCtrl; //Переменная для управления списковым представлением
};
Жирным шрифтом выделена переменная, которую необходимо добавить в данном пункте. CArray - это шаблонный класс, инкапсулирующий динамические массивы в MFC. В качестве первого аргумента шаблону передается тип хранимых в массиве объектов (в данном случае SDPoint ), вторым аргументом передается тип объектов, использующихся для получения доступа к данным массива, обычно это ссылка на тип, указанный в первом аргументе (в данном случае SDPoint ). Для ввода данных в диалоге была создана кнопка Add и два текстовых поля. Добавим в диалоговый класс связанные с текстовыми полями переменные mX, mY, mNum и mListCtrl с помощью мастера. Прежде чем обрабатывать нажатие на кнопку Add, настроим списковое представление. Добавим следующие строки в функцию OnInitDialog():
BOOL CRegrDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
//Настраиваем Элемент управления "списковое представление"
CRect cr;
mListCtrl.GetWindowRect(cr); //Получение размеров окна элемента управления
mListCtrl.SetExtendedStyle(LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT|LVS_EX_ONECLICKACTIVATE);
//Задаем необходимые стили
mListCtrl.InsertColumn(0,_T("X"),LVCFMT_LEFT,(cr.right-cr.left)/2); //Добавляем колонку
mListCtrl.InsertColumn(1,_T("Y"),LVCFMT_RIGHT,(cr.right-cr.left)/2); //Добавляем еще одну
//Настраиваем самую левую колонку
LVCOLUMN lvm; //Структура для хранения информации о колонке
lvm.mask = LVCF_FMT; //Инициализация полей структуры
lvm.fmt = LVCFMT_RIGHT;
mListCtrl.SetColumn(0,lvm); //Задание необходимого стиля колонки
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
return TRUE; // return TRUE unless you set the focus to a control
}
Жирным шрифтом выделен добавленный код. А теперь добавим обработчик кнопки Add и Delete. Обработчик нажатия на кнопку Add:
void CRegrDlg::OnBnClickedButtonAdd()
{
CString str;
UpdateData(true); //Обмен данными
for(int i = 0; i < mInitArr.GetSize(); i++) //Перебираем все точки в массиве
if(mInitArr[i].x == mX mInitArr[i].y == mY) return; //Если существует такая же, то выходим
mInitArr.Add(SDPoint(mX,mY)); //Проверка сделана, повторов нет, добавляем точку в массив
str.Format("%f",mX); //Форматирум строку для вывода в таблицу
mListCtrl.InsertItem((int)mInitArr.GetSize()-1,str); //Добавляем строку в таблицу
str.Format("%f",mY); //Повторяем для следующей колонки
mListCtrl.SetItemText((int)mInitArr.GetSize()-1,1,str);
}
Обработчик нажатия на кнопку Delete:
void CRegrDlg::OnBnClickedButtonDel()
{
int n = mListCtrl.GetSelectionMark(); //Получаем номер выделенной строки списка
if(n < 0) return; //Если ничего не выделено,то выходим
mInitArr.RemoveAt(n); //Удаляем точку из массива
mListCtrl.DeleteItem(n); //Удаляем строку из списка
}
3. Построение системы линейных уравнений и ее решение
Для построения системы линейных уравнений будем использовать шаблонный класс матриц matrix<type>. Этот класс выделен в отдельный заголовочный файл matrix.h. Его необходимо скопировать в каталог с проектом и добавить в проект с помощью утилиты Solution Explorer (вызвать контекстное меню проекта в окне утилиты Solution Explorer $$\to$$ Add $$\to$$ Existing item… и далее указать имя файла). Полный код класса приведен в приложении. Напишем функции для поиска обратной матрицы. Введем их в файле RegrDlg.cpp после строк.
#include "stdafx.h" #include "Regr.h" #include "RegrDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif
Функции для поиска обратной матрицы:
//Функции для решения СЛАУ методом окаймления
matrix<double> bordering (matrix<double> u, matrix<double> v,
matrix<double> A, matrix<double> a)
{
matrix<double> b = a - u*A*v;
b.setcell(1/b.getcell(0,0),0,0);
matrix<double> s = -u*A*b.getcell(0,0);
matrix<double> w = -A*v*b.getcell(0,0);
matrix<double> B = A - A*v*s;
B.addcolend(w);
s.addcolend(b);
B.addrowend(s);
return B;
}
matrix>double> getreverse (matrix<double> M)
{
matrix<double> Res;
if(M.getcolsize() == M.getrowsize())
{
Res = M.getminor(0,0,0,0);
Res.setcell(1/Res.getcell(0,0),0,0);
for(uni i = 1; i < M.getcolsize(); i++)
{
matrix<double> v = M.getminor(i,0,i,i-1);
matrix<double> u = M.getminor(0,i,i-1,i);
matrix<double> a = M.getminor(i,i,i,i);
Res = bordering(u,v,Res,a);
}
}
return Res;
}
Для формирования и решения системы мы создали кнопку Start approximation. Обработаем нажатие на нее. Обработчик нажатия на кнопку Start approximation:
void CRegrDlg::OnBnClickedButtonStart()
{
UpdateData(true);
CRichEditCtrl *pRich = (CRichEditCtrl *) GetDlgItem(IDC_RICHEDIT_RES); //Переменная для управления RichEditom
if(mNum >= mInitArr.GetSize()) //Если данные не соответствуют
{
pRich->SetWindowTextA(CString("Number of points mismatches approximation degree")); //Выводим предупреждение
return; //Выходим
}
mNodeMatrix.setsize(mNum + 1,mNum + 1); //Задаем размерность матрицы для поиска коэффициентов
int n = (int) mInitArr.GetSize();
for(int i = 0; i < mNum + 1; i++) //Идем по строкам матрицы
for(int j = 0; j < mNum + 1; j++) //Идем по столбцам матрицы
{
double elem = 0; //вспомогательная переменная
for(int k = 0; k < n; k++) //Идем по точкам таблицы
elem += pow(mInitArr[k].x,(double)i+j); //Формируем элемент матрицы
mNodeMatrix.setcell(elem/n,j,i); //Инициализируем элемент матрицы
}
mRightPart.setsize(1,mNum + 1); //Задаем размерностьправой части системы
for(int i = 0; i < mNum + 1; i++) //Идем по строкам правой части
{
double elem = 0; //Вспомогательный элемент
for(int k = 0; k < n; k++) //Идем по точкам таблицы
elem += mInitArr[k].y*pow(mInitArr[k].x,(double)i); //Формируем элемент правой части
mRightPart.setcell(elem/n,0,i); //Инициализируем элемент правой части
}
matrix<double> Solution(::getreverse(mNodeMatrix)*mRightPart); //Решаем уравнение
CString str; //Строка для вывода результатов
str.Format("f(x) = %f",Solution.getcell(0,0));
//Формируем строку полинома
for(uni i = 1; i < Solution.getrowsize(); i++)
{
CString temp;
temp.Format(" + %f*x^%d",Solution.getcell(0,i),i);
str += temp;
}
pRich->SetWindowTextA(str); //Выводим ее
}
4. Вывод информации
Вывод осуществляется в двух направлениях: вывод таблицы узлов и вывод аналитической формулы полинома. За вывод узлов отвечает элемент управления списковое представление, код вывода точек в таблице представлен в обработчиках кнопок Add и Delete. За вывод формулы полинома отвечает элемент управления текстовое поле с форматированием, код вывода формулы приведен в обработчике кнопик Start approximation. Для завершения программы добавим строку:
#include "matrix.h"
в файл RegrDlg.h перед описанием класса SDPoint. Скомпилируем и запустим приложение. В результате в соответствии с введенными данными должно получиться следующее: рис 5.10
(рис 5.10) Форма исполняемой программы
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.