    Работа с текстом и решение проблемы перерисовки 

    До сих пор вывод на экран осуществлялся через окна сообщений или 
диалоговые окна — рабочая область окна игнорировалась. В этой главе мы 
вернемся немного назад и рассмотрим различные способы управления выводом 
текста в рабочую область, имеющиеся в MFC. Также будут изучены некоторые 
принципиальные методы, позволяющие решить проблему обновления содержимого 
окна, если оно перекрывается другим окном. В конце главы обсуждается вопрос, 
каким образом в приложении могут быть использованы дополнительные шрифты. 
    Как и во многих других аспектах среды Windows, программист имеет 
фактически неограниченные возможности по управлению выводом текста в рабочую 
область окна. Однако в пределах одной главы полностью рассмотреть их 
практически невозможно. Тем не менее, изучив основы, описанные в данной главе, 
вы вполне сможете освоить и более сложные вопросы. 

    181 

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

    Координаты окна 

    Как известно, за вывод текста в MFC отвечает функция CDC::TextOut(). Она 
позволяет вывести строку в указанной позиции, причем координаты всегда 
указываются относительно окна. Поэтому размещение самого окна на экране никак 
не связано с координатами, указываемыми в функции Text0ut(). Считается, что 
верхний левый угол окна имеет координаты (0,0). Значения координаты Х 
увеличиваются слева направо, а координаты Y — сверху вниз. 
    Ранее при использовании координат в функции Text0ut() и при размещении 
различных элементов в диалоговом окне мы не упоминали о том, к чему эти 
координаты в действительности относятся. Настало время прояснить некоторые 
детали. Дело в том, что в функции Text0ut() указываются логические координаты. 
Т.е. они измеряются в логических единицах (это относится и к другим функциям 
вывода информации в окно, включая графические функции, рассматриваемые в главе 
9). В процессе отображения информации Windows преобразует логические единицы в 
пиксели. До сих пор мы не беспокоились о том, как это происходит, поскольку 
логическими единицами по умолчанию как раз и являются пиксели. Это, конечно, 
очень удобно, но нужно помнить, что существуют режимы отображения, в которых 
могут использоваться другие соотношения. 

    Задание цвета текста и фона 

    При выводе текста с помощью функции TextOutQ по умолчанию он выводится 
черным на текущем фоне. Однако цвет текста и цвет фона можно изменять с 
помощью функций SetTextColor() и SetBkColorQ, являющихся членами класса CDC. 
Их прототипы таковы: 

    virtual COLORREF CDC::SetTextColor(COLORREF Color}; 
    virtual COLORREF CDC::SetBkColor(COLORREF Color); 

    Функция SetTextColoi-0 устанавливает для вызывающего ее контекста 
устройства текущим цветом текста тот, который указан в параметре Color (или 
ближайший к нему, который может отобразить данное устройство). Аналогично, 
функция SetBkColor() устанавливает текущим цветом фона тот цвет, который 
указан в параметре Color (или ближайший к нему). Обе функции возвращают 
предыдущие установки цвета. 
    Цвет задается в виде значения типа COLORREF, являющегося 32-разрядным 
целым числом. В Windows цвет можно задавать тремя различными способами. 
Первый, наиболее распространенный, состоит в том, чтобы задавать значение 
цвета в формате RGB (красный, зеленый, синий). Здесь относительные 
интенсивности трех основных цветов в сумме составляют реальный цвет. Второй 
способ заключается в 

    182                                    MFC: основы программирования 

    указании индекса цвета в логической палитре цветов. В третьем способе 
значение цвета в формате RGB указывается относительно палитры. В этой главе мы 
рассмотрим только первый способ. 
    Для значения типа COLORREF, передаваемого в качестве параметра функциям 
SetTextColor() и SetBkColorQ, используется такая система кодировки (табл. 8.1): 

    Таблица 8.1. Кодировка значения типа COLORREF 

    Байт	Цвет 

    0 (младший байт)	Красный 
    1	Зеленый 
    2	Синий 
    3 (старший байт)	Должен равняться нулю 

    Значение каждого из основных цветов в формате RGB задается в диапазоне от 
О до 255, причем 0 означает самую низкую интенсивность, а 255 — самую высокую. 
    Хотя можно самостоятельно задавать значение типа COLORREF, в Windows есть 
макрокоманда RGB(), которая позволяет сделать это автоматически. Ее общий вид таков: 

    COLORREF RGB (BYTE red, BYTE green, BYTE blue) ; 

    Значения red, green и blue должны быть указаны в диапазоне от 0 до 255. 
Например, для задания ярко-пурпурного цвета используется вызов RGB(255, 0, 
255). Белому цвету соответствует вызов RGB(255, 255, 255), черному — RGB(0, 0, 
0). Другие цвета могут быть заданы комбинацией интенсивностей трех основных 
цветов. Например, RGB(0, 100, 100) задает светло-голубой цвет. Вы можете 
самостоятельно поэкспериментировать с разными значениями, чтобы определить, 
какой цвет наиболее подходит конкретному приложению. 

    Задание режима отображения фона 

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

    int CDC::SetBkMode(int mode); 

    Функция определяет, что происходит с текущим цветом фона при отображении 
текста (а также некоторых других элементов). Режим фона для контекста 
устройства, связанного с вызывающим функцию объектом, определяется параметром 
mode, который может принимать одно из двух значений: OPAQUE (непрозрачный) и 
TRANSPARENT (прозрачный). Функция возвращает значение предыдущей установки. 
    Если значение mode равно OPAQUE, то каждый раз при выводе текста цвет фона 
будет меняться на текущий цвет. Если значение mode равно TRANSPARENT, то фон 
не изменяется. В этом случае все вызовы функции SetBkColorQ игнорируются. По 
умолчанию режимом фона является OPAQUE. 

    8. Работа с текстом и решение проблемы перерисовки                  J83 


    Получение метрик текста 

    Как уже говорилось в главе 3, символы на экране могут иметь неодинаковые 
размеры. В частности, в Windows большинство текстовых шрифтов пропорциональны. 
Это означает, что символ "i", например, будет иметь не такую ширину, как "w". 
К тому же высота каждого символа и размер подстрочных элементов символов могут 
быть разными у разных шрифтов. Кроме того, расстояние между строками текста 
также не остается постоянным. В принципе, это не имело бы особого значения, 
если бы не тот факт, что при написании программ для Windows приходится 
программно управлять практически всем процессом вывода текста на экран. 
    Windows обеспечивает лишь минимальную поддержку вывода текста в рабочую 
область окна. Основной функцией вывода текста является функция Text0ut(). Она 
дает возможность вывести текстовую строку, начиная с указанной позиции. Эта 
функция не позволяет задавать формат вывода и даже не выполняет автоматически 
последовательность "возврат каретки/перевод строки". Короче говоря, вся работа 
по выводу текста лежит на программисте. 
    Поскольку в программе могут использоваться различные шрифты, то, 
следовательно, должен существовать способ определять размеры и другие атрибуты 
текущего шрифта. Например, при выводе одной текстовой строки после другой 
предполагается. что известна высота шрифта и количество пикселей между 
строками. Получить информацию о текущем шрифте можно с помощью функции 
GetTextMetricsQ. являющейся членом класса CDC. Ее прототип таков: 

    BOOL CDC::GetTextMetrics(LPTEXTMETRIC IpTextAttrib) const; 

    Здесь параметр IpTexlAttrib является указателем на структуру типа 
TEXTMETRIC. в которой после выполнения функции будут содержаться текстовые 
установки текущего шрифта, связанного с данным контекстом устройства. Функция 
возвращает ненулевое значение при успешном завершении и ноль — в противном 
случае. Структура TEXTMETRIC определена следующим образом: 

    typedef struct tagTEXTMETRIC 
    ( 
    LONG tmHeight; // полная высота шрифта 
    LONG tmAscent; // высота над основной линией 
    LONG tmDescent; // высота подстрочных элементов 
    LONG tiTiInternalLeading; // пустое пространство над символами 
    LONG tmExternalLeading; // интервал между строками 
    LONG tmAveCharWidth; // средняя ширина символов 
    LONG tmMaxCharWidth; // максимальная ширина символов 
    LONG tmWeight; // насыщенность (жирность) символов 
    LONG tmOverhang; // дополнительная насыщенность для 
    // специальных шрифтов 
    LONG tmDigitizedAspectX; // горизонтальный коэффициент сжатия 
    LONG tmDigitizedAspectY; // вертикальный коэффициент сжатия 
    BYTE tmFirstChar; // первый символ шрифта 
    BYTE tmLastChar; // последний символ шрифта 
    BYTE tmDefaultChar; // символ, выбираемый по умолчанию 
    BYTE tmBreakChar; // символ, используемый для разделения слов 


    184                                    MFC: основы программирования 


    BYTE tmltalic; // не ноль, если шрифт курсивный 
    BYTE ri-nUnderlined; // не ноль, если шрифт с подчеркиванием 
    BYTE tmStruckOut; // не ноль, если шрифт перечеркнутый 
    BYTE tmPitchAndFamily; // тип и семейство шрифта 
    BYTE tmCharSet; // идентификатор базового набора символов 
    ) TEXTMETRIC; 

    Хотя большинство значений, получаемых функцией GetTextMetricsQ, в данной 
главе использованы не будут, два из них чрезвычайно важны, так как с их 
помощью можно вычислять вертикальный интервал между строками текста. Значение 
интервала необходимо знать при выводе в окно более одной строки текста. В 
отличие от приложений, работающих с консолью, где используется только один 
шрифт фиксированного размера, при работе с окном могут использоваться 
различные шрифты. По сути, для каждого шрифта задана своя высота символов и 
интервал между строками. Это означает, что невозможно заранее предсказать, 
какой будет вертикальная (Y) координата следующей строки. Определить, где 
должна начинаться следующая строка, можно с помощью функции GetTextMetricsQ, 
поскольку именно она возвращает значения высоты символов и интервала между 
строками. Эти значения записываются в поля tmHeight и tmExternalLeading, 
соответственно. Складывая эти значения, можно получить число логических единиц 
по вертикали между строками. 
    Помните: В поле tmExternalLeading содержится число логических единиц по 
вертикали, которое должно быть оставлено пустым между строками текста. Это не 
то же самое, что и высота символа. Поэтому для вычисления начала следующей 
строки необходимы обе координаты. Дальше мы рассмотрим, как это реализуется на практике. 

    Вычисление длины строки 

    Поскольку символы текущего шрифта могут иметь разную ширину, невозможно 
вычислить длину строки в логических единицах только на основании количества 
символов в строке. Т.е. результат, возвращаемый функцией strlen(), не имеет 
значения при выводе текста в окно. Для разрешения этой проблемы в класс CDC 
включена функция GetTextExtentQ, которая имеет такой прототип: 

    CSize CDC::GetTextExtent(LPCSTR IpszString, int Len) const.

    Здесь параметр Ips^String указывает на строку, длину которой необходимо 
вычислить. Параметр Len определяет количество символов в строке. Длина и 
высота строки алогических единицах возвращаются в объекте типа CSize. Он 
включает стандартную структуру SIZE, которая определена следующим образом: 

    typedef struct tagSIZE 
    { 
    LONG ex; // ширина 
    LONG су; // высота 
    ) SIZE; 


    8. Работа с текстом и решение проблемы перерисовки                  185 

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

    Получение системных метрик 

    Хотя Windows поддерживает и автоматически переводит логические координаты 
в пиксели, иногда необходимо знать действительный размер экрана компьютера, на 
котором выполняется приложение. Для получения этой, и подобной, информации, 
используется API-функция GetSystemMetrics(), прототип которой следующий: 

    int GetSystemMetrics(int what); 

    Обратите внимание, что эта функция не является членом ни одного из классов 
MFC. Для получения системных метрик проще использовать стандартные функции 
API, так как системная информация не зависит ни от какого приложения. Параметр 
what является идентификатором получаемой метрики. Функция GetSystemMetricsO 
может использоваться для получения различных значений. Значения координат 
экрана возвращаются в пикселях. Параметр what может содержать одну из 
следующих констант (табл. 8.2): 

    Таблица 8.2. Системные метрики 

    Константа	Получаемая метрика 

    SMCXFULLSCREEN	Ширина рабочей области окна, развернутого на весь экран 
    SMCYFL)LLSCREEN	Высота рабочей области окна, развернутого на весь экран 
    SMCX[CON	Ширина большой пиктограммы 
    SMCYICON	Высота большой пиктограммы 
    SMCXSMICON	Ширина малой пиктограммы 
    SMCYSMICON	Высота малой пиктограммы 
    SMCXSCREEN	Ширина экрана 
    SMCYSCREEN	Высота экрана 
    

    Демонстрационная программа работы с текстом 

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

    ; TEXT.RC 
    #include <afxres.h> 
    #include "ids.h" 


    186                                     MFC: основы программирования 


    TextMenu MENU ( 
    POPUP "Demonstrate Text" 
    { 
    
    MENUITEM "&Show\tF2", IDM_SHOW 
    MENUITEM "&Reset\tF3", IDM_RESET 
    MENUITEM "E&xit\tF4", IDM_EXIT 
    } 
    MENUITEM "SHelp", IDM_HELP 
    } 
    TextMenu ACCELERATORS 
    ( 
    VK_F2, IDM_SHOW, VIRTKEY 
    VK_F3, IDM_RESET, VIRTKEY 
    VK_F4, IDM_EXIT, VIRTKEY 
    VK_F1, IDM_HELP, VIRTKEY 
    } 

    Программа также включает библиотечный файл IDS.H, текст которого показан 
ниже. Константа IDM_FONT пока использована не будет. 

    // IDS.H 
    #define IDM_SHOW   100 
    #define IDM_RESET 101 
    #define IDM_FONT   102 
    #define IDM_EXIT   103 
    #define IDM_HELP   104 
    Вот текст файла ТЕХТ.Н, содержащего объявления порождаемых классов: 
    // ТЕХТ.Н 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    ( 
    public: 
    int m_X, m_Y; // текущая позиция для вывода 
    char m str[255]; // содержит строку для вывода 
    CMainWin() ; 
    afx msq void OnShowO; 
    afx msg void OnResetO; 
    afx msg void OnExitO; 
    afx msg void OnHelpO; 
    DECLARE_MESSAGE_MAP() 
    ) ; 
    // Это класс для создания приложения 
    class CApp : public CWinApp 


    S. Работа с текстом и решение проблемы перерисовки                  187 


    { 
    public: 
    BOOL Initlnstance (); 
    ); 
    Это текст самой программы: 
    // Демонстрирует вывод текста на экран 
    ^include <afxwin.h> 
    #include <string.h> ftinclude "text.h" 
    #include "ids.h" 
    int maxX, maxY; // размеры экрана 
    CMainWin::CMainWin() ( 
    Create(NULL, "Demonstrate Text Output", 
    WSJ3VERLAPPEDHINDOW, rectDefault, 
    NULL, "TextMenu") ; 
    // Загрузка таблицы быстрого доступа 
    if(!LoadAccelTable("TextMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    m_X = m_Y = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN) ; 
    maxY = GetSystemMetrics(SM_CYSCREEN) ; 
    } 
    // Инициализация приложения 
    BOOL CApp::Initlnstance() 
    ( 
    m pMainWnd = new CMainWin; 
    m pMainWnd->ShowWindow(m nCmdShow); 
    m pMainWnd->UpdateWindow() ; 
    return TRUE; 
    } 
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_COMMAND(IDM_SHOW, OnShow) 
    ON_COMMAND(IDM_RESET, OnReset) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    // Обработка сообщения IDM_EXIT 
    afx msg void CMainWin::OnExit () 
    int response; 


    188                                    MFC: основы программирования 


    response = MessageBox("Quit the Program?", "Exit", MB YESNO); 
    if(response == IDYES) 
    SendMessage(WM_CLOSE); // завершение приложения } 
    // Обработка сообщения IDM HELP 
    afx msg void CMainWin::OnHelp() 
    ( 
    MessageBox("Text Demo", "Help"); 
    ) 
    // Вывод текста 
    afx_msg void CMainWin::OnShow() 
    ( 
    CClientDC DC(this); 
    TEXTMETRIC tm; 
    CSize size; 
    // цвет символов устанавливается черным 
    DC.SetTextColor(RGB(0, 0, 0)) ; 
    // цвет фона устанавливается голубым 
    DC.SetBkColor(RGB(0, 255, 255)); 
    // получение метрик текста 
    DC.GetTextMetrics(&tm) ; 
    wsprintf(m_str, "The font is %ld pixels high.", tm.tmHeight); 
    DC.TextOut(m__X, m__Y, m_str, strlen(m_str)); // вывод строки 
    т Y = т Y + tm.tmHeight + tm.tmExternalLeading; // переход 
    //на следующую строку 
    strcpy(m_str, "This is on the next line."); 
    DC.TextOut(m X, m Y, m str, strlen(m str)); // вывод строки 
    // вычисление длины строки 
    size = DC.GetTextExtent(m_str, strlen(m_str)); 
    wsprintf(m str, "Previous string is %ld units long", size.ex); 
    m X = size.ex; // переход на конец предыдущей строки 
    DC.TextOut(m X, m Y, m str, strlen(m str)); 
    m Y = m Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    m X = 0; // сброс координаты Х 
    wsprintf(m str, "Screen dimensions: %d %d", maxX, maxY); 
    DC.TextOut(m X, m Y, m str, strlen(m_str)); 
    m_Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 1 
    // Обнуление текущих координат 
    afx_msg void CMainWin::OnReset() 


    8. Работа с текстом и решение проблемы перерисовки                  189 


    { 
    m_X = m_Y = 0; 
    ) 
    CApp Арр; // создание экземпляра приложения 

    Наберите эти файлы, а затем откомпилируйте и запустите программу. Ес.;г 
выбрать команду Show в меню Demonstrate Text, на экран будет выведено 
несколько строк текста черными буквами на голубом фоне. Пример работы 
программы показан на рис. 8.1. 

    Рис. 8.1. Окно демонстрационной программы работы с текстом                 

    При создании окна в глобальные переменные тахХ и maxY записываютсяразмеры 
экрана, получаемые путем вызова функции GetSystemMetrics(). Хотя в программе 
они не играют какой-то определенной роли, они понадобятся нам в последующих примерах. 
    Обратите внимание, что в классе CMainWin() объявлены две переменные т_Х и 
m_Y, которым присваиваются нулевые значения при создании окна. В этих 
переменных хранятся текущие координаты позиции окна, в которой будет 
выводиться текст Эти значения постоянно обновляются программой после каждой 
операции вывода текста. 
    Наиболее интересная часть программы — это функция-обработчик OnShowQ 
Давайте рассмотрим ее подробнее. Вначале в этой функции запрашивается контекст 
устройства. Затем цвет выводимых символов устанавливается черным, а цвет фона 
— голубым. Поскольку функции при каждом вызове предоставляется новый контекст 
устройства, эти цвета должны каждый раз устанавливаться заново. Т.е. их 
нельзя, например, установить один раз при запуске программы. 

    190                                    MFC: основы программирования 

    После задания цветов в функции определяются метрики текста. Затем 
выводится первая строка. Строка сначала формируется с помощью функции 
wsprintf(), а только потом выводится на экран с помощью функции TextOutQ. Если 
помните, ранее уже говорилось о том, что ни функция TextOutQ, ни другие 
функции вывода не выполняют форматирование текста. Об этом должен позаботиться 
программист. После отображения строки переменная m_Y, в которой хранится 
координата Y следующей строки, увеличивается согласно формуле, описанной ранее. 
    Потом программа выводит строку "This is on the next line". Прежде чем эта 
строка будет изменена при следующем вызове функции wsprintf(), вычисляется 
текущая длина строки с помощью функции GetTextExtentQ. Полученное число 
используется для вычисления значения переменной т_Х, которое соответствует 
координате Х конца предыдущей строки. Таким образом определяются координаты 
вывода следующей строки. При этом координата Y (т.е. переменная m_Y) остается 
неизменной. В результате следующая строка будет выведена сразу же вслед за 
предыдущей строкой на той же линии. Затем значение переменной m_Y 
увеличивается на одну строку, а переменная т_Х сбрасывается в 0. Это означает, 
что последующий текст снова будет выведен в начале строки. Последним действием 
в функции отображаются размеры экрана и переменная m_Y увеличивается на одну строку. 
    Каждый раз при выборе команды Show набор строк отображается ниже в окне и 
не затирает предыдущий текст. 
    Чтобы повторить всю процедуру, выберите в меню Demonstrate Text команду 
Reset. В результате начальные координаты будут установлены в позицию (0,0). 

    Решение проблемы перерисовки 

    Хотя в предыдущей программе демонстрировались некоторые функции работы с 
текстом, перед нами вновь возникла фундаментальная проблема, впервые 
упомянутая в главе 3. Проблема эта такова: если программа выводит на экран 
какой-то текст, а затем окно программы перекрывается другим окном, то та часть 
текста, которая была перекрыта, теряется, т.е. исчезает. Вся суть в том, что 
каждая программа должна обновлять содержимое своего окна при получении 
сообщения WM_PAINT, а предыдущая программа этого не делала. Здесь возникает 
принципиальный вопрос: какой механизм необходимо использовать для 
восстановления содержимого окна, которое было перекрыто? Как упоминалось в 
главе 3, есть три основных метода. Первый заключается в том, чтобы все время 
вычислять позицию выводимого текста. Во втором методе можно хранить запись 
событий, связанных с выводом информации в окно, и, при необходимости, 
"переигрывать" эти события. Третий метод состоит в том, чтобы поддерживать в 
программе виртуальное окно и просто копировать его содержимое в реальное окно 
при получении сообщения WM_PAINT. Наиболее общим, безусловно, является третий 
метод, который мы сейчас и рассмотрим. Как вы увидите, для него предусмотрена 
специальная поддержка в Windows и MFC. 

    Теория виртуального окна 

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

    8. Работа с текстом и решение проблемы перерисовки                  191 

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

    Дополнительные API-функции 

    Для реализации виртуального окна необходимо использовать некоторые функции 
MFC. Четыре из них уже были рассмотрены ранее. Это CreateCompatibleDCO. 
Select0bject(), CreateStockObject() и BitBltQ. Кроме того, используются 
функции CreateCompatibleBitmapO и PatBltQ. 
    Функция CreateCompatibleBitmapO создает растровое изображение, совместимое 
с указанным контекстом устройства. Впоследствии это изображение может быть 
использовано любым контекстом области памяти, совместимым с данным контекстом. 
Эта функция является членом класса CBitmap и имеет такой прототип: 

    BOOL CBitmap::CreateCompatibleBitmap(CDC *pDC, int Width, int Height); 

    Здесь параметры Width и Height определяют размер изображения. Их значение 
указываются в пикселях. Функция возвращает ненулевое значение при успешно" 
завершении и ноль — в противном случае. 
    Функция PatBItQ заполняет прямоугольную область согласно цвету и стилю 
текущей кисти. Эта функция является членом класса CDC. Заполнение области с 
помощью кисти также часто называется закраской. Функция PatBltQ имеет такой прототип: 

    BOOL CDC::PatBlt(int X, int У, int Width, int Height, DWORD dwRasterOp) ; 

    Здесь параметры Хтл /определяют координаты верхнего левого угла 
закрашиваемой области. Горизонтальный и вертикальный размер области задаются 
параметрами Width и Height. Параметр dwRasterOp определяет способ 
использования кисти. Это должна быть одна из следующих констант (табл. 8.3): 

    Таблица 8.3. Значения параметра dwRasterOp 

    Константа  Значение

BLACKNESS 
WHITENESS 
PATCOPY 
PATINVERT
DSTINVERT	

Область закрашивается черным цветом (кисть игнорируется) 
Область закрашивается белым цветом (кисть игнорируется) 
Область закрашивается цветом и стилем текущей кисти
Над содержимым области и содержимым кисти выполняется операция побитового ИЛИ
Цвет области инвертируется (кисть игнорируется) 


    192 MFC: основы программирования \ 

    Поэтому, если текущая кисть должна быть применена без изменений, в 
качестве значения параметра dwRasterOp необходимо указать PATCOPY. Функция 
PatBltQ возвращает ненулевое значение при успешном завершении и ноль — в 
противном случае. 
    Теперь, когда все необходимые функции нам известны, пора приступить к 
созданию виртуального окна. 

    Создание и использование виртуального окна 

    Сначала давайте уточним, что мы хотим создать. Чтобы обеспечить легкий и 
удобный способ восстановления содержимого окна после получения сообщения 
WM_PAINT, необходимо организовать виртуальное окно и направлять весь вывод на 
экран через это окно. Каждый раз, когда принимается указанное сообщение, 
содержимое виртуального окна должно быть скопировано в физическое окно на 
экране. Это нам и предстоит реализовать. 

    Создание виртуального окна 

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

    CDC m rnemDC; // контекст устройства, связанный с виртуальным окном 
    CBitmap m bmp; // образ виртуального окна 
    CBrush m bkbrush; // кисть для виртуального окна 

    Вот часть текста конструктора CMainWin(), предназначенная для создания 
виртуального окна: 

    maxX = GetSystemMetrics(SM_CXSCREEN); 
    maxY = GetSystemMetrics(SM_CYSCREEN); 
    CClientDC DC (this) ; 
    // создание образа виртуального окна 
    m memDC.CreateCompatibleDC(&DC) ; 
    m bmp.CreateCompatibleBitmap(&DC, maxX, maxY) ; 
    m memDC.SelectObject(&m bmp) ; 
    // выбор стандартного фона 
    m_bkbrush.CreateStockObject(WHITE_BRUSH) ; 
    m memDC.SelectObject(&m_bkbrush) ; 
    // закраска фона виртуального окна 
    ni_niemDC.PatBlt (0, 0, maxX, maxY, PATCOPY); 


    8. Работа с текстом и решение проблемы перерисовки                  193 

    Давайте внимательно рассмотрим весь процесс. Сначала определяются размеры 
экрана. Затем для текущего окна запрашивается контекст устройства, который 
используется для создания совместимого контекста области памяти. После этого 
создается совместимое растровое изображение. Такая последовательность действий 
позволяет организовать побитовое соответствие между содержимым виртуального и 
физического окна. Размеры изображения устанавливаются такими же, как и размеры 
всего экрана. Это дает уверенность, что данное изображение будет достаточно 
большим, чтобы полностью восстановить содержимое любого окна независимо от его 
размера. Вообще говоря, размер изображения может быть немного меньше размера 
экрана, поскольку рамка окна не перерисовывается. Но это незначительное 
изменение предоставляется вам в качестве упражнения. Следующим шагом создается 
стандартная белая кисть. Она выбирается в контекст области памяти, после чего 
функция PatBltQ закрашивает с помощью этой кисти вес виртуальное окно. Таким i 
образом, фон виртуального окна будет белым, что соответствует фону физического 
окна в следующем примере. Помните, что все цвета здесь выбираются произвольно. 
Вы, если хотите, вправе задавать их по своему усмотрению. И наконец, контекст 
устройства, связанный с физическим окном, освобождается (это выполняется 
автоматически при завершении конструктора CMainWin()). Однако контекст области 
памяти будет продолжать существовать до завершения программы. 

    Использование виртуального окна 

    Когда виртуальное окно создано, весь вывод на экран направляется в него. 
Вывод непосредственно на экран происходит только при получении сообщения 
WM_PAINT. Ниже приводится обновленная версия обработчика OnShowQ, в котором 
производится работа с виртуальным окном. 

    // Вывод текста через виртуальное окно 
    afx msg void CMainWin::OnShow() 
    ( 
    TEXTMETRIC tm; 
    CSize size; 
    // цвет символов устанавливается черным 
    m memDC.SetTextColor(RGB(0, 0, 0)) ; 
    // цвет фона устанавливается голубым 
    m_memDC.SetBkColor(RGB(0, 255, 255)); 
    // получение метрик текста 
    m memDC.GetTextMetrics(&tm); 
    wsprintf(m_str, "The font is %ld pixels high.", tm.tmHeight); 
    m memDC.TextOut(m X, m Y, m str, strlen(m_str)); // вывод строки 
    m Y = m Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    strcpy(m_str, "This is on the next line."); 
    m memDC.TextOut(m X, m У, m str, strlen(m str)); // вывод строки 


    194                                     MFC: основы программирования 


    // вычисление длины строки 
    size = m mernDC .GetTextExtent (m str, strlen (m str)); 
    wsprintf(m str, "Previous string is %ld units long", size.ex); 
    m X = size.ex; // переход на конец предыдущей строки 
    m_memDC.TextOut(m_X, m_Y, m_str, strlen(m_str)); 
    m Y = m Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    m X = 0; // сброс координаты Х 
    wsprintf(m str, "Screen dimensions: %d %d", maxX, maxY) ; 
    m_memDC.TextOut(m_X, m_Y, m__str, strlen(m_str)); 
    rn_Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    InvalidateRect(NULL) ; 
    1 

    Как видите, здесь весь вывод на экран направляется через переменную 
m_memDC. Когда содержимое физического окна должно быть обновлено, вызывается 
функция InvalidateRectQ. 
    Каждый раз при получении сообщения WM_PAINT содержимое виртуального окна 
копируется в физическое окно. Это реализуется с помощью обработчика OnPaintO: 

    // Вывод на экран содержимого виртуального окна 
    afx msg void CMainWin::OnPaint() 
    ( 
    CPaintDC DC (this) ; 
    DC.BitBlt(0, 0, maxX, maxY, &m_memDC, О, О, SRCCOPY); 
    ) 

    Здесь для копирования изображения из переменной m_meinDC в физическое окно 
используется функция BitBIt(). Параметр STRCOPY означает простое копирование 
содержимого исходной области на экран без изменений. Поскольку вся выводимая 
информация хранилась в переменной m_memDC, то в результате выполнения данной 
функции эта информация действительно будет отображена на экране. Но еще более 
важно другое: поскольку сообщение \VM_PAINT посылается программе каждый раз, 
когда главное окно перекрывается другим окном, данный обработчик будет 
автоматически восстанавливать содержимое главного окна. 
    Как уже упоминалось ранее, есть несколько способов восстанавливать 
содержимое окна, но рассмотренный только что метод применим для большого числа 
ситуаций и, как правило, весьма эффективен. Более того, поскольку программе 
передаются координаты области, которая должна быть перерисована (в поле 
rcPaint структуры PAINTSTRUCT — см. главу 3), предыдущую функцию можно сделать 
еще эффективнее, восстанавливая лишь ту часть окна, которая была уничтожена. 
Попробуйте сделать это самостоятельно! 

    8. Работа с текстом и решение проблемы перерисовки                  195 


    Демонстрационная программа работы с виртуальным окном 

    В данном параграфе приводится текст программы, которая организует работу с 
виртуальным окном. В ней используется тот же файл ресурсов и тот же 
библиотечный файл IDS.H, что и в предыдущем примере. Вот текст файла 
V7RTWIN.H, содержащего объявления порождаемых классов: 

    // VIRTWIN.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    { 
    public: 
    int m X, m_Y; // текущая позиция для вывода 
    char m_str[255]; // содержит строку для вывода 
    CDC m memDC; // контекст устройства, связанный с 
    // виртуальным окном 
    CBitmap m bmp; // образ виртуального окна 
    CBrush т bkbrush; // кисть для виртуального окна                  \ 
    CMainWin() ; 
    afx msg void OnPaintO; 
    afx_rnsg void OnShow(); 
    afx msg void OnResetO; 
    afx msg void OnExitO; 
    afx_msg void OnHelpO; 
    DECLARE_MESSAGE_MAP() 
    }; 
    // Это класс для создания приложения 
    class CApp : public CWinApp < public: 
    BOOL Initlnstance (); 
    ); 

    Это текст самой программы: 

    // Демонстрирует вывод текста на экран 
    #include <afxwin.h> 
    #include <string.h> 
    #include "virtwin.h" 
    ^include "ids.h" 
    int maxX, maxY; // размеры экрана 
    CMainWin::CMainWin() 
    ( 
    Create(NULL, "Demonstrate Text Output", 


    196                                    MFC: основы программирования у 


    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "TextMenu") ; 
    // Загрузка таблицы быстрого доступа 
    if(!LoadAccelTable("TextMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    m_X = m_Y = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN); 
    maxY = GetSystemMetrics(SM_CYSCREEN) ; 
    CClientDC DC(this) ; 
    // создание образа виртуального окна 
    m_memDC.CreateCompatibleDC(&DC) ; 
    m bmp.CreateCompatibleBitmap(&DC, maxX, maxY); 
    m memDC.SelectObject(&m_bmp); 
    // выбор стандартного фона 
    m_bkbrush.CreateStockObject(WHITE_BRUSH) ; 
    m memDC.SelectObject(&m bkbrush); 
    // закраска фона виртуального окна 
    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY); 
    } 
    // Инициализация приложения 
    BOOL САрр::Initlnstance() 
    ( 
    m pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow) ; 
    m_pMainWnd->UpdateWindow() ; 
    return TRUE; 
    ) 
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_PAINT() 
    ON_COMMAND(IDM__SHOW, OnShow) 
    ON_COMMAND(IDM_RESET, OnReset) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    // Вывод на экран содержимого виртуального окна 
    afx msq void CMainWin::OnPaint() 
    ( 
    CPaintDC DC(this) ; 
    DC.BitBlt(0, 0, maxX, maxY, &m_memDC, О, О, SRCCOPY) ; 
    ) 


    8. Работа с текстом и решение проблемы перерисовки                  197 


    // Обработка сообщения IDM EXIT 
    afx_msg void CMainWin::OnExit() 
    ( 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB YESNO) ; 
    if(response == IDYES) 
    SendMessage(WM_CLOSE); // завершение приложения 
    } 
    // Обработка сообщения IDM HELP 
    afx msg void CMainWin::OnHelp() 
    ( 
    MessageBox ("Text Demo", "Help"); 
    ) 
    // Вывод текста через виртуальное окно 
    afx msg void CMainWin::0n3how() 
    ( 
    TEXTMETRIC tm; 
    CSize size;                                                        ^И 
    // цвет символов устанавливается черным                            ^Н 
    mJ-nemDC.SetTextColor (RGB (О, О, 0));                                 • 
    // цвет фона устанавливается голубым                               • 
    m_memDC.SetBkColor(RGB(0, 255, 255));                               J 
    // получение метрик текста                                         • 
    m_memDC.GetTextMetrics(Stm);                                        • 
    wsprintf(m_str, "The font is %ld pixels high.", tm.tmHeight);      ^B 
    m_memDC.TextOut(m_X, m_Y, m_str, strlen(m_str)); // вывод строки   ^Н 
    m_Y ^ ni__Y + tm.tmHeight + tm.tmExternalLeading; // переход на      ^Н 
    // следующую строку ^Н 
    strcpy(m_str, "This is on the next line.");                        ^B 
    m_memDC.TextOut(m_X, m_Y, m_str, strlen(m_str)); // вывод строки    J 
    // вычисление длины строки 
    size = m memDC.GetTextExtent(m str, strlen(m str)); 
    wsprintf(m_str, "Previous string is %ld units long", size.ex); 
    m_X = size.ex; // переход на конец предыдущей строки 
    m memDC.TextOut(m_X, m Y, m str, strien(m str)) ; 
    m__Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    m X == 0; // сброс координаты Х 
    wsprintf(m_str, "Screen dimensions: %d %d", maxX, maxY);            ^ 


    198                                     MFC: основы программирования  •i 


    m memDC.TextOut(m X, m Y, m str, strlen(m str)) ; 
    m Y = m Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    InvalidateRect(NULL) ; 
    // Обнуление текущих координат и очистка окна 
    afx msg void CMainWin::OnReset() 
    ( 
    m_X = m_Y = 0; 
    ni_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY); 
    InvalidateRect(NULL) ; 
    } 
    CApp App; // создание экземпляра приложения 

    Запустив программу, вы обнаружите два существенных изменения. Во-первых, 
когда главное окно перекрывается, а затем вновь активизируется, его содержимое 
каждый раз восстанавливается. И во-вторых, при выборе команды Reset окно также 
очищается. Это выполняется путем вызова функции PatBltQ. 

    Изменение шрифтов 

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

    Использование встроенных шрифтов 

    Встроенные шри4)ты являются готовыми объектами, которые могут быть выбраны 
с помощью MFC-функции CreateStuckObject(), как это было описано в главе 7. На 
момент написания книги в MFC поддерживалось несколько встроенных шрифтов. 
Названия констант, связанных с этими шрифтами, приведены в табл. 8.4: 

    Таблица S.4.   Константы шрифтов     

    Константа  Описание

ANS1F1XEDFONT 
ANSIVARFONT 
DEV1CEJDEFAULTFONT 
DEFAULTGUIFONT
OEMF1XEDFONT 
SYSTEMFONT 
SYSTEMFIXEDFONT	

ANSI-шрифт фиксированного размера 
ANSI-шрифт переменного размера 
Стандартный шри[})т устройства
Стандартный шрифт, используемый в диалоговых окнах
Шрифт, определенный производителем 
Системный шрифт Windows 95 
Системный шрифт старых версий Windows 


    8. Работа с текстом и решение проблемы перерисовки 199 

    Системные шрифты используются в меню и диалоговых окнах. В старых версиях 
Windows использовался системный шрифт фиксированного размера, но уже в Windows 
3.0 и более поздних версиях системы (включая Windows 95 и Windows NT) 
используется системный шрифт переменного размера. 
    Выбрать встроенный шрифт и работать с ним довольно просто. Для этого в 
программе сначала должен быть создан объект типа CFont. Затем он должен 
загрузит ч, требуемый шрифт с помощью функции CreateStockObject(). Для 
переключен!'; шрифтов используется функция SelectObjectQ с именем нового 
шрифта в качеств параметра. Эта функция возвращает указатель на прежний шрифт, 
который можно сохранить, чтобы, при необходимости, вернуться к старому шрифту. 
Нужно, однако. помнить о том, что объект, адресуемый данным указателем, может 
быть линч временным. 
    В следующем примере демонстрируется, как можно менять шрифты в программе 
Здесь в файл ресурсов из предыдущего примера добавлен еще один элемент меню 
названный Change Font. Каждый раз при выборе этого элемента в программе 
происходит переключение шрифтов между стандартным системным шрифтом и 
ANSI-шрифтом переменного размера. В программе используется тот же библиотечный 
файл TDS.H, что и описанный ранее. Вот текст файла ресурсов: 

    ; SYSFONT.RC 
    ^include <afxres.h> 
    #include "ids.h" 
    TextMenu MENU 
    ( 
    POPUP "Demonstrate Text" < 
    MENUITEM "&Show\tF2", IDM_SHOW 
    MENUITEM "&Reset\tF3", IDM_RESET 
    MENUITEM "SChange Font\tF4", IDM_FONT 
    MENUITEM "E&xit\tF5", IDM_EXIT 
    ) 
    MENUITEM "SHelp", IDM_HELP 
    ) 
    TextMenu ACCELERATORS 
    ( 
    VK_F2, IDM_SHOW, VIRTKEY 
    VK_F3, IDM_RESET, VIRTKEY 
    VK_F4, IDM^FONT, VIRTKEY 
    VK_F5, IDM_EXIT, VIRTKEY 
    VK_F1, IDM_HELP, VIRTKEY 
    } 
    Это текст файла SYSFONT.H, содержащего объявления порождаемых классов: 
    // SYSFONT.H 
    // Это класс для создания главного окна 


    200                                    MFC: основы программирования 


    class CMainWin : public CFrameWnd 
    ( 
    public: 
    int m X, m_Y; // текущая позиция для вывода 
    char m str[255]; // содержит строку для вывода 
    CDC m memDC; // контекст устройства, связанный с 
    // виртуальным окном 
    CBitmap m bmp; // образ виртуального окна 
    CBrush m bkbrush; // кисть для виртуального окна 
    CFont m SystemFont, m AnsiVarFont; // объекты шрифтов 
    CMainWin() ; 
    afx msg void OnPaint(); 
    afx msq void OnShow(); 
    afx msg void OnFont(); 
    afx msg void OnResetO; 
    afx_msg void OnExitO; 
    afx_msg void OnHelpO; 
    DECLARE_MESSAGE_MAP() 
    1; 
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance (); 
    ); 

    Вот текст самой программы: 

    // Работа со встроенными шрифтами 
    ^include <afxwin.h> 
    ^include <strinq.h> 
    itinclude "sysfont.h" 
    Hnclude "ids.h" 
    int maxX, maxY; // размеры экрана 
    CMainWin::CMainWin() 
    ( 
    Create(NULL, "Demonstrate Built-in Fonts", 
    WSJ3VERLAPPEDWINDOW, rectDefault, 
    NULL, "TextMenu") ; 
    // Загрузка таблицы быстрого доступа 
    if(!LoadAccelTable("TextMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 


    8. Работа с текстом и решение проблемы перерисовки                  201 


    m_X = m_Y = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN); 
    maxY = GetSystemMetrics(SM_CYSCREEN); 
    CClientDC DC(this) ; 
    // создание образа виртуального окна 
    m_memDC.CreateCompatibleDC(&DC) ; 
    m_bnip.CreateCompatibleBitmap(&DC, maxX, maxY); 
    m memDC.SelectObject(Sm bmp); 
    // выбор стандартного фона 
    m_bkbrush.CreateStockObject(WHITE_BRUSH) ; 
    m_meniDC. SelectObject (&m_bkbrush) ; 
    // закраска фона виртуального окна 
    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY) ; 
    // инициализация объектов встроенных шрифтов 
    m_AnsiVarFont.CreateStockObject(ANSI_VAR_FONT) ; 
    m_SystemFont.CreateStockObject(SYSTEM_FONT) ; 
    } 
    // Инициализация приложения 
    BOOL CApp::Initlnstance() 
    < 
    m_pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow) ; 
    ni_pMainWnd->UpdateWindow(); 
    return TRUE; 
    ) 
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_PAINT() 
    ON_COMMAND(IDM_SHOW, OnShow) 
    ON_COMMAND(IDM_FONT, OnFont) 
    ON_COMMAND(IDM_RESET, OnReset) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP <) 
    // Вывод на экран содержимого виртуального окна 
    afx msg void CMainWin::OnPaint() 
    { 
    CPaintDC DC (this) ; 
    DC.BitBlt(0, 0, maxX, maxY, &m_memDC, О, О, SRCCOPY) ; 
    i 
    // Обработка сообщения IDM_EXIT 
    afx_msg void CMainWin::OnExit() 


    202                                     MFC: основы программирования 


    { 
    int responses
    response = MessageBox ("Quit the Program?", "Exit", MB_YESNO); 
    if (response == IDYES) 
    SendMessage(WM CLOSE); // завершение приложения 
    1 
    // Обработка сообщения IDM_HELP 
    afx msg void CMainWin::OnHelp () 
    ( 
    MessageBox ("Text Demo", "Help"); 
    // Вывод текста 
    afx msg void CMainWin::OnShow() 
    ! 
    TEXTMETRIC tm; 
    CSize size; 
    // цвет символов устанавливается черным 
    m_memDC.SetTextColor(RGB(0, 0, 0) ) ; 
    // получение метрик текста 
    m_memDC.GetTextMetrics(&tm) ; 
    wsprintf(m str, "The font is %ld pixels high.", tm.tmHeight) ; 
    т memDC.TextOut(m X, m Y, m str, strlen(m str)); // вывод строки 
    m_Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    strcpy(m str, "This is on the next line."); 
    m memDC.TextOut(m X, m Y, m str, strlen(m str)); // вывод строки 
    // вычисление длины строки 
    size = m_memDC.GetTextExtent(m_str, strlen(m_str)); 
    wsprintf(m str, "Previous string is %ld units long", size.ex); 
    m X = size.ex; // переход на конец предыдущей строки 
    m_memDC.TextOut(m_X, m_Y, m_str, strlen(m_str)); 
    m_Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    m X = 0; // сброс координаты Х 
    wsprintf(m str, "Screen dimensions: %d %d", maxX, maxY); 
    m memDC.TextOut(m X, m Y, m str, strlen(m str)) ; 
    m_Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    InvalidateRect(NULL) ; 
    ) 


    8. Работа с текстом и решение проблемы перерисовки                  203 


    // Обнуление текущих координат и очистка окна 
    afx msg void CMainWin::OnReset () 
    { 
    m_X = m_Y = 0; 
    m_memDC.PatBlt (0, 0, maxX, maxY, PATCOPY); 
    InvalidateRect(NULL) ; 
    } 
    // Смена шрифтов 
    afx msg void CMainWin::OnFont() 
    { 
    static whichfont = 0; 
    if(!whichfont) { 
    m_memDC.SelectObject(&m_AnsiVarFont) ; 
    whichfont = 1; 
    } 
    else { 
    m memDC.SelectObject(&m SystemFont); 
    whichfont = 0; 
    } 
    ) 
    CApp App; // создание экземпляра приложения 

    Пример работы программы показан на рис. 8.2. 

    Рис. 8.2. Работа со встроенными шрифтами 


    Создание собственных шрифтов 

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

    204                                     MFC: основы программирования 

    своеобразно и выделяет его среди других приложений. Во-вторых, создание 
собственного шрифта позволяет в точности контролировать процесс вывода текста 
на экран. 
    Необходимо отметить, что мы не будем определять совершенно новый шрифт. 
Работа будет вестись с уже существующим шрифтом, который будет изменяться 
согласно заданным критериям. (Таким образом, не нужно будет задавать вид 
каждого символа в новом шрифте.) 
    Для создания нового шрифта используется функция CreateFontQ, которая 
является членом класса CFont. Ее прототип таков: 

    BOOL CFont::CreateFont (int Height, int Width, int Escapement, 
    int Orient ation, int Height, 
    BYTE Ital, BYTE Underline, 
    BYTE StrikeThru, BYTE Charset, 
    BYTE Precision, BYTE ClipPrecision, 
    BYTE Quality, BYTE Pitch, 
    LPCSTR FontName}; 

    Высота шрифта указывается в параметре Height. Если он равен нулю, то 
выбирается значение по умолчанию. Ширина шрифта задается в параметре Width. 
Если и он равен нулю, то Windows выбирает подходящее значение, основываясь на 
текущем коэффициенте пропорциональности шрифта. Оба параметра задаются в 
логических единицах. 
    Угол наклона текста может быть любым. Значение угла наклона задается 
параметром Escapement. Для обычного горизонтального текста это значение должно 
равняться нулю. В противном случае оно указывает количество десятых долей 
градуса, на которые текст должен быть повернут. Например, значение 900 говорит 
о том, что текст будет повернут на 90 градусов, т.е. вертикально. 
    Наклон каждого символа задается параметром Orientation. Его значение также 
указывается в десятых долях градуса, на которые будет повернут каждый символ 
относительно направления вывода текста. 
    Параметр Weight задает требуемую насыщенность (жирность) шрифта в 
диапазоне от 0 до 1000. Если он равен нулю, то будет выбрано значение по 
умолчанию. Нормальной насыщенности соответствует значение 400. Жирному шрифту 
(bold) соответствует значение 700. При задании этого параметра можно 
использовать следующие константы: 

    FWJ30NTCARE 
    FW_THIN 
    FW_EXTRALIGHT 
    FW_ULTRALIGHT 
    FH_LIGHT 
    FW_NORMAL 
    FW_REGULAR 
    FW_MEDIUM 
    FW_SEMIBOLD 
    FW_DEMIBOLD 
    FW_BOLD 
    FW_EXTRABOLD 
    FW_ULTRABOLD 
    FM_BLACK 
    FW_HEAVY 


    8. Работа с текстом и решение проблемы перерисовки                 205 

    Если создаваемый шрифт должен быть курсивным (italic), значение параметра 
//а/должно быть ненулевым. В противном случае оно должно равняться нулю. Если 
создается шрифте подчеркиванием (underline), значение параметра Underline 
должно быть ненулевым. В противном случае оно должно равняться нулю. Если 
создаваемый шрифт должен быть перечеркнутым (strike-through), значение 
параметра SirikeThru должно быть ненулевым. В противном случае оно должно 
равняться нулю. 
    Параметр Charset определяет базовый набор символов. В следующем примере 
используется набор ANSI_CHARSET. Параметр Precision задает точность, с которой 
отображаемый текст должен соответствовать указанным характеристикам шрифта. В 
следующем примере этот параметр равен OUT_DEFAULT_PRECIS. Значение 
ClipPrecision задает точность отсечения символов. Точность отсечения 
определяет, каким образом символы, выходящие за пределы области отображения, 
будут "отсекаться". В следующем примере значение этого параметра равно CLIP_DEFAULT_PRECIS. 
    Параметр Quality определяет, насколько точно логический шрифт должен 
соответствовать физическому шрифту, который может быть реально отображен на 
экране. Этот параметр может принимать одно из трех значении: 

    DEFAULT_QUALITY 
    DRAFT_QUALITY 
    PROOF_QUALITY 

    Параметр Pitch определяет тип и семейство шрифта. Тип шрифта может 
определяться следующими значениями: 

    DEFAULT_PITCH 
    FIXED_PITCH 
    VARIABLE_PITCH 

    Для семейств шрифтов определены шесть значений: 

    FF_DECORATIVE 
    FF_DONTCARE 
    FF_MODERN 
    FF_ROMAN 
    FF_SCRIPT 
    FF_SWISS 

    Для задания значения параметра Pitch необходимо объединить с помощью 
операции побитового ИЛИ одну константу размера и одну константу семейства шрифта. 
    Указатель на имя шрифта передается в параметре FontName. Имя шрифта не 
должно содержать более 32 символов. Указанный шрифт должен быть инсталлирован 
в системе. 
    В случае успешного завершения функция CreateFontQ возвращает ненулевое 
значение. При возникновении ошибки возвращается ноль. Еще раз подчеркнем, что 
эта функция в действительности не создает нового шрифта. Она формирует его с 
максимально возможной точностью согласно заданным характеристикам из реального 
физического шрифта, доступного в системе. 
    Шрифт, созданный с помощью функции CreateFontQ, должен быть удален из 
памяти до завершения программы. Обычно это выполняется автоматически 
деструктором класса CFont. Но можно сделать это программным путем с помощью 
функции DeleteObjectQ. 

    206                                     MFC: основы программирования 

    Ниже приводится пример программы, демонстрирующий работу с двумя 
пользовательскими шрифтами. Первый создается на основе шрифта Courier New, а 
второй — на основе Century Gothic. Каждый раз при выборе команды меню Change 
Font устанавливается один из этих шрифтов. На его основе выводятся несколько 
строк текста. В программе используется тот же файл ресурсов и библиотечный 
файл 1DS.H, что и в предыдущем примере. Вот текст файла USERFONT.H, 
содержащего объявления порождаемых классов: 

    // USERFONT.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    ( 
    public: 
    int m_X, m_Y; // текущая позиция для вывода 
    char m str[255]; // содержит строку для вывода 
    CDC m memDC; // контекст устройства, связанный с 
    // виртуальным окном 
    CBitmap m_bmp; // образ виртуального окна 
    CBrush m bkbrush; // кисть для виртуального окна 
    CFont m_SystemFont; // встроенный шрифт 
    CFont m CourierNew, m CenturyGothic; // пользовательские шрифты 
    char m fontname[80]; // содержит имя шрифта 
    CMainWin () ; 
    afx msg void OnPaintO; 
    afx msg void OnShowO; 
    afx msg void OnFontO; 
    afx msg void OnResetO; 
    afx msg void OnExitO; 
    afx msq void OnHelpO; 
    DECLARE_MESSAGE__MAP() 
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    public: 
    BOOL Initlnstance () ; 

    Это текст самой программы: 

    // Работа с пользовательскими шрифтами 
    #include <afxwin.h> 
    ^include <string.h> 
    ^include "userfont.h" 
    Hnclude "ids.h" 


    8. Работа с текстом и решение проблемы перерисовки                  207 


    int maxX, maxY; // размеры экрана 
    CMainWin::CMainWin() 
    { 
    Create (NULL, "Demonstrate Custom Fonts", 
    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "TextMenu"); 
    // Загрузка таблицы быстрого доступа 
    if('LoadAccelTable("TextMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    m_X = in_Y = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN); 
    maxY = GetSystemMetrics(SM_CYSCREEN); 
    CClientDC DC(this); 
    // создание образа виртуального окна 
    m_memDC.CreateCompatibleDC(&DC) ; 
    m_bmp.CreateCompatibleBitmap(&DC, maxX, maxY) ; 
    m_memDC.SelectObject (&ni_bmp) ; 
    // выбор стандартного фона 
    m_bkbrush.CreateStockObject(WHITE_BRUSH) ; 
    m memDC.SelectObject(&m bkbrush); 
    // закраска фона виртуального окна 
    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY) ; 
    // инициализация объекта системного шрифта 
    m_SystemFont.Create3tockObject(SYSTEM_FONT) ; 
    // инициализация пользовательских шрифтов 
    m_CourierNew.CreateFont(14, О, О, О, FW_NORMAL, 
    О, О, О, ANSI_CHARSET, 
    OUTJ3E FAULT_PRECIS, 
    CLIP_DEFAULT_PRECIS, 
    DEFAULT_QUALITY, 
    DEFAULT_PITCH | FF_DONTCARE, 
    "Courier New") ; 
    m_CenturyGothic.CreateFont(20, О, О, 0, FW_SEMIBOLD, 
    О, О, О, ANSI_CHARSET, 
    OUT_DEFAULT_PRECIS,                      ; 
    CLIP_DEFAULT_PRECIS, 
    DEFAULT_QUALITY, 
    DEFAULT_PITCH | FF_DONTCARE, 
    "Century Gothic");                       I 
    strcpy(m fontname, "Default");                                      ' 
    } 


    208                                    MFC: основы программирования ' 


    // Инициализация приложения 
    BOOL CApp::lnitlnstance() 
    ( 
    rti pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow) ; 
    m pMainWnd->UpdateWindow(); 
    return TRUE; 
    } 
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_PAINT () 
    ON_COMMAND(IDM_SHOW, OnShow) 
    ON_COMMAND(IDM_FONT, OnFont) 
    ON_COMMAND(IDM_RESET, OnReset) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    // Вывод на экран содержимого виртуального окна 
    afx msg void CMainWin::OnPaint() 
    ( 
    CPaintDC DC(this) ; 
    DC.BitBlt(0, 0, maxX, maxY, &m_memDC, О, О, SRCCOPY) ; 
    ) 
    // Обработка сообщения IDM_EXIT 
    afx msg void CMainWin::OnExit() 
    ( 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB_YESNO) ; 
    if (response == IDYES) 
    SendMessage(WM CLOSE); // завершение приложения 
    1 
    // Обработка сообщения IDM HELP 
    afx msg void CMainWin::OnHelp () 
    ( 
    MessageBox("Text Demo", "Help"); 
    1 
    // Вывод текста 
    afx_msg void CMainWin::OnShow() 
    ( 
    TEXTMETRIC tm; 
    CSize size; 
    // цвет символов устанавливается черным 


    8. Работа с текстом и решение проблемы перерисовки                  209 


    m_memDC.SetTextColor(RGB(0, 0, 0) ) ; 
    // получение метрик текста 
    m__memDC .GetTextMetrics (&tm) ; 
    wsprintf(m str, "%s is %ld pixels high.", 
    m fontname, tm.tmHeight); 
    m_memDC.TextOut(m_X, m_Y, m_str, strlen(m_str)); // вывод строки 
    m_Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    strcpy(m_str, "This is on the next line."); 
    m memDC.TextOut(m X, m Y, m str, strlen(m str)); // вывод строки 
    // вычисление длины строки 
    size = m memDC.GetTextExtent(m str, strlen(m str)); 
    wsprintf(m str, "Previous string is %ld units long", size.ex); 
    m_X = size.ex; // переход на конец предыдущей строки 
    m_memDC.TextOut(m_X, m_Y, m_str, strlen(m_str)); 
    m_Y = m_Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    m X = 0; // сброс координаты Х 
    wsprintf(m str, "Screen dimensions: %d %d", maxX, maxY); 
    m_memDC.TextOut(m_X, m_Y, m_str, strlen(m_str)); 
    m Y ^ m Y + tm.tmHeight + tm.tmExternalLeading; // переход на 
    // следующую строку 
    InvalidateRect(NULL) ; 
    } 
    /I Обнуление текущих координат и очистка окна 
    afx msg void CMainWin::OnReset() 
    ( 
    m_X = m_Y = 0; 
    m_memDC.PatBIt(0, 0, maxX, maxY, PATCOPY) ; 
    InvalidateRect(NULL) ; 
    } 
    // Смена шрифтов 
    afx msg void CMainWin::OnFont() 
    ( 
    static whichfont = 0; 
    switch(whichfont) 
    ( 
    case 0: 
    m_memDC.SelectObject(&m_CourierNew) ; 
    whichfont = 1; 
    strcpy(m fontname, "Courier New"); 
    break; 
    case 1:                                                           i 


    210                                     MFC: основы программирования 


    m_memDC.SelectObject(&m_CenturyGothic) ; 
    whichfont = 2; 
    strcpy(m fontname, "Century Gothic"); 
    break; 
    case 2: 
    m_rnemDC .Select Object) &m_SystemFont) ; 
    whichfont = 0; 
    strcpy(m_fontname, "Default") ; 
    break; 
    ) 
    CApp App; // создание экземпляра приложения 

    Пример работы программы показан на рис. 8.3. Помните, что в Windows 
имеются достаточно богатые средства поддержки шрифтов и функций работы с 
текстом. При желании можно исследовать эту область самостоятельно. Но, как вы, 
видимо, уже убедились, использование библиотеки MFC существенно облегчает 
работу с текстом. 

    Рис. 8.3. Работа с пользовательскими шрифтами 

    8. Работа с текстом и решение проблемы перерисовки                    211