Tvorba komponent pro C++ Builder FormExhalupa

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)

Diskuze (2) Další článek: Volný zvyšuje přístupovou kapacitu

Témata článku: Software, Microsoft, Programování, TV +, Case, Komponenta, Tvor, Brush, Tvorba, Break, Read, Extender, Komp, TFA


Určitě si přečtěte

Vybrali jsme 12 programovatelných hraček a stavebnic pro děti a jejich rodiče

Vybrali jsme 12 programovatelných hraček a stavebnic pro děti a jejich rodiče

** Získejte děti pro matematiku a základy techniky ** Kupte jim hračku nebo stavebnici, které vdechnou vlastní život ** Vybrali jsme stavebnice pro malé caparty i budoucí experty

Jakub Čížek | 10

Jakub Čížek
Stavebnice
Velký den pro Apple: Uvedl tři nové Macy s vlastním procesorem M1
Lukáš Václavík
PočítačeApple
Google spouští vlastní VPN a konkurenci se to vůbec nelíbí
Lukáš Václavík
SoukromíVPNGoogle

Aktuální číslo časopisu Computer

Jak prodloužit výdrž notebooku

Velké testy: gamepady a inkoustové tiskárny

Důkladný test Sony Playstation 5