#include <vcl.h>
#pragma hdrstop
#include "Code.h"
#define MAIN_PAGE
"bcdev.narod.ru"
#define E_MAIL
yerm@mail.ru
////////////////////////////////////////////////////////
// Это не FAQ (часто задаваемые вопросы) и CAQ (обычно
// задаваемые вопросы). Скорее всего это
можно
// охарактеризовать как коллекцию
фрагментов кода из
// реально работающих программ. Очень
часто, разрабатывая
// новый проект, сталкиваешься с ситуацией,
когда вдруг
// понимаешь, что подобная задача уже была
однажды решена
// тобой. К сожалению, найти предыдущее
решение бывает
// не всегда легко. А в случае смены места
работы и вовсе
// невозможно. Поэтому я решил создать эту
коллекцию и
// обнародовать ее в Инете. Там иногда найти
легче, чем
// на своем компьютере :). К тому же, может
быть, это
// будет представлять интерес не только для
меня.
// Фрагменты снабжены комментариями, поясняющими
суть
// решаемой проблемы.
////////////////////////////////////////////////////////
//
// 1.Сохранение-чтение
неопубликованного свойства типа enum
// 2.Присвоить глобальную
функцию событию
// 3.Вызвать метод класса как
функцию (по "обычному" адресу)
// 4.Вызвать опубликованный
метод класса по имени
// 5.Получить имя
опубликованного метода
// 6.Подмена Caption для
комплексной строки в компоненте
// TdxDBInspector (DevExpress)
// 7.Перенаправление
на актуальный DataModule
// 8.Вызов защищенного
виртуального метода
// 9.Создание
компонента по его метаклассу:
// 10.Динамическое добавление Action
в ActionManager и ActionMainMenuBar
// 11.Исправление
ошибки в примере
Examples\ShellControls
// 12.Получение
интерфейса текущего проекта (Tool API)
// 13.Получение
опций текущего проекта (Tool API)
////////////////////////////////////////////////////////
// Здесь надо
обратить внимание, каким образом
определяется
//символьный эквивалент значения
переменной типа enum.
//Основное требование, при котором данный
код сработает,
//заключается в том, чтобы этот тип enum был
зарегистрирован
//в RTTI, т.е. хоть раз был использован в
качестве типа для
//опубликованного свойства. В данном случае
речь идет о типе
//TWindowState, использовался как тип для published
свойства
//WindowState в TForm. Надо заметить, что если для
получения
//информации о типе использовать TCustomForm,
функция GetPropInfo
//либо выдаст exception (C++Builder 5), либо NULL(C++Builder 6),
//т.к. в TCustomForm это свойство объявлено лишь
как public
//-----------------------------------------------------
void __fastcall TDataWrapper::DefineProperties(TFiler* Filer)
{
inherited::DefineProperties(Filer);
//определяем,
какие свойства будут сохраняться и функции
//это
будут выполнять
Filer->DefineProperty("FormState",ReadFormState, WriteFormState, true);
}
//-----------------------------------------------------
void __fastcall
TDataWrapper::ReadFormState(TReader* Reader)
{
TEntryPointForm* Form = (TEntryPointForm*)Owner;
//получение информации о
свойстве, имеющего тип TWindowState
Typinfo::PTypeInfo pTypeInfo =
*(Typinfo::GetPropInfo(__typeinfo(TForm),"WindowState"))->PropType;
//чтение значения свойства в
символьном виде
AnsiString strEnumValue = Reader->ReadIdent();
//перевод символьного вида в
значение типа enum
TWindowState state = (TWindowState)GetEnumValue(pTypeInfo,strEnumValue);
if(Form->SettingsClient->FStoredParams.Contains(spState))
Form->FWindowState = state;
}
//------------------------------------------------------
void __fastcall TDataWrapper::WriteFormState(TWriter* Writer)
{
TEntryPointForm* Form = (TEntryPointForm*)Owner;
//получение информации о
свойстве, имеющего тип TWindowState
Typinfo::PTypeInfo pTypeInfo =
*(Typinfo::GetPropInfo(__typeinfo(TForm),"WindowState"))->PropType;
//запись значения enum в
символьном виде
Writer->WriteIdent(GetEnumName(pTypeInfo,Form->FWindowState));
}
////////////////////////////////////////////////////////
// Ниже приведен код,
иллюстрирующий работу с адресами
//методов класса
// Присвоить глобальную
функцию событию
// Вызвать метод класса как
функцию (по "обычному" адресу)
// Вызвать опубликованный
метод класса по имени
// Получить имя
опубликованного метода
//-----------------------------------------------------
class TForm1 :
public TForm
{
__published: // IDE-managed Components
TButton*
Button1;
TButton* Button2;
void __fastcall Button1Click(TObject
*Sender)
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//-----------------------------------------------------
//через первый параметр будет перадаваться
this
void __fastcall GlobalClick(void* This, TObject *Sender)
{
ShowMessage(AnsiString("Global:")+
((TComponent*)This)->Name + "->" +
((TComponent*)Sender)->Name);
}
//------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
//присвоить глобальную функцию событию
TMethod Method;
Method.Data = this;
Method.Code= GlobalClick;
Button2->OnClick = *(TNotifyEvent*)&Method;
//вызвать метод
по обычную адресу
TNotifyEvent Click = &Button1Click;
TMethod Method1 = *(TMethod*)&Click;
//через первый скрытый параметр передаем this
typedef void (__fastcall *Func)(void*,TObject *);
Func func;
func = (Func)Method1.Code;
func(this, Button1);
//вызвать опубликованный метод по имени
ShortString ProcName = "Button1Click";
TMethod Method2;
Method2.Code = MethodAddress(ProcName);
if (Method2.Code)
{
Method2.Data = this;
TNotifyEvent Click = *(TNotifyEvent*)&Method2;
Click(Button1);
}
//получить имя обработчика события
TMethod Method3 = *(TMethod*)&(Button1->OnClick);
ShortString ProcName1 = MethodName(Method3.Code);
ShowMessage( ProcName1);
}
//------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage(AnsiString("Method:") +
this->Name + "->" + ((TComponent*)Sender)->Name);
}
//------------------------------------------------------
////////////////////////////////////////////////////////
//Пример, как простенькая
задача может превратиться в
//головную боль. Требовалось всего на всего
подменить
//стандартный Caption, который набирается из
Caption строк,
//входящих в комплексную строку, на свой. Для
этого фирма
//DevExpress предлагает воспользоваться событием
OnDrawCaption,
//в котором нужно задать требуемый текст и
указать, что
//дальнейшая отработка не требуется
void __fastcall
TAttrValueSetFrame::InspectorDrawCaption(
TdxInspectorRow *Sender, TCanvas *ACanvas, const TRect &ARect,
AnsiString &AText, TFont *AFont, TColor &AColor, bool &ADone)
{
AText = "Мой
собственный Caption";
ADone = true;
}
//Все
работает замечательно, пока эта строка не
является
//подстрокой. В этом случае справа от Caption
резервируется
//область под кнопку, отрисовка которой
происходит после
//события OnDrawCaption. А поскольку установив ADone
в true,
//мы указали,
что дальнейшей отрисовки не требуется,
вместо
//кнопки появлялся всякий мусор. Если же
установить ADone=true,
//кнопка отрисовывалась нормально, но
вместо нужного Caption
//выводился стандартный. Переписка с DevExpress
ничего не дала.
//Они предлагали отрисовывать кнопку прямо
в этом событии.
//Это приводило довольно к объемному коду,
поскольку нужно было
//анализировать нужна кнопка или нет и вид
кнопки в зависимости
//от стиля. Мной найден был более простой
способ
void __fastcall TAttrValueSetFrame::InspectorDrawCaption(
TdxInspectorRow *Sender, TCanvas *ACanvas, const TRect &ARect,
AnsiString &AText, TFont *AFont, TColor &AColor, bool &ADone)
{
class TdxInspectorAccess:public Dxinspct::TdxInspector
{
public:
__property Indent;
};
TdxInspectorComplexRow* ComplexRow = dynamic_cast<TdxInspectorComplexRow*>(Sender);
if (ComplexRow)
{
TRect TextRec = ARect;
TextRec.Left = ((TdxInspectorAccess*)Sender->Inspector)->Indent+1;
ACanvas->Brush->Color = AColor;
ACanvas->TextRect(TextRec, ARect.Left + 1, TextRec.Top + 1,
"Мой
собственный Caption");
//Изюминка
здесь. Дальнейшая обработка не прерывается
//Просто стандартный Caption будет
отрисовываться
//в области с нулевой шириной.
TextRec.Right = TextRec.Left;
//А здесь
снимаем защиту разработчиков, которые
//запретили изменение ARect
const_cast<TRect&>(ARect) =
TextRec;
//и разрешаем
дальнейшую отрисовку.
ADone = false;
}
//Надо
сказать, что компоненты DevExpress хороши, когда
их
//используешь как есть. Но если требуется
что-то неординарное
//возникает куча проблем из-за
недостаточной продуманности
//их структуры.
////////////////////////////////////////////////////////
//Решение проблемы, которая
возникает как правило
//при использовании пары TForm - TDataModule. Суть
проблемы
//состоит в следующем: в приложении
динамически создаются
//экземпляр TForm1 и TDataModule1. При этом Data Contols
формы
//ссылаются TDataSource модуля. Если эти ссылки
присваивать в
//в дизайне, то при создании вторых
экземпляров формы и модуля
//в приложении в рантайме, котролы второго
экземпляра формы будут
//ссылаться на TDataSource's первого экземпляра
модуля. Для того,
//что бы перенаправить их на нужный модуль
можно использовать
//следующую универсальную функцию, которая
должна вызываться или
//после создания формы или непосредственно
в конструкторе
void __fastcall TEntryPointForm::Redirect(TComponent *Root,
TDataModule *DataModule)
{
Typinfo::TTypeKinds SupportKinds;
SupportKinds << tkClass;
for(int i = 0; i < Root->ComponentCount; ++i)
{
TComponent* Component = Root->Components[i];
Typinfo::TPropList pList;
int nPropCount = GetPropList((Typinfo::PTypeInfo)(Component->ClassInfo()),
SupportKinds,((Typinfo::PPropList)(&pList)));
for( int j = 0; j < nPropCount; j++)
if (__classid(TDataSource) == GetObjectPropClass(Component, pList[j]->Name))
{
TDataSource* Source = dynamic_cast<TDataSource*>
(GetObjectProp(Component, pList[j], __classid(TDataSource)));
if (Source)
{
TDataSource* Destination =
dynamic_cast<TDataSource*>(DataModule->FindComponent(Source->Name));
if (Destination)
SetObjectProp(Component, pList[j], Destination);
}
}
if (Component->ComponentCount)
Redirect(Component, DataModule);
}
}
//А теперь использование
TDataModule1*
Module1 = new TDataModule(Application);
TForm1* Form1 = new TForm1(Application);
Form1->Redirect(Form1,Module1);
////////////////////////////////////////////////////////
// Ответ на вопрос, заданный на
одном из форумов:
//"Как вызвать у компонента TStringGrid
защищенный
// виртуальный метод DrawCell?". Не вдаваясь в
обсуждение
// целесообразности вызова DrawCell просто
привожу код
// как это можно сделать
void __fastcall TForm1::Button1Click(TObject *Sender)
{
class TPublicGrid:public TStringGrid
{
public:
void __fastcall
DrawCell(int ACol, int ARow,
const Windows::TRect &ARect, Grids::TGridDrawState AState){}
};
((TPublicGrid*)StringGrid1)->DrawCell(1,1, TRect(), TGridDrawState());
}
////////////////////////////////////////////////////////
// Создание компонента по его
метаклассу:
// (Из форума RSDN)
TComponent *CreateComponent(
TComponentClass CClass, TComponent *AOwner)
{
typedef TComponent *( __fastcall *TConstructor)(
TComponentClass, bool, TComponent *);
TConstructor constructor = ((TConstructor *) CClass)[ vmtCreateObject -
1];
return constructor( CClass, true, AOwner);
};
////////////////////////////////////////////////////////
// Динамическое создание Action,
ActionManager
// и ActionMainMenuBar и добавление
Action в ActionManager
// и ActionMainMenuBar
TActionManager* ActionManager = new TActionManager(this);
//создание
первого Action
TAction* Action = new TAction(this);
Action->Name = "TestAction1";
Action->Category = "Main";
Action->OnExecute = Action1Execute;
//добавление в ActionManager
Action->ActionList = ActionManager;
//создание
второго Action
void __fastcall TCustomShellListView::RootChanged(void)
////////////////////////////////////////////////////////
_di_IOTAProject __fastcall TPluginModuleEditor::GetProject()
////////////////////////////////////////////////////////
Variant __fastcall TPluginModuleEditor::GetOptions(_di_IOTAProject
a_Project,
Action = new TAction(this);
Action->Name = "TestAction2";
Action->Category = "Main";
Action->OnExecute = Action2Execute;
//добавление в ActionManager
Action->ActionList = ActionManager;
MenuBar->Parent = this;
public:
__property Items;
using TCustomActionBar::CreateControl;
};
Item->Caption = "Main";
for( int i= 0; i < ActionManager->ActionCount; i++)
if ( ((TAction*) ActionManager->Actions[i])->Category == "Main"
)
Item->Items->Add()->Action = ActionManager->Actions[i];
// Исправления, которые необходимо
внести в ShellCtrls.cpp
// для того, чтобы компонент TShellListView
нормально работал
// (см.
\Examples\ShellControls)
{
bool StayFresh;
if(FUpdating)
return;
FUpdating = true;
try
{
StayFresh = FAutoRefresh;
//AutoRefresh = false;-ошибка,
исправлено на
FAutoRefresh = false;
SynchPaths();
Populate();
if(ViewStyle == vsReport)
EnumColumns();
//AutoRefresh = StayFresh;-ошибка,
исправлено на
FAutoRefresh = StayFresh;
}
__finally
{
FUpdating = false;
}
}
//
Получение интерфейса текущего проекта (Tool API)
//
{
_di_IOTAModuleServices svc;
BorlandIDEServices->Supports(svc);
_di_IOTAProject result = 0;
for (int i = 0; i < svc->ModuleCount; ++i)
{
_di_IOTAModule module = svc->Modules[i];
_di_IOTAProjectGroup group;
if (module->Supports(group)) {
result = group->ActiveProject;
break;
}
return result;
}
// Получение
опций текущего проекта (Tool API)
//
const AnsiString& a_Value)
{
_di_IOTAProjectOptions Options = a_Project->GetProjectOptions();
return Options->Values[a_Value];
}
...
//Получение
финального пути
AnsiString OutputDir = GetOptions(a_Project,
"OutputDir");
//Получение
версии
int MajorVersion = GetOptions(a_Project,"MajorVersion");
int MinorVersion = GetOptions(a_Project,"MinorVersion");
int Release
= GetOptions(a_Project,"Release");
int Build
= GetOptions(a_Project,"Build");