Министерство образования и науки Российской Федерации
Санкт-Петербургский государственный университет
М.В. Свиркин, А.С. Чуркин
Программирование под Windows в среде Visual C++ 2005
Санкт-Петербург 2008
Содержание Введение......................................................................................................................................... 4 Часть 1. Разработка Windows-приложений .............................................................................. 5 Глава 1. Программирования под Windows с использование Win API................................ 5 §1. Основные понятия и термины, используемые при разработке Windows приложений................... 5 §2. Создание простейшего Windows-приложения с использованием Win API..................................... 7 §3. Пример разложения в ряд функции. Графический вывод ............................................................... 12 Глава 2. Библиотека классов MFC ........................................................................................... 15 §1. Microsoft Foundation Classes (MFC). Иерархия классов................................................................... 15 §2. Обработка сообщений в MFC............................................................................................................. 17 §3. Ресурсы................................................................................................................................................. 19 §4. Элементы управления и диалоги ....................................................................................................... 23 §5. Графический вывод ............................................................................................................................. 33 §6. Разработка Windows-приложения c использованем библиотеки MFC .......................................... 41 Часть 2. Среда разработки MS Visual Studio 2005. Разработка полноценных Windowsприложений.................................................................................................................................. 56 Глава 1. Теоретические основы работы в среде MS Visual Studio 2005........................... 56 §1. Интегрированная среда разработки MS Visual Studio 2005. Понятие проекта и решения........... 56 §2. Утилиты и мастера MS Visual Studio 2005........................................................................................ 57 §3. Создание приложения по шаблону с помощью мастера MFC Application Wizard ....................... 60 Глава 2. Практика работы в среде визуального программирования ............................... 61 §1. Начало работы в Visual C++ ............................................................................................................... 61 §2. Панель инструментов, меню, акселераторы ..................................................................................... 65 §3. Диалоговые окна. Работа с элементами управления – кнопками, текстовыми полями ............... 67 §4. Списки. Комбинированные поля и бегунки...................................................................................... 72 §5. Сериализация. Работа с файлами ....................................................................................................... 75 §6. Графика. Работа с растровыми изображениями (фракталы) ........................................................... 80 Глава 3. Разработка полноценных Windows-приложений .................................................. 84 §1. Приложение «Интерполяционный полином»................................................................................... 84
2
§2. Приложение «Шифр Виженера»...................................................................................................... 106 §3. Приложение «Метод наименьших квадратов» ............................................................................... 124 Часть 3. Приложение ................................................................................................................ 131 Класс матриц............................................................................................................................................ 132 Класс полиномов ..................................................................................................................................... 140 Класс комплексных чисел....................................................................................................................... 141 Использованные материалы.................................................................................................. 145
3
Введение В данном пособии рассматривается раздел современной технологии программирования – визуальное программирование под Windows. Пособие является дополнением к материалу третьего семестра базового курса "Технологии программирования", читаемого на факультете "Прикладной математики – процессов управления" Санкт Петербургского государственного университета. Предполагается, что студенты прослушали два семестровых курса, посвященных процедурному программированию на языке С и объектно-ориентированному программированию (включая STL) на языке С++. В качестве среды разработки в пособии используется MS Visual Studio 2005, а языка программирования – язык С++. Пособие состоит из трех частей. Первая часть посвящена созданию приложений под Windows с использованием Win API и библиотеки классов MFC. В этой части рассматривается общая теория программирования под Windows, иерархия классов MFC и использование этой библиотеки при создании приложений. Во второй части рассматривается программирование на языке С++ в среде разработки MS Visual Studio 2005. В первой главе этой части дается теоретический материал по созданию в среде разработки Windowsприложений, вторая глава посвящена элементам практического создания Windowsприложений. Третья глава посвящена созданию полноценных Windows-приложений. Третья часть состоит из Приложения, в котором находится код классов, используемых для создания полноценных Windows-приложений. Целью данного пособия было дать необходимый для программирования под Windows теоретический материал, продемонтрировать практические элементы работы по созданию Windows-приложений в среде разработки MS Visual Studio 2005, дать примеры создания полноценных приложений с исследованием используемого математического аппарата. Например, при написании приложения "Интерполяционный полином" были рассмотрены вопросы интерполирования полиномами (создан класс полиномов), графики (функции для аппаратно независимого вывода графического изображения), работа с курсором и мышью, модальными диалоговыми окнами, растровыми изображениями, панелями управления и строками состояния. Выбор тем Windows-приложений основывался на желании авторов пособия осветить важные для студентов математиков, часто встречающиеся при программировании, вопросы – работа с графикой (приложение " Интерполяционный полином "), работа с текстовыми данными (приложение "Шифр Вижинера"), работа с матрицами (приложение "Метод наименьших квадратов"). Рассмотренные в пособии теоретические вопросы, практические примеры и полноценные Windows-приложения могут стать основой для активного использования студентами языка С++, среды разработки MS Visual Studio 2005 при создании своих собственных Windows-приложений.
4
Часть 1. Разработка Windows-приложений
Глава 1. Программирования под Windows с использование Win API §1. Основные понятия и термины, используемые при разработке Windows приложений 1. Ядро Windows:
•
USER (16, 32) .dll – функции ввода с клавиатуры мыши, ввод через интерфейс и т.д. (взаимодействие приложений с пользователями и средой Windows).
•
KERNEL (16, 32) .dll – функции операционной системы (память, распределение системных ресурсов, загрузка …). • GDI (16, 32) .dll – графический интерфейс (функции создания и отображения графических объектов). 2. GUI (Graphics User Interface) – стандартный графический интерфейс пользователя. Это та часть Windows, которая обеспечивает поддержку аппаратно-независимой графики. 3. API (Application Program Interface) — интерфейс прикладных программ (набор
функций, сосредоточенных в ядре Windows и дополнительных библиотеках). 4. DLL (Dynamic Link Libraries) — библиотека динамической компановки. Функции
API содержатся в библиотеках динамической загрузки. 5. DDE – динамический обмен данными. 6. Идентификатор – уникальное число (длинное целое), которое идентифицирует объект. Нотация Windows («венгерская нотация Чарльза Симони») При программировании под Windows принято использовать префиксы перед именами переменных, указывающие на принадлежность к типу данных. Рекомендуется давать имена собственным переменным и идентификаторам, придерживаясь следующих принципов: 1. мнемоническое значение – идентификатор должен легко запоминаться; 2. смысловое значение – роль идентификатора должна быть ясна из его названия; 3. преемственность – похожие объекты должны иметь похожие идентификаторы; 4. быстрота принятия решения – придумывание, ввод и редактирование
идентификатора не должны занимать много времени.
5
Некоторые префиксы венгерской нотации: Префикс A B By C Cb Cr cx,cy Dbl Dw Flt Fn g_ H hDC I Id L Lp Lpsz m_ N Np P Pfn Pst Psz Pv S Sz U Tm V W x, y
Значение массив логический тип (int) беззнаковый символьный тип (byte) символьный тип (1 байт) счетчик байтов цвет короткий тип (short) double (с плавающей точкой) беззнаковое длинное целое число (dword) float (вещественная с плавающей точкой) функция префикс для глобальной переменной (глобальная переменная) handle (беззнаковое целое число) handle (указатель на контекст устройства) целое (integer) интегральное значение идентификатора длинный тип (long) длинный указатель дальний указатель на строку, закачивающуюся нулевым байтом переменная класса short или int ближний указатель указатель указатель на функцию указатель на структуру указатель на строку, заканчивающуюся нулевым байтом указатель на тип void строка строка, заканчивающая нуль-символом беззнаковый символ текстовая метрика тип void беззнаковое целое (word, 16-бит) короткое целое число (координата x или y)
Часто используемые типы данных Windows: Тип данных HANDLE HWND HDC
Описание определяет идентификатор; 32-разрядное целое, используемое в качестве дескриптора – числа, определяющего некоторый ресурс определяет идентификатор окна определяет идентификатор контекста устройства 6
LONG LPSTR NULL UINT WCHAR
32-битовое целое со знаком определяет линейный указатель 0 тип данных Win32 (32 бита для Win32) 16-битовый символ UNICODE. Используется для представления символов языков мира
§2. Создание простейшего Windows-приложения с использованием Win API Элементы Windows-приложения Построение приложения Windows включает выполнение следующих этапов: 1. Создание WinMain(…) и связанных с ней функций на языке C или C++. 2. Создание описаний меню и всех дополнительных ресурсов, помещение описаний в файл описания ресурсов. 3. Создание уникальных курсоров, пиктограмм и битовых образов. 4. Создание диалоговых окон. 5. Создание файла проекта. 6. Компиляция и компоновка всего кода. Простейшая программа. Создание и вывод Windows-окна на экран 1. 2. 3. 4. 5. 6. 7.
Создадим пустой проект Windows- приложения с помощью мастера: File→New→Project. Project types: Win32 Templates: Win32 Project. Ok. Установить галочку Empty project. Добавить в проект файл *.cpp. Project→Properties. Вкладка Configuration Properties→General. Значение поля Character Set устанавливаем Use Multi-Byte Character Set.
Добавим следующий код: #include <windows.h> LONG WINAPI WndProc(HWND, UINT, WPARAM,LPARAM); int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND hwnd; MSG msg; WNDCLASS w; memset(&w,0,sizeof(WNDCLASS)); w.style = CS_HREDRAW | CS_VREDRAW; w.lpfnWndProc = WndProc; w.hInstance = hInstance; w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
7
w.lpszClassName = "My Class"; RegisterClass(&w); hwnd = CreateWindow("My Class", "Окно пользователя", WS_OVERLAPPEDWINDOW,500, 300, 500, 380, NULL, NULL, hInstance, NULL); ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam) { switch (Message) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, Message, wparam, lparam); } return 0; }
Скомпилируем и запустим программу. На экране появится Windows-окно. Комментарии к программе Все приложения Windows должны содержать два основных элемента: функцию WinMain(…) и функцию окна WndProc. Функция WinMain(…) служит точкой входа в приложение. Эта функция отвечает за следующие действия: 1. 2. 3. 4.
регистрацию типа класса окон приложения; выполнение всех инициализирующих действий; создание и инициализацию цикла сообщений приложения; завершение программы (обычно при получении сообщения WM_QUIT).
Функция WndProc отвечает за обработку сообщений Windows. Эта часть программы является наиболее содержательной с точки зрения выполнения поставленных перед программой задач. Если мы хотим, чтобы программа обращала на наши действия внимание, то необходимо добавить ветки case для оператора switch в оконную процедуру WndProc. Например, если мы хотим, чтобы наше приложение обращало внимание на щелчок левой кнопкой мыши – добавляем ветку case WM_LBUTTONDOWN. В настоящий момент в оконной процедуре происходит только обработка сообщения WM_DESTROY. Больше Windows-окно пока ничего делать не умеет. Заголовочный файл windows.h нужен для любой традиционной Windows программы на C. Именно в нём содержатся разные определения констант (WM_DESTROY и т. д.).
8
Параметры функции WinMain: 1. hInstance (тип HINSTANCE) – является идентификатором текущего экземпляра
приложения. Данное число однозначно определяет программу, работающую под управлением Windows. 2. hPrevInstance (тип HINSTANCE) – указывал ранее (Windows 3.1) на предыдущий запущенный экземпляр приложения. В современных версиях Windows он равен NULL. 3. lpCmdLine – это указатель на строку, заканчивающуюся нулевым байтом. В этой строке содержатся аргументы командной строки приложения (как правило, содержит NULL). 4. nCmdShow – этот параметр принимает значение одной из системных констант, определяющих способ изображения окна (например, SW_SHOWNORMAL, SW_SHOWMAXIMIZED или SW_SHOWMINIMIZED). Регистрация класса окна Каждое окно, которое создается в рамках приложения Windows, должно основываться на классе окна – шаблоне, в котором определены выбранные пользователем стили, шрифты, заголовки и т.д. Для всех определений класса окна используется стандартный тип структуры. Таким образом, сначала определяется структура WNDCLASS w, а затем поля структуры заполняются информацией о классе окна. У этой структуры много полей, но большинство из них можно определить нулем. В программе это делается строкой memset(&w,0,sizeof(WNDCLASS)); Рассмотрим следующий фрагмент программы: w.style = CS_HREDRAW | CS_VREDRAW; w.lpfnWndProc = WndProc; w.hInstance = hInstance; w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); w.lpszClassName = "My Class"; RegisterClass(&w);
В этом фрагменте определяется стиль класса (все идентификаторы стилей начинаются с префикса CS_). В программе значения поля стиля задаются константами. CS_HREDRAW – обеспечивает перерисовку содержимого клиентской области окна при изменении размера окна по горизонтали, CS_VREDRAW – обеспечивает перерисовку содержимого клиентской области окна при изменении размера окна по вертикали. w.lpfnWndProc = WndProc ─ значение указателя на функцию окна (WndProc), которая выполняет все задачи, связанные с окном. w.hInstance = hInstance ─ определяется экземпляр приложения, регистрирующий класс окна. w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH) ─ определяется кисть, используемая для закраски фона окна. w.lpszClassName = "My Class" ─ указатель на строку символов, заканчивающуюся на 0, которая определяет имя класса. Класс здесь – это не класс в смысле ООП (объектно
9
ориентированного программирования). Термин один, но смысла два. Исторически классы окон возникли раньше, чем классы ООП. RegisterClass(&w) ─ регистрация происходит при помощи вызова функции RegisterClass(). Создание окна на основе класса окна
hwnd = CreateWindow("My Class","Окно пользователя", WS_OVERLAPPEDWINDOW,500,300,500,380,NULL,NULL,hInstance,NULL); Первый параметр функции служит для задания класса окна. Второй – это заголовок окна. Третий – определяет стиль окна (обычное перекрывающее окно с заголовком, кнопкой вызова системного меню, кнопками минимизации и максимизации и рамкой). Следующие шесть параметров определяют положение окна на экране (по оси X и по оси Y), размеры окна по оси X и по оси Y, идентификатор родительского окна и идентификатор меню окна. Следующее поле (hInstance) содержит идентификатор экземпляра программы, далее следует информация об отсутствии дополнительных параметров (NULL). Если создание окна прошло успешно, то функция CreateWindow(…) возвращает идентификатор созданного окна, в противном случае – NULL. После того как окно создано, его надо показать и обновить. Для того чтобы вывести главное окно приложения на экран, необходимо вызвать функцию Windows ShowWindow(…). ShowWindow(hwnd,nCmdShow) выводит окно на экран. Параметр hwnd содержит идентификатор окна, созданного при вызове CrerateWindow(…). Второй параметр определяет, как окно выводится в первый момент. Последний шаг при выводе окна на экран заключается в вызове функции Windows UpdateWindow(hwnd), которая приводит к перерисовке клиентской области окна. Создание цикла обработки сообщений Теперь программа готова выполнять свою главную задачу – обрабатывать сообщения. Используется стандартный цикл С/С++ цикл while: while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
Использование функции GetMessage Вызов этой функции позволяет получить для обработки следующее сообщение из очереди сообщений приложения. Данная функция копирует сообщение в структуру сообщения, на которую указывает указатель msg, и передает его в основной блок программы. Если значение следующего параметра NULL, то будут поступать сообщения, относящиеся к любому окну приложения. Значения последних параметров (0,0) указывает функции, что не надо применять никаких фильтров сообщений. Фильтры могут применяться для распределения получаемых сообщений по категориям, например, нажатия на клавишу или перемещение мыши. После входа в цикл обработки сообщений, выйти из него можно,
10
получив только одно сообщение: WM_QUIT. Когда обрабатывается сообщение WM_QUIT, то возвращается значение “ложь” и цикл обработки сообщений завершается. Использование функции TranslateMessage Функция TranslateMessage(…) преобразует сообщения виртуальных клавиш в сообщения о символах. Использование функции DispatchMessage Функция DispatchMessage(…) используется для распределения текущего сообщения соответствующей функции окна. Оконная функция WndProc Вторая часть в любой программе под Windows – это оконная процедура. В рассматриваемом примере она маленькая (так как программа ничего не делает), но, именно эта часть и является самой главной и интересной в приложении. Рассмотрим функцию WndProc: LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam) { switch (Message) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, Message, wparam, lparam); } return 0; }
Основное назначение оконной функции – это обработка сообщений Windows. Каждое приложение получает много сообщений. Их источник может быть разным. Например, сообщения от пользователя или от самой Windows. Обработка этих сообщений происходит именно в оконной функции. Это означает, что для каждого сообщения необходимо написать свой обработчик. Если обработчика не будет, то приложение не будет обращать внимание на сообщение. У оконной функции четыре параметра. Первый из них hwnd типа HWND задает окно, которое будет обрабатывать сообщение. Второй UINT Message – это передаваемое сообщение. Два последних WPARAM wparam, LPARAM lparam задают дополнительные параметры для передаваемого сообщения. Они для каждого сообщения свои. Оконная процедура отправляет сообщение в switch, который в примере имеет только один case: switch (Message) { case WM_DESTROY: ...
То есть, пока рассматриваемая программа обращает внимание только на сообщение WM_DESTROY. Это сообщение окно получает только при своем уничтожении. После принятия этого сообщения необходимо вызвать функцию PostQuitMessage(…). В ответ на сообщение WM_DESTROY необходимо поместить в очередь сообщение WM_QUIT. Это и
11
делает функция PostQiutMessage(…), посылая это сообщение в очередь и говоря, что процесс должен быть завершен. Если мы хотим, чтобы наша программа реагировала ещё на чтонибудь, то надо написать еще case. Рассмотрим далее ветку default. В ней идёт вызов функции DefWindowProc(hwnd, Message, wparam, lparam). Основное предназначение этой функции – обработка сообщений Windows, которые не обрабатываются в нашей программе (то есть, для которых нет своего case). При этом ничего не делается, но очередь из сообщений идет.
§3. Пример разложения в ряд функции. Графический вывод Проиллюстируем рассмотренный в предыдущем параграфе теоретичекий материал на примере написания Windows-приложения решения задачи о разложении в ряд некоторой функции. Задача: Разложить в ряд Тейлора в окрестности точки 0 функции sin(x), cos(x) и вывести в окне график функции f ( x) = sin( x) + cos( x) .
Напишем функции вычисления синуса и косинуса – разложения в ряд Тейлора в окрестности точки 0. Разложения в ряд имеют вид: sin( x) = x −
x3 x 2⋅n −1 + ... + (−1) n −1 ⋅ + ... 3! (2 ⋅ n − 1)!
cos( x) = 1 −
x2 x 2⋅n + ... + (−1) n ⋅ + ... 2 (2 ⋅ n)!
Радиус сходимости для этих рядов – ∞ . const const const const
double double double double
epsilon = 0.0000001; pi = 3.14159265; x_start = -2*pi; x_end = 2*pi;
double sin_e(double arg, double eps) { double result = 0, rn = arg; for(int i = 1; ;i++) { if(rn < eps && rn > -eps) return result; result += rn; rn = -rn*arg*arg/(i+1)/(i+2); i++; } } double cos_e(double arg, double eps) { double result = 0, rn = 1; for(int i = 0; ;i++) { if(rn < eps && rn > -eps) return result; result += rn; rn = -rn*arg*arg/(i+1)/(i+2); i++; } }
12
Для дальнейшего решения задачи – вывода графика функции в клиентском окне, будем обрабатывать сообщения, которые будут посылаться окну. Когда необходима перерисовка окна (изменились размеры окна, часть окна перекрылась другим окном и т.п.), система посылает окну сообщение
WM_PAINT. Добавим функции, которые будут выводить в клиентскую часть окна график и координатные оси, а также, необходимые ветви case в операторе switch. Для построения изображения используем API функции операционной системы: MoveToEx(…), LineTo(…), GetClientRect(…) и т.д. Информацию об этих функциях них можно получить в справочной системе. void DrawAxis(HDC hdc, RECT rectClient) { HPEN penGraph = CreatePen(PS_SOLID,2,RGB(0,0,255)); HGDIOBJ gdiOld = SelectObject(hdc, penGraph); MoveToEx(hdc, 0, rectClient.bottom/2, NULL); LineTo(hdc, rectClient.right, rectClient.bottom/2); LineTo(hdc, rectClient.right - 5, rectClient.bottom/2 + 2); MoveToEx(hdc, rectClient.right, rectClient.bottom/2, NULL); LineTo(hdc, rectClient.right - 5, rectClient.bottom/2 - 2); MoveToEx(hdc, rectClient.right/2, rectClient.bottom, NULL); LineTo(hdc, rectClient.right/2, rectClient.top); LineTo(hdc, rectClient.right/2 - 2, rectClient.top + 5); MoveToEx(hdc, rectClient.right/2, rectClient.top, NULL); LineTo(hdc, rectClient.right/2 + 2, rectClient.top + 5); SelectObject(hdc, gdiOld); } void DrawGraph(HDC hdc, RECT rectClient) { HPEN penGraph = CreatePen(PS_SOLID,2,RGB(255,0,0)); HGDIOBJ gdiOld = SelectObject(hdc, penGraph); double x_current = x_start; double step = (x_end - x_start)/rectClient.right; double y_start = cos_e(x_start, epsilon) + cos_e(x_start, epsilon); MoveToEx(hdc, 0, int(-y_start/step) + rectClient.bottom/2, NULL); while(x_current < x_end) { x_current += step; double y_next = cos_e(x_current, epsilon) + cos_e(x_current, epsilon); LineTo(hdc, int(x_current/step) + rectClient.right/2, int(y_next/step) + rectClient.bottom/2); } SelectObject(hdc, gdiOld); } void OnPaint(HWND hwnd) { PAINTSTRUCT ps; RECT rectClient; HDC hdc = BeginPaint(hwnd,&ps); GetClientRect(hwnd, &rectClient); DrawAxis(hdc, rectClient); DrawGraph(hdc, rectClient); ValidateRect(hwnd,NULL); EndPaint(hwnd,&ps); }
13
LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam) { switch (Message) { case WM_PAINT: OnPaint(hwnd); break; case WM_MOUSEMOVE: SetCapture(hwnd); SetCursor(LoadCursor(NULL,IDC_ARROW)); ReleaseCapture(); break; case WM_WINDOWPOSCHANGED: UpdateWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, Message, wparam, lparam); } UpdateWindow(hwnd); return 0; }
Функция DrawGraph(…) выводит на экран график, функция DrawAxis(…) выводит координатные оси. При выводе графика функции на экран используем преобразование системы координат окна приложения в логическую систему координат и наоборот. Подробно этот вопрос рассмотрен в §5. главы 2 (теория) и §1. главы 3 части 2. В результате в клиентской области окна получим искомый график фнкции f ( x) = sin( x) + cos( x) :
14
Глава 2. Библиотека классов MFC §1. Microsoft Foundation Classes (MFC). Иерархия классов MFC – это библиотека классов, написанных на языке C++. MFC является оболочкой для Win32 API и содержит многоуровневую иерархию классов. Не все функции Win32 API включены в MFC. С другой стороны, эта библиотека классов охватывает большую часть функциональных возможностей Windows, и предоставляет разработчику ряд дополнительных механизмов для проектирования и создания программных продуктов.
На вершине иерархии MFC находится единственный базовый класс – CObject. Все остальные классы библиотеки MFC можно условно разбить на две группы: производные и не производные от него. Чаще всего, создание нового MFC-приложения поручается мастеру MFC Application Wizard. Мастер генерирует основной скелет приложения, который впоследствии заполняется нужным кодом, давая готовое приложение. Основные классы MFC
Некоторые классы MFC порождаются непосредственно от CObject. Наиболее широко используемыми среди них являются CCmdTarget, CFile, CDC, CGDIObject и CMenu. Класс CmdTarget предназначен для обработки сообщений. Класс CFile предназначен для работы с файлами. Класс CDC обеспечивает поддержку контекстов устройств. В этот класс включены практически все функции графики GDI. CGDIObject является базовым классом для различных GDI-объектов, таких как перья, кисти, шрифты и другие. Класс СMenu предназначен для работы меню. Класс CCmdTarget
От класса CCmdTarget порождается очень важный класс CWnd. Он является базовым для создания всех типов окон, включая масштабируемые ("обычные") и диалоговые, а также различные элементы управления. Наиболее широко используемым производным классом является CFrameWnd. В большинстве программ главное окно создается с помощью именно этого класса. От класса CCmdTarget, через класс CWinThread, порождается единственный из наиболее важных классов, обращение к которому в MFC-программах происходит напрямую, это класс CWinApp. Это один из фундаментальных классов, поскольку предназначен для создания самого приложения. В каждой программе имеется один и только один объект этого класса. Как только он будет создан, приложение начнет выполняться. Класс CWinApp
Класс CWinApp является базовым классом, на основе которого образуют обязательный объект – приложение Windows. Основными задачами объекта этого класса являются инициализация и создание главного окна, а затем опрос системных сообщений. Иерархия класса CWinApp: CObject → CCmdTarget → CWinThread → CWinApp
15
Класс CWnd
Класс CFrameWnd ("окна-рамки") и производные от него классы определяют окна-рамки на мониторе. Элементы управления, создаваемые при проектировании интерфейса пользователя, принадлежат семейству классов элементов управления. Появляющиеся в процессе работы приложения диалоговые окна – это объекты классов, производных от CDialog. Классы CView, CFrameWnd, CDialog и все классы элементов управления наследуют свойства и поведение своего базового класса CWnd ("окно"), определяющего, по существу, Windows-окно. Этот класс, в свою очередь, является наследником базового класса CObject ("объект"). Как правило, структура приложения определяется архитектурой Document-View (документвид). Это означает, что приложение состоит из одного или нескольких документов – объектов, классы которых являются производными от класса CDocument (класс "документ"). С каждым из документов связаны один или несколько видов – объектов классов, производных от CView (класс "вид "), и определяющих методы обработки объектов класса документа. Соглашение об именах MFC
В качестве префикса, обозначающего имя класса, библиотека MFC использует заглавную букву "C" (от слова "class"), за которой идет имя, характеризующее назначение класса. Например: • CWinApp – класс, определяющий приложение; • CWnd – базовый класс для всех оконных объектов; • CDialog – класс диалога. При определении имен функций-членов классов используется три варианта: 1. Имя объединяет глагол и существительное – DrawText (нарисовать текст). 2. Имя состоит только из существительного – DialogBox (блок диалога). 3. Для функций, предназначенных для преобразования одного типа в другой, используются такие имена, как XtoY (из X в Y). Для членов классов библиотеки MFC используется следующий способ назначения имен: обязательный префикс m_ (от class member – член класса), за которым идет префикс, характеризующий тип данных, и завершается все заданием содержательного имени переменной. Например, m_pMainWnd – указатель на класс главного окна. Для переменных, которые не являются членами класса, m_ не ставится. Включаемые файлы AFXWIN.H – содержит описание основных классов библиотеки и сводит воедино все включаемые файлы, необходимые для работы MFC. AFX.H – содержит описания классов общего назначения, макросы, базовые типы данных MFC. AFXRES.H – подключает стандартные идентификаторы ресурсов.
16
§2. Обработка сообщений в MFC Операционная система Windows взаимодействует с приложением, посылая ему сообщения. Таким образом, обработка сообщений является ядром всех приложений. В традиционных приложениях Windows (написанных с использованием только API), каждое сообщение передается в качестве аргументов оконной функции. В оконной функции, с помощью оператора switch, определяется тип сообщения, извлекается информация и производятся нужные действия. Используя библиотеку MFC, все это можно сделать проще. Карта сообщений
Для создания стандартного окна в приложении должен наследоваться класс от CFrameWnd. Он содержит конструктор и макрос DECLARE_MESSAGE_MAP(). Макрос декларирует карту сообщений, которая определяет, какая член-функция класса должна вызываться в ответ на сообщение Windows. Этот макрос применяется для любого окна, в котором обрабатываются сообщения. Он должен быть последним в декларировании класса, использующего карту сообщений. В конце программы помещается реализация карты сообщений: BEGIN_MESSAGE_MAP(CMainWnd /*класс окна*/, CFrameWnd /*класс-предок*/) END_MESSAGE_MAP() Первый макрос всегда имеет два параметра, первый – класс окна, второй – класс, от которого порожден класс окна. В данном примере карта сообщений пустая, то есть все сообщения обрабатывает MFC. В библиотеке MFC все возможные сообщения разделены на три основные категории: 1. сообщения Windows; 2. извещения элементов управления; 3. командные сообщения (команды). В первую категорию входят сообщения, имена которых начинаются с префикса WM_, за исключением WM_COMMAND. Во вторую категорию входят извещения (nitification messages) от элементов управления и дочерних окон, направляемых родительским окнам. Третья категория охватывает все сообщения WM_COMMAND, называемых командами (командными сообщениями), от объектов интерфейса пользователя, который включает меню, кнопки панелей инструментов и акселераторы. В MFC включен набор предопределенных функций – обработчиков сообщений, которые можно использовать в программе. Если программа содержит такую функцию, то она будет вызываться в ответ на поступившее, связанное с ней, сообщение. При наличии дополнительной информации в сообщении, она передается в качестве аргументов функции. Для организации обработки сообщений нужно выполнить следующие действия: 1. Включить в карту сообщений программы команду соответствующего сообщения. 2. Включить прототип функции-обработчика в описание класса, ответственного за обработку данного сообщения. 3. Включить в программу реализацию функции-обработчика.
17
Включение макрокоманд в карту сообщений
Чтобы программа могла ответить на сообщение, в карту сообщений должна быть включена соответствующая макрокоманда. Названия макрокоманд соответствуют именам стандартных сообщений Windows, но дополнительно имеют префикс ON_ и заканчиваются парой круглых скобок. Из этого правила есть исключение: сообщению WM_COMMAND соответствует макрокоманда ON_COMMAND(...). Причина в том, что это сообщение обрабатывается особым образом. Чтобы включить макрокоманду в очередь сообщений, необходимо поместить ее между командами BEGIN_MESSAGE_MAP(…) и END_MESSAGE_MAP(). Например, если необходимо обработать в программе сообщение WM_CHAR, то очередь должна выглядеть так: BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd) ON_WM_CHAR() END_MESSAGE_MAP() В очереди может находиться более одной макрокоманды. Сообщение WM_CHAR генерируется при нажатии алфавитно-цифровой клавиши на клавиатуре. Включение обработчиков сообщений в описание класса
Каждое сообщение, явно обрабатываемое в программе, должно быть связано с одним из обработчиков. Обработчик – это член-функция класса, вызываемая приложением в ответ на сообщение, связанное с ней с помощью карты сообщений.
Прототипы для обработчиков всех сообщений заранее заданы в MFC. Например, объявим класс с обработчиком сообщения WM_PAINT. Это сообщение посылается окну, когда оно должно перерисовать свою клиентскую область. Пример: Class CMainWnd: public CFrameWnd { public: CMainWnd(); afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() }
Спецификатор afx_msg означает объявление обработчика сообщения. На данный момент он не используется и представляет собой пустой макрос. Но в будущем возможны расширения. Поэтому использование спецификатора нужно считать обязательным. Для каждого обработчика должна быть описана его реализация. В ней могут производиться самые разные действия, которые требуются по логике работы программы. Сообщение WM_PAINT
Операционная система Windows устроена таким образом, что за обновление содержимого окна отвечает программа. Например, если часть окна была перекрыта другим окном, а затем
18
вновь открыта, или минимизированное окно было восстановлено, то окну посылается сообщение WM_PAINT. В ответ на него окно должно обновить свою клиентскую область. Прототип обработчика WM_PAINT следующий: afx_msg void OnPaint(); Макрокоманда называется ON_WM_PAINT(). Для примера создадим обработчик, который выводит строку "Использование OnPaint()" в клиентскую область по координатам x = 25, y = 25: afx_msg void CMainWnd::OnPaint() { CPaintDC paintDC(this); paintDC.TextOut(25, 25, CString("Использование OnPaint()")); }
В обработчике WM_PAINT нужно всегда пользоваться классом CPaintDC, который представляет собой класс клиентской области, но предназначенный для использования именно с этим сообщением. Это обусловлено архитектурой самой Windows. Функция TextOut(…) предназначена для вывода текста в контекст устройства (в данном случае – в окно). При ее использовании, по умолчанию первые два параметра определяют координаты верхнего левого угла текстовой строки. По умолчанию координаты представляют собой реальные пиксели, ось x направлена слева направо, ось y – сверху вниз. Эта функция перегруженная, наиболее удобный для нас вариант – когда третий параметр имеет тип CString. Этот класс входит в MFC и является очень удобной заменой для строк, завершаемых нулем. Большинство реальных окон (за исключением диалогов) должны обрабатывать сообщение WM_PAINT. Более того, если Вы хотите написать корректную программу, то весь вывод в окно должен осуществляться только в обработчике WM_PAINT. В случае получения контекста из обработчика WM_PAINT с помощью класса CPaintDC, Windows гарантирует наличие свободного контекста.
§3. Ресурсы Понятие ресурсов
Структура EXE-файла для Windows такова, что в его конец могут быть записаны некоторые данные, совершенно не зависящие от самой программы. Они называются ресурсами. Ресурсы могут редактироваться совершенно раздельно, хотя находятся в том же файле, где код и данные. При загрузке программы на выполнение, ресурсы обычно не загружаются в память, а это делается лишь по запросу от программы. В ресурсах программа может хранить любые данные, какого угодно размера. Но есть стандартные типы ресурсов, такие как иконки, битовые образы, курсоры, диалоги, меню. Большинство диалоговых окон не создаются программным путем, а просто их шаблоны загружаются из ресурсов. Сами шаблоны, как и другие ресурсы, редактируются визуально с помощью специальных ресурсных редакторов. При сборке проекта приложения, ресурсы добавляются к EXE-файлу уже после связывания. Для описания ресурсов существует специальный язык, а сами описания хранятся в текстовых файлах с расширением rc. Раньше программисты вручную писали сценарии ресурсов на языке ресурсов, сейчас используются визуальные редакторы.
19
Перед добавлением к исполняемому файлу, сценарии преобразуются в бинарный вид с помощью компилятора ресурсов, в результате получается файл с расширением res. Как правило, все эти шаги выполняются автоматически при работе из интегрированной среды Visual C++. Заметим, что “ручное” редактирование ресурсных сценариев требуется сейчас уже очень редко, лишь при определении нестандартных ресурсов в сложных проектах. Каждый ресурс имеет свой уникальный идентификатор. Это может быть либо строка, либо число (константа). Числа можно использовать всегда, а строки – не всегда. Редактор ресурсов из Visual C++ помещает имена констант в файл resource.h, который нужно включить в файлы программы. Стандартные идентификаторы хранятся в файле afxres.h, который обычно используется автоматически ресурсным редактором. Меню
Меню, как правило, создаются визуально. В Visual C++ за это отвечает редактор ресурсов. Среда автоматически добавляет в проект сценарий ресурсов. При создании меню, для отдельных пунктов могут быть установлены опции выделения серым цветом (в этом случае при выполнении программы пункт меню будет недоступен), вставки разделительной горизонтальной черты, перехода на новую строку (в этом случае пункты верхнего уровня будут начинаться с новой строки, а нижнего – в новом столбце через вертикальную черту). Меню, как отдельному ресурсу, должен быть присвоен числовой или символьный идентификатор. При редактировании символьные идентификаторы заключаются в кавычки. Также, каждому пункту меню должен быть присвоен уникальный числовой идентификатор. Это позволит программе реагировать на выбор пункта в меню, и будет вызываться соответствующий обработчик. По принятому соглашению, все идентификаторы пунктов меню начинаются с IDM_. В самих названиях пунктов можно указывать ключевые клавиши, поставив перед буквой символ &. В этом случае, если меню активно, пункт можно выбрать также и с клавиатуры. Включение меню в окно приложения
Когда ресурс меню уже создан, его можно использовать в окне программы. Это можно сделать, указывая меню при создании окна: строковый идентификатор ресурса меню нужно указать в качестве последнего параметра в функции Create(): this->Create(0, "Приложение с меню", WS_OVERLAPPEDWINDOW, rectDefautl, 0, "MYMENU"); В результате будет создано окно с меню. Но, для того чтобы меню можно было использовать, необходимо создать обработчики сообщения WM_COMMAND для каждого пункта меню. Если для какого-то пункта нет обработчика, то MFC заблокирует этот пункт (он будет выделен серым цветом). Сообщение WM_COMMAND
Это очень широко используемое сообщение. Так, оно посылается окну, когда пользователь выбирает пункт в меню. Идентификатор пункта меню передается как параметр сообщения. Идентификатор определяет, какой из обработчиков должен быть вызван. Для размещения обработчика этого сообщения используется следующая макрокоманда:
20
ON_COMMAND(Идентификатор, ИмяОбработчика); Каждый обработчик для WM_COMMAND должен возвращать значение void. Обработчики не имеют параметров. Имя выбирается произвольно, обычно используется префикс On. Таким образом, можно написать обработчики для каждого пункта меню. Акселераторы
Это специальный ресурс, не имеющий визуального представления. Он представляет собой таблицу из комбинаций клавиш и соответствующих им идентификаторов команд. Таблица может быть загружена для конкретного окна с помощью функции с прототипом: BOOL CFrameWnd::LoadAccelTable(LPCSTR ResourceName); После загрузки таблицы акселераторов, нажатие заданных в ней комбинаций клавиш приводит к автоматической генерации сообщения WM_COMMAND с идентификатором, определенным в этой таблице для данной комбинации клавиш. Акселераторы легко создавать в среде Visual C++. Для каждого элемента таблицы нужно нажать желаемую клавишу или их комбинацию, и указать числовой идентификатор. Если указать идентификаторы, которые уже использовались в меню, то мы получим клавиши быстрого доступа, дублирующие команды меню. Окна сообщений
Это простейшие диалоговые окна, предопределенные в системе. Для создания окна сообщения используется функция с прототипом: int CWnd::MessageBox(LPCSTR MessageText, LPCSTR WindowTitle = 0, UINT MessageBoxType = MB_OK); Параметр MessageText определяет само сообщение. Параметр WindowTitle - заголовок окна сообщения. Параметр MessageBoxType задает стиль окна, иконку, отображаемую слева от сообщения, и одну, или несколько кнопок. Этот параметр задается комбинацией констант с помощью операции "|", начинающихся на префикс MB_. Все наборы кнопок заранее определены. Функция возвращает идентификатор нажатой кнопки: IDABORT, IDRETRY, IDIGNORE, IDCANCEL, IDNO, IDYES, или IDOK. Функция MessageBox(…) выполняет все действия по созданию, отображению и удалению окна, а также обработку сообщений. Программист не должен об этом заботиться. Иконки и курсоры
Иконки и курсоры являются ресурсами и обычно хранятся в области ресурсов исполняемого файла. Создание иконки и курсора
Для создания иконки и курсора нужно использовать ресурсный редактор. Иконки сохраняются в файлах с расширением ico, а курсоры – в файлах с расширением cur. Все курсоры имеют размер 32x32. Обычно используются монохромные курсоры. Каждый курсор имеет так называемую горячую точку, по которой определяется положение курсора на
21
экране. Она может располагаться в любом месте курсора. Для курсоров типа "указатель" это обычно вершина указателя. Иконки могут иметь размер 16х16, 32х32 и 48х48. Последний размер обычно не используется. Иконки могут иметь 16 или 256 цветов. При использовании редактора ресурсов среды Visual C++ 4.0 и выше иконки всех размеров можно хранить в одном файле, что обычно и делается. Для современных приложений обязательно наличие иконок размером как 16х16, так и 32х32. Загрузка иконки и курсора из ресурсов
Для загрузки иконок и курсоров удобно использовать функции Win API. Перед рассмотрением этих функций следует рассмотреть понятие дескриптора. Дескриптор – это 32-разрядное беззнаковое целое значение, которое идентифицирует объект в Windows.
При традиционном SDK-программировании дескрипторы используются очень широко. Так, свои дескрипторы имеют иконки, курсоры и само приложение. Для дескрипторов каждого объекта существует свой тип, например, HICON, HCURSOR, HINSTANCE. Будем предполагать, что в декларировании класса основного окна (речь идет о классе С++) объявлены переменные m_hIconSmall, m_hIconBig, m_hCursor – соответственно дескрипторы иконки 16х16, иконки 32х32 и курсора. Тогда для загрузки иконок и курсора нужно выполнить следующий код: // Получить дескриптор модуля (приложения) HINSTANCE hInst = AfxGetInstanceHandle(); // Загрузить иконку 16х16 m_hIconSmall = (HICON) ::LoadImage(hInst,MAKEINTRESOURCE(IDI_ICON),IMAGE_ICON,16,16,LR_DEFAULTCOLOR; // Загрузить иконку 32x32 m_hIconBig = (HICON) ::LoadImage(hInst,MAKEINTRESOURCE(IDI_ICON),IMAGE_ICON,32,32,LR_DEFAULTCOLOR; // Загрузить курсор m_hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR);
Сначала мы получаем дескриптор модуля с помощью глобальной функции MFC AfxGetInstanceHandle(). Затем мы загружаем маленькую и большую иконки с помощью функции Win API LoadImage(…), и получаем их дескрипторы. Эта функция позволяет загружать изображения из ресурсов и из файлов. И, наконец, мы загружаем курсор уже с помощью функции MFC LoadCursor(…), члена класса CWinApp. Функция AfxGetApp() возвращает адрес объекта приложения. Изменение иконки и курсора окна После того как мы получили дескрипторы иконок и курсора, надо изменить класс окна. Класс окна надо изменять для установки курсора, а для установки иконок использовать функции MFC. Для этого используется API-функция SetClassLong(…), которая изменяет атрибут класса окна (здесь опять имеется в виду структура данных). Первый параметр этой функции – дескриптор окна. Дескриптор окна хранится в члене класса MFC CWnd под названием m_hWnd. Если главным окном приложения является диалог, то для изменения иконки и курсора потребуется следующий код (он будет работоспособен и для обычных окон): // Устанавливаем курсор для диалогового окна. SetClassLong(m_hWnd, GCL_HCURSOR, (long) m_hCursor);
22
// Делаем то же самое для всех элементов управления for(int i = 0; i < 0xDFFF; ++i) { CWnd *pCtrl = this->GetDlgItem(i); if(pCtrl != 0) SetClassLong(pCtrl->m_hWnd, GCL_HCURSOR, (long) m_hCursor); } // Устанавливаем иконки. Здесь можно использовать MFC. this->SetIcon(m_hIconBig, TRUE); this->SetIcon(m_hIconSmall, FALSE);
Сначала, мы модифицируем курсор в оконном классе самого окна. Затем, то же самое проделываем со всеми элементами управления, которые есть или могут быть в диалоговом окне (если их нет в окне, то ничего страшного не произойдет). Мы перебираем все возможные идентификаторы, и, если элемент присутствует, изменяем его оконный класс. И, наконец, мы изменяем большую и маленькую иконки. Стандартные иконки и курсоры
Часто в программах необходимо использовать курсоры и иконки, уже предопределенные в Windows. Для этого можно использовать или функцию API LoadImage(…), или функции MFC LoadStandardIcon(…) и LoadStandardCursor(…). Работа со стандартными курсорами и иконками почти ничем не отличается от работы с пользовательскими курсорами и иконками. Для стандартных иконок есть предопределенные идентификаторы с префиксом IDI_, а для стандартных курсоров – с префиксом IDC_. Например, стандартный курсор в виде "песочных часов" имеет идентификатор IDC_WAIT.
§4. Элементы управления и диалоги Диалоги. Знакомство с элементами управления
Диалог (диалоговое окно) представляет собой специальный вид окон, которые предназначены для взаимодействия с пользователем. Обычно они используются для изменения настроек приложения и ввода информации. Например, практически все окна настроек приложения Microsoft Word являются диалогами. Есть два типа диалогов: модальные и немодальные. Наиболее распространены первые. В случае модальных диалогов, при активизации диалога основное окно приложения становится пассивным и перестает реагировать на действия пользователя до тех пор, пока он не закроет диалог. В случае немодальных диалогов, диалог существует независимо от других окон, и основное окно также может быть активизировано. Взаимодействие между диалогом и пользователем
Взаимодействие между диалогом и пользователем осуществляется с помощью элементов управления. Это особый тип окон для ввода или вывода. Элемент управления принадлежит окну-владельцу, в данном случае – диалогу. Все версии Windows поддерживают некоторый набор стандартных элементов управления, к которым относятся кнопки, контрольные переключатели, селекторные кнопки, списки, поля ввода, комбинированные списки, полосы прокрутки и статические элементы.
23
Рассмотрим кратко каждый из них: • Обыкновенная кнопка (push button) – это кнопка, которую пользователь "нажимает" мышью или клавишей Enter, переместив предварительно на нее фокус ввода. • Контрольный переключатель (check box, флажок) может быть либо выбранным, либо нет. Если в диалоге есть несколько контрольных переключателей, то могут быть выбраны одновременно несколько из них. • Селекторная кнопка (radio button) – это, почти тоже, что и контрольный переключатель. Отличие состоит в том, что при наличии нескольких кнопок в группе может быть выбрана только одна. • Список (list box) содержит набор строк, из которого можно выбрать одну или несколько. Широко используется при отображении имен файлов. • Поле ввода (edit box) – это элемент, позволяющий ввести строку текста. • Комбинированный список (combo box) представляет собой список со строкой ввода. • Статический элемент (static control) предназначен для вывода текста или графики, но не для ввода. Элементы управления способны как генерировать сообщения в ответ на действия пользователя, так и получать их от приложения. В последнем случае сообщения являются, фактически, командами, на которые элемент управления должен отреагировать. Классы MFC для элементов управления
В MFC содержатся классы для всех стандартных элементов управления. Эти классы описывают сами элементы, а также содержат функции для работы с ними. Их называют классами управления. Они порождаются от класса CWnd. Таким образом, все они обладают характеристиками окна. Ниже приведены основные классы управления: Класс CButton CEdit CListBox CComboBox CScrollBar CStatic
Элемент управления Кнопки, селекторные кнопки и контрольные переключатели Поля ввода Списки Комбинированные списки Полосы прокрутки Статические элементы
В MFC допускается непосредственное обращение к элементам управления, но на практике это происходит очень редко. Удобнее пользоваться соответствующими классами. Наиболее часто элементы управления используются с диалоговыми окнами, хотя можно создавать и отдельные элементы, расположенные в главном окне. Диалоги как ресурсы
Диалоги не создаются программно. При необходимости из ресурсов загружаются описания диалогов, и Windows по этому описанию формирует окно и размещает на нем все элементы управления. Диалоги редактируются визуально из ресурсного редактора. Диалог вместе со
24
всеми элементами управления представляет собой один ресурс со своим идентификатором. Кроме того, каждый элемент управления имеет свой идентификатор, который может быть только числовым. Обычно идентификаторы имеют префикс в соответствии с названием данного элемента управления, хотя при желании можно использовать любые идентификаторы. Класс CDialog
В MFC все диалоги являются экземплярами либо класса CDialog, либо порожденных от него классов. Лишь самые простые диалоги используют непосредственно класс CDialog. В общем же случае, необходимо определять собственный класс. Класс CDialog имеет конструкторы со следующими прототипами: CDialog::CDialog(LPCSTR ResourceName, CWnd *Owner = 0); CDialog::CDialog(UINT ResourceID, CWnd *Owner = 0); CDialog::CDialog(); Параметр ResourceName или ResourceID определяет идентификатор диалога в ресурсах, строковый или числовой. Параметр Owner – это указатель на окно-собственник, если равен 0, то собственником будет главное окно приложения. Последняя форма конструктора предназначена для создания немодальных диалогов. Обработка сообщений от диалогов
Все диалоги являются разновидностью окон, поэтому для них используется такой же механизм сообщений, как и для главного окна. Для каждого диалога организуется собственная очередь сообщений, так же точно, как и для главного окна. Когда элемент управления диалога активизируется, диалогу посылается сообщение WM_COMMAND. С этим сообщением передается идентификатор элемента управления. Для обработки сообщений в карту сообщений диалога нужно поместить макрос ON_COMMAND(). Многие элементы управления генерируют также идентификационный код, который позволяет определить, какое действие было произведено с элементом управления. Во многих случаях по этому коду выбирается тот или иной обработчик. Вызов модального диалога
После того, как объект класса диалога создан, необходимо вызвать член-функцию DoModal(). Результатом вызова будет модальное отображение диалога. Прототип функции следующий: virtual int CDialog::DoModal(); Функция возвращает код завершения, генерируемый диалогом при закрытии, или -1, если окно не может быть отображено. Если при отображении диалога произошла ошибка, возвращается IDABORT. Функция не завершается, пока диалог не будет закрыт. Закрытие модального диалога
По умолчанию диалог закрывается при получении сообщения с идентификатором либо IDOK, либо IDCANCEL. Они предопределены и обычно связаны с кнопками подтверждения
25
и отмены. Класс CDialog содержит встроенные обработчики для этих двух случаев, OnOK() и OnCancel(). Их не нужно включать в очередь сообщений диалога. Но, их можно переопределить, что дает возможность программисту управлять закрытием диалога. Для программного закрытия диалога необходимо вызвать член-функцию с прототипом: void CDialog::EndDialog(int RetCode); Параметр определяет значение, которое вернет функция DoModal(). Обычно возвращаются значения IDOK или IDCANCEL, другие значения используются редко. Инициализация диалога
Часто на практике возникает ситуация, когда различные переменные и элементы управления, связанные с диалогом, должны быть инициализированы до того, как диалог будет отображен. Чтобы позволить диалогу выполнить подобные действия, Windows автоматически посылает ему сообщение WM_INITDIALOG в момент создания. При получении такого сообщения MFC автоматически вызывает метод OnInitDialog(), который является стандартным обработчиком, определенным в классе CDialog. Эта функция переопределяется в программе, если необходимо выполнение инициализации. Прототип функции: virtual BOOL CDialog::OnInitDialog(); Функция вызывается до того, как диалог будет отображен. Она должна возвращать TRUE, чтобы Windows могла передать фокус ввода (т. е. сделать активным) на первый элемент управления в окне. Первым действием в переопределенной функции должен быть вызов функции CDialog::OnInitDialog(). Немодальные диалоги
Немодальные диалоги получают сообщения параллельно с основным окном приложения. То есть, как минимум два окна будут одновременно активными. Поэтому, работа с немодальными диалогами требует больше усилий – должны быть выполнены дополнительные операции. Для создания немодального диалога, необходимо создать "пустой" объект диалога, то есть не связанный с шаблоном из ресурсов. Привязка к ресурсам осуществляется через функцию Create(…). Рассмотрим этот процесс подробнее. Для создания объекта немодального диалога, необходимо использовать конструктор CDialog::CDialog() без параметров. Он объявлен как protected-член класса. Это означает, что он может быть вызван только изнутри члена-функции порожденного класса. Это сделано для того, чтобы программист обязательно определял свой порожденный класс для немодального диалога, и определял в нем дополнительные операции для немодального диалога. Когда экземпляр создан, он привязывается к ресурсам с помощью функций: BOOL CDialog::Create(LPCSTR ResourceName, CWnd *Owner = 0); BOOL CDialog::Create(UINT ResourceId, CWnd *Owner = 0); Первый параметр определяет идентификатор диалога в ресурсах. Второй параметр определяет окно-собственник для диалога. Необходимо помнить о том, что объект немодального диалога должен существовать в течение всего времени использования диалога.
26
Функция Create(…) отображает окно и после этого немедленно завершает свою работу. А объект окна должен существовать. В отличие от модальных окон, немодальные не становятся автоматически видимыми при вызове. Чтобы диалог сразу был видимым, необходимо в ресурсном редакторе установить опцию Visible. Или можно использовать функцию ShowWindow(…). Для закрытия немодального диалога, необходимо использовать функцию DestroyWindow(). Это означает, что функции OnCancel() и/или OnOK() должны быть переопределены. Использование диалога в качестве главного окна
Использование диалога в качестве главного окна часто бывает очень удобным. Реализовать этот случай достаточно просто. Во-первых, необходимо создать диалог в ресурсах. Вовторых, породить класс главного окна приложения от CDialog. Перед конструктором класса главного окна необходимо вызвать конструктор класса CDialog, и в нем привязать объект к ресурсам, например: CMainFrame::CMainFrame():CDialog(IDD_MYDIALOG) { //... здесь тело конструктора }
В-третьих, в функции CApp::InitInstance() должен присутствовать следующий код: // Создаем объект диалогового окна CMainFrame dlgWnd; // Cообщаем MFC адрес окна m_pMainWnd = &dlgWnd; // Отображаем модальный диалог dlgWnd.DoModal(); // Возвратим FALSE, чтобы MFC не пыталась инициировать // очередь сообщений главного окна. return FALSE;
Мы отображаем модальный диалог. Так как при завершении функции DoModal() нам уже не нужна очередь сообщений, мы "обманываем" MFC, делая вид, что инициализация прошла неудачно. Элементы управления Windows
В данном пункте рассмотрим подробнее стандартные элементы управления. Списки
Список является одним из наиболее распространенных элементов управления. В MFC работа со списком осуществляется через класс CListBox. Списки являются элементами управления, требующими двустороннего взаимодействия между ними и программой. То есть, список может как посылать, так и принимать сообщения. Например, сообщения посылаются списку при его инициализации. Сюда входит передача набора строк, которые будут отображены в окне списка (по умолчанию список создается пустым). Когда список инициализирован, он посылает сообщения о действиях, произведенных с ним пользователем.
27
Прием идентификационных кодов списка
Список может генерировать сообщения различных типов. Например, сообщения посылаются при двойном щелчке на элементе списка, при потере списком фокуса ввода и при выборе другого элемента из списка. Каждое такое событие описывается идентификационным кодом. Этот код является частью сообщения WM_COMMAND. Некоторые другие элементы также используют идентификационные коды. Рассмотрим код LBN_DBLCLK. Он посылается, когда пользователь выполняет двойной щелчок на элементе списка. При определении списка в ресурсах должна быть установлена опция Notify, чтобы он мог генерировать это сообщение. Когда выбор произведен, необходимо запросить список, чтобы узнать о том, какой элемент выбран. Для обработки сообщения LBN_DBLCLK необходимо поместить его обработчик в карту сообщений. Но это будет не макрос ON_COMMAND(). Вместо этого используются специальные макрокоманды. Для нашего сообщения это будет ON_LBN_DBLCLK(). Она имеет такой вид: ON_LBN_DBLCLK (ИдентификаторСписка, ИмяОбработчика) Многие сообщения обрабатываются подобным образом. Названия всех макросов для таких сообщений начинаются с префикса ON_LBN_. Передача сообщений списку
В традиционных Windows-программах сообщения посылаются элементам управления с помощью API-функций, например SendDlgItemMessage(). Но, в программах на MFC, для этих целей применяются соответствующие функции-члены класса. Эти функции автоматически посылают необходимое сообщение элементу управления. В этом заключается преимущество использования MFC по сравнению с традиционным методом программирования. Списку может быть послано несколько разных сообщений. Для каждого сообщения класс CListBox содержит отдельный член-функцию класса. Например, рассмотрим следующие функции: int CListBox::AddString(LPCSTR StringToAdd); int CListBox::GetCurSel() const; int CListBox::GetText(int Index, LPCSTR StringVariable); Функция AddString(…) вставляет указанную строку в список. По умолчанию, она вставляется в конец списка, при этом, начало списка имеет индекс 0. Функция GetCurSel() возвращает индекс текущего выделенного элемента. Если ни один элемент не выбран, то функция возвращает LB_ERR. Функция GetText(…) получает строку, связанную с указанным индексом. Строка копируется в символьный массив по адресу StringVariable. Получение указателя на список
Функции CListBox работают с объектами CListBox. Поэтому, необходимо получить указатель на объект списка, что делается с помощью функции GetDlgItem(), являющейся членом класса CWnd: СWnd *CWnd::GetDlgItem(int ItemIdentifier) const;
28
Функция возвращает указатель на объект, чей идентификатор передан как параметр. Если такой объект не существует, то возвращается 0. Значение, возвращенное функцией, должно быть приведено к типу указателя на конкретный класс управления. Например, в нашем случае это тип CListBox*. Инициализация списка
По умолчанию, список создается пустым, поэтому он должен инициализироваться каждый раз, когда отображается диалог. Для этого, необходимо переопределить функцию OnInitDialog(), в которой в список добавлялись бы строки. Если при добавлении элементов в список их число превысит то, которое помещается в окне списка, то в этом окне автоматически появится вертикальная полоса прокрутки. Поле ввода
На практике поля ввода используются очень широко, так как дают возможность ввести строку по своему усмотрению. Поля ввода принимают многие сообщения и сами могут генерировать несколько типов сообщений. Но, обычно отвечать на большинство из них нет необходимости, так как поля ввода самостоятельно выполняют большинство функций редактирования. Для этого не требуется взаимодействия с программой. Необходимо только решить, когда затребовать содержимое поля ввода. Для получения текущего содержимого поля вода, состоящего из одной строки, используется функция GetWindowText(). Ее прототип: int CWnd::GetWindowText(LPSTR StringVariable, int MaxStringLen) const; В результате выполненияфункции, содержимое поля ввода будет скопировано в строку по адресу StringVariable. Эта функция позволяет получить текст, связанный с любым окном или элементом управления. Применительно к обычному окну, функция получает заголовок окна. В момент создания поле ввода является пустым. Для инициализации его содержимым используется еще одна функция-член класса CWnd – SetWindowText(). Она отображает строку в элементе управления, который вызвал эту функцию. Ее прототип: void CWnd::SetWindowText(LPCSTR String); Контрольные переключатели
Контрольный переключатель – это элемент управления, предназначенный для установки или снятия определенной опции. Визуально он состоит из маленького прямоугольного поля, в котором может стоять метка выбора. Кроме этого, с переключателем связано текстовое поле с описанием предоставляемой переключателем опции. Если в переключателе стоит метка выбора, то говорится, что он выбран (установлен). Контрольные переключатели в MFC описываются с помощью класса CButton (так как контрольный переключатель – разновидность кнопки). Контрольные переключатели могут быть автоматическими и программными. Автоматический переключатель сам меняет свое состояние при щелчке мышью. Программный же этого не делает, а подразумевается, что сообщение о щелчке будет обработано в программе, и она изменит состояние переключателя. На практике почти всегда используются автоматические переключатели.
29
Сообщения контрольного переключателя
Каждый раз, когда пользователь щелкает мышью на контрольном переключателе (или нажимает клавишу Spacebar, когда фокус ввода находится на переключателе), диалогу посылается сообщение WM_COMMAND с идентификационным кодом BN_CLICKED. Это сообщение обрабатывается с помощью макроса ON_BN_CLICKED(). При работе с автоматическими переключателями отвечать на это сообщение нет необходимости. Но при работе с программными переключателями, чтобы изменять их состояние, необходимо отвечать на это сообщение. Для этого необходимо поместить макрос в карту сообщений и написать обработчик. Установка и чтение состояния контрольного переключателя
Чтобы установить контрольный переключатель в заданное состояние, необходиимо использовать функцию SetCheck(…) c прототипом: void CButton::SetCheck(int Status); Параметр определяет требуемое состояние: если он равен 1, то переключатель устанавливается, если 0 - сбрасывается. По умолчанию, при первом вызове диалога переключатель будет сброшен. Автоматический переключатель также может быть установлен в требуемое состояние этой функцией. Текущее состояние переключателя можно определить с помощью функции GetCheck(): int CButton::GetCheck() const; Функция возвращает 1, если переключатель установлен, и 0 в противном случае. Инициализация контрольных переключателей
При вызове диалога переключатели сброшены. Но, обычно они должны устанавливаться в предыдущее состояние при каждом вызове диалога. Таким образом, переключатели необходимо инициализировать. Для этого необходимо переопределить функцию OnInitDialog(), и в ней использовать функкцию SetCheck() для установки начальных состояний. Статические элементы управления
Статическим называется элемент, который не принимает и не генерирует сообщений. Формально, этим термином называют то, что просто отображается в диалоговом окне, например, текстовая строка или рамка, предназначенная для визуального объединения нескольких элементов управления, или рисунок. Если элементу присвоен идентификатор IDC_STATIC (-1), то он не будет принимать и генерировать сообщений. Но, в общем случае, статические элементы управления могут генерировать и принимать сообщения. Для этого элементу нужно присвоить другой идентификатор. Тогда элемент уже не будет статическим. Это часто используется. Например, можно поменять текст в текстовой строке с помощью функции SetWindowText(), чтобы отобразить некоторую информацию.
30
Селекторные кнопки
Использование селекторных кнопок очень похоже на использование контрольных переключателей. Только их работа организована таким образом, что из группы кнопок может быть установлена только одна. При установке другой кнопки, предыдущая установка сбрасывается. Селекторные кнопки бывают программные и автоматические; но, так как управлять радиокнопками сложно, то сейчас почти всегда используются автоматические. Радиокнопки объединяются в группы. В одном диалоге может быть несколько групп. Для первой радиокнопки каждой группы в редакторе ресурсов нужно установить опцию Group, а для других радиокнопок группы она должна быть сброшена. Радиокнопки нумеруются в порядке значений их идентификаторов (то есть в порядке их создания в редакторе ресурсов). Если в диалоге все радиокнопки образуют одну группу, то опцию Group можно не устанавливать. Селекторные кнопки управляются с помощью класса CButton. Также как для контрольных переключателей, состояние селекторных кнопок можно изменять с помощью функции SetCheck() и читать с помощью функции GetCheck(). При создании диалога все селекторные кнопки сброшены. Таким образом, в функции OnInitDialog() необходимо установить начальное состояние программно. Хотя из программы можно установить сразу несколько селекторных кнопок или сбросить все, хороший стиль программирования под Windows предполагает, что всегда будет установлена одна и только одна селекторная кнопка. Полосы прокрутки
В Windows есть два типа полос прокрутки. Элементы первого типа являются частью окна (включая диалоговое окно), поэтому их называют полосами прокрутки окна. Элементы второго типа существуют независимо и называются независимыми полосами прокрутки. Элементы первого типа описываются классом CWnd, а второго - CScrollBar. Создание стандартных полос прокрутки
Если требуется, чтобы окно содержало стандартные полосы прокрутки, они должны быть явно заданы. Применительно к главному окну это означает, что при вызове функции Create() в качестве параметров стиля должны быть указаны опции WS_VSCROLL и WS_HSCROLL. В случае диалогового окна, достаточно установить соответствующие опции диалога в ресурсном редакторе. Если все это сделано, то полосы прокрутки будут отображаться в окне автоматически. Независимые полосы прокрутки в диалогах
Для включения в диалог независимой полосы прокрутки используется ресурсный редактор. Можно создавать горизонтальные и вертикальные полосы прокрутки. Также, можно установить требуемые длину и ширину полосы прокрутки. Полоса прокрутки, так же как и любой другой элемент управления, должна иметь свой уникальный идентификатор. Обработка сообщений полосы прокрутки
Так как полоса прокрутки пришла из 16-разрядной Windows 3.1, то управлять полосой прокрутки довольно сложно. Полоса прокрутки сама ничего не делает. Даже для того, чтобы
31
она "прокручивалась" на экране, необходим дополнительный программный код. Полосы прокрутки при выполнении над ними действий посылают сообщения WM_VSCROLL и WM_HSCROLL при активизации соответственно вертикальной или горизонтальной полосы прокрутки. Эти сообщения обрабатываются функциями со следующими прототипами: afx_msg void CWnd::OnVScroll(UINT SBCode, int Pos, CScrollBar *SB); afx_msg void CWnd::OnHScroll(UINT SBCode, int Pos, CScrollBar *SB); Следует отметить, что при наличии нескольких горизонтальных или вертикальных полос прокрутки для всех них будет вызываться один и тот же обработчик. Первый параметр, SBCode, содержит код выполненного над полосой прокрутки действия. Если работа ведется с вертикальной полосой прокрутки, то при каждом изменении положения ползунка на одну позицию вверх посылается код SB_LINEUP. При изменении позиции на одну вниз посылается код SB_LINEDOWN. Аналогично, при постраничном перемещении генерируются коды SB_PAGEUP и SB_PAGEDOWN. Если работа ведется с горизонтальной полосой прокрутки, то при каждом передвижении ползунка на одну позицию влево посылается код SB_LINELEFT. При изменении его положения на одну позицию вправо посылается код SB_LINERIGHT. При постраничном перемещении генерируются сообщения SB_PAGELEFT и SB_PAGERIGHT. Для обоих типов полос прокрутки при перемещении ползунка на новую позицию посылается код SB_THUMBPOSITION. Если при этом кнопка мыши удерживается нажатой, то дополнительно генерируется сообщение с кодом SB_THUMBTRACK. Это позволяет отслеживать перемещения ползунка, прежде чем мышь будет отпущена. Параметр Pos указывает текущую позицию ползунка. Если сообщение сгенерировано стандартной полосой прокрутки, то параметр SB будет равен 0. Если же оно было сгенерировано независимой полосой прокрутки, то этот параметр будет содержать указатель на объект. Это предоставляет весьма неуклюжий способ различать, какая конкретно независимая полоса прокрутки сгенерировала сообщение. Для этого нужно использовать функцию CWnd:: GetDlgCtrlID(), которая возвращает идентификатор элемента управления. Такое неудобство связано с тем, что MFC повторяет внутреннее устройство Windows, а не является библиотекой сверхвысокого уровня для быстрой разработки приложений. Управление полосой прокрутки
Ранее, для установки различных параметров полосы прокрутки, использовались отдельные функции, которые были в Windows 3.1. С появлением Windows 95 появилась возможность управления полосами прокрутки с помощью одной функции SetScrollInfo(). Эта функция позволяет сделать полосу прокрутки пропорциональной (в этом случае, чем меньше диапазон полосы прокрутки, тем длиннее будет ее ползунок). Функция GetScrollInfo() предназначена для чтения параметров полосы прокрутки. В отличие от старых функций, эти функции работают с 32-разрядными данными. Для стандартных полос прокрутки используется функция: BOOL CWnd::SetScrollInfo(int Which, LPSCROLLINFO pSI, BOOL Redraw = TRUE); Значение Which указывает, с горизонтальной или вертикальной полосой ведется работа. Параметр pSI указывает на структуру, содержащую информацию для полосы прокрутки. Последний параметр задает необходимость перерисовки полосы прокрутки. Обычно
32
используется значение по умолчанию. Для независимых полос прокрутки используется функция: BOOL CScrollBar::SetScrollInfo(LPSCROLLINFO pSI, BOOL Redraw = TRUE); Оба параметра имеют такой же смысл.Для чтения параметров стандартных полос прокрутки используется функция: BOOL CWnd::GetScrollInfo(int Which, LPSCROLLINFO pSI, UINT Mask = SIF_ALL); Информация, получаемая от полосы прокрутки, записывается в структуру по адресу pSI. Значение параметра Mask определяет, какая информация записывается в структуру. По умолчанию заполняются все поля. Для независимых полос прокрутки вариант функции таков: BOOL CScrollBar::SetScrollInfo(LPSCROLLINFO pSI, UINT Mask = SIF_ALL); Значение параметров аналогичное предыдущему случаю. Во всех вариантах функций используется следующая структура типа SCROLLINFO: typedef struct tagSCROLLINFO { UINT cbSize; // размер самой структуры UINT fMask; // маска для параметров int nMin; // нижняя граница диапазона int nMax; // верхняя граница диапазона UINT nPage; // размер страницы int nPos; // позиция ползунка int nTrackPos; // позиция ползунка во время перемещения } SCROLLINFO; Поле fMask определяет, какое из полей структуры содержит требуемую информацию. Используются константы с префиксом SIF_, которые можно объединять операцией "|". Поле nPos содержит статическую позицию ползунка. Поле nPage содержит размер страницы для пропорциональных полос прокрутки. Для получения обычной пропорциональной полосы прокрутки в этом поле нужно задать значение 1. Поля nMin и nMax содержат нижнюю и верхнюю границы диапазона полосы прокрутки. Поле nTracksPos содержит позицию ползунка при его перемещении, это значение не может быть установлено.
§5. Графический вывод Классические функции графического устройства
При выводе на экран графической информации: линии, текста, изображения и т.п. программа обращается к функциям GDI (graphic device interface) интерфейса графического устройства. Эти функции поддерживаются каркасом MFC и для удобства разработчика объединены в классы. Основным классом для работы с графикой является класс CDC и производные от него CPaintDC, CClientDC, CWindowDC, которые отличаются от базового
33
класса только конструкторами и деструкторами. Исключением является класс CMetaFileDC. Класс CDC инкапсулирует понятие контекста устройства DC (device context). Контекст устройства DC(device context) – структура данных, которая определяет набор графических объектов и методов для графического вывода.
Контекст устройства является посредником между операционной системой Windows и устройством вывода, тем самым обеспечивается аппаратная независимость программы. Весь процесс отображения графики осуществляется с помощью этого класса. Особенности производных классов контекста устройства Объекты CMetaFileDC обеспечивают доступ к метафайлам Windows. Вызовы функцийчленов класса CMetaFileDC записываются в связанном с соответствующим объектом файле. Для построения изображения требуется воспроизвести последовательность команд, записанных в метафайле. Классы CClientDC и CWindowDC отличаются друг от друга лишь тем, что представляют для рисования различные области окна. CClientDC представляет клиентскую часть (часть окна без рамки, заголовка, меню, панели управления и строки состояния). CWindowDC – полнооконный контекст устройства, позволяет рисовать в произвольной области окна программы. Объекты класса CPaintDC используются только в обработчике сообщения WM_PAINT, генерируемого в ответ на вызов функций UpdateWindow или RedrawWindow и необходимы, если требуется переопределить функцию OnPaint() для конкретного дисплея. По умолчанию обработчик OnPaint() вызывает OnDraw(CDC*) с уже настроенным нужным образом контекстом. Конструктор CPaintDC определён так, что выполняет все действия необходимые для инициализации данного дисплея. Создание и уничтожение объектов CDC
Управление созданием и удалением объектов CDC является важной частью каждой программы. При неправильной работе с контекстами теряется память до завершения работы программы. Рассмотрим два варианта корректной работы с объектами CDC. 1. Создать объект в стеке, тогда он будет уничтожен автоматически: void CMyView::SomeFunction(...) { CRect cr; CClientDC dc(this); dc.GetClipBox(cr); }
2. Получить указатель на объект с помощью CWnd::GetDC(), при этом перед выходом из функции вызвать ReleaseDC(): void CMyView::SomeFunction(...) { CRect rect;
34
CDC* pDC = GetDC(); pDC->GetClipBox(rect); ReleaseDC(pDC); }
Замечание: нельзя удалять CDC – объект, указатель на который передаётся функции OnDraw. За его удаление отвечает каркас MFC. Состояние контекста устройства. Объекты GDI
Состояние контекста устройства определяется связанными с ним графическими объектами. Свойства контекста устройства назначаются с помощью методов класса CDC. GDI- объекты загружаются в контекст устройства вызовом перегруженной функции SelectObject. В любой текущий момент с контекстом может быть связан только один объект каждого типа. Все объекты GDI представлены в MFC с помощью классов. CGdiObject – базовый абстрактный класс для GDI объектов, которые являются экземплярами классов наследников. 1. CBitmap – класс, инкапсулирующий растровые изображения (битовые массивы). Растровые изображения используются для отображения картинок и создания кистей. 2. CBrush – кисть. Точечный шаблон, использующийся для закрашивания областей окна. 3. CFont – шрифт. Полный набор символов алфавита определённого вида и размера. Шрифты хранятся на диске как ресурсы. 4. CPallete – палитра. Таблица преобразования цветов, позволяет приложению полностью задействовать цветовые возможности устройства, не вызывая конфликта с другими приложениями, работающими с этим же устройством. 5. CPen – перо. Инструмент для рисования линий и границ фигур. 6. CRgn – регион. Область окна, определяемая прямоугольником, эллипсом или всевозможными их комбинациями. При создании GDI объектов вызываются конструкторы соответствующих классов. Но для некоторых этого недостаточно. Например, создание объектов типа CFont или CRgn требует вызова CreateFont(…) или CreatePolygonRgn(…). Прежде чем удалять GDI объект, его требуется вначале “отсоединить” от контекста устройства. Память, выделенная под GDI объекты, принадлежит процессу и освобождается при его завершении. Такие объекты как растровые изображение занимают значительный объем памяти и за их своевременным удалением необходимо следить. Пример работы с GDI – объектом: void CMyView::OnDraw(CDC* pDC) { CPen myPen(PS_SOLID, 2, RGB(255,0,0)); CPen *oldPen = pDC->SelectObject(&myPen); //Присоединение нового пера и сохранение старого //-----------------------------------//Рисование.... //------------------------------------
35
pDC->SelectObject(oldPen); //Возвращение контекста устройства в прежнее состояние } //созданное в стеке перо будет удалено при выходе из функции
При уничтожении контекста устройства все связанные с ним GDI объекты отсоединяются. Если известно, что контекст устройства будет уничтожен раньше, чем удалены выбранные в него объекты, отсоединять эти объекты не надо. При работе с контекстом дисплея, в начале каждой функции обработчика сообщений создаётся новый контекст, набор выбранных объектов, а также режим преобразования координат и другие параметры теряются. Поэтому, необходимо настраивать его каждый раз заново. Для настройки преобразования координат используется функция OnPrepareDC, но собственными GDI объектами необходимо управлять самим. Основные методы класса CDC контекста устройства
Из всех методов класса CDC можно выделить две основные группы: функции создания и настройки контекста устройства и функции рисования. Рассмотрим их подробнее. BOOL CreateDC(LPCTSTR lpszDriverName, LPCTSTR lpszDeviceName, LPCTSTR lpszOutput, const void* lpInitData) Это основная функция для инициализации контекста устройства. Первый параметр – указатель на строку с именем драйвера устройства. Второй параметр – указатель на строку с именем устройства, необходим, если драйвер поддерживает несколько устройств. Третий параметр – указатель на строку с именем файла или порта, куда будет осуществляться вывод. Четвёртый – содержит особые параметры для настройки данного устройства. Функция возвращает true или false в зависимости от успеха или неудачи. Эта функция редко используется, обычно каркас MFC сам создаёт необходимый контекст. BOOL CreateCompatibleDC(CDC* pDC) Создаёт в памяти объект контекста устройства, указатель на который передаётся в качестве параметра, совместимый с данным. SelectObject(…) Это основная функция для связи с контекстом устройства GDI объекта. Рассмотри подробнее её прототип. CPen* SelectObject (CPen* pPen) – связывает перо, указатель на которое передан в качестве параметра, с контекстом устройства. Возвращает указатель на перо, которое находилось в контексте устройства, до вызова функции. При неудаче возвращает NULL. Далее приведены перегруженные варианты данной функции. CBrush* SelectObject(CBrush* pBrush) virtual CFont* SeiectObject (CFont* pFont) CBitmap* SelectObject (CBitmap* pBitmap) int SelectObject (CRgn* pRgn) В зависимости от типа параметра с контестом устройства связывается соответствующий GDI объект.
36
virtual CGdiObject* SelectStockObject (int nlndex) Связывает с контекстом один из стандартных объектов, идентификатор которого передаётся в качестве параметра. Также как и SelectObject(…), возвращает указатель на объект, который был связан с контекстом до её вызова, в случае успеха, в случае неудачи – NULL. CPen* GetCurrentPen( ) const Возвращает указатель на выбранное в контекст перо. Далее приведены примеры аналогичных функций. CPalette* GetCurrentPalette( ) const CBrush* GetCurrentBrush( ) const Функции с префиксом Get возвращают определённые параметры контекста устройства. int SetBkMode (int nBkMode) Устанавливает режим закрашивания фона. В качестве параметра передаётся идентификатор, определяющий тип закрашивания. Возвращает идентификатор предыдущего типа закрашивания. Функции для преобразования системы координат Будут далее рассмотрены подробнее. Ниже приведены их прототипы. virtual int SetMapMode(int nMapMode) virtual CPoint SetViewportOrg(CPoint point) СPoint SetWindowOrg(CPoint point) virtual CSize SetViewportExt(CSize size) virtual CSize SetWindowExt(CSize size) void DPtoLP(LPPOINT lpPoints, int nCount = 1) const void LPtoDP(LPPOINT lpPoints, int nCount = 1) const СOLORREF SetPixel(int X, int Y, COLORREF Color) Закрашивает пиксель области с координатами (X,Y) переданными в качестве первых двух аргументов цветом Color (третий аргумент функции). Возвращает цвет, который имел пиксель до вызова данной функции. BOOL Rectangle(int upX, int upY, int lowX, int lowY) Функция, которая рисует прямоугольник загруженным в контекст пером и заполняет его загруженной в контекст кистью. В качестве аргументов передаются координаты противоположных углов. BOOL RoundRect(int upX, int upY, int lowX, int lowY, int curveX, int curveY) Рисует и заполняет текущей кистью контекста прямоугольник со скруглёнными углами. Параметры curveX и curveY задают ширину и высоту эллипса, определяющего дугу для скруглённых углов. BOOL Ellipse(int upX, int upY, int lowX, int lowY) Рисует и заполняет текущей кистью контекста эллипс, вписанный в прямоугольник, координаты углов которого передаются в функцию в качестве параметров. CPoint MoveTo(CPoint point) Перемещает фокус в точку point. Возвращает предыдущие координаты фокуса.
37
BOOL LineTo(POINT point) Проводит линию пером, загруженным в контекст устройства, из фокуса в точку, переданную функции в качестве параметра. Битовые образы
Битовые образы – очень важная часть Windows. При хранении битовых образов в отдельном файле, обычно используется расширение BMP (это единственный растровый формат, который напрямую поддерживается Windows). Битовые образы могут храниться и в ресурсах. Битовые образы используются чаще, чем все остальные ресурсы. Это объясняется наличием для них чрезвычайно мощной поддержки. В Windows многие вещи, которые можно легко нарисовать программно, отображаются с помощью готовых битовых образов. Например, кнопки в нажатом и отпущенном состоянии, каркасы для целых окон. Так как компьютеры теперь имеют большие жесткие диски, то выбор между программным рисованием объекта и готовой картинкой часто однозначно решается в пользу последней. Создание битовых образов
В MFC битовые образы описываются классом CBitmap. Для их создания можно использовать либо ресурсный редактор, либо импортировать в ресурсы готовые файлы BMP, созданные при помощи графических пакетов. Битовый образ является таким же ресурсом, как иконка, или диалог. Необходимо помнить, что область ресурсов с битовыми образами в EXE-файле может занимать большой размер. Но это не критично, так как ресурсы автоматически не загружаются в память. Вывод битового образа на экран
Когда битовый образ помещен в ресурсы, его можно выводить на экран. Сначала необходимо создать объект типа CBitmap и с помощью функции LoadBitmap() загрузить в него битовый образ из ресурсов. Прототип функции: BOOL CBitmap::LoadBitmap(LPCSTR ResourceName); Параметр определяет строковый идентификатор ресурса. После загрузки битового образа, его необходимо вывести в клиентскую область окна. Для этого обработчик WM_PAINT должен содержать приблизительно такой код (предполагается, что битовый образ загружен в объект backgroundBitmap): CPaintDC clientDC(this); CDC memDC; // Контекст памяти // Создать совместимый контекст памяти memDC.CreateCompatibleDC(&clientDC); // Выбрать битовый образ в контекст устройства memDC.SelectObject(&backgroundBitmap); // Получить характеристики битового образа в структуру BITMAP BITMAP bmp; backgroundBitmap.GetBitmap(&bmp); // Скопировать битовый образ из контекста памяти в контекст клиентской области clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);
Сначала объявляются два контекста устройства. Первый связан с текущим окном. Второй не инициализирован и предназначен для области памяти, в которой будет храниться
38
изображение. Затем, с помощью функции CreateCompatibleDC(…), этот контекст объявляется совместимым с контекстом окна. Функция имеет прототип: virtual BOOL CDC::CreateCompatibleDC(CDC *pDC); Область памяти используется для вывода изображения на экран. Перед выводом на экран изображение должно быть выбрано в контекст устройства, связанный с областью памяти, с помощью функции SelectObject(…). Мы используем ее вариант с прототипом: CBitmap *CDC::SelectObject(CBitmap *pBmp); Параметр pBM – это указатель на объект битового образа. Для вывода изображения на экран используется функция BitBlt(…), которая копирует изображение из исходного контекста устройства в контекст, связанный с вызывающим функцию объектом. Прототип функции такой: BOOL CDC::BitBlt(int x, int y, int Width, int Height, CDC *pSourceDC, int SourceX, int SourceY, DWORD RasterOpCode); Первые два параметра задают координаты начальной точки изображения. Размеры изображения задают следующие два параметра. Параметр pSourceDC является указателем на исходный контекст устройства. Координаты SourceX и SourceY задают левый верхний угол изображения и обычно равны 0. Последний параметр задает код операции, которая будет проделана при передаче изображения из одного контекста в другой. Мы будем использовать только значение SRCCOPY, в этом случае изображение просто копируется. Существует также много других констант. Следует отметить, что указанным методом нельзя корректно выводить битовые образы более чем с 16 цветами в видеорежимах с 256 цветами. В режимах же HiColor и TrueColor без всяких проблем этим методом выводятся любые битовые образы. Так как на всех современных компьютерах используются, по крайней мере, HiColor режимы, мы не будем рассматривать ограничения худших режимов и манипуляции с палитрой. В библиотеке MFC, распространяющейся вместе с MS Visual Studio 2005 Professional, встроен класс CImage, который упрощает работу с битовыми образами. Принципы работы с ним рассмотрены далее. Настройка системы координат Стандартные функции каркаса MFC для настройки систем координат
В MFC встроены функции для настройки аппаратной и логической систем координат. Также предусмотрены функции перехода от одной к другой. Задача программиста состоит в том, чтобы определить, когда и какую систему координат использовать. Основные правила при работе с системами координат: • Все параметры, передаваемые в методы CDC, – это логические координаты. • Все параметры, передаваемые в методы CWnd, – это аппаратные координаты. • Значения, сохраняемые длительное время, должны использовать логические координаты.
39
Рассмотрим функции для работы с системами координат. Функция virtual int SetMapMode(int nMapMode) устанавливает направления осей и определяет логические единицы. Возможные значения параметра nMapMode приведены в таблице: Одна логическая единица равна одному пикселю, ось x направлена вправо, ось у — вниз. Режим задан по умолчанию. Одна логическая единица равна 0.001 дюйма, ось x направлена вправо, MM_HIENGLISH ось у — вверх. Одна логическая единица равна 0.01 миллиметра, ось x направлена MM_HIMETRIC вправо, ось у — вверх. Одна логическая единица равна 0.01 дюйма, ось x направлена вправо, MM_LOENGLISH ось у — вверх. Одна логическая единица равна 0.1 миллиметра, ось x направлена MM_LOMETRIC вправо, ось у — вверх. MM_ANISOTROPIC Режим позволяет настраивать (с помощью функций SetWindowExt и SetViewportExt) размерность (отдельно для каждой из осей), их направления и начало отсчета Режим позволяет настраивать (с помощью функций SeWindowExt и MM_ISOTROPIC SetViewportExt) размерность осей, их направления и начало отсчета, единица оси x равна единице оси у. Одна логическая единица — твипс (twips) — равна 1/20 пункта (point) MM_TWIPS или 1/1440 дюйма, ось x направлена вправо, ось у — вверх. ММ_ТЕХТ
Функции для перемещения центров систем координат: virtual CPoint SetViewportOrg(CPoint point) и СPoint SetWindowOrg(CPoint point). Первая смещает центр аппаратных координат, а вторая – логических, в точку, переданную в качестве параметра. Обе функции возвращают координаты предыдущего центра. Функции virtual CSize SetViewportExt(CSize size) и virtual CSize SetWindowExt(CSize size) используются для задания единиц измерения. Первая функция устанавливает единицы измерения аппаратной системы координат, вторая – логической. Для перехода от аппаратных координат к логическим используется функция void DPtoLP(LPPOINT lpPoints, int nCount = 1) const, а для перехода от логических к аппаратным – void LPtoDP(LPPOINT lpPoints, int nCount = 1) const. Аргументами обоих функций являются указатель на массив с точками, которые нужно преобразовать, и размерность этого массива. Эти функции универсальны, хорошо подходят для использования в задачах, где не требуются сложные преобразования системы координат. Для осуществления сложных многоуровневых преобразований рекомендуется вводить собственные функции преобразования системы координат.
40
§6. Разработка Windows-приложения c использованем библиотеки MFC Создание и вывод Windows-окна на экран с использованием MFC
Создадим простейшую программу под Windows средствами Visual C++ 2005 1. File→New→Project. 2. Project types: Win32, Templates: Win32 Project. 3. Ok. 4. An empty project. 5. Finish. 6. Добавляем в проект файл *.cpp. 7. Project→Properties. Вкладка Configuration Properties→General. 8. Значение поля Character Set установливае Use Multi-Byte Character Set. 9. Значение поля Use of MFC устанавливаем Use MFC in a Static Library. 10. Ok. Введем код программы: #include "afxwin.h"
//MFC основные и стандартные компоненты
class CMyApp : public CWinApp { public: CMyApp(); //Конструктор по умолчанию virtual BOOL InitInstance(); //Стандартная инициализация }; class CMainWnd : public CFrameWnd { public: CMainWnd(); };
//Конструктор по умолчанию
CMainWnd::CMainWnd() { Create(NULL,"Окно приложения пользователя", WS_OVERLAPPEDWINDOW, rectDefault, NULL, NULL); //Создать окно программы } CMyApp::CMyApp() {}
//Конструктор главного класса приложения
BOOL CMyApp::InitInstance() //Стандартная инициализация { m_pMainWnd=new CMainWnd(); //Создать указатель на класс окна ASSERT(m_pMainWnd); //Проверить его правильность m_pMainWnd->ShowWindow(SW_SHOW); //Показать окно m_pMainWnd->UpdateWindow(); //Обновить окно return TRUE; //Вернуть, что все нормально }; CMyApp theApp; //Запуск приложения
41
Комментарий к программе
Первая строка – файл заголовков afxwin.h, предназначен для программирования под Windows с использованием библиотеки MFC. Он включает описание классов, функций и переменных, а также ссылается на windows.h. Замечание: В каждой программе на С++ есть главная функция программы. В Dos, это main(), в Windows – WinMain(). В рассматриваемой программе ее нет, так как в MFC есть класс CWinApp, который включает главную функцию программы.
Во второй строке создается класс CMyApp, как производный от CWinApp и который наследует все его свойства, методы и т.д. В этом классе объявлен конструктор по умолчанию (без параметров). Он необходим, иначе не скомпилировать программу. В конце программы от класса CMyApp создается объект класса CMyApp theApp. Он тоже без параметров. CWinApp имеет виртуальный метод InitInstance(). Этот метод должен возвращать ненулевое значение, если инициализация прошла нормально, а иначе 0. Он предназначен, чтобы описать класс окна программы и отобразить окно на экране. В конструкторе класса CMainWnd вызывается функция создания окна Create(…), в которой есть несколько параметров. Первый параметр указывает на имя класса окна, он пока не нужен и поэтому NULL, дальше указатель – имя окна программы. WS_OVERLAPPEDWINDOW определяет стиль окна (обычное перекрывающее окно с заголовком, кнопкой вызова системного меню, кнопками минимизации и максимизации и рамкой). Параметр rectDefault говорит о том, что размер окна присвоит Windows по умолчанию. Рассмотрим функцию Create(…) подробнее: BOOL CFrameWnd::Create(LPCSTR ClassName, LPCSTR Title, DWORD Style = WS_OVERLAPPEDWINDOW, const RECT &XYSize = rectDefault, CWnd *Parent = 0, LPCSTR MenuName = 0, DWORD ExStyle = 0, CCreateContext *Context = 0); Первый параметр, ClassName, определяет имя класса окна для оконной подсистемы Windows. Обычно его не нужно явно задавать, так как MFC выполняет всю необходимую работу. Параметр Title определяет заголовок окна. Параметр Style задает стиль окна. По умолчанию создается стандартное перекрываемое окно. Можно задать свой стиль, объединив с помощью операции "или" (|)несколько констант из приведенных ниже: Константа WS_OVERLAPPED WS_MAXIMIZEBOX WS_MINIMIZEBOX WS_SYSMENU WS_HSCROLL WS_VSCROLL
Элемент окна Стандартное окно с рамкой Кнопка максимизации Кнопка минимизации Системное меню Горизонтальная полоса прокрутки Вертикальная полоса прокрутки
В примере используется член-функция со следующим прототипом: virtual BOOL CWinApp::InitInstance(); Это виртуальная функция, которая вызывается каждый раз при запуске программы. В ней должны производиться все действия, связанные с инициализацией приложения. Функция
42
должна возвращать TRUE при успешном завершении и FALSE в противном случае. В программе в функции сначала создается объект класса CMainWnd, и указатель на него запоминается в переменной m_pMainWnd. Эта переменная является членом класса CWinThread. Она имеет тип CWnd* и используется почти во всех MFC-программах, так как содержит указатель на главное окно. Так как m_pMainWnd указывает на CMainWnd, то можно вывести на экран окно: m_pMainWnd->ShowWindow(SW_SHOW); Параметр определяет, каким образом окно будет показано на экране. Наиболее распространенные значения следующие: Константа SW_HIDE SW_MAXIMIZE SW_MINIMIZE SW_SHOW SW_RESTORE
Действие Окно становится невидимым Окно максимизируется Окно минимизируется Окно отображается, если было невидимо Окно приводится к нормальному размеру
Показать окно: m_pMainWnd->UpdateWindow(); Запустив программу на выполнение, получим в результате окно. Дополнение. При создании окна часто используют структуру Rect.
Пример: RECT x; x.top = 30; x.left = 30; x.bottom = 300; x.right = 300; Сreate(NULL, "My New Window", WS_OVERLAPPEDWINDOW, x);
Замечание. В библиотеке MFC используется ряд глобальных функций. Все они начинаются с префикса Afx. (Когда MFC только разрабатывалась, то проект назывался AFX – Application Framework). После ряда существенных изменений, AFX была переработана в MFC, но прежнее название сохранилось во многих идентификаторах библиотеки и в названиях файлов. Продолжение программы. Вставка элементов управления в окно (Controls)
Для работы возьмем наш первый проект и внесем изменения. Добавим следующий код после всех #include: #define IDC_MYBUTTON 100 #define IDC_MYEDIT 102
//Идентификатор кнопки //Идентификатор поля редактирования
43
Необходимо изменить описания конструктора класса окна: class CMainWnd : public CFrameWnd { public: CMainWnd(); //Конструктор по умолчанию ~CMainWnd(); //Деструктор private: CStatic* MyStatic; //Указатель на объект надпись CButton* MyButton; //Указатель на объект кнопка CEdit* MyEdit; //Указатель на объект поле редактирования }; CMainWnd::CMainWnd() { Create(NULL,"Окно пользователя", WS_OVERLAPPEDWINDOW, rectDefault, NULL, NULL); //Создать окно программы MyStatic = new CStatic(); if (MyStatic!=NULL) MyStatic->Create("MyStatic", WS_CHILD|WS_VISIBLE|SS_CENTER, CRect(10, 10, 100, 50),this); MyButton = new CButton(); if (MyButton!=NULL) MyButton->Create("MyButton", WS_CHILD|WS_VISIBLE|SS_CENTER, CRect(120, 10, 220, 50), this, IDC_MYBUTTON); MyEdit = new CEdit(); if (MyEdit != NULL) MyEdit->Create(WS_CHILD|WS_VISIBLE|WS_BORDER, CRect(240, 10, 340, 50), this, IDC_MYEDIT); } CMainWnd::~CMainWnd() { if (MyStatic != NULL) delete MyStatic; if (MyButton != NULL) delete MyButton; if (MyEdit != NULL) delete MyEdit; }
//Деструктор класса //Удалить динамический объект //Удалить динамический объект //Удалить динамический объект
Комментарии к программе
Изменения в конструкторе окна выделены жирным шрифтом. Результат создания объектов оператором new проверяется через проверку на NULL. Если ошибка, то все действия с этим элементом отменятся. Программный код реализации элементов управления находится в DLL Windows. То есть, в компиляторе нет реализации этих функций в библиотеках (lib), там только ссылки. Все элементы управления должны иметь идентификатор – число, которое определяет этот элемент управления. Первыми двумя строками, используя #define, объявляем идентификаторы. Дальше в класс рамки окна вставляются указатели на объекты элементов управления. Это только указатели. Объекта самого нет. Каждый объект использует память и, до того как он не понадобится, хранится только указатель. В конструкторе окна создадим соответствующие объекты с помощью оператора new, а после этого вызовем функцию Create(…), которая из объекта создаст элемент управления. Заметим, что у MyStatic нет идентификатора. Он задан по умолчанию, как 0xffff.
44
Функция Create(…) используется очень часто и есть у многих объектов. Обычно она требует: 1. 2. 3. 4. 5.
строку для имени или надписи; стиль; размеры и положение; куда вставлять; идентификатор.
Продолжение программы. События элемента управления Задача. Проиллюстрировать работу событий элемента управления на примере работы с мышью. При нажатии левой кнопки мыши элемент управления – кнопка должна переместиться в заданное место окна, а при нажатии правой кнопки мыши кнопка должна возвратиться в исходное место. #include "afxwin.h"
//MFC Основные и стандартные компоненты
#define IDC_MYBUTTON 100 #define IDC_MYEDIT 102
//Идентификатор кнопки //Идентификатор поля редактирования
class CMyButton: public CButton //Создание нового класса { public: afx_msg void OnLButtonDown(UINT, CPoint); afx_msg void OnRButtonDown(UINT, CPoint); private: DECLARE_MESSAGE_MAP(); //Таблица откликов кнопки }; void CMyButton::OnLButtonDown(UINT, CPoint) { MoveWindow(10, 110, 90, 30); } void CMyButton::OnRButtonDown(UINT, CPoint) { MoveWindow(10, 40, 90, 30); } BEGIN_MESSAGE_MAP(CMyButton, CButton) //Таблица откликов на сообщения для кнопки ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN() END_MESSAGE_MAP() class CMainWnd : public CFrameWnd { public: CMainWnd(); ~CMainWnd(); private: … CMyButton* MyButton; … }; … CMyApp theApp;
//Конструктор по умолчанию //Деструктор //Указатель на объект кнопка
45
Продолжение программы. Создание строки состояния Задача. Создать строку состояния (пока пустую).
В описания включаемых файлов добавим: #include "afxext.h"
//MFC расширения
В описание класса рамки окна: class CMainWnd : public CFrameWnd { public: CMainWnd(); //Конструктор по умолчанию int OnCreate(LPCREATESTRUCT lpCreateStruct); ~CMainWnd(); // Деструктор private: CStatic* MyStatic; //Указатель на объект надпись CMyButton* MyButton; //Элемент управления кнопка CEdit* MyEdit; //Указатель на объект поле редактирования CStatusBar m_wndStatusBar; //Класс панели состояния DECLARE_MESSAGE_MAP(); //Таблица откликов };
В таблице откликов: BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd) ON_WM_CREATE() END_MESSAGE_MAP()
//Таблица откликов на сообщения //Событие создания окна
Реализация объявленной процедуры: int CMainWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; m_wndStatusBar.Create(this); return 0; }
Продолжение программы. Создание меню Задача. Создать меню со следующими элементами: Программа→Выход; Движение→Старт; Движение→Стоп. Добавить обработку выбора пункта меню Программа→Выход, остальные пункты пока не обрабатывать.
Чтобы создать меню, необходимо создать файл ресурсов: • Resource View→клик правой кнопкой мыши→Add→Resource… • Выберем тип ресурса Menu. • Создаем меню (в свойствах меню укажем имя идентификатора: IDR_MENU, в свойствах каждого из пунктов также укажем нужные имена идентификаторов).
46
Добавим код: … #include "afxext.h" // MFC Расширения #include "resource.h" // Идентификаторы ресурсов #define IDC_MYBUTTON 100 // Идентификатор кнопки … class CMainWnd : public CFrameWnd { … int OnCreate(LPCREATESTRUCT lpCreateStruct);//функция вызывается при создании окна void MenuExit(); //Процедура реакции на выбор пункта меню ~CMainWnd(); //Деструктор private: CStatic* MyStatic; //Указатель на объект надпись CMyButton* MyButton; //Элемент управления кнопка CEdit* MyEdit; //Указатель на объект поле редактирования CStatusBar m_wndStatusBar; //Класс панели состояния CMenu m_wndMenu; //Это наш класс Меню DECLARE_MESSAGE_MAP(); //Таблица сообщений }; CMainWnd::CMainWnd() { Create(NULL,"Окно приложения пользователя",WS_OVERLAPPEDWINDOW,rectDefault,NULL,NULL); //Создать окно программы ON_WM_CREATE() //Событие создания окна ON_COMMAND(ID_FILE_EXIT, MenuExit) //Обработка реакции на выбор меню END_MESSAGE_MAP() … if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; m_wndStatusBar.Create(this); m_wndMenu.LoadMenu(IDR_MENU); // Загрузить меню из файла ресурса SetMenu(&m_wndMenu); // Установить меню return 0; } void CMainWnd::MenuExit() { DestroyWindow(); // Уничтожить окно } CMyApp theApp;
Скомпилируем и проверим работу программы с меню. Продолжение программы. Таймер Задача. Вывести в клиентскую часть окна круг, который при выборе пункта меню Движение→Старт, начинает движение и отскакивает от стенок клиентской области, а при выборе пункта меню Движение→Стоп, останавливается. Обработать нажатие клавиш: “←,↑,→,↓” ускоряющих (замедляющих) движение шарика в направлениях указанных стрелками.
47
Добавим в описание класса окна следующий код: class CMainWnd : public CFrameWnd { … private: int VX, sgn_x; //Приращение координаты по оси x и знак приращения int VY, sgn_y; //Приращение координаты по оси y и знак приращения CRect newPlace; //квадрат, в который вписан отображаемый круг CRect oldPlace; //квадрат, в который был вписан отображаемый круг в предыдущий момент … DECLARE_MESSAGE_MAP(); // таблица откликов окна public: … afx_msg void MenuStart(); // процедура реакции на выбор пункта меню afx_msg void MenuStop(); // процедура реакции на выбор пункта меню afx_msg void OnTimer(UINT_PTR); // обработка сообщения WM_TIMER afx_msg void OnPaint(); // обработка WM_PAINT afx_msg void OnKeyDown(UINT, UINT, UINT); //обработка нажатия клавиши клавиатуры };
В карту сообщений окна добавим: BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd) // таблица откликов на сообщения … ON_COMMAND(ID_MOTION_START,MenuStart) //выбор пункта "старт" ON_COMMAND(ID_MOTION_STOP,MenuStop) //выбор пункта "стоп" ON_WM_TIMER() ON_WM_PAINT() ON_WM_KEYDOWN() // реакция на нажатие клавиши END_MESSAGE_MAP()
Добавим инициализацию полей в конструкторе класса: CMainWnd::CMainWnd() { oldPlace.left = 150; oldPlace.top = 150; oldPlace.right = 170; oldPlace.bottom = 170; newPlace = oldPlace; VX = VY = sgn_x = sgn_y = 1; … }
Реализация объявленных методов: void CMainWnd::MenuStart() { SetTimer(ID_TIMER_FOR_MOT,10,NULL); } void CMainWnd::MenuStop() { KillTimer(ID_TIMER_FOR_MOT); }
//Запуск таймера
//Остановка таймера
void CMainWnd::OnTimer(UINT_PTR nIDEvent) { CRect rectClient;
48
}
GetClientRect(&rectClient); rectClient.bottom -= 20; rectClient.left += 110; if(newPlace.right > rectClient.right || newPlace.left < rectClient.left) sgn_x = -sgn_x; if(newPlace.bottom > rectClient.bottom || newPlace.top < rectClient.top) sgn_y = -sgn_y; newPlace.left += sgn_x*VX; newPlace.right += sgn_x*VX; newPlace.top += sgn_y*VY; newPlace.bottom += sgn_y*VY; InvalidateRect(oldPlace); //Затирается круг со старым центром InvalidateRect(newPlace); //Прорисовка круга с новым центром oldPlace = newPlace;
void CMainWnd::OnPaint() { CPaintDC dc(this); CBrush bBrush(RGB(0,0,255)); dc.SelectObject(&bBrush); dc.Ellipse(newPlace); } void CMainWnd::OnKeyDown(UINT nChar, UINT, UINT) { switch(nChar) //Обработка символов с соответствующими кодами { case 40: if(sgn_y > 0) VY++; else VY--; break; case 38: if(sgn_y > 0) VY--; else VY++; break; case 37: if(sgn_x > 0) VX--; else VX++; break; case 39: if(sgn_x > 0) VX++; else VX--; break; } }
Комментарий
Функция SetTimer(…) запускает системный таймер с идентификатором, указанным в параметрах, и генерирует синхронно сообщение WM_TIMER для окна, вызвавшего её, пока таймер не будет уничтожен функцией KillTimer(…) с соответсвующим параметром – идентификатором работающего таймера. Для одного окна может быть запущено несколько таймеров. Продолжение программы. Вывод данных в строку состояния Задача. Отобразить в панели состояния текущие координаты курсора.
В проекте создадим ресурс, содержащий две строки: Resource View→клик правой кнопкой мыши→Add Resource… Выберем тип ресурса String Table. Нажмём OK. Появится новый ресурс. Он используется для хранения текстовых строк.
49
В него нужно добавить две строки: • кликаем правой кнопкой мыши и выбираем New String (IDS_STRING_X); • Caption→10 символов подчеркивания (резервируем под вывод 10 символов); • создадим еще одну строку – IDS_STRING_Y. Создадим массив, в котором будут находиться идентификаторы строк (после #define): static UINT indicators[] = { IDS_STRING_X, IDS_STRING_Y };
//Идентификатор первой строки в ресурсах //Идентификатор второй строки в ресурсах
Изменим функцию OnCreate для того, чтобы установить индикаторы: int CMainWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; m_wndStatusBar.Create(this); //Создать строку состояния m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT)); //Установить в строку идентификаторы m_wndStatusBar.SetPaneInfo(0,0,0,50); //Изменение размеров первой секции строки состояния m_wndStatusBar.SetPaneInfo(1,0,0,50); //Изменение размеров второй секции строки состояния m_wndMenu.LoadMenu(IDR_MENU); //Загрузить … }
В таблицу откликов занесем реакцию на движение мыши: BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd) … ON_WM_CREATE() ON_WM_MOUSEMOVE() … END_MESSAGE_MAP()
//Таблица откликов окна //Событие создания окна //Движение мыши
И описание соответствующей функции в классе рамки окна: class CMainWnd : public CFrameWnd { public: … afx_msg void OnMouseMove(UINT, CPoint cp); … };
//Движение мыши
Реализация: void CMainWnd::OnMouseMove(UINT, CPoint cp) { char chX[10]; char chY[10];
//Буфер для координат //Буфер для координат
50
itoa(cp.x,chX,10); itoa(cp.y,chY,10); CString csStatusX(chX); CString csStatusY(chY); m_wndStatusBar.SetPaneText(0,csStatusX); m_wndStatusBar.SetPaneText(1,csStatusY);
//Число переводим в строку //Число переводим в строку //Формируем строку //Формируем строку //Выводим первую панель //Выводим вторую панель
}
Скомпилировать и запустить проект – при перемещении курсора его координаты отображаются в строке состояния. Комментарии
Для отображения в панели состояния, необходимо создать массив идентификаторов. Это необходимо для начала работы панели, а массив служит как шаблон. При запуске будет выведено именно то, что есть в строках, находящихся в ресурсах. Функция SetIndicators(…) говорит панели состояния о том, что у нее будут две панели. В параметрах – массив идентификаторов и количество элементов: BOOL SetIndicators(const UINT* lpIDArray, int nIDCount); Параметр lpIDArray – указатель на массив, nIDCount – количество элементов в массиве, sizeof(indicators)/sizeof(UINT) – размер массива/размер одного элемента Движение мыши отслеживается с помощью сообщения – ON_WM_MOUSEMOVE(). Обработчиком этого сообщения является функция – OnMouseMove(). Этой функции передается положение мыши в виде CPoint. Далее, необходимо сделать перевод числа в строку - itoa, использовать объект класса CString, и, для вывода в строку состояния, использовать функцию SetPaneText(…) BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE ); nIndex – номер панели для вывода, lpszNewText – текст для вывода, bUpdate – обновлять. Окончательный текст программы #include
#include #include "resource.h" #define IDC_MYBUTTON 100 #define IDC_MYEDIT 102 #define ID_TIMER_FOR_MOT 104 static UINT indicators[] = { IDS_STRING_X, IDS_STRING_Y };
//MFC основные и стандартные компоненты //MFC расширения //Идентификаторы ресурсов //Идентификатор кнопки //Идентификатор поля редактирования //Идентификатор для таймера
//Идентификатор первой строки в ресурсах //Идентификатор второй строки в ресурсах
class CMyButton: public CButton //Создание нового класса { public: afx_msg void OnLButtonDown(UINT, CPoint); afx_msg void OnRButtonDown(UINT, CPoint); private:
51
};
DECLARE_MESSAGE_MAP(); //Таблица откликов кнопки
class CMyApp : public CWinApp { public: CMyApp(); virtual BOOL InitInstance(); };
//конструктор по умолчанию //стандартная инициализация
class CMainWnd : public CFrameWnd { public: CMainWnd(); //Конструктор по умолчанию int OnCreate(LPCREATESTRUCT lpCreateStruct); ~CMainWnd(); //Деструктор private: int VX, sgn_x; //Приращение координаты по оси x и знак приращения int VY, sgn_y; //Приращение координаты по оси y и знак приращения CRect newPlace; //Квадрат, в который вписан отображаемый круг CRect oldPlace; //Квадрат, в который был вписан отображаемый круг в предыдущий момент CStatic* MyStatic; //Указатель на объект надпись CMyButton* MyButton; //Указатель на объект кнопка CEdit* MyEdit; //Указатель на объект поле редактирования CStatusBar m_wndStatusBar; //Объект строки состояния CMenu m_wndMenu; //Объект меню DECLARE_MESSAGE_MAP(); //Таблица откликов окна public: afx_msg void MenuExit(); //Процедура реакции на выбор пункта меню "выход" afx_msg void MenuStart(); //Процедура реакции на выбор пункта меню "старт" afx_msg void MenuStop(); //Процедура реакции на выбор пункта меню "стоп" afx_msg void OnTimer(UINT_PTR); afx_msg void OnPaint(); afx_msg void OnKeyDown(UINT, UINT, UINT); afx_msg void OnMouseMove(UINT, CPoint cp); //Движение мыши }; BEGIN_MESSAGE_MAP(CMyButton, CButton) //Таблица откликов на сообщения для кнопки ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN() END_MESSAGE_MAP() BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd) ON_WM_CREATE() ON_COMMAND(ID_APP_EXIT,MenuExit) "выход" ON_COMMAND(ID_MOTION_START,MenuStart) ON_COMMAND(ID_MOTION_STOP,MenuStop) ON_WM_TIMER() ON_WM_PAINT() ON_WM_KEYDOWN() ON_WM_MOUSEMOVE() END_MESSAGE_MAP()
//Таблица откликов на сообщения //Событие создания окна //Обработка реакции на выбор меню //Выбор пункта "старт" //Выбор пункта "стоп" //Обработка WM_TIMER //Реакция на нажатие клавиши
void CMyButton::OnLButtonDown(UINT, CPoint) { MoveWindow(10,110,90,30); }
52
void CMyButton::OnRButtonDown(UINT, CPoint) { MoveWindow(10,40,90,30); } CMyApp::CMyApp(){}
// конструктор главного класса приложения
BOOL CMyApp::InitInstance() // стандартная инициализация { m_pMainWnd=new CMainWnd(); // создать указатель на класс окна ASSERT(m_pMainWnd); // проверить его правильность m_pMainWnd->ShowWindow(SW_SHOW); // показать окно m_pMainWnd->UpdateWindow(); // обновить окно return TRUE; // вернуть, что все нормально }; CMainWnd::CMainWnd() { oldPlace.left = 150; oldPlace.top = 150; oldPlace.right = 170; oldPlace.bottom = 170; newPlace = oldPlace; VX = VY = sgn_x = sgn_y = 1; Create(NULL,"Окно приложения пользователя", WS_OVERLAPPEDWINDOW, rectDefault, NULL, NULL); // создать окно программы MyStatic = new CStatic(); if (MyStatic != NULL) MyStatic->Create("Надпись", WS_CHILD|WS_VISIBLE|SS_CENTER, CRect(10, 10, 100, 30), this); MyButton = new CMyButton(); if (MyButton != NULL) MyButton->Create("Кнопка", WS_CHILD|WS_VISIBLE|SS_CENTER, CRect(10, 40, 100, 70), this, IDC_MYBUTTON); MyEdit = new CEdit(); if (MyEdit != NULL) MyEdit->Create(WS_CHILD|WS_VISIBLE|WS_BORDER, CRect(10, 80, 100, 100), this, IDC_MYEDIT); } CMainWnd::~CMainWnd() { if (MyStatic != NULL) delete MyStatic; if (MyButton != NULL) delete MyButton; if (MyEdit != NULL) delete MyEdit; }
//Деструктор класса //Удалить динамический объект //Удалить динамический объект //Удалить динамический объект
int CMainWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; m_wndStatusBar.Create(this); m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)); m_wndStatusBar.SetPaneInfo(0, 0, 0, 50); //Изменение размеров первой секции строки состояния m_wndStatusBar.SetPaneInfo(1, 0, 0, 50); //Изменение размеров второй секции строки состояния m_wndMenu.LoadMenu(IDR_MENU1); //Загрузить меню из файла ресурса SetMenu(&m_wndMenu); //Установить меню return 0; }
53
void CMainWnd::MenuExit() { DestroyWindow(); }
//Уничтожить окно
void CMainWnd::MenuStart() { SetTimer(ID_TIMER_FOR_MOT,10,NULL); //Запустить таймер } void CMainWnd::MenuStop() { KillTimer(ID_TIMER_FOR_MOT); //Остановить таймер } void CMainWnd::OnTimer(UINT_PTR nIDEvent) { CRect rectClient; GetClientRect(&rectClient); rectClient.bottom -= 20; rectClient.left += 110; if(newPlace.right > rectClient.right || newPlace.left < rectClient.left) sgn_x = -sgn_x; if(newPlace.bottom > rectClient.bottom || newPlace.top < rectClient.top) sgn_y = -sgn_y; newPlace.left += sgn_x*VX; newPlace.right += sgn_x*VX; newPlace.top += sgn_y*VY; newPlace.bottom += sgn_y*VY; InvalidateRect(oldPlace); //Обновить прямоугольник, в котором был круг InvalidateRect(newPlace); //Обновить прямоугольник, в котором круг сейчас oldPlace = newPlace; } void CMainWnd::OnPaint() { CPaintDC dc(this); CBrush bBrush(RGB(0,0,255)); dc.SelectObject(&bBrush); dc.Ellipse(newPlace); } void CMainWnd::OnKeyDown(UINT nChar, UINT, UINT) { switch(nChar) //Обработка в зависимости от кода символа { case 40: if(sgn_y > 0) VY++; else VY--; break; case 38: if(sgn_y > 0) VY--; else VY++; break; case 37: if(sgn_x > 0) VX--; else VX++; break; case 39: if(sgn_x > 0) VX++; else VX--; break; } } void CMainWnd::OnMouseMove(UINT, CPoint cp)
54
{
}
char chX[10]; char chY[10]; itoa(cp.x,chX,10); itoa(cp.y,chY,10); CString csStatusX(chX); CString csStatusY(chY); m_wndStatusBar.SetPaneText(0,csStatusX); m_wndStatusBar.SetPaneText(1,csStatusY);
CMyApp theApp;
//Буфер для координат //Буфер для координат //Число переводим в строку //Число переводим в строку //Формируем строку //Формируем строку //Выводим первую панель //Выводим вторую панель //Запуск приложения
В результате компиляции и запуска на выполнение. В результате получим окно приложения следующего вида:
55
Часть 2. Среда разработки MS Visual Studio 2005. Разработка полноценных Windows-приложений Глава 1. Теоретические основы работы в среде MS Visual Studio 2005 §1. Интегрированная среда разработки MS Visual Studio 2005. Понятие проекта и решения Visual C++ является частью Microsoft Visual Studio 2005 – комплекта средств разработки приложений. Visual C++ – это интегрированная среда разработки, и все создаваемые с помощью неё приложения представляют собой проекты. Проект – это набор взаимосвязанных исходных файлов компиляция и компоновка, которых позволяет создать исполняемую Windows программу или DLL.
Исходные файлы проекта хранятся в отдельном каталоге, кроме того, проект часто зависит от внешних файлов, таких как подключаемых (include) и библиотечных файлов. В проекте Visual С++ взаимозависимости между отдельными компонентами описаны в текстовом файле проекта с расширением VCPROJ. А специальный текстовый файл решения с расширением SLN содержит список всех проектов данного решения. Решение (Solution) – набор проектов, объединённых вместе, которые решают одну задачу.
Для того чтобы начать работу с существующим проектом, необходимо открыть в Visual C++ соответствующий SLN файл. Типы файлов создаваемых в проекте Visual C++ указаны ниже: Расширение файла APS BSC IDL NCB SLN SUO VCPROJ
Описание
Поддержка просмотра ресурсов Информация браузера Файл на языке описания интерфейсов IDL Поддержка просмотра классов Файл решения Поддержка параметров и конфигурации решения Файл проекта
Среда разработки Visual Studio предлагает множество инструментов для создания и настройки приложений любого типа. С помощью неё можно: • • • • •
Генерировать скелет приложения без написания кода вручную. Окрывать проект в нескольких различных режимах представления. Редактировать файлы с исходным кодом и включаемые файлы. Подключаться к внешним ресурсам (базам данных). Разрабатывать визуальный интерфейс (меню, иконки, диалоговые окна). 56
• Компилировать и связывать приложение. • Производить отладку приложения в процессе работы. С технической точки зрения Visual C++ представляет собой один из инструментов Visual Studio. С помощью этой интегрированной среды, вы можете использовать любые другие языки программирования, в том числе разработанные не Microsoft. Так выглядит открытый проект в среде MS Visual Studio:
§2. Утилиты и мастера MS Visual Studio 2005 Создание Windows программ «с чистого листа» вручную требует много времени, причём большая его часть уходит на создание и отладку каркаса приложения. Если вы программируете, используя Win API, то это – написание функций WinMain и WndProc, цикла обработки сообщений, если же используйте библиотеку MFC, то это написание собственного класса приложения, его метода InitInstance и класса окна. Среда MS Visual Studio 2005 предоставляет набор мастеров и утилит для автоматизации процесса создания каркаса приложения, и, тем самым, избавляет вас от рутинной работы, которую необходимо проделывать при создании Windows приложения. Основные мастера и утилиты MS Visual Studio 2005 Мастера для создания проектов:
1. MFC Application Wizard (exe) – мастер для создания проектов Windows-приложений на основе классов библиотеки MFC. Мастер предоставляет программисту богатый выбор настроек проекта. С его помощью можно создавать приложения с однодокументным, многодокументным или диалоговым интерфейсом. Однодокументное приложение позволяет пользователю работать только с одним
57
2.
3.
4.
5.
6.
7.
8.
файлом. Многодокументное приложение может одновременно предоставить работу с несколькими документами, каждым в собственном окне. Пользовательский интерфейс диалогового приложения представляет собой единственное диалоговое окно. MFC DLL Wizard – этот мастер приложений позволяет создать структуру DLL, основанную на MFC. При помощи него можно определить характеристики будущей DLL. ATL Project Wizard – это средство позволяет создать элемент управления ActiveX или сервер автоматизации, используя новую библиотеку шаблонов ActiveX (ActiveX Template Library - ATL). Опции этого мастера дают возможность выбрать активный сервер (DLL) или исполняемый внешний сервер (exe-файл). Custom Wizard – при помощи этого средства можно создать пользовательские мастера AppWizard. Пользовательский мастер может базироваться на стандартных мастерах для приложений MFC или DLL, а также на существующих проектах или содержать только определяемые разработчиком шаги. Visual Studio Add-in Wizard – мастер дополнений позволяет создавать дополнения к Visual Studio. Библиотека DLL расширений может поддерживать панели инструментов и реагировать на события Visual Studio. MFC ActiveX Control Wizard – мастер элементов управления реализует процесс создания проекта, содержащего один или несколько элементов управления ActiveX, основанных на элементах управления MFC. Win32 Project Wizard – этот мастер позволяет создать проект обычного Windowsприложения или динамически подключаемой библиотеки. Тип проекта определяется выбором соответствующих опций в диалоговых окнах мастера. Проект создается незаполненным, файлы с исходным кодом в него следует добавлять вручную. Win32 Console Application Wizard – мастер создания проекта консольного приложения. Проект консольного приложения создается пустым, предполагая добавление файлов исходного текста в него вручную.
Утилиты для редактирования проектов: 1. Утилита Class View. Окно Class View открывается при выборе команды View→Class
View и отображает дерево всех классов проекта с методами и полями. Чтобы увидеть код элемента, необходимо дважды кликнуть по нему. При внесении изменений в исходный текст, содержимое окна Class View автоматически обновляется. За создание новых классов, добавлением их в проект, созданием виртуальных функций и функций обработчиков сообщений отвечает утилита Class View. 2. Редактор ресурсов используется для создания и редактирования ресурсов (меню, панелей управления, строк состояния, курсоров, диалогов и т.д.) в режиме WISIWIG (what you see is what you get – что вижу то и получаю). Окно для просмотра ресурсов проекта открывается при выборе команды View→Resource. Для перехода в режим редактирования уже созданного ресурса, необходимо дважды кликнуть по нему. Для создания нового ресурса, необходимо вызвать контекстное меню (нажать правой кнопкой мыши) в окне просмотра ресурсов и выбрать пункт Add→Resource… 3. Утилита Solution Explorer. В Solution Explorer отображается структура всего решения. Окно Solution Explorer содержит древовидное представление элементов проекта, которые можно открывать по отдельности для модификации или выполнения
58
задач по управлению. Для добавления нового элемента в проект, необходимо щёлкнуть по одному из внутренних узлов дерева в окне Solution Explorer правой кнопкой мыши и выбрать пункт Add… Для настройки компиляции и компоновки проекта, необходимо нажать Project→Properties…, или нажать правой кнопкой мыши на имя проекта в окне Solution Explorer и выбрать пункт Properties. Рассмотрим подробнее утилиту ClassView. Утилита Class View Создание нового класса
При помощи Class View можно добавить новый класс в проект, созданный на основе базовых классов. Утилита позволяет использовать в качестве базовых классов как классы каркаса MFC, так и собственные классы. Объекты, порожденные от класса CCmdTarget, могут обрабатывать сообщения Windows и команды, поступающие от меню, кнопок, акселераторов. Класс CCmdTarget и другие, наследованные от него классы, имеют таблицу сообщений (Message Map) – набор макрокоманд, позволяющий сопоставить сообщения Windows и команды метода класса. Для того чтобы добавить класс, необходимо: 1. Вызвать контекстное меню проекта в окне утилиты Class View (клик правой кнопкой мыши по имени проекта)→Add→Class… 2. Выбрать в появившемся диалоговом окне тип добавляемого класса (например, Categories: MFC. Templates: MFC class). Нажать Ok. 3. В появившемся окне ввести необходимые данные. Нажать Ok. Полученная заготовка класса полностью работоспособна. Ее можно дополнить по своему усмотрению новыми методами и данными. Эту работу можно выполнить вручную, но удобнее воспользоваться услугами утилиты Class View. Для этого необходимо: 1. Вызвать контекстное меню соответствующего класса в окне утилиты Class View (клик правой кнопкой мыши по имени класса) →Add→Function… или Add→Variable… 2. Следовать дальнейшим инструкциям мастера. Редактирование классов с помощью Class View
С помощью Class View можно редактировать уже созданные классы, добавлять в них обработчики сообщений для наследников класса CCmdTarget, переопределять виртуальные функции. За счет использования Class View процедура редактирования собственного класса значительно ускоряется и уменьшается вероятность совершить ошибку во время объявления методов. Для добавления обработчиков или переопределения методов необходимо: 1. Вызвать контекстное меню соответствующего класса в окне утилиты Class View→Properties. 2. Во всплывающем окне нажать кнопку Events, Messages или Overrides в зависимости от того, что требуется сделать в данный момент (обработать событие, обработать сообщение или переопределить виртуальную функцию).
59
Class View позволяет не только добавить в класс новые методы, но и удалить их. Class View самостоятельно удалит объявление метода из прототипа класса и его тело из cpp файла. Включение в класс новых элементов данных
Class View позволяет включать в класс не только новые поля и методы, но и элементы данных, связанные с полями диалоговых панелей, форм просмотра и форм для просмотра записей баз данных и полей наборов записей. Class View использует специальные процедуры, чтобы привязать созданные им элементы данных к классам и полям диалоговых панелей. Эти процедуры носят названия "обмен данными диалоговой панели" и "проверка данных диалоговой панели" (Dialog Data Exchange and Dialog Data Validation - DDX/DDV). Чтобы привязать поля из наборов записей к переменным, используется процедура обмена данными с полями записей (Record Field Exchange - RFX).
§3. Создание приложения по шаблону с помощью мастера MFC Application Wizard Благодаря мастеру MFC Application Wizard, среда разработки позволяет быстро создавать новые Windows приложения по шаблону. Разработчику достаточно ответить на ряд вопросов, касающихся того, какое приложение требуется создать, и исходные тексты приложения вместе с файлами ресурсов будут созданы. Эти тексты можно оттранслировать и получить готовый загрузочный модуль приложения, однако прикладную часть приложения должен написать программист. Работу мастера MFC Application Wizard рассмотрим на примере. Создадим программу с однодокументным интерфейсом с поддержкой технологии «документ-вид». 1. File→New→Project… 2. Project types: MFC. Templates: MFC Application. Введём имя “MyProg” проекта в строку Name. Снимем флажок в пункте Create directory for solution. Нажмём Ok. 3. Нажмём Next. В следующем окне выберем Application type: Single document. Снимем флажок в пункте Use Unicode libraries. 4. Далее нажимаем Next до конца, пока кнопка не будет заблокирована (в появляющихся окнах мастер предлагает добавить или удалить различные компоненты: поддержка баз данных, поддержка печати и т.п.). 5. В последнем окне можно увидеть список классов, которые будут созданы мастером. Нажмём кнопку Finish. Приложение готово. Можно скомпилировать и запустить. В указанной директории “MyProg” будут созданы файлы: • • • • • • • •
MyProg.vcproj MyProg.h MyProg.cpp StdAfx.h StdAfx.cpp MainFrm.h MainFrm.cpp MyProgDoc.h
основной файл проекта заголовочный файл приложения исходный текст приложения заголовочный файл для стандартного "каркаса" приложения исходный текст стандартного "каркаса" приложения заголовочный файл главного окна исходный текст главного окна заголовочный файл документа 60
• • • • • • • •
MyProgDoc.cpp MyProgView.h MyProgView.cpp Resource.h MyProg.rc MyProg.ncb MyProg.sln res
исходный текст документа заголовочный файл вида исходный текст вида файл с ресурсными константами файл с ресурсами файл с информацией о представлении и взаимных связях файл решения каталог для ресурсов
Программа будет состоять из четырех основных частей: 1. Объект приложения находится в файлах MyProg.h и MyProg.cpp. Это то, что
Windows запускает при старте программы. Когда этот объект начинает работу, он размещает на экране главное окно. 2. Объект главного окна находится в файлах MainFrm.h и MainFrm.cpp и отображает главное окно программы: в нем находится меню, заголовок окна и панель инструментов. Рабочая зона программы называется клиентской областью окна. 3. Объект документа находится в файлах MyProgDoc.h и MyProgDoc.cpp и хранит данные программы. 4. Объект вида находится в файлах MyProgView.h и MyProgView.cpp и предназначен для работы с клиентской областью. Отображает данные, хранящиеся в объекте документа. Схема взаимосвязи частей программы:
CMyProgApp
CMainFrame
CMyProgView
CMyProgDoc
Глава 2. Практика работы в среде визуального программирования §1. Начало работы в Visual C++ Вывод текста в окно программы
Создадим программу, которая будет считать символы с клавиатуры и отображать их в клиентском окне. Для этого будем обрабатывать сообщение Windows WM_CHAR. Свяжем с ним функцию OnChar(UINT nChar, UINT nRepCnt, UINT nFlags). Назовем программу Key. Будем создавать ее с помощью MFC Application Wizard, интерфейс SDI (Single Document Interface).
61
Этапы написания программы
1. 2. 3. 4.
Подготовить буфер под хранение полученных символов. Организовать чтение символов с клавиатуры. Сохранить символы в документе. Отобразить текст.
1. Подготовка буфера для хранения символов
Определим в классе CKeyDoc, прототип которого содержится в заголовочном файле KeyDoc.h, строку-объект StringData класса MFC CString. Для этого добавим следующий код: class CKeyDoc : public CDocument { …… DECLARE DYNCREATE(CKeyDoc) Public: CString StringData; …… };
Проинициализируем переменную пустой строкой "". Это делается в конструкторе объекта документа, расположенном в файле KeyDoc.cpp. Конструктор объекта документа CKeyDoc: CKeyDoc::CKeyDoc() { // TODO: StringData=""; }
2. Чтение символов с клавиатуры
При нажатии клавиши Windows посылает сообщение WM_CHAR. Его надо связать с методом OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) объекта вида. Для этого используем утилиту Class View 1. Вызовем контекстное меню класса CKeyView (нажмём правой кнопкой по имени класса в окне утилиты Class View) → выберем пункт Properties. 2. Во всплывшем окне свойств нажмём на кнопку Messages. 3. Выбираем в списке сообщений WM_CHAR. Раскрываем список действий в пустом поле напротив названия сообщения нажатием левой кнопкой мыши. Выбираем единственный вариант OnChar. Мастер автоматически создаст обработчик OnChar и откроет файл KeyView.cpp для редактирования тела метода. 3. Сохранение символа в документе
Введенный символ находится в параметре nChar и его необходимо сохранить в строковом объекте StringData
62
Обработчик OnChar: void CKeyView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { //TODO: CKeyDoc *pDoc = GetDocument(); //Получение указателя на объект документа ASSERT_VALID(pDoc); //Проверка указателя pDoc->StringData += nChar; //Обновление строки символов Invalidate(); //Вызов метода OnDraw }
Макрос ASSERT_VALID проверяет, что полученный указатель действительно ссылается на документ (иначе ошибка). 4. Отображение текста
Вывод будем осуществлять в методе OnDraw(CDC* pDC) класса вида. Для вызова метода OnDraw используется функция Invalidate Метод OnDraw: void CKeyView::OnDraw(CDC *pDC) { … pDC->TextOut(0,0,pDoc->StringData); }
Скомпилируем и запустим программу. Усложним задачу. Потребуем, чтобы текст выводился в центре клиентской области окна. Для решения поставленной задачи изменим метод OnDraw Изменённый метод OnDraw: void CKeyView::OnDraw(CDC* pDC) { CKeyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CRect rect; //Переменная для хранения размеров клиентской области GetWindowRect(&rect); //Получение размеров клиентской области int x=rect.Width()/2; //Задание координаты X центра клиентской области int y=rect.Height()/2; //Задание координаты Y центра клиентской области CSize size = pDC->GetTextExtent(pDoc->StringData); //Определение размров выводимой строки в пикселях x -= size.cx/2; //Сдвиг координаты X на половину длины выводимой строки влево y -= size.cy/2; //Сдвиг координаты Y на половину высоты выводимой строки вверх pDC->TextOut(x,y,pDoc->StringData); //Вывод строки в центр клиентской области }
Работа с курсором и мышью
Напишем программу, которая будет создавать курсор, в выбранной произвольной точке клиентской области и выводить текст с указанной позиции. Создадим SDI программу под названием Mouse. Воспользуемся материалом предыдущего пункта: создадим обработчик
63
сообщения WM_CHAR. Чтобы создать курсор, необходимо знать его размеры (обычно высота равна высоте символа, а ширина 18 ширины символа). Курсор будем создавать в методе OnDraw. Чтобы определить размеры курсора, необходимо получить данные из структуры TEXTMETRIC. Для этого необходимо создать объект типа TEXTMETRIC и заполнить его поля, используя метод GetTextMetrics(&tm). Но сначала добавим в прототип класса CMouseView следующие переменные class CMouseView : public CView { …… DECLARE DYNCREATE(CMouseView) public: CPoint CaretPosition; int x, y; …… };
Переменная CaretPosition типа CPoint предназначена для хранения координат курсора. Метод OnDraw: void CMouseView::OnDraw(CDC* pDC) { CMouseDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here TEXTMETRIC textmetric; //Переменная для хранения информации о шрифте pDC->GetTextMetrics(&textmetric); //Получение информации о шрифте CreateSolidCaret(textmetric.tmAveCharWidth/8,textmetric.tmHeight); //Создаём курсор CaretPosition.x=CaretPosition.y=0; //Задаём начальное положение курсора SetCaretPos(CaretPosition); //Задаем позицию курсора ShowCaret(); //Отображаем курсор CSize size=pDC->GetTextExtent(pDoc->StringData);//Получение размеров выводимой строки в пикселях HideCaret(); //Скрыть курсор CaretPosition.x=size.cx; //Присваивание x корд. конца SetCaretPos(CaretPosition); //Перемещение курсора в новое положение ShowCaret(); //Вывод на экран pDC->TextOut(0,0,pDoc->StringData); //Вывод текста }
Скомпилируем и запустим приложение. Рассмотрим задачу вывода строки с места, на который указывает указатель мыши. Свяжем сообщение WM_LBUTTONDOWN с методом OnLButtonDown(UINT nFlags, CPoint point). Параметр point (объект класса CPoint) содержит текущие координаты указателя мыши. Для очистки строкового объекта используется метод Empty() класса CString. Обработчик нажатия левой кнопки мыши OnLButtonDown: void CMouseView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default x=point.x; //Запоминаем координату X места где была нажата левая кнопка мыши y=point.y; //Запоминаем координату Y места нде была нажата левая кнопка мыши
64
CMouseDoc* pDoc = GetDocument(); //Получаем указатель на объект документа ASSERT_VALID(pDoc); //Проверяем правильность указателя pDoc->StringData.Empty(); //Очищаем выводимую строку Invalidate(); //Вызывваем OnDraw CView::OnLButtonDown(nFlags, point); }
В методе класса OnDraw сделаем изменения: CaretPosition.x=x+size.cx; //Присваивание x CaretPosition.y=y; …… pDC->TextOut(x,y,pDoc->StringData);
корд. конца
Скомпилируем и запустим приложение.
§2. Панель инструментов, меню, акселераторы В этом параграфе будем рассматривать работу с меню с помощью редактора ресурсов на примере создания простой программы. Создание меню
1. Создадим SDI программу с названием Menu. 2. Вкладка Resource View, идём по дереву Menu→Menu.rc →Menu→IDR_MAINFRAME двойной клик. 3. Вставим пункт меню: File→Type Hear, вводим название Print Welcome. 4. Вызовем свойства пункта меню: Клик правой кнопкой по имени→Properties. 5. Запишем в поле ID: ID_FILE_PRINTWELCOME. 6. Закроем редактор меню и запустим программу. В меню File появилась команда, но она не работает (не подключена). Для того чтобы программа реагировала на выбор только что созданного пункта меню, свяжем с ним обработчик. 1. 2. 3. 4.
В окне утилиты Class View вызовем свойства класса CMenuView. В окне свойств нажмём кнопку Events. Выберем в списке идентификаторов ID_FILE_PRINTWELCOME и раскроем ветку. Раскроем список напротив поля COMMAND и выберем OnFilePrintwelcome.
Создадим переменную типа CString и инициализируем в объекте документа. Добавим код, выделенный жирным, в прототип класса CMenuDoc. public: virtual ~CMenuDoc(); CString StringData;
Проинициализируем переменную в конструкторе: StringData="";
Напишем код обработчика OnFilePrintwelcome
65
Обработчик OnFilePrintwelcome: void CMenuView::OnFilePrintWelcome() { CMenuDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData="Добро пожаловать в меню."; Invalidate(); }
Добавим следующую строку в метод OnDraw: pDC->TextOut(0,0,pDoc->StringData);
Скомпилируем и запустим программу. Усложним задачу. Создадим полномасштабную ветку меню с собственными подменю. Добавим в меню пункт Demo. 1. Войдём в редактор меню. 2. Выделим Edit→Вызовем контекстное меню→Выберем Insert New→ Введём имя Demo. 3. Введем пункты: • Grayed • Checked • Submenus Если в начало ввести &, то первая буква меню будет подчеркнута. Пусть это буква D, тогда Alt+D – это клавишный ускоренный вызов. Добавим подменю в пункте Submenus. Выделим пункт Submenus, в раскрывшейся слева вкладке Type Hear введём имя первого элемента подмею. Добавим в подменю ещё оди пункт. Получим: • Sub1 • Sub2 Создание акселератора Акселератор – сочетание клавиш для выбора команды меню.
Чтобы добавить акселератор, сделаем следующее: 1. Откроем папку Accelerator в редакторе ресурсов. Дважды кликнем IDR_MAINFRAME. 2. Выделим последнюю пустую строку. 3. В появившемся списке в поле ID выберем строку ID_SUBMENUS_SUB1. 4. Выберем строку Ctrl из списка в поле Modifiers. 5. Выберем строку VK_F5 из списка в поле Key. 6. Выход. 7. Добавить в название клавиши Sub2\tCtrl+F5.
66
Добавление кнопок на панель инструментов
Создадим кнопку на панели для команды Sub1 1. Откроем папку Toolbar в редактрое ресурсов. Дважды кликнем IDR_MAINFRAME 2. Выделим пустую кнопку и сделаем рисунок. 3. Вызовем свойства нарисованной кнопки. Выберем в поле ID имя идентификатора ID_SUBMENUS_SUB1. Добавим код для пункта Sub1. Для этого вызовем свойства класса CMenuView из утилиты Class View. Перейдём к списку событий Events. Раскроем вкладку ID_SUBMENUS_SUB1 и добавим обработчик. Обработчик OnSubmenusSub1: void CMenuView::OnSubmenusSub1() { CMenuDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData="Меню 1"; Invalidate(); }
Тоже можно сделать и для Sub2.
§3. Диалоговые окна. Работа с элементами управления – кнопками, текстовыми полями Модальное диалоговое окно
Создадим программу, которая будет выводить диалоговое окно в ответ на выбор пользователем пункта Show Dialogs в меню File. В диалоговом окне создадим текстовое поле и кнопку. При нажатии на кнопку в текстовое поле должна заноситься символьная строка. Символьную строку изменим и при выходе из диалогового окна в клиентской области выведем измененную символьную строку. Создание диалогового окна
1. Создадим SDI программу Dialog. 2. Включим в меню File команду Show Dialogs и свяжем с обработчиком. 3. Войдём в редактор ресурсов. Щёлкнем правой кнопкой по папке Dialog, выберем Insert Dialog. Среда переведёт нас в режим визуального редактирования диалогового окна. 4. Вставим два элемента: кнопку и текстовое поле. 5. Изменим надпись на кнопке через свойства Properties: “Print String”. Создадим класс диалогового окна на основе созданного только что ресурса: 1. Project→Add Class… 2. В списке Categories: выберем MFC. В списке Templates: выберем MFC Class. Нажмём Add.
67
3. В поле Base class: выберем CDialog. В поле Dialog ID выберем идентификатор только что созданного ресурса. 4. Введём имя класса: “CDlg” и нажмём Finish. Связывание обработчиков с элементами диалоговых окон
При нажатии на кнопку в текстовом поле должна появиться строка "Текст в диалоговом окне". Свяжем сообщение о нажатии на кнопку с обработчиком этого сообщения 1. Вызовем свойства класса CDlg через Class View. 2. Перейдём к списку событий Events. 3. Выберем поле ID_BUTTON1 и добавим обработчик OnBnClickedButton1. Связывание переменных с элементами диалоговых окон
Visual C++ позволяет связывать переменные класса с элементами диалоговых окон. Создадим переменную m_text для хранения строки текстового поля с помощью мастера: 1. Вызовем контекстное меню класса CDlg через Class View. Выберем пункт Add→Add Variable… 2. Установим флажок Control Variable. Выберем в поле Control ID: IDC_EDIT1, в поле Category: Value, Variable type: CString, Variable name: m_text. 3. Finish. Обработчик OnButton1: void CDlg::OnBnClickedButton1() { m_text="Текст в диалоговом окне"; UpdateData(false); }
Обмен информацией между переменной и элементом IDC_EDIT1 осуществляется в специальном методе, включенном в класс диалогового окна: void CDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT1, m_text); }
Код этого метода генерируется мастером, изменять его вручную не рекомендуется. Вызов метода UpdateData(BOOL) с параметром false заносит в текстовое поле значение переменной m_text. Вызов с параметром true присваивает переменной m_text содержимое текстового поля. Переопределение метода для кнопки Ok
Переопределение необходимо, если поле редактировалось и необходимо сохранить текущее содержимое текстового поля. Для того чтобы переопределить метод, вызовем свойства класса CDlg, перейдём к списку виртуальных функций, нажав кнопку Overrides. Выберем метод OnOk().
68
Переопределённый метод OnOk: void CDlg::OnOK() { UpdateData(true); CDialog::OnOK(); }
Отображение диалогового окна
Чтобы класс вида мог работать с членами класса CDlg, необходимо включить в него Dlg.h – заголовочный файл класса CDlg: #include "Dlg.h"
В методе OnFileShowdialogs() создадим новый объект класса CDlg и отобразим его, пользуясь методом DoModal(). Далее присвоим переменной StringData значение переменной m_text и выведем значение переменной StringData в клиентском окне в левом верхнем углу Обработчик OnFileShowdialogs: void CDialogView::OnFileShowdialogs() { Dlg dlg; int result=dlg.DoModal(); if(result==IDOK) { CDialogDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData=dlg.m_text; Invalidate(); } }
Добавим в метод OnDraw строку: pDC->TextOut(0,0,pDoc->StringData);
Использование диалогового окна в качестве главного
Создадим диалоговое окно в качестве главного и выведем в нем, по нажатию на кнопку, текстовое сообщение. Создадим программу BaseDialog. 1. На первом шаге работы мастера Application Wizard выбрать Dialog based (базовым классом для новой программы станет класс CDialog). 2. Finish. 3. Перейдём в редактор ресурсов, откроем ресурс диалогового окна для редактирования. 4. Поместим кнопку и текстовое поле. 5. Свяжем кнопку с обработчиком (двойной клик по OnButton1). Создавать класс диалогового окна в этом случае не надо. Его создал уже автоматически Application Wizard.
69
Рассмотрим общий способ работы с элементами управления – будем рассматривать их как объекты: 1. Вызовем контекстное меню текстового поля (щелчок правой кнопкой мыши по элементу управления в редактрое ресурсов). 2. Выберем пункт Add Variable… 3. Выберем необходимый тип и введём имя переменной. 4. Нажмём Finish. Обработчик OnButton1: void CBaseDialogDlg::OnButton1() { m_edit.SetWindowText(CString("Диалоговое окно!")); }
Скомпилируем и запустим программу. Совместное использование флажков и переключателей Флажок – управляющий элемент, который позволяет выбрать один или несколько вариантов. Переключатели осуществляют выбор одного из нескольких элементов. Флажки
Создадим диалоговое окно с тремя флажками и текстовым полем, в котором будет отображаться информация о выбранном флажке. Создадим программу Maps на основе диалогового окна (Dialog based). Редактор диалоговых окон: Удалить надпись "TODO …", и добавить три флажка и текстовое поле. Флажки надо выстроить в виде столбца. Текстовое поле расположим внизу. Названия флажков – Flag 1, Flag 2, Flag 3. Выравнивание элементов в редакторе диалоговых окон: Нажать на Ctrl и, не отпуская ее, щелкнуть на каждом из флажков. Последний из выбранных флажков будет служить "эталоном" (он выделен синими маркерами). • Выравнивание по горизонтали (по левому краю): Контекстное меню->Align Lefts. • Выравнивание по вертикали (расстояния между флажками будут одинаковыми): Контекстное меню -> Align Tops. Связывание флажков с кодом программы: С помощью Class View каждый флажок свяжем со своим обработчиком (OnCheck1(), OnCheck2(), OnCheck3()). Создадим переменную m_text и свяжем её с текстовым полем. Обработчики флажков: void CMapsDlg::OnCheck1() { m_text="Выбран 1 флажок"; UpdateData(false);
70
} void CMapsDlg::OnCheck1() { m_text=" Выбран 2 флажок "; UpdateData(false); } void CMapsDlg::OnCheck1() { m_text=" Выбран 3 флажок "; UpdateData(false); }
Скомпилируем и запустим программу. Переключатели
Создать диалоговое окно с тремя переключателями и текстовым полем, в котором будет отображаться информация о выбранном переключателе. Создадим программу Radios на основе диалогового окна (Dialog based). Редактор диалоговых окон: Удалить надпись "TODO …" , и добавить три переключателя и текстовое поле. Переключатели надо выстроить в виде столбца и внизу текстовое поле. Названия переключателей: Radio 1, Radio 2, Radio 3. Обработчики переключателей: void CRadiosDlg::OnRadio1() { m_text="Выбран 1 переключатель"; UpdateData(false); } void CRadiosDlg::OnRadio2() { m_text=" Выбран 2 переключатель"; UpdateData(false); } void CRadiosDlg::OnRadio3() { m_text=" Выбран 3 переключатель"; UpdateData(false); }
Скомпилируем и запустим программу. Совместное использование флажков и переключателей
Создать диалоговое окно с тремя флажками, тремя переключателями и текстовым полем, в котором будет отображаться информация о выбранном варианте. Использовать групповые поля. Групповые поля предназначены для группировки элементов – как визуальной, так и функциональной. Все переключатели внутри группового поля работают совместно.
Создадим программу Choice на основе диалогового окна (Dialog based). Редактор диалоговых окон: Удалить надпись "TODO …" , и добавить две группы флажков и переключателей и одно текстовое поле. Названия переключателей – Math, Gum, Inf, Bio, флажки – Math, Phys, Hist, Bio. Создадим для каждого переключателя свой обработчик – OnRadio1(),…, OnRadio4(). Для текстового поля введем переменную m_text. Свяжем с флажками
71
переменные, которые будут изменять состояния флажков. Эти переменные будут представлять весь элемент. Зададим нужное состояние флагов в обработчиках для каждого переключателя. Обработчики переключателей: void CChoiceDlg::OnRadio1() { m_check1=true; m_check2=true; m_check3=false; m_check4=true; m_text="85 ball"; UpdateData(false); } void CChoiceDlg::OnRadio2() { m_check1=false; m_check2=true; m_check3=true; m_check4=true; m_text="70 ball"; UpdateData(false); } void CChoiceDlg::OnRadio3() { m_check1=true; m_check2=true; m_check3=true; m_check4=false; m_text="83 ball"; UpdateData(false); } void CChoiceDlg::OnRadio4() { m_check1=true; m_check2=true; m_check3=false; m_check4=true; m_text="70 ball"; UpdateData(false); }
Скомпилируем и запустим программу.
§4. Списки. Комбинированные поля и бегунки Список – управляющий элемент, представляющий пользователю перечень из нескольких строк. (Выделить –1 клик, выбрать – 2 клика.) Комбинированное поле – сочетание текстового поля, раскрывающегося списка и кнопки, с помощью которой пользователь открывает список. Бегунок – управляющий элемент. Чаще всего применяется для ввода числовых величин – например, интенсивности цвета.
72
Списки
Создадим список и выведем выбранную строку в текстовое поле. Создадим программу Lists на основе диалогового окна, добавим список, текстовое поле и 2 надписи ("Дважды щелкните по строке" и "Вы выбрали"). Замечание: Управляющие элементы можно отображать и в обычном окне (недиалоговым). В этом случае нельзя воспользоваться редактором WISIWYG. Создание объекта для работы со списком
1. Вызовем контестное меню списка в редакторе диалогового окна и выберем пункт Add Variable. 2. В поле Category: выберем значение Control. 3. Variable name: m_list. 4. Finish. Инициализация данных в списке
Для инициализации данных используем метод OnInitDialog() и метод AddString(…) объекта m_list. Внесем список группы. Метод OnInitDialog: BOOL CListsDlg::OnInitDialog() { CDialog::OnInitDialog(); m_list.AddString("Васильев Н.И."); … m_list.AddString("Яковлева Е.А."); … }
Замечание: Если сортировка не нужна, то сделать клик правой кнопкой мыши в редакторе диалоговых окон и вызвать свойства списка Properties→Sort = false. Обработка двойных кликов в списках
При двойном клике заносится текст выбранной строки в текстовое поле. Создадим переменную m_text типа CString, связанную с текстовым полем, и обработчик OnDblclkList1(). Для определения выбранной строки будем использовать метод GetCurSel(). Это метод класса CListBox. Он возвращает индекс строки, которую пользователь дважды кликнул левой кнопкой мыши. Содержимиое строки получим при помощи метода GetText(…) класса CListBox. void CListsDlg::OnDblclkList1() { m_list.GetText(m_list.GetCurSel(),m_text); UpdateData(false); }
Скомпилируем и запустим программу
73
Комбинированные поля
Создадим программу Combos на основе диалогового окна. Создадим комбинированное поле и текстовое поле. Добавим переменные m_combo типа CComboBox, связанную с элементом управления комбинированное поле, и m_text типа CString, связанную с текстовым полем. Комбинированные поля инициализируются в методе OnInitDialog(): BOOL CCombosDlg::OnInitDialogs() { CDialog::OnInitDialog(); m_combo.AddString("Васильев Н.И."); … m_combo.AddString("Яковлева Е.А."); m_combo.SetCurSel(0); //Выбор первой строки списка … }
Программа должна сообщать, какую строку списка выбрал пользователь. Комбинированное поле генерирует событие CBN_SELCHANGE. Добавим обработчик этого события в класс диалогового окна. Properties (Класс диалогового окна)→Events→IDC_COMBO1→CBN_SELCHANGE→OnSelchangeCombo1() Обработчик OnSelchangeCombo1: void CComboDlg::OnSelchangeCombo1() { m_combo.GetLBText(m_combo.GetCurSel(),m_text); UpdateData(false); }
Прокрутка и использование бегунков
Создадим программу Slider на базе диалогового окна. Добавим текстовое поле, две надписи и бегунок (ему присвотся идентификатор IDC_SLIDER1) и, связанную с ним, переменную m_slider типа CSliderCtrl. При инициализации бегунка необходимо задать его интервал. Эта величина определяет возможные позиции бегунка от крайнего левого до крайнего правого положения. В примере бегунок принимает значения от 1 до 100. Эти значения задаются методами SetRangeMin(…) и SetRangeMax(…) класса CSliderCtrl. Второй параметр этих методов показывает, нужно ли перерисовывать ползунок после изменения интервала. Передавая значение false, мы отказываемся от перерисовки. Метод OnInitDialog: BOOL {
CSliderDlg::OnInitDialog()
CDialog::OnInitDialog(); m_slider.SetRangeMin(1, false); m_slider.SetRangeMax(100, false); m_text="1"; //Связываем переменную с содержимым текстового поля и присваем значение 1 UpdateData(false); … }
74
Обработка сообщений бегунка
При перемещении бегунка элемент посылает сообщение WM_HSCROLL. Добавим его обработчик. Нам необходимо перехватывать сообщения с кодом SB_THUMBPOSITION, посылаемые при перемещение бегунка. Выведем в текстовом поле его новую позицию. Создадим переменную m_text, связанную с содержимым текстового поля, и присвоим ей значение параметра nPos обработчика OnHScroll(). Объект m_text относится к классу CString, а параметр nPos имеет целый тип. Для представления целого числа в виде текстовой строки воспользуется методом Format() класса CString. Обработчик OnHScroll: void CSliderDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { if(nSBCode == SB_THUMBPOSITION) { m_text.Format("%ld",nPos); UpdateData(false); } else CDialog::OnHScroll(nSBCode, nPos, pScrollBar); }
Скомпилируем и запустим приложение.
§5. Сериализация. Работа с файлами Сериализация – процесс записи (чтения) объектовов и данных на диск (с диска). Сериализация объектов класса CString
Рассмотрим сериализацию как встроенных классов Visual C++ (например, CString), так и нестандартных. Если в программе отсутствует документ, на который можно возложить выполнение файловых операций (программы на базе диалоговых окон), то работают с классом MFC CFile. Рассмотрим программу Writer, которая будет записывать на диск введенную строку, а затем, по требованию пользователя, загружать ее из файла. 3. Создатим SDI программу Writer. 4. Создадим объект CString StringData и инициализируем её. 5. Создадим обаботчик WM_CHAR OnChar(). Ввод данных и сохранение в строке: void CWriterView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { //TODO: CKeyDoc *pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData += nChar; }
75
Отобразим данные в OnDraw: pDC->TextOut(0,0,pDoc->StringData);
Класс документа (файл WriterDoc.cpp) содержит встроенный метод Serialize(CArchive& ar). В этом методе происходит сериализация объекта StringData. Методу Serialize(CArchive& ar) передается ссылка на объект ar класса CArchive. Работа с объектом ar практически не отличается от работы с потоками cout и cin. if(ar.IsStoring()) ar<<StringData; else ar>>StringData;
Чтобы сообщить приложению об изменении данных (заносим новый символ), вызовем в OnChar(…) метод объекта документа SetModifiedFlag(). Если будет сделана попытка выхода из программы без сохранения данных, то приложение выведет диалоговое окно с предложением сохранить данные. Добавим строку в метод OnChar(…): pDoc->SetModifiedFlag();
Скомпилируем и запустим программу. При создании нового документа следует стереть старое содержимое StringData и обновить вид программы новыми данными документа. Метод OnNewDocument: BOOL CWriterDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here // (SDI documents will reuse this document) StringData=""; UpdateAllViews(NULL); return TRUE; }
Сериализация нестандартных объектов
Создадим пользовательский класс и организуем сериализацию его объектов. Пусть класс содержит строковую переменную (типа CString). Кроме этого в классе должен быть реализован конструктор, три метода для работы со строками: AddText() – добавление текста в конец строки, DrawText() – вывод текста в констексте устройства и ClearText() – очистка (стирание содержимого) строки . Алгоритм организации сериализации нестандартных объектов
1. Создать SDI программу Serializer. 2. Добавить в проект заголовочный файл. 3. Включить в этот файл описание класса (наследуем его от CObject) – члены и методы класса. 4. Включить в заголовочный файл документа ссылку на новый файл.
76
5. Создать объект класса. 6. Использовать новый объект (его методы) в приложении. 7. Включить в определение класса макрос Visual C++ DECLARE_SERIAL, объявляющий методы, используемые в процессе сериализации. 8. Переопределить метод Serialize() класса CObject. 9. Для написания новой версии Serialize(), добавить в проект новый файл .cpp и определить в нем метод Serialize(). Реализуем пользовательский класс CData, наследуемый от CObject. Для этого создадим заголовочный файл Data.h и включим его в проект. class CData: public CObject { private: CString data; DECLARE_SERIAL(CData); public: CData(){data=CString("");} void AddText(CString text){data+=text;} void DrawText(CDC* pDC) {pDC->TextOut(0, 0, data);} void ClearText(){data="";} void Serialize(CArchive& archive); };
Подключим Data.h в файл SerializerDoc.h, для этого добавим в начало файла строку: #include "Data.h"
В классе документа введём переменную типа CData … public: CData DataObject; …
Добавим в документ вида метод OnChar(…) и используем методы объекта DataObject. Обработчик OnChar: void CSerializerView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default CSerializerDoc* pDoc=GetDocument(); ASSERT_VALID(pDoc); pDoc->DataObject.AddText(CString(nChar)); Invalidate(); CView::OnChar(nChar, nRepCnt, nFlags); }
Метод OnDraw: void CSerializerView::OnDraw(CDC* pDC) { CSerializerDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->DataObject.DrawText(pDC); }
Добавим в проект новый файл Data.cpp и определим Serialize(CArchive &archive)
77
Файл Data.cpp: #include "stdafx.h" #include "SerializerDoc.h" void CData::Serialize(CArchive& archive) { CObject::Serialize(archive); // Вызов метода Serialize() базового класса (CObject) if(archive.IsStoring())archive<>data; } IMPLEMENT_SERIAL(CData, CObject, 0)
Макрос IMPLEMENT_SERIAL содержит дополнительные методы, ипользуемые Visual C++ для сериализации. Чтобы выполнить сериализацию для объекта DataObject класса CData, следует вызвать его метод Serialize(…) внутри метода Serialize(…) документа. Метод Serialize класса документа: void CSerializerDoc::Serialize(CArchive& ar) { DataObject.Serialize(ar); // if (ar.IsStoring()) //{ // TODO: add storing code here //} //else //{ // TODO: add loading code here //} }
Скомпилируем и запустим программу. Работа с файлами. Класс CFile
Некоторые методы класса CFile: Метод Abort Close GetLength GetPosition Open Read Remove Rename Seek SeekToBegin SeekToEnd SetLength Write
Назначение Закрывает файл, игнорируя любые предупреждения и ошибки Закрывает файл и удаляет объект Получает длину файла Получает текущую позицию файлового указателя Производит открытие файла с возможностью проверки ошибок Читает данные из файла с текущей позиции Удаляет заданный файл Переименовывает заданный файл Перемещает файловый указатель в заданную позицию Перемещает файловый указатель в начало файла Перемещает файловый указатель в конец файла Изменяет длину файла Записывает данные в файл с текущей позиции 78
Режимы открытия файлов в конструкторе класса CFile: Константа CFile::modeCreate CFile::modeNoTruncate CFile::modeRead CFile::modeReadWrite CFile::modeWrite CFile::typeBinary CFile::typeText
Назначение создает новый файл Комбинируеся с modeCreate – если создаваемый файл уже существует, он не обрезается до нулевой длины Открывает файл толоько для чтения Открывает файл для чтения – записи Открывает файл только для записи Устанавливает двоичный режим Устанавливает текстовый режим со специальной обработкой пар символов конца/перевода строки
Задача: Написать программу, которая на базе диалогового окна по нажатию кнопки записывает в заданный файл на диске некоторый текст и считывает этот текст в тектовое поле в обратном порядке.
Создадим на базе диалогового окна программу Filer. Создадим 4 текстовых записи, длина каждой записи 20 символов. Реализуем эти записи в программе как массив из 4-х символьных строк OutString. Добавление необходимых переменных в класс CFilerDlg: … protected: HICON m_hIcon; char OutString[4][20]; …
Добавим в диалоговое окно необходимые управляющие элементы – два текстовых поля и кнопку. Для кнопки создадим функцию обработчик, создадим также две переменные m_text1 и m_text2 и свяжем их с текстовыми полями. Имя кнопки: Write and Read. Функция OnInitDialog (инициализация данных): BOOL CFilerDlg::OnInitDialog() { CDialog::OnInitDialog(); strcpy(OutString[0], "Иллюстрация "); strcpy(OutString[1], "работы "); strcpy(OutString[2], "с "); strcpy(OutString[3], "файлами "); m_text1 = CString(OutString[0]) + CString(OutString[1]) + CString(OutString[2]) + CString(OutString[3]); UpdateData(false); }
Для записи данных в файл будем использовать класс CFile. При нажатии на кнопку будет создаваться и открываться для записи файл data.txt. Запись будем производить с помощью метода Write(…), для чтения используем методы Read(…) и Seek(…) класса CFile. void CFilerDlg::OnButton1() { UpdateData(true);
79
CFile OutFile("data.txt",CFile::modeCreate | CFile::modeWrite); //Создание обекта OutFile.Write(m_text1.GetBuffer(), m_text1.GetLength()); //Запись данных в файл OutFile.Close(); //Закрытие файла CFile InFile("data.txt",CFile::modeRead); ULONGLONG pos = InFile.SeekToEnd(); while(pos != CFile::begin) { char buf; InFile.Seek(--pos,CFile::begin); int n = InFile.Read(&buf,1); m_text2+=CString(buf); } UpdateData(false); InFile.Close(); }
§6. Графика. Работа с растровыми изображениями (фракталы) Алгебраические фракталы
Создадим программу Fractal для построения алгебраических фракталов и рассмотрим на её примере работу с растровыми изображениями на основе класса CImage. Алгебраические фракталы строятся на основе алгебраических формул, при этом по алгоритму производится последовательность преобразований исходных данных, и результаты очередного шага зависят от предыдущего. Будем строить фрактальное множество, заданное формулой:
z n +1 = z nzn + z n3 + c , где z n , c – комплексные числа, а n – номер текущей итерации, z 0 = 0 . Для того чтобы получить изображение (фрактал), надо произвести определенное количество итераций для точки c комплексной плоскости. Алгоритм
1. Все точки c последовательно перебираются и закрашиваются разными цветами в зависимости от номера итерации при выполнении следующих условий: 2. z n ≥ 2 и n достигает заданного предела. Можно установить определенные цвета для закрашивания (в программе 16 цветов). Процесс продолжается до тех пор, пока не будут перебраны все точки. 3. Для того чтобы определить цвет каждого пиксела во множестве, необходимо выполнить ряд действий: • Задать значение мнимой части параметра c равным максимальному значению по мнимой оси, а значение действительной части параметра c – равной минимальному значению по действительной оси. • Выбрать текущий столбец пикселов. • Для каждой строки текущего столбца пикселов увеличить счетчик итераций и проверить, не превышает ли модуль комплексного числа на данной итерации значение 2 и не достигнуто ли предельное количество итераций. • Если хотя бы одно из этих условий не выполняется, нарисовать точку с текущими координатами, задаваемыми номерами столбца и строки, окрашенную цветом, зависящим от номера текущей итерации.
80
• •
Перейти к следующей точке столбца, уменьшив значение мнимой части c . Перейти к следующему столбцу, увеличив значение действительной части.
Создание приложения
Напишем программу, реализующую данный алгоритм. Создадим SDI приложение без поддержки архитектуры «документ вид» (для этого необходимо снять соответствующий флажок во втором окне мастера MFC Application Wizard). Добавим в главное меню пункт Save для сохранения полученного фрактала. Объявление необходимых переменных в прототипе класса CChildView (заголовочный файл ChildView.h): public: CImage imgOriginal; //Объект класса CImage CRect m_ClientRect; //Переменная для хранения размеров клиентской области double m_LeftBound; double m_RightBound; double m_TopBound; double m_BottomBound; COLORREF m_ColorTable[16]; //Таблица цветов const unsigned int m_MaxIter; //Константа - максимальное число итерраций
Проведём необходимую инициализацию в конструкторе (исходный файл ChildView.cpp): CChildView::CChildView(): m_MaxIter(256), m_RightBound(0), m_LeftBound(-1.5), m_TopBound(0.5), m_BottomBound(-0.5) { //Создание таблицы цветов m_ColorTable[0] = RGB(128,0,0); m_ColorTable[1] = RGB(255,0,0); m_ColorTable[2] = RGB(255,128,128); m_ColorTable[3] = RGB(255,128,192); m_ColorTable[4] = RGB(255,0,255); m_ColorTable[5] = RGB(128,0,255); m_ColorTable[6] = RGB(0,0,255); m_ColorTable[7] = RGB(0,0,128); m_ColorTable[8] = RGB(0,128,128); m_ColorTable[9] = RGB(0,255,0); m_ColorTable[10] = RGB(128,255,128); m_ColorTable[11] = RGB(255,255,128); m_ColorTable[12] = RGB(255,255,0); m_ColorTable[13] = RGB(255,128,0); m_ColorTable[14] = RGB(255,128,64); m_ColorTable[15] = RGB(128,64,0); }
Добавим в класс CChildView с помощью мастера обработчик изменения размеров окна OnSize. Обработчик OnSize: void CChildView::OnSize(UINT nType, int cx, int cy) { GetClientRect(&m_ClientRect); }
81
Добавим в класс CChildView функцию для построения фрактала. В прототип класса (файл CChildView.h) добавим строку: public: void BuildFractal();
Определим её в файле ChildView.cpp Функция BuildFractal: void CChildView::BuildFractal() { imgOriginal.Destroy(); imgOriginal.Create(m_ClientRect.right,m_ClientRect.bottom,24); double f_Dx = (m_RightBound - m_LeftBound)/m_ClientRect.right; double f_Dy = (m_TopBound - m_BottomBound)/m_ClientRect.bottom; Complex c0(m_LeftBound,m_TopBound); for(int i = 0; i < m_ClientRect.right; i++) { for(int j = 0; j < m_ClientRect.bottom; j++) { Complex z0(0,0); int n_Iter = 0; while(n_Iter < m_MaxIter && z0.Abs() < 4) { z0 = pow(z0,z0) + z0*z0*z0 + c0; n_Iter++; } imgOriginal.SetPixel(i,j,m_ColorTable[n_Iter%16]); c0 -= Complex(0,f_Dy); } c0 = Complex(c0.GetRe(),m_TopBound); c0 += f_Dx; } }
В данной функции используются методы класса CImage: Destroy() – для удаления растрового изображения из объекта, Create(int nWidth, int nHeight, int nBPP) – для создания растрового изображения с размерами nWidth и nHeight и глубиной цвета nBPP, SetPixel(int x, int y, COLORREF color) – для закрашивания одного пикселя изображения. Также используется класс Complex, его полный исходный код приведён в приложении. Чтобы его использовать, нужно добавить в программу с помощью утилиты Solution Explorer файлы Complex.h и Complex.cpp в проект и добавить в файл ChildView.cpp следующую строку: #include "Complex.h"
Построение и вывод изображения на экран будем производить по двойному клику левой кнопкой мыши. Обработчик OnLButtonDblClick (файл CChildView.cpp): void CChildView::OnLButtonDblClk(UINT nFlags, CPoint point) { BuildFractal(); Invalidate(); CWnd::OnLButtonDblClk(nFlags, point); }
82
Обработчик OnPaint (файл CChildView.cpp): void CChildView::OnPaint() { CPaintDC dc(this); // device context for painting if(!imgOriginal.IsNull()) imgOriginal.StretchBlt(dc,0,0,imgOriginal.GetWidth(),imgOriginal.GetHeight (),SRCCOPY); }
Функция StretchBlt копирует изображение объекта класса CImage в контекст устройства, переданный ей в качестве параметра. Чтобы сохранить полученное изображение, обработаем пункт меню Save. Обработчик OnFileSave: void CChildView::OnFileSave() { CString strFilter; HRESULT hResult; //Строка с поддерживаемыми расширениями strFilter = "Bitmap image|*.bmp|JPEG image|*.jpg|GIF image|*.gif|PNG image|*.png||"; //Создание стандартного диалога сохранения CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter); hResult = (int)dlg.DoModal(); //Вызов диалога сохранения if (FAILED(hResult)) return; //Создание строки с путем и полным именем файла, в который будет производиться сохранение CString strFileName; CString strExtension; strFileName = dlg.m_ofn.lpstrFile; if (dlg.m_ofn.nFileExtension == 0) { switch (dlg.m_ofn.nFilterIndex) { case 1 : strExtension = "bmp"; break; case 2 : strExtension = "jpg"; break; case 3 : strExtension = "gif"; break; case 4 : strExtension = "png"; break; default : break; } strFileName = strFileName + '.' + strExtension; } //Сохранение hResult = imgOriginal.Save(strFileName); if (FAILED(hResult)) { CString fmt; fmt.Format("Save image failed:\n%x", hResult); ::AfxMessageBox(fmt); return; } }
Для сохранения изображения используется метод класса CImage: Save, которому необходимо передать строку с путём и именем файла.
83
Для использования класса CImage и математических формул, необходимо добавить в файл stdafx.h строки: #include #include
Программа готова. Скомпилируем и запустим. В результате должно получиться следующее изображение:
Глава 3. Разработка полноценных Windows-приложений §1. Приложение «Интерполяционный полином» В данном параграфе на примере написания приложения «Интерполяционный полином» рассматриваются следующие вопросы: графический вывод, работа с курсором и мышью, работа с модальными диалоговыми окнами, работа с растровыми изображениями, работа с панелями управления и строками состояния, работа с полиномами. Постановка задачи. Теоретический материал
Пусть у нас есть следующая таблица:
X x1 ... x n Y y1 ... y n где {x1 ,..., x n , y1 ,..., y n } ⊂ R
Требуется построить полином y = f ( x) , принимающий значения согласно таблице. Интерполяционный полином – полином, принимающий значения y i в точках xi согласно
указанной таблице. Значения x1 ,..., x n называются узлами интерполяции.
84
Согласно теореме высшей алгебры, если все узлы xi различны, то существует полином
f ( x) ⊂ R[x ], deg f ≤ n − 1 такой, что f ( x1 ) = y1 ,..., f ( x n ) = y n . Существуют разные алгоритмы
построения интерполяционного полинома, мы воспользуемся методом Лагранжа. Данный метод даёт аналитическое представление интерполяционного полинома в следующем виде: n
y j ⋅ Wn ( x )
j =1
( x − x j ) ⋅ Wn/ ( x j )
f ( x) ≡ ∑
где Wn ( x) = ( x − x1 ) ⋅ ... ⋅ ( x − x n )
Для построения интерполяционного полинома напишем Windows приложение с графическим интерфейсом, удовлетворяющим следующим требованиям: 1. должна быть реализована возможность ввода узлов интерполирования двумя способами: с клавиатуры, посредством мыши; 2. должен быть реализован вывод аналитической формулы и графика полинома на экран, сохранение изображения графика в файл; 3. должна быть реализована возможность параллельного переноса центра системы координат и её масштабирование. Согласно требованиям, наша программа должна осуществлять полноценный графический вывод информации на экран. При этом возникает проблема аппаратно-независимого вывода изображения. Каждое Windows окно имеет собственную стандартную (аппаратную) систему координат. Стандартная (аппаратная) система координат – левая система координат с центром в левом верхнем углу окна. Ось X направлена вправо, а ось Y вниз. Единица измерения – 1 пиксель.
Стандартные координаты аппаратно зависимы. А это значит, что на разных мониторах и принтерах изображения размером n×n пикселей будут иметь различные размеры, в зависимости от размеров пикселей поддерживаемых устройством вывода. Аппаратная зависимость координат также не позволяет выводить на экран изображения, размеры которых превосходят размеры клиентской области окна. Для преодоления аппаратной зависимости необходимо выполнить преобразование координат. Принципы построения собственных функций преобразования координат
Чтобы реализовать аппаратно независимый вывод графики, можно ввести 2 логические системы координат: видимую и невидимую и организовать переход от одной к другой. Видимая система координат – основная, она и определяет логические координаты точек. Невидимая система координат – вводится для реализации точки наблюдения.
85
Геометрический смысл преобразования. Переход от стандартной системы координат к видимой логической выполняется для преодоления проблемы несоответствия размеров пикселей на разных устройствах. А переход от видимой логической системы координат к невидимой системе координат необходим, если требуется отобразить ту часть изображения, которая не помещается в окне. Математическая реализация. Пусть ( ~ x, ~ y ) координаты точки в невидимой логической
системе координат, а ( x, y ) – координаты той же самой точки в стандартной системе координат. Пусть (a, b) – координаты центра видимой логической системы координат в стандартной системе координат, а (c, d ) – координаты точки наблюдения в видимой логической системе координат. Тогда формула для выполнения нужного перехода: x ⎞ ⎛ c ⎞⎞ ⎛ x⎞ ⎛a⎞ ⎛1 0 ⎞ ⎛⎛ ~ ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ + S * ⎜⎜ ⎟⎟ * ⎜⎜ ⎜⎜ ~ ⎟⎟ + ⎜⎜ ⎟⎟ ⎟⎟ ⎝ y⎠ ⎝b⎠ ⎝ 0 − 1⎠ ⎝ ⎝ y ⎠ ⎝ d ⎠ ⎠
x⎞ ⎛c⎞ ⎛~ Где ⎜⎜ ~ ⎟⎟ + ⎜⎜ ⎟⎟ - логические координаты в видимой системе. ⎝ y ⎠ ⎝d ⎠ S - коэффициент масштабирования (он выбирается в зависимости от характеристик монитора). Теперь, пользуясь этой формулой, можно, зная логические координаты, переходить к системным координатам, что необходимо для рисования. А так же, изменяя координаты точки наблюдения, отображать невидимые ранее части образа.
Нетрудно получить и обратное выражение логических координат через системные координаты. Это нужно, чтобы выводить информацию о положении на плоскости. Проектирование и разработка приложения Этапы разработки
1. Написать функции перехода от логических координат к системным координатам и наоборот.
86
2. Реализовать операции параллельного переноса центра логической системы координат и масштабирования. 3. Организовать ввод начальных данных. 4. Построить интерполяционный полином по введённым данным. 5. Организовать вывод результатов. Создадим с помощью мастера MFC стандартное SDI приложение, с поддержкой архитектуры «документ-вид». Отключим поддержку символов Unicode. 1. Функции перехода от логических координат к системным координатам и наоборот
Логические координаты на плоскости представляют собой упорядоченную пару вещественных чисел. Для их программной реализации следует ввести структуру SDPoint. Вынесем её в новый заголовочный файл (например, DoublePoint.h) struct SDPoint { double x; double y; };
Далее приведён полный исходный код класса вида CGpView программы. Пояснения ко всем введённым переменным, функциям и обработчикам приведены в пунктах, при реализации которых использовались эти элементы. В рамках данного пункта рассмотрим функции перехода от системных координат к логическим координатам, выделенные жирным шрифтом. Прототип класса вида: // GpView.h : interface of the CGpView class #pragma once class CGpView : public CView { protected: // create from serialization only CGpView(); DECLARE_DYNCREATE(CGpView) // Attributes public: CGpDoc* GetDocument() const; // Operations public: // Overrides public: virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: // Implementation public: virtual ~CGpView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const;
87
#endif protected: // Generated message map functions protected: DECLARE_MESSAGE_MAP() private: //Добавляем необходимые переменные int ScaleXY; //Масштабный коэффициент (сколько пикселей в логической единице) SDPoint CameraPoint; //Логические координаты точки наблюдения bool parallel_shift; //флаг для входа(выхода) в(из) режим(а) параллельного переноса bool move_camera; //флаг для активации параллельного переноса CPoint MousePosPoint; //Системные координаты курсора мыши CFont axisFont; //Шрифт для подписи осей CPen polPen; //Перо для рисования графика полинома bool mark_points; //Флаг для входа(выхода) в(из) режим(а) визуального редактирования узлов интерполирования CImage imgOriginal; //Переменная для сохранения изображения клиентской области окна (графика) public: //Необходимые функции SDPoint SysToLog(CPoint SysPoint); //Функция перехода от системных к логическим координатам CPoint LogToSys(double xl, double yl); //Функция перехода от логических коодинат к системным void DrawAxis(CDC* pDC); //Функция для прорисовки осей void DrawPolinom(CDC* pDC); //Функция для прорисовки полинома void DrawInitPoints(CDC* pDC); //Функция для прорисовки узлов интерполяции public: //Обработчики (добавлены с помощью мастера) afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnToolsParallelshift(); afx_msg void OnUpdateToolsParallelshift(CCmdUI *pCmdUI); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); afx_msg void OnToolsAddpoint(); afx_msg void OnToolsMarkinitialpoints(); afx_msg void OnUpdateToolsMarkinitialpoints(CCmdUI *pCmdUI); afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); afx_msg void OnToolsShowpolinomialkoefficients(); afx_msg void OnUpdateToolsShowpolinomialkoefficients(CCmdUI *pCmdUI); afx_msg void OnFileSaveAs(); }; #ifndef _DEBUG // debug version in GpView.cpp inline CGpDoc* CGpView::GetDocument() const { return reinterpret_cast(m_pDocument); } #endif
Функция перехода от системных координат к логическим координатам: SDPoint CGpView::SysToLog(CPoint SysPoint) { CRect cr; GetClientRect(&cr); SDPoint Res;
88
Res.x = (static_cast<double>(SysPoint.x) static_cast<double>(cr.right)/2)/ScaleXY - CameraPoint.x; Res.y = (static_cast<double>(cr.bottom)/2 static_cast<double>(SysPoint.y))/ScaleXY - CameraPoint.y; return Res; }
Функция перехода от логических координат к системным координатам: CPoint CGpView::LogToSys(double xl, double yl) { CRect cr; GetClientRect(&cr); CPoint Res; Res.x = static_cast((xl + CameraPoint.x)*ScaleXY + cr.right/2); Res.y = static_cast(-(yl + CameraPoint.y)*ScaleXY + cr.bottom/2); return Res; }
CameraPoint – переменная для хранения координат точки наблюдения типа SDPoint. Функция GetClientRect(&cr) используется для получения клиентского прямоугольника. ScaleXY – переменная типа int, которая хранит масштаб (число пикселей в логической единице). Ее значение может быть задано произвольно, а может быть связано с характеристиками монитора с помощью соответствующих функций (например, int GetDeviceCaps(HDC hdc, int nIndex)). Это основные функции, которые обеспечивают правильную работу программы в дальнейшем. 2. Параллельный перенос и масштабирование плоскости
Создадим пункт меню Tools (инструменты). В нём создадим подпункт Parallel Shift (параллельный перенос) и связанную с ним кнопку на панели инструментов, которые переводят программу в режим ожидания параллельного переноса или выводят её из него. Если программа находится в режиме ожидания параллельного переноса, то нажатие левой кнопки активирует его и программа начинает обрабатывать событие WM_MOUSEMOVE (движение мыши). В соответствии с перемещением курсора, перемещается точка наблюдения. Отпускание левой кнопки мыши деактивирует перенос и возвращает программу в режим ожидания. В данном пункте необходимо добавить обработчики движения мыши, нажатия левой кнопки мыши и её отпускания. Обработчик движения мыши (важные для этого пункта строки выделены жирным шрифтом): void CGpView::OnMouseMove(UINT nFlags, CPoint point) { CRect cr; //Переменная для хранения размеров клиентского прямоугольника CString str; //Строка для записи и вывода координат курсора GetClientRect(&cr); //Получение размеров клиентского прямоугольника CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Указатель на основную рамку (необходимо для обращения к строке состояния) CStatusBar *pStatus = &pFrame->m_wndStatusBar; //Указатель на строку состояния (будет отображать координаты курсора) if(cr.PtInRect(point)) //Если курсор в клиентском прямоугольнике то... {
89
SetCapture(); //Захватываем мышь if(parallel_shift) //Если в режиме ожидания параллельного переноса то... if(move_camera) //Если перенос актвирован { //Сдвигается точка наблюдения на разность между текущим и предыдущим положениями курсора CameraPoint.x += (static_cast<double>(point.x) static_cast<double>(MousePosPoint.x))/ScaleXY; CameraPoint.y -= (static_cast<double>(point.y) static_cast<double>(MousePosPoint.y))/ScaleXY; Invalidate(); //Обновление клиентсой области MousePosPoint = point; //Сохранение положения курсора } else SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Если перенос не активирован то устанавливается нужный курсор if(mark_points) SetCursor(LoadCursor(NULL,IDC_CROSS)); //Если в режиме визуального редактирования то устанавливается соответствующий курсор } else //Если курсор вне клиентского прямоугольника то... { ReleaseCapture(); //Освобождается мышь move_camera = false; //Деактивируется перенос } if(pStatus) //Если указатель на строку состояния верный то... { str.Format("x = %f", SysToLog(point).x); //Запись во вспомогательную строку координаты x pStatus->SetPaneText(0,str); //Вывод в строку состояния str.Format("y = %f", SysToLog(point).y); //Запись во вспомогательную строку координаты y pStatus->SetPaneText(1,str); //Вывод в строку соостояния } CView::OnMouseMove(nFlags, point); }
MousePosPoint – это переменная типа CPoint, которая служит для хранения координат курсора и объявлена в прототипе класса CGpView с модификатором private. С тем же модификатором объявлены переменные parallel_shift и move_camera типа bool. Первая переменная сигнализирует, нажата ли кнопка параллельного переноса, а вторая – активирован ли перенос (нажата или отпущена левая кнопка мыши). Функция SetCapture() перенаправляет все сообщения мыши на обработку окну, которое её вызвало. Функция ReleaseCapture() восстанавливает стандартный путь сообщений мыши в операционной системе. Функция SetCursor(...) меняет изображение курсора. Функция LoadCursorA(...), член класса приложения, загружает курсор из ресурсов приложения, вызвавшего её, и возвращает его описатель, в качестве входного параметра требует его идентификатор. Функция AfxGetApp() возвращает указатель на экземпляр приложения. Обработчик нажатия левой кнопки мыши: void CGpView::OnLButtonDown(UINT nFlags, CPoint point) левой кнопки мыши {
//Обработчик нажатия
90
CRect cr; //Переменная для хранения размеров клиентского прямоугольника GetClientRect(&cr); //Получение размеров клиентского прямоугольника if(parallel_shift && cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник... { MousePosPoint = point; //Сохранение координат нажатия move_camera = true; //Активация переноса SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_FIST)); //Устанавливается соответствующий курсор } CView::OnLButtonDown(nFlags, point); }
Обработчик отпускания левой кнопки мыши: void CGpView::OnLButtonUp(UINT nFlags, CPoint point) //Обработчик отпускания левой кнопки мыши { CRect cr; //Переменная для хранения размеров клиентского прямоугольника GetClientRect(&cr); //Получение размеров клиентского прямоугольника if(parallel_shift && cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник ... { move_camera = false; //Деактивация переноса SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Устанавливается соответствующий курсор } CView::OnLButtonUp(nFlags, point); }
Для корректной работы программы создадим и отредактируем соответствующие обработчики для кнопки Parallel Shift. В рамках данного пункта важны строки, выделенные жирным шрифтом. Обработчик пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов: void CGpView::OnToolsParallelshift() { mark_points = false; //Если в режиме визуального редактирования узлов то выход из него parallel_shift = !parallel_shift; //Вход или выход в режим ожидания переноса }
Обработчик изменения внешнего вида пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов: void CGpView::OnUpdateToolsParallelshift(CCmdUI *pCmdUI) //Обрабатывает изменение внешнего вида кнопки (пункта меню) { pCmdUI->SetCheck(parallel_shift); //Смена статуса (активна или не активна) в зависимости от флага }
91
Для масштабирования следует обрабатывать прокрутку колеса мыши. В зависимости от направления вращения будет меняться масштабный коэффициент. Обработчик колёсика мыши: BOOL CGpView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { CString str("Updating coordinates. Please move cursor."); //Информативная строка CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Получение указатель на основную рамку CStatusBar *pStatus = &pFrame->m_wndStatusBar; //Получение указатель на строку состояния if(zDelta > 0) {if(ScaleXY < 200) ScaleXY += 5;} //Если вращение вперёд, то увеличивается массштабный коэффициент else if(ScaleXY > 40) ScaleXY -= 5; //Если вращение назад, то уменьшается масштабный коэффициент Invalidate(); //Перерисовываем if(pStatus) //Если указатель на строку состояния истина, то { //Вывод информативной строки в строку состояния pStatus->SetPaneText(0,str); pStatus->SetPaneText(1,str); } return CView::OnMouseWheel(nFlags, zDelta, pt); }
3. Ввод начальных данных
Рассмотрим два способа ввода начальных данных: ввод точных данных с клавиатуры и визуальное редактирование таблицы узлов посредством мыши. Первый подход реализован с помощью создания и подключения к приложению диалогового окна с необходимыми элементами управления, а второй с помощью обработки двойного нажатия левой кнопки мыши. Также необходимо организовать хранение информации об узлах. Ввод данных с клавиатуры. Создадим ресурс диалогового окна с помощью редактора ресурсов. В диалоговом окне должны присутствовать 2 текстовых поля ввода (координаты x и y), 4 кнопки (Add, Delete, Ok, Cancel) и список для отображения таблицы узлов. После создания ресурса, добавим класс диалогового окна на его базе. Создадим подпункт меню Add/Delete Initial Points в пункте Tools и аналогичную кнопку на панели управления, при нажатии на которые будет появляться диалоговое окно. Прототип класса диалогового окна для ввода узлов: #pragma once #include "afxwin.h" // CAddPointDlg dialog class CAddPointDlg : public CDialog { DECLARE_DYNAMIC(CAddPointDlg) public: CAddPointDlg(CWnd* pParent = NULL); virtual ~CAddPointDlg();
// standard constructor
92
// Dialog Data enum { IDD = IDD_ADDPOINT_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support DECLARE_MESSAGE_MAP() public: //переменные связанные с элементами управления добавлены с помощью мастера CListBox PointList; //Переменная связанная со списком double ValX; //Переменная связанная с первым текстовым полем double ValY; //Переменная связанная со вторым текстовым полем vector<SDPoint> ListedPointsVec; //Вектор узлов интерполирования (добавлена вручную) public: //Обработчики и переопределённые виртуальные функции добавлены с помощью мастера afx_msg void OnBnClickedButtonAdd(); //Обработчик кнопки Add virtual BOOL OnInitDialog(); afx_msg void OnBnClickedButtonDelete(); //Обработчик кнопки Delete };
Обработчик кнопки Add на диалоговом окне: void CAddPointDlg::OnBnClickedButtonAdd() { CString str; //Вспомогательная строка для вывода узла в список SDPoint temp; //Вспомогательная точка UpdateData(true); //Обмен данными temp.x = ValX; //Инициализация вспомогательной точки temp.y = ValY; for(size_t i = 0; i < ListedPointsVec.size(); i++) //Перебор всех узлов интерполяции if(ListedPointsVec[i].x == ValX && ListedPointsVec[i].y == ValY) //Если один из узлов совпал с вводимым то... return; //выход из функции ListedPointsVec.push_back(temp); //Добавление нового узла в вектор (проверка сделана, одинаковых нет) str.Format("(%f;%f)",ValX,ValY); //Формирование строки для добавления в список PointList.AddString(str); //Добавление строки в конец списка }
Функция для инициализации переменных диалогового окна: BOOL CAddPointDlg::OnInitDialog() { CDialog::OnInitDialog(); CString str; //Вспомогательная строка для формирования списка for(size_t i = 0; i < ListedPointsVec.size(); i++) //Перебор по всему вектору узлов { str.Format("(%f;%f)",ListedPointsVec[i].x,ListedPointsVec[i].y); //Формирование строки PointList.AddString(str); //Добавление её в список } return TRUE; }
93
Обработчик кнопки Delete на диалоговом окне: void CAddPointDlg::OnBnClickedButtonDelete() { int n = PointList.GetCurSel(); //Получение номера выделенной строки списка if(n == LB_ERR) return; //Если ничего не выделено – выход if(n == ListedPointsVec.size()) //Если выделена последняя строка, то... ListedPointsVec.pop_back(); //удаление последнего элемента из таблицы else //иначе... { for(size_t i = n; i < ListedPointsVec.size()-1; i++) //перебор узлов начиная с n-ого и до конца { //Смещение узлов в векторе ListedPointsVec[i].x = ListedPointsVec[i+1].x; ListedPointsVec[i].y = ListedPointsVec[i+1].y; } ListedPointsVec.pop_back(); //Удаление последнего элемента } PointList.DeleteString(n); //Удаление ненужней строки списка }
Для корректной работы последней функции, в списке должна быть отключена сортировка. Когда диалоговый класс реализован, необходимо подключить его к программе. Для этого используем соответствующий пункт меню и кнопку на панели управления – Add/Delete Initial Points. Также необходимо передать введённые данные в основное хранилище информации (объект класса документа). Обработчик пункта меню Add/Delete Initial Points и связанной с ним кнопки на панели инструментов: void CGpView::OnToolsAddpoint() { CAddPointDlg dlg; //Создаём экземпляр класса диалогового //Стандартная процедура получения указателя на документ и его проверки на //корректность CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; pol_ready = false; //Сигнализируем о том что построенный полином устарел (сейчас будет меняться таблица) Invalidate(); //Перерисовываем dlg.ListedPointsVec = pDoc->InitPoints; //переписываем копию узлов в диалоговую переменную switch(dlg.DoModal()) //Ждём реультата работы диалога { case -1: AfxMessageBox("AddPoiintDialog creation error"); return; //Если диалог не запустился case IDOK: pDoc->InitPoints = dlg.ListedPointsVec; Invalidate(); break; //Если нажали Ok то переписываем новую таблицу в документ и перерисовываем
94
case IDCANCEL: break;
//Если Cancel ничего не делаем
} }
Этот обработчик осуществляет связь между механизмом изменения (диалоговое окно) узлов и механизмом их хранения (объект документа). Прежде чем переходить к реализации визуального редактирования, рассмотрим механизм хранения узлов и интерполяционного полинома. Объект документа полностью соответствует требованиям хранилища необходимых программе данных. Прототип класса документа: // GpDoc.h : interface of the CGpDoc class // #pragma once #include "TPolinom.h" class CGpDoc : public CDocument { protected: // create from serialization only CGpDoc(); DECLARE_DYNCREATE(CGpDoc) // Attributes public: // Operations public: // Overrides public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); // Implementation public: virtual ~CGpDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: DECLARE_MESSAGE_MAP() public: //Необходимые переменные vector<SDPoint> InitPoints; //Таблица узлов реализована с помощью вектора вещественных точек плоскости TPolinom<double> InterPol; //Интерполяционный полином bool pol_ready; //Флаг сигнализирующий о том что полином построен public: bool BuildPolinom(void); //Функция для построения полинома afx_msg void OnToolsBuilddrawpolinom(); };
Важным для данного пункта является объект InitPoints класса vector<SDPoint>. В него записываются и хранятся на протяжении работы программы узлы интерполирования.
95
Визуальное редактирование таблицы узлов. Создадим подпункт Mark initial points в меню и соответствующую кнопку, которые будут переводить программу в режим ожидания редактирования (двойного клика по левой кнопке мыши) и выводить из него. Добавим обработчик сообщения WM_LBTNDBLCLICK Обработчик нажатия пункта меню Mark initial points и связанной с ним кнопки на панели инструментов: void CGpView::OnToolsMarkinitialpoints() { parallel_shift = false; //Если в режиме ожидания переноса то выход из него mark_points = !mark_points; //Вход или выход в(из) режима визуального редактирования узлов }
Обработчик изменения внешнего вида пункта меню Mark initial points и связанной с ним кнопки на панели инструментов: void CGpView::OnUpdateToolsMarkinitialpoints(CCmdUI *pCmdUI)//Обрабатываем изменение внешнего вида кнопки (пункта меню) { pCmdUI->SetCheck(mark_points); //Смена статуса (активна или не активна) в зависимости от флага }
Обработчик двойного клика по левой кнопке мыши: void CGpView::OnLButtonDblClk(UINT nFlags, CPoint point) { if(mark_points) //Если в режиме визуального редактирования, то... { //Получение указателя на документ CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; SDPoint help = SysToLog(point); //Вспомогательная переменная для проверки (вводится ли уже существующий узел) for(size_t i = 0; i < pDoc->InitPoints.size(); i++) //Перебор по всем узлам и проверка... if(help.x == pDoc->InitPoints[i].x && help.y == pDoc>InitPoints[i].y) //Совпадает с вводимым хотя бы оди узел таблицы return; //Если да, то выход из функции без добавления вводимого узла в таблицу pDoc->InitPoints.push_back(help); //Добавление вводимого узла в таблицу (проверка сделана, повторов нет) Invalidate(); //Обновление клиентской области окна } CView::OnLButtonDblClk(nFlags, point); }
96
4. Построение интерполяционного полинома
За построение и хранение интерполяционного полинома отвечает объект класса документа. Интерполяционный полином является объектом класса TPolinom<double>. Это шаблонный класс, инкапсулирующий алгебраические полиномы. Этот класс вынесен в файл TPolinom.h, который необходимо скопировать в каталог с проектом и добавить в проект с помощью утилиты Solution Explorer (вызвать контекстное меню проекта в окне утилиты Solution Explorer→Add→Existing item… и далее указать имя файла). Полный код класса приведён в приложении. Функция, объявленная в классе документа bool BuildPolinom(void), строит полином по введенным узлам. Функция построения полинома: bool CGpDoc::BuildPolinom(void) { InterPol = 0; if(InitPoints.size() <= 1) { AfxMessageBox("Error. Number of points is not enough."); return false; } else { for(size_t i = 0; i < InitPoints.size(); i++) { TPolinom <double> Lk(1); for(size_t j = 0; j < InitPoints.size(); j++) { if(i == j) continue; else { double initTp[] = {-InitPoints[j].x,1}; TPolinom <double> Tp(2, initTp); Lk = Lk*Tp; } } if(Lk(InitPoints[i].x)) InterPol = InterPol + Lk*(InitPoints[i].y/Lk(InitPoints[i].x)); else { AfxMessageBox("Incorrect initial points. Error."); return false; } } } return true; }
Построение происходит при нажатии на пункт меню Build and draw polinom или на соответствующую кнопку на панели инструментов. Обработчик будет разобран в следующем пункте.
97
5. Вывод данных
Выведем в окно приложения график полинома (в клиентскую область окна), коэффициенты полинома (в отдельное диалоговое окно), координаты курсора мыши (в строку состояния). Также следует добавить возможность сохранения изображения клиентской области окна в файл. За вывод информации отвечает класс вида и класс основного окна рамки. До того как будет отображён график полинома, необходимо нарисовать оси системы координат и отметить узлы интерполирования на плоскости. За это отвечают функции void DrawAxis(CDC* pDC) и void DrawInitPoints(CDC* pDC); Функция для рисования осей координат: void CGpView::DrawAxis(CDC* pDC) { //Получаем клиентский прямоугольник CRect cr; GetClientRect(&cr); CFont *oldFont = pDC->SelectObject(&axisFont); //Загрузка в контекст усройства шрифта для подписи осей CPoint VisibleCenter = LogToSys(0,0); //Сохраняем системные координаты логического центра //Рисование оси Y pDC->MoveTo(VisibleCenter.x,cr.bottom); pDC->LineTo(VisibleCenter.x,0); //Рисование стрелки на конце pDC->LineTo(VisibleCenter.x-3,7); pDC->MoveTo(VisibleCenter.x,0); pDC->LineTo(VisibleCenter.x+3,7); //Рисование оси X pDC->MoveTo(0,VisibleCenter.y); pDC->LineTo(cr.right,VisibleCenter.y); //Рисование стрелки на конце pDC->LineTo(cr.right-7,VisibleCenter.y-3); pDC->MoveTo(cr.right,VisibleCenter.y); pDC->LineTo(cr.right-7,VisibleCenter.y+3); pDC->TextOutA(cr.right-7,VisibleCenter.y-19,"x",1); //Обозначение оси X pDC->TextOutA(VisibleCenter.x+7,0,"y",1); // Обозначение оси Y pDC->TextOutA(VisibleCenter.x+2,VisibleCenter.y-14,"0",1); // Обозначение начала координат //Разбиение в соответствии с масштабным коэффициентом for(int i = VisibleCenter.x; i < cr.right; i += ScaleXY) { pDC->MoveTo(i,VisibleCenter.y - 2); pDC->LineTo(i,VisibleCenter.y); } for(int i = VisibleCenter.x; i > 0; i -= ScaleXY) { pDC->MoveTo(i,VisibleCenter.y + 2); pDC->LineTo(i,VisibleCenter.y); } for(int i = VisibleCenter.y; i < cr.bottom; i += ScaleXY) {
98
pDC->MoveTo(VisibleCenter.x + 2,i); pDC->LineTo(VisibleCenter.x,i); } for(int i = VisibleCenter.y; i > 0; i -= ScaleXY) { pDC->MoveTo(VisibleCenter.x - 2,i); pDC->LineTo(VisibleCenter.x,i); } pDC->SelectObject(oldFont); //Возврат шрифта по умолчанию в контекст усройства }
Функция для рисования узлов интерполирования: void CGpView::DrawInitPoints(CDC* pDC) { //Получение указателя на документ CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; //Перебор всех узлов в векторе for(size_t i = 0; i < pDoc->InitPoints.size(); i++) { //Расстановка их на плоскости CPoint help = LogToSys(pDoc->InitPoints[i].x,pDoc->InitPoints[i].y); pDC->SetPixel(help,RGB(255,0,0)); pDC->SetPixel(help.x+1,help.y,RGB(255,0,0)); pDC->SetPixel(help.x-1,help.y,RGB(255,0,0)); pDC->SetPixel(help.x,help.y+1,RGB(255,0,0)); pDC->SetPixel(help.x,help.y-1,RGB(255,0,0)); } }
Функция для рисования графика полинома: void CGpView::DrawPolinom(CDC* pDC) { //Получение указателя на документ CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; if(pDoc->pol_ready) //Если полином построен, то... { CPen *oldPen = pDC->SelectObject(&polPen); //Загрузка в контекст пера для рисования полинома //Получение клиентского прямоугольника CRect cr; GetClientRect(&cr); //Создание вспомогательных переменных double XStart = SysToLog(CPoint(0,0)).x; //Логическая координата X левого края клиентского прямоугольника double XEnd = SysToLog(CPoint(cr.right,0)).x; //Логическая координата X правого края клиентского прямоугольника
99
double step = abs(XEnd-XStart)/static_cast<double>(cr.right); //Шаг для последовательного вычисления значений полинома pDC->MoveTo(LogToSys(XStart,pDoc->InterPol(XStart))); //Устанавка фокуса в начальное положение for(double i = XStart + step; i < XEnd; i += step) //Пока в пределах клиентского прямоугольника pDC->LineTo(LogToSys(i,pDoc->InterPol(i))); //Рисование линии к следующей точке pDC->SelectObject(oldPen); //Возврат стандартного пера в контекст усройства } else return; //Если полином не построен, то выход из функции }
Эти три функции обеспечивают вывод графической информации. Все они имеют один и тот же прототип, но различные имена. Каждая функция принимает в качестве аргумента указатель на контекст устройства CDC* pDC (таким образом, основная задача графического вывода разбивается на 3 более простых). Остаётся только последовательно вызвать их в методе класса вида OnDraw(CDC* pDC). Функция OnDraw: void CGpView::OnDraw(CDC* pDC) { DrawAxis(pDC); DrawInitPoints(pDC); DrawPolinom(pDC); }
Для запуска механизма построения и отображения полинома, необходимо обработать выбор соответствующего пункта меню и нажатие на кнопку на панели инструментов. Обработчик пункта меню Build and draw polynom и связанной с ним кнопки на панели инструментов (определён в классе документа): void CGpDoc::OnToolsBuilddrawpolinom() { InterPol = 0; //Стираем старый полином if(BuildPolinom()) pol_ready = true;//Если полином построен удачно, то else pol_ready = false; //Устанавливаем флаг UpdateAllViews(NULL); //Сигнализируем виду о том, что документ изменился }
Далее необходимо осуществить вывод координат курсора в строку состояния. Для этого отредактируем класс основного окна рамки. Прототип класса основного окна рамки: // MainFrm.h : interface of the CMainFrame class // #pragma once class CMainFrame : public CFrameWnd {
100
protected: // create from serialization only CMainFrame(); DECLARE_DYNCREATE(CMainFrame) // Attributes public: // Operations public: // Overrides public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif public: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; // Generated message map functions protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); DECLARE_MESSAGE_MAP() public: afx_msg void OnViewStatusBar(); afx_msg void OnUpdateViewStatusBar(CCmdUI *pCmdUI); };
Жирным шрифтом отмечены изменения. Объекты члены m_wndStatusBar и m_wndToolBar необходимо объявить с модификатором public. В реализации класса основного окна рамки необходимо изменить состав массива indicators[] и функцию OnCreate(LPCREATESTRUCT lpCreateStruct). Массив indicators: static UINT indicators[] = { ID_SEPARATOR, ID_SEPARATOR, };
// status line indicator
Функция инициализации основного окна рамки: int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar\n");
101
return -1; // fail to create } if (!m_wndStatusBar.Create(this,WS_CHILD|WS_VISIBLE|CBRS_BOTTOM,ID_INFO_STATUS_BAR) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create } // TODO: Delete these three lines if you don't want the toolbar to be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); return 0; }
Также необходимо создать в ресурсах идентификатор с именем ID_INFO_STATUS_BAR для новой строки состояния и добавить обработчики для пункта меню Status Bar. Обработчик пункта меню Status Bar: void CMainFrame::OnViewStatusBar() { m_wndStatusBar.ShowWindow(!(m_wndStatusBar.GetStyle() & WS_VISIBLE)); RecalcLayout(); }
Обработчик изменения внешнего вида пункта меню Status Bar: void CMainFrame::OnUpdateViewStatusBar(CCmdUI *pCmdUI) { pCmdUI->SetCheck(m_wndStatusBar.GetStyle() & WS_VISIBLE); }
Вывод коэффициентов полинома организован с помощью диалогового окна. Создадим в ресурсах прообраз диалогового окна с расширенным текстовым полем RichEdit. Создадим класс на его основе. Прототип класса диалога для вывода коэффициентов интерполяционного полинома: #pragma once // CShowkoefsDlg dialog class CShowkoefsDlg : public CDialog { DECLARE_DYNAMIC(CShowkoefsDlg) public: CShowkoefsDlg(CWnd* pParent = NULL); // standard constructor virtual ~CShowkoefsDlg(); // Dialog Data enum { IDD = IDD_SHOWKOEFS_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
102
DECLARE_MESSAGE_MAP() public: CString InitStr; public: // virtual BOOL OnInitDialog(); };
Переменная связана с текстовым полем, в неё будут записываться коэффициенты полинома. Чтобы использовать в программе элемент управления RichEdit, необходимо вызвать в методе InitInstance() класса приложения функцию AfxInitRichEdit(). Для вывода диалогового окна на экран, необходимо создать пункт меню Show polynomial coefficients и соответствующую кнопку на панели управления, и обработать нажатие на них. Обработчик пункта меню Show polynomial coefficients и связанной с ним кнопки на панели управления: void CGpView::OnToolsShowpolinomialkoefficients() { CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; CShowkoefsDlg dlg; vector help = pDoc->InterPol.GetTPolinomString(); for(size_t i = 0; i < help.size(); i++) { CString t; t.Format("x^%d: ",i); dlg.InitStr += t + help[i] + CString("\n"); } dlg.DoModal(); }
Обработчик изменения внешнего вида пункта меню Mark initial points и связанной с ним кнопки на панели инструментов: void CGpView::OnUpdateToolsShowpolinomialkoefficients(CCmdUI *pCmdUI) { CGpDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; pCmdUI->Enable(pDoc->pol_ready); }
Для сохранения клиентской области (графика полинома) в файл, создадим обработчик OnFileSaveAs() в классе вида. Используем переменную imgOriginal типа CImage как вспомогательную для сохранения. Обработчик сохранения графика полинома: void CGpView::OnFileSaveAs() { CRect clRect; //Переменная для сохранения размеров клиентской области
103
CString strFilter; //Строка со списком поддерживаемых форматов CString strFileName; //Строка с путём и именем файла CString strExtension; //Строка расширения файла в который происходит сохранение strFilter = "Bitmap image|*.bmp|JPEG image|*.jpg|GIF image|*.gif|PNG image|*.png||"; CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter); //Диалог для получения пути и имени файла SetCapture(); //Захват мыши HCURSOR hcurs = SetCursor(LoadCursor(NULL,IDC_WAIT)); //Установка курсора в виде часов imgOriginal.Destroy(); //Очистка переменной imgOriginal GetClientRect(&clRect); //Получение размеров клиентского прямоугольника imgOriginal.Create(clRect.right,clRect.bottom,24); //Инициализация изображения в соответствии с размерами клиентского прямоугольника CDC* sourceDC = GetDC(); //Получение указателя на используемый контест устройства for(int i = 0; i < clRect.right; i++) //Копирование изображения из контекста в переменную imgOriginal for(int j = 0; j < clRect.bottom; j++) imgOriginal.SetPixel(i,j,sourceDC->GetPixel(i,j)); SetCursor(hcurs); //Возвращение предыдущего курсора ReleaseCapture(); //Освобождение мыши if (dlg.DoModal() == IDOK) //Если нажата кнопка OK ... { strFileName = dlg.m_ofn.lpstrFile; //Запись пути и имени файла в строку if (dlg.m_ofn.nFileExtension == 0) //Если имя правильное { switch (dlg.m_ofn.nFilterIndex) //В соответствии с указанным в диалоге расширением инициализация строки расширения { case 1: strExtension = "bmp"; break; case 2: strExtension = "jpg"; break; case 3: strExtension = "gif"; break; case 4: strExtension = "png"; break; default: break; } strFileName = strFileName + '.' + strExtension; //Инициализация полной строки с именем и расширением для сохранения } } else return; //Иначе выход из функции HRESULT hResult = imgOriginal.Save(strFileName); //Сохранение изображения if (FAILED(hResult)) //Если ошибка { CString fmt; //Строка с кодом ошибки fmt.Format("Save image failed:\n%x - %s", hResult, _com_error(hResult).ErrorMessage()); AfxMessageBox(fmt); //Вывод сообщения об ошибке return; } }
Данная функция выполняет сохранение изображения клиентской области окна в файл в виде растрового изображения. Для сохранения используется метод Save(…) класса CImage. Для работы с этим классом необходимо подключить заголовочный файл atlimage.h к программе.
104
Все основные части программы написаны. Перед компиляцией добавим в файл stdafx.h строки: #include #include #include #include #include #include
"DoublePoint.h"
После строки #include
// MFC Automation classes
И строку using namespace std; в конец файла. Добавим строки в файл GpView.cpp #include "AddPointDlg.h" #include "ShowkoefsDlg.h"
После строки #include "GpView.h” Добавим строку в файл GpDoc.h #include "TPolinom.h"
После строки #pragma once Скомпилируем и запустим приложение. Если взять в качестве узлов следующие точки (указаны на рисунке):
105
то в результате получим соответствующий график интерполяционного полинома:
§2. Приложение «Шифр Виженера» В данном параграфе, на примере написания приложения «Шифр Виженера», рассматриваются следующие вопросы: работа с классом CRichEdit, ввод и форматирование текста, сохранение форматированного текста в файл, работа с модальными диалоговыми окнами, работа с таймером и элементом управления «индикатор хода процесса». Постановка задачи. Теоретический материал Алгоритм шифрования Виженера
Пусть у нас есть текст. Все буквы текста принадлежат алфавиту A = {a1 ,..., a n }, где ai – i -ый символ алфавита. Требуется зашифровать этот текст по алгоритму Виженера. Шифр Виженера – это шифр простой замены, с секретным ключом.
Шифрование происходит по формуле xi → ci = xi + ε i (mod k ) (mod n) , а дешифрование по формуле ci → xi = ci − ε i (mod k ) (mod n) , где n – число символов в алфавите, k – число символов в секретном слове (ключе), xi – i -ый символ открытого текста, ci – i -ый символ шифрованного текста, ε i (mod k ) – i -ый символ ключа.
106
Задача. Написать приложение для реализации шифрования текста по алгоритму Вижинера.В приложении должна быть:
1. реализована возможность ввода, редактирование, форматирование открытого (зашифрованного) текста; 2. реализована возможность выполнения операции шифрования (дешифрования) по алгоритму Вижинера; 3. реализована возможность сохранения форматированного текста в файл. В качестве алфавита в программе будем использовать наиболее распространённые печатные символы ASCII кодировки. Для работы с текстом будем использовать специализированный класс CRichEditCtrl. Специализированные классы MFC для работы с текстом
MFC предоставляет два основных средства редактирования текста. Это – обычное поле ввода (Edit Control) и поле ввода с форматированием (Rich Edit Control). Ими можно воспользоваться как элементами управления в диалоговых окнах, а можно создавать на их основе окна представлений, как в стандартных текстовых редакторах Windows: Notepad и WordPad. Такую гибкость обеспечивают классы CEditView и CRichEditView. Класс CEditView
В основе этого класса – элемент управления Windows поле ввода. Мастер MFC Application Wizard позволяет наследовать ваш собственный класс «вида» от CEditView. При работе с объектами класса CEditView доступны все методы классов CView и CEdit. Множественное наследование здесь не применяется. Размер текста обрабатываемого данным классом по умолчанию ограничен – не более 2 20 − 1 = 1048575 символов, его можно изменить, отправив сообщение EM_LIMITTEXT. Класс CEditView имеет следующие ограничения: • • • •
CEditView не поддерживает WYSIWYG редактирование. В случае, когда стоит выбор между читаемостью текста на дисплее и соответствию размеров при печати, CEditView отобразит текст в соответствии с первым критерием. CEditView может отображать текст только в одном формате. Нет специализированной поддержки шрифтов. Существуют ограничения на количество вводимых символов.
Класс CRichEditView
Этот класс базируется на элементе управления «поле ввода с форматированием». Элемент управления «поле ввода с форматированием» – окно, в которое пользователь может вводить и редактировать текст. Данный элемент управления позволяет работать с форматированным текстом и встроенными OLE объектами, предоставляет методы для управления параметрами шрифтов и абзацев, поддерживает большие объёмы текста. Класс CRichEditView предназначен для совместного использования с классом CRichEditDoc и CRichEdiCtrl, что позволяет создавать полноценные контейнерные приложения ActiveX.
107
Класс CRichEditCtrl
Этот класс представляет собой оболочку для элемента управления «поле ввода с форматированием». В нём переопределены обработчики сообщений клавиатуры и мыши для ввода и редактирования текстовой информации. Также он предоставляет множество методов для работы с текстом. Рассмотрим их подробнее. 1. Методы для создания объектов CRichEditCtrl Метод virtual BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID) создаёт окно элемента управления «поле ввода с форматированием» и связывает его с классом CRichEditCtrl. Параметр dwStyle задаёт набор флагов, определяющих стиль окна, rect задаёт оконный прямоугольник, pParentWnd – указатель на родительское окно (если метод Create(…) вызывается в диалоговом окне, то параметр pParentWnd должен иметь значение NULL), nID – идентификатор окна. Если поле ввода с форматированием используется в диалоговом окне, то в функции InitInstance класса приложения необходимо вызвать функцию AfxInitReichEdit() для того, чтобы загрузить необходимые библиотеки элементов управления. 2. Методы управления строками Метод int GetLine(int nIndex, LPTSTR lpszBuffer, int nMaxLength) const копирует строку, номер которой указан в параметре nIndex в буфер lpszBuffer длиной в nMaxLength символов. Скопированная строка не содержит в конце символа окончания строки. Метод int GetLineCount() const возвращает количество строк в текстовом поле. Метод int LineLength(int nLine = -1) const возвращает длину строки в байтах, номер которой указан в параметре nLine, по умолчанию возвращает длину строки, содержащей каретку. 3. Методы выделения текста Метод void Clear() удаляет выделенный текст. Метод void GetSel(long& nStartChar, long& nEndChar) const записывает границы выделенного текста в переменные nStartChar (начало) и nEndChar (конец). Метод CString GetSelText( ) const возвращает объект-строку с выделенным текстом. Метод void SetSel(long nStartChar, long nEndChar) выделяет текст, начало выделения передаётся в параметре nStartSel, конец – в параметре nEndSel. 4. Методы форматирования текста Метод DWORD GetDefaultCharFormat(CHARFORMAT& cf) const возвращает в параметр cf формат символов, принятый текстовым полем по умолчанию. Метод DWORD GetSelectionCharFormat(CHARFORMAT& cf) const возвращает в параметр cf формат выделенного текста.
108
Метод BOOL SetDefaultCharFormat(CHARFORMAT& cf) устанавливает формат символов, который текстовое поле будет использовать по умолчанию. Параметр cf определяет устанавливаемый формат. Метод BOOL SetSelectionCharFormat(CHARFORMAT& cf) устанавливает формат выделенных в текстовом поле символов, указанный в параметре cf. Все вышеуказанные методы требуют в качестве параметра ссылку на объект структуры CHARFORMAT. Поля данной структуры подробнее рассмотрены далее. 5. Методы работы с потоками Метод StreamIn(int nFormat, EDITSTREAM& es) предназначен для чтения данных из потока. Параметр nFormat определяет формат читаемых данных, может принимать значения: SF_TEXT и SF_RTF. В первом случае прочтённые данные воспринимаются как неформатированный текст, во втором – как форматированный. Параметр es определяет поток, из которого следует читать информацию, имеет тип EDITSTREAM. Метод StreamOut(int nFormat, EDITSTREAM& es) предназначен для записи данных в поток. Имеет такой же набор аргументов, как и предыдущий метод. Закончим на этом рассмотрение специализированных методов класса CRichEditCtrl для работы с текстом. Их применение будет продемонстрировано ниже в примере «Шифр Виженера». Проектирование и разработка приложения Этапы разработки приложения
1. 2. 3. 4.
Настроить каркас MFC для ввода и хранения текстовой информации. Организовать возможность форматирования текста. Реализовать алгоритм шифрования (дешифрования) текста. Организовать вывод результатов и возможность их сохранения.
Создадим с помощью мастера MFC Application Wizard SDI приложение без поддержки архитектуры «документ – вид». Отключим поддержку символов Unicode. 1. Настройка каркаса MFC для ввода и хранения текстовой информации
Основным классом для хранения и отображения данных в случае, когда отключена поддержка каркаса «документ – вид», является класс CChildView, наследник класса CWnd. Мы воспользуемся специализированным классом CRichEditCtrl для работы с текстом. Этот класс предоставляет множество методов для форматирования, редактирования, отображения и сохранения текстовой информации. Добавим в прототип класса CChildView следующую строку: public: CRichEditCtrl m_rich; форматированием
//Переменная для управления текстовым полем с
109
m_rich – переменная, через которую будем осуществлять взаимодействие с текстовым полем типа CRichEditCtrl. Далее, для инициализации введённой переменной, обработаем сообщения WM_CREATE и WM_SIZE. Обработчик сообщения WM_CREATE: int CVijinerView::OnCreate(LPCREATESTRUCT lpCreateStruct) { CRect cr(0,0,0,0); //Создадим прямоугольник if (CView::OnCreate(lpCreateStruct) == -1) return -1; m_rich.Create(ES_AUTOVSCROLL|ES_MULTILINE|ES_WANTRETURN|WS_CHILD|WS_VISIBL E|WS_VSCROLL,cr,this,1); //Инициализируем переменную для управления текстовым полем }
return 0;
При создании окна конструируется поле ввода нулевых размеров, т.к. на данном этапе выполнения программы длина и ширина клиентской области ещё неизвестны. Для создания объекта класса CRichEditCtrl используется метод Create(…). Первый аргумент метода – набор флагов, определяющих стиль, второй – прямоугольник с размерами, третий – указатель на окно родителя, четвёртый – идентификатор окна элемента управления. Обработчик WM_SIZE: void CVijinerView::OnSize(UINT nType, int cx, int cy) { CRect cr; //Создание переменной для получения размеров клиентского прямоугольника CView::OnSize(nType, cx, cy); GetClientRect(&cr); //Получение размеров клиентского прямоугольника m_rich.SetWindowPos(&wndTop,0,0,cr.right-cr.left,cr.bottomcr.top,SWP_SHOWWINDOW); //Изменение размеров поля ввода }
Функция GetClientRect(&cr) используется для получения клиентского прямоугольника. Функция SetWindowPos(&wndTop,0,0,cr.right-cr.left,cr.bottom-cr.top,SWP_SHOWWINDOW) устанавливает новое положение и размеры окна (в данном случае окна элемента управления Rich Edit). В параметрах указаны: указатель на окно предшественник (окно, поверх которого будет располагаться окно, вызвавшее функцию SetWindowPos(…)), координаты левого верхнего угла окна, длина и ширина окна, флаг со специальными параметрами отображения. Если скомпилировать и запустить приложение на данном этапе, то появится окно с растянутым на всю клиентскую область полем ввода, которое обрабатывает все сообщения клавиатуры. Добавим возможность считывания текстовой информации из файла в это поле ввода. Добавим элемент главного меню File→Open… и обработаем нажатие на него. Добавим следующую строку в прототип класса CChildView: protected: static DWORD CALLBACK FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb); //Функция обратного вызова для чтения данных из файла
110
Определим данную функцию в файле ChildView.cpp: DWORD CALLBACK CChildView::FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb) { CFile *pFile = (CFile*) dwCookie; *pcb = pFile->Read(pbBuf,cb); return 0; }
Обработчик пункта меню File→Open… void CChildView::OnFileOpen() { CString strFilter; //строка с фильтром форматов strFilter = "Text file|*.txt|Rich text format file|*.rtf||"; CFileDialog dlg(TRUE,NULL,NULL,OFN_FILEMUSTEXIST,strFilter); //Создание диалога для открытия файла if(dlg.DoModal() == IDOK) //Если выбран файл и нажата кнопка Ok... { EDITSTREAM es; //Создаём структуру потока CFile InFile(dlg.GetFileName(),CFile::modeRead); //Открываем файл с указанным именем для чтения es.dwCookie = (DWORD) &InFile; //Определяем поток es.pfnCallback = FileStreamInCallback; switch(dlg.m_ofn.nFilterIndex) //В зависимости от того, какой формат выбран, читаем текст из файла { case 1: m_Rich.StreamIn(SF_TEXT,es); break; case 2: m_Rich.StreamIn(SF_RTF,es); break; default: break; } AfxGetMainWnd()->SetWindowTextA(dlg.GetFileName()); //Выводим в заголовок окна название открытого файла } }
Для чтения текстовой информации из файла используется специализированный метод StreamIn(SF_TEXT,es) класса CRichEditCtrl. В качестве первого аргумента методу передаётся флаг, который определяет формат данных для чтения. Вторым аргументом является объект структуры EDITSTREAM. Поле dwCookie этой структуры определяет первый параметр, передаваемый в функцию чтения (обычно это идентификатор некоторого объекта, из которого следует читать данные). В поле pfnCallback записывается адрес функции чтения, которая определяет способ работы с данными и источником (она имеет строго определённый прототип). В данном случае мы сами создали функцию чтения данных: DWORD CALLBACK FileStreamInCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb). Первый параметр – идентификатор источника, второй – указатель на буфер, третий – число байтов, которое необходимо считать, четвёртый – указатель на переменную, в которую записывается число прочтённых байтов. Метод StreamIn(…) повторно вызывает функцию чтения, пока не произойдёт одно из следующих событий: • функция чтения не вернёт ненулевое значение; • функция не запишет в переменную *pcb 0; • возникнет ошибка в процессе обмена данных между элементом управления и буфером; • будет прочтён код завершающий rtf блок (для текста в формате rtf); • будет прочтён символ перехода на новую строку (для однострочного текстового поля).
111
Теперь, когда стало возможным чтение данных из файла и их ввод с клавиатуры, реализуем простейшие операции работы с буфером обмена (вырезать, копировать, вставить и т.п.). Для этого будем обрабатывать соответствующие пункты главного меню. Обработчик пункта меню Edit→Copy: void CChildView::OnEditCopy() { m_Rich.Copy(); }
Обработчик изменения внешнего вида пункта меню Edit→Copy: void CChildView::OnUpdateEditCopy(CCmdUI *pCmdUI) { LONG nStartSel, nEndSel; //Переменные для хранения начальной и конечной позиции выделенного текста m_Rich.GetSel(nStartSel,nEndSel); //Получение начальной и конечной позиции выделенного текста pCmdUI->Enable(nStartSel != nEndSel); //Если текст выделен, то можно скопировать }
Метод GetSel(nStartSel,nEndSel) записывает позиции начала и конца выделенного текста в соответствующие переменные, переданные ему в качестве параметров. Обработчик пункта меню Edit→Cut: void CChildView::OnEditCut() { m_Rich.Cut(); }
Обработчик изменения внешнего вида пункта меню Edit→Cut: void CChildView::OnUpdateEditCut(CCmdUI *pCmdUI) { LONG nStartSel, nEndSel; m_Rich.GetSel(nStartSel,nEndSel); pCmdUI->Enable(nStartSel != nEndSel); }
Код данного обработчика совпадает с кодом обработчика изменения внешнего вида элемента Edit→Copy. Обработчик пункта меню Edit→Paste: void CChildView::OnEditPaste() { m_Rich.Paste(); }
Обработчик изменения внешнего вида пункта меню Edit→Paste: void CChildView::OnUpdateEditPaste(CCmdUI *pCmdUI) { pCmdUI->Enable(m_Rich.CanPaste()); }
112
В данном обработчике используется метод CanPaste() класса CRichEditCtrl, который возвращает значение TRUE, если данные, скопированные в буфер, поддерживаются элементом управления, и FALSE в любом другом случае. Обработчик пункта меню Edit→Undo: void CChildView::OnEditUndo() { m_Rich.Undo(); }
Обработчик изменения внешнего вида пункта меню Edit→Undo: void CChildView::OnUpdateEditUndo(CCmdUI *pCmdUI) { pCmdUI->Enable(m_Rich.CanUndo()); }
Данные обработчики полностью реализуют необходимые функции работы с буфером обмена. Добавим возможность вызова контекстного меню с элементами, соответствующими пунктам меню Edit. Для этого обработаем сообщение WM_CONTEXTMENU. Обработчик сообщения WM_CONTEXTMENU: void CChildView::OnContextMenu(CWnd* /*pWnd*/, CPoint point) { CMenu Menu; //Создаём объект меню Menu.LoadMenuA(IDR_MAINFRAME); //Загружаем ресурс меню Menu.GetSubMenu(1)>TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this); //Вызываем меню }
Метод LoadMenuA(IDR_MAINFRAME) загружает из ресурсов приложения меню, идентификатор которого передан в качестве параметра. Метод GetSubMenu(1) возвращает указатель на подменю. Метод TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this) раскрывает контекстное меню в точке point. 2. Форматирование текста
Текстовое поле Rich Edit предназначено для работы с данными в формате rtf. То есть, существует возможность работы со шрифтами (изменение цвета, размера символов, их выделение, подчёркивание и т.п.). Реализуем некоторые функции работы со шрифтами в нашей программе. Для этого создадим пункт меню Format, а в нём подпункт Format→Font…, который будем обрабатывать. Обработчик пункта меню Format→Font… void CChildView::OnFormatFont() { LONG nStartSel, nEndSel; //Переменные для хранения начала и конца выделения CHARFORMAT cf_old, cf_new; //Переменные для хранения формата символов m_Rich.GetSel(nStartSel,nEndSel); //Получение начала и конца выделения
113
if(nStartSel == nEndSel) m_Rich.GetDefaultCharFormat(cf_old); //Если ничего не выделено, то сохраняем формат по умолчанию else m_Rich.GetSelectionCharFormat(cf_old); //иначе сохраняем формат выделенного текста CFontDialog fdlg(cf_old); //На основе полученного формата создаём диалог работы со шрифтом if(fdlg.DoModal() == IDOK) //Если пользователь нажал Ok { //Заполняем поля объекта структуры CHARFORMAT cf_new.cbSize = sizeof(CHARFORMAT); cf_new.dwMask = CFM_BOLD|CFM_COLOR|CFM_FACE|CFM_ITALIC|CFM_SIZE|CFM_UNDERLINE|CFM_CHARSET|CFM_ST RIKEOUT; cf_new.dwEffects = (fdlg.m_lf.lfItalic ? CFE_ITALIC : 0)|(fdlg.m_lf.lfUnderline ? CFE_UNDERLINE : 0)|(fdlg.m_lf.lfWeight > 400 ? CFE_BOLD : 0)|(fdlg.m_lf.lfStrikeOut ? CFE_STRIKEOUT : 0); cf_new.yHeight = -MulDiv(fdlg.m_lf.lfHeight,1440,GetDC()>GetDeviceCaps(LOGPIXELSY)); cf_new.bPitchAndFamily = fdlg.m_lf.lfPitchAndFamily; cf_new.bCharSet = fdlg.m_lf.lfCharSet; cf_new.crTextColor = fdlg.m_cf.rgbColors; if(nStartSel == nEndSel) { m_Rich.SetFocus();//Возвращаем фокус текстовому полю m_Rich.SetDefaultCharFormat(cf_new); //Если текст не был выделен, то обновляем шрифт по умолчанию } else //иначе { m_Rich.SetFocus(); //Возвращаем фокус текстовому полю m_Rich.SetSel(nStartSel,nEndSel); //Выделяем форматируемый текст m_Rich.SetSelectionCharFormat(cf_new); //Обновляем формат выделенного текста } } }
Для получения текущего формата символов и установки нового формата используются методы: • GetDefaultCharFormat(cf_old) – сохраняет формат символов, который текстовое поле использует по умолчанию, в переменную cf_old • GetSelectionCharFormat(cf_old) – сохраняет формат выделенных символов в переменную cf_old • SetDefaultCharFormat(cf_new) – обновляет формат символов, который текстовое поле использует по умолчанию • SetSelectionCharFormat(cf_new) – обновляет формат выделенных символов. Наибольший интерес представляет собой структура CHARFORMAT, объектами которой являются переменные cf_old и cf_new. Рассмотрим поля данной структуры. • UINT cbSize – размер заданной структуры в байтах. • DWORD dwMask – поле, содержащее информацию о доступных для изменения атрибутов шрифта. • DWORD dwEffects – параметры шрифта (жирный, подчёркнутый, курсив и т.п.). • LONG yHeight – высота символа в твипах (1/1440 дюйма).
114
• BYTE bPitchAndFamily – определяет семейство шрифта и расстояние между символами. • BYTE bCharSet – определяет набор символов (алфавит). • COLORREF crTextColor – цвет символов. Возможные значения полей данной структуры представлены в MSDN Library. Остановимся подробнее только на высоте символа. В программе она задаётся строкой cf_new.yHeight = MulDiv(fdlg.m_lf.lfHeight,1440,GetDC()->GetDeviceCaps(LOGPIXELSY)). Функция MulDiv умножает два 32-битных значения (первый и второй аргумент), а затем делит получившееся 64-битное значение на третий аргумент и округляет результат до целых. Значение fdlg.m_lf.lfHeight определяет высоту символа в логических единицах дисплея, а GetDeviceCaps(LOGPIXELSY) определяет число логических единиц в дюйме. Таким образом, по формуле (высота символа в логических единицах) ⋅ (число твиасов в дюйме) число логических единиц в дюйме мы переходим к нужным единицам измерения. Поле m_lf класса CFontDialog имеет тип LOGFONT и является GDI структурой, которая определяет параметры шрифта в логических единицах дисплея. 3. Шифрование и дешифрование текста
Создадим ресурс диалога для шифрования с идентификатором IDD_CIPHER_DIALOG. Добавим два текстовых поля (Edit Control), два флажка-переключателя (Check Box), кнопку (Button), индикатор сосотояния (Progress Bar) и групповое поле (Group Box). Шаблон получившегося диалога приведён на рисунке:
Первое текстовое поле предназначено для ввода ключа шифрования, свойство Password должно иметь значение true. В групповое поле объединены элементы управления, отвечающие за вывод информации. Кнопка открывает диалог для указания пути к файлу в который будет сохранён шифротекст. Путь к файлу выводится во второе текстовое поле. Защитим его от изменения, установив свойство Read Only в значение true. Первый флажок
115
переключатель сигнализирует о необходимости записи конечного результата в файл. Второй–о необходимости вывода результата в основное текстовое поле программы. Добавим в приложение класс диалога, созданный на основе данного ресурса. Добавим также необходимые переменные. Класс CCipherDialog: #pragma once // CCipherDialog dialog class CCipherDialog : public CDialog { DECLARE_DYNAMIC(CCipherDialog) public: CCipherDialog(CWnd* pParent = NULL); // standard constructor virtual ~CCipherDialog(); // Dialog Data enum { IDD = IDD_CIPHER_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support DECLARE_MESSAGE_MAP() public: BOOL m_bCiphType; //Флаг указывает на то,какое действие необходимо выполнить (шифрование/дешифрование) CString m_sFilePathStr; //Переменная, связанная с текстовым полем, в которое выводится путь файла для сохранения CString m_sBufStr; //Переменная буфер BOOL m_bToFile; //Переменная, связанная с первым флажком переключателем BOOL m_bDisplay; //Переменная, связанная со вторым флажком переключателем CString m_sKeyStr; //Переменная, связанная с первым текстовым полем, в кторое вводится ключ int table[161]; //Таблица алфавита int m_nTimer; //Счётчик таймера int m_nCount; //Переменная индикатор хода процесса шифрования/дешифрования bool m_bFinProc; //Флаг, сигнализирующий о том, прошёл ли процесс до конца public: afx_msg void OnBnClickedButtonBrowse(); afx_msg void OnTimer(UINT_PTR nIDEvent); protected: virtual void OnCancel(); virtual void OnOK(); };
Конструктор класса CCipherDialog: CCipherDialog::CCipherDialog(CWnd* pParent /*=NULL*/) : CDialog(CCipherDialog::IDD, pParent) //Инициализация переменных , m_bCiphType(FALSE) , m_sFilePathStr(_T("")) , m_sBufStr(_T("")) , m_bToFile(FALSE) , m_bDisplay(FALSE) , m_sKeyStr(_T("")) , m_nTimer(0) , m_nCount(0) , m_bFinProc(true) { for(int i = 0; i < 95; i++) //Создание таблицы для шифрования символов table[i] = i + 32; //Используются ASCII коды только печатных символов
116
}
for(int i = 95; i < 159; i++) table[i] = i - 159; table[159] = -88; table[160] = -72;
Функция DoDataExchenge: void CCipherDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT_FILEPATH, m_sFilePathStr); DDX_Check(pDX, IDC_CHECK_TOFILE, m_bToFile); DDX_Check(pDX, IDC_CHECK_TOCLIENT, m_bDisplay); DDX_Text(pDX, IDC_EDIT_KEY, m_sKeyStr); }
Функция DoDataExchenge используется для создания связей между переменными и элементами управления. При добавлении переменных с помощью мастера, её код генерируется автоматически. Для связи диалога с основным окном программы создадим пункт меню Cipher и в нём два подпункта Cipher→Encipher… и Cipher→Decipher… и обработаем их. Для создания объектов класса CCipherDialog добавим в начало файла ChildView.h строку #include "CipherDialog.h"
Обработчик пункта меню Cipher→Encipher… void CChildView::OnCipherEncipher() { CCipherDialog dlg; //Создание диалога шифрования/дешифрования dlg.m_bCiphType = TRUE; //Инициализация флага индикатора действия (будем производить шифрование) m_Rich.GetWindowTextA(dlg.m_sBufStr); //Копирование текста из текстового поля в строковую переменную - буфер диалога if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok { if(dlg.m_bToFile) //Если установлен флаг сохранения результата шифрования/дешифрования { int nBuf; //количество символов в тексте char *dinBuf; //буфер с текстом if(dlg.m_sFilePathStr != "") //Если верно указан путь файла { CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записиси nBuf = dlg.m_sBufStr.GetLength() + 1; //Записываем количество символов текста в переменную dinBuf = new char [nBuf]; //Инициализируем буфер strcpy_s(dinBuf,nBuf,dlg.m_sBufStr); //Копируем данные из диалога в буфер OutFile.Write(dinBuf,nBuf); //Записываем данные из буфера в файл } else AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено"); //Если неверно указан путь к файлу,то сообщаем об ошибке }
117
экран
if(dlg.m_bDisplay)
//Если установлен флаг вывода результатов на
{
m_Rich.Clear(); //Очищаем текстовое поле m_Rich.SetWindowTextA(dlg.m_sBufStr); //Записываем результат шифрования/дешифрования в текстовое поле } } }
В данном пункте важен код, выделенный жирным шрифтом, остальное относится к выводу результатов шифрования/дешифрования на экран или их сохранения в файл. Для получения текста из элемента управления Rich Edit, используется метод GetWindowText(CString &), аргументом ему передаётся строка, в которую будет записан текст. Для записи текста в текстовое поле, используется метод SetWindowText(CString). Обработчик пункта меню Cipher→Decipher… void CChildView::OnCipherDecipher() { CCipherDialog dlg; m_Rich.GetWindowTextA(dlg.m_sBufStr); if(dlg.DoModal() == IDOK) { if(dlg.m_bToFile) { int nBuf; char *dinBuf; if(dlg.m_sFilePathStr != "") { CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite); nBuf = dlg.m_sBufStr.GetLength() + 1; dinBuf = new char [nBuf]; strcpy_s(dinBuf,nBuf,dlg.m_sBufStr); OutFile.Write(dinBuf,nBuf); } else AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено"); } if(dlg.m_bDisplay) { m_Rich.Clear(); m_Rich.SetWindowTextA(dlg.m_sBufStr); } } }
Также как и в предыдущем обработчике строки, важные в данном пункте, выделены жирным шрифтом. Единственное отличие данного обработчика от предыдущего состоит в том, что мы не инициализируем переменную dlg.m_bCiphType. Это говорит о том, что будет происходить дешифрование. Теперь реализуем алгоритм шифрования. Переопределим виртуальную функцию OnOk() класса CCipherDialog.
118
Переопределённый метод OnOk: void CCipherDialog::OnOK() { MSG message; //Структура сообщение UpdateData(true); //Обновляем данные GetDlgItem(IDOK)->EnableWindow(FALSE); //Отключаем возможность повторного нажатия кнопки Ok пока происходит шифрование int nKeyLength = m_sKeyStr.GetLength(); //Инициализация переменной для хранения длины ключа if(nKeyLength) //Если ключ введён { m_nTimer = (int)SetTimer(1,100,NULL); //Пускаем таймер с интервалом 100 милисекунд CString tempstr; //Вспомогательная строка for(m_nCount = 0; m_nCount < m_sBufStr.GetLength(); m_nCount++) //Пока не конец открытого текста { int buf_ascii = int(m_sBufStr[m_nCount]); //Шифруем или дешифруем по алгоритму if(buf_ascii == 10 || buf_ascii == 13 || buf_ascii == 9) //Табуляции и переносы на новую строку не шифруем { tempstr += m_sBufStr[m_nCount]; continue; } int nTextChar = std::find(table,table+160,int(m_sBufStr[m_nCount])) - table; int nKeyChar = std::find(table,table+160,int(m_sKeyStr[m_nCount % nKeyLength])) - table; if(m_bCiphType) tempstr += char(table[(nTextChar + nKeyChar) % 161]); else tempstr += char(table[(nTextChar + 161 - nKeyChar) % 161]); if(::PeekMessageA(&message,NULL,0,0,PM_REMOVE)) //При этом на каждой иттерации цикла обрабатываем сообщения { ::TranslateMessage(&message); ::DispatchMessageA(&message); } } if(m_bFinProc) //Если процесс полностью завершён { m_sBufStr = tempstr; //Записываем все полученные данные в буфер KillTimer(m_nTimer); //Сбрасывем таймер CDialog::OnOK(); //Закрываем диалог } else //Иначе { m_bFinProc = true; //Сбрасываем флаг завершения процедуры в начальное положение m_nCount = 0; //Сбрасываем счётчик символов CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH); //Ининциализируем переменную для управления индикатором хода процесса pBar->SetPos(0); //Устанавливаем индикатор в начальное состояние KillTimer(m_nTimer); //Сбрасываем таймер } } else //Если ключ не введён {
119
}
}
GetDlgItem(IDOK)->EnableWindow(TRUE); //Разблокируем кнопку Ok AfxMessageBox("Введите ключ шифрования"); //Просим ввести ключ
Обратим внимание на использовании в данном методе функций: PeekMessageA(&message,NULL,0,0,PM_REMOVE), TranslateMessage(&message) и DispatchMessageA(&message). Они позволяют обрабатывать сообщения на каждой иттерации цикла, создавая тем самым впечатление параллельности вычислений и реакции на действия пользователя. Функция SetTimer(1,100,NULL) запускает системный таймер, т.е. с интервалом, указанным во втором параметре данной функции, посылаются сообщения типа WM_TIMER. Первый аргумент функции – целое число, идентифицируещее таймер, третий – указатель на функцию, которая должна обрабатывать сообщение WM_TIMER. Если он равен NULL, то сообщение WM_TIMER посылается в общую очередь сообщений программы. Функция std::find(table,table+160,int(m_sBufStr[m_nCount])) – это алгоритм stl, который используется для поиска элементов в контейнерах (массивах). Добавим в класс CCipherDialog обработчик сообщения WM_TIMER, для того чтобы выводить информацию о ходе шифрования в индикатор состояния. Обработчик сообщения WM_TIMER: void CCipherDialog::OnTimer(UINT_PTR nIDEvent) { CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH); //Инициализируем переменную для управления индикатором процесса if(m_sBufStr.GetLength()) //Если буфер не пуст pBar->SetPos(m_nCount*100/m_sBufStr.GetLength()); //Устанавливаем позицию индикатора процесса в соответствии с текущей позицией шифрования/дешифрования CDialog::OnTimer(nIDEvent); }
Функция GetDlgItem(IDC_PROGRESS_CIPH) возвращает указатель на объект элемента управления диалога, идентификатор которого передаётся ей в качестве аргумента. Также необходимо переопределить функцию OnCancel() для данного диалога. Она прерывает процесс шифрования/дешифрования, если процесс начался, но не закрывает диалог. И закрывает диалог если процесс не запущен. Переопределённый метод OnCancel(): void CCipherDialog::OnCancel() { if(m_nCount) //Если идёт процесс, то { GetDlgItem(IDOK)->EnableWindow(TRUE); //разблокируем кнопку Ok m_nCount = m_sBufStr.GetLength(); //завершаем цикл шифрования, присваивая переменной счётчику конечное значение m_bFinProc = false; //Указываем на то, что шифрование/дешифрование было прервано } else Cdialog::OnCancel(); //Иначе закрываем диалог }
120
Осталось обработать нажатие на кнопку Browse… Она должна открывать диалог для указания пути к файлу, в который будет выводиться результат шифрования/дешифрования. Обработчик кнопки Browse… void CcipherDialog::OnBnClickedButtonBrowse() { Cstring strFilter; //Строка с поддерживаемыми форматами Cstring strFileName; //Строка с путём и именем файла Cstring strExtension; //Строка с расширением файла UpdateData(true);//Обмен данными между элементами управления и переменными strFilter = “Text file|*.txt||”; //Инициализация строки поддерживаемыми форматами CfileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter); //Создание диалога if (dlg.DoModal() == IDOK) //Если нажата кнопка Ok { strFileName = dlg.m_ofn.lpstrFile; //Определяем путь и имя файла в который будем сохранять if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввёл расширение, то { switch (dlg.m_ofn.nFilterIndex) //В зависимости от того, какой из поддерживаемых форматов выбран { case 1: strExtension = “txt”; break; //Инициализируем строку с расширением default: break; } strFileName = strFileName + ‘.’ + strExtension; //Окончательно определяем строку с именем путём и расширением } m_sFilePathStr = strFileName; //Записывае готовую строку с полным именем в пременную член, связанную с текстовым полем UpdateData(false); //Обновляем данные в элементах управления в соответствии со связанными с ними переменными } }
4. Вывод результатов и их сохранение
Описание вывода результатов частично затронуто в предыдущем пункте. Теперь рассмотрим вывод форматированного текста в файл. Для этого создадим пункт меню File→Save as…, и обработаем нажатие на него. Но прежде добавим в файл ChildView.h строку: protected: static DWORD CALLBACK FileStreamOutCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb); //Функция обратного вызова для записи данных в файл
и определим объявленный метод в файле ChildView.cpp: DWORD CALLBACK CChildView::FileStreamOutCallback(DWORD dwCookie, LPBYTE pbBuf, LONG cb, LONG *pcb) { CFile *pFile = (CFile*) dwCookie; pFile->Write(pbBuf,cb); *pcb = cb; return 0; }
121
Обработчик пункта меню File→Save as… void CChildView::OnFileSaveas() { CString strFilter; //Строка для поддерживаемых форматов данных CString strFileName; //Строка для имени файла CString strExtension; //Строка для расширения файла strFilter = "Text file|*.txt|Rich text format file|*.rtf||"; //Инициализация строки поддерживаемыми форматами CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_EXPLORER,strFilter) ; //Создание диалога сохранения if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok { strFileName = dlg.m_ofn.lpstrFile; //Инициализируем строку с именем файла if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввёл расширение { switch (dlg.m_ofn.nFilterIndex) //В зависисмости от выбранного формата { case 1: strExtension = "txt"; break; //Инициализируем строку case 2: strExtension = "rtf"; break; //с расширением default: break; } strFileName = strFileName + '.' + strExtension; //Окончательно записываем путь имя и расширение файла } EDITSTREAM es; //Создаём структуру потока CFile OutFile(strFileName,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записи es.dwCookie = (DWORD) &OutFile; //Определяем поле структуры, указывающее куда записывать данные es.pfnCallback = FileStreamOutCallback; //Задаём функцию записи switch(dlg.m_ofn.nFilterIndex) //В зависимости от выбранного формата { case 1: m_Rich.StreamOut(SF_TEXT,es); break; //Записываем данные case 2: m_Rich.StreamOut(SF_RTF,es); break; //в файл default: break; } } }
Процедура записи данных в файл похожа на процедуру чтения. Мы пользуемся методом StreamOut(int nFormat, EDITSTREAM &es) класса CRichEditCtrl, который требует аргументами формат записываемых данных и структуру потока. Структура потока EDITSTREAM определяется заданием пункта назначения и функции записи. Добавим строку: #include
в конец файла stdafx.h. Скомпилируем и запустим приложение. В результате, в соответствии с открытым текстовым документом, получается следующее:
122
Открытый текст в формате rtf:
Диалог шифрования:
123
Зашифрованный текст (ключ шифрования «крипто»):
§3. Приложение «Метод наименьших квадратов» В данном параграфе, на примере создания приложения «Метод наименьших квадратов (МНК)», рассматриваются следующие вопросы: диалог в качестве основного окна приложения, элемент управления CListCtrl, элемент управления CRichEditCtrl, класс CArray, работа с матрицами. Постановка задачи. Теоретический материал
Пусть у нас есть следующая таблица:
X x1 ... x n Y y1 ... y n где {x1 ,..., x n , y1 ,..., y n } ⊂ R Требуется построить полином y = f ( x) заданной степени k : k < n − 1 такой, что сумма квадратов расстояний от точек таблицы до графика полинома была наименьшей. Для поиска n
коэффициентов полинома составим следующий функционал: F = ∑ ( y i − f ( xi )) 2 и поставим i =1
задачу минимизации этого функционала F → min . Полином запишем в виде f ( x) = a 0 + a1 ⋅ x + ... + a k ⋅ x k , тогда необходимое условие минимума запишется в виде:
124
n ⎧ ∂F k = ⎪∂a ∑ ( yi − a0 − a1 ⋅ xi − ... − ak ⋅ xi ) = 0 ⎪ 0 i =1 ⎪ ∂F n = ∑ ( yi − a0 − a1 ⋅ xi − ... − ak ⋅ xik ) ⋅ ( xi ) = 0 ⎪ a ⇒ A⋅ a = b ∂ ⎨ 1 i =1 ⎪M ⎪ n ⎪ ∂F k k ⎪∂a = ∑ ( yi − a0 − a1 ⋅ xi − ... − ak ⋅ xi ) ⋅ ( xi ) = 0 ⎩ k i =1
⎛ ⎜ ⎜ 1 ⎜ ⎜ n ⎜ ∑ xi ⎜ i =1 ⎜ n ⎜ M ⎜ n k ⎜ ∑ xi ⎜⎜ i =1 ⎝ n
n
i =1 n
L
n
∑x i =1
n
2 i
L
n M
∑x i =1
n
⎛ n k ⎞ x ⎟ ⎜ ∑ yi ∑ i i =1 ⎟ ⎜ i =1 ⎜ n ⎟ a n ⎛ 0⎞ ⎜ n n ⎟ xik +1 ⎟ ⎜⎜ a ⎟⎟ ⎜ ∑ y i ⋅ xi ∑ i =1 ⎟ ⋅ ⎜ 1 ⎟ = ⎜ i =1 n ⎟ ⎜ M ⎟ ⎜ n M ⎟ ⎜⎝ a k ⎟⎠ ⎜ M n n ⎟ ⎜ k xi2⋅k ⎟ ⎜ ∑ y i ⋅ xi ∑ i =1 ⎟⎟ ⎜⎜ i =1 n ⎠ n ⎝ n
∑ xi
O k +1 i
L
⎞ ⎟ ⎟ ⎟ ⎟ ⎟ ⎟ ⎟ ⎟ ⎟ ⎟ ⎟⎟ ⎠
Данная система имеет единственное решение, если k < n − 1 . Значит, существует единственный полином доставляющий минимум этому функционалу, с коэффициентами представимыми решением указанной системы. Напишем программу для нахождения коэффициентов полинома. Проектирование и разработка приложений Этапы разработки
1. 2. 3. 4.
Создать ресурс диалогового окна. Организовать ввод и редактирование начальных данных. Построить СЛАУ и решить её. Организовать вывод результатов.
Создадим с помощью мастера MFC приложение на основе диалогового окна. Для этого установим Application type в значение Dialog based. Снимем флажок Use Unicode libraries. 1. Создание ресурса диалогового окна
Создадим диалоговое окно с тремя текстовыми полями для ввода данных в таблицу и указания степени аппроксимации, четырьмя кнопками(Add, Delete, Start Approximation, Exit), списковым представлением для отображения таблицы точек и текстовым полем RichEdit для вывода строки результата. Шаблон диалога приведён на рисунке:
125
Для всех элементов управления типа поле ввода, установим свойство Right Align Text в значение true. Для элемента управления типа поле ввода, расположенного напротив надписи Approximation degree: установим свойство Number в значение true. Для элемента управления типа поле ввода с форматированием, установим свойство Auto HScroll в значение true, свойство Align Text в значение center, свойство Horizontal Scroll в значение true, свойство Read Only в значение true. Для элемента управления типа списковое представление, установим свойство Border в значение false (убираем рамку для того, чтобы проще было получить размеры клиентской области элемента управления), свойство Single Selection в значение true, свойство View в значение Report (будем выводить таблицу). 2. Ввод и редактирование начальных данных
Начальные данные представляют собой точки плоскости. Как и в примере «Интерполяционный полином», введём класс, инкапсулирующий вещественную точку на плоскости. Добавим в файл RegrDlg.h, содержащий прототип класса основного диалога, следующий код: class SDPoint { public:
126
};
double x; double y; SDPoint():x(0),y(0){} //Конструктор по умолчанию SDPoint(double a, double b):x(a),y(b){} //Конструктор с параметрами
после строки #pragma once
Теперь, создадим в классе диалога массив объектов класса SDPoint, который будет хранить начальные данные. Прототип класса CRegrDlg: // CRegrDlg dialog class CRegrDlg : public CDialog { // Construction public: CRegrDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data enum { IDD = IDD_REGR_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() //Обработчики событий добавлены с помощью мастера public: afx_msg void OnBnClickedButtonAdd(); afx_msg void OnBnClickedButtonDel(); afx_msg void OnBnClickedButtonStart(); public: matrix<double> mRightPart; //Вектор - правая часть системы matrix<double> mNodeMatrix; //Матрица системы CArray<SDPoint,SDPoint&> mInitArr; //Массив точек //Следующие переменные добавлены с помощью утилиты ClassView и связаны с элементами управления double mX; //Координата X точки связана с первым текстовым полем double mY; //Координата Y связана со вторым текстовым полем int mNum; //Степень аппроксимации связана с третьим текстовым полем CListCtrl mListCtrl; //Переменная для управления списковым представлением };
Жирным шрифтом выделена переменная, которую необходимо добавить в данном пункте. CArray – это шаблонный класс, инкапсулирующий динамические массивы в MFC. В качестве первого аргумента шаблону передаётся тип хранимых в массиве объектов (в данном случае SDPoint), вторым аргументом передаётся тип объектов, использующихся для получения доступа к данным массива, обычно это ссылка на тип, указанный в первом аргументе (в данном случае SDPoint&). Для ввода данных в диалоге была создана кнопка Add и два текстовых поля. Добавим в диалоговый класс связанные с текстовыми полями переменные
127
mX, mY, mNum и mListCtrl с помощью мастера. Прежде чем обрабатывать нажатие на кнопку Add, настроим списковое представление. Добавим следующие строки в функцию OnInitDialog(): BOOL CRegrDlg::OnInitDialog() { CDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } //Настраиваем Элемент управления "списковое представление" CRect cr; mListCtrl.GetWindowRect(&cr); //Получение размеров окна элемента управления mListCtrl.SetExtendedStyle(LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT|LVS_EX_ON ECLICKACTIVATE); //Задаём необходимые стили mListCtrl.InsertColumn(0,_T("X"),LVCFMT_LEFT,(cr.right-cr.left)/2); //Добавляем колонку mListCtrl.InsertColumn(1,_T("Y"),LVCFMT_RIGHT,(cr.right-cr.left)/2); //Добавляем ещё одну //Настраиваем самую левую колонку LVCOLUMN lvm; //Структура для хранения информации о колонке lvm.mask = LVCF_FMT; //Инициализация полей структуры lvm.fmt = LVCFMT_RIGHT; mListCtrl.SetColumn(0,&lvm); //Задание необходимого стиля колонки SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon return TRUE; // return TRUE unless you set the focus to a control }
Жирным шрифтом выделен добавленный код. А теперь добавим обработчик кнопки Add и Delete. Обработчик нажатия на кнопку Add: void CRegrDlg::OnBnClickedButtonAdd() { CString str; UpdateData(true); //Обмен данными for(int i = 0; i < mInitArr.GetSize(); i++) //Перебираем все точки в массиве if(mInitArr[i].x == mX && mInitArr[i].y == mY) return; //Если существует такая же, то выходим mInitArr.Add(SDPoint(mX,mY)); //Проверка сделана, повторов нет, добавляем точку в массив str.Format("%f",mX); //Форматирум строку для вывода в таблицу mListCtrl.InsertItem((int)mInitArr.GetSize()-1,str); //Добавляем строку в таблицу
128
}
str.Format("%f",mY); //Повторяем для следующей колонки mListCtrl.SetItemText((int)mInitArr.GetSize()-1,1,str);
Обработчик нажатия на кнопку Delete: void CRegrDlg::OnBnClickedButtonDel() { int n = mListCtrl.GetSelectionMark(); //Получаем номер выделенной строки списка if(n < 0) return; //Если ничего не выделено,то выходим mInitArr.RemoveAt(n); //Удаляем точку из массива mListCtrl.DeleteItem(n); //Удаляем строку из списка }
3. Построение системы линейных уравнений и её решение
Для построения системы линейных уравнений будем использовать шаблонный класс матриц matrix. Этот класс выделен в отдельный заголовочный файл matrix.h. Его необходимо скопировать в каталог с проектом и добавить в проект с помощью утилиты Solution Explorer (вызвать контекстное меню проекта в окне утилиты Solution Explorer→Add→Existing item… и далее указать имя файла). Полный код класса приведён в приложении. Напишем функции для поиска обратной матрицы. Введём их в файле RegrDlg.cpp после строк. #include "stdafx.h" #include "Regr.h" #include "RegrDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif
Функции для поиска обратной матрицы: //Функции для решения СЛАУ методом окаймления matrix<double> bordering (matrix<double> u, matrix<double> v, matrix<double> A, matrix<double> a) { matrix<double> b = a - u*A*v; b.setcell(1/b.getcell(0,0),0,0); matrix<double> s = -u*A*b.getcell(0,0); matrix<double> w = -A*v*b.getcell(0,0); matrix<double> B = A - A*v*s; B.addcolend(w); s.addcolend(b); B.addrowend(s); return B; } matrix<double> getreverse (matrix<double> M) { matrix<double> Res; if(M.getcolsize() == M.getrowsize()) { Res = M.getminor(0,0,0,0); Res.setcell(1/Res.getcell(0,0),0,0); for(uni i = 1; i < M.getcolsize(); i++) { matrix<double> v = M.getminor(i,0,i,i-1); matrix<double> u = M.getminor(0,i,i-1,i);
129
}
} } return Res;
matrix<double> a = M.getminor(i,i,i,i); Res = bordering(u,v,Res,a);
Для формирования и решения системы мы создали кнопку Start approximation. Обработаем нажатие на неё. Обработчик нажатия на кнопку Start approximation: void CRegrDlg::OnBnClickedButtonStart() { UpdateData(true); CRichEditCtrl *pRich = (CRichEditCtrl *) GetDlgItem(IDC_RICHEDIT_RES); //Переменная для управления RichEditom if(mNum >= mInitArr.GetSize()) //Если данные не соответствуют { pRich->SetWindowTextA(CString("Number of points mismatches approximation degree")); //Выводим предупреждение return; //Выходим } mNodeMatrix.setsize(mNum + 1,mNum + 1); //Задаём размерность матрицы для поиска коэффициентов int n = (int) mInitArr.GetSize(); for(int i = 0; i < mNum + 1; i++) //Идём по строкам матрицы for(int j = 0; j < mNum + 1; j++) //Идём по столбцам матрицы { double elem = 0; //вспомогательная переменная for(int k = 0; k < n; k++) //Идём по точкам таблицы elem += pow(mInitArr[k].x,(double)i+j); //Формируем элемент матрицы mNodeMatrix.setcell(elem/n,j,i); //Инициализируем элемент матрицы } mRightPart.setsize(1,mNum + 1); //Задаём размерностьправой части системы for(int i = 0; i < mNum + 1; i++) //Идём по строкам правой части { double elem = 0; //Вспомогательный элемент for(int k = 0; k < n; k++) //Идём по точкам таблицы elem += mInitArr[k].y*pow(mInitArr[k].x,(double)i); //Формируем элемент правой части mRightPart.setcell(elem/n,0,i); //Инициализируем элемент правой части } matrix<double> Solution(::getreverse(mNodeMatrix)*mRightPart); //Решаем уравнение CString str; //Строка для вывода результатов str.Format("f(x) = %f",Solution.getcell(0,0)); //Формируем строку полинома for(uni i = 1; i < Solution.getrowsize(); i++) { CString temp; temp.Format(" + %f*x^%d",Solution.getcell(0,i),i); str += temp; } pRich->SetWindowTextA(str); //Выводим её }
130
4. Вывод информации
Вывод осуществляется в двух направлениях: вывод таблицы узлов и вывод аналитической формулы полинома. За вывод узлов отвечает элемент управления списковое представление, код вывода точек в таблице представлен в обработчиках кнопок Add и Delete. За вывод формулы полинома отвечает элемент управления текстовое поле с форматированием, код вывода формулы приведён в обработчике кнопик Start approximation. Для завершения программы добавим строку: #include "matrix.h"
в файл RegrDlg.h перед описанием класса SDPoint. Скомпилируем и запустим приложение. В результате в соответствии с введёнными данными должно получиться следующее:
Часть 3. Приложение В приложении приведены: класс матриц, класс полиномов, класс комплексных чисел, которые использовались при написании примеров в данном пособии. Создание классов подробно рассматривается в пособии, посвящённом технологии объектно-ориентированного программирования «Объектно-ориентированное программирование в C++» М.В. Свиркин, А.С. Чуркин.
131
Класс матриц matrix.h #include using namespace std; typedef unsigned int uni; template class matrix { type **arr; size_t srow, scol; virtual void error (int ne); public: matrix() {arr = NULL; srow = 0; scol = 0; } matrix(size_t c, size_t r); matrix(matrix &); ~matrix(); void initnum(type k); void initstat(type *p, size_t c, size_t r); void initdinam(type **p, size_t c, size_t r); void addrowend(matrix &mrow); void addcolend(matrix &mcol); void setcol(size_t c, matrix col); void setrow(size_t r, matrix row); void setminor(size_t cs, size_t rs, matrix &m); void swaprow(size_t fr, size_t sr); void swapcol(size_t fc, size_t sc); void setsize(size_t c, size_t r); void setsingle(size_t s); void setcell(type val, size_t c, size_t r); size_t getrowsize(); size_t getcolsize(); type getcell(size_t c, size_t r); matrix getrow(size_t r); matrix getcol(size_t c); matrix getminor(size_t cs, size_t rs, size_t ce, size_t re); void transp(); void delrowend(); void delcolend();
};
void operator= (matrix &m); matrix operator+ (matrix &m); matrix operator- (matrix &m); matrix operator* (matrix &m); matrix operator* (type c); matrix operator- ();
matrix.cpp template matrix::matrix(size_t c, size_t r) { if(c && r) { arr = new type* [c]; assert(arr != NULL); for(size_t i = 0; i < c; i++) { arr[i] = new type [r]; assert(arr[i] != NULL);
132
} scol = c; srow = r;
} else error(-1);
} template matrix::matrix(matrix &m) { arr = NULL; srow = scol = 0; if(m.srow > 0 && m.scol > 0) { srow = m.srow; scol = m.scol; arr = new type* [scol]; assert(arr != NULL); for(size_t i = 0; i < scol; i++) { arr[i] = new type [srow]; assert(arr[i] != 0); } for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow; j++) arr[i][j] = m.arr[i][j]; } else error(1); } template matrix::~matrix() { if(arr != NULL) for(size_t i = 0; i < scol; i++) if(arr[i] != NULL) delete [] arr[i]; delete [] arr; } template void matrix::setsize(size_t c, size_t r) { if(c == 0 && r == 0) { if(arr != NULL) { for(size_t i = 0; i < scol; i++) if(arr[i] != NULL) delete [] arr[i]; delete [] arr; } arr = NULL; scol = srow = 0; return; } if(c > 0 && r > 0) { if(arr != NULL) { for(size_t i = 0; i < scol; i++) if(arr[i] != NULL) delete [] arr[i]; delete [] arr; } scol = c; srow = r; arr = new type* [scol]; assert(arr != NULL);
133
for(size_t i = 0; i < scol; i++) { arr[i] = new type [srow]; assert(arr[i] != NULL); }
} else error(2);
} template void matrix::initstat(type *p, size_t c, size_t r) { this->setsize(c,r); for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow; j++) arr[i][j] = p[i*c + j]; } template void matrix::initdinam(type **p, size_t c, size_t r) { this->setsize(c,r); for(size_t i = 0; i < c; i++) for(size_t j = 0; j < r; j++) arr[i][j] = p[i][j]; } template void matrix::addcolend(matrix &mcol) { if(arr != NULL) { if(mcol.srow == srow && mcol.scol == 1) { matrix temp(*this); for(size_t i = 0; i < scol; i++) if(arr[i] != NULL) delete [] arr[i]; delete [] arr; arr = new type* [scol + 1]; assert(arr != NULL); for(size_t i = 0; i < scol + 1; i++) { arr[i] = new type [srow]; assert(arr[i]); } for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow; j++) arr[i][j] = temp.arr[i][j]; for(size_t i = 0; i < srow; i++) arr[scol][i] = mcol.arr[0][i]; scol++; } else error(3); } else { arr = new type* [1]; assert(arr != NULL); arr[0] = new type [mcol.srow]; assert(arr[0] != NULL); for(size_t i = 0; i < mcol.srow; i++) arr[0][i] = mcol.arr[0][i]; scol = 1; srow = mcol.srow; } } template void matrix::delcolend() { if(arr != NULL)
134
{
delete [] arr[scol-1]; scol--;
} else error(4);
} template void matrix::addrowend(matrix &mrow) { if(arr != NULL) { if(mrow.scol == scol && mrow.srow == 1) { matrix temp(*this); for(size_t i = 0; i < scol; i++) if(arr[i] != NULL) delete [] arr[i]; delete [] arr; arr = new type* [scol]; assert(arr != NULL); for(size_t i = 0; i < scol; i++) { arr[i] = new type [srow + 1]; assert(arr[i] != NULL); } for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow; j++) arr[i][j] = temp.arr[i][j]; for(size_t i = 0; i < scol; i++) arr[i][srow] = mrow.arr[i][0]; srow++; } else error(5); } else { arr = new type* [mrow.scol]; assert(arr != NULL); for(size_t i = 0; i < mrow.scol; i++) { arr[i] = new type [1]; assert(arr != NULL); } for(size_t i = 0; i < mrow.scol; i++) arr[i][0] = mrow.arr[i][0]; scol = mrow.scol; srow = 1; } } template void matrix::delrowend() { if(arr != NULL) { matrix temp(*this); for(size_t i = 0; i < scol; i++) if(arr[i] != NULL) delete [] arr[i]; delete [] arr; arr = new type* [scol]; assert(arr != NULL); for(size_t i = 0; i < scol; i++) { arr[i] = new type [srow - 1]; assert(arr[i] != NULL); }
135
for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow - 1; j++) arr[i][j] = temp.arr[i][j]; srow--;
} else error(6);
} template type matrix::getcell(size_t c, size_t r) { type res; if(c >= 0 && c < scol && r >= 0 && r < srow) res = arr[c][r]; else error(7); return res; } template matrix matrix::getcol(size_t c) { matrix res; if(arr != NULL) { if(0 <= c && c < scol) { res.initstat(arr[c],1,srow); return res; } else error(8); } else error(9); return res; } template size_t matrix::getcolsize() { size_t res; if(arr != NULL) res = scol; else error(10); return res; } template matrix matrix::getminor(size_t cs, size_t rs, size_t ce, size_t re) { matrix res; if(arr != NULL) { if(0 <= cs && ce < scol && cs <= ce && 0 <= rs && re < srow && rs <= re) { res.arr = new type* [ce - cs + 1]; assert(res.arr != NULL); for(size_t i = 0; i < ce - cs + 1; i++) { res.arr[i] = new type [re - rs + 1]; assert(res.arr != NULL); } for(size_t i = cs; i <= ce; i++) for(size_t j = rs; j <= re; j++) res.arr[i-cs][j-rs] = arr[i][j]; res.scol = ce - cs + 1; res.srow = re - rs + 1; } else error(11); } else error(12); return res;
136
} template matrix matrix::getrow(size_t r) { matrix res; if(arr != NULL) { if(0 <= r && r < srow) { type *temp = new type [scol]; assert(temp != NULL); for(size_t i = 0; i < scol; i++) type[i] = arr[i][r]; res.initstat(temp,sco,1); } else error(13); } else error(14); return res; } template size_t matrix::getrowsize() { size_t res; if(arr != NULL) res = srow; else error(15); return res; } template void matrix::initnum(type k) { if(arr != NULL) { for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow; j++) arr[i][j] = k; } else error(16); } template void matrix::setcol(size_t c, matrix { if(arr != NULL) if(0 <= c && c < scol && col.scol == 1 && col.srow == srow col.arr != NULL) for(size_t i = 0; i < srow; i++) arr[c][i] = col.arr[0][i]; else error(17); else error(18); } template void matrix::setrow(size_t r, matrix { if(arr != NULL) if(0 <= r && r < srow && row.srow == 1 && row.scol == scol row.arr != NULL) for(size_t i = 0; i < scol; i++) arr[i][r] = row.arr[i][0]; else error(19); else error(20); } template void matrix::setsingle(size_t s) { if(s > 0) { setsize(s,s); for(size_t i = 0; i < s; i++) for(size_t j = 0; j < s; j++)
col) &&
row) &&
137
} else error(21);
if(i == j) arr[i][j] = 1; else arr[i][j] = 0;
} template void matrix::setminor(size_t cs, size_t rs, matrix &m) { if(arr != NULL) if(0 <= cs && cs + m.scol < scol && 0 <= rs && rs + m.srow < srow) for(size_t i = cs; i < cs + m.scol; i++) for(size_t j = rs; j < rs + m.srow; j++) arr[i][j] = m.arr[i - m.scol][j - m.srow]; else error(22); else error(23); } template void matrix::swapcol(size_t fc, size_t sc) { if(arr != NULL) if(0 <= fc && fc < scol && 0 <= sc && sc < scol) for(size_t i = 0; i < srow; i++) { type temp = arr[fc][i]; arr[fc][i] = arr[sc][i]; arr[sc][i] = temp; } else error(24); else error(25); } template void matrix::swaprow(size_t fr, size_t sr) { if(arr != NULL) if(0 <= fr && fr < srow && 0 <= sr && sr < srow) for(size_t i = 0; i < scol; i++) { type temp = arr[i][fr]; arr[i][fr] = arr[i][sr]; arr[i][sr] = temp; } else error(26); else error(27); } template void matrix::transp() { if(arr != NULL) if(srow == scol) for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < i; j++) { type temp = arr[i][j]; arr[i][j] = arr[j][i]; arr[j][i] = temp; } else error(28); else error(29); } template matrix operator + (matrix &m) { matrix res; if(arr != NULL && m.arr != NULL && srow == m.srow && scol == m.scol) { res.setsize(scol,srow); for(size_t i = 0; i < scol; i++)
138
for(size_t j = 0; j < srow; j++) res.arr[i][j] += arr[i][j];
} else error(30); return res;
} template void matrix::operator = (matrix &m) { this->initdinam(m.arr,m.scol,m.srow); } template matrix matrix::operator * (matrix &m) { matrix res; if(arr != NULL && m.arr != NULL && scol == m.srow) { res.setsize(m.scol,srow); for(size_t i = 0; i < m.scol; i++) for(size_t j = 0; j < srow; j++) { res.arr[i][j] = 0; for(size_t k = 0; k < scol; k++) res.arr[i][j] += arr[k][j]*m.arr[i][k]; } } else error(31); return res; } template matrix matrix::operator - (matrix &m) { matrix res; if(arr != NULL && m.arr != NULL && srow == m.srow && scol == m.scol) { res.setsize(scol,srow); for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow; j++) res.arr[i][j] = arr[i][j] - m.arr[i][j]; } else error(32); return res; } template matrix matrix::operator * (type c) { matrix res; if(arr != NULL) { res.initdinam(arr,scol,srow); for(size_t i = 0; i < scol; i++) for(size_t j = 0; j < srow; j++) res.arr[i][j] *= c; } else error(33); return res; } template matrix matrix::operator - () { return (*this)*(-1); } template void matrix::setcell(type val, size_t c, size_t r) { if(arr != NULL && 0 <= c && c < scol && 0 <= r && r < srow) arr[c][r] = val; else error(34);
139
} template void matrix::error(int ne) { exit(ne); }
Класс полиномов TPolinom.h #include using namespace std; template class Polinom { public: vector Pol; //a0 + a1*x + a2*x^2 + ... + an*x^n public: Polinom(){Pol.resize(1); Pol[0] = 0;} Polinom(A a){Pol.resize(1); Pol[0] = a;} Polinom(int n, A *koef){Pol.resize(n); for(int i = 0; i < n; i++) Pol[i] = koef[i];} Polinom(const Polinom& initPol){Pol = initPol.Pol;} Polinom operator - (); Polinom operator + (Polinom); Polinom operator - (Polinom); Polinom operator * (Polinom); Polinom operator = (Polinom); bool operator == (Polinom); A operator () (A arg); };
TPolinom.cpp template Polinom Polinom :: operator + (Polinom add) { Polinom result; Polinom *md; size_t maxdeg, mindeg; if(this->Pol.size() <= add.Pol.size()) md = &add; else md = this; maxdeg = max(this->Pol.size(),add.Pol.size()); mindeg = min(this->Pol.size(),add.Pol.size()); result.Pol.resize(maxdeg); for(unsigned int i = 0; i < mindeg; i++) result.Pol[i] = this->Pol[i] + add.Pol[i]; for(size_t i = mindeg; i < maxdeg; i++) result.Pol[i] = md->Pol[i]; return result; } template Polinom Polinom :: operator - () { Polinom result; for(uni i = 0; i < this->Pol.size(); i++) result.Pol[i] = -this->Pol[i]; return result; } template Polinom Polinom :: operator - (Polinom sub) { Polinom result;
140
result = *this + (-sub); return result;
} template Polinom Polinom :: operator * (Polinom mult) { Polinom result; result.Pol.resize(this->Pol.size() + mult.Pol.size() - 1); for(unsigned int i = 0; i < this->Pol.size(); i++) for(unsigned int j = 0; j < mult.Pol.size(); j++) result.Pol[i+j] += this->Pol[i]*mult.Pol[j]; return result; } template Polinom Polinom :: operator = (Polinom right) { this->Pol = right.Pol; return *this; } template A Polinom :: operator () (A arg) { size_t n = Pol.size(); A result = Pol[n-1]*arg + Pol[n-2]; for(size_t i = n-1; i > 1; i--) result = result*arg + Pol[i-2]; return result; } template bool Polinom :: operator == (Polinom comp) { bool result = true; if(Pol.size() != comp.Pol.size()) return false; for(unsigned int i = 0; i < Pol.size(); i++) if(Pol[i] == comp.Pol[i]) continue; else result = false; return result; }
Класс комплексных чисел Complex.h #pragma once class Complex { double Re; double Im; public: Complex():Re(0.0),Im(0.0){} Complex(double x, double y):Re(x),Im(y){} Complex(Complex& z){Re=z.Re; Im = z.Im;} ~Complex(){} double GetRe(){return Re;} double GetIm(){return Im;} void SetRe(double x){Re = x;} void SetIm(double y){Im = y;} friend Complex operator * (double friend Complex operator + (double friend Complex operator - (double friend Complex operator / (double Complex operator * (double op); Complex operator * (Complex& op); Complex operator + (double op); Complex operator + (Complex& op); Complex operator - (double op);
op1, op1, op1, op1,
Complex& Complex& Complex& Complex&
op2); op2); op2); op2);
141
Complex operator - (Complex& op); Complex operator / (double op); Complex operator / (Complex& op); void operator = (Complex& op); void operator += (Complex& op); void operator -= (Complex& op); void operator *= (Complex& op); void operator /= (Complex& op); void operator += (double op); void operator -= (double op); void operator *= (double op); void operator /= (double op); bool operator == (Complex& op); Complex operator - ();
};
double Abs(){return Re*Re + Im*Im;} double Arg(){return atan2(Im,Re);}
Complex Complex Complex Complex Complex
exp(Complex z); sin(Complex z); cos(Complex z); pow(Complex z_base, Complex z_pow); ln(Complex z);
Complex.cpp #include "stdafx.h" #include "Complex.h" Complex Complex::operator + (Complex& op) { Complex Res; Res.Re = Re + op.Re; Res.Im = Im + op.Im; return Res; } Complex Complex::operator * (Complex& op) { Complex Res; Res.Re = Re*op.Re - Im*op.Im; Res.Im = Re*op.Im + Im*op.Re; return Res; } Complex Complex::operator * (double op) { return *this*Complex(op,0); } Complex Complex::operator - () { return *this*(-1.0); } Complex operator * (double op1, Complex& op2) { return op2*op1; } Complex Complex::operator + (double op) { return *this + Complex(op,0); } Complex operator + (double op1, Complex& op2) { return op2 + op1;
142
} Complex Complex::operator / (Complex& op) { Complex Res; Res.Re = (Re*op.Re + Im*op.Im)/(op.Re*op.Re + op.Im*op.Im); Res.Im = (Im*op.Re - Re*op.Im)/(op.Re*op.Re + op.Im*op.Im); return Res; } Complex Complex::operator / (double op) { return *this/Complex(op,0); } Complex operator / (double op1, Complex& op2) { return Complex(op1,0)/op2; } Complex Complex::operator - (Complex& op) { return *this + (-op); } Complex Complex::operator - (double op) { return *this - Complex(op,0); } Complex operator - (double op1, Complex& op2) { return Complex(op1,0) - op2; } void Complex::operator = (Complex& op) { Re = op.Re; Im = op.Im; } bool Complex::operator == (Complex& op) { return Re==op.Re && Im==op.Im ? true : false; } void Complex::operator += (Complex& op) { *this = *this + op; } void Complex::operator += (double op) { *this = *this + op; } void Complex::operator -= (Complex& op) { *this = *this - op; } void Complex::operator -= (double op) { *this = *this - op; } void Complex::operator *= (Complex& op) { *this = *this*op; } void Complex::operator *= (double op) { *this = *this*op; } void Complex::operator /= (Complex& op) {
143
*this = *this/op; } void Complex::operator /= (double op) { *this = *this/op; } Complex exp(Complex z) { Complex Res; Res.SetRe(exp(z.GetRe())*cos(z.GetIm())); Res.SetIm(exp(z.GetRe())*sin(z.GetIm())); return Res; } Complex ln(Complex z) { Complex Res; Res.SetRe(log(z.Abs())); Res.SetIm(z.Arg()); return Res; } Complex pow(Complex z_base, Complex z_pow) { if(z_pow == Complex())return Complex(1,0); else return exp(z_base*ln(z_pow)); } Complex sin(Complex z) { Complex Res; Res.SetRe(sin(z.GetRe())*(exp(z.GetIm()) + exp(-z.GetIm()))/2); Res.SetIm(cos(z.GetRe())*(exp(z.GetIm()) - exp(-z.GetIm()))/2); return Res; } Complex cos(Complex z) { Complex Res; Res.SetRe(cos(z.GetRe())*(exp(z.GetIm()) + exp(-z.GetIm()))/2); Res.SetIm(-sin(z.GetRe())*(exp(z.GetIm()) - exp(-z.GetIm()))/2); return Res; }
144
Использованные материалы 1. Грегори К. Использование Microsoft Visual C++ .NET. Специальное издание – Вильямс 2002 – 784 с. 2. Утешев А.Ю. Высшая алгебра – Золотое сечение 2006 – 184 с. 3. Утешев А.Ю. Черкасов Т.М. Шапошников А.А. Цифры и шифры – СПБ: Издательство Санкт-Петербургского университета 2001 – 132 с. 4. Холзнер С. Visual C++6: Учебный курс – СПб: Питер, 2001. – 576 с. 5. Шефферд Дж. Программирование на Microsoft Visual C++ .NET – Русская редакция; СПБ: Питер 2007 – 928 с. 6. http://www.firststeps.ru/
145