Формы
и модули.
Часть 1.Собственные свойства и события.
Вячеслав
Ермолаев. http://bcdev.narod.ru
e-mail:yerm@mail.ru
Одним из наиболее мощных и
распространенных приемов повторного
использования функциональности в С++
является механизм наследования классов. И
поскольку в качестве языка
программирования в С++Builder используется
язык С++, механизмы наследования применимы и
в этой среде. В статье мы разберем некоторые
аспекты использования механизма
наследования применительно к таким
основополагающим классам VCL, как TForm и TDataModule.
Вообще-то создание дочернего
класса на основе базового - задача
достаточно тривиальная для программиста,
использующего С++. Проблема заключается в
том, что одним из основных преимуществ
среды разработки С++Builder и ей подобных
является использование парадигмы
визуального программирования.
Естественно, что при создании дочернего
класса, возникает желание не потерять
это преимущество. Для того, чтобы сохранить
возможность использования удобств
визуального программирования и в то же
время использовать мощь наследования при
работе с формами и модулями, разработчики
от Borland предлагают использовать Object Repository. В
пределах одного проекта достаточно открыть
Object Repository, выбрать нужную форму, которая
будет базовой н нажать кнопку OK. IDE
автоматически сгенерит производную форму,
cpp и h файл. При этом все возможности
визуального программирования полностью
сохраняются. Процесс становится немногим
более сложнее, если необходимо создать
производную форму в другом проекте. В этом
случае первоначально нужно вставить
базовую форму в репозитарий с помощью
всплывающего меню . После этого форма
становится доступной для вставки в любые
проекты, причем в трех режимах:
Copy - в проект вставляется
копия базовой формы,
Inherited - в проект вставляется
форма, производная от базовой,
Use - в проект вставляется сама
форма, причем все изменения сделанные в
этой форме автоматически
спроецируются на все проекты, где имеются
производные формы.
К сожалению описанный выше
подход не лишен недостатков. Первый - в
проект в любом случае вставляются исходные
тексты. Т.е. аналогичного результата можно
было бы добиться посредством банального
копирования файлов cpp, h, dfm в директорию
проекта и добавления в проект файла cpp. Если
при этом проектируемое приложение будет
состоять из хостового приложения и набора
dll, то исходный код прилинкуется во все dll, в
которых будет использована форма. Минусы
такой линковки очевидны. Второй недостаток
- вновь созданные опубликованные свойства и
события не появятся в инспекторе
производной формы. Это не только создает
определенные неудобства, но и делает
невозможным реализацию ряда алгоритмов,
в которых код выполняемый в конструкторе,
зависит от значения свойства. Для
наглядности сформулируем простейшую
задачу: требуется создать форму TStorageForm,
которая будет иметь возможность сохранять
некоторые свойства, заданные
программистом при своем уничтожении о и
восстанавливать их при создании.
Конкретный механизм восстановления-сохранения
реализуется программистом в событиях OnRestore
и OnSave. Программист также в дизайне и в
рантайме (последнее имеет смысл сохранения)
может управлять необходимостью
выполнения этих событий при помощи
свойств AutoRestore и AutoSave. В результате
решения задачи мы должны получить
возможность создавать в дизайн-режиме
формы, которые являются производными от TStorageForm
и ,в отличие от стандартной, в своем
инспекторе имеют два дополнительных
свойства AutoRestore и AutoSave, и два
дополнительных события OnRestore и OnSave. Итерфейсная
часть и реализация приведена ниже TStorageForm
//----------------------------------------------------------
#ifndef StorageFormH
#define StorageFormH
//----------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------
class PACKAGE TStorageForm :
public TForm
{
private:
bool FAutoRestore;
bool FAutoSave;
TNotifyEvent FOnRestore;
TNotifyEvent FOnSave;
void __fastcall SetOnRestore(TNotifyEvent value);
TNotifyEvent __fastcall GetOnRestore();
void __fastcall SetOnSave(TNotifyEvent value);
TNotifyEvent __fastcall GetOnSave();
public: // User declarations
__fastcall TStorageForm(TComponent* Owner);
__fastcall ~TStorageForm(void);
__published:
__property bool AutoRestore = { read=FAutoRestore,
write=FAutoRestore };
__property bool AutoSave = { read=FAutoSave, write=FAutoSave };
__property TNotifyEvent OnRestore = { read=GetOnRestore,
write=SetOnRestore };
__property TNotifyEvent OnSave = { read=GetOnSave,
write=SetOnSave };
protected:
void __fastcall DoRestore(void);
void __fastcall DoSave(void);
};
//------------------------------------------------------------
#endif
#include <vcl.h>
#pragma hdrstop
#include "StorageForm.h"
//------------------------------------------------------------
#pragma package(smart_init)
//------------------------------------------------------------
static inline void ValidCtrCheck(TStorageForm *)
{
new TStorageForm(NULL);
}
//------------------------------------------------------------
__fastcall TStorageForm::TStorageForm(TComponent* Owner)
: TForm(Owner)
{
if (!ComponentState.Contains(csDesigning) &&
FAutoRestore)
DoRestore();
}
//------------------------------------------------------------
void __fastcall TStorageForm::SetOnRestore(TNotifyEvent value)
{
if(FOnRestore != value) FOnRestore = value;
}
//------------------------------------------------------------
TNotifyEvent __fastcall TStorageForm::GetOnRestore()
{
return FOnRestore;
}
//------------------------------------------------------------
void __fastcall TStorageForm::SetOnSave(TNotifyEvent value)
{
if(FOnSave != value) FOnSave = value;
}
//------------------------------------------------------------
TNotifyEvent __fastcall TStorageForm::GetOnSave()
{
return FOnSave;
}
//------------------------------------------------------------
void __fastcall
TStorageForm::DoRestore(void)
{
if (FOnRestore) FOnRestore(this);
}
//------------------------------------------------------------
void __fastcall
TStorageForm::DoSave(void)
{
if (FOnSave) FOnSave(this);
}
//------------------------------------------------------------
__fastcall
TStorageForm::~TStorageForm(void)
{
if (!ComponentState.Contains(csDesigning) &&
FAutoSave)
DoSave();
}
//------------------------------------------------------------
Код достаточно прозрачен и комментировать его не имеет смысла. Стоит лишь отметить, что текст создавался программно и форма не имеет dfm. Для того, чтобы использовать его в своих программах, создадим проект Storage.bpk - проект runtime only package и добавим туда cpp-файл с выше указанным кодом ( StorageForm.cpp ). Откомпилируем проект и получим в свое распоряжение рантайм пакет, который мы можем использовать в своих программах, создавая производнные формы с унаследованным функционалом. Правда при этом пока эти формы невозможно использовать в дизайн режиме. Чтобы ликвидировать этот досадный недостаток создадим еще один проект dclStorage - designtime only package и добавим туда файл Register.cpp, в которой состоит практически из одной строчки кода, а именно вызова функции RegisterCustomModule, которая сопоставляет наш новый базовый класс TStorageForm с классом TCustomModule.
#include <vcl.h>
#pragma hdrstop
#include <dsgnintf.hpp>
#include "StorageForm.h"
#pragma package(smart_init)
//------------------------------------------------------------
namespace Register
{
void __fastcall PACKAGE Register()
{
RegisterCustomModule(__classid(TStorageForm),
__classid(TCustomModule));
}
}
//------------------------------------------------------------
Что-то более определенное по этому вопросу сказать трудно за практически полным отсутствием документации. В раздел Requires нашего проекта добавим Storage.bpi, откомпилируем и затем инсталлируем полученный package в IDE. Теперь мы готовы создавать свои формы от собственной базовой. Вопрос в только в том, как это реально сделать. Более профессиональным и правильным подходом для добавления в проект собственной формы является написание соответствующего эксперта, но в принципе можно попытаться обойтись и без него. При этом немного придется поработать ручками. Создадим любой проект с формой. Переименуем форму в BaseStorageForm, в h-файл внесем следующие изменения: добавим строку #include "StorageForm.h" и переправим базовую форму TForm на TStorageForm.
#ifndef BaseStorageFrmH
#define BaseStorageFrmH
//------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include "StorageForm.h" //Добавлено
//------------------------------------------------------------
class TBaseStorageForm : public TStorageForm
//public TForm
{
__published: // IDE-managed Components
private: // User declarations
public: // User declarations
__fastcall TBaseStorageForm(TComponent* Owner);
};
//------------------------------------------------------------
extern PACKAGE TBaseStorageForm *BaseStorageForm;
//------------------------------------------------------------
#endif
Не забудем внести соответствующие изменения и в cpp-файл:
#include <vcl.h>
#pragma hdrstop
#include "BaseStorageFrm.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TBaseStorageForm *BaseStorageForm;
//---------------------------------------------------------------------------
__fastcall TBaseStorageForm::TBaseStorageForm(TComponent* Owner)
: TStorageForm(Owner) //TForm(Owner)
{
}
В
заключение переименуем файл в BaseStorageFrm.cpp.
Теперь наша работа закончена. Однако, когда
мы откроем инспектор этой формы, то
ожидаемых изменений не увидим. К нашему
разочарованию все осталось по прежнему. А
теперь внимание: еще одна практически
незначащая операция, которую Вы, наверное,
проделывали не один раз. Просмотрим форму
как текст (View as Text) и, ничего не исправляя,
вернемся обратно (View as Form). И вот после
этой манипуляции в инспекторе
наконец-то появляются дополнительные
свойства и события. Чтобы не проделывать
эту операцию по модификации файлов каждый
раз, когда нам потребуется форма,
производная от TStorageForm , воспользуемся
любезностью разработчиков С++Builder и вставим
форму BaseStorageFrm в репозитарий (Object Repository):
всплывающее меню -> пункт Add to Repository.
Теперь она будет у нас всегда под рукой и
без всяких затруднений можем вставить ее в
любой проект, реализация этой формы скрыта
в bpl и в дизайн режиме в Object Inspector имеются так
необходимые нам события и свойства. Что и
требовалось получить.
Следующая статья из этой серии будет
посвящена написанию эксперта для TStorageForm
Примечание.
Вышеописанная методика была опробована на
С++Builder5. К сожалению, на С++Builder 6 аналогичного
результата получить не удалось. Будем
пробовать дальше. Код к этой статье можно
взять здесь. Если
возникли вопросы (а они возникнут
обязательно, т.к. имеются "подводные
камни", неупомянутые в этой статье), прошу
обращаться по e-mail:yerm@mail.ru.