Формы и модули. 
Часть 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.   

 

     

   

 

 

   



Сайт управляется системой uCoz