Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система Microsoft Windows 3.1 для программиста

© Александр Фролов, Григорий Фролов
Том 13, М.: Диалог-МИФИ, 1993, 284 стр.

[Назад] [Содеожание] [Дальше]

1.12. Приложение SMARTPAD

Для иллюстрации всего сказанного выше относительно использования меню и органа управления Toolbar мы приведем исходные тексты приложения SMARTPAD, которое реализует основные функции, связанные с редактированием текста (рис. 1.19).

Рис. 1.19. Главное окно приложения SMARTPAD

Так как мы пока еще не умеем работать с принтерами и шрифтами, первая версия нашего редактора текста несколько ограничена. Кроме того, пока не реализованы функции меню "Help". Тем не менее это приложение имеет Toolbar, использует акселераторы для доступа к основным функциям, модифицирует системное меню и по щелчку правой клавиши мыши создает в окне редактирования плавающее меню.

На примере этого приложения мы демонстрируем не только способы работы с различными типами меню, но и способ "перехвата" управления у функции окна органа управления класса "edit".

Орган управления Toolbar выделен в отдельный класс (в терминах языка программирования С++), исходные тексты которого находятся в двух файлах. Вы сможете с помощью этого класса без труда (почти) создавать в приложениях свой собственный Toolbar.

Основной файл приложения SMARTPAD приведен в листинге 1.8.


Листинг 1.8. Файл smartpad/smartpad.cpp


#define STRICT
#include <windows.h>
#include <commdlg.h>
#include <mem.h>
#include <string.h>
#include <stdlib.h>

#include "toolbar.hpp"
#include "smartpad.hpp"

// ======================================================
// Прототипы функций
// ======================================================

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

BOOL CALLBACK _export
DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);

LRESULT CALLBACK _export
EditWndProc(HWND hwnd, UINT msg,WPARAM wParam,LPARAM lParam);

HFILE OpenFile(void);
HFILE OpenSaveFile(void);
int SaveFileAs(HWND hwnd);
int SaveFile(HWND hwnd);
int SelectFile(HWND hwnd);

// ======================================================
// Глобальные переменные
// ======================================================

// Имя класса окна
char const szClassName[]   = "SmartPadAppClass";

// Заголовок окна
char const szWindowTitle[] = "Smart Pad";

// Глобальная переменная для хранения идентификатора
// текущей копии приложения
HINSTANCE hInst;

// Указатель на объект органа управления TOOLBAR
Toolbar *Tb;

// Переменные для хранения идентификаторов меню
HMENU hmenuAppMenu;
HMENU hmenuSystemMenu;

// Идентификатор таблицы акселераторов
HACCEL haccel;

// Идентификатор редактора текста
HWND hEdit;

// Признак внесения изменений в текст 
BOOL bNeedSave;

// Путь к редактируемому файлу
char szCurrentFileName[128];

// Временный буфер
char szTempBuffer[128];

// Признак запрета редактирования
BOOL bReadOnly = FALSE;

// Идентификаторы файлов
HFILE hfSrcFile, hfDstFile;

// Переменные для хранения адресов функций
DLGPROC lpfnDlgProc;
WNDPROC lpfnEditOldWndProc;
WNDPROC lpfnEditWndProc;

// Идентификатор главного окна
HWND    hwndMain;

// ======================================================
// Функция WinMain
// ======================================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, 
   HINSTANCE hPrevInstance,
   LPSTR     lpszCmdLine, 
   int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Сохраняем идентификатор текущей копии приложения
  hInst = hInstance;

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // Загружаем основное меню приложения
  hmenuAppMenu = LoadMenu(hInstance, "APP_MENU");

  // После успешной инициализации приложения создаем
  // главное окно приложения
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // задаем размеры и расположение
    CW_USEDEFAULT,       // окна, принятые по умолчанию 
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    0,                   // идентификатор родительского окна
    hmenuAppMenu,        // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);          // указатель на дополнительные параметры

  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  // Сохраняем идентификатор главного окна в
  // глобальной переменной
  hwndMain = hwnd;

  // Рисуем главное окно
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Создаем орган управления TOOLBAR
  Tb = new Toolbar(hInstance, hwnd, TB_FIRST);

  // Создаем кнопки в органе управления TOOLBAR
  Tb->InsertButton(0,
     MAKEINTRESOURCE(IDB_NEWUP),
     MAKEINTRESOURCE(IDB_NEWDOWN),
     MAKEINTRESOURCE(IDB_NEWUP));
  Tb->InsertButton(1,
     MAKEINTRESOURCE(IDB_OPENUP),
     MAKEINTRESOURCE(IDB_OPENDOWN),
     MAKEINTRESOURCE(IDB_OPENGR));
  Tb->InsertButton(2,
     MAKEINTRESOURCE(IDB_SAVEUP),
     MAKEINTRESOURCE(IDB_SAVEDOWN), 
     MAKEINTRESOURCE(IDB_SAVEGR));
  Tb->InsertButton(4,
     MAKEINTRESOURCE(IDB_CUTUP),
     MAKEINTRESOURCE(IDB_CUTDOWN), 
     MAKEINTRESOURCE(IDB_CUTGR));
  Tb->InsertButton(5,
     MAKEINTRESOURCE(IDB_COPYUP),
     MAKEINTRESOURCE(IDB_COPYDOWN),
     MAKEINTRESOURCE(IDB_COPYGR));
  Tb->InsertButton(6,
     MAKEINTRESOURCE(IDB_PASTUP),
     MAKEINTRESOURCE(IDB_PASTDOWN),
     MAKEINTRESOURCE(IDB_PASTGR));
  Tb->InsertButton(7,
     MAKEINTRESOURCE(IDB_UNDOUP),
     MAKEINTRESOURCE(IDB_UNDODOWN),
     MAKEINTRESOURCE(IDB_UNDOGR));
  Tb->InsertButton(9,
     MAKEINTRESOURCE(IDB_EXITUP),
     MAKEINTRESOURCE(IDB_EXITDOWN),
     MAKEINTRESOURCE(IDB_EXITGR));
  Tb->InsertButton(10,
     MAKEINTRESOURCE(IDB_HELPUP),
     MAKEINTRESOURCE(IDB_HELPDOWN),
     MAKEINTRESOURCE(IDB_HELPGR));

  // Загружаем таблицу акселераторов
  haccel = LoadAccelerators(hInstance, "APP_ACCELERATORS");

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    if(!haccel || !TranslateAccelerator(hwnd, haccel, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  return msg.wParam;
}

// ======================================================
// Функция InitApp
// Выполняет регистрацию класса окна
// ======================================================
BOOL InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации класса окна

  // Записываем во все поля структуры нулевые значения
  memset(&wc, 0, sizeof(wc));

  wc.lpszMenuName  = NULL;
  wc.style         = 0;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APPICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszClassName = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// ======================================================
// Новая функция окна для редактора текста
// ======================================================
LRESULT CALLBACK _export
EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // Если в окне редактора текста пользователь нажал
  // правую клавишу мыши, выводим в позиции курсора мыши
  // плавающее меню
  if(msg == WM_RBUTTONDOWN)
  {
    HMENU hmenuPopup;
    POINT pt;

    // Преобразуем координаты курсора мыши в экранные
    pt = MAKEPOINT(lParam);
    ClientToScreen(hwnd, &pt);

    // Создаем пустое временное меню
    hmenuPopup = CreatePopupMenu();

    // Заполняем временное меню
    AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED,
         CM_FILENEW, "&New");
    AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED,
         CM_FILEOPEN, "&Open");
    AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED,
         CM_FILESAVE, "&Save");
    AppendMenu(hmenuPopup, MF_SEPARATOR, 0, 0);
    AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED,
         CM_FILEEXIT, "E&xit");

    // Выводим плавающее меню в позиции курсора мыши
    TrackPopupMenu(hmenuPopup,
      TPM_CENTERALIGN | TPM_LEFTBUTTON,
      pt.x, pt.y, 0, hwndMain, NULL);

    // Удаляем временное меню
    DestroyMenu(hmenuPopup);
  }
  // Вызываем старую функцию окна редактора текста
  return CallWindowProc(lpfnEditOldWndProc,
     hwnd, msg, wParam, lParam);
}

// ======================================================
// Функция главного окна приложения WndProc
// ======================================================
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HFONT hfont;

  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, 30, 100, 100,
         hwnd, (HMENU) ID_EDIT, hInst, NULL);

      // Задаем для редактора текста шрифт с
      // переменной шириной символов
      hfont = GetStockFont(ANSI_VAR_FONT);
      SendMessage(hEdit, WM_SETFONT, (WPARAM)hfont,
        (LPARAM)MAKELONG((WORD)TRUE, 0));

      // Создаем переходник для новой функции 
      // окна редактора текста
      lpfnEditWndProc =
       (WNDPROC)MakeProcInstance((FARPROC)EditWndProc,hInst);
       
      // Определяем адрес старой функции 
      // окна редактора текста
      lpfnEditOldWndProc =
        (WNDPROC)GetWindowLong(hEdit, GWL_WNDPROC);

      // Подключаем к редактору текста новую функцию окна
      SetWindowLong(hEdit, GWL_WNDPROC,
        (LONG)lpfnEditWndProc);

      // Устанавливаем максимальную длину
      // редактируемого текста, равную 32000 байт
      SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L);

      // Сбрасываем флаг обновления текста и флаг
      // запрета редактирования
      bNeedSave = FALSE;
      bReadOnly = FALSE;

      // Так как редактируемый файл не открывался и не
      // сохранялся, в переменную пути к нему записываем
      // пустую строку
      lstrcpy(szCurrentFileName, "");

      // Устанавливаем заголовок окна приложения
      SetWindowText(hwnd, "SmartPad - [UNTITLED]");

      // Определяем идентификатор системного меню
      hmenuSystemMenu = GetSystemMenu(hwnd, FALSE);

      // Добавляем в системное меню разделительную линию
      // и строку "About"
      AppendMenu(hmenuSystemMenu, MF_SEPARATOR, 0, 0);
      AppendMenu(hmenuSystemMenu, MF_BYCOMMAND | MF_ENABLED,
         CM_SYSABOUT, "&About...");

      // Блокируем в системном меню строку "Close"
      EnableMenuItem(hmenuSystemMenu, SC_CLOSE,
        MF_BYCOMMAND | MF_GRAYED);

      return 0;
    }

    case WM_SIZE:
    {
      // Устанавливаем новую ширину дочернего окна
      // органа управления TOOLBAR
      Tb->SetWidth(LOWORD(lParam));

      // Устанавливаем размер органа управления
      // (текстового редактора) в соответствии
      // с размерами главного окна приложения
      MoveWindow(hEdit, 0, 26, LOWORD(lParam),
         HIWORD(lParam) - 26, TRUE);

      return 0;
    }

    // Когда главное окно приложения получает
    // фокус ввода, отдаем фокус редактору текста
    case WM_SETFOCUS:
    {
      SetFocus(hEdit);
      return 0;
    }

    // Это сообщение приходит от системного меню
    case WM_SYSCOMMAND:
    {
      // Необходимо замаскировать четыре младших бита
      // параметра wParam
      if((wParam & 0xfff0) == CM_SYSABOUT)
      {
        // Переходник для функции диалоговой панели
        lpfnDlgProc = 
          (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst);

        // Создаем модальную диалоговую панель
        DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc);
        return 0;
      }

      // Блокируем строку "Close". Эта строка не является
      // обязательной, так как мы уже заблокировали эту
      // строку функцией EnableMenuItem
      else if((wParam & 0xfff0) == SC_CLOSE)
        return 0;
      break;
    }

    // Сообщение от меню и органа управления TOOLBAR
    case WM_COMMAND:
    {
       switch(wParam)
       {
         // Обработка извещений текстового редактора
         case ID_EDIT:
         {
           // Ошибка
           if(HIWORD(lParam) == EN_ERRSPACE)
           {
             MessageBox(hwnd, "Мало памяти",
               szWindowTitle, MB_OK);
           }

           // Произошло изменение в редактируемом
           // тексте
           else if(HIWORD(lParam) == EN_UPDATE)
           {
             // Устанавливаем флаг обновления текста
             bNeedSave = TRUE;
           }
           return 0;
         }

         // Эти строки меню пока заблокированы, так как
         // соответствующие функции не реализованы
         case CM_HELPUSING_HELP:
         case CM_HELPPROCEDURES:
         case CM_HELPCOMMANDS:
         case CM_HELPKEYBOARD:
         case CM_HELPINDEX:
         case CM_FILEPRINTER_SETUP:
         case CM_FILEPAGE_SETUP:
         case CM_FILEPRINT:
         {
         MessageBox(hwnd, "В текущей версии "
           "редактора SmartPad данная функция"
           " не реализована", NULL, MB_OK);
           return 0;
         }

         // На запрос подсказки выводим диалоговую панель
         // с информацией о программе
         case TB_HELP:
         case CM_HELPABOUT:
         {
           // Переходник для функции диалоговой панели
           lpfnDlgProc =
             (DLGPROC)MakeProcInstance(
             (FARPROC)DlgProc, hInst);

           // Создаем модальную диалоговую панель
           DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc);

           // Ликвидируем переходник
           FreeProcInstance((FARPROC) lpfnDlgProc);

           return 0;
         }

         // Переключение режима запрета редактирования
         case CM_EDITSETREADONLY:
         {
           // Если режим запрета редактирования выключен,
           // включаем его 
           if(!bReadOnly)
           {
             // Запрещаем редактирование
             SendMessage(hEdit, EM_SETREADONLY, TRUE, 0L);

             // Отмечаем соответствующую строку в меню
             CheckMenuItem(hmenuAppMenu, CM_EDITSETREADONLY,
               MF_BYCOMMAND | MF_CHECKED);

             // Устанавливаем флаг запрета редактирования
             bReadOnly = TRUE;
           }
           // Если режим запрета редактирования включен,
           // выключаем его 
           else
           {
             SendMessage(hEdit, EM_SETREADONLY, FALSE, 0L);
             CheckMenuItem(hmenuAppMenu, CM_EDITSETREADONLY,
               MF_BYCOMMAND | MF_UNCHECKED);
             bReadOnly = FALSE;
           }

           // Устанавливаем фокус ввода на редактор текста
           SetFocus(hEdit);
           return 0;
         }

         case CM_EDITPASTE:
         case TB_PAST:
         {
           SendMessage(hEdit, WM_PASTE, 0, 0L);
           SetFocus(hEdit);
           return 0;
         }

         case CM_EDITCOPY:
         case TB_COPY:
         {
           SendMessage(hEdit, WM_COPY, 0, 0L);
           SetFocus(hEdit);
           return 0;
         }

         case CM_EDITCUT:
         case TB_CUT:
         {
           SendMessage(hEdit, WM_CUT, 0, 0L);
           SetFocus(hEdit);
           return 0;
         }

         case CM_EDITCLEAR:
         {
           SendMessage(hEdit, WM_CLEAR, 0, 0L);
           SetFocus(hEdit);
           return 0;
         }

         case CM_EDITSELALL:
         {
           SendMessage(hEdit, EM_SETSEL, 0, 
              MAKELPARAM(0, -1));
           SetFocus(hEdit);
           return 0;
         }

         case CM_EDITUNDO:
         case TB_UNDO:
         {
           SendMessage(hEdit, EM_UNDO, 0, 0L);
           SetFocus(hEdit);
           return 0;
         }

         // Завершаем работу приложения
         case TB_EXIT:
         case CM_FILEEXIT:
         {
           // Проверяем флаг обновления
           if(bNeedSave)
           {
             // Если в тексте были изменения,
             // спрашиваем у пользователя, надо ли
             // сохранять текст в файле
             if(IDYES == MessageBox(hwnd,
               "Файл был изменен. Желаете сохранить?",
               szWindowTitle, MB_YESNO | MB_ICONQUESTION))
             {
               // Если файл ни разу не сохранялся,
               // спрашиваем путь и имя нового файла
               if(szCurrentFileName[0] == '\0')
               {
                 SaveFileAs(hwnd);

                 // Изменяем заголовок главного окна
                 // приложения в соответствии 
                 // с именем и путем к файлу 
                 wsprintf(szTempBuffer, "SmartPad - [%s]",
                    (LPSTR)szCurrentFileName);
                 SetWindowText(hwnd, szTempBuffer);
               }

               // Если файл уже сохранялся, записываем его
               // на прежнее место
               else
                 SaveFile(hwnd);
               }
             }

           // Завершаем работу приложения
           DestroyWindow(hwnd);
           return 0;
         }

         case CM_FILENEW:
         case TB_NEW:
         {
           // Проверяем флаг обновления
           if(bNeedSave)
           {
             if(IDYES == MessageBox(hwnd,
               "Файл был изменен. Желаете сохранить?",
               szWindowTitle, MB_YESNO | MB_ICONQUESTION))
             {
               if(szCurrentFileName[0] == '\0')
               {
                 SaveFileAs(hwnd);
                 wsprintf(szTempBuffer, "SmartPad - [%s]",
                   (LPSTR)szCurrentFileName);
                 SetWindowText(hwnd, szTempBuffer);
               }
               else
                 SaveFile(hwnd);
             }
           }

           // Сбрасываем содержимое текстового редактора
           SetWindowText(hEdit, "\0");

           bNeedSave = FALSE;

           lstrcpy(szCurrentFileName, "");
           SetWindowText(hwnd, "SmartPad - [UNTITLED]");

           SetFocus(hEdit);
           return 0;
         }

         case CM_FILEOPEN:
         case TB_OPEN:
         {
           // Проверяем флаг обновления
           if(bNeedSave)
           {
             if(IDYES == MessageBox(hwnd,
               "Файл был изменен. Желаете сохранить?",
                szWindowTitle, MB_YESNO | MB_ICONQUESTION))
             SaveFile(hwnd);
           }


           if(!SelectFile(hwnd))
           {
             lstrcpy(szCurrentFileName, "");
             SetWindowText(hwnd, "SmartPad - [UNTITLED]");
           }
           else
           {
             wsprintf(szTempBuffer, "SmartPad - [%s]",
               (LPSTR)szCurrentFileName);
             SetWindowText(hwnd, szTempBuffer);
           }

           return 0;
         }

         case CM_FILESAVEAS:
         {
           if(SaveFileAs(hwnd))
           {
             wsprintf(szTempBuffer, "SmartPad - [%s]",
               (LPSTR)szCurrentFileName);
             SetWindowText(hwnd, szTempBuffer);
           }
           else
           {
             lstrcpy(szCurrentFileName, "");
             SetWindowText(hwnd, "SmartPad - [UNTITLED]");
           }
           return 0;
         }

         case CM_FILESAVE:
         case TB_SAVE:
         {
           if(szCurrentFileName[0] == '\0')
           {
             if(SaveFileAs(hwnd))
             {
               wsprintf(szTempBuffer, "SmartPad - [%s]",
                 (LPSTR)szCurrentFileName);
               SetWindowText(hwnd, szTempBuffer);
             }
             else
             {
               lstrcpy(szCurrentFileName, "");
               SetWindowText(hwnd, "SmartPad - [UNTITLED]");
             }
           }
           else
             SaveFile(hwnd);
           return 0;
         }

         default:
           break;
       }
       break;
    }

    // Это сообщение приходит при завершении работы
    // операционной системы Windows. Если мы не сохранили
    // редактируемый текст, спрашиваем у пользователя,
    // надо ли это делать
    case WM_QUERYENDSESSION:
    {
      // Проверяем флаг обновления
      if(bNeedSave)
      {
        if(IDYES == MessageBox(hwnd,
          "Файл был изменен. Желаете сохранить?",
          szWindowTitle, MB_YESNO | MB_ICONQUESTION))
        {
           if(szCurrentFileName[0] == '\0')
           {
              SaveFileAs(hwnd);
              wsprintf(szTempBuffer, "SmartPad - [%s]",
                (LPSTR)szCurrentFileName);
              SetWindowText(hwnd, szTempBuffer);
           }
           else
              SaveFile(hwnd);
        }
      }

      // Разрешаем операционной системе Windows
      // завершить свою работу
      return 1L;
    }

    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);

    // Сохраняем путь к открытому файлу
    lstrcpy(szCurrentFileName, ofn.lpstrFile);

    // Возвращаем идентификатор файла
    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);

    // Сохраняем путь к файлу
    lstrcpy(szCurrentFileName, ofn.lpstrFile);
    return hf;
  }
  else
    return 0;
}

// ======================================================
// Функция SaveFileAs
// Сохранение текста в новом файле
// ======================================================
int SaveFileAs(HWND hwnd)
{
  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);

  // Так как файл был только что сохранен,
  // сбрасываем флаг обновления 
  bNeedSave = FALSE;
  SetFocus(hEdit);

  return 1;
}

// ======================================================
// Функция SaveFile
// Сохранение текста в старом файле
// ======================================================
int SaveFile(HWND hwnd)
{
  WORD wSize;
  HANDLE hTxtBuf;
  NPSTR  npTextBuffer;

  // Открываем выходной файл
  hfDstFile = _lopen(szCurrentFileName, OF_WRITE);
  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);

  // Так как файл был только что сохранен,
  // сбрасываем флаг обновления 
  bNeedSave = FALSE;
  SetFocus(hEdit);

  return 1;
}

// ======================================================
// Функция SelectFile
// Загрузка текста из файла для редактирования
// ======================================================
int SelectFile(HWND hwnd)
{
  LPSTR lpTextBuffer;
  DWORD dwFileSize, dwCurrentPos;

  // Открываем входной файл.
  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);

  // Сбрасываем флаг обновления
  bNeedSave = FALSE;
  SetFocus(hEdit);
  return 1;
}

// ======================================================
// Функция DlgProc
// ======================================================
#pragma argsused

BOOL CALLBACK _export
DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
    // Инициализация диалоговой панели
    case WM_INITDIALOG:
    {
      return TRUE;
    }

    case WM_COMMAND:
    {
      switch(wParam)
      {
        // Сообщение от кнопки "OK"
        case IDOK:

        // Отмена диалоговой панели.
        // Это сообщение приходит, когда пользователь
        // нажимает на клавишу <Esc>
        case IDCANCEL:
        {
          // Устанавливаем флаг завершения диалога
          EndDialog(hdlg, 0);
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}

В области глобальных переменных среди прочих определений располагается определение указателя на класс Toolbar:

Toolbar *Tb;

Через этот указатель (разумеется, после создания объекта и инициализации указателя) будет выполняться одна из операций, определенных для Toolbar, - создание кнопки в заданной позиции.

Отметим также переменную bNeedSave типа BOOL, которая используется как признак необходимости сохранения редактируемого текста в файле.

В массиве szCurrentFileName[128] хранится путь к редактируемому файлу. Эта информация используется для формирования заголовка главного окна приложения и для сохранения файла при помощи строки "Save" из меню "File" или кнопки в Toolbar с изображением дискеты.

С помощью строки "Read Only" меню "Options" вы можете запретить редактирование текста. При этом в глобальную переменную bReadOnly типа BOOL записывается значение TRUE.

Три глобальные переменные используются для хранения, соответственно, адреса функции диалога, появляющегося при выборе строки "About..." (рис. 1.20), адреса стандартной функции диалога редактора текста и новой функции диалога, необходимой для вывода плавающего меню.

Рис. 1.20. Диалоговая панель "About" приложения SMARTPAD

Функция WinMain инициализирует приложение обычным образом и загружает основное меню из ресурсов приложения, вызывая функцию LoadMenu. Далее создается главное окно приложения, идентификатор которого сохраняется в глобальной переменной hwndMain.

После отображения главного окна создается орган управления Toolbar :

Tb = new Toolbar(hInstance, hwnd, TB_FIRST);

Конструктору объекта Toolbar передается идентификатор копии приложения hInstance, идентификатор окна, в котором необходимо расположить Toolbar и функция которого будет получать сообщение WM_COMMAND, и константу TB_FIRST, определяющую значение параметра wParam сообщения WM_COMMAND для самой левой кнопки в органе управления Toolbar. Для второй кнопки слева значение этого параметра будет равно TB_FIRST + 1, для третьей TB_FIRST + 2, и т. д.

Далее функция WinMain создает все необходимые кнопки, вызывая метод InsertButton, определенный в классе Toolbar:

  Tb->InsertButton(0,
     MAKEINTRESOURCE(IDB_NEWUP),
     MAKEINTRESOURCE(IDB_NEWDOWN),
     MAKEINTRESOURCE(IDB_NEWUP));
  Tb->InsertButton(1,
     MAKEINTRESOURCE(IDB_OPENUP),
     MAKEINTRESOURCE(IDB_OPENDOWN),
     MAKEINTRESOURCE(IDB_OPENGR));
  Tb->InsertButton(2,
     MAKEINTRESOURCE(IDB_SAVEUP),
     MAKEINTRESOURCE(IDB_SAVEDOWN), 
     MAKEINTRESOURCE(IDB_SAVEGR));
  Tb->InsertButton(4,
     MAKEINTRESOURCE(IDB_CUTUP),
     MAKEINTRESOURCE(IDB_CUTDOWN), 
     MAKEINTRESOURCE(IDB_CUTGR));
  Tb->InsertButton(5,
     MAKEINTRESOURCE(IDB_COPYUP),
     MAKEINTRESOURCE(IDB_COPYDOWN),
     MAKEINTRESOURCE(IDB_COPYGR));
  Tb->InsertButton(6,
     MAKEINTRESOURCE(IDB_PASTUP),
     MAKEINTRESOURCE(IDB_PASTDOWN),
     MAKEINTRESOURCE(IDB_PASTGR));
  Tb->InsertButton(7,
     MAKEINTRESOURCE(IDB_UNDOUP),
     MAKEINTRESOURCE(IDB_UNDODOWN),
     MAKEINTRESOURCE(IDB_UNDOGR));
  Tb->InsertButton(9,
     MAKEINTRESOURCE(IDB_EXITUP),
     MAKEINTRESOURCE(IDB_EXITDOWN),
     MAKEINTRESOURCE(IDB_EXITGR));
  Tb->InsertButton(10,
     MAKEINTRESOURCE(IDB_HELPUP),
     MAKEINTRESOURCE(IDB_HELPDOWN),
     MAKEINTRESOURCE(IDB_HELPGR));

Первый параметр функции InsertButton определяет расположение кнопки в области, выделенной для ToolBar. Обратите внимание, что номера кнопок увеличиваются не монотонно. После кнопки с номером 2 следует кнопка с номером 4, а после кнопки с номером 7 следует кнопка с номером 9. Пропущенным номерам соответствуют пустые позиции в окне Toolbar.

Учтите, что параметр wParam сообщения WM_COMMAND, получаемого от Toolbar, зависит от номера позиции и от значения константы TB_FIRST, переданной конструктору при создании класса. Так как мы не создали кнопки с номерами 3 и 8, функция главного окна приложения никогда не получит сообщение WM_COMMAND с параметром wParam, равным TB_FIRST + 3 и TB_FIRST + 8.

Второй, третий и четвертый параметры функции InsertButton должны содержать идентификаторы изображений bitmap для кнопки в нормальном, нажатом и неактивном состоянии, соответственно.

После создания кнопок функция окна приложения будет получать от Toolbar сообщение WM_COMMAND.

Таким образом, использование класса Toolbar предельно просто. Вы должны создать объект класса Toolbar, указав идентификатор копии приложения, идентификатор главного окна приложения (или другого окна, которое будет содержать Toolbar). Затем вам надо вставить кнопки, вызвав несколько раз функцию InsertButton, которая является методом класса Toolbar. После этого орган управления Toolbar начинает работать, посылая в функцию окна сообщение WM_COMMAND.

Для установки ширины дочернего окна Toolbar определена функция SetWidth. Вы должны вызывать эту функцию в функции главного окна приложения при обработке сообщения WM_SIZE:

case WM_SIZE:
{
   Tb->SetWidth(LOWORD(lParam));
     ..... (другие строки) ......
   return 0;
}

Класс Toolbar определен в файле toolbar.hpp, поэтому в исходный текст программы необходимо включить следующую строку:

#include "toolbar.hpp"

После создания Toolbar функция WinMain загружает с помощью функции LoadAccelerators таблицу акселераторов, используемую для ускоренного доступа к строкам меню, после чего запускается цикл обработки сообщений. Для работы с акселераторами в цикле обработки сообщений вызывается функция TranslateAccelerators. Для получения символьных сообщений мы должны также вызывать в этом цикле функцию TranslateMessage.

Новая функция окна для редактора текста EditWndProc перехватывает сообщение WM_RBUTTONDOWN, возникающее в тот момент, когда пользователь нажимает правую кнопку мыши, и выводит плавающее меню.

Координаты курсора мыши передаются через параметр lParam. Эти координаты вычислены относительно верхнего левого угла внутренней области окна (client region). Так как функция TrackPopupMenu, создающая плавающее меню, использует экранные координаты, мы выполняем соответствующее преобразование с помощью функции ClientToScreen.

Если обработка сообщения от мыши завершена или пришло другое сообщение, предназначенное для функции окна редактора текста, новая функция окна EditWndProc передает сообщение без изменений стандартной функции окна через переходник lpfnEditOldWndProc:

return CallWindowProc(lpfnEditOldWndProc,
    hwnd, msg, wParam, lParam);

Теперь рассмотрим работу функции главного окна приложения, которая называется WndProc.

При создании главного окна приложения управление получает обработчик сообщения WM_CREATE. Он создает стандартный редактор текста на базе предопределенного класса окна "edit".

Для того чтобы придать редактируемому тексту более привлекательный вид, изменяем шрифт, используемый редактором текста на шрифт ANSI с переменной шириной букв:

hfont = GetStockFont (ANSI_VAR_FONT);
SendMessage(hEdit, WM_SETFONT , (WPARAM)hfont,
  (LPARAM)MAKELONG((WORD)TRUE, 0));

Подробное описание этой операции и сообщения WM_SETFONT мы отложим до главы, посвященной шрифтам.

Далее обработчик сообщения WM_CREATE создает переходник для новой функции окна редактора текста, определяет адрес старой (т. е. стандартной) функции окна редактора текста и подключает новую функцию окна. Этот процесс был описан раньше.

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

Хотя в этом нет никакой необходимости, приложение SMARTPAD изменяет системное меню. Мы сделали это исключительно для иллюстрации методов работы с системным меню.

Сначала с помощью функции GetSystemMenu мы определяем идентификатор системного меню. Затем в системное меню добавляется горизонтальная разделительная линия и строка "About...", при выборе которой на экране появляется диалоговая панель 'About". Для добавления используется функция AppendMenu.

Мы также блокируем строку "Close" в системном меню, для чего вызываем функцию EnableMenuItem. Напомним, что все стандартные строки системного меню имеют идентификаторы, для которых в файле windows.h определены символические константы. В честности, строка "Close" имеет идентификатор SC_CLOSE.

Обработчик сообщения WM_SIZE устанавливает новую ширину дочернего окна Toolbar и новый размер редактора текста:

{
  Tb->SetWidth(LOWORD(lParam));
  MoveWindow(hEdit, 0, 26, LOWORD(lParam),
     HIWORD(lParam) - 26, TRUE);
  return 0;
}

Обработчик сообщения WM_FOCUS передает фокус окну редактора текста, вызывая функцию SetFocus.

Для обработки сообщений от системного меню в функции главного окна приложения предусмотрен обработчик сообщения WM_SYSCOMMAND. Мы уже рассказывали вам об особенности этого сообщения - перед сравнением младшие четыре бита параметра wParam необходимо замаскировать.

Обработчик сообщения WM_SYSCOMMAND реагирует на добавленную нами в системное меню строку "About..." с идентификатором CM_SYSABOUT. Дополнительно мы перехватываем сообщение от строки "Close" и "изымаем" его, блокируя работу соответствующей строки.

Обработчик сообщения WM_COMMAND получился достаточно сложным, так как наше приложение имеет большое меню и Toolbar. Все основные выполняемые функции объясняются в комментариях, поэтому мы для экономии места остановимся только на некоторых моментах.

Обработка извещений от редактора текста выполняется аналогично тому, как это сделано в приложении TEDIT, описанном в предыдущем томе "Библиотеки системного программиста".

При выборе строки "Read Only" из меню "Options" инвертируется содержимое флага запрета редактирования bReadOnly. Если этот флаг находился в состоянии FALSE, редактору текста посылается сообщение EM_SETREADONLY с параметром WParam, равном TRUE, после чего содержимое флага меняется на TRUE. После этого строка "Read Only" отмечается галочкой при помощи функции CheckMenuItem.

Если выбрать эту строку еще раз, запрет редактирования отменяется, флаг запрета редактирования bReadOnly снова инвертируется, вслед за чем удаляется отметка в строке меню.

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

Обратите внимание на обработчик сообщения WM_QUERYENDSESSION. Это сообщение передается приложению перед завершением работы операционной системы Windows. Приложение может запретить завершение работы Windows, вернув нулевое значение, или разрешить, вернув значение 1. Наше приложение в ответ на это сообщение проверяет состояние флага bNeedSave. Если текст не был сохранен, на экран выводится соответствующий запрос. Если в ответ на этот запрос вы нажмете клавишу "Yes", текст будет сохранен в файле. После этого обработчик сообщения WM_QUERYENDSESSION возвращает 1, разрешая операционной системе завершить свою работу.

Файл smartpad.hpp содержит определения всех используемых в файле smartpad.cpp символических констант (листинг 1.9).


Листинг 1.9. Файл smartpad/smartpad.hpp


// Идентификаторы пиктограмм
#define IDB_NEWUP        300
#define IDB_NEWDOWN      301
#define IDB_NEWGR        302

#define IDB_OPENUP       303
#define IDB_OPENDOWN     304
#define IDB_OPENGR       305

#define IDB_SAVEUP       306
#define IDB_SAVEDOWN     307
#define IDB_SAVEGR       308

#define IDB_EXITUP       309
#define IDB_EXITDOWN     310
#define IDB_EXITGR       311

#define IDB_COPYUP       312
#define IDB_COPYDOWN     313
#define IDB_COPYGR       314

#define IDB_PASTUP       315
#define IDB_PASTDOWN     316
#define IDB_PASTGR       317

#define IDB_CUTUP        318
#define IDB_CUTDOWN      319
#define IDB_CUTGR        320

#define IDB_UNDOUP       321
#define IDB_UNDODOWN     322
#define IDB_UNDOGR       323

#define IDB_HELPUP       324
#define IDB_HELPDOWN     325
#define IDB_HELPGR       326

#define CM_FONT               24347
#define CM_SYSABOUT           0x8880

#define CM_HELPABOUT          24346
#define CM_HELPUSING_HELP     24345
#define CM_HELPPROCEDURES     24344
#define CM_HELPCOMMANDS       24343
#define CM_HELPKEYBOARD       24342
#define CM_HELPINDEX          24341

#define CM_EDITPASTE          24324
#define CM_EDITCOPY           24323
#define CM_EDITCUT            24322
#define CM_EDITUNDO           24321
#define CM_EDITCLEAR          24320
#define CM_EDITSELALL         24319
#define CM_EDITSETREADONLY    24318

#define CM_FILEEXIT           24338
#define CM_FILEPRINTER_SETUP  24337
#define CM_FILEPAGE_SETUP     24336
#define CM_FILEPRINT          24335
#define CM_FILESAVEAS         24334
#define CM_FILESAVE           24333
#define CM_FILEOPEN           24332
#define CM_FILENEW            24331

/* Идентификатор редактора текста */
#define ID_EDIT   200

/* Идентификаторы кнопок TOOLBAR */
#define TB_FIRST  100

#define TB_NEW    TB_FIRST + 0
#define TB_OPEN   TB_FIRST + 1
#define TB_SAVE   TB_FIRST + 2

#define TB_CUT    TB_FIRST + 4
#define TB_COPY   TB_FIRST + 5
#define TB_PAST   TB_FIRST + 6
#define TB_UNDO   TB_FIRST + 7

#define TB_EXIT   TB_FIRST + 9
#define TB_HELP   TB_FIRST + 10

Обратите внимание на описание идентификатора строки "About..." системного меню:

#define CM_SYSABOUT           0x8880

Значение этого идентификатора должно отличаться от значений других идентификаторов строк меню, причем младшие четыре бита не должны участвовать в сравнении.

В файле smartpad.rc (листинг 1.10) описаны ресурсы приложения.


Листинг 1.10. Файл smartpad/smartpad.rc


#include "smartpad.hpp"

IDB_NEWUP   BITMAP  "newup.bmp"
IDB_NEWDOWN BITMAP  "newdown.bmp"
IDB_NEWGR   BITMAP  "newgr.bmp"

IDB_OPENUP BITMAP   "openup.bmp"
IDB_OPENDOWN BITMAP "opendown.bmp"
IDB_OPENGR BITMAP   "opengr.bmp"

IDB_SAVEUP BITMAP   "saveup.bmp"
IDB_SAVEDOWN BITMAP "savedown.bmp"
IDB_SAVEGR BITMAP   "savegr.bmp"

IDB_EXITUP BITMAP   "exitup.bmp"
IDB_EXITDOWN BITMAP "exitdown.bmp"
IDB_EXITGR BITMAP   "exitgr.bmp"

IDB_COPYUP BITMAP   "copyup.bmp"
IDB_COPYDOWN BITMAP "copydown.bmp"
IDB_COPYGR BITMAP   "copygr.bmp"

IDB_PASTUP BITMAP   "pastup.bmp"
IDB_PASTDOWN BITMAP "pastdown.bmp"
IDB_PASTGR BITMAP   "pastgr.bmp"

IDB_CUTUP BITMAP    "cutup.bmp"
IDB_CUTDOWN BITMAP  "cutdown.bmp"
IDB_CUTGR BITMAP    "cutgr.bmp"

IDB_UNDOUP BITMAP   "undoup.bmp"
IDB_UNDODOWN BITMAP "undodown.bmp"
IDB_UNDOGR BITMAP   "undogr.bmp"

IDB_HELPUP BITMAP   "helpup.bmp"
IDB_HELPDOWN BITMAP "helpdown.bmp"
IDB_HELPGR BITMAP   "helpgr.bmp"

APP_MENU MENU
BEGIN
  POPUP "&File"
  BEGIN
    MENUITEM "&New\tCtrl+N",      CM_FILENEW
    MENUITEM "&Open...\tCtrl+O",  CM_FILEOPEN
    MENUITEM "&Save\tCtrl+S",     CM_FILESAVE
    MENUITEM "Save &as...",       CM_FILESAVEAS
    MENUITEM SEPARATOR
    MENUITEM "&Print...",         CM_FILEPRINT,        GRAYED
    MENUITEM "Page se&tup...",    CM_FILEPAGE_SETUP,   GRAYED
    MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP,GRAYED
    MENUITEM SEPARATOR
    MENUITEM "E&xit",             CM_FILEEXIT
  END

  POPUP "&Edit"
  BEGIN
    MENUITEM "&Undo\tCtrl+Z",     CM_EDITUNDO
    MENUITEM SEPARATOR
    MENUITEM "&Cut\tCtrl+X",      CM_EDITCUT
    MENUITEM "&Copy\tCtrl+C",     CM_EDITCOPY
    MENUITEM "&Paste\tCtrl+V",    CM_EDITPASTE
    MENUITEM "C&lear\tCtrl+Del",  CM_EDITCLEAR
    MENUITEM SEPARATOR
    MENUITEM "Select &All",       CM_EDITSELALL
  END

  POPUP "&Options"
  BEGIN
    MENUITEM "&Read-only",         CM_EDITSETREADONLY
    MENUITEM SEPARATOR
    MENUITEM "&Set Font...",       CM_FONT, GRAYED
  END

  POPUP "&Help"
  BEGIN
    MENUITEM "&Index\tF1",  CM_HELPINDEX,      GRAYED
    MENUITEM "&Keyboard",   CM_HELPKEYBOARD,   GRAYED
    MENUITEM "&Commands",   CM_HELPCOMMANDS,   GRAYED
    MENUITEM "&Procedures", CM_HELPPROCEDURES, GRAYED
    MENUITEM "&Using help", CM_HELPUSING_HELP, GRAYED
    MENUITEM SEPARATOR
    MENUITEM "&About...",   CM_HELPABOUT
  END
END

APPICON ICON "appicon.ico"

ABOUT DIALOG 25, 34, 118, 67
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About"
BEGIN
  CTEXT "Редактор текста\nSmart Pad\nVersion 1.0\n"
    "(C) Frolov A.V., 1994", -1, 34, 6, 79, 36, WS_CHILD |
    WS_VISIBLE | WS_GROUP
  ICON "APPICON", -1, 14, 16, 16, 16, WS_CHILD | WS_VISIBLE
  DEFPUSHBUTTON "OK", IDOK, 42, 47, 33, 14, WS_CHILD |
    WS_VISIBLE | WS_TABSTOP
END

APP_ACCELERATORS ACCELERATORS 
BEGIN
  "N", CM_FILENEW,         VIRTKEY, CONTROL
  "S", CM_FILESAVE,        VIRTKEY, CONTROL
  "O", CM_FILEOPEN,        VIRTKEY, CONTROL
  "Z", CM_EDITUNDO,        VIRTKEY, CONTROL
  "X", CM_EDITCUT,         VIRTKEY, CONTROL
  "C", CM_EDITCOPY,        VIRTKEY, CONTROL
  "V", CM_EDITPASTE,       VIRTKEY, CONTROL
  VK_DELETE, CM_EDITCLEAR, VIRTKEY, CONTROL
  VK_F1, CM_HELPINDEX,     VIRTKEY
END

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

В листинге 1.11 изображены файлы *.bmp, на которые есть ссылки в файле описания ресурсов.


Листинг 1.11. Файлы smartpad/*.bmp


newup.bmp openup.bmp saveup.bmp cutup.bmp
newdown.bmp opendown.bmp savedown.bmp cutdown.bmp
newgr.bmp opengr.bmp savegr.bmp cutgr.bmp
copyup.bmp pastup.bmp undoup.bmp exitup.bmp helpup.bmp
copydown.bmp pastdown.bmp undodown.bmp exitdown.bmp helpdown.bmp
copygr.bmp pastgr.bmp undogr.bmp exitgr.bmp helpgr.bmp


Определение класса Toolbar, предназначенного для создания органа управления Toolbar, находится в файле toolbar.hpp (листинг 1.12).


Листинг 1.12. Файл smartpad/toolbar.hpp


#include <windows.h>
#include <windowsx.h>
#include <mem.h>

// Прототип функции окна TOOLBAR
LRESULT CALLBACK _export
ToolbarWndProc(HWND, UINT, WPARAM, LPARAM);

// ======================================================
// Определение класса TbMain
// ======================================================
class TbMain
{
public:
  // Идентификатор приложения
  static HINSTANCE hInst;

  // Идентификаторы кнопок в различном состоянии          
  static HBITMAP   hbmpUp[20];     // исходное состояние 
  static HBITMAP   hbmpDown[20];   // нажатые
  static HBITMAP   hbmpGrayed[20]; // заблокированные

  // Идентификатор родительского окна, создавшего TOOLBAR
  static HWND      hwndParent;

  // Идентификатор первой кнопки в TOOLBAR
  static int       nFirstId;
};

// ======================================================
// Определение класса Toolbar
// ======================================================
class Toolbar
{
  RECT     rcParent;        // размеры родительского окна
  RECT     rcToolbar;       // размеры TOOLBAR
  ATOM     aWndClass;       // атом для кода возврата
  char     szClassName[20]; // имя класса
  HWND     hwndToolbar;     // идентификатор окна TOOLBAR
  HWND     hButton[20];     // идентификаторы кнопок
  int      errno;           // код ошибки

public:

// ======================================================
// Вычисление размеров окна TOOLBAR
// ======================================================
  void GetRect(void)
  {
    rcToolbar.left   = GetRectLeft();
    rcToolbar.top    = GetRectTop();
    rcToolbar.right  = GetRectRihgt();
    rcToolbar.bottom = GetRectBottom();
  }

// ======================================================
// Регистрация класса для окна TOOLBAR
// ======================================================
  virtual BOOL RegisterWndClass(void)
  {
    WNDCLASS wc; // структура для регистрации класса окна

    memset(&wc, 0, sizeof(wc));

    wc.style         = 0;
    wc.lpfnWndProc   = (WNDPROC) ToolbarWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = TbMain::hInst;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.lpszMenuName  = (LPSTR)NULL;

    // Определяем кисть для закрашивания окна
    wc.hbrBackground = GetBrush();

    // Имя класса
    lstrcpy(szClassName, "FrolovAVToolBar");
    wc.lpszClassName = (LPSTR)szClassName;

    // Регистрация класса
    aWndClass = RegisterClass(&wc);
    return (aWndClass != 0);
  }

// ======================================================
// Создание дочернего окна, которое будет использоваться
// для размещения кнопок TOOLBAR
// ======================================================
  void CreateTbWindow(void)
  {
    hwndToolbar = CreateWindow(
    szClassName,                 // имя класса окна
    NULL,                        // заголовок окна
    WS_CHILDWINDOW | WS_VISIBLE, // стиль окна
    rcToolbar.left,              // указываем расположение
    rcToolbar.top,               //   и размеры окна
    rcToolbar.right,
    rcToolbar.bottom,
    TbMain::hwndParent,  // идентификатор родительского окна
    0,                   // идентификатор меню
    TbMain::hInst,       // идентификатор приложения
    NULL);          // указатель на дополнительные параметры
  }

// ======================================================
// Устанавливаем ширину окна TOOLBAR
// ======================================================
  void SetWidth(int nWidth)
  {
    RECT rcOld;

    GetClientRect(hwndToolbar, &rcOld);

    MoveWindow(hwndToolbar,
      rcOld.left, rcOld.top,
      nWidth, rcOld.bottom, TRUE);
  }

// ======================================================
// Вставляем кнопку в TOOLBAR
// ======================================================
  BOOL InsertButton(UINT nPosition,
       LPCSTR lpszBmpUp, LPCSTR lpszBmpDown, 
       LPCSTR lpszBmpGrayed)
  {
     // Загружаем указанные в параметрах функции изображения
     TbMain::hbmpUp[nPosition] =
       LoadBitmap(TbMain::hInst, lpszBmpUp);
     TbMain::hbmpDown[nPosition] =
       LoadBitmap(TbMain::hInst, lpszBmpDown);
     TbMain::hbmpGrayed[nPosition] =
       LoadBitmap(TbMain::hInst, lpszBmpGrayed);

     // Создаем орган управления - кнопку, которую
     // рисует родительское окно. В нашем случае это будет
     // окно TOOLBAR
     hButton[nPosition] = CreateWindow("button", NULL,
       WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
       GetBtnX(nPosition),  // определяем расположение кнопки 
       GetBtnY(nPosition),  //  исходя из ее номера
       GetBmpWidth(),       // ширина кнопки    
       GetBmpHeigt(),       // высота кнопки
       hwndToolbar,         // родительское окно для кнопки

       // Идентификатор кнопки
       (HMENU)(nPosition + TbMain::nFirstId), 

       TbMain::hInst, NULL);
     return TRUE;
  }

// ======================================================
// Конструктор для класса Toolbar
// ======================================================
  Toolbar(HINSTANCE hInst, HWND hwndParent, int nFirstid);

// ======================================================
// Деструктор для класса Toolbar
// ======================================================
  ~Toolbar();

  // Проверка признака ошибки
  int Error(void) { return errno; }

  // Определение координат окна TOOLBAR
  virtual int GetRectLeft(void)   { return 0; }
  virtual int GetRectTop(void)    { return 0; }
  virtual int GetRectRihgt(void)  { return rcParent.right; }
  virtual int GetRectBottom(void) { return 26; }

  // Определение кисти для окна TOOLBAR
  virtual HBRUSH GetBrush(void)
  {
     return GetStockBrush(LTGRAY_BRUSH);
  }

  // Определение расположения кнопки исходя из ее номера
  virtual int GetBtnX(int nPosition)
  {
     return 3 + (nPosition * 30);
  }

  virtual int GetBtnY(int nPosition)   { return 3; }

  // Определение размеров кнопки
  virtual int GetBmpWidth(void) { return 30; }
  virtual int GetBmpHeigt(void) { return 20; }
};

В этом файле определен также класс TbMain, все члены которого описаны как статические. Такой класс можно использовать вместо набора глобальных переменных, что улучшает структуру программы.

В классе TbMain хранится идентификатор приложения hInst, три массива, в которых хранятся идентификаторы изображений bitmap для кнопок в исходном (hbmpUp), нажатом (hbmpDown) и заблокированном (hbmpGrayed) состоянии, идентификатор родительского окна, на поверхности которого создается Toolbar, идентификатор самой левой кнопки в окне Toolbar (nFirstId). Все перечисленные выше переменные должны быть доступны для методов класса Toolbar и для функции окна Toolbar.

В классе Toolbar определены переменные, предназначенные для хранения размеров родительского окна и окна Toolbar, имени класса Toolbar, идентификаторов кнопок, кода ошибки.

Метод GetRect предназначен для определения размеров дочернего окна Toolbar, на поверхности которого создаются кнопки. Этот метод вызывает функции GetRectLeft, GetRectTop, GetRectRight, GetRectBottom, определяющие соответственно расположение левой, верхней, правой и нижней границ дочернего окна.

Вы можете создать свой класс как дочерний для класса Toolbar и переопределить все или некоторые из перечисленных функций, создав, например, вертикальный Toolbar.

Метод RegisterWndClass регистрирует класс для дочернего окна Toolbar. Этот метод использует функцию GetBrush для определения кисти, используемой для закраски поверхности дочернего окна. В классе Toolbar используется светло-серая кисть. Создавая собственный класс на базе класса Toolbar вы можете переопределить функцию GetBrush и закрасить поверхность дочернего окна в любой цвет.

Метод CreateTbWindow создает дочернее окно Toolbar. Перед вызовом этого метода необходимо с помощью метода GetRect определить размеры и расположение дочернего окна органа управления Toolbar.

Для того чтобы при изменении размеров основного окна обеспечить соответствующее изменение размеров окна Toolbar, в классе Toolbar определен метод SetWidth. Единственный параметр nWidth должен содержать значение новой ширины окна. Метод SetWidth необходимо вызывать из функции главного окна приложения (или другого окна, на поверхности которого расположен Toolbar) при обработке сообщения WM_SIZE.

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

Для вставки в Toolbar кнопки вы должны вызвать метод InsertButton.

Параметр NPosition определяет расположение кнопки на поверхности Toolbar и идентификатор кнопки, передаваемый в родительское окно через параметр wParam сообщения WM_COMMAND. Идентификатор кнопки зависит от расположения кнопки в окне Toolbar и определяется при помощи следующего выражения:

nPosition + TbMain::nFirstId 

Таким образом, самой левой кнопке в горизонтальном органе управления Toolbar соответствует идентификатор TbMain::nFirstId, значение которого вы задаете при вызове конструктора класса Toolbar:

Toolbar(HINSTANCE hInst, HWND hwndParent, int nFirstid);

Дополнительно вы должны указать конструктору идентификатор текущей копии приложения hInst и идентификатор родительского окна, на поверхности которого расположен Toolbar. Определение конструктора и деструктора класса Toolbar находится в файле toolbar.cpp (листинг 1.13).

Среди других методов, определенных в классе Toolbar, отметим метод GetBtnX. Этот метод используется для вычисления X-координаты кнопки в окне Toolbar в зависимости от ее номера. Если вы создаете свой Toolbar на базе класса Toolbar, вы можете использовать другой алгоритм размещения кнопок, переопределив эту функцию, а также функцию GetBtnY, предназначенную для вычисления Y-координаты кнопки.

По умолчанию для кнопок используются изображения bitmap размером 30 х 20 пикселов. Если вам нужны кнопки другого размера, переопределите методы GetBmpWidth и GetBmpHeight.

В файле toolbar.cpp (листинг 1.13) находятся определения некоторых методов класса Toolbar, членов класса TbMain и функции дочернего окна Toolbar.


Листинг 1.13. Файл smartpad/toolbar.cpp


#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <mem.h>
#include "toolbar.hpp"

LRESULT CALLBACK _export
  ToolbarWndProc(HWND, UINT, WPARAM, LPARAM);

void DrawButton(LPDRAWITEMSTRUCT lpInfo);
void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap);

// Определяем члены статического класса TbMain
HBITMAP   TbMain::hbmpUp[20];
HBITMAP   TbMain::hbmpDown[20];
HBITMAP   TbMain::hbmpGrayed[20];
HWND      TbMain::hwndParent;
HINSTANCE TbMain::hInst;
int       TbMain::nFirstId = 0;

// ======================================================
// Конструктор класса Toolbar
// ======================================================
Toolbar::Toolbar(HINSTANCE hInstance, HWND hwndParentWindow,
   int nFirstId)
{
  // Сбрасываем признак ошибки
  errno = 0;

  // Сохраняем идентификатор копии приложения,
  // идентификатор родительского окна, создавшего
  // TOOLBAR, и идентификатор первой кнопки в
  // органе управления TOOLBAR
  TbMain::hInst = hInstance;
  TbMain::hwndParent = hwndParentWindow;
  TbMain::nFirstId = nFirstId;

  // Определяем размеры внутренней области
  // родительского окна
  GetClientRect(TbMain::hwndParent, &rcParent);

  // Определяем размеры дочернего окна, которое
  // будет использовано для создания органа TOOLBAR
  GetRect();

  // Регистрируем класс для дочернего окна TOOLBAR
  if(!RegisterWndClass())
  {
    errno = 1;
    return;
  }

  // Создаем дочернее окно TOOLBAR
  CreateTbWindow();

  if(hwndToolbar == NULL)
  {
    errno = 2;
    return;
  }
}

// ======================================================
// Деструктор класса Toolbar
// ======================================================
Toolbar::~Toolbar()
{
  // Уничтожаем дочернее окно TOOLBAR
  DestroyWindow(hwndToolbar);
}

// ======================================================
// Функция окна ToolbarWndProc для дочернего окна TOOLBAR
// ======================================================

LRESULT CALLBACK _export
ToolbarWndProc(HWND hwnd, UINT msg, WPARAM wParam,
  LPARAM lParam)
{
  switch (msg)
  {
     // Передаем сообщение WM_COMMAND от кнопок в
     // родительское окно
     case WM_COMMAND:
     {
       SendMessage(TbMain::hwndParent, WM_COMMAND,
         wParam, lParam);
       return 0;
     }

     // Это сообщение приходит при изменении состояния
     // дочернего окна органа управления, когда окно
     // нужно перерисовать
     case WM_DRAWITEM:
     {
       // Перерисовываем кнопку
       DrawButton( (LPDRAWITEMSTRUCT)lParam );
       break;
     }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

// ======================================================
// Функция DrawButton
// Перерисовывает кнопку
// ======================================================
void DrawButton(LPDRAWITEMSTRUCT lpInfo)
{
   HBITMAP hbm;

   // Обрабатываем сообщение WM_DRAWITEM
   // только если оно поступило от кнопки 
   if(lpInfo->CtlType != ODT_BUTTON)
     return;

   hbm = TbMain::hbmpUp[(lpInfo->CtlID) - TbMain::nFirstId];

   // Если кнопка выбрана, рисуем ее в нажатом
   // состоянии
   if (lpInfo->itemState & ODS_SELECTED)
   {
     hbm = TbMain::hbmpDown[(lpInfo->CtlID) -
           TbMain::nFirstId];
   }

   // Если кнопка неактивна, загружаем идентификатор
   // изображения кнопки в неактивном состоянии
   else if (lpInfo->itemState & ODS_DISABLED)
   {
     hbm = TbMain::hbmpGrayed[(lpInfo->CtlID) -
           TbMain::nFirstId];
   }

   // При ошибке ничего не рисуем
   if(!hbm) return;

   // Если кнопка выбрана и ее надо целиком
   // перерисовать, вызываем функцию DrawBitmap
   if((lpInfo->itemAction & ODA_DRAWENTIRE) ||
      (lpInfo->itemAction & ODA_SELECT))
   {
     // Рисуем кнопку
     DrawBitmap(lpInfo->hDC,
       (lpInfo->rcItem).left,
       (lpInfo->rcItem).top , hbm);
   }
}

Конструктор класса Toolbar сбрасывает признак ошибки errno, инициализирует члены класса TbMain, определяет размеры внутренней области родительского окна, размеры дочернего окна, регистрирует класс дочернего окна и создает само дочернее окно.

Работа деструктор заключается в уничтожении дочернего окна, для чего используется функция DestroyWindow.

Функция дочернего окна ToolbarWndProc обрабатывает сообщение WM_COMMAND, поступающее от кнопок, и формирует это же сообщение для дочернего окна. Кроме того, эта функция обеспечивает работу кнопок, для чего в ней предусмотрены обработчик сообщения WM_DRAWITEM. Этот обработчик вызывает функцию DrawButton, предназначенную для рисования кнопок.

Функция DrawButton рисует кнопку в нужном виде, выбирая идентификаторы соответствующих изображений bitmap из массивов, определенных в классе TbMain. Для рисования изображения вызывается функция DrawBitmap (листинг 1.14). Мы уже пользовались этой функцией.


Листинг 1.14. Файл smartpad/drawbmp.cpp


// ======================================================
// Рисование изображения типа bitmap
// ======================================================

#define STRICT
#include <windows.h>

void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap)
{
  HBITMAP hbm, hOldbm;
  HDC hMemDC;
  BITMAP bm;
  POINT  ptSize, ptOrg;

  // Создаем контекст памяти, совместимый
  // с контекстом отображения
  hMemDC = CreateCompatibleDC(hDC);

  // Выбираем изображение bitmap в контекст памяти
  hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);

  // Если не было ошибок, продолжаем работу
  if (hOldbm)
  {
    // Для контекста памяти устанавливаем тот же
    // режим отображения, что используется в
    // контексте отображения
    SetMapMode(hMemDC, GetMapMode(hDC));

    // Определяем размеры изображения
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

    ptSize.x = bm.bmWidth;   // ширина
    ptSize.y = bm.bmHeight;  // высота

    // Преобразуем координаты устройства в логические
    // для устройства вывода
    DPtoLP(hDC, &ptSize, 1);

    ptOrg.x = 0;
    ptOrg.y = 0;

    // Преобразуем координаты устройства в логические
    // для контекста памяти
    DPtoLP(hMemDC, &ptOrg, 1);

    // Рисуем изображение bitmap
    BitBlt(hDC, x, y, ptSize.x, ptSize.y,
           hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);

    // Восстанавливаем контекст памяти
    SelectObject(hMemDC, hOldbm);
  }

  // Удаляем контекст памяти
  DeleteDC(hMemDC);
}

Файл определения модуля для приложения SMARTPAD приведен в листинге 1.15.


Листинг 1.15. Файл smartpad/smartpad.def


; =============================
; Файл определения модуля
; =============================
NAME        SMARTPAD
DESCRIPTION 'Приложение SMARTPAD, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   5120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

[Назад] [Содеожание] [Дальше]