Dnes si popíšeme velmi šikovnou komponentu RichEdit a ve druhé části kapitoly ji použijeme k vytvoření jednoduchého textového editoru.
Komponenta RichEdit se nachází v paletě Win32. Slouží k poměrně sofistikované práci s textem. Podíváme se na ni blíže a následně ji použijeme v reálné aplikaci – postavíme na ní textový editor.
Komponenta RichEdit obsahuje velké množství vlastností formátu textu RTF (Rich Text Format). Podrobnější informace o formátu RTF můžete nalézt třeba na www.wotsit.org.
„Odlehčenou“ verzí ovládacího prvku RichEdit je komponenta Memo, která je popsána v sedmém dílu našeho seriálu.
Vlastnosti textu (TTextAttributes)
Pomocí komponenty RichEdit můžeme mnoha způsoby pracovat s textem. Můžeme upravovat typ fontu, barvu, velikost, styl apod. Výhodou RichEditu je, že všechny tyto atributy lze nastavovat buď celému textu, nebo jen vybrané části.
Než se podíváme na konkrétní vlastnosti a metody pro práci s textem, musíme si něco říci k třídě TTextAttributes. Tato třída slouží právě k uchování informací o atributech textu a je použita v několika atributech vlastního RichEditu, ve kterých uchováváme informace o textu. Podívejme se na vlastnosti třídy TTextAttributes:
Charset - specifikuje znakovou sadu fontu. Typem jde o celé číslo v rozmezí 0…255. K dispozici je také celá řada předdefinovaných konstant:
- ANSI_CHARSET (0) - ANSI znaky
- DEFAULT_CHARSET (1) – font je vybrán pouze na základě specifikovaného jména a velikosti (vlastnosti Name a Size)
- SYMBOL_CHARSET (2) - standardní symbol
- OEM_CHARSET (255) – záleží na kódové stránce operačního systému
- …dále jsou definovány konstanty pro jednotlivé znakové sady (např. CHINESEBIG5_CHARSET (136) – tradiční čínština, RUSSIAN_CHARSET (204), apod.
Color – barva písma. Přípusné hodnoty jsou buď přímo barvy (např. clGreen, clRed, apod.) nebo barvy definované systémem. V takovém případě záleží na nastavení barevného schématu ve Windows (např. clMenuText = barva písma v menu, clHightlightText = barva vybraného textu, apod.). Pokud máte ve svých Windows nastavenu barvu vybraného (tj. označeného) textu např. na zelenou, způsobí přiřazení barvy na clHighlightText zezelenání textu.
ConsistentAttributes – read-only vlastnost říkající, které vlastnosti TTextAttributes jsou konzistentní v celém označeném textu současného výběru v RichEditu. Jistě si dovedete představit využití – pokud bude celý označený text mít stejnou velikost písma, můžete tuto velikost zobrazit. Pokud ne, nezobrazíte nic, apod.
Height – výška (velikost) písma. Je udávána v pixelech.
Name – jméno fontu
Pitch – udává, jakou šířku mají jednotlivé znaky, lépe řečeno, mají-li všechny stejnou šířku nebo nikoliv. Přípustné hodnoty: fpDefault (hodnota záleží na fontu specifikovaném ve vlastnosti Name), fpFixed (všechny znaky mají stejnou šířku), fpVariable (znaky mají různou šířku). Poznámka: nastavení šířky písma nepřinutí Windows roztáhnout nebo smrštit znaky aktuálního fontu:-), i když by to byl jistě zajímavý pohled, ale vyberou font, který danou vlastnost splňuje. Pokud máte např. vybrán font MS Serif a nastavíte fpFixed, zobrazí se Courier.
Protected – logická hodnota indikující, je-li text reprezentovaný v TTextAttributes „protected“. Protected text nemůže být uživatelem změněn. Pokusí-li se uživatel editovat výběr, který obsahuje protected text, RichEdit generuje událost OnProtectChange. V obsluze této události můžeme povolit nebo zakázat editaci textu. Pokud jsme nevytvořili obsluhu události OnProtectChange, protected text je read-only.
Size – velikost písma. Vztah mezi hodnotou v pixelech (vlastnost Height) a hodnotou Size je následující: Size = Height * 72 / ScreenPixelsPerInch. Hodnotu ScreenPixelsPerInch lze získat z proměnné Screen.PixelsPerInch.
Style – styl textu, přípustné hodnoty: fsBold (tučné), fsItalic (kurzíva), fsUnderline (podtržené), fsStrikeout (horizontálně škrtnuté). Typ vlastnosti Style je množina, proto může obsahovat více hodnot (písmo může být zároveň podtržené a tučné, tedy [fsUnderline, fsBold]).
Tolik k vlastnostem třídy TTextAttributes. Tato třída je v RichEditu využívána pro vybraný (označený) text (tj. vlastnost SelAtributes) a ostatní text (tj. vlastnost DefAtributes). K těmto vlastnostem se ještě vrátíme v další podkapitole.
Na závěr se podívejme, jak v programu zmíněné vlastnosti nastavit. Předpokládejme, že máme na formuláři (TwndHlavni) umístěn RichEdit (reEditor). Podívejme se, jak například zjistit, je-li celý označený text tučný (nebo celý netučný):
with TwndHlavni.reEditor do
if caBold in SelAttributes.ConsistentAttributes then
ShowMessage(`Celý text je tučný nebo netučný`)
else
ShowMessage(`Část textu je tučná, část ne`);
K práci s textem slouží především vlastnosti, které shrnuje následující tabulka. Upozorňuji, že většina jich je runtime, proto je nehledejte v Object Inspectoru.
Vlastnost |
Význam |
DefAttributes |
je typu TTextAttributes (viz předchozí podkapitola) a popisuje „normální“, tedy neoznačený text. V podstatě říká, jak bude vypadat nově přidávaný text. |
HideScrollBars |
říká, mají-li se skrýt posuvníky, nejsou-li potřeba (celý text se na stránku vejde). Nastavení této vlastnosti nemá žádný význam, pokud je vlastnost ScrollBars (celého RichEditu) nastavena na ssNone. |
HideSelection |
říká, bude-li vizuální indikace označení textu viditelná i po ztrátě zaměření RichEditu (jednoduše: bude-li text stále vidět jako označený, když se přepnete z RichEditu jinam v rámci aplikace). |
Lines |
klíčová vlastnost, obsahuje vlastní text. Je typu TStrings. |
Paragraph |
specifikuje formát odstavce textu, podrobněji viz další podkapitola |
PlainText |
velmi důležitá vlastnost, kladná hodnota (True) říká, že soubor se bude ukládat jako čistý text <.TXT), kdežto záporná hodnota (false) znamená ukládání ve formátu RTF |
SelAttributes |
je typu TTextAttributes (viz předchozí podkapitola) a popisuje označený text. |
SelLength |
délka (tj. počet znaků) označeného textu |
SelStart |
pozice začátku označeného textu (ve znacích od začátku dokumentu, 0 znamená první znak) |
SelText |
označený text. Tato vlastnost je typu string (nikoliv TStrings) |
WordWrap |
říká, budou-li zalamovány řádky, které přesahují viditelnou oblast obrazovky |
Než se podíváme na vlastnosti odstavce, povíme si ještě o několika metodách komponenty RichEdit, které jsou velmi praktické:
Metoda |
Význam |
FindText |
Mocná vlastnost, vyhledává text v RichEditu. Parametry: FindText (co: string; odkud, délka: Integer, možnosti: TSearchTypes). Tato metoda je použita v příkladu níže. |
Print |
Vytiskne obsah RichEditu |
ClearSelection |
Vymaže označený text |
CopyToClipboard |
Zkopíruje označený text do schránky |
CutToClipboard |
Vystřihne označený text do schránky, tj. text z dokumentu vyjme |
PasteFromClipboard |
Vloží na pozici kurzoru text ze schránky. Je-li označen jiný text, nahradí se. |
SelectAll |
Označí celý dokument |
Jednou z vlastností komponenty RichEdit je odstavec (Paragraph). Paragraph je runtime vlastností, která specifikuje formátovací informaci aktuálního odstavce. Formátovací informací rozumíme zarovnávání, odrážky, číslování a tabulátory. Aktuálním odstavcem je myšlen odstavec obsahující označený text. Pokud není označen žádný text, je aktuálním odstavcem ten, na (ve) kterém je kurzor. Paragraph je typu TParaAttributes.
Poznámka pro pokročilé uživatele:
Paragraph je read-only vlastnost, protože objekt TCustomRichEdit má pouze jeden objekt TParaAttributes, který nemůže být měněn. Atributy aktuálního odstavce ovšem mohou být měněny, a to nastavováním vlastností objektu TParaAttributes. Mohou být nastavovány jedna po druhé nebo najednou podle existujícího objektu TParaAttributes zavoláním Paragraph.Assign.
Podívejme se tedy na vlastnosti odstavce.
Vlastnost |
Význam |
Alignment |
Specifikuje, jak má být text odstavce zarovnán. Přípustné hodnoty: taLeftJustify (vlevo), taCenter (na střed), taRightJustify (vpravo). |
FirstIndent |
Říká, jak moc bude první řádka odstavce „odražená“ od levého okraje, udává se v pixelech. |
LeftIndent |
Říká, jak moc budou ostatní řádky odstavce „odraženy“ od levého okraje. Tato vlastnost je použitelná např. k „vypíchnutí“ některého důležitého odstavce, který má jiné postavení v textu, apod. LeftIndent, stejně jako předchozí FirstIndent, nesouvisí s odrážkami (tedy puntíky). |
Numbering |
Specifikuje odrážku aktuálního odstavce (tedy onen puntík). Je-li nastavena vlastnost LeftIndent, je odrážka posunuta o uvedenou vzdálenost. Přípustné hodnoty: nsNone, nsBullet. |
RightIndent |
Analogie LeftIndent. Je efektní zvláště při nastavení Alignment = taRightJustify. |
Tab |
Určuje počet pixelů, po kterých se bude „poskakovat“ tabulátorem. Tab je indexovaná vlastnost. Pozice prvního „tab stopu“ je tedy Tab[0], dalšího Tab[1], atd. |
TabCount |
Počet definovaných zastávek tabulátoru („tab stopů“) v tomto odstavci. |
Podívejme se na příklad procedury, která definuje pozici zarážek tabulátoru:
procedure NastavTabStops (RichEdit: TRichEdit; TabStops: array of Integer);
var
i: Integer; // indexy oznacujeme zpravidla malymi pismeny
begin
with RichEdit.Paragraph do begin
TabCount := High(TabStops) + 1; // nejvyssi index + 1
for i := 0 to TabCount – 1 do
Tab[i] := TabStops[i];
end;
end;
Nyní nadefinujeme zarážky tabulátoru:
NastavTabStops(reEditor, [25, 50, 80, 100]);
Tolik tedy obecně k RichEditu. Nyní již jsme schopni vytvořit onen kýžený textový editor, ovšem nejprve vyřešíme restík, který nám zbyl z předchozího dílu. Tam jsme si ukázali dialogy pro vyhledávání a nahrazování v textu – FindDialog, resp. ReplaceDialog. Řekli jsme si, že je ovšem nutné ručně zpracovat vlastní vyhledávání. Na to se tedy nyní vrhneme.
Uvedu možnou obsluhu události pro vyvolání (otevření) FindDialogu (např. po stisku tlačítka, vybrání příkazu v menu apod.). Její součástí bude automatické vyplnění políčka s vyhledávaným textem v případě, že uživatel nějaký text označil.
procedure TwndHlavni.btnNajdiClick(Sender: TObject);
begin
dlgFind.FindText := reEditor.SelText;
dlgFind.Execute;
end;
Následuje kód ošetřující událost OnFind dialogu FindDialog:
procedure TwndHlavni.dlgFindFind(Sender: TObject);
var
Pozice: Longint;
begin
with reEditor do begin
Pozice := Pos(dlgFind.FindText, Lines.Text);
if Pozice > 0 then begin
SelStart := Pozice - 1;
SelLength := Length(dlgFind.FindText);
end
else
MessageDlg(`Nenalezeno!`, mtError, [mbOk], 0);
SetFocus; // jako aktivni nastav reEditor
dlgFind.CloseDialog;
end;
end;
Poznámka: tento kód je použitelný k hledání obecně. Pokud ale vyhledáváme konkrétně v RichEditu (což se právě chystáme provádět), je lepší použít vlastnost RichEditu FindText a implementovat vyhledávání takto:
procedure TwndHlavni.dlgFindFind(Sender: TObject);
var
Pozice: Longint;
begin
with reEditor do begin
Pozice := reEditor.FindText(dlgFind.FindText, 0, Length(text), []);
if Pozice > 0 then begin
SelStart := Pozice;
SelLength := Length(dlgFind.FindText);
end
else
MessageDlg(`Nenalezeno!`, mtError, [mbOk], 0);
dlgFind.CloseDialog;
SetFocus;
end;
end;
Nyní již nám nechybí vůbec nic k tomu, abychom naprogramovali textový editor. Protože to určitě zvládnete sami, budu psát jen body a podrobněji se zastavím jen u „zapeklitějších“ situací.
Vytvoříme nový projekt. Na formulář umístíme následující komponenty (v závorce uvedu, jak je nazveme): RichEdit (reEditor), MainMenu (mnHlavni), PopupMenu (mnPopup). Dále na formulář umístíme dialogy: OpenDialog (dlgOpen), SaveDialog (dlgSave), FontDialog (dlgFont), ColorDialog (dlgColor), PrintDialog (dlgPrint), PrinterSetupDialog (dlgSetup), FindDialog (dlgFind).
Otevřeme si Menu Designer u mnHlavni a vytvoříme hlavní nabídku programu. V té budou následující položky:
- Soubor - s podpoložkami Nový, Otevřít, Ulož, Ulož jako, Nastavení tisku, Tisk, Konec
- Úpravy – s podpoložkami Kopíruj, Vystřihni, Vlož, Vybrat vše, Najít
- Formát – s podpoložkami Písmo, Barva, Zalamování řádků
- Nápověda – s podpoložkami O programu a Nápověda.
Poznámka: mějte na paměti, že správně vytvořené menu (viz 9. díl našeho seriálu) by mělo obsahovat možnost zkrácené volby (tj. používání ampersandu & před příslušným písmenem textu položky) a možnost používat klávesové zkratky (např. Ctrl+C by mělo zkopírovat označený text apod.).
V návrhu aplikace je ještě vhodné nastavit „Default“ hodnotu titulku okna. Aby bylo možno kód plně opět využít:-), použijeme konstantu JM_PRG, která bude obsahovat název programu, tedy v našem případě „Textový editor“. Další konstanta (NO_NAME) označuje název souboru „Bez názvu.txt“. Konstanty budou definovány „nahoře“ v modulu, tedy v sekci definic, buď v části interface nebo implementation.
const
JM_PRG = `dfsd`;
NO_NAME = `Bez názvu.txt`;
wndHlOkno.Caption := NO_NAME + JM_PRG;
Nyní přijde důležitý okamžik. Budeme postupně ošetřovat jednotlivé položky menu.
Soubor – Nový: program by se měl zeptat, zda uložit změněný soubor (pokud byl změněný), a pak vymazat celý obsah RichEditu. K tomu účelu je možné např. vytvořit funkci UlozZmeny, která zjistí, zda byl soubor změněn, a podle uživatelova přání jej uloží. Použitelná je vlastnost RichEditu Modified a událost RichEditu OnChange. Použitá procedura UlozSoubor bude definována níže. Budeme udržovat privátní atribut JmenoSouboru, ve kterém bude uloženo jméno právě zpracovávaného souboru. V souladu s poznatky o objektově orientované architektuře víme, že je nutné definovat metodu, která bude toto jméno souboru nastavovat. Ukázka kódu funkce pro nastavení jména:
procedure TwndHlavni.NastavJmenoSouboru(const Jmeno: String);
begin
JmenoSouboru := Jmeno;
wndHlavni.Caption := ExtractFileName(JmenoSouboru) + ` - ` + JM_PRG;
end;
Poznámka: při každém vkládání nového jména souboru budeme muset volat funkci NastavJmenoSouboru. To je sice poměrně běžný postup, ale není úplně hifi:-), to jaksi všichni cítíme. Ideálním řešením by bylo použití tzv. property. Tato problematika bude popsána v druhém díle zaměřeném na objektové koncepty.
Kód funkcí pro test změny textu uložení změn:
function TwndHlavni.UlozZmeny: TModalResult;
begin
if reEditor.Modified then begin
Result:= MessageDlg(`Soubor byl změněn, chcete jej uložit?`, mtConfirmation, mbYesNoCancel, 0);
if Result = mrYes then
UlozSoubor;
end
else
Result := mrOK;
end;
function TwndHlavni.UlozSoubor;
begin
reEditor.Lines.SaveToFile(JmenoSouboru);
reEditor.Modified := False;
end;
Kód vlastní položky menu:
procedure TwndHlavni.NovyClick(Sender: TObject);
begin
if (UlozZmeny <> mrCancel) then begin
NastavJmenoSouboru(NO_NAME);
reEditor.Lines.Clear;
reEditor.Modified := False;
end;
end;
Soubor – Otevři: program by měl nejprve otestovat (stejně jako v předchozím případě), zda je nutné soubor uložit. Pak by měl otevřít dialog OpenDialog. Protože RichEdit jako takový umožňuje práci se soubory TXT a RTF, měl by tento dialog být nastaven pro práci právě se zmíněnými soubory. Ukázka kódu:
procedure TwndHlavni.OtevriClick(Sender: TObject);
begin
if (UlozZmeny <> mrCancel) then
if dlgOpen.Execute then begin
NastavJmenoSouboru(dlgOpen.FileName);
reEditor.Lines.LoadFromFile(JmenoSouboru);
reEditor.Modified := False;
reEditor.ReadOnly := ofReadOnly in dlgOpen.Options;
end;
end;
Soubor – Ulož: soubor bude (bez ptaní) uložen pod svým aktuálním jménem. Jedinou výjimkou bude situace, kdy se soubor bude jmenovat „Bez názvu.txt“. V takovém případě se otevře dialog Uložit jako.
procedure TwndHlavni.UlozClick(Sender: TObject);
begin
if JmenoSouboru = NO_NAME then
UlozJakoClick(Sender)
else
UlozSoubor;
end;
Soubor – Ulož jako: bude otevřen dialog SaveFile, ve kterém bude uživatel moci vybrat jméno, pod kterým se bude soubor ukládat.
procedure TwndHlavni.UlozJakoClick(Sender: TObject);
begin
if dlgSave.Execute then begin
NastavJmenoSouboru(dlgSave.FileName);
UlozSoubor;
end;
end;
Další příkazy musíme z důvodu rozsahu článku probrat již jen povrchně.
Soubor - Nastavení tisku: vyvoláme dialog PrintSetupDialog
Soubor - Tisk: vyvoláme dialog PrintDialog. Ukážeme si kód této procedury:
procedure TwndHlavni.TiskClick(Sender: TObject);
begin
if dlgPrint.Execute then
reEditor.Print(JmenoSouboru);
end;
Soubor - Konec: nejprve bychom měli opět otestovat, zda je soubor potřeba uložit. Pak ukončíme aplikaci. Není zrovna vhodné provádět další explicitní dotazy typu „Chcete opravdu skončit?“, protože uživatel bude často potřebovat editor opakovaně vypínat a znovu spouštět. Tato situace může nastat třeba při editaci textu, jehož originál je umístěn na vzdáleném stroji. Pak je totiž potřeba editor zavřít, aby byla lokální kopie souboru přenesena zpět (typicky Windows Commander).
Úpravy - Kopíruj: na zkopírování označeného textu do schránky existuje metoda komponenty RichEdit (lépe řečeno zděděná metoda z TCustomEdit).
procedure TwndHlavni.CopyClick(Sender: TObject);
begin
wndHlavni.reEditor.CopyToClipboard;
end;
Úpravy – Vyřízni, Vlož: k těmto dvěma činnostem existují analogické metody komponenty RichEdit jako pro kopírování do schránky. Popsány jsou v tabulce v předchozí podkapitole.
Úpravy – Vybrat vše: opět existuje metoda, konkrétně SelectAll.
Úpravy – Najít: kód, který uvedeme do obsluhy. Události vybrání této položky v menu jsou uvedeny v předchozí podkapitole, stejně jako kód, který je nutno napsat do obsluhy události OnFind dialogu FindDialog.
Formát – Písmo: nejprve zjistíme, zda je označena nějaká část textu (a zda se tedy bude nastavování týkat atributu SelAttributes nebo DefAttributes). Pak zavoláme dialog FontDialog:
procedure TwndHlavni.PismoClick(Sender: TObject);
begin
with reEditor do begin
if SelLength > 0 then begin
dlgFont.Font.Assign(SelAttributes);
if dlgFont.Execute then
SelAttributes.Assign(dlgFont.Font);
end
else begin
dlgFont.Font.Assign(DefAttributes);
if dlgFont.Execute then
DefAttributes.Assign(dlgFont.Font);
end;
end;
end;
Formát – Barva: provedeme analogicky jako font
Formát – Zalamování řádků: použjeme vlastnosti RichEditu WordWrap. Praktické je u této položky v menu zobrazovat zatržení říkající, zda je tato volba právě aktivována či nikoliv.
Nápověda – O programu: vytvoříme volbou v menu File – New Form nový formulář. Na jeho designu se můžeme klidně vyřádit; pokud nechceme zůstat u „střízlivého“ vzhledu, zkusíme si třeba zařadit na formulář nějaký posuvný text (námět na domácí úkol – víte, jak na to?).
Nápověda – Nápověda: buďto vytvoříme klasickou nápovědu (soubor *.HLP, což je vcelku „pakárna“ a kromě toho to bude obsahem samostatného dílu našeho seriálu), nebo budeme líní (a u takovéto aplikace by to asi až tolik nevadilo) a nápovědu vytvoříme jako nový formulář s komponentou Memo (nebo RichEdit:-)), do které napíšeme text nápovědy, tedy stručné shrnutí voleb a možností programu.
Pryč s Poznámkovým blokem
Jednoduchý editor máme hotov. Můžete ze svého systému vymazat Poznámkový blok a používat místo něho váš vlastní výtvor – určitě vás to při každém použití potěší. Samozřejmě, nabízelo by se mnoho vylepšení. Iniciativě se meze nekladou. Můžete vylepšit editor o stavový řádek (na kterém by se třeba zobrazovaly údaje o aktuálním souboru, číslo řádky apod). Dále by se daly implementovat statistické údaje, např. počet řádků, znaků; dále aktuální doba psaní apod. Vzhled by mohly vylepšit ikonky (pro otevření a uzavření souboru) – na vylepšování formulářů se podíváme právě v příštím díle. Na závěr dodávám, že podobný příklad je k nalezení v podadresáři DEMOS\RICHEDIT adresáře, ve kterém máte nainstalováno Delphi. Je ovšem samozřejmě v angličtině.
A ještě jedna poznámka na závěr: kdo mi jako první pošle vytvořený vlastní zdroják příkladu z tohoto dílu seriálu (s dobrou štábní kulturou:-)), získá čestný titul „Živě’s Best Delphi Programmer“ a nehynoucí slávu plynoucí ze zveřejnění svého jména v příštím díle seriálu…:-)