Visual C++

advertisement
1
Visual C++
Обзор
Visual C++ входит в состав Visual Studio, которая представляет собой семейство
программных продуктов, предназначенных для разработки ПО. Встроенная справочная
система интегрирована с программой просмотра библиотеки Microsoft Developer Network
(MSDN).
Visual C++ представляет собой интегрированную среду разработки.
Процесс создания программы в Visual C++ следующий:
Таким образом одно приложение получается из группы исходных файлов,
представляющих собой один проект. Имеется один текстовый файл проекта с
расширением .DSP, который содержит список всех файлов, входящих в проект, параметры
компилятора и компоновщика и т.п. Проекты также можно объединить в группу,
называемую рабочим пространством. Файл рабочего пространства имеет расширение
.DSW. Чаще всего рабочее пространств состоит только из одного проекта.
Компоненты, входящие в состав Visual C++:
1. Редактор исходного текста;
2. Компилятор С/С++;
3. Редакторы ресурсов: редактор меню, графический редактор диалоговых окон и т.п.;
4. Компилятор ресурсов;
5. Компоновщик;
6. Отладчик;
2
7. AppWizard – мастер приложений – генератор кода, создающий рабочую заготовку
Windows приложения в соответствие с тем что было задано программистом в
диалоговых окнах;
8. ClassWizard – мастер классов – программа, генерирующая и обновляющая
описание и реализацию класса в соответствие с тем, что программист задает в
диалоговых окнах;
9. Средство просмотра исходного кода позволяет анализировать программу. Работает
в следующих режимах:
a. Definitions and References (определения и ссылки). Выбрав что-либо (класс,
функцию, переменную) можно увидеть где они определены и используются;
b. Call Graph/Caller Graph (схемы вызываемых и вызывающих функций) –
графическое представление функций, вызываемых ею, либо вызывающих
ее;
c. Derived Class Graph/Base Class Graph (схемы производных и базовых
классов) – графическое представление иерархии классов;
d. File Outline (конспект файла) – выписываются классы, их члены и
свободные функции с указанием на все места программы, где они
определены и используются.
10. Интерактивная справочная система – MSDN, MSDN Library Viewer.
11. Диагностические утилиты позволяющие: показать дерево процессов, потоков и
окон, программ для тестирования элементов управления ActiveX , средства
построения справочных файлов и т.п;
12. Средство контроля исходного кода при коллективной работе над проектом
(SourceSafe);
13. Components and Controls Gallery – галерея компонентов и элементов управления –
служит для организации повторного использования кода. Оперирует с тремя
типами модулей:
a. Элементы управления ActiveX;
b. Модули исходного кода на С++;
c. Компоненты Visual C++;
14. Microsoft Foundation Class Library 6.0 (библиотека MFC). Visual C++, вообще,
позволяет создавать Windows-приложения на языке С, используя только Win32
API, но обычно приложения создаются с использованием каркаса приложений –
библиотеки MFC. MFC определяет общую структуру приложения, предоставляет
классы, которые будут использованы в приложении. Инструменты AppWizard и
ClassWizard специфичны для библиотеки MFC.
15. Microsoft Active Template Library (библиотека ATL). Средство построения
элементов управления ActiveX, отдельное от MFC.
Конфигурации Win32 Debug и Win 32 Release
На панели инструментов Build находится список, в котором можно выбрать
названия конфигураций сборки, представляющих разные наборы параметров сборки.
Параметр
Окончательная сборка (Release) Отладочная сборка (Debug)
Отладка по исходным
Отключена
Включена
текстам
Диагностические
Отключены
Включены
макросы MFC
Библиотеки
Рабочие библиотеки MFC
Отладочные библиотеки
MFC
Оптимизация
Оптимизация по скорости
Без оптимизации –
ускоренная компиляция
3
Разработка приложений ведется в режиме отладочной сборки, а перед поставкой
программа собирается заново в режиме окончательной сборки. По умолчанию результаты
и промежуточные файлы сборки проекта, созданные в отладочном режиме хранятся в
подкатологе Debug, а файлы для окончательной сборки – в подкатологе Release.
При необходимости можно создавать собственные конфигурации и назначать свои
каталоги.
Два способа запуска программы
Visual C++ позволяет запускать программу непосредственно (CTRL+F5) и в
отладчике (F5)
Каркас приложений библиотеки MFC
Каркас приложений (application framework) – интегрированный набор объектноориентированных программных компонентов, обеспечивающих все, что нужно для
программы общего назначения.
Отличие каркаса приложений от обычной библиотеки классов заключается в
следующем. Библиотека классов – это набор взаимосвязанных классов, которые могут
быть использованы в приложении. Каркас приложения – это надмножество библиотеки
классов. В отличие от библиотеки классов, он определяет структуру самой программы.
AppWizard – программа, которая генерирует основу конкретного приложения на
основе каркаса приложений MFC.
Соглашения об именах библиотеки MFC
В качестве префикса, обозначающего имя класса, библиотека MFC использует
заглавную букву С от слова “class”, за которым идет имя, характеризующее его
назначение. (CWnd, CWinApp, CDialog).
Для имен методов класса используется три способа. При первом способе имя
объединяет глагол и существительное (LoadIcon, DrawText). При втором имя состоит
только из существительных (DialogBox). Для функций, предназначенных для
преобразования обычными являются имена типа XtoY.
Для полей классов библиотеки MFC принят следующий способ назначения имен:
обязательный префикс m_ (от class member – член класса), затем идет префикс,
характеризующий тип данных, далее – содержательное имя переменной. Например
m_pMainWnd, p – префикс, соответствующий указателю.
Для переменных, не являющихся полями классов, способ формирования имен тот
же, но префикс m_ не используется.
Основные составляющие приложения на базе библиотеки MFC
Прежде всего, такое приложение всегда содержит один глобальный статический
объект, являющийся объектом-приложением Windows. Это объект класса, производного
от CWinApp. Основное его назначение – создание процесса в системе, поэтому, то что он
может быть только один совершенно логично.
Далее следует обратить внимание на отсутствие в коде функции WinMain – она
скрыта в каркасе приложения. При запуске приложения Windows вызывает эту функцию,
та ищет глобально сконструированный объект класса, производного от CWinApp. Далее
вызывается его виртуальная функция-элемент InitInstance, которая делает все
необходимое для создания и отображения на экране основного окна-рамки программы.
InitInstance единственная функция-элемент CWinApp, которая обязательно должна быть
переопределена в производном классе «приложение». В классе CWinApp эта функция не
делает ничего. AppWizard обычно сам создает реализацию этой функции в соответствии с
тем, что задал разработчик при создании проекта.
4
Если инициализация прошла успешно, далее функция WinMain запускается цикл
обработки сообщений. (Этот цикл выбирает сообщения из очереди сообщений
приложения и направляет их обратно к Windows, которая затем вызывает оконную
функцию программы.) Для этого в WinMain вызывается функция-элемент классаприложения Run. Эта функция не требует переопределения, хотя и допускает его.
Если пришло сообщение WM_QUIT, выполнение цикла обработки сообщений
заканчивается и осуществляется завершение работы приложения. При этом вызывается
функция-элемент класса-приложения ExitInstance. Реализованная версия этой функции
записывает сохраняемые настройки в INI-файл приложения. Эту функцию следует
переопределять только если есть необходимость в проведении специальной обработки при
завершении приложения – возврат ресурсов.
Каждое приложение обязательно должно создавать (и разрушать) главное окно. С
одной стороны окно – это объект одного из оконных классов библиотеки MFC или класса,
производного от них. С другой – окно Windows это внутренняя структура данных ОС,
которая формируется функцией интерфейса прикладного программирования Win32 API
CreateWindow, а разрушается функцией DestroyWindow. Таким образом, приложение
обычно содержит один или более классов, определяющих оконные объекты. В функции
InitInstance класса, определяющего приложение, обязательно выполняются следующие
действия – создается объект «главное окно программы» и это окно регистрируется в
Windows.
Классы окон библиотеки MFC
В Windows существует три основных типа окон:
- перекрывающиеся окна (например главное окно приложения);
- вспомогательные или всплывающие окна. Обычно они отображают
информацию короткого промежутка времени – например диалоговые окна или
окна сообщений. Основное отличие – они всегда отображаются поверх
остальных окон;
- дочерние окна – создаются тогда, когда у приложения уже есть главное окно.
Такие окна никогда не отображаются вне родительского окна. Пример – окна
документов, элементы управления.
В иерархии классов MFC все классы, предназначенные для работы с окнами,
являются производными от CWnd.
Иерархия классов
CObject
CCmdTarget
CWnd
- CFrameWnd (фреймы)
o CMDIChildWnd
o CMDIFrameWnd
o CMiniFrameWnd
o COleIPFrameWnd
- CControlBars (панели элементов управления)
- CPropertySheet (свойства)
- CDialog (блоки диалога)
- CView (представления)
- Элементы управления
Класс CWnd
Является базовым для всех классов окон. Служит для создания дочерних окон.
Включает:
- поле HWND CWnd::m_hWnd – дескриптор окна Windows – взаимодействие
класса окна с ОС;
5
-
конструктор;
функции инициализации:
o Create, CreateEx – создание окна Windows и присоединение его к объекту
класса;
o GetStyle, GetExStyle – позволяют узнать стиль окна;
o Attach, Detach – позволяют присоединить или отсоединить окно Windows
от объекта класса;
o GetSafeWnd – получение дескриптора окна Windows;
- функции состояния окна: позволяют изменить активное окно, окно переднего
плана, установить фокус ввода, модифицировать стиль окна и т.п.
- функции изменения размеров и позиции окна;
- функции доступа к окну – например функция, которая позволяет определить,
существует ли в системе окно, заданное именем класса и именем окна;
- функции обновления-перерисовки:
o UpdateWindow – обновляет рабочую область, посылая сообщение
WM_PAINT,
o Invalidate – задает область модификации для последующей перерисовки
при обработке ближайшего сообщения WM_PAINT;
o ShowWindow – отображает окно на экране;
- функции преобразования координат – взаимное преобразование рабочих и
экранных координат окна;
- функции работы с текстом;
- функции работы с сообщениями.
CFrameWnd
Является непосредственным потомком CWnd. Служит для создания
перекрывающихся и всплывающих окон – является базовым классом. Поддерживает
однодокументный интерфейс Windows (SDI). С этим классом (и производными от него)
тесно связано понятие фрейма (frame window). Под фреймом понимается окно, которое
координирует взаимодействие пользователя с приложением. Оно отображается в виде
рамки с необязательной строкой состояния и стандартными элементами управления. Этот
класс берет на себя выполнение всех основных функций приложения Windows на базе
интерфейса SDI.
Функции этого класса можно сгруппировать по следующим категориям:
- конструктор
- инициализация
o Create, LoadFrame – создание окна и присоединение его к текущему
объекту класса;
o LoadAccelTable – загрузка таблицы командных клавиш;
o группа функций для организации панелей элементов управления;
- операции – категория функций для работы с документами и их
представлениями, с панелями элементов управления, строкой состояния и для
управления модальностью фрейма окна.
CMDIFrameWnd
Базовый класс для главного окна (фрейма) многодокументного приложения.
CMDIChildWnd
Базовый класс для дочернего окна многодокументного приложения.
CDialog
Базовый класс для окон диалога.
6
Типы приложений
На первом шаге диалога создания нового проекта выбирается тип проекта MFC
AppWizard (exe) соответствует проекту, создаваемому на основе каркаса приложений
MFC в результате компиляции которого будет получено приложение.
На втором шаге диалога выбирается тип приложения на основе каркаса
приложений MFC. Имеется три варианта:
- single document – однодокументное приложение;
- multiple documents – многодокументное приложение;
- dialog based – приложение на основе диалога;
Также на этом шаге диалога можно включить или отключить поддержку
архитектуры документ/вид.
Проект single document без поддержки архитектуры документ/вид включает
следующие классы:
class CNameApp : public CWinApp – класс приложения (Name по умолчанию
совпадает с именем проекта);
class CAboutDlg : public CDialog – класс диалога Help/About;
class CChildView : public CWnd – класс представления окна приложения;
class CMainFrame : public CFrameWnd – класс окна-рамки приложения;
и одну глобальную переменную:
CNameApp theApp;
Интерес представляет метод CNameApp::InitInstance
BOOL CNameApp::InitInstance()
{
//включение поддержки ActiveX эл-ов управления
AfxEnableControlContainer();
//включение возможности отображения 3D элементов управления
#ifdef _AFXDLL
Enable3dControls();
// Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
// Создаем объект окна-рамки
CMainFrame* pFrame = new CMainFrame;
m_pMainWnd = pFrame;
// Создаем окно-рамку Windows и присоединяем его к объекту
pFrame->LoadFrame(IDR_MAINFRAME,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
NULL);
/*
Первый параметр – идентификатор ресурсов, ассоциированных с окном (меню окнарамки)
Второй – стиль окна
Третий – указатель на родительское окно (NULL)
Четвертый – указатель на структуру CCreateContext (NULL)
*/
// Делаем окно видимым и перерисовываем его
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
7
return TRUE;
}
Проект multiple documents без поддержки архитектуры документ/вид включает
следующие классы:
class CNameApp : public CWinApp – класс приложения (Name по умолчанию
совпадает с именем проекта);
class CAboutDlg : public CDialog – класс диалога Help/About;
class CChildFrame : public CMDIChildWnd – класс дочернего окна
многодокументного приложения;
class CChildView : public CWnd – класс представления дочернего окна;
class CMainFrame : public CMDIFrameWnd – класс окна-рамки многодокументного
приложения;
и одну глобальную переменную:
CNameApp theApp;
Проект dialog based включает следующие классы:
class CNameApp : public CWinApp – класс приложения (Name по умолчанию
совпадает с именем проекта);
class CAboutDlg : public CDialog – класс диалога Help/About;
class CNameDlg : public CDialog – класс основного диалога;
и одну глобальную переменную:
CNameApp theApp;
Обработка сообщений
Поскольку Windows является ОС ориентированной на сообщения, большая часть
программирования под Wndows представляет собой написание обработчиков сообщений.
Типы сообщений MFC
Все возможные сообщения разделены на 3 основных категории:
- сообщения Windows. Сообщения, начинающиеся с префикса WM_, за
исключением WM_COMMAND. Это сообщения Windows для обработки
окнами и представлениями;
- извещения элементов управления. Сообщения от элементов управления и
других дочерних окон своим «родителям». Причем элемент управления
посылает своему родителю сообщение WM_COMMAND, содержащее
конкретный код извещения. Например, элемент управления BUTTON посылает
извещение BN_CLICKED;
- командные сообщения (команды). Все сообщения WM_COMMAND от
объектов интерфейса пользователя (меню, кнопки панелей инструментов,
командные клавиши.
Сообщения первых двух категорий предназначены для объектов классов,
образованных от CWnd, т.е. имеющих дескриптор окна Windows. Обработка сообщений
третьей категории может производиться множеством объектов, включающим документы,
шаблоны документов, объект-приложение.
Таблица сообщений
Библиотека MFC предлагает программную модель, оптимизированную для
программирования, основанного на сообщениях. Эта модель представляет собой карту
(таблицу) сообщений (message maps), которая используется, чтобы назначить функцииобработчики сообщения для конкретного класса.
Эта модель в общем предпочтительнее использованию виртуальных функций по
следующим соображениям:
- получится очень громоздкая виртуальная таблица,
8
-
обработчики сообщений о командах меню и щелчках по кнопкам панелей
инструментов не определишь в базовом классе окна, т.к. они могут отличаться.
Таблица сообщений содержит один или более макросов, которые специфицируют –
какое сообщение будет обрабатываться какой функцией. Например:
BEGIN_MESSAGE_MAP( CMyDoc, CDocument )
//{{AFX_MSG_MAP( CMyDoc )
ON_COMMAND( ID_MYCMD, OnMyCommand )
// ... More entries to handle additional commands
//}}AFX_MSG_MAP
END_MESSAGE_MAP( )
Таблица сообщений обычно создается ClassWizard а не вручную.
Для
определения
карты
сообщений
используется
три
макроса:
BEGIN_MESSAGE_MAP, END_MESSAGE_MAP, DECLARE_MESSAGE_MAP.
Макрос DECLARE_MESSAGE_MAP располагается в конце объявления класса,
использующего карту сообщений.
Макросы BEGIN_MESSAGE_MAP и END_MESSAGE_MAP представляют собой
операторные скобки, в которые заключается карта сообщений. Карта сообщений должна
объявляться вне какой-либо функции или объявления класса.
Макрос BEGIN_MESSAGE_MAP имеет два параметра – имя класса, владельца
карты сообщений и имя базового класса.
Компоненты таблицы сообщений
Для каждого стандартного сообщения Windows определен свой макрос в форме:
ON_WM_XXX (например ON_WM_PAINT)
Имя макроса указывается в таблице сообщений без параметров. Прототип
функции-обработчика такого сообщения определен. Имя всегда начинается с префикса
On, за которым следует имя соответствующего обработчика Windows без префикса WM_,
записанное строчными буквами. Например:
afx_msg void OnPaint();
afx_msg void OnLButtonUp(UNIT nFlags, Cpoint point);
Элемент afx_msg преобразуется препроцессором в пустую строку и напоминает о
том, что это функция таблицы сообщений.
Описания всех обработчиков стандартных сообщений Windows находятся в файле
afxwin.h.
Для обработки извещений от элементов управления применяется макрос
ON_CONTROL(wNotifyCode, id, memberFn)
WNotifyCode – код извещения;
id – идентификатор элемента управления;
memberFn – произвольное имя функции обработчика, которая должна быть
элементом соответствующего класса и иметь вид:
afx_msg void memberFn();
Помимо этого макроса, общего для всех элементов управления и дочерних окон,
для кнопок, элементов редактирования, списка и комбинированного списка используются
специальные макросы. Например:
ON_BN_CLICKED(id,memberFn);
и др.
Для некоторых элементов управления необходимо передавать в обработчик
параметры. В этом случае используется сообщение Windows не WM_COMMAND, а
WM_NOTIFY и соответствующий макрос Visual C++:
ON_NOTIFY(wNotify, id, memberFn)
Прототип обработчика:
Afx_msg void memberFn(NMHDR * pNotifyStruct, LRESULT * result);
9
Командные сообщения Windows от меню, командных клавиш и кнопок панелей
инструментов обрабатываются макросом:
ON_COMMAND(id, memberFn)
Id – идентификатор команды;
mMemberFn – имя обработчика команды, прототип
afx_msg void memberFn();
Команды обновления выполняются посредством того же механизма, что и для
«обычных» команд, но используется макрос:
ON_UPDATE_COMMAND_UI(id, memberFn)
Обработчики таких команд имеют вид:
afx_msg void memberFn(CCmdUI *pCmdUI)
Если необходимо использовать один обработчик для нескольких команд или
извещений от элементов управления, то используют следующие макросы:
ON_COMMAND_RANGE(idFirst,idLast,memberFn)
ON_CONTROL_RANGE(wNotifyCode,idFirst,idLast,memberFn)
ON_NOTIFY_RANGE(wNotifyCode,idFirst,idLast,memberFn)
В качестве параметра используется два идентификатора idFirst и idLast, которые
составляют непрерывный ряд. Обработчик будет вызываться, если в оконую процедуру
поступит сообщение, содержащее в параметре wParam идентификатор команды,
принадлежащий диапазону idFirst idLast. Прототипы обработчиков:
afx_msg void memberFn(UNIT nID);
afx_msg void memberFn(UNIT nID,NMHDR *pNotifyStruct, LRESULT *result););
Аналогичный макрос существует для команд обновления
ON_UPDATE_COMMAND_UI_RANGE(idFirst,idLast, memberFn)
Обработчик, как и для макроса ON_UPDATE_COMMAND_UI имеют вид:
afx_msg void memberFn(CCmdUI *pCmdUI)
Можно включать в карту сообщений сообщения и команды, определяемые
пользователем при помощи макросов
ON_MESSAGE(WM_NAMEMSG, OnNameMsg)
ON_REGISTERED_MESSAGE(nMessageVariable, memberFn)
Стандартный маршрут команды
Когда
программа
(функция
CWinThread::Run)
получает
сообщение,
обрабатываемое окном, то его направляют в соответствующее окно, в котором оно и будет
обрабатываться. Для команд все ниначе. Поскольку они формируются в результате
взаимодействия пользователя с программой, то обычно команда начинает путь от
главного окна приложения. Обнаружив командное сообщение от окна-рамки каркас
приложения начинает поиск соответствующих обработчиков в следующей
последовательности:
В SDI-приложении
В MDI-приложении
Класс «вид»
Класс «вид»
Класс «документ»
Класс «документ»
Класс «основное окно-рамка SDI»
Класс «дочеренее окно-рамка MDI»
Класс «основное окно-рамка MDI»
Класс «приложение»
Класс «приложение»
В большинстве приложений обработчик конкретной команды присутствует в
одном классе, но если их два, то будет вызван тот, который будет найден первым.
Обработка сообщений в производных классах
Как для командных, так и для стандартных сообщений действует следующий
механизм: если обработчик не найден в карте сообщений некоторого класса, то он ищется
10
в карте сообщений базового класса. Чтобы переопределить какую-либо функцию таблицы
сообщений базового класса, в производный класс надо добавить как функцию, так и
соответствующий элемент таблицы сообщений.
Для стандартных сообщений Windows более эффективным является механизм
виртуальных функций. Виртуальные функции всех стандартных сообщений Windows
определены в классе CWnd.
Посылка сообщений
Для посылки сообщений используется две функции:
SendMessage – посылает сообщение, непосредственно вызывая оконную процедуру
и не выходит из нее, пока та не обработает сообщение;
PostMessage – помещает сообщение в очередь приложения и заканчивает работу
без ожидания его обработки.
Окно- рамка
В состав как SDI, так и
MDI приложений входит класс CMainFrame,
представляющий собой основное окно, или окно-рамку, приложения. Именно этому окну
принадлежит заголовок и меню. Клиентскую область основного окна занимают дочерние
окна, в том числе окно представления, окно панели инструментов и окно строки
состояния. Каркас приложений обеспечивает взаимодействие между окном-рамкой и
окном представления, доставляя сообщения от первого второму.
Строка меню
Меню представляет собой ресурс. Обычно для окна-рамки определяется ресурс
меню по умолчанию, загружаемый при создании этого окна. Можно определить ресурс
меню не связанный с каким-либо окном ракой. В этом случае нужно вызывать функции
для загрузки и активизации меню.
В состав Visual C++ входит редактор меню. Он позволяет создавать и
редактировать окно в режиме WYSIWYG. Для каждой команды меню может быть
открыто окно свойств, где задаются все ее характеристики и присваивается свой
идентификатор (желательно осмысленный). Полученное определение ресурса сохраняется
в RC-файле проекта.
Атрибуты команд меню
Separator – элемент представляет собой горизонтальную разделительную линию,
служащую для разбиения элементов меню на группы;
Checked – при выводе на экран элемент меню отмечается слева галочкой;
Pop-up – элемент определяет подменю
Grayed – элемент отображается серым цветом и находится в заблокированном
состоянии;
Help – элемент (и все правее него) выравнивается по правому краю полосы меню;
Break – может принимать одно из трех значений: None – обычный элемент меню;
Column – для меню верхнего уровня элемент выводится с новой строки, для подменю – в
новом столбце; Bar – дополнительный столбец подменю отделяется вертикальной линией;
ID – идентификатор команды, генерируемой элементом меню;
Caption – текст, определяющий имя элемента меню; текстовая строка может
содержать символы & - буква, перед которой стоит этот знак будет подчеркнута; \t –
включает в строку символ табуляции; \a – выравнивает текст по правой границе меню;
Prompt – текст, который будет отображаться в строке состояния и (после \n) в окне
всплывающей подсказки
Inactive – элемент находится в заблокированном состоянии, но отображается
обычным цветом.
Имеется система быстрых клавиш – символы, подчеркнутые в меню. Вызываются
(Alt+символ), задаются при описании с помощью & перед символом. Кроме этого имеется
11
ресурс клавиш-акселераторов. Это таблица, связывающая комбинации клавиш с
идентификаторами команд. Это могут быть те же идентификаторы, что и для элементов
меню, но могут быть и комбинации клавиш, которым не соответствуют команды меню.
Если клавиша-акселератор связана с командой меню или кнопкой панели инструментов,
то при отключении команды или кнопки отключается и эта клавиша.
В MFC имеется класс CMenu, который инкапсулирует объект Windows,
определяемый дескриптором HMENU и предоставляет функции для работы с меню. С
помощью этих функций можно создать меню «с нуля» или загрузить меню из файла
ресурсов. Объект этого класса агрегирован в класс CWnd. Все операции по созданию
этого объекта и загрузки в него меню IDR_MAINFRAME скрыты в каркасе приложения.
Иногда надо изменять состояние меню в процессе выполнения команды, например
временно отключать некоторые команды меню. Для этого можно во всех
соответствующих местах программы получить указатель на соответствующий объект
меню и работать, используя его функции, но это может оказаться громоздко.
В каркасе приложений MFC для этого используется следующий подход: при
каждом открытии меню вызывается функция-обработчик команды обновления
UPDATE_COMMAND_UI. Аргумент этой функции типа указатель на CCmdUI
представляет собой указатель на соответствующий элемент меню. С помощью него можно
изменить внешний вид команды меню. Понятно, что к меню верхнего уровня такой
способ неприменим.
Контекстное меню
Для создания контекстного меню с помощью редактора ресурсов следует:
1. открыть редактор меню и добавить в файл ресурсов новое пустое обычное
меню
2. ввести какое-нибудь имя для крайнего слева элемента верхнего уровня и
добавить команды контекстного меню в получившееся раскрывающееся меню;
3. Вставить обработчик сообщения WM_CONTEXTMENU в класс окна,
получающего сообщения от кнопки мыши (например класс «вид»):
void CXXXView::OnContextMenu(CWnd* pWnd, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MENU1);
menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,
point.x,point.y,this);
}
Можно создать контекстное меню используя только функции класса CMenu.
Системное меню
Чтобы добавить новый пункт в системное меню следует добавить:
CMainFrame::OnCreate(…)
{
…
//получаем доступ к системному меню параметр = FALSE – можно выполнять
модификацию, TRUE – возвращает системное меню в состояние «по-умолчанию»
CMenu * pSysMenu = GetSystemMenu(FALSE);
//добавляем разделитель и новый элемент
pSysMeny->AppendMenu(MF_SEPARATOR);
pSysMeny->AppendMenu(MF_BYCOMMAND|MF_STRING,
IDM_YYY,Cstring(“yyy”));
}
12
Кроме этого необходимо сопоставить идентификатор ресурса, который будет
назначен новой команде системного меню с числом (View/Resource symbols/New/New
Symbol, задать Name и Value)
Самоотображение элементов меню
Панели инструментов
Панель инструментов – строка графических кнопок, иногда объединенных в
группы. Изображения кнопок хранятся в общем растровом изображении, входящем в
ресурсы приложения. При щелчке по кнопке она посылает командное сообщение подобно
меню и быстрым клавишам.
Растровое изображение одно для всех кнопок панели инструментов. В нем каждой
кнопке выделяется ячейка высотой 15 и шириной 16 пикселов. Растровое изображение
хранится в виде .bmp файла в подкатологе RES. Модифицируется это изображение с
помощью специального графического редактора, встроенного в IDE.
Библиотека MFC для панелей инструментов предлагает класс CToolBar,
являющийся производным от CControlBar (производный от CWnd). В этом классе
инкапсулированы функции для работы с панелью инструментов. Для создания панели
инструментов необходимо: создать объект этого класса, создать окно панели
инструментов и загрузить туда соответствующий ресурс.
Объект этого класса, соответствующий стандартной панели инструментов главного
окна, агрегирован в класс CMainFrame. Функции, необходимые для е создания
вызываются в CMainFrame::OnCreate.
Для изменения стандартной панели инструментов следует:
1. изменить соответствующий ресурс (Resource View / Toolbar /
IDR_MAINFARAME); При этом может быть вызвано окно свойств, где каждой
кнопке ставится в соответствие ID и Prompt – текст, который будет
отображаться в строке состояния и (после \n) в окне всплывающей подсказки, а
также может быть изменен размер кнопки;
2. Назначить обработчики команд, генерируемых кнопками. Чаще всего кнопка на
панели инструментов дублирует команду меню. В таком случае ей назначается
тот же идентификатор команды, что и для элемента меню и обработчик команд
будет общим. Если у кнопки нет эквивалента в меню, ей рекомендуется
назначить быструю клавишу, генерирующую команду с тем же ID.
Для кнопок панелей инструментов, также как и для меню имеются команды
обновления пользовательского интерфейса UPDATE_COMMAND_UI. Аргумент
функции-обработчика указатель на CСmdUI. Обработчики вызываются в моменты
простоя приложения и, если для элемента меню и кнопки используется общий
обработчик, то и при открытии меню.
В функции обработчике можно: отключать кнопку (CCmdUI::Enable, аналогичная
команда делает неактивным элемент меню), реализовать кнопку со свойствами флажка
(CСmdUI::SetCheck – для меню ставится галочка).
Строка состояния
Строка состояния – это панель управления, отображающая полосу, которую можно
разделить на несколько областей для вывода в них информации.
За реализацию строки состояния в MFC отвечает класс CStatusBar. Для создания
строки состояния следует:
1. создать объект класса CStatusBar;
2. создать массив идентификаторов строки состояния – массив индикаторов:
static UINT indicators[] =
{
ID_SEPARATOR,
// status line indicator
13
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
Определить для идентификаторв соответствующие строковые ресурсы (String
Table)
3. создать окно строки состояния;
4. загрузить масиив индикаторов.
В класс CMainFrame агрегирован объект класса CClassBar, представляющий собой
строку состояния по-умолчанию. Функции по ее созданию находятся в
CMainFrame::OnCreate. Массив индикаторов определен в файле MainFrm.cpp.
Стандартной строке состояния присваивается идентификатор дочернего окна
AFX_IDW_STATUS_BAR. Его каркас приложений использует для вывода подсказки по
элементам меню. Обработчики сообщений обновления пользовательского интерфейса
используют три идентификатора строковых ресурсов для индикаторов состояния
клавиатуры в базовом классе окна-рамки: ID_INDICATOR_CAPS, ID_INDICATOR_NUM,
ID_INDICATOR_SCRL. Для изменения логики управления строкой состояния следует
применить другой идентификатор дочернего окна и другие константы для индикаторов.
Идентификатор для окна строки состояния назначается вызовом CStatusBar::Create
в функции-члене OnCreate производного класса окна-рамки.
Таким образом, для изменения строки состояния следует:
1.
Отредактировать ресурс строковой таблицы: в Resource View
редактировать StringTable – добавить новые ID с соответствующими
Caption
(текст,
который
будет
отображаться).
Имеются
предопределенные ID (ID_INDICATOR_CAPS, ID_INDICATOR_NUM,
ID_SEPARATOR);
2.
Задать для строки состояния новый идентификатор View/Resource
Symbols – сопоставить ID и числовое значение;
3.
Изменить содержимое массива индикаторов в MainFrm.cpp, использовать
созданные на шаге 1 ID:
static UINT indicators[] =
{
ID_SEPARATOR,
// status line indicator
ID_SEPARATOR,
// status line indicator
ID_INDICATOR_XXX,
ID_INDICATOR_YYY,
};
4.
Отредактировать функцию MainFrame::OnCreate:
if
(!m_wndStatusBar.Create(this,WS_CHILD|WS_VISIBLE|CBRS_BOTTOM,
ID_XXX_STATUS_BAR) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
5.
Объявить CmainFrame::m_wndStatusBar public;
6.
Если необходимо изменять состояние индикаторов строки состояний, то
вручную добавить элементы таблицы сообщений для класса, в котором
предполагается их обрабатывать:
ON_UPDATE_COMMAND_UI(ID_INDICATOR_XXX,OnUpdateXXX).
Сообщения посылаются в цикле простоя приложения.
Вставить функции-члены этого класса, отвечающие за обновление
индикаторов:
14
afx_msg void OnUpdateXXX(CCmdUI* pCmdUI); - лучше внутри
скобок AFX_MSG
void C________::OnUpdateXXX(CCmdUI* pCmdUI)
{
pCmdUI->Enable(…);
pCmdUI->SetText("…");
}
7.
При необходимости назначить обработчики команд в меню
View/StatusBar, которые либо делают строку состояния видимой, либо
скрывают ее:
void CMainFrame::OnViewStatusBar()
{
m_wndStatusBar.ShowWindow((m_wndStatusBar.GetStyle() & WS_VISIBLE)
== 0);
RecalcLayout();
}
void CMainFrame::OnUpdateViewStatusBar(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck((m_wndStatusBar.GetStyle() & WS_VISIBLE) != 0);
}
8.
В тех местах программы, где необходимо изменить содержимое строки
состояния вставить следующее:
a. В функции, не являющейся членом CMainFrame:
CString str;
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
CStatusBar* pStatus = &pFrame->m_wndStatusBar;
if (pStatus)
{
str.Format("x = %d", x);
pStatus->SetPaneText(0,str);
str.Format("y = %d", y);
pStatus->SetPaneText(1,str);
}
b.
В функции, являющейся членом CMainFrame:
m_wndStatusBar. SetPaneText(0,str);
Блоки диалога
Блоки диалога – специальные окна, поддерживающие гибкие средства для
взаимодействия пользователя с программой.. Как правило они включают дочерние окна,
называемые элементами управления. Блоки диалога бывают двух типов: модальные и
немодальные. Большинство – модальные.
Создание и использование блока диалога, как модального, так и немодального,
требует одновременного наличия трех компонент:
- шаблона блока диалога;
- программного кода, отображающего его на экране;
- диалоговой процедуры, обслуживающей взаимодействие пользователя с блоком
диалога.
Шаблон блока диалога представляет собой ресурс, определенный в файле ресурсов
проекта (.rc). Этот ресурс может быть создан и модифицирован в редакторе диалоговых
15
окон (ResourceView/Dialog). При этом процесс создания и редактирования является
визуальным.
Для работы с диалогами в MFC служит класс CDialog : CWnd. Он позволяет
создавать как модальные, так и немодальные блоки диалога. После того, как создан ресурс
блока диалога, ClassWizard позволяет автоматически сгенерировать класс для этого
диалога, производный от CDialog. После этого программа может создавать блок диалога.
Для создания модального блока диалога на основе шаблона, хранящегося в
ресурсах необходимо:
1. создать объект класса: CDialogXXX dlg – это может быть локальный
статический объект;
2. создать и активизировать блок диалога Windows: int res = dlg.DoModal; функция завершает работу, когда пользователь закрывает блок диалога и
возвращает: IDOK, IDCANCEL в зависимости от выбора пользователя дибо –1,
если программа не смогла создать блок диалога.
Для создания немодального блока диалога на основе шаблона, хранящегося в
ресурсах необходимо:
1. создать объект класса: CDialogXXX, причем этот объект должен иметь время
жизни большее, чем выполнение метода-обработчика сообщения, т.е. это может
быть переменная-член класса (например CView) типа указатель на объект и
объект тогда создается динамически: dlg = new CDialog1; и его где-то надо
уничтожить – возможно создаем в конструкторе CView, уничтожаем – в
деструкторе;
2. Далее для создания немодального блока диалога Windows вызывается функция
Create:
dlg->Create(CDialog1::IDD), она получает идентификатор ресурса как
параметр. В отличие от DoModal, она возвращает управление немедленно, при
этом диалоговое окно остается на экране;
3. Сделать окно диалога видимым:
dlg->ShowWindow(TRUE);
Имеется возможность создавать как модальный, так и немодальный блок диалога
на основе шаблона, хранящегося не в ресурсе, а создаваемого в памяти непосредственно в
процессе выполнения программы.
После окончания работы с немодальным блоком диалога необходимо вызвать
функцию CWnd::DestroyWindow для того, чтобы уничтожить его – и это надо сделать
явно.
Блок диалога имеет специфическое сообщение WM_INITDIALOG. Это сообщение
посылается при вызове функций Create или DoModal после того, как созданы окна всех
его элементов управления, но до того как они стали видимыми. В обработчике этого
сообщения должна производиться инициализация элементов управления. Имеется
стандартный обработчик. Если нужна специальная обработка, то его следует
переопределить. Для этого в ClassWizard назначается обработчик сообщения
WM_INITDIALOG, но ClassWizard не создает новый элемент таблицы сообщений, а
виртуально перекрывает функцию OnInitDialog базового класса. В новом обработчике
необходимо вызвать OnInitDialog базового класса (CDialog::OnInitDialog();) для
корректной инициализации.
Базовый класс CDialog имеет две виртуальные функции OnOK и OnCancel. Эти
функции вызываются при нажатии кнопок Ok и Cancel, которые обычно находятся в блоке
диалога а также при нажатии клавиш Enter и Esc, даже если кнопок Ok и Cancel нет
(команды IDOK и IDCANCEL).
Функция CDialog::OnOK фиксирует данные блока диалога и завершает работу
модального блока диалога. Для немодального блока диалога эту функцию надо
16
переопределить, так как именно в ней надо явно вызвать CWnd::DestroyWindow для
уничтожения соответствующего окна Windows (иначе окно просто сделается невидимым,
а не уничтожится). Другой вариант корректного завершения работы немодального
диалогового окна – послать пользовательское сообщение объекту вид, который и
уничтожит диалоговое окно (а возможно и соответствующий объект). Кроме этого в ней
следует вызывать функцию UpdateData(TRUE) для фиксации данных диалогового окна.
Функция CDialog::OnCancel завершает работу модального блока диалога yt
фиксируя данные. Для немодального блока диалога эту функцию также надо
переопределить (UpdateData вызывать не надо).
Если одно и тоже диалоговое окно предполагается использовать и как модальное и
как немодальное, следует написать функции OnOK и OnCancel таким образом, чтобы они
анализировали, создано ли окно как модальное или немодальное и, в случае модального
окна вызывали соответствующие виртуальные функции базового класса CDialog а в
случае немодального – UpdateData для OnOK и DestroyWindow как для OnOK, так и для
OnCancel. Информацию для анализа, было ли окно создано как модальное или
немодальное можно передавать в качестве параметра конструктора объекта диалоговое
окно и хранить в специальной переменной-члене производного класса.
Иногда пользователю бывает неудобно то, что при нажатии клавиши Enter
диалоговое окно закрывается. Для того, чтобы избежать этого необходимо перехватить
контроль при выходе по OnOK. Для этого:
1. Создается обработчик IDOK – виртуальная функция OnOK;
2. В этом обработчике удаляется вызов виртуальной функции CDialog::OnOK –
т.е. создается заглушка;
3. Кнопке Ok сопоставляется новый идентификатор команды;
4. Создается обработчик этой новой команды;
5. В нем вызывается CDialog::OnOK.
Аналогично можно поступить с OnCancel.
Обмен данными с блоком диалога
При создании класса, производного от CDialog для ресурса диалогового окна в
ClassWizard на вкладке Member Variables следует сопоставить элементам управления
диалогового окна пременные-члены класса соответствующих типов. Некоторые
соответствия элементов управления и типов:
Элемент управления
Сопоставимые типы переменных
Элемент редактирования EDITBOX
CString, int, UINT, long, DWORD,
float, double, short, BOOL
Обычный флажок CHECKBOX
BOOL
Флажок с тремя состояниями CHECKBOX
Int
Первый переключатель в группе RADIOBUTTON Int
Несортированный список LISTBOX
CString, int
Комбинированный список COMBOBOX
CString, int
Остальные виды списков
CString
Кроме этого можно задать условия, которым должны удовлетворять воводимые
значения: для строки – максимальная длина, для числа – диапазон.
За обмен данными между элементами управления и этими переменными отвечают
две функции: UpdateData и DoDataExchange.
BOOL CWnd::UpdateData (BOOL bSaveAndValidate = TRUE). Эта функция
вызывается когда надо либо проинициализировать данные в блоке диалога, либо вернуть
данные из блока диалога с проверкой допустимости возвращаемых значений. Функция
имеет единственный параметр. Если он равен TRUE о данные, введенные пользователем в
блоке диалога записываются в соответствующие переменные класса, в противном случае
значения переменных класса инициализируют элементы управления блока диалога.
17
Каркас приложения MFC вызывает эту функцию в функции CDialog::OnInitDialog с
параметром = FALSE для инициализации данных при создании модального блока диалога,
и в функции CDialog::OnOK с параметром = TRUE для получения и проверки на
допустимость данных из блока диалога. Функция не переопределяется в классе потомке.
Для выполнения действий по обмену данных функция UpdateData вызывает виртуальную
функцию DoDataExchange.
virtual void CWnd::DoDataExchange.(CdataExchange * pDX) осуществляет обмен
данными и проверку их корректности. Вызывается каркасом приложения. Никогда не
следует вызывать ее непосредственно. Эту функцию необходимо переопределять для
каждого блока диалога, который должен обеспечивать взаимодействие с пользователем.
Переопределенная версия генерируется ClassWizard автоматически при назначении
Member Variables. Переопределенная функция имеет следующую стандартную форму:
void CDialog1::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDialog1)
DDX_Text(pDX, IDC_EDIT1, m_iEdit);
DDV_MinMaxInt(pDX, m_iEdit, -10, 10);
DDX_Radio(pDX, IDC_RADIO1, m_iRad);
//}}AFX_DATA_MAP
}
В нее вставляются функции DDX, по одной на каждый элемент управления,
участвующий в обмене и, дополнительно, для тех элементов управления, для которых
заданы условия, функции DDV для проверки корректности данных. Редактировать
сгенерированный код не рекомендуется.
Блок диалога в качестве главного окна приложения
Можно создать приложение на базе диалогового окна (Dialog Based). В этом случае
после генерации каркаса приложений AppWizard будет автоматически открыт редактор
ресурсов для диалога. В состав приложения будут входить три класса: CXXXApp,
CAboutDlg и CXXXDlg – класс для основного диалога.
Элементы управления вводятся как обычно. Им также ставятся в соответствие
переменные класса CXXXDialog. Для кнопок на диалоговом окне назначаются
обработчики. Если такой обработчик должен использовать данные, вводимые
пользователем в элементы управления, то перед началом обработки следует вызвать
UpdateData(TRUE).
Есть возможность создавать приложения на базе одного диалогового окна с
поддержкой архитектуры документ-вид.
Стандартные блоки диалога
Стандартные блоки диалога (Common Dialog Box) применяются для выполнения
стандартных действий. В MFC имеются следующие классы стандартных диалогов:
CFileDialog
CFontDialog
CColorDialog
CPageSetupDialog
CPrintDialog
CFindReplaceDialog
Для работы с такими блоками диалога необходимо создать объект
соответствующего класса и выполнить блок диалога. Например:
CFileDialog dlg(TRUE, “bmp”, “*.bmp”);
if (dlg.DoModal() == IDOK)
18
{
CFile file;
file.Open(dlg.GetPathName(),CFile::modeRead);
}
Наборы свойств
Набор свойств представляет собой особый вид диалогового окна. Набор свойств
содержит три основные части:
- блок диалога, внутри которого размещаются все остальные элементы набора
свойств;
- одна или несколько страниц свойств;
- вкладки, расположенные поверх каждой страницы.
Библиотека MFC предоставляет специальные классы для создания набора свойств
и обеспечения работы с ним:
- CPropertySheet и CPropertySheetEx позволяют создавать набор свойств;
- CPropertyPage и CpropertyPageEx предназначены для создания включаемых в
набор страниц свойств;
- CTabCtrl – предназначен для создания вкладок и работы с ними.
Процесс создания окна свойств с использованием инструментальных средств Visual
C++ следующий – вариант 1 (ручной)
1.
В редакторе ресурсов создается набор шаблонов диалоговых окон
примерно одинакового размера. Их заголовки будут строками,
отображаемыми на ярлычках;
2.
С помощью ClassWizard генерируется класс для каждого шаблона. В
качестве базового класса используется CPropertyPage. Добавляются
переменные для элементов управления;
3.
С помощью ClassWizard создается класс, производный от CPropertySheet;
4.
В этот класс добавляется по одной переменной для каждого класса
страницы свойств;
5.
В конструкторе класса окна свойств вызывается функция-член AddPage
для каждой страницы свойств;
6.
При необходимости запрограммировать обработку кнопки Apply
(Применить).
Вариант 2 (автоматический) – в Visual C++ имеется галерея повторно
используемых компонентов (Project/Add To Project/Components and Controls/Visual
C++ Components/Property Sheet
1. В диалоге выбрать: будет ли набор свойств модальным или нет, задать класс
проекта, который будет иметь функцию создания набора свойств, указать
количество страниц, задать имена соответствующих классов;
2. В редакторе ресурсов на созданные автоматически шаблоны диалогов для
каждой страницы набора свойств поместить элементы управления;
3. С помощью ClassWizard в классы страниц свойств добавляются переменные для
элементов управления;
4. При необходимости запрограммировать обработку кнопки Apply (Применить).
Для работы с набором свойств при исполнении программы следует:
1. Создать соответствующий объект;
2. Создать модальное (DoModal) или немодальное (Create, ShowWindow) окно
свойств;
3. Немодальное окно свойств по завершении работы следует уничтожить
(DestroyWindow).
19
При автоматическом создании набора свойств в указанном классе будут
организованы возможности для работы с ним. В случае модального набора свойств –
функция
void CXXXView::OnProperties()
{
CMyPropertySheet propSheet;
propSheet.DoModal();
}
которую можно сделать обработчиком команды, по которой надо выводить на
экран окно свойств.
В случае немодального окна свойств будут созданы переменная-член:
protected:
CPropertyFrame* m_pPropFrame;
И функция, делающая окно видимым:
void CMainFrame::OnProperties()
Обмен данными с набором свойств реально является обменом данными с его
отдельными страницами. Процедура обмена не отличается от обмена в обычном блоке
диалога. Процедура обмена для страницы вызывается каждый раз, когда пользователь
переключается с нее на другую страницу и когда пользователь вызывает команду IDOK.
Помимо кнопок OK и Cancel на окне свойств автоматически создается кнопка
Apply (Применить). По умолчанию эта кнопка неактивно. Ее можно удалить вообще или
перопределить для нее виртуальную функцию-обработчик для нее функцию обработчик
(CPropertyPage::OnApply).
Использование элементов управления ActiveX
ActiveX элемент управления, подобно обычному элементу управления можно
включить в диалоговое окно. Для этого он добавляется в проект (Project/Add TO
Project/Components and Controls), а дальше с ним можно работать как с обычным
элементом управления. Для того, чтобы приложение имело возможность работать с
ActiveX элементами управления в AppWizard должен быть установлен флажок ActiveX
Controls). Тогда в CXXXApp::InitInstance будет вставлена строка:
AfxEnableControlContainer()
А в файле StdAfx.h проекта появится строка:
#include <afxdisp.h>
// MFC Automation classes
При необходимости их можно добавить и вручную.
Имеется возможность тестировать ActiveX элементы управления не добавляя их в
проект (Tools/ActiveX Control Test Container).
При добавлении элемента управления в проект для него автоматически будет
создан класс-обертка С++, производный от CWnd. Сгенерированный класс содержит
функции-члены для всех свойств и методов, причем для каждого свойства существует
пара функций: Get и Set. Для того, чтобы получить к ним доступ, необходимо сопоставить
с элементом управления ActiveX переменную-член класса диалога с помощью
ClassWizard. Ее тип будет класс-обертка. Далее через эту переменную можно получить
доступ к функциям класса-обертки.
В отличие от обычных элементов управления ActiveX-элемент не посылает своему
контейнеру уведомляющих сообщений WM_ - вместо этого он генерирует события.
Событие носит символьное имя и может обладать произвольным набором параметров.
Событие – это функция контейнера, которую вызывает элемент управления ActiveX.
Создавать функции-обработчики можно как обычно с помощью ClassWizard.
20
Доступ к свойствам на этапе разработки с помощью страницы свойств – как и для
обычных элементов управления.
Обычно ActiveX-элемент проецируется на адресное пространство приложения
только на то время, пока активно его родительское диалоговое окно. То есть, каждый раз,
когда пользователь открывает это окно, элемент загружается заново. Для повышения
производительности можно заблокировать Active-X-элемент в памяти. Для этого в
переопределенную функцию OnInitDialog после вызова функции базового класса
добавляется строка:
AfxOleLockControl(m_grid.getClsid());
В этом случае ActiveX-элемент останется в адресном пространстве приложения до
тех пор, пока не завершится программа или не будет вызвана функция
AfxOleUnlockControl.
Архитектура документ-вид (документ-представление)
Одним из основных достоинств архитектуры «документ-вид» является то, что
данные в ней отделены от их изображения. Любые изменения данных осуществляются с
использованием специального класса документа, который обеспечивает необходимое
пространство для их хранения в памяти, отвечает за запись и чтение документа с диска и
предоставляет интерфейс для доступа и обновления данных. За изображение данных на
экране, принтере или любом другом устройстве отвечает класс вид. Объект этого класса
представляет собой окно, посредством которого осуществляется взаимодействие с
пользователем. Окно «вид» находится внутри окна-рамки: либо основного окна
однодокументного приложения, либо дочернего окна многодокументного приложения.
Важно: одному объекту «документ» может соответствовать много представлений –
объектов «вид», но не наоборот. Каждое представление отображает все или часть данных
документа в какой-либо форме (например: таблица и график). Одновременно могут
отображаться на экране либо одно, либо несколько представлений для одного документа
(даже в однодокументной архитектуре).
В состав однодокументного приложения входит один класс «документ»,
производный от CDocument и один или несколько классов «вид», производных
непосредственно или опосредованно от класса CView.
Основные функции взаимодействия «документ-вид»
С объектом «вид» связан один и только один объект-документ. Функция
CView::GetDocument позволяет получить указатель на тот документ, с котороым
сопоставлено данное представление. Допустим, объект «вид» получил сообщение, что
пользователь ввел новые данные. Он должен уведомить об этом документ, чтобы тот
обновил свои внутренние данные. Для этого ему необходим указатель на
соответствующий объект «документ», чтобы обращаться к его функциям или
переменным.
Если содержание документа по какой-либо причине изменилось, об этом следует
уведомить все объекты «вид», чтобы они обновили свое представление данных. Для этого
используется функция CDocument::UpdateAllViews. При вызове ее из функции
производного класса документа ее первый параметр pSender равен NULL. Если она
вызывается из функции производного класса «вид», параметр pSender указывает на
текущий объект «вид», уведомляя каркас приложения, сто этот вид обновлять не надо:
GetDocument()->UpdateAllViews(this);
У этой функции есть необязательные параметры, через которые можно передавать
объекту «вид» специфичную для приложения информацию о том, какие части
представления следует обновить.
В ответ на вызов функции CDocument::UpdateAllViews каркас приложения
вызывает функцию CView::OnUpdate для тех объектов «вид», данные которых должны
быть обновлены. Эта функция является виртуальной и должна быть переопределена в
21
производных классах таким образом, чтобы обратившись к документу и, получив его
данные, обновить представление.
Виртуальная функция CView::OnInitialUpdate вызывается при запуске приложения
а также при создании и открытии документа. Версия OnInitialUpdate, определенная в
базовом классе CView просто вызывает OnUpdate. В этой функции может быть
произведена инициализация объекта «вид».
Инициализацию документа можно производить в перекрытой виртуальной
функции CDocument::OnNewDocument. Ее каркас приложения вызывает после создания
объекта «документ».
Простейшее приложение на базе архитектуры «документ-вид»
Рассмотрим вариант, когда нужно только одно представление документа. В этом
случае можно не испоьзовать функции UpdateAllViews и OnUpdate, а придерживаться
более простой схемы:
1.
В заголовочном файле производного класса документа, сгенерированном
AppWizard, объявить переменные-члены, в которых хранятся данные
документа. Их (или функции доступа к ним) следует объявить открытыми либо
сделать производный класс «вид» дружественным классу документа.
2.
В производном классе «вид» переопределить виртуальную функцию
OnInitialUpdate так, чтобы она обновляла представление в соответствии с
данными документа (например считанными из файла).
3.
Назначить функции-обработчики командных сообщений для класса «вид».
4.
В функциях производного класса «вид», которым необходим доступ к данным
документа (обработчики оконных и командных сообщений и функция OnDraw),
обеспечить этот доступ путем непосредственного обращения к переменнымчленам класса «документ», используя для этого функцию GetDocument.
Усложненное взаимодействие документ-вид
Если необходимо множественное представление данных, то взаимодействие
«документ-вид» должно обеспечивать обновление всех представлений, когда
пользователь редактирует данные в одном из них. При разработке приложения
используют следующую схему:
1.
В заголовочном файле производного класса документа, сгенерированном
AppWizard, объявить переменные-члены, в которых хранятся данные
документа. Их (или функции доступа к ним) следует объявить открытыми либо
сделать производный классы «вид» дружественным классу документа.
2.
В производных классах «вид», используя ClassWizard, переопределить
виртуальную функцию OnUpdate таким образом, чтобы она изменяла
представление в соответствии с данными документа. Доступ к данным
документа с помощью функции GetDocument.
3.
Проанализировать командные сообщения. Некоторые из них относятся к
одному из представлений, некоторые к документу. Сопоставить команды
соответствующим классам.
4.
В функциях-обработчиках командных сообщений классов «вид» предусмотреть
обновление данных в документе (GetDocument) и вызов UpdateAllViews.
5.
В функциях-обработчиках командных сообщений для класса «документ»
обеспечить обновление данных и вызов UpdateAllViews.
Шаблон документа
Базовым классом шаблонов документов является CDocTemplate. Объект шаблона
документа (один или более) обычно создается в функции InitInstance производного класса
приложения. Шаблон документа определяет связи между тремя типами классов:
- класс документа, производный от CDocument;
- класс-вид, производный от CView или его потомков;
- класс окна рамки, производный от CFrameWnd или CMDIChildWnd.
22
Приложение имеет один шаблон документа для каждого типа документа, который
оно поддерживает. Каждый шаблон документа позволяет создавать и управлять всеми
документами своего типа.
Шаблон документа хранит указатели на объекты CRuntimeClass для классов
документ, вид и окно-рамка. Эти данные передаются в качестве параметров конструктора.
Шаблон документа содержит ID ресурсов, соответствующих типу документа (таких
как меню, значок или таблица акселераторов). Кроме этого шаблон документа содержит
строку, содержащую дополнительную информацию о типе документа. Она содержит имя
типа документа, расширение файла документа и некоторые необязательные
дополнительные сведения.
CDocTemplate является абстрактным классом, поэтому он не может использоваться
непосредственно. Обычно, в приложениях используются два производных от него класса:
CSingleDocTemplate для SDI приложений и CMultiDocTemplate для MDI приложений.
Если приложение фундаментально отличается от SDI и MDI, можно породить свой
собственный класс – потомок CDocTemplate.
Конструкторы классов шаблонов документов вызываются в CXXXApp::InitInstance
и имеют вид:
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CXXXDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CXXXView));
Первый параметр – идентификатор ресурсов, остальные – указатели на структуру
CRuntimeClass для соответствующих классов документ, фрейм и вид).
Далее шаблоны документов должны быть зарегистрированы в классе приложении с
помощью функции CWinApp::AddDocTemplate():
AddDocTemplate(pDocTemplate);
При этом устанавливается связь между классами, а объектов «документ», «рамка»,
«вид» в этот момент еще нет. Каркас приложения будет создавать их автоматически, когда
возникнет необходимость. При открытии или создании документа создается объект
документ, находится соответствующий шаблон, который создает фрейм и представление
соответствующего документа. При этом, если приложение поддерживает несколько
шаблонов документов, то при открытии файла для поиска соответствующего шаблона
библиотека MFC использует расширение файла, а при создании на экран выводится
специальный блок диалога.
Документ
Документы содержат и управляют данными приложения. Для реализации
документа в типичном приложении необходимо:
- для каждого типа документа образовать класс на базе CDocument;
- добавить в него переменные для хранения всех данных документа и
специальные функции для их чтения, установки и выполнения операций над
ними;
- реализовать функции для чтения и модификации этих данных;
- переопределить функцию CObject::Serialize в новом классе документа для
организации чтения/записи данных документа с диска и на диск.
Рассмотрим некоторые функции этого класса:
CDocTemplate* CDocument::GetDocTemplate() – возвращает указатель на объектшаблон для документа этого типа;
void CDocument::AddView(CView *pView) – присоединяет представление pView к
документу, добавляя его к общему списку представлений, ассоциированных с этим
документом. При этом указатель m_pDocument объекта pView устанавливается на этот
23
документ. Вызывается при создании нового представления документа, обычно
обработчиками команд ID_FILE_NEW, ID_FILE_OPEN, ID_WINDOW_NEW или при
организации разделяемого окна;
void CDocument::RemoveView(CView *pView) – отсоединяет представление pView
от документа и удаляет его из общего списка представлений, ассоциированных с этим
документом. Указатель m_pDocument представления устанавливается в NULL.
Вызывается при закрытии фрейма или области разделенного окна;
virtual POSITION CDocument::GetFirstViewPosition() – позволяет получить позицию
первого представления в общем списке представлений, ассоциированных с документом.
Используется для начала поиска требуемого представления;
virtual CView* CDocument::GetNextView(POSITION &rPosition) – возвращает
указатель на объект класса CView или производного от него, определяемый егопозицией
rPosition в списке представлений документаю После этого устанавливает rPosition в
значение для следующего представления в списке. Если полученное представление
последнее в списке, то rPosition устанавливается в NULL. Позиция представления в списке
полностью определяется порядком записи в него;
void CDocument::UpdateAllViews() – обновить представления;
virtual BOOL CDocument::CanCloseFrame(CFrameWnd *pFrame) вызывается
библиотекой MFC перед закрытием фрейма документа. Реализация базового класса
проверяет все фреймы, которые отображают документ, и, если этот фрейм единственный
и были изменения, выводит на экран окно запроса на сохранение. Переопределяется, если
нужна специальная обработка по закрытию фрейма.
virtual void CDocument::DeleteContents() – удаляет данные документа без
разрушения самого объекта. Вызывается непосредственно перед разрушением документа
или при многократном его использовании. Реализация базового класса не делает ничего,
поэтому данную функцию надо переопределять;
virtual BOOL CDocument::OnNewDocument() – вызывается каркасом приложения
при обработке команды ID_FILE_NEW. Реализация базового класса вызывает функцию
DeleteContents для очистки документа и подготовки его к новым данным. Для SDIприложений эта функция вместо создания нового переинициализирует существующий
объект документа. Для MDI-приложений каждый раз создается новый объект и в этой
функции производится его инициализация.
virtual BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName) – вызывается
MFC при обработке команды ID_FILE_OPEN. Реализация по-умолчанию открывает файл,
имя которого определяется параметром, вызывает функцию DeleteContents для очистки
документа, функцию Serialize для чтения содержимого файла и помечает документ как
немодифицированный. Переопределяется, если испольщуется какой-либо специальный
механизм для работы с архивами или файлами – например распознавание типа документа.
virtual BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName) – вызывается
каркасом приложения при обработке команд ID_FILE_SAVE или ID_FILE_SAVE_AS.
Реализация по умолчанию открывает файл, имя которого определяется параметром,
вызывает функцию Serialize для записи в файл данных документа и помечает его как
немодифицированный.
Сериализация
Идея сериализации состоит в том, что существуют устойчивые «persistent»
объекты, которые можно сохранять на диске при завершении программы и
восстанавливать при следующем запуске. Этот процесс называют «сериализацией» преобразованием в последовательную форму и обратно. Поддерживающие сериализацию
классы библиотеки MFC содержат виртуальную функцию-член Serialize, определенную в
CObject. Наличие виртуальной функции позволяет унифицировать процесс
сохранения/восстановления объектов. Для реализации процесса следует переопределить
24
виртуальную функцию. Ее вызов осуществляется каркасом приложения по указателю на
объект базового класса.
Сериализация не заменяет систему управления БД. Все объекты, связанные с
конкретным документом, последовательно считываются или записываются в один
дисковый файл.
Для того, чтобы сделать класс сериализуемым, он должен быть производным от
CObject. Кроме того, в объявлении класса должен присутствовать макрос
DECLARE_SERIAL, а в файле реализации – макрос IMPLEMENT_SERIAL. Необходимо
переопределить виртуальную функцию Serialize(CArchive& ar). В этой функции
необходимо реализовать чтение/запись в файл данных, определенных в этом классе, и
вызов функции Serialize соответствующего базового класса, которая обеспечит работу с
его данными. Функции Serialize для классов CObject и CDocument не делают ничего
полезного, поэтому вызывать их не имеет смысла.
Параметр функции Serialize имеет тип ссылка на CArchive. Это так называемый
класс архива. В MFC дисковые файлы представляются объектами класса Cfile, которые
инкапсулируют описатель двоичного файла. Между функцией сериализации и объектом
CFile располагается объект-архив класса CArchive. Он буферизует данные для объекта
CFile и поддерживает внутренний флажок, указывающий, записывается ли архив на диск
или считыватся с него. С каждым файлом одновременно может быть связан только один
активный архив. За создание объектов CFile и Carchive, открытие дискового файла и
связывание объекта-архива с файлом отвечает каркас приложений. Функция Serialize
просто работает с объектом архивом, ссылку на который получает в качестве параметра.
Функция-член BOOL CArchive::IsStoring возвращает не 0, если архив сохраняется и
0 в ином случае. У класса CArchive имеются переопределенные операторы вставки (<<) и
извлечения (>>) для многих встроенных типов С++. У многих MFC-классов, не
являющихся производными от CObject (например CString, CRect), есть свои
переопределенные операторы вставки и извлечения для CArchive. Пример:
class CStudent : public CPerson
{
public:
CString
m_strSubject;
int
m_nGrade;
…
virtual void Serialize(CArchive& ar);
…
}
…
void CStudent::Serialize(CArchive& ar)
{
CPerson::Serialize(ar);
if (ar.IsStoring())
{
ar<<m_strName<<m_nGrade;
}
else
{
ar>>m_strName>>m_nGrade;
}
};
Операторы вставки принимают параметры по значению, операторы извлечения –
по ссылке. Иногда приходится прибегать к преобразованию типов – например при
использовании перечислимого типа:
25
ar<<(int) m_nType;
ar>>(int&)m_nType;
Если в сериализуемый объект внедрены другие объекты, являющиеся объектами
классов, производных от CObject – т.е. сериализуемых, то для них надо явно вызвать
функцию сериализации, т.к. для CObject не предусмотрены переопределенные операторы
>> и <<.
Если класс содержит указатель на объект класса, производного от CObject, то
можно либо явно вызвать для него функцию Serialize, при этом следует явно создать
объект при чтении данных. Другой вариант – так как операторы вставки и извлечения в
CArchive для указателей на CObject переопределены, то можно их использовать, при этом
за создание объекта класса отвечает каркас приложения.
Пример:
class CXXX : public CObject
{
public:
CYYY m_Y1;
CYYY m_Y2;
CYYY* m_pY3;
CYYY* m_pY4;
…
virtual void Serialize(CArchive& ar);
…
}
…
void CStudent::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar<<m_pY3;
//
ar<<&m_Y2; //нельзя т.к. операторы << >>не работают с
//
внедренными оьъектами классов, производных от CObject
}
else
{
ar>>m_pY3;
//
ar>>&m_Y2; //нельзя
m_pY4 = new CYYY;
}
m_Y1.Serialize(ar);
m_pY4->Serialize(ar);
};
Представления
Представления это специальная группа классов дочерних окон фреймов, которая
отвечает за отображение данных документа и за взаимодействие с пользователем.
Представление ассоциируется с некоторым документом (причем только одним) и
действует как посредник между ним и пользователем.
Классы представлений библиотеки MFC:
CView : CWnd
CCtrlView
CEditView
CRichEditView
CListView
26
CTreeView
CScrollView
CFormView
CHtmlView
CRecordView
CDaoRecordView
COleDBRecordView
Любой из них можно использовать в качестве базового класса для производного
класса вид приложения.
Класс CView
Этот класс предоставляет базовые функциональные возможности для всех классов
представлений. Основные функции этого класса:
CDocument* CView::GetDocument() – возвращает указатель на объект документ, к
которому присоединен данный объект вид;
virtual void CView::OnInitialUpdate() – вызывается каркасом приложений после
того, как представление присоединено к документу, но до его первого отображения.
Реализация по умолчанию вызывает OnUpdate. Можно переопределить для выполнения
инициализации представления;
virtual void CView::OnUpdate(…) – вызывается из CDocument::UpdateAllViews
после того, как документ был модифицирован. Реализация по умолчанию помечает всю
рабочую область как недействительную для ее перерисовки при получении следующего
сообщения WM_PAINT. При переопределении в нее добавляются действия, которые
изменяют состояние объекта представления в соответствии с изменениями,
произошедшими с объектом документ. В ней можно выполнять и непосредственно
перерисовку, но лучше передать координаты области, требующей перерисовки в функцию
InvalidateRect, чтобы осуществить рисование при получении следующего сообщения
WM_PAINT;
virtual void CView::OnDraw(CDC *pDC) = 0 – чисто виртуальная функция, которая
используется для изображения образа документа. Обязательно должна быть
переопределена в производных классах. Каркас приложения использует эту функцию как
для отображения представления на экране, так и для его печати. Вызывается когда
необходимо перерисовать окно представления, если либо пользователь изменил размеры
окна или открыл его невидимые ранее части или когда произошло изменение данных
документа. В первом случае вызывается каркасом приложения автоматически во втором –
должна быть вызвана функция Invalidate или InvalidateRect (например в функции
CView::OnUpdate), которые инициируют посылку сообщения WM_PAINT.
Функция InvalidateRect имеет параметр типа указатель на CRect, который задает
прямоугольник окна, который должен быть перерисован. Аналогично поступает и
Windows. Функция OnDraw может игнорировать эту информацию и переисовывать все
окно при каждом вызове или, для ускорения работы, получать недействительный
прямоугольник с помощью функции (CDC::GetClipBox) чтобы не рисовать за пределами
этого прямоугольника.
В качестве параметра в функцию OnDraw передается указатель на объект класса
CDC – контекст устройства.
Контекст устройства
Одной из главных особенностей Windows является независимость графического
вывода от устройства. Программное обеспечение, которое поддерживает независимость,
содержится в динамических библиотеках: GDI.DLL обеспечивает графический интерфейс
устройства вообще и для каждого конкретного устройства имеется драйвер (VGA.DLL,
EPSON9.DLL). Перед операцией вывода на некоторое устройство приложение должно
запросить GDI о загрузке соответствующего драйвера (происходит автоматически). Далее
происходит настройка параметров устройства. Эти данные: параметры и ссылка на
27
физическое устройство вывода – его имя, драйвер и другие атрибуты сохраняется ситемой
Windows в специальной структуре, называемой контекстом устройства (device context
DC).
Контекст устройства – это структура данных, связывающая приложение с
драйвером устройства, которая полностью определяет состояние драйвера и способ
вывода информации.
В Windows определены 4 основных типа контекстов устройств:
- для экрана;
- для принтера;
- для объектов в памяти;
- информационный – обеспечивает получение данных об устройстве.
В библиотеке MFC контекст устройства представлен в виде объекта класса CDC
(или производных). Все функции рисования осуществляются через функции-члены CDCобъекта.
Класс CCtrlView
Назначение этого класса – быть базовым для таких классов как CEditView,
CListView и .т.д. Служит только для установки некоторых общих параметров. Вся
основная работа ложиться на производные классы.
Класс CEditView
Этот класс подобно классу CEdit инкапсулирует возможности элемента управления
EDIT и может применяться для реализации простейшего текстового редактора. Но в
отличие от CEdit содержит также возможности класса представления в архитектуре
документ/вид. Вообще, он агрегирует объект класса CEdit.
Этот тип представления нарушает основной принцип архитектуры документ/вид –
данные хранятся в нем, а не в объекте документ. Имеет функции для доступа к этим
текстовым данным (SetWindowText, GetWindowText), функции поиска и замены, функции
для работы с буфером обмена.
Имеет функцию SerializeRaw для сериализации содержимого объекта в текстовый
файл. Эта функция вызывается из функции сериализации соответствующего документа:
void CTextDoc::Serialize(CArchive& ar)
{
// CEditView contains an edit control which handles all serialization
((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);
}
Класс CRichEditView
Класс CRichEditView подобен элементу управления CRichEdit. Он также позволяет
вводить и редактировать текст. Могут быть отформатированы символы и параметры.
Текст может включать внедренные OLE объекты. Класс обеспечивает программный
интерфейс форматирования текста. Класс CRichEditView обеспечивает хранение текста и
его формата (в нарушение принципа архитектуры документ/вид).
При создании приложения с представлением CRichEditView класс документа будет
производным от
CRichEditDoc, который содержит список OLE клиентов на
представлении. Этот класс инкапсулирует функцию сериализации:
void CText1Doc::Serialize(CArchive& ar)
{
CRichEditDoc::Serialize(ar);
}
Классы ClistView и CTreeView
Эти классы позволяют упростить использование элементов управления Windows
LIST VIEW и TREE VIEW и инкапсулировавших их возможности классов CListCtrl и
CTreeCtrl в архитектуре документ/вид. Они содержат всего по две функции:
конструкторы и
28
CListCtrl& CListView::GetListCtrl()
CTreeCtrl& CTreeView::GetTreeCtrl() – позволяют получить доступ к
соответствующим элементам управления.
Все остальные возможности они наследуют от своих базовых классов и
соответствующих элементов управления.
Класс CScrollView
Если класс представления порожден непосредственно от Cview, то решение
проблемы прокрутки и масштабирования целиком возлагается на программиста – в нем
должны обрабатываться сообщения от полос прокрутки. Класс CScrollView берет на себя
большую часть работы по поддержанию автоматической прокрутки и масштабирования.
Для реализации прокрутки в классе, производном от CView следует в
переопределенных функциях OnInitialUpdate и OnUpdate вызвать функцию SetScrollSize.
Ее вызов устанавливает режим отображения для представления, его полный размер
(который рассчитывается исходя из размера данных документа) и величины вертикальной
и горизонтальной прокрутки.
Для реализации масштабирования следует воспользоваться функцией
SetScaleToFitSize.
Класс CFormView
Класс, производный от CFormView связан с диалоговым ресурсом, определяющим
параметры окна и список элементов управления. Он поддерживает те же DDX- и DDVфункции обмена и проверки данных, что и CDialog.
Объект CFormView получает уведомляющие сообщения непосредственно от своих
элементов управления, а также принимает командные сообщения от каркаса приложений.
Класс CFormView не является производным от CDialog, поэтому он не содержит
виртуальных функций OnInitDialog, OnOK, OnCancel. Функции-члены класса CFormView
не вызывают UpdateData. Эту функцию нужно вызывать в обработчиках сообщений и в
функциях OnInitialUpdate и OnUpdate.
Разделяемые окна
Download