OnMouseMove

Tato užitečná událost (event) se vyskytuje u některých komponent, a také u samotného formuláře (Form). Její obsluhu zapíšeme tak, že vybereme příslušnou komponentu s touto vlastností (v našem případě prázdný formulář) a v Object Inspectoru (F11) na kartě Events nalezneme příslušnou řádku. Dvojklikem v nevyplněném políčku (v obrázku napravo je příslušné místo označeno červeným kolečkem) vygenerujeme základní obslužnou proceduru této události:

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
  
  end;

Pro začátek se soustřeďme na souřadnice X a Y, které máme v hlavičce procedury. V nich nám příslušný volající program předává souřadnice myši relativně k objektu (u formuláře to je relativně k levému hornímu rohu funkční plochy, která se v Delphi označuje jako Client, v rozsahu Form1.ClientWidth a Form1.ClientHeight). Máme-li souřadnice, můžeme je někam napsat. Protože jsme si nepřipravili Label ani Edit, asi budeme muset psát přímo do nadpisu okna. Máme jenom jedno místo, kam zapisovat, takže je nutno oba řetězce, vzniklé konverzí souřadnic, spojit do jednoho. V Pascalu na to stačí znaménko plus. Prostým sečtením by se nám ale číslice slily a nebylo by vidět, co je X a co je Y. Proto přičteme alespoň čárku:

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
    Form1.Caption := IntToStr(X) + ',' + IntToStr(Y);
  end;

Program vyzkoušíme. Všimněte si, že nulové souřadnice má levý horní roh, nikoli dolní, oproti například grafům v Excelu bude tedy všechno vzhůru nohama (z hlediska vykreslování obrazu na crt monitoru je to takhle ale správně).
Nyní by to chtělo nějaké další využití této události. Jednou z možností je tlačítko, které je sice malé, ale jde snadno stisknout, protože se na ně myší vždy strefíme. Za tím účelem na relativně velký formulář umístěte relativně malé tlačítko. Pokud se tlačítko bude jmenovat Button1, může být obsluha následující:

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
    Form1.Caption := IntToStr(X) + ',' + IntToStr(Y);
    Button1.Left := X - Button1.Width div 2; {kursor doprostřed tlačítka}
    Button1.Top := Y - Button1.Height div 2;
  end;

Pokud sjedeme ukazatelem myši z tlačítka, tlačítko poskočí, aby na něj myš stále ukazovala. Bude tedy opravdu snadné je stisknout. Tento obrat můžeme použít například v případě, že se uživatele ptáme, zda se mu náš program líbí.

Množiny

Množiny zde cvičit nechci, nicméně alespoň okrajově se jich všimnout musíme, protože Delphi je s oblibou používají. Jako množinu (set of) označujeme proměnnou, jejíž hodnotu reprezentuje výčet přítomných prvků (z jisté množiny možných, která se stanoví při deklaraci). V tomto základním kursu budeme potřebovat dva obraty. Za prvé vyčíslení hodnoty proměnné vyjmenováním jejích prvků - jednotlivé prvky (jen ty, použité při deklaraci), vyjmenujeme za použití hranatých závorek:

nákup := [okurky, švestky, hrušky, meruňky, řepa];

Za druhé pak vyhodnocení, zda je daná položka obsažena v dané množině. Příklad ukazuje použití v podmíněném příkaze:

if ořechy in nákup then ...

Jako set je definována proměnná Shift z naší vygenerované procedury. Může obsahovat mimo jiné následující prvky:

ssShift v okamžiku vzniku události byla stisknuta klávesa Shift
ssCtrl v okamžiku vzniku události byla stisknuta klávesa Ctrl
ssAlt v okamžiku vzniku události byla stisknuta klávesa Alt (levý)
ssLeft bylo stisknuto levé tlačítko myši
ssDouble byl detekován dvojklik (levým tlačítkem myši)

Označení "ss" značí ShiftState. Nás zajímá především, bylo-li stisknuto levé tlačítko myši. To můžeme použít k malování po formuláři.

Canvas

Toto slovo označuje například malířské plátno. Objekty, které obsahují property Canvas, je možné pomalovat. Canvas je real-time property, je tedy dostupná jen při běhu programu. Nehledejte ji proto v Object Inspectoru.

Canvas reprezentuje plně principy objektově orientovaného programování. K práci s Canvasem slouží řada procedur (metod), které jsou přímo součástí Canvasu - například obsahuje procedury, které na něj namalují čáru, obdélník, napíší text.

Kombinací property Canvas formuláře a události OnMouseMove můžeme snadno vytvořit jakýsi primitivní malovací program. Stačí v případě, že je stisknuto levé tlačítko, namalovat puntík. V praxi by takto vzniklé čáry byly nesouvislé, bude tedy lépe nám známé polohy bodu spojovat čárou. Čára se na Canvas namaluje metodou LineTo, která ovšem vyžaduje, aby byla předem nastavena hodnota, kde čára začíná. Tento pomyslný kursor přesunuje funkce MoveTo. Pokud je LineTo voláno několikrát za sebou, jsou body spojeny lomenou čarou (polygonem). Stačí tedy zavolat LineTo, pokud je stisknuté levé tlačítko myši, nebo MoveTo - v ostatních případech.

Odstraňte tlačítka a naši oblíbenou proceduru doplňte následovně (obsluha polohy tlačítka byla rovněž odstraněna):

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
    Form1.Caption := IntToStr(X) + ',' + IntToStr(Y);
    if ssLeft in Shift then Form1.Canvas.LineTo(X,Y)
                       else Form1.Canvas.MoveTo(X,Y);
  end;

Zdůrazňuji, že před else není středník (jsme uprostřed podmíněného příkazu). Nyní můžete program spustit a namalovat si trpaslíka.

Image

Není zrovna obvyklé malovat po formuláři a Windows to ani příliš nepodporují - když například formulář zmenšíme do ikony a pak jej obnovíme, plocha formuláře se smaže. Pokud se má s namalovaným obrázkem rozumně pracovat, je třeba jej již definovat jako obrázek. Na to slouží komponenta TImage, kterou nalezneme na liště Additional. Každý takto definovaný obrázek má samozřejmě také Canvas, po kterém lze malovat. Kromě toho má ovšem také property Picture (přesněji, Canvas u komponenty Image je jakýsi link, ve skutečnosti Image obsahuje Picture, ten obsahuje Bitmap, a ten teprve obsahuje Canvas).

Property Picture obsahuje dvě pro nás velmi důležité metody, LoadFromFile a SaveToFile, které umí obrázek načíst z / ukládat do souboru (ve verzi Delphi 4 stále jen ve formátu Windows Bitmap, *.BMP, který není právě úsporný co do prostoru. Potřebné komponenty OpenDialog a SaveDialog již byly popsány u Mema, takže ...

Příklad: Doplňte do formuláře komponenty OpenDialog a SaveDialog, nastavte u nich filtr (a typ souboru *.BMP) a výchozí adresář, připojte dvě vhodná tlačítka a použijte je v programu.

Pomůcka: Pokud jste nepřejmenovali (v Object Inspectoru pomocí "property" Name) komponenty, hodí se vám pro obsluhu prvního tlačítka řádka

if OpenDialog1.Execute then Image1.Picture.LoadFromFile(OpenDialog1.Filename);

Druhou lze rovněž zkopírovat a drobně poopravit. Také by šlo použít LoadPictureDialog - zkuste je porovnat.

Samotný Image má důležitou property Align. Základní hodnota je alNone. Pokud se ovšem změní na nějakou jinou, obsadí obrázek stanovenou část v našem připadě formuláře (jako "mezikus" lze v obdobých případech použít ještě komponentu Panel - poslední v liště Standard). Hodnota alClient pak znamená, že obrázek je přes celou plochu nadřízeného objektu (u nás přes celý formulář).

Tlouštka a barva čáry

Tak, jako Canvas objektu obsahuje všechny procedury a funkce na to, aby se sám "počmáral", obsahuje také vlastnosti tohoto kreslení.
Pro čárové objekty je to property Pen (pero). To má property barva čáry, typ čáry, tloušťka čáry, mód (normální a "xorput"). Ukážeme si, jak je nastavit, ale nejprve odbočku:
Pro plošné objekty je to property Brush. Obsahuje také barvu, ale především "vzorek" (Style, resp Bitmap - ale v obou případech se jedná jen o pole 8x8 bodů, což je při současném rozlišení grafiky kriticky málo; tímto vzorkam se pak objekt vyplňuje; ostatní body mimo zvolené šrafování zůstávají nezměněny; objekt je obtažen barvou dle property Pen.Color, včetně zahrnutí tloušťky pera Pen.Width).

Pro naše potřeby zatím nastavíme jen tloušťku pera. Dobrým pomocníkem je v tomto případě SpinEdit. Ten nalezneme na liště Samples . Můžeme u něj nastavit minimální a maximální hodnotu, jak již jsme zkoušeli u ukázky na prohlížení pole. Tloušťka čáry má smysl od jedné výše, při nižších číslech se kreslí čárou o tloušťce 1. Nechají se nastavit i nesmyslně vysoké hodnoty, např. 200 bodů. Jako obsluhu události této komponenty napíšeme (například pro obrázek):

procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
  Image1.Canvas.Pen.Width := SpinEdit1.Value;
end;

Barvu nastavíme zapomocí komponenty z lišty Dialogs "ColorDialog". U dialogů lze očekávat, že použijeme ustálený obrat s jejich funkcí Execute:

procedure TForm1.Button2Click(Sender: TObject);
begin
  if ColorDialog1.Execute
  then Image1.Canvas.Pen.Color := ColorDialog1.Color;
end;

V předchozí ukázce byla volba barvy umožněna stisknutím tlačítka.
Pokud budete potřebovat namalovat obdélník, můžete použít funkci Canvas.Rectangle, např. Rectangle(10+i,10+j,33+i*3,55+j*7); a podobně. Pokud nastavíme styl štětce na bsSolid, můžeme tímto obdélníkem obrázek i smazat. Delphi lepší prostředek na mazání obrázků stejně neposkytují (vykreslování obdélníků je maximálně urychleno, takže by smazání plochy jednoúčelovou funkcí trvalo zhruba stejně dlouho):

procedure TForm1.Button3Click(Sender: TObject);
begin
  Image1.Canvas.Pen.Color := clWhite;
  Image1.Canvas.Brush.Color := clWhite;
  Image1.Canvas.Brush.Style := bsSolid;
  Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height);
end;

Někdy nás může obtěžovat donekonečna opisovat "Image1.Canvas.". Pascal poskytuje pro tento případ direktivu with, která umožňuje předpokládat, že v ní uvozeném příkaze jsou všechny proměnné nejprve vyhledávány v uvedené struktuře (teprve pokud tam nejsou, tak mimo ni; funkci lze i zřetězit, pak platí "vnitřnější" with; pozor, může nedopatřením dojít k záměně prvků i tam, kde to původně nebylo zamýšleno!).
Málokdy dává smysl direktivu with vztahovat na jeden příkaz, proto se téměř výhradně používá ve spojení se závorkami begin - end. My můžeme napříkald napsat:

procedure TForm1.Button3Click(Sender: TObject);
begin
  with Image1.Canvas do begin
    Pen.Color := clWhite;
    Brush.Color := clWhite;
    Brush.Style := bsSolid;
    Rectangle(0, 0, Image1.Width, Image1.Height);
  end;
end;

V tomto případě by snad ještě bylo jednodušší použít kopírování bloků Ctrl+C a Ctrl+V, ale v řadě případů nám with usnadní orientaci ve složitém programu.

TRect

Při prohlížení seznamu metod v helpu se setkáme se dvěma funkcemi, FrameRect a FillRect pro prázdný a plný obdélník, které vyžadují zadat body "levý horní roh" (x1,y1) - "pravý dolní roh" (x2,y2) jako jedinou strukturu zvláštního datového typu "TRect":
procedure FrameRect(const Rect: TRect);
Není ovšem žádný problém takovou strukturu vytvořit, dokonce si ani nemusíme definovat proměnnou požadovaného typu. Stačí použít konverzní funkci "Rect", jejímiž parametry jsou právě ony čtyři souřadnice rohových bodů, například

Image1.Canvas.FrameRect(Rect(33,35,75,77));

... nakreslí jeden pixel široký obrys okolo zvoleného místa. Pro nakreslení obdélníku tlustší čarou je třeba použít proceduru Rectangle a property Canvasu "Brush.Style" nastavit na bsClear, kdy se vnitřek vůbec nevykresluje.

Pixels

Jednou z klíčových vlastností Canvasu je Pixels. Obsahuje hodnoty bodů příslušné plochy a samozřejmě je dostupná jen během běhu programu (nelze ji měnit Object Inspectorem). Není dostupná pro všechny objekty, nicméně pro Image je a můžete si ji vyzkoušet.

Pixels je pole o rozměrech Canvasu, jehož každý prvek má hodnotu TColor, tedy obsahuje barvu příslušného bodu. Dle svých představ si ji zde můžete měnit. Pokud například vytvoříme následující obsluhu tlačítka Button7:

procedure TForm1.Button7Click(Sender: TObject);
  var
    i,j : integer;
  begin
    for i:=0 to Image1.Width do
      for j:=0 to Image1.Height do
        Image1.Canvas.Pixels[i,j]:=
          Image1.Canvas.Pixels[i,j] xor -1;
  end;

získáme po kliknutí na toto tlačítko inverzi našeho obrazu (funkcí xor bod po bodu). Pokud bychom chtěli pracovat s barvami, musíme si nejprve typ TColor rozložit na jednotlivé barevné složky (r, g a b odpovídá red, grean a blue, červené, zelené a modré složce obrazu):

procedure TForm1.Button7Click(Sender: TObject);
  var
    i,j,c : integer;      {4 byty}
    r,g,b : byte;
  begin
    for i:=0 to Image1.Width do
      for j:=0 to Image1.Height do begin
        c:=Image1.Canvas.Pixels[i,j];
        b:=c and $FF;
        c:=c shr 8;
        g:=c and $FF;
        c:=c shr 8;
        r:=c and $FF;
        r:=128 + r div 2;    {operace s barevnými složkami}
        c:=r*$10000+g*$100+b;
        Image1.Canvas.Pixels[i,j]:=c;
      end;
  end;

Zde jsou ovšem bez varování použity dva obraty, které nevysvětluji, protože souběžně probíhá předmět Mikropočítače a aplikace, který je povinný a kde se probírají. Tedy: místo dělení je použit posun (rotace) doprava, kterou v Delphi provádí funkce shr. Místo zbytku po dělení je použito "přemaskování" potřebné části funkcí and. Znak dolaru uvozuje v Delphi zápis hexadecimální hodnoty čísla. Každý pixel (TColor) obsahuje v hexadecimálním zápise barvy dle schematu

         $00rrggbb

kde každé dvě písmena představují jeden byte. S jednotlivými složkami můžeme dle uvážení pracovat, zbývá dodat, že číslo obsahuje hodnotu rozsvícení paprsku obrazovky - samé "jedničky" ($FFFFFF) jsou bílá, nula označuje černou. Další poznámka by měla směřovat k reprezentaci obrázku v počítači - opravdu je uložen jen na obrazovce. Pokud máme barevné rozlišení například jen 256 barev, nemůžeme počítat s osmibitovou hloubkou zobrazení.