OnMouseMoveTato 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 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.
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.
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ář).
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.
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.
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í.