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

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

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

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

5.4. Приложение RtfPad

Приложение RtfPad представляет собой достаточно мощный текстовый редактор, способный работать с неформатированными текстовыми файлами и файлами в формате RTF. С его помощью вы можете преобразовывать текстовые файлы в RTF-файлы и обратно (разумеется, с потерей форматирования). На рис. 5.1 представлено главное окно приложения RtfPad.

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

С помощью строк Open и Save as меню File можно, соответственно, загружать для редактирования и сохранять файлы в текстовом формате и формате RTF. Строка Print этого же меню позволяет вывести редактируемый текст на печать, причем он будет напечатан с использованием шрифтового оформления и с учетом указанного оформления параграфов.

Меню Edit предназначено для выполнения стандартных операций с универсальным буфером обмена Clipboard , такие как копирование, вставка и перемещение. С помощью строки Undo вы можете отменить последнюю выполненную операцию.

С помощью меню Format (рис. 5.2) вы можете выделить символы жирным или наклонным начертанием, сделать их подчеркнутыми, выбрать любой установленный в системе шрифт. Вы можете также выбрать для текущего параграфа или для выделенных параграфов выравнивание по правой или левой границе окна, а также центрирование.

Рис. 5.2. Меню Format позволяет задать шрифтовое оформление символов, а также тип выравнивания для параграфа

Для того чтобы не загромождать исходные тексты приложения, мы не стали снабжать его такими органами управления, как Toolbar или Statusbar. При необходимости вы сможете сделать это самостоятельно, обратившись ко второй главе нашей книги. Кроме этого, мы не стремились задействовать максимально все возможности органа управления Rich Edit, так как их немало, а объем книги ограничен. Исходные тексты более сложного редактора текста вы сможете найти в SDK (приложение WritePad ).

Исходные тексты приложения RtfPad

Все функции приложения RtfPad определены в файле rtfpad.c (листинг 5.1).

Листинг 5.1. Файл rtfpad\rtfpad.c



#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"
#include "afxres.h"
#include "rtfpad.h"

HINSTANCE hInst;
char szAppName[]  = "RtfEditApp";
char szAppTitle[] = "Rich Text Editor RtfPad";
HWND hwndEdit;
HINSTANCE hRTFLib;

// -----------------------------------------------------
// Функция WinMain
// -----------------------------------------------------
int APIENTRY 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX wc;
  HWND hWnd;
  MSG msg;
  
  hInst = hInstance;

  // Преверяем, не было ли это приложение запущено ранее
  hWnd = FindWindow(szAppName, NULL);
  if(hWnd)
  {
    if(IsIconic(hWnd))
      ShowWindow(hWnd, SW_RESTORE);
    SetForegroundWindow(hWnd);
    return FALSE;
  }

  // Загружаем библиотеку RICHED32.DLL
  hRTFLib = LoadLibrary("RICHED32.DLL");
  if(!hRTFLib)
    return FALSE;

  // Регистрируем класс окна
  memset(&wc, 0, sizeof(wc));
  wc.cbSize = sizeof(WNDCLASSEX);
  wc.hIconSm = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0);
  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC)WndProc;
  wc.cbClsExtra  = 0;
  wc.cbWndExtra  = 0;
  wc.hInstance = hInst;
  wc.hIcon = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
  wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU);
  wc.lpszClassName = szAppName;
  if(!RegisterClassEx(&wc))
    if(!RegisterClass((LPWNDCLASS)&wc.style))
	  return FALSE;
    
  // Создаем главное окно приложения
  hWnd = CreateWindow(szAppName, szAppTitle,
   WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
   NULL, NULL, hInst, NULL);
  if(!hWnd) return(FALSE);

  // Отображаем окно и запускаем цикл обработки сообщений
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);
  while(GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// -----------------------------------------------------
// Функция WndProc
// -----------------------------------------------------
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
    HANDLE_MSG(hWnd, WM_CREATE,     WndProc_OnCreate);
    HANDLE_MSG(hWnd, WM_DESTROY,    WndProc_OnDestroy);
    HANDLE_MSG(hWnd, WM_COMMAND,    WndProc_OnCommand);
    HANDLE_MSG(hWnd, WM_SIZE,       WndProc_OnSize);
    HANDLE_MSG(hWnd, WM_SETFOCUS,   WndProc_OnSetFocus);

	default:
      return(DefWindowProc(hWnd, msg, wParam, lParam));
  }
}

// -----------------------------------------------------
// Функция WndProc_OnCreate
// -----------------------------------------------------
BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
  RECT rc;

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

  // Создаем орган управления Rich Edit
  hwndEdit = CreateWindowEx(0L, "RICHEDIT", "",
    WS_VISIBLE | WS_CHILD | WS_BORDER | 
    WS_HSCROLL | WS_VSCROLL |
    ES_NOHIDESEL | ES_AUTOVSCROLL | ES_MULTILINE | 
    ES_SAVESEL | ES_SUNKEN,
    0, 0, rc.right - rc.left, rc.bottom - rc.top,
    hWnd, (HMENU) IDC_RTFEDIT, hInst, NULL);

  if(hwndEdit == NULL)
    return FALSE;

  // Передаем фокус ввода органу управления Rich Edit
  SetFocus(hwndEdit);

  return TRUE;
}

// -----------------------------------------------------
// Функция WndProc_OnDestroy
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnDestroy(HWND hWnd)
{
  // Уничтожаем орган управления Rich Edit
  if(hwndEdit)
    DestroyWindow(hwndEdit);

  // Освобождаем библиотеку RICHED32.DLL
  if(hRTFLib)
    FreeLibrary(hRTFLib);

  PostQuitMessage(0);
  return 0L;
}

// -----------------------------------------------------
// Функция WndProc_OnCommand
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnCommand(HWND hWnd, int id, 
  HWND hwndCtl, UINT codeNotify)
{
  CHARFORMAT cf;
  CHOOSEFONT chfnt;
  LOGFONT lf;
  HDC hDC;
  PARAFORMAT pf; 

  switch (id)
  {
    // Изменяем жирность символов
    case ID_FORMAT_BOLD:
    {
      cf.cbSize = sizeof(cf);
      
      // Определяем формат символов
      SendMessage(hwndEdit,EM_GETCHARFORMAT,TRUE,(LPARAM)&cf);

      // Изменяем бит поля dwEffects, с помощью которого
      // можно выделить символы как bold (жирное начертание)
      cf.dwMask = CFM_BOLD;

      // Инвертируем бит, определяющий жирное начертание
      cf.dwEffects ^= CFE_BOLD;
               
      // Изменяем формат символов
      SendMessage(hwndEdit, EM_SETCHARFORMAT, 
        SCF_SELECTION, (LPARAM)&cf);

      return 0L;
      break;
    }

    // Устанавливаем или отменяем наклонное
    // начертание символов
    case ID_FORMAT_ITALIC:
    {
      cf.cbSize = sizeof(cf);
      SendMessage(hwndEdit, EM_GETCHARFORMAT, 
        TRUE, (LPARAM)&cf);

      cf.dwMask = CFM_ITALIC;
      cf.dwEffects ^= CFE_ITALIC;
      SendMessage(hwndEdit, EM_SETCHARFORMAT, 
        SCF_SELECTION, (LPARAM)&cf);

      return 0L;
      break;
    }

    // Устанавливаем или отменяем выделение
    // символов подчеркиванием
    case ID_FORMAT_UNDERLINE:
    {
      cf.cbSize = sizeof(cf);
      SendMessage(hwndEdit, EM_GETCHARFORMAT, 
        TRUE, (LPARAM)&cf);

      cf.dwMask = CFM_UNDERLINE;
      cf.dwEffects ^= CFE_UNDERLINE;
      SendMessage(hwndEdit, EM_SETCHARFORMAT, 
        SCF_SELECTION, (LPARAM)&cf);

      return 0L;
      break;
    }

    // Изменяем шрифт символов
    case ID_FORMAT_FONT:
    {
      cf.cbSize = sizeof(cf);
      
      // Определяем текущий формат символов
      SendMessage(hwndEdit, EM_GETCHARFORMAT, 
        TRUE, (LPARAM)&cf);

      // Сбрасываем содержимое структур, которые будут
      // использоваться для выбора шрифта
      memset(&chfnt, 0, sizeof(chfnt));
      memset(&lf, 0, sizeof(lf));

      // Получаем контекст отображения
      hDC = GetDC(hWnd);

      // Если было задано выделение наклоном или жирным
      // шрифтом,подбираем шрифт с соответствующими атрибутами
      lf.lfItalic    = (BOOL)(cf.dwEffects & CFE_ITALIC);
      lf.lfUnderline = (BOOL)(cf.dwEffects & CFE_UNDERLINE);

      // Преобразуем высоту из TWIPS-ов в пикселы.
      // Устанавливаем отрицательный знак, чтобы 
      // выполнялось преобразование и использовалось
      // абсолютное значение высоты символов
      lf.lfHeight = - cf.yHeight/20;

      // Набор символов, принятый по умолчанию
      lf.lfCharSet = ANSI_CHARSET;

      // Качество символов, принятое по умолчанию
      lf.lfQuality = DEFAULT_QUALITY;

      // Выбираем семейство шрифтов
      lf.lfPitchAndFamily = cf.bPitchAndFamily;

      // Название начертания шрифта
      lstrcpy(lf.lfFaceName, cf.szFaceName);

      // Устанавливаем вес шрифта в зависимости от того,
      // было использовано выделение жирным шрифтом 
      // или нет
      if(cf.dwEffects & CFE_BOLD)
        lf.lfWeight = FW_BOLD;
      else
        lf.lfWeight = FW_NORMAL;

      // Заполняем структуру для функции выбора шрифта
      chfnt.lStructSize = sizeof(chfnt);
      chfnt.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
      chfnt.hDC         = hDC;
      chfnt.hwndOwner   = hWnd;
      chfnt.lpLogFont   = &lf;
      chfnt.rgbColors   = RGB(0,0,0);
      chfnt.nFontType   = SCREEN_FONTTYPE;

      // Выводим на экран диалоговую панель для
      // выбора шрифта
      if(ChooseFont(&chfnt))
      {
        // Можно использовать все биты поля dwEffects
        cf.dwMask = CFM_BOLD | CFM_FACE | CFM_ITALIC |
          CFM_UNDERLINE | CFM_SIZE | CFM_OFFSET;

        // Преобразование в TWIPS-ы
        cf.yHeight = - lf.lfHeight * 20;

        // Устанавливаем поле dwEffects 
        cf.dwEffects = 0;
        if(lf.lfUnderline)
          cf.dwEffects |= CFE_UNDERLINE;

        if(lf.lfWeight == FW_BOLD)
          cf.dwEffects |= CFE_BOLD;

        if(lf.lfItalic)
          cf.dwEffects |= CFE_ITALIC;

        // Устанавливаем семейство шрифта
        cf.bPitchAndFamily = lf.lfPitchAndFamily;

        // Устанавливаем название начертания шрифта
        lstrcpy(cf.szFaceName, lf.lfFaceName);

        // Изменяем шрифтовое оформление символов
        SendMessage(hwndEdit, EM_SETCHARFORMAT, 
          SCF_SELECTION, (LPARAM)&cf);
      }
      
      // Освобождаем контекст отображения
      ReleaseDC(hWnd, hDC);

      return 0L;
      break;
    }

    // Устанавливаем выравнивание параграфа по левой границе
    // окна органа управления Rich Edit
    case ID_FORMAT_PARAGRAPH_LEFT:
    {
      pf.cbSize = sizeof(pf);
      pf.dwMask = PFM_ALIGNMENT;
      pf.wAlignment = PFA_LEFT;

      // Изменяем тип выравнивания текущего параграфа
      SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf);

      return 0L;
      break;
    }

    // Устанавливаем выравнивание параграфа по правой границе
    // окна органа управления Rich Edit
    case ID_FORMAT_PARAGRAPH_RIGHT:
    {
      pf.cbSize = sizeof(pf);
      pf.dwMask = PFM_ALIGNMENT;
      pf.wAlignment = PFA_RIGHT;
      SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf);
      return 0L;
      break;
    }

    // Выполняем центровку текущего параграфа
    case ID_FORMAT_PARAGRAPH_CENTER:
    {
      pf.cbSize = sizeof(pf);
      pf.dwMask = PFM_ALIGNMENT;
      pf.wAlignment = PFA_CENTER;
      SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf);
      return 0L;
      break;
    }

    // Реализуем стандартные функции меню Edit
    case ID_EDIT_UNDO:
      SendMessage(hwndEdit, EM_UNDO, 0, 0L);
      return 0L;
      break;

    case ID_EDIT_CUT:
      SendMessage(hwndEdit, WM_CUT, 0, 0L);
      return 0L;
      break;

    case ID_EDIT_COPY:
      SendMessage(hwndEdit, WM_COPY, 0, 0L);
      return 0L;
      break;

    case ID_EDIT_PASTE:
      SendMessage(hwndEdit, WM_PASTE, 0, 0L);
      return 0L;
      break;

    case ID_EDIT_DELETE:
      SendMessage(hwndEdit, WM_CLEAR, 0, 0L);
      return 0L;
      break;

    // Выделяем весь текст, который есть в окне
    // органа управления Rich Edit
    case ID_EDIT_SELECTALL:
    {
      CHARRANGE charr;

      charr.cpMin = 0;  // от начала... 
      charr.cpMax = -1; // ... и до конца текста
      
      SendMessage(hwndEdit, EM_EXSETSEL, 0, (LPARAM)&charr);
      return 0L;
      break;
    }

    // При создании нового текста удаляем текущее
    // содержимое окна редактирования
    case ID_FILE_NEW:
      SetWindowText(hwndEdit,"\0");
      return 0L;
      break;

    case ID_FILE_OPEN:
      FileOpen(hWnd);   // загружаем файл для редактирования
      return 0L;
      break;
    
    case ID_FILE_SAVEAS:
      FileSaveAs(hWnd); // сохраняем текст в файле
      return 0L;
      break;

    case ID_FILE_PRINT:
      FilePrint();      // печатаем текст
      return 0L;
      break;

    case ID_FILE_EXIT:  
      PostQuitMessage(0); // завершаем работу приложения
      return 0L;
      break;
      
      case ID_HELP_ABOUT:
      MessageBox(hWnd, 
        "Rich Text Editor RtfPad, v.1.0\n"
        "(C) Alexandr Frolov, 1995\n"
        "Email: frolov@glas.apc.org",
        szAppTitle, MB_OK | MB_ICONINFORMATION);
      return 0L;
      break;

      default:
      break;
  }
  return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify,
    DefWindowProc);
}

// -----------------------------------------------------
// Функция WndProc_OnSize       
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
  MoveWindow(hwndEdit, 0, 0, cx, cy, TRUE);
  return FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc);
}

// -----------------------------------------------------
// Функция WndProc_OnSetFocus
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnSetFocus(HWND hwnd, HWND hwndOldFocus)
{
  // Когда главное окно нашего приложения получает 
  // фокус ввода, оно передает фокус ввода окну
  // органа управления Rich Edit
  SetFocus(hwndEdit);
  return FORWARD_WM_SETFOCUS(hwnd, hwndOldFocus, 
    DefWindowProc);
}

// -----------------------------------------------------
// Функция FileSaveAs
// -----------------------------------------------------
void FileSaveAs(HWND hwnd)
{
  OPENFILENAME ofn;
  char szFile[256] = "untitled.rtf";
  char szDirName[512];
  char szFileTitle[256];
  
  // Фильтр допускает сохранение текста в файле с
  // расширением имени rtf, txt, или любым другим
  char szFilter[256] = 
    "Rich Text Files\0*.rtf\0Text Files\0*.txt\0"
    "Any Files\0*.*\0";

  HFILE      hFile;
  OFSTRUCT   of;
  EDITSTREAM es;

  memset(&ofn, 0, sizeof(OPENFILENAME));
    
  // Определяем путь к текущему каталогу
  GetCurrentDirectory(sizeof(szDirName), szDirName);
  
  // Заполняем структуру для выбора выходного файла
  ofn.lStructSize     = sizeof(OPENFILENAME);
  ofn.hwndOwner       = hwnd;
  ofn.lpstrFilter     = szFilter;
  ofn.lpstrInitialDir = szDirName;
  ofn.nFilterIndex    = 1;
  ofn.lpstrFile       = szFile;
  ofn.nMaxFile        = sizeof(szFile);
  ofn.lpstrFileTitle  = szFileTitle;
  ofn.nMaxFileTitle   = sizeof(szFileTitle);
  ofn.lpstrDefExt = "rtf";
  ofn.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;

  // Выводим на экран диалоговую панель, предназначенную
  // для выбора выходного файла
  if(GetSaveFileName(&ofn))
  {
    // Если файл выбран, открываем его для записи или
    // создаем
    if (*ofn.lpstrFile)
    {
      hFile = OpenFile(ofn.lpstrFile, &of, OF_CREATE);

      // Устанавливаем параметры функции обратного вызова,
      // которая будет выполнять запись
      es.dwCookie    = (DWORD)hFile; 
      es.dwError     = 0; 
      es.pfnCallback = SaveCallback; 
            
      // Если расширение файла rtf, файл сохраняется как
      // rtf-файл. В противном случае он сохраняется как
      // обычный текстовый файл
      _strupr(&ofn.lpstrFile[ofn.nFileExtension]); 
   
      if(!strncmp(&ofn.lpstrFile[ofn.nFileExtension],"RTF",3))
         SendMessage(hwndEdit,EM_STREAMOUT,SF_RTF,
           (LPARAM)&es);
      else
         SendMessage(hwndEdit,EM_STREAMOUT,SF_TEXT,
           (LPARAM)&es);

      // Закрываем файл
      _lclose(hFile);

      // Сбрасываем признак изменения содержимого окна
      // редактора текста
      SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0L);
    }
  }
}

// -----------------------------------------------------
// Функция SaveCallback
// -----------------------------------------------------
DWORD CALLBACK 
SaveCallback(DWORD dwCookie, LPBYTE pbBuff,
  LONG cb, LONG *pcb)
{
  // Выполняем запись блока данных длиной cb байт
  cb = _lwrite((HFILE)dwCookie, pbBuff, cb);
  *pcb = cb;
  return 0;
}

// -----------------------------------------------------
// Функция FileOpen
// -----------------------------------------------------
void FileOpen(HWND hwnd)
{
  OPENFILENAME ofn;
  char szFile[256];
  char szDirName[256];
  char szFileTitle[256];
  char szFilter[256] = 
    "Rich Text Files\0*.rtf\0Text Files\0*.txt\0"
    "Any Files\0*.*\0";

  HFILE      hFile;
  OFSTRUCT   of;
  EDITSTREAM es;

  memset(&ofn, 0, sizeof(OPENFILENAME));
  GetCurrentDirectory(sizeof(szDirName), szDirName);
  szFile[0] = '\0';

  // Подготавливаем структуру для выбора входного файла
  ofn.lStructSize     = sizeof(OPENFILENAME);
  ofn.hwndOwner       = hwnd;
  ofn.lpstrFilter     = szFilter;
  ofn.lpstrInitialDir = szDirName;
  ofn.nFilterIndex    = 1;
  ofn.lpstrFile       = szFile;
  ofn.nMaxFile        = sizeof(szFile);
  ofn.lpstrFileTitle  = szFileTitle;
  ofn.nMaxFileTitle   = sizeof(szFileTitle);
  ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

  // Выводим на экран диалоговую панель, предназначенную
  // для выбора входного файла
  if(GetOpenFileName(&ofn))
  {
    // Если файл выбран, открываем его для чтения
    if (*ofn.lpstrFile)
    {
      hFile = OpenFile(ofn.lpstrFile, &of, OF_READ);
      
      // Устанавливаем параметры функции обратного вызова,
      // которая будет выполнять чтение
      es.dwCookie    = (DWORD)hFile; 
      es.dwError     = 0; 
      es.pfnCallback = OpenCallback; 

      // Если расширение файла rtf, файл загружается как
      // rtf-файл. В противном случае он загружается как
      // обычный текстовый файл
      _strupr(&ofn.lpstrFile[ofn.nFileExtension]); 
      if(!strncmp(&ofn.lpstrFile[ofn.nFileExtension],"RTF",3))
        SendMessage(hwndEdit,EM_STREAMIN,SF_RTF,(LPARAM)&es);
      else
        SendMessage(hwndEdit,EM_STREAMIN,SF_TEXT,(LPARAM)&es);

      // Закрываем файл
      _lclose(hFile);

      // Сбрасываем признак изменения содержимого окна
      // редактора текста
      SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0L);
    }
  }
}

// -----------------------------------------------------
// Функция OpenCallback
// -----------------------------------------------------
DWORD CALLBACK 
OpenCallback(DWORD dwCookie, LPBYTE pbBuff,
  LONG cb, LONG *pcb)
{
  // Выполняем чтение блока данных длиной cb байт
  *pcb = _lread((HFILE)dwCookie, pbBuff, cb);
  if(*pcb <= 0)
    *pcb = 0;
  return 0;
}

// -----------------------------------------------------
// Функция FilePrint
// -----------------------------------------------------
void FilePrint(void)
{
  FORMATRANGE fr;
  DOCINFO docInfo;
  LONG lLastChar, lTextSize;
  PRINTDLG pd;
  int nRc;
  HDC hPrintDC;

  // Инициализируем поля структуры PRITDLG
  memset(&pd, 0, sizeof(pd));
  pd.lStructSize = sizeof(PRINTDLG);
  pd.hwndOwner   = hwndEdit;
  pd.hInstance   = (HANDLE)hInst;
  pd.Flags       = PD_RETURNDC | PD_NOPAGENUMS | 
    PD_NOSELECTION | PD_PRINTSETUP | PD_ALLPAGES;
  pd.nFromPage   = 0xffff;
  pd.nToPage     = 0xffff;
  pd.nMinPage    = 0;
  pd.nMaxPage    = 0xffff;
  pd.nCopies     = 1;

  // Выводим на экран диалоговую панель, предназначенную
  // для печати документа
  if(PrintDlg(&pd) == TRUE)
  {
    hPrintDC = pd.hDC;

    // Инициализируем поля структуры FORMATRANGE 
    memset(&fr, 0, sizeof(fr));
  
    // Будем печатать с использованием контекста 
    // принтера, полученного от функции PrintDlg
    fr.hdc = fr.hdcTarget = hPrintDC;

    // Печатаем весь документ
    fr.chrg.cpMin = 0;     
    fr.chrg.cpMax = -1;
  
    // Устанавливаем размеры страницы в TWIPS-ах
    fr.rcPage.top = 0;
    fr.rcPage.left = 0;
    fr.rcPage.right = 
      MulDiv(GetDeviceCaps(hPrintDC, PHYSICALWIDTH), 
      1440, GetDeviceCaps(hPrintDC, LOGPIXELSX));

    fr.rcPage.bottom = MulDiv(GetDeviceCaps(hPrintDC,
      PHYSICALHEIGHT),1440,
      GetDeviceCaps(hPrintDC, LOGPIXELSY));
    fr.rc = fr.rcPage;

    // Оставляем поля
    if(fr.rcPage.right > 2*3*1440/4+1440)
      fr.rc.right -= (fr.rc.left = 3*1440/4);
    if(fr.rcPage.bottom > 3*1440)
      fr.rc.bottom -= (fr.rc.top = 1440);
    
    // Заполняем поля структуры DOCINFO
    memset(&docInfo, 0, sizeof(DOCINFO));
    docInfo.cbSize = sizeof(DOCINFO);
    docInfo.lpszOutput = NULL;
    docInfo.lpszDocName = "RtfPad document";


    // Начинаем печать документа
    nRc = StartDoc(hPrintDC, &docInfo);
    
    // Если произошла ошибка, получаем и выводим на экран
    // код ошибки
    if(nRc < 0)
    {
      char szErr[128];
      DWORD dwErr = GetLastError();
      wsprintf(szErr, "Print Error %ld", dwErr);

      MessageBox(NULL, szErr,
        szAppTitle, MB_OK | MB_ICONEXCLAMATION);
      
      DeleteDC(hPrintDC);
      return;
    }

    // Начинаем печать страницы
    StartPage(hPrintDC);

    lLastChar = 0;

    // Определяем длину текста в байтах
    lTextSize = SendMessage(hwndEdit, WM_GETTEXTLENGTH, 0, 0);

    // Цикл по всем страницам документа
    while (lLastChar < lTextSize)
    {
      // Форматируем данные для принтера и печатаем их
      lLastChar = SendMessage(hwndEdit, EM_FORMATRANGE, TRUE, 
        (LPARAM) &fr);
        
      if(lLastChar < lTextSize)
      {
        // Завершаем печать очередной страницы
        EndPage(hPrintDC);
        
        // Начинаем новую страницу
        StartPage(hPrintDC);    
        fr.chrg.cpMin = lLastChar;
        fr.chrg.cpMax = -1;
      }
    }

    // Удаляем информацию, которая хранится в 
    // органе управления Rich Edit
    SendMessage(hwndEdit, EM_FORMATRANGE, TRUE, (LPARAM)NULL);

    // Завершаем печать страницы
    EndPage(hPrintDC);

    // Завершаем печать документа
    EndDoc(hPrintDC);

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

Файл rtfpad.h (листинг 5.2) содержит описание функций и определение константы IDC_RTFEDIT (идентификатор органа управления Rich Edit).

Листинг 5.2. Файл rtfpad\rtfpad.h


// -----------------------------------------------------
// Описание функций
// -----------------------------------------------------
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL WndProc_OnCreate(HWND hWnd, 
  LPCREATESTRUCT lpCreateStruct);
void WndProc_OnDestroy(HWND hWnd);
void WndProc_OnCommand(HWND hWnd, int id, 
  HWND hwndCtl, UINT codeNotify);
LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, 
  NMHDR FAR * pnmhdr);
void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy);
void WndProc_OnSetFocus(HWND hwnd, HWND hwndOldFocus);
void FileSaveAs(HWND hwnd);
DWORD CALLBACK SaveCallback(DWORD dwCookie, LPBYTE   pbBuff,
  LONG cb, LONG *pcb);
void FileOpen(HWND hwnd);
DWORD CALLBACK 
OpenCallback(DWORD dwCookie, LPBYTE   pbBuff,
  LONG cb, LONG *pcb);
void FilePrint(void);
#define IDC_RTFEDIT 1236

В файле resource.h (который создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения RtfPad. Этот файл представлен в листинге 5.3.

Листинг 5.3. Файл rtfpad\resource.h


//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by RTFPAD.RC
//
#define IDR_APPMENU                     102
#define IDI_APPICON                     103
#define IDI_APPICONSM                   104
#define ID_FILE_EXIT                    40001
#define ID_HELP_ABOUT                   40003
#define ID_FORMAT_BOLD                  40010
#define ID_FORMAT_ITALIC                40011
#define ID_FORMAT_UNDERLINE             40012
#define ID_FORMAT_FONT                  40013
#define ID_FORMAT_PARAGRAPH_LEFT        40014
#define ID_FORMAT_PARAGRAPH_RIGHT       40015
#define ID_FORMAT_PARAGRAPH_CENTER      40016
#define ID_EDIT_DELETE                  40021
#define ID_FILE_SAVEAS                  40024
#define ID_EDIT_SELECTALL               40028

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        121
#define _APS_NEXT_COMMAND_VALUE         40029
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Файл rtfpad.rc (листинг 5.4) содержит определение ресурсов приложения RtfPad. Он создается автоматически.

Листинг 5.4. Файл rtfpad\rtfpad.rc


//Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
//////////////////////////////////////////////////////////////
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

//////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

//////////////////////////////////////////////////////////////
// Menu
//
IDR_APPMENU MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&New",                        ID_FILE_NEW
        MENUITEM SEPARATOR
        MENUITEM "&Open...",                    ID_FILE_OPEN
        MENUITEM "&Save as...",                 ID_FILE_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "&Print...",                   ID_FILE_PRINT
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       ID_FILE_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo",                       ID_EDIT_UNDO
        MENUITEM SEPARATOR
        MENUITEM "Cu&t",                        ID_EDIT_CUT
        MENUITEM "&Copy",                       ID_EDIT_COPY
        MENUITEM "&Paste",                      ID_EDIT_PASTE
        MENUITEM "&Delete",                     ID_EDIT_DELETE
        MENUITEM SEPARATOR
        MENUITEM "&Select All",            ID_EDIT_SELECTALL
    END
    POPUP "&Format"
    BEGIN
        MENUITEM "&Bold",                 ID_FORMAT_BOLD
        MENUITEM "&Italic",               ID_FORMAT_ITALIC
        MENUITEM "&Underline",            ID_FORMAT_UNDERLINE
        MENUITEM SEPARATOR
        MENUITEM "&Font...",              ID_FORMAT_FONT
        MENUITEM SEPARATOR
        POPUP "&Paragraph"
        BEGIN
            MENUITEM "&Left",       ID_FORMAT_PARAGRAPH_LEFT
            MENUITEM "&Right",      ID_FORMAT_PARAGRAPH_RIGHT
            MENUITEM "&Center",     ID_FORMAT_PARAGRAPH_CENTER
        END
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About...",                   ID_HELP_ABOUT
    END
END

#ifdef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "\r\n"
    "\0"
END

//////////////////////////////////////////////////////////////
#endif    // APSTUDIO_INVOKED

//////////////////////////////////////////////////////////////
// Icon
//

IDI_APPICON             ICON    DISCARDABLE     "rtfpad.ico"
IDI_APPICONSM           ICON    DISCARDABLE     "rtfpadsm.ico"

//////////////////////////////////////////////////////////////
// String Table
//

STRINGTABLE DISCARDABLE 
BEGIN
    ID_FILE_EXIT            "Quits the application"
END

#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

Описание функций

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

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

В переменной hInst хранится идентификатор приложения, полученный функцией WinMain. Строчные массивы szAppName и szAppTitle хранят, соответственно, имя и заголовок приложения.

Переменная hwndEdit используется для хранения идентификатора созданного органа управления Rich Edit.

Для инициализации DLL-библиотеки, отвечающей за работу органа управления Rich Edit, мы используем переменную hRTFLib (в нее записывается идентификатор загруженной библиотеки RICHED32.DLL).

WinMain

Функция WinMain не имеет никаких особенностей, за исключением того что в ней выполняется явная загрузка библиотеки RICHED32.DLL . Для этого мы используем функцию LoadLibrary :

hRTFLib = LoadLibrary("RICHED32.DLL");
  if(!hRTFLib)
    return FALSE;

WndProc

Функция WndProc обрабатывает следующие сообщения: WM_CREATE, WM_DESTROY, WM_COMMAND, WM_SIZE и WM_SETFOCUS. Обработка выполняется с использованием макрокоманды HANDLE_MSG.

WndProc_OnCreate

Обработчик сообщения WM_CREATE создает орган управления Rich Edit. Размеры окна органа управления устанавливаются равными размерам внутренней области главного окна приложения и в дальнейшем изменяются обработчиком сообщения WM_SIZE.

После создания окно органа управления Rich Edit получает фокус ввода, для чего вызывается функция SetFocus , описанная нами в 12 томе "Библиотеки системного программиста":

SetFocus(hwndEdit);

WndProc_OnDestroy

Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она уничтожает окно органа управления Rich Edit и освобождает загруженную при инициализации приложения библиотеку RICHED32.DLL, вызывая функцию FreeLibrary :

if(hRTFLib)
  FreeLibrary(hRTFLib);

Затем функция WndProc_OnDestroy останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.

WndProc_OnCommand

Функция WndProc_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения. Рассмотрим процедуры обработки сообщений для каждой строки меню. Название строки мы будем отделять от названия меню символом "/".

  • Format/Bold

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

После повторного выбора строки оформление жирным шрифтом отменяется.

Соответствующий обработчик вначале определяет текущее оформление символов, посылая окну органа управления сообщение EM_GETCHARFORMAT :

cf.cbSize = sizeof(cf);
SendMessage(hwndEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf);

Ниже приведены параметры этого сообщения:

wParam = (WPARAM)(BOOL)fSelection; 
lParam = (LPARAM)(CHARFORMAT FAR *)lpFmt;

Параметр fSelection может принимать значения TRUE или FALSE. В первом случае будет определено оформление символов, принятое по умолчанию, во втором - оформление для выделенного текста.

Параметр lpFmt должен содержать указатель на структуру типа CHARFORMAT , в которую будут записаны атрибуты форматирования. Эта структура имеет следующий формат:

typedef struct _charformat
{ 
  UINT     cbSize;            // размер структуры в байтах
  _WPAD    _wPad1;            // зарезервировано
  DWORD    dwMask;            // маски полей атрибутов
  DWORD  dwEffects;// эффекты, использованные при оформлении
  LONG     yHeight;           // высота символов
  LONG     yOffset;           // смещение от базовой линии
  COLORREF crTextColor;       // цвет текста
  BYTE     bCharSet;          // набор символов
  BYTE     bPitchAndFamily;         // семейство шрифтов
  TCHAR    szFaceName[LF_FACESIZE]; // название шрифта
  _WPAD    _wPad2;                  // зарезервировано
} CHARFORMAT;

Перед использованием структуры CHARFORMAT в поле cbSize следует записать размер структуры, как мы это сделали в приведенном выше примере.

Если структура CHARFORMAT используется для установки формата, в поле dwMask следует записать маски, соответствующие устанавливаемым атрибутам оформления (сведения об этих атрибутах будут записаны в поле dwEffects, рассмотренное ниже, и в другие поля структуры CHARFORMAT).

Значение маски Поля структуры CHARFORMAT
CFM_BOLD Значение CFE_BOLD поля dwEffects
CFM_COLOR Поле crTextColor и значение CFE_AUTOCOLOR в поле dwEffects
CFM_FACE Поле szFaceName
CFM_ITALIC Значение CFE_ITALIC поля dwEffects
CFM_OFFSET Поле yOffset
CFM_PROTECTED Значение CFE_PROTECTED поля dwEffects
CFM_SIZE Поле yHeight
CFM_STRIKEOUT Значение CFE_STRIKEOUT поля dwEffects
CFM_UNDERLINE . Значение CFE_UNDERLINE поля dwEffects

Если же вы применяете структуру CHARFORMAT для определения форматирования, в поле dwMask будут записаны маски для тех полей, в которых были занесены полученные значения.

В поле dwEffects может находиться комбинация следующих значений (объединенных при помощи логической операции ИЛИ):

Значение Описание
CFE_AUTOCOLOR Для отображения текста используется системный цвет COLOR_WINDOWTEXT
CFE_BOLD Символы выделены жирным шрифтом (bold)
CFE_ITALIC Символы выделены наклоном (italic)
CFE_STRIKEOUT Символы перечеркнуты
CFE_UNDERLINE Символы выделены подчеркиванием
CFE_PROTECTED Данная группа символов защищена от изменения. Если пользователь пытается их изменить, родительское окно получит извещение с кодом EN_PROTECTED

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

Опишем кратко остальные поля структуры CHARFORMAT.

Поле yHeight содержит высоту символов в логических единицах, соответствующих выбранному режиму отображения.

Поле yOffset содержит смещение символов от базовой линии. Смещение может быть положительное (например, для надстрочных индексов) или отрицательное (для подстрочных индексов).

В поле crTextColor заносится цвет символов. Подробное обсуждение структуры COLORREF и структуры LOGFONT вы сможете найти в 14 томе "Библиотеки системного программиста", который называется "Графический интерфейс GDI в Microsoft Windows".

В поле bCharSet может находиться одно из значений, которое определено для поля lfCharSet структуры LOGFONT :

Константа Описание
ANSI_CHARSET Набор символов в кодировке ANSI
DEFAULT_CHARSET Не используется при отображении шрифтов. Определяется при необходимости запросить шрифт с заданным именем и размером шрифта. Следует использовать с осторожностью, так как если указанного шрифта нет, GDI может выделить шрифт с любым набором символов
SYMBOL_CHARSET Символьный шрифт, такой как, например, Wingdings
SHIFTJIS_CHARSET Шрифт, в котором для представления символов используется двухбайтовая кодировка. Нужен для работы с японской версией Windows
OEM_CHARSET Набор символов в кодировке OEM

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

Фиксированная или переменная ширина символов задается при помощи следующих констант:

Константа Описание
DEFAULT_PITCH Не имеет значения, будет ли шрифт иметь фиксированную или переменную ширину символов
FIXED_PITCH Нужен шрифт с фиксированной шириной символов
VARIABLE_PITCH Нужен шрифт с переменной шириной символов

Вы можете объединить при помощи логической операции ИЛИ эти константы со следующими константами, соответствующими семейству шрифта:

Константа Описание
FF_DECORATIVE Шрифт, содержащий маленькие рисунки (пиктограммы). Примером такого шрифта может послужить шрифт Wingdings, поставляемый в составе Windows
FF_DONTCARE Семейство шрифта не имеет значения
FF_MODERN Семейство Modern. Фиксированная ширина символов, могут быть засечки (но могут и не быть)
FF_ROMAN Семейство Roman. Переменная ширина букв, есть засечки
FF_SCRIPT Семейство Script. Рукописный шрифт
FF_SWISS Семейство Swiss. Переменная ширина букв, нет засечек

Поле szFaceName содержит строку, закрытую двоичным нулем, которая служит названием внешнего вида шрифта. Размер строки (включая закрывающий строку нуль) не должен превышать LF_FACESIZE байт.

В нашем приложении после определения текущего оформления обработчик сообщения, поступающего от строки Bold меню Format, изменяет на противоположное содержимое бита CFE_BOLD в поле dwEffects структуры CHARFORMAT. Перед этим мы устанавливаем маску CFM_BOLD в поле dwMask этой же структуры:

cf.dwMask = CFM_BOLD;
cf.dwEffects ^= CFE_BOLD;
SendMessage(hwndEdit,EM_SETCHARFORMAT,SCF_SELECTION,
  (LPARAM)&cf);

Установка формата выполняется с помощью сообщения EM_SETCHARFORMAT. Это сообщение имеет следующие параметры:

wParam = (WPARAM)(UINT)uFlags; 
lParam = (LPARAM)(CHARFORMAT FAR *)lpFmt;

Параметр uFlags может принимать значения SCF_SELECTION или (SCF_SELECTION | SCF_WORD). В первом случае будет установлен формат выделенного фрагмента текста или формат, используемый по умолчанию (если выделение отсутствует). Если же дополнительно указано значение SCF_WORD, будет изменен формат слова, в позиции которого находится курсор, или формат выделенной группы слов.

Параметр lpFmt должен указывать на предварительно подготовленную структуру типа CHARFORMAT, которая только что была нами описана.

  • Format/Italic

При выборе строки Italic из меню Format выполняется оформление с использованием наклонного начертания символов. При этом применяются только что описанные сообщения и структуры данных.

  • Format/Underline

Эта строка меню выделяет символы подчеркиванием. Соответствующий обработчик аналогичен тому, что вызывается для предыдущих двух строк меню Format.

  • Format/Font

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

Вначале соответствующий обработчик определяет текущее оформление символов, посылая органу управления Rich Edit сообщение EM_GETCHARFORMAT.

Затем определяется текущий контекст отображения и заполняется структура chfnt типа CHOOSEFONT, которая нужна для функции вызова стандартной диалоговой панели ChooseFont. Эта функция была описана в 14 томе "Библиотеки системного программиста", поэтому для экономии места мы не будем к ней возвращаться.

После выбора шрифта его атрибуты копируются в структуру cf типа CHARFORMAT. Адрес этой структуры передается в качестве последнего параметра функции SendMessage, посылающей окну органа управления Rich Edit сообщение EM_SETCHARFORMAT :

SendMessage(hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, 
  (LPARAM)&cf);

После установки нового шрифта обработчик освобождает полученный ранее контекст отображения.

  • Format/Paragraph/Left

Для изменения оформления параграфа наше приложение посылает окну органа управления Rich Edit сообщение EM_SETPARAFORMAT :

pf.cbSize = sizeof(pf);
pf.dwMask = PFM_ALIGNMENT;
pf.wAlignment = PFA_LEFT;
SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf);

Параметр wParam этого сообщения не используется и должен быть равен нулю. Через параметр lParam передается адрес предварительно заполненной структуры типа PARAFORMAT:

lParam = (LPARAM)(PARAFORMAT FAR *)lpFmt;

Структура PARAFORMAT определена следующим образом:

typedef struct _paraformat 
{ 
    UINT cbSize; // размер структуры в байтах
    _WPAD _wPad1;                   // зарезервировано
    DWORD dwMask;                   // поле масок
    WORD  wNumbering;               // порядок нумерации
    WORD  wReserved;                // зарезервировано
    LONG  dxStartIndent; // отступ для первой строки параграфа
    LONG  dxRightIndent; // отступ от правой границы листа
    LONG  dxOffset;      // отступ второй и следующих 
                         // строк параграфа
    WORD  wAlignment;            // выравнивание параграфа
    SHORT cTabCount; // количество символов табуляции
    LONG  rgxTabs[MAX_TAB_STOPS];// массив абсолютных позиций
                                 // для символов табуляции
} PARAFORMAT;

Поле маски dwMask определяет, какие из остальных полей структуры PARAFORMAT будут использованы для установки формата параграфа. В этом поле могут находиться следующие значения:

Значение маски Поля структуры PARAFORMAT
PFM_ALIGNMENT wAlignment
PFM_NUMBERING wNumbering
PFM_OFFSET dxOffset
PFM_OFFSETINDENT dxStartIndent
PFM_RIGHTINDENT dxRightIndent
PFM_STARTINDENT dxStartIndent
PFM_TABSTOPS cTabStobs и rgxTabStops

Для поля wNumbering вы можете указать нулевое значение или константу PFN_BULLET .

Поле wAlignment структуры PARAFORMAT задает выравнивание параграфа. Вы можете указать здесь следующие значения:

Значение Тип выравнивания параграфа
PFA_LEFT По левой границе окна редактирования
PFA_RIGHT По правой границе окна редактирования
PFA_CENTER Центрирование
  • Format/Paragraph/Right

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

  • Format/Paragraph/Center

Выравнивание по центру выполняется аналогично выравниванию по левой и правой границе окна редактирования.

  • Edit/Undo
  • Edit/Cut
  • Edit/Copy
  • Edit/Paste
  • Edit/Delete

Обработка сообщений от строк Undo, Cut, Copy, Paste и Delete выполняется очень просто. Органу управления Rich Edit посылается соответствующее сообщение: EM_UNDO , WM_CUT , WM_COPY , WM_PASTE или WM_CLEAR . Например:

case ID_EDIT_UNDO:
  SendMessage(hwndEdit, EM_UNDO, 0, 0L);
  return 0L;
  break;
  • Edit/Select all

В некоторых случаях удобно выполнять какую-либо операцию со всем текстом сразу. Для того чтобы можно было выделить весь текст, органу управления Rich Text посылается сообщение EM_EXSETSEL :

CHARRANGE charr;
charr.cpMin = 0;  // от начала... 
charr.cpMax = -1; // ... и до конца текста
SendMessage(hwndEdit, EM_EXSETSEL, 0, (LPARAM)&charr);

Через параметр lParam этого сообщения необходимо передать адрес заполненной структуры типа CHARRANGE. Формат этой структуры приведен ниже:

typedef struct _charrange 
{ 
  LONG cpMin; // номер первого выделяемого символа
  LONG cpMax; // номер последнего выделяемого символа
} CHARRANGE;

Для того чтобы выделить весь текст, в поле cpMin необходимо записать нулевое значение, а в поле cpMax - значение -1.

  • File/New

Когда пользователь выбирает строку New из меню File, содержимое редактора удаляется простейшим способом - при помощи функции SetWindowText :

SetWindowText(hwndEdit,"\0");

Отметим, что в нашем приложении не выполняется проверка, было ли предварительно выполнено сохранение редактируемого текста. При необходимости вы сможете доработать исходные тексты приложения, сделав его более "безопасным". Например, вы можете организовать обработку извещения EN_CHANGE , которое посылается, если пользователь изменяет содержимое редактируемого текста.

  • File/Open

Обработчик сообщения от строки Open меню File вызывает функцию FileOpen, определенную в нашем приложении. Эта функция позволяет загружать для редактирования обычные текстовые файлы или файлы в формате RTF .

Функция FileOpen будет описана немного позже.

  • File/Save as

Аналогично, при выборе пользователем строки Save as из меню File, вызывается функция FileSaveAs, которая позволяет сохранить файл в обычном текстовом формате или в формате RTF. Позже мы рассмотрим исходный текст этой функции.

  • File/Print

Строка Print меню File позволяет пользователю распечатать содержимое редактируемого текста. Печать выполняется функцией FilePrint, определенной в нашем приложении. О ней мы расскажем позже.

  • File/Exit

С помощью строки Exit меню File пользователь может завершить работу приложения.

  • Help/About

Эта строка выдает некоторую информацию о приложении RtfPad.

WndProc_OnSize

Приложение изменяет размеры органа управления Rich Edit таким образом, чтобы они всегда соответствовали размерам внутренней области главного окна приложения. Для этого оно обрабатывает сообщение WM_SIZE , изменяя размеры окна органа управления при помощи функции MoveWindow.

WndProc_OnSetFocus

Когда главное окно приложения RtfPad получает фокус ввода, она передает его органу управления Rich Edit, вызывая для этого функцию SetFocus :

SetFocus(hwndEdit);

FileSaveAs

Когда пользователь сохраняет редактируемый текст в файле, приложение вызывает функцию FileSaveAs. Эта функция выводит на экран стандартную диалоговую панель Save as, вызывая для этого функцию GetSaveFileName . Функцией GetSaveFileName мы уже пользовались, например, в приложении TEDIT, описанном в 12 томе "Библиотеки системного программиста".

Когда пользователь выберет файл, путь к нему будет записан в переменную ofn.lpstrFile. Затем этот файл будет открыт для записи функцией OpenFile . Хотя в Microsoft Windows 95 и в Microsoft Windows NT существуют специальные средства для работы с файлами, пока мы используем этот хорошо знакомый вам способ, который тоже работает.

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

Инициирование процесса записи выполняется посылкой сообщения EM_STREAMOUT , причем в зависимости от нужного формата выходного файла (текстовый формат или формат RTF) для этого сообщения указывается разное значение параметра wParam:

_strupr(&ofn.lpstrFile[ofn.nFileExtension]); 
if(!strncmp(&ofn.lpstrFile[ofn.nFileExtension], "RTF", 3))
  SendMessage(hwndEdit, EM_STREAMOUT, SF_RTF, (LPARAM)&es);
else
  SendMessage(hwndEdit, EM_STREAMOUT, SF_TEXT, (LPARAM)&es);

В любом случае через параметр lParam необходимо передать адрес предварительно подготовленной структуры типа EDITSTREAM , которая определена следующим образом:

typedef struct _editstream 
{ 
  DWORD dwCookie; 
  DWORD dwError; 
  EDITSTREAMCALLBACK pfnCallback; 
} EDITSTREAM;

Через параметр dwCookie можно передать функции обратного вызова любое 32-разрядное значение. Так как в нашем случае функция обратного вызова будет выполнять запись в файл, через этот параметр мы передаем идентификатор открытого файла:

es.dwCookie    = (DWORD)hFile; 
es.dwError     = 0; 
es.pfnCallback = SaveCallback; 

Через поле dwError передается код ошибки, поэтому перед началом процесса записи мы записываем в него нулевое значение.

И, наконец, через поле pfnCallback необходимо передать адрес функции обратного вызова, которая будет выполнять запись данных в файл. Наша функция называется SaveCallback; вы можете выбрать любое другое имя. Функция обратного вызова SaveCallback будет описана ниже.

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

Дополнительно мы сбрасываем признак модификации редактируемого файла, так как все изменения были только что сохранены. Для этого мы посылаем органу управления Rich Edit сообщение EM_SETMODIFY :

SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0L);

Если параметр wParam этого сообщения имеет значение FALSE, флаг модификации сбрасывается, если TRUE - устанавливается.

В любой момент времени вы можете определить значение флага модификации, послав органу управления Rich Edit сообщение EM_GETMODIFY . Если текст был изменен, функция SendMessage вернет значение TRUE, если нет - FALSE.

SaveCallback

Функция обратного вызова SaveCallback, которая используется для сохранения текста в файле, имеет следующий прототип:

DWORD CALLBACK 
SaveCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, 
  LONG *pcb);

Через первый параметр этой функции передается значение, которое было записано в поле dwCookie структуры EDITSTREAM, подготовленной для сообщения EM_STREAMOUT.

Через параметр pbBuff передается адрес буфера, откуда необходимо взять данные для записи в файл. Размер этого буфера указывается параметром cb. Количество действительно записанных байт следует записать по адресу, который передается через последний параметр pcb.

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

FileOpen

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

Выбор файла выполняется с помощью стандартной диалоговой панели Open, которая отображается на экране функцией GetOpenFileName . Эта функция использовалась нами в 12 томе "Библиотеки системного программиста".

После того как файл выбран, он открывается функцией OpenFile для чтения. Далее мы подготавливаем структуру типа EDITSTREAM для функции обратного вызова OpenCallback, которая будет выполнять чтение файла.

Затем, в зависимости от расширения имени выбранного файла, мы загружаем этот файл как текстовый или как файл, имеющий формат RTF. Для загрузки органу управления Rich Edit посылается сообщение EM_STREAMIN .

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

OpenCallback

Функция обратного вызова OpenCallback выполняет чтение файла в память органа управления Rich Edit. Прототип этой функции уже был описан, однако теперь назначение некоторых ее параметров и возвращаемое значение изменились.

Через параметр dwCookie передается идентификатор файла, из которого будет выполняться чтение. Чтение данных необходимо выполнять в буфер pbBuff, размер которого передается через параметр cb. По адресу pcb необходимо записать количество действительно прочитанных байт или 0, если достигнут конец файла.

Функция обратного вызова должна возвратить количество прочитанных байт или 0, если был достигнут конец файла. Так как мы читаем весь файл за один вызов функции _lread, то функция возвращает нулевое значение.

FilePrint

Функция FilePrint, как видно из ее названия, выполняет печать файла. При этом в приложении RtfPad используется упрощенный вариант технологии, описанной нами в 14 томе "Библиотеки системного программиста".

Функция выводит на экран стандартную диалоговую панель Print, вызывая для этого функцию PrintDlg . Кроме всего прочего, эта функция получает контекст устройства для выбранного принтера, который сохраняется в переменной hPrintDC.

Как вы знаете, процесс печати из приложений Microsoft Windows полностью отличается от того, к чему вы, возможно, привыкли в DOS. Печать выполняется теми же функциями GDI , с помощью которых вы рисуете изображение на экране видеомонитора.

Орган управления Rich Edit способен самостоятельно выполнять печать, если известен контекст устройства печати (принтера). Для этого ему достаточно переслать сообщение EM_FORMATRANGE . Это сообщение имеет следующие параметры:

wParam = (WPARAM)(BOOL)fRender; 
lParam = (LPARAM)(FORMATRANGE FAR *)lpFmt;

Если значение параметра fRender равно TRUE, орган управления Rich Edit выполняет форматирование текста и его печать, если же FALSE - выполняется только форматирование с целью определения геометрических размеров, которые будут получены при печати.

Через параметр lpFmt должен передаваться указатель на предварительно подготовленную структуру типа FORMATRANGE , определенную следующим образом:

typedef struct _formatrange 
{ 
  HDC hdc; 
  HDC hdcTarget; 
  RECT rc; 
  RECT rcPage; 
  CHARRANGE chrg; 
} FORMATRANGE;

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

В структуру rc следует записать размеры области, в которую будет выполняться вывод, а в структуру rcPage - размеры листа бумаги. Наше приложение оставляет небольшие поля по краям листа. Размеры должны быть указаны в TWIPS -ах (один TWIPS составляет 1/1440 часть дюйма).

Структура chrg описывает фрагмент текста, который должен быть выведен на печать. Приложение RtfPad выводит текст целиком, однако при необходимости вы сможете организовать печать только выделенного фрагмента или текущего листа. Оставляем вам для упражнения реализацию этих возможностей.

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