Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 12, М.: Диалог-МИФИ, 1993, 255 стр. 2.4. Редактор текстаВ операционной системе Microsoft Windows зарегистрирован класс окна с именем "edit", на базе которого вы можете создать однострочный или многострочный текстовый редактор. Такой редактор может быть использован для ввода значений текстовых или числовых переменных, а также для создания и редактирования текстовых файлов (без функций форматирования текста). Встроенный текстовый редактор умеет выполнять функции выделения текста, может работать с универсальным буфером обмена Clipboard. Для того чтобы в среде Windows сделать свой собственный текстовый редактор, вам достаточно создать на базе класса "edit" орган управления, вызвав функцию CreateWindow. После этого функция родительского окна будет получать от редактора сообщение с кодом WM_COMMAND (как и от других аналогичных органов управления). Вместе с этим сообщением в функцию окна будут передаваться коды извещения, отражающие изменения состояния редактора текста. Вы также можете с помощью функции SendMessage посылать текстовому редактору около трех десятков различных управляющих сообщений, с помощью которых можно изменять редактируемый текст, получать отдельные строки, выделять фрагменты текста, копировать выделенный фрагмент текста в Clipboard и т. д. Использование предопределенного класса "edit" - самый простой способ создания в приложении редактора текста. Фактически в этом случае вы будете использовать готовый текстовый редактор. Вам не придется самостоятельно обрабатывать сообщения от клавиатуры, управлять текстовым курсором, учитывать ширину букв в пропорциональном шрифте, выполнять свертку текста в окне редактирования по вертикали или горизонтали, заниматься выделением фрагментов текста, работать с Clipboard или решать другие задачи, возникающие при создании текстовых редакторов. Создание редактора текстаДля создания редактора текста (однострочного или многострочного) следует вызвать функцию CreateWindow, передав ей в качестве первого параметра указатель на строку "edit": hwndEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT, 30, 30, 300, 30, hwnd, (HMENU) ID_EDIT, hInst, NULL); Заголовок окна не используется, поэтому второй параметр следует указать как NULL. Если при создании текстового редактора не указать стиль окна WS_BORDER, область редактирования не будет выделена. Это неудобно для пользователя, особенно если в окне имеется несколько редакторов. При использовании стиля WS_BORDER вокруг редактора будет нарисована рамка. Кроме обычных стилей окна для текстового редактора указывают стили, символическое имя которых начинается с префикса ES_. Это стили редактора текста. Они влияют на внешний вид редактора и выполняемые им функции. Подробно стили редактора текста будут описаны в следующем разделе. Остальные параметры функции CreateWindow указываются так же, как и для других органов управления. Параметры с четвертого по седьмой используются для определения расположения и размеров текстового редактора. Восьмой параметр - идентификатор родительского окна, в функцию которого будет поступать сообщение WM_COMMAND. Девятый параметр определяет идентификатор редактора текста. Десятый указывает идентификатор копии приложения. Последний параметр должен быть задан как NULL. Стили редактора текстаПриведем список стилей окна, которые используются при создании редактора текста.
Для создания однострочного редактора текста достаточно указать стиль ES_LEFT (который, кстати, определен в файле windows.h как 0). Для обеспечения свертки текста по горизонтали используйте дополнительно стиль ES_AUTOHSCROLL. Если вам нужен многострочный редактор текста, укажите стиль ES_MULTILINE. Для обеспечения автоматической свертки текста по горизонтали и вертикали следует также указать стили ES_AUTOHSCROLL и ES_AUTOVSCROLL. Если в многострочном редакторе текста не указан стиль ES_AUTOHSCROLL, но указан стиль ES_AUTOVSCROLL, при достижении в процессе ввода текста правой границы окна ввода выполняется автоматический перенос слова на новую строку. Если свертка не используется, в описанной выше ситуации будет выдан звуковой сигнал. Многострочный редактор текста может иметь вертикальную и горизонтальную полосы просмотра. Для создания полос просмотра достаточно в стиле редактора указать константы WS_HSCROLL и WS_VSCROLL. Коды извещенияТекстовый редактор посылает в родительское окно сообщение WM_COMMAND с параметром wParam, равным идентификатору редактора. Этот идентификатор можно использовать для того чтобы различать сообщения, поступающие от разных органов управления (в частности, от разных текстовых редакторов, если в одном окне их создано несколько штук). Младшее слово параметра lParam содержит идентификатор окна, полученный от функции CreateWindow при создании редактора. Старшее слово параметра lParam содержит код извещения. Анализируя этот код, приложение может определить событие, послужившее причиной появления сообщения WM_COMMAND. Приведем список кодов извещений.
Ваше приложение должно обрабатывать, по крайней мере, извещение с кодом EN_ERRSPACE, которое приходит в том случае, если редактор текста не смог заказать для себя дополнительную память. Сообщения для редактора текстаС помощью функции SendMessage вы можете посылать в редактор текста различные сообщения. Коды сообщений, специально предназначенных для текстового редактора, имеют символические имена с префиксом EM_. Приведем список таких сообщений. EM_CANUNDOС помощью этого сообщения можно проверить, поддерживает ли редактор текста операцию отмены последнего действия редактирования. Эта операция выполняется по сообщению WM_UNDO, когда оно посылается в редактор текста. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: TRUE, если операция поддерживается, FALSE - если нет EM_EMPTYUNDOBUFFERСброс содержимого буфера, используемого для отмены последнего действия редактирования.Параметры и возвращаемое значение не используются. EM_FMTLINESУправление режимом добавления или удаления символов конца строки в процессе переноса слов на новую строку. Параметры: wParam = (WPARAM)(BOOL)fAddEOL; lParam = 0L; Значение флага fAddEOL: TRUE - вставка, FALSE - удаление. Возвращаемое значение:TRUE - вставка, FALSE - удаление EM_GETFIRSTVISIBLELINEПолучение номера самой верхней видимой строки в окне редактирования. Используется в Windows версии 3.1 и более поздних версиях. Параметры: не используются. Возвращаемое значение: Номер строки. Первой строке соответствует значение 0 EM_GETHANDLEПолучение идентификатора локальной памяти, используемой редактором для хранения текста.Параметры: не используются. Возвращаемое значение: Идентификатор блока памяти EM_GETLINEКопирование строки из редактора текста в буфер. Параметры: wParam = (WPARAM)nLine; lParam = (LPARAM)(LPSTR)lpCh; nLine - номер строки, lpCh - адрес буфера для строки. Возвращаемое значение: Номер скопированных в буфер байт данных или 0, если указанный номер строки превосходит количество строк в тексте EM_GETLINECOUNTОпределение количества строк в тексте. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Количество строк текста или 1, если окно редактирования не содержит ни одной строки текста EM_GETMODIFYОпределение значения флага обновления. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: TRUE, если текст был изменен или FALSE - если нет EM_GETPASSWORDCHARПолучение символа, используемого для вывода при вводе пароля. Используется в Windows версии 3.1 и более поздних версиях. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Код символа EM_GETRECTОпределение координат прямоугольной области, используемой для редактирования текста. Эта область по своим размерам не обязательно совпадает с областью, занятой самим органом управления. Параметры: wParam = 0; lParam = (LPARAM)(RECT FAR *)lprc; lprc - указатель на структуру RECT, в которую будут записаны искомые координаты. Возвращаемое значение: не используется EM_GETSELОпределение положения первого и последнего символа в выделенном фрагменте текста. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Двойное слово. Младшее слово содержит положение первого символа в выделенном фрагменте, старшее - положение символа, следующего за выделенным фрагментом текста EM_GETWORDBREAKPROCПолучение адреса текущей функции, которая используется для переноса слов с одной строки на другую. Используется в Windows версии 3.1 и более поздних версиях. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Адрес функции или NULL, если такая функция не существует EM_LIMITTEXTОпределение максимального количества символов, которое можно ввести в окно редактирования. Параметры: wParam = (WPARAM)cCmax;lParam = 0L; cCMax - размер текста. Возвращаемое значение: не используется EM_LINEFROMCHARОпределение номера строки, содержащей символ в заданной позиции. Параметры: wParam = (WPARAM) iChar; lParam = 0L; iChar - номер позиции. Можно задать как -1, в этом случае используется текущая строка (строка, в которой установлен текстовый курсор), или строка, в которой начинается выделенный фрагмент текста (если в тексте есть выделенный фрагмент). Возвращаемое значение: Номер строки. Первой строке соответствует значение 0 EM_LINEINDEXОпределение смещения в байтах от начала текста заданной строки. Параметры: wParam = (WPARAM) nLine; lParam = 0L; nLine - номер строки. Можно задать как -1, в этом случае используется текущая строка. Возвращаемое значение: Смещение в байтах или -1, если указана строка с номером, превосходящим количество строк в окне редактирования EM_LINELENGTHОпределение размера строки в байтах. Параметры: wParam = (WPARAM) iChar; lParam = 0L; iChar - номер позиции символа, который находится в строке. Можно задать как -1, в этом случае используется текущая строка, для которой возвращается количество невыбранных символов Возвращаемое значение: Размер строки в байтах EM_LINESCROLLСвертка заданного количества строк. Параметры: wParam = 0; lParam = MAKELPARAM(dv, dh); dv - количество сворачиваемых строк по вертикали,dh - количество символов для свертки по горизонтали, не используется для текста, выравненного по правой границе или центрированного. Возвращаемое значение: TRUE, если сообщение был послано многострочному редактору, или FALSE - если однострочному EM_REPLACESELЗаменить выделенный фрагмент текста. Если в тексте нет выделенных фрагментов, строка будет вставлена в текущей позиции. Параметры: wParam = 0; lParam = (LPARAM)(LPCSTR)lpszStr ;lpszStr - адрес строки, которая должна заместить собой выделенный текст Возвращаемое значение: не используется EM_SETHANDLEНазначение буфера для хранения текста. Параметры: wParam = (WPARAM)(HLOCAL)hLoc; lParam = 0L; hLoc - идентификатор локального блока памяти, полученный с помощью функции LocalAlloc. Возвращаемое значение: не используется EM_SETMODIFYУстановка флага обновления. Параметры: wParam = (WPARAM)(UINT)fMod; lParam = 0L; fMod - новое значение для флага обновления. TRUE, если текст надо отметить, как обновленный, FALSE - если как необновленный. Возвращаемое значение: не используется EM_SETPASSWORDCHARУстановка символа, который используется для вывода текста (в редакторе, имеющим стиль ES_PASSWORD). Параметры: wParam = (WPARAM)(UINT)chChar; lParam = 0L; chChar - код символа. Возвращаемое значение: TRUE, если сообщение посылается редактору, созданному как орган управления EM_SETREADONLYУстановка или сброс состояния редактора, в котором пользователю позволяется только просматривать текст, но не редактировать (режим "только чтение"). Параметры: wParam = (WPARAM)(UINT)fReadOnly; lParam = 0L; fReadOnly - TRUE для установки режима "только чтение", FALSE - для сброса. Возвращаемое значение: TRUE, если установка выполнена без ошибок или FALSE при ошибке EM_SETRECTИзменение размеров или расположения области, используемой для редактирования текста. Эта область находится внутри окна органа управления и сразу после создания совпадает с этим окном по размерам и расположению. Параметры: wParam = 0; lParam = (LPARAM)(RECT FAR *)lprc; lprc - указатель на структуру RECT, в которую будут записаны новые координаты области. Возвращаемое значение: не используется EM_SETRECTNPАналогично предыдущему, за исключением того что окно редактирования не перерисовывается EM_SETSELВыделение заданных символов в окне редактирования. Параметры: wParam = (WPARAM)(UINT)fScroll; lParam = MAKELPARAM(ichStart, ichEnd); fScroll - если этот параметр равен 1, текстовый курсор сворачивается, если 0 - нет. ichStart - начальная позиция. ichEnd - конечная позиция. Если начальная позиция равна 0, а конечная -1, выбирается весь текст. Если начальная позиция равна -1, выделение фрагмента (если оно было) исчезает. Возвращаемое значение: TRUE, если сообщение посылается редактору, созданному как орган управления EM_SETTABSTOPSУстановка позиций табуляции. Параметры: wParam = (WPARAM)cTabs; lParam = (LPARAM)(const int FAR *) lpTabs; cTabs - расстояние для табуляции. Если этот параметр указан как 0, используется значение по умолчанию - 32. lpTabs - массив беззнаковых целых чисел, определяющих расположение позиций табуляции в единицах, используемых для диалоговых панелей. Эти единицы будут описаны в следующей главе. Возвращаемое значение: TRUE, если позиции табуляции были установлены ил FALSE при ошибке EM_SETWORDBREAKPROCУстановка новой функции для переноса слов с одной строки на другую. Используется в Windows версии 3.1 и более поздних версиях. Параметры: wParam = 0; lParam = (LPARAM)(EDITWORDBREAKPROC) ewpbrc; ewpbrc - адрес переходника для новой функции, которая будет использована для переноса слов. Этот адрес необходимо получить при помощи функции MakeProcInstance, указав последней адрес функции переноса слов. Возвращаемое значение: не используется EM_UNDOОтмена последней операции редактирования текста. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: не используется Помимо описанных выше, текстовому редактору можно посылать некоторые сообщения, символические имена которых начинаются с префикса WM_. Это сообщения WM_COPY, WM_PASTE, WM_CUT, WM_CLEAR. Приведем краткое описание этих сообщений. WM_COPYКопирование выделенного фрагмента текста в универсальный буфер обмена Clipboard. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: не используется WM_PASTEВставка текста из буфера обмена Clipboard в текущую позицию редактируемого текста. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: не используется WM_CUTУдаление выделенного текста с записью его в Clipboard. Удаленный текст можно восстановить, если послать в редактор сообщение EM_UNDO. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: не используется WM_CLEARУдаление выделенного текста без записи в Clipboard. Удаленный текст можно восстановить, если послать в редактор сообщение EM_UNDO. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: не используется Приложение EDITПосле описания всех стилей и сообщений текстового редактора у вас могло сложиться впечатление, что создание текстового редактора - очень сложная задача. Однако вам редко нужны все возможности органа управления класса "edit". Обычно для приложения требуется несложный однострочный или многострочный редактор, позволяющий вводить отдельные строки текста. Приложение EDIT (рис. 2.11) демонстрирует работу с простейшим однострочным текстовым редактором.
Рис. 2.11. Главное окно приложения EDIT Это приложение создает в своем главном окне однострочный редактор текста и кнопку с надписью "OK". Вы можете вводить текст при помощи клавиатуры, выделять фрагменты текста мышью или клавиатурой, а также копировать текст в буфер Clipboard или вставлять текст из этого буфера в окно редактирования. Для выделения текста и работы с буфером Clipboard вы можете использовать стандартные приемы, описанные в руководстве пользователя операционной системы Windows. После ввода текста нажмите клавишу "OK". На экране появится сообщение, состоящее из введенного вами текста (рис. 2.12).
Рис. 2.12. Сообщение приложения EDIT Главный файл приложения EDIT приведен в листинге 2.22. Листинг 2.22. Файл edit\edit.cpp // ---------------------------------------- // Редактор текста // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> // Идентификатор редактора текста #define ID_EDIT 1 // Идентификатор кнопки #define ID_BUTTON 2 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "EditAppClass"; // Заголовок окна char const szWindowTitle[] = "Edit Demo"; // Идентификатор копии приложения HINSTANCE hInst; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем расположение и размеры CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, // CW_USEDEFAULT, // 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { // Создаем символьные сообщения TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Идентификатор редактора текста static HWND hEdit; // Идентификатор кнопки static HWND hButton; switch (msg) { case WM_CREATE: { // Создаем редактор текста hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT, 30, 30, 300, 30, hwnd, (HMENU) ID_EDIT, hInst, NULL); // Создаем кнопку hButton = CreateWindow("button", "OK", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 30, 80, 100, 30, hwnd, (HMENU) ID_BUTTON, hInst, NULL); return 0; } // Когда главное окно приложения получает // фокус ввода, отдаем фокус редактору текста case WM_SETFOCUS: { SetFocus(hEdit); return 0; } case WM_COMMAND: { // Обработка извещения текстового редактора // об ошибке if(wParam == ID_EDIT) { if(HIWORD(lParam) == EN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); } } // Сообщение от кнопки else if(wParam == ID_BUTTON) { BYTE chBuff[80]; WORD cbText; // Записываем в первое слово буфера // значение размера буфера в байтах * (WORD *) chBuff = sizeof (chBuff) - 1; // Получаем от редактора текста содержимое // первой строки. Функция возвращает количество // байт, скопированных в буфер cbText = SendMessage(hEdit, EM_GETLINE, 0, (LPARAM)(LPSTR)chBuff); // Закрываем буфер двоичным нулем chBuff[cbText] = '\0'; // Выводим содержимое буфера на экран MessageBox(hwnd, chBuff, szWindowTitle, MB_OK); } return 0; } case WM_PAINT: { HDC hdc; PAINTSTRUCT ps; // Получаем индекс контекста устройства hdc = BeginPaint(hwnd, &ps); // Выводим текстовую строку TextOut(hdc, 30, 10, "Введите строку и нажмите кнопку 'OK'", 36); // Отдаем индекс контекста устройства EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } В начале исходного текста определены идентификаторы редактора текста и кнопки: #define ID_EDIT 1 #define ID_BUTTON 2 Функция WinMain приложения EDIT не имеет никаких особенностей. Она создает одно главное окно и организует цикл обработки сообщений. Так как текстовый редактор работает с символьными сообщениями, в цикле обработки сообщений вызывается функция TranslateMessage: while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } В функции главного окна приложения определены две статические переменные для хранения идентификаторов созданных органов управления - редактора текста и кнопки: static HWND hEdit; static HWND hButton; По сообщению WM_CREATE функция окна создает редактор текста и кнопку, вызывая функцию CreateWindow. При создании текстового редактора используется комбинация стилей WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT. При этом создается дочернее окно, которое изначально является видимым и имеет рамку. Для текста задается выравнивание по левой границе: hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT, 30, 30, 300, 30, hwnd, (HMENU) ID_EDIT, hInst, NULL); Когда главное окно приложения получает фокус ввода, в его функцию передается сообщение WM_SETFOCUS. Но нам нужно, чтобы фокус ввода получил редактор текста, так как именно он будет обрабатывать символьные сообщения, поступающие от клавиатуры. Поэтому в ответ на сообщение WM_SETFOCUS функция главного окна отдает фокус ввода текстовому редактору, вызывая функцию SetFocus: case WM_SETFOCUS: { SetFocus(hEdit); return 0; } Сообщение WM_COMMAND может приходить в функцию главного окна от текстового редактора или от кнопки. Если сообщение пришло от текстового редактора, параметр wParam этого сообщения содержит идентификатор редактора ID_EDIT. В этом случае обработчик сообщения получает код извещения из старшего байта параметра lParam. Обрабатывается только одно извещение с кодом EN_ERRSPACE (мало памяти). Если же сообщение WM_COMMAND пришло от кнопки, параметр wParam этого сообщения содержит идентификатор кнопки ID_BUTTON. Обработчик такого сообщения читает содержимое первой (и единственной) строки текстового редактора в специальным образом подготовленный буфер и выводит строку на экран, вызывая функцию MessageBox. Подготовка буфера заключается в том, что в его первое слово записывается размер буфера в байтах: * (WORD *) chBuff = sizeof (chBuff) - 1; Затем текстовому редактору посылается сообщение EM_GETLINE: cbText = SendMessage(hEdit, EM_GETLINE, 0, (LPARAM)(LPSTR)chBuff); В качестве парамера lParam используется адрес подготовленного буфера. После посылки сообщения функция SendMessage возвращает количество скопированных в буфер символов, причем буфер нулем не закрывается. Далее обработчик сообщения закрывает буфер нулем и выводит содержимое буфера на экран: chBuff[cbText] = '\0'; MessageBox(hwnd, chBuff, szWindowTitle, MB_OK); Обработчик сообщения WM_PAINT выводит в окно приглашение для ввода текста, вызывая функцию TextOut: TextOut(hdc, 30, 10, "Введите строку и нажмите кнопку 'OK'", 36); Файл определения модуля для приложения EDIT приведен в листинге 2.23. Листинг 2.23. Файл edit\edit.def ; ============================= ; Файл определения модуля ; ============================= NAME EDIT DESCRIPTION 'Приложение EDIT, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple Приложение TEDITПриложение TEDIT представляет собой текстовый редактор, аналогичный редактору Notepad. В отличие от последнего наш редактор пока не имеет меню. Он способен создавать новые файлы, загружать и редактировать имеющиеся, сохранять текст в старом или новом файле (рис. 2.13).
Рис. 2.13. Главное окно приложения TEDIT В верхней части главного окна приложения TEDIT расположены четыре кнопки. Окно редактирования имеет горизонтальную и вертикальную полосу просмотра. Размер главного окна приложения можно изменять при помощи толстой рамки. При этом также изменяется размер окна редактирования. Кнопка "New" предназначена для создания нового текста. Если перед тем как нажать на эту кнопку вы загрузили в редактор текст (или набрали его при помощи клавиатуры), на экран будет выдано предупреждающее сообщение о том, что содержимое редактируемого файла была изменено и его нужно сохранить (рис. 2.14).
Рис. 2.14. Предупреждающее сообщение Если нажать на кнопку "Yes", вы вернетесь в режим редактирования и сможете сохранить текст, нажав кнопку "Save". Если же нажать на кнопку "No", содержимое окна редактирования будет стерто и вы сможете набирать новый текст. Кнопка "Open" предназначена для загрузки в редактор текстового файла. Если нажать на эту кнопку, на экране появится стандартная диалоговая панель "Open", с помощью которой вы сможете загрузить файл (рис. 2.15).
Рис. 2.15. Диалоговая панель "Open" Если перед загрузкой нового файла окно редактирования содержало несохраненный текст, на экран будет выведено предупреждающее сообщение (рис. 2.14). Размер загружаемого файла не должен превосходить 32000 байт, в противном случае на экран будет выведено предупреждающее сообщение. Кнопка "Save" предназначена для сохранения текста в файле. Если нажать на эту кнопку, на экране появится стандартная диалоговая панель "Save As" (рис. 2.16), с помощью которой можно выбрать файл для сохранения текста.
Рис. 2.16. Диалоговая панель "Save As" И, наконец, последняя кнопка с надписью "Exit" завершает работу приложения. Если перед завершением в окне редактирования имеется несохраненный текст, на экране появляется знакомое вам предупреждающее сообщение (рис. 2.14). Из приведенного выше описания видно, что наш редактор текста выполняет достаточно сложные функции. Однако тем не менее листинг основного файла исходного текста приложения (листинг 2.24) занимает чуть больше десяти страниц, а размер загрузочного модуля составляет примерно 7 Кбайт. Это возможно благодаря тому, что вся основная работа выполняется не приложением, а модулями, расположенными в библиотеках динамической загрузки операционной системы Windows. Листинг 2.24. Файл tedit\tedit.cpp // ---------------------------------------- // Редактор текстовых файлов // ---------------------------------------- #define STRICT #include <windows.h> #include <commdlg.h> #include <mem.h> #include <string.h> #include <stdlib.h> // Идентификатор редактора текста #define ID_EDIT 1 // Идентификаторы кнопок #define ID_NEW 2 #define ID_OPEN 3 #define ID_SAVE 4 #define ID_EXIT 5 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); HFILE OpenFile(void); HFILE OpenSaveFile(void); // Имя класса окна char const szClassName[] = "TEditAppClass"; // Заголовок окна char const szWindowTitle[] = "Text Editor"; // Идентификатор копии приложения HINSTANCE hInst; // Флаг изменений в тексте BOOL bUpdate; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем расположение и размеры CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, // CW_USEDEFAULT, // 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Идентификатор редактора текста static HWND hEdit; // Идентификаторы кнопок static HWND hButtNew; static HWND hButtOpen; static HWND hButtSave; static HWND hButtExit; // Идентификаторы файлов static HFILE hfSrcFile, hfDstFile; switch (msg) { case WM_CREATE: { // Создаем редактор текста hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 0, 0, 0, hwnd, (HMENU) ID_EDIT, hInst, NULL); // Устанавливаем максимальную длину // редактируемого текста, равную 32000 байт SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L); // Сбрасываем флаг обновления текста bUpdate = FALSE; // Создаем кнопки hButtNew = CreateWindow("button", "New", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 50, 20, hwnd, (HMENU) ID_NEW, hInst, NULL); hButtOpen = CreateWindow("button", "Open", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 50, 0, 50, 20, hwnd, (HMENU) ID_OPEN, hInst, NULL); hButtSave = CreateWindow("button", "Save", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 100, 0, 50, 20, hwnd, (HMENU) ID_SAVE, hInst, NULL); hButtExit = CreateWindow("button", "Exit", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 150, 0, 50, 20, hwnd, (HMENU) ID_EXIT, hInst, NULL); return 0; } case WM_SIZE: { // Устанавливаем размер органа управления // (текстового редактора) в соответствии // с размерами главного окна приложения MoveWindow(hEdit, 0, 20, LOWORD(lParam), HIWORD(lParam) - 20, TRUE); return 0; } // Когда главное окно приложения получает // фокус ввода, отдаем фокус редактору текста case WM_SETFOCUS: { SetFocus(hEdit); return 0; } case WM_COMMAND: { // Обработка извещений текстового редактора if(wParam == ID_EDIT) { // Ошибка if(HIWORD(lParam) == EN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); } // Произошло изменение в редактируемом // тексте else if(HIWORD(lParam) == EN_UPDATE) { // Устанавливаем флаг обновления текста bUpdate = TRUE; } return 0; } // Нажата кнопка сохранения текста else if(wParam == ID_SAVE) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer; // Открываем выходной файл hfDstFile = OpenSaveFile(); if(!hfDstFile) return 0; // Определяем размер текста wSize = GetWindowTextLength(hEdit); // Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); // Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf); // Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile); MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK); return 0; } // Закрываем файл _lclose(hfDstFile); // Расфиксируем блок памяти LocalUnlock(hTxtBuf); // Так как файл был только что сохранен, // сбрасываем флаг обновления bUpdate = FALSE; return 0; } // Создание нового файла else if(wParam == ID_NEW) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Сбрасываем содержимое текстового редактора SetWindowText(hEdit, "\0"); return 0; } // Загрузка файла для редактирования else if(wParam == ID_OPEN) { LPSTR lpTextBuffer; DWORD dwFileSize, dwCurrentPos; // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Открываем входной файл. hfSrcFile = OpenFile(); if(!hfSrcFile) return 0; // Определяем размер файла dwCurrentPos = _llseek(hfSrcFile, 0L, 1); dwFileSize = _llseek(hfSrcFile, 0L, 2); _llseek(hfSrcFile, dwCurrentPos, 0); // Размер файла не должен превосходить 32000 байт if(dwFileSize >= 32000) { _lclose(hfSrcFile); MessageBox(hwnd, "Размер файла больше 32000 байт", szWindowTitle, MB_OK); return 0; } // Заказываем память для загрузки файла lpTextBuffer = (LPSTR)malloc(32000); if(lpTextBuffer == NULL) return 0; // Загружаем текст из файла в буфер _lread(hfSrcFile, lpTextBuffer, dwFileSize); // Закрываем буфер двоичным нулем lpTextBuffer[(WORD)dwFileSize] = '\0'; // Закрываем файл _lclose(hfSrcFile); // Переносим содержимое буфера в // текстовый редактор SetWindowText(hEdit, lpTextBuffer); // Освобождаем буфер free((void *)lpTextBuffer); // сбрасываем флаг обновления bUpdate = FALSE; } else if(wParam == ID_EXIT) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Посылаем в функцию главного окна // сообщение WM_CLOSE SendMessage(hwnd, WM_CLOSE, 0, 0L); return 0; } return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ------------------------------- // Функция OpenFile // Сохранение файла // ------------------------------- HFILE OpenFile(void) { // Структура для выбора файла OPENFILENAME ofn; // Буфер для записи пути к выбранному файлу char szFile[256]; // Буфер для записи имени выбранного файла char szFileTitle[256]; // Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; // Идентификатор открываемого файла HFILE hf; // Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0'; // Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля // Размер структуры ofn.lStructSize = sizeof(OPENFILENAME); // Идентификатор окна ofn.hwndOwner = NULL; // Адрес строки фильтра ofn.lpstrFilter = szFilter; // Номер позиции выбора ofn.nFilterIndex = 1; // Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile; // Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile); // Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle; // Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle); // В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL; // Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ); // Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; } // ------------------------------- // Функция OpenSaveFile // Выбор файла для редактирования // ------------------------------- HFILE OpenSaveFile(void) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0"; HFILE hf; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY; // Выбираем выходной файл if (GetSaveFileName(&ofn)) { // При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; } Функция WinMain приложения создает главное окно и запускает цикл обработки сообщений. В этом цикле вызывается функция TranslateMessage, необходимая для получения символьных сообщений. Как обычно, внешний вид и функциональные возможности нашего приложения определяет функция главного окна. По сообщению WM_CREATE создается редактор текста: hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 0, 0, 0, hwnd, (HMENU) ID_EDIT, hInst, NULL); В нашем приложении используется многострочный редактор текста с рамкой, вертикальной и горизонтальной полосой просмотра, выполняющий функции автоматической свертки текста по вертикали и горизонтали, с выравниванием текста по левой границе. Пусть вас не смущает, что мы указали нулевые значения для координат и размеров редактора текста. Размеры редактора текста зависят от размеров главного окна приложения, поэтому они будут установлены при обработке сообщения WM_SIZE. Далее обработчик сообщения WM_CREATE устанавливает максимальную длину редактируемого текста, для чего посылает редактору сообщение EM_LIMITTEXT: SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L); После этого сбрасывается флаг обновления текста: bUpdate = FALSE; Этот флаг будет устанавливаться при внесении в текст любых изменений и проверяться перед созданием или загрузкой нового текста, а также перед завершением работы приложения. Далее в верхней части главного окна приложения создаются четыре кнопки: "New", "Open", "Save" и "Exit". Во время обработки сообщения WM_SIZE (поступающего в функцию окна при создании окна и при изменении его размера) устанавливаются правильные размеры и расположение редактора текста: case WM_SIZE: { MoveWindow(hEdit, 0, 20, LOWORD(lParam), HIWORD(lParam) - 20, TRUE); return 0; } Редактор текста располагается на 20 пикселов ниже верхней границы внутренней области окна и имеет высоту, на 20 пикселов меньшую, чем высота внутренней области окна. В результате в верхней части основного окна приложения остается место для четырех кнопок, управляющих работой приложения. Так же как и в предыдущем приложении, обработчик сообщения WM_SETFOCUS передает фокус ввода текстовому редактору, для чего вызывает функцию SetFocus. Обработчик сообщения WM_COMMAND получает сообщения, приходящие от окна редактирования и кнопок. Если сообщение пришло от окна редактирования, проверяется код извещения. Код извещения EN_ERRSPACE соответствует ошибке при запросе дополнительной памяти. При его обработке выдается предупреждающее сообщение. Код извещения EN_UPDATE поступает при любом изменении содержимого редактируемого текста. Обработчик этого кода извещения устанавливает флаг обновления текста, сигнализируя о том, что вы изменили текст и его необходимо сохранить: else if(HIWORD(lParam) == EN_UPDATE) { // Устанавливаем флаг обновления текста bUpdate = TRUE; } Если нажата кнопка сохранения текста, обработчик сообщения WM_COMMAND открывает выходной файл, вызывая функцию OpenSaveFile, определенную в нашем приложении. Последняя использует стандартную диалоговую панель "Save As", с которой вы уже знакомы. Далее обработчик определяет размер текста, находящегося в окне редактирования (в байтах), вызывая функцию GetWindowTextLength: wSize = GetWindowTextLength(hEdit); Далее, посылая сообщение EM_GETHANDLE, функция определяет идентификатор блока памяти, используемой редактором для хранения текста: hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); Управление памятью в Windows мы рассмотрим позже. Сейчас только отметим, что получив идентификатор блока памяти, мы еще не имеем к этой памяти непосредственного доступа. Для получения доступа, а заодно и адреса блока памяти, этот блок следует зафиксировать, вызвав (в нашем случае) функцию LocalLock: npTextBuffer = (NPSTR)LocalLock(hTxtBuf); После этого мы записываем весь блок памяти в файл, закрываем файл и освобождаем зафиксированный блок памяти: if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { _lclose(hfDstFile); MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK); return 0; } _lclose(hfDstFile); LocalUnlock(hTxtBuf); Так как файл был только что записан на диск, мы сбрасываем флаг обновления: bUpdate = FALSE; При создании нового файла прежде всего проверяется флаг обновления. Если он сброшен, содержимое текстового редактора сбрасывается, для чего в него записывается пустая строка: SetWindowText(hEdit, "\0"); При загрузке файла для редактирования после проверки флага обновления вызывается функция OpenFile. Эта функция, определенная в нашем приложении, открывает файл с помощью стандартной диалоговой панели "Open", с которой вы уже знакомы. Далее определяется размер файла, который не должен превосходить 32000 байт. Если файл имеет подходящий размер, приложение заказывает буфер для загрузки файла, вызывая функцию malloc (для приложений Windows есть и другие способы получения памяти, но этот тоже работает): lpTextBuffer = (LPSTR)malloc(32000); Далее файл читается в буфер, после чего буфер закрывается двоичным нулем: _lread(hfSrcFile, lpTextBuffer, dwFileSize); lpTextBuffer[(WORD)dwFileSize] = '\0'; Для переноса текста из буфера в редактор вызывается функция SetWindowText: SetWindowText(hEdit, lpTextBuffer); После этого буфер можно освободить, вызвав функцию free. При завершении работы приложения с помощью кнопки "Exit" после проверки флага обновления в функцию главного окна приложения посылается сообщение WM_CLOSE: SendMessage(hwnd, WM_CLOSE, 0, 0L); Файл определения модуля приложения TEDIT приведен в листинге 2.25. Листинг 2.25. Файл tedit\tedit.def ; ============================= ; Файл определения модуля ; ============================= NAME TEDIT DESCRIPTION 'Приложение TEDIT, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple |