V tomto článku si ukážeme příklad nevizuální komponenty, která bude mít možnost rozšířit vlastnosti formuláře (TForm) a snadno zachytávat některé události, které standardní TForm nemá implementovány.
Na úvod jenom zdůrazním, že výsledek, který si zde můžete stáhnout, není hotová komponenta k okamžitému použití. Tyto články v žádném případě nejsou recenzemi komponent ke stažení, ale slouží těm, kteří se chtějí naučit komponenty vytvářet. Takže pokud použijete to, co se v tomto článku naučíte, nebude pro vás problém, abyste si staženou komponentu sami dotvořili do stavu, ve kterém by byla použitelná pro „širokou obec“ vizuálních vývojářů.
Ale pojďme již k dnešnímu tématu. „Dnešní“ komponentu jsem nazval FormEx, tedy něco jako „Form extender“. Po jejím umístění na formulář bude možno v ObjectInspectoru snadno nastavit některé další vlastnosti a události příslušného formuláře. Jako ukázku budeme implementovat následující:
- Property BackBitmap (typu Bitmap> umožňující definovat bitmapu tvořící pozadí formuláře.
- Property NoMinimize a NoMaximize umožňující zakázat minimalizaci popř. maximalizaci formuláře.
- Událost OnXButtonDown zachycující stlačení jednoho z dalších tlačítek myši (tzv. x-buttonů), které má např. myš Microsoft IntelliMouse. Jsou to tlačítka standardně sloužící k navigaci v průzkumníku „zpět“ a „vpřed“.
- Událost OnNCLButtonDown, zachycující stlačení levého tlačítka myši v neklientské oblasti okna, tj. například v titulkovém pruhu a okrajích
- Událost OnNCHitTest, zachycující všechny zprávy myši v neklientské oblasti
Nejdříve si nadefinujeme vlastní typy událostí, které použijeme pro výše zmíněné události:
class PACKAGE TRPJFormEx : public TComponent
{
protected:
typedef void __fastcall(__closure *TXButtonEvent)
(TObject* Sender, WORD Button, WORD VirtKeys, int xPos, int yPos);
typedef void __fastcall(__closure *TNCMouseEvent)
(TObject* Sender, UINT HitTest, int xPos, int yPos);
typedef void __fastcall(__closure *TNCHitTest)
(TObject* Sender, UINT HitTestValue, int x, int y);
private:
…..
Když máme připravené tyto vlastní typy, můžeme si vytvořit (snadno například pomocí ClassExploreru, jak jsme se již naučili v předcházejících dílech) následující publikované property a events:
__published:
__property Graphics::TBitmap* BackBitmap =
{ read=FBackBitmap, write=SetBackBitmap };
__property bool NoMinimize = { read=FNoMinimize, write=SetNoMinimize };
__property bool NoMaximize = { read=FNoMaximize, write=SetNoMaximize };
__property TXButtonEvent OnXButtonDown =
{ read=FOnXButtonDown, write=SetOnXButtonDown };
__property TNCMouseEvent OnNCLButtonDown =
{ read=FOnNCLButtonDown, write=SetOnNCLButtonDown };
__property TNCHitTest OnNCHitTest = { read=FOnNCHitTest, write=SetOnNCHitTest };
Nyní si můžeme nejdříve zkompletovat deklaraci třídy, tedy přidat další členy, které budeme dále potřebovat. K bližšímu vysvětlení se dostaneme, nyní si tedy přidejme tyto členy třídy TFormEx:
private:
TFarProc m_NewProc;
TFarProc m_PrevProc;
TForm* m_Form;
Graphics::TBitmap* FBackBitmap;
bool FNoMinimize;
bool FNoMaximize;
TXButtonEvent FOnXButtonDown;
TNCMouseEvent FOnNCLButtonDown;
TNCHitTest FOnNCHitTest;
void __fastcall FormWndProc(TMessage& Msg);
// …..
// zde jsou funkce Setxxxx
// …..
protected:
void __fastcall Loaded();
public:
Jak je vidět, budeme přepisovat virtuální funkci třídy TComponent
virtual void __fastcall Loaded(void);
a dále si vytvoříme vlastní funkci (FormWndProc), která bude mít strukturu procedury okna, tak jak ji implementuje systém VCL. Na ni si přesměrujeme proceduru okna formuláře, jak si za chvíli ukážeme.
Nyní tedy přejdeme k vlastnímu kódu. Kromě již dříve vysvětleného vytvoření instance třídy pro uložení property BackBitmap (v konstruktoru komponenty, a v destruktoru ji opět zrušíme) musíme především získat ukazatel na objekt formuláře, který naši komponentu vlastní, a dále se „napíchnout“ na jeho proceduru okna. Asi jediné vhodné „místo“ pro tyto akce je právě zmíněná virtuální metoda Loaded. Podívejme se, jak vypadá naše přepsaná varianta:
void __fastcall TRPJFormEx::Loaded()
{
TComponent::Loaded();
if ( !ComponentState.Contains(csDesigning) )
{
TForm* owner = dynamic_cast<TForm*>(Owner);
if ( !owner )
return;
m_PrevProc = (void>SetWindowLong(owner->Handle, GWL_WNDPROC,
(LONG)m_NewProc);
m_Form = owner;
// nastavíme bitmapu do štětce, kterým se “automaticky” vykresluje pozadí
if ( !FBackBitmap->Empty )
{
m_Form->Brush->Bitmap = FBackBitmap;
m_Form->Repaint();
}
}
}
Jak je vidět z výpisu, nejdříve se s použitím dynamického přetypování pokusíme získat ukazatel na třídu formuláře-vlastníka. V případě úspěchu si ho uložíme do členské proměnné m_Form a dále pomocí funkce SetWindowLong (popřípadě z důvodu „budoucí kompatibility“ bychom použili SetWindowLongPtr, kterou by však starší verze překladače nemusely znát, proto ji zde nepoužívám L) nastavíme proceduru okna formuláře na naši vlastní funkci m_NewProc (typu TFarProc), kterou nastavíme na vytvořenou funkci FormWndProc) pomocí – jak bývá u Borlandu bohužel zvykem – „nedokumentované“ funkce
extern PACKAGE void * __fastcall MakeObjectInstance(Controls::TWndMethod Method);
To provedeme již v konstruktoru; na ten se nyní podívejme současně s destruktorem třídy. Jejich „konečná“ podoba vypadá takto:
__fastcall TRPJFormEx::TRPJFormEx(TComponent* Owner)
: TComponent(Owner)
{
m_NewProc = 0;
m_PrevProc = 0;
m_Form = NULL;
FBackBitmap = new Graphics::TBitmap;
if (!ComponentState.Contains(csDesigning))
m_NewProc = MakeObjectInstance(FormWndProc);
}
__fastcall TRPJFormEx::~TRPJFormEx()
{
if ( m_NewProc )
{
TForm* ownerForm = dynamic_cast<TForm*>(Owner);
if (!ownerForm)
return;
SetWindowLong(ownerForm->Handle, GWL_WNDPROC, (long)m_PrevProc);
m_PrevProc = 0;
FreeObjectInstance(m_NewProc);
m_Form = NULL;
}
delete FBackBitmap;
}
Nyní se již budeme moci podívat, jak realizovat funkčnost přidaných vlastností. Jak jsem se zmínil na začátku, zpracováváme pouze „ukázkové“ zprávy, tj. například WM_NCLBUTTONDOWN je použita jako ukázka celé skupiny zpráv, jejichž zpracování je již rutinní záležitostí – konkrétně mám na mysli třeba zprávy WM_NCRBUTTONDOWN (pravé tlačítko), WM_NCMBUTTONDOWM (střední tlačítko) a tak bych mohl pokračovat, princip by byl stejný. Podívejme se tedy na „kompletní“ opis naší procedury okna:
POINTS pts;
void __fastcall TRPJFormEx::FormWndProc(TMessage& Msg)
{
switch ( Msg.Msg )
{
case WM_NCLBUTTONDOWN:
if ( OnNCLButtonDown == NULL ) break;
pts = MAKEPOINTS(Msg.LParam);
OnNCLButtonDown(this, Msg.WParam, pts.x, pts. y);
break;
case WM_NCHITTEST:
if ( m_Form == NULL ) break;
if ( OnNCHitTest == NULL ) break;
pts = MAKEPOINTS(Msg.LParam);
if ( m_PrevProc )
{
Msg.Result = CallWindowProc((FARPROC)m_PrevProc,
m_Form->Handle, Msg.Msg, Msg.WParam, Msg.LParam);
}
OnNCHitTest(this, Msg.Result, pts.x, pts.y);
return;
case WM_SYSCOMMAND:
switch ( Msg.WParam )
{
case SC_MINIMIZE:
if ( !NoMinimize ) break;
Msg.Result = 0;
return;
case SC_MAXIMIZE:
if ( NoMaximize )
{
Msg.Result = 0;
return;
}
break;
}
break;
case WM_XBUTTONDOWN:
if ( m_Form == NULL ) break;
if ( OnXButtonDown != NULL )
{
pts = MAKEPOINTS(Msg.LParam);
OnXButtonDown(this, Msg.WParamHi, Msg.WParamLo, pts.x, pts.y);
}
break;
}
if ( m_PrevProc )
{
Msg.Result = CallWindowProc((FARPROC)m_PrevProc,
m_Form->Handle, Msg.Msg, Msg.WParam, Msg.LParam);
}
}
Na závěr ještě připomenu, že nesmíme zapomenout (v případě použití „vizuálního“ přidání property BackBitmap) opravit funkci SetBackBitmap, kde musíme použít metodu Assign místo přiřazení ukazatele rovnítkem.
void __fastcall TRPJFormEx::SetBackBitmap(Graphics::TBitmap* value)
{
if (FBackBitmap != value)
{
FBackBitmap->Assign(value);
}
}
Zde si můžete stáhnout zdrojový kód form_ex.zip (3 kB)