Эта глава посвящена основе построения приложений в C++Builder- Библиотеке Визуальных Компонент VCL (Visual Component Library). Обсуждается иерархическая структура компонент VCL, объясняется назначение общих свойств, методов и событий, присущих различным базисным уровням в иерархии.
Глава дает "взгляд изнутри" на принципы построения различных компонент и порождающих их классов. Для понимания материала, изложенного в данной главе, читатель должен быть знаком с языком C++, терминологией ООП и методикой использования компонент в интегрированной среде визуальной разработки C++Builder.
Библиотека Визуальных Компонент была впервые введена системой программирования Delphi 1.0 на языке Объектный Паскаль и модифицирована в Delphi 2.0 для поддержки 32-разрядных приложений. Delphi оказалась наиболее популярной на рынке систем быстрой разработки программных приложений, однако, многие потребители высказывали интерес к подобной системе для языка C++, которая в конце концов и воплотилась в C++Builder. C++Builder унаследовал версию Библиотеки Визуальных Компонент Delphi 2.0 без каких-либо изменений.
Библиотека VCL интегрирована в среду C++Builder, что, в отличие от других систем программирования, позволяет манипулировать классами визуальных компонент при проектировании приложения, на стадии создания его прототипа. Поведение и вид ваших компонент определяются по мере разработки приложения, хотя можно модифицировать их и в процессе выполнения программы.
6.1 Назначение и устройство VCL
Библиотека Визуальных Компонент позволяет программистам визуально создавать программные приложения, не прибегая более к кодированию классов "вручную", или кодированию в рамках стандартных библиотек MFC (Microsoft Foundation Class), или OWL (Object Windows Library).
C++ программистам теперь не надо создавать или манипулировать объектами интерфейса с пользователем путем написания соответствующего кода. Подавляющее большинство приложений вы будете разрабатывать визуально с помощью Редактора форм C++Builder, добавляя лишь несколько строк к .обработчикам ключевых событии компонент. Используйте объекты всегда, когда это возможно; твердо сопротивляйтесь позыву написать новый код то тех пор, пока все другие возможности не будут исчерпаны.
Вам потребуется оперативное владение устройством Библиотеки Визуальных Компонент. Глубина необходимых программистам знаний о составе и функциональных характеристиках Библиотеки определяется тем, как вы собираетесь ее использовать. С помощью команды главного меню Help | VCL Reference вы можете получать сведения из справочной службы в процессе работы с Библиотекой.
6.1.1 VCL для прикладных программистов
Программист создает законченное приложение посредством интерактивного взаимодействия с интегрированной визуальной средой C++Builder, используя компоненты VCL для создания интерфейса программы с пользователем и с другими управляющими элементами: обслуживания баз данных, контролируемого ввода параметров и т.д. Характерная для C++Builder методика визуального стиля разработки программного обеспечения не применяется множеством других систем программирования.
Программисты должны знать свойства, методы и события, присущие используемым компонентам. Более того, понимание архитектуры VCL позволяет совершенствовать вашу программу в тех местах, где ощущается необходимость развития существующих или создания новых компонент. Прежде, чем изобретать новый элемент, удостоверьтесь, как принято, не создал ли уже кто-то компоненту с нужными вам характеристиками.
6.1.2 VCL для системных программистов
Системные программисты развивают существующую Библиотеку — либо добавляя в нее новые элементы, либо расширяя функциональность уже имеющихся компонент. Разработчики компонент должны иметь более глубокие знания о внутреннем устройстве VCL, нежели прикладные программисты. Нужно четко представлять себе, какой прием быстрее приведет к поставленной цели: развитие имеющейся или написание новой компоненты. Написание компонент представляет собой более традиционную задачу программирования и сопряжено с большими условностями, нежели визуальное создание приложений.
Варианты C++Builder Professional и C++Builder Client/Server Suite поставляются вместе с исходными текстами VCL. Наличие исходных текстов облегчает задачу программистов, которые занимаются разработкой новых компонент и расширением функциональных возможностей уже имеющихся компонент Библиотеки.
Для создания новых компонент можно с одинаковым успехом пользоваться средствами C++Builder или Delphi, однако если разработанные компоненты предлагаются для внешнего применения, автор обязан удостовериться, что они работают в рамках обеих систем.
6.1.3 Компоненты VCL
Компоненты — это строительные кирпичи, из которых конструируется интерфейс программы с пользователем, с помощью которых "здание" программы приобретает новый внешний облик и скрытые особенности. Для прикладного программиста любая компонента VCL представляет собой объект, который можно "перетащить" из вкладок Палитры компонент (Рис. 6.1) на форму создаваемого приложения. Поместив компоненту на форму, можно манипулировать ее свойствами (посредством Редактора форм) и кодом (с помощью Редактора кода), придавая компоненте специфическое поведение.
Рис. 6.1. Палитра компонент с выбранной пиктограммой TLahel.
Для разработчика компоненты представляют собой объекты на C++ или на Объектном Паскале. Некоторые компоненты инкапсулируют поведение типовых элементов управления, предоставляемых операционными системами Windows. Другие компоненты вводят совершенно новые видимые и невидимые элементы, программный код которых полностью определяет их поведение.
Сложность компонент различна. Так TLabel из вкладки Standard Палитры компонент способна лишь отображать статистический текст. Можно сконструировать значительно более сложную компоненту, которая, например, инкапсулирует законченное обслуживание бухгалтерских документов специализированной базы данных.
6.2 Типы компонент
С точки зрения прикладного программиста компонентный объект представляет собой законченную конструкцию, содержащую свойства, методы и события. В отличие от разработчика компонент, пользователю компонент безразлично, от какого класса произведена данная компонента.
Прикладные программисты принимают как факт, что каждая компонента имеет свойства Тор и Left, которые определяют положение компоненты на форме-владельце; для них не существенно, что эти свойства унаследованы от общего предшественника TComponent. Напротив, когда вы создаете компоненту, вы обязаны знать, от какого родителя можно заимствовать нужные вам свойства, а также все остальные его характеристики, так, чтобы их можно было наследовать, а не создавать вновь.
Из определения объектных классов вы знаете, что при определении некоторого класса (наследника) вы производите его от существующего объектного типа (непосредственного предшественника). Стандартный абстрактный тип TObject является, по умолчанию, первым предшественником (прародителем) всех объектов Библиотеки Визуальных Компонент.
Компоненты по сути представляют собой объекты специальных типов. Лишь несколько исключении выделяют структуру компонент из общих правил структурирования объектов на языке C++:
• Большинство компонент представляют собой элементы управления интерфейсом с пользователем, причем некоторые обладают весьма сложным поведением.
• Все компоненты являются прямыми или косвенными потомками одного общего класса-прародителя (TComponent).
• Компоненты обычно используются непосредственно, путем манипуляций с их свойствами; они сами не могут служить базовыми классами для построения новых подклассов.
• Компоненты размещаются только в динамической памяти с помощью оператора new.
Понимание VCL основано на трех фундаментальных принципах. Во-первых, вам придется ознакомиться со специальными характеристиками четырех базисных типов компонент: стандартного управления, оригинального управления (custom control), графического управления и невидимых компонент. Во-вторых, вы должны понимать структуру Библиотеки, в которую встроены описания и реализации кодов компонент. В третьих, вы должны знать положение упомянутых четырех типов компонент в иерархии VCL.
6.2.1 Стандартные компоненты
Некоторые компоненты VCL инкапсулируют поведение таких типовых элементов управления операционной системы Windows, как TButton, TListbox и TEdit. Вы найдете стандартные компоненты на вкладках Standard и Win95 Палитры компонент.
Любая стандартная компонента выглядит и ведет себя точно так же, как и инкапсулированный ею элемент управления Windows. VCL добавляет обрамление, которое никак не меняет свойств исходного элемента управления, а лишь делает доступной модификацию вида и поведения компоненты посредством свойств и методов.
Если вы собираетесь использовать стандартные компоненты без изменений, вам не нужно вникать в правила построения обрамлений VCL. Разработчик компонент может открыть файл исходных текстов стандартных компонент, входящий в поставку версий C++Builder Professional или Client/Server Suite, чтобы понять, каким образом известные элементы управления Windows обрамляются при включении в Библиотеку.
Например, компонента TListBox отображает элементы списка в один столбец, хотя инкапсулирует класс простого списка LISTBOX из Windows, который может отображать список в несколько столбцов. Чтобы изменить поведение компоненты, вам придется реализовать перегрузку метода создания данной компоненты, принятого по умолчанию.
6.2.2 Оригинальные компоненты
В отличие от стандартных компонент, оригинальные компоненты представляют собой элементы управления, у которых нет ни метода для собственного отображения, ни заранее определенного поведения. Разработчик компонент должен предусмотреть код, реализующий рисование самой компоненты и код, определяющий поведение компоненты, когда пользователь взаимодействует с ней. Примерами оригинальных компонент являются TPanel и TStringGrid.
Следует отметить, что как стандартные, так и оригинальные компоненты всегда ассоциируются с некоторым окном управления, поэтому иногда называются оконными (windowed components). Данный аспект подробно обсуждается в параграфе, описывающем класс TWinControl. Оконные компоненты обладают следующими свойствами: они могут быть активизированы (принять фокус ввода), используют системные ресурсы и могут служить контейнерами, т.е. являться родителями других элементов управления. Примером контейнерной компоненты является TPanel.
6.2.3 Графические компоненты
Графические компоненты представляют собой видимые элементы управления, которые не могут принять фокус ввода, т.к. не являются оконными. Графические компоненты обеспечивают отображение объектов без использования системных ресурсов, они требуют меньших "накладных расходов", нежели стандартные или оригинальные компоненты. Примерами графических компонент являются TImage и TShape.
Графические компоненты не могут служить контейнерами для других элементов управления, т.е. не могут владеть другими компонентами.
6.2.4 Невидимые компоненты
Во время выполнения программы невидимые компоненты не появляются на форме в виде каких-либо элементов управления. Поведение невидимых компонент определяется на этапе проектирования, путем инкапсуляции нужных свойств объекта.
С помощью Инспектора объектов вы можете модифицировать свойства невидимых компонент и предусматривать код обработчиков событий для них. Примерами таких компонент являются TOpenDialog, TTable или TTimer.
6.2.5 Контейнерные компоненты
Некоторые компоненты в VCL могут владеть другими компонентами или являться родителями других компонент. Указанные аспекты имеют разное смысловое значение, что и проясняется в следующих параграфах.
6.2.5.1 Право владения
Любая компонента может находиться во владении (ownership) других компонент, но не все компоненты могут являться владельцами. Свойство компоненты
Owner (Владелец) содержит ссылку на компоненту, которая ею владеет. Рис. 6.2 показывает иерархию владения некоторой формы.
Рис. 6.2. Пример иерархии владения.
Владелец ответствен за освобождение тех компонент, которыми владеет, когда сам разрушается. Так в процессе конструирования формы, она автоматически становится владельцем всех компонент, размещенных на ней, даже если часть их размещена на другой компоненте, такой как TPanel. Владение применимо не только к видимым, но и к невидимым (Ttimer, DataSource) компонентам.
Когда компонента создается динамически в процессе выполнения программы, конструктору компоненты передается ее владелец в качестве параметра. В следующем примере неявный владелец формы (this) передается конструктору компоненты TButton как параметр. TButton выполнит присваивание значения переданного параметра свойству Owner кнопки MyButton:
MyButton = new TButton(this);
Когда форма, владеющая компонентой TButton освобождается, автоматически уничтожается и кнопка MyButton.
Вы можете создать компоненту, у которой нет владельца, передавая значение параметра 0 конструктору компоненты. Однако, когда эта компонента перестает быть нужной, ее уничтожение выполняется принудительно (с помощью оператора delete). Следующий пример иллюстрирует обращение с компонентой TTable, не имеющей владельца:
TTable* MyTable = new TTable(0)
// Код, реализующий работу с MyTable
delete MyTable;
Свойство Components типа массив содержит перечень компонент, которыми владеет данная компонента. Листинг 6.1 содержит фрагмент кода обработчика события OnClick с циклом отображения имен классов всех компонент, которыми владеет некоторая форма.
void _fastcall TFormI::ButtonlClick(TObject *Sender) {
for (int i=0; i<ComponentCount; i++)
ShowMessage(Components[i]->ClassName()) ;
}
Листинг 6.1. Использование свойства Components.
6.2.5.2 Родительское право
Понятие родительского права (parentship) существенно отличается от права владения и применимо только к видимым (оконным) компонентам.Родитель компоненты не может быть ее владельцем.
Родительские компоненты обращаются к соответствующим внутренним функциям, чтобы вызвать отображение компонент-потомков. Родитель также ответствен за освобождение своих потомков, когда сам родитель уничтожается. Свойство компоненты Parent (Родитель) содержит ссылку на компоненту, которая является ее родителем. Рис. 6.3 показывает родительскую иерархию некоторой формы.
Рис. 6.3. Пример родительской иерархии.
Многие свойства видимых компонент (например. Left, Width, Top, Height) относятся к родительским элементам управления. Другие свойства (например, ParentColor и ParentFont) позволяют потомкам использовать свойства родителей.
К оконным компонентам относятся такие видимые элементы, как TEdit, TListBox и TMemo. Чтобы отобразить оконную компоненту, ей надо присвоить родителя, ответственного за отображение. Это присваивание выполняется автоматически на стадии проектирования, когда вы перетаскиваете нужную компоненту из Палитры компонент на форму. Напротив, при создании компоненты во время выполнения программы вы должны явно записать это присваивание, иначе компонента не будет отображена (Листинг 6.2).
void _fastcall TFormI::FormCreate(TObject *Sender)
{
MyEdit = new TEdit(this); // Передать this как владельца MyEdit->Parent = this; // Передать this как родителя
}
Листинг 6.2. Создание компоненты TEdit во время выполнения.
6.2.5.3 Поточность
Поточность (streaniabilily) компоненты выражается в способе хранения самой компоненты и информации, относящейся к значениям ее свойств, в файле или в отведенной области памяти. Например, создаваемый C++Builder ресурсный файл с расширением .dfm содержит информацию о форме и компонентах, размещенных на ней. Эта информация автоматически сбрасывается в поток ресурсного файла.
Разработчики компонент VCL должны разбираться в механизме поточного ввода/вывода, поскольку им придется вручную сбрасывать в поток специальные данные о новых компонентах. VCL не автоматизирует этот процесс.
6.3 Свойства компонент
Определение класса в языке C++ содержит инкапсуляцию членов данных и методов, оперирующих с данными и определяющих поведение объекта. Эта концепция всех систем ООП принята в VCL.
VCL позволяет манипулировать видом и функциональным поведением компонент не только с помощью методов (как это делается с обычными классами), но и посредством свойств и событий, присущих только классам компонент.
Свойства представляют собой расширение понятия членов данных. Разрешены любые типы свойств, за исключением файлового типа. В отличие от члена данных, свойство не хранит данные, однако его методы чтения и записи позволяют получить доступ к защищенному члену данных объекта.
Таким образом, присваивание значения члену данных посредством присваивания свойству вызывает "побочный эффект", за которым могут скрываться сложные операции над компонентой. Например, побочный эффект при изменении свойства Caption (Название) некоторой формы проявляется в немедленном изменении названия заголовка окна этой формы, при отсутствии явного обращения к методу, реализующему операцию изменения заголовка.
6.3.1 Зачем нужны свойства?
Наиболее очевидное достоинство свойств проявляется в том, что они представлены в окне Инспектора объектов. Это существенно упрощает работу программиста: вместо того, чтобы реализовывать ввод и обработку параметров при конструировании объектов во время выполнения, вы всего лишь считываете значения. присвоенные пользователем.
С точки зрения пользователя, компоненты выглядят как обычные переменные. Пользователь может считывать или менять значения свойств точно так же, как это делает программа с членами данных во время выполнения. Единственное, что пользователи не могут делать, - это передавать указатели свойств методам в качестве параметров. С точки зрения разработчика компонент, в свойствах заключены гораздо большие возможности, нежели в обычных членах данных. Действительно:
• Пользователи могут устанавливать значения свойств на стадии проектирования. Это очень важная характеристика, поскольку в отличие от методов, проявляющих себя во время работы программы, свойства дают возможность пользователю манипулировать поведением компоненты до момента запуска приложения. В целом, ваши компоненты не должны содержать много методов: большинство из них, вероятно, будут инкапсулированы свойствами.
• В отличие от члена данных, свойство может прятать детали реализации от пользователя, например, данные свойства могут храниться в зашифрованном виде и появляться в нормальном виде только при чтении или установке его значений. Хотя фактическим значением свойства может являться обычное число, компонента может, скажем, выбирать его из базы данных или выполнять сложные вычисления, чтобы выдать это значение.
• Применение свойств приводит к побочным эффектам в операторах присваивания членов данных. Эти внешне простые операторы могут подразумевать вызов метода, который, в принципе, может сделать все, что угодно. Простым примером служит свойство всех компонент под названием Тор. Присваивание не только меняет значение этого свойства, но и, благодаря побочному эффекту, приводит к перемене положения компоненты на форме и перерисовке ее содержания. Отметим, что побочный эффект установки свойств может не ограничиваться данной компонентой. Например, установка значения true свойства Down в одной из компонент группы быстрых кнопок приводит к изменению свойств всех других быстрых кнопок этой группы на значение false.
• Методы реализации свойства могут быть виртуальными, т.е. в разных компонентах одно и то же свойство может быть связано с различными действиями.
6.3.2 Объявление свойств
C++Builder использует ключевое слово _property для идентификации свойств. Синтаксис описания свойства имеет вид:
property <тип свойства> <имя свойства> = {<список атрибутов>} ;
где список атрибутов содержит перечисление следующих атрибутов свойства:
write = < член данных или метод записи >;
read = < член данных или метод чтения >;
default = < булева константа, управляющая сохранением значения>;
stored = < булева константа или функция, сохраняющая значение >.
Внимательный читатель заметит, что определение свойства уже давалось в главе 3. Дело в том, что информация, необходимая разработчикам новых компонент для реализации оригинальных операции со значениями свойств, требует более детального изложения.
6.3.2.1 Доступ к внутренним данным свойств
C++Builder не вводит ограничений на способ хранения значений свойств. Однако, все компоненты VCL придерживаются следующих соглашений:
• Значения свойств хранят члены данных объекта.
• Идентификаторы членов данных, хранящих значения свойств, образуются добавлением префикса F к имени этого свойства. Так исходные значения свойства Width компоненты TControl хранит член данных под именем FWidth.
• Идентификаторы членов данных, хранящих значения свойств, должны быть объявлены как private. При этом компонента, объявившая эти свойства, имеет к ним доступ, а пользователь данной компоненты и ее производные - нет.
• Производные компоненты должны использовать само наследованное свойство, не пытаясь осуществить прямой доступ к памяти внутренних данных.
Целесообразность изложенных соглашений поддерживает простой принцип ООП: только методы, реализующие свойство, имеют право доступа к своим значениям. Если какому-то другому методу или компоненте понадобилось изменить эти значения, они должны осуществлять это посредством данного свойства, а не обращаясь напрямую к его внутренним данным.
Прямой доступ является простейшим способом обращения к значениям свойств. Атрибуты read и write объявления свойства указывают, что чтение или присваивание значений внутренним членам данных свойства происходит непосредственно, без вызова соответствующих методов. Прямой доступ чаще всего используется для чтения значений свойств. При этом побочный эффект не возникает, однако данное свойство будет представлено в окне Инспектора объектов.
Методы чтения и записи замещают имена членов данных в атрибутах read и write объявления свойства. Вне зависимости от конкретной реализации методов, они должны быть объявлены как приватные, а производные компоненты должны использовать наследованные методы для чтения и записи значений свойства. Объявление методов приватными защищает пользователя производной компоненты от случайного вызова неадекватных методов, модифицирующих свойства не так, как ожидалось.
Значение свойства по умолчанию представляет собой именно то начальное значение инициализации свойства, которое устанавливает конструктор данной компоненты. C++Builder использует объявленное значение свойства по умолчанию default, чтобы определить, следует ли сохранять свойство в файле формы с расширением .dim (если атрибут stored явно не запрещает это).
Листинг 6.3 объявляет компоненту с единственным свойством IsTrue, имеющим значение по умолчанию true, а также конструктор, который устанавливает это значение при инициализации компонентного объекта. Заметим, что если свойство имеет значение по умолчанию false, то не нужно явно устанавливать его в конструкторе, поскольку все объекты (включая компоненты) всегда инициализируют свои члены данных значением 0, т.е. false.
class TMyComponent : public TComponent { private:
Boolean FIsTrue;
public:
virtual _fastcall TMyComponent(TComponent* AOwner);
_J?ublished:
_property Boolean IsTrue =
{ read=FIsTrue, write=FIsTrue, default=true };
};
_fastcall TMyComponent::
TMyComponent (TComponent* AOwner) : TComponent (AOwner) {
FIsTrue = true;
}
Листинг 6.3. Установка конструктором значения свойства по умолчанию
6.3.2.2 Свойства обеспечивают доступ к членам данных
Существует два способа, посредством которых свойства обеспечивают доступ к внутренним членам данных компонент: прямой или косвенный посредством методов чтения/записи.
class _declspec(delphiclass) TCustomEdit;
class _declspec(pascalimplementation) TCustomEdit :
public Controls::TWinControl { private:
int FMaxLength;
void _fastcall SetMaxLength(int Valued-protected:
property int MaxLength = { read=FMaxLength, write=SetMaxLength, default=false };
};
Листинг 6.4. Способы доступа свойства к членам данных.
Листинг 6.4 содержит код объявления компоненты TCustomEdit, взятый из VCL. TCustomEdit — это базовый класс для таких компонент редактирования как, TEdit и TMemo.
TCustomEdit имеет внутренний член данных FMaxLength типа int, который определяет максимальное значение длины символьной строки, которую пользователь может ввести в данный элемент управления. Вместо прямого присваивания FMaxLength этого значения, выполняется присваивание свойству MaxLength, которое обеспечивает доступ к члену FMaxLength.
Определение свойства MaxLength содержит тип, имя свойства и объявления атрибутов: прямого чтения члена данных FMaxLength, косвенного присваивания свойству MaxLength посредством метода записи SetMaxLength и значения по умолчанию 0.
6.3.2.3 Методы записи и чтения свойств
Метод записи имеет единственный параметр того же типа, что и свойство, и не возвращает значения. По соглашению, название функции образуется из слова "Set", за которым следует имя свойства. Например, метод записи свойства MaxLength имеет имя SetMaxLength и тип void. Метод записи свойства присвоит значение своего параметра соответствующему члену данных. Одна из причин использования метода записи — вызвать побочный эффект как результат операции присваивания свойству.
Листинг 6.5 представляет пример реализации ранее объявленного метода записи SetMaxLength.
void TCustomEdit::SetMaxLength(int Value) {
if (FMaxLength i= Value) {
FMaxLength = Value;
if (HandleAllocated) SendMessagefHandle, EM_LIMITTEXT, Value, 0);
} }
Листинг 6.5. Пример реализации метода записи.
Метод SetMaxLength проверяет, не присваивается ли свойству то значение, которое уже в нем хранится. Если нет, то новое значение присваивается члену данных FMaxLength. Кроме того, метод выдает сообщение EM_LIMITTEXT Windows в то окно, которое инкапсулирует компонента TCustomEdit. По этому сообщению устанавливается верхний предел длины текста, который пользователь может ввести в данный элемент управления. Последнее действие представляет собой простейший вариант побочного эффекта, вызываемого методом записи свойства.
Метод чтения представляет собой функцию без параметров (или с параметрами индексов для свойства типа массив), которая возвращает типизированное значение свойства. По соглашению, название функции образуется из слова "Get", за которым следует имя свойства. Например, метод чтения свойства MaxLength имеет имя GetMaxLength и возвращает значение типа int.
Метод чтения может осуществлять преобразование типа члена данных. Такие операции производят, например, методы AsString, AsFloat и Aslnteger, реализованные в компоненте TField.
6.3.3 Переопределение свойств
Все компоненты наследуют свойства своих предшественников, причем абстрактные базовые классы обычно объявляют свои свойства преимущественно в секциях public или protected. Чтобы такие свойства стали доступными пользователям производных компонент (как на стадии проектирования, так и во время выполнения программы), они обязаны переопределить их с ключевым словом _published.
Как видно из предыдущего примера, свойство MaxLength было определено в секции protected базовой компоненты TCustomEdit. Листинг 6.6 содержит переопределение свойства MaxLength как _published в производной компоненте TEdit.
class TEdit : public TCustomEdit { published:
property int MaxLength = { nodefault } ;
// Другие объявления };
Листинг 6.6. Переопределение свойства в производном классе.
Такое переопределение только снимает ограничения свойства, т.е. вы можете переопределить protected свойство как public, но не можете "спрятать" свойство, объявив его как protected. При переопределении свойства достаточно указать его имя, однако вы можете изменить значения атрибутов stored и default. Отметим также, что свойство MaxLength теперь вообще не имеет значения по умолчанию, хотя в унаследованном свойстве оно было задано.
Другая принципиальная особенность свойств заключается в том, что их можно изменять во время работы программы с помощью Инспектора объектов.
Правила видимости, объявленные ключевыми словами private, protected и public, действуют на свойства так же как и на обычные члены данных и методы. Единственное отличие объявлений, сделанных в секции _published, от объявлений в секции public, проявляется в том, что во время работы программы Инспектору объектов передается информация RTTI (Run-Time Type Identification) о типах членов данных и свойств.
6.3.4 Типы свойств
Свойство может быть любого типа, который способна возвратить функция (так как реализация свойства может возлагаться на функцию). Разные типы свойств по-разному представлены в окне Инспектора объектов и определяют разные варианты их редактирования, предлагаемые Инспектором. Более того, мы уже знаем, что некоторые свойства имеют собственные редакторы. .
Правилами языка C++ устанавливаются следующие обобщенные группы типов компонентных свойств:
Тип свойства | Действия Инспектора объектов |
Simple | Простые числовые, символьные и строчные свойства показываются Инспектором в виде чисел, символов или символьных строк, соответственно. Можно непосредственно вводить и редактировать значения простых свойств. |
Enumerated | Свойства перечисляемого типа (в том числе булевы) показываются Инспектором в виде значений, определенных в исходном тексте программы. Можно выбирать возможные значения из выпадающего списка перечислений. |
Set | Свойства типа множества показываются Инспектором в виде элементов множества. При расширении множества следует обращаться с каждым его элементом как с булевым значением: true, если элемент принадлежит множеству, или false в противном случае. |
Object | Свойства, которые сами по себе являются объектами, обычно обслуживаются своими собственными редакторами. Инспектор позволяет индивидуально редактировать те объектные свойства, которые объявлены какpublished. Объектные свойства должны быть производными от TPersistent |
Array | Свойства типа массив должны обслуживаться своими собственными редакторами свойств. Инспектор не имеет встроенных средств для редактирования таких свойств. |
6.3.4.1 Свойства типа множество
Как известно из главы 3, C++Builder объявляет несколько шаблонных классов для встроенных типов Delphi, которых нет в языке C++.
В частности, типы стиля шрифта определяются следующим образом:
enum TFontStyle
{ fsBold, fsltalic, fsUnderline, fsStrikeOut } ;
typedef Set
<TFontStyle, fsBold, fsStrikeOut>TFontStyles ;
TFontStyle является перечисляемым типом. TFontStyles определен как множество — неупорядоченная коллекция типа TFontStyle. Инспектор объектов позволяет переключать булевы значения элементов множества, выбирая значения false или true. Рис. 6.4 показывает пример манипуляций свойством Style типа множество в окне Инспектора. Это свойство, определяющее основные характеристики шрифта, имеют многие компоненты, в том числе, сама форма.
Рис. 6.4. Свойство Style.
6.3.4.2 Свойства типа массив
Эти свойства имеют множественные элементы, каждый из которых соответствует некоторому индексному значению. Например, свойство Lines стандартной компоненты TMemo представляет собой индексируемый список (массив) текстовых строк, составляющих многострочное поле редактирования. В данном случае свойство Lines предоставляет пользователю естественный доступ к указанному элементу массива (строке) в большом тексте.
Листинг 6.7 содержит объявление свойства String и метода чтения GetNumberSize, возвращающего индексируемую строку массива.
class TDemoComponent : public TComponent { private:
String _fastcall GetNumberSize(int Index);
public:
property String Number[int Index] = { read=GetNumber };
// Другие объявления };
String _fastcall TDemoComponent::GetNumberSize(int Index) {
String Result;
switch (Index) {
case 0: Result = "Zero"; break;
case 100: Result = "Medium"; break;
case 1000: Result = "Large"; break;
default: Result = "Unknow size";
) : .. return Result;
}
Листинг 6. 7. Свойство типа массив и его метод чтения.
Объявление свойств типа массив имеет следующие особенности:
• Объявление свойства включает один или несколько индексов, любого типа (по числу размерностей массива). В этом состоит первое отличие от обычных массивов, где индекс всегда имеет тип int.
• Доступ к элементам массива реализуется посредством методов чтения/записи, которые имеют дополнительные параметры - индексы массива, перечисляемые в том же порядке, что и при объявлении методов. В этом состоит второе отличие от обычных массивов, где допускаются ссылки не только на отдельные элементы, но и на весь массив.
6.4 События
События представляют собой средства, с помощью которых компонента сообщает пользователю о том, что на нее оказано некоторое предопределенное воздействие. Типичные простые события — нажатие кнопки на форме или клавиши на клавиатуре. Вкладка "События" (Events) Инспектора объектов позволяет получить доступ к событиям выбранной компоненты.
6.4.1 Зачем нужны события?
В общих словах событие определяют как механизм связи происшествия с некоторым кодом. Более точно событие - это closure, указатель на специфический метод в специфическом экземпляре класса.
С точки зрения прикладного программиста, событие представляет собой имя, которому можно присвоить некоторый вызываемый метод. Например, экземпляр компоненты TButton - кнопка Buttoni имеет методы для события OnClick. По умолчанию C++Builder генерирует обработчик события с именем ButtonlClick и присваивает его событию OnClick. Когда происходит нажатие кнопки, объект вызывает метод, присвоенный событию OnClick, в данном случае, ButtonlClick.
Таким образом, прикладной программист воспринимает событие как способ указания, к какому из написанных им методов должна обращаться программа, когда данное событие происходит. С точки зрения разработчика компонент, в понятии события заключено значительно большее. Автор компоненты обязан предусмотреть программный интерфейс, к которому можно подключить методы, вызываемые при определенных воздействиях на компоненту. Ваша компонента предоставляет "разъемы", в которые прикладной программист сможет вставлять специфические коды реакции на события.
6.4.2 Определение событий
Формально C++Builder определяет событие как типизированный указатель на метод в специфическом экземпляре класса:
<тип> (_closure * <имя метода>) (<список параметров>)
Для разработчика компонент closure представляет собой некоторую программную заглушку: когда пользователь определяет реакцию на некоторое событие, место заглушки занимает его обработчик, который вызывается вашей программой при возникновении этого события.
Когда пользователь выполняет присваивание некоторому событию, происходит присваивание не просто метода с конкретным именем, а метода в специфическом экземпляре класса. В качестве экземпляра класса, указатель которого this передается как скрытый параметр, обычно (но не всегда) выступает форма, которая содержит данную компоненту.
все символы к верхнему регистру. Для этого надо определить следующий обработчик события нажатия клавиши:
void _fastcall TFormI::EditlKeyPress
(TObject *Sender, char SKey) {
Key = UpCase(Key) ;
)
Передача адресных аргументов может также использоваться для переопределения поведения обработчика события по умолчанию.
Присваивание обработчиков всем возможным событиям вашей компоненты вовсе не обязательно. Этот принцип оказывает существенное влияние на разработку ваших компонент и их событий. Очевидно, работа вашей компоненты не должна нарушаться из-за того, что пользователь просто не предусмотрел обработчика какого-то события.
При разработке компонент нужно учитывать следующие аспекты обработчиков событий:
• Прикладные программисты не обязаны обрабатывать события. Различные события возникают практически постоянно при работе любого приложения Windows. Простое смещение курсора по компоненте вызывает передачу многочисленных сообщений Windows данной компоненте о передвижении мыши, которые компонента транслирует в события OnMouseMove. Если поведение компоненты не зависит от манипуляций мышью, то в большинстве случаев программа просто не обращает внимание на такие события.
• Прикладные программисты могут написать любой код обработки события. Компоненты VCL реализуют свои события так, чтобы свести к минимуму риск неверной реакции вследствие логических ошибок в обработчике события. Конечно, невозможно защититься от всех ошибок, однако можно, например, перед вызовом обработчика выполнить инициализацию всех структур данных, чтобы пользователи не получали неопределенной информации.
6.4.2.3 Стандартные события
Все компоненты VCL наследуют большинство общих сообщений Windows стандартные события. Такие события встроены в защищенные секции компонент, поэтому пользователи не могут подсоединять к ним свои обработчики.
Существует две категории стандартных событий: определенные для всех компонент и определенные только для оконных компонент (стандартных и оригинальных). Все компоненты наследуют от базового абстрактного класса Tcontrol следующие стандартные события:
OnClick______OnDragDrop___OnEndDrag____OnMouseMove
OnDblClick____| OnDragOver___| OnMouseDown | OnMouseUp
В дополнение к этим событиям, оконные компоненты наследуют от базового абстрактного класса TWinControl еще и следующие стандартные события:
OnEnter | OnKeyDown | OnKeyPress |
OnKeyUp | OnExit |
Чтобы стандартные события вашей компоненты были видимы Инспектору объектов на стадии проектирования или во время выполнения программы, вы должны переопределить свойства событии в секции public или published. Листинг 6.9 показывает, как сделать стандартное событие OnClick видимым.
class TMyControl : public TCustomControl {
_published:
_property OnClick; // OnClick стало видимым Инспектору };
Листинг 6.9. Переопределение стандартного события
Все стандартные события имеют соответствующие защищенные динамические методы, унаследованные от TControl, имена которых образованы от названия события без частицы "On". Например, события OnClick вызывают метод Click.
Как правило, вы сначала обращаетесь к наследованному методу, разрешая пользовательскому обработчику события произвести свои действия перед тем, как сработает код вашего переопределения. Предположим, вы пишете новую компоненту, в которой хотите модифицировать реакцию на щелчки мышью. Вместо того, чтобы присвоить соответствующий обработчик события OnClick, как это сделал бы прикладной программист, вы переопределяете защищенный метод Click (Листинг 6.10).
void _fastcall TMyControl::Click() {
// Стандартное обслуживание, включающая вызов обработчика
TWinControl::Click() ;
// Далее следует ваш код переопределения метода
}
Листинг 6.10. Переопределение защищенного метода.
6.4.2.4 Собственные события
Необходимость в определении совершенно новых событий возникает довольно редко. Скорее всего окажется достаточным переопределить обработку уже существующих событий. Если вы все же придумали компоненту с совершенно оригинальным поведением, вам придется ввести и новые события.
Существует две причины возникновения событий: воздействие пользователя на компоненту и изменение ее состояния. События в результате воздействия пользователя почти всегда вызываются сообщениями Windows, указывающими, что на это воздействие может потребоваться некоторая реакция. События в результате изменения состояния компоненты могут также соотносится с сообщениями Windows (изменение фокуса или запрещение компоненты), хотя чаще они являются следствием изменений свойств компоненты. Разработчик компонент должен полностью контролировать причины возникновения собственных событий, чтобы пользователи знали как их использовать.
Определение события проходит через четыре этапа, для реализации которых вам необходимо:
1. Знать, какое действие вызывает событие. Для некоторых событий ответ очевиден. Например, при нажатии левой кнопки мыши Windows посылает сообщение WM_LBUTTONDOWN. Принимая это сообщение, компонента вызывает метод MouseDown, который в свою очередь обращается к обработчику события, который пользователь подсоединил к событию OnMouseDown. Некоторые события менее очевидно связаны с внешними воздействиями. Так линейка прокрутки имеет событие OnChange, которое возникает по разным причинам - в результате щелчков мышью, нажатия управляющих клавиш или изменений в других родственных компонентах.
2. Определить тип обработчика события. Обработчик события может просто опознавать событие нотификации (объектного типа TNotifyEvent) или содержать обработку событий специфического типа. TNotifyEvent задает единственный аргумент - указатель объекта, благодаря которому обработчик "узнает" компоненту, сгенерировавшую это событие. Например, щелчки мышью генерируют события нотификации. Обработчик этих событий знает только то, на какой компоненте пользователь щелкнул мышью. Передача дополнительных адресных аргументов используется, например, обработчиком события типа TKeyPressEvent, вызываемого при нажатии любой клавиши на клавиатуре.
3. Объявить тип и свойство для события. Инспектор объектов определяет, что данное свойство является событием, обнаруживая тип свойства closure, и представляет его на вкладке События. Давайте событиям значимые и описательные имена, по которым пользователь догадается о том, что это за событие. C++Builder рекомендует начинать имена событий с частицы "On.".
4. Создать виртуальный метод, который вызывает обработчик события пользователя и обеспечивает обработку по умолчанию. Правильное функционирование вашей компоненты не должно зависеть от конкретной реакции, которую пользователь заложил в обработчик события. В частности, пустой обработчик события так же допустим, как и его отсутствие. Более того, пользователь имеет право переопределить обработку по умолчанию. Чтобы предоставить ему такую возможность, передайте в обработчик дополнительный адресный аргумент, значение которого можно проверять при возврате. При этом пустой обработчик события не изменит значения аргумента, и обработка по умолчанию всегда будет иметь место после возврата из пустого обработчика.
6.4.2.5 События и сообщения Windows
Опытный программист определенно заметит сходство некоторых событий C++Builder и сообщений Windows. В следующей таблице приведен краткий список событий объекта TForm и соответствующих сообщений Windows, которые вы использовали бы в обычной программе на языке С:
Событие VCL | Сообщение Windows |
OnCreate | WM CREATE |
OnClose | WM DESTROY |
OnReSize | WM SIZE |
OnActivate, OnDeactivate | WMACTIVATE |
OnShow, OnHide | WM SHOWWINDOW |
OnKeyDown | WM KEYDOWN |
OnKeyUp | WM KEYUP |
OnKeyDown | WM KEYDOWN |
OnMouseDown | WM LBUTTONDOWN, WM RBUTTONDOWN |
On Mouse Up | WM LBUTTONUP,WM RBUTTONUP |
OnMouseMove | WMMOUSEMOVE |
OnDblClkk | WM LBUTTONDBLCLK, WMRBUTTONDBLCLK |
OnPaint | WM PAINT |
He всякому сообщению Windows можно найти соответствующее событие VCL. Например, в обычной программе на языке С для Windows сообщение WM_COMMAND используется как для обслуживания нажатий на кнопки, так и выбора команд из меню. В C++Builder для этих целей используются разные события: TButton::OnClick и TMenuItem::OnCUck, соответственно.
С другой стороны, некоторые события VCL расширяют функциональность встроенных сообщений Windows. Так события OnDragOver и OnDragDrop Объекта TForm просто и прямолинейно реализуют операции перетаскивания (drag-and-drop) в вашей программе. Большинство компонент на вкладках Standard и Win95 Палитры компонент лишь специальным образом обрамляют известные элементы управления Windows. Компоненты на других вкладках представляют совершенно новые элементы управления (и события) для особых областей функционирования.
Компоненты вкладок Standard и Win95 инкапсулируют стандартные элементы управления Windows. За взаимодействие между пользователем и программой, которое ранее поддерживалось реакцией на сообщения Windows, теперь отвечают обработчики событий компонент VCL. Однако, в некоторых ситуациях возникает необходимость "взять на себя" те сообщения Windows, которые не имеют соответствующих событий VCL или не адекватны им. Для таких случаев в VCL предусмотрена методика ООП, обеспечивающая непосредственный отклик на события Windows, подобно средствам библиотек базовых классов OWL или MFC. Эта методика, реализуемая с помощью макросов BEGIN_MESSAGE_MAP, MESSAGE.HANDLER и END_MESSAGE_MAP, весьма трудоемка и здесь не рассматривается. Поэтому предварительно тщательно просмотрите имеющиеся в VCL компоненты, которые могут содержать подходящие события.
6.4.3 Обработка событий
Событиям программист ставит в соответствие коды обработчиков событий, выполняющиеся всякий раз, когда соответствующее событие происходит. Некоторым событиям в Инспекторе объектов предписаны выпадающие списки, содержащие опции стандартных функций обработки событий, объявления и вызов которых C++Builder генерирует автоматически.
Рис. 6.5 показывает вкладку Events Инспектора объектов с выбранным событием OnClick компоненты TButton. Это событие возникает при нажатии кнопки Button 1. Если дважды щелкнуть мышью на поле события, C++Builder автоматически сгенерирует соответствующее объявление метода в файле объявлений Unitl.h, и Редактор кода переведет вас в нужную позицию в кодовом файле Unitl.cpp, где вы сможете написать код, реализующий этот метод.
Нижеследующий пример иллюстрирует процесс создания кода обработчика события OnClick (Нажата кнопка Button 1) для компоненты TButton.
Опытному программисту уже стало понятно, что событие будет содержать указатель функции обработки этого события.
Чтобы связать ваш собственный обработчик с событием OnClick компоненты TButton воремя выполнения программы, вы должны сначала создать метод для обслуживания этого события. Как и любой метод, он должен принадлежать существующему объекту — форме, которая владеет компонентой TButton и на которой она размещена.
Рис. 6.6 показывает окно Редактора кода с файлами Unitl.h и Unintl.cpp программного модуля, реализующего обработку события OnClick. Объявленный метод становится обработчиком события, когда событию Buttonl->OnClick присваивается указатель некоторого метода MyOnClickEvent.
Указанное присваивание можно также сделать динамически при работе программы в обработчике события OnCreate вашей формы. Результат будет таким же, как и при создании обработчика событий с помощью Инспектора объектов на этапе проектирования, за исключением того, что в этом случае C++Builder сохраняет связь события с его обработчиком в ресурсном файле с расширением .dfm. При запуске приложения VCL считывает форму из ресурсного файла и динамически присваивает значения свойств и событий компонент, размещенных на форме.
Рис. 6.6. Определение метода обработки события OnClick.
Когда вы определяете методы для обработчиков событий, эти методы должны быть того же типа, что и типы свойства и членов данных, на которые ссылается свойство. Например, событие OnClick ссылается на внутренний член данных FOnClick функционального типа TNotifyEvent.
6.5 Методы
Компонентные методы ничем не отличаются от других объектных функции-членов. Хотя C++Builder не вводит никаких специальных ограничении на оформление компонентных методов, имеется ряд правил, которых стоит придерживаться:
1. Минимизируйте число методов, которые вызывает прикладной программист, чтобы использовать вашу компоненту. Многое из того, что вы намеревались реализовать в виде методов, вероятно, лучше инкапсулировать в свойства компоненты. В отличие от свойств, методы работают только во время исполнения программы.
2. Избегайте взаимной зависимости методов, максимально изолируя методы друг от друга. Нельзя требовать, чтобы методы выполнялись в определенном порядке. Никакой метод не должен переводить компоненту в такое состояние, когда вызовы других методов станут запрещенными, т.к. смогут нарушить нормальное функционирование компоненты.
3. Придерживайтесь соглашения об именах методов. Названия должны быть описательными, т.е. включать глагол действия и отражать возвращаемое значение (например, метод PasteFromClipboard передает в компоненту данные из доски объявлений, а метод GetHorizontalPosition возвращает горизонтальную позицию некоторой величины).
4. Правильно защищайте методы. Все методы (включая конструкторы и .деструкторы), к которым имеют право обращаться пользователи вашей компоненты, следует объявлять как public. Все методы, к которым имеют право обращаться производные объекты вашей компоненты, следует объявлять как protected, что запрещает пользователям преждевременно вызывать методы, данные для которых еще не созданы. Только те методы, которые реализуют доступ к свойствам компоненты, должны быть объявлены как private, поскольку пользователи оперируют со значениями свойств напрямую.
5. Объявляйте методы виртуальными, когда хотите обеспечить полиморфное поведение переопределенных версий в разных классах.
6.5.1 Вызовы статических методов
Все объектные методы определяются как статические (static) по умолчанию, если вы не объявили их иначе. Вызовы статических методов выглядят как вызовы обычных функций: определяемый при компиляции адрес метода связывает его с объектом. Поэтому обращения к статическим методам выполняются быстрее, нежели к виртуальным, адреса которых неизвестны на этапе компиляции.
class TFirstComponent : public Tcomponent
{
public:
void _fastcall Move() ;
void _fastcall Flash();
};
class TSecondComponent : public TPirstComponent { public:
void _fasfccall Move(); // отличен от наследованного метода
int _fastcall Flash(int HowOften); // тоже отличается };
Листинг 6.11. Переопределение статических методов.
Адреса статических методов не меняются при наследовании, а значит, статические методы не следует переопределять - они призваны выполнять одинаковые действия во всех родственных компонентных объектах. Листинг 6.11 показывает, что переопределение статических методов в производном классе по сути заменяет родительские методы.
6.5.2 Вызовы виртуальных методов
Вызов виртуального метода записывается точно так же, как вызовы других функций, однако механизм обращения слегка усложняется, делая его более гибким. Поскольку виртуальные методы могут быть переопределены в производных классах, их адреса невозможно определить на стадии компиляции. Фактические адреса виртуальных методов становятся известными только во время выполнения программы.
Ключевое слово virtual в объявлении метода создает запись в таблице виртуальных методов объекта (VMT). При выполнении программы эти записи преобразуются в фактические адреса всех виртуальных методов данного компонентного класса. Когда вы производите новый объектный класс, он наследует VMT всех своих предшественников, добавляя к таблице дополнительные виртуальные методы, объявленные в производном классе. Кроме того, производный класс может переопределять наследованные виртуальные методы. Любой конструктор компоненты всегда должен быть виртуальным.
6.6 Иерархия классов VCL
Ранее в этой главе мы ознакомились с характеристиками четырех базисных типов компонент: стандартные, оригинальные, графические и невидимые. Теперь покажем место и назначение этих типов в иерархии объектов.
Рис. 6.7 показывает ключевые классы в иерархической структуре, от которых произведены все компоненты VCL. Каждый объект представляет некоторый набор методов, событий и свойств и имеет специальное назначение.
TObiect
TPersistent
TComponent
TControl
TGraphicControl TWinControl
TCustomControl
Рис. 6.7. Иерархия ключевых базовых классов VCL.
Подобно тому как TObject является базовым классом для всех порождаемых классов, TComponent является базовым классом для всех порождаемых компонент. $
Невидимые компоненты произведены от класса TComponent. Графические компоненты, не ассоциированные с оконными элементами управления, произведены от класса TGraphicControl.
Являясь оконными элементами, компоненты стандартного управления произведены непосредственно от класса TWinControl, а оригинальные компоненты — косвенно от класса TCustomControl, восходящего к TWinControl. Именно на уровне TWinControl и вводится "оконный дескриптор" (window handle). Рис. 6.8 S продолжает иерархическую структурную схему компонентных классов VCL.
* TButtonControl
о TButton
+ TBitBtn о TCustomCheckBox
+ TCheckBox
+ TDBCheckBox о TRadioButton
* TCustomComboBox о TComboBox о TDBComboBox о TDriveComboBox о TFilterComboBox
* TCustoinControl
о TCustomGrid
+ TCustomDBGrid + TDBGrid + TDBLookupList
+ TPopupGrid + TCustomOutline
+ TOutline + TDrawGrid
+ TStringGrid о TCustomGroupBox
+ TCustomRadioGroup + TDBRadioGroup + TRadioGroup
+ TGroupBox о TCustomPanei
+ TDBNavigator
+ TPanel о TDBImage о TDBLookupControl
+ TDBLookupComboBox
+ TDBLookupListBox
+ TPopupDataList о THeader о THintWindow о TMediaPlayer о TNotebook о TOleContainer о TPage о TScroller о TTabSet
* TCuatonEdit
о TCustomMaskEdit
+ TDBEdit
+ TInplaceEdit
+ TMaskEdit о TCustomMemo
+ TCustomRichEdit + TRichEdit
+ TDBMemo
+ TMemo о TDBLookupCombo о TEdit
* TCustomHotKey о THotKey
* TCuafcomListBox о TDBListBox о TDirectoryListBox о TFileListBox о TListBox
* TCuetomLietView о TListView
* TCuetomTabControl о TPageControl о TTabbedNotebook о TTabControl
* TCustomTreeView о TTreeView
* TCustomUpDown 0 TUpDown
* TDBCtrlGrid
* TDBCtrlPanel
* THeaderControl
* TOleControl
* TProgressBar
* TScrollBar
* TScrollingWinControl
о TForm
+ TDesignWindow + TInputReqDialog + TLoginDialog + TPasswordDialog
о TScrollBox
* TStatueBar
* TTabPage
* TTabSheet
* TTrackBar
Puc. 6.8. Дерево производных компонент от TCustomControl и TWinConlrol.
6.6.1 TObject
TObject является базовым классом для всех прочих порождаемых классов. TObject инкапсулирует общее для всех объектов системы C++Builder функциональное поведение, обусловленное методами, которые обеспечивают:
• Способность конструктора создавать, а деструктора разрушать объект-экземпляр класса в динамической памяти. Конструктор TObject возвращает указатель на создаваемый объект.
• Информацию RTTI об имени, типе производного объекта и его свойствах, которые объявлены как _published.
• Поддержку обработки сообщений.
Большинство этих методов предназначены для внутреннего использования средой C++Builder, поэтому не следует прямо обращаться к ним из вашей программы. Часть методов TObject объявлены как статические (с ключевым словом static). Это означает, что вам не нужно создавать экземпляр данного класса для того, чтобы обратиться к его статическим методам.
Все компоненты должны порождаться непосредственно от класса TComponent или от его потомков. TComponent, будучи в свою очередь потомком TObject, наследует его члены данных, методы и свойства.
Используйте TObject для объявления простых объектов, которые не являются компонентами и не нуждаются в поточности и присваивании. Среди полезных не компонентных классов отметим TStringList, TIniFile и TPrinter.
6.6.2 TPersistent
Класс TPersistent непосредственно произведен от TObject. Этот абстрактный класс не определяет никаких специальных свойств или событий, однако его производные приобретают особые способности присваивания и поточности.
TPersistent определяет ряд поточных методов, используемых разработчиками компонент, которые могут быть перегружены производными компонентами:
• Assign позволяет присваивать значения свойствам.
• AssignTo позволяет присваивать содержимое одного объекта другому (например, как делает это производный от TPersistent класс TClipboard).
• DefineProperties позволяет определить процедуру загрузки и сохранения в потоке особых дополнительных свойств. По умолчанию сохраняются только свойства, объявленные как _published.
6.6.3 TComponent
Класс TComponent непосредственно произведен от TPersistent. Как уже было сказано, все компоненты являются производными от TComponent и могут находится в его владении. TComponent инкапсулирует общее для всех компонент функциональное поведение, обусловленное свойствами и методами, которые обеспечивают:
• Перенос на форму из Палитры компонент и манипуляции в окне Редактора
форм.
• Способность владения и обслуживания других компонент.
• Специальные характеристики поточности, с которыми может манипулировать Инспектор объектов на этапе проектирования.
• Возможность манипулирования некоторыми невидимыми компонентами на стадии проектирования.
Класс TComponent определяет ряд свойств, которые придают объекту особую функциональность:
Свойство Назначение
Owner_______| Ссылается на владельца компоненты.
ComponentCount | Число компонент в перечне, которыми владеет данная компонента.
Componentlndex Индекс компоненты в перечне, начиная с 0.
Components | Свойство, содержащее перечень компонент, которыми владеет данная компонента.
ComponentState Текущее состояние компоненты.
ComponentStyle Стиль, определяющий поведение компоненты.
Name Имя компоненты.
Tag | Свойство типа int, которое не имеет предопределенного значения и может содержать любые данные или указате-ли, по усмотрению программиста.
Designlnfo | Используется Редактором форм.
Класс TComponent определяет ряд методов, которые придают объекту право владения другими компонентами и возможность доступа к ним посредством Инспектора объектов:
• Destroying и DestroyComponents устанавливают атрибуты данной компоненты и компонент, которыми она владеет, в состояние, указывающее на то, что они подлежат уничтожению.
• HasParent возвращает булево значение, указывающее на наличие родителя компоненты. Обращаться к этому методу следует до ссылок к родителю данной компоненты. Отметим, что наличие владельца компоненты не идентифицируется.
• insertComponent добавляет компоненту, передаваемую в качестве параметра, к перечню компонент, которыми владеет данная компонента, а RemoveComponent удаляет компоненту из этого перечня.
• FindComponent возвращает указатель экземпляра компоненты, о которой известно только имя, но неизвестна ссылка на владельца. Допустим, что форма содержит экземпляр компоненты TEdit с именем Editl. Чтобы получить указатель на экземпляр Editl и адресовать его текст, используйте следующий код:
void_fastcall TFormI::ButtonlClick(TObject *Sender)
{
TEdit * Editlnstance = FindComponent("Editl");
(TEdit *)(FindComponent("Editl"))->Text = "Hello";
}
He создавайте экземпляров класса TComponent. Используйте TComponent в качестве базового класса при создании невидимых компонент.
6.6.4 TControl
Класс TControl определяет общие для видимых компонент члены данных, методы и события. Поскольку элементы TControl обладают способностью отображать себя, некоторые его свойства оперируют с положением, размером и видом объекта (Top, Left, Width, Height и Cursor, Hint), а другие свойства относятся к параметрам области клиента (ClientRect, ClientWidth и ClientHeight).
TControl также вводит свойства, устанавливающие видимость, доступность” цвет и шрифт элементов управления (Visible, Enabled, Color и Font). Свойств” Text и Caption обеспечивают установку редактируемых текстов и названий.
Наличие свойства Parent (Родитель), содержащего соответствующую ссылку, обусловлено возможностью класса TControl иметь родителя. Этот родитель может быть производным от TWinControl, поскольку родители обязаны быть оконными элементами управления.
TControl содержит ряд событий, возникающих при манипуляциях мышью над, видимыми элементами управления (OnClick, OnDblClick, OnMouseDowit, OnMouseMove, OnMouseUp, OnDragOver, OnDragDrop и OnEndDrag).
Поскольку TControl редко используется непосредственно, его события большинство свойств объявлены в секции protected. Разработчики производных компонент могут, таким образом, выбирать, какие свойства и события перенести в секцию public или _published, расширяя тем самым права доступа.
Большинство компонент являются производными от TWinControl или TGraphicControl. Эти базовые классы рассматриваются в следующих параграфах.
6.6.5 TWinControl
Класс TWinControl инкапсулирует оконные элементы управления с дескрипторами. Некоторые производные от TWinControl (компоненты TEdit, TListBox и TComboBox) инкапсулируют стандартные элементы управления Windows — поля редактирования, простые и комбинированные списки и т.д. Поэтому вам не придется манипулировать с ними посредством стандартных функций Windows API, a пользоваться свойствами и методами, предоставляемыми самими компонентами.
Производные компоненты от TWinControl обладают тремя основными характеристиками: они имеют оконные дескрипторы, способны принимать фокус ввода и могут являться родителями других элементов управления. Поэтому многие свойства TWinControl предназначены для изменения фокуса, обслуживания событий клавиатуры и отображения потомков компоненты:
Свойство | Назначение |
Brush | Управляет цветом и орнаментом канвы, используемой при заливке графических фигур и фона. |
Controls | Содержит список элементов управления, для которых TWinControl является родителем. |
ControlCount | Содержит число элементов управления, для которых TWinControl является родителем. |
Ctl3d | Определяет, требуется ли трехмерное отображение компоненты. |
Handle | Ссылается на оконный дескриптор объекта Windows, который инкапсулирует TWinControl. Это свойство передается тем стандартным функциям Windows API, которые принимают дескриптор как параметр. |
HelpContext | Задает номер контекстной справки, соответствующий некоторому окну в файле помощи с расширением .hip. Это свойство позволяет организовать контекстно-чувствительный поиск в справочной службе для отдельных компонент. |
Showing | Указывает, видима или невидима компонента. |
TabStop | Указывает, можно ли использовать клавишу табуляции для выбора нужной компоненты. |
TabOrder | Определяет позиции табулятора выбора компонент. |
Методы TWinControl главным образом ориентированы на разработчиков компонент и предназначены для управления фокусом, получения статусной информации, диспетчеризации сообщений и позиционирования:
• Broadcast используется для рассылки сообщений всем потомкам TWinControl.
• CanFocus возвращает булево значение, которое определяет, может ли TWinControl принять фокус ввода. Например, компонента не сможет принять фокус, если ее свойство Visible имеет значение false.
• ContainsControl определяет, содержится ли данный элемент управления внутри класса TWinControl. Этот метод не сообщает о том, является ли данный элемент потомком по отношению к TWinControl. Например, внешний класс TWinControl может быть родителем другого элемента, и эта родительская преемственность может продолжаться далее. Однако, все внутренние элементы содержатся во внешнем классе TWinControl.
• ContrblAtPos возвращает ссылку на потомка, если элемент управления заключен в заданных координатах области клиента родителя. Таким образом можно найти относительное положение потомка по отношению к родителю.
• DisableAlign и EnableAlign используются для временного запрещения или разрешения выравнивания компонент внутри TWinControl.
• Focused возвращает значение true, если TWinControl находится в фокусе ввода, т.е. является активным элементом формы, на которой он размещен.
• HandleAl located возвращает значение true, если элемент управления имеет оконный дескриптор. HandleNeeded создает новый дескриптор, если он еще не был создан. Аналогичное действие выполняется автоматически при прямом обращении к свойству Handle.
• InsertControl добавляет элемент управления к свойству Controls (типа массив), делая TWinControl своим родителем. Лучший способ добавить потомка во время работы программы — просто присвоить ссылку на родителя свойству Parent. RemoveControl удаляет элемент управления из массщ Controls.
• Invalidate и Repaint выполняют перерисовку компоненты. Мето;
Repaint обрабатывает сообщение WMJPAINT, обращается к метол Update, который в свою очередь вызывает функцию Windows АP UpdaleWindow. PaintTo может использоваться для перерисовки содержимого TWinControl в область (device context) другого элемента управления. ReAlign вызывает повтор выравнивания компонент внутри TWinControl. ScaleBy используется для масштабирования TWinControl в заданном процентном отношении к исходному размеру. ScrollBy можно использовать, если вам не нравится логика прокрутки TWinControl, принятая по умолчанию.
• SetBounds устанавливает свойства границ компоненты (Left, Top, Width, Height) для TWinControl. Прямое изменение каждого из указанных свойств менее эффективно, поскольку всякий раз сопряжено с перерисовкой SetFocus активизирует TWinControl.
Другие методы, применяемые разработчиками компонент, предназначены для создания и уничтожения инкапсулированных TWinControl окон и их дескрипторов: .
• CreateWnd создает оконный элемент управления, инкапсулированны TWinControl, посредством последовательного обращения к CreateParams и CreateWindowHandle.
• CreateParams инициирует начальные значения всех оконных параметров Перегрузка этого метода позволяет менять оконные параметры, установлен ные по умолчанию.
• CreateWindowHandle создает оконный дескриптор путем обращения функции Windows API Create WindowEx.
• DestroyWnd уничтожает инкапсулированный оконный элемент управления путем обращения к методу DestroyWindowHandle, который в свою очередь обращается к функции Windows API Destroy Window.
TWinControl имеет события, вызываемые взаимодействием с клавиатурой i изменением фокуса (OnKeyDown, OnKey Press, OnKeyUp, OnEnter и OnExif).
Разрабатываемые компоненты редко происходят непосредственно от TWinControl. Лучше производить новые компоненты от общего класса TCustomControl, который предоставляет канву для рисования и обрабатывает сообщение WM_PAINT, или от некоторых более специализированных классов (TButtonControl, TCustomComboBox, TCustomEdit или TCustomListBox).
6.6.6 TGraphicControl
Производные от абстрактного класса TGraphicControl, в отличие от TWinControl, не имеют оконного дескриптора, не могут принять фокус ввода и не могут являться родителями других элементов управления.
Производные TGraphicControl используются в тех ситуациях, когда вы хотите изобразить на форме текст или графику, не обращаясь к функциональным возможностям обычных оконных элементов управления. Отметим следующие достоинства такого подхода. Во-первых, TGraphicControl не пользуется системными ресурсами Windows, так как не требует оконного дескриптора. Во-вторых, метод рисования TGraphicControl исполняются немного быстрее за счет того, что перерисовка компоненты не связана с диспетчеризацией сообщений Windows, a реализуется процессом рисования, заложенным в родителе данного элемента.
Производные TGraphicControl имеют обработчики событий, вызываемые манипуляциями с мышью.
TGraphicControl возлагает на пользователя операции перерисовки. Этот класс содержит свойство Canvas (Канва), которое обеспечивает доступ к отведенной для рисования поверхности, и виртуальный метод Paint, который вызывается в ответ на сообщение WM_PAINT, принимаемое родительским элементом управления.
6.6.7 TCustomControl
Стандартные компоненты, как производные от TWinControl (например, TEdit и TListbox), уже имеют способности собственного отображения, предоставленные инкапсулированными элементами управления Windows. А как создать оконную компоненту, которая отображает себя в виде, соответствующем оригинальным требованиям пользователя? Решение именно этой задачи и обеспечивает TCustomControl.
Будучи производным от класса TWinControl, TCustomControl является оконным элементом управления и, следовательно, может принять фокус ввода. Разработанные компоненты могут быть произведены от TCustomControl. Как и TGraphicControl, TCustomControl содержит свойство Canvas (Канва), которое предоставляет возможность произвольного рисования на выбранной прямоугольной области. По существу, производные компоненты от TCustomControl предоставляют в ваше распоряжение виртуальный метод Paint, перегрузка которого позволит рисовать компоненты так, как вы пожелаете.
6.7 Схема разработки компонент
Процесс разработки собственной компоненты (мы будем называть ее TMyComponent) проходит через выполнение следующих этапов:
1. Создание модуля для новой компоненты.
2. Наследование производного класса от существующего базового компонентного класса.
3. Добавление нужных свойств, событий и методов.
4. Регистрация компоненты в C++Builder.
5. Отладка.
6. Инсталляция компоненты на Палитру.
7. Сохранение файлов компоненты.
Далее будет показано, как некоторые из перечисленных действий, программируемых далее вручную. Мастер компонент способен выполнить автоматически (создание файлов модуля, наследование компонентного класса, объявление нового конструктора и регистрация компоненты).
6.7.1 Создание модуля компоненты
Программный модуль состоит из двух файлов МуСотр.срр и MyComp.h, которые компилируются в объектный файл с расширением MyComp.obj C++Builder использует модули в различных целях - каждая форма и большинств компонент (или их логических групп) имеют свой собственный модуль. При paзработке компоненты вы либо создаете новый модуль для компоненты, или до бавляете ее к существующему модулю.
Чтобы создать модуль, выполните команду File | New и в открывшемся диалоre New Items выберите значок Unit. Чтобы добавить компоненту к существую щему модулю, выполните команду File | Open и в открывшемся диалоге отьшите ваш файл МуСотр.срр. Имея открытый модуль в окне Редактора кода, вы можете приступить к разработке компоненты. Для начала перечислите в МуСотр.h необходимые файлы фазы предкомпиляции и объявите производный класс ваше компоненты (Листинг 6.12).
^include <vcl\sysutils.hpp>
#include <vcl\controls.hpp>
#include <vcl\classes.hpp>
#include <vcl\forms.hpp>
class TMyComponent : public < базовый компонентный класс >
{
};
Листинг 6.12. Заготовка файла MyComp.h модуля компоненты. 1
Пока мы создали компоненту, которая ничем не отличается от своего родите", ля. В следующем разделе описываются варианты наследования в зависимости от выбранного типа базового компонентного класса.
6.7.2 Наследование компоненты
Любая компонента является производной от общего прародителя TComponent, от его более специализированных наследников (таких как TControl или TGraphicControl) или от существующего компонентного класса. Компонеитой может стать практически любой элемент вашей программы, поведением которого вы хотите управлять на стадии проектирования.
Цель | Базовый класс |
Модификация существующей компоненты | Любая существующая компонента, например, TListBox, или абстрактный компонентный класс TCustomListBox. |
Создание оригинальной компоненты | TCustomControl |
Создание графической компоненты | TGraphicControl |
Создание невидимой компоненты | TComponent |
6.7.2.1 Модификация существующих компонент
Простейший способ построить новую компоненту - это начать с существующей и изменить ее свойства. Вашей целью может являться добавление, исключение или замена значений по умолчанию некоторых свойств компоненты-образца. Вы можете использовать для этой цели любой подходящий абстрактный класс VCL, в название которого входит слово "Custom".
Например, вы можете произвести новую компоненту списка со специальными свойствами, которых нет в стандартном классе TListBox. Поскольку нельзя прямо модифицировать TListBox, вы должны начать с ее ближайшего предшественника в иерархии классов. Для этой цели лучше всего подходит TCustomListBox, которая реализует все мыслимые свойства производных компонент списка, однако не выставляет всех их в секции _published.
Наследуя вашу компоненту от одного из абстрактных типов (таких как TCustomListBox), вы всего лишь объявляете в секции _published те свойства, которые хотите включить в вашу компоненту, оставляя остальные в секции protected.
6.7.2.2 Создание оригинальных оконных компонент
С оконным интерфейсным элементом, видимым во время работы программы, пользователь обычно может взаимодействовать. Все оконные компоненты являются производными от базового класса TWinControl. Стандартный элемент оконного управления характеризует так называемый "оконный дескриптор" (window handle), который заключен в свойстве Handle. Благодаря оконному дескриптору, Windows "узнает" данную компоненту, в частности, что она может принять фокус ввода и передавать оконный дескриптор функциям Windows API для идентификации рабочего окна.
Хотя вы можете создать оригинальный интерфейсный элемент (который не имеет существующих аналогов и никак не связан с ними), используя TWinControl как отправную точку, C++Builder предоставляет компоненту TCustomControl как раз для этой цели. TCustomControl - это специализированный оконный элемент управления, который упрощает рисование сложных визуальных изображений. Если вашей компоненте не нужно принимать фокус ввода, вы можете наследовать ее от графического элемента управления, который дает экономию системных ресурсов. Все компоненты стандартного оконного управления: кнопки, списки, поля редактирования (за исключением TLabel, которая никогда не принимает фокус ввода) - являются косвенными производными TWinControl.
6.7.2.3 Создание графических компонент
Оригинальные оконные и графические компоненты очень сходны. Однако в отличие от производных TCustomControl, графические компоненты лишены оконного дескриптора и не могут принять фокус ввода. Windows "не узнает" графические компоненты, поэтому их применение не приводит к дополнительным накладным расходам на передачу дескриптора. Графические компоненты обеспечивают отображение объектов без использования системных ресурсов.
Вы должны производить новые графические компоненты от базового абстрактного класса TGraphicControl (который, в свою очередь, является потомком TControl). TGraphicControl предоставляет выбор канвы для рисования и обрабатывает сообщения WM_PAINT. Все, что вам следует сделать, это переопределить метод рисования Paint в соответствии заданными требованиями.
6.7.2.4 Создание невидимых компонент
Мы знаем, что все без исключения компоненты имеют общего прародителя абстрактный класс TComponent. Однако, только невидимые компоненты можно наследовать непосредственно от TComponent.
Любая производная от TComponent наследует все встроенные в нее свойства и методы. Невидимые компоненты встречаются довольно редко и используются, главным образом, в качестве интерфейсных элементов с другими компонентами (доступа к базам данных или как держатели диалоговых окон.
6.7.3 Добавление свойств, событий и методов.
Свойства представляют главную отличительную черту компонент, главным образом потому, что пользователи могут видеть и манипулировать ее свойствами во время проектирования, немедленно наблюдая реакцию времени выполнения программы. Мы уже знаем, что свойства, в отличие от члена данных, сами не хранят данные, однако методы чтения и записи организуют к ним доступ. Помните об этом, когда решите создать или изменить компонентные свойства.
События играют чрезвычайно ответственную роль в поведении компонент, Событие - это связь между некоторым происшествием в системе (таким как воздействие пользователя на компоненту или изменение фокуса) и кодом-обработчиком события, который реагирует на это происшествие. Обработчик события почти всегда пишется прикладным программистом, ибо только он знает, какой должна быть реакция на данное событие. Используя события, прикладной программист может приспособить поведение компонент к своим требованиям, без необходимости изменения самих объектов. Предоставить прикладному программисту такую возможность в отношении новой компоненты и является задачей разработчика компоненты. События, возникающие в результате наиболее типичных действий пользователя (например, движений мышью) встроены во все стандартные компоненты VCL, однако вы также можете определить новые события. Мы уже знаем, что C++Builder реализует события как свойства. Помните об этом, когда решите создать или изменить компонентные события.
Мы уже знаем, что компонентные методы ничем не отличаются от других объектных функций-членов. Хотя C++Builder не вводит никаких специальных ограничений на оформление компонентных методов, имеется ряд правил, которых стоит придерживаться. Помните об этом, когда решите создать или изменить компонентные методы.
6.7.4 Регистрация компоненты
Перед тем как ваша компонента сможет работать на стадии проектирования приложения, C++Builder должен зарегистрировать ее. При регистрации становится известным положение новой компоненты в Палитре компонент C++Builder.
Регистрация компоненты - это простой процесс, который информирует C++Builder о том, какая компонента добавляется к VCL и на какой вкладке Палитры она должна появиться.
Чтобы зарегистрировать компоненту:
1. Добавьте функцию Register к файлу МуСотр.срр, заключив ее имя в пространство имен (заметьте, что название пространства имен начинается с заглавной буквы, а все остальные буквы - прописные).
2. В теле функции Register объявите массив типа TComponentClass, в который введите регистрируемую компоненту.
3. В теле функции Register вызовите функцию RegisterComponents стремя параметрами: название вкладки Палитры компонент, массив компонентных классов и размер компонентных классов.
namespace Mycomp {
void _fastcall Register()
{
TComponentClass classes[1] = {_claesid(TMyComponent)};
RegisterComponents("Samples", classes, 0) ;
) }
Листинг 6.13. Регистрация компоненты.
Листинг 6.13 представляет включение в файл МуСотр.срр кода для регистрации компоненты TMyComponent на вкладке Samples Палитры компонент.
Когда компонента зарегистрирована, вы можете испытать и инсталлировать ее на Палитру, завершая тем самым процесс разработки новой компоненты.
6.7.5 Отладка неинсталлированной компоненты
Вам следует испытать поведение созданной компоненты при выполнении программы до ее инсталляция на Палитру. По существу, вам придется смоделировать те действия, которые производит C++Builder, когда пользователь перемещает компоненту из Палитры на форму. Этот процесс требует выполнения следующих шагов:
1. Включите файл модуля MyComp.h вашей компоненты в заголовочный файл некоторой формы.
2. Добавьте объектный член данных, представляющий испытываемую компоненту, в конец секции public объявлений класса формы, вне области объявлений, которые делает C++Builder.
3. Подсоедините обработчик к событию OnCreate формы.
4. Вызовите конструктор компонентного объекта (компонента отвечает за самоуничтожение, когда наступит время) из обработчика этого события, передав ему параметр, указывающий на владельца компоненты. Обычно параметром служит указатель this на объект, который содержит этот метод. В нашем примере параметр this ссылается на форму.
5. Сразу же за вызовом конструктора установите свойство Parent - родителя компоненты, обычно представляющего собой форму, группирующую рамку или панель инструментов. Обычно значением этого свойства является указатель this. Внимание: Если ваша компонента не является элементом управления, т.е. вы
не наследовали ее от TControl, пропустите этот шаг.
6. Инициируйте значения других свойств компоненты.
Предположим, вы собираетесь испытать компоненту TMyComponent в модуле с именем МуСотр. Создайте новый проект, а затем следуйте перечисленным шагам процесса подготовки модуля формы. Листинг 6.14 содержит законченный образец текста модуля формы отлаживаемой компоненты.
#ifndef TestFormH
#define TestFormH
//---_-_______-_-__-________-_-_._-_____________-_-___-_-_
ttinclude <Classes.hpp>
#include <Controls.hpp> ftinclude <StdCtrls.hpp>
#include <Forms.hpp>
#include "MyComp.h" // 1. //----—---——----—-----—-----—---—_--____-_----------
class TFormI : public Tform {
publ i shed:
private:
public:
TMyComponent* MyComponentI; // 2,
virtual _fastcall TFormI (TComponent* Owner); •// 3,
};
//----—--—---—------—-—-———---—--——-—------——--
extern TFormI *Forml;
//_--__--______----—-----—---------------------—--------
#endif
// Это файл MyComp.cpp модуля формы:
#include <vcl.h>
#pragma hdrstop
#include "TestForm.h" //——-----——-——--—------—-—--——---—--—----—--—--
ftpragma resource "*.dfm"
TFormI *Forml;
//---------—--—-------------------—---—--------------—
_fastcall TFormI::TFormI(TComponent* Owner) : TForm(Owner)
{
MyComponentI = new TMyComponent(this); // 4. MyComponent->Parent = this; // 5. MyComponentl->Left = 12; // 6.
}
Листинг 6.14. Текст модуля формы новой компоненты.
6.7.6 Инсталляция компоненты на Палитру
Компонентные модули, написанные на C++, имеют расширение .срр, а компоненты, написанные на Объектном Паскале, имеют расширение .pas.
При инсталляции новой компоненты или при выполнении команды Component | Rebuild Library, Библиотека Визуальных Компонент перестраивается, и C++Builder создает временный файл CMPLIB32.CPP исходных текстов VCL. Чтобы сохранить этот файл, с помощью команды Options | Environment | Library откройте диалог опций и установите флаг Save Library Source Code.
Чтобы добавить к VCL компоненту, выполните следующие шаги:
1. С помощью команды Component | Install откройте диалоговое окно инсталляции компонент.
2. Нажмите кнопку Add, которая открывает диалог добавления модуля. Введите имя модуля непосредственно в поле Module Name или найдите его местоположение, нажав на кнопку поиска Browse. Имя добавленного вами компонентного модуля появится внизу списка названий группы Installed Components. В списке Component classes вы увидите имена компонентных классов, уже находящихся в выбранной группе Библиотеки. У вновь введенного модуля имя компонентного класса отсутствует.
3. Нажмите кнопку ОК, закрывая диалог инсталляции компонент. Библиотека будет перестроена и новая компонента установлена на ту вкладку Палитры, которую вы определили как параметр функции регистрации (см. Листинг 6.13).
Чтобы удалить компоненту из VCL, выполните следующие шаги:
1. Выполните команду Component | Install, которая открывает диалоговое окно установки компонент.
2. Найдите ненужный вам более компонентный класс в списке Component classes выбранной группы Библиотеки и нажмите кнопку Remove.
3. Нажмите кнопку ОК. Библиотека будет перестроена и новая компонента удалена из Палитры.
Чтобы перестроить Палитру, выполните следующие шаги:
1. Откройте диалог установки опций Палитры с помощью команд Component | Configure Palette или Options | Environment | Palette.
2. Нажмите кнопку Add и выберите имя для новой вкладки. Имя добавленной вами вкладки появится внизу списка Pages названий вкладок.
3. Перетащите мышью выбранную компоненту в списке Components на нужную вкладку списка Pages.
4. Нажмите кнопку ОК. Библиотека и Палитра будут перестроены.
6.7.7 Сохранение файлов новой компоненты
Когда вы закончите процесс разработки, созданная компонента будет представлена следующими файлами:
• объектный файл результата компиляции MyComp.obj;
• заголовочный файл объявлений, сгенерированный компилятором (MyComp.h для исходного текста на C++ или MyComp.hpp для исходного текста на Объектном Паскале);
• файл битового образа Палитры (MyComp.res или MyComp.dcr);
• файл формы MyComp.dfm, если компонента использует форму.
Перед тем, как использовать вновь созданную компоненту, перепишите в каталог \.. .\CBuilder\LIB\OBJ следующие файлы компоненты МуСотр: все двоичные файлы (с расширениями .dfm, .res и .dcr), все исходные файлы (с расширениями .срр или .pas), все объектные файлы (с расширениями .obj и .Но) и все заголовочные файлы (с расширениями .h или .hpp). Желательно создать и сохранить контекстно-справочный файл (с расширением .hip).
6.8 Разработка простой компоненты
Перед тем, как приступить к разработке новой компоненты, вы должны заранее четко представить себе, что точно она должна делать и как будет реализовано ее оригинальное поведение. Удостоверьтесь, что ни одна из имеющихся компонент не обладает требуемыми вами способностями. Поскольку в cтaндapтнoм варианте поставки C++Builder отсутствует Инструкция по написанию компонент и исходные тексты VCL, пришлось заимствовать элементарное руководство Криса Эриксона, которое я скачал по сети Internet. Его компонента моделирует бинарный индикатор, который меняет цвет при изменении состоянии. Пока очевидно только, что некоторое свойство компоненты будет хранить текущее состояние (true, если индикатор включен, и false - в противном случае).
Из данной главы мы уяснили, что желательно выбрать для наследования наиболее близкий в иерархии VCL базовый компонентный класс; очевидно, что индикатор представляет собой графическую компоненту семейства TGraphicControl. Поскольку мы разрабатываем "ну очень простую" компоненту, пусть она будет иметь тривиальную форму окружности, а не более хитроумный битовый образ. На первый взгляд, компонента TShape из вкладки Палитры Additional выглядит ближайшим родственником. При внимательном рассмотрении TShape имеет больше свойств и событий, чем нам требуется, хотя и обладает всей нужной функциональностью.
Внимание: Вы не сможете удалить свойства или события из базовой компоненты, а только подняться выше по иерархии объектов на пути к общему предку TComponent. Это означает, что выбрав слишком далекого предшественника, вы рискуете запутать потенциального пользователя (да и самого себя тоже) обилием избыточных свойств и событий, которые по сути не нужны вашей компоненте. Напротив, выбрав слишком близкого предшественника, вам придется самостоятельно и полностью программировать функциональное поведение своей компоненты.
Итак, все что мы хотим изменить при наследовании от TShape - это форму; индикатора и цвет кисти при его переключении. Звучит достаточно просто. По";
еле того, как мы точно сформулировали, что будет делать создаваемая компонента, настало время перейти к ее фактической разработке.
6.8.1 Форма тестового приложения
Пока мы не убедились, что разрабатываемая компонента работает надлежащим образом (а это может занять много времени даже с простейшим элементом управления), ее нельзя включать в VCL. Сначала следует создать тестовое приложение с прототипом новой компоненты LED:
=> С помощью команды File | New Application создайте пустую форму. => Разместите кнопку TButton на форме.
=> С помощью команды File | Save All сохраните форму и проект приложения в файлах под именами LEDForm.cpp и LEDProj.mak.
6.8.2 Модуль тестового приложения
Мастер компонент (Component Wizard) упрощает начальные шаги создания компоненты. Мастер не может автоматически добавлять компоненту к существующему модулю, вам придется проделать это вручную.
=> Выполните команду Component | New и в открывшемся диалоге Мастера компонент заполните поля диалога указанными значениями (Рис. 6.9). Нажмите кнопку ОК.
Рис. 6.9. Диалог Мастера компонент.
=> С помощью команды File | Save или File | Save As сохраните файл Unitl.cpp под именем LED.cpp.
Теперь можно посмотреть в окне Редактора кода, что сделал C++Builder для подготовки нашей компоненты. Файл LED.h будет содержать объявление нового компонентного класса с конструктором, а также несколько заголовочных файлов предкомпиляции. Файл LED.cpp будет содержать пустой конструктор объекта и функцию Register для регистрации компоненты. Не слишком много для автоматизированного начала...
6.8.3 Члены данных, свойства и методы
Ознакомившись с заготовками программного модуля компоненты, которые создал для нас C++Builder, можно приступить к написанию собственно кода компоненты. Прежде всего, в файле LED.h опишем булеву переменную состояния индикатора и две переменные перечисляемого типа TColor для хранения цветов, отображающих оба состояния. Из главы 3 об основах объектно-ориентированного программирования мы знаем как ограничивать область видимости и уяснили, что эти члены данных следует спрятать в секции private объявлений класса. Там же расположим прототипы функций записи соответствующих свойств, а сами свойства объявим в секции _published (Листинг 6.15).
//---------------________________________________-_____-_--
#ifndef LEDH
#define LEDH //------------_____-_____--_________________________-___--.
#include <vcl\SysUtils.hpp>
#include <vcl\Controls.hpp>
#include <vcl\Classes.hpp>
#include <vcl\Forms.hpp> ^include <vcl\ExtCtrls.hpp> //____----------__________________________________------—
class TLED : public TShape { private:
bool FOnOff; TColor FOnColor; TColor FOffColor;
void _fastcall SetOnOff(const bool Value) ;
void _fastcall ' SetOnColor
void _fastcall SetOffColor (conet TColor OffColor) ;
protected:
public:
_fastcall TLED(TComponent* Owners-published:
property bool LEDOn= { read= FOnOff,write= SetOnOff) ; property ТСоlог OnColor= { read=FOnColor,write=SetOnColor} ; :.,. __property TColor OffColor=
{ read=FOff Color, write= SetOffColor} ;
};
//_____________-___-_----____________------------------——
#endif
Листинг 6.15. Добавления в файл LED.h модуля компоненты.
Еще проще окажутся добавления в файл LED.cpp (Листинг 6.16). Необходимо написать три функции для присвоения значений свойств соответствующим членам данных, а также наполнить конструктор компоненты инструкциями для инициализации переменных.
#include <vcl\vcl.h>
#pragma hdrstop
# include "LED.h" //_-------—----—-____--------—---.————---—---—----—--
static inline TLED *ValidCtrCheck() {
return new TLED(MULL) ;
)
void _fastcall TLED::SetOnOf?(const bool Value) {
POnOff = Value;
Brush->Color ” (POnOff) ? FOnColor : POffColor?
}
//_———————————————-——-^-.-——.————.——————
void _fastcall TLED::SetOnColor(const TColor OnColor) {
POnColor = OnColor;
Brush->Color = (FOnOff) ? FOnColor : FOffColor;
}
//-—---——-„--—--------————----------——--———---—--
void _fastcall TLED::SetOЈfColor(const TColor OffColor) {
FOffColor = OffColor;
Brush->Color = (FOnOff) ? FOnColor : FOffColor;
}
//---_------------------------------------------------------
_fastcall TLED::TLED(TComponent* Owner) : TShape(Owner)
{
Width = 15; // тирина по умолчанию Height = 15; // высота по умолчанию FOnColor = cILime; // зеленый, когда включен FOffColor = cIRed; // красный, когда выключен FOnOff = false; // выключен по умолчанию Shape ? atEllipse; //в форме эллипса по умсвдчани Pen->Color = clBlack; // черный контур по умолчанию Pen->Width = 2; // ширина контура по уйолчанию Brush->Color ^ FOffColor; // цвет заливки по умолчанию
}
//---------------____-______________________--________.
namespace Led
{
void _fastcall Register () {
TComponentClass classes[1] = {__classid(TLED)} ;
RegisterComponents("Samples", classes, 0) ;
} }
Листинг 6.16. Добавления в файл LED.cpp модуля компоненты.
Установленные конструктором значения членов данных по умолчанию па вятся в окне Инспектора объектов при создании объекта индикатора. Дейстительно, при помещении компоненты на форму конструктор вызывается автоматически. В результате появляется возможность менять значения свойств компоненты не только во время выполнения программы, но и на стадии проектирования приложения. *
6.8.4 Испытание компоненты
Теперь, когда мы закончили с написанием текста модуля компоненты, проверим работает ли она.
=> С помощью команды File | Save All сохраните все сделанные добавления. => Выбрав вкладку LEDForm.cpp в окне Редактора кода, по команд File | Include Unite Hdr включите строку #include "LED. h" в заголовок файла формы. По неведомой причине, эта команда не работает с файлом LEDForm.h, поэтому строку #include "LED.h" приходится вставлять вручную. К секции private этого же файла добавьте описание объекта индикатора:
private: // User declarations TLED* LED1;
=> Активизируйте форму Formi и в окне Инспектора объектов дважды щелкните мышью в графе значений события OnCreate. С помощью Редактора кода введите обработчик этого события в файл LEDForm.cpp. Следующий код создаст компоненту TLED динамически (определяя ее родителя Parent и помещая в в центре родительской формы) во время выполнения тестового приложения:
void_fastcall TFormI::FormCreate(TObject *Sender)
t
LED1 = new TLED(this);
LED1->Parent = this;
// Центрировать компоненту по ширине формы LEDl->Left = (Width/2)-(LEDl->Width/2);
// Центрировать компоненту по высоте формы LEDl->Top = (Height/2)-(LEDl->Height/2);
}
Чтобы кнопка управляла индикатором, дважды щелкните мышью в графе значений события OnClick объекта Buttoni в окне Инспектора объектов. С помощью Редактора кода введите следующую инструкцию в тело обработчика события:
void_fasfccall TPormI::ButtonlClick(TObject *Sender) (
LEDl->LEDOn = !LEDl->LEDOn;
}
=> Наконец, скомпилируйте и запустите тестовое приложение посредством команды Run | Run.
Если компилятор не выдаст ошибок (а их не должно быть, если вы точно следовали изложенной процедуре), то посередине формы тестового приложения вы увидите красный индикатор в состоянии "выключен". Нажав кнопку, вы включите индикатор и он окрасится зеленым цветом (Рис. 6.10).
Теперь осталось создать битовый образ пиктограммы, которой новая компонента будет представлена в Палитре. Из меню редактора изображений, открывающегося по команде Tools | Image Editor, выберите File | New | Resource File, a затем - Resource | New) Bitmap.
В диалоге свойств битового образа установите размеры пиктограммы 24х24 и число цветов VGA (16 Colors). Переименуйте битовый образ компоненты (TLED) по команде Resourse | Rename и дважды щелкните мышью на выбранном имени в древовидном списке ресурсных файлов, чтобы нарисовать подходящую картинку индикатора (например, зеленый кружок). Командой File | Save As сохраните ресурсный файл LED.res в своем рабочем каталоге и закройте Редактор изображений.
Рис. 6.10. Динамическое создание компоненты индикатора.
6.8.5 Инсталляция компоненты
Перед тем, как приступить к инсталляции новой компоненты на Палитру, выполните последний раз команду File | Save All.
=> С помощью команды Component | Install откройте диалоговое окно инсталляции компонент. Нажмите кнопку Add, которая открывает диалог добавления модуля. Найдите местоположение модуля LED.cpp, нажав на кнопку поиска Browse. Нажмите кнопку ОК и приготовьтесь ждать окончания перестройки VCL и установки новой компоненты на Палитру.
=> Выполните команду File | Close All, а затем File | New Application. Поместите новую компоненту LED и кнопку TButton на форму. Снова определите обработчик события OnClick кнопки управления индикатором:
void_fastcall TFormI::ButtonlClick(TObject *Sender) {
LEDl->LEDOn = lLEDl->LEDOn;
}
=> Выполните команду Run | Run и вы увидите, что компонента действительно работает.
Порадуйтесь тому, как просто все оказалось на деле, сохраните на всякий случай все рабочие файлы (Borland рекомендует использовать каталог \.. ACBuilder\LIB\OBJ) и приступайте к планированию вашей следующей компоненты.
6.9 Итоги
Планируете ли вы использовать Библиотеку Визуальных Компонент при создании прикладного программного обеспечения, или развивать существующую Библиотеку при разработке новых компонент — глубокие знания состава и уст роиства VCL будут способствовать успешному решению поставленных задач