Шаг 46.
Библиотека OWL.
Создание текстового редактора

    На этом шаге мы рассмотрим создание текстового редактора.

    Приведем текстовый редактор, управляющий многими документами, обладающий такими сложными функциями редактирования, как поиск и замена, имеющий блок команд редактирования.

#include <owl\applicat.h> 
#include <owl\decmdifr.h> 
#include <owl\editfile.h> 
#include <owl\mdi.h> 
#include <owl\controlb.h> 
#include <owl\statusba.h> 
#include <owl\buttonga.h> 
#include "pr46_1.rc"

//Класс приложения.
class TMDIApp: public TApplication
{
  public:
	 TMDIApp():TApplication(){}
	 void InitMainWindow();
  protected:
     TMDIClient *clienTWndw; 
     TControlBar *controlBar;
     void CmFileNew(); 
     void CmFileOpen();

     DECLARE_RESPONSE_TABLE (TMDIApp);
};

DEFINE_RESPONSE_TABLE1(TMDIApp, TApplication) 
    EV_COMMAND (CM_FILENEW, CmFileNew), 
    EV_COMMAND (CM_FILEOPEN, CmFileOpen),
END_RESPONSE_TABLE;

// TMDIApp::InitMainWindow()
// Эта функция создает главное окно приложения,
// окно-клиент, панель инструментов и 
// строку состояния.
void TMDIApp::InitMainWindow()
{
  // Создать окно-клиент.
  clienTWndw = new TMDIClient;
  // Создать окно обрамления. 
  TDecoratedMDIFrame *frameWnd = new TDecoratedMDIFrame ("Текстовый редактор",
			 MENU_1, *clienTWndw, TRUE);
  // Определить размеры и расположение окна обрамления.
  frameWnd->Attr.X = 50;
  frameWnd->Attr.Y = 50;
  frameWnd->Attr.W = 500;
  frameWnd->Attr.H = 300;
  // Создать инструментальную линейку. 
  controlBar = new TControlBar (frameWnd); 
  controlBar->Insert(*new TButtonGadget(ID_NEW, CM_FILENEW));
  controlBar->Insert(*new TButtonGadget(ID_OPEN, CM_FILEOPEN));
  controlBar->Insert(*new TButtonGadget(ID_SAVE, CM_FILESAVE));
  controlBar->Insert(*new TSeparatorGadget (10));
  controlBar->Insert(*new TButtonGadget(ID_CUT, CM_EDITCUT));
  controlBar->Insert(*new TButtonGadget(ID_COPY, CM_EDITCOPY));
  controlBar->Insert(*new TButtonGadget(ID_PASTE, CM_EDITPASTE));
  controlBar->Insert(*new TButtonGadget(ID_UNDO, CM_EDITUNDO));
  // Установить режим подсказки, чтобы сразу
  // показывать подсказку.
  controlBar->SetHintMode(TGadgetWindow::EnterHints);
  // Вставить панель инструментов в окно обрамления. 
  frameWnd->Insert(*controlBar, TDecoratedFrame::Top);
  // Создать строку состояния.
  TStatusBar *statusBar = new TStatusBar (0,
     TGadget::Recessed,
     TStatusBar::CapsLock | TStatusBar::NumLock |
     TStatusBar::Overtype);
  // Вставить строку состояния в окно обрамления. 
  frameWnd->Insert(*statusBar, TDecoratedFrame::Bottom);
  // Установить пиктограмму в окно обрамления. 
  frameWnd->SetIcon (this, ID_MDIEDIT);
  // Активизировать трехмерные элементы управления Microsoft. 
  EnableCtl3d (TRUE);
  // Установить значение указателя MainWindow приложения.
  SetMainWindow(frameWnd);
}

// TMDIApp::CmFileNew()
// Эта функция создает новое окно, когда пользователь
// выбирает команду New из меню File или когда
// пользователь выбирает кнопку New на панели инструментов. 
void TMDIApp::CmFileNew()
{
  // Создать новый управляющий элемент
  // редактирования файла.
  TEditFile *editFile = new TEditFile;
  // Создать дочернее окно для нового файла.
  TMDIChild *childWnd = new TMDIChild (*clienTWndw, "", editFile);
  // Установить пиктограмму окна.
  childWnd->SetIcon(this,ID_DOCUMENT);
  // Отобразить новое дочернее окно.
  childWnd->Create();
}

// TMDIApp::CmFileOpen()
// Эта функция создает новое окно, когда пользователь
// выбирает команду Open из меню File или когда
// пользователь выбирает кнопку Open на панели инструментов. 
void TMDIApp::CmFileOpen()
{
  // Создать объект TData диалогового окна.
  TOpenSaveDialog::TData fileData (OFN_FILEMUSTEXIST | 
    OFN_HIDEREADONLY | OFN_PATHMUSTEXIST,
	 "Все файлы (*.*) | *.* |Текстовые файлы (*.txt) | *.txt |", 0, 0, " * ");
  // Задать имя файла как пустую строку. 
  strcpy(fileData.FileName, "");
  // Создать диалоговое окно Open. 
  TFileOpenDialog *dialog =
            new TFileOpenDialog (MainWindow, fileData);
  // Выполнить диалоговое окно Open. 
  int result = dialog->Execute();
  // Если пользователь выходит по кнопке ОК...
  if (result == IDOK)
  {
    // Создать новый управляющий элемент 
    // редактирования файла. 
    TEditFile *editFile =
           new TEditFile (0, 0, 0, 0, 0, 0, 0, fileData.FileName);
    // Создать новое дочернее окно для файла. 
    TMDIChild *childWnd =
			 new TMDIChild (*clienTWndw, "", editFile);
    // Создать пиктограмму файла. 
    childWnd->SetIcon (this, ID_DOCUMENT);
    // Отобразить новое дочернее окно. 
    childWnd->Create();
  }
  // Удалить старый текст подсказки. 
  controlBar->SetHintCommand (-1);
}

int OwlMain(int,char *[])
{
  return TMDIApp().Run();
}

    Файл ресурсов:

#ifndef WORKSHOP_INVOKED
#include "windows.h"
#endif

#include <owl\mdi.rh>
#include <owl\editfile.rh>

#define MENU_1         100
#define ID_MDIEDIT     101
#define ID_DOCUMENT    102
#define ID_UNDO        CM_EDITUNDO
#define ID_SAVE        CM_FILESAVE
#define ID_PASTE       CM_EDITPASTE 
#define ID_OPEN        CM_FILEOPEN 
#define ID_NEW         CM_FILENEW 
#define ID_CUT         CM_EDITCUT
#define ID_COPY        CM_EDITCOPY

#ifdef RC_INVOKED

#include <owl\except.rc>
#include <owl\statusba.rc>

MENU_1 MENU
{
   POPUP "&File" 
   {
       MENUITEM "&New",        CM_FILENEW 
       MENUITEM "&Open",       CM_FILEOPEN 
       MENUITEM "&Save",       CM_FILESAVE, GRAYED 
       MENUITEM "Save &As...", CM_FILESAVEAS 
       MENUITEM SEPARATOR 
       MENUITEM "E&xit",       CM_EXIT
   }
   POPUP "&Edit" 
   {
       MENUITEM "&Undo",       CM_EDITUNDO 
       MENUITEM SEPARATOR 
       MENUITEM "&Cut",        CM_EDITCUT 
       MENUITEM "C&opy",       CM_EDITCOPY 
       MENUITEM "&Paste",      CM_EDITPASTE 
       MENUITEM "&Delete",     CM_EDITDELETE 
       MENUITEM "C&lear All",  CM_EDITCLEAR
   }
   POPUP "&Search"
   {
       MENUITEM "&Find",       CM_EDITFIND 
       MENUITEM "&Replace",    CM_EDITREPLACE 
       MENUITEM "&Next",       CM_EDITFINDNEXT
   }
   POPUP "&Window"
   {
       MENUITEM "C&reate",     CM_CREATECHILD
       MENUITEM "&Cascade",    CM_CASCADECHILDREN
       MENUITEM "&Tile",       CM_TILECHILDREN 
       MENUITEM "Arrange &Icons", CM_ARRANGEICONS 
       MENUITEM "C&lose All",  CM_CLOSECHILDREN
   }
}

STRINGTABLE
{
   CM_FILENEW, "Создание нового документа" 
   CM_FILEOPEN,  "Открытие документа" 
   CM_FILESAVE, "Сохранение документа" 
   CM_FILESAVEAS, "Сохранение документа под новым именем"
   CM_EXIT, "Выход из редактора"
   CM_EDITUNDO, "Отменить последнюю операцию"
   CM_EDITCOPY, "Скопировать выделенный текст в буфер обмена"
   CM_EDITCUT, "Поместить выделенный текст в буфер обмена" 
   CM_EDITPASTE, "Вставить текст из буфера" 
   CM_EDITCLEAR, "Очистить окно"
   CM_EDITDELETE, "Удалить выделенный текст" 
   CM_EDITREPLACE, "Найти и заменить фрагмент текста" 
   CM_EDITFIND, "Найти фрагмент текста" 
   CM_EDITFINDNEXT, "Найти следующее вхождение фрагмента текста" 
   CM_CASCADECHILDREN "Все окна каскадом" 
   CM_TILECHILDREN, "Все окна мозаикой" 
   CM_ARRANGEICONS, "Иконки окон выровнять внизу" 
   CM_CLOSECHILDREN, "Закрыть все окна" 
   IDS_UNTITLEDFILE, "(Без имени)"
   IDS_UNABLEREAD, "Ошибка при чтении файла %s с диска."
   IDS_UNABLEWRITE, "Ошибка при записи файла %s на диск."
   IDS_FILECHANGED, "Файл %s был изменен. \n\nСохранить изменения?"
   IDS_CANNOTFIND, "Фрагмент ""%s"" отсутствует."
}

ID_UNDO  BITMAP "undo.bmp" 
ID_CUT   BITMAP "cut.bmp" 
ID_COPY  BITMAP "copy.bmp" 
ID_PASTE BITMAP "paste.bmp" 
ID_NEW   BITMAP "new.bmp" 
ID_OPEN  BITMAP "open.bmp" 
ID_SAVE  BITMAP "save.bmp"

ID_DOCUMENT ICON  "doc.ico" 
ID_MDIEDIT  ICON "mdiedit.ico"

#endif
Текст этого приложения со всеми дополнительными файлами можно взять здесь.

    Запустив эту программу, вы увидите, что это полностью оформленный текстовый редактор (рисунок 1), который выполняет почти все операции, за исключением печати файлов.


Рис.1. Результат работы приложения

    Приложение имеет полные меню File, Edit, Search и Window, которые содержат все известные вам команды. Кроме того, вы можете открыть столько документов, сколько захотите, уменьшить документ до размеров пиктограммы и получить доступ ко многим командам меню из панели инструментов приложения. При попадании курсора мыши на любую команду меню или управляющую кнопку, в строке состояния появится текст подсказки, описывающей команду, и строка состояния отслеживает статус клавиш клавиатуры NumLock, CapsLock и Insert. Наконец, если вы минимизируете приложение, его пиктограмма появится внизу на панели задач Windows.

    Все это реализовано благодаря специальным оконным классам MDI библиотеки OWL: TMDIFrame, TMDIClient, TMDIChild и TDecoratedMDIFrame, которые включают большую часть необходимого для обработки МDI-приложения. Большую работу выполняет OWL-класс TEditFile, управляющий редактированием, который инкапсулирует большую часть свойств окна редактирования текста (TEditFile "наследует" свои свойства из классов TEdit и TEditSearch OWL; их происхождение может быть прослежено через TStatic и TControl до TWindow).

    Начнем с изучения класса приложения, который выглядит совсем по-другому по сравнению с предыдущими классами:

class TMDIApp: public TApplication
{
  public:
	 TMDIApp():TApplication(){}
	 void InitMainWindow();
  protected:
     TMDIClient *clienTWndw; 
     TControlBar *controlBar;
     void CmFileNew(); 
     void CmFileOpen();

     DECLARE_RESPONSE_TABLE (TMDIApp);
};

DEFINE_RESPONSE_TABLE1(TMDIApp, TApplication) 
    EV_COMMAND (CM_FILENEW, CmFileNew), 
    EV_COMMAND (CM_FILEOPEN, CmFileOpen),
END_RESPONSE_TABLE;

    Этот класс имеет два защищенных члена-данных, clienTWndw и controlBar, которые указывают соответственно на окно-клиент приложения и панель инструментов. Данные указатели позволяют приложению обращаться к объектам его окна-клиента и панели инструментов в любом месте внутри класса приложения. Приложение имеет также обычный конструктор и функцию InitMainWindow(). Кроме того, две защищенные функции отклика на сообщение CmFileNew() и CmFileOpen() обеспечивают обработку команд New и Open из меню File.

    Впервые класс приложения получил задачу обработки сообщений. Так как получаемые MDI-приложением сообщения проходят от приложения к активному дочернему окну, к окну-клиенту и, наконец, к окну обрамления, вы можете перехватить отдельное сообщение в ряде точек. Обрабатывая файловые сообщения в объекте приложения, программа избегает создания новых классов TDecoratedMDIFrame и TMDIClient и вместо них просто использует готовые классы OWL с теми же именами. Так как класс приложения обрабатывает сообщения CM_FILENEW и CM_FILEOPEN, то, конечно, он должен также определять таблицу откликов.

    Прежде, чем окно появится на экране, большая часть работы в этой программе выполняется в функции InitMainWindow() класса приложения. Во-первых, эта функция конструирует окно-клиент:

    clienTWndw  =  new  TMDIClient;

    Затем, так как это приложение содержит панель инструментов и строку состояния, оно конструирует окно TDecoratedMDIFrame, а не простое окно TMDIFrame:

  TDecoratedMDIFrame *frameWnd = new TDecoratedMDIFrame ("Текстовый редактор",
			 MENU_1, *clienTWndw, TRUE);

    Класс TDecoratedMDIFrame является производным от классов TMDIFrane и TDecoratedFrame и представляет собой просто окно обрамления MDI, которое может управлять такими элементами оформления, как панель инструментов и строка состояния. Его конструктор получает пять параметров, три последних из которых имеют задаваемые по умолчанию значения. Этими параметрами являются строка заголовка окна, идентификатор ресурса меню, ссылка на окно-клиент, булева величина, указывающая, разрешено ли отслеживание меню курсором мыши (tracking), и объект TLibId.

    Первые два параметра - строка названия окна и идентификатор ресурса меню - обязательны. Если вы не обеспечите ссылку на окно-клиент, конструктор автоматически создает окно TMDIClient. Булев аргумент по умолчанию имеет значение, равное FALSE, аргумент TLibId по умолчанию равен 0. Так как эта программа использует отслеживание пунктов меню для отображения текста подсказки в строке состояния, предшествущий вызов конструктора TDecoratedMDIFrame обеспечивает значение TRUE для булева аргумента. Заметьте, что для того, чтобы использовать класс TDecoratedMDIFrame, вы должны включить заголовочный файл DECMDIFR.H в свою программу.

    После того, как окно обрамления сконструировано, программа использует указатель этого окна для получения доступа к структуре Attr, с помощью которой устанавливаются размеры и положение окна:

  frameWnd->Attr.X = 50;
  frameWnd->Attr.Y = 50;
  frameWnd->Attr.W = 500;
  frameWnd->Attr.H = 300;

    Следующей задачей для конструктора InitMainWindow() является конструирование панели инструментов приложения:

  controlBar = new TControlBar (frameWnd); 
  controlBar->Insert(*new TButtonGadget(ID_NEW, CM_FILENEW));
  controlBar->Insert(*new TButtonGadget(ID_OPEN, CM_FILEOPEN));
  controlBar->Insert(*new TButtonGadget(ID_SAVE, CM_FILESAVE));
  controlBar->Insert(*new TSeparatorGadget (10));
  controlBar->Insert(*new TButtonGadget(ID_CUT, CM_EDITCUT));
  controlBar->Insert(*new TButtonGadget(ID_COPY, CM_EDITCOPY));
  controlBar->Insert(*new TButtonGadget(ID_PASTE, CM_EDITPASTE));
  controlBar->Insert(*new TButtonGadget(ID_UNDO, CM_EDITUNDO));

    Этот фрагмент программы должен быть вам знаком из предыдущих шагов, за исключением следующих моментов. Вместо того, чтобы сначала создать TButtonGadget и затем использовать указатель без ссылок в обращении к Insert(), программа комбинирует оба шага, помещая конструктор TButtonGadget внутрь обращения к Insert(). Это типичный способ решения такой задачи. Хотя текст программы получается менее ясным, но зато он короче, и не требуется указатель на TButtonGadget.

    Далее InitMainWindow() устанавливает режим подсказки для панели инструментов:

  controlBar->SetHintMode(TGadgetWindow::EnterHints);

    Как вам уже известно, в режиме EnterHints подсказки отображаются немедленно, когда курсор мыши проходит по управляемому объекту, тогда как режим PressHints отображает подсказки только при выборе соответствующего объекта.

    Теперь, когда панель инструментов готова к работе, InitMainWindow() добавляет ее в верхнюю часть окна обрамления:

  frameWnd->Insert(*controlBar, TDecoratedFrame::Top);

    Затем программа конструирует строку состояния:

  TStatusBar *statusBar = new TStatusBar (0,
     TGadget::Recessed,
     TStatusBar::CapsLock | TStatusBar::NumLock |
     TStatusBar::Overtype);

    После создания строки состояния программа добавляет ее в нижнюю часть окна обрамления:

  frameWnd->Insert(*statusBar, TDecoratedFrame::Bottom);

    Наконец, InitMainWindow() назначает пиктограмму для родительского окна, разрешает доступ к библиотеке трехмерных элементов управления Microsoft и устанавливает указатель MainWindow приложения на окно обрамления:

   // Установить пиктограмму в окно обрамления. 
  frameWnd->SetIcon (this, ID_MDIEDIT);
  // Активизировать трехмерные элементы управления Microsoft. 
  EnableCtl3d (TRUE);
  // Установить значение указателя MainWindow приложения.
  SetMainWindow(frameWnd);

    После вызова функции SetIcon() окна обрамления, пиктограмма с идентификатором ресурса IDI_MDIEDIT будет появляться в окне Windows всякий раз, когда приложение будет минимизироваться. Эта 16-цветная пиктограмма имеет размеры 32*32 пикселей и создана в Resource Workshop Borland.

    Вызов EnableCtl3d(), функции-члена класса TApplicaion, делает, в основном, то же самое, что и EnableBWCC(), за исключением того, что первая из них включает библиотеку трехмерных элементов управления Microsoft, а не библиотеку Borland.

    Когда пользователь выбирает команду из New меню File, OWL вызывает CmFileNew():

void TMDIApp::CmFileNew()
{
  // Создать новый управляющий элемент
  // редактирования файла.
  TEditFile *editFile = new TEditFile;
  // Создать дочернее окно для нового файла.
  TMDIChild *childWnd = new TMDIChild (*clienTWndw, "", editFile);
  // Установить пиктограмму окна.
  childWnd->SetIcon(this,ID_DOCUMENT);
  // Отобразить новое дочернее окно.
  childWnd->Create();
}

    Эта функция сначала конструирует новый элемент управления TEditFile. Затем она создает новое окно TMDIChildWindow, используя управляющий элемент TEditFile в качестве окна-клиента. Так как это окно является дочерним MDI-окном, которое может быть минимизировано, программа присваивает окну пиктограмму. Эта пиктограмма появляется внизу главного окна всякий раз, когда пользователь минимизирует дочернее окно. Два аргумента SetIcon() включают указатель на окно и идентификатор ресурса пиктограммы. Наконец, CmFileNew() вызывает функцию Create() дочернего окна для его отображения.

    В этот момент пользователь получает новое окно для редактирования текста с полным набором функций. Более того, так как классы управляющих элементов редактирования из OWL имеют собственные переключатели доступности команд, вам нет необходимости обеспечивать ими меню Edit и Search.

    Когда пользователь выбирает команду Open из меню File, выполнение программы переходит к CmFileOpen():

void TMDIApp::CmFileOpen()
{
  // Создать объект TData диалогового окна.
  TOpenSaveDialog::TData fileData (OFN_FILEMUSTEXIST | 
    OFN_HIDEREADONLY | OFN_PATHMUSTEXIST,
	 "Все файлы (*.*) | *.* |Текстовые файлы (*.txt) | *.txt |", 0, 0, " * ");
  // Задать имя файла как пустую строку. 
  strcpy(fileData.FileName, "");
  // Создать диалоговое окно Open. 
  TFileOpenDialog *dialog =
            new TFileOpenDialog (MainWindow, fileData);
  // Выполнить диалоговое окно Open. 
  int result = dialog->Execute();
  // Если пользователь выходит по кнопке ОК...
  if (result == IDOK)
  {
    // Создать новый управляющий элемент 
    // редактирования файла. 
    TEditFile *editFile =
           new TEditFile (0, 0, 0, 0, 0, 0, 0, fileData.FileName);
    // Создать новое дочернее окно для файла. 
    TMDIChild *childWnd =
			 new TMDIChild (*clienTWndw, "", editFile);
    // Создать пиктограмму файла. 
    childWnd->SetIcon (this, ID_DOCUMENT);
    // Отобразить новое дочернее окно. 
    childWnd->Create();
  }
  // Удалить старый текст подсказки. 
  controlBar->SetHintCommand (-1);
}

    Так как пользователю необходимо выбрать файл для открытия, CmFileOpen() должен отобразить диалоговое окно Open системы Windows. Первым шагом в отображении этого диалогового окна является конструирование объекта TData. Затем программа присваивает fileData.Filename значение пустой строки, так как окно диалога не будет содержать имя файла, задаваемое по умолчанию. Если объект TData успешно создан и инициализирован, CmFileOpen() конструирует новое диалоговое окно TFileOpenDialog и вызывает функцию Execute() для его отображения.

    Когда пользователь выходит из диалогового окна, нажатая кнопка будет появляться в result. Если result равен IDOK, то выход произошел по кнопке ОК, и пользователь выбрал имя файла. CmFileOpen() использует имя файла, которое хранится в fileData.FileName, при обращении к конструктору TEditFile.

    Конструктор TEditFile имеет 9 аргументов, по умолчанию установленных в 0. Этими аргументами являются указатель на родительское окно, идентификатор ресурса, указатель на текст, который должен появиться в окне, координаты х и у управляющего элемента редактирования, ширина и высота управляющего элемента редактирования, указатель на имя файла и указатель на объект TModule. При таком способе конструирования объекта TEditFile не только создается объект, но и автоматически загружается в окно файл, заданный в аргументе имени файла.

    После конструирования объекта TEditFile, CmFileOpen() создает новое окно TMDIChild, которое использует управляющий элемент TEditFile в качестве окна-клиента, вызывает SetIcon(), чтобы установить пиктограмму окна, и Create() для отображения окна. Наконец, гак как текст подсказки для команды Open меню File остается в строке состояния, вызов функции SetHintCommand() панели инструментов (функция, унаследованная из TGadgetWindow) со значением -1 очищает строку состояния.

    Итак, вы имеете многодокументный текстовый редактор объемом менее 100 строк, если не считать комментарии, пустые строки и т.п.

    Теперь, когда вы овладели основами применения ObjectWindows для создания профессиональных приложений, настало время углубиться в OWL. Далее вы не только познакомитесь с окном OWL и графическими классами, но также изучите, как создать DLL и программы - хранители экрана, работать с Windows Clipboard и программировать различные пользовательские управляющие элементы.

    Со следующего шага мы начнем рассматривать более подробно оконные классы OWL.




Предыдущий шаг Содержание Следующий шаг