автордың кітабын онлайн тегін оқу Программирование на C++. Трюки и эффекты
А. Чиртик
Программирование на C++. Трюки и эффекты (+CD)
Научный редактор Д. Романов
Художественные редакторы Н. Гринчик, М. Моисеева
Технический редактор Д. Романов
Литературный редактор Д. Романов
Художники Н. Гринчик, О. Махлина, А. Смага, А. Татарко, С. Шутов
Корректоры Н. Терех, Ю. Цеханович
Верстка А. Барцевич
А. Чиртик
Программирование на C++. Трюки и эффекты (+CD). — СПб.: Питер, 2014.
ISBN 978-5-49807-102-2
© ООО Издательство "Питер", 2014
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Введение
В мире компьютерной литературы издается большое количество книг, предназначенных для обучения разным подходам к программированию в среде Borland C++ Builder, а раз таких книг очень много, то в чем же особенность данного издания?
В представленной вашему вниманию книге вы сможете найти примеры реализации разнообразных возможностей среды: начиная от создания фильтров, предназначенных для обработки изображений и звука, и заканчивая созданием средств сканирования жесткого диска компьютера на предмет произведенных без вашего ведома изменений. Кроме того, в некоторых примерах используются функции, выходящие за пределы возможностей библиотеки компонентов Borland за счет использования Windows API.
Данная книга предназначена для программистов, обладающих хотя бы небольшим опытом работы в среде программирования Borland C++ Builder. Здесь вы не встретите описания азов программирования на языке C++ и работы в данной среде разработки, зато найдете множество интересных и, надеюсь, полезных примеров программ. В некоторых из приведенных примеров используются объектно-ориентированные возможности языка C++, поэтому вам, уважаемый читатель, чтобы полностью понять принцип работы применяемых в таких примерах алгоритмов, нужно по крайней мере знать основы построения программ с использованием объектно-ориентированного подхода.
Материал книги сгруппирован по тематике рассматриваемых трюков в восемь глав. В конце книги приведены приложения, содержимое которых может вам пригодиться при заимствовании и самостоятельной доработке примеров из данной книги.
От издательства
Ваши замечания, предложения и вопросы отправляйте по адресу электронной почты dgurski@minsk.piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На сайте издательства http://www.piter.com вы найдете подробную информацию о наших книгах.
Глава 1. Окна
•Привлечение внимания к окну
•Окно приложения
•Растягиваемые формы
•Окна нестандартной формы
•Области окна
•Настраиваемый интерфейс
•Окна других приложений
При работе в операционной системе Windows окна нам встречаются повсюду. Отсюда, собственно, и название этой операционной системы, в которой идея организации оконного пользовательского интерфейса прижилась очень хорошо.
Для пользователя понятие «окно» чаще всего означает прямоугольную область в рамке с каким-то содержимым, например полем для редактирования текстового документа и, возможно, полосами прокрутки. Для программиста все несколько интереснее: окнами могут быть как окна приложений (которые видит пользователь), так и кнопки, надписи, сами полосы прокрутки и все, что угодно, — все, что пользователь видит или не видит на экране. Только одни окна будут перекрывающимися (например, главное окно приложения), а другие — дочерними (кнопки, полосы прокрутки и т. д.). Дочерние окна располагаются поверх родительских окон, визуально подчеркивая свою принадлежность родительскому окну.
Теперь стоит оговориться, почему в книге иногда употребляется понятие «окно», а иногда — «форма». Под «окном» понимается любое окно, действительно созданное в системе. Форма — это тоже окно, но свойства формы, а также расположение на ней компонентов задаются визуально в среде разработки. Процесс создания самой формы, равно как и всех ее компонентов, скрыт от программиста. Другими словами, форма — это окно, созданное по некоторому шаблону, причем как внутреннее представление шаблона, так и способ создания окна по нему не должны волновать программиста.
На форму в среде разработки Borland C++ Builder могут быть помещены компоненты. Они могут быть как отображаемыми (надпись, кнопка, набор вкладок), так и неотображаемыми (таймер, серверный сокет и т. д.). Отображаемые компоненты (в книге также называются элементами управления) могут быть как оконными, то есть являться полноценными дочерними окнами Windows, так и не обладающими собственным окном, как, например, компонент TLabel, который отображается на форме, но собственного окна не имеет — он просто в нужный момент отображается в нужном месте формы.
В Windows окно является не только частью графического интерфейса — оно также служит для взаимодействия приложения с пользователем или системой. При возникновении какого-либо события в системе, которое затрагивает работающее приложение (например, пользователь провел указателем мыши над окном приложения, изменились системное время или дата, разрешение монитора), окно приложения получает сообщение. Получение сообщения окном представляется как вызов определенной пользовательской функции, зарегистрированной в системе как функция-обработчик сообщения окна (оконная функция).
Каждое окно принадлежит к определенному оконному классу. К одному оконному классу может принадлежать множество окон. Класс определяет свойства и способ реализации одинаковых окон (например, класс окон кнопок). Так, оконная функция одна для всех окон одного класса. В приведении более подробного описания оконных классов особой необходимости нет, тем более что здесь они не пригодятся.
В завершение несколько слов о том, как обрабатываются полученные окном сообщения. Классическая реализация оконной функции предполагает написание подобия большой конструкции switch, в каждой ветви типа case которой должна быть записана реакция на определенное сообщение. Если же для реализации оконного приложения используется библиотека классов наподобие той, что реализована в Borland, оконная функция скрывается в недрах библиотеки. Реакцией на приход определенного сообщения в таком случае является вызов одного из методов объекта, соответствующего окну — получателю сообщения. Такой метод часто называется обработчиком события, хотя его можно назвать и обработчиком сообщения. За исключением написания сложных алгоритмов, прикладной программист, реализующий приложение с пользовательским интерфейсом, по большей части занимается именно написанием нужных обработчиков событий.
Привлечение внимания к окну
Существует несколько способов привлечь внимание к окну приложения. Одними из самых простых и в то же время достаточно эффективных из неназойливых способов являются инверсия заголовка окна, требующего внимания, изменение окраски кнопки приложения на Панели задач или и то, и другое вместе. Эти способы часто используются в приложениях.
Для инверсии заголовка окна или кнопки на Панели задач никаких функций перерисовки создавать не нужно — достаточно воспользоваться простой API-функцией FlashWindowEx. Данная функция принимает единственный аргумент — указатель на структуру FLASHWINFO, объявленную следующим образом (листинг 1.1).
Листинг 1.1. Объявление структуры FLASHWINFO
typedef struct {
UINT cbSize;
HWND hwnd;
DWORD dwFlags;
UINT uCount;
DWORD dwTimeout;
} FLASHWINFO;
В табл. 1.1 приведены описания полей структуры FLASHWINFO.
Значение параметра dwFlags формируется из приведенных ниже констант с использованием операции побитового ИЛИ:
• FLASHW_CAPTION — инвертирует состояние заголовка окна;
• FLASHW_TRAY — заставляет мигать кнопку на Панели задач;
• FLASHW_ALL — реализует совместное использование операторов FLASHW_CAPTION и FLASHW_TRAY;
• FLASHW_TIMER — заставляет периодически изменяться состояние заголовка окна и/или кнопки на Панели задач вплоть до того момента, пока функция FlashWindowEx не будет вызвана с флагом FLASHW_STOP;
• FLASHW_TIMERNOFG — заставляет периодически изменяться состояние заголовка окна и/или кнопки на Панели задач до тех пор, пока окно не станет активным;
• FLASHW_STOP — восстанавливает исходное состояние окна и кнопки на Панели задач.
Таблица 1.1. Поля структуры FLASHWINFO
Поле
Тип
Назначение
cbSize
UINT
Размер структуры FLASHWINFO (для отслеживания версий)
hwnd
HWND
Дескриптор окна
dwFlags
DWORD
Набор флагов, задающий режим использования функции FlashWindowEx. Значения этого флага и их описания приведены после таблицы
uCount
UINT
Количество инверсий заголовка окна и/или кнопки на Панели задач
dwTimeout
DWORD
Время между изменениями состояния заголовка окна и/или кнопки на Панели задач. Если значение равно нулю, используется системное значение таймаута
Несмотря на необходимость заполнять поля структуры, пользоваться описываемой функцией крайне просто. Так, в листинге 1.2 приведена функция, инвертирующая заголовок окна (hWnd) и его кнопки на Панели задач с интервалом, определяемым параметром time_out в миллисекундах, определенное количество раз (параметр count).
Листинг 1.2. Инвертирование заголовка окна и кнопок на Панели задач
void FlashWindow( HWND hWnd, UINT count, DWORD time_out )
{
FLASHWINFO flash_info = {0};
flash_info.cbSize = sizeof(flash_info);
flash_info.hwnd = hWnd;
flash_info.uCount = count;
flash_info.dwTimeout = time_out;
flash_info.dwFlags = FLASHW_ALL;
::FlashWindowEx( &flash_info );
}
Чтобы использовать приведенную в листинге 1.2 функцию, достаточно определиться с интервалом и количеством «миганий» и получать дескриптор окна, например, следующим образом:
FlashWindow( pForm->Handle, 3, 100 );
Однако в этом случае не изменяется окраска кнопки на Панели задач. Дело в том, что главным окном приложения при использовании Borland C++ Builder (как и Delphi) является не какая-нибудь их форма, а еще одно окно, которое видимо, но имеет нулевой размер. Дескриптором этого окна является как раз значение свойства Handle объекта Application. Тогда одновременную инверсию заголовка окна и кнопки на Панели задач можно организовать так:
FlashWindow( pForm->Handle, 3, 100 );
FlashWindow( Application->Handle, 3, 500 );
Окно приложения
Как было сказано в предыдущем разделе, оконное приложение, реализованное в Borland C++ Builder, имеет окно, которое не отображается, но все же играет важную роль. Это окно можно очень просто увидеть, для чего достаточно задать ему ненулевой размер. Например, так, как показано в листинге 1.3.
Листинг 1.3. Отображение окна приложения
void __fastcall TForm1::Button1Click(TObject *Sender)
{
::SetWindowPos(Application->Handle, 0, 0, 0, 300, 200,
SWP_NOZORDER | SWP_NOMOVE);
}
Рис. 1.1. Окно приложения
Обратите внимание, что текст окна (рис. 1.1) и является текстом кнопки на Панели задач. Этот текст может быть легко изменен с помощью свойства Title объекта Application.
Изменяя видимость (не размер, а именно стиль видимо/невидимо) окна приложения, можно с легкостью скрывать или отображать кнопку на Панели задач. Так, скрыть или показать окно приложения, а с ним и кнопку на Панели задач можно следующим образом (листинг 1.4).
Листинг 1.4. Скрытие и отображение кнопки на Панели задач
void __fastcall TForm1::Button3Click(TObject *Sender)
{
::ShowWindow(Application->Handle, SW_HIDE); //Скрытие
//кнопки
}
void __fastcall TForm1::Button4Click(TObject *Sender)
{
::ShowWindow(Application->Handle, SW_SHOW); //Отображение
//кнопки
}
Растягиваемые формы
При создании сложных форм бывает очень полезно дать пользователю возможность изменять их размер и пропорции. Но как быть с компонентами на растягиваемой или, наоборот, уменьшаемой форме? В данном случае разработчики, использующие среду программирования Borland C++ Builder, могут радоваться: у них в распоряжении есть встроенная возможность привязки компонентов к сторонам формы (так называемый механизм якорей).
Взгляните на группу свойств для задания якорей компонента, показанную на рис. 1.2.
Рис. 1.2. Настройка якорей компонента
Четыре булевых значения этой группы предназначены для задания привязки краев компонента к краям формы:
• akLeft — левый край компонента сохраняет постоянное расстояние до левого края формы (по умолчанию);
• akTop — верхний край компонента сохраняет постоянное расстояние до верхнего края формы (по умолчанию);
• akRight — правый край компонента сохраняет постоянное расстояние до правого края формы;
• akBottom — нижний край компонента сохраняет постоянное расстояние до нижнего края формы.
Таким образом, несколькими щелчками кнопки мыши можно легко создать форму, компоненты которой изменяют свои размеры автоматически при ее растягивании. Пример такой формы приведен на рис. 1.3.
Рис. 1.3. Форма до и после изменения размера
Для компонентов показанной на рисунке формы назначены якоря, описанные в табл. 1.2.
Таблица 1.2. Якоря формы, показанной на рис. 1.3
Компонент
Якоря
Edit1, Edit2, Edit2
akLeft, akRight, akTop
Button1 (кнопка OK), Button2 (кнопка Отмена)
akRight, akTop
Image1 (единственный рисунок на форме)
akLeft, akRight, akTop, akBottom
При применении описанного здесь механизма стоит учесть еще и возможность задания ограничений на минимальный и максимальный размер как формы, так и любого из ее компонентов. Группа свойств для задания ограничений размера показана на рис. 1.4.
Рис. 1.4. Ограничения размеров компонента
При нулевых значениях ограничения на размер формы или компонента не накладываются. Если задать, например, максимальное значение ширины, то форму или компонент нельзя будет растянуть до размера, превышающего заданный. При этом если это ограничение задано для одного из компонентов формы и применяется упомянутый выше механизм якорей, то размер формы станет невозможно увеличивать, как только будет достигнута максимальная ширина одного из компонентов.
Окна нестандартной формы
Ниже приведены несколько примеров, демонстрирующих способы изменения внешнего вида приложений. Речь идет о создании окон нестандартных непрямоугольных форм. Но сначала ознакомьтесь немного с теорией, чтобы было понятно, как все работает.
Создание и использование регионов
Рассмотренные далее эффекты основаны на использовании регионов (областей) отсечения — в общем случае сложных геометрических фигур, ограничивающих область прорисовывания окна. По умолчанию окна (в том числе и окна элементов управления) имеют область отсечения, заданную прямоугольным регионом с высотой и шириной, равной высоте и ширине самого окна.
Однако использование регионов прямоугольных форм для указания областей отсечения совсем не обязательно, в чем могут убедиться пользователи Windows XP. Пример использования отсечения по заданному непрямоугольному региону при рисовании произвольного окна наглядно продемонстрирован на рис. 1.5. На этом рисунке буквой «а» обозначен внешний вид, который имела бы форма, будь область отсечения прямоугольной. Буквой «б» обозначен применяемый регион, формирующий область отсечения. Буквой «в» обозначен вид формы, полученный в результате рисования с отсечением по границам заданного региона.
Теперь вместо рассмотрения подробностей обработки регионов отсечения, которые имеют место в функциях рисования, лучше сразу перейти к рассмотрению операций, позволяющих создавать, удалять и модифицировать регионы.
Создание и удаление регионов
Создавать несложные регионы различной формы можно с помощью следующих API-функций:
HRGN CreateRectRgn( int x1, int y1, int x2, int y2);
HRGN CreateEllipticRgn( int x1, int y1, int x2, int y2);
HRGN CreateRoundRectRgn( int x1, int y1, int x2, int y2, int w, int h);
Все перечисленные здесь и ниже функции создания регионов возвращают дескриптор GDI-объекта «регион». Этот дескриптор впоследствии передается в различные функции, работающие с регионами.
а б в
Рис. 1.5. Рисование окна по заданному региону
Итак, первая из приведенных функций (CreateRectRgn) предназначена для создания регионов прямоугольной формы. Параметры этой функции необходимо толковать следующим образом:
• x1 и y1 — горизонтальная и вертикальная координаты левой верхней точки прямоугольника;
• x2 и y2 — горизонтальная и вертикальная координаты правой нижней точки прямоугольника.
Следующая функция (CreateEllipticRgn) предназначена для создания региона эллиптической формы. Параметры этой функции — координаты прямоугольника (аналогично CreateRectRgn), в который вписывается эллипс.
Третья функция (CreateRoundRectRgn) создает регион — прямоугольник со скругленными углами. При этом первые четыре параметра функции аналогичны соответствующим параметрам функции CreateRectRgn. Параметры h и w — ширина и высота сглаживающих углы эллипсов (рис. 1.6).
Рис. 1.6. Скругление углов прямоугольного региона функцией CreateRoundRectRgn
С помощью трех приведенных функций, если нужно, можно создавать регионы даже очень сложной формы. Это достигается посредством использования многочисленных операций над простыми регионами, как в приведенном далее примере создания региона по битовому шаблону. Однако существует еще одна несложная функция, которая позволяет сразу создавать регионы-многоугольники по координатам вершин многоугольников:
HRGN CreatePolygonRgn(const POINT *points, int count, int fillmode);
Функция CreatePolygonRgn принимает следующие параметры:
• points — указатель на массив структур типа POINT; каждый элемент массива описывает одну вершину многоугольника; координаты не должны повторяться;
• count — количество элементов в массиве, на который указывает параметр points;
• fillmode — режим заливки региона (в данном случае определяет, попадает ли внутренняя область многоугольника в заданный регион).
Параметр FillMode может принимать значения WINDING (попадает любая внутренняя область) или ALTERNATE (попадает внутренняя область, если она находится между четной и следующей нечетной сторонами многоугольника).
Поскольку регион является GDI-объектом (подробнее о данных объектах читайте в гл. 2), то для его удаления, если он не используется системой, применяется функция удаления GDI-объектов DeleteObject:
BOOL DeleteObject(HGDIOBJ hObj);
Единственным параметром этой функции является дескриптор GDI-объекта, который после вызова DeleteObject становится недействительным.
Регион как область отсечения при рисовании окна
Обычно необходимо удалять регион, если он не используется системой. Однако после того как регион назначен окну в качестве области отсечения, удалять его не следует. Функция назначения региона окну имеет следующий вид:
int SetWindowRgn(HWND hWnd, HRGN hRgn, BOOL bRedraw);
Функция возвращает 0, если произвести операцию не удалось, и ненулевое значение в противном случае. Параметры функции SetWindowRgn следующие:
• hWnd — дескриптор окна, для которого устанавливается область отсечения (свойство Handle формы или элемента управления);
• hRgn — дескриптор региона, назначаемого в качестве области отсечения (в простейшем случае является значением, возвращенным одной из функций создания региона);
• bRedraw — флаг перерисовки окна после назначения новой области отсечения; для видимых окон обычно используется значение true, для невидимых — false.
Чтобы получить копию региона, формирующего область отсечения окна, можно использовать API-функцию GetWindowRgn:
int GetWindowRgn(HWND hWnd, HRGN hRgn);
Первый параметр функции — дескриптор рассматриваемого окна. Второй параметр — дескриптор предварительно созданного региона, который в случае успеха модифицируется функцией GetWindowRgn так, что становится копией региона, формирующего область отсечения окна. Описания целочисленных констант — возможных возвращаемых значений функции — приведены ниже:
• NULLREGION — пустой регион;
• SIMPLEREGION — регион в форме прямоугольника;
• COMPLEXREGION — регион сложнее, чем прямоугольник;
• ERROR — при выполнении функции возникла ошибка (либо окну неверно задана область отсечения).
Ниже приведен пример использования функции GetWindowRgn (предполагается, что приведенный ниже код является телом одного из методов класса формы) (листинг 1.5).
Листинг 1.5. Использование функции GetWindowRgn
HRGN rgn = ::CreateRectRgn(0,0,0,0); //Первоначальная форма региона не важна
if ( ::GetWindowRgn(Handle, rgn) != ERROR )
{
//Операции с копией региона, формирующего область отсечения
//окна
//...
}
::DeleteObject(rgn); //Использовалась копия региона, которую
//нужно удалить
//(здесь или в ином месте, но
//самостоятельно)
Операции над регионами
При описании функций создания регионов упоминалось о возможности комбинирования регионов для получения регионов сложных форм. Пришло время кратко рассмотреть операции над регионами. Все операции по комбинированию регионов осуществляются с помощью функции CombineRgn:
int CombineRgn(HRGN dest, HRGN src1, HRGN src2, int mode);
Параметры этой функции имеют следующий смысл:
• dest — регион (предварительно созданный), предназначенный для сохранения результата;
• src1, src2 — регионы-аргументы выполнения функции;
• mode — тип операции над регионами.
Результат выполнения функции CombineRgn при различных значениях параметра mode показан в табл. 1.3. В таблице приведены все значения параметра mode, кроме RGN_COPY, при котором происходит простое копирование региона, заданного параметром src1.
Таблица 1.3. Операции, выполняемые функцией CombineRgn
Значение mode
Операция
Пример
RGN_AND
Пересечение регионов
RGN_OR
Объединение регионов
RGN_DIFF
Разность регионов (возвращает часть региона src1, не являющуюся частью src2)
RGN_XOR
Так называемое исключающее ИЛИ (объединение непересекающихся частей регионов src1 и src2)
Внимательно рассчитывая координаты точек регионов-аргументов, можно создавать регионы самых причудливых форм.
Наконец, после теоретического отступления ознакомьтесь с некоторыми примерами создания и преобразования регионов.
Непрямоугольная форма
Первым и самым простым примером является создание стандартного непрямоугольного региона и назначение его в качестве области отсечения формы. В листинге 1.6 продемонстрировано, как можно задать область отсечения в форме эллипса.
Листинг 1.6. Создание эллиптического региона
void __fastcall TForm2::optEllipseClick(TObject *Sender)
{
//Создание эллиптического региона формы
HRGN rgn = ::CreateEllipticRgn(0, 0, Width, Height);
::SetWindowRgn(Handle, rgn, true);
}
В свою очередь, создание и назначение областью отсечения региона в форме скругленного прямоугольника приводится в листинге 1.7.
Листинг 1.7. Создание региона в форме прямоугольника с закругленными краями
void __fastcall TForm2::optRoundRectClick(TObject *Sender)
{
//Создание региона-прямоугольника с закругленными краями
HRGN rgn = ::CreateRoundRectRgn(0, 0, Width, Height, Width/5, Height/5);
::SetWindowRgn(Handle, rgn, true);
}
И, наконец, создание региона в форме не слишком сложного многоугольника приведено в листинге 1.8.
Листинг 1.8. Создание многоугольного региона
void __fastcall TForm2::optPolygonClick(TObject *Sender)
{
//Создание региона по набору точек
int w = Width, h = Height;
POINT pts[] =
{
{0,0}, {w,0},
{4*w/5,h/6}, {w,2*h/6},
{4*w/5,3*h/6}, {w,4*h/6},
{4*w/5,5*h/6}, {w,h},
{0,h}
};
int pts_count = sizeof(pts)/sizeof(POINT);
HRGN rgn = ::CreatePolygonRgn(pts, pts_count, WINDING);
::SetWindowRgn(Handle, rgn, true);
}
Изменения, происходящие с формой при изменении области отсечения, продемонстрированы на рис. 1.7.
Это наиболее простые случаи применения регионов. Ниже приведен более интересный пример создания комбинированного региона.
Рис. 1.7. Варианты применения простой непрямоугольной области отсечения окна
«Дырявая» форма
Теперь более сложный пример, демонстрирующий проведение операций над регионами (а точнее, многократное применение одной операции разности регионов). Приведенная ниже программа представляет собой подобие простого графического редактора. В нем с помощью указателя мыши можно указать контур будущего региона отсечения. Внешний вид формы, используемой в рассматриваемом примере, приведен на рис. 1.8.
Переключателями в верхней части формы может выбираться вид создаваемого региона: эллипс, прямоугольник или скругленный прямоугольник, то есть простые регионы, требующие две опорные точки. При щелчке кнопкой мыши точка, в которой находится указатель, становится первой точкой региона. Точка, в которой кнопка мыши будет отпущена, становится второй точкой региона. Во время перемещения курсора при нажатой кнопке мыши на форме отображается контур будущего региона. Именно в таком состоянии показана форма на рис. 1.8.
Рис. 1.8. «Дырявая» форма
Каждый новый регион исключается от текущего региона, используемого в качестве области отсечения формы. Таким образом, через некоторое время работы с программой в форме образуется приличное количество «дырок». Отсюда и название примера.
Рассмотрение реализации примера начнется с описания основной функции — функции, трансформирующей регион, код которой приведен в листинге 1.9.
Листинг 1.9. Создание «дырки» в форме
void __fastcall TForm3::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
if ( Button == mbLeft && m_drawing )
{
m_pt2.x = X;
m_pt2.y = Y;
//Переведем координаты точек клиентской области
//в координаты региона формы
TRect wndRect;
::GetWindowRect(Handle, &wndRect);
TPoint clientOrg(0, 0);
clientOrg = ClientToScreen(clientOrg);
int dx = clientOrg.x - wndRect.Left;
int dy = clientOrg.y - wndRect.Top;
TPoint pt1(m_pt1.x + dx, m_pt1.y + dy);
TPoint pt2(m_pt2.x + dx, m_pt2.y + dy);
//Создаем регион-«дырку»
m_drawing = false;
HRGN hRgn = NULL;
if (m_ht == htEllipse)
{
hRgn = ::CreateEllipticRgn(pt1.x, pt1.y, pt2.x, pt2.y);
}
else if (m_ht == htRect)
{
hRgn = ::CreateRectRgn(pt1.x, pt1.y, pt2.x, pt2.y);
}
else
{
hRgn = ::CreateRoundRectRgn(pt1.x, pt1.y, pt2.x, pt2.y,
abs(pt2.x - pt1.x)/5, abs(pt2.y - pt1.y)/5);
}
//Комбинируем с регионом формы
HRGN hWndRgn = ::CreateRectRgn(0,0,0,0);
::GetWindowRgn(Handle, hWndRgn);
::CombineRgn(hWndRgn, hWndRgn, hRgn, RGN_DIFF);
::SetWindowRgn(Handle, hWndRgn, true);
::DeleteObject(hRgn);
Refresh();
}
}
Суть всего, что совершается кодом, приведенным в листинге 1.9, очень проста. После завершения выделения области, которую необходимо удалить, остается в распоряжении пара точек (TPoint) m_pt1 и m_pt2. Затем анализируется вид региона, который хочет создать пользователь по значению переменной m_ht. Далее по координатам двух точек m_pt1 и m_pt2 создается регион нужного вида (для скругленного прямоугольника полуоси скругляющего эллипса принимаются равными пятой части ширины и высоты прямоугольника). Создается регион, использующийся как область отсечения формы, в нем вырезается «дырка» с помощью функции CombineRgn, после чего полученная разность регионов назначается областью отсечения формы.
В листинге 1.9 заключена основная идея рассматриваемой программы. Но, чтобы все действительно работало так, как описано выше, потребовалось написать несколько дополнительных методов и назначить несколько переменных. Ниже кратко описано, что нужно сделать, чтобы программа начала работать.
С помощью приведенного в листинге 1.10 кода осуществляется объявление формы, по которому можно судить о задействованных для реализации примера обработчиках событий.
Листинг 1.10. Объявление формы региона
class TForm3 : public TForm
{
__published: // IDE-managed Components
TRadioButton *optEllipse;
TRadioButton *optRect;
TRadioButton *optRoundRect;
void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall optEllipseClick(TObject *Sender);
void __fastcall optRectClick(TObject *Sender);
void __fastcall optRoundRectClick(TObject *Sender);
void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X,
int Y);
void __fastcall FormPaint(TObject *Sender);
void __fastcall FormCreate(TObject *Sender);
private: // User declarations
enum THoleType{ htEllipse, htRect, htRoundRect };
THoleType m_ht; //Тип «дырки», вырезаемой в форме
bool m_drawing; //Если true, то начато построение региона-
//«дырки»
TPoint m_pt1, m_pt2; //Начальная и конечная точки для
//создания
//региона-«дырки»
public: // User declarations
__fastcall TForm3(TComponent* Owner);
};
Из листинга 1.10 также видно, что три переключателя, используемых для указания вида региона-дырки, называются optEllipse, optRect и optRoundRect для эллипса, прямоугольника и скругленного прямоугольника соответственно. При включении одного из этих переключателей переменной m_ht присваивается значение htEllipse, htRect или htRoundRect соответственно.
В листинге 1.11 приведены описания обработчиков щелчков кнопкой мыши и перемещений указателя.
Листинг 1.11. Обработка щелчков кнопкой мыши и перемещения указателя
void __fastcall TForm3::FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
if ( Button == mbLeft )
{ //Начало построения контура нового региона отсечения
m_drawing = true;
m_pt1.x = m_pt2.x = X;
m_pt1.y = m_pt2.y = Y;
}
}
void __fastcall TForm3::FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y)
{
if (m_drawing)
{
m_pt2.x = X;
m_pt2.y = Y;
Refresh(); //Перерисовка контура будущего региона отсечения
}
}
Чтобы в ходе перемещения указателя были видны контуры будущего региона отсечения, реализован следующий обработчик перерисовки формы (листинг 1.12).
Листинг 1.12. Создание контуров региона
void __fastcall TForm3::FormPaint(TObject *Sender)
{
if (m_drawing)
{
if (m_ht == htEllipse)
{
Canvas->Ellipse(m_pt1.x, m_pt1.y, m_pt2.x, m_pt2.y);
}
else if (m_ht == htRect)
{
Canvas->Rectangle(m_pt1.x, m_pt1.y, m_pt2.x, m_pt2.y);
}
else
{
Canvas->RoundRect(m_pt1.x, m_pt1.y, m_pt2.x, m_pt2.y,
abs(m_pt2.x - m_pt1.x)/5, abs(m_pt2.y - m_pt1.y)/5);
}
}
}
И последнее — в ряде случаев первый вызов функции GetWindowRgn, задействованной в листинге 1.9, может возвращать пустой регион, потому необходимо явно определить первоначальный регион для области отсечения при создании формы так, как показано в листинге 1.13.
Листинг 1.13. Задание первоначального региона для области отсечения
void __fastcall TForm3::FormCreate(TObject *Sender)
{
HRGN hWndRgn = ::CreateRectRgn(0, 0, Width, Height);
::SetWindowRgn(Handle, hWndRgn, true);
}
Это все, что потребовалось для реализации рассматриваемого примера.
Использование шаблона
В завершение рассмотрим еще один небольшой пример применения операций над регионами. Теперь регион для области отсечения формы будет вырезаться по битовому шаблону. Шаблоном будет служить растровое изображение. Любой пиксел изображения с цветом, отличным от цвета фона, будет включен в регион, а на месте пиксела, цвет которого соответствует цвету фона, будет «дырка».
Пример формы, регион области отсечения которой составлен по битовому шаблону, показан на рис. 1.9. Для пущей выразительности на форму еще и наложен рисунок, по которому была составлена область отсечения.
Рис. 1.9. Регион, вырезанный по растровому шаблону
В листинге 1.14 приведена главная функция этого примера, позволяющая создавать регион по изображению, хранимому в значении параметра pict. В результирующий регион включаются все точки, цвет которых не равен значению параметра backcolor (цвет фона).
Листинг 1.14. Формирование региона по растровому изображению
HRGN RegionFromPicture(TPicture *pict, TColor backcolor)
{
HRGN resRgn = ::CreateRectRgn(0, 0, 0, 0); //Результирующий регион
HRGN rgn;
int x, y, xFirst;
//Анализируем каждую скан-линию рисунка (по горизонтали)
for (y = 0; y < pict->Height; y++)
{
x = 0;
while (x < pict->Width)
{
if (pict->Bitmap->Canvas->Pixels[x][y] != backcolor)
{
xFirst = x;
x++;
//Определим часть линии, окрашенной не цветом фона
while (x < pict->Width &&
pict->Bitmap->Canvas->Pixels[x][y] != backcolor) x++;
//Создаем регион для части скан-линии и добавляем его
//к результирующему
rgn = ::CreateRectRgn(xFirst, y, x-1, y+1);
::CombineRgn(resRgn, resRgn, rgn, RGN_OR);
::DeleteObject(rgn);
}
x++;
}
}
return resRgn;
}
Обратите внимание, что для реализации примера оказалось достаточно одного вида операций над регионами — операции ИЛИ. Единственной оптимизацией в этом примере явилось то, что полученный на предыдущих этапах регион соединяется не с каждой новой точкой рисунка подходящего цвета, а с частью скан-линии, состоящей из таких точек.
Для назначения созданного по рисунку региона областью отсечения формы реализован простой обработчик события FormCreate, описание которого приведено в листинге 1.15.
Листинг 1.15. Обработчик события FormCreate
void __fastcall TForm4::FormCreate(TObject *Sender)
{
::SetWindowRgn( Handle,
RegionFromPicture(m_pict, (TColor)RGB(255,255,255)),
true );
}
Для правильной инициализации и удаления объекта TPicture, указатель на который хранится в переменной m_pict (элемент класса TForm4), достаточно написать следующий код (листинг 1.16).
Листинг 1.16. Создание, загрузка и удаление изображения
__fastcall TForm4::TForm4(TComponent* Owner)
: TForm(Owner)
{
m_pict = new TPicture;
m_pict->LoadFromFile("шаблон.bmp");
}
__fastcall TForm4::~TForm4()
{
delete m_pict;
}
Если же захочется наложить на форму изображение, по которому была сформирована область отсечения, можно применить обработчик события FormPaint, реализованный в листинге 1.17.
Листинг 1.17. Перенос изображения на форму
void __fastcall TForm4::FormPaint(TObject *Sender)
{
//Переведем координаты точек клиентской области в
//координаты
//региона формы
TRect wndRect;
::GetWindowRect(Handle, &wndRect);
TPoint clientOrg(0, 0);
clientOrg = ClientToScreen(clientOrg);
int dx = clientOrg.x - wndRect.Left;
int dy = clientOrg.y - wndRect.Top;
//Собственно рисование
this->Canvas->Draw(-dx, -dy, m_pict->Bitmap);
}
Вся сложность в наложении изображения в точности на область отсечения формы кроется в переводе координат левой верхней точки клиентской области формы в координаты, начало которых находится в левом верхнем углу формы (в координаты формы). Ведь регион для области отсечения создавался в системе координат формы, а выводить изображение приходится в координатах клиентской области формы.
Области окна
Как вы, наверное, не раз замечали, многие окна имеют специальные области, такие как строка заголовка, кнопка системного меню (значок в левой части заголовка), границы окна и др. Каждый раз, когда указатель мыши перемещается над окном, окну посылается сообщение WM_NCHITTEST. Так система узнает, над какой именно областью окна находится указатель мыши.
Приведенный ниже трюк позволяет подменить расположение границ и строки заголовка окна. Достигается это посредством реализации собственного обработчика события WMNCHitTest, код которого приведен в листинге 1.18.
Листинг 1.18. Подмена областей окна
void __fastcall TForm1::WMNCHitTest(Messages::TWMNCHitTest &Message)
{
if ( InControl(borderBottom,Message.Pos) )
Message.Result = HTBOTTOM;
else if ( InControl(borderTop,Message.Pos) )
Message.Result = HTTOP;
else if ( InControl(borderLeft,Message.Pos) )
Message.Result = HTLEFT;
else if ( InControl(borderRight,Message.Pos) )
Message.Result = HTRIGHT;
else if ( InControl(borderTopLeft, Message.Pos) )
Message.Result = HTTOPLEFT;
else if ( InControl(borderBottomLeft, Message.Pos) )
Message.Result = HTBOTTOMLEFT;
else if ( InControl(borderTopRight, Message.Pos) )
Message.Result = HTTOPRIGHT;
else if ( InControl(borderBottomRight, Message.Pos) )
Message.Result = HTBOTTOMRIGHT;
else if ( InControl(title, Message.Pos) )
Message.Result = HTCAPTION;
else // Message.Result = HTCLIENT;
Message.Result = ::DefWindowProc(Handle, Message.Msg, 0,
65536 * Message.YPos + Message.XPos);
}
Чтобы приведенный в листинге 1.18 код начал работать, в объявление класса формы TForm1 достаточно добавить следующий текст (листинг 1.19).
Листинг 1.19. Объявление обработчика события WMNCHitTest и связывание с ним сообщения WM_NCHITTEST
void __fastcall WMNCHitTest(Messages::TWMNCHitTest &Message);
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_NCHITTEST, TWMNCHitTest, WMNCHitTest);
END_MESSAGE_MAP(TForm);
Суть трюка такова: на форме (рис. 1.10) размещены графические примитивы (TShape), которые в последующем будут считаться частями рамки или строкой заголовка окна. С помощью обработчика WMNCHitTest проверяется, над каким компонентом формы находится указатель мыши, и возвращается код соответствующей области. Таким образом, размер формы можно изменить, потянув за один из компонентов TShape, формирующих подобие рамки окна, а переместить форму можно за скругленный прямоугольник с надписью Область заголовка окна.
Рис. 1.10. «Новые» области окна
Для упрощения проверки положения указателя мыши предусмотрена функция, код которой приведен в листинге 1.20.
Листинг 1.20. Проверка принадлежности точки компоненту
BOOL InControl(TControl *pCtrl, TSmallPoint pt)
{
TRect rc = pCtrl->ClientRect;
TPoint point = pCtrl->ScreenToClient(TPoint(pt.x, pt.y));
return ::PtInRect(&rc, point);
}
Единственная задача, решаемая данной функцией, — перевод экранных координат курсора в координаты клиентской области компонента.
В рассмотренном здесь примере вручную задается расположение лишь некоторых областей, которые могут располагаться в окне. Для определения остальных областей используется обработчик сообщений по умолчанию DefWindowProc (API-функция). Ниже приведен полный список значений, которые могут возвращаться при обработке события WMNCHitTest:
• HTBORDER — возвращается, если указатель мыши находится над границей окна (при неизменяемом размере окна);
• HTBOTTOM, HTTOP, HTLEFT, HTRIGHT — значения возвращаются, если указатель находится над нижней, верхней, левой или правой границей окна соответственно (размер окна можно изменить, «потянув» за границу);
• HTBOTTOMLEFT, HTBOTTOMRIGHT, HTTOPLEFT, HTTOPRIGHT — возвращаются, если указатель находится в левом нижнем, правом нижнем, левом верхнем или правом верхнем углу окна (размер окна можно изменять по диагонали);
• HTSIZE, HTGROWBOX — значения возвращаются, если указатель находится над областью, предназначенной для изменения размера окна по диагонали (обычно в правом нижнем углу окна);
• HTCAPTION — возвращается, если указатель находится над строкой заголовка окна (удерживая нажатой кнопку мыши, окно можно перемещать за эту область);
• HTCLIENT — значение возвращается, если указатель находится над клиентской областью окна;
• HTCLOSE — возвращается, если указатель находится над кнопкой закрытий окна;
• HTHELP — значение возвращается, если указатель находится над кнопкой вызова контекстной справки;
• HTREDUCE, HTMINBUTTON — возвращаются, если указатель находится над кнопкой минимизации окна;
• HTZOOM, HTMAXBUTTON — значения возвращаются, если указатель находится над кнопкой максимизации окна;
• HTMENU — возвращается, если указатель находится над полоской меню окна;
• HTSYSMENU — значение возвращается, если указатель находится над иконкой окна (используется для вызова системного меню);
• HTHSCROLL, HTVSCROLL — возвращаются, если указатель находится над вертикальной или горизонтальной полоской прокрутки соответственно;
• HTTRANSPARENT — если возвращается это значение, то сообщение пересылается окну, находящемуся под данным окном (окна должны принадлежать одному потоку);
• HTNOWHERE — указатель не находится над какой-либо из областей окна (например, на границе между окнами);
• HTERROR — то же, что и HTNOWHERE, только при возврате этого значения обработчик по умолчанию (DefWindowProc) воспроизводит системный сигнал (beep), сообщая об ошибке.
Настраиваемый интерфейс
В этом разделе будут рассмотрены два примера реализации гибкого пользовательского интерфейса: использования технологии Drag & Dock и создания формы с изменяемым расположением компонентов.
Стыкуемые формы
Технология стыковки Drag & Dock позволяет легко создавать приложения, внешний вид которых в определенных пределах может изменяться пользователем. Разработчики, использующие среду Borland C++ Builder, могут радоваться тому, что возможности стыковки уже встроены в формы и некоторые другие компоненты, а потому для использования технологии стыковки остается настроить лишь пару параметров и написать пару строк кода.
За поведение компонентов и форм в процессе стыковки отвечают два следующих свойства:
• DockSite — принимает значение true, если компонент может быть портом стыковки, и false, если нет;
• DragKind — задает вид компонента при перемещении (значение dkDrag для простого перемещения и dkDock для перемещения при стыковке).
Ниже приведен пример стыковки форм. Приложение состоит из трех форм: порта стыковки (Form1) и двух стыкуемых к порту форм (Form2 и Form3). Значения свойств, относящихся к стыковке, заданы следующим образом:
• Form1 — DockSite = true, DragKind = dkDrag;
• Form2, Form3 — DockSite = false, DragKind = dkDock.
Внешний вид форм до и после стыковки приведен на рис. 1.11.
В данном примере стыковку форм можно отменить или включить в любой момент. Для этого в форме-порту стыковки (Form1) предусмотрено меню Панели. Для отмены стыковки используется пункт меню Панели-Разбросать, а для включения (принудительной стыковки форм) — пункт меню Панели-Прикрепить. Обработчики для этих пунктов меню приведены в листинге 1.21.
Листинг 1.21. Отмена стыковки и принудительная стыковка
void __fastcall TForm1::mnuUndockClick(TObject *Sender)
{
//Открепляем панели
Form2->ManualDock(NULL);
Form3->ManualDock(NULL);
}
void __fastcall TForm1::mnuDockClick(TObject *Sender)
{
//Помещение панелей (двух других форм) на эту форму
Form2->ManualDock(this);
Form3->ManualDock(this);
}
Перемещаемые компоненты
Рассматриваемый здесь пример демонстрирует возможность изменения расположения компонентов на форме во время выполнения программы. Пример немного искусственный и может быть реализован только для компонентов, имеющих свойство Handle (то есть являющихся полноценными окнами). К слову, компонент TLabel таким компонентом не является, но эти компоненты вполне можно заменять аналогичными (к примеру, вместо TLabel использовать TEdit, настроенный только для чтения).
На рис. 1.12 показан внешний вид формы при первом запуске приложения.
Допустим, необходимо изменить расположение компонентов, заданное разработчиком. Для этого выберите команду меню Перемещение-Включить. В результате форма примет вид, показанный на рис. 1.13.
Рис. 1.12. Исходный вид формы
Перемещая окна компонентов и изменяя их размер, а также размер формы, можно создать более удобный вид интерфейса (рис. 1.14).
Рис. 1.13. Вид формы после включения функции перемещения компонентов
Для завершения нужно закрепить компоненты на форме, выполнив команду меню Перемещение-Выключить. В результате форма примет окончательный вид (рис. 1.15), причем расположение и размер ее компонентов сохранятся при выходе из приложения, и при последующих запусках форма будет принимать заданный ранее вид.
Реализация такого интерфейса на самом деле не представляет никакой сложности. Внешний вид и поведение окна (возможность его перемещения) во многом определяются стилем, который приписан этому окну. Так, окна компонентов формы часто имеют стиль WS_CHILD, WS_VISIBLE и еще несколько дополнительных стилей, но не имеют рамки и заголовка из-за отсутствия соответствующих стилей. Но что мешает добавить стили, обеспечивающие появление рамки и строки заголовка? Собственно, в этом и кроется суть рассматриваемого здесь трюка. Чтобы сделать доступной возможность перемещения компонентов формы, окнам этих компонентов нужно добавить стили WS_OVERLAPPED, WS_THICKFRAME и WS_CAPTION, а также расширенный стиль WS_EX_TOOLWINDOW, позволяющий задать размер строки заголовка, как у панелей инструментов. Чтобы отключить возможность перемещения, эти стили нужно удалить.
Рис. 1.14. Вид формы после перемещения компонентов
Рис. 1.15. Окончательный вид формы
В листинге 1.22 приведен пример реализации функции включения перемещения окна, заданного дескриптором hWnd (свойство Handle оконного компонента).
Листинг 1.22. Включение возможности перемещения компонента
void MakeMovable(HWND hWnd)
{
//Разрешаем перемещение элемента управления
DWORD style = ::GetWindowLong(hWnd, GWL_STYLE);
style |= WS_OVERLAPPED | WS_THICKFRAME | WS_CAPTION;
::SetWindowLong(hWnd, GWL_STYLE, style);
DWORD exstyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
exstyle |= WS_EX_TOOLWINDOW;
::SetWindowLong(hWnd, GWL_EXSTYLE, exstyle);
//Сделаем так, чтобы положение клиентской области
//получившегося окна
//совпадало с первоначальным положением элемента
//управления.
//Расчет размера окна
TRect rc;
::GetWindowRect(hWnd, &rc);
TPoint pt1(rc.left, rc.top), pt2(rc.right, rc.bottom);
::ScreenToClient(::GetParent(hWnd), &pt1);
::ScreenToClient(::GetParent(hWnd), &pt2);
rc.left = pt1.x;
rc.top = pt1.y;
rc.right = pt2.x;
rc.bottom = pt2.y;
::AdjustWindowRectEx(&rc, style, false, exstyle);
//Перемещение окна
DWORD flags = SWP_DRAWFRAME | SWP_NOZORDER;
::SetWindowPos(hWnd, NULL, rc.Left, rc.Top,
rc.Right - rc.Left, rc.Bottom - rc.Top, flags);
}
Легко заметить, что в листинге 1.22 реализация основной идеи трюка занимает меньшую часть функции. Большая часть функции, как ни странно, отведена под вспомогательный код, рассчитывающий координаты места расположения получаемого окна с рамкой, которые должны быть такими, чтобы клиентская область этого окна находилась в точности там, где был расположен компонент. Таким образом удается избежать проблемы с ограничениями, возникающей при изменении размера компонента (окно с используемой в данном случае тонкой рамкой имеет минимальный предел ширины и высоты). Кроме того, при использовании такого метода реализации компонент во время перемещения выглядит точно так же, как и после выключения возможности перемещения.
Ниже в листинге 1.23 приведен текст реализации функции, позволяющей отключить возможность перемещения компонента. Она также принимает в качестве параметра дескриптор окна hWnd, перемещение которого следует завершить.
Листинг 1.23. Отключение возможности перемещения компонента
void MakeUnmovable(HWND hWnd)
{
//Запомним положение клиентской области, чтобы потом
//правильно
//расположить элемент управления
TRect rc;
::GetClientRect(hWnd, &rc);
TPoint pt1(rc.left, rc.top), pt2(rc.right, rc.bottom);
::ClientToScreen(hWnd, &pt1);
::ScreenToClient(::GetParent(hWnd), &pt1);
::ClientToScreen(hWnd, &pt2);
::ScreenToClient(::GetParent(hWnd), &pt2);
//Запрещаем перемещения элемента управления
DWORD style = ::GetWindowLong(hWnd, GWL_STYLE);
style = style & ~WS_OVERLAPPED & ~WS_THICKFRAME & ~WS_CAPTION;
::SetWindowLong(hWnd, GWL_STYLE, style);
style = ::GetWindowLong(hWnd, GWL_EXSTYLE);
style = style & ~WS_EX_TOOLWINDOW;
::SetWindowLong(hWnd, GWL_EXSTYLE, style);
//Перерисуем в новом состоянии
DWORD flags = SWP_DRAWFRAME | SWP_NOZORDER;
::SetWindowPos(hWnd, NULL, pt1.x, pt1.y, pt2.x - pt1.x, pt2.y - pt1.y,
flags);
}
В листинге 1.23 сначала рассчитывается будущее положение компонента на форме по расположению клиентской области окна с рамкой. Потом удаляются стили окна компонента, добавленные функцией MakeMovable, и компонент закрепляется на форме в заданном положении.
Включение и выключение возможности перемещения компонентов в приведенном примере происходит при выполнении обработчика выбора пунктов меню (листинг 1.24).
Листинг 1.24. Обработчики выбора пунктов меню
void __fastcall TForm1::mnuDisableDragClick(TObject *Sender)
{
//Запрещаем перемещение компонентов
AllowMove(false);
mnuDisableDrag->Checked = true;
}
void __fastcall TForm1::mnuEnableDragClick(TObject *Sender)
{
//Разрешаем перемещение компонентов
AllowMove(true);
mnuEnableDrag->Checked = true;
}
Текст реализации функции-члена класса TForm1, вызываемой в обработчиках выбора пунктов меню, приведен в листинге 1.25. Она в зависимости от значения аргумента bAllow разрешает или запрещает перемещение всех компонентов формы, используя рассмотренные ранее функции MakeMovable и MakeUnmovable.
Листинг 1.25. Включение/выключение возможности перемещения всех компонентов формы
void TForm1::AllowMove(bool bAllow)
{
if ( bAllow )
{
//Разрешаем перемещение компонентов
MakeMovable(Button1->Handle);
MakeMovable(Button2->Handle);
MakeMovable(Edit1->Handle);
MakeMovable(Memo1->Handle);
MakeMovable(Chart1->Handle);
MakeMovable(TrackBar1->Handle);
}
else
{
//Запрещаем перемещение компонентов
MakeUnmovable(Button1->Handle);
MakeUnmovable(Button2->Handle);
MakeUnmovable(Edit1->Handle);
MakeUnmovable(Memo1->Handle);
MakeUnmovable(Chart1->Handle);
MakeUnmovable(TrackBar1->Handle);
}
}
Возможность перемещения компонентов формы, описываемая в этом подразделе, будет совершенно бесполезной, если будет отсутствовать возможность сохранения заданного пользователем расположения компонентов. В данном примере сохранение и восстановление координат мест расположения компонентов формы, а также положения и размера самой формы реализовано. Для этого был написан небольшой класс TLayoutManager, текст объявления которого приведен в листинге 1.26.
Листинг 1.26. Объявление класса TLayoutManager
class TLayoutManager
{
TForm *m_pForm; //Форма, за которую отвечает экземпляр класса
public:
TLayoutManager(TForm *pForm);
void SaveLayout();
void LoadLayout();
};
Экземпляр этого класса инициализируется указателем на форму. Данный класс способен только сохранять и восстанавливать позицию и размер формы, а также расположение компонентов на форме.
Текст реализации класса TLayoutManager приводится в листинге 1.27. Она не отличается особой сложностью. Единственное, что стоит помнить, это то, что данные сохраняются в файле с именем формата <имя_приложения>_<имя_формы>, например Project1.exe_Form1.
Листинг 1.27. Реализация класса TLayoutManager
TLayoutManager::TLayoutManager(TForm *pForm)
{
m_pForm = pForm;
}
void TLayoutManager::SaveLayout()
{
String strFileName = ParamStr(0) + "_" + m_pForm->Name;
TFileStream *ostr = new TFileStream( strFileName, fmCreate );
TRect rc;
//Сохранение положения и размера формы
rc.left = m_pForm->Left;
rc.top = m_pForm->Top;
rc.right = rc.left + m_pForm->Width;
rc.Bottom = rc.top + m_pForm->Height;
ostr->Write(&rc, sizeof(rc));
//Сохранение расположения компонентов
for ( int i=0; i<m_pForm->ControlCount; i++ )
{
TControl *ctrl = m_pForm->Controls[i];
rc.left = ctrl->Left;
...