Разработка полноценных Windows-приложений
Проектирование и разработка приложения
Этапы разработки приложения
- Настроить каркас MFC для ввода и хранения текстовой информации.
- Организовать возможность форматирования текста.
- Реализовать алгоритм шифрования (дешифрования) текста.
- Организовать вывод результатов и возможность их сохранения.
Создадим с помощью мастера MFC Application Wizard SDI приложение "Vijiner" без поддержки архитектуры "документ - вид ". Отключим поддержку символов Unicode.
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
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
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(…) повторно вызывает функцию чтения, пока не произойдет одно из следующих событий:
- функция чтения не вернет ненулевое значение;
- функция не запишет в переменную *pcb 0;
- возникнет ошибка в процессе обмена данных между элементом управления и буфером;
- будет прочтен код завершающий rtf блок (для текста в формате rtf);
- будет прочтен символ перехода на новую строку (для однострочного текстового поля).
Теперь, когда стало возможным чтение данных из файла и их ввод с клавиатуры, реализуем простейшие операции работы с буфером обмена (вырезать, копировать, вставить и т.п.). Для этого будем обрабатывать соответствующие пункты главного меню.
Обработчик пункта меню Edit
Copy:
void CChildView::OnEditCopy()
{
m_Rich.Copy();
}Обработчик изменения внешнего вида пункта меню Edit
Copy:
void CChildView::OnUpdateEditCopy(CCmdUI *pCmdUI)
{
LONG nStartSel, nEndSel; //Переменные для хранения начальной и конечной позиции выделенного текста
m_Rich.GetSel(nStartSel,nEndSel); //Получение начальной и конечной позиции выделенного текста
pCmdUI->Enable(nStartSel != nEndSel); //Если текст выделен, то можно скопировать
}Метод GetSel(nStartSel,nEndSel) записывает позиции начала и конца выделенного текста в соответствующие переменные, переданные ему в качестве параметров.
Обработчик пункта меню Edit
Cut:
void CChildView::OnEditCut()
{
m_Rich.Cut();
}Обработчик изменения внешнего вида пункта меню Edit
Cut:
void CChildView::OnUpdateEditCut(CCmdUI *pCmdUI)
{
LONG nStartSel, nEndSel;
m_Rich.GetSel(nStartSel,nEndSel);
pCmdUI->Enable(nStartSel != nEndSel);
}Код данного обработчика совпадает с кодом обработчика изменения внешнего вида элемента Edit
Copy.
Обработчик пункта меню Edit
Paste:
void CChildView::OnEditPaste()
{
m_Rich.Paste();
}Обработчик изменения внешнего вида пункта меню Edit
Paste:
void CChildView::OnUpdateEditPaste(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_Rich.CanPaste());
}В данном обработчике используется метод CanPaste() класса CRichEditCtrl, который возвращает значение TRUE, если данные, скопированные в буфер, поддерживаются элементом управления, и FALSE в любом другом случае.
Обработчик пункта меню Edit
Undo:
void CChildView::OnEditUndo()
{
m_Rich.Undo();
}Обработчик изменения внешнего вида пункта меню Edit
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.x,point.y,this) раскрывает контекстное меню в точке point.
2. Форматирование текста
Текстовое поле Rich Edit предназначено для работы с данными в формате rtf. То есть, существует возможность работы со шрифтами (изменение цвета, размера символов, их выделение, подчеркивание и т.п.). Реализуем некоторые функции работы со шрифтами в нашей программе. Для этого создадим пункт меню Format, а в нем подпункт Format
Font…, который будем обрабатывать.
Обработчик пункта меню Format
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); //Обновляем формат выделенного текста
}
}
}Для получения текущего формата символов и установки нового формата используются методы:
- GetDefaultCharFormat(cf_old) - сохраняет формат символов, который текстовое поле использует по умолчанию, в переменную cf_old
- GetSelectionCharFormat(cf_old) - сохраняет формат выделенных символов в переменную cf_old
- SetDefaultCharFormat(cf_new) - обновляет формат символов, который текстовое поле использует по умолчанию
- SetSelectionCharFormat(cf_new) - обновляет формат выделенных символов.
Наибольший интерес представляет собой структура 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
мы переходим к нужным единицам измерения. Поле m_lf класса CFontDialog имеет тип LOGFONT и является GDI структурой, которая определяет параметры шрифта в логических единицах дисплея.
