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

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

Проектирование и разработка приложения

Этапы разработки приложения

  1. Настроить каркас MFC для ввода и хранения текстовой информации.
  2. Организовать возможность форматирования текста.
  3. Реализовать алгоритм шифрования (дешифрования) текста.
  4. Организовать вывод результатов и возможность их сохранения.

Создадим с помощью мастера 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 \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(…) повторно вызывает функцию чтения, пока не произойдет одно из следующих событий:

  • функция чтения не вернет ненулевое значение;
  • функция не запишет в переменную *pcb 0;
  • возникнет ошибка в процессе обмена данных между элементом управления и буфером;
  • будет прочтен код завершающий rtf блок (для текста в формате rtf);
  • будет прочтен символ перехода на новую строку (для однострочного текстового поля).

Теперь, когда стало возможным чтение данных из файла и их ввод с клавиатуры, реализуем простейшие операции работы с буфером обмена (вырезать, копировать, вставить и т.п.). Для этого будем обрабатывать соответствующие пункты главного меню. Обработчик пункта меню 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.x,point.y,this) раскрывает контекстное меню в точке 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);  //Обновляем формат выделенного текста
    }
  }
}

Для получения текущего формата символов и установки нового формата используются методы:

  • 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

Формула перехода

Рис. 5.4. Формула перехода

мы переходим к нужным единицам измерения. Поле m_lf класса CFontDialog имеет тип LOGFONT и является GDI структурой, которая определяет параметры шрифта в логических единицах дисплея.