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

Umíme to s Delphi: 110. díl – různé jazykové verze aplikací: řetězcové resources

Václav Kadlec - 24.11. 2003

Dnešní díl našeho seriálu navazuje na předchozí články, které se týkaly problematiky zdrojů (resources) systému Windows. Před týdnem jsme si krok za krokem vysvětlili, jakým způsobem vytvořit nové zdroje (ukazovali jsme si příklad týkající se ikon), dále jak pomocí Image Editoru získat soubor se zdroji a jak nakonec tento soubor použít v aplikaci, kterou programujeme v Delphi.

Dnešní článek naváže na tuto problematiku – a zároveň se pokusí předvést zase něco nového. Dnes se totiž budeme zabývat dalším typem zdrojů: textovými řetězci.

Zatímco u obrázků je využití zdrojů trochu sporné (i když samozřejmě mají celou řadu výhod), v případě textových řetězců je situace naprosto jednoznačná. Pokud totiž budeme v aplikaci pracovat s textovými řetězci ve formě zdrojů, získáme (kromě jiných) tu ohromnou výhodu, že budeme moci relativně jednoduše vytvářet různé jazykové mutace svých aplikací. A to stojí za to, co říkáte?

Textové řetězce jakožto zdroje

Hned na úvod si bohužel musíme sdělit špatnou zprávu: Image Editor, standardní nástroj dodávaný formou Borland s vývojovým prostředím Delphi, můžeme s klidem nechat vypnutý, neboť s řetězci pracovat prostě neumí. Na tomto místě bych si dovolil jen stručnou odbočku – firma Borland bývá za svůj nástroj Image Editor často kritizována, a to hned z několika důvodů. Image Editoru bývá vytýkáno třeba to, že dokáže pracovat jen s obrázky velmi omezených atributů (málo barev apod.), dále to, že není prakticky nijak inovován a v neposlední řadě také skutečnost, že neumožňuje pracovat se všemi druhy zdrojů, ale pouze s ikonami, bitmapami a kurzory. Na tomto místě bych snad jen poznamenal, že zatímco oprávněnost prvních dvou výtek je zřejmě neoddiskutovatelná, třetí problém (nemožnost pracovat se všemi zdroji) je asi záměrem – firma Borland nikdy netvrdila, že dodává aplikaci pro práci se zdroji: Image Editor má ostatně v názvu „Image“ právě pro zdůraznění faktu, že se používá k práci s obrázky.

Borland nešíří s Delphi žádný editor zdrojů jednoduše proto, že při běžné práci v Delphi se zdroji prostě pracovat nemusíme. Filozofie Delphi je taková, že většina prvků, které se v jiných vývojových prostředích ukládají ve zdrojích, se uchovávají jinde a jinak (většinou nějak interně ve formě vlastností objektů a komponent), takže od resources je vývojář v podstatě odstíněn. Na druhou stranu je fakt, že spousta programátorů prostě zdroje rutinně používá i při práci v Delphi.

Ale i kdybychom firmě Borland odpustili, že Image Editor slouží jen k úpravě bitmap, ikon a kurzorů (pokud se tedy týká zdrojů), je asi pořád nutné souhlasit s tvrzením, že Image Editor by si nějaké to vylepšení zasloužil.

Tolik tedy odbočka k Image Editoru; pojďme se vrátit k náplni dnešního dílu a tím jsou textové řetězce jakožto resources. Řekli jsme si, že Image Editor nám v našem snažení tentokráte nepomůže a budeme si muset pomoci jinak. Naštěstí v případě textových řetězců to nabude příliš obtížné.

Krok jedna – vytvoření zdrojového souboru s řetězci

Celý postup budeme opět strukturovat, jak je naším tradičním zvykem, do posloupnosti elementárních kroků, které si budeme podrobně popisovat. Začneme tím, že vytvoříme jakýsi „zdrojový soubor“ zdrojů, tedy textový soubor obsahující všechny texty, které budeme chtít uchovávat ve zdrojích.

Spustíme tedy jakýkoliv textový editor, který je schopen ukládat soubory ve formátu čistého textu (Plain Text). Programátorským labužníkům poslouží jistě nejlépe nástroj z nejsofistikovanějších – mnoha mýty ověnčený, oblíbený a milovaný Poznámkový blok (Start – Programy – Příslušenství – Poznámkový blok).

V něm napíšeme následující „zdrojový kód“:

STRINGTABLE
{
  0, "Soubor"
  1, "Otevři"
  2, "Ulož"
  3, "Ulož jako"
  4, "Zavři"
  5, "Toto je ukázkový řetězec, který bude použit v aplikaci
      demonstrující práci s resources"
}

Tento soubor nyní uložíme, třeba pod názvem CZECH.RC, viz následující obrázek:

První krok je v tomto okamžiku hotov.

Krok druhý – kompilace zdrojového souboru

Druhý krok, který nyní přichází na řadu, spočívá v kompilaci tohoto zdrojového souboru. Abychom totiž mohli zapsané řetězce použít ve formě zdrojů v naší aplikaci, nestačí nám textový soubor vytvořený v předchozím kroku. Vzpomeňte si: v některém z předchozích dílů jsme si uváděli, že soubor se zdroji je zásadně binární (ponechme nyní stranou výjimku spočívající v textovém souboru DFM v novějších verzích Delphi). Uváděli jsme si dokonce ukázku jednoho ze souborů se zdroji – byl skutečně uložen v binární podobně a z jeho obsahu jsme nebyli schopni vyčíst v podstatě vůbec nic.

My však nyní máme textový soubor CZECH.RC, který je nám prozatím k ničemu a který rozhodně nemůžeme použít v žádné aplikaci. Z tohoto textového souboru totiž musíme nejprve vytvořit zmíněný binární soubor. Provedeme jednoduše kompilaci tohoto zdrojového textového souboru.

Kompilace je nesmírně jednoduchá a použijeme k ní nástroj „Borland Resource Compiler“. Tento kompilátor je dodáván společně s Delphi a naleznete jej v podadresáři Bin adresáře, ve kterém máte instalováno Delphi.Jedná se o soubor BRC32.EXE.

Tento program je tedy nutné spustit a jako parametr mu „předhodit“ náš slavně vytvořený soubor CZECH.RC. Spusťte tedy třeba příkazovou řádku (podle systému Windows, kterou používáte, spusťte buď Start – Spustit – command nebo Start – Spustit – cmd). V příkazovém řádku je pak nutné spustit uvedený překladač s parametrem CZECH.RC. Věřím, že nemusíme vysvětlovat, jakým způsobem přecházet mezi adresáři tak, aby bylo možné spustit překladač a zároveň mu předat soubor CZECH.RC (který je typicky v úplně jiném adresáři než překladač). Pokud by to někomu dělalo problémy, ozvěte se prosím do diskuse. Dejme tedy tomu, že se vše podaří a spustíte překlad. Důležité je, abyste nezapomněli na parametr –r, s jehož pomocí spustíme pouze kompilaci. Další parametry kompilátoru, které jsou k dispozici, nebudeme potřebovat, jen pro informaci si je však můžete prohlédnout na následujícím obrázku:

BRC32.EXE –r CZECH.RC

Výsledkem překladu je nyní soubor CZECH.RES, který se nám již líbí o mnoho více: nerozumíme mu, těžko z něho něco vyčteme (i když to není vyloučeno) a je uložen v binárním formátu. Bezvadné.

Krok třetí – vytvoření aplikace v Delphi

Konečně se dostáváme do prostředí Delphi, kde si ukážeme použití vytvořeného souboru ze zdroji. Vytvořte tedy novou aplikaci a nezapomeňte připsat do zdrojového kódu příslušného modulu (Unit1.pas) direktivu $R, s jejíž pomocí dojde k přilinkování souboru se zdroji do aplikace. Nezapomeňte zajistit, aby soubor se zdroji byl pokud možno v témže adresáři, v němž je uložen vytvářený projekt:

{$R CZECH.RES}

Zbývá vyřešit problém, jak načíst řetězce ze zdrojů. Pokud jste četli minulý díl seriálu, víte, že jsme načítali ikony pomocí funkce LoadIcon. Analogická funkce pro načítání řetězců ze zdrojů se nazývá LoadString. Ve stručnosti si ji popíšeme:

int LoadString(

    HINSTANCE hInstance, // handle of module containing string resource
    UINT uID, // resource identifier
    LPTSTR lpBuffer, // address of buffer for resource
    int nBufferMax // size of buffer
  );

Funkce od nás dostane čtyři parametry a za to nám předá jako svou návratovou hodnotu celé číslo označující počet znaků načteného řetězce. Význam parametrů je následující:

V našem případě tedy bude situace jednoduchá: vložíme do aplikace komponentu MainMenu a komponentu Label. Komponentě MainMenu vytvoříme několik položek a popíšeme je v podstatě jakkoliv; při spuštění aplikace (v obsluze OnCreate formuláře) pak do vlastností Caption načteme správně řetězce ze souboru se zdroji:


procedure TForm1.FormCreate(Sender: TObject);
const MAX=255;
var
  retezec: array[0..MAX] of char;

begin
  LoadString(hInstance, 0, retezec, MAX);
  PolozkaSoubor1.Caption := retezec;

  LoadString(hInstance, 1, retezec, MAX);
  PolozkaOtevri1.Caption := retezec;

  LoadString(hInstance, 2, retezec, MAX);
  PolozkaUloz1.Caption := retezec;

  LoadString(hInstance, 3, retezec, MAX);
  PolozkaUlozJako1.Caption := retezec;

  LoadString(hInstance, 4, retezec, MAX);
  PolozkaZavri1.Caption := retezec;

  LoadString(hInstance, 5, retezec, MAX);
  Label1.Caption := retezec;
end;

Na tomto místě snad jen stojí za to upozornit, že funkce LoadString pracuje s tzv. nulou ukončenými řetězci, proto je nutné použít pro pomocnou proměnnou „retezec“ pole znaků (array of char). Nicméně takto získaný (načtený) řetězec je pak normálně možné vložit do příslušných Caption (v Delphi je možné v přiřazovacích příkazech kombinovat běžné a nulou ukončené řetězce, potřebné konverze proběhnou implicitně).

Když nyní aplikaci přeložíte, dojde při spuštění k načtení veškerých řetězců ze zdrojů, viz následující obrázek:

Krok čtvrtý – vytvoření jiné jazykové mutace

Pokud se nyní rozhodneme, že chceme vytvořit další jazykovou verzi naší aplikace, případně pokud budeme chtít opravit některé texty či provést jakékoliv jiné změny v řetězcích, je postup velmi jednoduchý:

Nová jazyková verze aplikace je tak hotova skutečně během několika málo minut s tím, že největší díl práce zabere překlad řetězců. Neztratíme však řádnou dobu hledáním řetězců někde „po zdrojácích“.

Ukázku anglické verze naší aplikace (i s mezikrokem v podobě anglického zdrojového souboru s řetězci) vidíte na následujících výpisech a obrázcích:

STRINGTABLE
{
  0, "File"
  1, "Open"
  2, "Save"
  3, "Save as"
  4, "Close"
  5, "This is the sample string, which will be used in an application demonstrating resources managing"
}

Na závěr bych rád ještě jednou zdůraznil, že vytvoření nové jazykové verze aplikace je v této aplikační architektuře sice relativně snadné, ale v každém případě je k němu nezbytný opětovný překlad aplikace. Jinak řečeno – uživatel si tímto způsobem nemůže v nějakém menu běžící aplikace zvolit požadovanou jazykovou verzi: změnu je možné provést výhradně při kompilaci projektu.

Poznámka: jistě by ale bylo možné vymyslet nějaké řešení, které by tyto nevýhody dokázalo obejít – mohli bychom třeba nedávat odpovídajícím řetězcům v jednotlivých zdrojových souborech tytéž identifikátory, ale odlišovat je nějakým prefixem (např. 1 – Otevři, 11 – Open, 21 – Otvorit apod.). Při spuštění aplikace bychom pak načetli všechny řetězce (všechny mutace) a podle nějakého nastavení bychom do jednotlivých Caption ukládali řetězce ze zvoleného jazyka.

Pozor na velikost textů

Na tomto místě bych ještě rád upozornil na to, že při vytvoření nové jazykové mutace může dojít k částečnému nebo úplnému „rozhození“ uživatelského prostředí aplikace. Když totiž aplikaci vytváříme, doladíme prostředí podle jednoho jazyka, přičemž si neuvědomíme, že jednotlivé nápisy, titulky, popisky a další texty budou v jiném jazyce jinak dlouhé. Při použití jiné jazykové verze pak třeba nejsou čitelné konce řádků, případně může dojít k jiným chybám. V této oblasti je proto o to důležitější provést důkladné testování všech vytvořených verzí, případně ošetřit vytváření uživatelského prostředí silněji na úrovni zdrojového kódu aplikace (ošetřit zalamování řádků, nastavit pevné nebo pohyblivé šířky jednotlivých komponent apod.).

Zdrojový kód

Na úplný závěr opět uvedeme kompletní zdrojový kód aplikace:


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Menus, StdCtrls;

type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    Label1: TLabel;
    PolozkaSoubor1: TMenuItem;
    PolozkaOtevri1: TMenuItem;
    PolozkaUloz1: TMenuItem;
    PolozkaUlozJako1: TMenuItem;
    PolozkaZavri1: TMenuItem;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}
{$R ENGLISH.RES}

procedure TForm1.FormCreate(Sender: TObject);
const MAX=255;
var
  retezec: array[0..MAX] of char;

begin
  LoadString(hInstance, 0, retezec, MAX);
  PolozkaSoubor1.Caption := retezec;

  LoadString(hInstance, 1, retezec, MAX);
  PolozkaOtevri1.Caption := retezec;

  LoadString(hInstance, 2, retezec, MAX);
  PolozkaUloz1.Caption := retezec;

  LoadString(hInstance, 3, retezec, MAX);
  PolozkaUlozJako1.Caption := retezec;

  LoadString(hInstance, 4, retezec, MAX);
  PolozkaZavri1.Caption := retezec;

  LoadString(hInstance, 5, retezec, MAX);
  Label1.Caption := retezec;
end;

end.

Na závěr

Dnešní, ryze praktický článek, se pokusil vysvětlit, jakým způsobem v Delphi pracovat s řetězcovými zdroji (resources). Ukázali jsme si, jak vytvořit několik jazykových verzí své aplikace, aniž by bylo nutné jakkoliv měnit její zdrojový kód (s výjimkou názvu souboru se zdroji v direktivě $R). Na závěr jsme prodiskutovali výhody, nevýhody a některé další aspekty tohoto řešení. Věřím, že byl dnešní článek pro vás užitečný a těším se na shledanou zase za týden.