Živě.cz o počítačích a internetu

Tipy a triky v Delphi, díl 31. - Detekce změny času; Zachycení změny obsahu schránky; Pohyb okna; Ošetření neočekávaných chyb

Jan Šindelář - 27.2.2002

Detekce změny času

Dnes začneme hezky pozvolna od velmi jednoduchého příkladu, na kterém si ukážeme, jak je možné detekovat změnu systémového času či data. Pochopitelně je myšlena změna času nebo data, která je vyvolána "uměle" (například uživatelem). Někteří uživatelé se tímto způsobem například pokoušejí obejít časové omezení shareware aplikací (většinou marně), ale změna času je pochopitelně běžnou legitimní činností a čas od času je potřeba systémové hodiny seřídit (pokud k tomu nepoužíváte například nějaký server). Jestliže je například vaše aplikace takového druhu, že by nešetrné změny času mohly způsobit nechtěný efekt, je dobré si to pohlídat. Jak jinak, opět se bude jednat o "odchycení" zprávy systému, konkrétně WM_TIMECHANGE.

Zde je tedy zdrojový kód:

.
.
.
private
  { Private declarations }
  procedure WMTimeChange(var Msg: TMessage); message WM_TIMECHANGE;
.
.
.

procedure TForm1.WMTimeChange(var Msg: TMessage);
begin
inherited;
ShowMessage(`Došlo ke změně systémového času/data.`);
end;

Zachycení změny obsahu schránky

I druhý dnešní tip bude založen na odchycení zprávy systému. Tentokrát to bude detekce změny obsahu schránky, což je možná ještě o něco užitečnější tip, než náš dnešní úvodní příklad. Jistě sami přijdete na řadu využití, takže bez dalších řečí si rovnou ukažme zdrojový kód:

.
.
.
public
  { Public declarations }
  procedure ClipBoardChanged(var Message: TMessage); message WM_DRAWCLIPBOARD;
.
.
.

procedure TForm1.ClipBoardChanged(var Message: TMessage);
begin
ShowMessage(`Došlo ke změně obsahu schránky`);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
SetClipboardViewer(Handle);
end;

Pohyb okna

Rovněž pohyb okna aplikace je velmi zajímavá a hlavně detekovatelná činnost (či lépe řečeno stav) a samozřejmě i v tomto případě půjde o zprávy systému. Využití je taktéž široké, namátkou mě napadá třeba využití při dnes velmi oblíbeném "přichytávání" či "přilepování" okna aplikace k okrajům pracovní plochy (viz. například Winamp). Zde je však třeba upozornit na jeden detail. Jak uvidíte později ve zdrojovém kódu, při pohybu okna jsou čteny aktuální souřadnice okna (které jsou v naší ukázce vypisovány do titulkového pruhu formuláře). Těch se dá právě využít například k již výše zmíněnému efektu přichytávání okna na okraje pracovní plochy. Jedná se o souřadnice levého horního rohu formuláře, avšak bez titulkového pruhu. Souřadnice 0,0 jsou tedy "o něco níže", než by se na první pohled mohlo zdá. Nicméně zpět k našemu původnímu záměru odchytit pohyb okna (příklad s přilepováním na okraje plochy si ukážeme snad jindy, ale věřím že jej každý zvládne sám). Zdrojový kód tedy vypadá takto:

.
.
.
private
  { Private declarations }
  procedure WndMove(var msg: TMessage); message WM_Move;
.
.
.

procedure TForm1.WndMove(var msg: TMessage);
begin
Form1.Caption := `Pozice okna x: ` + IntToStr(longrec(Msg.LParam).lo) + ` y: ` + IntToStr(longrec(Msg.LParam).hi);
end;

Ošetření neočekávaných chyb

Jistě znáte starou, klasickou a pravdivou poučku, že v každém programu jsou chyby. Chybám v programech se prostě úplně na 100 % nevyhneme nikdy, ale přesto dobře víte, že prostřednictvím definování určitých výjimek existuje možnost, jak vzniklé chyby podchytit. Tedy v tom případě, že víte že může nebo nemusí dojít k nějakému chybovému stavu, upravíte kód tak, aby s oběma možnostmi počítal. V dnešních běžně používaných programovacích nástrojích je tento systém výjimek (Exception) propracován velmi dobře, takže není problém jej dostatečně využít. I když aplikace přímo nezkolabuje a program se právě prostřednictvím těchto výjimek snaží jaksi "zachránit", i tak se zobrazí "ošklivý" dialog o tom, že program vyvolal výjimku na adrese...a tak dále. Pokud se vám to nelíbí a raději byste chtěli, aby se zobrazilo vaše vlastní hlášení pro uživatele, pak si ukážeme jak na to.

Vlastně by stačilo celý příklad zjednodušit a napsat si pouze vlastní obsluhu výjimky na jeden řádek, která by zobrazila například jednoduché upozornění prostřednictvím ShowMessage a v podstatě to vlastně i náš příklad dělá, ale ještě navíc jako "bonus" zapisuje vzniklé chyby do textového souboru - logu. Zde se nabízí například srovnání s tím, co možná někteří již znáte z Windows XP, kde se při nějaké neočekávané chybě systému či aplikací zobrazí informační dialog, který vám nabízí odeslání informací o chybě rovnou "domů" do Microsoftu. Samozřejmě že náš příklad se nemůže "error reportu" z XPček rovnat, protože v něm jsou obsaženy výpisy paměti a mnoho dalších informací. My se spokojíme pouze s tím, že se nám zapíše pouze typ výjimky, oblast paměti a modul, který ji způsobil. Jedná se vlastně přesně o ty informace, které by se bývaly zobrazily v dialogu, pokud bychom si výjimku neošetřili sami. Avšak uživatel - laik bude jistě méně zmaten, pokud se mu zobrazí náš vlastní dialog s vysvětlením, co se stalo. A můžeme též připojit žádost na uživatele, aby nás jako autora na vzniklou chybu upozornil a případně zaslal vytvořený soubor s popisem chyby, který se vytvořil. Na něco podobného, tedy bez té fáze odesílání či vytváření záznamu o chybě, můžete narazit například v oblíbeném Windows Commanderu.

Ale dost zbytečných řečí, pojďme na náš příklad. Jak bylo řečeno, při vzniku chyby, která není ošetřena jiným způsobem, zobrazí dialogové okno s upozorněním pro uživatele a zapíše popis do souboru error.log do adresáře s aplikací. Zdrojový kód tedy obsahuje vlastně jen přesměrování výjimek na naši proceduru (to zařídíme v události OnCreate hlavního formuláře) a samotná procedura již jen zobrazí jednoduchý dialog a zapíše data na disk.

.
.
.
public
  { Public declarations }
  procedure AppOnException(Sender: TObject; E: Exception);
.
.
.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnException := AppOnException;
end;

procedure TForm1.AppOnException(Sender: TObject; E: Exception);
var
  ErrFileHandle: THandle;
  ErrFileName : String;
  ErrMsg, s : String;
  T : TComponent;
begin
    s := ` -> `;
    t := Sender As TComponent;
    while (t <> nil) and (t.Owner <> nil) do begin
        s := s + ` ` + t.Name;
        t := t.Owner;
    end;

    ErrFileName := ExtractFilePath(Application.ExeName) + `error.log`;
    ErrFileHandle := CreateFile(PChar(ErrFileName), GENERIC_WRITE, FILE_SHARE_READ, NIL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if ErrFileHandle <> INVALID_HANDLE_VALUE then
    begin
        ErrMsg := Format(`%s : %s - %s`#13#10, [DateTimeToStr(Now), E.ClassName, E.Message]);
        SetFilePointer(ErrFileHandle, 0, nil, soFromEnd);
        FileWrite(ErrFileHandle, Pointer(ErrMsg)^, Length(ErrMsg));
        CloseHandle(ErrFileHandle);
    end;
    ShowMessage(`Pozor, v programu došlo k nečekané chybě. Doporučujeme uložit všechna data a ukončit aplikaci.`);
end;

Pochopitelně by bylo vhodné náš informační dialog o chybě, který se uživateli zobrazí, trošku více propracovat. Popsat pokud možno druh chyby (avšak netechnicky, spíše volně pro běžného uživatele), připojit název souboru se záznamy (aby uživatel věděl, kde ho má hledat) a pochopitelně adresu, kam může uživatel soubor poslat, pokud vám bude chtít pomoci s vývojem aplikace a odstraňováním chyb. Obzvláště ve fázi betaverzí je tento kontakt s uživatelem důležitý a čím nedodělanější verze aplikace, tím propracovanější soubor se záznamy chyb je potřeba. Jistě by tedy bylo vhodné zahrnout do souboru též informaci o verzi operačního systému, některé hardwarové informace a podobně (vše, co lze detekovat bez zásahu uživatele, abychom jej tím příliš neotravovali).

Málem bych zapomněl na jednu důležitou věc. Když se vrátíme k našemu příkladu, možná vás napadne, jak jej vlastně otestovat v praxi, zda vůbec funguje ? Asi by nemělo smysl čekat na nějakou nenadálou chybu, takže si ji prostě budeme muset vyrobit sami. Fantazii se meze nekladou a jistě budete schopni nějakou "umělou" chybu sami spáchat. Pro ty, kterým se nechce přemýšlet, dám tutový tip. Co třeba zkusit dělení nulou ? :)