ВИЗУАЛЬНАЯ СРЕДА ПРОГРАММИРОВАНИЯ КОНСПЕКТ



УДК 681.3.06

Министерство образования РФ

Московский государственный Университет приборостроения и информатики

КАФЕДРА ПЕРСОНАЛЬНАЯ ЭЛЕКТРОНИКА

КОНСПЕКТ ЛЕКЦИЙ ПО КУРСУ "ВИЗУАЛЬНАЯ СРЕДА ПРОГРАММИРОВАНИЯ"

Для магистров специальности 11.04.03

Москва 2014

Аннотация

Настоящий конспект лекций предназначен для магистров специальности 11.04.03, изучающих курс "Визуальная среда программирования" на 2 году обучения в магистратуре. Курс лекций охватывает в основном вопросы создания современных приложений для повсеместно распространенной в настоящее время программной среды Windows, а также вопросы алгоритмизации основныхтиповых вычислительныхконструкций. Он содержит основные сведения по программированию на высоком уровне в рамках интегрированной среды Visual С++, так как большинство приложений создается именно на этом языке. В конспекте приводятся простейшие примеры использования типовых элементов и приемов программирования, много справочной информации по версии Visual С++ 6.0.

Конспект лекций предназначен для помощи при подготовке расчетно-графической работы по данному курсу.

Конспект подготовил к.т.н. доц. Малиновский А.К.

Конспект лекций рассмотрен и одобрен на заседании кафедры ПР-7.

Протокол № _____ от "___"_________2014г.

1. Рабочие области и проекты Visual C++

Наша первая программа будет называтьсяfirst, и мы создадим ее как новый проект C++. Программные задачи в Visual C++ оформляются в виде проектов, причем для каждой отдельной программы создается свой проект.Проект (ргoject)  представляет собой набор файлов, которые совместно используются для создания одной программы.

Кроме того, сами проекты размещаются врабочих областях (workspaces) причем одна рабочая область может содержать несколько проектов (активный проект задается в С++ командойProject-->Set Active Project).  Visual С++ автоматически создает их для каждого нового проекта, поэтому в первую очередь нас  интересуют проекты, а не рабочие области. Все же необходимо помнить о том, что вы работаете в рабочей области Visual С++, а проект принадлежит ей.

Давайте создадим и запустим первый проект:

1. Выполните командуFile|New в Visual C++; на экране появляется окноNew.

2. Перейдите на вкладкуProjectsи выберите из списка строкуWin32 Console Application.

3. Введите в текстовом поле Project nameимя проектаfirst, а в полеLocationукажите каталог для проекта, лучше оставить каталог по умолчанию.

4. Нажмите кнопку ОКв диалоговом окнеNew. На экране появляется новое диалоговое окно с именемWin32 Application.

5. Нажмите кнопкуFinish- Visual C++ создаст новую программу с именемfirst. В нее входят два важных файла:first.dswопределяет параметры новой рабочей области, afirst.dsp — параметры нового проекта.

ПОДСКАЗКА:  Чтобы продолжить работу над программой, над которой вы трудились раньше, воспользуйтесь командой Visual C++File |Open Workspaces, и откройте файл рабочей области программы (с расширением.dsw).

Итак, мы создали новую рабочую область, а в ней - проект с именем first. Теперь нужно ввести исходный текст программы; нам нужно, чтобы программа выводила строку "Welcome to C++".

Создание файла с исходным текстом

Для текста программы нам понадобится новый файлfirst.cpp. Расширение .срр присваивается файлам с текстами программ на C++.

ПРИМЕЧАНИЕ: Помимо файлов с исходными текстами в программах на C++  часто встречаютсязаголовочные файлы с расширением.h. Как мы увидим далее, заголовочные файлы содержат объявления переменных и функций.

Давайте создадим файл first.cpp и включим его в проект:

1. Снова выполните командуFile|New, только на этот раз перейдите на вкладкуFiles(рис. 1.3).

2. Выберите из списка строкуC++ Source File и введите имя файлаfirst.cppв текстовом полеFile name.

3. Проследите, чтобы флажокAdd to projectбыл установлен. Нажмите  кнопку ОК.

4. Файл first.cpp создается и открывается в среде Visual C++.

Среда Visual C++ состоит из трех основных окон: слева расположено окно просмотра с корешками вкладок в нижней части (Class ViewиFileView). Справа от него  находится окно редактора, в котором происходит редактирование документов (в настоящий момент в нем открыт пустой файл first.cpp). Внизу расположено окно результатов со вкладкамиBuild,DebugиFind in Files.

В окне просмотра выводится общая структура проекта, но ее конкретное представление зависит от выбранной вкладки —ClassViewилиFileView(о других вкладках мы поговорим позже). На вкладкеClassViewпоказана иерархияклассов C++ в рабочей области; вскоре мы узнаем, что такое классы. На вкладкеFileViewпоказана иерархия файлов в рабочей области (на рис. 1.4 она состоит из рабочей области, проекта и файла first.cpp).

ПРИМЕЧАНИЕ: Далее, когда мы начнем работать с полноценными программами на Visual C++, в окне просмотра появится еще одна вкладка —Resources. Такие объекты как меню, диалоговые окна и панели инструментов/ в программировании для Windows являютсяресурсами.

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

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

Добавление кода на C++

Теперь мы добавим программный код в файл first.cpp, который в настоящий момент открыт в окне редактирования Visual C++. Чтобы создать свою первую программу на C++, введите следующий текст:

#include <iostream.h>

void main()

{

  cout << "Welcome to C++ \n";

}

Так выглядит наша первая программа. В ней мы воспользовались стандартными  средствами C++ для вывода текстовой строки, в нашем случае — "Welcome to C++". Сначала мывключаем (#include) в программу файл iostream.h. Это позволит нам работать со стандартными средствами экранного вывода C++ (содержимое включенного файла просто вставляется в исходный текст программы).

Затем мы определяем функцию с именем main(). Эта функция вызывается при запуске программы. Другими словами, когда вы запускаете файл (например, first.exe), Windows ищет в нем функцию main() и выполняет содержащийся в ней код; поэтому мы и включили в нее тот код, который необходимо выполнить. Вскоре мы рассмотрим функцию main() более подробно.

ПРИМЕЧАНИЕ: В программах далее функция main()  не используется: ее место занимает функция WinMain().

Выяснив, с чего начинается выполнение программы, давайте рассмотрим то, что  она делает — а именно отображение строки на экране при помощи средств ввода/вывода языка C++.

Что такое потоки C++?

Мы записываем данные впоток C++ с именемcout, для чего используется оператор cout <<"Welcome to C++ \n";:

Текст "Welcome to C++ \n" отправляется в выходной поток C++ и попадает прямо на экран. Потоки C++ можно рассматривать как каналы для пересылки данных в различные места; потокcout посылает текст на экран.

Символ\n в конце строки представляет собой служебный символ, который переводит курсор на следующую строку после вывода текста. Приведенная выше строка программы выводит сообщение на экране, посылая его в поток cout.

Мы не будем надолго останавливаться на специфике работы с cout, потому что в стандартных Windows-программах вывод текста происходит без участия потоков; мы воспользовались cout лишь для того, чтобы временно облегчить себе задачу. Сейчас он нужен только затем, чтобы мы могли увидеть результаты работы наших классов и объектов при запуске программы.

ПОДСКАЗКА Кроме потокаcoutв консольных программах также можно —пользоваться потокомcinдля получения символов с клавиатуры.

На этом наше знакомство с кодом программы first.cpp завершается. Предварительная подготовка закончена, пора посмотреть, как работает наша программа.

Запуск первой программы

Для того, чтобы запустить нашу первую программу, сначала необходимо ееоткомпилироватьто есть преобразовать в выполняемый файл first.exe. Выполните командуBuild -->Build First.exe, и исходный файл first.cpp будет скомпилирован в first.exe.

Во время компиляции в окне результатов Visual C++ (в нижней части окнаDeveloperStudio) отображается следующий текст:

--Configuration: first - Win32 Debug—

Compiling... first.cpp

first.ехе - 0 error(s), 0 warning(s)

Теперь запустите программу командойBuild> Execute first.exe.

В окне виден текст сообщения Welcome to C++ и подсказкаPress any key to continue,вставленная Visual C++. Если нажать любую клавишу, окно исчезнет с экрана.

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

Классы и объекты C++

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

Что такое объект?

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

В концепции объектов нет ничего сложного. Программа просто делится на фрагменты, каждый из которых предназначен для решения отдельной задачи; эти фрагменты и называются объектами. Например, весь код для экранного вывода можно выделить в объект с именемscreen(экран). Объекты обладают большими возможностями, чем простые функции и переменные, поскольку в них могут содержаться как функции, так и переменные, что облегчает работу с ними. Наш объект screen может содержать не только все данные, отображаемые на экране, но и функции для работы с этими данными, например drawString() (для вывода строк) или drawLine() (для рисования линий). Это означает, что вся работа с экраном изолируется от остальной части программы, что упрощает работу программиста.

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

Что такое класс?

Как создать объект? На помощь приходятклассы. Класс для объекта — пример вроде, что форма для печенья; другими словами, класс можно рассматривать  шаблон или "форму" для изготовления объектов. В программировании подобная связь существует между типом данных (например, целочисленным) и переменной. Например, в следующем примере создается целая переменная с именем the_data:

int the_data;

Именно так создаются целочисленные переменные в C++. Здесь int — тип переменной, a the_data — имя самой переменной. Аналогичная связь существует между классом и объектом. Приближенно класс можно рассматривать как тип.

ПОДСКАЗКА Как мы вскоре убедимся, C++ поддерживает все стандартные  типы данных, принятые в программировании — int/ double/  long/ float и т. д.

Например, если заранее подготовить классscreenclass, можно создать объект этого класса с именемscreen:

screenclass screen;

Важно помнить о том, что все рабочие данные хранятся в объекте. Класс не содержит никаких данных; он лишь описывает общую структуру и поведение объекта.

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

На этом краткий обзор классов и объектов завершается. Подведу итог: класс представляет собой программную конструкцию для объединения (илиинкапсуляции)функций и данных. Объект можно рассматривать как переменную, тип которой совпадает с данным классом -- подобно тому, как объект screen принадлежал  классу screenclass.

В комплект Visual C++ входит целая библиотека заранее написанных классов —  Microsoft Foundation Classes (MFC), которая заметно облегчает работу программиста. На ее основе можно создавать объекты для работы с кнопками, текстовыми полями, полосами прокрутки и т. д.Фирма Microsoft уже поработала за нас, и позднее мы воспользуемся этим.

А теперь давайте рассмотрим пример программы, в которой используются классы и объекты.

Наша первая программа на C++ с классами и объектами

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

Создайте в Visual C++ новый проект с именемclassesтак, как это было сделано в предыдущем примере. Затем включите в него новый файл с именем classes.cpp. После этого можно добавить код; как и раньше, начнем со включения файла iostream.h:

#include <iostream.h>

После этого объявим новый класс DataClass:

#include <iostream.h>

class DataClass  {

}

Новые классы объявляются с ключевым словомclass. Оставшаяся часть объявления класса заключается в фигурные скобки { и }.

Почти во всех объектах C++ хранятся определенные данные; такая возможность будет предусмотрена и в классе DataClass. Для хранения данных используютсяпеременные класса. Например, в класс можно включить переменную типа int (то есть создать целую переменную класса) с именем PrivateDataMember:

#include <iostream.h>

class DataClass {

private:

 int PrivateDataMember;

}

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

ПОДСКАЗКА: Если опустить модификатор доступа в объявлении класса, по |умолчанию используется модификаторprivate.

Чтo такое модификатор доступа?

Помимо модификатора доступаprivate, можно воспользоваться модификаторомpublic— он сообщает о том, что переменная класса являетсяоткрытой и ею можно безо всяких ограничений пользоваться в любом месте программы. Позднее мы познакомимся с третьим модификатором доступа,protected, который ограничивает доступ к переменной и допускает к ней только объекты нашего класса, а также объекты классов,производных от него (вскоре вы поймете, что означает это.

Давайте воспользуемся ключевым словомpublicи объявим переменную с именемPubllcDataMember:

#include <iostream.h>

class DataClass {

int PrivateDataMember;

int PublicDataMember;

}

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

Теперь давайте включим в класс DataClass какую-нибудь функцию. Функции, принадлежащие классу, называютсяметодами. В нашем примере будет добавлен метод с именемPublicMethod():

#include <iostream.h>

class DataClass

{

private:

int PrivateDataMember;

public:

int PublicDataMember;

int PublicMethod(void);

};

ПРИМЕЧАНИЕ: Методы обычно работают с внутренними данными класса, причем подробности этой работы скрываются от внешних частей программы. Это чрезвычайно полезно, поскольку большую программу удается разбить на блоки приемлемого размера. Собственно, объекты были предназначены именно для этой цели.

Данные передаются методам в виде переменных, которые называютсяпараметрами. Это может выглядеть, например, так: add(variable1, variable2). Метод использует передаваемые данные так, как это делается и в других языках программирования.

Однако наш метод не получает параметров. На это указывает ключевое словоvoid: int PublicMethod(void) (присутствиеvoidв круглых скобках необязательно - можно написать простоint PublicMethod ();). Кроме того, приведенное выше объявление сообщает, что метод возвращает значение целого типа: для этого в начале объявления ставится ключевое словоint. Итак, наш метод не получает параметров и возвращает целое значение.

Метод PublicMethod() объявлен. Остается понять, как же написать его код? Мы отделим определение метода от объявления класса и снабдим определение префиксом DataClass::. Он сообщает Visual C++, к какому классу относится определяемый метод.

#include <iostream.h>

class DataClass

{

private:

int PrivateDataMember;

public:

int PublicDataMember;

int PublicMethod(void);

}:

int DataClass::PublicMethod(void)

{

}

Теперь все готово к написанию кода метода. Наш метод не получает параметров, но возвращает целое значение. Какое значение он должен возвращать? Поскольку переменнаяPrivateDataMemberнедоступна за пределами класса, пусть методPublicMethodвозвращает ее значение, чтобы его можно было узнать в любом месте программы.

ПОДСКАЗКА: Обращение к закрытым переменным через методы наподобие PublicMethod() входит в число стандартных приемов программирования; тем самым удается ограничить доступ к закрытым данным класса.

Код метода PubllcMethod() будет выглядеть так (поскольку он принадлежит классу DataClass, то может беспрепятственно обращаться к закрытой переменнойPrivateDataMember):

#include <iostream.h>

class DataClass

{

private:

int PrivateDataMember;

public:

int PublicDataMember;

int PublicMethod(void);

}

int DataClass::PublicMethod(void)

{

    return PrivateDataMember;

 }

МетодPublicMethod() должен возвращать значение переменной классаPrivateDataMember— но какое значение в ней будет храниться? Мы еще не инициализировали эту переменную и не присвоили ей никакого значения, так что возвращаемая методом PublicMethod() величина не будет иметь никакого смысла. Необходимо позаботиться об инициализации PrivateDataMember.

Инициализация данных класса в конструкторе

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

Конструктором класса называется специальный метод, который выполняется в программе при создании объекта данного класса. Как мы сейчас увидим, в конструкторе удобно инициализировать данные класса. Имя конструктора совпадает с именем класса, и он не возвращает никакого значения. Тем не менее, конструктору можно передать любое количество параметров. В нашем примере конструктор будет получать всего один параметр — значение, которое должно быть присвоено PrivateDataMember при создании объекта.

Объявление конструктора выглядит так:

#include <iostream.h>

class DataClass

{

private:

int PrivateDataMember:

public:

DataClass(int value);

int PublicDataMember;

int PublicMethod(void);

};

Затем необходимо определить конструктор:

DataClass::DataClass(int Value)

{

}

В нашем примере достаточно сохранить переданную величину в PrivateDataMember:

DataClass::DataClass(int Value)

{

PrivateDataMember = Value;

}

Готово. У нас есть класс с открытыми и закрытыми переменными и методом, который можно вызывать в программе. Осталось лишь воспользоваться им, и в этом  Вам поможет функция main().

Использование класса DataClass

Включите в файл classes.cpp функцию main():

#include <iostream.h>

class DataClass

{

private:

int PrivateDataMember:

public:

DataClass(int value);

int PublicDataMember;

int PublicMethod(void);

};

DataClass::DataClass(int Value)

{

PrivateDataMember = Value;

}

int DataClass::PublicMethod(void)

{

return PrivateDataMember;

}

void main()

{

}

Функцияmain() содержит код, выполняемый при запуске программы. Мы начнем ее с объявления объекта классаDataClass. Одновременно с этим мы передадим значение конструктору класса (оно будет присвоено переменной PrivateDataMember). Пусть это будет значение 1; тогдаобъектDataObject, принадлежащий классуDataClass, будет создаваться следующим образом:

void main()

{

DataClass DataObject(1);

}

Теперь у нас есть объект с именемDataObject. Он содержит данные, которые мы в него занесли, а также поддерживает методPublicMethod().

Чтобы присвоить значение открытой переменной класса, PublicDataMember, можно воспользоваться оператором "точка" (.) языка C++. В следующем фрагменте этой переменной присваивается значение 2:

void main()

{

DataClass DataObject(1);

DataObject.PublicDataMeniber = 2;

}

Обычно все обращения к членам объекта (переменным и функциям) происходят именно так. Например, мы можем вывести значение переменной PublicDataMeinber, посылая его в поток cout:

void main()

{

DataClass DataObject(1);

DataObject.PublicDataMeniber = 2;

cout <<"DataObject.PublicDataMember = "

<<DataObject. PublicDataMeniber<<\n";

}

Но как получить значение, присвоенноеPrivateDataMember? Поскольку код в функции main() не принадлежит объекту DataObject, из него нельзя напрямую обратиться  к закрытой переменной этого объекта. Чтобы добраться до нее, надо вызвать методPublicMethod()и вывести полученное значение:

void main()

{

DataClass DataObject(1);

DataObject.PublicDataMeniber = 2;

cout <<"DataObject.PublicDataMember = "

<<DataObject.PublicDataMember<<\n";

cout <<DataObject.PrivateDataMember = "

<<DataObject.PrivatePublicMethod()<<\n";

}

Программа работы с классами готова — запустите ее. Результаты ее работы будут в окнеclasses. Как видно из рисунка, нам удалось вывести значения и открытой, и закрытой переменных объекта DataClass. Первое знакомство с классами и объектами C++ состоялось.

Наш первый пример был крайне примитивным — он всего лишь показал, как создать класс и объект в программе.

Он совершенно не раскрывает основной смысл объектов — хранение данных и внутреннюю работу с ними.

Помимо конструкторов в C++ существуютдеструкторы — они вызываются при уничтожении объекта и содержат код "сборки мусора". В нашем примере деструктор будет уничтожать выделенную ранее область памяти.

Деструкторы C++

Деструктор отчасти похож на конструктор, только он автоматически вызывается при уничтожении объекта. Междуними существует даже внешнее сходство, но имя деструктора снабжается префиксом ~ ("тильда"):

#include <iostream.h>

class SchoolClass {

int *ClassData;

int ClassDataIndex;

public:

SchoolClass(int NumberStudents);

~SchoolClass(void);

};

SchoolClass::SchoolClass(int NumberStudents)

{

ClassData= new int[NumberStudents];

ClassDataIndex = 0;

SchoolClass::~SchoolClass(void)

{

}

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

SchoolClass::~SchoolClass(void)

{

  delete ClassData;

}

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

Что такое наследование и переопределение?

Наследование— еще одна важная составляющая объектно-ориентированного программирования. Благодаря емупроизводныеклассы наследуют функциональностьбазовыхи добавляют к ней свои собственные возможности. Например, мы можем создать базовый классvehicle(транспортное средство) и породить от него два производных класса:truck(грузовик) иhelicopter(вертолет). Оба имеют один и тот же базовый класс (и содержат все методы базового класса), что экономит время при программировании.

Хотя классыhelicopter иtruck порождаются от одного и того же базового классаvehicle, каждый из них по-своему модифицирует его поведение, так что в итоге  мы получаем два вполне самостоятельных класса.

ПОДСКАЗКА: Помните об отличиях между модификаторамидоступа private, protected и public. К открытым (public) членам класса можно обращаться из любого места программы, к закрытым (private) -  только из объектов того же класса, а к защищенным (protected) — из объектов того же самого или производных от него классов.

Оба производных класса могут пользоваться методамиvehicle, например,start()(завести) или move()(двигаться). Однако вертолет движется иначе, чем грузовик. Как дополнить или даже изменить поведение класса vehicle, чтобы приспособить его для новых классов? Одна из возможностей заключается впереопределении,  методов базового класса (переопределение методов считается важной составляющей объектно-ориентированного программирования). Когда мы переопределяем  метод базового класса в производном классе, новая версия метода полностью заменяет старую. Таким образом, мы можем изменить поведение базового класса в производном классе и заставить его вести себя так, как нам нужно.

Давайте посмотрим, как наследование и переопределение применяются на практике. Создайте новый проект с именемInheritance. Объявим класс транспортного средстваvehicleс двумя методами,start()иmove():

#include <lostream.h>

class vehicle {

public: void start();

void move();

};

Метод start() класса vehicle в нашем примере будет просто выводить строку "Starting...", а метод move() — строку "Driving...":

#include <lostream.h>

class vehicle {

public: void start();

void move();

};

void vehicle: :start() { cout << "Starting.. .\n";}

void vehicle::move() { cout<<"Driving...\n";}

Теперь создадим новый классhelicopterи сделаем его производным от классаvehicle.

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

Класс helicopter порождается от базового класса vehicle так:

#include <lostream.h>

class vehicle {

public: void start();

void move();

};

class helicopter: public vehicle {

public: void move();

};

void vehicle: :start() { cout << "Starting.. .\n";}

void vehicle::move() { cout<<"Driving...\n";}

Чтобы сделать класс helicopter производным от класса vehicle, следует после объявления класса helicopter поставить двоеточие и указать имя базового класса

Поскольку класс helicopter является производным от vehicle, он изначально содержит методы класса vehicle: start() и move().

Изменение метода — переопределение

Однако метод move() класса vehicle выводит строку "Driving..." (то есть "Еду..."),  что для вертолета это не подходит. Следовательно, мы должны переопределить метод move() в классе helicopter так, чтобы он выводил строку "Flying..." (то есть "лечу..."):

#include <lostream.h>

class vehicle {

public: void start();

void move();

};

class helicopter : public vehicle {

public:

void move();

};

void vehicle::start() { cout << "Starting.. .\n";}

void vehicle::move() { cout<<"Driving...\n";}

void helicopter: move() { cout<< "Flying...\n";}

Методы start() и niove() класса helicopter можно свободно вызывать в функции

#include <lostream.h>

class vehicle {

public: void start();

void move();

};

class helicopter : public vehicle {

public:

void move();

};

void vehicle: :start() { cout << "Starting.. .\n";}

void vehicle::move() { cout<<"Driving...\n";}

void helicopter::move() { cout<< "Flying...\n";}

void main()

{

helicopter whirly;

whirly.start();

whirly.move();

}

Переопределенный метод move() успешно работает и выводит строку "Flying..." вместо "Driving...". В этой программе мы познакомились с техникой наследования и переопределения методов в C++.

Нам осталось рассмотреть лишь перегрузку функций. Этим мы сейчас займемся.

Перегрузка функций в С++

Один и тот же метод можно вызывать с различными типами и даже различным  количеством параметров. Например, может существовать метод display(), отображающий на экране отдельный символ, используя для этого тип char C++, и он - же целую строку символов, которая в стандартном C++ интерпретируется как массив символов. Иначе говоря, метод display() может вызываться как для отдельного символа, так и для массива символов. Рассмотрим как это делается на примере новой программыoverloading. Создайте проект, а в нем — файл overloading.срр"

Здесь в функции main() будет создаваться новый объектDisplayObject:

void main()

{

    DisplayClass DisplayObject;

}

В этом объекте будет находиться метод display(). Сначала мы вызовемего для отображения единственного символа 'h':

void main()

{

DisplayClass DisplayObject;

DisplayObject.dlsplay('h');

}

Тот же метод можно вызвать и для строки (то есть массива символов), которая в нашем примере состоит всего лишь из однойбуквы " i " (в C++ двойные кавычки говорят о том, что речь идет о строке, а не об отдельном символе):

void main()

{

DisplayClase DisplayObject;

DisplayObject.display('h');

DisplayObject.display("i");

cout << "\n";

}

Один и тот же метод вызывается для аргументов двух типов — символа и строки. Но как это сделать? В C++ это совсем не сложно — нужно лишь определить метод дважды, по одному разу для каждого типа предполагаемых параметров. Повторное определение метода display() может выглядеть так:

#include <iostream.h>

class DisplayClass

{

public:

    void display(char character);

    void display(char* string);

};

void DisplayClass::display(char character)

{

    cout<< character;

}

void DisplayClass::display(char* string)

{

   cout <<string;

}

Но как C++ определяет, какую версию display() использовать при вызове метода? По типу передаваемого параметра. Например, если параметр относится к символьному типу, вызывается версия display() для символьного параметра. Если же передать  ему строку, будет использована вторая версия.

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

2. Наша первая настоящая программа для Windows на Visual C++

Ранее мы приступили к работе с C++ и научились создавать консольные приложения Win32. Сейчас мы напишем настоящую программу, умеющую работать с окнами. Мы создадим свою первую полноценную Windows-программу при помощи инструментов Visual C++ и запустим ее.

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

При создании программ на Visual C++ обычно применяютсямастера(wizards), например Visual C++ AppWizard, которым мы воспользуемся далее. После того как мастер сгенерирует код, мы отредактируем программу и настроим ее в соответствии со своими требованиями. Для этого нужно понимать, как работают различные компоненты программы на Visual C. Давайте немедленно приступим к делу.

Наша первая настоящая (то есть работающая в окне) программа на Visual C++ будет очень простой: так легче изучить новую тему. Все что она будет делать — это создавать окно и выводить в нем текст "Добро пожаловать в Visual C++".

На самом деле возможности нашей первой программы вовсе не ограничиваются! простым выводом сообщения. Visual C++ встроит в нее меню и панель инструментов, хотя они и не будут ничего делать (вскоре мы научимся работать с меню и панелями инструментов). Но давайте займемся построением программы. Это станет нашим первым шагом в полноценном программировании для Windows на Visual C++.

1. Запустите Visual C++ и выполните команду File|New; открывается окно для выбора New.

2. Выберите из списка МFC AppWizard(exe).

3. Введите в текстовом полеProject Nameимя проектаwelcome.

4. Нажмите кнопку ОК, чтобы запустить мастер Visual C++AppWizard.

5. AppWizard напишет за нас основную часть программного кода.

6. На экране появляется окно первого (Step 1) из шести этапов работы с AppWizard.

7. Оставьте без изменений все стандартные параметры AppWizard, кроме одного — по умолчанию AppWizard создает программы с несколькими окнами, а нам нужнапрограмма с одним окном. Это облегчит знакомство с темой и сделает ее более понятной.

8. Установите переключательSingle Document и нажмите кнопку Next. Мы переходим ко второму этапу (Step 2) работы с AppWizard.

9. На этапе 2 AppWizard спрашивает, следует ли включить в программу какую-либо поддержку баз данных; оставьте установленным переключательNone.

10. Нажимайте кнопкуNextдо тех пор, пока не доберетесь до шестого этапа (Step 6) AppWizard..

11. На шестом этапе AppWizard сообщает, какие классы он собирается создать в новой программе:CWelcomeApp, CMainFrame, CWelcomeDocиCWelcomeView. Далее мы рассмотрим все эти классы.

12. Нажмите кнопкуFinish— откроется окноNew Project Information.

13. Нажмите кнопку ОK, чтобы завершить создание проекта. В указанном вами каталоге будут созданы следующие файлы:

Welcome.clw

Файл Class Wizard

Welcome.dsw

Основной файл рабочей области

Welcome.h

Заголовочный файл приложения

welcome.cpp

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

StdAfx.h

Заголовочный файл для стандартного «каркаса» приложения

StdAfx.cpp

Исходный текст стандартного «каркаса» приложения

MainFrm.h

Заголовочный файл главного окна

MainFrm.cpp

Исходный текст главного окна

welcomeDoc.h

Заголовочный файл документа

welcomeDoc.cpp

Исходный текст документа

welcomeView.h

Заголовочный файл вида

welcomeView.cpp

Исходный текст вида

Resource.h

Файл с ресурсными константами

welcome.rc

Файл с ресурсами

welcome.ncb

Файл с информацией о представлении и взаимных связях

welcome.dsp

Файл проекта

res

Каталог для ресурсов

14. Наша программа готова!

AppWizard также создает файл с именемReadMe.txt,в котором более подробно разъясняется назначение некоторых файлов, созданных AppWizard:

MICROSOFT FOUNDATION CLASS LIBRARY: welcome

AppWizardсоздалприложение welcomeзавас.Оно не только демонстрирует основные принципы использования классов Microsoft Foundation, но и является отправной точкой для написания вашего собственного приложения.

В этом файле содержится краткое описание всех файлов, составляющих приложение welcome.

welcome.dsp

Файл проекта содержит общие сведения о проекте и используется при построении отдельного проекта или подпроекта. Другие пользователи могут пользоваться файлом проекта (.dsp), однако они должны выполнить локальное экспортирование make-файлов.

welcome.h

Главный заголовочный файл приложения. Он включает другие заголовочные файлы проекта (в том числе Resource.h) и объявляет класс приложения СWelcomeApp.

AppWizard создает один тип документа и один вид:

welcomeDoc.h, welcomeDoc.cpp - документ

Файлы содержат класс CWelcomeDoc. Отредактируйте их, чтобы внести в документ специфические данные и реализовать сохранение/загрузку файлов (через CWelcomeDoc::Serialize).

welcomeView.h, welcomeView.cpp - вид документа

Файлы содержат класс CWelcomeView. Объекты CWelcomeView используются для просмотра объектов CWelcomeDoc.

Другие стандартные файлы:

StdAfx.h, StdAfx.cpp

Файлы используются для построения предварительно компилированных файлов - заголовочного (РСН) файла welcome.pen, а также файла типов StdAfx.obj.

Resource.h

Стандартный заголовочный файл, в котором определяются идентификаторы новых ресурсов. Microsoft Visual C++ читает этот файл и обновляет его содержимое.

Примечания:

AppWizard помечает комментарием "TODO:" те части исходного текста, которые вам следует дополнить или изменить.

Итак, наша программа готова. Давайте познакомимся с ней поближе.

Части программы на Visual C++

Программа, созданная Visual C++ AppWizard, состоит из четырех основных частей:объекта приложения, объекта главного окна, объекта документа и объекта вида.

Объект приложения

Объект приложения, находящийся в файлах welcome.h и welcome.cpp (файл .h coдержит определения констант, а также объявления переменных и методов класса), то что Windows запускает при старте программы. Когда этот объект начинает работу, он размещает на экране главное окно.

Объект главного окна

Объект главного окна отображает на экране саму программу; в нем находится меню, заголовок окна и панель инструментов. Объект главного окна отвечает за вce, что происходит вокруг того места, где собственно работает наша программа (где она рисует, выводит текст и т. д.). Рабочая зона программы называетсяклиентской областьюокна; например, текст выводится в клиентской области текстового редактора. За работу с клиентской областью отвечает объект вида.

Объект вида

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

Объект документа

Вобъекте документа хранятся данные программы. Возможно, у вас возникнет вoпрос — а почему бы не хранить эти данные в объекте вида? Дело в том, чтотаких данных бывает много, причем не все они отображаются в клиентской области. Visual C++ облегчает нашу задачу и позволяет сохранить все данные в объекте документа, а затем поручить объекту вида отобразить лишь те данные, которые попадают в клиентскую область объекта вида. Как мы вскоре увидим, для одного документа можно иметь несколько одновременно открытых видов.

Взаимосвязь четырех частей программы на Visual C++ выглядит так:

А теперь давайте заставим нашу программу хоть что-тоделать.

Вывод приветствия

Пока что мы рассматривали программу, написанную мастером AppWizard. Давайте изменим ее так, чтобы она выводила сообщение «Добро пожаловать в Visual C++!» Для этого мы добавим небольшой фрагмент кода в методOnDraw() классаCWelcomeView (вспомните, что этот класс служит для отображения данных). Программа вызывает методOnDraw(), когда ей требуется вывести что-либо в клиентской области про граммы (например, при запуске программы, при свертывании и восстановлении окна или при перемещении другого окна, закрывающего часть клиентской области). В данный момент метод OnDraw() выглядит так (см. файл welcomeView.cpp):

void CWelcomeView::OnDraw(CDC* pDC)

{

CWelcomeDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: добавьте код для отображения данных ,

}

Мы добавим в этот метод код для вывода сообщения,

1. Чтобы приступить к редактированию метода, перейдите в окне просмотра Visual C++ на вкладкуClassView.

2. Найдите в иерархическом списке название класса CWelcomeView.

3. Щелкните на знаке <плюс> рядом с элементом. На экране выводится список методов данного класса.

4.Найдите в списке методOnDraw() и дважды щелкните на нем — метод откроется в текстовом редакторе.

5.Чтобы изменить поведение нашей программы, добавьте в OnDraw() всего две строки:

void CWelcomeView::OnDraw(CDC* pDC)

{

CString welcome_string = "Добропожаловатьв Visual C++";

CWelcomeDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, welcome_string);

}

6.Программаготова.Запуститеее:дляэтоговыполнитекомандуBuild |Build welcome.exe,азатемкомандуBuild | Execute welcome.exe.

7. Как видите, программа успешно выводит в окне сообщение. Настало время разобрать ее по кусочкам и понять, как она работает.

Исследуем объект приложения

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

Что такое «сообщения Windows»? Это особые сигналы с минимальным объемом служебных данных, посредством которых различные объекты в среде Windows общаются между собой. Например, когда пользователь завершает работу программы, Windows посылает объекту приложения сообщениеWM_QUIT. Если же пользователь изменяет размеры окна программы, она получает сообщениеWM_SIZE. Наиболее интересные сообщения Windows перечислены в табл. 2.1.

Таблица 2.1. Некоторые сообщения Windows

Сообщение Windows

Значение

WМ_ACTIVATE

Окно становится активным или неактивным

WM_ACTIVATEAPP

Активизируемое окно принадлежит другому приложению

WM_CANCELMODE

Отмена системного режима

WМ_CHILDACTIVATE

Активизируется дочернее окно

WM_CLOSE

Окно было закрыто

WM_.CREATE

Была вызвана функция создания окна

WM_DESTROY

Была вызвана функция уничтожения окна

WM_ENABLE

Окно было заблокировано или разблокировано

WM_ENDSESSION

Сеанс работы завершается

WM_ENTERIDLE

Начало пассивного цикла, которым можно воспользоваться для нужд программы

wm_erasebkgnd

Необходимо стереть фон окна

WM_GETMINMAXINFO

Получение информации о размерах окна

WM_GETTEXT

Получение текста, связанного с окном

WM_GETTEXTLENGTH

Получение длины текста, связанного с окном

WM_ICONERASEBKGND

Необходимо стереть фон окна

WM_KILLFOCUS

Окно теряет фокус ввода

WM_MENUCHAR

Пользователь нажал клавишу, не используемую в текущем меню

WM_MENUSELECT

Выбрана команда меню

WM_MOVE

Окно переместилось

WM_PAINT

Перерисовать часть окна

WM_PAINTICON

Перерисовать часть значка приложения

WM_PARENTNOTIFY

Окно создается или уничтожается

WM_QUERYENDSESSION

Получена команда на завершение сеанса

WM_QUIT

Завершение работы приложения

WM_SETFOCUS

Окно получило фокус ввода

WM_SETFONT

Изменился шрифт

WM_SETREDRAW

Снимает флаг перерисовки

WM_SETTEXT

Задает текст в заголовке окна

WM_SHOWWINDOW

Окно необходимо скрыть или вывести на экран

WM_SIZE

Изменился размер окна

Объект приложения отсылает большинство полученных сообщений объекту главного окна (не считая WM_QUIT, которое завершает работу объекта приложения).

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

Код объекта приложения содержится в файле welcome.cpp. Наибольший интерес для нас представляет методInitlnstance(), поскольку именно в нем программа  собирает остальные классы (главного окна, документа и вида) и объединяет их вшаблон документа, в соответствии с которым объект приложения организует работу программы:

BOOL CWelcomeApp::lnitlnstance()

{

AfxEnableControlContainer();

// Стандартная инициализация

// Если вы не пользуетесь какими-либо функциями и желаете

// сократить размер выполняемого файла, удалите

// ненужные процедуры инициализации.

#ifdef _AFXDLL

Enable3dControls();             // Вызывается прииспользовании

// MFC в виде совместной DLL

else

Enable3dControlsStatic();    // Вызывается при статической

// компоновке MFC .

#endif

// Изменить ключ реестра, под которым будут сохраняться .

     // параметры. Замените строку более подходящей, например,

// названием вашей компании или организации.SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();  //Загрузитьстандартныепараметры

//из INI-файла (включая MRU)

// Зарегистрировать шаблоны документов приложения.

// Шаблоны документов объединяют документы, обрамленные окна и виды.

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CWelcomeDoc),

RUNTIME_CLASS(CMainFrame).       //Главноеобрамленноеокно SDI

RUNTIME_CLASS(CWelcomeView));

AddDocTemplate(pDocTemplate);

// Просмотреть командную строку в поисках стандартных команд оболочки,

// DDE и открытия файлов

CCommandLineInfo cmdinfo;

ParseCommandLine(cmdInfo);

// Организовать обработку команд, содержащихся в командной строке

if (!ProcessShellCommand(cmdInfo)) return FALSE;

// Было инициализировано всего одно окно; отобразить и обновить его.

m_pMainWnd->ShowWindow(SW_SHOW):

m_pMainWnd->UpdateWindow();

return TRUE;

}

Обратите также внимание на строкуm_pMainWnd->ShowWindow(SW_SHOW); в конце методаInitInstance(). Именно в ней наш объект приложения отображает главное окно программы, вызывая методShowWindow() объекта главного окна. Переменнаяm_pMainWnd содержит указатель на объект главного окна, а оператор-> действует точно так же, как и оператор . («точка»), только он работает с указателем на объект. Другими словами, этот оператор обращается к переменным и методам того объекта, на который ссылается данный указатель.

Имена переменных классов в Visual C++ имеют стандартный префиксm_. На самом деле в Visual C++ аналогичные префиксы используются для всех имен переменных (так называемая «венгерская запись»). Префикс обозначает тип перемен ной; например, префикссиспользуется для символьных типов, поэтому с первого взгляда становится ясно, что cInput — символьная переменная. Наиболее распространенные префиксы венгерской записи перечислены в табл. 2.2.

Таблица 2.2.Префиксы венгерской записи

Префикс

Значение

а

Массив

b

Логический тип (int)

by

Беззнаковый символьный тип (byte)

с

Символьный тип

cb

Счетчик байтов

cr

Цвет

сх, су

Короткий тип (short)

dw

Беззнаковый длинный тип (dword)

fn

Функция

h

Логический номер (handle)

i

Целое

m

Переменная класса

n

Short или int

np

Ближний указатель

P

Указатель

1

Длинный тип (long)

ip

Длинный указатель

s

Строка

sz

Строка, заканчивающаяся нуль-символом

tm

Текстовая метрика

w

Беззнаковое целое (word)

X, Y

Короткий тип (координата х или у)

Исследуем объект главного окна

Объект главного окна отвечает за всю работу окна программы, за исключением клиентской области.

Это означает, что объект главного окна отвечает за заголовок, строку меню, панель инструментов и строку состояния в нижней части окна. Заголовок и строка меню создаются автоматически при создании окна, а панель инструментов и строка со стояния добавляются в методе OnCreate().

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

Исследуем объект вида

В объекте вида выводится наше сообщение «Добро пожаловать в Visual C++!». Это происходит в методеOnDraw() файлаwelcomeView.cpp:

void CWelcomeView::OnDraw(CDC* pDC)

{

CString welcome_string = "Добропожаловатьв Visual C++";

CWelcomeDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, welcome_string);

}

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

Программирование, управляемое событиями

Программы для Windowsуправляются событиями (event-driven). Это означает, что они реагируют на действия пользователя, называемыесобытиями — щелчки Кнопкой мыши, нажатия клавиш и т. д. При наступлении любого из этих событий в программе вызывается соответствующий метод. Тем самым удается разбить ее на  небольшие методы, соответствующие различным сообщениям Windows. Например, метод OnDraw() вызывается при необходимости перерисовать клиентскую область — когда окно впервые отображается на экране, когда пользователь перемещает окно, закрывавшее часть нашего окна, или же сворачивает и разворачивает текущее окно.

Вывод сообщения в объекте вида

Что же происходит в методеOnDraw() при выводе текста? Прежде всего, мы создадим новый объектwelcome_string, принадлежащий классу MFCCString, и заносим в него текст:

void CWelcomeView::OnDraw(CDC* pDC)

{

CString welcome_string = "Добропожаловатьв Visual C++";

CWelcomeDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, welcome_string);

}

КлассCString содержит все операции со строками. Он чрезвычайно полезен и является заметным достижением по сравнению с принятой в языке С интерпретацией строк как символьных массивов. Класс CString содержит следующие методы:

AllocSysString

AnsiToOem

Collate

Compare

CompareNoCase

CString

Empty

Find

FindOneOf

Format

FormatMessage

FreeExtra

GetAt

GetBuffer

GetBufferSetLength

GetLength

IsEmpty

Left

LoadString

LockBuffer

MakeLower

MakeReverse

MakeUpper

Mid

OemToAnsi

оператор [ ]

оператор +

оператор +=

оператор «

оператор ==

оператор == < и т. д.

оператор »

оператор LPCTSTR

ReleaseBuffer

ReverseFind

Right

SetAt

SetSysString

SpanExcluding

Spanlncluding

TrimLeft

TrimRight

UnlockBuffer

Методы, специфические для Windows

Затем в методе OnDraw() мы выводим текстовую строку через переданный методу указатель pDC, который ссылается на объект класса MFC с именем CDC. Конкретно, мы вызываем метод TextOut() этого объекта

Класс CDC чрезвычайно важен для нас, поскольку он предназначен для работы сконтекстами устройств.Весь графический и текстовый вывод, выполняемый нами  в Visual C++, будет осуществляться через контексты устройств.

Что такое контекст устройства?

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

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

Чтобы рисовать в нашем объекте вида, мы получаем контекст устройства, относящийся к нему. Кроме того, можно получить контекст устройства для всего окна, всего экрана или принтера. Для рисования в контексте устройства используются перечисленные ниже методы класса CDC; обратите внимание на их количество.

FrameRect

GetArcDirection

GetBkMode

GetCharABCWidths

GetColorAdjustnient

GetCurrentFont

GetCurrentPosition

GetGlyphOutline

GetMapMode

GetOutlineTextMetriс

GetOutputTextExtent

GetPixel

GetSafeHdc

GetTextAlign

GetTextExtent

GetViewportExt

GetWindowExt

HIMETRICtoDP

InvertRect

LPtoDP

MoveTo

OffsetWindowOrg

Pie

PolyBezier

Polygon

PolyPolygon

QueryAbort

RectVisible

ResetDC

SaveDC

ScrollDC

SelectObject

SetAbortProc

SetBkColor

SetBrushOrg SetKapperFlags

JsetPixel

SetPlxelV

SetROP2

SetStretchBltMode SetTextCharacterExtra

SetTextColoг

SetVlewportExt

SetViewportOrg

SetWindowOrg

StartDoc

SretchBIt

StrokeAndFillPath TabbedTextOut

TextOut

WidenPath

AbortPath

Arc

BeginPath

Chord

CreateDC

DeleteTempMap

DPtoLP

DrawEdge

DrawFrameControl

DrawText

EndPage

Escape

ExtFloodFill

FillRect

FlattenPath

FrameRgn

GetAspectRatioFilter

GetBoundsRect

GetCharWidth

GetCurrentBitmap

GetCurrentPalette

GetDeviceCaps

GetHalftoneBrush

GetMiterLimt

GetOutputCharWidth

GetOutputTextMetrics

GetPolyFillMode

GetStretchBltMode

GetTextCharacterExtra

GetTextFace

GetViewportOrg

GetWindowOrg

HIMETRICtoLP

InvertRgn

LPtoHIMETRIC

OffsetClipRgn

PaintRgn

PlayMetafile

PolyBezierTo

Polyline

PolyPolyllne

RealizePalette

ReleaseAttrlbDC

RestoreDC

ScaleViewportExt

SelectClipPath

SelectPalette

SetArcOirection

SetBkMode

SetColorAdjustment

SetMiterlimit

FromHandle

GetBkColor

GetBrushOrg

GetClipBox

GetCurrentBrush

GetCurrentPen

GetFontData

GetKerningPairs

GetNearestColor

GetOutputTabbedTextExtent

GetPath

GetROP2

GetTabbedTextExtent

GetTextColor

GetTextMetrics

GetWindow

GrayString

IntersectClipRect

LineTo

MaskBIt

OffsetViewpotOrg

PatBIt

PIgBIt

PolyOraw

PolylineTo

PtVisible

Rectangle

ReleaseOutputDC

RoundRect

ScaleWindowExt

SelectClipRgn

SelectStockObject

SetAttribDC

SetBoundsRect

SetMapMode SetOutputDC

SetPolyFillMode

SetTextAlign

SetTextJustification

SetWindowExt

StartPage

StrokePath

UpdateColors

В нашем случае для вывода в объекте вида (соответствующего клиентской области окна) был использован методTextOutклассаCDC. МетодуOnDraw() передается Указатель pDC, ссылающийся на контекст устройства для нашего вида. Чтобы вывести текстwelcome_stringв клиентской области, достаточно выполнить следующую строку:

pDC->TextOut(0, 0, welcome_string);

Методу TextOut() передаются координаты левого верхнего угла той области окна, где должна располагаться наша строка. В нашем случае текст выводится с точки (0,0)— левого верхнего угла клиентской области. Затем метод получает саму строку, welcome_string.

ПОДСКАЗКА: В нашей клиентской области точка (0, 0) расположена в левом  верхнем углу. Значение координаты х возрастает слева направо, а координаты у — сверху вниз. Такую координатную систему можно сравнить с чтением текста на странице: во время чтения вы перемещаетесь взглядом сверху вниз и слева направо.

Итак, выполнение этой строки программы приводит к тому, что наша строка выводится на экран. Так данные программы отображаются в объекте вида.

Исходный текст объекта вида находится в файлах welcomeView.h/welcomeView.cpp.

ПОДСКАЗКА: В различные моменты работы программа может передавать методу OnDraw() разные контексты устройств. Например, когда мы добавили код в метод OnDraw(), в нашей программе появилась возможность вывода на печать. Если пользователь выполнит командуFile> Print или нажмет кнопку с принтером на панели инструментов, программа передаст методу OnDraw() контекст устройства для принтера и наш текст будет напечатан. Если вы захотите предпринять какие-то специальные действия для подготовки печати, например, особым образом отформатировать свой документ), можете внести соответствующий код в метод OnPreparePrinting().

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

Исследуем объект документа

В объекте документа хранятся данные программы. В примере welcome нам потребовался только объект welcome_string. Для простоты мы разместили его в объекте вида, но на самом деле ему следовало бы находиться в объекте документа.

Как разместитьwelcome_string в объекте документа? Прежде всего, нам следовало бы объявить welcome_string в заголовочном файле документаwelcomeDoc.h:

// welcomeDoc.h: интерфейс класса CWelcomeDoc

//

#if!defined(AFXJJELCOMEDOC_hL_AF072C89_900AJ1DO_8860_444553540000_INCLUDED_)

#define AFX_WELCOMEDOC_H,_AF072C89_900A_11DO_8860_444553540000_INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

class CWelcomeDoc: public CDocument

{

protected: //созданиетолькоприсериализации

CWelcomeDoc();

DECLARE_DYNCREATE(CWelcomeDoc)

CString welcome_Strlng;

}

Затем мы инициализируем объект welcome_string в конструкторе класса документа, расположенном в файле welcomeDoc.cpp:

CWelcomeDoc::CWelcomeDoc()

{

welcome_string ="Добро пожаловать в Visual C++!";

}

Данные хранятся в объекте документа и готовы к работе—но как обратиться к ним из объекта вида?

Обращение к документу из вида

Если взглянуть на код, сгенерированный AppWizard для нашего класса вида, можно заметить, что в нем уже есть фрагмент для получения указателя на объект документа с помощью метода GetDocument() класса вида. AppWizard присвоил этому указателю имя pDoc:

void CWelcomeView::OnDraw(CDC* pDC)

{

    CWelcomeDoc* pDoc = GetDocument();

    ASSERT_VALID(pDoc);

}

ПРИМЕЧАНИЕ: Возможно, вы обратили внимание на строку ASSERT_VALID(pDoc)  в приведенном выше фрагменте. Здесь Visual C++ вызывает  макрос ASSERT_VALID и проверяет, что все прошло нормально и полученным указателем на документ можно пользоваться (макросом называется набор заранее написанных команд C++).

Обращение кwelcome_string в объекте документа может выглядеть так:pDoc-> welcome_string; поэтому в итоге содержимое welcome_string будет отображаться следующим образом:

void CWelcomeView::OnDraw(CDC* pDC)

{

CWelcomeDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, pDoc->welcome_string);

}

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

Сохранение данных на диске

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

void CWelcomeDoc: :Serialize(CArchive& ar) {

if (ar.IsStoring())

{

// TODO: добавьте код сохранения данных

}

else

{

// TODO: добавьте код загрузки данных

} }

В нашей программе уже есть несколько встроенных команд меню для работы с файлами: Save, Save As и Open. Программа берет на себя почти всю работу по сохранению файла на диске, но мы должны изменить ее текст так, чтобы она сохраняла и  наш объект welcome_string. Методу Serialize() передается объект с именемar; с ним можно работать точно так же, как и с потокомcoutв предыдущем разделе. В частности, для сохранения объекта welcome_string при записи данных на диск и для его последующего чтения при загрузке данных с диска следует добавить следующий фрагмент:

void CWelcomeDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

аг<< welcome_string;

}

else{

ar >>welcome_string;

}

}

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

3. Работа с клавиатурой

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

Дело даже не в клавиатуре и не во вводе символов. Важнее то, что мы научимся обрабатывать сообщения Windows - в данном случае от клавиатуры. Поскольку все наши программы управляются событиями (то есть реагируют на щелчки мышью, нажатия клавиш, выбор команд меню и т. д.), это станет лишь первым шагом на длинном пути. Мы научимся пользоватьсяClassWizardдля того, чтобы сопоставить полученному сообщению Windows метод, называемыйобработчиком сообщения.В нашем примере для сообщения Windows WM_CHAR будет использоваться обработчик с именемOnChar(). Это означает, что при нажатии клавиши пользователем программа будет вызывать метод OnChar().

Начнем с ввода символов.

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

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

Наша программа будет называтьсяkeystrokes. Создайте ее с помощьюMFC AppWizard, выберите вAppWizardинтерфейсSDI(переключательSingle Document  Interface (SDI)) и присвойте программе имяkeystrokes.

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

Подготовка буфера для хранения данных

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

Каждый новый символ, вводимый пользователем, добавляется к строке, хранящейся в объекте класса MFCCString. Эта строка отображается в объекте вида (с помощью методаOnDraw()). Мы назовем ееStringDataи объявим среди других переменных документа в заголовочном файлеkeystrokesDoc.h:

   // keystrokesDoc.h: интерфейс класса CKeystrokesDoc

  class CKeystrokesDoc: public CDocument

{

protected: // создание только при сериализации

CKeystrokesDoc();

DECLARE_DYNCREATE(CKeystrokesDoc)

CString StringData;

}

Затем объект необходимо проинициализировать пустой строкой " ". Это делается в конструкторе объекта документа, расположенном в файле keystrokesDoc.cpp:

CKeystrokesDoc::CKeystrokesDoc()

{

//TODO: добавьте код конструктора

StringData = " ";

}

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

Чтение нажатых клавиш

Мы подготовили место для хранения символов, вводимых пользователем, но как узнать, какая клавиша была нажата? При каждом нажатии клавиши Windows посылает сообщениеWM_CHAR, которое мы свяжем с методомOnChar() объекта вида. Для этого мы воспользуемся услугами Visual C++ ClassWizard.

ПРИМЕЧАНИЕ: Во время сеанса работы с Windows может быть открыто несколько окон, однако лишь одно из них будет реагировать на сообщения от клавиатуры. Если окно в данный момент активно и получает сообщения от клавиатуры, говорят, что оноимеет фокус.

Чтобы запустить мастер ClassWizard, выполните команду View \ClassWizard. На экране появляется окно ClassWizard. Проверьте, чтобы была выбрана вкладкаMessage Maps— на ней мастер связывает сообщения Windows с программой и использует для этого так называемую схему сообщений(message map).

Мы добавим новый обработчик сообщений,OnChar(), в класс видаCKeystrokesView,поэтому убедитесь, что в спискеClass name выбран именно он. Нас интересует сообщение WindowsWM_CHAR, которое посылается программе при нажатии клавиши пользователем. После того как методOnChar() будет связан с сообщением WM_CHAR, он будет вызываться каждый раз, когда пользователь введет символ. Найдитеэто сообщение в спискеMessagesи дважды щелкните на нем. В списке Member functionsнемедленно появляется методOnChar().ClassWizard пo умолчанию присваивает имя OnChar() обработчику сообщенияWM_CHAR.Обработчик сообщенияWM_MOUSEMOVE по умолчанию получает имя ОnMOUSEMOVE

ПОДСКАЗКА:Кроме сообщения WM_CHAR Windows также посылает нашей программе сообщениеWM_KEYDOWN при каждом нажатии клавиши и сообщениеWM_KEYUP— приее отпускании.ClassWizard обрабатывает эти сообщения в методах OnKeyDown()иOnKeyUp().

Найдите метод OnChar() в файле keystrokesView.cpp:

void CKeystrokesView::OnChar(UINT nChar. UINT nRepCnt,UINT nFlags)

{

// TODO:добавьтекодобработкисообщения

// и/или вызовите обработчик по умолчанию

CView::OnChar(nChar, nRepCnt, nFlags);

}

ПОДСКАЗКА: Создав новый обработчик сообщения, можно быстро перейти к его коду — для этого дважды щелкните на его имени в списке Member functions.

Созданный метод будет вызываться при вводе символа с клавиатуры. Обратите внимание: ClassWizard автоматически включил в него код для вызова метода OnChar() базового класса (дляCKeystrokeView базовым является класс MFC с именемCView). Эта ситуация характерна для обработчиков  сгенерированных ClassWizard, — в них присутствует вызов обработчика сообщения из базового класса на случай,  если мы захотим возложить обработку сообщения на базовый класс вместо того, чтобы писать собственный код.

Фактическое значение символа передается в параметреnChar. Если пользователь не отпускает клавишу что приводит к автоматической генерации нажатий, то количество нажатий данной клавиши будет передано в параметреnRepCnt. Отдельные биты параметраnFlagsимеют следующие значения:

Бит состояния перехода равен 1, если клавиша была отпущена, и 0, если она была нажата. Бит предыдущего состояния равен 1, если клавиша была ранее нажата, и 0, если она была отпущена. Код контекста равен 1, если нажата клавиша Alt.

ПОДСКАЗКА: Если пользователь не отпускает клавишу и клавиатура посылает повторяющиеся нажатия, параметр предыдущего состояния вOnChar() будет равен 1.

Теперь нужно позаботиться о сохранении введенного символа.

Сохранение символа в документе

Введенный символ находится в параметреnChar, и его необходимо сохранить в строковом объектеStringData. Этот объект принадлежит документу, поэтому сначала мы должны получить указатель на объект документа pDoc. Это делается так:

void CKeystrokesView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: добавьте код обработки сообщения

// и/или вызовите обработчик по умолчанию

CKeystrokesDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

}

Обратите внимание на макрос ASSERT_VALID. Как и прежде, он помогает Visual C++ проверить, что полученный указатель действительно ссылается на pDoc, в противном случае будет сгенерирована ошибка.

Далее мы добавляем к строке StrlngData символ nChar. Это проще простого: мы просто складываем символ со строкой (обратите внимание на использование оператора -> для обращения к объекту StringData документа, на который ссылается pDoc):

void CKeystrokesView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TOOO: добавьте код обработки сообщения

// и/или вызовите обработчик по умолчанию

    CKeystrokesDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData += nChar;

}

Мы воспользовались сокращенным оператором C++ +=. На самом деле наша строка эквивалентна следующей:

pDoc->StringData = pDoc->StringData + nChar;

ПОДСКАЗКА: Помимо оператора +=, в C++ имеются и другие сокращенные  операторы: *=,  /= и т. д.

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

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

Вывод данных в нашем приложении будет выполняться там же, где он выполняется и в других приложениях, сгенерированных AppWizard, — в методе OnDraw().

ПОДСКАЗКА: На самом деле текст можно было вывести прямо из метода OnChar(), но все же желательно сделать это в OnDraw(). Если  наше окно будет перерисовано или появится из-под другого окна, то для перерисовки и восстановления изображения программа вызовет OnDraw(), а не OnChar().

Чтобы заставить программу вызвать OnDraw() и перерисовать обновленную строку (вскоре мы добавим в OnDraw() соответствующий код), мы вызовем методInvalidate()объекта вида:

void CKeystrokesView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: добавьте код обработки сообщения

// и/или вызовите обработчик по умолчанию

CKeystrokesDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData += nChar;

Invalidate();

CView::OnChar(nChar, nRepCnt, nFlags);

}

Этот метод заставляет программу перерисовать объект вида методом OnDraw(), поэтому теперь перейдите к методу On0raw():

void CKeystrokesView::OnDraw(CDC* pDC)

{

CKeystrokesDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: добавьте код для отображения данных

}

ПОДСКАЗКА:Вы можете воспользоваться Invalidate() для того, чтобы объявить недействительной лишь некоторую часть вида — в этом случае программа перерисует только ее.

Все, что нам нужно, — вывести текстовую строку. Для этой цели мы воспользуемся методом TextOut():

void CKeystrokesView::OnDraw(CDC* pDC)

{

CKeystrokesDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, pDoc->StringData);

}

Программаготова.Запустите ее и введите какой-нибудь текст. Соответствующие символы появляются в окне. Программа работает успешно, а мы узнали, как получать символы от клавиатуры.

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

Вывод текста в центре окна

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

Чтобы выровнять текст по центру клиентской области, нужно определить два размера: клиентской области и нашей текстовой строки на экране.

Для начала создайте новую однодокументную (SDI) программу и назовите ееcentered. Мы будем получать символы с клавиатуры, как это делалось в прошлом примере. Создайте в документе строковый объект с именемStringData:

// centeredDoc.h : интерфейс класса CCenteredDoc

class CCenteredDoc: public CDocument

{

protected: // создание толькопри сериализации

CCenteredDoc();

DECLARE_DYNCREATE(CCenteredDoc)

CString StringData;

Затем объект StringData необходимо инициализировать:

CCenteredDoc::CCenteredDoc()

{

StringData = "";

// TODO: добавьте код конструктора

}

Воспользуйтесь ClassWizard и добавьте метод OnChar() в класс вида программы centered, CCenteredView:

void CCenteredView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: добавьте код обработки сообщения

// и/или вызовите обработчик по умолчанию

CCenteredDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CView::OnChar(nChar, nRepCnt, nFlags);

}

Наконец, добавьте код для сохранения введенных символов:

void CCenteredView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {

// TODO: добавьте код обработки сообщения

// и/или вызовите обработчик по умолчанию

CCenteredDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData += nChar;

Invalidate();

CView::OnChar(nChar, nRepCnt, nFlags);

}

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

Определение размеров окна

Текст выводится в методе OnDraw(), который в данный момент выглядит так:

void CKeystrokesView::OnDraw(CDC* pDC)

{

  CKeystrokesDoc* pDoc = GetDocument();

     ASSERT_VALID(pDoc);

// TODO: добавьте код для отображения данных

}

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

Цля получения размеров клиентской области можно воспользоваться методомGetWindowRect() классаCWnd(наш класс вида, CCenteredView, является производным от класса MFC CView, который, в свою очередь, порожден от базового класса окна MFC, CWnd). Чтобы получить размеры вида, нужно передать методу GetWindowRect() указатель на объект классаCRect, который используется для хранения размеров прямоугольника; в нашем случае этим прямоугольником будет клиентская область.

Мы создаем объект классаCRect с именемrectи передаем указатель на него методу GetWindowRect() с помощью оператора C++ &:

void CCenteredView::OnDraw(CDC* pDC)

{

  CCenteredDoc* pDoc = GetDocument();

  ASSERT_VALID(pDoc);

CRect rect;

  GetWindowRect(&rect);

}

ПОДСКАЗКА: Если вам захочется определить размеры всего главного окна, включая заголовок, строку состояния и т. д., воспользуйтесь в объекте вида строкойGetParent() ->GetWindowRect().Это допустимо, поскольку главное окно является родительским по отношению к виду.

Тenepь объектrectсодержит размеры клиентской области. Мы воспользуемся методамиWidth() и Height() классаCRect, чтобы определить, соответственно, ширину и высоту прямоугольника. Выравнивание текста по центру начинается с того,  что мы узнаем, где находится центр клиентской области, и сохраняем координаты этой точки в двух переменных, х и у:

void CCenteredView::OnDraw(CDC* pDC)

{

CCenteredDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CRect rect;

GetWindowRect(&rect);

int x =rect.Width()/2;

int у = rect.Height()/2;

}

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

Определение размера строки

Для определения размеров строки в клиентской области мы воспользуемся методомGetTextExtent()класса CDC (вспомните, что CDC — класс контекста устройства, и что наш текст должен выводиться в этом контексте).

Методу GetText Extent () необходимо передать текстовую строку, а он вернет размеры в виде объекта класса MFC CSize. Мы назовем этот объектsize:

void CCenteredView::OnDraw(CDC* pDC)

{

CCenteredDoc* pDoc == GetDocument();

ASSERT_VALID(pDoc);

CRect rect;

GetWindowRect(&rect);

int x = rect.Width()/2;

intу == rect.HeightO/2;

CSize size == pDC->GetTextExtent(pDoc->StringData);

}

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

void CGenteredView::OnOraw(COC* pDC)

{

CCenteredDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CRect rect;

GetWindowRect(&rect);

int x = rect.Width()/2;

intу == rect.Height()/2;

CSize size = pDC->GetTextExtent(pDoc->StringData);

x =x + size.cx/2;

у = y + size.cy/2;

}

ПОДСКАЗКА: Зная, как определить высоту отображаемой на экране строки вы можете самостоятельно изменить программу чтения символов и предусмотреть в ней обработку клавиши Enter. Сравните полученный символ с символом перевода строки "\r" и в случае совпадения перейдите на следующую строку, добавив ее высоту к переменной у.

Остается лишь вывести текстовую строку по новым координатам (x, у). Для этого мы воспользуемся знакомым нам методом TextOut():

void CCenteredView::OnDraw(CDC* pDC)

{

CCenteredDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CRect rect;

GetWindowRect(&rect);

int x = rect.Width()/2;

intу = rect.Height()/2;

CSize size = pDC->GetTextExtent(pDoc->StringData);

x = x + size.cx/2;

у = y+ size.cy/2;

pDC->TextOut(x, y, pDoc->StringData);

}

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

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

В предыдущем разделе мы познакомились с обработкой сообщений Windows и организовали в своей программе ввод символов с клавиатуры. В этом разделе мы продолжим изучение сообщений Windows, только на этот раз они будут поступать от другого источника — мыши. Мышь может порождать несколько видов сообщений, от WM_LBUTTONDOWN (при нажатии левой кнопки) до WM_МОUSEМОVЕ (при перемещении мыши).

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

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

Давайте посмотрим, как нарисовать курсор в объекте вида Visual C++ н организовать работу с ним в программе. Начнем с программы, которая будет создавать курсор в окне и перемещать его по мере ввода текста. Затем мы перейдем к работе с мышью и сделаем так, чтобы пользователь смог разместить свой текст в произвольном месте вида.

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

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

Посмотрим, как это делается. С помощью AppWizard создайте однодокументную (SDI) программу с именемcarets. Сначала необходимо создать в заголовочном файле документа объектStringDataдля хранения вводимого текста:

class CCaretsDoc: public CDocument

{

  protected: // создание только при сериализации

CCaretsDoc();

DECLARE_DYNCREATE(CCaretsDoc)

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

virtual ~CCaretsDoc();

CString StringData;

}

В конструкторе документа присвоим этому объекту пустую строку:

CCaretsDoc::CCaretsDoc()

{

StringData = " ";

// TODO: добавьте код конструктора

}

Воспользуйтесь ClassWizard и свяжите сообщение WindowsWM_CHAR с методомOnChar() объекта вида. Как и в предыдущем разделе, для сохранения введенных символов мы добавим в него следующий фрагмент кода:

void CCaretsView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

CCaretsDoc* pDoc = GetDocument():

ASSERT_VALID(pDoc);

pDoc->StringData += nChar;

Invalidate();

CView::OnChar(nChar, nRepCnt. nFlags);

}

Теперь можно приступать к созданию курсора в объекте вида.

Чтобы создать курсор, необходимо выбрать его размер (курсоры не имеют стандартных размеров), а для этого нужно знать кое-что о тексте, с которым мы работаем. Обычно высота курсора совпадает с высотой символов текущего шрифта, а его ширина равна 1/8 средней ширины символов. Для определения высоты и ширины символов мы воспользуемся методомGetTextMetrics() классаCDC (вспомни те, что класс CDC предназначен для работы с контекстом устройства).

Определение параметров шрифта по структуре TEXTMETRIC

Наш курсор будет создан в методе OnDraw(). Прежде всего мы создадим в объекте вида логическую переменную с именемCaretCreated, по которой можно будет определить, был ли курсор создан ранее:

// caretsView.h:интерфейскласса CCaretsView

class CCaretsView: public CView

{

protected: // создание только при сериализации

CCaretsView();

DECLARE_DYNCREATE(CCaretsView)

boolean CaretCreated;

}

ПОДСКАЗКАЛогические переменные, также называемые флагами, могут принимать всего два значения: "истина" (true) и "ложь" (false).

После того как в конструкторе вида переменной CaretCreated было присвоено значениеfalse:

CcaretsView::CcaretsView()

{

CaretCreated =false;

}

в методе OnDraw() необходимо проверить, был ли создан курсор:

 void CCaretsView::OnDraw(CDC* pDC)

{

CaretsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

if(!CaretCreated){

}

Если курсор еще не создан, необходимо сделать это. Сначала выберем размер курсора. Необходимую информацию можно взять из структурыTEXTMETRIC, но сначала следует вызвать методGetTextMetrics(). Метод GetTextMetrlcs() заполняет структуру типа TEXTMETRIC, которая состоит из следующих полей:

typedef struct tagTEXTMETRIC

{

LONG tmHeight;

LONG tmAscent;

LONG tmDescent;

LONG tmInternalLeading;

LONG tmExternalLeading;

LONG tmAveCharWidth;

LONG trnMaxCharWidth;

LONG tmWeight;

LONG tmOverhang;

LONG ImDigitizedAspectX;

LONG mDigitizedAspectY;

BCHAR tmFirstChar;

BCHAR tmLastChar;

BCHAR tmDefaultChar;

BCHAR tmBreakChar;

BYTE tmltalic;

BYTE tmUnderlined;

BYTE tmStruckOut;

BYTE tmPitchAndFamily;

BYTE tmCharSet;

} TEXTMETRIC;

Внашейпрограмместруктуратипа TEXTMETRICсименем textmetricзаполняетсятак:

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

 if(CaretCreated){

TEXTMETRIC textmetric;

pDC->GetTextMetrics(&textmetric);

} }

Все готово к созданию курсора. Его высота будет равна высоте символов(textmetric.tmHeight), а ширина— 1/8 средней ширины символа (textmetric.tmAveCharWidth/8). Для создания курсора мы воспользуемся методом CreateSolidCaret():

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

 if(CaretCreated){

TEXTMETRIC textmetric;

pDC->GetTextMetrics(&textmetric);

CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight);

}

}

ПОДСКАЗКА:Кроме методаCreateSolidCaret()существует также метод CreateGrayCaret() для создания затушеванного, "серого" курсора. Помимо этого, для работы с курсорами предназначены методыShowCaret(), SetCaretPos() и HideCaret().

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

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

Положение курсора будет храниться в новом объекте классаCPointс именемCaretPosition. КлассCPointсодержит две переменные, х и у, в которых хранятся значения координат:

 // caretsView.h :интерфейсклассаCCaretsView

class CCaretsView : public CView

{

protected: // создание только при сериализации

CCaretsView();

DECLARE_DYNCREATE(CCaretsView)

CPoint CaretPosition;

boolean CaretCreated;

}

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

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pDoc = GetDocumentO;

ASSERT_VALID(pDoc);

if(CaretCreated){

TEXTMETRIC textmetric;

pDC->GetTextMetrics(&textmetric);

CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight);

CaretPosition.x = CaretPosition.у = 0;

}

}

ПОДСКАЗКА: Обратите внимание на удобную сокращенную форму записи, которая позволяет в C++ присвоить одинаковое значение сразу нескольким переменным: x=y=z=1;.

3атем мы задаем позицию курсора методомSetCaretPos(), отображаем курсор на экране (ShowCaret()) и устанавливаем значение флагаCaretCreated вtrue:

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pDoc = GetDocumentO;

ASSERT_VALID(pDoc);

if(CaretCreated){

TEXTMETRIC textmetric;

pDC->GetTextMetrics(&textmetric);

CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight);

CaretPosition.x = CaretPosition.у = 0;

SetCaretPos(CaretPosition);.

ShowCaret();

   CaretCreated = true;

}

}

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

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

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pDoc = GetDocumentO;

ASSERT_VALID(pDoc);

if(CaretCreated){

TEXTMETRIC textmetric;

pDC->GetTextMetrics(&textmetric);

CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight);

CaretPosition.x = CaretPosition.у = 0;

SetCaretPos(CaretPosition);.

    ShowCaret();

   CaretCreated = true;

}

pDC->TextOut(0, 0, pDoc->StringData);

}

Затем нужно переместить курсор в конец строки, но предварительно необходимо выяснить, где она кончается. Для этого мы заполним объект классаCSizeс именемsize, для чего вызовем методGetTextExtent():

void CCaretsView::OnDraw(CDC* pDC)

{

      CCaretsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, pDoc->StringData);

CSize size = pDC->GetTextExtent(pDoc->StringData);

Прежде чем выводить курсор в конце строки, мы с помощью методаHideCaret()скроем его:

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, pDoc->StringData);

CSize size = pDC->GetTextExtent(pDoc->StringData);

HideCaret();

}

ВНИМАНИЕ: Если не скрыть курсор перед перемещением, его изображение может остаться на прежнем месте.

Затем переменнойхобъектаCaretPositionприсваивается координата конца текстовой строки на экране:

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pOoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, pDoc->StringData);

CSize size = pDC->GetTextExtent(pDoc->StringData);

HideCaret();

CaretPosition.x = size.ex;

}

Наконец, мы перемещаем курсор в новое положение и сновавыводим его на экран:

void CCaretsView::OnDraw(CDC* pDC)

{

CCaretsDoc* pDoc = GetDocument();

ASSERT.VALID(pDoc);

pDC->TextOut(0, 0. pDoc->StringData);

CSize size = pDC->GetTextExtent(pDoc->StringData);

HideCaret();

CaretPosition.x = size.ex;

SetCaretPos(CaretPosition);

ShowCaret();

}

Запуститепрограмму.Как видите, курсор постоянно находится в конце строки. Программа carets прекрасно работает, а мы научились работать с курсором.

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

Скрытие/отображение курсора при потере/получении фокуса

При потере фокуса программа получает сообщениеWM_KILLFOCUS, а при получении —WM_SETFOCUS. Воспользуйтесь ClassWizard и свяжите с сообщением WM_KILLFOCUS новый обработчик. ClassWizard присваивает ему имяOnKillFocus():

void CCaretsView::OnKillFocus(CWnd* pNewWnd)

{

CView::OnKillFocus(pNewWnd);

// TODO: добавьте код обработки сообщения

}

Данный метод обрабатывает сообщение о потере фокуса, поэтому курсор необходимо скрыть:

void CCaretsView::OnKillFocus(CWnd* pNewWnd) {

CView::OnKillFocus(pNewWnd);

  HideCaret();

// TODO:добавьтекодобработкисообщения

}

По аналогии добавьте методOnSetFocus()для обработки сообщения WM_SETFOCUS и включите в него код для отображения курсора при получении фокуса:

void CCaretsView::OnSetFocus(CWnd* pOldWnd) {

CView::OnSetFocus(pOldWnd);

ShowCaret();

// TODO:добавьтекодобработкисообщения

}

Программа готова. Теперь при получении фокуса курсор появляется, а при потере — исчезает.

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

Работа с мышью

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

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

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

Использование методов ClassWizard для работы с мышью

Создайте проект SDI-программы с именемmouser. Наделите ее теми же функциями работы с клавиатурой, что и предыдущую программу carets; иначе говоря, вводимые пользователем символы должны сохраняться в объекте документаStringData. Кроме того, добавьте методыOnKillFocus() и OnSetFocus()и включите в них вызовыHideCaret()иShowCaret (), как это было сделано в предыдущей программе (можно скопировать необходимые фрагменты кода). Перейдем к отображению данных, вводимых пользователем.

Если пользователь щелкнул мышью в некоторой точке клиентской области, он хочет, чтобы текст выводился в указанном им месте. Мы должны воспользоваться ClassWizard и добавить метод для обработки сообщения WindowsWM_LBUTTONDOWN; ClassWizard присваивает ему имяOnLButtonDown().

Запустите ClassWizard. Проследите, чтобы в спискеClass name был выбран класс видаCMouserView, и найдите в спискеMessagesсообщениеWM_LBUTTONDOWN. Дважды щелкните на его имени; при этом создается и выводится в спискеMember functions методOnLButtonDown( ). Дважды щелкните на строке с его именемOnLButtonDown(), чтобы перейти к коду:

void CMouserView::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: добавьте код обработки сообщения

// и/или вызовите обработчик по умолчанию

Cview::OnLButtonDown(nFlags, point);

}

Помимо методаOnLButtonDown(), можно использовать и другие методы для работы с мышью —OnLButtonUp(), отвечающий за обработку отпускания левой кнопки мыши,OnRButtonDown(), соответствующий нажатию правой кнопки мыши,OnRButtonDblClick(), обрабатывающий двойной щелчок левой кнопкой, и т. д. Методы для работы с мышью перечислены в табл. 4.1.

Таблица 4.1. Методы Class Wizard для работы с мышью

Метод

Назначение

OnLButtonDblClick

Вызывается при двойном щелчке левой кнопкой мыши

OnLButtonDown

Вызывается при нажатии левой кнопки мыши

OnLButtonUp

Вызывается при отпускании левой кнопки мыши

OnMButtonDblClick

Вызывается при двойном щелчке средней кнопкой мыши

OnMButtonDown

Вызывается при нажатии средней кнопки мыши

OnMButtonUp

Вызывается при отпускании средней кнопки мыши

OnMouseActivate

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

OnMouseMove

Вызывается при перемещении указателя мыши

OnMouseWheel

Вызывается при повороте колеса мыши. Используется в Windows NT 4.0

OnRButtonDblClick

Вызывается при двойном щелчке правой кнопкой мыши

OnRButtonDown

Вызывается при нажатии правой кнопки мыши

OnRButtonUp

Вызывается при отпускании правой кнопки мыши

OnRegisteredMouseWheel

Вызывается при повороте колеса мыши. Используется в Windows 95 и Windows NT 3.51

OnSetCursor

Вызывается при перемещении указателя мыши в окне/ если мышь не была ранее захвачена приложением

МетодуOnLButtonDown() передаются два параметра,nFlagsиpoint. Первый содержит информацию о состоянии различных служебных клавиш и может принимать следующие значения:

MK_CONTROL        Нажата клавиша Ctrl

MK_LBUTTON        Нажата левая кнопка мыши

MK_MBUTTON        Нажата средняя кнопка мыши

MK_RBUTTON        Нажата правая кнопка мыши

MK_SHIFT          Нажата клавиша Shift

Параметр point, объект класса CPoint, содержит текущие координаты указателя мыши.

Итак, кнопка мыши нажата. Первым делом необходимо сохранить текущее положение указателя. Мы воспользуемся переменными х и у и присвоим им значения, полученные из переменных х и у объекта point:

void CMouserView::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: добавьте код обработки сообщения

// и/или вызовите обработчик по умолчанию

  х = point.х;

 у = point.у;

}

Переменные для хранения координат объявляются в заголовочном файлевидаmouserView.h:

// mouserView.h: интерфейс класса CMouserView

protected: // создание только при сериализации

CMouserVlew();

DECLARE_DYNCREATE(CMouserView)

CPoint CaretPoiitlon;

boolean CaretCreated:

int x, y;

Щелчок мышью означает, что текст будет выводиться в новом положении, — для очистки строкового объекта мы воспользуемся методомEmpty()класса CString:

void CMouserView::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: добавьте код обработки сообщения

// и/или вызовите обработчик по,умолчанию

х = point.х;

у = point.у;

CMouserDoc* pDoc=GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData.Empty();

}

Остается лишь объявить текущее состояние вида недействительным, чтобы перерисовать его и отобразить курсор в новом месте (соответствующий код был добавлен в методOnDraw()):

void CMouserView::OnLButtonDown(UINT nFlags.CPoint point)

{

     // TODO: добавьте код обработки сообщения

     // и/или вызовите обработчик по умолчанию

      х = point.х;

у = point.у;

CMouserDoc* pDoc = GetDocument();

      ASSERT_VALID(pDoc);

      pDoc->StringData.Empty();

 Invalidate();

CView::OnLButtonDown(nFlags, point);

}

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

Вывод текста с заданной точки

Для вывода текста мы воспользуемся методом OnDraw(). Начнем с создания текстового курсора, как это было сделано в предыдущем примере carets:

void CMouserView::OnDraw(CDC* pDC)

{

CMouserDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

       if(!CaretCreated){

TEXTMETRIC textmetric;

       pDC->GetTextMetrics(&textmetric);

         CreateSolidCaret(textmetric.tmAveCharWidth/8,

           textmetric.tmHeight);

        CaretPosition.x = CaretPosition.y = 0;

       SetCaretPos(CaretPosition);

ShowCaret();

        CaretCreated = true;

}

}

Затем мы выводим содержимое строкового объекта StringData. Координаты щелчка были сохранены в переменных х и у, поэтому начало строки окажется в этой точке:

void CMouserView::OnDraw(CDC* pDC)

{

CMouserDoc* pDoc == GetDocument();

ASSERT_VALID(pDoc);

if(!CaretCreated){

pDC->TextOut(x, y, pDoc->StringData);

}

 Курсор должен находиться в конце выведенной строки. Сначала мы определяем, где кончается строка, и скрываем курсор:

void CMouserView::OnDraw(CDC* pDC)

{

  CMouserDoc* pDoc = GetDocument();

ASSERT.VALID(pDoc);

if(!CaretCreated){

pDC->TextOut(x, y, pDoc->StringData);

CSize size =pDC->GetTextExtent(pDoc->StringData);

HideCaret();

}

Затем переводим курсор в конец строки и снова отображаем его:

void CMouserView::OnDraw(CDC* pDC)

{

 CMouserDoc* pDoc == GetDocument();

ASSERT_VALID(pDoc);

 if(!CaretCreated){

 pDC->TextOut(x, y, pDoc->StringData);

CSize size = pDC->GetTextExtent(pDoc->StringData);

HideCaret();

CaretPosition.x = x + size.ex;

CaretPosition.y = y;

SetCaretPos(CaretPosition);

ShowCaret();

  }

}

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

5. Пример программы с меню

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

Мы научимся включать в меню новые команды, а также создавать новые меню. Кроме того, здесь рассматриваются все стандартные "фокусы", встречающиеся в меню, — пометки-галочки, выделение недоступных команд серым цветом, клавиши ускоренного вызова команд, акселераторы и подсказки в строке состояния. Мы увидим, как создавать и использовать подменю (то есть меню нижнего уровня, которые открываются при выборе команды меню). попутно вы узнаете, как поместить новую кнопку на панель инструментов.

Первая программа  будет очень простой и поможет нам освоить азы работы с меню. Мы добавим в менюFileновую командуPrint Welcome.

Когда пользователь выполнит ее, в клиентской области окна должен появиться текст "Добро пожаловать в меню!" (см. далее). На примере программы menus мы научимся работать с меню. Воспользуйтесь AppWizard и создайте программу с однодокументным интерфейсом (SDI). Давайте начнем с редактора меню Visual С++ и включим с его помощью новую команду в меню File нашей программы.

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

Редактор меню — один из самых полезных инструментов Visual C++. Он значительно упрощает создание меню в программе. Чтобы запустить редактор меню, перейдите на вкладкуResourceView в окне просмотра Visual C++ (крайнее левое окнo) — дело в том, что меню в программах для Windows обычно считаетсяресурсом(описание ресурсов программы хранится в файле проекта с расширением.гс). Откроется перечень ресурсов программыmenus.

Найдите в списке строкуMenusи откройте ее. Дважды щелкните на строке IDR_MAINFRAME— это приведет к запуску редактора меню.

ПРИМЕЧАНИЕ: Обратите внимание на то, что ресурс меню имеет идентификаторIDR_MAINFRAME (идентификатором ресурса называется числовое значение, по которому ресурс можно отличить от других ресурсов). Как нетрудно догадаться по идентификатору, меню принадлежит объекту главного окна.

Добавление новой команды в меню

Мы собираемся добавить в меню новую команду Print Welcome. Щелкните на менюFile— оно раскрывается в редакторе меню. Новая команда должна оказаться между существующимиPrintиPrint Preview, поэтому щелкните наPrint Preview и нажмите клавишуInsert— в меню появится новая команда (она окружена толстой пунктирной рамкой).

Дважды щелкните на ней — откроется диалоговое окноMenu Item Properties, содержащее свойства команды. Занесите в полеCaptionстрокуДобро пожаловать в меню и закройте окно. Новой команде меню необходимо присвоить идентификаторID_FILE_PRINTWELCOME(FILEприсутствует в имени идентификатора, потому что новая команда находится в менюFile).

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

Подключение команды меню к коду программы

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

Вы обнаружите, что идентификатор новой команды,ID_FILE_PRINTWELCOME, уже присутствует в спискеObject IDs. Сейчас мы соединим команду с методом-обработчиком (проследите, чтобы в спискеClassбыл выбран класс видаCMenusView). Выделите строку ID_FILE_PRINTWELCOME (идентификатор команды) в спискеObject IDs, затем щелкните на строкеCommandв спискеMessages(она соответствует выполнению команды меню). ClassWizard предложит присвоить обработчику имяOnFilePrintWelcome()— нажмите кнопку ОК. После этого новый метод появляется в спискеMember functions окна ClassWizard.

Дважды щелкните на строкеOnFilePrintWelcome() в спискеMember functions, и в окне справа появится код метода:

void CMenusView::OnFilePrintwelcome()

 {

    // TODO: добавьте код обработки сообщения

 }

Этот метод будет вызываться при выполнении пользователем команды Print Welcome, поэтому в него следует поместить соответствующий код. В нашем примере при выполнении команды должна выводиться строка "Добро пожаловать в меню!", поэтому мы должны выделить в объекте документа место для хранения этой строки:

// menusDoc.h : интерфейс класса CMenusDoc

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

public:

virtual ~CMenusDoc();

CString StringData;

В конструкторе документа StringData объект будет инициализироваться пустой строкой:

CMenusDoc::CMenusDoc()

{

StringData = "";

}

Далее в методеOnFilePrintwelcome(), принадлежащем объекту вида, мы получаем указательpDocна объект документа:

void CMenusView::OnFilePrintWelcome()

{

CMenusDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

}

Остается лишь занести в объектStringDataстроку "Добро пожаловать в меню!", отобразить ее в окне. Для этого мы объявим вид недействительным, чтобы методOnDraw()сделал всю работу за нас:

void CMenusView::OnFilePrintWelcome()

{

CMenusDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData = "Добро.пожаловатьвменю!";

Invalidate();

}

Наконец, мы добавляем в OnDraw() код для вывода строки:

void CMenusView::OnDraw(CDC* pDC)

{

CMenusDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, pDoc->StringData);

}

Запуститепрограмму;вменюFileпоявилась новая команда. Выберите ее, и в клиентской области программы появится строка "Добро пожаловать в меню!". Наша новая программа успешно справляется со своей задачей, а мы приступили работе с меню!

Программа menus демонстрирует основные принципы работы с меню. Однако у нас осталось множество вопросов, не нашедших ответа: как добавить в программу меню (а не отдельную команду)? Как задать сочетания клавиш для ускоренного  вызова команд? Как создать подменю? Ответы на эти (и некоторые другие) вопросы мы получим в следующем примере.

Программа для полноценной работы с меню

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

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

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

Кроме того, в нашей программе будут присутствовать акселераторы, подсказка-справка в строке состояния и т. д. Сделать предстоит немало, поэтому давай перейдем к делу.

Создайте при помощи AppWizard SDI-программу с именемfullmenus. Откройте ресурс меню,IDR_MAINFRAME, в редакторе меню. Начнем с добавления менюDemo. Оно должно располагаться между менюFileиEdit. Выделите (щелчком) менюEditв редакторе и нажмите клавишуInsertВ  строке  появится новое меню; дважды щелкните на нем и присвойте ему имяDemoв окне Menu Item Properties (несмотря на то, что мы работаем с меню, а не с отдельной командой, Visual C++ продолжает использовать окноMenu Item Properties, поскольку в нем не существует специального окнаMenu Properties для подменю). В cтроке появляется менюDemo.

Теперь мы должны добавить в менюDemoкоманды; Редактор меню уже включил в него пустую команду — щелкните на ней и дайте ей имяGrayed. Присвойте ей идентификаторID_DEMO_GRAYED.

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

Добавление клавиш ускоренного вызова

Буква D в названии меню Demo подчеркнута; это означает, что если пользователь нажметAlt+D, откроется менюDemo. Благодаря этому появляется возможность вызывать меню с помощью клавиатуры. КомандеSubmenuменю Demo назначена клавиша ускоренного вызова S. Если пользователь нажмет Alt+S приоткрытом меню Demo, на экране появится подменю с дополнительными командами.

ВНИМАНИЕ! При выборе клавиш ускоренного вызова для меню или отдельных команд проследите, чтобы они не повторялись. В противном случае программа не сможет разобраться, что она должна делать при нажатии той или иной клавиши ускоренного вызова.

Создать клавишу ускоренного вызова очень просто - достаточно вставить символ "амперсанд" (&) перед той буквой в названии меню или команды, которая будет использоваться для ускоренного вызова. Например, чтобы использовать букву G для ускоренного вызова команды Grayed, измените название команды на &Grayed.

Аналогично задаются клавиши ускоренного вызова и для других команд меню. Клавиши ускоренного вызова готовы к работе.

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

Когда пользователь выделяет команду меню, в строке состояния — полосе в нижней части окна программы — можно вывести дополнительную информацию. Для этого достаточно разместить нужный текст в полеPromptокнаMenu Item Properties.

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

Давайте посмотрим,  как добавить в меню Demo подменю.

Добавление подменю

Чтобы по командеSubmenusна экране открывалось подменю, выполните следующие действия:

1. Дважды щелкните мышью на командеSubmenusв редакторе меню — откроется окно Menu Item Properties.

2.  Установите флажокPop-up.

3. В командеSubmenuоткрывается новое подменю с одной пустой командой.

4. Если выбрать эту команду в редакторе меню, на экране появляется подменю.

5. Мы включим в последнее две команды:Sub Item 1иSub Item 2. Воспользуйтесь стандартным способом — дважды щелкните на пустой команде и задайте для нее имя в окнеMenu Item Properties.

6. Предварительная работа по созданию подменю завершена. Остается лишь добавить необходимый код, чем мы и займемся вскоре.

Добавление акселераторов

Акселераторомназывается сочетание клавиш, которое пользователь может нажать в любой момент (даже при закрытом меню) и которое равносильно выбору команды меню. В нашем случае сочетание клавиш Ctrl+М действует так же, как  команда Sub Item 2.

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

1. Переидите в окно просмотра и откройте в списке ресурсов папкуAccelerator.

2. Двойной щелчок на папкеAccelerator— запускается редактор акселераторов.

3. Дважды щелкните на последней пустой строке в списке акселераторов — открывается окноAccelerator Properties.

4. Выберите идентификатор команды Sub Item 2, ID_DEMO_SUBMENUS_SUBITEM2. Если ввести несколько начальных символов идентификатора при открытом списке,  он будет выбран автоматически. Если это не происходит,наберите идентификатор вручную.

5. Чтобы связать сочетание клавиш Ctrl+М с данным акселератором, установите   флажокCtrlв группеModifiers.

6. Затем наберите в спискеKeyбукву М (лат.) (сокращение VK означает "virtual key", то есть "виртуальная клавиша").

7. Закройте окноAccel Properties. Теперь сочетание клавишCtrl+Мстанет акселератором команды менюSub Item 2.

8. Чтобы указать на наличие акселератора для командыSub Item 2, замените в редакторе меню ее название наSub Item 2\tCtrl+М. \t - представляет собой код символа табуляции, поэтому в новом названии текст Ctrl+М будет расположен справа, что указывает на то, что это сочетание клавиш является акселератором для команды меню.

Акселератор готов, а теперь мы займемся размещением новой кнопки на панели инструментов.

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

Любую команду меню можно продублировать на панели инструментов в виде кнопки. Давайте создадим такую кнопку для команды Sub Item 1.

Для начала выберем внешний вид кнопки. Откройте в окне просмотра папкуToolbarи щелкните в строкеIDR_MAINFRAME. Изображение кнопки появится в графическом редакторе.

Мы ограничимся простейшим изображением — рамкой. Для этого нам понадобится инструмент в виде карандаша из панели редактора, расположенной справа. Щелкните на пустой кнопке в левой части панели инструментов и нарисуйте рамку на ее увеличенном изображении.

Дважды щелкните на новой кнопке на панели инструментов - открывается окно Toolbar Button Properties. Выберите из списка строкуID_DEMО_SUBMENUS_SUBITEM1, чтобы связать команду меню с кнопкой на панели инструментов.

Занесите в поле Prompt текстSub menu 1\nSub menu 1. Это означает, что когда пользователь задержит указатель мыши над новой кнопкой, в строке состояния появится текстSub menu 1. Кроме того, текст за символом \nбудет выведен в качестве экранной подсказки возле кнопки. В нашем случае подсказка будет состоять из текста Sub menu 1.

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

Блокировка команд меню

Давайте заблокируем одну из команд меню. ОткройтеClassWizard (выберитеClassName =CfullMenusView) и найдите в спискеObject IDs строкуID_DEMO_GRAYED. Теперь свяжите с этим идентификатором метод-обработчик, но вместо знакомой строкиCOMMANDщелкните рспискеMessages на строкеUPDATE_COMMAND_UI. СокращениеUIозначает"user interface", то есть "пользовательский интерфейс".

В результате будет создан новый методOnUpdateDemoGrayed ():

void CFullmenusView: :OnUpdateDemoGrayed(CCmdUI* pCmdUI)

{

   // TODO: добавьте код для обновления

   // пользовательского интерфейса команды

}

Этот метод будет вызываться программой перед отображением новой команды меню, и в нем можно заблокировать команду. Ему передается параметрpCmdUI, представляющий собой указатель на объект класса CCmdUI— методы этого класса перечислены в табл. 5.1.

Таблица 5.1. Методы класса CCmdUI

Метод

Назначение

ContinueRouting

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

Enable

Устанавливает или снимает блокировку элемента пользовательского интерфейса, соответствующего данной команде

SetCheck

Устанавливает пометку для элемента пользовательского интерфейса, соответствующего данной команде (Аргументы 1или 0).

SetRadio

Аналогичен методу SetCheck, но применяется для переключателей

SetText

Задает текст для элемента пользовательского  интерфейса, соответствующего данной команде

В этом методе мы собираемся заблокировать команду меню; для этого следует вызватьметодEnable() объектаCCmdUI, передав ему значениеfalse:

void CFullmenusView: :OnUpdateDemoGrayed(CCmdUI* pCmdUI)

{

   // TODO: добавьте код для обновления

   // пользовательского интерфейса команды

pCmdUI->Enable(false);

}

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

Аналогично устанавливается пометка "галочкой" для команды Checked.

Пометка команд меню

Теперь необходимо связать методOnUpdateDemoChecked() с сообщениемUPDATE_COMMAND_UIдля командыChecked:

void CFullmenusView::OnUpdateDemoChecked(CCmdUI* pCmdUI)

{

// TODO: добавьте код для обновления

// пользовательского интерфейса команды

}

Чтобы установить пометку для команды Checked, мы сначала разблокируем методомEnable(), а затем вызовем метод SetCheck() объекта CCmdUI с аргументом 1 (аргумент 0 удаляет пометку):

void CFullmenusView::OnUpdateDemoChecked(CCmdUI* pCmdUI)

{

// TODO: добавьте код для обновления

// пользовательского интерфейса команды

pCmdUI->Enable(true);

pCmdUI->SetCheck(1);

}

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

Добавление кода для команд подменю

Добавить код для обслуживания команд подменю ничуть не сложнее, чем для обычных команд. Чтобы добавить код для командыSub Item 1, найдите в ClassWizard идентификаторID_SUBMENUS_SUBITEM1и создайте для него обработчик (при использованииMessages:COMMAND):

void CFullmenusView::OnDemoSubmenusSubitem1()

{

}

Мы включим в объект документа строковый объектStrlngData и, когда пользователь выполнит командуSub Item 1, поместим в него соответствующий текст:

void CFullmenusView::OnDemoSubmenusSubitem1()

{

CFullmenusDoc* pDoc == GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData = "Выбранакоманда Sub Item 1.";

Invalidate();

}

Обработчик командыSub Item 2выглядит аналогично, отличается лишь выводимая строка:

void CFullmenusView::OnDemoSubmenusSubitem2()

{

CFullmenusDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData = "Выбранакоманда Sub Item 2.";

Invalidate();

}

He забудьте включить в методOnDraw()код для вывода текста по аналогии с предыдущей программойmenus.

Программа готова.

Меню Demo содержит три команды: первая из них заблокирована, вторая помечена, а третья открывает подменю с двумя дополнительными командами. Кроме того,  можно увидеть акселератор и клавиши ускоренного вызова.

На панели инструментов появилась новая кнопка. Если задержать на ней указатель мыши, на экране отображается подсказка. Щелчок на этой кнопке действует так же, как и выбор команды Sub Item 1.

Программаfullmenusработает в полном соответствии с нашими планами. В процессе разработки мы узнали много нового о работе с меню в Visual C++: от подсказок до кнопок на панелях инструментов; от пометки до блокировки команд; от клавиш ускоренного вызова до акселераторов.

6. Создание диалоговых окон

В этой главе мы научимся создавать диалоговые окна и использовать их в программах на Visual C++. Наверняка вам уже приходилось видеть диалоговые окна с кнопками, списками и другими элементами, предназначенными для передачи информации в программу. Одно из важнейших преимуществ диалоговых окон перед прочими элементами в Visual C++ заключается в том, что в них значительно проще располагать управляющие элементы(кнопки, текстовые поля, флажки и т. д.). Более того, в Visual C++ для этого имеется специальный инструмент — редактор диалоговых окон.

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

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

Начнем с создания простейшей программы с диалоговым окном.

Создание первого диалогового окна

На примере первой программы с диалоговыми окнами,dialogs, мы рассмотрим все аспекты работы с ними. В менюFileбудет включена новая команда,Show Dialog…. Обратите внимание на многоточие в конце команды — оно показывает, что данная команда вызывает диалоговое окно; в Windows такое обозначение является стандартным.

Пpи выборе этой команды мы выведем на экран диалоговое окно с кнопкамиОК, Cancel,  Нажми меня и текстовым полем.

При нажатии кнопкиНажми меня в текстовом поле должна появляться строка "Текст в диалоговом окне".

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

Создайте проект однодокументной (SDI) программы с именемdialogsи в редакторе меню включите в менюFileкомандуShow Dialog.... Воспользуйтесь ClassWzard и включите в программу метод для обработки данной команды,OnFileShowDialog():

void CDialogsView::OnFileShowdialog()

{

// TODO: добавьте код обработки команды

}

Когда пользователь выбирает в меню эту команду, на экране должно появиться диалоговое окно. Но как это сделать? Оказывается, мы должны создать новый класс диалогового окна и затем объявить в методе OnFileShowDialog() его объект. Для отображения диалогового окна в модальном режиме используется методDoModal() этого класса. Термин "модальный режим" означает, что пользователь не сможет продолжать работу с программой, пока не закроет диалоговое окно. Давайте посмотрим, как создается класс диалогового окна.

Создание диалогового окна

Чтобы создать диалоговое окно, выполните командуInsert>Resourceв Visual C++. Появляется окноInsert Resource. Выделите в списке строкуDialogи нажмите кнопкуNew.

КнопкаNewзапускает редактор диалоговых окон.

В редакторе уже присутствует "заготовка" для создаваемого окна. Диалоговые окна, как и меню, относятся к "ресурсам" Windows-программ. Нашему диалоговому окну присвоен идентификатор IDD_DIALOG1. В нем уже размещены две кнопки —ОКиCancel. Они присутствуют почти во всех диалоговых окнах; кнопкаОКподтверждает предыдущие действия пользователя, а кнопкаCancelотменяет их. Если пользователь закрывает диалоговое окно кнопкой ОК, методDoModal() возвращает значениеIDOK, а при закрытии окна кнопкойCancel DoModal()  возвращаетIDCANCEL.

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

ПРИМЕЧАНИЕ: В Windows принято всегда включать кнопку Cancel в диалоговые окна, чтобы пользователь мог отменить ошибочный вызов окна.

Добавление элементов в диалоговое окно

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

ПОДСКАЗКА:Если вы захотите узнать, какой элемент представлен тем или иным инструментом в палитре, ненадолго задержите над ним указатель мыши — на экране появится подсказка с названием элемента.

Щелкните в текстовом поле — вокруг него появится заштрихованная рамка с маленькими квадратиками, которые называются маркерами размеров. Вы можете захватить мышью любой маркер и "растянуть" элемент так, как считаете нужным. Мы растягиваем текстовое поле по горизонтали (обратите внимание: указатель мыши приобретает вид двойной стрелки).

Итак, у нас есть текстовое поле и кнопка с надписьюButton1. Мы хотим заменить эту надпись другой, "Нажни меня" — давайте сделаем это.

Изменение надписей

Чтобы изменить надпись элемента, выделите его щелчком  мыши и введите новую надпись. В случае нашей кнопки открывается окноPush Button Properties.

Введите в полеCaptionтекстНажми меня. Также обратите внимание на идентификатор, присвоенный кнопке редактором диалоговых окон:IDC_BU1TON1. Аналогично, текстовому полю по умолчанию присваивается идентификатор IDC_EDIT1.

С этим диалоговым окном мы будем работать в программе. Но как это сделать? В данный момент оно представляет собой ресурс, который хранится в файлеdialogs.rcи выглядит следующим образом:

IDD_DIALOG1 DIALOG DISCARDABLE0, 0, 186. 95

STYLE DS_MODALFRAME | WSJWUP| WS.CAPTION | WS.SYSMENU

CAPTION "Dialog"

FONT 8, "MS Sans Serif"

BEGIN

DEFPUSHBUTTON   "OK",IDOK,129,7,50.14

PUSHBUTTON      "Cancel",IDCANCEL,129,24,50,14

EDITTEXT        IDC_EDIT1,75.52,91,14,ES.AUTOHSCROLL

PUSHBUTTON      "Нажмименя",IDC_BUTTON1,21,53,50,14

END

Нам придется создать объект, который был бы связан с данным ресурсом, для которого можно вызывать методы диалоговых окон, такие как DoModal(). Чаще всего диалоговые окна включаются в программу именно так — после графического проектирования они связываются с классом. Давайте создадим такой класс.

Создание класса диалогового окна

Для создания нового класса диалогового окна нам понадобится ClassWizard.  3апустите его и нажмите кнопкуAdd Class, затем выберите из раскрывающегося меню строкуNew— открывается окноNew Class. Введите в полеNameтекстDlg(имя класса), затем выберите из спискаBase Class его базовый классCDialog. КлассCDialogв MFC является базовым для диалоговых окон. Его методы перечислены в табл. 6.1. Нажмите кнопку ОК в окне New Class.

Таблица 6.1.

Метод

Назначение

Cdialog

Конструирует объект Cdialog

Create

Инициализирует объект Cdialog

Createindirect

Создает немодальное диалоговое окно по шаблону, находящемуся в памяти

DoModal

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

EndDialog

Закрывает модальное диалоговое окно

GetDefID

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

GoToDlgCtrl

Передает фокус определенному управляющему элементу диалогового окна

IhitModalIndirect

Создает модальное диалоговое окно по шаблону, находящемуся в памяти; параметры сохраняются до вызова DoModal()

MapDialogRect

Преобразует координаты в единицах диалогового окна в экранные

NextDlgCtrl

Передает фокус следующему управляющему элементу диалогового окна

OnCancel

Переопределяется для обработки кнопки Cancel или клавиши Esc

OnInitDialog

Переопределяется для проведения дополнительной инициализации диалогового окна

OnOK

Переопределяется для обработки кнопки ОК в модальном диалоговом окне

OnSetFont

Переопределяется для задания шрифта, используемого элементом диалогового окна при отображении текста

PrevDlgCtrl

Передает фокус предыдущему управляющему элементу диалогового окна

SetDefID

Назначает кнопку диалогового окна, используемой по умолчанию

SetHelpID

Задает идентификатор контекстной справки

При нажатии кнопки ОК снова появляется окно ClassWizard. Проследите, чтобы в спискеClass name был выбран классDlg. В окне перечислены идентификаторы всех управляющих элементов нашего диалогового окна: IDC_BUTTON1, IDC_EDIT1, ID_OK (кнопка ОК) и ID_CANCEL (кнопка Cancel).

Теперь мы должны связать с кнопкой код Visual C++.

Связывание методов с элементами диалоговых окон

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

Выберите из списка в окне ClassWizard строку IDC_BUTTON1 и дважды щелкните на строкеBN_CLICKEDв спискеMessages. Она соответствует специальному сообщению, которое посылается нажатой кнопкой (BN означает "button", то есть "кнопка"). Другое сообщение, BN_DOUBLECLICKED, посылается кнопкой при двойном щелчке на ней. Когда вы дважды щелкнете на строкеBN_CLICKED, ClassWizard предложитприсвоить создаваемому обработчику имя OnButtont1(). Завершите создание OnButton1() нажатием кнопки ОК.

Перейдите к коду обработчика:

void Dlg::OnButton1()

{

// TODO: добавьте код для обработки оповещений от элемента

}

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

Мы сделали немало: спроектировали диалоговое окно и разместили в нем нужные элементы, настроили параметры этих элементов, связали диалоговое окно с классом и подключили метод для обработки сообщений от кнопки. Тем не менее, мы должны научиться работать с содержимым текстового поля, поскольку при нажатии кнопки в нем должна появляться наша строка. Как это сделать? Ведь текстовое поле — это всего лишь фрагмент описания из файла dialogs.rc:

IDD_DIALOG1 DIALOG DISCARDABLE 0, 0. 186, 95

STYLE OS.MODALFRAME | WS.POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "Dialog"

FONT 8, "MS Sans Serif"

BEGIN

  DEFPUSHBUTTON   "OK",IDOK,129,7,50,14

    PUSHBUTTON      "Cancel",IDCANCEL.129,24,50,14

  EDITTEXT        IDC_EDIT1,75.52,91,14.ES.AUTOHSCROLL

 PUSHBUTTON      "Нажмименя",IDC_BUTTON1,21.53.50,14

END

Необходимо найти такой способ работы с текстовым полем, который позволил  бы добавить в него наш текст. Visual C++ позволяет связывать переменные класса с элементами диалоговых окон; именно этой возможностью мы сейчас и воспользуемся. Такой подход позволяет создать переменнуюm_textдля хранения содержимого текстового поля, так что при нажатии кнопки "Нажмименя"останется лишь присвоить переменной нужное значение:

m_text = "Текст в диалоговом окне"

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

Связывание переменных с элементами диалоговых окон

ClassWizard поможет нам связать переменные класса с элементами диалогового окна. Запустите ClassWizard и перейдите на вкладку Member Variables. Убедитесь, что в поле Class name выбран класс диалогового окнаDlg, затем выделите идентификатор текстового поляIDC_EDIT1 и нажмите кнопкуAdd Variable.

Открываетсяокно Add Member Variable.В нем мы присвоим имя переменной для хранения содержимого текстового поля. Занесите в полеMember variable name строкуm_text и проследите, чтобы в спискеCategoryбыл выбран пунктValue, а в спискеVariable type — CString. Таким образом, мы связываем содержимое текстового поля и переменную класса CStrins с именемm_text.

ПОДСКАЗКА: Вы можете присвоить имя всему управляющему элементу, а не только его отдельномусвойству(свойством называется атрибут элемента, например строка, хранящаяся в текстовом поле).  В следующем примере мы увидим, как это делается.

Закройте окноAdd Member VariableкнопкойOKи вернитесь вClassWizard. Как видно, в перечне идентификаторов появилась новая переменная, m_text.

Переменная для работы с текстовым полем готова. Теперь мы можем задать содержимое текстового поля в методеOnButton1()(который вызывается при нажатии кнопки Нажми меня):

void Dig::OnButton1()

{

m_text = "Текст в диалоговом окне";

}

Впрочем, это еще не все — простое присвоение переменнойm_textеще не заставит текст появиться в диалоговом окне. Обмен информацией между переменной и элементомIDC_EDIT1осуществляется в специальном методе, включенном ClassWizard в класс диалогового окна:

void Dig::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(Dlg)

DDX_Text(pDX, IDC_EDIT1, m_text);

//}}AFX_DATA_MAP }

Остается лишь проследить за своевременным обновлением текстового поля. Для этого следует вызвать методUpdateData():

void Dig::OnButton1() {

m_text = "Текст в диалоговом окне";

UpdateData(false);

}

Вызов этого метода с параметромfalseзаносит в текстовое поле значение переменной m_text. Вызов с параметромtrueприсваивает переменной m_text содержимое текстового поля:

UpdateData(false)означает:  IDC_EDIT = m_text;

UpdateData(true)означает: m_text = IDC_EDIT;

ВНИМАНИЕ: При использовании переменных для работы с элементами диалогового окна сложнее всего вспомнить о необходимости вызватьUpdateData() для чтения или записи содержимого элемента. Если при изменении переменной в диалоговом окне ничего не происходит, проверьте, не забыли ли вы вызвать этот метод.

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

Переопределение метода для кнопки ОК

Методдля кнопки ОК добавляется точно также, как и для любой другой кнопки. Воспользуйтесь ClassWizard и свяжите методОnОК()с кнопкой ОК, идентификаторкоторой равен IDOK:

void Dlg::OnOK()

{

 // ТООО: добавьте дополнительную проверку

CDialog::ОnОК();

}

Обратите внимание на вызов методаCDialog:: ОпОК() — он закрывает диалоговоеокно и возвращает значение IDOK. Мы хотим занести в переменную m_text содержимое текстового поля, для чего следует вызватьUpdateData() со значениемtrue:

void Dlg::OnOK()

{

  // TODO: добавьте дополнительную проверку

  UpdateData(true);

  CDialog::OnOK();

}

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

Интерфейс диалогового окна Dlg готов, осталось лишь отобразить окно на экране. Давайте посмотрим, как это делается.

Отображение диалогового окна

На экране диалоговое окно должно появляться при выборе командыFile> Show Dialog....Данная команда связана с методомOnFileShowdialog()класса вида:

void CDialogsView::OnFileShowdlalog()

{

    // ТООО: добавьте код обработки команды

}

В этом методе мы создадим новый объект классаDlgи отобразим его, пользуясь методом DoModal(). Однако перед тем, как делать это, необходимо сообщить классу вида о существовании класса Dlg. Это может показаться довольно странным:

Разве классDlgне принадлежит тому же проекту, что и класс вида? Да, это действительно так, однако поддержка классаDlgреализована в отдельном файле,Dlg.cpp.

Чтобы класс вида мог работать с членами классаDlg, необходимо включить в негоDlg.h— заголовочный файл классаDlg:

// dialogsView.cpp : реализация класса CDialogsView ,

//

#include "stdafx.h"

#include "dialogs.h"

#include "dialogsDoc.h"

#include "dialogsView.h"

#include "Dlg.h"

Теперь можно воспользоваться классом Dlg в классе вида. Начнем с создания объекта dlg этого класса:

void CDialogsView::OnFileShowdialog()

{

Dlg dlg;

..

.

.

Затем отобразим диалоговое окно на экране методомDoModal(). Данный метод возвращает целое значение, которое мы сохраняем в целой переменнойresult:

void CDialogsView::OnFileShowdialog()

{

Dlg dlg;

int result = dlg.DoModal();

}

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

void CDialogsView::OnFileShowdialog()

{

Dlg dlg;

int result = dlg.DoModal();

 if(result == IDOK) {

}

}

ПОДСКАЗКА:При желании можно сделать так, чтобы диалоговое окно возвращало ваше собственное значение — для этого необходимо передать его методу EndDialog().

Если пользователь действительно нажал кнопку ОК, необходимо прочесть содержимое текстовой строки из переменнойm_text. Оно хранится в документе, поэтому сначала нужно получить указатель на документ:

void CDialogsView::OnFileShowdialog()

{

Dlg dlg;

int result = dlg.DoModal();

 if(result == IDOK) {

CDialogsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

}

}

Мы создадим в документе новый объект классаCStringс именемStringData(он инициализируется в конструкторе):

// dialogsDoc.h: интерфейс класса CDialogsDoc

class CDialogsDoc: public CDocument

  {

protected: // создание только при сериализации

CDialogsDoc();

DECLARE_DYNCREATE(CDialogsDoc)

 //Атрибуты

public:

CString StringData;

В объектStringDataбудет помещен текст из переменнойm_text:

void CDialogsView::OnFileShowdialog()

{

Dlg dlg;

int result = dlg.DoModal();

 if(result == IDOK) {

CDialogsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDoc->StringData = dlg.m_text;

Invalidate();

  }

}

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

Строкаm_text получена из диалогового окна. Остается вывести ее в клиентской области. Для этого мы воспользуемся методомOnDraw(), поэтому сейчас следует форсировать его вызов при помощи метода Invalidate().

Метод OnDraw() отображает строку из диалогового окна:

void CDialogsView::OnDraw(CDC* pDC)

{

CDialogsDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0, pDoc->StringData);

}

Программаготова.Запустите ее. Откройте диалоговое окно командой File->Show Dialog.... Нажмите кнопкуНажми меня — в текстовом поле появляется строка "Текст в диалоговом окне".

Закройте диалоговое окно. Содержимое текстового поля выводится в клиентской области главного окна.

Программа dialogs работает как положено — мы запускаем диалоговое окно и получаем данные при его закрытии. Мы узнали, как пользоваться диалоговыми окнами в Visual C++.

Диалоговое окно в качестве главного

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

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

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

Когда пользователь нажимает кнопкуНажми меня, в текстовом поле появляется надпись "Диалоговое окно!".

ПОДСКАЗКА:Хотя кнопки ОК и Cancel в нашем примере остаются, их можно  удалить, как и любые другие кнопки — достаточно выделить их в редакторе диалоговых окон и нажать клавишуDelete.

Приступим к созданию новой программы с именемbuttons:

На шаге 1 AppWizard установите переключательDialog based, чтобы  базовым классом для новой программы стал классCDialog.

Завершите создание проекта кнопкойFinish.

Перейдите на вкладкуResources, откройте папкуDialogи щелкните в списке на строке главного окна программы,IDD_BUTTONSDIALOG. Запускается редактор диалоговых окон.

Разместите в диалоговом окне кнопку с надписьюНажми меня и текстовое поле.

Откройте ClassWizard, чтобы связать новую кнопку с кодом программы.

Свяжите кнопку с обработчиком OnButtont (), как было сделано в предыдущем  примере.

Дважды щелкните на строкеBN_CLICKED в спискеMessages. В результате будет создан обработчик OnButton1():

void CButtonsDIg::OnButton1()

{

}

Программа buttons готова.

ПРИМЕЧАНИЕ: В этом примере мы не стали создавать новый класс диалогового окна, потому что AppWizard сделал это за нас во время генерации кода.

Мы хотим поместить в текстовое поле строку "Диалоговое окно!". Конечно, это можно сделать при помощи переменной, связываемой с содержимым текстового поля (m_text из предыдущей программы). Тем не менее, существует более общий способ работы с управляющими элементами — можно присвоить имя всему элементу и работать с соответствующим объектом, а не с отдельным свойством элемента. Давайте посмотрим, как это делается.

Запустите ClassWizard и перейдите на вкладкуMember Variables, затем выберите из спискаControl IDs строкуIDC_EDIT1. Нажмите на кнопкуAdd Variable, и откроется окноAdd Member Variable.

Мы присвоим объекту текстового поля имяm_edit. Введите в полеMember variable name строкуm_edit; убедитесь, что в спискеCategoryвыбран пунктControl, а в спискеVariableCEdit; нажмите кнопку ОК. Появляется новая переменнаяm_edi

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

Таблица 6.2. Методы класса CEdit

Метод

Назначение

CanUndo

Указывает/ можно ли отменить операцию редактирования в текстовом поле

Cedit

Создает объект Cedit

CharFromPos

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

Clear

Удаляет текущий выделенный фрагмент текстового поля

Copy

Копирует текущий выделенный фрагмент текстового поля в буфер обмена (clipboard)

Create

Создает текстовое поле как управляющий элемент Windows и связывает его с объектом Cedit

Cut

Удаляет текущий выделенный фрагмент текстового поля и копирует текст в буфер обмена

EmptyUndoBuffer

Сбрасывает флаг отмены для текстового поля

FmtLines

Разрешает/запрещает включение «мягких» разрывов строк

GetFirstVlsibleLine

Определяет верхнюю видимую строку текстового поля

GetHandle

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

GetLimitText

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

GetLine

Получает строку текста из текстового поля

GetLineCount

Определяет количество строк в многострочном текстовом поле

GetMargins

Получает размеры левого и правого полей

GetModify

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

GetPasswordChar

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

GetRect

Получает размера форматного прямоугольника

GetSel

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

LimitText

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

LineFromChar

Получает номер строки, содержащей символ с заданным индексом

LineIndex

Получает символьный индекс (то есть смещение в символах от начала текста) для заданной строки в многострочном текстовом поле

LineLength

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

LineScroll

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

Paste

Вставляет в текстовое поле данные из буфера обмена

PosFromChar

Получает координаты левого верхнего угла символа с заданным индексом

ReplaceSel

Заменяет текущий выделенный фрагмент текстового поля заданным текстом

SetHandle

Задает логический номер локальной области памяти, которая будет использоваться для многострочного текстового поля

SetLimitText

Задает максимальный объем текста, который может храниться в объекте CEdit

SetMargins

Задает размеры левого и правого полей

SetModify

Устанавливает или сбрасывает флаг изменений

SetPasswordChar

Задает или отменяет специальный символ, отображаемый при вводе текста в текстовое поле (например, используется при вводе пароля)

SetReadOnly

Устанавливает или отменяет для текстового поля режим «только для чтения»

SetRect

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

SetRectNP

Задает размеры форматного прямоугольника для многострочного текстового поля без перерисовки

SetSel

Задает текущий выделенный фрагмент

SetTabStops

Задает позиции табуляции для многострочного текстового поля

Undo

Отменяет последнюю операцию с текстовым полем

КлассCEditявляется производным отCWindow, а последний содержит метод SetWindowText() для задания текста окна, поэтому содержимое текстового поля можно задать следующим образом:

void CButtonsDIg::OnButton1()

{

m_edit.SetWindowText(CString("Диалоговое окно!"));

}

Подготовка закончена — запустите программу и нажмите кнопкуНажми меня.Наш текст появляется в текстовом поле.

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

7. Флажки и переключатели

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

Наверняка вам приходилось встречать в Windows флажки — маленькие квадратики, либо пустые, либо содержащие пометку в виде "галочки". Если щелкнуть на флажке, егосостояниеменяется на противоположное: из установленного он становится снятым и наоборот. Флажки позволяют выбрать один или несколько вариантов из предложенного списка.

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

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

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

Начнем с программы для работы с флажками.

Работа с флажками

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

Создайте проект программыchecksи установите переключательDialog based во время работы AppWizard. После того как программа будет создана, откройте ее  главное диалоговое окно с идентификаторомIDD_CHECKS_DIALOG в редакторе диалоговых окон.

Прежде всего, удалите надпись "//TODO: Place dialog controls here" (выделите ее и нажмите клавишу Del). Теперь мы добавим в окно элементы, необходимые для  программы checks, — три флажка и текстовое поле.

Добавление флажков в программу

Добавить три флажка и текстовое поле в диалоговое окно несложно. Перетащите их из палитры (флажок — четвертая сверху кнопка в левом столбце) и растяните  текстовое поле. Редактор диалоговых окон снабжает флажки надписями Check1, Check2 и Check3.

ПОДСКАЗКА: Чтобы изменить подпись рядом с флажком, щелкните на нем правой кнопкой мыши, выберите из появившегося контекстного меню командуPropertiesи введите в полеCaptionновый текст.

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

Выравнивание элементов в редакторе диалоговых окон

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

Чтобы выровнять флажки по горизонтали, выполните командуLayout> Align. Открывается подменю — выберите в нем командуLeft, чтобы выровнять флажки по левому краю эталонного флажка.

Горизонтальное выравнивание закончено, теперь неплохо бы сделать вертикальные  интервалы между флажками одинаковыми. Для этого выполните командуLayout>Space Evenly, после которой также открывается новое подменю. Выберите из него командуDown.

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

Связывание флажков с кодом программы

ClassWizard поможет связать флажки с обработчиками сообщений. Откройте ClassWizard и найдите в нем идентификаторы трех флажков (IDC_CHECK1, IDC_CHECK2 и IDC_CHECK3). Создайте обработчики для них — для этого последовательно щелкните каждый идентификатор и дважды щелкните на сообщенииВN_СLICKEDв спискеMessagesдля каждого из них.

В результате будут созданы обработчикиOnCheck1(), OnCheck2()и OnCheck3().При помощи ClassWizard создайте переменную m_text для содержимого текстового поля.

Теперь откройте методOnCheck1():

void CChecksDIg::OnCheck1()

{

// TODO: добавьте код для обработки оповещений от элемента

}

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

ПОДСКАЗКА: Кроме метода GetCheck(), который определяет состояние флажка, можно вызвать методSetCheck(), чтобы принудительно установить или снять флажок.

Таблица 7.1. Методы класса CButton

Метод

Назначение

fcButton

Создает объект класса CButton

Create

Создает кнопку как элемент Windows и связывает ее с объектом Cbutton

DrawItem

Переопределяется для нестандартного рисования кнопки

GetBitmap

Получает логический номер растрового изображения, заданный методом SetBitmap()

GetButtonStyle

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

GetCheck

Определяет состояние кнопки

GetCursor

Получает логический номер изображения указателя, заданный методом SetCursor()

Getlcon

Получает логический номер значка, заданный методомSetlcon()

GetState

Определяет параметры состояния кнопки: установку, выделение и наличие фокуса

SetBitmap

Задает растровое изображение, отображаемое на кнопке

SetButtonStyle

Изменяет стиль кнопки

SetCheck

Задает состояние установки для кнопки

SetCursor

Задает вид указателя мыши для данной кнопки

Setlcon

Задает значок, отображаемый на кнопке

SetState

Задает состояние выделения для кнопки

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

Таблица 7.2. Стили класса CButton

Стиль кнопки

Описание

BS_3TATE

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

BS_AUT03STATE

Аналогичен стилю флажка с тремя состояниями, но состояние автоматически изменяется при выделении элемента пользователем

BS_AUTOCHECKBOX

Аналогичен стилю флажка, но состояние автоматически изменяется при выделении элемента пользователем

BS_AUTORADIOBUTTON

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

ВЫ_СНЕСКВОХ

Создает флажок в виде маленькой квадратной кнопки, текст расположен справа

BS_DEFPUSHBUTTON

Создает кнопку, окруженную жирной черной рамкой

BS_GROUPBOX

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

BS_LEFTTEXT

Выводит текст слева от кнопки

BS_OWNERDRAW

Создает кнопку с нестандартной прорисовкой

BS_PUSHBUTTON

Стандартная «нажимаемая» кнопка, при нажатии посылающая окну-владельцу сообщение WMCOMMAND

BS_RADIOBUTTON

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

Когда пользователь щелкает на флажке, мы заносим в текстовое поле строку, указанную на 1 флажке:

void  CChecksDlg::OnCheck1()

{

m_text = "Щелчок на 1 флажке";

UpdateData(false);

}

Аналогичные строки будут выводиться и для флажков 2 и 3:

void  CChecksDlg::OnCheck2()

{

m_text = "Щелчок на 2 флажке";

UpdateData(false);

}

void  CChecksDlg::OnCheck3()

{

m_text = "Щелчок на 3 флажке";

UpdateData(false);

}

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

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

Работа с переключателями

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

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

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

При помощи AppWizard создайте новую программу на базе диалогового окна, присвойте ей имяradios. Откройте диалоговое (оно же главное) окно с идентификаторомIDD_RADIOS_DIALOG.

Разместите в нем текстовое поле и три переключателя (переключатель — четвертый сверху инструмент в правом столбце). Редактор диалоговых окон снабдит последние подписямиRadiol,Radio2иRadio3, но вы можете изменить их по своему усмотрению.

Кроме того, выровняйте переключатели по вертикали и горизонтали, как это было сделано с флажками в предыдущем примере. Откройте ClassWizard, найдите в нем идентификаторы трех переключателей: IDC_RADIO1, IDС_RADIO2 и IDC_RADIO3, — создайте обработчик для всех переключателей — OnRadio1 (), OnRadio2() и OnRadio3(). Наконец, создайте переменнуюm_text и свяжите ее с содержимым текстового поля.

Связывание переключателей с кодом программы

Откройте метод OnRadio1():

void CChecksDlg::OnRadio1()

{

// TODO: добавьте код для обработки оповещений от элемента

}

Созданный обработчик будет вызываться, когда пользователь щелкает переключатель 1. В нем мы сообщим о действии пользователя, помещая в текстовое поле строку "Щелчок на 1 переключателе":

void CChecksDIg::OnRadiol()

{

m_text = "Щелчок на 1 переключателе";

UpdateData(false);

}

Координация работы переключателей

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

Добавим код для второго и третьего переключателей:

void CChecksDIg::OnRadio2()

{

     m_text = "Щелчок на 2 переключателе";

UpdateData(false);

}

  void CChecksDIg::OnRadio3()

{

m_text = "Щелчок на3 переключателе";

UpdateData(false);

}

Программа radios готова. Запустите ее и щелкните, на каком-нибудь переключателе. Программа сообщает, на каком переключателе был сделан щелчок.

Вы можете щелкать на переключателях в любой последовательности — устанавливается всегда лишь тот, который был щелкнут последним. Мы научились работать с переключателями в Visual C++!

ПОДСКАЗКА: Чтобы получить или задать состояние переключателя в программе, следует воспользоваться методамиGetState() илиSetState().

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

Совместное использование флажков и переключателей

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

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

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

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

Назовем ееseller. При помощи AppWizard создайте новый проект на базе диалогового окна, затем откройте в редакторе диалоговых окон окно с идентификаторомIDD_SELLER_DIALOG.

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

Использование групповых полей

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

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

1. Щелкните на групповом поле правой кнопкой мыши в редакторедиалоговых окон.

2. Выберите в контекстном меню командуProperties.

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

В примере левое групповое поле называетсяБукет, а правое —Цветы.

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

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

Добавьте в диалоговое окно текстовое поле. В ClassWizard создайте для него переменную m_text, чтобы упростить работу с его содержимым. Кроме того, свяжите с каждым переключателем метод-обработчик (отOnRadio1()доOnRadio4()) и откройтеOnRadio1():

void CSellerDlg::OnRadio1()

{

// TODO: добавьте код для обработки оповещений от элемента

}

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

Добавление переменных для работы с флажками

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

1. Запустите Class Wizard и перейдите на вкладкуMember Variables.

2. Щелкните на идентификаторе первого флажка IDC_CHECK1 и нажмите кнопкуAdd Variable. Открывается окноAdd Member Variable.

3. Присвойте созданной переменной имяm_check1; убедитесь, что в спискеCategoryвыбрана строкаControl, а в спискеVariable Type— строкаBOOL.

14. Нажмите кнопкуОК и повторите описанные действия для трех оставшихся флажков, присвоив переменным именаm_check2 — m_check4. В результате каждый флажок будет иметь собственную переменную.

5. Остается лишь задать нужное состояние флажков в обработчиках для каждого переключателя. Это делается по аналогии с обработчиком OnRadio1():

void CSellerDlg: :OnRadio1()

{

m_check1 = true;

m_check2 = true;

m_check3 = true;

m_check4 = true;

 }

6. Занесите в текстовое поле строку с ценой букета и вызовите UpdateData():

void CSellerDlg::OnRadio1()

{

m_check1 = true;

m_check2 = true;

m_check3 = true;

m_check4 = true;

m_text = "Цена: $4.95";

UpdateData(false);

}

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

void CSellerDlg::OnRadio2()

{

m_check1 = true;

m_check2 = false;

m_check3 = true;

m_check4 = false;

m_text = "Цена: $3.95";

UpdateData(false);

}

void CSellerDlg::OnRadio3()

{

m_check1 = false;

m_check2 = true;

m_check3 = false;

m_check4 = true;

m_text = "Цена: $2.95";

UpdateData(false);

}

void CSellerDlg::OnRadio4()

{

m_check1 = false;

m_check2 = false;

m_check3 = false;

m_check4 = false;

m_text = "Цена: $0.00";

UpdateData(false);

}

9.Программа sellerготова.Мы создали два групповых поля, занесли в них переключатели и флажки и связали управляющие элементы с кодом программы. Запустите приложение.

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

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

8. Списки, комбинированные поля и ползунки

Сейчас мы рассмотрим три новых вида элементов: списки, комбинированные поля и слайдеры. Любой программист на Visual C++ должен уметь работать с этими популярными элементами.

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

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

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

Работа со списками

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

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

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

ПОДСКАЗКА: Управляющие элементы можно отображать и в обычном (не диалоговом) окне, но в этом случае вы не сможете пользоваться редактором диалоговых окон для их создания и размещения. Например, чтобы включить в окно список, вам придется объявить новый объект класса списков,CListBoxи вызвать для него методCreate(). Вам придется задать все стили; не забудьте включить в их числоWS_VISIBLE, чтобы список отображался на экране. Учтите, что в этом случае вы не сможете пользоваться ClassWizard, поэтому вам придется самостоятельно делать все, что обычно за вас делает мастер  — редактировать схемы сообщений, задавать идентификаторы ресурсов и т. д.

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

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

Теперь добавьте текстовое поле и две надписи.

Использование надписей для вывода текста

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

ПОДСКАЗКА: Если задержать указатель мыши над инструментом для создания надписей, возникает подсказкаStatic Text.

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

Создание объекта для работы со списком

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

1. Запустите  ClassWizard и перейдите на вкладкуMember Variables.

2 . Выберите из спискаControl IDs строкуIDC_LIST1 и нажмите на кнопкуAddVariable. Открывается окноAdd Member Variable.

3. Мы свяжем переменнуюm_list с идентификатором спискаIDC_LIST1. Введите в полеMember variable name строкуm_list. (ClassWizard заранее помещает в поле префиксm_).

4. Проследите, чтобы в спискеCategory была выбрана строкаControl: создаваемая переменная должна относиться ко всему элементу, а не к отдельному свойству (которое для списков соответствует текущей выбранной строке). ClassWizard присваивает переменной классCListBox.

5. Закройтеокно Add Member Variable кнопкой ОК. В программе появилась переменнаяm_list классаCListBox; через нее мы сможем работать со списком.

6. Можно переходить к заполнению списка. Методы класса CListBox перечислены в табл. 8.1.

Таблица 8.1. Методы класса CListBox

Метод

Название

AddString

Добавляет в список строку

CharToItem

Переопределяется для нестандартной обработки сообщения WM_CHAR

CListBox

Конструирует объект класса CListBox

CompareItem

Вызывается для определения положения новой строки в отсортированном списке с нестандартной прорисовкой

Create

Создает список как элемент Windows и связывает его с объектом CListBox

DeleteItem

Вызывается при удалении пользователем строки из списка с нестандартной прорисовкой

DeleteString

Удаляет строку из списка

Dir

Включает в список имена файлов

DrawItem

Вызывается, когда возникает необходимость в нестандартной прорисовке списка

FindString

Ищет в списке заданный текстовый фрагмент

FindStringExact

Ищет первую строку списка, точно совпадающую с заданной строкой

GetAnchorIndex

Получает индекс текущего опорного элемента в списке (значения индекса начинаются с нуля)

GetCaretIndex

Получает индекс строки, содержащей фокус в списке с множественным выделением

GetCount

Возвращает количество строк в списке

GetCurSel

Получает индекс текущей выбранной строки (значения индекса начинаются с нуля)

GetHorizontalExtent

Возвращает количество пикселей, на которое список может прокручиваться по горизонтали

GetItemData

Возвращает 32-разрядную величину, связанную со строкой списка

GetItemDataPtr

Возвращает 32-разрядную величину, связанную со строкой списка, в виде указателя

GetItemHeight

Определяет высоту строк в списке

GetItemRect

Возвращает внешний прямоугольник для текущей отображаемой строки списка

GetLocale

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

GetSel

Возвращает информацию о том, выделена ли заданная строка списка

GetSelCount

Возвращает количество выделенных строк для списков с множественным выделением

GetSelItems

Возвращает индексы выделенных строк

GetText

Копирует строку списка в буфер

GetTextLen

Возвращает длину строки списка в байтах

GetTopIndex

Возвращает индекс первой отображаемой строки списка

InitStorage

Выделяет блоки памяти для хранения строк списка

InsertString

Вставляет строку в заданную позицию списка

ItemFromPoint

Возвращает индекс строки, расположенной ближе всего к заданной точке

MeasureItem

Вызывается при создании списка с нестандартной прорисовкой для определения размеров

ResetContent

Удаляет из списка все строки

SelectString

Ищет и выбирает строку в списке с единичным выделением

SelItemRange

Выбирает или отменяет выбор интервала строк в списке с множественным выделением

SetAnchorIndex

Задает опорную точку в списке с множественным выделением для расширенного выбора строк

SetCaretIndex

Передает фокус строке с заданным индексом в списке с множественным выделением

SetColumnWidth

Задает ширину столбца в многостолбцовом списке

SetCurSel

Выбирает строку списка.

SetHorizontalExtent

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

SetItemData

Задает значение 32-разрядной величины, связанной со строкой списка

SetItemDataPtr

Присваивает 32-разрядной величине, связанной со строкой списка, заданный указатель

SetItemHeight

Задает высоту строк в списке

SetLocale

Задает идентификатор локального контекста для списка

SetSel

Выбирает строки в списке с множественным выделением или отменяет выбор

SetTabStops

Задает положение позиций табуляции в списке

SetTopIndex

Задает индекс первой отображаемой строки списка

VKeyToItem

Переопределяется для обработки сообщенияWM_KEYDOWN

Инициализация данных в списке

Инициализация данных диалогового окна выполняется в методеOnInitDialog() — найдите его в программе (это длинный метод, поэтому для краткости мы опускаем большую часть его кода):

BOOL CListsDIg::OnInitDialog()

{

CDialog:: OnInitDialog();

}

ПРИМЕЧАНИЕ: Мы выполняем инициализацию в методе OnInitDialog(), а не в конструкторе, поскольку к моменту вызова OnInitDialog() все управляющие элементы и само диалоговое окно будут созданы и готовы к использованию.

В методе OnInitDialog() мы должны заполнить список строками, чтобы к моменту отображения диалогового окна он содержал необходимую информацию. Мы занесем в список 12 строк с именами Строка 1, Строка 2 и т. д. и воспользуемся для этого методом AddString() объекта m_list:

BOOL CListsDIg::OnInitDialog() {

CDialog::OnInitDialog();

m_list.AddString("Строка 01");

m_list.Add8tring("Строка 02");

m_list.AddString("Строка 03");

m„list.AddString("Строка 04");

m_list.AddSt ring("Строка 05");

m_list.AddString("Строка 06");

m_list.AddString("Строка 07");

m_list.AddSt rihg("Строка 08");

m_list.AddString("Строка 09"):

m_list.AddString("Строка 10");

m_list.AddString("Строка 11");

m_list.Add8tring("Строка 12");

// Добавить команду "About..." в системное меню.

}

Возможно, у вас возникнет вопрос — почему мы используем запись "Строка 01" а не "Строка 1"? Дело в том, что строки списка должны выводиться по возрастанию от 1 до 12, а по умолчанию содержимое списка сортируется  алфавитной порядке. Следовательно, если бы во вторую строку был занесен текст "Строка 1", то "Строка 12" оказалась бы в списке перед ней.

ПОДСКАЗКА: Если вы не хотите сортировать содержимое списка, щелкните список правой кнопкой мыши в редакторе диалоговых окон, выберите из контекстного меню командуProperties, перейдите на вкладкуStyles в открывшемся окне и снимите флажокSort.

Заносимые в список строки автоматически нумеруются, и в дальнейшем к ним  можно обращаться по значению индекса. Первая строка списка имеет индекс 0,  вторая — 1 и т. д. Когда мы в своей программе запрашиваем у объекта списка выбранную пользователем строку, он возвращает индекс этой строки.

ПОДСКАЗКА: Помимо текста, содержащегося в  каждой строке списка, вы можете связать с ней дополнительную 32-разрядную величину - воспользуйтесь методомSetItemData(). Например, со строками можно связать пути и имена файлов, входящих в список. Для получения этих данных применяетсяметод GetItemData().

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

Обработка двойных щелчков в списках

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

Затем необходимо определить момент, когда пользователь дважды щелкнет внутри списка, и тут на помощь приходит ClassWizard. Откройте его, выберите идентификаторIDC_LIST1 и дважды щелкните на строке сообщенияLBN_DBLCLICK в спискеMessages. Это сообщение посылается программе от списка, когда пользователь дважды щелкает на одной из его строк. ПрефиксLBN означает "List Box Notification", то есть "оповещение от списка"; это семейство состоит из сообщенийLBN_DBLCLICK, LBN_ERRSPACE, LBN_KILLFOCUS, LBN_SELCANCEL, LBN_SELCHANGE и LBN_SETFOCUS. ClassWizard предлагает присвоить новому обработчику имяOnDblclkList1(). Подтвердите предложенное имя, нажав кнопку ОК.

ClassWizardсоздаетметод OnDblclklist1():

void CListsDlg::OnDblclkList1()

{

// TODO: добавьте код для обработки оповещений от элемента

}

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

Определение выбранной строки

Для определения текущего выбранного пункта мы воспользуемся методомGetCurSel() классаCListBox. Он возвращает индекс строки, на которой пользователь дважды щелкнул левой кнопкой мыши. Как получить фактическое содержимое этой строки в списке (скажем, "Строка 02")? При помощи метода GetText() класса CListBox, который заполняет переданный ему объект текстом нужной строки:

void CListsDlg::OnDblclkList1()

{

m_list.GetText(m_list.GetCurSel(), m_text);

}

Текст заносится в переменную m_text. Осталось лишь вызвать методUpdateData() для того, чтобы обновитьm_text на экране:

void CListsDlg::OnDblclklist1()

{

m_list.GetText(m_list.GetCurSel(). m_text);

UpdateData(false);

}

Запустите программу и дважды щелкните на одном из пунктов списка. Программа сообщает о выборе строки, отображая ее содержимое в текстовом поле. Умение работать со списками заметно увеличивает наш арсенал средств VisualC++.

Мы научились работать со списками; пора переходить к новому типу управляющих элементов — комбинированным полям.

9. Работа с комбинированными полями

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

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

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

Создайте в AppWizard программуcombos на базе диалогового окна. Все готово к  заполнению комбинированного поля данными.

Инициализация комбинированного поля

Комбинированные поля инициализируются в методе OnInitDialog():

BOOL CCombosDlg::OnInitDialog()

{

CDialog::OnInitDialog();

}

Теперь можно переходить к инициализации комбинированного поля. Она выполняется практически так же, как и в предыдущем примере. При помощи ClassWizard создайте переменную, представляющую комбинированное поле, и присвойте ей имяm_combo. Она должна принадлежать к классуCComboBox. Методы класса CComboBoх перечислены в табл. 8.2.

Таблица 8.2. Методы класса CComboBox

Метод

Назначение

AddString

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

CComboBox

Конструирует объект класса CComboBox

Clear

Удаляет текущий выделенный фрагмент в текстовом поле

Compareltem

Вызывается для определения положения новой строки в отсортированном комбинированном поле с нестандартной прорисовкой

Copy

Копирует текущий выделенный фрагмент в буфер обиена

Create

Создает комбинированное поле как элемент Windows и связывает его с объектом CComboBox

Cut

Удаляет текущий выделенный фрагмент и помещает его в буфер обмена

DeleteItem

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

DeleteString

Удаляет строку из списка

Dir

Включает в список комбинированного поля имена файлов

Drawltem

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

FindString

Ищет в списке первую строку с заданным префиксом

FindStringExact

Ищет в списке первую строку, которая точно совпадает с заданной

GetCount

Возвращает количество строк в списке

GetCurSel

Получает индекс текущей выбранной строки списка (если она есть)

GetDroppedControlRect

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

GetDroppedState

Определяет, виден ли в данный момент раскрывающийся список

GetDroppedWidth

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

GetEditSel

Определяет позицию первого и последнего символов текущего выделенного фрагмента

GetExtendedUI

Определяет, каким пользовательским интерфейсом обладает комбинированное поле — стандартным или расширенным

GetHorizontalExtent

Возвращает количество пикселей, на которое список может прокручиваться по горизонтали

GetItemData

Возвращает 32-разрядную величину, связанную со строкой списка

GetIternDataPtr

Возвращает 32-разрядную величину, связанную со строкой списка, в виде указателя

GetItemHeight

Определяет высоту строк в списке

GetLBText

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

GetLBTextLen

Получает длину-строки в списке

GetLocale

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

GetTopIndex

Возвращает индекс первой отображаемой строки списка

InitStorage

Выделяет блоки памяти для хранения строк списка

InsertString

Вставляет строку в список

LimitText

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

MeasureItem

Вызывается при создании комбинированного поля с нестандартной прорисовкой для определения его размеров

Paste

Вставляет в текстовое поле содержимое буфера обмена

ResetContent

Удаляет все содержимое списка и текстового поля

SelectString

Ищет и выбирает строку в списке

SetCurSel

Выбирает строку в списке

SetDroppedWidth

Задает минимальную допустимую ширину раскрывающегося списка

SetEditSel

Выделяет символы в текстовом поле

SetExtendedUI

Задает пользовательский интерфейс комбинированного поля — стандартный или расширенный

SetHorizontalExtent

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

SetItemData

Задает значение 32-разрядной величины, связанной со строкой списка

SetItemDataPtr

Присваивает 32-разрядной величине, связанной со строкой списка, заданный указатель

SetItemHeight

Задает высоту строк списка или высоту текстового поля

SetLocale

Задает идентификатор локального контекста "S для комбинированного поля

SetTopIndex

Задает индекс первой отображаемой строки списка

ShowDropDown

Отображает или скрывает список

ПОДСКАЗКА: Вы также можете связать переменную со значением комбинированного поля — им считается текущее содержимое текстов поля.

Как и в предыдущем примере, для занесения в список необходимых строк ("Строка 01" — "Строка 12") мы воспользуемся методомAddString():

BOOL CCombosDlg::OnInitDialog()

{

CDialog: :OnInitDialog();

m_combo.AddString("Строка01");

m_combo.AddString("Строка02");

m_combo.AddString("Строка03");

m_combo.AddString("Строка04");

m_combo.AddString("Строка05");

m_combo.AddString("Строка06");

m_combo.AddString("Строка07");

m_combo.AddString("Строка08");

rn_combo.AddString("Строка09");

m_combo.AddString("Строка10");

m_combo.AddString("Строка11");

m_combo.AddString("Строка 12");

// Добавить команду "About..." в системное меню.

}

Кроме того, необходимо выбрать из списка первую строку ("Строка 01"), чтобы при первом появлении комбинированного поля на экране в текстовом поле отображалось ее содержание (в противном случае оно окажется пустым):

BOOL CCombosDlg::OnInitDialog()

{

CDialog: :OnInitDialog();

m_combo.AddString("Строка01");

m_combo.AddString("Строка02");

m_combo.AddString("Строка03");

m_combo.AddString("Строка04");

m_combo.AddString("Строка05");

m_combo.AddString("Строка06");

m_combo.AddString("Строка07");

m_combo.AddString("Строка08");

rn_combo.AddString("Строка09");

m_combo.AddString("Строка10");

m_combo.AddString("Строка11");

m_combo.AddString("Строка 12");

m_combo.SetCurSel(0);

// Добавить команду "About..." в системное меню.

}

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

Определение выбранной строки

Наша программа должна сообщать, какую строку списка выбрал пользователь. В этой ситуации комбинированное поле посылает программе сообщениеCBN_SELCHANGE. Префикс CBN означает "Combo Box Notification", то есть "оповещение от комбинированного поля". Это семейство состоит из сообщенийCBN_CLOSEUP, CBN_BLCLICK, CBN_DROPDOWN, CBN_EDITCHANGE, CBN_EDITUPDATE, CBN_ERRSPACE, CBN_KILLFOCUS, CBN_ SELCHANGE, CBN_SELENDCANCEL, CBN_SELENDOK и CBN_SETFOCUS. С помощью ClassWizard свяжите обработчик с сообщением CBN_SELCHANGE.

ПОДСКАЗКА: Если вы забудете, какие сообщения управляющий элемент может посылать программе, найдите его в ClassWizard на вкладкеMessage Maps. Все возможные сообщения перечислены в спискеMessages.

ClassWizard предлагает присвоить новому методу имяOnSelchangeCombol(). Подтвердите предложенное имя и откройте код метода:

void CCombosDIg::OnSelchangeCombol()

{

// TODO: добавьте код для обработки оповещений от элемента

}

Вызов этого метода означает, что пользователь выбрал из списка комбинированного поля новую строку; мы хотим сообщить об этом в текстовом поле, используя переменную m_text. Делается это так же, как и в случае со списком, следует вызвать методGetCurSel(), после чего обновить содержимое текстового поля методом UpdateData():

void CCombosDIg::OnSelchangeCombol()

{

m_combo.GetLBText (m_combo. GetCurSel (), m_text);

UpdateData(false);

}

ПОДСКАЗКА: Чтобы перехватить изменения, вносимые в содержимое текстовой части комбинированного поля, следует с помощью ClassWizard организовать в программе обработку сообщенияCBN_EDITCHANGE.

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

10. Использование ползунков

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

Когда пользователь перетаскивает бегунок мышью, программа должна выводить eгo новое положение по шкале от 1 до 100 (крайнее левое положение — 1, крайнее правое — 100).

1. С помощью AppWizard создайте программуsliders на базе диалогового окна.

2. Разместите в окне необходимые элементы — текстовое поле, две надписи и ползунок (восьмой сверху в левом столбце палитры). Ему присваивается идентификаторIDC_SLIDER1.

3. С помощью ClassWizard добавьте в программу новую переменную классаCSliderCtrl. Методы этого класса перечислены в табл. 8.3.

Таблица 8.3. Методы класса CSlider

Метод

Назначение

ClearSel

Снимает текущее выделение

ClearTics

Удаляет метки со шкалы

Create

Создает ползунок как элемент Windows и связывает его с объектом CSliderCtrl

CSliderCtrl

Создает объект класса CSliderCtrl

GetChannelRect

Получает размеры канала ползунка

GetLineSize

Получает размер строки1 для ползунка

GetNumTics

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

GetPageSize

Получает размер страницы для ползунка

GetPos

Получает текущую позицию ползунка

GetRange

Получает минимальное и максимальное значения шкалы

GetRangeMax

Получает максимальное значение шкалы

GetRangeMin

Получает минимальное значение шкалы

GetSelection

Получает текущий выделенный интервал

GetThumbRect

Получает размеры бегунка

GetTic

Возвращает позицию заданной метки шкалы

GetTicArray

Возвращает массив с позициями меток шкалы

GetTicPos

Получает позицию заданной метки в системе клиентских координат

SetLineSize

Задает размер линии для ползунка

SetPageSize

Задает размер страницы для ползунка

SetPos

Задает текущую позицию ползунка

SetRange

Задает минимальное и максимальное значения шкалы

SetRangeMax

Задает максимальное значение шкалы

SetRangeMin

Задает минимальное значение шкалы

SetSelection

Задает текущий выделенный интервал

SetTic

Задает позицию метки шкалы

SetTicFreq

Задает частоту меток на шкале ползунка

VerifyPos

Проверяет, принадлежит ли текущая позиция бегунка заданному интервалу

Созданный ползунок необходимо инициализировать.

Инициализация ползунка

При создании ползунка необходимо задать его интервал. Эта величина определяет возможные позиции бегунка от крайнего левого до крайнего правого положения. В нашем примере ползунок может принимать значения от 1 до 100. Эти значения задаются методамиSetRangeMin() иSetRangeMax() классаCSliderCtrl (второй параметр этих методов показывает, нужно ли перерисовывать ползунок после изменения интервала; передавая значениеfalse, мы отказываемся от перерисовки):

Размером строки называется минимальный размер смещения бегунка, размером страницы — размер смещения при щелчке на шкале слева или справа.

BOOL CSlidersDIg::OnInitDialog()

{

CDialog::ОnInitDialog();

m_slider.SetRangeMin(1, false);

m_slider.SetRangeMax(100, false);

// Добавить команду "About..." в системное меню.

}

Чтобы вывести в текстовом поле исходное состояние ползунка (бегунок находится в позиции 1), следует связать переменную с содержимым текстового поля и присвоить ей значение "1":

BOOL CSlidersDIg::OnInitDialog()

{

CDialog::ОnInitDialog();

m_slider.SetRangeMln(1, false);

m_slider.SetRangeMax(100, false);

m_text = "1";

 UpdateData(false);

// Добавить команду "About..." в системное меню.

}

Ползунок готов к работе, но возникает вопрос — как узнать о перемещении пользователем бегунка?

ПОДСКАЗКА: Ползунки могут быть как горизонтальными, так и вертикальными. Ориентация задается в окнеProperties редактора диалоговых окон (щелкните на ползунке правой кнопкой мыши и вы6ерите командуProperties в контекстном меню).

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

При перемещении бегунка элемент посылает сообщениеWM_HSCROLL (WM_VSCROLL для вертикальных ползунков). При помощи ClassWizard свяжите сообщениеWM_HSCROLL с методомOnHScroll() (OnVScroll() для вертикальных ползунков). Проследите, чтобы при добавлении обработчика в ClassWizard был выбран идентификатор объектаCSliderDlg. Метод OnHScrol() обрабатывает сообщения, предназначенные для диалогового окна:

void CSlidersDlg: :OnHScroll(UINT nSBCode, UINT nPos, CScrollBar*    pScrollBar)

{

 // TODO: добавьте код обработки сообщения

 // и/или вызовите обработчик по умолчанию

}

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

Таблица 8.4. Сообщения о прокрутке в Windows

Код операции

Значение

SB_ENDSCROLL

Завершение прокрутки

SB_LEFT

Прокрутка в крайнюю левую позицию

SB_LINELEFT

Прокрутка влево

SB_LINERIGHT

Прокрутка вправо

SB_PAGELEFT

Прокрутка на страницу влево

SB_PAGERIGHT

Прокрутка на страницу вправо

SB_RIGHT

Прокрутка в крайнюю правую позицию

SB_THUMBPOSITION

Прокрутка в заданную позицию

SB_THUMBTRACK

Перемещение бегунка в заданную позицию

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

void CSlidersDlg: :OnHScroll(UINT nSBCode, UINT nPos, CScrollBar*    pScrollBar)

   {

if(nSBCode == SB_THUMBPOSITION){

}

else{

CDialog::OnHScroll(nSBCode, nPos, pScrollBar);

} }

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

Начнем с создания переменнойm_text, связанной с содержимым текстового поля. Мы хотим присвоить ей значение параметраnPos, но как это сделать? Объектm_text относится к классуCString, а параметрnPos имеет целый тип. В следующем разделе мы научимся представлять целые числа в виде текстовой строки.

Отображение чисел в текстовых полях

Для нашей цели можно воспользоваться методом Format() класса CString. Передаваемая ему форматная строка ничем не отличается от форматных строк, используемых в строковых функциях С. Это означает, что форматирование параметраnPos в виде длинного целого и его последующее занесение в текстовое поле могут быть выполнены так:

void CSlidersDIg::OnHScroll(UINT nSBCode. UINT nPos. CScrollBar*

pScrollBar)

{

if(nSBCode == SB_THUMBPOSITION){

m_text.Format("%ld", nPos);

UpdateData(false);

}

else{

CDialog::OnHScroll(nSBCode, nPos. pScroll Bar);

}

}

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

ПОДСКАЗКА: Если в вашей программе содержится несколько типов элементов с возможностью прокрутки (ползунки, стандартные полосы прокрутки и т. д.), учтите, что при горизонтальной прокрутке все эти элементы будут вызывать методOnHScroll(). Чтобы определить, кто именно вызвалOnHScroll(), поочередно сравните элемент, переданный методу в качестве параметра (то есть элемент, на который ссылается указательpScrollBar), с различными элементами своей  программы. Существует и другой способ — вы можете определить тип  элемента, вызвавшегоOnHScroll(), с помощью макроса Visual C++RUNTIМE_CLASS (макросом называется именованный набор заранее написанных инструкций). Макрос возвращает указатель на объектCRuntmeClass, несущий информацию о классе объекта.

11. Работа с файлами

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

Из предыдущих программ мы уже знаем, что созданные с помощью AppWizard приложения (не основанные на диалоговых окнах) имеют встроенное меню File с командами Save As, Open и New; мы узнаем, как наделить эти команды полезными функциями. Мы воспользуемся встроенными файловыми средствами программы - эта методика известна под названиемсериализацииданных. После должной подготовки процесса сериализации данных в методе Serialize()документа пользователь сможет пользоваться файловыми командами меню File — Open, Save и т. д. Здесь мы узнаем, как это делается, и научимся выполнять сериализацию (то есть чтение и запись на диск) как встроенных классов Visual C++ (например, CString), так и нестандартных, созданных нами классов.

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

Что такое сериализация?

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

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

Создание программы writer

При помощи AppWizard создайте однодокументную (SDI) программу writer:

1. Начнем с добавления кода, который позволит приложению получать и отображать вводимые пользователем символы. Как и прежде, они будут храниться в объектеStringDataклассаCString, который принадлежит документу:

// writerDoc.h :интерфейскласса CWriterDoc

class CWriterDoc : public CDocument

{

protected: // создается только при сериализацим

CWriterDoc();

DECLARE_DYNCREATE(CWriterDoc)

//Атрибуты

public:

CString StringData;

2. Объект StringData необходимо инициализировать пустой строкой в конструкторе документа:

CWriterDoc::CWriterDoc()

{

StringData = "";

}

При помощи ClassWizard свяжите сообщениеWM_CHAR с методомOnChar() и затем добавьте код для сохранения вводимых символов в объекте StringData:

void CWriterView::OnChar(UINT nChar, UINT nRepCnt, DINTnFlags)

{

 CWriterDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);.

pDoc->StringData += nChar;

Invalidate();

CView::OnChar(nChar, nRepCnt, nFlags);

}

4. Обратите внимание - с вводом очередного символа мы объявляем вид недействительным. Чтобы вывести в окне новую строку, добавьте в метод OnDraw() следующий фрагмент:

void CWriterView::OnDraw(CDC* pDC)

{

CWriterDoc* pDoc = GetDocumentO;

ASSERT_VALID(pDoc);

pDC->TextOut(0, 0. pDoc->StringData):

}

5. Наша программа получает символы от пользователя, отображает их в окне и сохраняет в объектеStringData.

Теперь необходимо сделать так, чтобы пользователь мог сохранить данные программы (то есть объектStringData), и загрузить их с диска.

Сериализация объекта StringData

Класс документа (файл writerDoc.cpp) содержит встроенный методSerlalize(), который выглядит следующим образом:

void CWriterDoc::Serialize(CArchive& ar) {

if (ar.IsStoring())

{

// TODO: добавьте код сохранения

}

else

{

// TODO: добавьте код загрузки

} }

Именно в этом методе должна происходить сериализация объекта StringData. Методу Serialize() передается ссылка на объект аrклассаCArchive (вообще говоря, ссылка содержит адрес объекта, но в программе ее можно использовать как обычный объект); работа с объектомаrпрактически не отличается от работы с потокамиcoutиcin. Сначала мы вызываем методIsStoring()объектааг, чтобы узнать, был ли метод вызван для записи на диск; в этом случае сериализация StringData выполняется так:

void CWriterDoc::Serialize(CArchive& ar) {

if (ar.IsStoring())

{

ar<<StringData;

}

else

{

// TODO:добавьтекодзагрузки

}

}

Если же потребуется загрузить объект StringData с диска, будет использована другая строка программы:

void CWriterDoc::Serialize(CArchive& ar) {

if (ar.IsStoring())

{ ar<<StringData;

}

else

{

ar>>StringData;

}

}

В принципе это все, что нужно, но мы добавим последний штрих— когда пользователь заносит в объект StringData новые символы, мы сообщаем документу об изменении данных. Если пользователь захочет выйти из программы, не сохранив данные, программа выведет хорошо знакомое всем пользователям Windows диалоговое окно с предложением сохранить изменения в документе.

Чтобы сообщить приложению об изменении данных, мы вызовем вOnChar() метод объекта документа SetModifiedFlag():

void CWriterView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

CWriterOoc* pDoc = GetDocument();

 ASSERT_VALID(pDoc);

 pDoc->StringData += nChar;

Invalidate();

 pDoc->SetModifiedFlag();

CView::OnChar(nChar, nRepCnt, nFlags);

}

ПОДСКАЗКА: Флаг изменения документа сбрасывается, когда пользователь сохраняет документ на диске.

Запустите программу. На рисунке показан текст, введенный в программу и готовый к сохранению на диске.

Когда пользователь выбирает в меню командуFile> Save As, открывается диалоговое окноSave As. Мы сохраним данные программы в виде файла text.dat.

После сохранения пользователь может снова прочитать файл командойFile> Open, которая открывает диалоговое окноOpen. Выбранный файл text.dat загружается в программу.

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