Операционная система 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, выделенный текст будет оформлен жирным шрифтом. Если же выбрать эту строку без предварительного выделения текста, указанное оформление получат символы, введенные после выполнения этой операции. После повторного выбора строки оформление жирным шрифтом отменяется. Соответствующий обработчик вначале определяет текущее оформление символов, посылая окну органа управления сообщение 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 для определения форматирования, в поле dwMask будут записаны маски для тех полей, в которых были занесены полученные значения. В поле dwEffects может находиться комбинация
следующих значений (объединенных при помощи
логической операции ИЛИ):
Заметьте, что установив атрибут оформления CFE_PROTECTED, вы можете защитить часть текста от изменений со стороны пользователя, что может быть удобно при создании специализированных редакторов текста. Опишем кратко остальные поля структуры CHARFORMAT. Поле yHeight содержит высоту символов в логических единицах, соответствующих выбранному режиму отображения. Поле yOffset содержит смещение символов от базовой линии. Смещение может быть положительное (например, для надстрочных индексов) или отрицательное (для подстрочных индексов). В поле crTextColor заносится цвет символов. Подробное обсуждение структуры COLORREF и структуры LOGFONT вы сможете найти в 14 томе "Библиотеки системного программиста", который называется "Графический интерфейс GDI в Microsoft Windows". В поле bCharSet может находиться одно из значений,
которое определено для поля lfCharSet структуры LOGFONT :
С помощью поля bPitchAndFamily можно определить, используется ли фиксированная или переменна ширина символов. Кроме этого, можно определить семейство, к которому должен принадлежать полученный шрифт. Фиксированная или переменная ширина символов
задается при помощи следующих констант:
Вы можете объединить при помощи логической
операции ИЛИ эти константы со следующими
константами, соответствующими семейству шрифта:
Поле 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, которая только что была нами описана.
При выборе строки Italic из меню Format выполняется оформление с использованием наклонного начертания символов. При этом применяются только что описанные сообщения и структуры данных.
Эта строка меню выделяет символы подчеркиванием. Соответствующий обработчик аналогичен тому, что вызывается для предыдущих двух строк меню Format.
Замечательной особенностью органа управления 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); После установки нового шрифта обработчик освобождает полученный ранее контекст отображения.
Для изменения оформления параграфа наше приложение посылает окну органа управления 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 будут использованы для
установки формата параграфа. В этом поле могут
находиться следующие значения:
Для поля wNumbering вы можете указать нулевое значение или константу PFN_BULLET . Поле wAlignment структуры PARAFORMAT задает выравнивание
параграфа. Вы можете указать здесь следующие
значения:
Эта строка меню предназначена для установки выравнивания параграфа по правой границе. Примененный в нашем приложении способ выполнения выравнивания был только что описан.
Выравнивание по центру выполняется аналогично выравниванию по левой и правой границе окна редактирования.
Обработка сообщений от строк 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;
В некоторых случаях удобно выполнять какую-либо операцию со всем текстом сразу. Для того чтобы можно было выделить весь текст, органу управления 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.
Когда пользователь выбирает строку New из меню File, содержимое редактора удаляется простейшим способом - при помощи функции SetWindowText : SetWindowText(hwndEdit,"\0"); Отметим, что в нашем приложении не выполняется проверка, было ли предварительно выполнено сохранение редактируемого текста. При необходимости вы сможете доработать исходные тексты приложения, сделав его более "безопасным". Например, вы можете организовать обработку извещения EN_CHANGE , которое посылается, если пользователь изменяет содержимое редактируемого текста.
Обработчик сообщения от строки Open меню File вызывает функцию FileOpen, определенную в нашем приложении. Эта функция позволяет загружать для редактирования обычные текстовые файлы или файлы в формате RTF . Функция FileOpen будет описана немного позже.
Аналогично, при выборе пользователем строки Save as из меню File, вызывается функция FileSaveAs, которая позволяет сохранить файл в обычном текстовом формате или в формате RTF. Позже мы рассмотрим исходный текст этой функции.
Строка Print меню File позволяет пользователю распечатать содержимое редактируемого текста. Печать выполняется функцией FilePrint, определенной в нашем приложении. О ней мы расскажем позже.
С помощью строки Exit меню File пользователь может завершить работу приложения.
Эта строка выдает некоторую информацию о приложении 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 выводит текст целиком, однако при необходимости вы сможете организовать печать только выделенного фрагмента или текущего листа. Оставляем вам для упражнения реализацию этих возможностей. |