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).