C++ Builder – Ikony a kurzory

V minulém článku jsem slíbil, že se budeme podrobněji věnovat ikonám a jejich specifikům v C++ Builderu. S tématem ikon úzce souvisí (úžeji než si možná leckdo myslí, jak dnes poznáte) téma kurzorů.
O tom, jak systém vybírá ikonu reprezentující příslušný exe soubor, a jak tento výběr ovlivnit, jsme si řekli minule. Dnes se nejprve podíváme na to, jak nastavit ikonu, zobrazující se v systémové nabídce formuláře a ikonu, která naši aplikaci reprezentuje na pruhu úloh.

Trocha teorie

Z hlediska standardního programování ve Windows je ikona (ve formě handle) přiřazena třídě oken. K této třídě samozřejmě může náležet více současně existujících oken. Všechny pak mají stejnou ikonu (tu, která se zobrazí v systémové nabídce a pokud jde o hlavní okno aplikace, tak také na pruhu úloh. Jak jsem již napsal v minulém článku, ikonu reprezentující exe soubor aplikace vybere systém jako ikonu s nejnižším identifikátorem (ze zdrojů – resources).

Přiřazení ikony třídě oken se provádí při registraci třídy přiřazením handle ikony do prvku hIcon struktury WNDCLASSEX. Za běhu programu, po vytvoření okna, můžeme tuto ikonu změnit pomocí funkce SetClassLongPtr:

ULONG_PTR SetClassLongPtr(
HWND hWnd,  // handle okna
int nIndex,  // index hodnoty, kterou chceme změnit
LONG_PTR dwNewLong  // nová hodnota
);

popřípadě můžeme použít starší funkci SetClassLong, která však není kompatibilní s 64bitovou verzí Windows.

Jako hodně věcí ve VCL knihovně, není to tak jednoduché. Objekty VCL knihovny si hodně vlastností udržují ve svých property, a nenechají si na ně „sáhnout zezdola“. Když použijete funkci SetClassLongPtr na handle okna formuláře, ikona se nezmění. Třída TForm má totiž jednu ze svých property TIcon, jejíž hodnota není (jen Borland ví proč) udržována ve vlastnostech třídy, ale ve vlastní třídě TIcon. Je tedy potřeba změnit properte Icon. To samozřejmě není nic složitého, lze to udělat přímo v ObjectInspectoru nebo za běhu programu, je dokonce možné ikonu načíst ze souboru.

V praxi však bývá nejčastějším postupem získání ikon ze zdrojů programu. Jejich „zásobníkem“ je tedy buď přímo exe soubor daného programu nebo nějaká knihovna dll. Když se podíváte nějakým programem který umí prohlížet ikony ve zdrojích, zjistíte že třeba takový soubor shell32.dll obsahuje řádově desítky ikon (používaných systémem). V tom případě použijeme funkci LoadIcon:

HICON LoadIcon(
INSTANCE hInstance, // handle instance
PCTSTR lpIconName // jméno ikony ve zdrojích
);

nebo lépe trochu sofistikovanější LoadImage:

HANDLE LoadImage(
INSTANCE hinst,  // handle instance
PCTSTR lpszName,  // jméno “obrázku” ve zdrojích
INT uType,  // typ obrázku
nt cxDesired,  // požadovaná šířka
nt cyDesired,  // požadovaná výška
INT fuLoad  // další volby
);

Vše si ukážeme za chvíli prakticky stejně jako to, že místo handle ikony lze použít handle kurzoru, načteného také funkcí LoadImage, nebo starší LoadCursor:

HCURSOR LoadCursor(
HINSTANCE hInstance,  // handle instance
LPCTSTR lpCursorName  // jméno kuzoru ve zdrojích
);

popřípadě, chceme-li načíst kurzor ze souboru LoadCursorFromFile:

HCURSOR LoadCursorFromFile(
LPCTSTR lpFileName  // jméno souboru
);

Touto funkcí lze načíst také animovaný kurzor. Ale již dost teorie a pojďme na praktické ukázky.

Jak na to?

Ještě než skočíme na praxi, musím se zmínit o jedné ze specifik aplikace používající VCL knihovnu. Kromě hlavního formuláře, a popřípadě dalších oken aplikace, je po spuštění aplikace vytvořeno okno třídy „TApplication“, které je neviditelné (má rozměry 0x0) a obhospodařuje některé akce takovéto aplikace. Popis a možnosti třídy TApplication naleznete v nápovědě a jejich podrobnější výklad je mimo rámec tohoto článku, samozřejmě s výjimkou jedné její property, kterou je Icon:

__property Graphics::TIcon* Icon = {read=FIcon, write=SetIcon};

A tato ikona je tou, která se zobrazuje na pruhu úloh. Zde tedy není zobrazována ikona hlavního okna aplikace, jak bývá zvykem.

Přidejme si tedy 2 členské proměnné do třídy formuláře:

HICON m_hIcon;
HCURSOR m_hCursor;

V konstruktoru si pak načteme ikonu ze zdrojů a kurzor třeba ze souboru:

__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
m_hIcon = LoadIcon(HInstance, "XHODINY");
if ( m_hIcon == NULL )
{
MessageBox(Handle, "Nelze načíst ikonu", "Chyba", MB_ICONERROR);
return;
}
m_hCursor = LoadCursorFromFile("kurzor.cur");
if ( m_hCursor == NULL )
{
MessageBox(Handle, "Nelze načíst kurzor", "Chyba", MB_ICONERROR);
return;
}

}

Nastavení ikony formuláře

Nyní, když budeme chtít nastavit ikonu formuláře (tu v systémové nabídce), můžeme použít jak ikonu tak kurzor tímto způsobem:

void __fastcall TForm1::OnFromResources(TObject *Sender)
{
Form1->Icon->Handle = m_hIcon;
}

void __fastcall TForm1::OnUseCursor(TObject *Sender)
{
Form1->Icon->Handle = m_hCursor;
}

Nastaveni ikony aplikace

Tímto však zůstane nedotčena ikona aplikace na pruhu úloh. Takto nastavíme tuto ikonu, klidně můžeme opět použít handle kurzoru:

void __fastcall TForm1::OnAppIconCursor(TObject *Sender)
{
Application->Icon->Handle = m_hCursor;
}

V obou případech samozřejmě přiřazujeme ikonu do property Handle té které ikony.

Animace ikony formuláře a aplikace

V ukázkové aplikaci jsem vytvořil jednoduchý příklad, jak můžeme animovat výše zmíněné ikony formuláře či aplikace. Přidáme si na formulář TImageList, který si naplníme několika ikonami, přidáme si členskou proměnnou (m_AktIndex), ve které bude index (z image-listu) právě zobrazované ikony, a TTimer. Na tlačítko spustíme timer a nastavíme formuláři vlastní ikonu (za chvíli vysvětlím proč).

void __fastcall TForm1::OnAnimovat(TObject *Sender)
{
m_aktIndex = 0;
Form1->Icon->Handle = m_hIcon;
Timer1->Enabled = true;
}

Na timer, tedy událost OnTimer, pak podle stavu check-boxů určujících, zda chceme animovat ikonu formuláře, aplikace či obojí, vezmeme z image-listu ikonu požadovaného indexu a přiřadíme ji aplikaci a formuláři.

void __fastcall TForm1::OnTimer(TObject *Sender)
{
m_aktIndex++;
if ( m_aktIndex >= ImageList1->Count )
  m_aktIndex = 0;
  if ( AnimForm->Checked )
  Form1->Icon->Handle =
  (HICON)ImageList_GetIcon((HIMAGELIST)ImageList1->Handle,
m_aktIndex, ILD_NORMAL);
  if ( AnimApp->Checked )
  Application->Icon->Handle =
  (HICON)ImageList_GetIcon((HIMAGELIST)ImageList1->Handle,
m_aktIndex, ILD_NORMAL);
}

Nyní k tomu, proč jsem na spuštění animace přiřadil formuláři vlastní ikonu: Pokud žádnou ikonu formuláři nenastavíme, tak tento si bere ikonu aplikace. Pak by při animaci ikony aplikace se měnila i ikona formuláře i v případě, že bychom to nepožadovali, tedy nenastavili tím Form1->Icon->Handle = ...

Kurzory a VCL

Nyní se zaměřme na kurzory. Systém knihovny VCL pracuje s kurzory trochu nestandardním způsobem, jak je ostatně ve VCL obvyklé.... Součástí VCL je objekt TScreen, který obsahuje 2 property týkající se kurzorů. První z nich je Cursor:

__property Controls::TCursor Cursor = {read=FCursor, write=SetCursor, nodefault};

která určuje globálně kurzor, a „přepíše“ tak kurzory nastavené u jednotlivých prvků.

Druhou je property Cursors:

__property HICON Cursors[int Index] = {read=GetCursors, write=SetCursors};

která představuje seznam kurzorů, které má aplikace k dispozici (míněno samozřejmě způsobem VCL, jak si ukážeme můžeme použít i další kurzory). Jednotlivé kurzory tohoto seznamu pak můžeme přiřazovat jednotlivým komponentám nebo formulářům. Ukážeme si nejprve, jak nastavíme vlastní kurzor globálně celému formuláři včetně komponent na něm umístěných.

#define crKurzor 1  // toto je někde v hlavičkovém souboru

void __fastcall TForm2::OnCustomCursor(TObject *Sender)
{
HCURSOR hc = LoadCursorFromFile("kurzor.cur");
if ( hc == NULL )
{
MessageBox(Handle, "Chyba načtení kurzoru", NULL, MB_ICONERROR);
return;
}
Screen->Cursors[crKurzor] = hc;
Screen->Cursor = (TCursor)crKurzor;
}

Jak použít animovaný kurzor?

Další příklad ukazuje jak podobným způsobem použít animovaný kurzor načtený ze souboru, tentokrát přiřazený pouze formuláři, tedy kurzory prvků (komponent) na tomto formuláři zůstanou nedotčeny.

#define crAnim 2 // v hlavičkovém souboru

void __fastcall TForm2::OnAniCursor(TObject *Sender)
{
HCURSOR hc = LoadCursorFromFile("kurzor.ani");
if ( hc == NULL )
{
MessageBox(Handle, "Chyba načtení kurzoru", NULL, MB_ICONERROR);
return;
}
Screen->Cursors[crAnim] = hc;
Form2->Cursor = (TCursor)crAnim;
}

Již jsem se zmínil o tom, jak se ve Windows standardně nastavuje ikona třídě oken. Podobně je tomu i s kurzorem, kde narozdíl od ikony lze tento způsob použít, i když pouze za splnění jistých podmínek. Nejdříve příklad jak takto nastavit formuláři kurzor pomocí funkce SetClassLongPtr:

void __fastcall TForm2::OnSetClassCursor(TObject *Sender)
{
HCURSOR hCursor = LoadCursorFromFile("kurzor.cur");
if ( hCursor == NULL )
{
MessageBox(Handle, "Chyba načtení kurzoru", NULL, MB_ICONERROR);
return;
}
SetClassLongPtr(Handle, GCLP_HCURSOR, (LONG_PTR)hCursor);
}

Napsal jsem „za jistých podmínek“. Tato metoda je úspěšná, pokud před tím nenastavíte kurzor „borlandí metodou“, tedy tím Form->Cursor = .... Jakmile totiž formulář (nebo jakákoli jiná komponenta VCL má specifikovanou property Cursor, tak to bere jako svaté a nějaké standardní nastavení ji nezajímá (i s takovými objekty musíme pracovat, Hlaváčku....).

Animovaný kurzor ze zdrojů programu

Ale abychom si stále nedobírali VCL, řekněme si, že nám umožňuje relativně jednoduchým způsobem („standardně“ je to o něco složitější) použít animovaný kurzor uložený ve zdrojích (resources). Přidejme si tedy do zdrojů animovaný kurzor jako typ „ANICUR“. To lze udělat různým způsobem. Pokud máte nějaký slušný editor zdrojů (například Visual C++) vytvoříte (raději v ImageEditoru) prázdný soubor xx.res, který přidáte do projektu. Tento soubor pak můžete otevřít ve Visual C++ (nebo nějakém jiném slušnějším editoru zdrojů) a do něj importovat nějaký soubor s animovaným kurzorem, přičemž jako typ zdroje zadáte „ANICUR“. Další možností je vytvořit si textový resource script (xxxx.rc) v kterém zapíšete třeba takto:

"ANINAME"  ANICUR  DISCARDABLE  "jmeno_souboru.ANI"

Pak můžete přiřadit formuláři tento kurzor tak, jak vidíte v následujícím příkladě:

void __fastcall TForm2::OnAniFromRes(TObject *Sender)
{
TResourceStream* rs =
new TResourceStream((int)HInstance,"ANINAME", "ANICUR");
rs->SaveToFile("temp.ani");
delete rs;
HCURSOR hc = LoadCursorFromFile("temp.ani");
if ( hc == NULL )
{
MessageBox(Handle, "Chyba načtení kurzoru", NULL, MB_ICONERROR);
return;
}
SetClassLongPtr(Handle, GCLP_HCURSOR, (LONG_PTR)hc);
DeleteFile("temp.ani");
}

Opět zde samozřejmě platí výše omezení použití funkce SetClassLongPtr ve VCL.

Jako potvrzení toho, že pomocí funkce SetClassLongPtr lze nastavit kurzor třídě, tedy i mnoho oknům najednou, si ukažme, jak takto změníme kurzor všech tlačítek nejen na formuláři, ale v celé aplikaci:

void __fastcall TForm2::OnSetButtonsCursor(TObject *Sender)
{
HCURSOR hCursor = LoadCursorFromFile("kurzor.cur");
if ( hCursor == NULL )
{
MessageBox(Handle, "Chyba načtení kurzoru", NULL, MB_ICONERROR);
return;
}
SetClassLongPtr(Button1->Handle, GCLP_HCURSOR, (LONG_PTR)hCursor);
}

Od této chvíle všechny tlačítka, kterým předtím nebo potom nenastavíme „borlandím způsobem“ vlastní kurzor, budou zobrazovat tento, přiřazený třídě.

Ikona jako kurzor

Nyní si ještě ukážeme, že jako kurzor lze použít i načtenou ikonu, navíc pokud použijete funkci LoadImage, můžete specifikovat rozměry výsledného obrázku. Ukažme si jako příklad načtení ikony(ze zdrojů), její roztáhnutí do velikosti 64 x 64 a její použití jako kurzoru, přičemž i výsledný kurzor bude mít velikost 64 x 64 pixelů:

void __fastcall TForm2::CurFromIco(TObject *Sender)
{
HICON hIcon =
LoadImage(HInstance, "XHODINY", IMAGE_ICON, 64, 64,
0);
SetClassLongPtr(Handle, GCLP_HCURSOR, (LONG_PTR)hIcon);
}

Myslím, že rozsah článku byl naplněn, ale když už se podrobněji zabýváme ikonami a kurzory, zůstaneme u nich i na začátku příštího pokračování, kde si ukážeme jak na formuláři zobrazovat ikony, kurzory, včetně jednotlivých snímků animovaného kurzoru.

Zde je ke stažení ukázková aplikace k tomuto článku: ikony_kurzory.zip.

Váš názor Další článek: Živě a PCtuning přejí veselé Vánoce

Témata článku: Software, Windows, Programování, FCU, Jednotlivé prvky, Ukázkový příklad, Timer, Icon, Jednotlivé komponenty, Read, Hicon, Kurz, Code, Kurzor, Zdroje


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

Air Bank, Fio banka a MONETA zakládají alianci pro bankovní identitu
Jakub Čížek
BankaČeskoeGovernment
Google spouští vlastní VPN a konkurenci se to vůbec nelíbí
Lukáš Václavík
SoukromíVPNGoogle
Šéf Spotify: Budeme zdražovat. Náš obsah se zlepšil
Markéta Mikešová
PředplatnéSpotify
CZ.NIC bezplatně naděluje USB/NFC klíče. Jak jej získat?
Lukáš Václavík
CZ.NICeGovernment
Lidl buduje chytrou domácnost, propojí všechno se vším
Lukáš Václavík
LidlChytrá domácnostIoT

Aktuální číslo časopisu Computer

Jak prodloužit výdrž notebooku

Velké testy: gamepady a inkoustové tiskárny

Důkladný test Sony Playstation 5