Tvorba komponent pro C++ Builder SimpleEdit 2

V tomto pokračování ještě zůstaneme u komponenty "Simple edit". Trochu si ji ještě rozšíříme a ukážeme si, jak určit "ikonku", která reprezentuje komponentu na paletě komponent.
V tomto pokračování ještě zůstaneme u komponenty "Simple edit". Trochu si ji ještě rozšíříme a ukážeme si, jak určit "ikonku", která reprezentuje komponentu na paletě komponent.

Nejdříve si ukážeme příklad

přidání události (event) vlastního typu.

Dosud jsme používali jako typ události některý z předdefinovaných typů, konkrétně v minulém díle ten nejjednodušší - TNotifyEvent. Dejme tomu, že budeme chtít vytvořit události (events) OnKillFocus a OnSetFocus, které nastanou, když komponenta ztratí (resp. získá) klávesnicový focus. O této události nás Windows informují zasláním zprávy WM_KILLFOCUS, resp. WM_SETFOCUS. V obou případech tyto zprávy obsahují jako parametr wParam handle okna (HWND), které získá klávesnicový fokus po našem okně (v případě zprávy WM_KILLFOCUS) a naopak, které mělo fokus před ním (v případě WM_SETFOCUS). Budeme tedy chtít, aby druhým parametrem eventu byl právě tento handle. Nadefinujeme se tedy nejprve typ eventu:

typedef void __fastcall(__closure *TFocusChangeEvent
  (TObject* Sender, HWND Handle);

Nyní si můžeme přidat 2 události OnSetFocus a OnKillFocus, obě budou typu TFocusChangeEvent. Jak přidat události pomocí ClassExploreru, jsme si ukázali v minulém článku, nebudu to proto zde podrobněji popisovat.

Pro zachytávání dalších zpráv si vytvořme přepsanou metodu WndProc, tedy proceduru okna. Je samozřejmě možné pro každou zprávu vytvořit vlastní handler v rámci mapy zpráv. Jaký způsob použijete, je již na "vkusu" každého programátora. Vyvolání událostí OnSetFocus a OnKillFocus tedy pak provedeme takto:

void __fastcall TRPJSimpleEdit::WndProc(Messages::TMessage &Message)
{
  BOOL bValidate;
  switch ( Message.Msg )
  {
    case WM_KILLFOCUS:
      if ( FOnKillFocus != NULL )
        FOnKillFocus(this, (HWND)Message.WParam);
      break;
    case WM_SETFOCUS:
      if ( FOnSetFocus != NULL )
        FOnSetFocus(this, (HWND)Message.WParam);
      break;
  }
  TWinControl::WndProc(Message);
}

Ve vlastní praxi jsem před časem dělal balík komponent na zakázku, které se měly z důvodů "kompatibility uživatelů - brrrr" chovat jako za časů, kdy se mezi jednotlivými editačními poli a dalšími prvky přeskakovalo kurzorovými klávesami, tedy šipka vlevo, nahoru atd. Ukažme si, jak to lze řešit. Komponenta bude mít 4 property typu TWinControl*, do nichž bude možno zadat (snadným vybráním v ObjectInspectoru) komponentu, na kterou se má přejít po požadavku uživatele na skok nahoru, vlevo atd. Nejprve si tedy běžným způsobem přidáme 4 property představující tyto „cílové“ komponenty:

__property TWinControl* GoLeft = { read=FGoLeft, write=SetGoLeft };
__property TWinControl* GoRight = { read=FGoRight, write=SetGoRight };
__property TWinControl* GoUp = { read=FGoUp, write=SetGoUp };
__property TWinControl* GoDown = { read=FGoDown, write=SetGoDown };

Dále si vytvoříme několik "public" funkcí, z nichž některé budeme využívat také v implementaci zmíněného skákáni na šipky.

public:
  int __fastcall GetCurPos();
  bool __fastcall SetCurPos(int NewPos);
  bool __fastcall IsOnBegin();
  bool __fastcall IsOnEnd();
  void __fastcall SelectAll();
  void __fastcall Deselect();

Pro jejich implementaci použijeme 2 zprávy: EM_GETSEL a EM_SETSEL, kterými můžeme zjistit nebo nastavit rozsah vybraného textu.

SendMessage(
  (HWND) hWnd,
  EM_GETSEL,
  (WPARAM) wParam, // počátek výběru (LPDWORD)
  (LPARAM) lParam  // konec výběru (LPDWORD)
);

SendMessage(
  (HWND) hWnd,
  EM_SETSEL,
  (WPARAM) wParam, // počátek výběru
  (LPARAM) lParam  // konec výběru
);

Pokud hodnoty wParam a lParam jsou shodné, znamená to samozřejmě prosté zjištění či nastavení pozice karetu (chcete-li kurzoru) v textu. Implementace vytvořených funkcí mohou tedy vypadat takto:

int __fastcall TRPJSimpleEdit::GetCurPos()
{
  DWORD dwStart, dwEnd;
  SendMessage(Handle, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);
  if ( dwStart != dwEnd ) // část textu je vyselektovaná
    return dwStart;
    // nebo return -1;
  return dwStart;
}

bool __fastcall TRPJSimpleEdit::SetCurPos(int NewPos)
{
  if ( GetWindowTextLength(Handle) <= NewPos )
    return false;
  SendMessage(Handle, EM_SETSEL, (WPARAM)NewPos, (LPARAM)NewPos);
  return true;
}

bool __fastcall TRPJSimpleEdit::IsOnBegin()
{
  DWORD dwStart, dwEnd;
  SendMessage(Handle, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);
  return ( dwStart == 0 && dwEnd == 0 );
}

bool __fastcall TRPJSimpleEdit::IsOnEnd()
{
  DWORD dwStart, dwEnd;
  INT TextLen = GetWindowTextLength(Handle);
  SendMessage(Handle, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);
  return ( TextLen == (INT)dwStart && TextLen == (INT)dwEnd );
}

void __fastcall TRPJSimpleEdit::SelectAll()
{
  ::SetFocus(Handle);
  SendMessage(Handle, EM_SETSEL, (WPARAM)0, (LPARAM)-1);
}

void __fastcall TRPJSimpleEdit::Deselect()
{
  SendMessage(Handle, EM_SETSEL, (WPARAM)-1, (LPARAM)0);
}

Takto vybaveni můžeme přejít k implementaci uvedeného způsobu "navigace". Vše vyřešíme v handleru zprávy WM_KEYDOWN (tedy stisknutí klávesy).

// ... procedura okna Wndproc
switch ( Message.Msg )
  {
    case WM_KEYDOWN:
      switch ( Message.WParam ) // kód virtuální klávesy
      {
        case VK_UP:
          if ( FGoUp != NULL )
          {
            ::SetFocus(FGoUp->Handle);
            return;
          }
          break;
        case VK_DOWN:
          if ( FGoDown != NULL )
          {
            ::SetFocus(FGoDown->Handle);
            return;
          }
          break;
        case VK_LEFT:
          if ( IsOnBegin() ) // pouze pokud jsme na začátku textu
            if ( FGoLeft != NULL )
            {
              ::SetFocus(FGoLeft->Handle);
              return;
            }
          break;
        case VK_RIGHT:
          if ( IsOnEnd() ) // pouze pokud jsme na konci textu
            if ( FGoRight != NULL )
            {
              ::SetFocus(FGoRight->Handle);
              return;
            }
        break;
      }
    break;
// ...

Vše tedy spočívá v předání fokusu oknu (pomocí API funkce SetFocus), jehož handle nám dává property Handle komponenty odvozené od TWinControl. Pouze v případě směrů vlevo a vpravo samozřejmě skáčeme pouze v případě, že jsme na začátku, resp. na konci textu.

Nyní si musíme říci o jedné velmi důležité metodě objektu TControl, kterou je

virtual void __fastcall Notification(Classes::TComponent* AComponent,  Classes::TOperation Operation);

Tato metoda je volána, pokud některá komponenta je přidána nebo odstraněna. Parametr Operation tedy může nabývat hodnot:

  • opInsert - objekt byl právě vytvořen
  • opRemove - objekt byl odstraněn a paměť uvolněna
Proč je pro nás tato metoda důležitá? Náš edit má vazbu na další komponenty na formuláři (property Goxxxx). Pokud bychom přiřadili do této property nějakou komponentu na formuláři a tu pak odstranili, dojde k chybě (výjimce). Přepsáním této virtuální metody v naší komponentě máme možnost toto ošetřit. Podívejme se jak:

void __fastcall TRPJSimpleEdit::Notification(Classes::TComponent* AComponent,
Classes::TOperation Operation)
{
  TWinControl::Notification(AComponent, Operation);
  if (Operation != opRemove)
    return;
  if (AComponent == GoLeft ) GoLeft = NULL;
  if (AComponent == GoRight ) GoRight = NULL;
  if (AComponent == GoUp ) GoUp = NULL;
  if (AComponent == GoDown ) GoDown = NULL;
}

V této metodě tedy máme šanci hodnotu property "vynulovat" dříve, než dojde ke zmíněné výjimce.

Tolik tedy asi ke komponentě SimpleEdit, resp. k rozšiřování její funkčnosti. Cílem bylo naučit se několik postupů při tvorbě takovéto komponenty tvořené "od základu" a nikoli vytvořit hotovou komponentu vhodnou k okamžitému použití. Její další případný rozvoj již je věcí každého z čtenářů.

Na závěr tohoto článku si ukažme, jak jsem slíbil

jak vytvořit vlastní "ikonku" komponenty na paletě komponent:

Použijeme Image Editor, v něm vytvoříme nový soubor "File -> New -> Component Resource File (.dcr)". Ten si uložíme nejlépe do stejné složky jako zdrojový soubor komponenty pod stejným názvem jako komponenta, tedy RPJSimpleEdit,drc – není to však podmínkou. Co však je podmínkou, je název bitmapy, kterou v tomto souboru zdrojů vytvoříme. Její název musí být shodný s názvem komponenty a silně doporučuji název psát stylem "všechna velká", tedy "TRPJSIMPLEEDIT. Tato bitmapa by měla mít rozměry 24 x 24 pixelů, a barevnou hloubku 16 nebo 256 barev. Nyní je prostor pro tvůrčí kreativitu s tím, že barva, kterou bude mít pixel (0,0), bude ve výsledném efektu transparentní. Já proto s oblibou používám jako podklad onu "ošklivou, oči rvoucí" fialovou barvu.

Tento soubor <.dcr) pak už jen musíme přilinkovat do balíčku, například na začátku kódu komponenty použijeme pragmu:

#include "RPJSimpleEdit.h"
#pragma package(smart_init)
#pragma resource "RPJSimpleEdit.dcr"

Po překompilování a linkování balíčku bychom již na paletě komponent měli vidět svůj grafický výtvor reprezentující naši komponentu.

Zde je ke stažení komponenta v tom stavu, do kterého jsme ji dotáhli v tomto článku simple_edit2.zip (3.3 kB).

Váš názor Další článek: Intel chystá nové balení procesorů

Témata článku: , , , , , , , , , , , ,