7. Zpracovßnφ zprßv

Jednφm z klφΦ∙ tradiΦnφho programovßnφ Windows je zpracovßnφ zprßv zasφlan²ch z Windows aplikaci. C++ Builder je v∞t╣inou zpracuje za nßs, ale v p°φpad∞ vytvß°enφ komponent je mo╛nΘ, ╛e budeme pot°ebovat zpracovat zprßvu, kterou C++ Builder nezpracovßvß nebo ╛e vytvo°φme svou vlastnφ zprßvu a budeme ji pot°ebovat zpracovat.
V tΘto kapitole se budeme zab²vat:

Seznßmenφ se systΘmem zpracovßnφ zprßv

V╣echny objekty C++ Builderu majφ mechanismus pro zpracovßnφ zprßv. Zßkladnφ my╣lenkou zpracovßnφ zprßv je to, ╛e objekt p°ijme zprßvu n∞jakΘho typu a zpracuje ji volßnφm jednΘ z mno╛iny specifikovan²ch metod zßvisejφcφch na p°ijatΘ zprßv∞. Neexistuje-li metoda pro jistou zprßvu, je pou╛ita implicitnφ obsluha. Nßsledujφcφ diagram ukazuje systΘm zpracovßnφ zprßv:
Knihovna VCL definuje systΘm zpracovßnφ zprßv, kter² p°eklßdß v╣echny zprßvy Windows (vΦetn∞ u╛ivatelem definovan²ch zprßv) urΦenΘ jistΘ t°φd∞ na volßnφ metod. Tento mechanismus nebudeme nikdy pot°ebovat m∞nit. Budeme pot°ebovat pouze vytvß°et metody zpracovßnφ zprßv.
Zprßva Windows je datovß struktura, kterß obsahuje n∞kolik d∙le╛it²ch polo╛ek. Nejd∙le╛it∞j╣φ z nich je hodnota, kterß identifikuje zprßvu. Windows definuje mnoho zprßv a soubor MESSAGES.HPP deklaruje identifikßtory pro v╣echny z nich.
Programßto°i Windows pou╛φvajφ p°i prßci definice Windows, kterΘ identifikujφ zprßvy, nap°. WM_COMMAND nebo WM_PAINT. TradiΦnφ programy Windows obsahujφ proceduru okna, kterß slou╛φ jako zp∞tnΘ volßnφ pro zprßvy generovanΘ systΘmem. V tΘto procedu°e je obvykle rozsßhl² p°φkaz switch pro rozli╣enφ v╣ech zprßv okna.
Dal╣φ d∙le╛itΘ informace ve zprßv∞ jsou obsa╛eny ve dvou polo╛kßch parametr∙ a polo╛ce v²sledku. Jeden parametr je 16 bitov² a druh² 32 bitov². Jak Φasto vidφme v k≤du Windows, odkazujeme se na tyto hodnoty jako na wParam (word parametr) a lParam (long parametr). ╚asto ka╛d² z t∞chto parametr∙ obsahuje vφce ne╛ jednu informaci a na Φasti parametr∙ se odkazujeme makry jako LOWORD a HIWORD. Nap°. volßnφm HIWORD(lParam) zφskßme vy╣╣φ slovo parametru lParam.
P∙vodn∞ si programßto°i Windows museli pamatovat, co ka╛d² parametr obsahuje. Pozd∞ji Microsoft tyto parametry pojmenoval, co╛ usnad≥uje jejich pou╛φvßnφ. Nap°. parametr zprßvy WM_KEYDOWN se nynφ naz²vß nVirtKey, co╛ je vφce informativnφ ne╛ wParam.
C++ Builder zjednodu╣uje systΘm zpracovßnφ zprßv v n∞kolika sm∞rech: Ka╛dß komponenta d∞dφ kompletnφ systΘm zpracovßnφ zprßv. Tento systΘm mß implicitnφ zpracovßnφ. Definujeme pouze obsluhy pro zprßvy, na kterΘ chceme reagovat specificky. M∙╛eme modifikovat pouze malΘ Φßsti zpracovßnφ zprßv a pro v∞t╣inu zpracovßnφ pou╛φt zd∞d∞nΘ metody.
ZnaΦnou v²hodou tohoto systΘmu zpracovßnφ zprßv je, ╛e m∙╛eme bezpeΦn∞ zasφlat kdykoli libovolnou zprßvu libovolnΘ komponent∞. Jestli╛e komponenta nemß pro zprßvu definovanou obsluhu, pak je pou╛ita implicitnφ obsluha, co╛ obvykle ignoruje zprßvu.
C++ Builder registruje metodu nazvanou MainWndProc jako proceduru okna po ka╛d² typ komponenty v aplikaci. MainWndProc obsahuje blok zpracovßnφ v²jimek, p°edßvajφcφ zßznam zprßvy z Windows virtußlnφ metod∞ nazvanΘ WndProc a zpracovßvajφcφ libovolnΘ v²jimky volßnφm metody HandleException objektu aplikace. MainWndProc je nevirtußlnφ metoda, kterß neobsahuje specißlnφ zpracovßnφ n∞kter²ch zprßv. P°izp∙sobenφ provßdφme a╛ v WndProc, nebo╗ ka╛d² typ komponenty m∙╛e p°edefinovat tuto metodu podle sv²ch pot°eb.
Metody WndProc testujφ zda zpracovßvßnφ nemß ignorovat n∞kterΘ neoΦekßvanΘ zprßvy. Nap°. TWinControl b∞hem ta╛enφ komponenty ignorujφ udßlosti klßvesnice. WndProc p°edßvß udßlosti klßvesnice pouze kdy╛ komponenta nenφ ta╛ena. KoneΦn∞ WndProc volß Dispatch, nevirtußlnφ metodu zd∞d∞nou od TObject, urΦujφcφ kterß metoda bude volßna k obsluze zprßvy.
Dispatch pou╛φvß polo╛ku Msg zßznamu zprßvy k urΦenφ jak zpracovat jistou zprßvu. Jestli╛e komponenta pro zprßvu nemß obsluhu pak Dispatch volß DefaultHandler.

Zm∞na zpracovßnφ zprßv

P°ed zm∞nou zpracovßnφ zprßv na╣φ komponenty se ujistφme, ╛e to skuteΦn∞ musφme ud∞lat. C++ Builder p°eklßdß mnoho zprßv Windows na udßlosti, kterΘ jak tv∙rce komponenty, tak i u╛ivatel komponenty m∙╛e obslou╛it. LΘpe ne╛ m∞nit chovßnφ zpracovßnφ zprßv je m∞nit chovßnφ zpracovßnφ udßlostφ. Ke zm∞n∞ zpracovßnφ zprßvy p°epφ╣eme metodu zpracovßvajφcφ zprßvu. M∙╛eme takΘ zabrßnit komponent∞ ve zpracovßnφ zprßvy p°i jist²ch situacφch zachycenφm zprßvy.
Pro zm∞nu zp∙sobu zpracovßnφ jistΘ zprßvy komponentou p°epφ╣eme metodu zpracovßnφ zprßvy pro tuto zprßvu. Jestli╛e komponenta nemß obsluhu pro jistou zprßvu, musφme deklarovat novou metodu obsluhy zprßvy. K p°epsßnφ metody zpracovßnφ zprßv, deklarujeme novou metodu v chrßn∞nφ Φßsti na╣i komponenty a to se stejn²m jmΘnem jako mß metoda kterou p°episujeme a mapujeme metodu na zprßvu pomocφ t°φ maker. Tato makra majφ tvar:
BEGIN_MESSAGE_MAP
  MESSAGE_HANDLER(parametr1, parametr2, parametr3)
END_MESSAGE_MAP
Parametr1 je index zprßvy definovan² Windows, parametr2 je typ struktury zprßvy a parametr3 je jmΘno metody zprßvy. Mezi BEGIN_MESSAGE_MAP a END_MESSAGE_MAP m∙╛eme vlo╛it n∞kolik maker MESSAGE_HANDLER.
Nap°. k p°epsßnφ obsluhy zprßvy WM_PAINT v komponent∞, op∞tovn∞ deklarujeme metodu WMPaint a t°emi makry mapujeme metodu na zprßvu WM_PAINT:
class PACKAGE TMojeKomponenta : public TComponent
{
protected:
  void __fastcall WMPaint(TWMPaint *Message);
BEGIN_MESSAGE_MAP
  MESSAGE_HANDLER(WM_PAINT, TWMPaint, WMPaint)
END_MESSAGE_MAP(TComponent)
};
Pouze uvnit° metody zpracovßnφ zprßvy mß na╣e komponenta p°φstup ke v╣em parametr∙m zßznamu zprßvy. Jeliko╛ zprßva je v╛dy parametr volan² odkazem, obsluha m∙╛e zm∞nit v p°φpad∞ pot°eby hodnoty parametr∙. ╚asto m∞nφme pouze parametr nßvratovΘ hodnoty zprßvy: hodnotu vrßcenou volßnφm SendMessage, kterß zasφlß zprßvu.
Proto╛e se typ parametru Message metody zpracovßnφ zprßvy m∞nφ s typem zpracovßvanΘ zprßvy, je nutnΘ se podφvat do dokumentace Windows na jmΘna a v²znam jednotliv²ch parametr∙. Jestli╛e se z n∞jakΘho d∙vodu pot°ebujeme odkazovat na parametr zprßvy jeho star²m stylem jmen (wParam, lParam apod.), m∙╛eme p°etypovat Message na obecn² typ TMessage, kter² pou╛φvß tato jmΘna parametr∙.
V jist²ch situacφch, m∙╛eme chtφt, aby na╣e komponenta ignorovala jistΘ zprßvy. Tj. m∙╛eme chtφt zabrßnit komponent∞ ve zpracovßnφ zprßvy svou obsluhou. Zachycenφ zprßvy provedeme p°epsßnφm virtußlnφ metody WndProc. Metoda WndProc filtruje zprßvy p°ed jejich p°edßnφm metod∞ Dispatch, kterß urΦuje metodu zpracujφcφ zprßvu. P°epsßnφm WndProc, m∙╛eme zm∞nit filtr zprßv p°ed jejich zpracovßnφm. P°epsßnφ WndProc provßdφme takto:
void __fastcall TMujOvladac::WndProc(TMessage* Message)
{
  // test na urΦenφ, zda pokraΦovat ve zpracovßnφ
  TWinControl->WndProc(Message);
}
Nßsleduje Φßst metody WndProc pro TControl jak je implementovßna ve VCL v Object Pascalu. TControl definuje rozsah zprßv my╣i, kterΘ jsou filtrovßny, kdy╛ u╛ivatel provßdφ ta╛enφ. P°epsßnφ WndProc tomu pomßhß dv∞ma zp∙soby: M∙╛eme filtrovat interval zprßv namφsto specifikovßnφ obsluhy pro ka╛dou z nich a m∙╛eme zabrßnit zpracovßnφ zprßv v celku a obsluhy nejsou nikdy volßny.
procedure TControl.WndProc(var Message: TMessage);
begin
  ...
  if (Message.Msg>=WM_MOUSEFIRST)and(Message.Msg<=WM_MOUSELAST) then
    if Dragging then    {specißlnφ zpracovßnφ ta╛enφ}
      DragMouseMsg(TWMouse(Message))
    else
      ...      {normßlnφ zpracovßnφ}
    end;
  ...      {jinak normßlnφ proces}
end;

Vytvß°enφ nov²ch obsluh zprßv

P°esto╛e C++ Builder poskytuje obsluhy pro mnoho zprßv Windows, m∙╛eme se dostat do situace, kdy budeme pot°ebovat vytvo°it novou obsluhu zprßv a to kdy╛ definujeme svou vlastnφ zprßvu. Prßce s u╛ivatelsk²mi zprßvami mß dva aspekty: definovßnφ svΘ vlastnφ zprßvy a deklarovßnφ novΘ metody zpracovßnφ zprßvy.
N∞kolik standardnφch komponent definuje zprßvy pro internφ pou╛itφ. Smyslem pro definovßnφ zprßv je vysφlßnφ informacφ nepodporovan²ch standardnφmi zprßvami Windows a oznßmenφ zm∞ny stavu. Definovßnφ zprßvy je dvou krokov² proces: deklarujeme identifikßtor zprßvy a deklarujeme typ zßznamu zprßvy.
Identifikßtor zprßvy je celoΦφselnß konstanta. Windows rezervuje zprßvy do 1024 pro svoje vlastnφ pou╛itφ a tak kdy╛ deklarujeme svou vlastnφ zprßvu musφme zaΦφt nad touto ·rovnφ. Konstanta WM_APP reprezentuje poΦßteΦnφ Φφslo pro u╛ivatelem definovanΘ zprßvy. Kdy╛ definujeme identifikßtory zprßv, musφme zaΦφt od WM_APP. Musφme si b²t v∞domi, ╛e n∞kterΘ standardnφ ovladaΦe Windows pou╛φvajφ zprßvy v rozsahu u╛ivatelsk²ch definic. Jsou to seznamy, kombinovanß okna, editaΦnφ okna a tlaΦφtka. Jestli╛e odvozujeme komponentu od n∞kterΘ z nich a chceme definovat novou zprßvu pro nφ, je pot°eba se podφvat do souboru MESSAGES.HPP a zjistit, kterΘ zprßvy Windows jsou skuteΦn∞ definovanΘ pro tyto ovladaΦe.
Nßsledujφcφ k≤d ukazuje dv∞ u╛ivatelem definovanΘ zprßvy:
#define WM_MOJEPRVNIZPRAVA (WM_APP + 400)
#define WM_MOJEDRUHAZPRAVA (WM_APP + 401)
Jestli╛e chceme dßt smysluplnß jmΘna parametr∙m na╣φ zprßvy, je pot°eba deklarovat typ struktury zprßvy pro tuto zprßvu. Struktura zprßvy je typ p°edßvanΘho parametru metod∞ zpracovßnφ zprßvy. Jestli╛e nepou╛φvßme parametr zprßvy nebo jestli╛e chceme pou╛φt star² zp∙sob zßpisu parametr∙ (wParam, lParam apod.), m∙╛eme pou╛φt implicitnφ zßznam zprßvy TMessage. P°i deklaraci typu struktury zprßvy pou╛φvßme tyto konvence: JmΘno typu zßznamu v╛dy zaΦφnß T a nßsleduje jmΘno zprßvy. JmΘno prvnφ polo╛ky v zßznamu je Msg a je typu TMsgParam. Definujeme dal╣φ dv∞ slabiky, kterΘ odpovφdajφ wParam (dal╣φ dv∞ slabiky jsou nepou╛ity). Definujeme dal╣φ Φty°i slabiky, kterΘ odpovφdajφ lParam. Nakonec p°idßme polo╛ku nazvanou Result, kterß je typu Longint.
Nßsleduje zßznam zprßv pro v╣echny zprßvy my╣i, TWMKey:
struct TWMKey
{
  Cardinal Msg;       // prvnφ parametr je identifikßtor zprßvy
  Word CharCode;      // toto je wParam
  Word Unused;
  Longint KeyData;    // toto je lParam
  Longint Result;     // toto je datovß slo╛ka Result
};
Jsou dv∞ situace, kterΘ vy╛adujφ deklarovßnφ novΘ metody zpracovßnφ zprßvy: na╣e komponenta vy╛aduje zpracovßnφ zprßvy Windows, kterß nenφ zpracovßvßna standardnφmi komponentami a definovßnφ svΘ vlastnφ zprßvy pro pou╛itφ v na╣ich komponentßch.
Deklaraci metody zpracovßnφ zprßvy provedeme takto: Deklarujeme metodu v chrßn∞nΘ Φßsti deklarace t°φdy komponenty. Ujistφme se, ╛e metoda vracφ void. Nazveme metodu po zpracovßvanΘ zprßv∞, ale bez znak∙ podtr╛enφ. P°edßvßme ukazatel nazvan² Message, typu struktury zprßvy. Mapujeme metodu na zprßvu pou╛itφm maker. Zapφ╣eme k≤d pro specifickΘ zpracovßnφ v komponent∞. Volßme zd∞d∞nou obsluhu zprßvy.
Nßsleduje deklarace obsluhy zprßvy pro u╛ivatelem definovanou zprßvu nazvanou CM_CHANGECOLOR:
#define CM_CHANGECOLOR (WM_APP + 400)
class PACKAGE TMojeKomponenta : public TControl
{
protected:
  void __fastcall CMChangeColor(TMessage &Message);
BEGIN_MESSAGE_MAP
  MESSAGE_HANDLER(CM_CHANGECOLOR, TMessage, CMChangeColor)
END_MESSAGE_MAP(TComtrol)

void __fastcall TMojeKomponenta::CMChangeColor(TMessage &Message)
{
  Color = Message.LParam;
  TControl::CMChangeColor(Message);
}


  1. ZaΦneme s v²vojem dal╣φ komponenty. C++ Builder poskytuje n∞kolik typ∙ abstraktnφch komponent, kterΘ m∙╛eme pou╛φt jako zßklad pro p°izp∙sobovßnφ komponent. V tomto bod∞ si ukß╛eme jak vytvo°it mal² jednom∞sφΦnφ kalendß° na zßklad∞ komponenty m°φ╛ky TCustomGrid. Vytvo°enφ kalendß°e provedeme v sedmi krocφch: vytvo°enφ a registrace komponenty, zve°ejn∞nφ zd∞d∞n²ch vlastnostφ, zm∞nu inicializaΦnφch hodnot, zm∞na velikosti bun∞k, vypln∞nφ bun∞k, navigaci m∞sφc∙ a rok∙ a navigaci dnφ.

  2. Vytvo°enß komponenta se bude podobat komponent∞ TCalendar ze strßnky Samples Palety komponent.
    Pou╛ijeme ruΦnφ postup vytvß°enφ a registrace komponenty s t∞mito specifikami: jednotku komponenty nazveme CalSamp, odvodφme nov² typ komponenty nazvan² TSampleCalendar od TCustomGrid a registrujeme TSampleCalendar na strßnce Samples Palety komponent. V²sledkem tΘto prßce je (musφme p°idat i hlaviΦkov² soubor Grids.hpp):
    #ifndef CALSAMPH
    #define CALSAMPH
    #include <vcl\sysutils.hpp>
    #include <vcl\controls.hpp>
    #include <vcl\classes.hpp>
    #include <vcl\forms.hpp>
    #include <vcl\grids.hpp>
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    private:
    protected:
    public:
    __published:
    };
    #endif
    Soubor CPP vypadß takto:
    #include <vcl\vcl.h>
    #pragma hdrstop
    #include "CALSAMP.h"
    #pragma package(smart_init);
    static inline TSampleCalendar *ValidCtrCheck()
    {
      return new TSampleCalendar(NULL);
    }
    namespace Calsamp
    {
      void __fastcall PACKAGE Register()
      {
        TComponentClass classes[1] = {__classid(TSampleCalendar)};
        RegisterComponents("Samples", classes, 0);
      }
    }
  3. Abstraktnφ komponenta m°φ╛ky TCustomGrid poskytuje velk² poΦet chrßn∞n²ch vlastnostφ. M∙╛eme zvolit, kterΘ z t∞chto vlastnostφ chceme zp°φstupnit v na╣i vytvß°enΘ komponent∞. K zve°ejn∞nφ zd∞d∞n²ch chrßn∞n²ch vlastnostφ, op∞tovn∞ deklarujeme vlastnosti ve zve°ej≥ovanΘ Φßsti deklarace na╣i komponenty. Pro kalendß° zve°ejnφme nßsledujφcφ vlastnosti a udßlosti:

  4. class PACKAGE TSampleCalendar : public TCustomGrid
    {
    __published:
      __property Align;
      __property BorderStyle;
      __property Color;
      __property Ctl3D;
      __property Font;
      __property GridLineWidth;
      __property ParentColor;
      __property ParentCtl3D;
      __property ParentFont;
      __property OnClick;
      __property OnDblClick;
      __property OnDragDrop;
      __property OnDragOver;
      __property OnEndDrag;
      __property OnKeyDown;
      __property OnKeyPress;
      __property OnKeyUp;
    };
    Existuje je╣t∞ n∞kolik dal╣φch vlastnostφ, kterΘ jsou takΘ zve°ej≥ovanΘ, ale kterΘ pro kalendß° nepot°ebujeme. P°φkladem je vlastnost Options, kterß umo╛≥uje u╛ivateli nap°. volit typ m°φ╛ky.
  5. Kalendß° je m°φ╛ka s pevn²m poΦtem °ßdk∙ a sloupc∙, i kdy╛ ne v╣echny °ßdky v╛dy obsahujφ data. Z tohoto d∙vodu, jsme nezve°ejnili vlastnosti m°φ╛ky ColCount a RowCount, nebo╗ je jasnΘ, ╛e u╛ivatel kalendß°e nebude chtφt zobrazovat nic jinΘho ne╛ sedm dnφ v t²dnu. NicmΘn∞ musφme nastavit poΦßteΦnφ hodnoty t∞chto vlastnostφ tak, aby t²den m∞l v╛dy sedm dnφ. Ke zm∞n∞ poΦßteΦnφch hodnot vlastnostφ komponenty, p°epφ╣eme konstruktor a nastavφme po╛adovanΘ hodnoty. Musφme takΘ p°edefinovat Φirou metodu DrawCell. Dostaneme toto:

  6. class PACKAGE TSampleCalendar : public TCustomGrid
    {
    protected:
      virtual void __fastcall DrawCell(long ACol, long ARow,
                          const Windows::TRect &Rect, TGridDrawState AState);
    public:
      __fastcall TSampleCalendar(TComponent* Owner);
    };
    Do souboru CPP zapφ╣eme konstruktor:
    __fastcall TSampleCalendar::TSampleCalendar(TComponent* Owner)
     : TCustomGrid(Owner)
    {
      ColCount = 7;
      RowCount = 7;
      FixedCols = 0;
      FixedRows = 1;
      ScrollBars = ssNone;
      Options = (Options >> goRangeSelect) << goDrawFocusSelected;
    }
    void __fastcall TSampleCalendar::DrawCell(long ACol, long ARow,
                          const Windows::TRect &Rect, TGridDrawState AState)
    {
    }
    Nynφ, kalendß° mß sedm °ßdk∙ a sedm sloupc∙ s pevn²m hornφm °ßdkem. Pravd∞podobn∞ budeme chtφt zm∞nit velikost ovladaΦe a ud∞lat v╣echny bu≥ky viditelnΘ. Dßle si ukß╛eme jak reagovat na zprßvu zm∞ny velikosti od Windows k urΦenφ velikosti bun∞k.
  7. Kdy╛ u╛ivatel nebo aplikace zm∞nφ velikost okna nebo ovladaΦe, Windows zasφlß zprßvu nazvanou WM_SIZE zm∞n∞nΘmu oknu nebo ovladaΦi, kterΘ tak m∙╛e nastavit sv∙j obraz na novou velikost. Na╣e komponenta m∙╛e reagovat na tuto zprßvu zm∞nou velikosti bun∞k a zaplnit tak celou plochu ovladaΦe. K reakci na zprßvu WM_SIZE, p°idßme do komponenty metodu reagujφcφ na zprßvu.

  8. V na╣em p°φpad∞ ovladaΦ kalendß°e vy╛aduje k reakci na WM_SIZE p°idat chrßn∞nou metodu nazvanou WMSize °φzenou indexem zprßvy WM_SIZE, a zapsat metodu, kterß vypoΦφtß pot°ebnΘ rozm∞ry bun∞k, co╛ umo╛nφ aby v╣echny bu≥ky byly viditelnΘ:
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    protected:
      void __fastcall WMSize(TWMSize& Message);
      BEGIN_MESSAGE_MAP
        MESSAGE_HANDLER(WM_SIZE, TWMSize, WMSize);
      END_MESSAGE_MAP(TCustomGrid);
    };

    void __fastcall TSampleCalendar::WMSize(TWMSize &Message)
    {
      int GridLines;
      GridLines = 6 * GridLineWidth;
      DefaultColWidth   = (Message.Width - GridLines) / 7;
      DefaultRowHeight  = (Message.Height - GridLines) / 7;
    }
    Nynφ, jestli╛e p°idßme kalendß° na formulß° a zm∞nφme jeho velikost, je v╛dy zobrazen tak, aby jeho bu≥ky ·pln∞ zaplnili plochu ovladaΦe. Zatφm ale kalendß° neobsahuje data.

  9. OvladaΦ m°φ╛ky je zapl≥ovßn bu≥ku po bu≥ce. V p°φpad∞ kalendß°e to znamenß vypoΦφtat datum (je-li) pro ka╛dou bu≥ku. Implicitnφ zapl≥ovßnφ bun∞k provßdφme virtußlnφ chrßn∞nou metodou DrawCell. Knihovna b∞hu programu obsahuje pole s krßtk²mi jmΘny dnφ a my je pou╛ijeme v nadpisu ka╛dΘho sloupce:

  10. class PACKAGE TSampleCalendar : public TCustomGrid
    {
    protected:
      virtual void __fastcall DrawCell(Longint ACol, Longint ARow,
                              const TRect &ARect, TGridDrawState AState);
    };

    void __fastcall TSampleCalendar::DrawCell(long ACol, long ARow,
                                 const TRect &ARect, TGridDrawState AState)
    {
      String TheText;
      int TempDay;
      if (ARow == 0) TheText = ShortDayNames[ACol+1];
      else
      {
        TheText = "";
        TempDay = DayNum(ACol, ARow);
        if (TempDay != -1) TheText = IntToStr(TempDay);
      }
      Canvas->TextRect(ARect, ARect.Left + (ARect.Right - ARect.Left ?
        Canvas->TextWidth(TheText)) / 2, ARect.Top + (ARect.Bottom ?
        ARect.Top - Canvas->TextHeight(TheText)) / 2, TheText);
    };

  11. Pro ovladaΦ kalendß°e je vhodnΘ, aby u╛ivatel a aplikace m∞li mechanismus pro nastavovßnφ dne, m∞sφce a roku. C++ Builder uklßdß datum a Φas v prom∞nnΘ typu TDateTime. TDateTime je zak≤dovanß Φφselnß reprezentace datumu a Φasu, kterß je vhodnß pro poΦφtaΦovΘ zpracovßnφ, ale nenφ pou╛itelnß pro pou╛itφ Φlov∞kem. M∙╛eme tedy uklßdat datum v zak≤dovanΘm tvaru, poskytnout p°φstup k tΘto hodnot∞ p°i b∞hu aplikace, ale takΘ poskytnout vlastnosti Day, Month a Year, kterΘ u╛ivatel komponenty m∙╛e nastavit p°i nßvrhu.

  12. K ulo╛enφ datumu pro kalendß°, pot°ebujeme soukromou polo╛ku k ulo╛enφ datumu a vlastnosti b∞hu programu, kterΘ poskytujφ p°φstup k tomuto datumu. P°idßnφ internφho datumu ke kalendß°i vy╛aduje t°i kroky: V prvnφm deklarujeme soukromou polo╛ku k ulo╛enφ datumu:
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    private:
      TDateTime FDate;
    };
    V druhΘm inicializujeme datumovou polo╛ku v konstruktoru:
    __fastcall TSampleCalendar::TSampleCalendar(TComponent* Owner)
     : TCustomGrid(Owner)
    {
      ...
      FDate = CurrentDate();
    }
    V poslednφm deklarujeme vlastnost b∞hu programu k poskytnutφ p°φstupu k zak≤dovanΘmu datumu. Pot°ebujeme metodu pro nastavenφ datumu, proto╛e nastavenφ datumu vy╛aduje aktualizaci obrazu ovladaΦe na obrazovce:
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    private:
      void __fastcall SetCalendarDate(TDateTime Value);
    public:
      __property TDateTime CalendarDate={read=FDate,write=SetCalendarDate,nodefault};
    };

    void __fastcall TSampleCalendar::SetCalendarDate(TDateTime Value)
    {
      FDate = Value;
      Refresh();
    }
    Zak≤dovanΘ datum je vhodnΘ pro aplikaci, ale lidΘ dßvajφ p°ednost prßci s dny, m∞sφci a roky. M∙╛eme poskytnout alternativnφ p°φstup k t∞mto prvk∙m ulo╛enΘho zak≤dovanΘho datumu vytvo°enφm vlastnostφ. Proto╛e ka╛d² prvek datumu (den, m∞sφc a rok) je celΘ Φφslo a jeliko╛ nastavenφ ka╛dΘho z nich vy╛aduje dek≤dovßnφ datumu, m∙╛eme se vyhnout opakovßnφ k≤du sdφlenφm implementaΦnφch metod pro v╣echny t°i vlastnosti. Tj. m∙╛eme zapsat dv∞ metody, prvnφ pro Φtenφ prvku a druhou pro jeho zßpis, a pou╛φt tyto metody k zφskßnφ a nastavovßnφ v╣ech t°φ vlastnostφ. Deklarujeme t°i vlastnosti a ka╛dΘ p°i°adφme jedineΦn² index:
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    public:
      __property int Day = {read=GetDateElement, write=SetDateElement,
                            index=3, nodefault};
      __property int Month = {read=GetDateElement, write=SetDateElement,
                              index=2, nodefault};
      __property int Year = {read=GetDateElement, write=SetDateElement,
                             index=1, nodefault};
    Dßle zapφ╣eme deklarace a definice p°φstupov²ch metod, pracujφcφch s hodnotami podle hodnoty indexu:
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    private:
      int __fastcall GetDateElement(int Index);
      void __fastcall SetDateElement(int Index, int Value);
    }

    int __fastcall TSampleCalendar::GetDateElement(int Index)
    {
      unsigned short AYear, AMonth, ADay;
      int result;
      FDate.DecodeDate(&AYear, &AMonth, &ADay);
      switch (Index) {
        case 1: result = AYear; break;
        case 2: result = AMonth; break;
        case 3: result = ADay; break;
        default: result = -1;
      }
      return result;
    }

    void __fastcall TSampleCalendar::SetDateElement(int Index, int Value)
    {
      unsigned short AYear, AMonth, ADay;
      if (Value > 0) {
        FDate.DecodeDate(&AYear, &AMonth, &ADay);
        switch (Index) {
          case 1: AYear = Value; break;
          case 2: AMonth = Value; break;
          case 3: ADay = Value; break;
          default: return;
        }
      }
      FDate = TDateTime(AYear, AMonth, ADay);
      Refresh();
    }
    Nynφ m∙╛eme nastavovat den, m∞sφc a rok kalendß°e b∞hem nßvrhu pou╛itφm Inspektora objekt∙ a p°i b∞hu aplikace pou╛itφm k≤du. Zatφm je╣t∞ ale nemßme p°idan² k≤d pro zßpis datumu do bun∞k.

  13. P°idßnφ Φφsel do kalendß°e vy╛aduje n∞kolik ·vah. PoΦet dnφ v m∞sφci zßvisφ na tom, o kter² m∞sφc se jednß a zda dan² rok je p°estupn². Dßle m∞sφce zaΦφnajφ v r∙znΘm dni v t²dnu, v zßvislosti na m∞sφci a roku. V p°edchozφ Φßsti je popsßno jak zφskat aktußlnφ m∞sφc a rok. Nynφ m∙╛eme urΦit, zda specifikovan² rok je p°estupn² a poΦet dnφ v m∞sφci. K tomuto pou╛ijeme funkci IsLeapYear a pole MonthDays z hlaviΦkovΘho souboru SysUtils.

  14. Kdy╛ ji╛ mßme informace o p°estupn²ch rocφch a dnech v m∞sφci, m∙╛eme vypoΦφtat, kde v m°φ╛ce je konkrΘtnφ datum. V²poΦet je zalo╛en na dni v t²dnu, kdy m∞sφc zaΦφnß. Proto╛e pot°ebujeme ofset m∞sφce pro ka╛dou bu≥ku, je praktiΦt∞j╣φ jej vypoΦφtat pouze, kdy╛ m∞nφme m∞sφc nebo rok. Tuto hodnotu m∙╛eme ulo╛it v polo╛ce t°φdy a aktualizovat ji p°i zm∞n∞ datumu. Zapln∞nφ dn∙ do p°φslu╣n²ch bun∞k provedeme takto: P°idßme polo╛ku ofsetu m∞sφce a metodu aktualizujφcφ hodnotu polo╛ky v objektu:
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    private:
      int FMonthOffset;
    protected:
      virtual void __fastcall UpdateCalendar();
    };

    void __fastcall TSampleCalendar::UpdateCalendar()
    {
      unsigned short AYear, AMonth, ADay;
      TDateTime FirstDate;
      if ((int)FDate != 0) {
        FDate.DecodeDate(&AYear, &AMonth, &ADay);
        FirstDate = TDateTime(AYear, AMonth, 1);
        FMonthOffset = 2- FirstDate.DayOfWeek();
      }
      Refresh();
    }
    P°idßme p°φkazy do konstruktoru a metod SetCalendarDate a SetDateElement, kterΘ volajφ novou aktualizaΦnφ metodu p°i zm∞n∞ data.
    __fastcall TSampleCalendar::TSampleCalendar(TComponent* Owner)
     : TCustomGrid(Owner)
    {
      ...
      UpdateCalendar();
    }
    void __fastcall TSampleCalendar::SetCalendarDate(TDateTime Value)
    {
      FDate = Value;
      UpdateCalendar();
    }
    void __fastcall TSampleCalendar::SetDateElement(int Index, int Value)
    {
      ...
      FDate = TDateTime(AYear, AMonth, ADay);
      UpdateCalendar();
    }
    P°idßme ke kalendß°i metodu, kterß vracφ Φφslo dne, kdy╛ p°edßme sou°adnice °ßdku a sloupce bu≥ky:
    int __fastcall TSampleCalendar::DayNum(int ACol, int ARow)
    {
      int result = FMonthOffset + ACol + (ARow - 1) * 7;
      if ((result < 1) || (result > MonthDays[IsLeapYear(Year)][Month])) result = -1;
      return result;
    }
    Nesmφme zapomenou p°idat deklaraci DayNum do deklarace t°φdy komponenty.
    Nynφ, kdy╛ ji╛ vφme ve kterΘ bu≥ce kterΘ datum je, m∙╛eme doplnit DrawCell k pln∞nφ bu≥ky datem:
    void __fastcall TSampleCalendar::DrawCell(long ACol, long ARow,
                               const TRect &ARect, TGridDrawState AState)
    {
      String  TheText;
      int TempDay;
      if (ARow == 0) TheText = ShortDayNames[ACol+1];
      else {
        TheText = "";
        TempDay = DayNum(ACol, ARow);
        if (TempDay != -1) TheText = IntToStr(TempDay);
      }
      Canvas->TextRect(ARect, ARect.Left + (ARect.Right - ARect.Left ?
        Canvas->TextWidth(TheText)) / 2, ARect.Top + (ARect.Bottom ?
        ARect.Top - Canvas->TextHeight(TheText)) / 2, TheText);
    };
    Jestli╛e nynφ op∞tovn∞ instalujeme komponentu a umφstφme ji na formulß°, vidφme sprßvnΘ informace pro souΦasn² m∞sφc.

  15. Kdy╛ ji╛ mßme Φφsla v bu≥kßch kalendß°e, je vhodnΘ p°esunout vybranou bu≥ku na bu≥ku se souΦasn²m datumem. Je tedy pot°eba nastavit vlastnosti Row a Col, kdy╛ vytvß°φme kalendß° a kdy╛ zm∞nφme datum. K nastavenφ v²b∞ru na tento den, zm∞nφme metodu UpdateCalendar tak, aby nastavila ob∞ vlastnosti p°ed volßnφm Refresh:

  16. void __fastcall TSampleCalendar::UpdateCalendar()
    {
      unsigned short AYear, AMonth, ADay;
      TDateTime FirstDate;
      if ((int)FDate != 0) {
        FDate.DecodeDate(&AYear, &AMonth, &ADay);
        FirstDate = TDateTime(AYear, AMonth, 1);
        FMonthOffset = 1- FirstDate.DayOfWeek();
        Row = (ADay - FMonthOffset) / 7 + 1;
        Col = (ADay - FMonthOffset) % 7;
      }
      Refresh();
    }
  17. Vlastnosti jsou u╛iteΦnΘ pro manipulace s komponentami, obzvlß╣t∞ b∞hem nßvrhu. Jsou ale typy manipulacφ, kterΘ Φasto ovliv≥ujφ vφce ne╛ jednu vlastnost, a je tedy u╛iteΦnΘ pro n∞ vytvo°it metodu. P°φkladem takovΘho manipulace je slu╛ba kalendß°e ?nßsledujφcφ m∞sφc?. Zpracovßnφ m∞sφce v rßmci m∞sφc∙ a p°φpadnß inkrementace roku je jednoduchß, ale velmi v²hodnß pro v²vojß°e pou╛φvajφcφ komponentu. Jedinou nev²hodou zaobalenφ manipulacφ do metody je, ╛e metody jsou p°φstupnΘ pouze za b∞hu aplikace.

  18. Pro kalendß° p°idßme nßsledujφcφ Φty°i metody pro nßsledujφcφ a p°edchozφ m∞sφc i rok:
    void __fastcall TSampleCalendar::NextMonth()
    {
      DecodeDate(IncMonth(CalendarDate, 1), Year, Month, Day);
    }
    void __fastcall TSampleCalendar::PrevMonth()
    {
      DecodeDate(IncMonth(CalendarDate, -1), Year, Month, Day);
    }
    void __fastcall TSampleCalendar::NextYear()
    {
      DecodeDate(IncMonth(CalendarDate, 12), Year, Month, Day);
    }
    void __fastcall TSampleCalendar::PrevYear()
    {
      DecodeDate(IncMonth(CalendarDate, -12), Year, Month, Day);
    }
    Musφme takΘ p°idat deklarace nov²ch metod k deklaraci t°φdy kalendß°e. Nynφ, kdy╛ vytvß°φme aplikaci, kterß pou╛φvß komponentu kalendß°e, m∙╛eme snadno implementovat prochßzenφ p°es m∞sφce nebo roky.
  19. K danΘm m∞sφci jsou mo╛nΘ dva zp∙soby navigace p°es dny. Prvnφ je pou╛itφ kurzorov²ch klßves a druh² je reakce na kliknutφ my╣i. Standardnφ komponenta m°φ╛ky zpracovßvß oboje jako kliknutφ. Tj. pou╛itφ kurzorovΘ klßvesy je chßpßno jako kliknutφ na odpovφdajφcφ bu≥ku.

  20. Zd∞d∞nΘ chovßnφ m°φ╛ky zpracovßvß p°esun v²b∞ru v reakci na stisknutφ kurzorovΘ klßvesy nebo kliknutφ, ale jestli╛e chceme zm∞nit vybran² den, musφme toto implicitnφ chovßnφ modifikovat. K obsluze p°esunu v kalendß°i, p°epφ╣eme metodu Click m°φ╛ky. Kdy╛ p°episujeme metodu jako je Click, musφme v╛dy vlo╛it volßnφ zd∞d∞nΘ metody, a neztratit tak standardnφ chovßnφ. Nßsleduje p°epsanß metoda Click pro m°φ╛ku kalendß°e. Nesmφme zapomenout p°idat deklaraci Click do TSampleCalendar:
    void __fastcall TSampleCalendar::Click()
    {
      TCustomGrid::Click();
      int TempDay = DayNum(Col, Row);
      if (TempDay != -1) Day = TempDay;
    }
    Nynφ, kdy╛ u╛ivatel m∙╛e zm∞nit datum v kalendß°i, musφme zajistit, aby aplikace mohla reagovat na tuto zm∞nu. Do TSampleCalendar p°idßme udßlost OnChange. Musφme deklarovat udßlost, polo╛ku k ulo╛enφ udßlosti a virtußlnφ metodu k volßnφ udßlosti:
    class PACKAGE TSampleCalendar : public TCustomGrid
    {
    private:
      TNotifyEvent FOnChange;
    protected:
      virtual void __fastcall Change();
    __published:
      __property TNotifyEvent OnChange = {read=FOnChange, write=FOnChange};
    };
    Dßle zapφ╣eme metodu Change:
    void __fastcall TSampleCalendar::Change()
    {
      if (FOnChange != NULL) FOnChange(this);
    }
    Na konec metod SetCalendarDate a SetDateElement musφme je╣t∞ p°idat p°φkaz volßnφ metody Change:
    void __fastcall TSampleCalendar::SetCalendarDate(TDateTime Value)
    {
      FDate = Value;
      UpdateCalendar();
      Change();
    }
    void __fastcall TSampleCalendar::SetDateElement(int Index, int Value)
    {
      ...
      FDate = TDateTime(AYear, AMonth, ADay);
      UpdateCalendar();
      Change();
    }
    Aplikace pou╛φvajφcφ komponentu m∙╛e nynφ reagovat na zm∞ny data komponenty p°ipojenφm obsluhy k udßlosti OnChange.
    Kdy╛ p°echßzφme po dnech v kalendß°i, zjistφme nesprßvnΘ chovßnφ p°i v²b∞ru prßzdnΘ bu≥ky. Kalendß° umo╛≥uje p°esunutφ na prßzdnou bu≥ku, ale nem∞nφ datum v kalendß°i. Nynφ zakß╛eme v²b∞r prßzdn²ch bun∞k. K urΦenφ, zda danß bu≥ka je vybφratelnß, p°edefinujeme metodu SelectCell m°φ╛ky. SelectCell je funkce, kterß jako parametry p°ebφrß Φφslo °ßdku a sloupce a vracφ logickou hodnotu indikujφcφ zda specifikovanß bu≥ka je vybφratelnß. Metoda SelectCell bude nynφ vypadat takto:
    bool __fastcall TSampleCalendar::SelectCell(long ACol, long ARow)
    {
      if (DayNum(ACol, ARow) == -1) return false;
      else return TCustomGrid::SelectCell(ACol, ARow);
    }
    Tφm jsme dokonΦili tvorbu na╣φ komponenty.
7. Zpracovßnφ zprßv