Tvorba komponent pro C++ Builder - vlastní komponenta Shape

V tomto článku si ukážeme jak vytvořit komponentu odvozenou do TGraphicControl. Vytvoříme si komponentu, která bude podobná standardní komponentě TShape s tím, že v některých směrech bude umět trochu víc.
Například bude možné jak výplň tvaru, tak výplň čáry (tedy pero) definovat bitmapou, která bude jako nová vlastnost (property). Dále budeme moci určit způsob zakončení čáry ( v případě tvaru "kříž" a "diagonální kříž"). Bude tedy aplikovat některé další možnosti pera, které objekt TPen neobsahuje.

Řekněme si nejdříve, že TGraphicControl je komponenta, která nemá vlastní okno, a tedy ani vlastnost Handle (typu HWND, tedy handle okna Windows). Veškeré její grafické vykreslování je implementováno ve virtuální metodě

virtual void __fastcall Paint(void);

jež je volána v reakci na zprávu WM_PAINT, kterou obdrží vlastník komponenty, obvykle TForm. "Uvnitř" této metody nám VCL inicializuje Canvas (tedy zapouzdření klasického kontextu zařízení (HDC)) a ten pak máme k dispozici pro vlastní kreslící operace. Vytvořme si tedy (a nejlépe přidejme do našeho balíčku) komponentu odvozenou od TGraphicControl, nazvanou TRPJShape. Nedefinujeme si následující výčtové tipy:

  enum eRPJShapeType {shtNone, shtEllipse, shtRect, shtCross, shtDiagCross };
  enum eRPJEndCapType { endRound, endSquare, endFlat };
  enum eRPJJoinType { joinBevel, joinMiter, joinRound };

Typ eRPJShapeType bude definovat typ tvaru, eRPJEndcapType bude definovat způsob zakončení čáry pera a eRPJJoinType způsob napojení čáry pera. Tyto vlastnosti jsou podrobněji popsány v dokumentaci například u popisu funkce ExtCreatePen, ke které se ještě vrátíme. Dále si přidejme následující property:

__published:
  __property TBrush* Brush  = { read=FBrush, write=SetBrush };
  __property eRPJShapeType Shape  = { read=FShape, write=SetShape };
  __property TColor PenColor = { read=FPenColor, write=SetPenColor };
  __property TPenStyle PenStyle = { read=FPenStyle, write=SetPenStyle };
  __property int PenWidth  = { read=FPenWidth, write=SetPenWidth };
  __property TPenMode PenMode  = { read=FPenMode, write=SetPenMode };
  __property Graphics::TBitmap* PenBitmap  = { read=FPenBitmap, write=SetPenBitmap };
  __property Graphics::TBitmap* BrushBitmap  = { read=FBrushBitmap, write=SetBrushBitmap };
  __property eRPJJoinType PenJoin  = { read=FPenJoin, write=SetPenJoin };
  __property eRPJEndCapType PenEndCap  = { read=FPenEndCap, write=SetPenEndCap };

Property Brush (štětec) má stejný význam jako u standardní komponenty TShape, navíc však budeme moci definovat tento štětec bitmapou určenou property BrushBitmap. Na rozdíl od TShape zde nebudeme definovat pero objektem TPen, ale jeho vlastnosti, včetně těch, které jsou navíc oproti Tpen, budou tvořit samostatné property. Vlastní pero (HPEN) budeme vždy vytvářet uvnitř kreslící funkce (Paint). Výplň pera pak bude definována (volitelně) property PenBitmap. Pokud některá z těchto výplňových bitmap bude prázdná, použije se barva pera či štětce.

Podobně jako v případě vlastního fontu (RolloverFont) u komponenty RPJHyperlink musíme opět opravit funkci "Set" u bitmapových property, vygenerovanou ClassExplorerem, pokud přidáváme property "vizuálně". Místo obyčejného přiřazení pomocí "=" musíme použít metodu Assign:

void __fastcall TRPJShape::SetPenBitmap(Graphics::TBitmap* value)
{
  if (FPenBitmap != value) {
    FPenBitmap->Assign(value);
    Repaint();
  }
}

void __fastcall TRPJShape::SetBrushBitmap(Graphics::TBitmap* value)
{
  if (FBrushBitmap != value) {
    FBrushBitmap->Assign(value);
    if ( FBrushBitmap->Empty )
      FBrush->Bitmap = NULL;
    else
      FBrush->Bitmap = FBrushBitmap;
    Repaint();
  }
}

Jak vidíte, při nastavení bitmapy štětce ještě tuto bitmapu nastavíme objektu Brush a v obou případech musíme programově vyvolat okamžité překreslení. Toto překreslení musíme samozřejmě vyvolávat ve všech "Set" funkcích, aby se požadovaná změna vizuálně projevila okamžitě. V opačném případě by ke změně došlo až při následujícím "systémem vynuceném" překreslení (tedy při zprávě WM_PAINT).

Nesmíme rovněž opomenout tyto bitmapy alokovat (přesněji vytvořit instanci třídy) v konstruktoru komponenty a v destruktoru je opět uvolnit. V konstruktoru také nastavíme výchozí hodnoty property:

__fastcall TRPJShape::TRPJShape(TComponent* Owner)
: TGraphicControl(Owner)
{
  FBrush = new TBrush();
  FPenBitmap = new Graphics::TBitmap();
  FBrushBitmap = new Graphics::TBitmap();
  Width = 70;
  Height = 40;
  m_hPen = NULL;
  PenColor = clBlack;
  PenMode = pmCopy;
  PenWidth = 1;
  PenStyle = psSolid;
}

__fastcall TRPJShape::~TRPJShape()
{
  if ( m_hPen != NULL )
    DeleteObject(m_hPen);
  delete FBrush;
  delete FBrushBitmap;
  delete FPenBitmap;
}

Nyní už zbývá vytvořit „kreslící funkci“, tedy přepsat virtuální funkci Paint. Řekli jsme si, že pero, tedy objekt typu HPEN, budeme vždy vytvářet právě v této funkci na základě nastavených hodnot těch property, které definují jeho vlastnosti. Vytvořme si pro to vlastní funkci, která nám vytvoří pero jako objekt typu (HPEN) a přiřadí do členské proměnné m_hPen:

bool __fastcall TRPJShape::MyCreatePen()
{
  LOGBRUSH logbrush;
  DWORD dwStyle;
  DeleteObject(m_hPen);
  if ( FPenBitmap->Empty )
  {
    switch ( FPenStyle )
    {
      case psSolid:
        dwStyle = PS_SOLID;
        break;
      case psDash:
        dwStyle = PS_DASH;
        break;
      case psDot:
        dwStyle = PS_DOT;
        break;
      case psDashDot:
        dwStyle = PS_DASHDOT;
        break;
      case psDashDotDot:
        dwStyle = PS_DASHDOTDOT;
        break;
      case psInsideFrame:
        dwStyle = PS_SOLID;
        break;
    }
    switch ( FPenEndCap )
    {
      case endRound:
        dwStyle |= PS_ENDCAP_ROUND;
        break;
      case endSquare:
        dwStyle |= PS_ENDCAP_SQUARE;
        break;
      case endFlat:
        dwStyle |= PS_ENDCAP_FLAT;
        break;
    }
    switch ( FPenJoin )
    {
      case joinBevel:
        dwStyle |= PS_JOIN_BEVEL;
        break;
      case joinMiter:
        dwStyle |= PS_JOIN_MITER;
        break;
      case joinRound:
        dwStyle |= PS_JOIN_ROUND;
        break;
    }
    logbrush.lbStyle = BS_SOLID;
    logbrush.lbColor = FPenColor;
    logbrush.lbHatch = 0;
    m_hPen = ExtCreatePen(dwStyle | PS_GEOMETRIC,
      FPenWidth,
      &logbrush,
      0,
      NULL);
  }
  else
  {
    logbrush.lbStyle = BS_PATTERN;
    logbrush.lbColor = 0x00;
    logbrush.lbHatch = (LONG)FPenBitmap->Handle;
    dwStyle = PS_GEOMETRIC;
    if ( FPenStyle == psInsideFrame )
      dwStyle |= PS_INSIDEFRAME;
    m_hPen = ExtCreatePen(dwStyle,
      FPenWidth,
      &logbrush,
      0,
    NULL);
  }
  return (m_hPen != NULL);
}

Vlastní kreslící funkce pak vypadá takto:

void __fastcall TRPJShape::Paint(void)
{
  MyCreatePen();
  SelectObject(Canvas->Handle, FBrush->Handle);
  SelectObject(Canvas->Handle, m_hPen);
  RECT rect = ClientRect;
  int diff = 0;
  if ( FPenWidth > 1 )
    diff = FPenWidth / 2;
  switch ( FShape )
  {
    case shtNone:
      break;
    case shtEllipse:
      InflateRect(&rect, -diff, -diff);
      Canvas->Ellipse(rect);
      break;
    case shtRect:
      InflateRect(&rect, -diff, -diff);
      Canvas->Rectangle(rect);
      break;
    case shtCross:
      Canvas->MoveTo(rect.right / 2, diff);
      Canvas->LineTo(rect.right / 2, rect.bottom - diff);
      Canvas->MoveTo(diff, rect.bottom / 2);
      Canvas->LineTo(rect.right-diff, rect.bottom / 2);
      break;
    case shtDiagCross:
      Canvas->MoveTo(diff, diff);
      Canvas->LineTo(rect.right - diff,
        rect.bottom - diff);
      Canvas->MoveTo(FPenWidth / 2, rect.bottom - diff);
      Canvas->LineTo(rect.right-diff, FPenWidth / 2);
      break;
  }
  if ( DeleteObject(m_hPen) )
    m_hPen = NULL;
}

Zde si můžete stáhnout zdrojový kód komponenty: shape.zip

Váš názor Další článek: Windows XP budou používat v kampani písničku od Madonny

Témata článku: Software, Windows, Programování, Switch, Komp, Tvorba, Case, VLA, Read, Nová vlastnost, Komponenta, Break, TV +, Canvas, Brush, Peru, Tvor, Pero


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

Apple Macbook Air M1: testujeme výkon, výdrž, a hlavně kompatibilitu aplikací [průběžně aktualizováno]

Apple Macbook Air M1: testujeme výkon, výdrž, a hlavně kompatibilitu aplikací [průběžně aktualizováno]

** Testujeme Apple Macbook Air s procesorem M1 ** Zajímá nás nejen výkon, ale zejména kompatibilita aplikací ** Článek je průběžně doplňován na základě vašich dotazů

Jiří Kuruc | 205

Jiří Kuruc
Apple
Google spouští vlastní VPN a konkurenci se to vůbec nelíbí
Lukáš Václavík
SoukromíVPNGoogle
26 užitečných rozšíření pro Chrome: Naučte prohlížeč nové věci

26 užitečných rozšíření pro Chrome: Naučte prohlížeč nové věci

** Prohlížeč Chrome obsahuje širokou škálu funkcí, neumí ale všechno ** Jeho schopnosti můžete rozšířit pomocí rozšíření ** Vybrali jsme pro vás zajímavé a užitečné doplňky

Karel Kilián | 44

Karel Kilián
Doplňky do prohlížečeChromeProhlížeče
Jak se šíří Covid v Česku: Čerstvá data, semafor PES, mapy okresů a obcí. Každý den aktualizované grafy

Jak se šíří Covid v Česku: Čerstvá data, semafor PES, mapy okresů a obcí. Každý den aktualizované grafy

** Vývoj COVID-19 v Česku: nakažení, úmrtí, testovaní, hospitalizovaní ** Mapa podle okresů, přehled podle věku, situace v Evropě i ve světě ** Každý den aktualizované grafy a mapy

Marek Lutonský | 172

Marek Lutonský
COVID-19Koronavirus
Velký den pro Apple: Uvedl tři nové Macy s vlastním procesorem M1
Lukáš Václavík
PočítačeApple
Google vymyslel technologii superpřesného GPS. Už ji podporuje Pixel 5 a dorazí i na ostatní telefony

Google vymyslel technologii superpřesného GPS. Už ji podporuje Pixel 5 a dorazí i na ostatní telefony

** Kvalita GPS ve městech občas stojí za starou bačkoru ** Mohou za to odrazy signálu od okolních budov ** Google má jejich 3D model, a tak spolupracuje s výrobci GPS čipů

Jakub Čížek | 40

Jakub Čížek
NavigaceTechnologieGoogle
AMD uvádí grafické karty Radeon RX 6800, 6800 XT a 6900 XT. Útočí přímo na modely od Nvidie

AMD uvádí grafické karty Radeon RX 6800, 6800 XT a 6900 XT. Útočí přímo na modely od Nvidie

** AMD představilo tři nové grafické karty ** Všechny s architekturou RDNA2, kterou používají i PS5 a Xbox Series ** Karty útočí přímo na GeForce RTX 3000

Karel Javůrek | 78

Karel Javůrek
Radeon RX 6000Grafické kartyAMD

Aktuální číslo časopisu Computer

Jak prodloužit výdrž notebooku

Velké testy: gamepady a inkoustové tiskárny

Důkladný test Sony Playstation 5