разработка графического интерфейса пользователя..

Глава 5
Разработка графического интерфейса пользователя
5.1 Требования к интерфейсу
пользователя приложений для Windows
5.1.1 Общие рекомендации по разработке графического интерфейса
Под графическим интерфейсом пользователя (Graphical User Interface GUI) подразумевается тип экранного представления, при котором пользователь может выбирать команды, запускать задачи и просматривать списки файлов, указывая на пиктограммы или пункты в списках меню, показанных на экране. Действия могут, как правило, выполняться с помощью мыши, либо нажатием клавиш на клавиатуре. Типичным примером графического интерфейса пользователя является Windows.
С++-Builder предоставляет разработчику приложения широкие возможности быстрого и качественного проектирования графического интерфейса пользователя различных окон, кнопок, меню и т.д. Так что разработчик может в полной мере проявить свою фантазию. Но полеты фантазии очень полезно ограничивать. Есть определенные принципы построения графического интерфейса пользователя, и пренебрегающий ими обречен на то, что его приложение будет выглядеть чужеродным объектом в среде Windows.
Для пользователя одним из принципиальных преимуществ работы с Windows является то, что большинство имеющихся приложений выглядят и ведут себя сходным образом. После того, как вы поработаете с несколькими приложениями, вы обнаружите, что можете заранее почти наверняка сказать, где можно найти ту или иную функцию в программе, которую только что приобрели, или какие быстрые клавиши надо использовать для выполнения тех или иных операций.
Хороший стиль программирования
Ваше приложение не должна выбиваться из общего стиля Windows ни своим внешним видом (шрифтами, цветом, меню), ни организацией диалогов. Фантазию надо обращать на построение эффективных и надежных алгоритмов функционирования, а не на экзотическое оформление приложения.
Фирма Microsoft предложила спецификации для разработки программного обеспечения Windows, направленные на то, чтобы пользователь не тратил время на освоение нюансов пользовательского интерфейса новой программы, чтобы он смог как можно скорее продуктивно применять ваше приложение. Эти спецификации образуют основу программы логотипа Windows, проводящейся Microsoft. Чтобы вы могли поставить на свой программный продукт штамп «Разработано для Windows», ваша программа должна удовлетворять определенным критериям. Когда вы видите этот логотип на каком-то изделии, аппаратном или программном, вы можете быть уверены, что оно будет работать нормально в среде Windows.
Конечно, вряд ли вы будете очень озабочены приобретением официального права на логотип Windows, если только не разработали сногсшибательную программу широкого применения, которую надеетесь успешно продавать на международном рынке. Но прислушаться к рекомендациям по разработке графического интерфейса пользователя в любом случае полезно. Они основаны на психофизиологических особенностях человека и существенно облегчат жизнь будущим пользователям вашей программы, увеличат производительность их работы.
Как вы сами можете видеть, работая с различными программами Windows, графический интерфейс пользователя любой серьезной программы должен включать в себя:
Главное меню. Реализуется компонентом MainMenu или с помощью компонентов ActionManager и ActionMainMenuBar (см. разд. 5.1.6, 3.8, 4.5).
Инструментальную панель быстрых кнопок, дублирующих основные разделы меню. Чаще всего это компонент ТооlBaг (см. разд. 3.9.4). Если панель большая, то целесообразно использовать ее совместно с компонентом PageScroller (см. разд. 3.9.4), обеспечивающим ее автоматическую прокрутку, Если у вас несколько инструментальных панелей и желательно дать пользователю возможность их перестроения, то панели ТооlBaг целесообразно размещать в компонентах CoolBar или ControlBar (см. разд. 3.9.5). Панель, настраиваемую пользователем, проще всего делать на основе компонентов ActionManager и ActionToolBar (см. разд. 4.5).
Контекстные меню (реализуется компонентом PopupMenu см. разд. 3.8), всплывающие при щелчке пользователя правой кнопкой мыши на том или ином компоненте.
Продуманную последовательность переключения фокуса управляющих элементов (см. разд. 5.1.8).
Клавиши быстрого доступа ко всем разделам меню и всем управляющим элементам (см. разд. 3.7.2), «горячие» клавиши для доступа к основным командам (см. разд. 3.8.1).
Ярлычки подсказок, всплывающие при перемещении курсора мыши над быстрыми кнопками и иными компонентами (см. разд. 5.1.9).
Полосу состояния (реализуется компонентом StatusBar см. разд. 3.9.6), используемую для развернутых подсказок и выдачи различной информации пользователю.
Файл справки, темы которого отображаются при нажатии клавиши F1 или при выборе пользователем соответствующего раздела-меню (см. разд. 5.1.9).
Информацию о версии, доступную пользователю при щелчке на пиктограмме приложения правой кнопкой мыши (см. разд. 2.3.5).
Возможность настройки приложения и запоминания настроек, чтобы при очередном сеансе работы восстанавливались настройки, установленные в прошлом сеансе (см. разд. 5.7.2, 5.7.3 и 4.5).
Средства установки приложения, регистрации его в Windows и удаления из Windows (это нужно для приложений, которые содержат не один, а несколько файлов. Для простых программ установка, регистрация и удаление не требует специальных средств.).
Помимо общих рекомендаций по разработке графического интерфейса пользователя полезно также поддерживать с первых шагов разработки приложения контакт со своими заказчиками или будущими пользователями. С этой точки зрения не стоит сразу браться за разработку всего приложения. Полезно сначала построить его прототип, отражающий предлагаемый вами графический интерфейс, за которым на первых порах не скрывается реальных программ. Этот прототип можно обсудить с заказчиками и пользователями и только после их одобрения приступать к действительной реализации приложения. Одним из достоинств С++ Builder как раз и является возможность очень быстрого построения подобных прототипов.
5.1.2 Многооконные приложения
Чаще всего сколько-нибудь сложное приложение не может ограничиться одним окном. Поэтому прежде всего вам нужно решить вопрос управления окнами. Есть две различные модели приложений: с интерфейсом одного документа (SDI) и с интерфейсом множества документов (MDI).
В большинстве случаев следует отдавать предпочтение интерфейсу SDI. Этот интерфейс не обязательно предполагает наличие действительно только одного окна, как в приложениях Windows, типа «Калькулятор». Такое приложение, как «Проводник» Windows, также является SDI приложением, но в нужные моменты оно создает вторичные окна для поиска файлов или папок, задания параметров, просмотра свойств файлов и других целей.
С другой стороны, у приложений MDI тоже есть свои преимущества. Хороший пример такого приложения Microsoft Word. В приложении MDI имеется родительское (первичное) окно и ряд дочерних окон (называемых также окнами документов). Бывают ситуации, когда выгодно отображать информацию в нескольких окнах, которые совместно используют элементы интерфейса (например, меню или инструментальные линейки). Окна документов управляются и ограничиваются родительским окном. Если вы уменьшаете размер родительского окна, то дочерние окна могут исчезать из поля зрения.
Случаи, когда нужно использовать модель MDI, довольно редки. Прежде всего, это следует делать только тогда, когда все дочерние окна будут содержать идентичные объекты например, текстовые документы или электронные таблицы. Не применяйте MDI, если вы собираетесь работать в приложении с дочерними окнами разного типа (например, текстовыми документами и электронными таблицами одновременно). Не применяйте MDI, если вы хотите управлять тем, какое из дочерних окон должно находиться поверх других, используя свойство «всегда наверху», или если вы хотите управлять размерами окон, делать их невидимыми и т.п. Интерфейс MDI предназначен для очень узкого диапазона приложений, в которых все дочерние окна однородны (как это имеет место в Word илн Excel). Приспособить его к чему-то другому не получится. Наконец, следует заметить, что Microsoft не поощряет разработку новых приложений MDI (в основном потому, что для Windows было написано слишком много плохих программ этого типа). Подробнее о технологии построения приложений MDI будет рассказано в разд. 5.5.4.
5.1.3 Стиль окон приложения
Основным элементом любого приложения является форма контейнер, в котором размещаются другие визуальные и невизуальные компоненты. С точки зрения пользователя форма это окно, в котором он работает с приложением. Каждой новой форме, вводимой в приложение, соответствует свой модуль (unit), описывающий эту форму как класс и включающий, если необходимо, какие-то дополнительные константы, переменные, функции и процедуры.
К внешнему виду окон в Windows предъявляются определенные требования. К счастью, C++Builder автоматически обеспечивает стандартный для Windows вид окон вашего приложения. Но вам надо продумать и указать, какие кнопки в полосе системного меню должны быть доступны в том или ином окне, должно ли окно допускать изменение пользователем его размеров, каким должен быть заголовок окна. Все эти характеристики окон обеспечиваются установкой и управлением свойствами формы.
Свойство BorderStyle определяет общий вид окна и операции с ним, которые разрешается выполнять пользователю. Это свойство может принимать следующие значения:
bsSizeable
Обычный вид окна Windows с полосой заголовка, о возможностью для пользователя изменять размеры окна с помощью кнопок в полосе заголовка или с помощью мыши, потянув за какой-либо край окна. Это значение BorderStyle задается по умолчанию.

bsDialog
Неизменяемое по размерам окно. Типичное окно диалогов.

bsSingle
Окно, размер которого пользователь не может изменить, потянув курсором мыши край окна, но может менять кнопками в полосе заголовка.

bsToolWindow
То же, что bsSingle, но с полосой заголовка меньшего размера.

bsSizeToolWin
То же, что bsSizeable, но с полосой заголовка меньшего размера и с отсутствием в ней кнопок изменения размера.

bsNone
Без полосы заголовка. Окно не только не допускает изменения размера, но и не позволяет переместить его по экрану.

Свойство Borderlcons определяет набор кнопок, которые имеются в полосе заголовка. Множество кнопок задается элементами:
bySistemMenu
Кнопка системного меню это кнопка с крестиком, закрывающая окно.

byMinimize
Кнопка Свернуть, сворачивает окно до пиктограммы.

byMaximize
Кнопка Развернуть, разворачивает окно на весь экран.

byHelp
Кнопка справки.

Следует отметить, что не все кнопки могут появляться при любых значениях BorderStyle.
На рис. 5.1 представлен вид окон форм во время выполнения при некоторых сочетаниях свойств BorderStyle и Borderlcons. Для создания диалоговых окон обычно используется стиль заголовка bsDialog (формы Form3 и Form4 на рис. 5.1), причем в этих окнах можно исключить кнопку системного меню (форма Form4) и в этом случае пользователь не может закрыть окно никакими способами, кроме как выполнить какие-то предписанные ему действия на этой форме. При стиле bsNone пользователь не может изменить ни размер, ни положение окна на экране. Формы Form3, Form6 и Form8 внешне различаются только размером полосы заголовка, уменьшенным в двух последних формах. Но между ними есть и принципиальное различие: размер формы Form6 (стиль заголовка bsSizeToolWin) пользователь может изменить, потянув курсором мыши за край окна. Для форм Form3 и Form8 это невозможно. Обратите также внимание, что в формах Form6 и Form8 задание кнопок свертывания и развертывания окна никак не влияет на его вид: эти кнопки просто не могут появляться в этих стилях полос заголовков окон.
Хороший стиль программирования
Без особой необходимости не делайте окна приложения с изменяемыми пользователем размерами. При изменении размеров, если не применены специальные приемы, описанные в разд. 5.2, нарушается компоновка окна и пользователь ничего не выигрывает от своих операций с окном. Окно имеет смысл делать с изменяемыми размерами, только если это позволяет пользователю изменять полезную площадь каких-то расположенных в нем компонентов отображения и редактирования информации: текстов, изображений, списков и т.п.
Рис. 5.1. Формы при разных сочетаниях свойств BorderStyle и Borderlcons
Хороший стиль программирования
Для основного окна приложения с неизменяемыми размерами наиболее подходящий стиль BorderStyle = bsSingle с исключением из числа доступных кнопок кнопки Развернуть (Barderlcons.byMaximize = false]. Это позволит пользователю сворачивать окно, восстанавливать, но не даст возможности развернуть окно на весь экрон или изменить размер окна.
Хороший стиль программирования
Для вторичных диолаговых окон наиболее подходящий стиль BorderStyle = bsDialog. Можно также использовать BorderStyle = bsSingle, одновременно исключая из числа доступных кнопок кнопку Развернуть (задавая Borderlcons.byMaximize = false). Это позволит пользователю сворачивать диалоговое окно, если оно зослоняет на экране что-то нужное ему, восстанавливать окно, но не даст возможности развернуть окно на весь экран или изменить размер окна.
Предупреждение
Избегайте, кок правило, стиля BorderStyle = bsNone. Невозможность переместить окно может создать пользователю трудности, если окно заслонит на экране что-то интересующее пользователя.
Свойство формы WindowState определяет вид, в котором окно первоначально предъявляется пользователю при выполнении приложения. Оно может принимать значения:
wsNormal
нормальный вид окна (это значение WindowState используется по умолчанию)

wsMinimized
окно свернуто

wsMaximized
окно развернуто на весь экран

Если свойство WindowState имеет значение wsNormal или пользователь, манипулируя кнопками в полосе заголовка окна, привел окно в это состояние, то положение окна при запуске приложения определяется свойством Position, которое может принимать значения:
poDesigned
Первоначальные размеры и положение окна во время выполнения те же, что во время проектирования. Это значение принимается по умолчанию, но обычно его следует изменить.

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

poDesktopCenter
Окно располагается в центре экрана. Размер окна тот, который был спроектирован. Этот режим не приспосабливается к приложениям с множеством мониторов (см. разд. 4.7).

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

poDefaultPosOnly
Местоположение окна определяет Windows. При последовательных показах окна его положение сдвигается немного вниз и вправо. Размер окна спроектированный.

poDefaultSizeOnly
Размер окна определяет Windows, учитывая размер и разрешение экрана. Положение окна спроектированное.

poMainFormCenter
Это значение предусмотрено только начиная с C++Builder 5. Окно располагается в центре главной формы. Размер окна тот, который был спроектирован. Этот режим не приспосабливается к приложениям с множеством мониторов (см. разд. 4.7). Используется только для вторичных форм. Для главной формы действует так же, как poScreenCenter.

poOwnerPormCenter
Это значение предусмотрено только начиная с C++Builder 6. Окно располагается в центре формы, указанной как владелец данной в свойстве Owner. Размер окна тот, который был спроектирован. Если свойство Owner указывает не форму, действует как poMainFormCenter.

Хороший стиль программирования
Обычно целесообразно для главной формы приложения задавать значение Position ровным poScreenCenter или poDefoultPosOnly. И только в сравнительно редких случаях, когда на экране при выполнении приложения должно определенным образом располагаться несколько окон, имеет смысл оставлять значение poDesigned, принимаемое по умолчанию.
Если выбранное значение свойства Position предусматривает выбор размера формы самим Windows по умолчанию, то на этот выбор влияют свойства Pixels-Perlnch и Scaled. По умолчанию первое из них задается равным количеству пикселов на дюйм в системе, второе установлено в false. Если задать другое число пикселов на дюйм, то свойство Scaled автоматически становится равным true. В этом случае при запуске приложения размер формы будет изменяться в соответствии с пересчетом заданного числа пикселов на дюйм к реальному числу пикселов на дюйм в системе (но только при разрешающем это значении свойства Position).
Свойство AutoScroll определяет, будут ли на форме в процессе выполнения появляться автоматически полосы прокрутки в случае, если при выбранном пользователем размере окна не все компоненты помещаются в нем. Если значение AutoScroll равно true, то будут. В противном случае при уменьшении размера окна пользователь теряет доступ к компонентам, не поместившимся в его поле.
Свойство Icon задает пиктограмму формы. По умолчанию используется стандартная пиктограмма C++Builder. Нажав в Инспекторе Объектов кнопку с тремя точками в строке свойства Icon, вы попадаете в окно Редактора Изображений (Picture Editor) (см. рис. 3.33 в разд. 3.7.2). Щелкнув в нем на кнопке Load (загрузить), вы можете выбрать любой файл с изображением пиктограммы (файл с расширением .ico). С C++Builder поставляется некоторое число пиктограмм, расположенных в каталоге lmages\lcons.
Свойство Icon задает только пиктограмму формы, которая отображается в левом верхнем углу окна приложения в его нормальном состоянии. Но если пользователь свернет окно, то в полосе задач будет видна другая пиктограмма пиктограмма приложения. Ту же пиктограмму увидит пользователь, если будет просматривать средствами Windows содержимое каталога. По умолчанию для нее используется стандартная пиктограмма C++Builder. При свертывании приложения рядом с пиктограммой в полосе задач пользователь будет видеть надпись по умолчанию это имя приложения. Если вы хотите, то можете изменить эту пиктограмму и эту надпись. Для этого вы должны выполнить команду Project
·Options и в открывшемся окне опций проекта перейти на страницу Application (рис. 5.2). В этом окне вы можете задать заголовок (Title), который увидит пользователь в полосе задач при сворачивании приложения. А кнопка Load Icon позволяет вам выбрать пиктограмму, которая будет видна в полосе задач при сворачивании приложения или при просмотре пользователем каталога, в котором расположен выполняемый файл приложения.
Рис. 5.2Страница Application окна опций проекта
Одно из основных свойств формы FormStyle, которое может принимать значения:
fsNormal
Окно обычного приложения. Это значение FormStyle принято по умолчанию.

fsMDIForm
Родительская форма приложения MDI, т.е. приложения с дочерними окнами, используемого при работе с несколькими документами одновременно.

fsMDIChild
Дочерняя форма приложения MDI.

fsStayOnTop
Окно, остающееся всегда поверх остальных окон Windows.

О приложениях MDI подробно будет рассказано в разд. 5.5.4. Так что значения fsMDIForm и fsMDIChild мы пока подробнее обсуждать не будем. А значение FormStyle = fsStayOnTop делает окно всегда остающимся на экране поверх остальных окон не только данного приложения, но и всех других приложений, в которые может перейти пользователь.
Хороший стиль программирования
Используйте стиль FormStyle = fsStayOnTop для отображения окон сообщений пользователю о каких-то аварийных ситуациях.
В ряде случаев полезно предоставить пользователю самому решать, сделать ли данное окно располагающимся всегда поверх остальных, или нет. Например, ему может временно потребоваться перейти в какое-то другое приложение, чтобы получить необходимую информацию, и при этом будет хотеться видеть поверх этого приложения ваше окно, чтобы сравнивать в этих двух окнах какие-то данные. Такую возможность легко предоставить пользователю. Введите в меню вашего окна раздел Поверх остальных, задайте свойство этого раздела AutoCheck = true, чтобы его состояние при каждом выполнении переключалось, и в обработчик щелчка на этом разделе вставьте оператор
if (MStayOnTop->Checked)
Form1->FormStyle = fsStayOnTop;
else
Form1->FormStyle = fsNormal;
В этом коде подразумевается, что объект раздела меню, о котором идет речь, назван MStayOnTop. При выборе пользователем этого раздела меню в нем появится индикатор, а окно приобретет статус расположенного всегда поверх остальных. При повторном выборе этого раздела индикатор исчезнет и окно приобретет обычный статус.
Свойство AutoCheck введено только в C++Builder 6. В более ранних версиях перед приведенным выше оператором надо вставить оператор
MStayOnTop->Checked -= !MStayOnTop->Checked;
который программно переключает состояние раздела.
Рассмотренными традиционными формами не исчерпываются возможности создания окон. Иногда желательно создавать формы или окна на них непрямоугольной формы. В отношении форм это чаще всего связано со стремлением украсить свое приложение, отойти от традиционных прямоугольных форм. В отношении непрямоугольных окон, помимо стремления к нетрадиционности, могут преследоваться и более прагматические цели: выделение каких-то областей на форме, создание перемещаемых окон различной конфигурации в задачах конструирования и т.п. Рассмотрение вопросов создания непрямоугольных окон выходит за рамки данной книги. Эта тема подробно рассмотрена в книге [3]. Ниже приведено два примера из этой книги, чтобы продемонстрировать возможности непрямоугольных окон. Примеры даны на диске, приложенном к книге, без исходных текстов, поскольку в данной книге невозможно рассматривать их реализацию.
Первый пример (приложение PForms.exe в каталоге Chapter5\Forms на приложенном к книге диске) демонстрирует применение функций, названия которых вы можете прочитать около радиокнопок на рис. 5.3. Левая группа кнопок задает форму главного окна, а правая группа управляет формой панели, расположенной на главной форме. Нажимая различные кнопки, вы сможете увидеть, как те или иные функции влияют на форму. Например, эллиптическая форма на рис. 5.3 б создана функцией CreateEllipticRgn. А форма и панель на рис. 5.3 в созданы комбинированием с помощью функции CombineRgn. Описания соответствующих функций можно найти в справке C++Builder или в источниках [3] и [б].
Рис. 5.3. Три состояния приложения с непрямоугольными окнами
Приложение, изображенное на рис. 5.3, демонстрирует возможность создания окон довольно причудливых форм, состоящих, тем не менее, из комбинации стандартных элементов: прямоугольников, эллипсов, многоугольников. Можно, однако, формировать форму на основе произвольного изображения. Это иллюстрирует приложение, показанное на рис. 5.4 (приложение Forms.exe в каталоге Chapter5\Forms на приложенном к книге диске). Главная форма приложения представлена на рис. 5.4 а. Кнопка Файл позволяет загрузить в приложение файл изображения (в каталоге имеется несколько соответствующих файлов). Кнопка Создать форму позволяет создать на основе этого изображения форму. А радиокнопки переключают различные алгоритмы создания формы. Например, форму можно создавать с изображением, без изображения или с пустотой в виде контура изображения. Кнопка вверху справа дает возможность указать цвет в изображении, на основе которого будет создаваться форма.
На рис. 5.4 б показан ряд созданных форм. Они помещены поверх окна программы Word, чтобы была понятнее их конфигурация. Показаны формы, построенные на основе изображений из файлов ATHENA.BMP (стандартное изображение фирмы Borland), Russia.bmp (карта России) и Ring.bmp (черное кольцо на сером фоне). Эти файлы имеются на приложенном к книге диске в том же каталоге, в котором находится проект Forms.

Рис. 5.4 Приложение создания форм по изображениям (а)
и созданные им формы на фоне окна Word (б)
В контекстном меню создаваемых форм имеется раздел, позволяющий запомнить контур формы. А приложение FRegion.exe в каталоге Chapter5\Forms на приложенном к книге диске читает это изображение из файла с именем Reg.rgn и создает на его основе форму. Пример такой формы показан на рис. 5.5. Смысл подобных форм или окон на форме отнюдь не сводится к желанию создать экзотическое приложение. Это может быть, например, контур карты некоторого региона, на котором можно поместить какие-то изображения: филиалов или клиентов некоторой фирмы, транспортные потоки и т.п.
Рис. 5.5 Форма, созданная на основе контура, запомненного в файле
5.1.4 Цветовое решение приложения
Цвет является мощным средством воздействия на психику человека. Именно поэтому обращаться с ним надо очень осторожно. Неудачное цветовое решение может приводить к быстрому утомлению пользователя, работающего с вашим приложением, к рассеиванию его внимания, к частым ошибкам. Слишком яркий или неподходящий цвет может отвлекать внимание пользователя или вводить его в заблуждение, создавать трудности в работе. А удачно подобранная гамма цветов, осмысленные цветовые акценты снижают утомляемость, сосредоточивают внимание пользователя на выполняемых в данный момент операциях, повышают эффективность работы. С помощью цвета вы можете на что-то намекнуть или привлечь внимание к определенным областям экрана. Цвет может также связываться с различными состояниями объектов.
Надо стремиться использовать ограниченный набор цветов и уделять внимание их правильному сочетанию. Расположение ярких цветов, таких, как красный, на зеленом или черном фоне затрудняет возможность сфокусироваться на них. Не рекомендуется использовать дополнительные цвета. Обычно наиболее приемлемым цветом для фона будет нейтральный цвет, например, светло-серый (используется в большинстве продуктов Microsoft). Помните также, что яркие цвета кажутся выступающими из плоскости экрана, в то время как темные как бы отступают вглубь.
Цвет не должен использоваться в качестве основного средства передачи информации. Можно использовать различные панели, формы, штриховку и другие методики выделения областей экрана. Microsoft даже рекомендует разрабатывать приложение сначала в черно-белом варианте, а уже потом добавлять к нему цвет.
Нельзя также забывать, что восприятие цвета очень индивидуально. А по оценке Microsoft девять процентов взрослого населения вообще страдают нарушениями цветовосприятия. Поэтому не стоит навязывать пользователю свое видение цвета, даже если оно безукоризненно. Надо предоставить пользователю возможность самостоятельной настройки на наиболее приемлемую для него гамму. К тому же не стоит забывать, что может быть кто-то захочет использовать вашу программу на машине с монохромным монитором.
Посмотрим теперь, как задаются цвета приложения, разрабатываемого в C++Builder. Большинство компонентов имеют свойство Color (цвет), который вы можете изменять в Инспекторе Объектов при проектировании или программно во время выполнения (если хотите, чтобы цвета в различных режимах работы приложения были разные). Щелкнув на этом свойстве в Инспекторе Объектов, вы можете увидеть в выпадающем списке большой набор предопределенных констант, обозначающих цвета. Все их можно разбить на две группы: статические цвета типа сlBlack черный, clGreen зеленый и т.д., и системные цвета типа clWindow текущий цвет фона окон, clMenuText текущий цвет текста меню и т.д. Полный список всех констант и их описание, а также способ конструирования любого другого статического цвета вы можете найти в справочной части книги в гл. 17, в разд. «Color».
Статические цвета вы выбираете сами, и они будут оставаться неизменными при работе приложения на любом компьютере. Это не очень хорошо, поскольку пользователь не сможет адаптировать вид вашего приложения к своим потребностям. При выборе желательной ему цветовой схемы пользователь может руководствоваться самыми разными соображениями: начиная с практических (например, он может хотеть установить черный фон, чтобы экономить энергию батареи), и кончая эстетическими (он может предпочитать, например, шкалу оттенков серого, потому что не различает цвета). Все это он не может делать, если вы задали в приложении статические цвета. Но уж если по каким-то соображениям вам надо их задать, старайтесь использовать базовый набор из 16 цветов. Если вы попытаетесь использовать 256 (или, что еще хуже, 16 миллионов) цветов, это может замедлить работу вашего приложения, или оно будет выглядеть плохо на машине пользователя с 16 цветами. К тому же подумайте (а, как правило, это надо проверить и экспериментально), как будет выглядеть ваше приложение на монохромном дисплее.
Исходя из изложенных соображений, везде, где это имеет смысл, следует использовать для своего приложения палитру системных цветов. Это те цвета, которые устанавливает пользователь при настройке Windows. Когда вы создаете новую форму или размещаете на ней компоненты, C++Builder автоматически присваивает им цвета в соответствии со схемой цветов, установленной в Windows. Конечно, вы будете менять эти установки по умолчанию. Но если при этом вы используете соответствующие константы системных цветов, то, когда пользователь изменит цветовую схему оформления экрана Windows, ваше приложение также будет соответственно меняться и не будет выпадать из общего стиля других приложений.
Хороший стиль программирования
Не злоупотребляйте в приложении яркими цветами. Пестрое приложение обычно признак дилетантизма разработчика, утомляет пользователя, рассеивает его внимание. Как правило, используйте системные цвета, которые пользователь может перестраивать по своему усмотрению. Из статических цветов обычно имеет смысл использовать только clBlack черный, clWhite белый и cIRed красный цвет предупреждения об опасности.
Единству цветового решения отдельных частей экрана способствует также использование свойства ParentColor. Если это свойство установлено в true, то цвет компонента соответствует цвету содержащего его контейнера или формы. Это обеспечивает единство цветового решения окна и, кроме того, позволяет программно изменять цвет сразу группы компонентов, если вы, например, хотите, чтобы их цвет зависел от текущего режима работы приложения. Для такого группового изменения достаточно изменить только цвет контейнера.
5.1.5 Шрифты текстов
Шрифт надписей и текстов компонентов C++Builder задается свойством Font, имеющим множество подсвойств. Кроме того, в компонентах имеется свойртво ParentFont. Если это свойство установлено в true, то шрифт данного компонента берется из свойство Font его родительского компонента панели или формы, на которой расположен компонент. Использование свойств ParentFont и ParentColor помогает обеспечить единообразие отображения компонентов в окне приложения.
По умолчанию для всех компонентов C++Builder задается имя шрифта MS Sans Serif и размер 8. Константа множества символов Charset задается равной DEFAULT_CHARSET. Последнее означает, что шрифт выбирается только по его имени и размеру. Если описанный шрифт недоступен в системе, то Windows заменит его другим шрифтом.
Чаще всего эти установки по умолчанию можно не изменять. Конечно, никто не мешает задать для каких-то компонентов другой размер шрифта или атрибуты типа полужирный, курсив и т.д. Но изменять имя шрифта для вашего приложения надо с определенной осторожностью. Дело в том, что шрифт, установленный на вашем компьютере, не обязательно должен иметься и на компьютере пользователя. Поэтому использование какого-то экзотического шрифта может привести к тому, что пользователь, запустив ваше приложение на своем компьютере, увидит вместо русского текста абракадабру на никому не понятном языке. Чтобы избежать таких казусов, вам придется прикладывать к своему приложению еще и файлы использованных шрифтов и пояснять пользователю, как он должен установить их на своем компьютере, если они там отсутствуют. Или вводить автоматическую проверку и установку нужных вам шрифтов в установочную программу вашего приложения.
Использование шрифтов по умолчанию: System или MS Sans Serif, чаще всего позволяет избежать подобных неприятностей. Впрочем, увы, не всегда. Если вы используете для надписей русские тексты, то при запуске приложения на компьютере с нерусифицированным Windows иногда возможны неприятности. Для подобных случаев все-таки полезно приложить файлы использованных шрифтов к вашей программе. Вы можете при установке вашего приложения узнать, имеется ли на компьютере пользователя нужный шрифт, например, с помощью следующего кода:
if (Screen->Fonts->IndexOf ("Arial") == -1)

В этом коде многоточием обозначены действия, которые надо выполнить, если нужного шрифта (в примере Arial) на компьютере нет. Эти действия могут заключаться в копировании файлов шрифта с установочной дискеты или CD ROM на компьютер пользователя.
Другой выход из положения ввести в приложение команду выбора шрифта пользователем. Это позволит ему выбрать подходящий шрифт из имеющихся в его системе. Осуществляется подобный выбор с помощью стандартного диалога, оформленного в виде компонента FontDialog (см. гл. 3, разд. 3.10.5). Проведенную пользователем установку можно запоминать в файле .ini, в реестре или в другом файле конфигурации и читать автоматически информацию из этого файла при каждом запуске приложения (см. разд. 5.7.2 и 5.7.3).
Подробное рассмотрение всех свойств компонентов, связанных со шрифтами, примеры их использования и исследования вы найдете в гл. 17, в разд. «Font».
5.1.6 Меню
Практически любое приложение должно иметь меню, поскольку именно меню дает наиболее удобный доступ к функциям программы. Существует несколько различных типов меню: главное меню с выпадающими списками разделов, каскадные меню, в которых разделу первичного меню ставится в соответствие список подразделов, и всплывающие или контекстные меню, появляющиеся, если пользователь щелкает правой кнопкой мыши на каком-то компоненте.
В C++Builder меню создаются компонентами MainMenu главное меню, и PopupMenu всплывающее меню. Оба компонента расположены на странице Standard. Кроме того, в C++Builder 6 имеется возможность создания меню, настраиваемого пользователем во время выполнения, с помощью компонентов ActionManager и ActionMainMenuBar. Все операции по проектированию меню с помощью этих компонентов подробно рассмотрены в разд. 3.8 и 4.5. А в данном разделе мы обсудим требования, предъявляемые к меню приложений Windows.
Основное требование к меню их стандартизация. Это требование относится ко многим аспектам меню: месту размещения заголовков меню и их разделов, форме самих заголовков, клавишам быстрого доступа, организации каскадных меню. Цель стандартизации облегчит пользователю работу с приложением. Надо, чтобы пользователю не приходилось думать, в каком меню и как ему надо открыть или сохранить файл, как ему получить справку, как работать с буфером обмена Clipboard и т.д. Для осуществления всех этих операций у пользователя, поработавшего хотя бы с несколькими приложениями Windows, вырабатывается стойкий автоматизм действий и недопустимо этот автоматизм ломать.
Начнем рассмотрение требований с размещения заголовков меню. Типичная полоса главного меню приведена на рис. 3.5 в разд. 3.2.4, гл. 3. Конечно, состав меню зависит от конкретного приложения. Но размещение общепринятых разделов должно быть стандартизированным. Все пользователи уже привыкли, что меню Файл размещается слева в полосе главного меню, раздел справки справа, перед ним в приложениях MDI размещается меню Окно и т.д. Главное меню должно также снабжаться инструментальной панелью (см. рис. 3.5), быстрые кнопки которой дублируют наиболее часто используемые команды меню. На этих кнопках надо использовать, по возможности, привычные картинки. В C++Builder имеется множество изображений кнопок на все случаи жизни, но, к сожалению, они не всегда имеют привычный для пользователя рисунок.
По возможности стандартным должно быть и расположение разделов в выпадающих меню. На рис. 5.3 приведены распространенные варианты меню Файл работа с файлами, Правка работа с текстами, Окно управление окнами в приложении MDI и меню Справка или ? просмотр справочных данных. Обратите внимание, что, например, раздел Выход всегда размещается последним в меню Файл, а раздел информации о версии программы О программе... последним в справочном меню.
Группы функционально связанных разделов отделяются в выпадающих меню разделителями.
Названия разделов меню должны быть привычными пользователю. Если вы не знаете, как назвать какой-то раздел, не изобретайте свое имя, а попытайтесь найти аналогичный раздел в какой-нибудь русифицированной программе Microsoft для Windows. Названия должны быть краткими и понятными. Не используйте фраз, да и вообще больше двух слов, поскольку это перегружает экран и замедляет выбор пользователя. Названия разделов должны начинаться с заглавной буквы. Применительно к английским названиям разделов существует требование, чтобы каждое слово тоже начиналось с заглавной буквы. Но применительно к русским названиям это правило не применяется.
Рис. 5.3Типовые меню
Названия разделов меню, связанных с вызовом диалоговых окон, должны заканчиваться многоточием, показывающим пользователю, что при выборе этого раздела ему предстоит установить в диалоге еще какие-то параметры.
Разделы, к которым относятся каскадные меню (например, раздел Упорядочить на рис. 5.3 в) должны заканчиваться стрелкой, указывающей на наличие дочернего меню данного раздела. C++Builder ставит эту стрелку автоматически, так что о ней вам думать не приходится. Вообще злоупотреблять каскадными меню не следует, так как пользователю не так просто до них добираться. Если в дочернем меню должно быть много разделов, например, связанных с какими-то опциями и настройками, то подумайте, не лучше ли вместо этого дочернего меню предусмотреть диалоговое окно, в котором эти опции будут более обозримыми и доступными.
В каждом названии раздела должен быть выделен подчеркиванием символ, соответствующий клавише быстрого доступа к разделу (клавиша Alt плюс подчеркнутый символ). Хотя вряд ли такими клавишами часто пользуются, но традиция указания таких клавиш незыблема. Впрочем, если у пользователя отказала мышь, то клавиши быстрого доступа позволяют выполнить какие-то заключительные операции и сохранить документы, с которыми велась работа.
Многим разделам могут быть поставлены в соответствие «горячие» клавиши, позволяющие обратиться к команде данного раздела, даже не заходя в меню. Комбинации таких «горячих» клавиш должны быть традиционными. Например, команды вырезания, копирования и вставки фрагментов текста практически всегда имеют «горячие» клавиши Ctrl-X, Ctrl-C и Ctrl-V соответственно. Заданные сочетания клавиш отображаются в заголовках соответствующих разделов меню (см. рис. 5.3 а и 5.3 б).
Многие разделы меню желательно снабжать пиктограммами, причем пиктограммы для стандартных разделов должны быть общепринятыми, знакомыми пользователю.
Некоторые разделы меню, соответствующие выбору каких-то настроек, могут содержать индикаторы, показывающие, выбран или нет данный раздел, и могут содержать радиокнопки. Способы создания подобных разделов меню см. в разд. 3.8.1.
Не все разделы меню имеют смысл в любой момент работы пользователя с приложением. Например, если в приложении не открыт ни один документ, то бессмысленно выполнять команды редактирования в меню Правка. Если в тексте документа ничего не изменялось, то бессмысленным является раздел этого меню Отменить, отменяющий последнюю команду редактирования. Такие меню и отдельные разделы должны делаться временно недоступными или невидимыми. Это осуществляется заданием значения false свойствам раздела Enabled или Visible соответственно. Различие между недоступными и невидимыми разделами в том, что недоступный раздел виден в меню, но отображается серым цветом, а невидимый раздел просто исчезает из меню, причем нижележащие разделы смыкаются, занимая его место. Выбор того или иного варианта дело вкуса и удобства работы пользователя. Вероятно, целиком меню лучше делать невидимыми, а отдельные разделы недоступными. Например, пока ни один документ не открыт, меню Правка можно сделать невидимым, чтобы он не отвлекал внимания пользователя. А раздел Отменить этого меню в соответствующих ситуациях лучше делать недоступным, чтобы пользователь видел, что такой раздел в меню есть и им можно будет воспользоваться в случае ошибки редактирования.
Вот и все основные требования к главному меню приложения. Проектирование меню не очень быстрый процесс и обидно повторять его снова и снова для каждого нового приложения, тем более что требование стандартизации приводит к тому, что одни и те же разделы с одинаковыми свойствами кочуют из приложения в приложение. Поэтому можно рекомендовать один раз потратить время, создать меню, содержащее большинство разделов, которые могут вам понадобиться в различных приложениях, и сохранить это меню как шаблон (в разд. 3.8.1 рассказано, как это делается с помощью команды Save As Template в Конструкторе Меню). В дальнейшем вы сможете использовать этот шаблон в любом своем приложении, загружая его командой Insert From Template Конструктора Меню в компонент MainMenu. А удалить после этого разделы, не используемые в данном приложении, не представляет никакого труда вы просто выделяете соответствующий раздел в Конструкторе Меню и нажимаете клавишу Del.
Технология проектирования меню подробно рассмотрена в разд. 3.8.1, 4.3 и 4.5. Поскольку меню обычно проектируется в комплексе с диспетчером действий, списком изображений, инструментальными панелями, то указанная выше возможность сохранения шаблона самого меню не слишком помогает избежать повторения множества проектных операций в каждом новом приложении. Поэтому можно рекомендовать сохранять как шаблон всю совокупность компонентов: диспетчер действий, список изображений, меню, инструментальные панели. Как это делается показано в разд. 8.2, гл. 8. Создание подобного шаблона заметно сэкономит ваше время при разработке последующих проектов и будет способствовать единообразию интерфейса в серии ваших проектов, что также немаловажно.
Помимо главного меню в приложении обычно должны быть контекстные всплывающие меню отдельных компонентов, например, окон редактирования. Эти меню обычно дублируют команды главного меню, используемые при работе с данным компонентом. В разд. 3.8.2 рассказано, как проектируются всплывающие меню с помощью компонента PopupMenu. Всплывающие меню желательно не перегружать разделами, вводя в них только действительно часто используемые команды.
5.1.7 Компоновка форм
Каждое окно, которое вы вводите в свое приложение, должно быть тщательно продумано и скомпоновано. Удачная компоновка может стимулировать эффективную работу пользователя, а неудачная рассеивать внимание, отвлекать, заставлять тратить лишнее время на поиск нужной кнопки или индикатора.
Управляющие элементы и функционально связанные с ними компоненты экрана должны быть зрительно объединены в группы, заголовки которых коротко и четко поясняют их назначение. Такое объединение позволяют осуществлять различные панели, рассмотренные в разд. 3.9. Можно рекомендовать, как правило, размещать компоненты не непосредственно на форме, а на таких панелях. Но и внутри панелей надо продумывать размещение компонентов, как с точки зрения эстетики, так и о точки зрения визуального отражения взаимоотношений элементов. Например, если имеется кнопка, которая разворачивает окно списка, то эти два компонента должны быть визуально связаны между собой: размещены на одной панели и в непосредственной близости друг от друга. Если же ваш экран представляет собой случайные скопления кнопок, то именно так он и будет восприниматься. И в следующий раз пользователь не захочет пользоваться вашей программой.
Каждое окно должно иметь некоторую центральную тему, которой подчиняется его композиция; Пользователь должен понимать, для чего предназначено данное окно и что в нем наиболее важно. При этом недопустимо перегружать окно большим числом органов управления, ввода и отображения информации. В окне должно отображаться главное, а все детали и дополнительную информацию можно отнести на вспомогательные окна. Для этого полезно вводить в окно кнопки с надписью «Больше...», многоточие в которой показывает, что при нажатии этой, кнопки откроется вспомогательное окно с дополнительной информацией. Помогают также разгрузить окно многостраничные компоненты с закладками, рассмотренные в разд. 3.9.3. Они дают возможность пользователю легко переключаться между разными по тематике страницами, на каждой из которых имеется необходимый минимум информации. Примеры удачной организации окон вы можете посмотреть в C++Builder, выполнив команду Tools
·Environment Options и полистав страницы окна опций.
Еще один принцип, которого надо придерживаться при проектировании окон стилистическое единство всех окон в приложении. Недопустимо, чтобы сходные по функциям органы управления в разных окнах назывались по-разиому или размещались в разных местах окон. Все это мешает работе с приложением, отвлекает пользователя, заставляет его думать не о сущности работы, а о том, как приспособиться к тому или иному окну. Появившийся в C++Builder 5 компонент Frame фрейм (см. разд. 3.9.7) позволяет один раз разработать некий повторяющийся фрагмент окна, поместить его в Депозитарий (см. разд. 2.6), а затем использовать его в разных формах и приложениях.
Стилистическому единству окон приложения способствует имеющаяся в C++Builder возможность создания иерархии форм. Эта возможность рассмотрена в разд. 2.6. Приступая к большому проекту полезно сначала создать иерархическую последовательность форм, а затем уже, используя эту иерархию, проектировать конкретные окна. Кроме всего прочего, это позволяет сэкономить немало времени при разработке, потому что внесение каких-то усовершенствований в одну форму автоматически ведет к тиражированию этих изменений в других формах.
Единство стилистических решений важно не только внутри приложения, но и в рамках серии разрабатываемых вами приложений. Это нетрудно обеспечить с помощью имеющихся в C++Builder многочисленных способов повторного использования кодов. Вам достаточно один раз разработать какие-то часто применяемые формы ввода пароля, запроса или предупреждения пользователя и т.п., включить их в Депозитарий, а затем вы можете использовать их многократно во всех своих проектах.
И последнее вам надо заботиться не только о единстве стиля внутри своих приложений, но и о том, как ваше приложение впишется в общую организацию рабочего пространства системы и как оно будет взаимодействовать с другими приложениями. Если приложение выпадает из общего ансамбля, оно напрашивается на неприятности.
5.1.8 Последовательность фокусировки элементов
При проектировании приложения важно правильно определить последовательность табуляции оконных компонентов. Под этим понимается последовательность, в которой переключается фокус с компонента на компонент, когда пользователь нажимает клавишу табуляции Tab. Это важно, поскольку в ряде случаев пользователю удобнее работать не с мышью, а с клавиатурой. Пусть, например, вводя данные о каком-то сотруднике, пользователь должен в отдельных окнах редактирования указать фамилию, имя и отчество. Конечно, набрав фамилию ему удобнее нажать клавишу Tab и набирать имя, а потом опять, нажав Tab, набирать отчество, чем каждый раз отрываться от клавиатуры, хватать мышь и переключаться в новое окно редактирования.
Свойство формы ActiveControI, установленное в процессе проектирования, определяет, какой из размещенных на форме компонентов будет в фокусе в первый момент при выполнении приложения. В процессе выполнения это свойство изменяется и показывает тот компонент, который в данный момент находится в фокусе.
Последовательность табуляции задается свойствами TabOrder компонентов. Первоначальная последовательность табуляции определяется просто той последовательностью, в которой размещались управляющие элементы на форме. Первому элементу присваивается значение TabOrder, равное 0, второму 1 и т.д. Значение TabOrder, равное нулю, означает, что при первом появлении формы на экране в фокусе будет именно этот компонент (если не задано свойство формы ActiveControl). Если на форме имеются панели, фреймы и иные контейнеры, включающие в себя другие компоненты, то последовательность табуляции составляется независимо для каждого компонента-контейнера. Например, для формы будет последовательность, включающая некоторую панель как один компонент. А уже внутри этой панели будет своя последовательность табуляции, определяющая последовательность получения фокуса ее дочерними оконными компонентами.
Каждый управляющий элемент имеет уникальный номер TabOrder внутри своего родительского компонента. Поэтому изменение значения TabOrder какого-то элемента на значение, уже существующее у другого элемента, приведет к тому, что значения TabOrder всех последующих элементов автоматически изменятся, чтобы не допустить дублирования. Если задать элементу значение TabOrder, большее, чем число элементов в родительском компоненте, он просто станет последним в последовательности табуляции.
Из-за того, что при изменении TabOrder одного компонента могут меняться TabOrder других компонентов, устанавливать эти свойства поочередно в отдельных компонентах трудно. Поэтому в среде проектирования C++Builder имеется специальная команда Edit
·Tab Order, позволяющая в режиме диалога задать последовательность табуляции всех элементов.
Значение свойства TabOrder играет роль, только если другое свойство компонента TabStop установлено в true и если компонент имеет родителя. Например, для формы свойство TabOrder имеет смысл только в случае, если для формы задан родитель в виде другой формы. Установка TabStop в false приводит к тому, что компонент выпадает из последовательности табуляции и ему невозможно передать фокус клавишей Tab (однако предать фокус мышью, конечно, можно).
Имеется и программная возможность переключения фокуса это метод SetFocus. Например, если вы хотите переключить в какой-то момент фокус на окно Edit2, вы можете сделать это оператором:
Edit2->SetFocus();
Выше говорилось, что в приложении с несколькими окнами редактирования, в которые пользователь должен поочередно вводить какие-то данные, ему удобно переключаться между окнами клавишей табуляции. Но еще удобнее пользователю, закончившему ввод в одном окне, нажать клавишу Enter и автоматически перейти к другому окну. Это можно сделать, обрабатывая событие нажатия клавиши OnKeyDown (см. о событиях при нажатии клавиш в разд. 5.3.2). Например, если после ввода данных в окно Edit1 пользователю надо переключаться в окно Edit2, то обработчик события OnKeyDown окна Edit1 можно сделать следующим:
void__fastcall TForml::EditlKeyDown(TObject *Sender, WORD SKey, TShiftState Shift)
{
if (Key == VK_RETURN)
Edit2->SetFocus();
}
Единственный оператор этого обработчика проверяет, является ли клавиша, нажатая пользователем, клавишей Enter. Если это клавиша Enter, то фокус передается окну Edit2 методом SetFocus.
Можно подобные операторы ввести во все окна, обеспечивая требуемую последовательность действий пользователя (впрочем, свобода действий пользователя от этого не ограничивается, поскольку он всегда может вернуться вручную к нужному окну). Однако можно сделать все это более компактно, используя один обработчик для различных окон редактирования и кнопок. Для этого может использоваться метод FindNextControl, возвращающий дочерний компонент, следующий в последовательности табуляции. Если компонент, имеющий в данный момент фокус, является последним в последовательности табуляции, то возвращается первый компонент этой последовательности. Метод определен следующим образом:
TWinControl* __fastcall FindNextControl(TWinControl* CurControL, bool GoForward,
bool CheckTabStop, bool CheckParent);
Он находит и возвращает следующий за указанным в параметре CurControl дочерний оконный компонент в соответствии с последовательностью табуляции.
Параметр GoForward определяет направление поиска. Вели он равен true, то поиск проводится вперед и возвращается компонент, следующий за CurControl. Если же параметр GoForward равен false, то возвращается предшествующий компонент.
Параметры CheckTabStop и CheckParent определяют условия поиска. Если CheckTabStop равен true, то просматриваются только компоненты, в которых свойство TabStop установлено в true. При CheckTabStop равном false значение TabStop не принимается во внимание. Если параметр CheckParent равен true, то просматриваются только компоненты, в свойстве Parent которых указан данный оконный элемент, т.е. просматриваются только прямые потомки. Если CheckParent равен false, то просматриваются все, даже косвенные потомки данного элемента.
Таким образом, для решения поставленной нами задачи автоматической передачи фокуса при нажатии клавиши Enter, вы можете написать единый обработчик событий OnKeyDown всех интересующих вас оконных компонентов, содержащий оператор:
if (Key == VK_RETURN)
FindNextControl((TWinControl *)Sender, true, true, false)->SetFocus();
Этот оператор обеспечивает передачу фокуса очередному компоненту. Для кнопок аналогичный оператор можно вставить в обработчик события OnClick:
FindNextControl( (TWinControl *)Sender, true, true, false)->SetFocus();
В приведенных кодах используется параметр Sender, передаваемый во все обработчики событий и соответствующий тому компоненту, в котором произошло событие. Этот параметр имеет тип TObject. А поскольку функция FindNextControl требует параметр объекта типа TWinControl, необходимо явным образом привести тип TObject к производному от него типу TWinControl с помощью операции (TWinControl *)Sender (см. подробнее о работе с параметром Sender в гл. 1, разд. 1.9.6.2).
5.1.9 Подсказки и контекстно-зависимые справки
Приложение должно предельно облегчать работу пользователя, снабжая его системой подсказок, помогающих сориентироваться в приложении. Эта система включает в себя:
Ярлычки, которые всплывают, когда пользователь задержит курсор мыши над каким-то элементом окна приложения. В частности, такими ярлычками обязательно должны снабжаться быстрые кнопки инструментальных панелей, поскольку нанесенные на них пиктограммы часто не настолько выразительны, чтобы пользователь без дополнительной подсказки мог понять их назначение.
Кнопка справки в полосе заголовка окна, позволяющая пользователю посмотреть во всплывающих окнах назначение различных элементов окна.
Более развернутые подсказки в панели состояния или в другом отведенном под это месте экрана, которые появляются при перемещении курсора мыши в ту или иную область окна приложения.
Встроенную систему контекстно-зависимой оперативной справки, вызываемую по клавише F1.
Раздел меню Справка, позволяющий пользователю открыть стандартный файл справки Windows .hlp, содержащий в виде гипертекста развернутую информацию по интересующим пользователя вопросам.
Тексты ярлычков и подсказок панели состояния устанавливаются для любых визуальных компонентов в свойстве Hint в виде строки текста, состоящей из двух частей, разделенных символом вертикальной черты '|’. Первая часть, обычно очень краткая, предназначена для отображения в ярлычке; вторая более развернутая подсказка предназначена для отображения в панели состояния или ином заданном месте экрана. Например, в кнопке, соответствующей команде открытия файла, в свойстве Hint может быть задан текст:
Открыть|Открытие текстового файла
Как частный случай, в свойстве Hint может быть задана только первая часть подсказки без символа '|’.
Для того чтобы первая часть подсказки появлялась во всплывающем ярлычке, когда пользователь задержит курсор мыши над данным компонентом, надо сделать следующее:
Указать тексты свойства Hint для всех компонентов, для которых вы хотите обеспечить ярлычок подсказки.
Установить свойства ShowHint (показать подсказку) этих компонентов в true или установить в true свойство ParentShowHint (отобразить свойство ShowHint родителя) и установить в true свойство ShowHint контейнера, содержащего данные компоненты.
Конечно, вы можете устанавливать свойства в true или false программно, включая и отключая подсказки в различных режимах работы приложения.
При ShowHint, установленном в true, окно подсказки будет всплывать, даже если компонент в данный момент недоступен (свойство Enabled = false).
Если вы не задали значение свойства компонента Hint, но установили в true свойство ShowHint или установили в true свойство ParentShowHint, а в родительском компоненте ShowHint = true, то в окне подсказки будет отображаться текст Hint из родительского компонента.
Правда, все описанное выше справедливо при значении свойства ShowHint приложения Application равном true (это значение задано по умолчанию). Если установить Application->ShowHint в false, то окна подсказки не будут появляться независимо от значений ShowHint в любых компонентах.
Свойства Hint компонентов можно также использовать для отображения текстов заключенных в них сообщений в какой то метке или панели с помощью функций GetShortHint и GetLongHint, первая из которых возвращает первую часть сообщения, а вторая вторую (если второй части нет, то возвращается первая часть). Например, эти функции можно использовать в обработчиках событий OnMouseMove, соответствующих прохождению курсора мыши над данным компонентом. Так обработчик
void fastcall TForm1::Button1MouseMove (TObject *Sender, TShiftState Shift, int X, int Y)
{
TControl *Send = (TControl *)Sender;
Panel1->Caption = GetShortHint(Send->Hint);
Panel2->Caption = GetLongHint(Send->Hint);
}
отобразит в панели Panel1 первую, а в панели Рапеl2 вторую часть свойства Hint всех компонентов, над которыми будет перемещаться курсор, если в этих компонентах в событии OnMouseMove указан этот обработчик Button1MouseMove. Причем это не зависит от значения их свойства SfaowHint.
Еще один пример. Пусть вы хотите, чтобы при нажатии некоторой кнопки Button1 вашего приложения в панели Panel1 высвечивалась подсказка пользователю, например, «Укажите имя файла», а сама кнопка имела всплывающий ярлычок подсказки с текстом «Ввод». Тогда вы можете задать свойству Hint этой кнопки значение «Ввод|Укажите имя файла», задать значение true свойству ShowHint, а в обработчик события нажатия этой кнопки вставить оператор
Panel1->Caption = GetLongHint (Button1->Hint>;
Если же вы не хотите отображать ярлычок подсказки для кнопки, то можете ограничиться значением Hint, равным «Укажите имя файла», а приведенный выше оператор оставить неизменным или заменить на эквивалентный ему в данном случае оператор
Panel1->Caption = GetShortHint(Button1->Hint); или даже просто на оператор
Panel1->Caption - Button1->Hint;
Перед тем моментом, когда должен отобразиться ярлычок какого-нибудь компонента, возникает событие приложения OnShowHint. В обработчике этого события можно организовать какие-то дополнительные действия, например, изменить отображаемый текст. Особенно легко работать с событиями приложения с помощью компонента ApplicationEvents, перехватывающего все эти события (см. подробнее в разд. 4.6). В обработчик его события OnShowHint можно поместить те операторы, которые надо выполнить перед отображением ярлычка. Заголовок этого обработчика имеет вид:
void __fastcall TForm1::AppllcationEventslShowHint(AnsiString &HintStr,
bool &CanShow, THintlnfo &Hintlnfo)
Здесь передаваемый по ссылке параметр HintStr отображаемый в ярлычке текст. В обработчике этот текст можно изменить. Так же по ссылке передается параметр CanShow. Если в обработчике установить его равным false, то ярлычок отображаться не будет. Третий параметр, передаваемый по ссылке Hintlnfo. Это структура, поля которой содержат информацию о ярлычке: его координаты, цвет, задержки появления и т.п. В частности, имеется поле HintControl компонент, сообщение которого должно отображаться в ярлычке, и поле HintStr отображаемое сообщение. По умолчанию Hintlnfo.HintStr первая часть свойства Hint компонента. Но в обработчике это значение можно изменить.
В разд. 3.2.3 и 4.6 приведены примеры использования обработчика события OnShowHint для отображения в ярлычке длинных текстов, содержащихся в окнах редактирования Edit.
Имеется еще один способ отображения второй части сообщения, записанного в Hint, в строке состояния или какой-то области экрана в моменты, когда курсор мыши проходит над компонентом это использование обработки события приложения OnHint. Это событие не того компонента, над которым проходит курсор мыши, а именно приложения объекта Application, так что оно перехватывается компонентом ApplicationEvents. Если обработчик этого события определен, то в момент прохождения курсора над компонентом, в котором задано свойство Hint, вторая часть сообщения компонента заносится в свойство Hint объекта Application. Если свойство Hint компонента содержит только одну часть, то в свойство Hint объекта Application заносится эта первая часть.
Если ваше приложение содержит инструментальную панель с быстрыми кнопками, то, как правило, эти кнопки должны снабжаться не только всплывающими
ярлычками, но и развернутыми подсказками в панели состояния (см. пример панели на рис. 3.5). Если у вас есть подобный пример, вы можете опробовать на нем методику отображения подсказок в панели состояния. А можете взять какой-нибудь более простой пример. Для реализации подсказок в панели состояния надо перенести на форму панель состояния компонент StatusBar со страницы Win32 (см. разд. 3.9.6). Если вам нужна односекционная панель, установите свойства SimplePanel и AutoHint панели StatusBar в true. Больше ничего делать не требуется свойство AutoHint обеспечит автоматическое отображение подсказок. Запустите приложение на выполнение. Вы увидите, что тексты подсказок отображаются в панели состояния, когда курсор мыши перемещается над тем или иным окном редактирования. Причем это не мешает появляться ярлычкам, отображающим тексты окон.
Если вы используете многосекционную панель состояния, то свойство AutoHint обеспечит отображение подсказок только в первой секции. Для отображения подсказок в другой секции надо перенести на форму компонент ApplicationEvents и в обработчик его события OnHint компонента вставить оператор
StatusBar1->Panels->Items[I]->Text = Application->Hint; где I индекс секции.
Более подробные пояснения пользователю может дать контекстно-зависимая справка, встроенная в приложение. Она позволяет пользователю нажать в любой момент клавишу F1 и получить развернутую информацию о том компоненте в окне, который в данный момент находится в фокусе. Для того, чтобы это осуществить, надо разработать для своего приложения файл справки hlр. Процедура создания этих файлов подробно описана в [3]. В каждом компоненте, для которого вы хотите обеспечить контекстно-зависимую справку, надо задать свойства, обеспечивающие ссылку на соответствующую тему справки.
В версиях, младше C++Builder 6, такое свойство одно HelpContext. Это номер темы, который задается в проекте справки специальной таблицей [MAP], содержащей эти условные номера и соответствующие им идентификаторы тем. В C++Builder 6 появилось еще два свойства: HelpKeyword и HelpType. Первое из них является идентификатором темы, содержащимся в сноске К. А второе определяет, каким свойством HelpContext или HelpKeyword задается ссылка на тему. Если HelpType = htContext используется свойство HelpContext; если HelpType = htKeyword используется свойство HelpKeyword.
Если HelpContext компонента равен нулю, то данный компонент наследует это свойство от своего родительского компонента. Например, для всех компонентов, размещенных на некоторой панели можно задать HelpContext = 0, а для самой панели задать отличное от нуля значение HelpContext, соответствующее теме, описывающей назначение всех компонентов панели.
Для того чтобы все это работало, надо выполнить команду Project
·Options и в окне Project Options (опции проекта) на странице Application (приложение) установить значение опции Help file, равное имени подготовленного файла .hlр. Это приведет к тому, что в головном файле проекта появится оператор вида:
Application->HelpFile = "<имя файла>. hlр";
В этом операторе используется метод HelpFile, определяющий файл справки, к которому обращается проект. Этот метод и подобный оператор можно использовать в приложении в любом обработчике события, если в какие-то моменты требуется сменить используемый файл справки.
Если предполагается, что файл справки будет расположен в том же каталоге, где находится само приложение, то имя файла и в окне Опции проекта, и в приведенном выше операторе надо задавать без указания пути. Иначе приложение, работающее на вашем компьютере, перестанет работать на компьютере пользователя, у которого каталоги не совпадают с вашими.
Для того, чтобы приложение в свойствах HelpContext могло ссылаться на какой-то номер контекстной справки, в файле проекта справки .hpj в разделе [MAP] надо поместить таблицу соответствия использованных значений HelpContext и тем файла hlр. Все необходимые для этого операции подробно рассмотрены в [3],
В заключение поговорим о традиционном разделе меню Справка, позволяющем пользователю открыть файл справки hlр и получить развернутую информацию по всем вопросам, связанным с данным приложением. В обработчик события при выборе данного раздела меню или при нажатии соответствующих кнопок помощи вставляются операторы вида
Application->HelpContext(<номер темы>);
Задаваемые в этих операторах номера тем аналогичны используемым при задании свойств HelpContext. Это номер той темы, которая первой отобразится при открытии окна справки. А в дальнейшем пользователь, как обычно, может перейти, работая с программой справки, к любой интересующей его теме.
Имеется еще несколько методов объекта Application, обеспечивающих работу со справочными файлами. Все они подробно рассмотрены в разд. 4.6.
Еще один механизм подсказок, связанный с файлом справок .hlр кнопка справки, присутствующая в заголовках многих современных окон Windows. Нажав ее, пользователь может подвести курсор мыши, изменивший свою форму на вопросительный знак, к какому-то компоненту, щелкнуть и во всплывшем окне появится развернутая подсказка, поясняющая назначение данного компонента. Для того чтобы ввести такую возможность в свое приложение, надо установить в true подсвойство byHelp свойства Borderlcons вашей формы. Однако не для всех форм это вызовет появление в заголовке окна кнопки справки. Кнопка появится только в случае, если свойство BorderStyle формы установлено в bsDialog. Никакого программирования работа с кнопкой справки не требует. Все будет выполняться автоматически. Достаточно предусмотреть в файле .hlр соответствующие темы и сделать на них ссылки в свойствах HelpContext компонентов. Эти темы будут появляться при соответствующих действиях пользователя во всплывающих окнах.
Возможности контекстно-зависимой справки не ограничиваются изложенным выше. Можно обеспечить отображение тем во всплывающих окнах, обеспечить программное управление кнопкой справки, способами отображения тем и многое другое. Все это подробно изложено в источниках [3] и [5].
5.2 Проектирование окон с изменяемыми размерами
5.2.1 Выравнивание компонентов свойство Align
Бели проектируется окно, размеры которого пользователь может изменять во время выполнения приложения, то необходимо принять меры, чтобы компоненты в окне при этом тоже изменяли свои размеры или местоположение, равномерно распределяясь по площади окна и не оставляя пустых мест.
Пусть, например, вы проектируете форму, окно которой содержит панель Panel1, на которой будут размещаться какие-то управляющие компоненты и список ListBox1, панель Рапеl2, в середине которой будет размещаться некоторая надпись в метке StaticText1, и компонент Memo1, в котором будут редактироваться тексты (рис. 5.7 а). Если, разместив все это на форме, вы не примете мер для того, чтобы при изменении размеров окна компоненты изменялись, то при том размере формы, который вы проектировали, все будет выглядеть нормально (рис. 5.7 а). Но если пользователь растянет размеры формы, надеясь увеличить площадь редактирования и длину списка, то приложение приобретет нелепый вид, показанный на рис. 5.7 б. Увеличение формы просто приводит к увеличению на ней пустого места.
Рис. 5.7 Результаты изменения размеров окна при неправильном проектировании
Чтобы избежать таких неприятностей, у многих компонентов и, в частности, у панелей, есть свойство Align выравнивание. По умолчанию оно равно alNone, что означает, что никакое выравнивание не осуществляется. Но его можно задать равным alTор, или alBottom, или alLeft, или alRight, что будет означать, что компонент должен занимать всю верхнюю, или нижнюю, или левую, или правую часть клиентской области компонента-контейнера. Под клиентской областью понимается вся свободная площадь формы или другого контейнера, в которой могут размещаться включенные в этот контейнер компоненты. Можно также задать свойству Align компонента значение alClient, что приводит к заполнению компонентом всей свободной клиентской области. Во всех этих случаях размеры компонента будут автоматически изменяться при изменении размеров контейнера. Начиная с C++Builder 6, добавлено еще одно возможное значение alCustom (заказное). В этом случае положение компонента определяется вызовами CustomAlignlnsertBefore и CustomAlignPosition (см. раздел «Align» в гл. 17, в разд. 17.1).
В приведенном выше примере логично для панели Рапеl2 задать значение Align, равным alTop, чтобы ширина панели автоматически менялась с изменением ширины окна. Панель займет всю верхнюю часть формы, а ее высота будет такой, какую вы установите. Для панели Panel1 следует задать значение Align, равным alLeft, так как увеличение ее ширины не имеет смысла, а увеличение высоты позволяет увидеть большую часть списка ListBox1. Компонент Panel1 займет всю левую часть клиентской области, оставшейся свободной после размещения Рапе12. Для компонента Memo1 следует задать значение Align, равным alClient, что позволит компоненту увеличиваться и в высоту, и в ширину, увеличивая площадь редактирования. При этом компонент Memo1 займет всю площадь клиентской области, оставшуюся после размещения Panel1 и Рапе12. В результате во время выполнения при изменении пользователем размеров окна приложение будет иметь вид, представленный на рис. 5.8.
Рис. 5.8 Изменение размеров панелей при использовании выравнивания
Задавать компонентам соответствующие значения свойства Align в приведенном примере надо именно в указанной выше последовательности: alTop для Panel2, alLeft для Panel1, alClient для Memo1. Если, например, начать с задания alClient для Memo1, то компонент Memo1 займет всю площадь формы, и остальные компоненты вообще не будут видны.
Значения свойства Align имеют определенный приоритет: значения аlTор. и alBottom имеют более высокий приоритет, чем значения alLeft и alRight. Поэтому не любые варианты размещения и выравнивания панелей можно реализовать непосредственно. Например, пусть вы хотите создать форму, вид которой приведен на рис. 5.9 а. Причем вам надо, чтобы при изменении размеров окна панель Panel1 продолжала занимать всю левую часть формы (т.е. надо задать Align = alLeft), Рапеl3 занимала бы всю нижнюю часть площади, свободной от Panel1 (т.е. для Рапеl3 надо задать Align = alBottom), a Panel2 занимала бы всю оставшуюся часть клиентской области (т.е. для Рапеl2 надо задать Align = alClient). Напрямую все это сделать невозможно. Если вы задали для Panel1 значение alLeft, а затем для Рапеl3 задаете значение alBottom, то в силу приоритета alBottom панель Рапеl3 займет всю нижнюю часть формы, вытеснив оттуда Panell.
Рис. 5.9 Проектирование с учетом приоритетов значений Align
В подобных случаях приходится вводить дополнительные панели-контейнеры. В приведенном примере на форме сначала надо разместить Panel1 и дополнительную панель Рапеl4, как показано на рис. 5.9 б. Для Panel1 задается alLeft, a для Рапеl4 задается alClient. Затем панели Рапеl2 и Рапеl3 размещаются на Рапеl4 и для них задаются значения: alBottom для Рапеl3 (поскольку клиентской областью для Рапеl3 является область панели Рапеl4 то Рапеl3 займет нижнюю часть правой половины экрана) и значение alClient для Рапеl2. В результате получим нужное выравнивание всех панелей.
5.2.2 Изменение местоположения и размеров компонентов
Заданием значений Align задачу планировки окна с изменяющимися размерами еще нельзя считать решенной. Вернемся к ранее рассмотренному примеру. Рис. 5.8 показал, что в нем при изменении размеров окна метка StaticText1, остающаяся на месте, перестает быть в середине панели, а размер списка ListBox1 не изменяется.
Чтобы устранить эти недостатки, можно воспользоваться свойствами компонента, определяющими его привязку, местоположение и размеры.
Оконные компоненты имеют свойство Anchors привязку к родительскому компоненту при изменении размеров последнего. Это свойство представляет собой множество, которое может содержать следующие элементы:
акТор
Верхний край компонента привязан к верхнему краю родительского компонента.

akLeft
Левый край компонента привязан к левому краю родительского компонента.

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

akBottom
Нижний край компонента привязан к нижнему краю родительского компонента.

Если в множестве Anchors присутствуют привязки к противоположным сторонам родительского компонента, то при изменении размеров родительского компонента происходит растяжение или сжатие дочернего компонента, поскольку расстояния от сторон родительского компонента выдерживаются. Сжатие может происходить вплоть до полного исчезновения изображения данного компонента. Поэтому необходимо ограничивать диапазон допустимого изменения размеров. Способы такого ограничения будут рассмотрены в разд. 5.2.4.
По умолчанию привязка осуществляется к левому и верхнему краям родительского компонента. Т.е. по умолчанию свойство Anchors равно [akLeft, akTop]. Если задать в свойстве Anchors привязку к противоположным сторонам родительского компонента, то при изменении размеров родительского компонента будут меняться размеры и данного компонента. Если для списка ListBox1 в нашем примере задать свойство Anchors равным [akLeft, akTop, akBottom], то при изменении высоты панели, содержащей этот список, будут поддерживаться постоянными расстояния верхнего и нижнего краев списка соответственно от верхнего и нижнего краев панели. Таким образом, увеличивая высоту окна пользователь может увеличивать число строк, видимых в списке без прокрутки. Если задать свойство Anchors равное [akLeft, akTop, akRight] для метки StaticText1, то при изменении ширины содержащей ее панели будут поддерживаться постоянными расстояния левого и правого краев метки от соответствующих краев панели. Метка будет оставаться в центре панели, но ее размер будет изменяться. Вариант нашего примера с заданными описанным способом свойствами Anchors для ListBox1 и StaticText1, приведен на рис. 5.10. Как видно из него, теперь приложение сохраняет нормальный вид и при увеличении пользователем размеров окна. А в списке ListBox1 при увеличении размера даже исчезла полоса прокрутки, т.к. весь текст уместился на экране.
Рис. 5.10 Изменение размеров компонентов при использовании привязки Anchors
Для того чтобы это нормально работало, надо еще в обработчик события OnResize формы или панели, содержащей метку StaticText1, вставить оператор
StaticText1->Repaint();
Этот оператор обращается к методу Repaint, который перерисовывает метку. Если этого не сделать, то, как вы легко можете проверить, при изменении размеров окна в метке появится надпись на новом месте, но не исчезнет и прежняя надпись, т.е. получится сдвоенная надпись, что, конечно, недопустимо.
В приведенном примере неоправданно изменился размер метки StaticText1. Такое поведение компонента не всегда желательно. В подобных случаях надо переходить к программному изменению местоположения компонента.
Местоположение компонента задается свойствами Left и Тор, характеризующими координаты левого верхнего угла компонента. При этом началом координат считается левый верхний угол клиентской области компонента-контейнера. Горизонтальная координата отсчитывается вправо от этой точки, а вертикальная вниз.
Размеры компонента определяются свойствами Width ширина и Height высота. Есть еще одно свойство BoundsRect, характеризующее одновременно и местоположение, и размеры компонента, но оно менее удобно и используется редко.
Имеется еще два свойства, определяющие размер клиентской области компонента-контейнера: ClientWidth и ClientHeight. Для некоторых компонентов они совпадают со свойствами Width и Height. Но могут и отличаться от них. Например, в форме ClientHeight меньше, чем Height, за счет бордюра, полосы заголовка, меню и полосы горизонтальной прокрутки, если она имеется.
Поскольку нам надо изменять местоположение и размеры компонентов при изменении размеров формы, то соответствующие операторы следует размещать в обработчиках событий OnResize формы или соответствующей панели. Эти события возникают при любом изменении пользователем размеров окна приложения или размеров панели. В нашем примере оператор обработки этих событий может иметь вид: .
StaticTextl->Left = Panel2->Left+(Panel2->ClientWidth – StaticText1->Width) / 2;
Этот оператор сдвигает левый край компонента StaticText1 так, чтобы метка всегда размещалась в середине панели Рапеl2. Только чтобы это нормально работало, надо убрать привязку метки к правому краю панели, которая была введена нами ранее. Иначе размер метки все-таки будет меняться.
5.2.3 Панели с перестраиваемыми границами
В ряде случаев описанного автоматического изменения размеров различных панелей оказывается недостаточно для создания удобного пространства для действий пользователя. Даже при увеличении окна на весь экран какая-то панель приложения может оказаться перегруженной информацией, а другая относительно пустой. В этих случаях полезно предоставить пользователю возможность перемещать границы, разделяющие различные панели, изменяя их относительные размеры. Пример такой возможности можно увидеть в программе Windows «Проводник».
В библиотеке C++Builder на странице Additional имеется специальный компонент Splitter, который позволяет легко осуществить это. При работе с ним надо соблюдать определенную последовательность проектирования. Если вы хотите установить Splitter между двумя панелями, первая из которых будет выровнена к какому-то краю клиентской области, а вторая займет всю клиентскую область, то сначала надо выровнять первую панель, например, влево. Затем надо перенести на форму Splitter и выровнять его в ту же сторону (тоже влево). Splitter прижмется к соответствующему краю первой панели. После этого можно задать выравнивание второй панели на всю оставшуюся площадь клиентской области. В результате Splitter окажется зажатым между двумя панелями и при запуске приложения он
позволит пользователю изменять положение соответствующей границы между этими панелями.
Подобные разделители Splitter можно разместить между всеми панелями приложения, дав пользователю полную свободу изменять топологию окна, с которым он работает.
Компонент Splitter имеет событие OnMoved, которое наступает после конца перемещения границы. В обработчике этого события надо предусмотреть, если необходимо, упорядочение размещения компонентов на панелях, размеры которых изменились: переместить какие-то метки, изменить размеры компонентов и т.д.
Свойство ResizeStyle компонента Splitter определяет поведение разделителя при перемещении его пользователем. Поэкспериментируйте с ним, чтобы увидеть различие в режимах перемещения разделителя. По умолчанию свойство Splitter равно rsPattern. Это означает, что пока пользователь тянет курсором мыши границу, сам разделитель не перемещается, и панели тоже остаются прежних размеров. Перемещается только шаблон линии, указывая место намечаемого перемещения границы. Лишь после того, как пользователь отпустит кнопку мыши, разделитель переместится, и панели изменят свои размеры. Практически такая же картина наблюдается, если установить ReaizeStyle = rsLine. При ResizeStyle = rsUpdate в процессе перетаскивания границы пользователем разделитель тоже перемещается, и размеры панелей все время меняются. Это, может быть, удобно, если пользователь хочет установить размер панели таким, чтобы на ней была видна какая-то конкретная область. Но так как процесс перетаскивания в этом случае сопровождается постоянной перерисовкой панелей, наблюдается неприятное мерцание изображения. Так что этот режим можно рекомендовать только в очень редких случаях. Если установить ResizeStyle = rsNone, то в процессе перетаскивания границы не перемещается ни сама граница, ни изображающая ее линия. Вряд ли это удобно пользователю, так что я не могу представить случая, когда было бы выгодно использовать этот режим.
Свойство MinSize компонента Splitter устанавливает минимальный размер в пикселах обеих панелей, между которыми зажат разделитель. Задание такого минимального размера необходимо, чтобы при перемещениях границы панель не сжалась бы до нулевого размера или до такой величины, при которой на ней исчезли бы какие-то необходимые для работы элементы управления. К сожалению, в версиях C++Builder, младше C++Builder 5, свойство MinSize не всегда срабатывает верно. Начиная с C++Builder 5, введено новое свойство компонента Splitter AutoSnap. Если оно установлено в true (по умолчанию), то при перемещении границы возможны те же неприятности, что в младших версиях C++Builder. Но если установить AutoSnap в true, то перемещение границы панелей сверх пределов, при которых размер одной из панелей станет меньше MinSize, просто блокируется. Так что можно рекомендовать всегда устанавливать AutoSnap в true.
Впрочем, и это не решает всех задач, связанных с перемещением границ панелей. Дело в том, что свойство MinSize относится к обеим панелям, граница между которыми перемещается, а в ряде случаев желательно раздельно установить различные минимальные размеры одной и другой панели. Это проще сделать, задав в панелях соответствующие значения свойства Constraints, о котором будет рассказано в следующем разделе.
5.2.4 Ограничение пределов изменения размеров окон и компонентов
Все рассмотренные ранее методы изменения размеров панелей и компонентов на них имеют общий недостаток: при чрезмерном уменьшении пользователем размеров окна какие-то компоненты могут исчезать из поля зрения. Иногда к некрасивым с точки зрения эстетики результатам приводит и чрезмерное увеличение размеров окна. Хотелось бы иметь средства, ограничивающие пользователя в его манипуляциях с окном и не позволяющие ему чрезмерно уменьшать и увеличивать размеры.
Таким средством является свойство Constraints, присущее всем компонентам и позволяющее задавать ограничения на допустимые изменения размеров. Свойство имеет четыре основных подсвойства: MaxHeight, Max Width, MinHeight и MinWidth соответственно максимальная высота и ширина и минимальная высота и ширина. По умолчанию значения всех этих подсвойств равны 0, что означает отсутствие ограничений. Но задание любому из этих свойств положительного значения приводит к соответствующему ограничению размера заданным числом пикселов.
Чтобы какие-то компоненты не исчезали из поля зрения, можно задать им ограничения минимальной высоты и длины. Таким образом можно поддерживать нормальные пропорции отдельных частей окна. Можно задать ограничения на минимальные и максимальные размеры формы, т.е. всего окна. Например, если вы зададите для формы значения MaxHeight = 500 и MaxWidth = 500, то пользователь не сможет сделать окно большим, чем квадрат 500x500. Причем это ограничение будет действовать, даже если пользователь нажмет системную кнопку, разворачивающую окно на весь экран. Окно развернется, но его размеры не превысят заданных. Это иногда полезно делать, чтобы развернутое окно не заслонило какие-то другие нужные пользователю окна.
5.2.5 Масштабирование компонентов
Говоря о размерах компонентов, следует упомянуть еще об одной возможности их изменения о методе ScaleBy. Он определен как:
void__fastcall ScaleBy(int M, int D);
где М и D множитель и делитель, определяющие изменение масштаба компонента. Масштабируются такие свойства компонента, как Width и Height, определяющие его размер. Свойства Тор и Left остаются неизменными. Масштабируется также размер шрифта, если только в компоненте не установлено ParentFon = true. В последнем случае шрифт наследуется от родительского компонента и поэтому не изменяется.
Если компонент является контейнером, содержащим другие компоненты, то его дочерние компоненты также масштабируются. Причем у них изменяются не только Width и Height, но также пропорционально изменяются Тор и Left, определяющие их местоположение. Если во всех дочерних компонентах установлено ParentFont = true, а в компоненте-контейнере ParentFont = false, то пропорционально изменяются и шрифты всех компонентов (но, конечно, не непрерывно, а скачками, доступными тому или иному типу шрифта).
Параметры М и D определяют соответственно множитель и делитель масштаба. Например, чтобы уменьшить размеры на 10% от начального значения, можно задать М равным 9, a D равным 10(9/10). Если же вы хотите увеличить размер на 1/3, то можно задать М = 133 и D = 100 (133/100) или М = 4 и D = 3 (4/3).
Приведем примеры. Оператор
Edit1->ScaleBy(11,10);
масштабирует окно редактирования Edit1. В любом случае при выполнении этого оператора длина окна (свойство Width) увеличивается на 10%, что обеспечивает возможность наблюдать и редактировать в нем более длинный текст. Высота окна (свойство Height) будет изменяться пропорционально, только если свойство компонента AutoSize равно false. В противном случае высота определяется только размером шрифта и при постоянном шрифте будет неизменной. А размер шрифта будет меняться, только если свойство компонента ParentFont равно false, т.к. иначе шрифт определяется родительским компонентом. Таким образом, при AutoSize = true и ParentFont = true изменяется только длина окна редактирования.
Оператор
Panell->ScaleBy(11,10);
увеличивает размер панели, а также координаты и размер всех ее компонентов. Если в панели ParentFont = false, то шрифты во всех компонентах также увеличиваются независимо от значений их свойства ParentFont, Причем они увеличиваются и в неоконных компонентах, например, в метках. Бели же в панели ParentFont = true, то шрифты увеличиваются только в компонентах, в которых ParentFont = false.
Приведенный выше оператор изменяет масштаб сразу группы компонентов, но при этом сдвигает их позиции, поскольку действует на свойства Тор и Left. Можно избежать этого, обращаясь по отдельности к каждому дочернему оконному компоненту панели с помощью, например, такого оператора:
for (int i = 0; i< Panel1->ControlCount; i++)
if(Panell->Controls[i]->InheritsFrom(__classid(TWinControl)))
((TWinControl *)PaneZl->Controls[i])->ScaleBy(11,10);
При этом надо иметь в виду, что непосредственно применять ScaleBy можно только к оконным компонентам, являющимся наследниками класса TWinControl. Поэтому в приведенном операторе сначала проверяется, является ли компонент наследником TWinControl. Для этого к компоненту применяется функция InheritsFrom, возвращающая true, если класс объекта наследует классу, передаваемому в функцию как параметр. В данном случае в качестве параметра в функцию передается класс TWinControl, который преобразуется в нужную форму с помощью ключевого слова __classid.
Если установлено, что компонент является оконным (его класс наследует TWinControl), то к нему применяется функция ScaleBy, для чего используется явное приведение его типа к TWinControl::(TWinControl *).
Приведенный код масштабирует только оконные компоненты. Неоконные компоненты, например метки, масштабироваться не будут.
Подробнее функции и подходы, использованные в приведенном коде, рассмотрены в гл. 1, в разд. 1.9.6.2.
5.3 Обработка событий клавиатуры и мыши
Все действия пользователя при взаимодействии с приложением сводятся к перемещению мыши, нажатию кнопок мыши и нажатию клавиш клавиатуры. Рассмотрим обработку в приложении событий, связанных с этими манипуляциями пользователя.
5.3.1 События мыши
5.3.1.1 Последовательность событий
В компонентах C++Builder определен ряд событий, связанных с мышью. Это события:
Событие
Описание

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

OnDblClick
Двойной щелчок мыши на компоненте.

OnMouseDown
Нажатие клавиши мыши над компонентом. Возможно распознавание нажатой кнопки и координат курсора мыши.

OnMouseMove
Перемещение курсора мыши над компонентом. Возможно распознавание нажатой кнопки и координат курсора мыши.

OnMouseUp
Отпускание ранее нажатой кнопки мыши над компонентом. Возможно распознавание нажатой кнопки и координат курсора мыши.

OnStartDrag
Начало процесса «перетаскивания» объекта. Возможно распознавание перетаскиваемого объекта.

OnDragOver[
Перемещение «перетаскиваемого» объекта над компонентом. Возможно распознавание перетаскиваемого объекта и координат курсора мыши.

OnDragDrop
Отпускание ранее нажатой кнопки мыши после «перетаскивания» объекта. Возможно распознавание перетаскиваемого объекта и координат курсора мыши.

OnEndDrag
Еще одно событие при отпускании ранее нажатой кнопки мыши после «перетаскивания» объекта. Возможно распознавание перетаскиваемого объекта и координат курсора мыши.

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

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

OnMouseWheel
Событие при вращении колесика мыши в любую сторону.

nMouseWheelUp
Вращение колесика мыши вверх. Наступает, если вращение не обработано по событию OnMouseWheel.

OnMouseWheelDown
Вращение колесика мыши вниз. Наступает, если вращение не обработано по событию OnMouseWheel.

OnMouseUp
Отпускание ранее нажатой кнопки мыши над компонентом. Возможно распознавание нажатой кнопки и координат курсора мыши.

Как видно из приведенной таблицы, эти события охватывают все мыслимые манипуляции с мышью и даже дублируют многие из них. Наиболее широко используется событие OnClick. Обычно оно наступает, если пользователь щелкнул на компоненте, т.е. нажал и отпустил кнопку мыши, когда указатель мыши находился на компоненте. Но это событие происходит также и при некоторых других действиях пользователя. Оно наступает, если:
Пользователь выбрал элемент в сетке, дереве, списке, выпадающем списке, нажав клавишу со стрелкой.
Пользователь нажал клавишу пробела, когда кнопка или индикатор были в фокусе.
Пользователь нажал клавишу Enter, а активная форма имеет кнопку по умолчанию, указанную свойством Default.
Пользователь нажал клавишу Esc, а активная форма имеет кнопку прерывания, указанную свойством Cancel.
Пользователь нажал клавиши быстрого доступа к кнопке или индикатору. Например, если свойство Caption индикатора записано как «&Полужирный» и символ 'П’ подчеркнут, то нажатие пользователем комбинации клавиш Alt+П вызовет событие OnClick в этом индикаторе.
Приложение установило в true свойство Checked радиокнопки RadioButton.
Приложение изменило свойство Checked индикатора CheckBox.
Вызван метод Click элемента меню.
Для формы событие OnClick наступает, если пользователь щелкнул на пустом месте формы или на недоступном компоненте.
Обилие событий, связанных с мышью, а также фактическое дублирование некоторых из них требуют четкого представления о последовательности отдельных событий, наступающих при том или ином действии пользователя. Рассмотрим эти последовательности.
В момент запуска приложения из рассматриваемых нами событий наступает только событие OnEnter в компоненте, на который передается фокус. Это событие не связано с какими-то действиями пользователя, так что не будем на нем останавливаться.
Теперь рассмотрим простейшее действие пользователя: переключение с помощью мыши фокуса с одного элемента на другой. Последовательность событий в этом случае приведена в табл. 5.1.
Таблица 5.1. Последовательность событий мыши при переключении фокуса
Действие пользователя
Событие

Перемещение курсора мыши в пределах первого компонента
Множество событий OnMouseMove
в первом компоненте

Перемещение курсора мыши в пределах формы
Множество событий OnMouseMove в форме

Перемещение курсора мыши в пределах второго компонента
Множество событий OnMouseMove во втором компоненте

Нажатие кнопки мыши
OnExit в первом компоненте
OnEnter во втором компоненте
OnMouseDown во втором компоненте

Отпускание кнопки мыши
OnClick во втором компоненте
OnMouseUp во втором компоненте

События OnMouseMove происходят постоянно в процессе перемещения курсора мыши и даже просто при его дрожании, неизбежном, если пользователь не снимает руки с мыши. Это надо учитывать и пользоваться этим событием очень осторожно, поскольку оно, в отличие от других, происходит многократно.
Как видно из приведенной таблицы, каждое действие пользователя, связанное с нажатием или отпусканием кнопки мыши приводит к серии последовательно наступающих событий. В обработчиках событий OnMouseDown и OnMouseUp можно распознать, какая кнопка мыши нажата, и в какой точке компонента находится в данный момент курсор мыши.
Рассмотренная в табл. 5.1 последовательность событий имеет место, если во втором компоненте свойство DragMode (см. разд. 5.4) равно dmManual (ручное начало процесса перетаскивания), как это установлено по умолчанию. Если же это свойство равно dmAutomatic (автоматическое начало процесса перетаскивания), то все рассмотренные события, связанные с манипуляцией мышью, заменяются следующими:
OnMouseDown
Заменяется на OnStartDrag.

OnMouseMove
Заменяется на событие OnDragOver того компонента, над которым перемещается курсор мыши.

OnMouseUp
Заменяется на событие OnDragDrop компонента, над которым завешается перетаскивание (если компонент может воспринять информацию от перетаскиваемого объекта), и последующее событие OnEndDrag компонента, который перетаскивался.

События OnExit и OnEnter вообще не возникают, поскольку переключения фокуса не происходит. Не наступает также событие OnClick.
В дальнейшем в данном разделе события, связанные с перетаскиванием, рассматриваться не будут. Подробное описание этих событий см. в разд. 5.4,
Если в примере, приведенном в табл. 5.1, щелчок делается на объекте, который уже находится в этот момент в фокусе, то не происходят события OnExit и OnEnter. В этом случае при нажатии кнопки наступает только событие OnMouseDown, а при отпускании кнопки события OnClick и OnMouseUp.
Теперь рассмотрим последовательность событий при двойном щелчке на компоненте. Она приведена в табл. 5.2. Распознавать нажатую кнопку мыши по-прежнему можно только в событиях OnMouseDown и OnMouseUp. Если же надо распознать именно двойной щелчок какой-то определенной кнопкой мыши, то можно, например, ввести некую переменную, являющуюся флагом двойного щелчка, устанавливать этот флаг в обработчике события OnDblClick, а в обработчиках событий OnMouseDown или OnMouseUp проверять этот флаг и, если он установлен, то сбрасывать его и выполнять запланированные действия.
Таблица 5.2. Последовательность событий мыши при двойном щелчке на компоненте
Действие пользователя
Событие

Первое нажатие кнопки мыши
OnMouseDown. Возможно распознавание нажатой кнопки и координат курсора мыши.

Первое отпускание кнопки мыши
OnClick
OnMouseUp. Возможно распознавание нажатой кнопки и координат курсора мыши.

Второе нажатие кнопки мыши
OnDblClick
OnMouseDown. Возможно распознавание нажатой кнопки и координат курсора мыши.

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

5.3.1.2 Распознавание источника события, нажатых кнопок и клавиш, координат курсора
Во все обработчики событий, связанных с манипуляциями мыши (как и во все другие обработчики) передается параметр Sender типа указателя на TObject. Этот параметр содержит указатель на компонент, в котором произошло событие. Он не требуется, если пишется обработчик события для одного конкретного компонента. Однако часто один обработчик применяется для нескольких компонентов. При этом какие-то операции могут быть общими для любых источников события, а какие-то требовать специфических действий. Тогда Sender можно использовать для
распознания источника события. Правда, поскольку тип TObject не имеет никаких полезных для пользователя свойств и методов, то объект Sender следует рассматривать как объект одного из производных от TObject типов.
Операции, связанные с обработкой параметра Sender, подробно рассмотрены в гл. 1, в разд. 1.9.6.2. Там показано, как можно распознать объект, на который указывает Sender, по имени, узнать его класс, определить, является ли этот класс наследником какого-то другого определенного класса.
Помимо параметра Sender в обработчики событий OnMouseDown и OnMouseUp передаются параметры, позволяющие распознать нажатую кнопку, нажатые при этом вспомогательные клавиши, а также определить координаты курсора мыши. Заголовок обработчика события OnMouseDown или OnMouseUp может иметь, например, следующий вид:
void__fastcall TForml:;EditlMouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
Помимо уже рассмотренного нами параметра Sender в обработчик передаются параметры Button, Shift, X и Y.
Параметр Button типа TMouseButton определяет нажатую в этот момент кнопку мыши. Тип TMouseButton перечислимый тип, определяемый следующим образом:
enum TMouseButton { mbLeft, mbRight, mbMiddle };
Значение mbLeft соответствует нажатию левой кнопки мыши, значение mbRight правой, а значение mbMiddle средней. Например, если вы хотите, чтобы обработчик реагировал на нажатие только левой кнопки, вы можете его первым оператором написать:
if(Button != mbLeft)
return;
Тогда, если значение Button не равно mbLeft, т.е. нажата не левая кнопка, выполнение обработчика прервется.
Параметр Shift типа TShiftState определяет, какие вспомогательные клавиши на клавиатуре нажаты в момент нажатия кнопки мыши. Тип TShiftState множество, определенное следующим образом:
enum Classes__1 { ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble };
typedef Set TShiftState;
Элементы этого множества соответствуют нажатию клавиш Shift (ssShift), Alt (ssAlt), Ctrl (ssCtrl), а также кнопок: левой (ssLeft), правой (ssRight), средней (ssMiddle). Информация о нажатых кнопках в параметрах Button и Shift имеет различный смысл. Параметр Button соответствует кнопке, нажимаемой в данный момент, а параметр Shift содержит информацию о том, какие кнопки были нажаты, включая и те, которые были нажаты ранее. Например, если пользователь нажмет левую кнопку мыши, а затем, не отпуская ее, нажмет правую, то после первого нажатия множество Shift будет равно [ssLeft], а после второго [ssLeft, ssRight]. Если до этого пользователь нажал и не отпустил какие-то вспомогательные клавиши, то информация о них также будет присутствовать в множестве Shift.
Поскольку Shift является множеством, проверять наличие в нем тех или иных элементов надо методом Contains (см. разд. 14.6 в гл. 14). Например, если вы хотите прореагировать на событие, заключающееся в нажатии левой кнопки мыши при нажатой клавише Alt, можно использовать оператор:
if((Button == mbLeft) && (Shift.Contains{ssAlt)))

В приведенной структуре if первое условие (Button == mbLeft) можно заменить эквивалентным ему условием, проверяющим параметр Shift:
if (Shift.Contains(ssLeft) && (Shift.Contains(ssAlt)))
Аналогичные параметры Button и Shift передаются и в обработчик события OnMouseUp. Отличие только в том, что параметр Button соответствует не нажимаемой в данный момент, а отпускаемой кнопке. Параметр Shift передается также в обработчик события OnMouseMove, так что и в этом обработчике можно определить, какие клавиши и кнопки нажаты.
Во все события, связанные с мышью, передаются также координаты курсора X и Y. Эти параметры определяют координаты курсора в клиентской области компонента. Благодаря этому можно обеспечить различную реакцию в зависимости от того, в какой части клиентской области расположен курсор.
Рассмотрим теперь события OnMouseWheel, OnMouseWheelUp и OnMouseWheelDown. Эти события связаны с вращением колесика, которое имеется в современных вариантах мыши. Заголовок обработчика события OnMouseWheel имеет вид:
void __fastcall TForm1::FormMouseWheel(TObject *Sender,
TShiftState Shift, int WheelDelta, TPoint SMousePos, bool SHandled)
Параметры Sender и Shift не отличаются от рассмотренных ранее. Параметр WheelDelta показывает, сколько раз повернулось колесико. Это положительное число при вращении вверх и отрицательное при вращении вниз. Параметр MousePos типа TPoint (см. в разд. 17.4) определяет позицию курсора мыши, а передаваемый по ссылке параметр Handled указывает, завершена ли обработка события.
Если обработчик события OnMouseWheel отсутствует или если в нем задано Handled = false, то, в зависимости от направления вращения колесика, наступает событие OnMouseWheelUp или OnMouseWheelDown. Заголовки обработчиков этих событий одинаковы и имеют вид:
void __fastcall TForm1::FormMouseWheelDown(TObject *Sender,
TShiftState Shift, TPoint SMousePos, bool &Handled)
Они отличаются от обработчика OnMouseWheel только отсутствием параметра WheelDelta.
Многие оконные компоненты многострочные окна редактирования, списки и т.п. имеют обработчики вращений колесика по умолчанию. Это относится к Memo, RichEdit, ListBox, TrackBar и многим другим. Правда, эти обработчики по умолчанию будут срабатывать только в том случае, если в компоненте заданы полосы прокрутки (собственно, это обработчики не самих элементов, а полос прокрутки).
Обработчики по умолчанию обеспечивают при вращении колесика сдвиг содержимого в соответствующем направлении. Так что писать обработчики событий, связанных с колесиком, имеет смысл только в случаях, когда надо обеспечить нестандартную реакцию компонентов. Например, если вы зададите для формы обработчики
void fastcall TForm1::FormMouseWheelUp(TObject *Sender, TShiftState Shift,
TPoint SMousePos, bool &Handled)
{
ScaleBy(101,100);
}
void __fastcall TForm1::FormMouseWheelDown(TObject *Sender, TShiftState Shift,
TPoint SMousePos, bool SHandled)
{
ScaleBy(100,101);
}
то при вращении колесика размеры всех компонентов формы будут плавно уменьшаться или увеличиваться.
Мы рассмотрели работу с событиями мыши на уровне компонентов. Существуют и другие возможности отслеживания этих событий и управления курсором мыши. Можно с помощью так называемых ловушек перехватывать события мыши, наступающие в любых приложениях, исполняющихся на компьютере. Можно эмулировать события мыши и тем самым управлять сторонними приложениями. Можно задавать положение и форму курсора мыши, в том числе использовать и анимированные курсоры. Конечно, рассмотрение всех этих возможностей выходит далеко за рамки этой книги. Вы можете найти всю необходимую информацию по перечисленным вопросам в книге [3].
5.3.2 События клавиатуры
5.3.2.1 Последовательность событий
В оконных компонентах C++Builder определены три события, связанные с клавиатурой. Это события:
Событие
Описание

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

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

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

Кроме того, при нажатии пользователем клавиши табуляции фокус может переключаться с элемента на элемент, что вызывает описанные в разд. 5.3.1.1 события OnEnter и OnExit.
Важно четко представлять последовательность событий, происходящих при нажатии пользователем клавиши или комбинации клавиш. Пусть, например, пользователь нажал клавишу Shift (перевел ввод в верхний регистр), а затем нажал клавишу символа 'н'. Последовательность событий для этого случая приведена в табл. 5.3. В таблице указано, что именно можно распознать при каждом событии. Подробнее это будет рассмотрено ниже, а пока отметим, что различить символ в верхнем и нижнем регистрах, и различить латинский символ и символ кириллицы можно только в обработчике события OnKeyPress. Действительно, хотя в событии OnKeyDown при нажатии клавиши 'н' можно определить, что при этом одновременно нажата и клавиша Shift, этого еще мало, чтобы утверждать, что символ относится к верхнему регистру. Ведь если перед этим была включена клавиша CapsLock, то при нажатой клавише Shift символ окажется в нижнем регистре. А информация о том, включена или выключена клавиша CapsLock, в обработчик события OnKeyDown не передается.
Таблица 5.3. Последовательность событий клавиатуры при нажатии клавиш Shift-н
Действие пользователя
Событие

Нажатие клавиши Shift
OnKeyDown. Возможно распознавание нажатой клавиши Shift.

Нажатие клавиши «н»
OnKeyDown. Возможно распознавание нажатой клавиши Shift, нажатой клавиши «н», но отличить верхний регистр от нижнего и латинский символ от русcкого невозможно.


OnKeyPress. Возможно распознавание символа с учетом регистра и языка, но невозможно распознавание; нажатой клавиши Shift.

Отпускание клавиши «н»
OnKeyUp. Возможно распознавание нажатой клавиши Shift, отпущенной клавиши «н», но отличить верхний регистр от нижнего и латинский символ от русского невозможно.

Отпускание клавиши .Shift
OnKeyUp. Возможно распознавание отпущенной клавиши Shift.

Следует отметить, что событие OnKeyPress заведомо наступает, если нажимается только клавиша символа или клавиша символа при нажатой клавише Shift. Бели же клавиша символа нажимается одновременно с какой-то из вспомогательных клавиш, то событие OnKeyPress может не наступить (произойдут только события OnKeyDown при нажатии и OnKeyUp при отпускании) или, если и наступит, то укажет на неверный символ. Например, при нажатой клавише Alt событие OnKeyPress при нажатии символьной клавиши не наступает. А при нажатой клавише Ctrl событие OnKeyPress при нажатии символьной клавиши наступает, но символ не распознается.
В заключение надо остановиться на вопросе, куда поступают события клавиатуры. У формы имеется свойство KeyPreview. Оно влияет на обработку событий, поступающих от клавиатуры (в число этих событий не входит нажатие клавиш со стрелками, клавиш табуляции и т.п.). По умолчанию свойство KeyPreview равно false и события клавиатуры поступают на обработчики, предусмотренные в активном в данный момент компоненте. Но если задать значение KeyPreview равным true, то сначала эти события будут поступать на обработчики формы, если таковые предусмотрены, и только потом поступят на обработчики активного компонента.
Имеется также событие OnShortCut приложения (Application), которое возникает при нажатии пользователем клавиши. Событие возникает до того, как возникло стандартное событие OnKeyDown компонента или формы. Это событие, как и все события приложения, перехватывает введенный в C++Builder 5 компонент ApplicationEvents. Обработчик этого события позволяет предусмотреть нестандартную реакцию на нажатие какой-то клавиши. В него передается параметр сообщения Windows Msg, поле CharCode которого (Msg.CharCode) содержит виртуальный код нажатой клавиши. Передается также по ссылке параметр Handled. Если задать ему значение true, то стандартные события OnKeyDown, OnKeyPress, OnKeyUp не наступят. В разд. 4.6 приведен пример использования обработчика события OnShortCut.
5.3.2.2 Распознавание нажатых клавиш
Заголовок обработчика события OnKeyDown может иметь, например, следующий вид:
void fastcall TForm1::Edit1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
Параметр Sender, указывающий на источник события, уже обсуждался выше при описании событий мыши (см. разд. 5.3.1.2). Там же рассматривался и параметр Shift, представляющий собой множество элементов, отражающих нажатые в это время функциональные клавиши. Только в обработчике события OnKeyDown множество возможных элементов параметра Shift сокращено до ssShift (нажата клавиша Shift), ssAIt (нажата клавиша Alt) и ssCtrl (нажата клавиша Ctrl). Информация о нажатых кнопках мыши отсутствует.
Основной параметр, которого не было раньше это параметр Key. Обратите внимание, что он передается по ссылке, т.е. может изменяться в обработчике события. Кроме того, обратите внимание, что это целое число, а не символ.
Параметр Key определяет нажатую в момент события клавишу клавиатуры. Для не алфавитно-цифровых клавиш используются виртуальные коды API Windows. Полная таблица этих кодов приведена в справочной части книги в гл. 16, разд. 16.1.1. Ниже, в табл. 5.4 приведены для дальнейшего обсуждения только несколько строк из нее, соответствующих наиболее распространенным клавишам.
Таблица 5.4. Некоторые коды клавиш
Клавиша
Десятичное число
Шестнадцатеричное число
Символическое имя
Сравнение по символу

F1
112
0x70
VK_F1


Enter
13
0x0D
VK_RETURN


Shift
116
0x10
VK_SHIFT


Ctrl
17
0x11
VK CONTROL


Alt
18
0x12
VK MENU


Esc
27
0x1B
VK_ESCAPE


0)
48
0x30

0

1 !
149
0x31

1

n N т Т
78
0x4E

N

у Y н Н
89
0x59

Y

Параметр Key является целым числом, определяющим клавишу, а не символ. Например, один и тот же код соответствует прописному и строчному символам "Y" и "у". Если, как это обычно бывает, в русской клавиатуре этой клавише соответствуют символы кириллицы "Н" и "н", то их код будет тем же самым. Различить прописные и строчные символы или символы латинские и кириллицы невозможно.
Проверять нажатую клавишу можно, сравнивая Key с целым десятичным кодом клавиши, приведенном во втором столбце табл. 5.4. Например, реакцию на нажатие пользователем клавиши Enter можно оформить оператором:
if(Key ==13)
... ;
Можно сравнивать Key и с шестнадцатеричным эквивалентом кода, приведенным в третьем столбце табл. 5.4. Например, приведенный выше оператор можно записать в виде:
if(Key == 0x0D)
... ;
Для клавиш, которым не соответствуют символы, введены также именованные константы, которые облегчают написание программы, поскольку не требуют помнить численные коды клавиш. Например, приведенный выше оператор можно записать в виде:
if(Key == VK_RETURN)
... ;
Для клавиш символов и цифр можно производить проверку сравнением с десятичным или шестнадцатеричным кодом, но это не очень удобно, так как трудно помнить коды различных символов. Другой путь воспользоваться тем, что коды латинских символов в верхнем регистре совпадают с виртуальными кодами, используемыми в параметре Key. Поэтому, например, если вы хотите распознать клавишу, соответствующую символу "Y", вы можете написать:
if(Key == 'Y')
... ;
В этом операторе можно использовать только латинские символы в верхнем регистре. Если вы напишете 'у' или захотите написать русские символы, соответствующие этой клавише 'Н' или 'н', то оператор не сработает.
Помните также, что оператор будет действовать на все символы, относящиеся, к указанной клавише: "Y", "у", "Н" и "н". Иногда это хорошо, а иногда плохо. Например, если вы задали пользователю какой-то вопрос, на который он должен ответить Y (да) или N (нет), то подобный оператор избавляет пользователя от необходимости следить, в каком регистре он вводит символ и какой язык английский или русский включен в данный момент. Ему достаточно просто нажать клавишу, на которой написано "Y". Однако если пользователь более привык к символам кириллицы, то могут возникнуть неприятности, поскольку нажимая клавишу с латинской буквой "Y" и русской буквой "Н" он может думать, что отвечает не положительно (Yes да), а отрицательно (Нет).
Приведем еще один пример автоматическую передачу фокуса очередному оконному компоненту при нажатии пользователем клавиши Enter. Это можно сделать, включив в общий обработчик событий OnKeyDown всех оконных компонентов оператор:
if(Key == VK_RETURN)
FindNextControl((TWinControl *)Sender, true, true, false)->SetFocus();
Этот оператор с помощью метода FindNextControl ищет очередной компонент в последовательности табуляции и передает ему фокус. Подробнее этот пример и метод FindNextControl рассмотрен в разд. 5.1.8.
В заключение приведем пример распознавания комбинации клавиш. Пусть вы хотите, например, распознать комбинацию Alt-X. Для этого вы можете написать оператор:
if((Key== 'X') && Shift.Contains(ssAlt))
... ;
Мы рассмотрели распознавание клавиш при событии OnKeyDown. Заголовок обработчика события OnKeyUp имеет такой же вид, так что все сказанное в равной степени относится и к событиям при отпускании клавиш.
Теперь перейдем к рассмотрению события OnKeyPress. Заголовок обработчика этого события имеет вид:
void _fastcall TForml: :Edit1KeyPress (TObject *Sender, char. &Key)
В этот обработчик, как и в описанные выше, передается параметр Key, определяющий нажатую клавишу символа. Но обратите внимание, что тип этого параметра не целое число, как в предыдущих случаях, a char символ. В данном случае в обработчик передается не виртуальный код клавиши, а символ, по которому можно определить, прописная это буква, или строчная, русская, или латинская. Поэтому описанных выше сложностей с распознаванием символов не возникает.
Пусть, например, вы задали пользователю вопрос, на который он должен ответить символами "Д" или "д" (да), или символами "Н" или "н" (нет). Тогда распознать положительный ответ в обработчике события OnKeyPress можно оператором:
if ((Key == 'Д') || (Key == 'д'))
... ;
Приведенный оператор реагирует только на положительный ответ пользователя, не реагируя на отрицательный или ошибочный ответ. Реакцию на все возможные ответы обеспечивает структура множественного выбора switch (см. разд. 13.8.1.2, в гл. 13):
switch(Key)
{
case 'Д’:
case 'д’:
... ;
break;
case 'H' :
case 'н';
...;
break;
default:
Beep();
}
Здесь предусмотрена реакция на положительный и отрицательный ответ, а также звуковой сигнал при ошибочном ответе.
Посмотрев на приведенный ранее заголовок обработчика, вы можете увидеть, что параметр Key передается по ссылке. Это позволяет в обработчике изменять Key, изменяя соответственно его стандартную обработку в компоненте, поскольку ваш обработчик события срабатывает раньше стандартного обработчика компонента. Пусть, например, вы хотите иметь на форме окно редактирования Edit1, в котором пользователь должен вводить только целые числа без знака. Вы можете обеспечить безошибочный ввод, подменяя все недопустимые символы нулевым с помощью, например, следующего кода:
Set Dig;
if (!(Dig < '0' < '1' < '2' < '3' < 4' < '5'
< '6' < '7' < '8' < '9' < '\b').Contains(Key))
If (! Dig.Contains(Key))
{
Key = 0;
Beep();
}
При нажатии пользователем любой клавиши, кроме клавиш с цифрами и клавиши BackSpace, символы подменяются нулевым символом и просто не появляются в окне редактирования, как вы можете убедиться, сделав приложение с этим простым примером. Символ Backspace ('\b') добавлен в число допустимых, чтобы упростить пользователю редактирование вводимого текста. При нажатии пользователем ошибочной клавиши функция Веер воспроизводит звуковой сигнал.
Имеется еще много возможностей работы с клавиатурой. Можно производить асинхронный опрос клавиш, который показывает, нажималась ли какая-то клавиша за определенный интервал времени. Можно эмулировать нажатие клавиш, обеспечивая, например, в нужные моменты работу с русской или английской раскладкой клавиатуры, с верхним или нижним регистром. Такую эмуляцию можно также использовать для управления сторонним приложением. Можно перехватывать с помощью так называемых ловушек события клавиатуры, наступающие в любых приложениях, исполняющихся на компьютере. Рассмотрение всех этих возможностей выходит, конечно, далеко за рамки этой книги. Вы можете найти всю необходимую информацию по перечисленным вопросам в книге [3].
5.4 Перетаскивание объектов
5.4.1 Перетаскивание информации об объектах технология Drag&Drop
Процесс перетаскивания с помощью мыши информации из одного объекта в другой (Drag&Drop), коротко называемый перетаскиванием, очень широко используется в Windows. Например, вы можете перемещать файлы между папками, перемещать сами папки, включая их в другие папки, и т.д. Посмотрим, как осуществляется подобное перетаскивание информации об объектах в C++Builder.
Все свойства, методы и события, связанные с процессом перетаскивания, определены в классе TControl, являющемся прародителем всех визуальных компонентов C++Builder. Поэтому они являются общими для всех компонентов.
Начало процесса перетаскивания определяется свойством DragMode, которое может устанавливаться в процессе проектирования или программно равным dmManual или dmAutomatic. Значение dmAutomatic (автоматическое) определяет автоматическое начало процесса перетаскивания при нажатии пользователем кнопки мыши над компонентом. Имейте в виду, что в этом случае событие OnMouseDown, связанное с нажатием пользователем кнопки мыши, для этого компонента вообще не наступает. Значение же dmManual (ручное) говорит о том, что начало процесса перетаскивания должен определять программист. Для этого он должен в соответствующий момент вызвать метод BeginDrag. Например, он может поместить вызов этой функции в обработчик события OnMouseDown, наступающего в момент нажатия кнопки мыши. В этом обработчике он может проверить предварительно какие-то условия (режим работы приложения, нажатие тех или иных кнопок мыши и вспомогательных клавиш) и при выполнении этих условий вызвать BeginDrag.
Пусть, например, процесс перетаскивания должен начаться, если пользователь нажал левую кнопку мыши и клавишу Alt над списком ListBox1. Тогда свойство DragMode этого компонента надо установить в dmManual, а его обработчик события OnMouseDown может иметь вид:
void__fastcall TForm1::ListBox1MouseDown (TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
if((Button == mbLeft) && Shift.Contains(ssAlt))
ListBox1->BeginDrag(false,5);
}
Параметр Button обработчика события OnMouseDown показывает, какая кнопка мыши была нажата, а параметр Shift является множеством, содержащим обозначения нажатых в этот момент кнопок мыши и вспомогательных клавиш клавиатуры (см. разд. 5.3.1.2). Приведенный выше оператор проверяет, нажаты ли левая кнопка мыши и клавиша Alt. Если нажаты, то вызывается метод BeginDrag данного компонента.
В предыдущем примере в функцию BeginDrag переданы значения параметров false и 5. Первый из них означает, что процесс перетаскивания начнется не сразу, а только после того, как пользователь сдвинет мышь с нажатой при этом кнопкой на некоторое число пикселов, а второй параметр указывает величину перемещения 5. Это позволяет отличить простой щелчок мыши от начала перетаскивания. Если же передать в BeginDrag значение true и любое число, то перетаскивание начнется немедленно.
Когда начался процесс перетаскивания, обычный вид курсора изменяется. Пока он перемещается над формой или компонентами, которые не могут принять информацию, он обычно имеет тип crNoDrop: . Если же он перемещается над компонентом, готовым принять информацию из перетаскиваемого объекта, то приобретает вид, определяемый свойством перетаскиваемого объекта DragCursor.
По умолчанию это свойство равно crDrag, что соответствует изображению . Надо подчеркнуть, что вид курсора определяется свойством DragCursor перетаскиваемого объекта, а не того объекта, над которым перемещается курсор.
В процессе перетаскивания компоненты, над которыми перемещается курсор, могут сообщать о готовности принять информацию от перетаскиваемого объекта. Для этого в компоненте должен быть предусмотрен обработчик события OnDragOver, наступающего при перемещении над данным компонентом курсора, перетаскивающего некоторый объект. В этом обработчике надо проверить, может ли данный компонент принять информацию перетаскиваемого объекта, и если не может, задать значение false передаваемому в обработчик параметру Accept. По умолчанию этот параметр равен true, что означает возможность принять перетаскиваемый объект. Обработчик для списка ListBox1 может иметь, например, следующий вид:
void __fastcall TForm1::ListBox1DragOver{TObject *Sender,
TObject *Source, int X, int Y, TDragState State, bool &Adcept)
{
if(Sender != Source)
Accept = Source->ClassNameIs("TListBox");
Else
Accept = false;
}
В нем сначала проверяется, не являются ли данный компонент (Sender) и перетаскиваемый объект (Source) одним и тем же объектом. Это сделано, чтобы избежать перетаскивания информации внутри одного и того же списка. Если источник и приемник являются одним и тем же объектом, то срабатывает else и параметр Accept становится равным false, запрещая прием информации. Если же это разные объекты, то Accept делается равным true, если источником является какой-то другой список (компонент класса TListBox), и равным false, если источник является объектом любого другого типа. Таким образом компонент ListBox1 сообщает, что готов принять информацию из любого другого списка.
Значение параметра Accept, задаваемое в обработчике события OnDragOver, определяет вид курсора, перемещающегося при перетаскивании над данным компонентом. Если в компоненте не описан обработчик события OnDragOver, то считается, что данный компонент не может принять информацию перетаскиваемого объекта.
Процедура приема информации от перетаскиваемого объекта записывается в обработчике события OnDragDrop принимающего компонента. Это событие наступает, если после перетаскивания пользователь отпустил кнопку мыши над данным компонентом. В обработчик этого события передаются параметры Source (объект-источник) и X и Y координаты курсора. Если продолжить начатый выше пример перетаскивания информации из одного списка в другой, то обработчик события OnDragDrop может иметь вид:
void__fastcall TForm1::ListBox1DragDrop(TObject *Sender, TObject *Source, int X, int Y)
{
TListBox *S = (TListBox *)Source;
((TListBox*)Sender)->Items->Add(S->Items->Strings[S->Itemlndex]);
}
В этом обработчике первый оператор создает указатель S на объект класса TListBox, и передает в него ссылку на объект Source, воспринимаемый как объект TListBox. Это сделано просто для того, чтобы не повторять несколько раз в следующем операторе приведение типа Source к указателю на объект класса TListBox. A такое приведение типа необходимо по следующей причине. Параметр Source объявлен как указатель на объект класса TObject. Но в этом классе отсутствуют свойства Items и Itemlndex. Они имеются только в классе TListBox. Поэтому прежде, чем обратиться к этим свойствам, надо произвести соответствующее приведение типа Source. Подробнее это изложено в разд. 1.9.6.2, гл. 1.
Второй оператор обработчика заносит методом Add выделенную строку списка-источника S в список-приемник Sender. В этом переносе информации из одного списка в другой и заключается в нашем примере Drag&Drop.
В приведенном обработчике можно было бы обойтись и без создания указателя S. В этом случае обработчик имел бы следующий вид:
void __fastcall TForm1::ListBoxlDragDrop(TObject *Sender, TObject *Source, int X, int Y)
{
((TListBox*)Sender)->Items->Add(((TListBox *)Source)->
Items->Strings[((TListBox *)Source)->ltemlndex]);
}
После завершения или прерывания перетаскивания наступает событие ОnEndDrag, в обработчике которого можно предусмотреть какие-то дополнительные действия. Имеется также связанное с перетаскиванием событие OnStartDrag, которое позволяет произвести какие-то операции в начала перетаскивания. Это событие полезно при автоматическом начале перетаскивания, когда иным способом этот момент нельзя зафиксировать.
Теперь, объединив приведенные выше фрагменты обработки перетаскивания, просуммируем, что надо сделать, если вы имеете в приложении несколько списков ListBox и хотите обеспечить возможность копирования строк каждого из этих списков в любой другой.
Это потребует двух операций:
Выделите мышью все списки приложения как одну группу, перейдите в Инспекторе Объектов на страницу событий, сделайте двойной щелчок около строки события OnDragOver и напишите в Редакторе Кода приведенный выше обработчик события OnDragOver.
Аналогичным образом напишите общий для всех списков приведенный выше обработчик события OnDragDrop.
И это все! Для обеспечения возможности перетаскивания вам потребовалось написать всего два оператора. Если вы хотите начинать перетаскивание только при выполнении какого-то дополнительного условия, например, при нажатии клавиши Alt, то вам потребуется сделать еще один шаг:
Задайте для всех списков значение свойства DragMode, равное dmManual. Напишите для этих списков приведенный выше общий обработчик события ОпMouseDown.
Теперь вы можете создать форму с несколькими списками, занести в них информацию, написать эти обработчики и проверить все это на практике. На рис. 5.11 приведен пример, демонстрирующий различные аспекты перетаскивания. Этот пример имеется на диске, прилагаемом к книге, в каталоге Chapter5\Drag в проекте PDrag. Можете попробо
·вать для тренировки сами создать аналогичный пример, позволяющий перетаскивать строки и метки из одного списка в другой и из списков в окно редактирования Memo. В примере демонстрируются разные способы перетаскивания: автоматическое, ручное, только при определенной нажатой клавише, перетаскивание строк без удаления из списка-источника (т.е. копирование) и с удалением (перемещение). Думаю, что изложенного выше достаточно, чтоб вы сами смогли создать подобный пример.
Рис. 5.11 Пример приложения, использующего Drag&Drop
5.4.2 Перетаскивание и встраивание объектов Drag&Doc. Плавающие окна
В C++Builder реализована технология перетаскивание и встраивание оконных объектов Drag&Doc. Вы можете познакомится с результатами этой технологии, работая с Интегрированной Средой Разработки C++Builder. Она предоставляет пользователю полную свободу в перестройке интерфейса. Отдельные окна могут объединяться вместе, создавать многостраничное окно с закладками, затем опять разъединяться и т.д. Посмотрим, как подобный подход можно реализовать в своем приложении.
У оконных компонентов введено свойство DockSite, которое по умолчанию равно false, но если его установить в true, то компонент становится контейнером: приемником, способным принимать переносимые в него компоненты клиенты. Еще одно свойство приемника UseDockManager. Если оно равно true (это принято по умолчанию), то процессом встраивания клиента автоматически управляет диспетчер встраивания, соответствующий тому компоненту, который является приемником. Если же задать UseDockManager равным false, то встраивание клиентов ложится на плечи программиста.
В компонентах потенциальных клиентах надо установить свойство DragKind в dkDock. Кроме того, если вы хотите, чтобы процесс перетаскивания начинался автоматически, то, так же, как и в технологии Drag&Drop, надо установить свойство DragMode в dmAutomatic. Если оставить значение DragMode равным dmManual по умолчанию, то управление процессом Drag&Doc осуществляется так же, как описывалось ранее для процесса Drag&Drop.
Посмотрим, к чему все это может привести. Давайте построим тестовое приложение, в котором осуществим Drag&Doc, не написав ни одной строчки кода.
Начните новое приложение. Перенесите на форму 6 панелей, расположив их примерно так, как показано на рис. 5.12 а). В панели Panel1 установите свойство DockSite в true. Эта панель сможет служить у вас приемником. В панелях Panel2 Panel6 установите DragKind в dkDock и DragMode в dmAutomatic. Запустите приложение. Попробуйте перетаскивать мышью панели Рапе12 Рапе16. Вы увидите (рис. 5.12 б), что панель Panel1 может принимать другие панели, размещая их внутри себя, причем способ встраивания зависит от того, в какой части приемника вы завершаете буксировку клиента. Между размещенными клиентами имеются разделители, которые позволяют вам перемещать буксировкой границы между клиентами, изменяя их относительные размеры. Вы можете также видеть, что даже независимо от наличия или отсутствия в приложении приемника, при щелчке на компонентах-клиентах они превращаются в самостоятельные плавающие окна (панели Panel5 и Рапеl6 на рис. 5.12 б), которые можно перемещать, выходя даже за пределы родительской формы, можно изменять их размеры, можно закрывать их, после чего соответствующие компоненты исчезают. Причем все это достигнуто только заданием соответствующих свойств, без единого написанного вами оператора.
Рис. 5.12 Демонстрация техники Drag&Doc: форма (а) и окно приложения во время выполнения (б)
Приемником может быть и сама форма. Укажите для формы вашего приложения DockSite равным true, и выполните приложение опять. Поведение перетаскиваемых панелей несколько изменится. При щелчке на компонентах-клиентах они по-прежнему превращаются в плавающие окна, которые можно перемещать, изменять их размеры и т.п. Но при повторном щелчке они опять становятся обычными панелями, сохраняя при этом измененное местоположение и размеры. Объясняется это тем, что повторный щелчок .воспринимается в этом случае как встраивание клиента в приемник форму.
Теперь посмотрим, как можно управлять из компонента-приемника всеми этими процессами.
У приемника имеется свойство DockClients, объявленное как
TControl* DockClients[int Index]
которое позволяет получить доступ к каждому клиенту, перенесенному в приемник. Свойство DockClientCount содержит число клиентов. Оба свойства DockClients и DockClientCount только для чтения.
Определены соответствующие события, которые позволяют приемнику управлять процессом встраивания. Они генерируются только в том случае, если в компоненте установлено DockSite = true.
Первое из этих событий OnGetSitelnfo, возникающее в момент начала перетаскивания и повторяющееся непрерывно в процессе перетаскивания. Заголовок соответствующего обработчика имеет вид:
void__fastcall TForm1::Panel1GetSiteInfo(TObject *Sender,
TControl *DockClient, TRect slnfluenceRect, TPoint &MousePos, bool &CanDock)
В качестве параметров в обработчик передается DockClient перетаскиваемый объект, InfluenceRect прямоугольник рамки перетаскиваемого объекта, который можно изменять, MousePos положение курсора мыши и CanDock разрешение перетаскивания. Если в обработчике указать CanDock = false, это будет означать, что приемник отказывается принимать компонент. Например, вы можете написать в обработчике события OnGetSitelnfo панели Panel1 оператор:
CanDock = DockClient != Panel6;
Тогда Panel1 будет принимать любой компонент, кроме Рапе16.
Еще одно событие OnDockOver, возникающее и повторяющееся, когда компонент-клиент перемещается над компонентом-приемником, точнее, когда в поле приемника войдет курсор мыши, буксирующий клиента. Это событие аналогично событию OnDragOver в технологии Drag&Drop. Заголовок соответствующего обработчика имеет вид:
void__fastcall TForm1::Panel1DockOver(TObject *Sender,
TDragDockObject *Source, int X, int Y, TDragState State, bool &Accept)
В этом обработчике вы можете, как и в описанном выше, проверить, хотите ли вы, чтобы данный оконный компонент принял перетаскиваемый компонент, определяемый параметром Source. Если принимать не надо, то задается значение Accept = false.
Например, прием контейнером Panel1 любой панели, кроме Рапеl6, обеспечивается оператором:
Accept = Source->Control != Panel6;
Параметр Accept будет равен true значению по умолчанию, только если перетаскивается на Рапеl6. Впрочем, того же самого результата вы достигали ранее в обработчике события OnGetSitelnfo.
Объект перетаскивания Source типа TDragDockObject имеет свойство DockRect типа TRect, описывающее перемещаемую рамку. Вы можете управлять ею. Например, если вы хотите, чтобы ваша Рапе15 вела себя наподобие панели Microsoft Office, которая может прижиматься к одной из сторон окна или перемещаться в середине окна в виде квадратной панели, вы можете, установив предварительно i приемнике (в форме вашего приложения или в панели Panel1) DockSite равным true, написать обработчик события OnDockOver в виде:
int х = ClientOrigin.x;
int у = ClientOrigin.y;
if(Source->Control == Panel5)
{
if(Source->DockRect.Left <= x)
Source->DockRect = Rect(x,y,x+25,y+ClientHeight);
else
if(Source->DockRect.Right >= x+Clie'ntWidth)
Source->DockRect = Rect(x+ClientWidth-25,y,x+ClientWidth,y+ClientHeight)
else
if (Source->DockRect.Top <= y)
Source->DockRect = Rect(x,y,x+ClientWidth,y+25);
else
if(Source->DockRect.Bottom >= y+ClientHeight)
Source->DockRect = Rect(x,y+ClientHeight-25,
x+ClientWidth,y+ClientHeight);
else
Source->DockRect = Rect(Source->DockRect.Left,
Source->DockRect.Top, Source->DockRect-Left+100,
Source->DockRect.Top+100);
}
В этом коде х и у это не параметры X и Y, передаваемые в обработчик через заголовок процедуры, а координаты левого верхнего угла клиентской области формы. A ClientWidth и ClientHeight это ширина и высота ее клиентской области.
Если в процессе буксировки панели Рапе15 ее координаты выходят за пределы клиентской области приемника, то панель вытягивается вдоль соответствующего края приемника, а ее ширина равна 25. При перемещении панели внутри окна она представляется квадратом со стороной 100.
Это будет нормально срабатывать, если для приемника, например, для панели Panel1, установить UseDockManager равным false. В противном случае сначала сработает диспетчер встраивания, растянув рамку клиента, а затем наступит событие OnDockOver, так что его обработчик будет иметь дело с уже измененной рамкой, что приведет к ошибке встраивания. Поэтому надо отключить диспетчер встраивания для клиента Рапе15, не отключая его для остальных клиентов. Это легко сделать, включив в обработчик события OnGetSitelnfo оператор
Panel1->UseDocRManager =- DockClient != Panel5;
Этот оператор задаст значение UseDoekManager равным true для всех потенциальных клиентов, кроме Рапе15.
Еще одно событие, возникающее в компоненте-приемнике OnDockDrop. Это событие возникает в момент окончания перетаскивания клиента над приемником. Оно аналогично событию OnDragDrop в технологии Drag&Drop. Заголовок соответствующего обработчика имеет вид:
void__fastcall TForml::FormDragDrop(TObject *Sender, TObject *Source, int X, int Y)
Этот обработчик позволяет разместить клиента Source тем или иным образом в зависимости от его имени, типа, местоположения координат X и Y, Например, в этот момент можно что-то изменить в надписях приемника, сигнализируя пользователя о приеме клиента и числе клиентов.
Событие, возникающее в компоненте-приемнике в момент, когда пользователь перетаскивает клиента из приемника OnUnDock. Заголовок соответствующего обработчика имеет вид:
void __fastcall TForm1::FormUnDock(TObject *Sender,
TControl *Client, TWinControl *NewTarget, bool &Allow)
В обработчик передаются как параметры Client компонент, который был клиентом, NewTarget новый приемник, в который пользователь перетаскивает клиента, и Allow параметр, определяющий, можно ли перетащить клиента. Например, если в вашем тестовом приложении вы поместите в обработчик события OnUnDock панели Panel1 оператор:
Allow = Client != Panel2;
то, выполняя приложение, обнаружите, что Рапеl2, попав клиентом в Panel1, уже не может оттуда выбраться, так как ей это запрещено.
Теперь, когда мы познакомились с основными возможностями технологии Drag&Doc, давайте построим что-нибудь более полезное, чем просто игра панелями. Но сначала сохраните ваше тестовое приложение, поскольку оно еще нам потребуется.
Давайте построим многооконный редактор текстов, имеющий хранилище, в которое можно помещать документы на хранение и затем извлекать из него нужные документы. Подобный пример вы найдете на диске, приложенном к книге, в каталоге Chapter5\Drag в проекте PDoc.
Начните новый проект и выполните следующие пункты.
Назовите форму (свойство Name) Fmain.
Разместите на форме компонент MainMenu и задайте в нем всего один пункт Новый, подразумевая под этим создание нового документа.
Поместите на форму панель, задайте ее свойство Caption равным Хранилище документов и задайте Align = alTop.
Поместите на форму компонент PageControl и задайте Align = alClient, чтобы он занимал вся площадь окна, кроме полосы, занятой панелью Хранилище документов (рис. 5.13 а). Задайте в нем свойство DockSite равным true. Этот компонент будет служить приемником документов.
Рис. 5.13 Формы для приложения, использующего технику Drag&Doc
Добавьте в проект еще одну форму, выполнив команду File
· New Form. Назовите ее FDoc (свойство Name). Задайте ее свойство Visible равным true. Установите ее свойство DragKind равным dkDock, а свойство DragMode равным dmAutomatic. Эта форма будет служить клиентом компонента TPageControI на форме Fmain.
Поместите на форму FDoc компонент Memo, задав для него Align = alClient, чтобы он занял всю форму (рис. 5.13 б). Сотрите в нем текст (свойство Lines).
Выполните команду Project
· Options и перенесите форму FDoc из окна Auto-create forms в окно Available forms, поскольку она должна создаваться не автоматически, а при выборе пользователем раздела меню Новый (если эта операция непонятна, см. разд. 5.5.1, рис. 5.16).
Сохраните проект, назвав модуль с первой формой Umain а со второй UDoc. Мы создали необходимые нам формы. Осталось написать несколько операторов, чтобы это все работало.
Выполните для модуля Umain команду File
·Include Unit Hdr и подключите с ее помощью заголовочный файл [ Cкачайте файл, чтобы посмотреть ссылку ]h, чтобы можно было ссылаться на модуль UDoc.
Введите глобальную переменную LDoc типа TList. Она представляет собой список, в котором будут храниться указатели на создаваемые пользователем формы документов. TList * Ldoc;
В обработчик события OnCreate формы Fmain запишите оператор
LDoc = new TList();
Этот оператор создает список LDoc.
В обработчик события OnDestroy формы Fmain запишите оператор
delete Ldoc;
Этот оператор освобождает память при закрытии приложения.
Осталось написать обработчик щелчка на разделе меню Новый. Он может иметь следующий вид:
TFDoc * New = new TFDoc(this);
LDoc->Add(New);
New->Caption = "Документ "+IntToStr(LDoc->Count);
В этом обработчике вводится переменная New типа TFDoc, соответствующего типу формы FDoc. Первый оператор обработчика динамически размещает новую форму FDoc и присваивает указатель на нее переменной New. Следующий оператор добавляет указатель на эту форму в список LDoc. А последний оператор задает заголовок окна формы FDoc, равным «Документ ...», где многоточие заменяется на номер, соответствующий числу строк в списке LDoc.
Теперь ваше приложение полностью готово. Сохраните его и попробуйте в работе (рис. 5.14). При щелчке на Новый создается новая форма документа, в котором вы можете писать текст. Документы можно помещать в хранилище, в котором каждому документу автоматически отводится новая страница. Вы можете работать с документом непосредственно в хранилище, а можете изъять его оттуда, превратив опять в отдельное окно.
Рис. 5.14 Приложение, использующее технику Drag&Doc, во время выполнения
Для того, чтобы сделать из вашего приложения законченный продукт, нужна еще, конечно, некоторая техническая работа. Надо бы добавить разделы меню, позволяющие открыть заданный пользователем файл и прочитать его в новый документ, позволяющие сохранить документ в файле, надо бы ввести при закрытии приложения или документа запрос пользователю о необходимости сохранить измененный документ и т.п. Для всех этих операций вам пригодится созданный вами список LDoc, который в приведенном упрощенном примере практически не использовался. Но мы не будем этим заниматься, так как все эти манипуляции подробно рассмотрены в разд. 3.10.2. А для того, чтобы почувствовать мощь технологии Drag&Drop, достаточно и сделанного вами примера. Причем, заметьте, что для его реализации вам пришлось написать всего несколько операторов.
В заключение рассмотрим еще некоторые свойства и методы, управляющие встраиванием и работой с плавающими окнами.
У оконных компонентов есть свойство Floating (только для чтения), которое показывает, является компонент свободно плавающим окном, или размещен в другом оконном компоненте-приемнике.
При переводе ранее размещенного компонента в состояние плавающего окна и при встраивании плавающего окна можно использовать свойства, в которых запоминаются размеры компонента в предыдущем состоянии:

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

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

TBDockHeight
Высота компонента, когда он в последний раз размещался в контейнере вертикально

LRDockWidth
Ширина компонента, когда он в последний раз размещался в контейнере горизонтально.

Пользуясь этими свойствами можно восстанавливать при необходимости предшествующие размеры компонента. В частности, по умолчанию после перевода компонента из размещенного состояния в состояние плавающего окна его размеры восстанавливаются исходя из свойств UndockHeight и UndockWidth.
Имеется метод ManualFloat, который переводит размещенный компонент в состояние плавающего окна. Он объявлен следующим образом:
bool __fastcall ManualFloat(const Windows::TRect &ScreenPos);
Параметр функции ScreenPos определяет положение и размер компонента после его перевода в состояние плавающего окна.
Например, вы можете открыть свой тестовое приложение (рис. 5.12) и ввести в него кнопку, которая будет переводить в состояние плавающего окна, предположим, панель Рапе13. Текст обработчика щелчка на такой кнопке может быть следующим:
TRect TempRect;
TPoint Р;
Р = Panel3->ClientToScreen(Point(0, 0)) ;
TempRect.Left = P.x;
TempRect.Top = P.y;
P = Panel3->ClientToScreen(Point(Panel3->UndockWidth, Panel3->UndockHeigbt));
TempRect.Right = P.x;
TempRect.Bottom = P.y;
Panel3->ManualFloat(TempRect);
В этом коде вводится переменная TempRect, в которой формируется прямоугольник, определяющий размер и местоположение генерируемого плавающего окна. Этот прямоугольник восстанавливает размер панели Рапе13, бывший у нее в последний раз, когда она отображалась плавающим окном. Левый верхний угол прямоугольника совпадает с текущим положением панели. Для правильного размещения окна координаты с помощью метода ClientToScreen переводятся в координаты экрана. Последний оператор методом ManualFloat генерирует плавающее окно.
И последний метод, который мы рассмотрим ManualDock, размещающий компонент в указанном приемнике. Его описание:
bool __fastcall ManualDock(TWinControl* NewDockSite, TControl* DropControl, TAlign ControlSide);
Параметр NewDockSite определяет приемник, в котором должен размещаться клиент. Второй параметр DropControl определяет другой компонент в приемнике NewDockSite, который должен разместить данного клиента. Как правило, этот параметр задается равным nil. Параметр ControlSide определяет выравнивание клиента внутри приемника. Если это единственный клиент в приемнике, то значение этого параметра безразлично. Но если там уже есть клиенты, то задание, например, ControlSide = alLeft приведет к перестановке, если только данный клиент не самый левый в приемнике.
Можете ввести в свое тестовое приложение кнопку, снабдив ее обработчиком:
Panel4->ManualDock (Panel1, NULL, alLef't);
Этот обработчик будет размещать Рапеl2 в Panel1, выравнивая ее по левому краю приемника, если в нем расположены еще какие-нибудь клиенты.
Имеется, однако, особенность, не оговоренная в документации: метод ManualDock срабатывает только в случае, если компонент-клиент был ранее в состоянии плавающего окна. Так что вам придется предварительно один раз перевести Рапеl2 в это состояние или щелчком на панели, или с помощью кнопки, соответствующей ранее рассмотренному методу ManualFloat. Другой способ обойти эту трудность автоматически ввести соответствующий метод ManualFloat в обработчик события формы OnCreate.
Наконец, еще одно нестандартное использование методов ManualFloat и МаnualDоск. Пусть, например, вы хотите иметь форму, содержащую несколько панелей, которые пользователь мог бы перемещать, не переводя их в состояние плавающего окна и не изменяя размеров. Тогда вы можете в форме установить DockSite = true, сделать в панелях соответствующие установки свойств DragKind в dkDock и DragMode в dmAutomatic, а в событии формы OnCreate выполнить для каждой панели методы ManualFloat и ManualDock, назначив приемником в МаnualDock форму. Вы получите приложение, в котором пользователь сможет перемещать панели мышью. Это может быть полезно в каких-то приложениях, связанных с проектированием топологии или конструированием. Подобные задачи будут рассмотрены в следующем разделе.
5.4.3 Буксировка компонентов в окне приложения
В ряде случаев в приложениях Windows используется перемещение отдельных компонентов в поле окна или какой-то панели. Это перемещение может быть перемещением в какие-то заранее определенные позиции (это легко осуществляется заданием соответствующих значений свойствам Left и Тор) или осуществляться непрерывной буксировкой компонента с помощью мыши. Простой пример этого буксировка компонентов по площади формы в среде разработки C++Builder. Подобные задачи возникают достаточно часто в технических приложениях, связанных с компоновкой какого-то устройства, с формированием каких-то схем (например, электрических) и т.п.
Опробовать различные способы буксировки можно в тестовом приложении, подобном приведенному на рис. 5.15 (проект Move в каталоге Chapter5\Move на приложенном к книге диске). В нем на форме размещено четыре компонента Image, в которые загружены какие-то изображения или части изображения (о загрузке в Image изображений и о работе с этими компонентами см. разд. 6.1.1). Если хотите воспроизвести пример, подобный рис. 5.15, в котором на отдельных компонентах, как на детских кубиках, воспроизведены фрагменты единого изображения, то можете в обработчик события формы OnCreate вставить следующий код:
TImage * Pict = new TImage(Form1);
Pict->AutoSize = true;
/* В следующем операторе вместо ... надо указать имя файла */
Pict->Picture->LoadFromFile("...");
Imagel->Canvas->CopyRect(Imagel->ClientRect, Pict->Canvas,
Rect(0,0,Pict->Width/2, Pict->Height/2);
Image2->Canvas->CopyRect(Image2->ClientRect, Pict->Canvas,
Rect(Pict->Width/2,0,Pict->Width,Pict->Height / 2));
Image3->Canvas->CopyRect(Image3->ClientRect, Pict->Canvas,
Rect(0,Pict->Height/2,Pict->Width/2,Pict->Height));
Image4->Canvas->CopyRect(Image4->ClientRect, Pict->Canvas,
Rect(Pict->Width/2,Pict->Height/2, Pict->Width,Pict->Height));
delete Pict;
Сейчас мы не будем анализировать этот код. Он станет вам понятен после изучения гл. 6. 6 компонентах Image свойство AutoSize должно быть установлено в true.
Рис. 5.15 Приложение, демонстрирующее буксировку компонентов
Начнем рассмотрение на этом примере приемов буксировки. Все приведенные далее обработчики событий записаны в общем виде. Так что вы можете применять их одновременно ко всем вашим компонентам Image, а можете к разным компонентам применить разные методы, чтобы легче их было сравнивать друг с другом.
Все методы используют несколько глобальных переменных, объявление которых надо поместить в модуле вне каких-либо процедур:
int X0, Y0;
bool move = false;
Переменная move определяет режим буксировки. Она будет устанавливаться в true в начале буксировки и сбрасываться в false в конце. Поэтому по значению move можно будет различать перемещения мыши с буксировкой и без нее. Переменные Х0 и Y0 потребуются нам для запоминания координат курсора мыши.
Один из возможных вариантов решения нашей задачи буксировка самого компонента. Буксировка начинается при нажатии левой кнопки мыши на соответствующем компоненте Image. Поэтому начало определяется событием OnMouseDown, обработчик которого имеет вид:
void __fastcall TForm1::ImagelMouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
if(Button != mbLeft)
return;
X0 = X;
Y0 = Y;
move = true;
((TControl *)Sender)->BringToFront();
}
Сначала в этой процедуре проверяется, нажата ли именно левая кнопка мыши (равен ли параметр Button значению mbLeft, обозначающему левую кнопку). Затем в переменных Х0 и Y0 запоминаются координаты мыши X и Y в этот момент времени. Задается режим буксировки переменная move устанавливается в true. Последний оператор выдвигает методом BringToFront компонент, в котором произошло событие ((TControl *)Sender), на передний план. Это позволит ему в дальнейшем перемещаться поверх других аналогичных компонентов. Данный оператор не обязателен, но если его не записать, то в процессе буксировки перемещаемый компонент может оказаться под другими компонентами.
Во время буксировки компонента работает его обработчик события OnMouseMove, имеющий вид:
void __fastcall TForml::ImagelMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
if(move]
{
TImage * Im = (Tlmage *)Sender;
Im->SetBounds(Im->Left + X – X0, Im->Top + Y - Y0, Im->Width, Im->Height);
}
}
Он изменяет с помощью метода SetBounds координаты левого верхнего утла на величину сдвига курсора мыши (X Х0 для координаты X и Y Y0 для координаты Y). Тем самым поддерживается постоянное расположение точки курсора в системе координат компонента, т.е. компонент смещается вслед за курсором. Ширина Width и высота Height компонента остаются неизменными. Изменение координат можно было бы задавать непосредственным изменением свойств Left и Тор:
Im->Left += X -Х0;
Im->Top += Y - Y0;
Но поскольку эти операторы выполняются поочередно, то компонент будет делать каждый раз два перемещения: сначала по горизонтали, а потом по вертикали. Это приведет к заметному на глаз дрожанию изображения.
По окончании буксировки, когда пользователь отпустит кнопку мыши, наступит событие OnMouseUp. Обработчик этого события должен содержать всего один оператор:
move = false;
указывающий приложению на окончание буксировки. Тогда при последующих событиях OnMouseMove их обработчик, приведенный ранее, перестанет изменять координаты компонента.
Рассмотренный вариант буксировки не свободен от недостатков. Основной из них некоторое дрожание изображения при перемещении, что выглядит не очень приятно. Устранить этот недостаток позволяет другой вариант, заключающийся в букировке не самого компонента, а его контура (этот вариант вы можете видеть на рис. 5.15 а). При этом отмеченных выше неприятных явлений не наблюдается, поскольку сам компонент перемещается только один раз в момент окончания буксировки, когда требуемое положение уже выбрано, В этом варианте используются методы рисования на канве, которые подробно рассматриваются в гл. 6, в разд. 6.1.3. Для их применения нам потребуется еще одна глобальная переменная:
TRect геc;
Это объявление следует добавить к приведенным ранее объявлениям move, Х0 и Y0. Переменная геc будет использоваться для запоминания положения перемещаемого контура компонента. '
Начинается процесс буксировки, как и ранее, с события OnMouseDown, которое обрабатывается процедурой вида:
void__fastcall TForm1::Image2MouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
if(Button != mbLeft)
return;
X0 = X;
Y0 = Y;
rec = ( (TControl *)Sender)->BoundsRect;
move = true;
}
Эта процедура отличается от рассмотренной ранее только оператором
rec = ((TControl *(Sender)->BoundsRect;
который запоминает в переменной гее исходное положение компонента. В процедуре отсутствует также оператор BringToFront, поскольку сам компонент не будет перемещаться и не приходится заботиться о том, чтобы он оказался поверх аналогичных компонентов.
При дальнейшем перемещении курсора мыши срабатывает обработчик события OnMouseMove, имеющий вид:
void__fastcall TForm1::Image2MouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
if(!move)
return;
Canvas->DrawFocusRect(rec);
rec.left += X – X0;
rec.right += X - XO;
rec.top += Y – Y0;
rec.bottom += Y - Y0;
X0 = X;
Y0 = Y;
Canvas->DrawFocusRect(rec);
}
В этой процедуре перерисовывается и сдвигается только прямоугольник контура компонента с помощью метода DrawFocusRect. Первое обращение к этому методу стирает прежнее изображение контура, поскольку повторная прорисовка того же изображения по операции ИЛИ (ог) стирает нанесенное ранее изображение (см. гл. 6, разд. 6.1.5). Затем изменяются значения, хранимые в переменной геc, и той же функцией DrawFocusRect осуществляется прорисовка сдвинутого прямоугольника. При этом сам компонент остается на месте.
Когда пользователь отпускает кнопку мыши, наступает событие OnMouseUp и срабатывает следующая процедура:
void__fastcall TForm1::Image4MouseUp(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
Canvas->DrawFocusRect(rec);
((TControl *)Sender)->SetBounds(
rec.Left + X – X0, rec.Top + Y - YO, ((TControl *)Sender)->Width, ((TControl *)Sender)->Height);
((TControl *) Sender)->BringToFront();
move = false;
}
Первый ее оператор стирает последнее изображение контура, а второй оператор перемещает компонент в новую позицию. Оператор BringToFront не является обязательным. Он проявит себя только в случае, если перемещенный компонент ляжет поверх другого аналогичного компонента.
Приведенный алгоритм исключает мерцание изображения, так как само перемещение компонента осуществляется только в момент отпускания кнопки мыши. К тому же при этом в обработчике события OnMouseUp легко предусмотреть какие-то условия отказа от перемещения (например, нажатую клавишу Alt). Для этого достаточно в приведенный выше обработчик добавить проверку нажатых клавиш:
Canvas->DrawFocusRect(rec);
if(! Shift.Contains (ssAlt))
{
((TControl *)Sender)->SetBounds(
rec.Left + X – X0, rec.Top + Y – Y), ((TControl *)Sender)->Width,
((TControl *)Sender)->Height);
((TControl *)Sender)->BringToFront();
}
move = false;
В этом случае, если пользователь перед завершением буксировки нажмет клавишу Alt, компонент не переместится. Это полезно в некоторых приложениях, связанных с каким-то проектированием топологии или размещения предметов: прикинув новое положение компонента, пользователь может тут же вернуться к предыдущему, если новое расположение неудачно. С другой стороны, для некоторых задач буксировка только контура мало информативна. Например, при проектировании какой-то мозаики перемещение контура не даст представления о том, подойдет ли компонент по своему виду к данному месту. В этих случаях предпочтительнее буксировка всего изображения.
Описанный выше способ перемещения изображения контура компонента можно осуществить иначе с использованием техники Drag&Dock и методов ManualFloat и ManualDock, описанных в разд. 5.4,2. Чтобы опробовать этот подход, установите в форме свойство DockSite равным true и сделайте в перемещаемых компонентах (в нашем примере в Image) установку свойств DragKind в dkDock и DragMode в dmAutomatic. Это надо сделать по крайней мере в двух компонентах. Если перемещаемым будет только один компонент, описываемый метод не сработает. В начале работы приложения надо выполнить для каждого компонента, который в дальнейшем может перемещаться, методы ManualFloat и ManualDock, назначив приемником в ManualDock форму. Для этого можно вставить в обработчик события формы OnCreate, например, следующие операторы:
Image3->ManualFloat(RectLeft+Image3->Left, Form1->Top+Image3->Top,
Form1->Left+Image3->Left+Image3->Width, Forml->Top+Image3->Top+Image3->Height));
Image3->ManualDock(Form1, NULL, alLeft);
Аналогичные операторы с заменой имени компонента Image3 надо вставить и для других перемещаемых компонентов.
И это все. Вам не надо писать никаких обработчиков событий компонентов. Вы получили приложение, в котором пользователь может перемещать мышью ваши изображения. При этом в процессе буксировки перемещается только контур изображения в виде утолщенной рамки (рис. 5.15 б). Сам компонент перемещается на новое место только в конце буксировки. Если хотите, то можете в обработчик события OnEndDock внести оператор
((TControl *)Sender)->BringToFront();
который обеспечит в момент переноса размещение компонента поверх других.
В заключение отметим еще один способ буксировки, пожалуй, наиболее универсальный и простой. Он применим к любым оконным компонентам, включая формы. Сошлитесь в событиях OnMouseMove всех компонентов, которые вы хотите буксировать, на следующий универсальный обработчик:
const int SC_DRAGMOVE = 0xF012;
ReleaseCapture();
((TControl *)Sender)->Perform(WM_SYSCOMMAND, SC_DRAGMOVE, 0);
Этот обработчик освобождает курсор мыши и посылает методом Perform источнику события Sender сообщение Windows, соответствующее буксировке.
Можете ссылаться на подобный обработчик в событиях панелей, кнопок, списков, окон редактирования. Все подобные компоненты пользователь сможет буксировать, перестраивая таким образом интерфейс приложения. Если вы сошлетесь на подобный обработчик в событии формы, то пользователь сможет перемещать ее по экрану, взявшись за любую ее точку, а не только за точку в заголовке окна, как это принято в обычных окнах Windows. Именно это реализовано в примере, содержащемся на приложенном к книге диске.
Мы рассмотрели буксировку компонентов по площади формы. Можно также обеспечить пользователю возможность, помимо буксировки, изменять размеры всех компонентов. В каталоге Chapter5\Move на диске, приложенном к книге, имеется заимствованный из книги [3] пример PSizeMove.exe, демонстрирующий эти возможности. Имеются приемы, позволяющие запоминать проведенные пользователем изменения и восстанавливать их в следующем сеансе работы с приложением. Решение подобных задач вы можете найти в справках [5] и в книге [3].
5.5 Формы
5.5.1 Управление формами
Основным элементом любого приложения является форма контейнер, в котором размещаются другие визуальные и невизуальные компоненты. Рассмотрим некоторые свойства, методы и события, присущие формам.
Обычно сколько-нибудь сложное приложение содержит несколько форм. Включение в проект новой формы осуществляется командой File
· New
· Form или другими способами, подробно описанными в разд. 2.4. По умолчанию все формы создаются автоматически при запуске приложения и первая из введенных в приложение форм считается главной. Главная форма отличается от прочих рядом свойств. Во-первых, именно этой форме передается управление в начале выполнения приложения. Во-вторых, закрытие пользователем главной формы означает завершение выполнения приложения. В-третьих, главная форма так же, как и любая другая, может быть спроектирована невидимой, но если все остальные формы зарыты, то главная форма становится в любом случае видимой (иначе пользователь не смог бы продолжать работать с приложением и даже не смог бы его завершить).
Указанные выше условия, принятые по умолчанию (первая форма главная, все формы создаются автоматически), могут быть изменены. Главной в вашем приложении может быть вовсе не та форма, которая была спроектирована первой. Не стоит также в общем случае все формы делать создаваемыми автоматически. В приложении могут быть предусмотрены формы (например, формы для установки различных опций), которые требуются далеко не в каждом сеансе работы с приложением. Было бы варварским расточительством создавать на всякий случай такие формы автоматически при каждом запуске приложения и занимать под них память. А в приложениях MDI дочерние формы в принципе не могут быть автоматически создаваемыми, так как число таких форм определяет пользователь во время работы приложения, создавая каждую новую форму командой типа Новое окно.
Изменить принятые по умолчанию условия относительно форм можно в окне Опций проекта, которое вызывается, например, командой Project
· Options главного меню. В открывшемся окне Опций проекта (Project Options) надо выбрать страницу Forms, представленную на рис. 5.16 для C++ Builder 6 (в C++Builder 2006. она выглядит, практически, так же).
Рис. 5.16 Страница Forms окна Опций проекта в C++Builder б
В верхнем выпадающем списке Main forms можно выбрать главную форму среди имеющихся в проекте. Пользуясь двумя нижними окнами можно установить, какие формы должны создаваться автоматически, а какие не должны. Например, если надо исключить форму Form2 из списка автоматически создаваемых, то надо выделить ее в левом окне (Auto-create forms) и с помощью кнопки со стрелкой, направленной вправо, переместить в правое окно доступных форм (Available forms).
Для каждой автоматически создаваемой формы C++Builder добавляет в файл программы соответствующий оператор ее создания методом CreateForm. Это можно увидеть, если выполнить команду View
· Project Source и просмотреть появившийся в окне Редактора Кода файл проекта. Для примера, показанного на рис. 5.16, он будет содержать следующие выполняемые операторы:
Application->Initialize ();
Application->CreateForm(__classid(TForml), &Forml);
Application->CreateForm{__classid(TAboutBox), &AboutBox);
Application->Run();
Первый из них инициализирует приложение, второй и третий создают соответствующие формы, а последний начинает выполнение приложения.
Для форм, которые были исключены из списка автоматически создаваемых, аналогичный метод CreateForm надо выполнить в тот момент, когда форма должна быть создана. Например, чтобы создать в нужный момент при выполнении кода в модуле формы Form1 форму Form2, надо выполнить оператор:
Application->CreateForm(__classid[TForm2), &Focm2);
Форма Form2 будет создана и, если ее свойство Visible установлено в true, то пользователь в тот же момент увидит ее на экране. Только для того, чтобы это сработало, надо в модуль формы Form1 включить заголовочный файл модуля формы Form2. Если имя этого файла Unit2.h. то можно вручную включить в модуль формы Form1 директиву препроцессора
#include "Unit2.h"
То же самое можно сделать, выполнив команду главного меню File
·Include Unit Hdr и выбрав в появившемся диалоговом окне имя нужного файла. Тогда указанная выше директива препроцессора будет записана в модуль автоматически.
В момент создания формы возникает последовательность событий:
Событие
Источник события
Описание

OnCreate
форма
создание формы и всех управляемых ею компонентов

OnShow
форма
после этого события форма становится видимой

OnActivate
форма
управление (фокус) передается данной форме

OnEnter
первый компонент в последовательности табуляции формы
фокус передается компоненту, первому в последовательности табуляции

OnResize
форма
переустанавливаются размеры формы

OnPaint
форма
прорисовка изображения формы

Обратите внимание на событие OnCreate. Обработка этого события широко используется для настройки каких-то компонентов формы, создания списков и т.д. Это событие для каждой формы происходит только один раз. Все остальные события могут в процессе выполнения приложения неоднократно повторяться.
В нужный момент форму можно сделать видимой методами Show или ShowModal. Последний метод открывает форму как модальную. Это означает, что управление передается этой форме и пользователь не может передать фокус другой форме данного приложения до тех пор, пока он не закроет модальную форму. Более подробное описание модальных форм и примеры работы с ними будут рассмотрены позднее в разд. 5.5.2.
Метод ShowModal можно применять только к невидимой в данный момент формы. Если нет уверенности, что форма в данный момент видима, то прежде, чем применять эти методы, следует проверить свойство Visible формы. При выполнении методов Show или ShowModal возникает событие формы onShow. Это событие возникает до того момента, как форма действительно станет видимой. Поэтому обработку события onShow можно использовать для настройки каких-то компонентов открываемой формы. Отличие от упомянутой ранее настройки компонентов в момент события onCreate заключается в том, что событие OnCreate наступает для каждой формы только один раз в момент ее создания, а события onShow наступают каждый раз, когда форма делается видимой. Так что при этом в настройке можно использовать какую-то оперативную информацию, возникающую в процессе выполнения приложения.
Методом Hide форму в любой момент можно сделать невидимой. В этот момент в ней возникает событие onHide.
Необходимо напомнить, что для выполнения методов CreateForm, Show, ShowModal, Hide и вообще для обмена любой информацией между формами модули соответствующих форм должны видеть друг друга, а для этого в них надо включать описанные выше директивы препроцессора #include. Например, если форма в модуле Unit1 должна управлять формой в модуле Unit2, то в модуль Unit1 должна быть включена инструкция #include "Unit2.h". А если к тому же форма в модуле Unit2 должна пользоваться какой-то информацией, содержащейся в модуле Unit1, то в модуль Unit2 должна быть включена инструкция #include "Unit1.h". - Закрыть форму можно методом Close. При этом в закрывающейся форме возникает последовательность событий, которые можно обрабатывать. Их назначение проверить возможность закрытия формы и указать, что именно подразумевается под закрытием формы. Проверка возможности закрытия формы необходима, например, для того, чтобы проанализировать, сохранил ли пользователь документ, с которым он работал в данной форме и который изменял. Если не сохранил, приложение должно спросить его о необходимости сохранения и, в зависимости от ответа пользователя, сохранить документ, закрыть приложение без сохранения или вообще отменить закрытие.
Рассмотрим последовательность событий, возникающих при выполнении метода Close.
Первым возникает событие onCloseQuery. В его обработчик передается по ссылке булева переменная CanClose, определяющая, должно ли продолжаться закрытие формы. По умолчанию CanClose равно true, что означает продолжение закрытия. Но если из анализа текущего состояния приложения или из ответа пользователя на запрос о закрытии формы следует, что закрывать ее не надо, параметру CanClose должно быть присвоено значение false. Тогда последующих событий, связанных с закрытием формы не будет.
Например, пусть в приложении имеется окно редактирования RichEditl, в котором свойство Modified указывает на то, был ли изменен пользователем текст в этом окне с момента его последнего сохранения. Тогда обработчик события onCloseQuery может иметь вид:
void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
if (RichEditl->Modified)
{
int res = Application->MessageBox(
"Текст документа не cохранен. \n\n" "Сохранить документ в файле?\п\п"
"(Отмена - продолжение работы)",
"Подтвердите завершение работы", MB_YESNOCANCEL + MB_ICONQUESTlON);
switch (res)
{
case IDYES:
MSaveClick(Sender);
break;
case IDCANCEL:
CanClose = false;
}
}
}
В приведенном обработчике вызывается методом Application->MessageBox (см. его подробное описание в гл. 16, в разд. 16.7.2.3) диалоговое окно, показанное на рис. 5.14. Если пользователь ответит «Да», то будет выполнена описанная в приложении процедура сохранения MSaveClick. Если пользователь ответит «Нет», то никаких действий в структуре switch производиться не будет. В обоих случаях после выхода из обработчика события значение CanClose останется равным своему значению по умолчанию true и процесс закрывания формы будет продолжен. Но если пользователь при запросе ответит «Отмена» или нажмет клавишу Esc, то значение CanClose станет равно false и окно не закроется. Этот обработчик сработает при любой попытке пользователя закрыть приложение: нажатии в нем кнопки или раздела меню Выход, нажатии кнопки системного меню в полосе заголовка окна и т.п.
Рис. 5.17 Диалоговое окно с запросом о завершении работы
Если обработчик события onCIoseQuery отсутствует или если в его обработчике сохранено значение true параметра CanClose, то следом наступает событие OnClose. В обработчик этого события передается по ссылке переменная Action, которой можно задавать значения:
caNone
He закрывать форму. Это позволяет и в обработчике данного события еще отказаться от закрытия формы.

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

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

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

Не все значения Action допустимы для любых форм. Например, для дочерних форм в приложении MDI возможны значения только caNone и caMinimize.
Если в обработчике события OnClose задано значение Action, равное caFree, то при освобождении памяти возникает еще одно последнее событие OnDestroy. Оно обычно используется для очистки памяти от тех объектов, которые автоматически не уничтожаются при закрытии приложения.
Начиная с C++Builder 4, формы имеют свойство OldCreateOrder, определяющее моменты событий OnCreate и OnDestroy. Если это свойство установлено в false (значение по умолчанию), то событие OnCreate наступает после того, как закончили работу все конструкторы компонентов, содержащихся на форме, а событие OnDestroy наступает прежде, чем вызывается какой-либо деструктор. При OldCreateOrder = true, что соответствует поведению компонентов в C++Builder 3 и более ранних версиях, событие OnCreate наступает при выполнении конструктора TCustomForm, а событие OnDestroy наступает при выполнении деструктора TCustomForm.
5.5.2 Модальные формы
Открытие форм как модальных используется в большинстве диалоговых окон. Модальная форма приостанавливает выполнение вызвавшей ее процедуры до тех пор, пока пользователь не закроет эту форму. Модальная форма не позволяет также пользователю переключить фокус курсором мыши на другие формы данного приложения, пока форма не будет закрыта. Так что пользователь должен выполнить предложенные ему действия прежде, чем продолжить работу.
Модальной может быть сделана любая форма, если она делается видимой методом ShowModal. Если та же самая форма делается видимой методом Show, то она не будет модальной.
Поведение модальной формы определяется ее основным свойством ModalResult. Это свойство доступно только во время выполнения приложения. При открытии формы методом ShowModal сначала свойство ModalResult равно нулю. Как только при обработке каких-то событий на форме свойству ModalResult будет присвоено положительное значение, модальная форма закроется. А значение ее свойства ModalResult можно будет прочитать как результат, возвращаемый методом ShowModal. Таким образом, программа, вызвавшая модальную форму, может узнать, что сделал пользователь, работая с этой формой, например, на какой кнопке он щелкнул.
В C++Builder предопределены некоторые константы, облегчающие трактовку результатов, полученных при закрытии модальной формы:
Численное значение Modal-Result
Константа
Пояснение

0
mrNone


1
mrOk или idOK
Закрытие модальной формы нажатием кнопки ОК

2
mrCancel или idCancel
Закрытие модальной формы нажатием кнопки Cancel, или методом Close, или нажатием кнопки системного меню в полосе заголовка окна

3
mrAbort или idAbort
Закрытие модальной формы нажатием кнопки Abort

4
mrRetry или idRetry

Закрытие модальной формы нажатием кнопки Retry

5
mrlgnore или idlgnore
Закрытие модальной формы нажатием кнопки Ignore

6
mrYes или idYes
Закрытие модальной формы нажатием кнопки Yes

7
mrNo или idNo

Закрытие модальной формы нажатием кнопки No

8
mrAll

Закрытие модальной формы нажатием кнопки Аll

9
mrNoToAll

Закрытие модальной формы нажатием кнопки NoТоАll

10
mrYesToAll
Закрытие модальной формы нажатием кнопки YesТо All

Все приведенные выше пояснения значений ModalResult (кроме значений 0 и 2) носят чисто условный характер. В своем приложении вы вольны трактовать ту или иную величину ModalResult и соответствующие константы как вам угодно.
Требуемые значения ModalResult можно задавать в обработчиках соответствующих событий в компонентах модальной формы. Однако при использовании кнопок можно обойтись и без подобных обработчиков. Дело в том, что кнопки типа TButton и TBitBtn имеют свойство ModalResult, по умолчанию равное mrNone. Для кнопок, расположенных на модальной форме, значение этого свойства можно изменить и тогда не потребуется вводить каких-либо обработчиков событий при щелчке на них. В кнопках BitBtn при свойстве Kind, не равном bkCustom, заложены по умолчанию значения ModalResult, соответствующие назначению той или иной кнопки.
Ниже приведен пример использования модальных форм.
5.5.3 Пример приложения с модальными формами заставки и запроса пароля
Во многих больших приложениях при их запуске сначала на экране появляется форма-заставка, содержащая логотип приложения и сведения о программе и ее разработчике. Назначение этой формы чаще всего заключается в том, чтобы обеспечить начальную загрузку и настройку программы. Тогда эта форма должна закрываться не раньше, чем закончатся эти операции. Но иногда эта форма носит чисто информационный характер. В этих случаях желательно, чтобы она немедленно закрывалась при любых действиях пользователя и даже закрывалась через какое-то время без каких-либо действий со стороны пользователя. Именно такую форму-заставку мы и попробуем создать.
Помимо формы-заставки нередко в приложениях, особенно в тех, которые работают с базами данных, в начале работы приложения появляется форма с запросом пароля. При неверном пароле приложение закрывается, не позволяя пользователю работать с ним.
Формы-заставки и формы запроса пароля могут быть реализованы множеством различных способов. Рассмотрим один из них.
Откройте в C++Builder новое приложение. Пусть открывшаяся форма будет главной в нашем приложении (вместо такой пустой формы вы можете взять любое разработанное вами ранее приложение и добавлять форму-заставку и форму запроса пароля в него). Назовите для определенности главную форму приложения FMain.
Добавьте в приложение новую форму (File
· New
· Form). Пусть это будет ваша форма-заставка. Назовите ее FLog. Ее свойство BorderStyle надо сделать равным bsNone (см. разд. 5.1.3), чтобы в окне этой формы отсутствовала полоса заголовка. Вы можете поместить на форме какой-то рисунок (разместить компонент Image и вставить в его свойство Picture желаемый рисунок), надписи и т.п. В простейшем случае поместите в центре формы метку Label и напишите в ней какой-то текст. Размер формы-заставки задайте небольшим, меньшим, чем обычные окна приложения. Свойство Position следует сделать равным роScreenCenter, чтобы форма появлялась в центре экрана.
Теперь напишите обработчики событий, которые при любом действии пользователя закрывали бы форму. Щелкните на форме, чтобы в Инспекторе Объектов открылись относящиеся к ней страницы (если у вас форма накрыта панелями или рисунками, то, щелкнув на них, нажимайте клавишу Esc до тех пор, пока в Инспекторе Объектов не откроются страницы, относящиеся к форме). Перейдите в Инспекторе Объектов на страницу событий, выберите событие onKeyDown и напишите для него обработчик, состоящий из одного оператора Close(). Аналогичный обработчик напишите для события onMouseDown. Если на форме у вас имеются метки, компоненты Image и др., то выделите их все, задайте в событии onMouseDown ссылку на тот же обработчик, что вы сделали для формы, а в форме поставьте свойство KeyPreview в true, чтобы форма перехватывала все связанные с нажатием клавиш события компонентов.
Теперь форма будет закрываться при нажатии пользователем любой клавиши или кнопки мыши. Давайте сделаем так, чтобы и при отсутствии каких-то действий со стороны пользователя форма закрывалась сама, например, через 5 секунд.
Добавьте на форму компонент Timer со страницы System. Это невизуальный компонент, который может отсчитывать интервалы времени (см. разд. 3.7.8). Интервал задается в свойстве компонента Interval в миллисекундах. Задайте его равным 5000. Единственное событие таймера onTimer, наступающее по истечении заданного интервала времени. Напишите в обработчике этого события все тот же единственный оператор Close().
Теперь при любом действии и даже бездействии пользователя форма-заставка будет закрываться. Но что это означает? По умолчанию закрыть форму значит сделать ее невидимой. Однако форма-заставка не нужна после того, как она будет предъявлена пользователю в первый момент выполнения приложения. Хранить все время в памяти эту уже ненужную форму не имеет смысла. Поэтому в форме надо предусмотреть, чтобы закрытие формы означало ее удаление из памяти и освобождение памяти для чего-нибудь более полезного. Для этого надо сделать следующее.
В событие формы OnClose вставьте оператор: Action = caFree;
Как указывалось в предыдущих разделах, этот оператор приводит к уничтожению объекта формы и освобождению занимаемой формой памяти.
Форма-заставка готова к использованию. Проверьте только, имеет ли ее свойство Visible значение false. Это важно, поскольку только невидимую форму можно открыть методом ShowModal. В главной форме свойство Visible тоже должно иметь значение false.
Теперь можно записать в главной форме оператор ShowModal. Но чтобы это можно было сделать, необходимо сослаться в модуле главной формы на модуль формы-заставки.
Сохраните проект, дав файлу модуля главной формы имя UMain, а файлу модуля формы-заставки имя UFLog. Добавьте в модуль UMain директиву компилятора #include "UFLog.h" или выполните для модуля UMain команду File
·Include Unit Hdr и укажите в диалоге имя включаемого модуля UFLog.
Предварительное сохранение необходимо сделать, чтобы модули обрели свои окончательные имена. Иначе если вы сделаете ссылку, пока модули имеют имена по умолчанию (Unit1 и Unit2), а потом при сохранении дадите им более осмысленные имена (это всегда желательно делать), то прежние ссылки на модули окажутся неверными и вам придется их переделывать.
Теперь осталось написать в модуле UMain обработчик события формы OnShow.
Напишите в модуле UMain обработчик события формы OnShow, состоящий из одного оператора:
FLog->ShowModal();
Событие OnShow наступает перед тем, Как форма становится видимой. Поэтому в момент выполнения указанного оператора она еще не видна. Оператор открывает форму FLog как модальную, передает ей управление и дальнейшее выполнение программы в модуле UMain останавливается до тех пор, пока модальная форма не будет закрыта. После закрытия модальной формы выполнение программы продолжится, и главная форма станет видимой.
Можете сохранить проект, запустить приложение и убедиться, что все работает правильно.
Теперь добавим в приложение форму запроса пароля. Реальная форма такого типа должна предлагать пользователю ввести свое имя и пароль, сравнивать введенные значения с образцами, хранящимися где-то в системе, при неправильном пароле давать возможность пользователю поправиться. Бели пользователь так и не может ввести правильный пароль, форма должна закрыть приложение, не допустив к нему пользователя. При правильном пароле после закрытия формы запроса должна открыться главная форма приложения. Все это не трудно сделать, но мы упростим задачу, чтобы не отвлекаться от главного взаимодействия форм в приложении. Будем использовать всего один пароль, который непосредственно укажем в соответствующем операторе программы. И не будем давать пользователю возможности исправить введенный пароль.
Добавьте к приложению новую форму. Назовите ее FPSW и сохраните ее модуль в файле с именем UPSW. Уменьшите размер формы до разумных пределов, поскольку она будет содержать всего одно окно редактирования. Установите свойство формы BorderStyle равным bsDialog, свойство Position равным poScreenCenter. В свойстве Caption напишите «Введите пароль и нажмите Enter». Эта надпись будет служить приглашением пользователю.
Поместите в центре формы окно редактирования Edit, в котором пользователь будет вводить пароль. Очистите его свойство Text. Задайте в свойстве PasswordChar символ "*". В обработчике события OnKeyDown этого компонента запишите оператор:
if(Key == VK_RETURN)
Close();
Этот оператор анализирует нажатую клавишу (подробнее об этом смотрите в разд. 5.3.2.2). Если нажата клавиша Enter, то считается, что пользователь завершил ввод пароля, и форма закрывается. Анализ введенного пароля откладывается до события OnClose. Дело в том, что, несмотря на приглашение в заголовке окна нажать после ввода пароля Enter, пользователь может проигнорировать это и закрыть окно, например, системной кнопкой. Если он до этого ввел правильный пароль, то несправедливо не проверить его, наказывая таким образом пользователя за непослушание. Вообще, если вы хотите создать для пользователя дружественный интерфейс, поменьше диктуйте ему последовательность действий. Лучше исходите из правила: «Пользователь всегда прав». И старайтесь предусмотреть реакцию на его самые неразумные (с вашей точки зрения) действия.
В обработчике события OnClose формы FPSW напишите оператор
if(Edit1->Text == "1")
ModalResult = 6;
Этот оператор сличает введенный текст с паролем. В данном операторе для упрощения непосредственно указан правильный пароль символ '1'. Если введен правильный пароль, то свойству ModalResult присваивается некоторое условное число 6 (можно было бы выбрать и любое другое допустимое число, кроме 0 и 2). Если пароль неправильный, то оставляется значение ModalResult = 2 (mrCancel), которое автоматически присваивается при любой попытке закрыть форму. В обоих случаях форма закрывается, так как задание отличного от нуля положительного значения ModalResult равносильно закрытию формы. Но при правильном пароле значение ModalResult будет равно 6, а при неправильном 2.
На этом проектирование формы запроса пароля закончено. Теперь запишем в модуле главной формы UMain оператор» показывающий пользователю эту форму и анализирующий ответ пользователя. Для этого надо выполнить следующие операции.
В модуле UMain, надо, как и ранее, добавить ссылку на модуль UPSW, а в обработчике события OnShow после ранее введенного оператора Flog->ShowModaI() добавить оператор:
if (FPSW->ShowModal() != 6)
{
ShowMessage("Баш пароль неверный");
Close();
}
else
{
ShowMessage(“Bam пароль '" + FPSW->EPSW->Text + “’”);
delete FPSW
}
Этот оператор анализирует значение свойства ModalResult формы запроса пароля. Значение этого свойства возвращает функция FPSW->ShowModal()- Если результат не равен 6, то был введен неправильный пароль. Тогда главная форма, а с ней вместе и приложение, закрываются методом Close. При правильном пароле можно продолжать работу приложения. Оператор ShowMessage введен просто для того, чтобы показать, как можно использовать свойство другой формы в данном случае текст, введенный пользователем в качестве пароля. В реальном приложении по этому паролю можно было бы определить уровень доступа пользователя к конфиденциальной информации. Затем следует уничтожение формы запроса пароля операцией delete (см. гл. 13, разд. 13.9). Это необходимо сделать, чтобы освободить память. Сама по себе эта форма в момент ее закрытия не уничтожается, поскольку по умолчанию закрыть форму значит сделать ее невидимой. Уничтожать форму до этого момента было нельзя, так как при этом уничтожилась бы содержащаяся в ней информация введенный пароль.
На этом разработка нашего приложения закончена. Можете сохранить проект, запустить приложение и посмотреть, как оно работает.
Описанный выше способ управления формой запроса пароля не является оптимальным. Он просто призван был показать, как можно обрабатывать величину ModalResult, возвращаемую методом ShowModal. Но то же самое можно было бы сделать и проще. В обработчике события OnClose формы FPSW можно было бы написать оператор:
if(Editl->Text != "1")
Application->Terrainate();
При неверном пароле этот оператор завершает работу всего приложения методом Application->Terminate(). Тогда в главной форме не надо анализировать результат работы пользователя с формой FPSW, так как если приложение не закрылось при выполнении оператора ShowModal, то значит пароль введен правильный. Поэтому операторы в главной форме тоже упрощаются:
FPSW->ShowModal();
ShowMessage("Ваш пароль '"+FPSW->EPSW->Text+"'");
delete FPSW;
Проведите в вашем приложении соответствующие замены операторов и убедитесь, что приложение и в этом случае работает правильно.
5.5.4 Управление формами в приложениях с интерфейсом множества документов (приложениях MDI)
Типичным приложением MDI является привычный всем Word. В приложении MDI имеется родительская (первичная) форма и ряд дочерних форм (называемых также формами документов). Окна документов могут создаваться самим пользователем в процессе выполнения приложения с помощью команд типа Окно
· Новое. Число дочерних окон заранее неизвестно пользователь может создать их столько, сколько ему потребуется. Окна документов располагаются в клиентской области родительской формы. Поэтому чаще всего целесообразно в родительской форме ограничиваться только главным меню, инструментальными панелями и, если необходимо, панелью состояния, оставляя все остальное место в окне для окон дочерних форм. При этом обычно окно родительской формы в исходном состоянии разворачивают на весь экран.
Из родительской формы можно управлять дочерними формами.
Дочернюю форму нельзя уничтожить, пока не уничтожена родительская форма.
Требования, которые надо учитывать при разработке приложений MDI, подробно рассмотрены в разд. 5.1.2.
Для создания приложения MDI необходимо спроектировать родительскую и дочернюю формы. В родительской форме свойство FormStyle устанавливается в fsMDIForm, а в дочерней в fsMDIChild. Поскольку дочерние окна будет создавать сам пользователь в процессе выполнения приложения, дочернюю форму необходимо исключить из числа создаваемых автоматически (в разд. 5.5.1 рассказывалось, как это сделать с помощью окна Опций проекта).
Рассмотрим теперь, как можно сделать обработчик команды, по которой пользователь задает в родительском окне создание нового окна документов нового экземпляра дочерней формы. Этот обработчик может иметь вид:
класс_дочерней_формы * имя = new класс_дочерней_формы (Application);
if (!имя)
return;
операторы настройки, если они нужны
имя->Show();
Первый оператор процедуры создает операцией new объект дочерней формы и указатель на него с некоторым произвольным временным именем. Далее могут следовать какие-то операторы настройки нового дочернего окна. Например, новому окну надо присвоить какой-то уникальный заголовок (свойство Caption дочерней формы), чтобы пользователь мог отличать друг от друга окна документов. Последний оператор процедуры делает видимым вновь созданное окно.
Пусть, например, вы создали в модуле UMain родительскую форму, содержащую раздел меню Окно
· Новое, и создали в модуле UDoc дочернюю форму с именем FDoc, имеющую тип TFDoc (посмотреть для контроля имя и тип дочерней формы вы можете в верхнем выпадающем списке Инспектора Объектов, выделив интересующую вас форму, или в модуле, посмотрев автоматически создаваемый C++Builder оператор, объявляющий переменную формы и расположенный сразу после директив препроцессора).
Тогда в модуль родительской формы вы должны вставить директиву препроцессора, подключающую заголовочный файл дочерней формы UDoc (см. разд. 5.5.1 и 5.5.3). А в обработчике события, связанного с выбором пользователем раздела меню Окно | Новое, можно написать операторы:
TFDoc *TF = new TFDoc(Application);
if(!TF)
return;

TF->Show<);
В родительской форме имеется ряд свойств, позволяющих управлять дочерними окнами. Все они доступны только для чтения и только во время выполнения.
Свойство MDIChildCount определяет количество открытых дочерних окон. Приведем оператор, который можно вставить в предыдущий пример для задания уникального имени вновь созданного окна TF:
TF->Caption = "Документ " + IntToStr(MDIChildCount);
Свойство MDIChildren[int i] дает доступ к i-му окну (окна индексируются в порядке их создания, последнее созданное окно имеет индекс 0). Однако во время работы пользователя с окнами индексация может измениться. Поэтому можно рекомендовать использовать индексы только в циклах при проведении некоторых операций сразу со всеми дочерними окнами. Следующий пример показывает процедуру, с помощью которой из родительской формы Form1 можно закрыть (свернуть) все дочерние окна, начиная с последнего:
for(int i = MDIChildCount-1; i >= 0; i--)
MDIChildren[i]->Close();
В момент создания окон документов они автоматически располагаются каскадом в клиентской области родительской формы. При этом если размера клиентской области не хватает для размещения дочерних окон, размеры последних автоматически уменьшаются. Имеется ряд методов родительской формы, упорядочивающих размещение дочерних окон. Метод Cascade располагает все открытые (не свернутые) окна каскадом. Метод Tile располагает окна мозаикой. При этом учитывается свойство родительской формы TileMode. Если оно равно tbVertical, то упорядочивание производится по вертикали, а если TileMode равно tbHorizontal, то упорядочивание производится по горизонтали. Метод Arrangelcons упорядочивает расположение пиктограмм свернутых окон.
Отдельно надо упомянуть об объединении главных меню родительской и дочерних форм. Обычно обе эти формы имеют главные меню, но они различны. Например, родительская форма может иметь меню Окно, а дочерняя форма меню Файл и Правка. Меню дочерних форм не должно появляться в окнах документов, а должно всегда встраиваться в главное меню родительской формы. Поэтому свойство AutoMerge компонента типа TMainMenu на приложения MDI не влияет: встраивание меню происходит независимо от значения этого свойства. А места, на которые встраиваются разделы меню дочерней формы, определяются значениями свойства Grouplndex каждого раздела меню так же, как это имеет место в обычных многооконных приложениях при задании свойства AutoMerge равным true (см. гл. 3, разд. 3.8.1).
5.5.5 Пример приложения с интерфейсом множества документов простой многооконный редактор
Рассмотрим проектирование простого приложения MDI (проект PMDI в каталоге Chapter5\MDI на приложенном к книге диске). Пусть мы хотим создать многооконный редактор, в каждое окно которого можно загрузить содержимое заданного входного текстового файла, что-то в нем изменить и сохранить текст в заданном выходном файле (рис. 5.18).
Обратите внимание на следующие требования к интерфейсу MDI, проиллюстрированные на рис. 5.18.
Хороший стиль программирования
Если новое окно открывается без загрузки текста из файла, заголовок окна должен содержать уникальное имя типа «Документ ...» или «Окно ...», где вместо многоточия должен быть неповторяющийся номер окна. Если в окно загружается текст из файла, то заголовок окна должен содержать имя загруженного файла.
Хороший стиль программирования
Когда окно документа развертывается, его имя должно включаться в заголовок главного окна (рис. 5.18 в). К счастью, C++Builder производит эту операцию автоматически.
Хороший стиль программирования
Меню Окно должно завершаться разделами, содержащими список открытых окон (см. рис. 5.18 в). Как это сделать рассказано в гл. 3, в разд. 3.8.1 и будет повторено в рассматриваемом примере.
Построим дочерние окна с использованием компонентов RichEdit, но для простоты не будем тратить время на разработку сервиса, необходимого в реальных редакторах. Более серьезный пример имеется на прилагаемом к книге диске.
Начнем с построения формы окна документа.
Откройте в C++Builder новое приложение.
Назовите открывшуюся форму FDoc.
Разместите на форме компонент RicnEdit1 типа TRichEdit. Его свойство Align задайте равным alClient, чтобы окно редактирования заняло всю площадь окна. Сотрите текст, появившийся в RichEdit1 (свойство Lines).
Разместите на форме по одному компоненту типов TOpenDialog и TSaveDialog (см. разд. 3.10.2). Задайте в обоих диалогах свойства DefaultExt равными rtf, a в свойства Filter занесите строку «текстовые файлы» с шаблоном "*.rtf ;*.txt" и строку «все файлы» с шаблоном "*.*".
Рис. 5.18 Пример простого приложения MDI: многооконный редактор в режимах упорядочивания окон каскадом (а), по вертикали (б), с развернутым окном документа (в)
Разместите на форме компонент типа MainMenu. Создайте в нем раздел Файл (присвойте его свойству Name значение MFile) с подразделами Открыть (Name = МОреп) и Сохранить как (Name = MSave).
Теперь напишите обработчики событий для введенных подразделов меню. Для раздела МОреп обработчик может иметь вид:
if (OpenDialog1->Execute{))
{
RichEditl->Clear();
RichEditl->Lines->LoadFromFile(OpenDialogl->FileName);
}
Для раздела MSave обработчик может иметь вид:
if(SaveDialogl->Execute())
RichEditl->Lines->SaveToFile(SaveDialogl->FileName);
Сохраните проект, дав спроектированному модулю имя UDoc.
Простенький редактор (пока однооконный) построен. Можете скомпилировать его и проверить в работе.
Теперь давайте спроектируем родительскую форму и превратим наш однооконный редактор в многооконный.
Измените свойство FormStyle созданной вами ранее формы на fsMDIChild.
Откройте в вашем проекте новую форму (File
· New
·Form). Назовите ее FMDI. Сохраните ее модуль с именем UMDI (File
· Save As).
Измените свойство FormStyle новой формы на fsMDIForm. Задайте свойство WindowState равным wsMaximized, чтобы окно этой формы предъявлялось пользователю в первый момент развернутым на весь экран. Введите (вручную или командой File
· Include Unit Hdr) директиву препроцессора #include, ссылающуюся на заголовочный файл [ Cкачайте файл, чтобы посмотреть ссылку ] модуля UMDI. Иначе вы не сможете открывать дочерние формы и управлять ими.
Выполните команду Project
· Options и в открывшемся окне на странице Forms переведите дочернюю форму FDoc из списка автоматически создаваемых в список доступных форм. При этом, как вы сможете убедиться по выпадающему списку вверху окна, главной станет родительская форма FMDI единственная, оставшаяся в списке автоматически создаваемых форм.
Введите на форму FMDI компонент типа MainMenu. Сформируйте в нем раздел меню Окно (имя Name = MWind) с подразделами Новое (Name = MNew), Каскад (Name = MCascade), Упорядочить по горизонтали (Name = МНог), Упорядочить по вертикали (Name = MVert), Упорядочить значки (Name = MIcons). Свойство формы WindowMenu установите равным MWind. Это обеспечит появление внизу меню Окно разделов, содержащих список открытых окон.
Чтобы введенные разделы меню не заменялись во время выполнения разделами меню дочернего окна, необходимо задать им всем значения свойства GroupIndex, отличные от тех, которые имеют разделы меню дочерней формы. Если этого не сделать, то в процессе выполнения при открытии первого же дочернего окна меню Окно заменится на меню дочерней формы Файл. Следовательно, дальнейшее управление окнами будет потеряно. Это произойдет потому, что по умолчанию значения свойства Grouplndex для всех разделов всех меню равно 0.
Если мы хотим, чтобы в процессе выполнения приложения меню дочерней формы Файл встраивалось перед меню Окно, необходимо всем разделам меню Окно присвоить значение Grouplndex, большее, чем 0, соответствующий разделам дочернего меню.
Во всех введенных разделах меню Окно установите свойство Grouplndex равным 1.
Запишите обработчик события OnClick для раздела меню Новое (MNew). Этот обработчик может иметь вид, уже рассмотренный в предыдущем разделе:
void __fastcall TFMDI::MNewClick(TObject *Sender)
j
TFDoc* TF = new TFDoc(Application);
if (!TF) return; .TF->Caption = "Документ " + IntToStr(MDICnildCount);
TF->Show{); }
Внесите операторы в обработчики событий OnClick для остальных разделов меню. Для раздела Каскад:
Cascaded;
Для раздела Упорядочить по горизонтали:
TileMode = tbHorizontal; TileO;
Для раздела Упорядочить по вертикали:
TileMode = tbVertical;
Tile();
Для раздела Упорядочить значки:
Arrangelcons() ;
Ha этом проектирование многооконного редактора завершено. Можете сохранить проект и опробовать приложение в работе (см. рис. 5.18). Посмотрите, как ведет себя приложение при создании нового окна документа, если размеры родительского окна не достаточно велики. Проверьте, что было бы, если бы вы не изменили свойство Grouplndex в меню родительского окна или если форма документов имела бы свойство FormStyle равное fsNormal. Вообще поэкспериментируйте с этим приложением, чтобы уяснить все особенности приложений MDI.
5.6 Печать документов и изображений
Печать в C++Builder может осуществляться различными способами. В данном разделе обсуждаются простые способы печати текстов и изображений. Составление и печать сложных отчетов рассмотрены в гл. 11, в разд. 11.2.
5.6.1 Печать форм методом Print
Формы в C++Builder имеют метод Print, который печатает клиентскую область формы. При этом полоса заголовка формы и полоса главного меню не печатаются. Таким образом, можно включить в приложение форму, в которой пользователь во время выполнения размещает необходимые для печати результаты: тексты и изображения. Если имя этой формы Form2, то ее печать может выполняться оператором
Form2->Print();
Свойство формы PrintScale определяет опции масштабирования изображения при печати. Возможные значения PrintScale:
poNone
Масштабирование не используется. Размер изображения может изменяться в зависимости от используемого принтера.

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

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


5.6.2 Методы компонентов, обеспечивающие печать
Ряд компонентов в C++Builder имеют методы, обеспечивающие печать хранящихся в них данных. Например, компонент RichEdit имеет метод Print, позволяющий печатать в обогащенном формате текст, хранящийся в компоненте. В этот метод передается единственный параметр типа строки, назначение которого заключается только в том, что при просмотре в Windows очереди печатаемых заданий принтера эта строка появляется как имя задания. Например, оператор
RichEdit1->Print("Printing of RichEdit1");
обеспечивает печать текста компонента RichEdit1, причем задание на печать получает имя «Printing of RichEdit1».
Печать воспроизводит все заданные особенности форматирования. Перенос строк и разбиение текста на страницы производится автоматически. Длина строк никак не связана с размерами компонента RichEdit, содержащего этот текст.
Печатью через RichEdit можно воспользоваться и для печати файлов документов в текстовом формате или в формате RTF. Для этого надо последовательно выполнить оператор загрузки файла в компонент и оператор печати загруженного текста. Например:
RichEdit1->Lines->LoadFromFile("Test.txt");
RichEdit1->Print("печать файла Test.txt");
или
RichEdit1->Lines->LoadFromFile("Test.rtf");
RichEdit1->Print("печать файла Test.rtf");
Компонент Chart, используемый для отображения графиков и диаграмм (см. разд. 3.6.4), также имеет метод Print, обеспечивающий печать. Предварительно может быть выполнен метод PrintPortrait, задающий книжную (вертикальную) ориентацию бумаги, или метод PrintLandscape, задающий альбомную (горизонтальную) ориентацию. Масштабировать размер печатаемого графика можно, вызвав предварительно метод PrintRect,
procedure PrintRect (Const R : TRect);
в котором параметр R определяет размер области принтера, в которой осуществляется печать.
Компонент Chartfx (см. разд. 3.6.5) имеет быструю кнопку печати (пятая слева в инструментальной панели рис. 3.29), с помощью которой пользователь в любой момент может напечатать текущий график или диаграмму.
5.6.3 Печать средствами офисных приложений Windows с помощью функции ShellExecute и обращения к серверам СОМ
Для печати файлов средствами стандартных офисных приложений Windows можно использовать функцию ShellExecute. Подробно об этой функции см, разд. 7.2.5. А здесь мы коротко рассмотрим технологию такой печати без каких-либо дополнительных пояснений.
Чтобы воспользоваться функцией ShellExecute, надо ввести в модуль директиву препроцессора
#include "ShellApi.h"
которая подключает заголовочный файл ShellApi.h, содержащий объявление функции ShellExecute и некоторых других функций Windows API. Функция ShellExecute при соответствующем задании ее параметров ищет по расширению заданного для печати файла соответствующую ему системную программу Windows, и, если находит, то осуществляет печать. Например, обычно Windows настроен так, что файлам с расширением .txt соответствует программа Notepad, а файлам с расширением .doc Word. В этом случае выполнение оператора
ShellExecute(Handle, "print", "Test.txt",NULL,NULL,SW_HIDE);
вызовет печать файла с именем test.txt средствами программы Notepad, а оператор
ShellExecute(Handle, "print", "Test.doc",NULL,NULL,SW_HIDE);
вызовет печать файла с именем test.doc средствами программы Word.
Этот способ печати можно использовать как для распечатки заранее созданных файлов, так и для распечатки файлов, созданных во время выполнения приложения методами SaveToFile, имеющимися у многих компонентов.
Имеется также возможность печатать тексты и графику средствами стандартного редактора Windows Word или средствами Excel. Для этого в C++Builder (начиная с версии 5) имеются компоненты серверы СОМ, позволяющие сначала создать документ соответствующего приложения Windows, а затем его напечатать. Эти возможности рассмотрены в гл. 7, в разд. 7.9.
5.6.4 Печать с помощью объекта Printer
В C++Builder имеется класс печатающих объектов TPrinter, который обеспечивает печать текстов, изображений и других объектов, расположенных на его канве Canvas. Свойства канвы подробно рассмотрены в разд. 6.1.3 и здесь мы не 5удем на них останавливаться. Достаточно знать, что на канве могут размещаться различные изображения и текст.
Класс объектов TPrinter объявлен в модуле Printers. Поэтому для работы c этим классом надо включить в текст директиву препроцессора
#include
Рассмотрим некоторые свойства и методы объекта типа TPrinter.
Свойство, метод
Описание

:Canvas
Канва Canvas место в памяти, в котором формируется страница или документ перед печатью. Canvas обладает рядом свойств, включая Реп (перо) и Brush (кисть), которые позволяют вам делать рисунки и помещать на них текст. Подробное описание канвы и методов работы с ней вы найдете в разд. 6.1.3.

TextOut
Метод канвы, который позволяет посылать в нее текст.

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

BeginDoc
Используется для начала задания печати.

EndDoc
Используется для окончания задания печати. Фактическая печать происходит только при вызове EndDoc.

PageHeight
Высота страницы в пикселах.

PageWidth
Ширина страницы в пикселах.

NewPage
Принудительно начинает новую страницу на принтере.

PageNumber
Возвращает текущий номер печатаемой страницы.

Предположим, вы хотите напечатать текст и изображение, используя печатающий объект. Изображение размещено на форме в компоненте Image1. Вы можете осуществить эту печать следующим кодом:
TPrinter *Prntr = Printer();
Prntr->Canvas->Font->Size = 12;
Prntr->BeginDoc();
Prntr->Canvas->TextOut(10,10, "Я печатаю через объект Printer");
Prntr->Canvas->Draw(Prntr->PageWidth – Image1->Picture->Bitmap->Width)/2,
40, Image1->Picture->Bitmap);
Prntr->EndDoc();
Первый оператор, этого кода использует функцию Printer, которая создает глобальный объект типа TPrinter. В том же операторе создается указатель на этот объект Prntr.
Следующий оператор задает размер шрифта канвы принтера. Затем функция BeginDoc запускает задание на печать. Следующий оператор посылает на канву принтера с помощью метода канвы TextOut, начиная с точки с координатами (10, 10), текст «Я печатаю через объект Printer». Следующий оператор методом Draw рисует на канве принтера изображение. При этом изображение выравнивается по горизонтали на середину страницы. Координата верхней стороны изображения задается равной 40.
В заключение метод EndDoc вызывает печать текста и изображения и останавливает задание на печать.
Печатающий объект Printer не производит автоматического переноса строк и разбиения текста на страницы. Поэтому печать длинных текстов с помощью объекта Printer требует достаточно сложного программирования. Проще это делать описанными ранее способами или с помощью системы QuickReport, которая описана в разд. 11.2.
5.7 Работа с реестром и файлами настройки
5.7.1 Установка и настройка приложения: работа с системным реестром
Обсудим коротко вопросы установки вашего приложения на компьютере пользователя. Если это очень простое приложение, то никаких проблем нет достаточно скопировать выполняемый файл приложения с загрузочной дискеты или диска CD ROM в выделенный для приложения каталог. Если в дальнейшем пользователь решит удалить вашу программу со своего компьютера, ему будет достаточно удалить этот файл.
В более сложных приложениях обычно фигурируют различные файлы настройки, конфигурации и т.д., расположенные к тому же в разных каталогах. В этих случаях установить программу на компьютере и удалить ее, если она стала не нужна, уже гораздо сложнее.
Раньше, в Windows 3.x вся информация о конфигурации и настройках приложения хранилась в файлах .ini. Но для того чтобы упорядочить процессы установки и удаления программ, начиная с Windows 95 и NT Microsoft требует, чтобы вся информация о конфигурации системы хранилась в системном реестре. Реестр (Registry) это база данных для хранения информации о системной конфигурации аппаратуры, о Windows и о приложениях Windows. Почти все, что в Windows 3.x находилось в файлах .ini, перенесено в Windows 95 и NT в реестр. Реестр имеет иерархическую организацию, которая, содержит много уровней ключей, субключей и параметров. Информация хранится в виде иерархического дерева, каждый узел которого называется ключом. Ключ может содержать субключи и значения параметров,
Реестр делит все свои данные на две категории: характеризующие компьютер и характеризующие пользователя. Характеристики компьютера включают в себя все, связанное с техническими средствами, а также с установленными приложениями и их конфигурацией. Характеристики пользователя включают в себя установки по умолчанию для экрана, пользовательские конфигурации, информацию о выбранных пользователем принтерах, установки сети.
Все субключи относятся к пяти шести основным ключам реестра. Следующие ключи определяют характеристики компьютера:
Hkey_Local_Machine
Информация о компьютере, включая конфигурацию установленной аппаратуры и программного обеспечения

Hkey_Current_Config
Информация о текущем оборудовании..

Hkey_Dyn_Datai
Динамические данные о состоянии, используемые процедурами plug-and-play. Ключ присутствует не во всех Windows.

Hkey_Classes__Root
Информация об OLE, Drag&Drop, клавишах быстрого доступа и пользовательском интерфейсе.

Два ключа верхнего уровня определяют характеристики пользователя:

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

Hkey_Current_User
Информация о пользователе, зарегистрированном в данный момент.

Реестр хранится в файле SYSTEM.DAT в каталоге Windows. Просмотр и редактирование реестра из Windows осуществляется редактором реестра Regedit.exe (рис. 5.23). Только прежде, чем вы будете вручную что-то редактировать в реестре или опробовать изложенные ниже приемы работы с реестром из приложений C++Builder, прислушайтесь к следующему совету.
Совет
Прежде, чем изменять что-то в реестре, сохраните копию его файла SYSTEM.DAT, расположенного в каталоге Windows, в каком-то другом каталоге. Это позволит вам в случае неудачного вмешательства в реестр восстановить прежнее состояние этого файла. В противном случае ваши эксперименты могут закончиться плачевно вплоть до необходимости переустанавливать Windows.
Рис. 5.19 Окно редактора реестра Regedit.exe
Для работы с реестром в C++Builder имеется класс TRegistry, описанный в модуле registry. Если вы создаете в приложении объект класса TRegistry для работы с реестром, не забудьте обеспечить связь с этим модулем директивой
#include "registry.hpp";
Все ключи в объекте класса TRegistry создаются как субключи определенного корневого ключа, записанного в свойстве RootKey. По умолчанию RootKey = HKEY_CURRENT_USER. В каждый момент объект типа TRegistry имеет доступ только к одному текущему ключу в иерархии, начинающейся с ключа RootKey. Текущий ключ определяется свойством только для чтения CurrentKey. Но это значение вам ничего не скажет это просто некоторое целое значение. А вот свойство CurrentPath (тоже только для чтения) содержит строку, включающую имя текущего ключа и путь к нему по дереву. Изменить текущий ключ можно методом ОрепКеу:
bool __fastcall ОрепКеу(const AnsiString Key, bool CanCreate);
Этот метод открывает ключ Key, делая его текущим для объекта. Параметр Key строка полного пути по дереву ключей к открываемому ключу. Если Key пустая строка, то текущим делается корневой ключ, указанный свойством RootKey. Параметр CanCreate указывает, должен ли создаваться ключ Key, если его нет в реестре. Ключ открывается или создается с доступом KEY_ALL_ACCESS. Создаваемый ключ сохраняется в реестре при последующих запусках системы.
Запись значений параметров в ключ осуществляется группой методов: WriteInteger, WriteFIoat, WriteBool, WriteString и др. Объявления всех этих методов очень похожи. Например:
void __fastcall Writelnteger{const AnsiString Name, int Value);
void __fastcall WriteString(const AnsiString Name, const AnsiString Value);
Все они заносят значение Value в параметр с именем Name. Имеются аналогичные методы чтения: Readlnteger, ReadFloat, ReadBool, ReadString и др. Например:
int __fastcall Readlnteger(const AnsiString Name);
AnsiString __fastcall ReadString(const AnsiString Name);
Посмотрим на примере, как все это можно использовать для установки программы, запоминания ее настроек и для удаления программы.
Сделайте простое тестовое приложение (проект PRegistry в каталоге Chapter5\RegINI на приложенном к книге диске). Его вид показан на рис. 5.20. Перенесите на форму две кнопки Button, выпадающий список ComboBox и две метки. Первая кнопка (назовите ее BInst и задайте надпись Install) будет имитировать установку программы. Точнее, не саму установку, поскольку копировать файлы с установочной дискеты мы не будем, а только регистрацию нашего приложения в реестре. Вторая кнопка (назовите ее BUnlnst и задайте надпись Unlnstall) будет имитировать удаление программы. Тут мы не будем удалять саму программу с диска (жалко ее!), а только удалим из реестра ссылку на нее. Список ComboBox1 будет содержать перечень шрифтов, установленных в системе. Выбор в этом списке будет позволять изменять имя шрифта, используемого в форме, и обеспечит запоминание этого шрифта в реестре, чтобы в дальнейшем при запуске приложения можно было читать эту настройку и задавать ее форме. В компоненте ComboBox1 установите свойство ParentFont в false, чтобы при неудачном выборе шрифта, когда на кнопках и в метках отобразится абракадабра, это не затронуло бы список шрифтов.
Рис. 5.20 Имитация установки программы
Ниже приведен текст этого тестового приложения.
#include "registry.hpp";
TRegistry *Reg = new TRegistry;
//------------------------------------------------------------------
void __fastcall TForml::FormDestroy(TObject *Sender)
{
if (Reg->OpenKey("\\Software\\A Projects\\Pl", false))
// Запись имени шрифта только если имеется раздел PI
Reg->WriteString("Шрифт", this->Font->Name);
delete Reg;
}
//------------------------------------------------------------------
void __fas-tcall TForml: :FormCreate (TObject *Sender)
{
// Задается корневой каталог объекта Reg
Reg->RootKey = HKEY_LOCAL_MACHINE;
if (Reg->KeyExists ("\\SoftwareWA Projects\\Pl"))
{
// Если программа была установлена, то читается настройка шрифта
Reg->OpenKey("WSoftwareWA ProjectsWPl", true);
this->Font->Name = Reg->ReadString("Шрифт");
Label1->Caption = "Программа установлена";
}
else
Labell->Caption = "Программа не установлена";
// Заполнение списка доступных шрифтов
ComboBoKl->Items = Screen->Fonts,-
ComboBoxl->ItemIndex = ComboBoxl->Items->IndexOf(this->Font->Name);
Label2->Caption = "Шрифт " + this->Font->Name;
}
//------------------------------------------------------------------
void __fastcall TForml: :BItistClick(TObject *Sender)
{
// Имитация установки программы
Reg->OpenKey("\\Software\\A Projects",true);
Reg->WriteString("Тема","Мои приложения");
Reg->OpenKey("\\Software\\A Projects\\Pl", true) ;
Reg->WriteString("Приложение","TRegistry");
// Запись в параметр Файл имени и пути к выполняемому файлу
Reg->WriteString("Файл", ParamStr(0) ;
// Запись в параметр Шрифт имени шрифта
Reg->Writestring("Шрифт", this->Font->Name);
Labell->Caption = "Программа установлена";
}
//------------------------------------------------------------------
void__fastcall TForml::BUnInstClick(TObject *Sender)
{
// Удаление субключа PI из регистра
Reg~>DeleteKey(,"\\Software\\A Projects\\Pl") ;
Labell->Caption = "Программа не установлена";
}
//------------------------------------------------------------------
void__fastcall TForml::ComboBoxlChange(TObject *Sender)
{
// Смена шрифта формы
this->Font->Narae = ComboBox1->Text;
Label2->Caption = "Шрифт " + this->Font->Name;
}
В начале текста следует подключение к приложению модуля registry н создание объекта Reg типа TRegistry. Этот объект удаляется при закрывании формы в функции TForm1 ::FormDestroy. Предварительно в этой же функции происходит запоминание имени шрифта формы в параметре Шрифт субключа [ Cкачайте файл, чтобы посмотреть ссылку ]Projects\Pl. Этот субключ и параметр, как будет видно далее, существует в реестре, если приложение было установлено.
При создании формы приложения в процедуре TForm1::FormCreate корневым узлом объекта Reg задается ключ HKEY_LOCAL_MACHINE. Затем с помощью метода KeyExists проверяется, существует ли в реестре субключ [ Cкачайте файл, чтобы посмотреть ссылку ] Рrojects\Pl. Этот ключ, как мы позже увидим, создается при регистрации нашего приложения в реестре. Так что в настоящем приложении, если бы метод KeyExists вернул false, надо было бы выдать какое-то предупреждение о том, что приложение не зарегистрировано, и завершить работу.
Обратите внимание, что в текстовых строках символ “\" должен удваиваться: «\\Software\\A Projects\\PI».
Если метод KeyExists вернул true, то далее в процедуре открывается методом ОрепКеу субключ [ Cкачайте файл, чтобы посмотреть ссылку ] Projects\Pl и читается методом ReadString имя шрифта, занесенное в параметр Шрифт, в имя шрифта формы.
Далее список ComboBox1 заполняется именами шрифтов, зарегистрированных в системе (см. разд. 4.7). Индекс списка переключается на элемент, соответствующий текущему шрифту формы.
Теперь рассмотрим процедуру TForm1::BInstClick, имитирующую установку программы. В этой процедуре методами ОрепКеу создается иерархия ключей: А Projects и PI в субключе Software (этот субключ всегда существует в реестре). В ключ A Projects заносится один параметр Тема со значением Мои приложения.
Подразумевается, что этот субключ будет содержать ссылки на все ваши приложения, зарегистрированные в реестре. В ключ Р1 (субключ вашего регистрируемого проекта) заносится три параметра. Первый параметр Приложение со значением TRegistry. Второй параметр Файл со значением, равным полному имени файла приложения вместе с путем. Это имя получается из нулевого параметра командной строки, передаваемого функцией ParamStr(0), В третий параметр Шрифт заносится имя текущего шрифта, используемого в форме. В настоящей программе установки в эту процедуру надо было бы добавить копирование соответствующих файлов с установочной дискеты.
Процедура TForm1::BUnInstClick имитирует удаление приложения и ссылки на него (субключ РI) из реестра. В настоящем приложении прежде, чем удалять ключ из реестра, надо было бы прочитать значение параметра Файл и удалить этот файл с диска.
Процедура TForm1::ComboBpxlChange при выборе пользователем нового шрифта заносит его имя в шрифт формы.
Сохраните свое тестовое приложение и запустите его на выполнение. Нажмите кнопку Install. После этого запустите программу [ Cкачайте файл, чтобы посмотреть ссылку ] и посмотрите реестр. Вы увидите там свои ключи и параметры (именно они были показаны на рис. 5.19). Щелкните на кнопке. Unlnstall. Вернитесь в окно [ Cкачайте файл, чтобы посмотреть ссылку ] и выберите в нем команду Вид
· Обновить. Вы увидите, что ключ РI вместе со всеми своими параметрами исчез из дерева. Опять нажмите кнопку Install, а затем выберите в выпадающем списке шрифт с каким-нибудь другим именем, В окне [ Cкачайте файл, чтобы посмотреть ссылку ] вы сможете увидеть, что значение параметра Шрифт изменится. Закройте свое приложение и запустите его повторно. Вы увидите, что на форме применен тот шрифт, который вы зарегистрировали в реестре. Таким образом, приложение проимитировало установку программы, снятие программы с регистрации и запоминание текущих настроек в реестре.
Когда вы кончите экспериментировать с этим приложением, удалите с помощью [ Cкачайте файл, чтобы посмотреть ссылку ] из реестра ваш ключ A Projects, поскольку приложение удаляло только его субключ РI.
Естественно, при установке реального приложения, помимо регистрации в реестре, требуется решение еще ряда вопросов. Надо создать на диске соответствующий каталог и скопировать в него необходимые файлы. Это несложно сделать обычными операциями с файлами, рассмотренными в разд. 14.9 и 16.5. Но могут возникать и более специфические задачи. Например, может потребоваться перезагрузка системы, после которой надо будет продолжать установку. Иногда надо обеспечить автоматический запуск приложения при загрузке системы, и тогда надо внести в систему соответствующую запись об этом. Иногда требуется зарегистрировать в системе некие расширения файлов, специфичные для вашего приложения. Нередко требуется проверить наличие в системе каких-то шрифтов, и если они отсутствуют, то установить и зарегистрировать их. В рамках данной книги рассмотреть все эти задачи невозможно. Вы можете найти их" решение в книге [3] и в справках [5].
5.7.2 Работа с файлами .ini
В разд. 5.7.1 рассказывалось, как регистрировать приложение в системном реестре и фиксировать там текущие настройки приложения. Однако подобная работа с реестром возможна только в 32-разрядных Windows. Если же вы хотите, чтобы ваше приложение можно было использовать и в Windows 3.x, то вам надо регистрировать приложение и фиксировать его настройки в файлах типа .ini. Для 32-разрядных приложений Microsoft не рекомендует работать с файлами .ini. Впрочем, несмотря на это и 32-разрядные приложения, наряду с реестром, часто используют эти файлы. Да и разработки Microsoft не обходятся без этих файлов.
Файлы .ini это текстовые файлы, предназначенные для хранения информации о настройках различных приложений. Информация в файле логически группируется в разделы, каждый из которых начинается оператором заголовка, заключенным в квадратные скобки. Например, [Desktop]. В строках, следующих за заголовком, содержится информация, относящаяся к данному разделу, в форме:
<ключ> = <значение>
В качестве примера ниже приводится фрагмент файла ODBC.INI:
[ODBC 32 bit Data Sources]
dBASE Files=Microsoft dBase Driver (*.dbf) (32 bit)
Excel Files=Microsoft Excel Driver (*.xls) (32 bit)
FoxPro Files=Microsoft FoxPro Driver (*.dbf) (32 bit)
Text Files=Microsoft Text Driver (*.txt; *.csv) (32 bit)
[dBASE Files]
Driver32=C:\WINDOWS\SYSTEM\odbcjt32.dll
Файлы .ini, как правило, хранятся в каталоге Windows, который можно найти с помощью функции GetWindowsDirectory.
В C++Builder работу с файлами .ini проще всего осуществлять с помощью создания в приложении объекта типа TIuiFile. Этот тип описан в модуле inifiles, который надо подключать к приложению оператором uses (Автоматически это не делается).
При создании объекта типа TlniFile в него передается имя файла .ini, с которым он связывается. Файл должен существовать до создания объекта.
Для записи значений ключей существует много методов: WriteString, WriteInteger, WriteFloat, WrlteBool и др. Каждый из них записывает значение соответствующего типа. Объявления всех этих методов очень похожи. Например:
void __fastcall WriteString(const AnsiString Section, const AnsiString Ident, const AnsiString Value);
void __fastcall Writelnteger(const AnsiString Section, const AnsiString Ident, int Value);
Во всех объявлениях Section раздел файла, Ident ключ этого раздела, Value значение ключа. Если соответствующий раздел или ключ отсутствует в файле, он автоматически создается.
Имеются аналогичные методы чтения: ReadString, Readlnteger, ReadFloat, ReadBool и др. Например:
AnsiString __fastcall ReadString(const AnsiString Section,
const AnsiString Ident, const AnsiString Default);
int __fastcall Readlnteger(const AnsiString Section, const AnsiString Ident, int Default);
Методы возвращают значение ключа Ident раздела Section. Параметр Default определяет значение, возвращаемое в случае, если в файле не указано значение соответствующего ключа.
Проверить наличие значения ключа можно методом ValueExists, в который передаются имена раздела и ключа. Метод DeleteKey удаляет из файла значение указанного ключа в указанном разделе. Проверить наличие в файле необходимого раздела можно методом SectionExists. Метод EraseSection удаляет из файла указанный раздел вместе со всеми его ключами. Имеется еще ряд методов, которые вы можете посмотреть во встроенной справке C++Builder.
Посмотрим на примере, как все это можно использовать для установки программы, запоминания ее настроек и для удаления программы.
Сделайте простое тестовое приложение, аналогичное рассмотренному в разд. 5.7.1 и показанному на рис. 5.20 (проект PINI в каталоге Chapter5\RegINI на приложенном к книге диске). Перенесите на форму две кнопки Button, выпадающий список ComboBox и две метки. Первая кнопка (назовите ее BInst и задайте надпись Install) будет имитировать установку программы. Точнее, не саму установку, поскольку копировать файлы с установочной дискеты мы не будем, а только создание файла .ini в каталоге Windows. Вторая кнопка (назовите ее BUnlnst и задайте надпись Unlnstall) будет имитировать удаление программы. Тут мы не будем удалять саму программу с диска, а только удалим из каталога Windows наш файл .ini. Список ComboBox1 будет содержать перечень шрифтов, установленных в системе. Выбор в этом списке будет позволять изменять имя шрифта, используемого в форме, и обеспечит запоминание этого шрифта в файле .ini, чтобы в дальнейшем при запуске приложения можно было читать эту настройку и задавать ее форме. В компоненте ComboBox1 установите свойство ParentFont в false, чтобы при неудачном выборе шрифта, когда на кнопках и в метках отобразится абракадабра, это не затронуло бы список шрифтов.
Ниже приведен текст этого тестового приложения.
#include "inifiles.hpp";
#include
TIniFile *Ini; String sFile,-
//---------------------------------------------------------------------------------
void __fastcall TForml::FormCreate(TObject *Sender)
{
char APchar[255];
//Формирование имени файла в каталоге Windows
GetWindowsDirectory(APchar,255) ;
sFile = (String)APchar+"\\My.ini";
if(FileExists(sFile))
{
// Создание объекта Ini
Ini = new TIniFile(sFile);
//Чтение в имя шрифта формы значения ключа Шрифт
this->Font->Name = InI->ReadString("Параметры", "Шрифт","MS Sans Serif");
Labell->Caption = "Программа установлена";
}
else
Labell->Caption = "Программа не установлена";
// Заполнение списка доступных шрифтов
ComboBoxl->Items = Screen->Fonts;
ComboBoxl->ItemIndex = ComboBoxl->Items->IndexOf(this->Font->Name);
Label2->Caption = "Шрифт " + this->Font->Name;
}
//---------------------------------------------------------------------------------
void__fastcall TForml::BInstClick(TObject *Sender).
{
//Имитация установки программы
FILE *F;
//Проверка существования файла .ini
if (! FileExists(sFile))
{
//Создание файла .ini
if ((F = fopen(sFile.c_str(), "w+")) == NULL)
{
ShowMessage("Файл не удается открыть");
return;
}
fclose (F); // закрытие файла
}
// Создание объекта Ini
Ini = new TIniFile(sFile);
/* Создание раздела Files, ключа main и запись в него
имени выполняемого файла вместе с путем */
Ini->WriteString("Files","main",ParamStr(0));
/* Создание раздела Параметры, ключа Шрифт и запись в него
имени шрифта формы */
Ini->WriteString("Параметры","Шрифт",Font->Name);
Labell->Caption = "Программа установлена";
}
//---------------------------------------------------------------------------------
void __fastcall TForml::BUnInstClick(TObject *Sender)
{
FILE *F;
//Удаление с диска файла .ini, если он существует
if(FileExists(sFile)) DeleteFile(sFile);
Labell->Caption = "Программа не установлена";
}
//---------------------------------------------------------------------------------
void___fastcall TForml::FormDestroy(TObject 'Sender)
{
if(Ini == NULL)
return;
//Очистка буфера и запись файла на диск
Ini->UpdateFile();
//Освобождение памяти
delete Ini;
}
//---------------------------------------------------------------------------------
void __fastcall TForm1::ComboBoxlChange(TObject *Sefider)
{
// Смена шрифта формы
this->Font->Name = ComboBox1->Text;
if((ini != NULL) && Ini->ValueExists{"Параметры", "Шрифт"))
//Запись шрифта в ключ "Шрифт" раздела "Параметры"
Ini->Wri teSt ring ("Параметры",. "Шрифт ",Font->Name);
Label2->Caption = "Шрифт " + this->Font->Name;
}
В начале текста следует подключение к приложению модуля [ Cкачайте файл, чтобы посмотреть ссылку ], в котором объявлен тип TIniFile, и модуля stdio.h, необходимого для операций с файлами. Объявляется объект Ini типа TIniFile и переменная sFile, в которой будет формироваться имя файла и путь к нему. При создании формы приложения в функции TForml->FormCreate формируется имя файла («My.ini») вместе с путем к нему каталогом Windows. Этот путь определяется функцией GetWindowsDirectory. Далее функцией FileExists проверяется, существует ли этот файл, т.е. проведена ли уже установка программы. Если существует, то создается объект Ini, связанный с этим файлом, и значение ключа Шрифт раздела Параметры читается в имя шрифта формы. Тем самым читается настройка, произведенная при предыдущем выполнении приложения.
Теперь рассмотрим функцию TForml->BInstClick, имитирующую установку программы. В этой процедуре сначала функцией FileExists проверяется, существует ли в каталоге Windows файл «My.ini». Если не существует, этот файл (пока пустой) создается функцией fopen с параметром «w». Затем создается связанный с этим файлом объект Ini. Последующие операторы записывают в этот файл два раздела Files и Параметры с соответствующими ключами. В ключ main записывается имя приложения с путем к нему. Для этого используется функция ParamStr(0) (см. гл. 16, разд. 16.7.4). В результате в созданный файл записывается, например, такой текст:
[Files]
main=D:[ Cкачайте файл, чтобы посмотреть ссылку ]
[Параметры]
Шрифт=МS Sans Serif
Функция TForm1->BUnInstClick имитирует удаление приложения и его файла настройки. В данном случае просто удаляется файл .ini, но в настоящем приложении надо было бы прочитать имя файла (или файлов) приложения из раздела Files и удалить их с диска.
Функция TForm1->FormDestroy, срабатывающая при закрывании формы приложения, сначала методом UpdateFile переписывает содержимое объекта Ini в файл, а затем с помощью delete удаляет из памяти этот временный объект.
Функция TForm1->ComboBox1Cbange присваивает форме шрифт, который выбрал пользователь, и имя этого шрифта заносится в файл .ini.
Сохраните свое тестовое приложение и запустите его на выполнение. Нажмите кнопку Install, После этого убедитесь в наличии файла «My.ini» в каталоге Windows. Можете воспользоваться для этого программой Windows «Проводник» или любой другой. В частности, можно открыть этот файл просто из среды C++Builder. При нажатии кнопки Unlnstall файл должен удаляться с диска. Проверьте запись в файл настройки шрифта и чтение ее при последующих запусках. Для этого опять нажмите кнопку Install, а затем выберите в выпадающем списке шрифт с каким-нибудь другим именем. Потом закройте свое приложение и запустите его повторно. Вы увидите, что на форме применен тот шрифт, который вы зарегистрировали в файле настройки. Таким образом, приложение проимитировало установку программы, удаление программы и запоминание ее текущих настроек.
Мы рассмотрели запомиминание в файле .ini простых настроек, которые могут быть записаны в виде текста. Но иногда требуется запоминать более сложные настройки. Например, если пользователь может изменять расположение компонентов на форме (см. разд. 5.4) или их размеры, загружать в компоненты тексты или изображения из каких-то файлов, то запоминание всей этой информации в текстовом виде в файле .ini приводит к очень громоздким текстам. Имеются принципиально иные способы запоминания подобных настроек, которые вы можете найти в справках [5].
Глава 6
Графика и мультимедиа
6.1 Построение графических изображений
6.1.1 Использование готовых графических файлов
6.1.1.1 Компонент Image и некоторые его свойства
Нередко возникает потребность украсить свое приложение какими-то картинками. Это может быть графическая заставка, являющаяся логотипом вашего приложения. Или это могут быть фотографии при разработке приложения, работающего с базой данных сотрудников некоего учреждения. В первом случае вам потребуется компонент Image, расположенный на странице Additional библиотеки компонентов, во втором его аналог DBImage, связанный с данными и расположенный на странице Data Controls.
Начнем знакомство с этими компонентами. Откройте новое приложение и перенесите на форму компонент Image. Его свойство, которое может содержать картинку Picture. Нажмите на кнопку с многоточием около этого свойства или просто сделайте двойной щелчок на Image, и перед вами откроется окно Picture Editor (рис. 6.1), позволяющее загрузить в свойство Picture какой-нибудь графический файл (кнопка Load), а также сохранить открытый файл под новым именем или в новом каталоге. Щелкните на Load, чтобы загрузить графический файл. Перед вами откроется окно Load Picture, представленное на рис. 6.2. По мере перемещения курсора в списке по графическим файлам в правом окне отображаются содержащиеся в них изображения. Вы можете найти графические файлы в каталоге Images. Он обычно расположен в каталоге ...[ Cкачайте файл, чтобы посмотреть ссылку ] files\Common Files\Borland Shared.
На рис. 6.1 и 6.2 изображена загрузка файла ...\Images\Splash\16Color\earth.bmp. В окне загрузки графического файла (рис. 6.2) вы можете не только просмотреть изображение, хранящееся в выбираемом файле, но и увидеть размер изображения цифры в скобках справа вверху. В некоторых случаях, как вы увидите позднее, это важно.
После загрузки файла щелкните на ОК, и в вашем компоненте Image отобразится выбранная вами картинка. Можете запустить ваше приложение и полюбоваться ею. Впрочем, вы и так увидите картинку, даже не выполняя приложение.
Рис. 6.1 Окно Picture Editor
Рис. 6.2 Окно загрузки графического файла
Когда вы в процессе проектирования загрузили картинку из файла в компонент Image, он не просто отображает ее, но и сохраняет в приложении. Это дает вам возможность поставлять ваше приложение без отдельного графического файла. Впрочем, как мы увидим позднее, в Image можно загружать и внешние графические файлы в процессе выполнения приложения.
Вернемся к рассмотрению свойств компонента Image.
Если установить свойство AutoSize в true, то размер компонента Image будет автоматически подгоняться под размер помещенной в него картинки. Если же свойство AutoSize установлено в false, то изображение может не поместиться в компонент или, наоборот, площадь компонента может оказаться много больше площади изображения.
Другое свойство Stretch позволяет подгонять не компонент под размер рисунка, а рисунок под размер компонента. Установите AutoSize в false, растяните пли сожмите размер компонента Image и установите Stretch в true. Вы увидите, что рисунок займет всю площадь компонента, но поскольку вряд ли реально установить размеры Image точно пропорциональными размеру рисунка, то изображение исказится. Устанавливать Stretch в true может иметь смысл только для каких-то узоров, но не для картинок. Свойство Stretch не действует на изображения пиктограмм, которые не могут изменять своих размеров (см. разд. 6.1.1.3).
Свойство Center, установленное в true, центрирует изображение на площади Image, если размер компонента больше размера рисунка.
Рассмотрим еще одно свойство Transparent (прозрачность). Если Transparent равно true, то изображение в Image становится прозрачным. Это можно использовать для наложения изображений друг на друга. Поместите на форму второй компонент Image и загрузите в него другую картинку. Только постарайтесь взять какую-нибудь мало заполненную, контурную картинку. Можете, например, взять картинку из числа помещаемых обычно на кнопки, например, стрелку (файл ...[ Cкачайте файл, чтобы посмотреть ссылку ] files\common files\borland [ Cкачайте файл, чтобы посмотреть ссылку ]). Передвиньте ваши Image так, чтобы они перекрывали друг друга, и в верхнем компоненте установите Transparent равным true. Вы увидите, что верхняя картинка перестала заслонять нижнюю. Одно из возможных применений этого свойства наложение на картинку надписей, выполненных в виде битовой матрицы. Эти надписи можно сделать с помощью встроенной в C++Builder 6 программы Image Editor, которая будет рассмотрена позднее.
Учтите, что свойство Transparent действует только на битовые матрицы (о типах графических файлов см. в разд. 6.1.1.3).
6.1.1.2 Простое приложение для просмотра графических файлов
Вы создали приложение» в котором на форме отображается выбранная вами в процессе проектирования картинка. Вы можете легко превратить его в более интересное приложение, в котором пользователь сможет просматривать и загружать любые графические файлы. Для этого достаточно перенести на форму компонент ОрепPictureDialog, расположенный в библиотеке на странице Dialogs и вызывающий диалоговое окно открытия и предварительного просмотра изображения (рис. 6.2), а также кнопку, запускающую просмотр, или меню с единственным разделом Файл.
А теперь вам осталось написать всего один оператор в обработчике щелчка на кнопке или на разделе меню:
If(OpenPictureDialog1->Execute())
Image1->Picture->LoadFromFile(OpenPictureDialcg1->FileName);
Этот оператор загружает в свойство Picture компонента Image1 файл, выбранный в диалоге пользователем. Выполните свое приложение и проверьте его в работе. Щелкая на кнопке вы можете выбрать любой графический файл и загрузить его в компонент Image1.
В таком приложении есть один недостаток изображения могут быть разных размеров и их положение на форме или будет несимметричным, или они не будут помещаться в окне. Это легко изменить, заставив форму автоматически настраиваться на размеры изображения. Для этого надо установить в компоненте Image1 свойство AutoSize равным true, а приведенный ранее оператор изменить следующим образом:
if (OpenPictureDialogl->Execute())
{
Image1->Picture->LoadFromFile{OpenPictureDialogl->FileName);
Form1->ClientHeight = Image1>Height + 10;
Image1->Top = Forml->ClientRect.Top + (Form1->ClientHeight – lmage1->Height) / 2;
Forml->ClientWidth » Imagel->Width + 10;
Imagel->Left = Form1->ClientRect.Left + (Form1->ClientWidCh - Image1->Width) / 2; \
В этом коде размеры клиентской области формы устанавливаются несколько больше размеров компонента Image1, которые в свою очередь адаптируются к размеру картинки благодаря свойству AutoSize.
Запустите теперь ваше приложение (проект Image в каталоге Chapter6\lmage на приложенном к книге диске), и вы увидите (рис. 6.3), что при различных размерах изображения ваше приложение выглядит отлично.
Рис. 6.3 Адаптация формы к размерам изображения
6.1.1.3 Форматы графических файлов
Прежде, чем продвигаться дальше, поговорим немного о форматах графических файлов. C++Builder поддерживает различные типы файлов битовые матрицы, пиктограммы, метафайлы и некоторые другие. Все перечисленные типы файлов хранят изображения; различие заключается лишь в способе их хранения внутри файлов и в средствах доступа к ним. Битовая матрица (файл с расширением .bтр) отображает цвет каждого пиксела в изображении. При этом информация хранится таким образом, что любой компьютер может отобразить картинку с разрешающей способностью и количеством цветов, соответствующими его конфигурации.
Пиктограммы (файлы с расширением .ico) это маленькие битовые матрицы. Они повсеместно используются для обозначения значков приложений, в быстрых кнопках, в пунктах меню, в различных списках. Способ хранения изображений в пиктограммах схож с хранением информации в битовых матрицах, но имеются и различия. В частности, пиктограмму невозможно масштабировать, она сохраняет тот размер, в котором была создана.
Метафайлы (Metafiles) хранят не последовательность битов, из которых состоит изображение, а информацию о способе создания картинки. Они хранят последовательности команд рисования, которые и могут быть повторены при воссоздании изображения. Это делает такие файлы, как правило, более компактными, чем битовые матрицы.
C++Builder может работать со следующими файлами:
Тип файла
Расширение

JPEG Image File
.jpg, .jpeg

Битовые матрицы (Bitmaps)
.bmp

Пиктограммы
.ico

Enhanced Metafiles
.emf

Metafiles
.wmf

6.1.1.4 Классы для хранения графических объектов TPicture, TBitmap, Tlcon и TMetafile
Выше были рассмотрены типы графических файлов. Для хранения графических объектов, содержащихся в битовых матрицах, пиктограммах и метафайлах, в C++Builder определены соответствующие классы TBitmap, Tlcon и TMetafile. Все они являются производными от абстрактного базового класса графических объектов TGraphic. Кроме того, определен класс, являющийся надстройкой над TBitmap, Tlcon и TMetafile и способный хранить любой из этих объектов. Это класс TPicture, с которым вы уже познакомились в начале этой главы. Он имеет свойство Graphic, которое может содержать и битовые матрицы, и пиктограммы, и метафайлы. Более того, он может содержать и объекты определенных пользователем графических классов, производных от TGraphic. Для доступа к графическоу объекту можно использовать свойство TPicture->Graphic, но если тип графического объекта известен, то можно непосредственно обращаться к свойствам TPicture->Bitmap, TPicture->Icon или TPicture->Metafile.
Для всех рассмотренных классов определены методы загрузки и сохранения в файл:
void __fastcall LoadFromFile{const System::AnsiString Filename);
void __fastcall SaveToFile(const System::AnsiString Filename);
При этом для классов TBitmap, Tlcon и TMetafile формат файла должен соответствовать классу объекта. Объект класса TPicture может оперировать с любым форматом.
Для всех рассмотренных классов определены методы присваивания значений объектов:
void __fastcall Assign(TPersistent* Source);
Однако для классов TBitmap, TIcon и TMetafile присваивать можно только значения однородных объектов: соответственно битовых матриц, пиктограмм, метафайлов. При попытке присвоить значения разнородных объектов генерируется исключение. Класс TPicture универсальный, ему можно присваивать значения объектов любых из остальных трех классов. А значение TPicture можно присваивать только тому объекту, тип которого совпадает с типом объекта, хранящегося в нем.
Приведем пример. Часто в приложениях создается объект типа TBitmap, назначение которого запомнить содержимое графического изображения и затем восстанавливать его, если оно будет испорчено или изменено пользователем. Код, решающий эту задачу, может иметь вид:
// Объявление и создание объекта Bitmap
Graphics::TBitmap *Bitmap = new Graphics::TBitmap();
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
// Уничтожение Bitmap и освобождение памяти
Bitmap->Free();
}
//------------------------------------------------------------------------------
void __fastcall TForm1::MOpenClick(TObject *Sender)
{
// Загрузка изображения из файла в Bitmap и Imagel
if (OpenPictureDialog1->Execute())
{
Bitmap->LoadFromFile(OpenPictureDialogl->FileName);
Image1->Picture->Assign(Bitmap);
}
}
//------------------------------------------------------------------------------
void__fastcall TForm1::MSaveClick(TObject *Sender)
{
// Сохранение изображения из Image1 в Bitmap
Bitmap->Assign(Image1->Picture);
}
//------------------------------------------------------------------------------
void__fastcall TForm1::MRestoreClick(TObject «Sender)
{
// Восстановление изображения в Imagel из Bitmap I
Imagel->Picture->Assign(Bitmap);
}
В этом коде сначала объявляется переменная Bitmap типа TBitmap и создается соответствующий объект. Если вы создали объект Bitmap, то надо не забыть его уничтожить при окончании работы и освободить от него память. Автоматически это не делается. Поэтому надо освобождать память, например, в обработчике события формы OnDestroy (процедура FormDestroy) методом Free:
Bitmap->Free();
В процедуре MOpenClick в объект Bitmap методом LoadFromFile загружается изображение из выбранного пользователем файла. Затем оператор
Image1->Picture->Assign(Bitmap);
присваивает значение графического объекта Bitmap свойству Picture компонента Image1. Изображение тем самым делается видимым пользователю. Этот оператор можно записать иначе:
Form1->Imagel->Picture->Bitmap->Assign(Bitmap);
что даст тот же самый результат.
Если надо переписать в Bitmap отредактированное пользователем в Image1 изображение (о редактировании будет рассказано в последующих разделах), это можно сделать оператором (процедура MSaveClick):
Bitmap->Assign(Imagel->Picture);
Если же надо восстановить в Image1 прежнее изображение, испорченное по каким-то причинам, то это можно сделать оператором (процедура MRestoreClick):
Image1->Picture->Assign(Bitmap);
Таким образом, мы видим, что методом Assign можно копировать изображение из одного однотипного графического объекта в другой и обратно.
Загружать изображения можно не только из файлов, но и из ресурсов приложения с помощью методов
void __fastcall LoadFromResourceName (int Instance, const System::AnsiString ResName);
или
void __fastcall LoadFromResourcelDUnt Instance, int ResID);
где ResName имя графического объекта в файле ресурса, a ResID его идентификатор. Например, оператор
Bitmapl->LoadFromResourceName(HInstance,"MYBITMAP");
загружает в объект Bitmap1 из ресурса битовую матрицу с именем "MYBITMAP". Как создавать в ресурсах битовые матрицы и другие графические объекты будет рассказано в разд. 6.1.2.4.
Имеются еще методы загрузки и выгрузки графических объектов в поток и в буфер обмена Clipboard, но эти методы используются относительно редко, и вы можете посмотреть их в справке по C++Builder.
6.1.2 Редактор Изображений Image Editor в C++Builder 6
6.1.2.1 Создание файла изображения
B C++Builder 6 имеется встроенный Редактор Изображений Image Editor, который вызывается командой Tools
· Image Editor. Окно Редактора Изображений представлено на рис. 6.4 а. Это сравнительно простой редактор с не очень богатыми возможностями. Он позволяет создавать изображения в виде битовых матриц, пиктограмм, изображений курсоров и не только сохранять созданные изображения в виде файлов, но и сразу включать их в файл ресурсов приложения. В этом и заключается его основное отличие от других, более мощных графических редакторов. В С++-Builder 2006 такого редактора нет. Конечно, для создания изображений можно использовать любой графический редактор. Но Image Editor во многих отношениях удобен. Так что если вы переходите с C++Builder 6 на C++Builder 2006, удаляя программы C++Builder 6, я бы советовал не удалять этот редактор.
Рис. 6.4 Окно Редактора Изображений (а)
и его вспомогательное окно задания свойств изображения (б)
Работа начинается с меню File, в котором вы можете выбрать раздел Open открыть новый файл изображения или ресурсов, или раздел New создать новый файл. Если вы выбрали New, то вам предлагается сделать дополнительный выбор, определяющий вид файла, который вы хотите создать:
Resource File (.res)
файл ресурсов

Component Resource File (.dcr)
файл ресурсов компонента

Bitmap File (.bmp)
файл битовой матрицы

Icon File (.ico)
файл пиктограммы

\ Cursor File (.cur)
файл изображения курсора

Пусть, например, вы хотите создать свой рисунок для битовой матрицы. Тогда, выбрав раздел Bitmap File, вы попадаете в окно (рис. 6.4 б), в котором должны выбрать размер (Size) матрицы по горизонтали (Width) и вертикали (Height), а также выбрать набор цветов: 2,16 или 256. Вероятно, для начала вам будет вполне достаточно 16 цветов.
После сделанного выбора вы увидите в окне Редактора Изображений границы вашего будущего рисунка, как это показано на рис. 6.4 а. Вы можете начинать творить. Раздел меню View предоставляет вам возможность увеличить изображение в 2 раза (раздел Zoom In), уменьшить ранее увеличенное изображение (раздел Zoom Out) или посмотреть изображение в его реальном размере (раздел Actual Size).
Расположенная слева инструментальная панель предоставляет вам следующий инструментарий, достаточно типичный для любого графического редактора:

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


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


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


Ластик, перемещение которого стирает изображение, окрашивая пикселы вспомогательным цветом, если нажата левая кнопка мыши, или основным цветом, если нажата правая кнопка мыши.


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


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


Пульверизатор. Цвет зависит от нажатой кнопки мыши. Форма пятен выбирается из набора, расположенного внизу инструментальной панели.


Ввод текста. Перед началом ввода или сразу в момент окончания можно пользуясь меню Text выбрать тип и размер шрифта.


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


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

Кроме перечисленных инструментов на инструментальной панели вы можете видеть кнопки, соответствующие рисованию прямых линий, дуг, незаполненных и заполненных прямоугольников, прямоугольников со скругленными углами, эллипсов.
При выборе таких инструментов, как карандаш, кисть, пульверизатор, кнопки рисования линий, дуг, незаполненных фигур, внизу появляется палитра, позволяющая выбрать толщину линии или форму кисти.
В нижней части Редактора Изображений (рис. 6.4 а) расположена палитра цветов. В ее левой части имеются два квадрата. Цвет левого из них назовем его основным, используется, если при рисовании вы нажимаете левую кнопку мыши; цвет правого вспомогательный, используется, если при рисовании вы нажимаете правую кнопку мыши.
Вот, собственно, и все премудрости. Если вы обладаете художественными способностями (я, увы, ими не обладаю), то можете попробовать нарисовать что-нибудь стоящее. Если же нет, то можете воспользоваться каким-нибудь готовым файлом .bтр (команда File
·Open позволит вам его открыть) и что-то к нему добавить, например, текст. А еще проще напишите просто текст и сохраните в виде файла .bтр. В дальнейшем вы можете наложить его в своем приложении на любой рисунок с помощью свойства Transparent компонента Image, как было рассказано в разд. 6.1.1.1.
Файл пиктограммы создается аналогично. Вы можете создать, например, несложный файл изображения, и затем использовать его как вашу фирменную пиктограмму во всех своих приложениях.
6.1.2.2 Создание пиктограммы для шаблона компонента в библиотеке
Рассмотрим отдельно возможную процедуру создания собственных пиктограмм для библиотеки компонентов, если вы помещаете туда новый компонент или шаблон. Вы научитесь разрабатывать свои шаблоны и компоненты для библиотеки в разд. 8.2. А пока посмотрим, как их можно украсить собственными пиктограммами.
Эти пиктограммы должны иметь формат файла .bтр размером 24x24. Вы можете, конечно, нарисовать сами нужную пиктограмму, если сумеете. Но поскольку у меня, например, художественные способности отсутствуют, то мне представляется более легким и более качественным следующий путь.
Вы создаете новое приложение C++Builder, переносите на форму компонент, похожий на тот, который создаете, и изменяете в нем то, что надо. Например, в гл. 8 описана процедура создания шаблона окна редактирования, которое разрешает вводить только цифры. Вы можете взять за основу обычный компонент Edit, поместить его на форму и установить в нем текст "О", что укажет пользователю на специфику компонента. Далее надо уменьшить его размеры настолько, чтобы он нормально помещался в размер 24x24. При необходимости можно уменьшить соответственно размер шрифта (в этом вам часто может помочь шрифт Small Fonts). Полезно установить в false свойство AutoSize. Иначе высота компонента при выполнении приложения будет задана автоматически.
Затем вы запускаете приложение на выполнение и нажимаете клавиши All-Print Screen. В результате изображение окна вашего приложения будет записано в буфер обмена.








13PAGE 15


13PAGE 144815




15

Приложенные файлы

  • doc 14775949
    Размер файла: 1 MB Загрузок: 0

Добавить комментарий