Основы программирования с помощью MFC

Интерфейс вызовов функций в Windows

//

1. Введение.Эти главы являются некоторым пособием для тех, кто хочет познакомиться с языком программирования C++ и Visual C++.
  • Некоторые сведения о Windows-программировании
  • Программная среда Windows
  • Основы программирования для Windows
  • Преимущества использования библиотеки MFC

  • Обзор средств Microsoft Developer Studio
  • Библиотека MFC
  • Архитектура приложения
  • Каркас приложения
  • Каркас приложений
  • Проект приложения

  • Использование средств разработки
  • Типы мастеров проектов
  • Преимущества мастеров проектов
  • Обзор возможностей ClassWizard
  • Имена, используемые в MFC

  • В связи с тем, что сегодня уровень сложности программного обеспечения очень высок, разработка приложений Windows с использованием только какого-либо языка программирования (например, языка C) значительно затрудняется. Программист должен затратить массу времени на решение стандартных задач по созданию многооконного интерфейса. Реализация технологии связывания и встраивания объектов - OLE - потребует от программиста еще более сложной работы.
    Чтобы облегчить работу программиста практически все современные компиляторы с языка C++ содержат специальные библиотеки классов. Такие библиотеки включают в себя практически весь программный интерфейс Windows и позволяют пользоваться при программировании средствами более высокого уровня, чем обычные вызовы функций. За счет этого значительно упрощается разработка приложений, имеющих сложный интерфейс пользователя, облегчается поддержка технологии OLE и взаимодействие с базами данных.
    Современные интегрированные средства разработки приложений Windows позволяют автоматизировать процесс создания приложения. Для этого используются генераторы приложений. Программист отвечает на вопросы генератора приложений и определяет свойства приложения - поддерживает ли оно многооконный режим, технологию OLE, трехмерные органы управления, справочную систему. Генератор приложений, создаст приложение, отвечающее требованиям, и предоставит исходные тексты. Пользуясь им как шаблоном, программист сможет быстро разрабатывать свои приложения.

    Подобные средства автоматизированного создания приложений включены в компилятор Microsoft Visual C++ и называются MFC AppWizard. Заполнив несколько диалоговых панелей, можно указать характеристики приложения и получить его тексты, снабженные обширными комментариями. MFC AppWizard позволяет создавать однооконные и многооконные приложения, а также приложения, не имеющие главного окна, -вместо него используется диалоговая панель. Можно также включить поддержку технологии OLE, баз данных, справочной системы.

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

    Нужно отметить, что MFC AppWizard создает тексты приложений только с использованием библиотеки классов MFC (Microsoft Foundation Class library). Поэтому только изучив язык C++ и библиотеку MFC, можно пользоваться средствами автоматизированной разработки и создавать свои приложения в кратчайшие сроки.

    Некоторые сведения о программировании Windows-приложений

    MFC – это базовый набор (библиотека) классов, написанных на языке С++ и предназначенных для упрощения и ускорения процесса программирования под Windows. Перед изучением библиотеки MFC и ее использованием для создания Windows-приложений, следует вспомнить, как работает сама Windows и каковы принципы взаимодействия программ с ней, какова структура типичной Windows-программы.

    Программная среда Windows

    Рассмотрим наиболее важные моменты работы Windows и принципы взаимодействия программ с ней.



    Интерфейс вызовов функций в Windows



    Благодаря данному интерфейсу доступ к системным ресурсам осуществляется через целый рад системных функций. Совокупность таких функций называется прикладным программным интерфейсом, или API (Application Programming Interfase).


    Для взаимодействия с Windows приложение запрашивает функции API, с помощью которых реализуются все необходимые системные действия, такие как выделение памяти, вывод на экран, создание окон и т.п.

    Библиотека MFC инкапсулирует многие функции API. Хотя программам и разрешено обращаться к ним напрямую, все же чаще это будет выполняться через соответствующие функции-члены. Как правило, функции-члены либо аналогичны функциям API, либо непосредственно обращаются к нужной части интерфейса.



    Библиотеки динамической загрузки (DLL)



    Поскольку API состоит из большого числа функций, может сложиться впечатление, что при компиляции каждой программы, написанной для Windows, к ней подключается код довольно значительного объема. В действительности это не так. Функции API содержатся в библиотеках динамической загрузки (Dynamic Link Libraries, или DLL), которые загружаются в память только в тот момент, когда к ним происходит обращение, т.е. при выполнении программы. Рассмотрим, как осуществляется механизм динамической загрузки.

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



    Win16 или Win32



    В настоящее время широко распространены две версии API. Первая называется Win16 и представляет собой 16-разрядную версию, используемую в Windows 3.1. Вторая, 32-разрядная версия, называется Win32 и используется в Windows 95 и Windows NT. Win32 является надмножеством для Win16 (т.е. фактически включает в себя этот интерфейс), так как большинство функций имеет то же название и применяется аналогичным образом. Однако, будучи в принципе похожими, оба интерфейса все же отличаются друг от друга.


    Win32 поддерживает 32- разрядную линейную адресацию, тогда как Win16 работает только с 16-разрядной сегментированной моделью памяти. Это привело к тому, что некоторые функции были модифицированы таким образом, чтобы принимать 32-разрядные аргументы и возвращать 32-разрядные значения. Часть из них пришлось изменить с учетом 32-разрядной архитектуры. Была реализована поддержка потоковой многозадачности, новых элементов интерфейса и прочих нововведений Windows.

    Так как Win32 поддерживает полностью 32-разрядную адресацию, то логично, что целые типы данных (intergers) также объявлены 32-разрядными. Это означает, что переменные типа int и unsignerd будут иметь длину 32 бита, а не 16, как в Windows 3.1. Если же необходимо использовать переменную или константу длиной 16 бит, они должны быть объявлены как short. (дальше будет показано, что для этих типов определены независимые typedef-имена.) Следовательно, при переносе программного кода из 16-разрядной среды необходимо убедиться в правильности использования целочисленных элементов, которые автоматически будут расширены до 32 битов, что целочисленных элементов, которые автоматически будут расширены до 32 битов, что может привести к появлению побочных эффектов.

    Другим следствием 32-разрядной адресации является то, что указатели больше не нужно объявлять как near и far. Любой указатель может получить доступ к любому участку памяти. В Windows 95 и Windows NT константы near и far объявлены (с помощью директивы #define)пустыми.



    Интерфейс GDI



    Одним из подмножеств API является GDI (Graphics Device Interfase – интерфейс графического устройства). GDI – это та часть Windows, которая обеспечивает поддержку аппаратно-независимой графики. Благодаря функциям GDI Windows-приложение может выполняться на самых различных компьютерах.



    Многозадачность в Windows



    Как известно, все версии Windows поддерживают многозадачность. В Windows 3.1 имеется только один тип многозадачности – основанный на процессах. В более передовых системах, таких как Windows 95 и Windows NT, поддерживается два типа многозадачности: основанный на процессах и основанный на потоках.


    Давайте рассмотрим их чуть подробнее.

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

    Поток – это отдельная часть исполняемого кода. Название произошло от понятия “направление протекания процесса”. В многозадачности данного типа отдельные потоки внутри одного процесса также могут выполняться одновременно. Все процессы имеют по крайней мере один поток, но в Windows 95 и Windows NT их может быть несколько.

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

    Есть и другое существенное различие между многозадачностями Windows 3.1 и Windows 95/NT. В Windows 3.1 используется неприоритетная многозадачность. Это означает, что процесс, выполняющийся в данный момент, получает доступ к ресурсам центрального процессора и удерживает их в течение необходимого ему времени. Таким образом, неправильно выполняющаяся программа может захватить все ресурсы процессора и не давать выполняться другим процессам. В отличие от этого в Windows 95 и Windows NT используется приоритетная многозадачность. В этом случае каждому активному потоку предоставляется определенный промежуток времени работы процессора. По истечению данного промежутка управление автоматически передается следующему потоку. Это не дает возможность программам полностью захватывать ресурсы процессора. Интуитивно должно быть понятно, что такой способ более предпочтителен.



    Взаимодействие программ и Windows



    Во многих операционных системах взаимодействие между системой и программой инициализирует программа.


    Например, в DOS программа запрашивает разрешение на ввод и вывод данных. Говоря другими словами, не- Windows-программы сами вызывают операционную систему. Обратного процесса не происходит. В Windows все совершенно наоборот: именно система вызывает программу. Это осуществляется следующим образом: программа ожидает получения сообщения от Windows. Когда это происходит, то выполняется некоторое действие. После его завершения программа ожидает следующего сообщения.

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

    Основы программирования под Windows

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



    Функция WinMain()



    Все Windows-программы начинают выполнение с вызова функции WinMain(). При традиционном методе программирования это нужно делать явно. С использованием библиотеки MFC такая необходимость отпадает, но функция все-таки существует.



    Функция окна



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


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

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

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



    Цикл сообщений



    Как объяснялось выше, Windows взаимодействует с программой, посылая ей сообщения. Все приложения Windows должны организовать так называемый цикл сообщений (обычно внутри функции WinMain()). В этом цикле каждое необработанное сообщение должно быть извлечено из очереди сообщений данного приложения и передано назад в Windows, которая затем вызывает функцию окна программы с данным сообщением в качестве аргумента. В традиционных Windows-программах необходимо самостоятельно создавать и активизировать такой цикл. При использовании MFC это также выполняется автоматически.


    Однако важно помнить, что цикл сообщений все же существует. Он является неотъемлемой частью любого приложения Windows.

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



    Класс окна



    Как будет показано дальше, каждое окно в Windows-приложении характеризуется определенными атрибутами, называемыми классом окна. (Здесь понятие “класс” не идентично используемому в С++. Оно, скорее, означает стиль или тип.) В традиционной программе класс окна должен быть определен и зарегистрирован прежде, чем будет создано окно. При регистрации необходимо сообщить Windows, какой вид должно иметь окно и какую функцию оно выполняет. В то же время регистрация класса окна еще не означает создание самого окна. Для этого требуется выполнить дополнительные действия. При использовании библиотеки MFC создавать собственный класс окна нет необходимости. Вместо этого можно работать с одним из заранее определенных классов, описанных в библиотеке. В этом еще одно ее преимущество.



    Специфика программ для Windows



    Структура Windows-программ отличается от структуры программ других типов. Это вызвано двумя обстоятельствами: во-первых, способом взаимодействия между программой и Windows, описанным выше; во-вторых, правилами, которым следует подчиняться для создания стандартного интерфейса Windows-приложения (т.е. чтобы сделать программу “похожей “ на Windows-приложение).

    Цель Windows – дать человеку, который хотя бы немного знаком с системой, возможность сесть за компьютер и запустить любое приложение без предварительной подготовки. Для этого Windows предоставляет дружественный интерфейс пользователя. Теоретически, если пользователь сумел запустить одно Windows-приложение, то он сумеет запустить и любое другое. Конечно, на практике придется немного потренироваться, чтобы научиться использовать большинство программ с максимальной эффективностью.


    Однако это связано исключительно с тем, что программа делает, а не с тем, как ею пользоваться. Ведь, фактически, значительная часть кода Windows-приложения предназначена именно для организации интерфейса с пользователем.

    Хотя создание удобного интерфейса “под Windows” является основной задачей при написании любой Windows-программы, такой интерфейс не создается автоматически. То есть вполне можно написать программу, в которой элементы интерфейса используются неэффективно. Чтобы этого избежать, необходимо целенаправленно применять методику, описанную в данной книге. Только программы, написанные таким способом, будут выглядеть и работать действительно так, как надлежит Windows-программам.

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



    Типы данных в Windows



    В Windows-программах вообще (и в использующих библиотеку MFC в частности) не слишком широко применяются стандартные типы данных из С или С++, такие как int или char*. Вместо них используются типы данных, определенные в различных библиотечных (header) файлах. Наиболее часто используемыми типами являются HANDLE, HWND, BYTE, WORD, DWORD, UNIT, LONG, BOOL, LPSTR и LPCSTR. Тип HANDLE обозначает 32-разрядное целое, используемое в качестве дескриптора. Есть несколько похожих типов данных, но все они имеют ту же длину, что и HANDLE, и начинаются с литеры Н. Дескриптор – это просто число, определяющее некоторый ресурс. Например, тип HWND обозначает 32-разрядное целое – дескриптор окна. В программах, использующих библиотеку MFC, дескрипторы применяются не столь широко, как это имеет место в традиционных программах. Тип BYTE обозначает 8-разрядное беззнаковое символьное значение, тип WORD – 16-разрядное беззнаковое короткое целое, тип DWORD – беззнаковое длинное целое, тип UNIT - беззнаковое 32-разрядное целое.


    Тип LONG эквивалентен типу long. Тип BOOL обозначает целое и используется, когда значение может быть либо истинным, либо ложным. Тип LPSTR определяет указатель на строку, а LPCSTR – константный (const) указатель на строку.

    Преимущества использования MFC

    Как уже упоминалось, MFC – это базовый набор (библиотека) классов, написанных на языке С++ и предназначенных для упрощения и ускорения процесса программирования для Windows. Библиотека содержит многоуровневую иерархию классов, насчитывающую около 200 членов. Они дают возможность создавать Windows-приложения на базе объектно-ориентированного подхода. С точки зрения программиста, MFC представляет собой каркас, на основе которого можно писать программы для Windows.

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

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

    Еще одним существенным преимуществом MFC является упрощение взаимодействия с прикладным программным интерфейсом (API) Windows.


    Любое приложение взаимодействует с Windows через API, который содержит несколько сот функций. Внушительный размер API затрудняет попытки понять и изучить его целиком. Зачастую даже сложно проследить, как отдельные части API связанны друг с другом! Но поскольку библиотека MFC объединяет (путем инкапсуляции) функции API в логически организованное множество классов, интерфейсом становится значительно легче управлять.

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



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

    Обзор среды Microsoft Developer Studio

    Студия разработчика фирмы Microsoft (Microsoft Developer Studio) - это интегрированная среда для разработки, позволяющая функционировать различным средам разработки, одна из которых Visual C++, другая - Visual J++. В дальнейшем будет идти речь только о среде разработки Visual C++.

    В студии разработчика можно строить обычные программы на C и С++, создавать статические и динамические библиотеки, но основным режимом работы является создание Windows-приложений с помощью инструмента MFC AppWizard (Application Wizard - мастер приложений) и библиотеки базовых классов MFC (Microsoft Foundation Class Library). Такие приложения называются MFC-приложениями.


    Главная особенность этих Windows-приложений состоит в том, что они работают как совокупность взаимодействующих объектов, классы которых определены библиотекой MFC.

    Библиотека MFC

    Главная часть библиотеки MFC состоит из классов, используемых для построения компонентов приложения. С каждым MFC-приложением связывается определяющий его на верхнем уровне объект theApp, принадлежащий классу, производному от CWinApp.

    Как правило, структура приложения определяется архитектурой Document-View (документ-облик). Это означает, что приложение состоит из одного или нескольких документов - объектов, классы которых являются производными от класса CDocument (класс "документ"). С каждым из документов связаны один или несколько обликов - объектов классов, производных от CView (класс "облик ") и определяющих облик документа.

    Класс CFrameWnd ("окна-рамки") и производные от него определяют окна-рамки на дисплее. Элементы управления, создаваемые при проектировании интерфейса пользователя, принадлежат семейству классов элементов управления. Появляющиеся в процессе работы приложения диалоговые окна - это объекты классов, производных от CDialog.

    Классы CView, CFrameWnd, CDialog и все классы элементов управления наследуют свойства и поведение своего базового класса CWnd ("окно"), определяющего по существу Windows-окно. Этот класс в свою очередь является наследником базового ласса CObject ("объект").

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

    Архитектура приложения

    У всех Windows-приложений фиксированная структура, определяемая функцией WinMain. Структура приложения, построенного из объектов классов библиотеки MFC, является еще более определенной.

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


    Работа функции WinMain заключается в последовательном вызове двух методов объекта theApp: InitInstance и Run. В терминах сообщений можно сказать, WinMain посылает объекту theApp сообщение InitInstance, которое приводит в действие метод InitInstance.

    Получив сообщение InitInstance, theApp создает внутренние объекты приложения. Процесс создания выглядит как последовательное порождение одних объектов другими. Набор объектов, порождаемых в начале этой цепочки, определен структурой MFC практически однозначно - это главная рамка, шаблон, документ, облик. Их роли в работе приложения будут обсуждаться позже.

    Следующее сообщение, получаемое theApp, - Run - приводит в действие метод Run. Оно как бы говорит объекту: "Начинай работу, начинай процесс обработки сообщений из внешнего мира". Объект theApp циклически выбирает сообщения из очереди и инициирует обработку сообщений объектами приложения.

    Некоторые объекты имеют графический образ на экране, с которым может взаимодействовать пользователь. Эти интерфейсные объекты обычно связаны с Windows-окном. Среди них особенно важны главная рамка и облик. Именно им объект прежде всего распределяет сообщения из очереди через механизм Windows-окон и функцию Dispatch.

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

    При работе приложения возникают и обычные вызовы одними объектами методов других объектов. В объектно-ориентированной терминологии такие вызовы могут называться сообщениями. В Visual C++ некоторым методам приписан именно этот статус (например, методу OnDraw).

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


    Каркас приложения

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

    Объекты, их которых состоит приложение, являются объектами классов, производных от классов библиотеки MFC. Разработка приложения состоит в том, что программист берет из библиотеки MFC классы CWinApp, CFrameWnd, CDocument, CView и т.д. и строит производные классы. Приложение создается как совокупность объектов этих производных классов. Каждый объект несет в себе как наследуемые черты, определяемые базовыми классами, так и новые черты, добавленные программистом. Наследуемые черты определяют общую схему поведения, свойственную таким приложениям. Новые же черты позволяют реализовать специфические особенности поведения приложения, необходимые для решения стоящей перед ним задачи.

    При определении производного класса программист может:

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


  • Приложение, построенное на основе библиотеки MFC, - "айсберг", большая часть которого невидима, но является основой всего приложения. Часть приложения, лежащую в библиотеке MFC, - framework - называется каркасом приложения. Рассмотрим работу приложения как процесс взаимодействия между каркасом и частью приложения, разработанной программистом. Совершенно естественно, что в методах, определенных программистом, могут встречаться вызовы методов базового класса, что вполне можно рассматривать как вызов функции из библиотеки. Важнее, однако, что и метод производного класса, определенный программистом, может быть вызван из метода родительского класса.


    Другими словами, каркас и производный класс в этом смысле равноправны - их методы могут вызывать друг друга. Такое равноправие достигается благодаря виртуальным методам и полиморфизму, имеющимся в арсенале объектно-ориентированного программирования.

    Если метод базового класса объявлен виртуальным и разработчик переопределил его в производном классе, это значит, что при вызове данного метода в некоторой полиморфной функции базового класса в момент исполнения будет вызван метод производного класса и, следовательно, каркас вызывает метод, определенный программистом. Точнее говоря, обращение к этому методу должно производиться через ссылку на производный объект либо через объект, являющийся формальным параметром и получающий при вызове в качестве своего значения объект производного класса. Когда вызывается виртуальный метод М1, переопределенный разработчиком, то согласно терминологии Visual C++, каркас посылает сообщение М1 объекту производного класса, а метод М1 этого объекта обрабатывает это сообщение. Если сообщение М1 послано объекту производного класса, а обработчик этого сообщения не задан программистом, объект наследует метод М1 ближайшего родительского класса, в котором определен этот метод. Если же обработчик такого сообщения создан программистом, он автоматически отменяет действия, предусмотренные родительским классом в отсутствие этого обработчика.

    Каркас приложений

    С Visual C++ тесно связано еще одно понятие - каркас приложений, которое близко и созвучно понятию каркаса приложения, но в отличие от него относится не к одному конкретному приложению, а к библиотеке, с помощью которой строятся многие приложения. Каркас приложений - это библиотека классов, из которых программист берет не только набор классов, играющих роль дополнительных типов данных, но и классы, служащие строительными блоками приложения на самом верхнем уровне. С этой точки зрения, каркас приложения является частью каркаса приложений, относящейся к данному приложению. Примеры каркасов приложений - библиотеки классов MFC и OWL.


    Проект приложения

    О принципах устройства приложения рассказывалось выше. Теперь рассмотрим, как оно создается с помощью Visual C++. Сначала разберем одно важное понятие - проект. До сих пор приложение рассматривалось, как только как совокупность объектов базовых и производных классов. Но для обеспечения работы приложения требуется нечто большее - наряду с описанием классов необходимо описание ресурсов, связанных с приложением, нужна справочная система и т.п. Термин "проект" как раз и используется, когда имеется в виду такой более общий взгляд на приложение.

    В среде Visual C++ можно строить различные типы проектов. Такие проекты после их создания можно компилировать и запускать на исполнение. Фирма Microsoft разработала специальный инструментарий, облегчающий и ускоряющий создание проектов в среде Visual C++. Например, мастер MFC AppWizard (exe) позволяет создать проект Windows-приложения которое имеет однодокументный, многодокументный или диалоговый интерфейс и использует библиотеку MFC.

    Создаваемый остов приложения составлен так, что в дальнейшей работе с проектом можно использовать другое инструментальное средство - ClassWizard (мастер классов), предназначенное для создания остовов новых производных классов. Еще одно основное назначение ClassWizard в том, что он создает остовы для переопределяемых методов. Он позволяет показать все сообщения, приходящие классу, и создать остов обработчика любого из этих сообщений. Это только две основные функции ClassWizard. Он не всесилен, но его возможности довольно велики.

    Использование средств разработки

    В состав компилятора Microsoft Developer Studio встроены средства, позволяющие программисту облегчить разработку приложений. В первую очередь к ним относятся MFC AppWisard, ClassWizard и редактор ресурсов.

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


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

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

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

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

    Типы мастеров проектов

    В среде Visual C++ можно строить различные типы проектов. Такие проекты после их создания можно компилировать и запускать на исполнение. Фирма Microsoft разработала специальный инструментарий, облегчающий и ускоряющий создание проектов в среде Visual C++.

    Рассмотрим некоторые типы проектов, которые можно создавать при помощи различных средств (мастеров проектов) Microsoft Visual C++:



    MFC AppWizard (exe)
    – при помощи мастера приложений можно создать проект Windows-приложения которое имеет однодокументный, многодокументный или диалоговый интерфейс. Однодокументное приложеие может предоставлять пользователю в любой момент времени работать только с одним файлом. Многодокументное приложение, напротив, может одновременно представлять несколько документов, каждый в собственном окне. Пользовательский интерфейс диалогового приложения представляет собой единственное диалоговое окно.



    MFC AppWizard (dll) –
    этот мастер приложений позволяет создать структуру DLL, основанную на MFC. При помощи него можно определить характеристики будующей DLL.



    AppWizard ATL COM
    – это средство позволяет создать элемент управления ActiveX или сервер автоматизации, используя новую библиотеку шаблонов ActiveX (ActiveX Template Library - ATL). Опции этого мастера дают возможность выбрать активный сервер (DLL) или исполняемый внешний сервер (exe-файл).




    Custom AppWizard
    – при помощи этого средства можно создать пользовательские мастера AppWizard. Пользовательский мастер может базироваться на стандартных мастерах для приложений MFC или DLL, а также на существующих проектах или содержать только определеямые разработчиком шаги.



    DevStudio Add-in Wizard
    – мастер дополнений позволяет создавать дополнения к Visual Studio. Библиотека DLL расширений может поддерживать панели инструментов и реагировать на события Visual Studio.



    MFC ActiveX ControlWizard
    - мастер элементов управления реализует процесс создания проекта, содержащего один или несколько элементов управления ActiveX, основанных на элементах управления MFC.



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



    Win32 Console Application
    – мастер создания проекта консольного приложения. Консольная приложение – это программа, которая выполняется из командной cтроки окна DOS или Windows и не имеет графического интерфейса (окон). Проект консольного приложения создается пустым, предполагая добавление файлов исходного текста в него вручную.



    Win32 Dynamic-Link Library
    – создание пустого проекта динамически подключаемой библиотеки. Установки компилятора и компоновщика будут настроены на создание DLL. Исходные файлы следует добавлять вручную.



    Win32 Static Library
    – это средство создает пустой проект, предназначенный для генерации статической (объектной) библиотеки. Файлы с исходным кодом в него следует добавлять вручную.

    Преимущества мастеров проектов

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

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


    Построению таких заготовок способствует то, что приложения, создаваемые на основе MFC, строятся из элементов фиксированных классов. Логическим развитием этой идеи было введение специальных классов и специальной архитектуры построения приложения, которая подходила бы широкому классу приложений. О такой архитектуре уже упоминалось, когда речь шла о библиотеке MFC, - это архитектура Document-View. Она является основной, но не единственной при построении проектов в среде Visual C++.

    Суть этой архитектуры в том, что работу многих приложений можно рассматривать как обработку документов. При этом можно отделить сам документ, отвечающий за представление и хранение данных, от образа этого документа, видимого на экране и допускающего взаимодействие с пользователем, который просматривает и (или) редактирует документ. В соответствии с этой архитектурой библиотека MFC содержит два семейства классов, производных от базовых классов CDocument и CView.

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

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


    Тем не менее стартовое приложение можно транслировать и запускать на исполнение.

    Термин остов (приложения, класса, функции) применяется для заготовок, создаваемых инструментальными средствами AppWizard и ClassWizard. Нужно подчеркнуть - остов приложения и каркас приложения - разные понятия.

    Создаваемый остов приложения составлен так, что в дальнейшей работе с проектом можно использовать другое инструментальное средство - ClassWizard (мастер классов).

    Обзор возможностей ClassWizard

    Средство ClassWizard предоставляет широкий спектр услуг. Он позволяет не только добавлять к существующему классу новые методы и данные.



    Создание нового класса.
    При помощи ClassWizard можно добавить новый класс, созданный на основе базовых классов. В качестве базового класса можно использовать классы, наследованные от класса CCmdTarget или класса CRecordset. Для наследования классов от других базовых классов использовать средства ClassWizard нельзя. Такие классы надо создавать вручную, непосредственно в текстовом редакторе.

    Объекты, порожденные от класса CCmdTarget, могут обрабатывать сообщения Windows и команды, поступающие от меню, кнопок, акселераторов. Класс CCmdTarget и другие наследованные от него классы имеют таблицу сообщений (Message Map) - набор макрокоманд, позволяющий сопоставить сообщения Windows и команды метода класса.

    Полученная заготовка класса полностью работоспособна. Ее можно дополнить по своему усмотрению новыми методами и данными. Эту работу можно выполнить вручную, но гораздо лучше и проще воспользоваться услугами ClassWizard. За счет использования ClassWizard процедура создания собственного класса значительно ускоряется и уменьшается вероятность совершить ошибку во время объявления методов.



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


    ClassWizard не только позволяет добавить в класс новые методы, но и удалить их. ClassWizard самостоятельно удалит объявление метода из класса.



    Включение в класс новых элементов данных.
    ClassWizard позволяет включать в класс не только новые методы, но и элементы данных, связанные с полями диалоговых панелей, форм просмотра и форм для просмотра записей баз данных и полей наборов записей. ClassWizard использует специальные процедуры, чтобы привязать созданные им элементы данных к класса к полям диалоговых панелей. Эти процедуры носят названия "обмен данными диалоговой панели" и "проверка данных диалоговой панели" (Dialog Data Exchange and Dialog Data Validation - DDX/DDV). Чтобы привязать поля из наборов записей к переменным, используется процедура обмена данными с полями записей (Record Field Exchange - RFX).

    Процедуры DDX/DDV и RFX значительно упрощают программисту работу с диалоговыми панелями. Они позволяют связать поля диалоговых панелей и переменные. Когда пользователь редактирует поля диалоговых панелей, процедуры DDV проверяют введенные значения и блокируют ввод запрещенных значений. Затем процедуры DDX автоматически копируют содержимое полей диалоговых панелей в привязанные к ним элементы данных класса. И наоборот, когда приложение изменяет элементы данных класса, привязанные к полям диалоговой панели, процедуры DDX могут сразу отобразить новые значения полей на экране компьютера.

    Имена, используемые в MFC

    Библиотека MFC содержит большое количество классов, структур, констант и т.д. Для того, чтобы текст MFC-приложений был более легким для понимания, принято применять ряд соглашений для используемых имен и комментариев.

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

    Чтобы отличить элементы данных, входящих в класс, от простых переменных, их имена принято начинать с префикса m_.


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

    Библиотека MFC включает в себя, помимо классов, набор служебных функций. Названия этих функций начинаются с символов Afx, например AfxGetApp. Символы AFX являются сокращением от словосочетания Application FrameworkX, означающих основу приложения, его внутреннее устройство.

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

    Когда приложение разрабатывается средствами MFC AppWizard и ClassWizard, они размещают в исходном тексте приложения комментарии следующего вида:

    //{{AFX_ ... //}}AFX_

    Такие комментарии образуют блок кода программы, который управляется только средствами MFC AppWizard и ClassWizard. Пользователь не должен вручную вносить изменения в этом блоке. Для этого необходимо употреблять средства ClassWizard.

    В следующей таблице представлено краткое описание некоторых блоков //{{AFX_:



    Блок


    Описание


    //{{AFX_DATA
    //}}AFX_DATA


    Включает объявление элементов данных класса. Используется в описании классов диалоговых панелей.


    //{{AFX_DATA_INIT
    //}}AFX_DATA_INIT


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


    //{{AFX_DATA_MAP
    //}}AFX_DATA_MAP


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


    //{{AFX_MSG
    //}}AFX_MSG


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


    //{{AFX_MSG_MAP
    //}}AFX_MSG_MAP


    Включает макрокоманды таблицы сообщений класса. Используются совместно с AFX_MSG.


    //{{AFX_VIRTUAL
    //}}AFX_VIRTUAL


    Включает описание переопределенных виртуальных методов класса. Блок AFX_VIRTUAL используется при описании класса.
    <


    MFC AppWizard и ClassWizard помогают разрабатывать приложения. Они создают все классы и методы, необходимые для его работы. Программисту остается дописать к ним свой код. В тех местах, где можно вставить свой код, MFC AppWizard и ClassWizard, как правило помещают комментарии:

    //TODO:
    Для того что бы перейти к Visual C++, целесообразно получить некоторое представление о просто языке C++, так как он является базовым. Что бы начать обучение вам нужно сначала поставить какую-нибудь версию Visual C++. Лучше, конечно, если у вас есть место на HDD, поставить Visual C++ версии 6. Но можно поставить версию 5 или 4. Все мои примеры написаны на шестой версии, но они должны работать и на младших версиях. Многие думают, что если они поставили Visual C++, то могут писать программы только под Windows, но это не правильно. Visual C++ позволяет писать программы и на простом C++, как бы под DOS. Ну вот, я думаю, и можно начинать.

    К списку






    Использование класса CTab Ctrl Ernest Avagyan



    20. Использование класса CTabCtrl
    Tab control - это мощное средство для решению многих проблем в интерфейсе приложений. Оно позволят существенно увеличить скорость работы вашего приложения, разбить на "части" диалог в удобной для пользователя форме.

    В MFC есть встроенный класс по работе с Tab control - класс CTabCtrl.

    Для практики напишем программу, которая будет использовать класс CTabCtrl и в которой будет три "закладки" - диалога.

    Шаги создания проекта:
    1) Сначала создадим проект tab_control типа диалог. 2) В редакторе ресурсов добавить Tab Control в шаблон диалога и назначим ему ID = IDC_TAB. 3) Используя ClassWizard, добавим переменную-член типа CTabCtrl со свойством Control. 4) В OnInitDialog проинициализируем необходимые переменные для CTabCtrl. 5) Используя ClassWizard, добавим обработку необходимых сообщений от Tab control 'я. 6) Удалим за собой ненужные переменные.

    Для начала сделайте первые три пункта, создайте переменную m_ctrTab класса CTabCtrl. После этого в функцие BOOL CTab_controlDlg::OnInitDialog() добавте следующее: ...
    TC_ITEM TabItem; TabItem.mask = TCIF_TEXT; TabItem.pszText = "Закладка1"; m_ctrTab.InsertItem( 0, &TabItem ); TabItem.pszText = "Закладка2"; m_ctrTab.InsertItem( 1, &TabItem ); TabItem.pszText = "Закладка3"; m_ctrTab.InsertItem( 2, &TabItem );
    ...

    Это код инициализации Tab Control, мы создаём три закладки. Теперь нам надо, чтобы при нажатие на любую закладку, на экране появлялось то, что нам нужно. Самый простой вариант - это использовать на каждую закладку по диалогу - и потом просто в области Tab Control'а - выводить нужный диалог, в зависимости от текущей закладки.

    Сделаем это. Добавим три диалога в редакторе ресурсов и создадим каждому из них по классу - наследнику от CDialog. Назовем эти классы CPage1, CPage2 и CPage3( файлы Page1.cpp(h), Page2.cpp(h), Page3.cpp(h) ) .

    В свойствах этих трёх диалогов поставте Style как "Child" и Border как "none" - это очень важно, а в самих диалогах создайте какие либо элементы ( например, типа Static Text ), чтобы было видно отличие.

    Использование таймера Ernest Avagyan



    10. Использование таймера.

    1. Введение
    2. Создание и уничтожение таймера
    3. Сообщение WM_TIMER
    4. Первый способ использования таймера
    5. Второй способ использования таймера
    6. Пример Windows-приложения, использующего таймер

    1. Введение
    Во многих программах требуется следить за временем или выполнять какие-либо периодические действия. Программы MS-DOS для работы с таймером перехватывали аппаратное прерывание таймера, встраивая свой собственный обработчик для прерывания INT8h. Обычные приложения Windows не могут самостоятельно обрабатывать прерывания таймера, поэтому для работы с ним нужно использовать другие способы.
    Операционная система Windows позволяет для каждого приложения создать несколько виртуальных таймеров. Все эти таймеры работают по прерываниям одного физического таймера.
    Так как работа Windows основана на передаче сообщений, логично было бы предположить, что и работа виртуального таймера также основана на передаче сообщений. И в самом деле, приложение может заказать для любого своего окна несколько таймеров, которые будут периодически посылать в функцию окна сообщение с кодом WM_TIMER.
    Есть и другой способ, также основанный на передаче сообщений. При использовании этого способа сообщения WM_TIMER посылаются не функции окна, а специальной функции, описанной с ключевым словом _export. Эта функция напоминает функцию окна и, так же как и функция окна, вызывается не из приложения, а из Windows. Функции, которые вызываются из Windows, имеют специальный пролог и эпилог и называются функциями обратного вызова (callback function). Функция окна и функция, специально предназначенная для обработки сообщений таймера, являются примерами функций обратного вызова.
    К сожалению, точность виртуального таймера оставляет желать лучшего. Сообщения таймера проходят через очередь приложения, к тому же другое приложение может блокировать на некоторое время работу вашего приложения. Поэтому сообщения от таймера приходят в общем случае нерегулярно. Кроме того, несмотря на возможность указания интервалов времени в миллисекундах, реальная дискретность таймера определяется периодом прерываний, посылаемых таймером.
    Этот период ( то есть длительность одного такта таймера) можно узнать с помощью функции GetTimerResolution: DWORD WINAPI GetTimerResolution(void);

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

    2. Создание и уничтожение таймера

    Для создания виртуального таймера приложение должно использовать функцию SetTimer: UINT WINAPI SetTimer(HWND hwnd, UINT idTimer, UINT uTimeout, TIMERPROC tmprc);

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

    Второй параметр (idTimer) определяет идентификатор таймера (он не должен быть равен нулю). Идентификатор используется только в том случае, если первый параметр функции SetTimer содержит идентификатор окна. Так как для одного окна можно создать несколько таймеров, для того чтобы различать сообщения, приходящие от разных таймеров, приложение при создании должно снабдить каждый таймер собственным идентификатором.

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

    Третий параметр (uTimeout) определяет период следования сообщений от таймера в миллисекундах. Учтите, что физический таймер тикает приблизительно 18,21 раза в секунду (точное значение составляет 1000/54,925). Поэтому, даже если вы укажете, что таймер должен тикать каждую миллисекунду, сообщения будут приходить с интервалом не менее 55 миллисекунд.


    Последний параметр (tmprc) определяет адрес функции, которая будет получать сообщения WM_TIMER (мы будем называть эту функцию функцией таймера). Этот параметр необходимо обязательно указать, если первый параметр функции SetTimer равен NULL.

    Тип TIMERPROC описан в файле windows.h следующим образом: typedef void (CALLBACK* TIMERPROC)(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime);

    Сравните это с описанием типа WNDPROC, который используется для знакомой вам функции окна: typedef LRESULT (CALLBACK* WNDPROC)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

    Как видно из описания, функция таймера не возвращает никакого значения, имеет другие (по сравнению с функцией окна) параметры, но описана с тем же ключевым словом CALLBACK: #define CALLBACK _far _pascal

    Возвращаемое функцией SetTimer значение является идентификатором созданного таймера (если в качестве первого параметра функции было указано значение NULL). В любом случае функция SetTimer возвращает нулевое значение, если она не смогла создать таймер. В Windows версии 3.0 максимальное количество созданных во всей системе таймеров было 16. Для Windows версии 3.1 это ограничение снято.

    Тем не менее, если приложение больше не нуждается в услугах таймера, оно должно уничтожить таймер, вызвав функцию KillTimer: BOOL WINAPI KillTimer (HWND hwnd, UINT idTimer);

    Первый параметр функции (hwnd) определяет идентификатор окна, указанный при создании таймера функцией SetTimer.

    Второй параметр (idTimer) - идентификатор уничтожаемого таймера. Это должен быть либо тот идентификатор, который вы указали при создании таймера (если таймер создавался для окна), либо значение, полученное при создании таймера от функции SetTimer (для таймера, имеющего собственную функцию обработки сообщений).

    Функция KillTimer возвращает значение TRUE при успешном уничтожении таймера или FALSE, если она не смогла найти таймер с указанным идентификатором.

    3. Сообщение WM_TIMER

    Параметр wParam сообщения WM_TIMER содержит идентификатор таймера, который был указан или получен от функции SetTimer при создании таймера.


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

    После обработки этого сообщения приложение должно возвратить нулевое значение.

    Заметим, что сообщение WM_TIMER является низкоприоритетным. Это означает, что функция DispatchMessage посылает это сообщение приложению только в том случае, если в очереди приложения нет других сообщений. В этом отличие таймера Windows от аналогичных средств MS-DOS, реализованных с помощью перехвата прерывания INT8h.

    Выполнение программы MS-DOS прерывается синхронно с приходом аппаратного прерывания таймера и программа MS-DOS, перехватившая это прерывание, немедленно оповещается о нем. Выполнение приложения Windows тоже, разумеется, прерывается по аппаратному прерыванию таймера, но оповещение об этом событии приходит не всегда, и как правило, позже, вместе с сообщением WM_TIMER.

    4. Первый способ использования таймера

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

    Этот способ самый простой. Вначале вам надо вызывать функцию SetTimer, указав ей в качестве параметров идентификатор окна, идентификатор таймера и период, с которым от таймера должны приходить сообщения: #define FIRST_TIMER 1 int nTimerID; nTimerID = SetTimer(hwnd, FIRST_TIMER, 1000, NULL);

    В данном примере создается таймер с идентификатором FIRST_TIMER, который будет посылать сообщения примерно раз в секунду.

    Для уничтожения таймера, созданного этим способом, следует вызвать функцию KillTimer, указав параметры следующим образом: KillTimer(hwnd, FIRST_TIMER);

    Для изменения интервала посылки сообщений вам следует вначале уничтожить таймер, а потом создать новый, работающий с другим периодом времени: KillTimer(hwnd, FIRST_TIMER); nTimerID = SetTimer(hwnd, FIRST_TIMER, 100, NULL);

    5. Второй способ использования таймера

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


    Эта функция является функцией обратного вызова, определяется с ключевым словом _export и, так же как и функция окна, имеет специальный пролог и эпилог: void CALLBACK _export TimerProc(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime);

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

    Первый параметр функции таймера - идентификатор окна, с которым связан таймер. Если при создании таймера в качестве идентификатора было указано значение NULL, это же значение будет передано функции таймера.

    Второй параметр представляет собой идентификатор сообщения WM_TIMER.

    Третий параметр является идентификатором таймера, пославшего сообщение WM_TIMER.

    И наконец, последний параметр - текущее время по системным часам компьютера. Это время выражается в количестве тиков таймера с момента запуска Windows. Вы можете узнать текущее системное время в любой момент, если воспользуетесь функцией GetCurrentTime или GetTickCount: DWORD WINAPI GetCurrentTime(void); DWORD WINAPI GetTickCount(void);

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

    Если для создания приложения вы пользуетесь современными средствами разработки, такими, как Borland C++ версии 3.1, Microsoft С++ версии 7.0, Microsoft Visual C++, для определения функции обратного вызова достаточно использовать ключевое слово _export. В этом случае вы можете создать таймер, например, так: nTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)TimerProc);

    Для удаления таймера в этом случае необходимо использовать идентификатор, возвращенный функцией SetTimer: KillTimer(hwnd, nTimerID );

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

    Когда вы не можете ограничиться использованием ключевого слова _export, для работы с функциями обратного вызова нужно сделать специальный переходник (thunk), вызвав функцию MakeProcInstance: FARPROC WINAPI MakeProcInstance(FARPROC lpProc, HINSTANCE hinst);


    В качестве первого параметра функции (lpProc) необходимо передать адрес функции, для которой создается переходник, а в качестве второго (hinst) - идентификатор приложения hInstance, полученный функцией WinMain при запуске приложения.

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

    Процедура создания таймера с использованием функции MakeProcInstance может выглядеть следующим образом: TIMERPROC lpfnTimerProc; lpfnTimerProc = (TIMERPROC)MakeProcInstance( (FARPROC)TimerProc, hInstance); nTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)lpfnTimerProc);

    После уничтожения таймера следует уничтожить созданный функцией MakeProcInstance переходник, для чего следует вызвать функцию FreeProcInstance: void WINAPI FreeProcInstance(FARPROC lpProc);

    Этой функции необходимо передать адрес уничтожаемой функции-переходника: FreeProcInstance(lpfnTimerProc);

    Функции MakeProcInstance и FreeProcInstance можно использовать совместно с современными средствами разработки, понимающими ключевое слово _export. Это не приведет к неправильной работе приложения.

    Старые средства разработки приложений Windows требуют, чтобы все функции обратного вызова, такие, как функции окон и функции таймеров, были описаны в файле определения модуля, имеющем расширение .def, при помощи оператора EXPORTS, например: EXPORTS WndProc TimerProc

    Транслятор Borland C++ версии 3.1 распознает ключевое слово _export и автоматически формирует нужный пролог и эпилог для функций обратного вызова. То же самое относится и к трансляторам Microsoft C++ версии 7.0 и Microsoft Visual C++. Поэтому в наших примерах функции MakeProcInstance и FreeProcInstance, а также оператор файла определения модуля EXPORTS не используются.



    К списку

    Классы в C++ Ernest Avagyan



    7. Классы в C++.Одной из основных черт C++, которой нет в С, является концепция классов. По существу, классы - самое важное понятие в C++. Классы похожи на структуры языка С. Однако структура С определяет только данные, ассоциированные с этой структурой. Вот пример структуры С:

    struct CIRCLE
    {
    int radius;
    int color;
    {;

    После того как вы объявили структуру, вы можете использовать ее в пределах вашей функции main (), как показано ниже:

    void main()
    CIRCLE MyCircle;
    ...
    ...
    MyCircle.radius = 18;
    MyCircle.color = 255; // 255 задает цвет
    ...
    ...
    }


    Со структурой MyCircle (представляющей окружность) ассоциируются данные radius и color (радиус и цвет). Класс в C++, с другой стороны, имеет как ассоциированные с ним данные, так и функции. Данные класса называются элементами данных, а функции класса - элементами-функциями. Следовательно, в программе, которая использует классы, можно написать следующий код:

    MyCircle.radius = 20;
    MyCircle.color = 255;
    MyCircle.DisplayCircle() ;

    Первые два оператора присваивают значения элементам данных MyCircle radius и color; третий оператор вызывает функцию-элемент DisplayCircle() для вывода окружности MyCircle. MyCircle называется объектом класса circle. Ваша программа может объявить другой объект с именем HerCircle класса circle следующим образом:

    CIRCLE HerCircle;

    Следующие операторы присваивают значения элементам данных HerCircle radius и color:

    HerCircle.radius = 30;
    HerCircle.color = 0;

    Затем вы можете использовать функцию-элемент DisplayCircie () для вывода окружности HerCircle:

    HerCircle.DisplayCircle();

    Объявление класса

    Перед тем как работать с классом, ваша программа должна его объявить (так же как перед работой со структурой mystructure вы должны были объявить ее элементы данных). В данном разделе вы познакомитесь с синтаксисом объявления класса. Вы будете и дальше практиковаться с классом circle:

    class Circle (
    public:
    Circle () ;
    void SetRadius(void) ;
    void GetRadius(void) ;

    ~Circle () ;
    private:
    void CalculateArea(void);


    int radius;

    int color;
    };

    Объявление класса имеет следующее строение:

    class Circle {
    ...
    ...
    Здесь вы вводите объявление класса
    ...
    ...
    };
    Ключевое слово class показывает компилятору, что все находящееся в фигурных скобках ({}) принадлежит объявлению класса. (Не забывайте ставить точку с запятой в конце объявления.) Объявление класса содержит объявление элементов данных (например, int radius) и прототипы функций-элементов класса. В объявлении класса circle содержатся следующие элементы данных:

    int radius;
    int color;
    Объявление также содержит пять прототипов функций-элементов:

    Circle();

    void SetRadius(void) ;
    void GetRadius(void) ;
    ~Circle () ;
    void CalculateArea(void);


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

    Circle();


    Вы узнаете о роли конструктора позже в этом разделе, а пока запомните синтаксис, который используется в C++ для прототипа функции конструктора. Когда вы записываете прототип конструктора, вы должны следовать правилам, приведенным ниже:

    • Каждое объявление класса должно включать прототип функции конструктора.

    • Имя функции конструктора должно совпадать с именем класса, а после него должны следовать круглые скобки (). Если, например, вы объявляете класс с именем Rectangle, он должен включать объявление функции конструктора класса: Rectangle (). Следовательно, объявление класса Rectangle должно выглядеть так:

    class Rectangle
    {
    public:

    Rectangle(); // Конструктор
    ...
    ...
    private:
    ...
    ...
    };


    • Не упоминайте никакого возвращаемого значения для функции конструктора. (Функция конструктора должна иметь тип void, но не нужно это указывать.)
    • Функция конструктора должна располагаться под ключевым словом

    public.


    Функция конструктора всегда возвращает значение типа void (несмотря на то, что вы не указали его в прототипе). Как вы вскоре увидите, функция конструктора обычно имеет один или большее число параметров.

    Функция деструктора


    Функция деструктора записывается в объявлении класса следующим образом:

    class Circle
    (
    public:
    ...
    ...
    ~Circle (); //Деструктор private:
    ...
    ...
    };


    Обратите внимание на символ тильды (~), который предшествует прототипу функции деструктора. (На большинстве клавиатур вы можете найти символ тильды слева от клавиши 1.) При записи прототипа функции деструктора соблюдайте следующие правила:

    • Имя функции деструктора должно совпадать с именем класса и ему должен предшествовать символ ~. Если, например, вы объявляете класс с именем Rectangle, именем функции деструктора должно быть ~Rectangle. Следовательно, объявление класса Rectangle должно выглядеть следующим образом:

    class Rectangle
    {
    public:

    Rectangle(); // Конструктор
    ...
    ~Rectangle(); // Деструктор private:
    ...
    ...
    };

    • Не указывайте никакого возвращаемого значения для функции деструктора. (Функция деструктора должна иметь тип void, но не нужно это указывать.)
    • Функция деструктора не имеет никаких параметров.



    Ключевые слова public и private


    Прототипы функций и объявления элементов данных включаются в объявлении класса в разделы public (открытый) или private (закрытый). Ключевые слова public и private говорят компилятору о доступности элементов-функций и данных. Например, функция SetRadius() определена в разделе public, и это означает, что любая функция программы может вызвать функцию SetRadius(). Функция CalculateArea() определена в разделе private, и эту функцию можно вызвать только в коде функций-элементов класса Circle.

    Аналогично, поскольку элемент данных radius объявлен в разделе private, прямой доступ к нему (для установки или чтения его значения) возможен только в коде функций-элементов класса Circle. Если бы вы объявили элемент данных radius в разделе public, то любая функция программы имела бы доступ (для чтения и присваивания) к элементу данных radius.


    Перегруженные функции


    В C++ (но не в С) вы можете использовать одно и то же имя для нескольких функций. Например, вы можете объявить две функции с именем SetRadius() в объявлении класса CCircle. Такие функции называются перегруженными функциями.

    К списку

    Оптимизация вывода графики на экран Ernest Avagyan



    14. Оптимизация вывода графики на экран
    1. Описание класса CView
    2. Объекты GDI
    3. GDI-атрибуты контекста устройства
    4. Методы для рисования линий и фигур



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

    Сообщение и метод OnDraw

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

    Графическое устройство и его контекст

    Работа с графическими устройствами, такими, как принтер, плоттер, дисплей в системе Windows вообще и в Visual C++ в частности является аппаратно-независимой. Это значит, что при программировании под Windows средств прямого доступа к аппаратуре нет. Все взаимодействие с ней производится через функции API (Application Program Interface). При этом для вывода на графические устройства используется один и тот же набор функций.
    Для того, чтобы определить, на какое устройство осуществляется вывод, в Windows и в Visual C++ используется понятие контекста устройства (device context).
    Далее везде будет рассматриваться контекст устройства, реализованный в Visual C++. Контекст устройства - это объект класса CDC, содержащий все методы для построения изображения в окне. Кроме того, он содержит данные о графическом устройстве вывода. Для осуществления вывода создается контекст устройства и тем самым определяется конкретное устройство для вывода. А далее к созданному объекту можно применять все имеющиеся методы класса CDC.

    При выводе многие параметры долгое время остаются неизменными, например, цвет линии и другие. Указывать все такие параметры при каждом обращении к методам вывода неудобно. Контекст устройства содержит целый ряд таких параметров, обычно их называют атрибутами контекста устройства. Методы имеют лишь те параметры, что определяют существо их действия, а остальные атрибуты для рисования берутся из контекста устройства. При создании контекста устройства его атрибуты устанавливаются по умолчанию. Затем их можно изменять методами класса CDC.



    Поврежденная область и поврежденный прямоугольник



    При работе с окнами обычно "повреждается" только часть окна, так что перерисовывать все окно неэкономно. Поэтому система Windows фиксирует не только необходимость перерисовки, но и информацию о поврежденной области (invalid region). Но более важным является понятие поврежденный прямоугольник (invalid rectangle) - минимальный прямоугольник, покрывающий поврежденную область. Windows и Visual C++ обеспечивают следующие возможности при работе с поврежденным прямоугольником:

    Методы GetUpdateRect и GetUpdateRgn класса CWnd позволяют получить описание поврежденного прямоугольника и поврежденной области.

    Если производить перерисовку стандартным путем (например, внутри метода обработки сообщения OnDraw), то рисование в окне результативно только в области поврежденного прямоугольника. В этом случае говорят, что поврежденный прямоугольник задает область усечения, вне которой содержимое окна не изменяется.

    Если в момент возникновения поврежденной области сформированное ранее системой Windows сообщение WM_PAINT о необходимости перерисовки окна не было обработано приложением и стоит в очереди приложения, новое сообщение WM_PAINT в очередь не добавляется.


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

    Методы Invalidate, InvalidateRect и InvalidateRgn класса CWnd позволяют объявить соответственно клиентскую область, некоторые прямоугольник и область окна поврежденными и послать сообщение WM_PAINT в очередь приложения.

    Методы ValidateRect и ValidateRgn класса CWnd позволяют отменить объявление некоторого прямоугольника или области поврежденными. Это ведет к корректировке текущего поврежденного прямоугольника.

    При создании окна поврежденный прямоугольник устанавливается равным клиентской части окна. Обработчик сообщения Update класса CView также устанавливает поврежденный прямоугольник равным клиентской части окна.

    2. Объекты GDI

    При рисовании фигур в Visual C++ используются специальные объекты GDI, т.е. объекты интерфейса графического устройства (GDI - Graphics Device Interface) системы Windows.





    Название


    Класс


    Тип определителя в Windows


    Перо


    CPen


    HPEN


    Кисть


    CBrush


    HBRUSH


    Шрифт


    CFont


    HFONT


    Растровое изображение


    CBitmap


    HBITMAP


    Палитра


    CPalette


    HPALETTE


    Область (регион)


    CRgn


    HRGN
    Как и любые другие объекты в Visual C++, они должны быть созданы соответствующим образом. Создание объекта должно включать в себя и связывание с соответствующим объектом системы Windows. Эта операция осуществляется методом, имя которого начинается с префикса Create. Он может быть выполнен как после создания объекта Visual C++, так и в конструкторе объекта:

    CPen Pen1; Pen1.CreatePen(PS_DOT, 5, RGB(0,0,0)); // вариант 1
    CPen Pen2(PS_DOT, 5, Rface="Courier New">GB(0,0,0)); // вариант 2


    При завершении работы с объектом GDI необходимо обеспечить (вернее, не нарушать) его автоматическое удаление при выходе из соответствующей области видимости.



    Перо
    - объект для рисования линий. При создании пера можно задать его ширину, цвет и тип линии. Для создания пера используются метод CreatePen.



    Кисть
    представляет собой растровое изображение размером 8х8 пикселей для заполнения различных областей.


    Различают два варианта понятия кисти: логическая и физическая. Объект GDI задает логическую кисть, которая определяет полный набор свойств: цвет, стиль и т.п., - указываемых при ее создании. Возможно, не все эти свойства могут быть реализованы на данном компьютере. Свойства физической кисти реально воспроизводятся физическими устройствами. Они являются наиболее близким реализуемым приближением к свойствам логической кисти.

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

    Для создания сплошной кисти, все пиксели которой одного цвета, используется метод CreateSolidBrush. Стандартные кисти уже имеются в операционной системе (таких кистей 7). Чтобы их создать, используется метод CreateStockBrush. Этот метод на самом деле создает только объект класса CBrush. Сама кисть берется готовой из операционной системы. Штриховые кисти имеют цвет и штриховой рисунок. Имеется 6 видов рисунка. Кисти создаются такими методами CreateHatchBrush и GreateSysColorBrush. Шаблонные кисти могут иметь произвольный рисунок, задаваемый растровым изображением (BMP), либо аппаратно-независимым растровым изображением (DIB). Для создания кисти используются методы CreatePatternBrush, CreateDIBPatternBrush.

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



    Палитры
    появились потому, что многие типы мониторов физически могут воспроизводить очень много цветов, а видеокартам не хватает видеопамяти, чтобы поддерживать все цвета одновременно. Например, монитор воспроизводит сотни тысяч цветов, а видеокарта отводит для одного пиксела байт и тем самым может хранить 256 цветов для окраски одной точки. Для более полного использования возможностей монитора и существуют палитры. Они сопоставляют цвета числам от 0 до 2n-1, которые могут храниться в ячейке, отведенной для одного пиксела.


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

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

    Для создания палитры используются методы CreatePalette и CreateHalftonePalette. Созданную палитру можно изменить методом SetPaletteEntries.



    В Windows существует еще один способ управления цветом - макросом RGB. Он задает функцию, возвращающую значение типа COLORREF. У нее три параметра, задающий красный, зеленый и голубой компонент устанавливаемого цвета. Каждый компонент может принимать значения от 0 до 255. Итоговый цвет получается смешением красного, зеленого и синего цветов в соответствующих пропорциях.

    3. GDI-атрибуты контекста устройства

    Значениями ряда атрибутов контекста устройства являются объекты GDI. Как отмечалось ранее, в вызовах методов, рисующих фигуры на экране, многие параметры не указываются, а берутся из атрибутов контекста устройства. Чтобы эти параметры отличались от установленных в контексте устройства по умолчанию, необходимо:

    Сохранить старое значение атрибута. Установить новое. Выполнить необходимые действия. Восстановить старое значение атрибута.

    Последовательность этих действий иллюстрируется примером:

    void CMyView::OnDraw(CDC* pDC)
    {

    CPen Pen;
    if(Pen.CreatePen(PS_SOLID,2,RGB(0,0,0))
    {

    // сохранение старого и установление нового значения атрибута
    CPen* pOldPen=pDC->SelectObject(&Pen);
    // выполнение необходимых действий
    pDC->MoveTo(....); pDC->LineTo(....);
    // восстановление старого значения атрибута
    pDC->SelectObject(pOldPen);
    }
    }


    Метод SelectObject в качестве результата возвращает указатель на текущее перо и делает текущим перо, указанное в качестве параметра метода.


    4. Методы для рисования линий и фигур

    Пикселы



    Для установки цвета пикселя с логическими координатами (x,y) используются метод SetPixel. Получить значение цвета пикселя можно методом GetPixel.



    Цвет задается функцией RGB. Как уже отмечалось выше, если в физической палитре нет данного цвета, задаваемого фактическим параметром, Windows устанавливает наиболее близкий цвет. Он-то и возвращается методом SetPixel. Следует также отметить, что, хотя координаты являются логическими, устанавливается цвет только одного пикселя, даже если единица измерения для текущей системы координат иная.



    Линии



    Прежде, чем описывать методы для рисования линий, рассмотрим два важных для них атрибута контекста устройства.

    Первый называют текущим пером. Его значением является перо как объект GDI. Любая линия (в том числе и ограничивающая фигуру) рисуется пером. Если метод не содержит явного параметра, задающего перо, то для рисования берется текущее перо, которое можно установить методом SelectObject. Или, если в качестве параметра передать одну из констант BLACK_PEN, NULL_PEN, WHITE_PEN, то методом SelectStockObject



    Второй важный атрибут - текущая позиция пера. Чтобы изменить координаты текущей позиции пера, используются метод MoveTo. Чтобы нарисовать прямую линию от текущей позиции пера до нужной точки с логическими координатами (x,y), используется метод LineTo. После выполнения метода LineTo заданная в нем точка становится текущей позицией пера.

    Если имеется массив точек и требуется соединить линией каждую следующую точку с предыдущей, можно использовать метод PolyLine, в котором первый параметр - указатель на массив элементов типа POINT, а второй равен количеству точек. При выполнении этого метода текущая позиция пера не изменяется.

    Следующий метод аналогичен PolyLine за исключением того, что он устанавливает текущую позицию пера равной последней точке массива - PolyLineTo.



    Если же требуется соединить между собой все точки, содержащиеся в массиве, можно вызвать метод PolyPolyline.




    Фигуры



    В Visual C++ имеются методы для рисования: прямоугольника (Rectangle); эллипса (Ellipse); скругленного прямоугольника (RoundRect); сегмента эллипса (Chord); сектора эллипса (Pie); замкнутого многоугольника; составного замкнутого многоугольника.

    Для рисования фигур важен атрибут контекста устройства, называемый текущей кистью. Он задает кисть как объект GDI, с помощью которого производится закрашивание внутренней области фигуры. Текущая кисть устанавливается методом SelectObject или SelectStockObject, если в качестве параметра передать одну из констант: BLACK_BRUSH, DKGRAY_BRUSH, GRAY_BRUSH, HOLLOW_BRUSH, LTGRAY_BRUSH, NULL_BRUSH, WHITE_BRUSH.

    Во многих книжках работа с графикой описывается следующим образом:

    1. Все графические функции описывабтся в OnPaint().
    2. Далее в другом месте программы вызываются функции Invalidate, InvalidateRect или InvalidateRgn.
    3. Такой тип построения программы не совсем верен, так как в таком случае обычно всё мигает и это нервирует.
    4. Есть немного другой способ работы с графикой и ниже он будет описан.
    Этот метод заключается в следующем :

    1. Вся графика рисуется в какой-то функции F().
    2. По событию таймера или по другим событиям вызывается F().
    3. Эдементы графики рисуются сначала в памяти, а потом выводятся на экран.
    4. Предворительные расчёты можно вести как в F() так и в других частях программы.
    5. Функция OnPaint() содержит копию функции F(), это нужно только для перерисовки окна при изменении его положения или размера.

    К списку

    Основы программирования с помощью библиотеки MFC


    Картузов А. В., Гончаров В. И.
    Основы программирования с помощью библиотеки
    Microsoft Foundation Classes
    ОГЛАВЛЕНИЕ
    ВВЕДЕНИЕ

    Что необходимо знать для изучения MFC
    Необходимые инструменты
    Установка примеров

    ОСНОВЫ MFC
    Иерархия классов MFC
    Информация о типе времени выполнения
    Диагностика
    Сериализация
    Основные классы
    Функции-члены в MFC
    Глобальные функции в MFC
    Файл AFXWIN.H
    Каркас MFC-программы
    Исходные тексты примера
    Подробнее о создании масштабируемых окон

    ОБРАБОТКА СООБЩЕНИЙ
    Обработка сообщений в MFC
    Включение макрокоманд в карту сообщений
    Включение обработчиков сообщений в описание класса
    Пример программы с обработкой сообщений
    Контекст устройства
    Сообщение WM_PAINT
    Генерация сообщения WM_PAINT
    Пример программы
    Сообщения WM_TIMER и WM_DESTROY
    Сообщение WM_TIMER
    Сообщение WM_DESTROY
    Пример программы

    РЕСУРСЫ. МЕНЮ И АКСЕЛЕРАТОРЫ
    Понятие ресурсов
    Меню
    Включение меню в окно приложения
    Сообщение WM_COMMAND
    Акселераторы
    Окна сообщений
    Пример программы, использующей меню, акселераторы и окна сообщений

    ДИАЛОГИ. ЗНАКОМСТВО С ЭЛЕМЕНТАМИ УПРАВЛЕНИЯ
    Взаимодействие между диалогом и пользователем
    Классы MFC для элементов управления
    Модальные и немодальные диалоги
    Диалоги как ресурсы
    Класс CDialog
    Обработка сообщений от диалогов
    Вызов диалога
    Закрытие диалога
    Пример программы с диалоговым окном
    Инициализация диалога
    Списки
    Основы работы со списками
    Прием идентификационных кодов списка
    Передача сообщений списку
    Получение указателя на список
    Инициализация списка
    Пример программы
    Поле ввода
    Пример программы с полем ввода
    Немодальные диалоги
    Пример программы с немодальным диалогом

    ДОПОЛНИТЕЛЬНЫЕ ЭЛЕМЕНТЫ УПРАВЛЕНИЯ
    Контрольные переключатели
    Сообщения контрольного переключателя
    Установка и чтение состояния контрольного переключателя
    Инициализация контрольных переключателей
    Пример программы
    Статические элементы управления
    Радиокнопки
    Пример программы с радиокнопками и статическими элементами управления

    Полосы прокрутки

    Создание стандартных полос прокрутки

    Независимые полосы прокрутки в диалогах

    Обработка сообщений полосы прокрутки

    Управление полосой прокрутки

    Пример программы с пропорциональными полосами прокрутки



    ИКОНКИ И КУРСОРЫ

    Создание иконки и курсора

    Загрузка иконки и курсора из ресурсов

    Изменение иконки и курсора окна

    Использование диалога в качестве главного окна

    Пример программы

    Стандартные иконки и курсоры



    БИТОВЫЕ ОБРАЗЫ

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

    Вывод битового образа на экран

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

    Пример программы



    ВЫВОД ТЕКСТА И ШРИФТЫ

    Небольшое введение

    Координаты при выводе текста

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

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

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

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

    Инициализация объекта шрифта: выбор шрифта

    Пример программы



    ВВЕДЕНИЕ В ГРАФИЧЕСКИЕ ФУНКЦИИ. ОРГАНИЗАЦИЯ ВЫВОДА В ВИРТУАЛЬНОЕ ОКНО

    Система графических координат

    Перья

    Кисти

    Отображение точки

    Рисование прямоугольников

    Рисование эллипсов

    Организация виртуального окна

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

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

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

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



    ЗАКЛЮЧЕНИЕ

    РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА

    Введение



    На сегодняшний день, Windows является безусловно лидирующей операционной системой на рынке персональных компьютеров. Поэтому успех современного программиста напрямую зависит от его умения разрабатывать качественные и эффективные приложения Windows. Приложения постоянно усложняются и требуют все большего времени для их создания. Для облегчения программирования Windows-приложений фирмой Microsoft была разработана библиотека MFC (Microsoft Foundation Classes - Базовые Классы Microsoft), которая впервые была представлена на рынке в 1992 г вместе с компилятором Microsoft C/C++ 7.0. Сейчас она представляет собой мощный набор классов C++, которые позволяют программировать приложения Windows 95,98/NT на достаточно высоком уровне абстракции, и вместе с тем открывают для опытных программистов легкий доступ к функциям более низкого уровня, что позволяет писать эффективные приложения и полностью использовать все возможности операционной системы.


    MFC является альтернативой системам визуального программирования, таким как Delphi или Visual Basic, предназначенной для опытных программистов. На сегодняшний день подавляющее большинство программ разрабатывается при помощи Microsoft Visual С++ и MFC. MFC - это стандарт программирования под Windows и "интернациональный язык общения". Такая ситуация объясняется многими причинами. В частности, только MFC позволяет создавать наиболее эффективные и устойчивые приложения, которые будут корректно вести себя не только в системе разработчика, но и в системах реальных пользователей. Также очень важно, что MFC поддерживает все современные технологии, реализованные в Windows, и при дополнении Windows почти сразу же дополняется и MFC.

    MFC - это инструмент для программирования сложных приложений, от которых требуется высокая эффективность и надежность. MFC поощряет использование объектно-ориентированного программирования, что дает ощутимые преимущества при решении сложных (не с точки зрения только интерфейса пользователя) задач, по сравнению с компонентно-ориентированным подходом, применяемым в системах RAD (быстрой разработки приложений). Разрабатывая приложение в системе RAD, программист часто вообще не использует ООП, по крайней мере в явном виде, до тех пор, пока не соберется разработать собственный компонент. Это негативно сказывается на возможности последующего расширения возможностей. Тем не менее, не стоит воспринимать сказанное как критику систем RAD. Есть много классов приложений (например, базы данных), которые разумнее всего разрабатывать именно при помощи систем RAD, что и делают даже опытные Windows-программисты.



    Что необходимо знать для изучения MFC



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


    Если Вы не так свободно ориентируетесь в С++, то настоятельно рекомендуется перед изучением MFC укрепить свои знания по С++, например, с помощью книги Стенли Б. Липпмана "С++ для начинающих", или одной из множества других книг, подобных этой.

    Кроме того, необходимо иметь опыт работы с одной из современных версий Windows, а также представлять себе ее внутреннее устройство и принципы работы. Также необходим хотя бы минимальный опыт работы со средой Microsoft Visual C++ - данные методические указания не являются руководством пользователя по этой среде (если Вы хорошо знаете С++, то вероятно, Вы уже пользовались этой средой для создания консольных приложений). Предполагается, что Вы по крайней мере умеете создавать проекты, добавлять к ним файлы, менять настройки и собирать проекты.

    Однако, библиотека MFC построена так, что она повторяет структуру подсистем и объектов Windows API. Это сделано ради эффективности. Поэтому, если Вы уже программировали на С/С++ с использованием только "чистого" Windows API, то это существенно облегчит процесс изучения MFC. Опыт работы с системами RAD тоже будет полезен.



    Необходимые инструменты



    В первую очередь, необходима Windows 95, Windows NT 4.0 или их более поздние версии. Также необходим компилятор Microsoft Visual C++ 4.0 или более поздняя версия. Необходимо отметить, что компилятор Visual C++ очень медленный и требует много памяти. Поэтому минимальные рекомендуемые конфигурации компьютеров для Visual C++ 4.0 следующие: для Windows 95 - 486DX4/100, 32 МБ RAM, для Windows NT 4.0 - Pentium 100, 64 МБ RAM.



    Установка примеров



    К методическим указаниям прилагается дискета, содержащая текст всех примеров, EXE-файлы и файлы проектов для Visual C++ 4.0. На дискете находится инсталляционная программа setup.exe, которая установит примеры в каталог C:\LEARN.MFC, а также скопирует в системный каталог Windows файлы DLL для динамически компонуемой версии MFC и библиотеки времени выполнения для Visual C++ (DLL-версии использовались для того, чтобы не включать довольно большой код MFC в каждый выполняемый файл примеров).


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



    Основы MFC

    Иерархия классов MFC



    Библиотека MFC содержит большую иерархию классов, написанных на С++. Структура иерархии приведена на рис. 1. В ее вершине находится класс СObject, который содержит различные функции, используемые во время выполнения программы и предназначенные, в частности, для предоставления информации о текущем типе во время выполнения, для диагностики, и для сериализации.

    Информация о типе времени выполнения

    Если указатель или ссылка ссылается на объект, производный от класса CObject, то в этом случае предусмотрен механизм определения реального типа объекта с помощью макроса RUNTIME_CLASS(). Хотя в С++ имеется механизм RTTI, механизм, реализованный в MFC, намного более эффективен по производительности.

    Диагностика

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

    Сериализация

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

    Основные классы

    Некоторые классы порождаются непосредственно от CObject. Наиболее широко используемыми среди них являются CCmdTarget, CFile, CDC, CGDIObject

    и CMenu. Класс ССmdTarget предназначен для обработки сообщений. Класс СFile предназначен для работы с файлами. Класс CDC обеспечивает поддержку контекстов устройств. Об контекстах устройств мы будем говорить несколько позднее. В этот класс включены практически все функции графики GDI. CGDIObject является базовым классом для различных DGI-объектов, таких как перья, кисти, шрифты и другие.


    Класс СMenu предназначен для манипуляций с меню.

    От класса CCmdTarget порождается очень важный класс CWnd. Он является базовым для создания всех типов окон, включая масштабируемые ("обычные") и диалоговые, а также различные элементы управления. Наиболее широко используемым производным классом является CFrameWnd. Как Вы увидите в дальнейшем, в большинстве программ главное окно создается с помощью именно этого класса.



    От класса CCmdTarget, через класс CWinThread, порождается, наверное, единственный из наиболее важных классов, обращение к которому в MFC-программах происходит напрямую: CWinApp. Это один из фундаментальных классов, поскольку предназначен для создания самого приложения. В каждой программе имеется один и только один объект этого класса. Как только он будет создан, приложение начнет выполняться.



    Функции-члены в MFC



    Большинство функций, вызываемых в MFC-программе, являются членами одного из классов, определенных в библиотеке. Большинство функций API доступны через функции-члены MFC. Тем не менее, всегда можно обращаться к функциям API напрямую. Иногда это бывает необходимым, но все же в большинстве случаев удобнее использовать функции-члены MFC.



    Глобальные функции в MFC



    В библиотеке есть ряд глобальных функций. Все они начинаются с префикса Afx. (Когда MFC только разрабатывалась, то проект назывался AFX, Application Framework. После ряда существенных изменений AFX была переработана в MFC, но прежнее название сохранилось во многих идентификаторах библиотеки и в названиях файлов.) Например, очень часто используется функция AfxMessageBox(), отображающая заранее определенное окно сообщения. Но есть и член-функция MessageBox(). Таким образом, часто глобальные функции перекрываются функциями-членами.



    Файл AFXWIN.H



    Все MFC-программы включают заголовочный файл AFXWIN.H. В нем, а также в различных вспомогательных файлах, содержатся описания классов, структур, переменных и других объектов MFC. Он автоматически подключает большинство заголовочных файлов, относящихся к MFC, в том числе и WINDOWS.H, в котором определены все функции Windows API и другие объекты, которые используются при традиционном программировании на С и "чистом" API.




    Каркас MFC-программы



    Теперь мы создадим с помощью MFC

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

    В простейшем случае программа, написанная с помощью MFC, содержит два класса, порождаемые от классов иерархии библиотеки: класс, предназначенный для создания приложения, и класс, предназначенный для создания окна. Другими словами, для создания минимальной программы необходимо породить один класс от CWinApp, а другой - от CFrameWnd. Эти два класса обязательны для любой программы.

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

    Ниже приведен исходный код программы. Мы всегда будем помещать декларации классов и их реализацию в отдельные файлы. Это соответствует принятой практике программирования на С++.

    Исходные тексты примера



    simpwin.hpp



    #include

    // Класс основного окна приложения

    class CMainWin: public CFrameWnd {

    public:

    CMainWin();

    // Декларирование карты сообщений

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения. Должен существовать только один экземпляр этого класса.

    // Член-функция InitInstance() вызывается при запуске приложения.

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    simpwin.cpp



    #include

    #include

    #include "SIMPWIN.HPP"

    // Создание одного и только одного экземпляра приложения

    CApp App;

    //

    // Реализация

    //

    BOOL CApp::InitInstance()

    {

    // Создание главного окна приложения и его отображение.


    // Член CApp::m_pMainWnd - это указатель на объект главного окна.

    m_pMainWnd = new CMainWin;

    m_pMainWnd->ShowWindow(SW_RESTORE);

    m_pMainWnd->UpdateWindow();

    // Сигнализируем MFC об успешной инициализации приложения.

    return TRUE;

    }

    CMainWin::CMainWin()

    {

    // Создание окна с заголовком. Используется встроенный в MFC

    // класс окна, поэтому первый параметр 0.

    this->Create(0, "Простейшее приложение на MFC");

    }

    // Реализация карты сообщений

    BEGIN_MESSAGE_MAP(CMainWin /*класс окна*/, CFrameWnd /*класс-предок*/)

    END_MESSAGE_MAP()

    Рис. 2. Окно, созданное простейшим приложением на MFC

    Результат выполнения программы показан на рис. 2. Рассмотрим программу подробнее.

    Для создания стандартного окна в приложении должен наследоваться класс от CFrameWnd. В данном примере он называется CMainWin. Он содержит конструктор и макрос DECLARE_MESSAGE_MAP(). Макрос на самом деле разворачивается в декларацию карты сообщений, которая определяет, какая член-функция класса должна вызываться в ответ на сообщение Windows. Этот макрос применяется для любого окна, в котором обрабатываются сообщения. Он должен быть последним в декларации класса.

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

    Класс CApp

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

    virtual BOOL CWinApp::InitInstance();

    Это виртуальная функция, которая вызывается каждый раз при запуске программы. В ней должны производиться все действия, связанные с инициализацией приложения. Функция должна возвращать TRUE при успешном завершении и FALSE в противном случае. В нашем случае, в функции сначала создается объект класса CMainWin, и указатель на него запоминается в переменной m_pMainWnd.


    Эта переменная является членом класса CWinThread. Она имеет тип CWnd* и используется почти во всех MFC-программах, потому что содержит указатель на главное окно. В последующих двух строчках через нее вызываются функции-члены окна. Когда окно создано, вызывается функция c прототипом:

    BOOL CWnd::ShowWindow(int How);

    Параметр определяет, каким образом окно будет показано на экране. Наиболее распространенные значения следующие:



    Константа
    Действие
    SW_HIDE Окно становится невидимым
    SW_MAXIMIZE Окно максимизируется
    SW_MINIMIZE Окно минимизируетс
    SW_SHOW Окно отображается, если было невидимо
    SW_RESTORE Окно приводится к нормальному размеру
    При первом вызове в качестве первого параметра функции можно подставить член m_nCmdShow, в котором хранится значение кода отображения окна, переданное в программу при запуске (например, в ярлыке Windows 95 пользователь может определить, как показывать окно при запуске приложения).

    Функция UpdateWindow() посылает окну сообщение о том, что его содержимое нужно перерисовать. В дальнейшем мы рассмотрим этот процесс подробнее.

    В конце программы помещена реализация карты сообщений:

    BEGIN_MESSAGE_MAP(CMainWin /*класс окна*/, CFrameWnd /*класс-предок*/)

    END_MESSAGE_MAP()

    Первый макрос всегда имеет два параметра, первый - класс окна, второй - класс, от которого порожден класс окна. В данном примере карта сообщений пустая, то есть все сообщения обрабатывает MFC.

    Подробнее о создании масштабируемых окон

    В примере была использована функция Create(), которая на самом деле имеет много параметров. Ее прототип таков:

    BOOL CFrameWnd::Create(LPCSTR ClassName, // Имя Windows-класса окна

    LPCSTR Title, // Заголовок

    DWORD Style = WS_OVERLAPPEDWINDOW, // Стиль

    const RECT &XYSize = rectDefault, // Область

    CWnd *Parent = 0, // Окно-предок

    LPCSTR MenuName = 0, // Имя ресурса меню

    DWORD ExStyle = 0, // Расширенные стили

    CCreateContext *Context = 0

    // Доп. данные

    );

    Первый параметр, ClassName, определяет имя класса окна для оконной подсистемы Windows.


    Обычно его не нужно явно задавать, так как MFC выполняет всю необходимую черновую работу. В данных методических указаниях мы не будем использовать своих классов окон. Параметр Style задает стиль окна. По умолчанию создается стандартное перекрываемое окно. Можно задать свой стиль, объединив с помощью операции "или" несколько констант из приведенных ниже:



    Константа
    Элемент окна
    WS_OVERLAPPED Стандартное окно с рамкой
    WS_MAXIMIZEBOX Кнопка максимизации
    WS_MINIMIZEBOX Кнопка минимизации
    WS_SYSMENU Системное меню
    WS_HSCROLL Горизонтальная полоса прокрутки
    WS_VSCROLL Вертикальная полоса прокрутки
    В дальнейшем мы не будем так подробно расписывать здесь все возможные значения констант, потому что всегда можно обратиться к справке Visual C++ и получить подробную информацию.

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

    Все остальные параметры, которые мы будем использовать, будут рассмотрены в соответствующих разделах.



    Обработка сообщений



    Windows взаимодействует с приложением, посылая ему сообщения. Поэтому обработка сообщений является ядром всех приложений. В традиционных приложениях Windows (написанных с использованием только API) каждое сообщение передается в качестве аргументов оконной функции. Там обычно с помощью большого оператора switch определяется тип сообщения, извлекается инфоромация и производятся нужные действия. Это громоздкая и чреватая ошибками процедура. С помощью MFC все делается намного проще. Здесь мы рассмотрим обработку в программе некоторых наиболее часто используемых сообщений.



    Обработка сообщений в MFC



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


    Для организации обработки сообщений нужно выполнить следующие действия:

    1. В карту сообщений программы должна быть включена команда соответствующего сообщения.

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

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

    Включение макрокоманд в карту сообщений

    Чтобы программа могла ответить на сообщение, в карту сообщений должна быть включена соответствующая макрокоманда. Названия макрокоманд соответствуют именам стандартных сообщений Windows, но дополнительно имеют префикс ON_ и заканчиваются парой круглых скобок. Из этого правила есть исключение: сообщению WM_COMMAND соответствует макрокоманда ON_COMMAND(). Причина в том, что это сообщение обрабатывается особым образом.

    Чтобы включить макрокоманду в очередь сообщений, нужно просто поместить ее между командами BEGIN_MESSAGE_MAP() и END_MESSAGE_MAP(). Например, если необходимо обработать в программе сообщение WM_CHAR, то очередь должна выглядеть так:

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_WM_CHAR()

    END_MESSAGE_MAP()

    В очереди может находиться более одной макрокоманды. Сообщение WM_CHAR генерируется при нажатии алфавитно-цифровой клавиши на клавиатуре.

    Включение обработчиков сообщений в описание класса

    Каждое сообщение, явно обрабатываемое в программе, должно быть связано с одним из обработчиков. Обработчик - это член-функция класса, принимающего сообщения. Прототипы для обработчиков всех сообщений заранее заданы в MFC. Как правило, имя обработчика состоит из имени сообщения и префикса On. Например, обработчик сообщения WM_CHAR называется OnChar(), а для WM_LBUTTONDOWN - OnLButtonDown(). Последнее сообщение генерируется при нажатии левой кнопки мыши.

    Например, объявим класс с обработчиком сообщения WM_PAINT. Это сообщение посылается окну, когда оно должно перерисовать свою клиентскую область.

    Class CMainWin: public CFrameWnd {


    public:

    CMainWin();

    afx_msg void OnPaint();

    DECLARE_MESSAGE_MAP()

    }

    Спецификатор afx_msg означает объявление обработчика сообщения. На данный момент он не используется и представляет собой пустой макрос. Но в будущем возможны расширения. Поэтому использование спецификатора нужно считать обязательным.

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



    Пример программы с обработкой сообщений



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

    При нажатии на левую и правую кнопки соответственно генерируются сообщения WM_LBUTTONDOWN

    и WM_RBUTTONDOWN, и им соответствуют обработчики с прототипами:

    afx_msg void OnLButtonDown(UINT Flags, CPoint Loc);

    afx_msg void OnRButtonDown(UINT Flags, CPoint Loc);

    Первый параметр указывает на то, была ли при генерации сообщения нажата какая-нибудь клавиша или кнопка мыши. Этот параметр нас не будет пока интересовать. Второй параметр определяет координаты курсора мыши во момент нажатия кнопки. Класс CPoint порождается от структуры POINT, определенной так:

    typedef struct tagPOINT {

    LONG x;

    LONG y;

    } POINT;

    Таким образом, мы легко можем определить координаты для вывода текстовой строки.

    Обработчик сообщения WM_CHAR имеет прототип:

    afx_msg void OnChar(UINT Char, UINT Count, UINT Flags);

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

    Контекст устройства

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


    Например, в Windows нет совершенно никакой возможности выводить точки прямо на экран, как это делалось в DOS. Контекст устройства - это достаточно условное название (даже для английского языка, device context), не отражающее сути понятия. На самом деле, это структура данных, обеспечивающая связь графических функций с драйвером конкретного устройства. Эта структура определяет состояние драйвера, и способ вывода графики. В MFC есть классы, инкапсулирующие контексты устройств, поэтому нам не придется работать с ними напрямую.

    Например, в MFC можно получить контекст клиентской области окна, создав экземпляр класса CClientDС. Конструктор этого класса принимает один параметр - указатель на объект окна, обычно подставляется this. После создания этого объекта можно выводить графику в окно, используя функции-члены класса.

    Сообщение WM_PAINT

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

    Прототип обработчика WM_PAINT следующий:

    afx_msg void OnPaint();

    Макрокоманда называется ON_WM_PAINT(). Рассмотрим простой обработчик, который выводит строку "Привет" в клиентскую область по координатам x = 10, y = 20:

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 20, CString("Привет"));

    }

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

    Функция TextOut() предназначена для вывода текста в контекст устройства (в данном случае - в окно). При ее использовании по умолчанию первые два параметра определяют координаты верхнего левого угла текстовой строки. По умолчанию координаты представляют собой реальные пиксели, ось x направлена слева направо, ось y - сверху вниз.


    Эта функция перегруженная, наиболее удобный для нас вариант - когда третий параметр имеет тип CString. Этот класс входит в MFC и является очень удобной заменой для строк, завершаемых нулем. Он имеет много полезных функций - аналогов стандартных строковых функций, и поддерживает преобразования типов, в частности, из char* в CString (поэтому в примере можно было обойтись без явного создания объекта этого класса). Вы можете легко самостоятельно изучить этот класс и многие другие полезные классы общего назначения из состава MFC, пользуясь справочной системой Visual C++.

    Большинство реальных окон (за исключением диалогов, которые мы рассмотрим позднее) должны обрабатывать сообщение WM_PAINT. Более того, если Вы хотите написать корректную программу, то весь вывод в окно должен осуществляться только в обработчике WM_PAINT, и никак иначе. Например, крайне нежелательно получать контекст устройства в обработчике сообщения мыши и пытаться там что-то выводить. Это будет работать в большинстве случаев, но далеко не всегда. Дело в том, что Windows может предоставить одновременно всем программам лишь очень небольшое число контекстов устройств (Windows 95 - всего пять). Поэтому при запросе контекста не из обработчика WM_PAINT создание класса контекста может потерпеть провал, если другие приложения уже заняли все контексты. Тогда выводить информацию будет вообще невозможно. Это может вылиться в непредсказуемое поведение программы. В случае же получения контекста из обработчика WM_PAINT, обязательно с помощью класса CPaintDC, Windows гарантирует наличие свободного контекста. На самом деле, Windows не пошлет программе это сообщение до тех пор, пока в системе не будет свободного контекста. К сожалению, эта проблема не так широко известна, и во многих книгах сплошь и рядом правило вывода информации только по сообщению WM_PAINT не соблюдается. Это вызвано тем, что описанная проблема не проявляется до тех пор, пока в системе не будет запущено несколько задач, постоянно что-то рисующих (кроме отображения видео).


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

    Генерация сообщения WM_PAINT

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

    void CWnd::InvalidateRect(LPRECT pRegion, BOOL EraseBackground = TRUE);

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

    Пример программы



    message handling.hpp



    #include

    // Класс основного окна

    class CMainWin: public CFrameWnd {

    public:

    CMainWin();

    // Функции обработки сообщений

    afx_msg void OnChar(UINT ch, UINT, UINT);

    afx_msg void OnPaint();

    afx_msg void OnLButtonDown(UINT flags, CPoint Loc);

    afx_msg void OnRButtonDown(UINT flags, CPoint Loc);

    // Вспомогательные член-данные

    char str[50];

    int nMouseX, nMouseY, nOldMouseX, nOldMouseY;

    char pszMouseStr[50];

    // Декларирование карты откликов на сообщения

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    message handling.cpp



    #include

    #include

    #include "MESSAGE HANDLING.HPP"

    //

    // Реализация

    //

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin;

    m_pMainWnd->ShowWindow(SW_RESTORE);

    m_pMainWnd->UpdateWindow();

    return TRUE;

    }

    CMainWin::CMainWin()

    {

    // Создать основное окно


    this->Create(0, "Обработка сообщений");

    // Инициализировать переменные объекта

    strcpy(str, "");

    strcpy(pszMouseStr, "");

    nMouseX = nMouseY = nOldMouseX = nOldMouseY = 0;

    }

    // Реализация карты сообщений главного окна

    BEGIN_MESSAGE_MAP(CMainWin /* класс */, CFrameWnd /* базовый класс */)

    ON_WM_CHAR()

    ON_WM_PAINT()

    ON_WM_LBUTTONDOWN()

    ON_WM_RBUTTONDOWN()

    END_MESSAGE_MAP()

    // Реализация функций отклика на сообщения

    afx_msg void CMainWin::OnChar(UINT ch, UINT, UINT)

    {

    sprintf(str, "%c", ch);

    // Посылаем сообщение WM_PAINT

    // с необходимостью стереть и обновить все окно

    this->InvalidateRect(0);

    }

    afx_msg void CMainWin::OnPaint()

    {

    // Создадим контекст устройства для обработки WM_PAINT

    CPaintDC dc(this);

    // Затираем текст и снова выводим (возможно уже другой текст)

    dc.TextOut(nOldMouseX, nOldMouseY,

    " ", 30);

    dc.TextOut(nMouseX, nMouseY, pszMouseStr);

    dc.TextOut(1, 1, " ");

    dc.TextOut(1, 1, str);

    }

    afx_msg void CMainWin::OnLButtonDown(UINT, CPoint loc)

    {

    // Запоминаем в переменных класса координаты мыши и текст.

    // Затем посылаем сообщение WM_PAINT - его обработчик

    // выведет все на экран.

    nOldMouseX = nMouseX; nOldMouseY = nMouseY;

    strcpy(pszMouseStr, "Нажата левая кнопка");

    nMouseX = loc.x; nMouseY = loc.y;

    this->InvalidateRect(0);

    }

    afx_msg void CMainWin::OnRButtonDown(UINT, CPoint loc)

    {

    // Запоминаем в переменных класса координаты мыши и текст.

    // Затем посылаем сообщение WM_PAINT - его обработчик

    // выведет все на экран.

    nOldMouseX = nMouseX; nOldMouseY = nMouseY;

    strcpy(pszMouseStr, "Нажата правая кнопка");

    nMouseX = loc.x; nMouseY = loc.y;

    this->InvalidateRect(0);

    }

    CApp App; // Единственный экземпляр приложения

    Рис. 3. Пример работы программы с обработкой сообщений.

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


    Это делается с помощью вывода достаточено длинной строки из пробелов; строка выводится с белым фоном, поэтому предыдущая строка стирается. По умолчанию вывод в окно осуществляется системным шрифтом, ширина символов у которого различна. Поэтому, например, вывод символа "i" сразу поверх "W" не сотрет предыдущий символ.



    Сообщения WM_TIMER и WM_DESTROY



    Сообщение WM_TIMER

    В Windows существуют специальные объекты, называемые таймерами. Программа (точнее, окно) может запросить один или несколько таких объектов. После этого каждый таймер через регулярные заранее указанные промежутки времени будет посылать сообщение WM_TIMER. Они будут помещаться в очередь сообщений окна. Таким образом, в функции-обработчике этого сообщения можно выполнять некоторые действия через регулярные промежутки времени. Если создано несколько таймеров, то их можно различать по номерам, присвоенным им при запросе.

    Для запроса таймера у системы используется следующая функция:

    UINT CWnd::SetTimer(UINT Id, UINT Interval,

    void (CALLBACK EXPORT *TFunc)(HWND, UINT, UINT, DWORD));

    Третьим параметром пользуются очень редко, и обычно он равен 0. Мы не будем его использовать. Параметр Id задает уникальный идентификационный номер таймера. По этим номерам обычно различаются несколько созданных таймеров. Параметр Interval задает интервал между двумя посылками сообщений (интервал таймера) в миллисекундах. Разрешающая способность таймеров 55 мс, поэтому интервалы измеряются с такой точностью. Если даже задать значение интервала равным 1, то все равно будет использовано значение 55. Если задать 0, то таймер приостановит свою работу.

    Сообщения таймера обрабатываются функцией:

    afx_msg void OnTimer(UINT Id);

    Все таймеры вызывают один и тот же обработчик. Узнать, какой таймер послал сообщение, можно с помощью параметра Id, в котором передается номер таймера.

    Сообщение WM_DESTROY

    Это сообщение посылается окну, когда последнее должно быть удалено. Если его получает главное окно приложения, то это означает завершение приложения.


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

    afx_msg void OnDestroy();

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

    BOOL CWnd::KillTimer(int Id);

    Функция освобождает таймер с идентификатором Id.

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

    Пример программы



    message handling II.hpp



    #include

    // Класс основного окна

    class CMainWin: public CFrameWnd {

    public:

    CMainWin();

    afx_msg void OnPaint();

    // Обработчик сообщения WM_DESTROY

    afx_msg void OnDestroy();

    // Обработчик сообщения WM_TIMER

    afx_msg void OnTimer(UINT ID);

    char str[50];

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    message handling II.cpp



    #include

    #include

    #include

    #include "MESSAGE HANDLING - II.HPP"

    //

    // Реализация

    //

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin;

    m_pMainWnd->ShowWindow(SW_RESTORE);

    m_pMainWnd->UpdateWindow();

    // Установка таймера с идентификатором 1 и интервалом 500 мс

    m_pMainWnd->SetTimer(1, 500, 0);

    return TRUE;

    }

    CMainWin::CMainWin()

    {

    // Определение прямоугольника, в котором будет размещено окно

    RECT rect;

    rect.left = rect.top = 10;

    rect.right = 200;

    rect.bottom = 60;

    // Создание окна в определенном экранном прямоугольнике

    this->Create(0, "CLOCK", WS_OVERLAPPEDWINDOW, rect);

    strcpy(str, "");

    }

    // Реализация карты сообщений главного окна

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_WM_PAINT()

    ON_WM_DESTROY()

    ON_WM_TIMER()

    END_MESSAGE_MAP()


    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC dc(this);

    // Выводим в окно строку текущего времени

    dc.TextOut(1, 1, " ", 3);

    dc.TextOut(1, 1, str);

    }

    afx_msg void CMainWin::OnTimer(UINT ID)

    {

    // Предполагаем, что в программе один таймер, поэтому

    // не проверяем ID.

    // Получаем строку текущего времени

    CTime curtime = CTime::GetCurrentTime();

    tm *newtime;

    newtime = curtime.GetLocalTm();

    sprintf(str, asctime(newtime));

    str[strlen(str) - 1] = '\0';

    // Посылаем сообщение WM_PAINT -- его обработчик отобразит строку.

    this->InvalidateRect(0);

    }

    afx_msg void CMainWin::OnDestroy()

    {

    // При закрытии окна удаляем связанный с ним таймер.

    KillTimer(1);

    }

    CApp App; // Единственный экземпляр приложения.

    Рис. 4. Программа-"часы"

    Мы получаем текущее время с помощью класса CTime. Это еще один полезный класс общего назначения из MFC. Вы можете использовать приведенный выше код для получения текущего локального времени. Также, мы при создании окна явно задали координаты прямоугольника, в котором оно должно быть отображено, с помощью структуры RECT. Таким образом, всегда можно задать начальные размеры и положение окна на экране.



    Ресурсы. меню и акселераторы

    Понятие ресурсов



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


    При сборке проекта приложения, ресурсы добавляются к EXE-файлу уже после линковки. Для описания ресурсов существует специальный язык, а сами описания хранятся в текстовых файлах с расширением rc. Раньше программисты вручную писали сценарии ресурсов на языке ресурсов, сейчас же так никто не делает, а используются визуальные редакторы. Перед добавлением к исполняемому файлу сценарии преобразуются в бинарный вид с помощью компилятора ресурсов, в результате чего получается файл с расширением res. Как правило, все эти шаги выполняются автоматически при работе из интегрированной среды Visual C++. Вообще, ручное редактирование ресурсных сценариев требуется сейчас уже очень редко, лишь при определении нестандартных ресурсов в сложных проектах. Мы будем использовать только интегрированную среду, как это сейчас делают многие программисты.

    Каждый ресурс имеет свой уникальный идентификатор. Это может быть либо строка, либо число (константа). Числа можно использовать всегда, а строки - не всегда. Мы будем использовать и то, и другое. Редактор ресурсов из Visual C++ помещает имена констант в файл resource.h, который нужно включить в файлы программы. Стандартные идентификаторы хранятся в файле afxres.h, который обычно используется автоматически ресурсным редактором.



    Меню



    Меню обычно создаются визуально. В Visual C++ нужно нажать клавиши Ctrl+2

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

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

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


    При редактировании символьные идентификаторы заключаются в кавычки. Также, каждому пункту меню должен быть присвоен уникальный числовой идентификатор. Это позволит программе реагировать на выбор пункта в меню, в этом случае MFC будет вызывать соответствующий обработчик (об этом несколько позже). По принятому соглашению, все идентификаторы пунктов меню начинаются с IDM_. В самих названиях пунктов можно указывать ключевые клавиши, поставив перед буквой символ &. В этом случае, если меню активно, пункт можно выбрать также и с клавиатуры.

    На рис. 5 показан процесс создания меню в среде Visual C++. Другие ресурсы создаются аналогично. Этот процесс довольно прост.

    Рис. 5. Процесс визуального создания меню в среде Visual C++

    Включение меню в окно приложения

    Когда ресурс меню создан, его можно использовать в окне программы. Это можно сделать, указывая меню при создании окна: строковый идентификатор ресурса меню нужно указать в качестве последнего параметра в функции Create():

    this->Create(0, "Приложение с меню", WS_OVERLAPPEDWINDOW,

    rectDefautl, 0, "MYMENU");

    В результате будет создано окно с меню. Но для того, чтобы меню можно было использовать, необходимо создать обработчики сообщения WM_COMMAND для каждого пункта меню. Если для какого-то пункта нет обработчика, то MFC

    заблокирует этот пункт (он будет выделен серым цветом).



    Сообщение WM_COMMAND



    Это очень широко используемое сообщение. В частности, оно посылается окну, когда пользователь выбирает пункт в меню. Идентификатор пункта передается как параметр сообщения. Как уже было сказано ранее, это сообщение обрабатывается не так, как другие. Это вызвано тем, что смысл сообщения зависит от идентификатора. Идентификатор определяет, какой из обработчиков должен быть вызван. Для размещения обработчика этого сообщения используется следующая макрокоманда:

    ON_COMMAND(Идентификатор, ИмяОбработчика);

    Каждый обработчик для WM_COMMAND должен возвращать void. Обработчики не имеют параметров.


    Имя выбирается произвольно, обычно используется префикс On.

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



    Акселераторы



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

    BOOL CFrameWnd::LoadAccelTable(LPCSTR ResourceName);

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

    Акселераторы легко создавать в среде Visual C++. Для каждого элемента таблицы нужно нажать желаемую клавишу или их комбинацию, и указать числовой идентификатор. Если указать идентификаторы, которые уже использовались в меню, то мы получим клавиши быстрого доступа, дублирующие команды меню.



    Окна сообщений



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

    int CWnd::MessageBox(LPCSTR MessageText,

    LPCSTR WindowTitle = 0,

    UINT MessageBoxType = MB_OK);

    Параметр MessageText определяет само сообщение. Параметр WindowTitle - заголовок окна сообщения. И параметр MessageBoxType задает стиль окна, иконку, отображаемую слева от сообщения, и одну или несколько кнопок. Этот параметр задается комбинацией констант с помощью операции "|", начинающихся на префикс MB_. Все наборы кнопок заранее определены. Функция возвращает идентификатор нажатой кнопки: IDABORT, IDRETRY, IDIGNORE, IDCANCEL, IDNO, IDYES, или IDOK.

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

    Пример программы, использующей меню, акселераторы и окна сообщений



    menu.hpp



    #include "stdafx.h"

    // Класс основного окна

    class CMainWin: public CFrameWnd {


    public:

    CMainWin();

    afx_msg void OnPaint();

    // Обработчики команд меню и акселераторов

    afx_msg void OnCommand_Alpha();

    afx_msg void OnCommand_Beta();

    afx_msg void OnCommand_Gamma();

    afx_msg void OnCommand_Epsilon();

    afx_msg void OnCommand_Zeta();

    afx_msg void OnCommand_Eta();

    afx_msg void OnCommand_Theta();

    afx_msg void OnCommand_Help();

    afx_msg void OnCommand_Time(); //Только акселератор

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    menu.cpp



    #include "stdafx.h"

    #include

    #include

    #include "MENU.HPP"

    #include "IDS.H"

    CMainWin::CMainWin()

    {

    // Прямоугольник для построения окна

    RECT rect;

    rect.left = 20; rect.top = 10;

    rect.right = 600; rect.bottom = 460;

    this->Create(0, "Menus", WS_OVERLAPPEDWINDOW, rect, 0, "MYMENU");

    // Загрузка таблицы акселераторов

    this->LoadAccelTable("MYMENU");

    }

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin;

    m_pMainWnd->ShowWindow(SW_RESTORE);

    m_pMainWnd->UpdateWindow();

    return TRUE;

    }

    // Карта откликов на сообщения

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_WM_PAINT()

    ON_COMMAND(IDM_ALPHA, OnCommand_Alpha)

    ON_COMMAND(IDM_BETA, OnCommand_Beta)

    ON_COMMAND(IDM_GAMMA, OnCommand_Gamma)

    ON_COMMAND(IDM_EPSILON, OnCommand_Epsilon)

    ON_COMMAND(IDM_ZETA, OnCommand_Zeta)

    ON_COMMAND(IDM_ETA, OnCommand_Eta)

    ON_COMMAND(IDM_THETA, OnCommand_Theta)

    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_TIME, OnCommand_Time)

    END_MESSAGE_MAP()

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC dc(this);

    CString s("Press Ctrl- T to get current date and time");

    dc.TextOut(100, 200, s);

    }

    //

    // Далее идут обработчики сообщений WM_COMMAND.

    // В них используется функция для вывода окон сообщений

    // CWnd::MessageBox(LPCTSTR, LPCTSTR, UINT).

    //

    afx_msg void CMainWin::OnCommand_Alpha()

    {

    // Использование окна сообщений

    this->MessageBox("OnCommand_Alpha() handler called.", "WM_COMMAND message",


    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Beta()

    {

    this->MessageBox("OnCommand_Beta() handler called.", "WM_COMMAND message",

    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Gamma()

    {

    this->MessageBox("OnCommand_Gamma() handler called.", "WM_COMMAND message",

    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Epsilon()

    {

    this->MessageBox("OnCommand_Epsilon() handler called.", "WM_COMMAND message",

    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Zeta()

    {

    this->MessageBox("OnCommand_Zeta() handler called.", "WM_COMMAND message",

    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Eta()

    {

    this->MessageBox("OnCommand_Eta() handler called.", "WM_COMMAND message",

    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Theta()

    {

    this->MessageBox("OnCommand_Theta() handler called.", "WM_COMMAND message",

    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("OnCommand_Help() handler called.", "WM_COMMAND message",

    MB_OK | MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnCommand_Time()

    {

    // Получаем текущее время, используя класс MFC CTime

    CTime currentTime = CTime::GetCurrentTime();

    CString s = currentTime.Format("%A %B %#d, %Y, %#I:%M%p");

    s = "OnCommand_Time() handler called.\n\n" + ("Current date and time:\n" + s);

    this->MessageBox(s, "WM_COMMAND message");

    }

    CApp App; // Единственный объект приложения

    Рис. 6. Программа, использующая меню, акселераторы и окна сообщений

    Для упрощения первоначального ознакомления, идентификаторы ресурсов и команд были перенесены в файл IDS.H. В каждом обработчике WM_COMMAND выводится окно сообщения, сообщающее о том, какое действие произошло. Один из обработчиков активизируется только с клавиатуры, и при этом выводится окно сообщения с текущим временем и датой, о чем и говорит строка в середине основного окна.


    Попробуйте выбирать различные пункты в меню.

    Также внимательно изучите исходные тексты. Попробуйте изменить меню и акселераторы с помощью редактора ресурсов. Обратите внимание, что мы использовали в проекте два новых файла: stdafx.h

    и stdafx.cpp. Первый из них включает все самые распространенные заголовочные файлы MFC, а второй - ничего не делающий модуль, использующий заголовочный файл. Это сделано для облегчения использования прекомпилированных заголовочных файлов. Также, в этом случае файл stdafx.obj

    включает всю необходимую информацию о типах.



    Диалоги. Знакомство с элементами управления



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

    Диалог (диалоговое окно) представляет собой специальный вид окна, которые предназначены для взаимодействия с пользователем. Обычно они используются для изменения настроек приложения и ввода информации. Диалоги используются очень часто. Например, практически все окна настроек приложения Microsoft Word являются диалогами.



    Взаимодействие между диалогом и пользователем



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

  • Обыкновенная кнопка (push button) - это кнопка, которую пользователь "нажимает" мышью или клавишей Enter, переместив предварительно на нее фокус ввода.



  • Контрольный переключатель ( check box) может быть либо выбранным, либо нет. Если в диалоге есть несколько контрольных переключателей, то могут быть выбраны одновременно несколько из них.


  • Радиокнопка (radio button) - это почти то же самое, что и контрольный переключатель. Только при наличии нескольких кнопок в группе может быть выбрана только одна.


  • Список (list box) содержит набор строк, из которого можно выбрать одну или несколько. Широко используется при отображении имен файлов.


  • Поле ввода (edit box) - это элемент, позволяющий ввести строку текста.


  • Комбинированный список (combo box) представляет собой список со строкой ввода.


  • Статический элемент (static control) предназначен для вывода текста или графики, но не предназначен для ввода.


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



    Классы MFC для элементов управления



    В MFC содержатся классы для всех стандартных элементов управления. Эти классы описывают сами элементы, а также содержат функции для работы с ними. Их называют классами управления. Они порождаются от класса CWnd. Таким образом, все они обладают характеристиками окна. Ниже приведены основные классы управления:



    Класс
    Элемент управления
    CButton Кнопки, селекторные кнопки и контрольные переключатели
    CEdit Поля ввода
    CListBox Списки
    CComboBox Комбинированные списки
    CScrollBar Полосы прокрутки
    Cstatic Статические элементы
    В MFC допускается также и непосредственное обращение к элементам управления, но на практике это происходит очень редко. Намного удобнее пользоваться соответствующими классами. Наиболее часто элементы управления используются с диалоговыми окнами, хотя можно создавать и отдельные элементы, расположенные в главном окне.



    Модальные и немодальные диалоги



    Есть два типа диалогов: модальные и немодальные.


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



    Диалоги как ресурсы



    Как уже было упомянуто выше, диалоги не создаются программно. Вместо этого, при необходимости из ресурсов загружаются описания диалогов, и Windows по этому описанию формирует окно и размещает на нем все элементы управления.

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



    Класс CDialog



    В MFC все диалоги являются экземплярами либо класса CDialog, либо порожденных от него классов. Лишь самые простые диалоги используют непосредственно класс CDialog. В общем же случае, необходимо определять собственный класс. Класс CDialog имеет конструкторы со следующими прототипами:

    CDialog::CDialog(LPCSTR ResourceName, CWnd *Owner = 0);

    CDialog::CDialog(UINT ResourceID, CWnd *Owner = 0);

    CDialog::CDialog();

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

    Обработка сообщений от диалогов

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


    Для каждого диалога организуется собственная очередь сообщений, так же точно, как и для главного окна.

    Каждый раз, когда элемент управления диалога активизируется, диалогу посылается сообщение WM_COMMAND. С этим сообщением передается идентификатор элемента управления. Для обработки сообщений в карту сообщений диалога нужно поместить макрос ON_COMMAND(). Многие элементы управления генерируют также идентификационный код, который позволяет определить, какое действие было произведено с элементом управления. Во многих случаях по этому коду выбирается тот или иной обработчик. Как будет показано далее, в MFC

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

    Вызов диалога

    После того, как объект класса диалога создан, необходимо вызвать член-функцию DoModal(). Результатом будет модальное отображение диалога. Прототип функции следующий:

    virtual int CDialog::DoModal();

    Функция возвращает код завершения, генерируемый диалогом при закрытии, или -1, если окно не может быть отображено. Если при отображении диалога произошла ошибка, возвращается IDABORT. Функция не завершается, пока диалог не будет закрыт.

    Закрытие диалога

    По умолчанию диалог закрывается при получении сообщения с идентификатором либо IDOK, либо IDCANCEL. Они предопределены и обычно связаны с кнопками подтверждения и отмены. Класс CDialog содержит встроенные обработчики для этих двух случаев, OnOK() и OnCancel(). Их не нужно включать в очередь сообщений диалога. Но их можно переопределить, что дает возможность программисту управлять закрытием диалога. Для программного закрытия диалога нужно вызвать член-функцию с прототипом:

    void CDialog::EndDialog(int RetCode);

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

    Пример программы с диалоговым окном

    Ниже приведены исходные тексты примера программы с диалогом. Диалог выбирается с помощью меню.


    Он имеет три элемента управления - кнопки "Red", "Green" и "Cancel". Также переопределена функция OnCancel(), что позволяет при попытке закрытия диалога вывести окно с подтверждением. Диалог закрывается программно, с возвратом кода завершения 7, который затем возвращается функцией DoModal() и отображается в окне. Это сделано лишь для демонстрации, реально же обычно возвращается IDCANCEL. Диалог создавался визуально в среде Visual C++. Здесь приводится также файл с идентификаторами, сгенерированный средой. В дальнейшем этот файл приводиться не будет, потому что, вообще говоря, он не предназначен для чтения человеком. Так как все идентификаторы в примерах имеют осмысленные имена, то загрузив проект в среду IDE, Вы сможете сразу узнать, какие идентификаторы каким элементам управления соответствуют.



    resource.h



    //{{NO_DEPENDENCIES}}

    // Microsoft Developer Studio generated include file.

    // Used by Resources.rc

    //

    #define IDC_RED 1007

    #define IDC_GREEN 1008

    #define IDM_DIALOG 40001

    #define IDM_HELP 40002

    #define IDM_EXIT 40003

    // Next default values for new objects

    //

    #ifdef APSTUDIO_INVOKED

    #ifndef APSTUDIO_READONLY_SYMBOLS

    #define _APS_NO_MFC 1

    #define _APS_3D_CONTROLS 1

    #define _APS_NEXT_RESOURCE_VALUE 106

    #define _APS_NEXT_COMMAND_VALUE 40010

    #define _APS_NEXT_CONTROL_VALUE 1008

    #define _APS_NEXT_SYMED_VALUE 101

    #endif

    #endif



    Dialogs.hpp



    #include "stdafx.h"

    #include "resource.h"

    // Класс главного окна

    class CMainWin: public CFrameWnd {

    public:

    // Конструктор немного изменен.

    // Title -- заголовок окна, HowShow -- код показа окна

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    // Установка строки, отображаемой в верхней части окна

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr; // Строка, отображаемая в верхней части окна

    DECLARE_MESSAGE_MAP()

    };


    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалогового окна

    class CSampleDialog: public CDialog {

    public:

    // DialogName -- идентификатор диалога в ресурсах,

    // Owner -- окно-владелец (если NULL, то главное окно)

    CSampleDialog(char *DialogName, CWnd *Owner = NULL);

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnCancel();

    private:

    DECLARE_MESSAGE_MAP()

    };



    Dialogs.cpp



    #include "stdafx.h"

    #include "Dialogs.hpp"

    #include "resource.h"

    CApp App;

    //---------------------------------------------------------------------------

    CMainWin::CMainWin(CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "DIALOGMENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CSampleDialog sampleDialog("SAMPLEDIALOG", this);

    int result = sampleDialog.DoModal();

    char s[20];

    sprintf(s, "%i", result);

    this->SetInfoStr(infoStr + ". " +

    "Функция CDialog::DoModal() возвратила " + CString(s));

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->MessageBox("Завершение приложения.", "Dialogs", MB_ICONEXCLAMATION);

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Программа, использующая диалог.\n"

    "Написана с помощью MFC 4.0.",

    "Dialogs", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    // Карта откликов на сообщения главного окна

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)


    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Dialogs - приложение с диалогом", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    // Член-функция CWnd::GetOwner() возвращает указатель

    // на окно-собственник данного окна.

    // Так как в данном случае это главное окно,

    // мы проводим преобразование к CMainWin

    // и вызываем метод SetInfoStr()

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    // См. комментарий в CSampleDialog::OnCommand_Red()

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Green'");

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    // См. комментарий в CSampleDialog::OnCommand_Red()

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Cancel'");

    // Закрываем диалог, только если пользователь подтвердил намерение.

    // Значение 7 будет возвращено DoModal().

    int response = this->MessageBox("Вы уверены ?", "Cancel", MB_YESNO);

    if(response == IDYES)

    this->EndDialog(7);

    }

    // Карта откликов на сообщения диалога

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    Рис. 7. Пример приложения с диалогом



    Инициализация диалога



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


    При получении такого сообщения MFC автоматически вызывает член-функцию OnInitDialog(), которая является стандартным обработчиком, определенным в классе CDialog. Эта функция должна переопределяться в программе, если необходимо выполнение различных инициализационных действий. Прототип функции такой:

    virtual BOOL CDialog::OnInitDialog();

    Эта функция вызывается до того, как диалог будет отображен. Она должна возвращать TRUE, чтобы Windows могла передать фокус ввода (т. е. сделать активным) на первый элемент управления в окне.

    Первым действием в переопределенной функции должен быть вызов функции CDialog::OnInitDialog().

    В дальнейшем мы будем использовать эту функцию в примерах.



    Списки



    Список является одним из наиболее распространенных элементов управления. В MFC

    работа со списком осуществляется через класс CListBox.



    Основы работы со списками



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

    Прием идентификационных кодов списка

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

    Единственный код, которым мы воспользуемся, называется LBN_DBLCLK. Он посылается, когда пользователь выполняет двойной щелчок на элементе списка. (При определении списка в ресурсах должна быть установлена опция Notify, чтобы он мог генерировать это сообщение.) Когда выбор произведен, необходимо запросить список, чтобы узнать о том, какой элемент выбран.


    Для обработки сообщения LBN_DBLCLK необходимо поместить его обработчик в карту сообщений. Но это будет не макрос ON_COMMAND(). Вместо этого в данном случае используются специальные макрокоманды. Для нашего сообщения это будет ON_LBN_DBLCLK(). Она имеет такой вид:

    ON_LBN_DBLCLK(ИдентификаторСписка, ИмяОбработчика)

    Многие сообщения обрабатываются подобным образом. Названия всех макросов для таких сообщений начинаются с префикса ON_LBN_.

    Передача сообщений списку

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

    Списку может быть послано несколько разных сообщений. Для каждого класс CListBox содержит отдельную член-функцию. Например, рассмотрим следующие функции:

    int CListBox::AddString(LPCSTR StringToAdd);

    int CListBox::GetCurSel() const;

    int CListBox::GetText(int Index, LPCSTR StringVariable);

    Функция AddString() вставляет указанную строку в список. По умолчанию она вставляется в конец списка. Начало списка имеет индекс 0. Функция GetCurSel()

    возвращает индекс текущего выделенного элемента. Если ни один элемент не выбран, возвращает LB_ERR.

    Функция GetText() получает строку, связанную с указанным индексом. Строка копируется в символьный массив по адресу StringVariable.

    Получение указателя на список

    Функции CListBox работают с объектами CListBox. Значит, необходимо получить указатель на объект списка, что делается с помощью функции GetDlgItem(), являющейся членом класса CWnd:

    СWnd *CWnd::GetDlgItem(int ItemIdentifier) const;

    Эта функция возвращает указатель на объект, чей идентификатор передан как параметр. Если такой объект не существует, то возвращается 0. Необходимо понимать, что указатель, возвращаемый функцией, является временным.


    Он действителен только в пределах текущего обработчика.

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

    Инициализация списка

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

    Пример программы

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



    Listbox.hpp



    #include "stdafx.h"

    #include "resource.h"

    // Класс основного окна

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалога

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog(char *DialogName, CWnd *Owner = NULL);

    BOOL OnInitDialog();

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnSelectFruit();

    afx_msg void OnCancel();

    private:

    DECLARE_MESSAGE_MAP()

    };



    Listbox.cpp



    #include "stdafx.h"

    #include

    #include "Listbox.hpp"

    #include "Resource.h"

    //---------------------------------------------------------------------------


    //

    // Реализация CMainWin

    //

    CMainWin::CMainWin( CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "MENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CSampleDialog sampleDialog("SAMPLEDIALOG");

    sampleDialog.DoModal();

    this->SetInfoStr("Диалог закрыт.");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Демонстрация элемента управления Listbox",

    "Listbox", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr, infoStr.GetLength());

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Listbox", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Green'");

    }

    // Инициализация диалога

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog(); // Вызвать метод базового класса

    // Получить объект списка Listbox


    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    // Добавить строки в список

    listBoxPtr->AddString("Яблоко");

    listBoxPtr->AddString("Слива");

    listBoxPtr->AddString("Груша");

    listBoxPtr->AddString("Апельсин");

    listBoxPtr->AddString("Мандарин");

    listBoxPtr->AddString("Абрикос");

    listBoxPtr->AddString("Персик");

    listBoxPtr->AddString("Ананас");

    return TRUE;

    }

    // Обработчик сообщения выбора элемента

    // ( двойной щелчок на списке или нажатие специальной кнопки)

    afx_msg void CSampleDialog::OnSelectFruit()

    {

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    // Получить номер текущего выделенного элемента списка

    int index = listBoxPtr->GetCurSel();

    // Если результат LB_ERR, то ни один элемент еще не выделен

    if(index == LB_ERR) {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Ни один фрукт не выделен");

    }

    else {

    char s[100];

    // Получить текст выделенного элемента

    listBoxPtr->GetText(index, s);

    CString infoStr;

    infoStr.Format("Выбран фрукт %s с индексом %i", s, index);

    // Вывести сообщение

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr(infoStr);

    }

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Cancel'");

    this->EndDialog(TRUE);

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    ON_COMMAND(IDC_SELFRUIT, OnSelectFruit)

    ON_LBN_DBLCLK(IDC_LB1, OnSelectFruit) // Сообщение от списка

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    CApp App;

    Рис. 8. Пример приложения со списком



    Поле ввода



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


    Даже зная, как создавать только поля ввода и списки, уже можно писать полезные программы.

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

    Для получения текущего содержимого поля вода, состоящего из одной строки, используется функция GetWindowText(). Ее прототип таков:

    int CWnd::GetWindowText(LPSTR StringVariable, int MaxStringLen) const;

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

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

    void CWnd::SetWindowText(LPCSTR String);

    Пример программы с полем ввода



    edbox.hpp



    #include "stdafx.h"

    #include "resource.h"

    // Класс основного окна

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалога

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog(char *DialogName, CWnd *Owner);

    BOOL OnInitDialog();

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnCommand_EditOK(); // Здесь читаются данные поля ввода

    afx_msg void OnSelectFruit();


    afx_msg void OnCancel();

    private:

    DECLARE_MESSAGE_MAP()

    };



    edbox.hpp



    #include "stdafx.h"

    #include

    #include "Edbox.hpp"

    #include "resource.h"

    //

    // Реализация

    //

    //---------------------------------------------------------------------------

    CMainWin::CMainWin( CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "DIALOGMENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CSampleDialog sampleDialog("SAMPLEDIALOG", this);

    sampleDialog.DoModal();

    this->SetInfoStr("Диалог закрыт");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Программа, использующая Editbox\n",

    "Editbox", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Editbox", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    listBoxPtr->AddString("Яблоко");

    listBoxPtr->AddString("Слива");


    listBoxPtr->AddString("Груша");

    listBoxPtr->AddString("Апельсин");

    listBoxPtr->AddString("Мандарин");

    listBoxPtr->AddString("Абрикос");

    listBoxPtr->AddString("Персик");

    listBoxPtr->AddString("Ананас");

    // Получить объект Editbox

    CEdit *editBoxPtr = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    // Установить текст в Editbox

    editBoxPtr->SetWindowText("Вася");

    return TRUE;

    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    ((CMainWin *) (this->GetOwner()))->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    ((CMainWin *) (this->GetOwner()))->SetInfoStr("Нажата кнопка 'Green'");

    }

    // Здесь читается текст из Editbox

    afx_msg void CSampleDialog::OnCommand_EditOK()

    {

    // Получить объект Editbox

    CEdit *pCEdit = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    char s[1024];

    // Прочитать текст из Editbox

    pCEdit->GetWindowText(s, sizeof(s) / sizeof(s[0]) - 1);

    // Вывести информацию в окно

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(CString("Введена строка \"") + s + "\"");

    }

    afx_msg void CSampleDialog::OnSelectFruit()

    {

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    int index = listBoxPtr->GetCurSel();

    if(index == LB_ERR) {

    ((CMainWin *) (this->GetOwner()))->SetInfoStr("Ни один фрукт не выделен");

    }

    else {

    char s[100];

    listBoxPtr->GetText(index, s);

    CString infoStr;

    infoStr.Format("Выбран фрукт %s с индексом %i", s, index);

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(infoStr);

    }

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    this->EndDialog(TRUE);

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    ON_COMMAND(IDC_EDITOK, OnCommand_EditOK)

    ON_COMMAND(IDC_SELFRUIT, OnSelectFruit)

    ON_LBN_DBLCLK(IDC_LB1, OnSelectFruit)


    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    CApp App;

    Рис. 9. Приложение со строкой редактирования в диалоге



    Немодальные диалоги



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

    Для создания немодального диалога нужно выполнить два действия. Во-первых, нужно создать "пустой" объект диалога, то есть не связанный с шаблоном из ресурсов. Привязка к ресурсам осуществляется через функцию Create(). Рассмотрим этот процесс подробнее.

    Для создания объекта немодального диалога нужно использовать конструктор CDialog::CDialog() без параметров. Он объявлен как protected-член класса. Это означает, что он может быть вызван только изнутри член-функции порожденного класса. Это сделано для того, чтобы программист обязательно определял свой порожденный класс для немодального диалога, и определял в нем дополнительные операции для немодального диалога.

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

    BOOL CDialog::Create(LPCSTR ResourceName, CWnd *Owner = 0);

    BOOL CDialog::Create(UINT ResourceId, CWnd *Owner = 0);

    Первый параметр определяет идентификатор диалога в ресурсах. Второй параметр, как обычно, определяет окно-собственник для диалога.

    Необходимо помнить о том, что объект немодального диалога должен существовать в течение всего времени использования диалога. Функция Create() отображает окно и после этого немедленно завершается. А объект окна должен существовать.

    В отличие от модальных окон, немодальные не становятся автоматически видимыми при вызове. Чтобы диалог сразу был видимым, нужно в ресурсном редакторе установить опцию Visible. Или можно использовать функцию ShowWindow().

    Для закрытия немодального диалога нужно использовать функцию DestroyWindow(). Это означает, что функции OnCancel()


    и/или OnOK() должны быть переопределены.

    Пример программы с немодальным диалогом



    ModelessDialog.hpp



    #include "stdafx.h"

    #include "resource.h"

    class CMainWin;

    // Класс немодального диалога

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog();

    // Функция привязки диалога к ресурсам

    BOOL Create(LPCSTR DialogName, CWnd *Owner = 0);

    BOOL OnInitDialog();

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnCommand_EditOK();

    afx_msg void OnSelectFruit();

    afx_msg void OnCancel();

    private:

    // Переменная, показывающая, используется ли в данный момент диалог

    BOOL inUse;

    CMainWin *owner;

    DECLARE_MESSAGE_MAP()

    };

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    // Экземпляр объекта диалога -

    // существует все время, пока существует основное окно

    CSampleDialog dialog;

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    ModelessDialog.cpp



    #include "stdafx.h"

    #include

    #include "ModelessDialog.hpp"

    #include "resource.h"

    static CApp App;

    //---------------------------------------------------------------------------

    CMainWin::CMainWin(CString Title, int HowShow)

    // Вызываем конструктор без параметров класса диалога

    :dialog(), infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "DIALOGMENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    dialog.Create("SAMPLEDIALOG", this);

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);


    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Программа, использующая немодальный диалог.",

    "О программе", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Приложение с немодальным диалогом", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog()

    :CDialog()

    {

    // Объект создан, но пока не используется

    inUse = FALSE;

    }

    BOOL CSampleDialog::Create(LPCSTR DialogName, CWnd *Owner)

    {

    // Если диалог уже используется (был отображен), то только отобразить его

    if(inUse) {

    this->ShowWindow(SW_SHOW);

    return TRUE;

    }

    inUse = TRUE; // Диалог используется

    // Создать диалог и получить результат создания

    BOOL success = (CDialog::Create(DialogName, Owner) != FALSE);

    owner = (CMainWin *) Owner; // Собственник

    return success;

    }

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    listBoxPtr->AddString("Яблоко");

    listBoxPtr->AddString("Слива");

    listBoxPtr->AddString("Груша");

    listBoxPtr->AddString("Апельсин");

    listBoxPtr->AddString("Мандарин");

    listBoxPtr->AddString("Абрикос");

    listBoxPtr->AddString("Персик");

    listBoxPtr->AddString("Ананас");

    CEdit *editBoxPtr = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    editBoxPtr->SetWindowText("Вася");

    return TRUE;


    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    owner->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    owner->SetInfoStr("Нажата кнопка 'Green'");

    }

    afx_msg void CSampleDialog::OnCommand_EditOK()

    {

    CEdit *pCEdit = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    char s[1024];

    pCEdit->GetWindowText(s, sizeof(s) / sizeof(s[0]) - 1);

    owner->SetInfoStr(CString("Введена строка \"") + s + "\"");

    }

    afx_msg void CSampleDialog::OnSelectFruit()

    {

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    int index = listBoxPtr->GetCurSel();

    if(index == LB_ERR) {

    owner->SetInfoStr("Ни один фрукт не выделен");

    }

    else {

    char s[100];

    listBoxPtr->GetText(index, s);

    CString infoStr;

    infoStr.Format("Выбран фрукт %s с индексом %i", s, index);

    owner->SetInfoStr(infoStr);

    }

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    owner->SetInfoStr("Нажата кнопка 'Cancel'");

    // Удалить диалог

    this->DestroyWindow();

    // Больше диалог не используется

    inUse = FALSE;

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    ON_COMMAND(IDC_EDITOK, OnCommand_EditOK)

    ON_COMMAND(IDC_SELFRUIT, OnSelectFruit)

    ON_LBN_DBLCLK(IDC_LB1, OnSelectFruit)

    END_MESSAGE_MAP()

    Рис. 9. Пример приложения с немодальным диалогом



    Дополнительные элементы управления



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



    Контрольные переключатели



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


    Кроме того, с переключателем связано текстовое поле с описанием предоставляемой переключателем опции. Если в переключателе стоит метка выбора, то говорится, что он выбран (установлен). Контрольные переключатели в MFC описываются с помощью класса CButton (так как контрольный переключатель - разновидность кнопки).

    Контрольные переключатели могут быть автоматическими

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

    Сообщения контрольного переключателя

    Каждый раз, когда пользователь щелкает мышью на контрольном переключателе (или нажимает клавишу Spacebar, когда фокус ввода находится на переключателе), диалогу посылается сообщение WM_COMMAND с идентификационным кодом BN_CLICKED. Это сообщение обрабатывается с помощью макроса ON_BN_CLICKED(). При работе с автоматическими переключателями отвечать на это сообщение нет необходимости. Но при работе с программными переключателями, чтобы изменять их состояние, необходимо отвечать на это сообщение. Для этого нужно поместить макрос в карту сообщений и написать обработчик.

    Установка и чтение состояния контрольного переключателя

    Чтобы установить контрольный переключатель в заданное состояние, нужно использовать функцию SetCheck() c прототипом:

    void CButton::SetCheck(int Status);

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

    Текущее состояние переключателя можно определить с помощью функции GetCheck():

    int CButton::GetCheck() const;

    Функция возвращает 1, если переключатель установлен, и 0 в противном случае.


    Инициализация контрольных переключателей

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

    Пример программы



    CheckBox.hpp



    #include "stdafx.h"

    #include "resource.h"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Status();

    afx_msg void OnCommand_Exit();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    class CMyDialog: public CDialog {

    public:

    CMyDialog(char *DialogName, CWnd *Owner = 0);

    BOOL OnInitDialog();

    // Обработчик кликов мышью на неавтоматическом переключателе

    afx_msg void OnCheckbox1();

    afx_msg void OnOK();

    private:

    DECLARE_MESSAGE_MAP()

    };



    CheckBox.cpp



    #include "stdafx.h"

    #include "CheckBox.hpp"

    #include "resource.h"

    // Переменные, хранящие состояния переключателей

    int cb1status = 0, cb2status = 0;

    CApp App;

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMainWin::CMainWin(CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50; rect.bottom = 460; rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "MENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("MENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CMyDialog dialog("DIALOG", this);

    dialog.DoModal();

    }

    // Отображаем состояние переключателей

    afx_msg void CMainWin::OnCommand_Status()

    {

    CString str;

    if(cb1status != 0)

    str = "Checkbox1 выбран.\n";

    else

    str = "Checkbox1 очищен.\n";


    if(cb2status != 0)

    str = str + "Checkbox2 выбран.\n";

    else

    str = str + "Checkbox2 очищен.\n";

    this->MessageBox(str, "Состояние");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_STATUS, OnCommand_Status)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Использование контрольных переключателей",

    SW_RESTORE);

    return TRUE;

    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMyDialog::CMyDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    BOOL CMyDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    // Получаем адреса объектов переключателей

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    pcb1->SetCheck(cb1status);

    pcb2->SetCheck(cb2status);

    return TRUE;

    }

    afx_msg void CMyDialog::OnCheckbox1()

    {

    // Получаем адрес объекта

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    // Реализуем переключение

    if(pcb1->GetCheck() == 1)

    pcb1->SetCheck(0);

    else

    pcb1->SetCheck(1);

    }

    afx_msg void CMyDialog::OnOK()

    {

    // Получаем адреса объектов

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    // Считываем состояния переключателей

    cb1status = pcb1->GetCheck();

    cb2status = pcb2->GetCheck();

    // Печатаем в окне информацию о состоянии переключателей

    CString str;

    str.Format("Состояние первого переключателя %i, второго %i.",


    cb1status, cb2status);

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(str);

    this->EndDialog(0);

    }

    BEGIN_MESSAGE_MAP(CMyDialog, CDialog)

    // Обработка кликов на певом (неавтоматическом) переключателе

    ON_BN_CLICKED(IDC_CHECK1, OnCheckbox1)

    END_MESSAGE_MAP()

    Рис. 10. Приложение с контрольными переключателями



    Статические элементы управления



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

    Если элементу присвоен идентификатор IDC_STATIC (-1), то он не будет принимать и генерировать сообщений. Но вообще статические элементы управления могут генерировать и принимать сообщения. Для этого элементу нужно присвоить другой идентификатор. Тогда элемент уже не будет статическим. Это часто используется. Например, можно поменять текст в текстовой строке с помощью функции SetWindowText(), чтобы отобразить некоторую информацию. Мы будем использовать это в примере, демонстрирующем работу с полосами прокрутки.



    Радиокнопки



    Радиокнопки очень похожи на контрольные переключатели. Только их работа организована таким образом, что из группы кнопок может быть установлена только одна. При установке другой кнопки предыдущая сбрасывается. Радиокнопки также бывают программные и автоматические; но так как управлять радиокнопками сложно, то сейчас почти всегда используются автоматические. Мы рассмотрим только автоматические радиокнопки.

    Радиокнопки объединяются в группы. В одном диалоге может быть несколько групп. Для первой кнопки каждой группы в ресурсном редакторе нужно установить опцию Group, а для других кнопок группы она должна быть сброшена. Кнопки нумеруются в порядке значений их идентификаторов (то есть в порядке их создания в ресурсном редакторе). Если в диалоге все радиокнопки образуют одну группу, то опцию Group можно не устанавливать.


    Радиокнопки управляются с помощью класса CButton. Также, как для контрольных переключателей, состояние радиокнопок можно изменять с помощью функции SetCheck()

    и читать с помощью функции GetCheck().

    При создании диалога все радиокнопки сброшены. Таким образом, в функции OnInitDialog()

    нужно установить начальное состояние программно. Хотя из программы можно установить сразу несколько радиокнопок или сбросить все, нормальный стиль программирования под Windows предполагает, что всегда будет установлена одна и только одна радиокнопка. Таков принятый стиль.

    Пример программы с радиокнопками и статическими элементами управления



    Radio.hpp



    #include "stdafx.h"

    #include "resource.h"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Status();

    afx_msg void OnCommand_Exit();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалога с радиокнопками и статическими элементами управления

    class CMyDialog: public CDialog {

    public:

    CMyDialog(char *DialogName, CWnd *Owner);

    BOOL OnInitDialog();

    afx_msg void OnOK();

    private:

    DECLARE_MESSAGE_MAP()

    };



    Radio.cpp



    #include "stdafx.h"

    #include "radio.hpp"

    #include "resource.h"

    // Переменные, хранящие состояния радиокнопок

    // и контрольных переключателей

    int cb1status = 0, cb2status = 0;

    int rb1status = 1, rb2status = 0;

    CApp App;

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMainWin::CMainWin(CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50; rect.bottom = 460; rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "MENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("MENU");


    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CMyDialog dialog("DIALOG", this);

    dialog.DoModal();

    }

    // Вывод окна состояния

    afx_msg void CMainWin::OnCommand_Status()

    {

    CString str;

    if(cb1status != 0)

    str = "Checkbox1 выбран.\n";

    else

    str = "Checkbox1 очищен.\n";

    if(cb2status != 0)

    str = str + "Checkbox2 выбран.\n";

    else

    str = str + "Checkbox2 очищен.\n";

    if(rb1status != 0)

    str = str + "Radio1 выбран.\n";

    else

    str = str + "Radio1 очищен.\n";

    if(rb2status != 0)

    str = str + "Radio2 выбран.";

    else

    str = str + "Radio2 очищен.";

    this->MessageBox(str, "Состояние");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr, infoStr.GetLength());

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_STATUS, OnCommand_Status)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Радиокнопки",

    SW_RESTORE);

    return TRUE;

    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMyDialog::CMyDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    BOOL CMyDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    pcb1->SetCheck(cb1status);

    pcb2->SetCheck(cb2status);

    // Получаем адреса радиокнопок

    CButton *prb1 = (CButton *) (this->GetDlgItem(IDC_RADIO1));

    CButton *prb2 = (CButton *) (this->GetDlgItem(IDC_RADIO2));

    // Устанавливаем состояние радиокнопок

    prb1->SetCheck(rb1status);


    prb2->SetCheck(rb2status);

    return TRUE;

    }

    // Здесь читаем состояние кнопок и запоминаем его в переменных

    afx_msg void CMyDialog::OnOK()

    {

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    cb1status = pcb1->GetCheck();

    cb2status = pcb2->GetCheck();

    // Получем адреса радиокнопок

    CButton *prb1 = (CButton *) (this->GetDlgItem(IDC_RADIO1));

    CButton *prb2 = (CButton *) (this->GetDlgItem(IDC_RADIO2));

    // Запоминаем состояние радиокнопок

    rb1status = prb1->GetCheck();

    rb2status = prb2->GetCheck();

    // Выводим состояние в окно

    CString str;

    str.Format("Check1: %i, Check2: %i, "

    "Radio1: %i, Radio2: %i.",

    cb1status, cb2status, rb1status, rb2status);

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(str);

    this->EndDialog(IDOK);

    }

    BEGIN_MESSAGE_MAP(CMyDialog, CDialog)

    END_MESSAGE_MAP()

    Рис. 11. Пример приложения с радиокнопками



    Полосы прокрутки



    В Windows есть два типа полос прокрутки. Элементы первого типа являются частью окна (включая диалоговое окно), поэтому их называют полосами прокрутки окна. Элементы второго типа существуют независимо и называются независимыми полосами прокрутки.

    Элементы первого типа описываются классом CWnd, а второго - CScrollBar. Работа с независимыми полосами прокрутки требует чуть больше усилий. Мы рассмотрим здесь оба типа.



    Создание стандартных полос прокрутки



    Если требуется, чтобы окно содержало стандартные полосы прокрутки, они должны быть явно заданы. Применительно у главному окну это означает, что при вызове функции Create() в качестве параметров стиля должны быть указаны опции WS_VSCROLL и WS_HSCROLL. В случае диалогового окна, достаточно установить соответствующие опции диалога в ресурсном редакторе. Если все это сделано, то полосы прокрутки будут отображаться в окне автоматически.



    Независимые полосы прокрутки в диалогах



    Для включения в диалог независимой полосы прокрутки используется ресурсный редактор.


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



    Обработка сообщений полосы прокрутки



    Так как полоса прокрутки пришла из 16-разрядной Windows 3.1, управлять полосой прокрутки довольно сложно. Во всяком случае, полоса прокрутки сама ничего не делает. Даже для того, чтобы она "прокручивалась" на экране, необходим дополнительный программный код.

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

    afx_msg void CWnd::OnVScroll(UINT SBCode, int Pos, CScrollBar *SB);

    afx_msg void CWnd::OnHScroll(UINT SBCode, int Pos, CScrollBar *SB);

    Следует отметить, что при наличии нескольких горизонтальных или вертикальных полос прокрутки для всех них будет вызываться один и тот же обработчик. Первый параметр, SBCode, содержит код выполненного над полосой прокрутки действия. Все наиболее распространенные коды представлены в листинге программы-примера.

    Если работа ведется с вертикальной полосой прокрутки, то при каждом изменении положения ползунка на одну позицию вверх посылается код SB_LINEUP. При изменении позиции на одну вниз посылается код SB_LINEDOWN. Аналогично, при постраничном перемещении генерируются коды SB_PAGEUP и SB_PAGEDOWN.

    Если работа ведется с горизонтальной полосой прокрутки, то при каждом передвижении ползунка на одну позицию влево посылается код SB_LINELEFT. При изменении его положения на одну позицию вправо посылается код SB_LINERIGHT. При постраничном перемещении генерируются сообщения SB_PAGELEFT и SB_PAGERIGHT.

    Для обоих типов полос прокрутки при перемещении ползунка на новую позицию посылается код SB_THUMBPOSITION. Если при этом кнопка мыши удерживается нажатой, то дополнительно генерируется сообщение с кодом SB_THUMBTRACK.


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

    Если сообщение сгенерировано стандартной полосой прокрутки, то параметр SB будет равен 0. Если же оно было сгенерировано независимой полосой прокрутки, то этот параметр будет содержать указатель на объект. Это предоставляет весьма неуклюжий способ различать, какая конкретно независимая полоса прокрутки сгенерировала сообщение. Для этого нужно использовать функцию CWnd:: GetDlgCtrlID(), которая возвращает идентификатор элемента управления. Такое неудобство связано с тем, что MFC повторяет внутреннее устройство Windows, а не является библиотекой сверхвысокого уровня для быстрой разработки приложений.



    Управление полосой прокрутки



    Раньше для установки различных параметров полосы прокрутки использовались отдельные функции, которые были в Windows 3.1. С появлением Windows 95 появилась возможность управления полосами прокрутки с помощью одной функции SetScrollInfo(). Только эта функция позволяет сделать полосу прокрутки пропорциональной (в этом случае, чем меньше диапазон полосы прокрутки, тем длиннее будет ее ползунок). Функция GetScrollInfo() предназначена для чтения параметров полосы прокрутки. В отличие от старых функций, эти функции работают с 32-разрядными данными. Поэтому мы будем использовать только новые функции.

    Для стандартных полос прокрутки используется функция:

    BOOL CWnd::SetScrollInfo(int Which, LPSCROLLINFO pSI,

    BOOL Redraw = TRUE);

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

    Для независимых полос прокрутки используется функция:

    BOOL CScrollBar::SetScrollInfo(LPSCROLLINFO pSI, BOOL Redraw = TRUE);

    Оба параметра имеют такой же смысл.

    Для чтения параметров стандартных полос прокрутки используется функция:


    BOOL CWnd::GetScrollInfo(int Which, LPSCROLLINFO pSI,

    UINT Mask = SIF_ALL);

    Информация, получаемая от полосы прокрутки, записывается в структуру по адресу pSI. Значение параметра Mask определяет, какая информация записывается в структуру. По умолчанию заполняются все поля.

    Для независимых полос прокрутки вариант функции таков:

    BOOL CScrollBar::SetScrollInfo(LPSCROLLINFO pSI, UINT Mask = SIF_ALL);

    Значение параметров аналогично предыдущему случаю.

    Во всех вариантах функций используется следующая структура типа SCROLLINFO:

    typedef struct tagSCROLLINFO {

    UINT cbSize; // размер самой структуры

    UINT fMask; // маска для параметров

    int nMin; // нижняя граница диапазона

    int nMax; // верхняя граница диапазона

    UINT nPage; // размер страницы

    int nPos; // позиция ползунка

    int nTrackPos; // позиция ползунка во время перемещения

    } SCROLLINFO;

    Поле fMask определяет, какое из полей структуры содержит требуемую информацию. Используются константы с префиксом SIF_, которые можно объединять операцией "|". Поле nPos содержит статическую позицию ползунка. Поле nPage содержит размер страницы для пропорциональных полос прокрутки. Для получения обычной пропорциональной полосы прокрутки в этом поле нужно задать значение 1. Поля nMin и

    nMax содержат нижнюю и верхнюю границы диапазона полосы прокрутки. Поле nTracksPos содержит позицию ползунка при его перемещении, это значение не может быть установлено.

    Пример программы с пропорциональными полосами прокрутки



    Scrollbars.hpp



    #include "stdafx.h"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin();

    afx_msg void OnDialog();

    afx_msg void OnExit();

    // Обработчики сообщений стандартных полос прокрутки

    afx_msg void OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB);

    afx_msg void OnHScroll(UINT SBCode, UINT Pos, CScrollBar *SB);

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog(char *DialogName, CWnd *Owner = 0)


    :CDialog(DialogName, Owner) { }

    BOOL OnInitDialog();

    // Обработчик сообщения независимой вертикальной полосы прокрутки

    // в диалоге

    afx_msg void OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB);

    DECLARE_MESSAGE_MAP()

    };



    Scrollbars.cpp



    #include "stdafx.h"

    #include "Scrollbars.hpp"

    #include "resource.h"

    // Диапазон значений для полос прокрутки

    const int RANGEMAX = 10;

    int ctlsbpos = 0; // Текущее положение независимой полосы прокрутки диалога

    int vsbpos = 0; // То же, для вертикальной полосы прокрутки окна

    int hsbpos = 0; // То же, для горизонтальной полосы прокрутки окна

    CMainWin::CMainWin()

    {

    Create(0, "Полосы прокрутки (32-битные функции)",

    WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,

    rectDefault, 0, "MENU");

    LoadAccelTable("MENU");

    // Устанавливаем свойства пропорциональной полосы прокрутки

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_RANGE | SIF_PAGE;

    si.nMin = 0;

    si.nMax = RANGEMAX;

    si.nPage = 1;

    this->SetScrollInfo(SB_VERT, &si);

    this->SetScrollInfo(SB_HORZ, &si);

    }

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin;

    m_pMainWnd->ShowWindow(m_nCmdShow);

    m_pMainWnd->UpdateWindow();

    return TRUE;

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnDialog)

    ON_COMMAND(IDM_EXIT, OnExit)

    ON_WM_VSCROLL()

    ON_WM_HSCROLL()

    END_MESSAGE_MAP()

    afx_msg void CMainWin::OnDialog()

    {

    CSampleDialog dlg("SAMPLEDIALOG");

    dlg.DoModal();

    }

    afx_msg void CMainWin::OnExit()

    {

    SendMessage(WM_CLOSE);

    }

    // Оработчик сообщений вертикальной полосы прокрутки окна

    afx_msg void CMainWin::OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB)

    {

    // Анализируем коды полосы прокрутки

    if(SBCode == SB_LINEDOWN) {

    ++vsbpos;

    if(vsbpos > RANGEMAX)

    vsbpos = RANGEMAX;

    }

    if(SBCode == SB_LINEUP) {

    --vsbpos;

    if(vsbpos < 0)

    vsbpos = 0;

    }

    if(SBCode == SB_THUMBPOSITION) {

    vsbpos = Pos;

    }

    if(SBCode == SB_THUMBTRACK) {

    vsbpos = Pos;


    }

    if(SBCode == SB_PAGEDOWN) {

    vsbpos += 5;

    if(vsbpos > RANGEMAX)

    vsbpos = RANGEMAX;

    }

    if(SBCode == SB_PAGEUP) {

    vsbpos -= 5;

    if(vsbpos < 0)

    vsbpos = 0;

    }

    // Устанавливаем новое состояние

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_POS;

    si.nPos = vsbpos;

    this->SetScrollInfo(SB_VERT, &si);

    // Выводим состояние полосы прокрутки в окно.

    // Это только пример, так не следует делать в реальных программах!

    char str[255];

    CClientDC dc(this);

    wsprintf(str, "Vertical Scroll Bar: %i ", vsbpos);

    dc.TextOut(2, 2, str);

    }

    // Оработчик сообщений горизонтальной полосы прокрутки окна

    afx_msg void CMainWin::OnHScroll(UINT SBCode, UINT Pos, CScrollBar *SB)

    {

    // Анализируем коды полосы прокрутки

    if(SBCode == SB_LINERIGHT) {

    ++hsbpos;

    if(hsbpos > RANGEMAX)

    hsbpos = RANGEMAX;

    }

    if(SBCode == SB_LINELEFT) {

    --hsbpos;

    if(hsbpos < 0)

    hsbpos = 0;

    }

    if(SBCode == SB_THUMBPOSITION) {

    hsbpos = Pos;

    }

    if(SBCode == SB_THUMBTRACK) {

    hsbpos = Pos;

    }

    if(SBCode == SB_PAGERIGHT) {

    hsbpos += 5;

    if(hsbpos > RANGEMAX)

    hsbpos = RANGEMAX;

    }

    if(SBCode == SB_PAGELEFT) {

    hsbpos -= 5;

    if(hsbpos < 0)

    hsbpos = 0;

    }

    // Устанавливаем новое состояние

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_POS;

    si.nPos = hsbpos;

    this->SetScrollInfo(SB_HORZ, &si);

    // Выводим состояние полосы прокрутки в окно.

    // Это только пример, так не следует делать в реальных программах!

    char str[255];

    CClientDC dc(this);

    wsprintf(str, "Horizontal Scroll Bar: %i ", hsbpos);

    dc.TextOut(200, 2, str);

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_WM_VSCROLL()

    END_MESSAGE_MAP()

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    // Получаем адрес объекта элемента управления

    CScrollBar *SB = (CScrollBar *) GetDlgItem(IDC_SCROLLBAR);

    // Устанавливаем свойства полосы прокрутки

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_RANGE | SIF_PAGE;

    si.nMin = 0;

    si.nMax = RANGEMAX;

    si.nPage = 1;


    SB->SetScrollInfo(&si);

    ctlsbpos = 0;

    // Меняем текст в элементе управления "статический текст"

    CStatic *staticText = (CStatic *) (this->GetDlgItem(IDC_ST_SCRLINF));

    char s[255];

    wsprintf(s, "%d", SB->GetScrollPos());

    staticText->SetWindowText(s);

    return TRUE;

    }

    // Обработчик сообщения от независимой вертикальной полосы прокрутки

    afx_msg void CSampleDialog::OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB)

    {

    // Анализируем коды полосы прокрутки

    if(SBCode == SB_LINEDOWN) {

    ++ctlsbpos;

    if(ctlsbpos > RANGEMAX)

    ctlsbpos = RANGEMAX;

    }

    if(SBCode == SB_LINEUP) {

    --ctlsbpos;

    if(ctlsbpos < 0)

    ctlsbpos = 0;

    }

    if(SBCode == SB_THUMBPOSITION) {

    ctlsbpos = Pos;

    }

    if(SBCode == SB_THUMBTRACK) {

    ctlsbpos = Pos;

    }

    if(SBCode == SB_PAGEDOWN) {

    ctlsbpos += 5;

    if(ctlsbpos > RANGEMAX)

    ctlsbpos = RANGEMAX;

    }

    if(SBCode == SB_PAGEUP) {

    ctlsbpos -= 5;

    if(ctlsbpos < 0)

    ctlsbpos = 0;

    }

    // Устанавливаем новое состояние

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_POS;

    si.nPos = ctlsbpos;

    SB->SetScrollInfo(&si);

    // Выводим значение текущей позиции

    // в элемент статического текста

    char str[255];

    wsprintf(str, "%i", SB->GetScrollPos());

    CStatic *staticText = (CStatic *) (this->GetDlgItem(IDC_ST_SCRLINF));

    staticText->SetWindowText(str);

    }

    CApp App;

    Рис. 12. Приложение с использованием пропорциональных полос прокрутки

    Обратите внимание, как в программе используются новые 32-разрядные функции работы с полосами прокрутки. Помните, что эта программа не будет работать под Windows NT 3.51. Также посмотрите, как изменяется текст в элементе управления "статический текст", который имеет уникальный идентификатор.



    Иконки и курсоры



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


    Кроме того, мы рассмотрим, как сделать диалог главным окном программы. Это очень удобно для небольших утилит. Изменять иконку и курсор для диалога труднее, чем для обычного окна. Но мы рассмотрим именно универсальный метод, который годится для всех окон. Вам рекомендуется использовать в своих программах именно этот метод.



    Создание иконки и курсора



    Для создания иконки и курсора нужно использовать ресурсный редактор. Иконки сохраняются в файлах с расширением ico, а курсоры - в файлах с расширением cur.

    Все курсоры имеют размер 32x32. Обычно используются монохромные курсоры. Каждый курсор имеет так называемую горячую точку, по которой определяется положение курсора на экране. Она может располагаться в любом месте курсора. Для курсоров типа "указатель" это обычно вершина указателя.

    Иконки могут иметь размер 16х16, 32х32 и 48х48. Последний размер обычно не используется. Иконки могут иметь 16 или 256 цветов. При использовании ресурсного редактора среды Visual C++ 4.0 и выше иконки всех размеров можно хранить в одном файле, что обычно и делается. Для современных приложений обязательно наличие иконок размером как 16х16, так и 32х32.



    Загрузка иконки и курсора из ресурсов



    К сожалению, на текущий момент MFC плохо поддерживает этот процесс. Поэтому иногда удобнее пользоваться функциями Windows API. Перед рассмотрением функций следует рассмотреть понятие дескриптора. Дескриптор - это 32-разрядное беззнаковое целое значение, которое идентифицирует объект в Windows. При традиционном SDK-программировании дескрипторы используются очень широко. Например, свои дескрипторы имеют иконки, курсоры и само приложение. Для дескрипторов каждого объекта существует свой тип, например, HICON, HCURSOR, HINSTANCE.

    Следующее понятие, которое нам нужно рассмотреть, это класс окна. Класс окна - это структура данных в Windows, которая определяет атрибуты окна. Перед тем, как создать реальное окно, приложение регистрирует класс окна, а затем на его основании может быть создано сколько угодно окон.


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

    Будем предполагать, что в декларации класса основного окна (теперь уже идет речь о классе С++) объявлены переменные m_hIconSmall, m_hIconBig, m_hCursor - соответственно дескрипторы иконки 16х16, иконки 32х32 и курсора. Тогда для загрузки иконок и курсора нужно выполнить следующий код:

    // Получить дескриптор модуля (приложения)

    HINSTANCE hInst = AfxGetInstanceHandle();

    // Загрузить иконку 16х16

    m_hIconSmall = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 16, 16,

    LR_DEFAULTCOLOR);

    // Загрузить иконку 32x32

    m_hIconBig = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 32, 32,

    LR_DEFAULTCOLOR);

    // Загрузить курсор

    m_hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR);

    Сначала мы получаем дескриптор модуля с помощью глобальной функции MFC AfxGetInstanceHandle(). Затем мы загружаем маленькую и большую иконки с помощью функции Windows API LoadImage(), и получаем их дескрипторы. Эта функция очень мощная, и позволяет загружать изображения из ресурсов и из файлов. И наконец, мы загружаем курсор уже с помощью функции MFC LoadCursor(), члена класса CWinApp. Функция AfxGetApp() возвращает адрес объекта приложения.



    Изменение иконки и курсора окна



    После того, как мы получили дескрипторы иконок и курсора, нужно изменить класс окна. (Класс окна нужно изменять для установки курсора, а для установки иконок подойдут и функции MFC.) Для этого используется API-функция SetClassLong(), которая изменяет атрибут класса окна (здесь опять имеется в виду структура данных). Первый параметр этой функции - дескриптор окна. Дескриптор окна хранится в члене класса MFC CWnd под названием m_hWnd. Если главным окном приложения является диалог, то для изменения иконки и курсора потребуется следующий код (он будет работоспособен и для обычных окон):

    // Устанавливаем курсор для диалогового окна.


    // Так как MFC не поддерживает динамическое изменение курсоров окна,

    // делаем это с помощью API, модифицируя оконный класс.

    SetClassLong(m_hWnd, GCL_HCURSOR, (long) m_hCursor);

    // Делаем то же самое для всех элементов управления

    for(int i = 0; i < 0xDFFF; ++i) {

    CWnd *pCtrl = this->GetDlgItem(i);

    if(pCtrl != 0)

    SetClassLong(pCtrl->m_hWnd, GCL_HCURSOR, (long) m_hCursor);

    }

    // Устанавливаем иконки. Здесь уже можно использовать MFC.

    this->SetIcon(m_hIconBig, TRUE);

    this->SetIcon(m_hIconSmall, FALSE);

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

    Использование диалога в качестве главного окна

    Как уже говорилось, это часто бывает очень удобным. Сделать это достаточно просто. Во-первых, нужно создать диалог в ресурсах. Во-вторых, нужно класс главного окна приложения породить от CDialog. Перед конструктором класса главного окна нужно вызвать конструктор класса CDialog, где привязать объект к ресурсам, например:

    CMainFrame::CMainFrame()

    :CDialog(IDD_MYDIALOG)

    {

    ... здесь тело конструктора

    }

    И в-третьих, в функции CApp::InitInstance()

    должен присутствовать следующий код:

    // Создаем объект диалогового окна

    CMainFrame dlgWnd;

    // Cообщаем MFC адрес окна

    m_pMainWnd = &dlgWnd;

    // Отображаем модальный диалог

    dlgWnd.DoModal();

    // Возвратим FALSE, чтобы MFC не пыталась инициировать

    // очередь сообщений главного окна.

    return FALSE;

    Мы отображаем модальный диалог. Так как при завершении функции DoModal() нам уже не нужна очередь сообщений, мы "обманываем" MFC, делая вид, что инициализация прошла неудачно. На самом же деле просто уже пора завершать приложение.


    Пример программы



    CUR_ICO.hpp



    #include "STDAFX.H"

    // Класс диалогового окна, главного окна приложения

    class CMainFrame: public CDialog {

    public:

    CMainFrame();

    private:

    enum {IDD = IDD_MAINFRAME};

    BOOL OnInitDialog();

    // Внутренние дескрипторы иконки и курсора

    HICON m_hIconSmall, m_hIconBig;

    HCURSOR m_hCursor;

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    CUR_ICO.cpp



    #include "STDAFX.H"

    #include

    #include "resource.h"

    #include "CUR_ICO.HPP"

    CApp App;

    BOOL CApp::InitInstance()

    {

    // Создаем объект диалогового окна

    CMainFrame dlgWnd;

    // Cообщаем MFC адрес окна

    m_pMainWnd = &dlgWnd;

    // Отображаем модальный диалог

    int responce = dlgWnd.DoModal();

    // Возвратим FALSE, чтобы MFC не пыталась инициировать

    // очередь сообщений главного окна.

    return FALSE;

    }

    BEGIN_MESSAGE_MAP(CMainFrame, CDialog)

    ON_WM_DESTROY()

    END_MESSAGE_MAP()

    CMainFrame::CMainFrame()

    :CDialog(CMainFrame::IDD)

    {

    // Загружаем из ресурсов иконку и курсор

    // MFC плохо поддерживает процесс динамического изменения иконок,

    // поэтому используем функции Windows API

    HINSTANCE hInst = AfxGetInstanceHandle(); // Получить дескриптор модуля

    m_hIconSmall = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); //Иконка 16x16

    m_hIconBig = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR); //Иконка 32x32

    m_hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR);

    }

    BOOL CMainFrame::OnInitDialog()

    {

    // Устанавливаем курсор для диалогового окна.

    // Так как MFC не поддерживает динамическое изменение курсоров окна,

    // делаем это с помощью API, модифицируя оконный класс.

    SetClassLong(m_hWnd, GCL_HCURSOR, (long) m_hCursor);

    // Делаем то же самое для всех элементов управления

    for(int i = 0; i < 0xDFFF; ++i) {

    CWnd *pCtrl = this->GetDlgItem(i);

    if(pCtrl != 0)

    SetClassLong(pCtrl->m_hWnd, GCL_HCURSOR, (long) m_hCursor);


    }

    this->SetIcon(m_hIconBig, TRUE);

    this->SetIcon(m_hIconSmall, FALSE);

    return TRUE;

    }

    Рис. 13. Использование собственных иконки и курсора в диалоге

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



    Стандартные иконки и курсоры



    Часто в программах нужно использовать курсоры и иконки, уже предопределенные в Windows. Для этого можно использовать или функцию API LoadImage(), или функции MFC LoadStandardIcon() и LoadStandardCursor(). Работа со стандартными курсорами и иконками почти ничем не отличается от работы с пользовательскими. Для стандартных иконок есть предопределенные идентификаторы с префиксом IDI_, а для стандартных курсоров - с префиксом IDC_. Например, стандартный курсор в виде "песочных часов" имеет идентификатор IDC_WAIT.



    Битовые образы



    Битовые образы - очень важная часть Windows. При хранении битовых образов в отдельном файле обычно используется расширение BMP (это единственный растровый формат, который напрямую поддерживается Windows). Часто они хранятся и в ресурсах. Битовые образы используются шире, чем все остальные ресурсы. Это объясняется наличием для них чрезвычайно мощной поддержки. Если Вы имеете большой опыт программирования только под DOS, то будете удивлены, узнав, что в Windows многие вещи, которые можно легко нарисовать программно, отображаются с помощью готовых битовых образов. Например, кнопки в нажатом и отпущенном состоянии, каркасы для целых окон. Так как компьютеры теперь имеют большие жесткие диски, то выбор между программным рисованием объекта и готовой картинкой часто однозначно решается в пользу последней.



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



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


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



    Вывод битового образа на экран



    Когда битовый образ помещен в ресурсы, его можно выводить на экран. Но этот процесс не такой уж и простой.

    Сначала необходимо создать объект типа CBitmap и с помощью функции LoadBitmap() загрузить в него битовый образ из ресурсов. Прототип функции таков:

    BOOL CBitmap::LoadBitmap(LPCSTR ResourceName);

    Параметр определяет строковый идентификатор ресурса.

    После загрузки битового образа его нужно вывести в клиентскую область окна. Для этого обработчик WM_PAINT должен содержать приблизительно такой код (предполагается, что битовый образ загружен в объект backgroundBitmap):

    CPaintDC clientDC(this);

    CDC memDC; // Контекст памяти

    // Создать совместимый контекст памяти

    memDC.CreateCompatibleDC(&clientDC);

    // Выбрать битовый образ в контекст устройства

    memDC.SelectObject(&backgroundBitmap);

    // Получить характеристики битового образа в структуру BITMAP

    BITMAP bmp;

    backgroundBitmap.GetBitmap(&bmp);

    // Скопировать битовый образ из контекста памяти в контекст

    // клиентской области окна

    clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC,

    0, 0, SRCCOPY);

    Сначала объявляются два контекста устройства. Первый связан с текущим окном. Второй не инициализирован и предназначен для области памяти, в которой будет храниться изображение. Затем с помощью функции CreateCompatibleDC() этот контекст объявляется совместимым с контекстом окна. Функция имеет прототип:

    virtual BOOL CDC::CreateCompatibleDC(CDC *pDC);

    Область памяти используется для вывода изображения на экран. Перед выводом на экран изображение должно быть выбрано в контекст устройства, связанный с областью памяти, с помощью функции SelectObject(). Мы используем ее вариант с прототипом:

    CBitmap *CDC::SelectObject(CBitmap *pBmp);


    Параметр - это указатель на объект битового образа.

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

    BOOL CDC::BitBlt(int x, int y, int Width, int Height,

    CDC *pSourceDC, int SourceX, int SourceY,

    DWORD RasterOpCode);

    Первые два параметра задают координаты начальной точки изображения. Размеры изображения задают следующие два параметра. Параметр pSourceDC является указателем на исходный контекст устройства. Координаты SourceX и SourceY задают левый верхний угол изображения и обычно равны 0. Последний параметр задает код операции, которая будет проделана при передаче изображения из одного контекста в другой. Мы будем использовать только значение SRCCOPY, в этом случае изображение просто копируется. Существует также много других констант.

    Следует отметить, что указанным методом нельзя корректно выводить битовые образы более чем с 16 цветами в видеорежимах с 256 цветами. В режимах же HiColor и TrueColor без всяких проблем этим методом выводятся любые битовые образы. Так как на всех современных компьютерах используются по крайней мере HiColor режимы, мы не будем рассматривать ограничения худших режимов и манипуляции с палитрой. На сегодняшний день этот материал устарел.



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



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

    int GetSystemMetrics(int Index);

    Параметр определяет конкретное возвращаемое значение. Для наших целей нужны следующие значения:

    SM_CXDLGFRAME Ширина бордюра окна без выпуклой рамки (плоского диалогового окна)
    SM_CYDLGFRAME Высота бордюра окна без выпуклой рамки (плоского диалогового окна)
    SM_CXEDGE Ширина трехмерной выпуклой рамки окна
    SM_CYEDGE Высота трехмерной выпуклой рамки окна
    SM_CYCAPTION Высота заголовка окна
    <


    В нашем случае, функция возвращает указанные размеры в пикселях.

    Пример программы



    bmpshow.hpp



    #include "STDAFX.H"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow = SW_RESTORE);

    afx_msg void OnPaint();

    private:

    // Объект битового образа

    CBitmap backgroundBitmap;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    bmpshow.cpp



    #include "STDAFX.H"

    #include

    #include "resource.h"

    #include "BMPShow.HPP"

    CApp App;

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    CMainWin::CMainWin(CString Title, int HowShow)

    {

    backgroundBitmap.LoadBitmap("BACKGROUND_BITMAP");

    // Прямоугольник, в котором должно разместиться окно.

    RECT rect;

    rect.top = 0;

    rect.left = 0;

    BITMAP bmp;

    backgroundBitmap.GetBitmap(&bmp);

    // Определяем, чему должны быть равны размеры окна,

    // если размеры клиентской области должены быть равны размерам картинки.

    rect.right = rect.left + bmp.bmWidth

    + (GetSystemMetrics(SM_CXDLGFRAME) + GetSystemMetrics(SM_CXEDGE)) * 2;

    rect.bottom = rect.top + bmp.bmHeight + GetSystemMetrics(SM_CYCAPTION) +

    + (GetSystemMetrics(SM_CYDLGFRAME) + GetSystemMetrics(SM_CYEDGE)) * 2;

    this->Create(0, Title,

    WS_OVERLAPPED | WS_CAPTION | WS_DLGFRAME

    | WS_SYSMENU | WS_MINIMIZEBOX, rect, 0, 0);

    // Если не задана позиция окна, то центрируем его

    if(x0 < 0 || y0 < 0)

    this->CenterWindow();

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC clientDC(this);

    CDC memDC; // Контекст памяти

    // Создать совместимый контекст памяти

    memDC.CreateCompatibleDC(&clientDC);

    // Выбрать битовый образ в контекст устройства

    memDC.SelectObject(&backgroundBitmap);

    // Получить характеристики битового образа в структуру BITMAP

    BITMAP bmp;

    backgroundBitmap.GetBitmap(&bmp);

    // Скопировать битовый образ из контекста памяти в контекст


    // клиентской области окна

    clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);

    }

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Вывод битового образа", SW_RESTORE);

    return TRUE;

    }

    Рис. 14. Пример программы, выводящей битовый образ из ресурсов

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



    Вывод текста и шрифты



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



    Небольшое введение



    Любой шрифт, с которым мы имеем дело в Windows, характеризуется несколькими параметрами. Гарнитура (typeface) - это совокупность нескольких начертаний шрифта, объединенных стилевыми и другими признаками. Пример гарнитур: Arial, Times New Roman, MS Sans Serif. Размер шрифта - это высота прямоугольника, в который помещаются все символы шрифта, выражается в специальных единицах - пунктах. Пункт равен 1/72 части дюйма. Эта единица пришла из полиграфии. Начертание - это специфические характеристики шрифта. В Windows доступны четыре начертания: нормальное (normal), курсивное (italic), жирное (bold) и жирное курсивное (bold italic). Кроме того, шрифты могут быть моноширинные (fixed pitch), пример - Courier New, и пропорциональные (variable pitch), пример - Times New Roman.

    Сейчас в Windows в основном используются шрифты двух групп: растровые

    (примеры - MS Sans Serif, Fixedsys) и контурные TrueType (примеры - Arial, Courier New).


    Первые представляют собой жестко определенные битовые матрицы для каждого символа и предназначены для отображения не очень крупного текста на экране. Вторые представляют собой очень сложные объекты. В них заданы контуры символов, которые закрашиваются по определенным правилам. Каждый шрифт TrueType - это программа на специальном языке, которая выполняется интерпретатором под названием растеризатор. Программа шрифта полностью определяет способ расчета конкретных битовых матриц символов на основе контуров. При использовании символов маленького размера (высотой приблизительно до 30 точек) модель контуров становится некорректной и символы сильно искажаются. Для борьбы с этим в качественных шрифтах используется разметка (хинты). Разметка шрифта - чрезвычайно сложный и долгий процесс, поэтому на рынке встречается немного качественно размеченных шрифтов. Поэтому использовать TrueType шрифты для вывода текста на экран нежелательно, за исключением стандартных шрифтов Windows (Times New Roman, Arial и Courier New), которые очень качественно размечены.



    Координаты при выводе текста



    За вывод текста в окно в MFC отвечает функция CDC::TextOut(). Ей нужно указать координаты для вывода строки. Эти координаты являются логическими

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



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



    При выводе текста с помощью функции TextOut() по умолчанию текст выводится черным цветом на текущем (обычно белом) фоне. Однако с помощью следующих функций эти параметры можно изменить:

    virtual COLORREF CDC::SetTextColor(COLORREF Color);

    virtual COLORREF CDC::SetBkColor(COLORREF Color);

    Функции возвращают значение предыдущего цвета. Тип COLORREF представляет собой 32-разрядное беззнаковое целое число - представление цвета в виде красной, зеленой и синей компонент, каждая размером в 8 бит.


    Для формирования этого значения существует макрос RGB().



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



    C помощью функции SetBkMode() можно задать режим отображения фона. Прототип функции такой:

    int CDC::SetBkMode(int Mode);

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



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



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

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

    BOOL CDC::GetTextMetrics(LPTEXTMETRICS TextAtttrib) const;

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

    LONG tmHeight Полная высота шрифта
    LONG tmAscent Высота над базовой линией
    LONG tmDescent Высота подстрочных элементов
    LONG tmInternalLeading Пустое пространство над символами
    LONG tmExternalLeading Пустой интервал между строками
    LONG tmMaxCharWidth Максимальная ширина символов
    Для получения числа логических единиц по вертикали между строками нужно сложить значения tmHeight и tmExternalLeading. Это не то же самое, что и высота символов.



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



    Предположим, что мы имеем готовый инициализированный экземпляр класса CFont, содержащий некоторый шрифт (как это сделать, мы рассмотрим чуть ниже). Для изменения шрифта в контексте устройства используется член-функция SelectObject(), которая в нашем случае принимает один параметр - указатель на объект шрифта.


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



    Инициализация объекта шрифта: выбор шрифта



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

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

    BOOL CFont::CreateFont(int nHeight, int nWidth,

    int nEscapement,

    int nOrientation,

    int nWeight,

    BYTE bItalic,

    BYTE bUnderline,

    BYTE cStrikeOut,

    BYTE nCharSet,

    BYTE nOutPrecision,

    BYTE nClipPrecision,

    BYTE nQuality,

    BYTE nPitchAndFamily,

    LPCTSTR lpszFacename);

    Функция крайне неудобна, и смысл многих ее параметров на сегодняшний день не актуален. Функция была создана давно, и тогда казалась увлекательной идея подстановки и замены шрифтов, суть которой заключалась в следующем: программист задает такие параметры шрифта, какие он хочет иметь, а Windows сама на основе имеющихся в системе шрифтов произведет необходимые трансформации и синтезирует требуемый шрифт из имеющихся. Впоследствии оказалось, что эта технология не может быть удовлетворительно работоспособной (так как практически это сложная задача из области искусственного интеллекта). Также функция позволяет проводить трансформации шрифта - растягивать и сжимать его, выводить текст под углом. Сейчас это используется редко. Графические пакеты используют свои алгоритмы трансформаций, и часто используют шрифты PostScript.

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


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

    Учитывая вышесказанное, мы не будем подробно рассматривать параметры функции CreateFont(). Вместо этого мы рассмотрим пример кода, который позволяет задать размер шрифта, начертание и гарнитуру, минимизировав возможные трансформации шрифта и синтеза при отсутствии заданного. Такие же параметры для функции CreateFont() Вы можете использовать в своих программах. Желательно также проверять наличие шрифта, если он не является стандартным. Вот код, который используется в примере программы (указатель на объект шрифта хранится в переменной m_pFont):

    void CMainFrame::SetClientFont(CString Typeface, // Гарнитура

    int Size, // размер в пунктах

    BOOL Bold, // Признак жирного начертания

    BOOL Italic // Признак наклонного

    // начертания

    )

    {

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

    CWindowDC winDC(this);

    // Узнаем, сколько пикселей в одном логическом дюйме

    int pixelsPerInch = winDC.GetDeviceCaps(LOGPIXELSY);

    // Узнаем высоту в пикселях шрифта размером Size пунктов

    int fontHeight = -MulDiv(Size, pixelsPerInch, 72);

    // Устанавливаем параметр жирности для функции CreateFont()

    int Weight = FW_NORMAL;

    if(Bold)

    Weight = FW_BOLD;

    // Удаляем предыдущий экземпляр шрифта -- нельзя дважды

    // инициализировать шрифт вызовом CreateFont().

    delete m_pFont;

    m_pFont = new CFont;

    // Создание шрифта. Большинство параметров не используются.

    m_pFont->CreateFont(fontHeight, 0, 0, 0, Weight, Italic, 0, 0,

    DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,

    CLIP_DEFAULT_PRECIS, PROOF_QUALITY,

    DEFAULT_PITCH | FF_DONTCARE, Typeface);

    }

    Пример программы

    В этом примере мы поместим декларации и описания объектов приложения и главного окна в разные файлы.


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



    MainFrame.hpp



    #include "stdafx.h"

    class CMainFrame: public CFrameWnd {

    public:

    CMainFrame();

    ~CMainFrame();

    afx_msg void OnLButtonDown(UINT Flags, CPoint Loc);

    afx_msg void OnPaint();

    virtual void SetClientFont(CString Typeface, int Size, BOOL Bold, BOOL Italic);

    private:

    CWinApp *pApp;

    CFont *m_pFont;

    COLORREF m_textColor;

    DECLARE_MESSAGE_MAP();

    };



    MainFrame.cpp



    #include "stdafx.h"

    #include "MainFrame.hpp"

    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

    ON_WM_PAINT()

    ON_WM_LBUTTONDOWN()

    END_MESSAGE_MAP()

    CMainFrame::CMainFrame()

    {

    pApp = AfxGetApp();

    this->Create(0, "Вывод текста и шрифты",

    WS_OVERLAPPEDWINDOW, rectDefault, 0, 0);

    m_pFont = new CFont;

    this->SetClientFont("Arial", 20, FALSE, FALSE);

    this->ShowWindow(SW_RESTORE);

    this->UpdateWindow();

    }

    CMainFrame::~CMainFrame()

    {

    delete m_pFont;

    }

    afx_msg void CMainFrame::OnLButtonDown(UINT Flags, CPoint Loc)

    {

    // Получим структуру LOGFONT текущего шрифта, чтобы узнать имя гарнитуры

    LOGFONT lf;

    m_pFont->GetLogFont(&lf);

    // Циклически меняем шрифт

    if(CString(lf.lfFaceName) == "Arial"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 0) {

    this->SetClientFont("Arial", 20, FALSE, TRUE); // Arial Italic

    }

    else if(CString(lf.lfFaceName) == "Arial"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 1) {

    this->SetClientFont("Arial", 20, TRUE, FALSE); // Arial Bold

    }

    else if(CString(lf.lfFaceName) == "Arial" && lf.lfWeight > FW_NORMAL) {

    // Times New Roman

    this->SetClientFont("Times New Roman", 20, FALSE, FALSE);

    }

    else if(CString(lf.lfFaceName) == "Times New Roman"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 0) {

    // Times New Roman Italic

    this->SetClientFont("Times New Roman", 20, FALSE, TRUE);


    }

    else if(CString(lf.lfFaceName) == "Times New Roman"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 1) {

    // Times New Roman Bold

    this->SetClientFont("Times New Roman", 20, TRUE, FALSE);

    }

    else if(CString(lf.lfFaceName) == "Times New Roman"

    && lf.lfWeight > FW_NORMAL) {

    this->SetClientFont("Arial", 20, FALSE, FALSE); // Arial

    }

    // Установим случайный RGB-цвет текста

    m_textColor = RGB(MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX), MulDiv(rand(), 255, RAND_MAX));

    // Выведем информацию в окно новым шрифтом (пошлем сообщение WM_PAINT)

    this->InvalidateRect(0);

    }

    void CMainFrame::SetClientFont( CString Typeface, int Size, BOOL Bold, BOOL Italic)

    {

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

    CWindowDC winDC(this);

    // Узнаем, сколько пикселей в одном логическом дюйме

    int pixelsPerInch = winDC.GetDeviceCaps(LOGPIXELSY);

    // Узнаем высоту в пикселях шрифта размером Size пунктов

    int fontHeight = -MulDiv(Size, pixelsPerInch, 72);

    int Weight = FW_NORMAL;

    if(Bold)

    Weight = FW_BOLD;

    // Удаляем предыдущий экземпляр шрифта -- нельзя дважды

    // инициализировать шрифт вызовом CreateFont().

    delete m_pFont;

    m_pFont = new CFont;

    m_pFont->CreateFont(fontHeight, 0, 0, 0, Weight, Italic, 0, 0,

    DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,

    CLIP_DEFAULT_PRECIS, PROOF_QUALITY,

    DEFAULT_PITCH | FF_DONTCARE, Typeface);

    }

    afx_msg void CMainFrame::OnPaint()

    {

    CPaintDC paintDC(this);

    COLORREF oldColor = paintDC.SetTextColor(m_textColor);

    LOGFONT lf;

    m_pFont->GetLogFont(&lf);

    paintDC.SelectObject(m_pFont);

    TEXTMETRIC tm;

    paintDC.GetTextMetrics(&tm);

    int currY = 2;

    CString s = CString("Font: ") + lf.lfFaceName;

    paintDC.TextOut(2, currY, s);

    currY += (tm.tmHeight + tm.tmExternalLeading);

    CSize size = paintDC.GetTextExtent(s);

    s.Format("Previous string is %i units long", size.cx);

    paintDC.TextOut(2, currY, s);

    paintDC.SetTextColor(oldColor);

    }



    text_and_fonts.hpp




    #include "stdafx.h"

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    text_and_fonts.cpp



    #include "stdafx.h"

    #include "Text_And_Fonts.hpp"

    #include "MainFrame.hpp"

    CApp App;

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainFrame();

    return TRUE;

    }

    Рис. 15. Программа, отображающая в окне стандартные TrueType шрифты

    При нажатии левой кнопки мыши в клиентской области программа циклически меняет шрифт из набора всех начертаний двух стандартных TrueType-шрифтов, Arial и Times New Roman. При этом случайным образом устанавливается цвет символов. Выводится длина первой строки в логических единицах. Попробуйте изменить программу так, чтобы вместо TrueType шрифтов использовались растровые шрифты, например, MS Serif и MS Sans Serif. Посмотрите, что произойдет, если задать несуществующий в системе размер шрифта, или несуществующий шрифт.



    Введение в графические функции. Организация вывода в виртуальное окно



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

    Также мы рассмотрим самый удобный способ решения проблемы перерисовки окна по сообщению WM_PAINT - использование виртуального окна. Мы не использовали этот метод раньше, чтобы не загромождать примеры программ второстепенными деталями.



    Система графических координат



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

    В Windows используется так называемый текущий указатель, положение которого используется и изменяется некоторыми графическими функциями. При запуске программы он устанавливается в точку (0,0). Его местоположение на экране невидимо, он означает лишь позицию в окне для некоторых функций.




    Перья



    Графические объекты рисуются с помощью текущего пера. В MFC перья описываются с помощью класса CPen. По умолчанию установлено черное перо толщиной в 1 пиксель. Всего имеется три стандартных пера: черное, белое и нулевое (прозрачное). Конечно, этого недостаточно. Поэтому обычно создаются собственные перья. Для этого нужно вызвать функцию :

    BOOL CPen::CreatePen(int Style, int Width, COLORREF Color);

    Параметр Style определяет стиль созданного пера. Толщина пера в логических единицах задается параметром Width. Цвет пера задается с помощью параметра Color. Класс CPen также имеет перегруженный конструктор с такими же параметрами. Иногда легче использовать его.



    Кисти



    Кисти создаются подобно перьям. Только они описываются классом CBrush. Существуют различные стили кистей. Наиболее распространенным является стиль сплошной кисти. Такие кисти создаются с помощью функции:

    BOOL CBrush::CreateSolidBrush(COLORREF Сolor);

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

    Следующие функции позволяют создать также кисти со штриховкой и кисти по шаблону битового образа:

    BOOL Сbrush::CreateHatchBrush(int Index, COLORREF Color);

    BOOL Сbrush::CreatePatternBrush(CBitmap *pBitmap);

    Параметр Index указывает стиль кисти. Параметр pBitmap - это указатель на объект битового образа. Предварительно он может быть загружен из ресурсов. Битовый образ должен иметь размер 8х8.

    Мы будем использовать все эти функции в примере программы.

    Отображение точки

    Отобразить в контексте устройства точку определенного цвета можно с помощью следующей функции:

    СOLORREF CDC::SetPixel(int X, int Y, COLORREF Color);

    Функция отображает точку заданного цвета по указанным координатам. Она возвращает цвет, который был установлен в действительности. Он может отличаться от заданного, из-за округления до ближайшего цвета, поддерживаемого текущим видеорежимом.

    Рисование прямоугольников

    Для рисования прямоугольников текущим пером используются функции:


    BOOL CDC::Rectangle( int upX, int upY, int lowX, int lowY);

    BOOL CDC::RoundRect(int upX, int upY, int lowX, int lowY,

    int curveX, int curveY);

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

    Рисование эллипсов

    Для рисования эллипсов текущим пером используется функция:

    BOOL CDC::Ellipse(int upX, int upY, int lowX, int lowY);

    Координаты (upX, upY) и (lowX, lowY) задают левый верхний и правый нижний углы прямоугольника, в который будет вписан эллипс. Если нужно нарисовать окружность, то задается описанный квадрат. Специальной функции для рисования окружностей нет.

    Фигура заполняется с помощью текущей кисти контекста устройства.



    Организация виртуального окна



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

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

    Для реализации виртуального окна нужно использовать некоторые еще не рассмотренные нами функции MFC.

    Функция с прототипом:

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

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

    Следующая нужная нам функция:

    BOOL CDC::PatBlt(int x, int y, int Width, int Height, DWORD RasterOp);

    заполняет текущей кистью прямоугольную область с указанными начальными координатами и размером.


    Последний параметр определяет растровую операцию, аналогично функции BitBlt(). Нам будет нужно значение PATCOPY.

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

    Для этого в примере программы используется следующий код:

    // Поддержка виртуального окна

    // Получим размеры экрана

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

    m_memDC.SelectObject(&m_bkbrush);

    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY);

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

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

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

    в обработчике выполняется следующий код:

    CPaintDC paintDC(this);

    RECT clientRect;

    this->GetClientRect(&clientRect);

    paintDC.BitBlt(0, 0, clientRect.right, clientRect.bottom,

    &m_memDC, 0, 0, SRCCOPY);

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

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



    MainFrame.hpp



    #include "stdafx.h"

    class CMainFrame: public CFrameWnd {


    public:

    CMainFrame();

    ~CMainFrame();

    // Фигуры рисуются по сообщениям от таймера

    afx_msg void OnTimer(UINT id);

    // Обработчик сообщения WM_ERASEBKGND. Обычно по этому сообщению

    // стирается фон окна. Наш же обработчик ничего не делает, так как

    // в окнах с виртуальным окном перерисовывать фон не нужно.

    afx_msg BOOL OnEraseBkgnd(CDC* pDC);

    afx_msg void OnPaint();

    afx_msg void OnDestroy();

    private:

    CWinApp *pApp;

    CDC m_memDC;

    CBitmap m_bmp;

    CBrush m_bkbrush;

    int maxX, maxY;

    COLORREF m_textColor;

    DECLARE_MESSAGE_MAP();

    };



    MainFrame.cpp



    #include "stdafx.h"

    #include "resource.h"

    #include "MainFrame.hpp"

    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

    ON_WM_ERASEBKGND()

    ON_WM_PAINT()

    ON_WM_TIMER()

    ON_WM_DESTROY()

    END_MESSAGE_MAP()

    CMainFrame::CMainFrame()

    {

    pApp = AfxGetApp();

    this->Create(0, "Графика GDI и использование виртуального окна",

    WS_OVERLAPPEDWINDOW, rectDefault, 0, 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(BLACK_BRUSH);

    m_memDC.SelectObject(&m_bkbrush);

    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY);

    this->ShowWindow(SW_RESTORE);

    this->UpdateWindow();

    // Установим таймер с интервалом 0.1 с

    this->SetTimer(1, 100, 0);

    }

    CMainFrame::~CMainFrame()

    {

    }

    afx_msg void CMainFrame::OnTimer(UINT id)

    {

    // Получим прямоугольник клиентской области окна

    RECT clientRect;

    this->GetClientRect(&clientRect);

    //

    // Выбрать в контекст перо по случайному критерию

    //

    // Выбор стиля пера

    int penStyle;

    int random = rand();

    if(random < RAND_MAX / 3) {

    penStyle = PS_SOLID;

    }

    else if(random < RAND_MAX * 2 / 3) {


    penStyle = PS_DOT;

    }

    else {

    penStyle = PS_DASH;

    }

    // Толщина (от 1 до 3);

    int penWidth = MulDiv(rand(), 3, RAND_MAX);

    // Цвет (исключая черный)

    COLORREF penColor = 0;

    while(penColor == 0) {

    penColor = RGB(MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX));

    }

    CPen pen(penStyle, penWidth, penColor);

    m_memDC.SelectObject(pen);

    //

    // Выбрать в контекст кисть по случайному критерию

    //

    CBrush brush;

    // Цвет

    COLORREF brushColor = RGB(MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX));

    // Выбор стиля кисти

    CBitmap brushBitmap;

    brushBitmap.LoadBitmap(IDB_BRUSH);

    random = rand();

    if(random < RAND_MAX / 3) {

    // Сплошная кисть

    brush.CreateSolidBrush(brushColor);

    }

    else if(random < RAND_MAX * 2 / 3) {

    // Кисть с крестообразной штриховкой

    brush.CreateHatchBrush(HS_DIAGCROSS, brushColor);

    }

    else {

    // Кисть по шаблону

    brush.CreatePatternBrush(&brushBitmap);

    }

    m_memDC.SelectObject(brush);

    //

    // Рисуем фигуру

    //

    int x = MulDiv(rand(), clientRect.right, RAND_MAX);

    int y = MulDiv(rand(), clientRect.bottom, RAND_MAX);

    int r1 = MulDiv(rand(), 90, RAND_MAX) + 10;

    int r2 = MulDiv(rand(), 90, RAND_MAX) + 10;

    if(rand() < RAND_MAX / 2) {

    // Рисуем эллипс

    m_memDC.Ellipse(x - r1 / 2, y - r2 / 2, x + r1 / 2, y + r2 / 2);

    }

    else {

    // Рисуем прямоугольник со скругленными углами

    m_memDC.RoundRect(x - r1 / 2, y - r2 / 2, x + r1 / 2, y + r2 / 2,

    r1 / 5, r2 / 5);

    }

    // пошлем сообщение WM_PAINT

    this->InvalidateRect(0, FALSE);

    }

    afx_msg BOOL CMainFrame::OnEraseBkgnd(CDC* pDC)

    {

    // Ничего не делаем, так как фон окна прорисовывать не нужно -

    // он будет прорисован при копировании контекста памяти в контекст окна.

    return TRUE; // Якобы фон прорисован

    }

    afx_msg void CMainFrame::OnPaint()

    {

    CPaintDC paintDC(this);

    RECT clientRect;

    this->GetClientRect(&clientRect);

    paintDC.BitBlt(0, 0, clientRect.right, clientRect.bottom,

    &m_memDC, 0, 0, SRCCOPY);


    }

    afx_msg void CMainFrame::OnDestroy()

    {

    this->KillTimer(1);

    }



    Graphics_And_VirtWin.hpp



    #include "stdafx.h"

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    Graphics_And_VirtWin.cpp



    #include "stdafx.h"

    #include "Graphics_and_VirtWin.hpp"

    #include "MainFrame.hpp"

    CApp App;

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainFrame();

    return TRUE;

    }

    Рис. 15. Демонстрационная программа, использующая графические функции и вывод через виртуальное окно

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



    Заключение



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

    Возможно, первое, что Вы захотите подробно изучить, - это использование элементов управления OCX, а также создание собственных элементов управления. Это даст возможность использовать готовые высокоуровневые компоненты. Комбинируя технологию визуальной разработки интерфейса пользователя с использованием эффективного языка С++, Вы непременно добьетесь больших успехов в создании Windows-приложений.



    РЕКОМЕНДУЕМАЯ Литература



    1. Герберт Шилдт. MFC: Основы программирования: Пер. с англ. - Киев, издательская группа "BHV", 1997.

    2. Питер Нортон, Роб Макгрегор. Программирование для Windows 95/NT 4 с помощью MFC: Пер. с англ. В 2-х книгах. - М.: "СК Пресс", 1998.

    3. Microsoft Visual C++ Books Online.

    Применение указателей в C++ Ernest Avagyan



    4. Применение указателей в C++Напишем следующую программу, которая использует указатели. Предположим, что значение iNum1 равно 2, а адрес iNum1 — 1000. INum1 будет занимать байты с адресами 1000, 1001, 1002 и 1003. Если значение iNum2 было равно, то переменная iNum2 могла бы занимать ячейки с адресами 1004, 1005, 1006 и 1007. Следовательно, iNumI начинается с адреса 1000, а iNum2 начинается с адреса 1004. Однако, хотя iNumI занимает четыре адреса, в С/С++ адресом iNumI называется адрес 1000, а адресом iNum2 называется адрес 1004. Теперь объявим две переменные как указатели — pNum1 и pNum2. Ваша цель состоит в том, чтобы сохранить число 1000 (адрес iNumI) в pNum1 и число 1004 (адрес iNum2) в pNum2.

    Внесите следующие изменения в main(void):


    void main(void)
    {
    int iNum1;
    int iNum2;
    int iResult;
    int* pNum1;
    int* pNum2;
    iNum1 = 2;
    iNum2 = 3;
    pNum1 = &iNum1;
    pNum2 = &iNum2;
    iResult = *pNum1 + *pNum2;
    cout << "The result is: ";
    cout << iResult << endl;
    }


    Код, который вы ввели, объявляет три целых переменных:


    int iNum1;
    int iNum2 ;
    int iResult;

    Затем объявляются еще две переменные:


    int* pNum1;
    int* pNum2;

    Обратите внимание, что в объявлении использована запись int*. К какому же типу относится переменная pNum1? Можете ли вы сохранить целое значение в pNum1? Нет. В pNum1 вы можете сохранить адрес переменной типа int. Вы должны сохранить в переменной pNum1 число 1000, поскольку 1000 является адресом iNum1. Точно так же вы должны сохранять адрес целого значения и в переменной pNum2. После этого вы присваиваете значения переменным iNum1 и iNum2:


    iNum1 = 2;
    iNum2 = 2;

    Затем вы присваиваете значения переменным pNumI и pNum2:


    pNum1 = &iNum1;
    pNum2 = &iNum2;

    Эти два оператора сохраняют адрес переменной iNum1 в pNum1 и адрес iNum2 в pNum2. Далее вам нужно вычислить результат сложения iNum1 с iNum2. Вы могли бы бы просто написать оператор


    iResult = iNum1 + iNum2;


    Однако попробуем выполнить вычисления, применив указатели, а не переменные.
    Например, чтобы вычислить результат сложения iNuml и iNum2, вы пишете следующий оператор:



    iResult = *pNum1 + *pNum2;

    Когда вы используете указатель с предшествующим символом *, вы извлекаете значение, хранящееся по данному адресу. *pNum1 — это то же, что и *1000, так что программа обращается к значению, хранящемуся по адресу 1000. Поскольку переменная pNum1 была объявлена как int* (а компилятор знает, что целое значение занимает четыре байта памяти), программа обращается к адресам 1000, 1001, 1002 и 1003. Она находит по этим адресам значение 2, так как *pNum1 равно 2. Аналогично, *pNum2 равно 3, поскольку pNum2 равно 1004, а ячейки памяти 1004, 1005, 1006 и 1007 содержат целое со значением. И, наконец, выполняется оператор cout, который выводит на экран значение переменной iResult:



    cout << "The result is: " << endl;
    cout << iResult;

    Сохраните свою работу, выполните компиляцию и компоновку программы. Запустите программу и убедитесь, что значение iResult равно 5 (2+3=5).

    К списку

    Пример использования MFC в Ernest Avagyan



    8. Пример использования MFC в Visual C++.
    В этой главе будет показано как включить потдержку MFC в Visual C++ на примере двух типов приложений:


    1. Консольное приложение
    2. Приложение типа Windows Application


    1. Консольное приложение
    Для того, чтобы включить использование MFC в ваше консольное приложение, вам надо написать следующий код:


    #include
    #include

    int main( int argc, char* argv[] )
    {
    if ( !AfxWinInit( ::GetModuleHandle( NULL ), NULL, ::GetCommandLine( ), 0 ) )
    {
    cerr << _T( "MFC Failed to initialize.\n" );
    return 1;
    }

    // код вашей программы

    return 0;
    }


    После того, как Вы набрали код, обязательно сделайте следующее:


    Запустите программу - Build / Rebuild all ( будут ошибки ), выберите Build / Set active configuration - Win 32 Realise, выберите пункт меню "Project", далее "Settings...", закладку "C/C++", Category - Code Generation и в пункте "Use run-time library" выберите "Multithreaded". После этого сделайте опять Build / Rebuild all и программа будет работать.


    Если MFC инициализировалась правильно, то будет выполняться код вашей программы, если нет - выведется сообщение "MFC Failed to initialize." Если что то не так, проверте наличие библиотеки "afxwin.h" и правильность написания кода.

    2. Приложение типа Windows Application
    Что бы создать приложение типа Windows Application с использованиеи MFC нужно сделать следующие шаги( создадим для простоты приложение основанное на диалогах ):


    1. Запустите Visual C++.
    2. Выберите File / New.
    3. Выберите закладку "Projects" / "MFC AppWizard( exe )", введите имя проекта( Project name ) и место для проекта( Location ) и нажмите кнопку "OK". В ответ будут выводится диалоговые панели.
    4. MFC AppWizard - Step 1. Выберите интересующий тип проекта( простой документ, мулти-документ или документ, основанный на диалогах ) и нажмите кнопку "Next>" ( Вам надо выбрать "Dialog based").
    5. MFC AppWizard - Step 2. Нажмите кнопку "Next>".
    6. MFC AppWizard - Step 3. Нажмите кнопку "Finish".
    7. New Project Information. Нажмите кнопку "OK".


    Ну вот и всё, у Вас есть уже готовая программа, потдерживающая MFC.

    К списку


    Проигрывание Wave-файлов под MF Ernest Avagyan



    18. Проигрывание Wave-файлов под MFC
    1. Введение
    2. Проигрывание Wave-файла в виде ресурса
    3. Проигрывание Wave-файла с диска



    1. ВведениеВ этой главе мы создадим программу, проигрывающую WAVE-файлы. Для начала создадим проект mysound в диалоговом режиме с использованием MFC. В начало файла mysoundDlg.cpp надо написать #include , но это не всё, а теперь самое главное( если это не сделать, то будет ошибка при линковании ) :

    1. Выберите Project -> Settings... --> C/C++ --> Code Generation и поставте Multithreaded DLL
    2. Выберите Project -> Settings... --> Link --> General и поставте в поле Object/Library modules библиотеку winmm.lib( это очень важно, проверте !!! )

    2. Проигрывание Wave-файла в виде ресурсаПервым делом надо создать ресурс, для этого в файле mysound.rc2 надо вписать строчку IDSOUND_CORRECT sound res\correct.wav, где IDSOUND_CORRECT - индефикатор ресурса, sound - тип ресурса( название можно менять ), res\correct.wav - файл ресурса. После этого в файле Resource.h надо зарегистрировать ресурс: #define IDSOUND_CORRECT 130. Число 130 не должно совпадать с другими числами.

    Как только ресурс зарегистрирован можно написать в файле mysoundDlg.cpp функции проигрывания этого ресурса :

    static void PlayResource(LPCTSTR lpszSound) { HRSRC hRes; // resource handle to wave file HGLOBAL hData; BOOL bOk = FALSE; if ((hRes = ::FindResource(AfxGetResourceHandle(), lpszSound, _T("sound"))) != NULL && (hData = ::LoadResource(AfxGetResourceHandle(), hRes)) != NULL) { // found the resource, play it bOk = sndPlaySound((LPCTSTR)::LockResource(hData), SND_MEMORY|SND_SYNC|SND_NODEFAULT); FreeResource(hData); } if (!bOk) { AfxMessageBox("ERROR !!! Can not play the sound. \nNot find sound board !!!"); } }
    inline static void PlayResource(UINT nIDS) { PlayResource(MAKEINTRESOURCE(nIDS)); }
    В фунции ::FindResource(AfxGetResourceHandle(), lpszSound, _T("sound")) третий параметр - тип ресурса, который был описан выше.

    Теперь можно проиграть ресурс : PlayResource( IDSOUND_CORRECT );

    3. Проигрывание Wave-файла с дискаДля проигрывания WAVE-файла с диска можно использовать функцию :

    BOOL sndPlaySound( LPCTSTR lpszSoundName; UINT fuOptions; );

    Параметры функции:

    lpszSoundName

    Имя файла. Если этот параметр NULL, то проигрывание файла останавливается.

    fuOptions

    Специальные опции для проигрывания музыки. Они могут быть следующими:

    Значение Описание

    SND_SYNC Музыка играется синхронно, и функция не возвращает указатель пока не будет конца файла. SND_ASYNC Музыка играется асинхронно, и функция возвращает указатель сразу после начала проигрывания файла. Чтобы остановить проигрывание, надо вызвать функцию SndPlaySound с параметром lpszSoundName установленным в NULL. SND_NODEFAULT Если файл не найден, то функция возвращает указатель сразу и не проигрывает стандартный звуковой эффект Windows. SND_MEMORY Этот параметр нужен для проигрывания Wave-файла в виде ресурса( из памяти ). SND_LOOP Этот параметр нужен для проигрывания Wave-файла в циклическом режиме. Также при этом вы должны использовать влаг SND_ASYNC. Чтобы остановить проигрывание, надо вызвать функцию SndPlaySound с параметром lpszSoundName установленным в NULL. SND_NOSTOP Если музыка уже проигрывается, то функция возврвщает FALSE.

    Возвращаемое значение:

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

    Пример использования:

    BOOL bOk = sndPlaySound( "test.wav", SND_SYNC);

    if (!bOk) { AfxMessageBox("Error ! Can not play the sound. !!!"); }


    К списку

    Простая программа, использующая MDI интерфейс



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


    Создание проекта программы


    1. Создайте новый проект( у меня MDI ), использующая MDI интерфейс с поддержкой MFC. Все шесть шагов в MFC AppWizard оставте без изменения.

    2. Если вы сделали всё правильно, то создадутся пять классов : CMDIApp, CMainFrame, CChildFrame, CMDIDoc и CMDIView. В классе документов CMDIDoc вы пишите код для поддержки данных программы, а в классе представления CMDIView - код, отвечающий за то, что вы видите на экране. Вы будете писать код в функциях-элементах только этих двух классов.

    3. Объявляем элементы данных класса документа. Их будет два : координаты круга по X и по Y. Для этого открываем файл CMDIDoc.h и изменяем объявление класса CMDIDoc следующим образом:

    class CMDIDoc : public CDocument { protected: // create from serialization only CMDIDoc(); DECLARE_DYNCREATE(CMDIDoc)
    // Attributes public:
    int m_X; // координаты круга по x int m_Y; // координаты круга по y
    // Operations ... ... 4. Объявляем элементы данных класса представления. Их будет тоже два : координаты круга по X и по Y. Для этого открываем файл CMDIView.h и изменяем объявление класса CMDIView следующим образом:

    class CMDIView : public CView { protected: // create from serialization only CMDIView(); DECLARE_DYNCREATE(CMDIView)
    // Attributes public: CMDIDoc* GetDocument();
    int m_X; // координаты круга по x int m_Y; // координаты круга по y
    // Operations ... ... Как вы видите, имена переменных могут совпадать( обычно так и делается ).

    5. Инициализируем элементы данных класса документа. Для этого откройте файл MDIDoc.cpp, найдите в нём функцию OnNewDocument() и напишите в ней следующий код:

    BOOL CMDIDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE;

    m_X = 100; // начальное положение по X=100 m_Y = 100; // начальное положение по Y=100

    // TODO: add reinitialization code here // ( SDI documents will reuse this document)

    return TRUE; } 6. Инициализируем элементы данных класса представления. Для этого нужно создать функцию-элемент OnInitialUpdate() класса представления:

    Выберите ClassWizard в меню View. На странице Message Maps выберите следующие события:

    Class neme : CMDIView Object ID : CMDIView Message : OnInitialUpdate и нажмите на кнопку Add Function
    Напишите следующий код в функцию OnInitialUpdate():

    void CMDIView::OnInitialUpdate() { CView::OnInitialUpdate();

    // TODO: Add your specialized code here and/or call the base class

    CMDIDoc* pDoc = GetDocument(); // получить указатель на документ

    // обновить элементы данных представления // соответствующими значениями документа. m_X = pDoc->m_X; m_Y = pDoc->m_Y; pDoc->SetTitle("ANDY"); // всем документам даётся название ANDY } 7. Теперь напишем код для вывода круга на экран.
    Функция OnDraw() класса представления автоматически выполняется всякий раз, когда нужно вывести окно документа.

    Напишите следующий код в функции OnDraw() :

    void CMDIView::OnDraw(CDC* pDC) { CMDIDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here

    pDC->Ellipse(m_X - 20, m_Y - 20, m_X + 20, m_Y + 20); // рисуем круг диаметром 20 } 8. Напишем код для сохранения и считывания данных из файла.

    Откройте файл MDIDoc.cpp, найдите в нём функцию Serialize() и измените её:

    void CMDIDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here( это выполняется если выбрать SAVE ) ar<>m_X; // считываем значение из выбранного файла в m_X ar>>m_Y; // считываем значение из выбранного файла в m_Y } } 9.


    Часто бывает нужно изменить некоторые параметры программы, такие как заголовок главного окна или тип файла по умолчанию, который выводится в диалоговых панелях SAVE и OPEN. Для этого нужно выбрать закладку ResourceView и открыть пункт String Table. Вы увидите список переменных проекта( три колонки : ID, Value и Caption ).

    IDR_MAINFRAME - заголовок главного окна (изменяется в поле Caption)
    IDR_MCIRCLTYPE - тип файла по умолчанию, вы увидите 6 подстрок разделёнными знаком \n. Третья и четвёртая подстроки определяют тип документа по умолчанию. У меня CIR FILES( *.cir ) и .cir соответственно. Вы можете поставить свои значения.

    10. Теперь создадим кнопку в панеле инструментов. Для этого нужно выбрать закладку ResourceView и открыть пункт Toolbar. Вы увидите панель инструментов в режиме редактирования. Нажмите на самую правую кнопку( пунктирный квадрат ), ниже нарисуйте кнопку по вашему усмотрению. Теперь дважды нажмете на вашу кнопку и введите ID: ID_MYBUTTON и Prompt: Изменение координат круга\nИзменение координат круга. Ну вот и всё, кнопка готова. Теперь нужно создать функцию, которая будет выполняться при нажатии на вашу кнопку :

    Выберите пункт меню View далее ClassWizard, выберите закладку Message Maps, Project: MDI, Class name: CMDIView, Object IDs: ID_MYBUTTON, Message: COMMAND и нажмите на кнопку Add Function. В ответ создастся функция void CMDIView::OnMybutton().

    11. Теперь по аналогии с главой 15 создадим собственное диалоговое окно с ID: IDD_MY_DIALOG и классом CMyDialog и разместим в нём четыре Edit Box с переменными типа INT: m_DX - текущая позиция по X, m_DY - текущая позиция по Y, m_DXN - новая позииция по X, m_DYN - новая позииция по Y. Не забудте написать #include "MyDialog.h" в файлах MDIDoc.cpp и MDIView.cpp.

    12. Теперь напишем код в функцие OnMybutton().

    void CMDIView::OnMybutton() { // TODO: Add your command handler code here CMDIDoc* pDoc = GetDocument(); // получаем указатель на документ

    CMyDialog MyD; // создаём переменную класса CMyDialog MyD.m_DX = MyD.m_DXN = pDoc->m_X; // инициализмруем переменные диалога MyD.m_DY = MyD.m_DYN = pDoc->m_Y; .//

    MyD.DoModal(); // создаём новый диалог

    pDoc->m_X = MyD.m_DXN; // получаем новые значения pDoc->m_Y = MyD.m_DYN; //

    OnInitialUpdate(); // синхронизируем данные Invalidate( TRUE ); // перерисовываем экран( вызов OnDraw() ) pDoc->SetModifiedFlag(); // ставим флаг изменения документа }
    13. Ну вот и всё, программа готова.

    К списку


    Работа с библиотеками динамической компоновки (DLL) Ernest Avagyan



    12. Работа с библиотеками динамической компоновки (DLL)
  • Использование DLL
  • Библиотеки импортирования
  • Согласование интерфейсов
  • Загрузка неявно подключаемой DLL
  • Динамическая загрузка и выгрузка DLL
  • Пример обычной DLL и способов загрузки

  • Создание DLL
  • Функция DllMain
  • Экспортирование функций из DLL
  • Экспортирование классов
  • Память DLL
  • Полная компиляция DLL

  • DLL и MFC
  • Обычные MFC DLL
  • Динамические расширения MFC
  • Загрузка динамических расширений MFC
  • Экспортирование функций из динамических расширений

  • С самого рождения (или чуть позже) операционная система Windows использовала библиотеки динамической компоновки DLL (Dynamic Link Library), в которых содержались реализации наиболее часто применяемых функций. Наследники Windows — NT и Windows 95, а также OS/2 — тоже зависят от библиотек DLL в плане обеспечения значительной части их функциональных возможностей.
    Рассмотрим ряд аспектов создания и использования библиотек DLL:
  • как статически подключать библиотеки DLL;
  • как динамически загружать библиотеки DLL;
  • как создавать библиотеки DLL;
  • как создавать расширения МFC библиотек DLL.

  • Использование DLL
    Практически невозможно создать приложение Windows, в котором не использовались бы библиотеки DLL. В DLL содержатся все функции Win32 API и несчетное количество других функций операционных систем Win32.
    Вообще говоря, DLL — это просто наборы функций, собранные в библиотеки. Однако, в отличие от своих статических родственников (файлов . lib), библиотеки DLL не присоединены непосредственно к выполняемым файлам с помощью редактора связей. В выполняемый файл занесена только информация об их местонахождении. В момент выполнения программы загружается вся библиотека целиком. Благодаря этому разные процессы могут пользоваться совместно одними и теми же библиотеками, находящимися в памяти. Такой подход позволяет сократить объем памяти, необходимый для нескольких приложений, использующих много общих библиотек, а также контролировать размеры ЕХЕ-файлов.
    Однако, если библиотека используется только одним приложением, лучше сделать ее обычной, статической.
    Конечно, если входящие в ее состав функции будут использоваться только в одной программе, можно просто вставить в нее соответствующий файл с исходным текстом.

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

    Библиотеки импортирования

    При статическом подключении DLL имя .lib-файла определяется среди прочих параметров редактора связей в командной строке или на вкладке “Link” диалогового окна “Project Settings” среды Developer Studio. Однако .lib-файл, используемый при неявном подключении DLL, — это не обычная статическая библиотека. Такие .lib-файлы называются библиотеками импортирования (import libraries). В них содержится не сам код библиотеки, а только ссылки на все функции, экспортируемые из файла DLL, в котором все и хранится. В результате библиотеки импортирования, как правило, имеют меньший размер, чем DLL-файлы. К способам их создания вернемся позднее. А сейчас рассмотрим другие вопросы, касающиеся неявного подключения динамических библиотек.

    Согласование интерфейсов

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

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

    По умолчанию в Visual C++ интерфейсы функций согласуются по правилам C++. Это значит, что параметры заносятся в стек справа налево, вызывающая программа отвечает за их удаление из стека при выходе из функции и расширении ее имени. Расширение имен (name mangling) позволяет редактору связей различать перегруженные функции, т.е. функции с одинаковыми именами, но разными списками аргументов. Однако в старой библиотеке С функции с расширенными именами отсутствуют.


    Хотя все остальные правила вызова функции в С идентичны правилам вызова функции в C++, в библиотеках С имена функций не расширяются. К ним только добавляется впереди символ подчеркивания (_).

    Если необходимо подключить библиотеку на С к приложению на C++, все функции из этой библиотеки придется объявить как внешние в формате С:

    extern "С" int MyOldCFunction(int myParam);

    Объявления функций библиотеки обычно помещаются в файле заголовка этой библиотеки, хотя заголовки большинства библиотек С не рассчитаны на применение в проектах на C++. В этом случае необходимо создать копию файла заголовка и включить в нее модификатор extern "C" к объявлению всех используемых функций библиотеки. Модификатор extern "C" можно применить и к целому блоку, к которому с помощью директивы #tinclude подключен файл старого заголовка С. Таким образом, вместо модификации каждой функции в отдельности можно обойтись всего тремя строками:

    extern "С" { #include "MyCLib.h" }

    В программах для старых версий Windows использовались также соглашения о вызове функций языка PASCAL для функций Windows API. В новых программах следует использовать модификатор winapi, преобразуемый в _stdcall. Хотя это и не стандартный интерфейс функций С или C++, но именно он используется для обращений к функциям Windows API. Однако обычно все это уже учтено в стандартных заголовках Windows.

    Загрузка неявно подключаемой DLL

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

  • Каталог, в котором находится ЕХЕ-файл.
  • Текущий каталог процесса.
  • Системный каталог Windows.


  • Если библиотека DLL не обнаружена, приложение выводит диалоговое окно с сообщением о ее отсутствии и путях, по которым осуществлялся поиск. Затем процесс отключается.

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


    Теперь приложение может обращаться к функциям, содержащимся в DLL.

    Динамическая загрузка и выгрузка DLL

    Вместо того, чтобы Windows выполняла динамическое связывание с DLL при первой загрузке приложения в оперативную память, можно связать программу с модулем библиотеки во время выполнения программы (при таком способе в процессе создания приложения не нужно использовать библиотеку импорта). В частности, можно определить, какая из библиотек DLL доступна пользователю, или разрешить пользователю выбрать, какая из библиотек будет загружаться. Таким образом можно использовать разные DLL, в которых реализованы одни и те же функции, выполняющие различные действия. Например, приложение, предназначенное для независимой передачи данных, сможет в ходе выполнения принять решение, загружать ли DLL для протокола TCP/IP или для другого протокола.



    Загрузка обычной DLL



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

    HINSTANCE hMyDll; …… if((hMyDll=::LoadLibrary(“MyDLL”))==NULL) { /* не удалось загрузить DLL */ } else { /* приложение имеет право пользоваться функциями DLL через hMyDll */ }

    Стандартным расширением файла библиотеки Windows считает .dll, если не указать другое расширение. Если в имени файла указан и путь, то только он будет использоваться для поиска файла. В противном случае Windows будет искать файл по той же схеме, что и в случае неявно подключенных DLL, начиная с каталога, из которого загружается exe-файл, и продолжая в соответствии со значением PATH.

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

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


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

    // тип PFN_MyFunction будет объявлять указатель на функцию, // принимающую указатель на символьный буфер и выдающую значение типа int typedef int (WINAPI *PFN_MyFunction)(char *); …… PFN_MyFunction pfnMyFunction;

    Затем следует получить дескриптор библиотеки, при помощи которого и определить адреса функций, например адрес фунции с именем MyFunction:

    hMyDll=::LoadLibrary(“MyDLL”); pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll,”MyFunction”); …… int iCode=(*pfnMyFunction)(“Hello”);

    Адрес функции определяется при помощи функции ::GetProcAddress, ей следует передать имя библиотеки и имя функции. Последнее должно передаваться в том виде, в котором эксаортируется из DLL.

    Можно также сослаться на функцию по порядковому номеру, по которому она экспортируется (при этом для создания библиотеки должен использоваться def-файл, об этом будет рассказано далее):

    pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll, MAKEINTRESOURCE(1));

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

    ::FreeLibrary(hMyDll);

    Загрузка MFC-расширений динамических библиотек



    При загрузке MFC-расширений для DLL (подробно о которых рассказывается далее) вместо функций LoadLibraryи FreeLibrary используются функции AfxLoadLibrary и AfxFreeLibrary. Последние почти идентичны функциям Win32 API. Они лишь гарантируют дополнительно, что структуры MFC, инициализированные расширением DLL, не были запорчены другими потоками.



    Ресурсы DLL



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



    Замечание.
    С помощью функции LoadLibrary можно также загружать в память исполняемые файлы (не запускать их на выполнение!). Дескриптор выполняемого модуля может затем использоваться при обращении к функциям FindResource и LoadResource для поиска и загрузки ресурсов приложения.

    Работа с WinSocket Ernest Avagyan



    16. Работа с WinSocket
    Socket (гнездо, разъем) - абстрактное программное понятие, используемое для обозначения в прикладной программе конечной точки канала связи с коммуникационной средой, образованной вычислительной сетью. При использовании протоколов TCP/IP можно говорить, что socket является средством подключения прикладной программы к порту локального узла сети.
    Socket-интерфейс представляет собой просто набор системных вызовов и/или библиотечных функций языка программирования СИ, разделенных на четыре группы:
    Ниже рассматривается подмножество функций socket-интерфейса, достаточное для написания сетевых приложений, реализующих модель "клиент-сервер" в режиме с установлением соединения. Все функции сокетов содержаться в wsock32.dll перед тем как написать программу с использование функций сокетов необходимо задать компилятору чтобы он включил в программу файл wsock32.lib. В меню Рroject выберите пункт settings а там укажите раздел Link, wsock32.lib можно ввести в поле Library modules или project options. Не забудьте поставить #include "Winsock2.h".
    1. Создание сервера
    Прежде чем воспользоваться функцией socket необходимо проинициализировать процесс библиотеки wsock32.dll вызвав функцию WSAStartup например:
    WSADATA WsaData; int err = WSAStartup (0x0101, &WsaData); if (err == SOCKET_ERROR) { printf ("WSAStartup() failed: %ld\n", GetLastError ()); return 1; } Здесь 0х0101 версия библиотеки которую следует использовать.

    Теперь объявление переменную типа SOCKET например s : s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    Создание socket'а осуществляется следующим системным вызовом
    int socket (domain, type, protocol) int domain; int type; int protocol;
    Аргумент domain задает используемый для взаимодействия набор протоколов (вид коммуникационной области), для стека протоколов TCP/IP он должен иметь символьное значение AF_INET.
    Аргумент type задает режим взаимодействия:
  • SOCK_STREAM - с установлением соединения;
  • SOCK_DGRAM - без установления соединения.


  • Аргумент protocolзадает конкретный протокол транспортного уровня (из нескольких возможных в стеке протоколов). Если этот аргумент задан равным 0, то будет использован протокол "по умолчанию" (TCP для SOCK_STREAM и UDP для SOCK_DGRAM при использовании комплекта протоколов TCP/IP).

    При удачном завершении своей работы данная функция возвращает дескриптор socket'а - целое неотрицательное число, однозначно его идентифицирующее. Дескриптор socket'а аналогичен дескриптору файла ОС UNIX.

    При обнаружении ошибки в ходе своей работы функция возвращает число "-1".

    Далее мы задаем параметры для сокета (сервера) для этого нам необходимо объявить структуру SOCKADDR_IN sin далее заполняем параметры для сервера:

    SOCKADDR_IN sin; sin.sin_family = AF_INET; sin.sin_port = htons(80); sin.sin_addr.s_addr = INADDR_ANY;

    Структура SOCKADDR_IN используется несколькими системными вызовами и функциями socket-интерфейса и определена в include-файле in.h следующим образом:

    struct SOCKADDR_IN { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; };

    Поле sin_family определяет используемый формат адреса (набор протоколов), в нашем случае (для TCP/IP) оно должно иметь значение AF_INET.

    Поле sin_addr содержит адрес (номер) узла сети.

    Поле sin_port содержит номер порта на узле сети.

    Поле sin_zero не используется.

    Определение структуры in_addr (из того же include-файла) таково:

    struct in_addr { union { u_long S_addr; /* другие (не интересующие нас) члены объединения */ } S_un; #define s_addr S_un.S_addr };

    Структура SOCKADDR_IN должна быть полностью заполнена перед выдачей системного вызова bind. При этом, если поле sin_addr.s_addr имеет значение INADDR_ANY, то системный вызов будет привязывать к socket'у номер (адрес) локального узла сети.

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


    В сетях TCP/IP socket связывается с локальным портом. Системный вызов bind имеет следующий синтаксис:

    int bind (s, addr, addrlen) int s; struct SOCKADDR_IN *addr; int addrlen; Пример: err = bind( s, (LPSOCKADDR)&sin, sizeof(sin) );

    Аргумент s задает дескриптор связываемого socket'а.

    Аргумент addr в общем случае должен указывать на структуру данных, содержащую локальный адрес, приписываемый socket'у. Для сетей TCP/IP такой структурой является SOCKADDR_IN.

    Аргумент addrlen задает размер (в байтах) структуры данных, указываемой аргументом addr.

    В случае успеха bind возвращает 0, в противном случае - "-1".

    Для установления связи "клиент-сервер" используются системные вызовы listen и accept (на стороне сервера), а также connect (на стороне клиента). Для заполнения полей структуры socaddr_in, используемой в вызове connect, обычно используется библиотечная функция gethostbyname, транслирующая символическое имя узла сети в его номер (адрес).

    Системный вызов listen выражает желание выдавшей его программы-сервера ожидать запросы к ней от программ-клиентов и имеет следующий вид:

    int listen (s, n) int s; int n; Пример: err = listen( s, SOMAXCONN);

    Аргумент s задает дескриптор socket'а, через который программа будет ожидать запросы к ней от клиентов. Socket должен быть предварительно создан системным вызовом socketи обеспечен адресом с помощью системного вызова bind.

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

    Признаком удачного завершения системного вызова listen служит нулевой код возврата.

    Перед тем как воспользоваться функцией accept сначала объявите ещё одну переменную типа SOCKET, например s1 .

    SOCKADDR_IN from; int fromlen=sizeof(from); s1 = accept(s,(struct sockaddr*)&from, &fromlen); Это зделано для того что бы узнать IP адрес и порт удаленного компьютера. Что бы вывести на экран IP адрес и порт удаленного компа просто вставить в программу такие строки: printf("accepted connection from %s, port %d\n", inet_ntoa(from.sin_addr), htons(from.sin_port)) ;


    Для приема запросов от программ-клиентов на установление связи в программах-серверах используется системный вызов accept, имеющий следующий вид:

    int accept (s, addr, p_addrlen) int s; struct sockaddr_in *addr; int *p_addrlen;

    Аргумент s задает дескриптор socket'а, через который программа- сервер получила запрос на соединение (посредством системного запроса listen ).

    Аргумент addr должен указывать на область памяти, размер которой позволял бы разместить в ней структуру данных, содержащую адрес socket'а программы-клиента, сделавшей запрос на соединение. Никакой инициализации этой области не требуется.

    Аргумент p_addrlen должен указывать на область памяти в виде целого числа, задающего размер (в байтах) области памяти, указываемой аргументом addr.

    Системный вызов accept извлекает из очереди, организованной системным вызовом listen, первый запрос на соединение и возвращает дескриптор нового (автоматически созданного) socket'а с теми же свойствами, что и socket, задаваемый аргументом s. Этот новый дескриптор необходимо использовать во всех последующих операциях обмена данными.

    Кроме того после удачного завершения accept:

  • область памяти, указываемая аргументом addr, будет содержать структуру данных (для сетей TCP/IP это sockaddr_in), описывающую адрес socket'а программы-клиента, через который она сделала свой запрос на соединение;
  • целое число, на которое указывает аргумент p_addrlen, будет равно размеру этой структуры данных.


  • Если очередь запросов на момент выполнения accept пуста, то программа переходит в состояние ожидания поступления запросов от клиентов на неопределенное время (хотя такое поведение accept можно и изменить).

    Признаком неудачного завершения accept служит отрицательное возвращенное значение (дескриптор socket'а отрицательным быть не может).

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

    Вот мы соединились с клиентом, но как получить и передать информацию? Это делается с помощью команд send и recv ко не забывайте что вы теперь работаете с переменной s1:


    BYTE RecvBuffer[1]; while(recv(s1,RecvBuffer,sizeof(RecvBuffer),0)!=SOCKET_ERROR) { printf("%c",RecvBuffer[0]); send(s1,MsgText,sizeof(MsgText),MSG_DONTROUTE); } Мы поставили цикл while, потому что он будет выполнятся пока клиент не отключится

    Для получения данных от партнера по сетевому взаимодействию используется системный вызов recv, имеющий следующий вид

    int recv (s, buf, len, flags) int s; char *buf; int len; int flags;

    Аргумент s задает дескриптор socket'а, через который принимаются данные.

    Аргумент buf указывает на область памяти, предназначенную для размещения принимаемых данных.

    Аргумент len задает длину (в байтах) этой области.

    Аргумент flags модифицирует исполнение системного вызова recv. При нулевом значении этого аргумента вызов recv полностью аналогичен системному вызову read.

    При успешном завершении recv возвращает количество принятых в область, указанную аргументом buf, байт данных. Если канал данных, определяемый дескриптором s, оказывается "пустым", то recv переводит программу в состояние ожидания до момента появления в нем данных.

    Для посылки данных партнеру по сетевому взаимодействию используется системный вызов send, имеющий следующий вид

    int send (s, buf, len, flags) int s; char *buf; int len; int flags;

    Аргумент s задает дескриптор socket'а, через который посылаются данные.

    Аргумент buf указывает на область памяти, содержащую передаваемые данные.

    Аргумент len задает длину (в байтах) передаваемых данных.

    Аргумент flags модифицирует исполнение системного вызова send. При нулевом значении этого аргумента вызов send полностью аналогичен системному вызову write.

    При успешном завершении send возвращает количество переданных из области, указанной аргументом buf, байт данных. Если канал данных, определяемый дескриптором s, оказывается "переполненным", то send переводит программу в состояние ожидания до момента его освобождения.

    Для закрытия ранее созданного socket'а используется обычный системный вызов closesocket, применяемый в ОС UNIX для закрытия ранее открытых файлов и имеющий следующий вид


    int closesocket(s) int s;

    Аргумент s задает дескриптор ранее созданного socket'а.

    Однако в режиме с установлением логического соединения (обеспечивающем, как правило, надежную доставку данных) внутрисистемные механизмы обмена будут пытаться передать/принять данные, оставшиеся в канале передачи на момент закрытия socket'а. На это может потребоваться значительный интервал времени, неприемлемый для некоторых приложений. В такой ситуации необходимо использовать описываемый далее системный вызов shutdown.

    2. Создание клиентаПрограмма клиента делается аналогично до момента создания сокетов. Cоздайте сокет так как описано выше, но не пользуйтесь командой bind:

    SOCKADDR_IN anAddr; anAddr.sin_family = AF_INET; anAddr.sin_port = htons(80); anAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); Заполнение структуры производится почти также но нeжно указать теперь IP адрес сервера ( пример 127.0.0.1 ) .Дальше сразу можно соединятся:

    connect(s, (struct sockaddr *)&anAddr, sizeof(struct sockaddr));

    Для обращения программы-клиента к серверу с запросом на установление логической соединения используется системный вызов connect, имеющий следующий вид

    int connect (s, addr, addrlen) int s; struct sockaddr_in *addr; int addrlen;

    Аргумент s задает дескриптор socket'а, через который программа обращается к серверу с запросом на соединение. Socket должен быть предварительно создан системным вызовом socketи обеспечен адресом с помощью системного вызова bind.

    Аргумент addr должен указывать на структуру данных, содержащую адрес, приписанный socket'у программы-сервера, к которой делается запрос на соединение. Для сетей TCP/IP такой структурой является sockaddr_in. Для формирования значений полей структуры sockaddr_in удобно использовать функцию gethostbyname.

    Аргумент addrlen задает размер (в байтах) структуры данных, указываемой аргументом addr.

    Для того, чтобы запрос на соединение был успешным, необходимо, по крайней мере, чтобы программа-сервер выполнила к этому моменту системный вызов listen для socket'а с указанным адресом.


    При успешном выполнении запроса системный вызов connect возвращает 0, в противном случае - "-1" (устанавливая код причины неуспеха в глобальной переменной errno).

    Примечание. Если к моменту выполнения connect используемый им socket не был привязан к адресу посредством bind ,то такая привязка будет выполнена автоматически.

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

    Вот наконец установлена долгожданная связь c сервером( не забывайте проверять ошибки). Дальше воспользуемся функциями send и recv по своему усмотрению. 3. Приложение

    Для получения адреса узла сети TCP/IP по его символическому имени используется библиотечная функция

    struct hostent *gethostbyname (name) char *name;

    Аргумент name задает адрес последовательности литер, образующих символическое имя узла сети.

    При успешном завершении функция возвращает указатель на структуру hostent, определенную в include-файле netdb.h и имеющую следующий вид

    struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_lenght; char *h_addr; };

    Поле h_name указывает на официальное (основное) имя узла.

    Поле h_aliases указывает на список дополнительных имен узла (синонимов), если они есть.

    Поле h_addrtype содержит идентификатор используемого набора протоколов, для сетей TCP/IP это поле будет иметь значение AF_INET.

    Поле h_lenght содержит длину адреса узла.

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

    Для "экстренного" закрытия связи с партнером (путем "сброса" еще не переданных данных) используется системный вызов shutdown, выполняемый перед close и имеющий следующий вид

    int shutdown (s, how) int s; int how;

    Аргумент s задает дескриптор ранее созданного socket'а.

    Аргумент how задает действия, выполняемые при очистке системных буферов socket'а:

  • 0 - сбросить и далее не принимать данные для чтения из socket'а;
  • 1 - сбросить и далее не отправлять данные для посылки через socket;
  • 2 - сбросить все данные, передаваемые через socket в любом направлении.


  • К списку

    Работа со стандартными ресурсамиErnest Avagyan



    11. Работа со стандартными ресурсами.

    1. Методы класса CButton
    2. Методы класса CEdit
    3. Методы класса CListBox
    4. Методы класса CComboBox
    5. Методы класса CProgressCtrl
    6. Методы класса CSliderCtrl
    7. Методы класса CSpinButtonCtrl

    1. Методы класса CButton

    HBITMAP GetBitmap() const;

    Возвращает дескриптор растрового изображения, сопоставленного кнопке. Если такового не существует, то возвращается NULL.

    HBITMAP SetBitmap(HBITMAP hBitmap);

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

    HCURSOR GetCursor();

    Возвращает дескриптор курсора, сопоставленного кнопке методом SetCursor. Если у кнопки нет сопоставленного курсора, то возвращается NULL.

    HCURSOR SetCursor(HCURSOR hCursot);

    Сопоставляет кнопке курсор, изображение которого будет помещено на поверхность кнопки аналогично значку и растровому изображению.
    UINT GetState() const;

    Возвращает описание набора текущих состояний кнопки. Чтобы выделить из этого описания значения конкретных типов состояния, можно использовать маски:
  • 0х0003 - выделяет собственное состояние кнопки. Применимо только к флажку или переключателю. Если результат побитового умножения дает 0, значит кнопка находится в невыбранном состоянии, 1 - в выбранном, 2 - в неопределенном.
  • 0х0004 - выделяет состояние первого типа. Ненулевой вариант означает, что кнопка "нажата", нулевой - кнопка свободна.
  • 0х0008 - выделяет положение фокуса. Ненулевой вариант - кнопка в фокусе клавиатуры.

  • int GetCheck() const;

    Возвращает собственное состояние флажка или переключателя. Возвращаемое значение может принимать одно из значений: 0 - кнопка не выбрана; 1 - кнопка выбрана; 2 - кнопка в неопределенном состоянии. Если кнопка не является ни переключателем, ни флажком, возвращается 0.

    void SetCheck(int nCheck);

    Устанавливает собственное состояние флажка или переключателя. Значения задаются из набора: 0 - невыбранное; 1 - выбранное; 2 - неопределенное.
    Значение 2 применимо только к флажку со свойством 3State.



    UINT GetButtonStyle() const;



    Возвращает стиль кнопки.



    void SetButtonStyle(UINT nStyle, BOOL bRedraw=TRUE);



    Устанавливает стиль кнопки. Если параметр bRedraw равен TRUE, кнопка перерисовывается.



    HICON GetIcon() const;



    Возвращает дескриптор пиктограммы, сопоставленной кнопке. Если у кнопки нет сопоставленной пиктограммы, возвращает NULL.



    HICON SetIcon(HICON hIcon);



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

    Пиктограмма автоматически помешается на поверхность кнопки и сдвигается в ее центр. Если поверхность кнопки меньше пиктограммы, она обрезается со всех сторон до размеров кнопки. Положение пиктограммы может быть выровнено и не по центру. Для этого нужно, чтобы кнопка имела одно из следующих свойств: BS_LEFT, BS_RIGHT, BS_CENTER, BS_TOP, BS_BOTTOM, BS_VCENTER

    Данный метод устанавливает для кнопки только одну пиктограмму, которая будет наравне с текстом присутствовать при любом ее состоянии. Не надо путать ее с растровым изображением у растровой кнопки.

    2. Методы класса CEdit

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



    Общие методы :

    DWORD GetSel() const;

    void GetSel(int& nStartChar, int& nEndChar) const;



    Получает первую и последнюю позиции выделенного текста. Для значения типа DWORD младшее слово содержит позицию первого, старшее - последнего символа.



    void SetSel(DWORD dwSelection, BOOL bNoScroll=FALSE);

    void SetSel(int nStartChar, int nEndChar, BOOL bNoScroll=FALSE);



    Устанавливает новое выделение текста, задавая первый и последний выделенный символ. Значение FALSE параметра bNoScroll должно отключать перемещение курсора в область видимости.



    void ReplaceSel(LPCTSTR lpszNewText);



    Заменяет выделенный текст на строку, передаваемую в параметре lpszNewText.



    void Clear();



    Удаляет выделенный текст.




    void Copy();



    Копирует выделенный текст в буфер.



    void Cut();



    Переносит (копирует и удаляет) выделенный текст в буфер обмена.



    void Paste();



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



    BOOL Undo();



    Отмена последней операции, выполненной редактором. Если редактор однострочный, возвращается всегда неотрицательное значение, иначе неотрицательное значение возвращается лишь в случае успешной замены.



    BOOL CanUndo() const;



    Определяет, можно ли отменить последнюю операцию редактора.



    void EmptyUndoBuffer();



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



    BOOL GetModify() const;



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



    void SetModify(BOOL bModified=TRUE);



    Устанавливает или сбрасывает флаг модификации (см. предыдущий метод). Флаг сбрасывается при вызове метода с параметром FALSE и устанавливается при модификации содержимого окна редактирования или при вызове SetModify с параметром TRUE.



    BOOL SetReadOnly(BOOL bReadOnly=TRUE);



    Устанавливает режим просмотра (bReadOnly=TRUE) или редактирования (bReadOnly=FALSE).



    TCHAR GetPasswordChar() const;



    Возвращает символ, который при выводе пароля будет появляться на экране вместо символов, набираемых пользователем. Если такой символ не определен, возвращается 0. Устанавливается этот символ методом (по умолчанию используется "*"):



    void SetPasswordChar(TCHAR ch);

    void LimitText(int nChars=0);



    Устанавливает максимальную длину в байтах текста, который может ввести пользователь. Если значение параметра равно 0, длина текста устанавливается равной UINT_MAX.



    Методы работы с многострочным редактором :

    void LineScroll(int nLines, int nChars=0);




    Прокручивает текст в области редактирования. Параметр nLimes задает число строк для вертикальной прокрутки. Окно редактирования не прокручивает текст дальше последней строки. При положительном значении параметра область редактирования сдвигается вдоль текста к последней строке, при отрицательной - к первой.

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



    int GetFirstVisibleLine() const;



    Возвращает номер первой видимой строки.



    int GetLineCount() const;



    Возвращает число строк текста, находящегося в буфере редактирования. Если текст не вводился, возвращает 1.



    int GetLine(int nIndex, LPTSTR lpszBuffer) const;

    int GetLine(int nIndex, LPTSTR lpszBuffer, int nMaxLength) const;



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

    Метод возвращает число в действительности скопированных байтов. Если номер строки больше или равен числу строк в буфере окна редактирования, возвращает 0. Текст копируется без каких-либо изменений, нуль-символ не добавляется.



    int LineIndex(int nLine=-1) const;



    Возвращает номер первого символа в строке. Неотрицательное значение параметра принимается в качестве номера строки. Значение -1 задает текущую строку. Если номер строки больше или равен числу строк в буфере окна редактирования (строки нумеруются с 0), возвращается 0.

    3. Методы класса CListBox

    void ResetContent();



    Очищает содержимое списка, делая его пустым.



    int AddString( LPCSTR lpszItem);



    Добавляет строку lpszItem в список и сортирует его, если при создании включено свойство Sort. В противном случае элемент добавляется в конец списка.




    int DeleteString( UINT nIndex);



    Удаляет из списка элемент с индексом nIndex. Индексация элементов начинается с 0.



    int GetCurSel() const;



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



    int SetCurSel( int nSelect);



    Отмечает элемент с индексом nSelect как выбранный элемент списка. Если значение параметра равно -1, список не будет содержать отмеченных элементов.



    int GetText( int nIndex, LPSTR lpszBuffer) const;

    void GetText( int nIndex, CString& rString) const;



    Копирует элемент с индексом nIndex в буфер.



    int SetTopIndex( int nIndex);



    Организует прокрутку списка в окне так, чтобы элемент с индексом nIndex был видимым.



    int FindString( int nStartAfter, LPCSTR lpszItem) const;



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



    int FindStringExact( int nIndexStart, LPCSTR lpszFind) const;



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

    4. Методы класса CComboBox
    int GetCurSel() const;

    Возвращает целочисленный указатель выбранной строчки.

    int SetCurSel(int nSelect);;

    Ставит указатель на строчку с номером nSelect.

    int GetLBText(int nIndex, LPTSTR lpszText) const;
    void GetLBText(int nIndex, CString& rString) const;

    Записывает содержимое строчки с индексом nIndex в переменные LPTSTR lpszText или CString& rString.

    int GetLBTextLen(int nIndex) const;

    Возвращает длину строчки с индексом nIndex.

    int AddString(LPCTSTR lpszString);

    Добавляет строчку в список.

    int DeleteString(UINT nIndex);

    Удаление строчки с индексом nIndex.

    int InsertString(int nIndex, LPCTSTR lpszString);

    Заменяет строчку с индексом nIndex содержимым переменной LPCTSTR lpszString.

    5. Методы класса CProgressCtrl
    void SetRange(short nLower, short nUpper);
    void SetRange32(int nLower, int nUpper);


    Устанавливает минимальное ( nLower ) и максимальное значение ( nUpper ).

    void GetRange(int& nLower, int& nUpper);

    Записывает в переменные nLower и nUpper минимальное и максимальное значение.

    int GetPos();

    Возвращает текущее значение.

    int SetPos(int nPos);

    Устанавливает текущее значение в nPos.

    int SetStep(int nStep);

    Устанавливает шаг ( nStep ) вывода.

    6. Методы класса CSliderCtrl
    int GetRangeMax() const;
    int GetRangeMin() const;
    void GetRange(int& nMin, int& nMax) const;


    Первые две функции возвращают максимальное и минимальное знанение, а третья - записывает эти значения в nMax и nMin соответственно.

    void SetRangeMin(int nMin, BOOL bRedraw = FALSE);
    void SetRangeMax(int nMax, BOOL bRedraw = FALSE);
    void SetRange(int nMin, int nMax, BOOL bRedraw = FALSE);


    Первые две функции устанавливают максимальное и минимальное знанение, а третья - устанавливает эти значения из переменных nMax и nMin соответственно. Аргумент bRedraw отвечает за перерисовку слайдера.

    int GetPos() const;

    Возвращает текущую позицию.

    void SetPos(int nPos);

    Устанавливает текущую позицию в nPos.

    BOOL SetTic(int nTic);

    Устанавливает шаг ( nTic ).

    void SetTicFreq(int nFreq);

    Устанавливает частоту засечек ( nFreq ).

    7. Методы класса CSpinButtonCtrl
    int SetPos(int nPos);

    Устанавливает текущую позицию в nPos.

    int GetPos() const;;

    Возвращает текущую позицию.

    void SetRange(int nLower, int nUpper);
    void SetRange32(int nLower, int nUpper);


    Устанавливает максимальное и минимальное знанение из переменных nMax и nMin соответственно.

    void GetRange(int &lower, int& upper) const;
    void GetRange32(int &lower, int& upper) const;;


    Эти две функции записывают максимальное и минимальное знанение в upper и lower соответственно.

    К списку

    Создание простого FTP-клиента Ernest Avagyan



    22. Создание простого FTP-клиента
    В этой главе будет написана программа, которая может считывать файлы из Internet по FTP протоколу и записывать их на диск.
    Для связи с Internet в Visual C++ существует так называемый WinInet Class. В него входят несколько подклассов.
    Далее представлены ксассы WinInet:


    Классы Описание
    CInternetSession Создаёт Internet сессию. Все MFC WinInet приложения должны создавать CInternetSession объект перед использрванием других WinInet классов.
    CInternetConnection Создаёт коннект с Internet. Это базовый класс для классов CFtpConnection, CGopherConnection, и CHttpConnection.
    CFtpConnection Устанавливает соединение по FTP протоколу.
    CGopherConnection Создаёт Gopher коннект.
    CHttpConnection Устанавливает соединение по HTTP протоколу.
    CInternetFile Разрешает удалённый доступ к файлам на Internet серверах. Это базовый класс для классов CGopherFile and CHttpFile.
    CGopherFile Разрешает удалённый доступ к файлам на Gopher серверах.
    CHttpFile Разрешает удалённый доступ к файлам на HTTP серверах.
    CFileFind Разрешает поиск файлов в Internet. Это базовый класс для классов CFtpFileFind and CGopherFileFind.
    CFtpFileFind Разрешает поиск файлов на FTP серверах.
    CGopherFileFind Разрешает поиск файлов на Gopher серверах.
    CGopherLocator Отыскивает Gopher устройство ввода позиций от gopher сервера.
    CInternetException Управляет исключениями, сгенерированными WinInet классом.

    Наша программа будет использовать три класса WinInet: CInternetSession, CFtpFileFind и CFtpConnection
    Далее будут описаны методы( функции ) этих классов:

    Методы ( функции ) класса CInternetSession


    Функции Описание
    Close() Закрывает Internet сессию.
    EnableStatusCallback() Разрешает использование функции повторного вызова, которая используется для асинхронных действий.
    GetContext() Получает значение контекста Internet сессии.
    GetFtpConnection() Устанавливает подключение по FTP протоколу.
    GetGopherConnection() Устанавливает подключение с Gopher серверами.
    GetHttpConnection() Устанавливает подключение по HTTP протоклолу.
    OnStatusCallback() Модифицирует состояние операции.
    OpenURL() Соединяется с данным URL.
    QueryOption() Сервис проверки ошибки провайдера.
    ServiceTypeFromHandle() Получает тип сервиса от Internet дескриптора.
    SetOption() Устанавливает опции Internet сессии.
    <
    INTERNET_PORT nPort = 21; // интернет порт CString temp; char *temp2; char temp3[100];

    CInternetSession internetSession; // переменная класса CInternetSession CFtpConnection* ftpConnection; // переменная класса CFtpConnection BOOLEAN gotFile; BOOL ConnFlag = FALSE;

    LV_ITEM lvi; // переменная для List Control

    ... m_url = _T(""); m_temp = _T(""); m_user = _T("anonymous"); m_pass = _T(""); m_edit = _T(""); m_save = _T("c:\\save_to\\"); ...

    // TODO: Add extra initialization here

    szColumn[0] = "File names:"; // имя первой колонки szColumn[1] = "Lenght:"; // имя второй колонки

    // далее создаётся List Control с двумя колонками

    LV_COLUMN lvc; lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT;

    for (int j = 0; j < 2; j++) { if( j == 0 ) lvc.cx = 200; else lvc.cx = 80; lvc.pszText = szColumn[j]; if ((j == 1)) lvc.fmt = LVCFMT_RIGHT; lvc.iSubItem = j; m_ListView.InsertColumn(j, &lvc); }

    // --------------------------------------------------------------------------------------- m_numFiles = 0;

    m_url = "ftp://mark5.dhtp.kiae.ru/"; // URL для коннекта ..

    // --------------------------------------------------------------------------------------- void CFTP_ClientDlg::OnButtonConnect() { // TODO: Add your control notification handler code here // вызываем ф-ю MyConnect BOOL flag = MyConnect( MyGetURL(m_url), MyGetPath(m_url) ); if( flag == FALSE ) return; MyPrintFiles(); // печатаем список файлов в List Control

    } // --------------------------------------------------------------------------------------

    // функция коннектится к URL : url с текущей директорий : path

    BOOL CFTP_ClientDlg::MyConnect(CString url, CString path) { if( ConnFlag == TRUE ) MyEndConnect(); try { if( (m_user == "anonymous") || (m_user == "") ) ftpConnection = internetSession.GetFtpConnection(url, NULL, NULL, nPort, FALSE ); else ftpConnection = internetSession.GetFtpConnection(url, m_user, m_pass, nPort, FALSE );


    ftpConnection->SetCurrentDirectory( path ); ConnFlag = TRUE; m_ListView.SetFocus(); return TRUE; } catch (CInternetException* pException) { // если была ошибка pException->ReportError(); return FALSE; } }

    ..

    // --------------------------------------------------------------------------------------- Функция возвращает URL сервера, например, если было введено "ftp://www.site.ru/path/" , то будет возвращено "www.site.ru"

    CString CFTP_ClientDlg::MyGetURL(CString url) {

    char *pre = ""; char *first=""; char *ftp = "ftp://"; char sl = '/'; int len; CString null = "";

    len = url.GetLength();

    if( len <= 5 ) { for( int i = 0; i < len; i++ ) { if( char(url.GetAt(i)) != sl ) pre[i] = (char)url.GetAt( i ); else { pre[i] = 0; return (CString)&pre[0]; } } }

    for( int i=0; i<6; i++ ) first[i] = (char)url.GetAt( i ); first[6] = 0;

    if( (strcmp(first, ftp) == 0) && (len == 6) ) { return null; }

    if( strcmp(first, ftp) == 0 ) { for( i = 6; i < len; i++ ) { if( char(url.GetAt(i)) != sl ) pre[i-6] = (char)url.GetAt( i ); else { pre[i-6] = 0; i = (len - 1); } } return (CString)&pre[0];

    }

    if( strcmp(first, ftp) == 1 ) { for( i = 0; i < len; i++ ) { if( char(url.GetAt(i)) != sl ) pre[i] = (char)url.GetAt( i ); else { pre[i] = 0; i = (len - 1); } } return (CString)&pre[0]; }

    return null; } // ---------------------------------------------------------------------------------------

    Функция возвращает PATH, например, если было введено "ftp://www.site.ru/path/" , то будет возвращено "/path/"

    CString CFTP_ClientDlg::MyGetPath(CString url) { char *pre = ""; char *first=""; char *ftp = "ftp://"; char sl = '/'; CString null = ""; int len, num;

    len = url.GetLength();

    if( len <= 5 ) { num = -1; pre = ""; for( int i = 0; i < len; i++ ) if( char(url.GetAt(i)) == sl ) { num = i; i = len-1; } if( (char(url.GetAt(num+1)) == 0) || num < 0) { return CString("/"); }


    for( i = (num); i < len; i++ ) pre[i-num] = (char)url.GetAt( i ); pre[len - num] = 0;

    return (CString)&pre[0]; }

    for( int i=0; i<6; i++ ) first[i] = (char)url.GetAt( i ); first[6] = 0;

    if( (strcmp(first, ftp) == 0) && (len == 6) ) { MessageBeep(65535); return null; }

    if( strcmp(first, ftp) == 0 ) { num = -1; pre = ""; for( int i = 6; i < len; i++ ) if( char(url.GetAt(i)) == sl ) { num = i; i = len-1; } if( (char(url.GetAt(num+1)) == 0) || num < 0) { return (CString)"/"; }

    for( i = (num); i < len; i++ ) { pre[i-num] = (char)url.GetAt( i );}

    pre[len-num] = 0;

    return (CString)&pre[0]; }

    if( strcmp(first, ftp) == 1 ) { num = -1; pre = ""; for( int i = 0; i < len; i++ ) if( char(url.GetAt(i)) == sl ) { num = i; i = len-1; } if( (char(url.GetAt(num+1)) == 0) || num < 0) { return (CString)"/"; }

    for( i = (num); i < len; i++ ) pre[i-num] = (char)url.GetAt( i ); pre[len-num] = 0; return (CString)&pre[0]; }

    return null; }

    // --------------------------------------------------------------------------------------- // вукция выводит список файлов в List Control void CFTP_ClientDlg::MyPrintFiles() { MyEraseList(); // очистка

    ftpConnection->GetCurrentDirectory(m_curDirectory); // в m_curDirectory записываем текущую директорию CFtpFileFind ftpFileFind(ftpConnection); // создаём переменную класса CFtpFileFind ftpFileFind.FindFile(); // ищем все файлы

    m_temp = "Current directory = " + m_curDirectory ;

    int x = 1; int len = 0; CTime tim;

    lvi.mask = LVIF_TEXT | LVIF_IMAGE; lvi.iSubItem = 0;

    Files[0] = ".."; Leng[0] = "-"; m_ListView.SetTextColor( RGB(150,0,0 ) ); lvi.iItem = 0; lvi.pszText = Files[0]; lvi.cchTextMax = 0; lvi.iImage = 0; m_ListView.InsertItem(&lvi);

    m_ListView.SetItemText(0, 1, Leng[0]);

    do { temp = ""; temp2 = ""; len=0;

    gotFile = ftpFileFind.FindNextFile(); // ищем следующие файлы

    temp = (CString)ftpFileFind.GetFileName(); // в temp заносится имя файла


    len = temp.GetLength(); в len заносится длина файла for( int a = 0; a < len; a++ ) {temp2[a] = (char)temp.GetAt(a);} temp2[len] = 0; Files[x] = temp2;

    if( ftpFileFind.IsDirectory() ) // проверка на директорию { Leng[x] = "< Dir >";

    } else { itoa( (int)ftpFileFind.GetLength(), temp3, 10 ); Leng[x] = temp3;

    }

    lvi.iItem = x; lvi.pszText = Files[x]; lvi.cchTextMax = x; lvi.iImage = x; m_ListView.InsertItem(&lvi);

    m_ListView.SetItemText(x, 1, Leng[x]); ++x;

    }while ((x < NUM) && (gotFile)); // цикл пока не кончатся файлы

    m_numFiles = x; m_ListView.SetFocus(); m_ListView.SetHotItem( 0 );

    m_ListView.UpdateData( FALSE ); m_enter.EnableWindow( TRUE ); UpdateData( FALSE ); }

    // --------------------------------------------------------------------------------------- // функция очистки List Control void CFTP_ClientDlg::MyEraseList() { for( int i = 0; i < m_numFiles; i++ ) { Leng[i]=""; Files[i]="";} m_ListView.DeleteAllItems();

    m_numFiles = 0; UpdateData( FALSE ); }

    // --------------------------------------------------------------------------------------- // функция окончания сеанса void CFTP_ClientDlg::MyEndConnect() { delete ftpConnection; ConnFlag = FALSE; }

    // функция позволяет перемещаться по каталогам сервера void CFTP_ClientDlg::OnButtonEnter() { // TODO: Add your control notification handler code here POSITION pos; CString new_url, dir;

    pos = m_ListView.GetFirstSelectedItemPosition();

    (int)pos--; CString name = m_ListView.GetItemText( (int)pos, 0 ); CString len = m_ListView.GetItemText( (int)pos, 1 );

    if( ((int)pos == 0) && (m_curDirectory != "/" ) ) { int slashPosition = m_curDirectory.ReverseFind('/'); dir = m_curDirectory.Left(slashPosition); m_curDirectory = dir; ConnFlag = TRUE;

    BOOL flag = MyConnect( MyGetURL(m_url), m_curDirectory ); MyPrintFiles(); UpdateData( FALSE ); return; }

    if( len == "< Dir >" ) { if( m_curDirectory == "/" ) {dir = m_curDirectory + name;} else { dir = m_curDirectory + '/' + name;}


    m_curDirectory = dir; ConnFlag = TRUE;

    BOOL flag = MyConnect( MyGetURL(m_url), m_curDirectory ); MyPrintFiles(); UpdateData( FALSE ); return; }

    }

    // функция копирует выделенные файлы в выбранную директорию void CFTP_ClientDlg::OnCopy() { // TODO: Add your control notification handler code here POSITION cur_pos; int flag_pos; int count=0; CString file,len; CString new_url, dir;

    // здесь можно дописать создание директории если она не создана //char *d = "c:\\temp\\files\\a\\b\\"; //CreateDirectoryW( &d[0], NULL );

    m_edit = ""; cur_pos = m_ListView.GetFirstSelectedItemPosition(); // первый выбранный файл

    if( (int)cur_pos <= 1 ) { MessageBox( "Not write file(s)", "Error" ); return; };

    len = m_ListView.GetItemText( (int)cur_pos-1, 1 ); if( len != "< Dir >" ) { Sel_files[ 0 ] = (int)cur_pos-1; count++;}

    itoa( (int)cur_pos-1, temp3, 10 ); m_edit += "start = "; m_edit += &temp3[0]; m_edit += "\r\n"; do { flag_pos = m_ListView.GetNextSelectedItem( cur_pos ); // следующий файл itoa( (int)cur_pos-1, temp3, 10 ); m_edit += "next = "; m_edit += &temp3[0]; m_edit += "\r\n";

    len = m_ListView.GetItemText( (int)cur_pos-1, 1 ); if( len != "< Dir >" ) { Sel_files[ count++ ] = (int)cur_pos-1; }

    }while( (int)cur_pos >= 1 );

    count--; itoa( count, temp3, 10 ); m_edit += " Selected files = "; m_edit += &temp3[0]; m_edit += "\r\n"; m_edit += "\r\n"; if( count == 0 ) { MessageBox( "No selected file(s)", "Error" ); return; };

    for( int i = 0; i <= (count-1); i++ ) { len = m_ListView.GetItemText( Sel_files[i], 1 ); file = m_ListView.GetItemText( Sel_files[i], 0 );

    m_edit += "Copy file = "; m_edit += file; m_edit += " len = "; m_edit += len; m_edit += "\r\n";

    int down = ftpConnection->GetFile(file, m_save+file, FALSE); // копируем файлы

    if( down == 0 ) { // если нет директории MessageBeep( 65535 ); m_edit += "Error save file.Not create directory !!!"; UpdateData( FALSE ); m_ListView.SetFocus(); return; }

    }

    UpdateData( FALSE );

    m_ListView.SetFocus(); }

    Ну вот и всё, приложение готово.

    К списку

    Создание простого HTTP-клиента Ernest Avagyan



    21. Создание простого HTTP-клиента
    В этой главе будет написана программа, которая может считывать файлы из Internet по HTTP протоколу и записывать их на диск.
    Для связи с Internet в Visual C++ существует так называемый WinInet Class. В него входят несколько подклассов.
    Далее представлены ксассы WinInet:


    Классы Описание
    CInternetSession Создаёт Internet сессию. Все MFC WinInet приложения должны создавать CInternetSession объект перед использрванием других WinInet классов.
    CInternetConnection Создаёт коннект с Internet. Это базовый класс для классов CFtpConnection, CGopherConnection, и CHttpConnection.
    CFtpConnection Устанавливает соединение по FTP протоколу.
    CGopherConnection Создаёт Gopher коннект.
    CHttpConnection Устанавливает соединение по HTTP протоколу.
    CInternetFile Разрешает удалённый доступ к файлам на Internet серверах. Это базовый класс для классов CGopherFile and CHttpFile.
    CGopherFile Разрешает удалённый доступ к файлам на Gopher серверах.
    CHttpFile Разрешает удалённый доступ к файлам на HTTP серверах.
    CFileFind Разрешает поиск файлов в Internet. Это базовый класс для классов CFtpFileFind and CGopherFileFind.
    CFtpFileFind Разрешает поиск файлов на FTP серверах.
    CGopherFileFind Разрешает поиск файлов на Gopher серверах.
    CGopherLocator Отыскивает Gopher устройство ввода позиций от gopher сервера.
    CInternetException Управляет исключениями, сгенерированными WinInet классом.

    Наша программа будет использовать четыре класса WinInet: CInternetSession, CInternetFile, CHttpFile и CHttpConnection
    Далее будут описаны методы( функции ) этих классов:

    Методы ( функции ) класса CInternetSession


    Функции Описание
    Close() Закрывает Internet сессию.
    EnableStatusCallback() Разрешает использование функции повторного вызова, которая используется для асинхронных действий.
    GetContext() Получает значение контекста Internet сессии.
    GetFtpConnection() Устанавливает подключение по FTP протоколу.
    GetGopherConnection() Устанавливает подключение с Gopher серверами.
    GetHttpConnection() Устанавливает подключение по HTTP протоклолу.
    OnStatusCallback() Модифицирует состояние операции.
    OpenURL() Соединяется с данным URL.
    QueryOption() Сервис проверки ошибки провайдера.
    ServiceTypeFromHandle() Получает тип сервиса от Internet дескриптора.
    SetOption() Устанавливает опции Internet сессии.
    <
    br>Методы ( функции ) класса CInternetFile

    Функции Описание
    Abort() Закрывает файл и игнорирует все ошибки.
    Close() Закрывает файл.
    Flush() Сбрасывает файл на диск.
    Read() Счатывает байт из файла.
    ReadString() Считывает строку символов из файла.
    Seek() Переустанавливает указатель внутри файла.
    SetReadBufferSize() Устанавливает размер буфера для чтения.
    SetWriteBufferSize() Устанавливает размер буфера для записи.
    Write() Записывает байт в файл.
    WriteString() Записывает строку с нулевым символом в конце в файл.

    Методы ( функции ) класса CHttpFile

    Функции Описание
    AddRequestHeaders() Добавляет заголовок к HTTP запросу.
    Close() Закрывает CHttpFile объект.
    GetFileURL() Получает URL файла.
    GetObject() Получает объект по HTTP запросу.
    GetVerb() Получает заголовок запроса.
    QueryInfo() Получает ответ или заголовок запроса.
    QueryInfoStatusCode() Получает код состояния HTTP запроса.
    SendRequest() Посылает HTTP запрос.

    Далее напишем код программы и разберём каждую строчку:

    ...

    CString m_url = "mark5.dhtp.kiae.ru"; // имя URL CString m_mes; // переменная в которой будут хранится сообщения char temp[100]; // промежуточная переменная для перевода // данных из Int в char CString m_path; // имя файла для записи char strBody[1024]; // буфер из 1024 байт ...

    int CHTTP_ClientDlg::OnButtonConnect() { // создаём переменную session и открываем сессию ANDY CInternetSession session( _T( "ANDY" ), PRE_CONFIG_INTERNET_ACCESS );

    // создаём переменную pServer класса CHttpConnection CHttpConnection* pServer = NULL;

    // создаём переменную pFile класса CHttpFile CHttpFile* pFile = NULL;

    /* Обратите внимание, что все запросы к функциям членам WinInet классов включены в блок программы TRY. Это сделано так, потому что при соединении с каким либо URL есть риск неправильной ссылки, особенно, когда Вы полагаете, что пользователь сам печатает URL. Другая проблема - времена ожидания, которые возникают, когда требуемый URL в настоящее время неспособен обслужить подключение.


    Так же обработка WinInet исключений, которые представлены в классе CInternetException, является важной частью создания Internet приложения под MFC. */

    try { CString strServerName; // имя сервера CString strObject; // имя объекта INTERNET_PORT nPort; // номер порта для связи DWORD dwServiceType; // тип сервиса

    // функция AfxParseURL получает данные с указанного URL ( у нас m_url ) об сервере, // объекте, типе сервиса и порте

    if ( AfxParseURL( m_url, dwServiceType, strServerName, strObject, nPort ) == 0 ) { return 1; // выход из функции OnButtonConnect() } // вывод данных о сервере

    m_mes = ""; m_mes += "Server Name = "; m_mes += (CString)strServerName; m_mes += "\r\n"; m_mes += "Object Name = "; m_mes += (CString)strObject; m_mes += "\r\n"; m_mes += "Port = "; itoa( nPort, temp, 10 ); m_mes += (CString)&temp[0]; m_mes += "\r\n"; UpdateData( FALSE );

    // Устанавливаем подключение по HTTP протоклолу. pServer = session.GetHttpConnection( strServerName, nPort );

    // посылаем запрос об объекте ( strObject ) pFile = pServer->OpenRequest( CHttpConnection::HTTP_VERB_GET, strObject, NULL, 1, NULL, NULL, INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_AUTO_REDIRECT );

    // Добавляем заголовок к HTTP запросу pFile->AddRequestHeaders( _T( "Accept: */*\r\nUser-Agent: ANDY\r\n" ) );

    // посылаем запрос pFile->SendRequest( );

    DWORD dwRet; // переменная для хранения кода состояния pFile->QueryInfoStatusCode( dwRet ); // записываем код состояния в dwRet

    // вывод данных m_mes += "The HTTP GET returned a status code of "; itoa( dwRet, temp, 10 ); m_mes += (CString)&temp[0]; m_mes += "\r\n";

    CString strHeader; // переменная для хранения полученного заголовока запроса pFile->QueryInfo(HTTP_QUERY_RAW_HEADERS_CRLF, strHeader); // записываем заголовок в strHeader

    // вывод данных m_mes += "Header = "; m_mes += strHeader; UpdateData( FALSE );

    // если код состояния не равен 200, то выходим из функции if( dwRet != 200 ) { m_mes += "Program terminate!"; UpdateData( FALSE ); return 1; } // ----------------------------------------------------------


    // проверка выбора файла для записи m_mes += "Starting download the file."; m_mes += "\r\n";

    if( m_path == "" ) { m_mes += "Error! No file to save. Choese the file."; m_mes += "\r\n"; UpdateData( FALSE ); return 1; } else { m_mes += "File name to save : "; m_mes += m_path; m_mes += "\r\n"; UpdateData( FALSE ); }

    CFile file2; // объявляем переменную file2 класса CFile

    // открываем файл для записи в двоичном формате ( CFile::typeBinary ) !!! file2.Open((LPCTSTR)m_path, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary);

    int allRead = 0; // переменная для хранения общего числи считанных байт int nRead = pFile->Read( strBody, 1024 ); // считываем первые 1024 байта в буфер. // переменная nRead хранит количество // считанных байт

    allRead += nRead; // обновляем общее число считанных байт

    // вывод данных m_mes += "Loading "; itoa( nRead, temp, 10 ); m_mes += (CString)&temp[0]; m_mes += " bytes"; m_mes += "\r\n"; UpdateData( FALSE );

    // записываем буфер из nRead байт в файл file2.Write( strBody, nRead );

    // цикл считывания, пока nRead не будет равняться нулю while ( nRead > 0 ) { nRead = pFile->Read( strBody, 1024 );

    if( nRead != 0 ) { m_mes += "Loading "; itoa( nRead, temp, 10 ); m_mes += (CString)&temp[0]; m_mes += " bytes"; m_mes += "\r\n";

    file2.Write( strBody, nRead ); allRead += nRead; UpdateData( FALSE ); }

    }

    // вывод данных m_mes += "\r\n"; m_mes += "Total bytes = "; itoa( allRead, temp, 10 ); m_mes += &temp[0]; m_mes += "\r\n"; UpdateData( FALSE );

    file2.Close(); // закрываем файл

    pFile->Close(); // закрываем Internet файл pServer->Close(); // закрываем сервер

    m_mes += "Download is complete !!!"; m_mes += "\r\n";UpdateData( FALSE ); }

    catch ( CInternetException* pEx ) { // Если произошла ошибка в WinInet

    // вывод ошибки char szErr[1024]; pEx->GetErrorMessage( szErr, 1024 );

    m_mes += "Error: ( "; itoa( int(pEx->m_dwError), temp ,10 ); m_mes += (CString)&temp[0]; m_mes += " ) "; m_mes += (CString)&szErr[0]; m_mes += "\r\n"; UpdateData( FALSE );

    pEx->Delete( ); // удаление переменной класса CInternetException if ( pFile != NULL ) delete pFile; // закрываем Internet файл if ( pServer != NULL ) delete pServer; // закрываем сервер session.Close( ); // закрываем сессию return 1; }

    if ( pFile != NULL ) delete pFile; // закрываем Internet файл if ( pServer != NULL ) delete pServer; // закрываем сервер session.Close( ); // закрываем сессию return 0; }

    Ну вот и всё, приложение готово.

    К списку

    Создание собственных ActiveX элементов Ernest Avagyan



    19. Создание собственных ActiveX элементов
    1. Введение
    2. Создание проекта
    3. Настройка значка инструмента MyClock
    4. Рисование в элементе управления MyClock
    5. Вывод текущего времени в непрерывном режиме
    6. Включение базовых свойств в ActiveX MyClock
    7. Включение специального свойства в ActiveX MyClock


    1. Введение
    Элементов управления ActveX — это файл с расширением ОСХ (например, MyButton.OCX), который вы можете использовать в своем приложении Visual C++. Visual C++ и другие визуальные языки программирования дают вам возможность включить элемент управления ActiveX в свою программу и пользоваться им так же, как и стандартным элементом управления Visual C++. Вы помещаете элемент управления ActiveX в диалоговую панель, задаете его свойства и связываете код с его событиями. После того как ы создали собственный элемент управления ActiveX, вы ожете передавать его другим программистам, которые могут вводить его в свои программы.

    Поскольку расширением файла элемента управления ActiveX является .ОСХ, то иногда элементы управления ActiveX называют элементами ОСХ.

    В этой главе вы разработаете свой собственный элемент управления ActiveX — MyClock.ОСХ, который выполняет задачу вывода текущего времени. Когда программист помещает элемент управления MyClock.ОСХ в форму или в диалоговую панель, MyClock. ОСХ будет непрерывно отображать текущее время.

    2. Создание проекта
    Чтобы создать проект элемента управления MyClock.OCX : 1) Выберите New в меню File.
    В ответ Visual C++ выведет диалоговую панель New.
    2) Выберите закладку Projects диалоговой панели New.
    3) Выберите MFC ActiveX ControlWizard из списка типов проектов
    4) Напечатайте MyClock в окне Project Name.
    5) Щелкните на кнопке, которая расположена с правой стороны окна Location, и выберите каталог для проекта.
    6) Щелкните на кнопке ОК.
    В ответ Visual C++ выведет окно MFC ActiveX ControlWizard Step 1 of 2
    В окне ActiveX ControlWizard Step 1 оставьте все установки в состоянии по умолчанию и щелкните на кнопке Next.
    В окне ActiveX ControlWizard Step 2 оставьте все установки в состоянии по умолчанию и щелкните на кнопке Finish.


    В ответ Visual C++ выведет диалоговую панель New Project Information.
    Щелкните на кнопке ОК в диалоговой панели New Project Information и выберите Set Active Configuration в меню Build.
    В ответ Visual C++ выведет диалоговую панель Set Active Project Configuration.
    Выберите MyClock - Win32 Release в диалоговом окне Set Active Project Configuration и щелкните на кнопке ОК.

    Это все! Вы завершили создание файла проекта и каркасов файлов элемента управления ActiveX MyClock.ОСХ.

    3. Настройка значка инструмента MyClock
    Значок инструмента MyClock отображает буквы ОСХ. Вам нужно настроить элемент управления MyClock таким образом, чтобы значок его инструмента представлял собой рисунок часов. Для настройки значка инструмента MyClock вы должны отредактировать растровое изображение IDB_MYCLOCK. Это изображение было создано Visual C++.
    Чтобы вывести растровое изображение IDB_MYCLOCK в режиме проектирования, сделайте следующее:

    1) Выберите закладку ResourceView в окне Project Workspace, раскройте пункт MyClock resources, раскройте пункт Bitmap и дважды щелкните на пункте IDB_MYCLOCK.
    В ответ Visual C++ выведет растровое изображение IDB_MYCLOCK в режиме проектирования.
    2) Используя визуальные инструменты Visual C++, замените растровое изображение IDB_MYCLOCK букв ОСХ на рисунок простейших часов (окружность и две линии в качестве стрелок).

    4. Рисование в элементе управления MyClock
    Пока элемент управления MyClock выводит эллипс. Вам нужно, чтобы MyClock отображал текущее время, так что вы должны написать соответствующий код:

    Откройте файл MyClockCtl.cpp.

    Файл MyClockCtl.cpp — это файл реализации элемента управления МуСlock, созданный для вас Visual C++; в этом файле вы будете писать свой код для настройки MyClock.

    Найдите функцию OnDraw() в файле MyClockCtl.cpp и напишите следующий код:



    void CMyClockCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) {

    // TODO: Replace the following code with your own drawing // code.

    // Залить элемент управления выбранным цветом.


    pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))) ;

    char CurrentTime[30] ; struct tm *newtime; long lTime;

    // Получить текущее время time(&lTime) ;

    newtime=localtime(&lTime); // Преобразовать время в строку. strcpy(CurrentTime, asctime(newtime));

    // Дополнить строку одним символом пробела. CurrentTime[24]=' ';

    // Дополнить строку ограничивающи символом. CurrentTime[25] = 0;

    // Вывести текущее время pdc->ExtTextOut(rcBounds.left,rcBounds.top, ETO_CLIPPED, rcBounds, CurrentTime, strlen(CurrentTime), NULL) ; }


    5. Вывод текущего времени в непрерывном режиме
    Чтобы отображать время непрерывно, вам нужно сделать следующее:

    1) Написать код, который устанавливает таймер с 1000-миллисекундным периодом для элемента управления MyClock.
    2) Связать код с событием WM_TIMER элемента управления MyClock.

    После установки таймера каждые 1000 миллисекунд (каждую секунду) Windows будет посылать сообщение WM_TIMER элементу управления MyClock, в ответ на которое будет выполняться код, который вы свяжете с этим событием элемента управления. Этот код будет просто выводить текущее время, так что значение времени будет непрерывно обновляться.

    Таймер необходимо установить сразу после создания элемента управления, так что вам нужно связать код, устанавливающий таймер, с событием WM_CREATE элемента управления:

    Выведите диалоговую панель ClassWizard, выбрав ClassWizard в меню View. На странице Message Maps выберите следующее событие:

    Class Name: CMyClockCtrl
    Object ID: CMyClockCtrl
    Message: WM_CREATE

    Щелкните на кнопке Add Function.
    В ответ Visual C++ добавит в класс CMyClockCtrl функцию-элемент ОпСreate().
    Щелкните на кнопке Edit Code в ClassWizard.
    В ответ Visual C++ откроет файл MyClockCtrl.cpp с функцией OnCreate() в режиме редактирования.

    Напишите следующий код в функции OnCreate():



    int CMyClockCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (COleControl::OnCreate(lpCreateStruct) == -1) return -1;

    // TODO: Add your specialized creation code here


    // Установить таймер. SetTimer(1, 1000, NULL);

    return 0; }

    Введенный вами код состоит из одного оператора, который вызывает функцию SetTimer() для установки таймера с 1000-миллисекундным периодом:

    SetTiltier (1, 1000, NULL);
    Начиная с этого момента, каждые 1000 миллисекунд Windows будет посылать элементу управления сообщение WM_TIMER.
    Теперь вам нужно связать код с событием WM_TIMER:

    Выберите ClassWizard в меню View. На странице Message Maps выберите следующее событие:

    Class Name: CMyClockCtrl
    Object ID: CMyClockCtrl
    Message: WM_TIMER

    Щелкните на кнопке Add Function.
    В ответ Visual C++ добавит в класс CMyClockCtrl функцию-элемент OnTimer().
    Щелкните на кнопке Edit Code в ClassWizard.
    В ответ Visual C++ откроет файл MyClockCtrl.cpp с функцией OnTimer() в режиме редактирования.
    Напишите следующий код в функции OnTimerO:



    void CMyClockCtrl::OnTimer(UINT nIDEvent) {

    // TODO: Add your message handler code here and/or call // default

    // Переключить вызов на функцию OnDraw(). InvalidateControl() ;

    COleControl::OnTimer(nIDEvent) ; }


    6. Включение базовых свойств в ActiveX MyClock
    Базовые свойства( Stock properties ) - преопределены.
    Ниже приведён список базовых свойств:

    Appearance - Внешний вид( 3-х мерный или плоский )
    BackColor - Цвет фона
    BorderStyle - Стиль рамки
    Caption - Заголовок
    Enabled - Состояние доступен/недоступен
    Font - Шрифт
    ForeColor - Цвет переднего плана
    hWnd - Маркер окна
    ReadyState - Состояние готовности
    Text - Текст

    Для практики включим два базовых свойства в ActiveX MyClock: BackColor и ForeColor.
    Выполните следующие действия:

    View -> ClassWizard -> Automation( проверте, чтобы в окне Class name установлен класс CMyClockCtrl )
    Нажмите на кнопку Add Property
    Выберите из списка BackColor и нажмите OK
    Также добавьте и свойство ForeColor.

    Элемент управления MyClock имеет сейчас свойства BackColor и ForeColor, но пока не использует значения, хранящиеся в этих свойствах. Вам надо написать код в функции OnDraw(), который выполняет эту задачу:



    void CMyClockCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) {


    // TODO: Replace the following code with your own drawing // code.

    // Задать цвет переднего плана( цвет текста ) pdc->SetTextColor( TranslateColor(GetForeColor()));

    // Установить режим прозрачного фона pdc->SetBkMode(TRANSPARENT);

    // Создать кисть на основе значения BackColor CBrush bkBrush( TranslateColor(GetBackColor()));

    // Закрасить фон pdc->FillRect(rcBounds, &bkBrush);

    char CurrentTime[30] ; struct tm *newtime; long lTime;

    // Получить текущее время time(&lTime) ;

    newtime=localtime(&lTime); // Преобразовать время в строку. strcpy(CurrentTime, asctime(newtime));

    // Дополнить строку одним символом пробела. CurrentTime[24]=' ';

    // Дополнить строку ограничивающи символом. CurrentTime[25] = 0;

    // Вывести текущее время pdc->ExtTextOut(rcBounds.left,rcBounds.top, ETO_CLIPPED, rcBounds, CurrentTime, strlen(CurrentTime), NULL) ; }
    Ну вот и всё, теперь элемент управления MyClock имеет свойства BackColor и ForeColor.

    7. Включение специального свойства в ActiveX MyClock
    Во многих случаях вам понадобится включить в свой элемент управления такие свойства, которые не входят в список стандартных. Эти свойства называются специальными.
    Для примера включем в MyClock специальное свойство UpdateInterval - период обновления:

    View -> ClassWizard -> Automation( проверте, чтобы в окне Class name установлен класс CMyClockCtrl )
    Нажмите на кнопку Add Property
    В окне External name наберите UpdateInterval
    В окне Type выберите Long
    В окне Variable name должно быть m_updateinterval
    В окне Notification function поставьте OnUpdateIntervalChanged
    Проверте, что в камке Implementation выбрана кнопка Member variable и нажмите OK
    Тем самым мы определили, что со свойством UpdateInterval будет связана переменная m_updateinterval и всякий раз, когда значение свойства UpdateInterval будет именяться, автоматически выполнится функция OnUpdateIntervalChanged.

    Теперь надо проинициализировать свойство UpdateInterval:

    Откройте файл MyClockCtl.cpp
    Найдите функцию DoPropExchange() и напишиет в ней следующее:

    // Инициализация свойства UpdateInterval значением 1000
    PX_Long( pPX, _T("UpdateInterval"), m_updateinterval, 1000 );


    Теперь надо модернизировать функции OnUpdateIntervalChanged:

    // проверка на отризательность
    if( m_updateinterval < 0 )
    {
    MessageBox( "This property cannot be negative !!!" );
    m_updateinterval = 1000;
    }
    // Установка таймера
    SetTimer( 1, (UINT)m_updateinterval, NULL );

    и OnCreate: // Установка таймера
    SetTimer( 1, (UINT)m_updateinterval, NULL );


    Ну вот и всё ActiveX MyClock полностью готов !!!
    К списку

    Создание собственных диалоговых окон Ernest Avagyan



    15. Создание собственных диалоговых оконДля того, что бы создать собственное диалоговое окно надо сделать следующее:

    1) Создайте программу в диалоговом режиме ( с поддежкой MFC )
    2) Назовите её TEST, что бы было лучше сравнивать с моей рабочей программой
    3) Главный класс вашей программы будет CTestDlg
    4) Что бы создать другую диалоговую панель нужно создать новый класс, для этого выбери закладку ResourceView -> правой кнопкой на Dialog -> Insert Dialog -> создастся новый диалог.
    5) Имя диалога можно менять, поставте IDD_MY_DIALOG
    6) Дальше надо зарегистрировать новый класс, для этого: при открытом новом диалоге( это обязательно ) надо вызвать ClassWizard( в верхнем меню) -> Create new class -> надо ввести имя класса ( введите CMyDialog( MyDialog.cpp), имя должно начинаться с C, что означает class ) -> два раза ОК
    7) Теперь надо, чтобы выша главная программа "узнала" новый класс -> в начале файла TestDlg.cpp напишите строчку


    #include "MyDialog.h"

    8) Ну вот и всё, теперь можно использовать новое диалоговое окно ->

    CMyDialog MyDlg;
    MyDlg.DoModal();


    К списку


    Стандартные диалоговые панели Ernest Avagyan



    13. Стандартные диалоговые панели
    1. Введение
    2. Панель выбора цвета (класс CColorDialog)
    3. Панель выбора файлов (класс CFileDialog)
    4. Панель выбора шрифта (класс CFontDialog
    5. Панель для вывода документов на печать (класс CPrintDialog)
    6. Панель для выполнения поиска и замены (класс CFindReplaceDialog)



    1. Введение
    В состав библиотеки MFC входит ряд классов, представляющих стандартные диалоговые панели. Эти классы позволяют легко реализовать такие часто используемые операции, как открытие и сохранение файла, выбор цвета, выбор шрифта и т.д. Все эти классы наследуются от CCommonDialog, который в свою очередь является производным по отношению к базовому классу CDialog.
    Приведем классы стандартных диалоговых панелей и их назначение:
  • CColorDialog - Панель для выбора цвета
  • CFileDialog - Панель выбора файлов для открытия и сохранения на диске
  • CFindReplaceDialog - Панель для выполнения операции поиска и замены
  • CFontDialog - Панель для выбора шрифта
  • CPrintDialog - Панель для вывода документа на печать
  • CPageSetupDialog - Панель выбора формата документа
  • COleDialog - Панель для управления технологией OLE

  • Классы, управляющие стандартными диалоговыми панелями, определены в файле afxdlgs.h. Поэтому при использовании этих классов в приложении необходимо включить этот файл в исходный текст при помощи директивы #include.
    2. Панель выбора цвета (класс CColorDialog)
    Чтобы отобразить на экране стандартную диалоговую панель выбора цвета, надо создать объект класса CColorDialog, а затем вызвать метод DoModal. При создании объекта класса СColorDialog используется следующий конструктор:
    CColorDialog(COLORREF clrInit=0,DWORD dwFlags=0,CWnd* pParentWnd=NULL);
    Все параметры конструктора необязательны, однако в некоторых случаях использование этих параметров может помочь.
    Первый параметр clrInit позволяет указать цвет, выбранный по умолчанию сразу после открытия диалоговой панели. Если параметр не будет указан, в качестве цвета, выбранного по умолчанию, будет использоваться черный цвет.

    Когда цвет выбран, нужно нажать кнопку Add Custom Colors. Выбранный цвет будет добавлен к дополнительным цветам (Custom colors) - один из свободных прямоугольников окрасится соответствующим цветом.

    При помощи метода GetSavedCustomColors класса CColorDialog можно определить дополнительные цвета, выбранные пользователем в диалоговой панели Color. Этот метод возвращает указатель на массив из 16 элементов типа COLORREF. Каждый элемент массива описывает один дополнительный цвет.

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

    3. Панель выбора файлов (класс CFileDialog)

    Среди стандартных диалоговых панелей, для которых в библиотеке MFC создан специальный класс, есть панели для работы с файловой системой - Open и Save As. Диалоговая панель Open позволяет выбрать один или несколько файлов и открыть их для дальнейшего использования. Диалоговая панель Save As позволяет выбрать имя файла для записи в него документа.

    Для управления диалоговыми панелями Open и Save As предназначен один класс CFileDialog. Рассмотрим конструктор класса CFileDialog более подробно:

    CFileDialog(BOOL bOpenFileDialog,
    LPCTSTR lpszDefExt=NULL,LPCTSTR lpszFileName=NULL,
    DWORD dwFlags=OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
    LPCTSTR lpszFilter=NULL,CWnd* pParentWnd=NULL);


    Объекты класса CFileDialog представляют диалоговые панели Open или Save As в зависимости от параметра bOpenFileDialog. Если параметр bOpenFileDialog содержит значение TRUE, то создается объект, управляющий диалоговой панелью Open, а если FALSE - диалоговой панелью Save As.

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

    Чтобы создать объект класса CFileDialog , представляющий диалоговую панель для открытия файлов (mFileOpen), и объект, представляющий диалоговую панель для сохранения файлов (mFileSaveAs), можно воспользоваться следующими вызовами конструктора класса:


    CFileDialog mFileOpen(TRUE); // для панели открытия файлов
    CFileDialog mFileSaveAs(FA LSE); // для панели сохранения файла


    Во многих случаях имена файлов, которые нужно открыть или закрыть, имеют определенное расширение. Параметр lpszDefExt позволяет задать расширение файлов, используемое по умолчанию. То есть, если пользователь при определении имени файла не укажет расширение, имени файла автоматически присваивается расширение, принятое по умолчанию. Если при определении свойств диалоговой панели программист присвоит параметру lpszDefExt значение NULL, то расширение файлов должно задаваться пользователем явно.

    В некоторых случаях требуется, чтобы диалоговые панели отображались с уже выбранным именем файла. Чтобы указать имя файла, используемое по умолчанию, применяется параметр lpszFileName. Если параметр lpszFileName имеет значение NULL, данная возможность не реализуется.

    С помощью флага dwFlags можно изменить внешний вид и некоторые другие характеристики стандартных диалоговых панелей класса CFileDialog. В него можно записать комбинацию флагов, управляющих различными характеристиками этих панелей. Например, флаг OFN_HIDEREADONLY означает, что из диалоговой панели удаляется переключатель "Read Only", а флаг OFN_OVERWRITEPROMPT (используемый для панели Save As) - что необходимо выводить диалоговую панель с предупреждением, если пользователь выбирает для сохранения имя уже существующего файла.

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

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


    Если одному типу соответствует несколько расширений, они разделяются символом ;. Строка, содержащая имя фильтра, отделяется от строки с расширениями файлов символом |. Если используется несколько фильтров, то они также отделяются друг от друга символом |. Например, в качестве строки, задающей фильтры, можно использовать строку вида:

    char szFilters[] =
    " Data (*.dat) |*.dat| Packed Data (*.pac;*.wav) | *.pac; *.wav| All Files (*.*)| *.* ||";


    Диалоговые панели, представленные объектами класса CFileDialog, могут иметь или не иметь родительского окна. Чтобы указать родительское окно, нужно передать конструктору CFileDialog указатель на него через параметр pParentWnd.



    Методы класса CFileDialog



    Создание объекта класса CFileDialog еще не вызывает отображения соответствующей диалоговой панели. Для этого необходимо воспользоваться методом DoModal класса CFileDialog.При вызове метода DoModal для ранее созданного объекта класса CFileDialog на экране открывается соответствующая диалоговая панель. После того, как пользователь завершает работу с диалоговой панелью, метод DoModal вернет значение IDOK или IDCANCEL в случае успешного завершения и нуль - в случае возникновения ошибок:

    CFileDialog dlgOpen(TRUE);
    int iResult=dlgOpen.DoModal();


    После того, как пользователь закроет диалоговую панель и метод DoModal вернет управление, можно воспользоваться другими методами класса CFileDialog , чтобы определить имена выбранных файлов:

  • GetPathName - Определяет полный путь файла
  • GetFileName - Определяет имя выбранного файла
  • GetFileExt - Определяет расширение имени выбранного файла
  • GetFileTitle - Позволяет определить заголовок выбранного файла
  • GetNextPathName - Если диалоговая панель позволяет выбрать сразу несколько файлов, то этот метод можно использовать для определения полного пути следующего из выбранных файлов
  • GetReadOnlyPref - Позволяет узнать состояние атрибута "только для чтения" (read-only) выбранного файла
  • GetStartPosition - Возвращает положение первого элемента из списка имен файлов



  • Наиболее важный метод - GetPathName. Он получает полный путь файла, выбранного из диалоговых панелей Open или Save As. Если диалоговая панель позволяет выбрать сразу несколько файлов, тогда метод GetPathName возвращает массив строк, состоящий из нескольких строк, заканчивающихся двоичным нулем. Первая из данных строк содержит путь к каталогу, в котором расположены выбранные файлы, остальные строки содержат имена выбранных файлов. Выделение строки, содержащей путь к каталогу, проблем не вызывает, а чтобы получить имена выбранных файлов, необходимо воспользоваться методами GetStartPosition и GetNextPathName.



    Метод GetStartPosition возвращает значение типа POSITION. Оно предназначено для передачи методу GetNextPathName и получения очередного имени выбранного файла. Если пользователь не выбрал ни одного файла, метод GetStartPosition возвращает значение NULL. Значение, полученное этим методом, следует записать во временную переменную типа POSITION и передать ссылку на нее методу GetNextPathName. Метод GetNextPathName вернет полный путь первого из выбранных в диалоговой панели файлов и изменит значение переменной pos, переданной методу по ссылке. Новое значение pos можно использовать для последующих вызовов метода GetNextPathName и получения путей всех остальных выбранных файлов. Когда метод GetNextPathName вернет имена всех выбранных файлов, в переменную pos записывается значение NULL.

    В панелях Open и Save As имеется переключатель "ReadOnly". По умолчанию этот преключатель не отображается. Если есть необходимость воспользоваться этим переключателем, то нужно отказаться от использования флага OFN_HIDEREADONLY.

    Метод GetReadOnlyPref позволяет определить положение переключателя "ReadOnly". Если переключатель включен, то метод GetReadOnlyPref возвращает ненулевое значение. В противном случае GetReadOnlyPref возвращает нуль.

    4. Панель выбора шрифта (класс CFontDialog)

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


    Для управления диалоговой панелью Font в библиотеку классов MFC включен класс CFontDialog. Методы этого класса можно использовать для отображения панели Font и определения характеристик шрифта, выбранного пользователем. Конструктор класса CFontDialog:

    CFontDialog(LPLOGFONT lplfInitial=NULL,
    DWORD dwFlags=CF_EFFECTS | CF_SCREENFONTS,
    CDC* pdcPrinter,CWnd* pParentWnd=NULL);


    Все параметры конструктора являются необязательными. Настройка стандартной панели выбора шрифта, которая выполняется конструктором класса CFontDialog по умолчанию, удовлетворяет большинству пользователей.

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

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

    Через параметр pdcPrinter можно передать конструктору контекст отображения принтера, шрифты которого будут представлены в диалоговой панели Font. Данный параметр используется только в том случае, если в параметре dwFlags указаны флаги CF_PRINTERFONTS или CF_BOTH.

    Через параметр pParentWnd можно указать родительское окно для диалоговой панели Font.



    Методы класса CFontDialog



    Для отображения диалоговой панели Font предназначен виртуальный метод DoModal. Если пользователь выбрал шрифт и нажал кнопку OK, метод DoModal возвращает идентификатор IDOK, если пользователь отменил выбор шрифта, метод DoModal возвращает идентификатор IDCANCEL:

    CFontDialog dlgFont;
    int iResult=dlgFont.DoModal();


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

    Метод GetCurrentFont позволяет сразу определить все характеристики выбранного шрифта, записав их в структуру LOGFONT.


    Остальные методы класса позволяют определить только отдельные характеристики выбранного шрифта:

  • GetFaceName - Возвращает имя выбранного шрифта
  • GetStyleName - Возвращает имя стиля выбранного шрифта
  • GetSize - Возвращает размер выбранного шрифта
  • GetColor - Возвращает цвет выбранного шрифта
  • GetWeight - Возвращает плотность выбранного шрифта
  • IsStrikeOut - Определяет, является ли шрифт выделенным перечеркнутой линией
  • IsUnderline - Определяет, является ли шрифт выделенным подчеркиванием
  • IsBold - Определяет, является ли шрифт жирным
  • IsItalic - Определяет, является ли шрифт наклонным


  • 5. Панель для вывода документов на печать (класс CPrintDialog)

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

    В приложениях, подготовленных с использованием средств MFC AppWizard и построенные по модели документ-облик, по умолчанию встроена возможность вывода редактируемого документа на печать.

    В меню File такого приложения находятся три строки (Print, Print Preview и Print Setup), которые управляют процессом печати документов, подготовленных в приложении. Чтобы распечатать документ, достаточно выбрать из меню File строку Print. На экране появится диалоговая панель Print. В ней можно выбрать печатающее устройство для печати документов (группа Name), указать, будет печататься весь документ либо его часть (группа Print range), а также сколько копий документа будет напечатано (группа Copies). Также можно настроить различные характеристики печатающего устройства, если нажать кнопку Properties в группе Printer.

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


    Группа Paper задает формат бумаги и режим подачи бумаги в печатающее устройство. Группа Orientation включает только один переключатель, определяющий ориентацию бумаги. Он принимает положение Portrait для вертикальной ориентации изображения на бумаге (режим "портрет") или Landscape для горизонтальной ориентации изоборажения на бумаге (режим "ландшафт").

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

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

    6. Панель для выполнения поиска и замены (класс CFindReplaceDialog)

    Класс CFindReplaceDialog предназначен для управления диалоговыми окнами Find и Replace. Диалоговая панель Find используется для поиска известных строк в документе приложения, а панель Replace позволяет замену одной строки на другую.

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

    панели


    К списку

    Структуры в С++ Ernest Avagyan



    6. Структуры в С++
    Как вы уже знаете, переменная в C/C++ объявляется следующим образом:


    int iMyVariable;


    В приведенном операторе iMyVariable объявлена как целая переменная. А вот объявление переменной типа char:


    char cMyChar;


    Такие типы данных, как int, float, char и long, являются неотъемлемой частью C/C++ и вам не нужно писать никакого кода, чтобы сообщить компилятору о том, что означают эти слова. C/C++ позволяет вам также объ-являть свои собственные, специальные типы данных. В следующем разделе вы узнаете, как объявлять структуры, которые можно отнести к специальным типам данных.

    Напишите следующий исходный код:


    #include
    #include
    // Объявление структуры.
    struct MYSTRUCTURE
    {
    char sName[100];
    int iAge;
    };
    void main( void )
    {
    MYSTRUCTURE MyStructure;
    strcpy(MyStructure.sName, "Andy" );
    MyStructure.iAge = 13;
    cout << "My name is ";
    cout << MyStructure.sName;
    cout << " and I am ";
    cout << MyStructure.iAge;
    cout << " years old." << endl;
    )

    В коде, который вы написали, имеются два оператора #include:


    #include
    #include


    Файл iostream.h включен в код, поскольку в main(void) используется cout. Файл string.h включается потому, что в main(void) используется функция strcpy() (объявленная в файле string.h). Затем вы объявляете структуру:


    Struct MYSTRUCTURE
    {
    char sName[100];
    int iAge;
    };

    Обратите внимание на синтаксис объявления структуры. Оно начинается с ключевого слова struct, за которым следует имя типа-структуры. В этой программе типу структуры присвоено имя MYSTRUCTURE. Затем следует собственно определение структуры, заключенное в фигурные скобки. Не забудьте поставить точку с запятой после закрывающей фигурной скобки. Теперь посмотрите на код внутри фигурных скобок:


    char sName[100];
    int iAge;

    Это означает, что MYSTRUCTURE состоит из строки с именем sName и целого с именем iAge, sName и iAge называются элементами данных структуры; Вы объявили их "Внутри" cтруктуры MYSTRUCTURE.
    Код в main(void) объявляет переменную с именем MyStructure типа MYSTRUCTURE:



    MYSTRUCTORE MyStructure;

    Вспомните, что в объявляли переменную iNum1 следующим образом:



    int iNum1;

    Когда вы объявляете MyStructure , которая будет структурой типа MYSTRUCTURE, рассматривайте переменную MyStructure аналогично переменной iNum1. MyStructure - это имя переменной, а ее типом является MYSTRUCTURE точно так же, как типом переменной iNum1 является int. (Обратите внимание, что по традиции имя структуры составлено из символов нижнего регистра или в нем смешаны символы нижнего и верхнего регистров, как, например, в имени MyStructure, но в имени типа структуры используются только символы верхнего регистра, как, например, в MYSTRUCTURE.)
    Следующий оператор в main(void) копирует строку 'Andy' в элемент данных MyStructure.sName:



    strcpy ( MyStructure.sName, "Andy" );

    В этом операторе обращение к элементу данных sName записано как MyStructure.sName Следующий оператор присваивает значение 13 элементу данных iAge cтруктуры MyStructure: MyStructure.iAge - 13; Затем выполняется ряд операторов вывода cout:



    cout << "My name is ";
    cout << MyStructure.sName;
    cout << " and I am ";
    cout << MyStructure.iAge;
    cout << " years old." << endl;

    Сложив все вместе, мы видим, что программа MyStruct выводит сообщение My name is Andy and I am 13 years old. (Меня зовут Andy и мне 13 лет)

    К списку

    

        Программирование: Языки - Технологии - Разработка