Казахстан |
Разработка полноценных 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 структурой, которая определяет параметры шрифта в логических единицах дисплея.