Programování v C++ II - Materiály pro výuku IDE C++ Builder

Transkript

Programování v C++ II - Materiály pro výuku IDE C++ Builder
Programování v C++
C++ Builder
Ing. Drahomír Stanke
Zlepšování podmínek pro využívání ICT ve výuce
a rozvoj výuky angličtiny
na SPŠei Ostrava
č.projektu CZ.1.07/1.1.07/03.0089
Ostrava 2011
Obor: Informační technologie
Předmět: Programování
Ročník: 4
Autor: Ing. Drahomír Stanke
© Ing. Drahomír Stanke
© Střední průmyslová škola elektrotechniky a informatiky, Ostrava,
příspěvková organizace
OBSAH
Úvod ............................................................................................................................... 9
1
Historie IDE C++ Builder ..................................................................................... 10
2
Základy funkce Windows .................................................................................... 12
3
Nová vlastnost tříd __property ........................................................................... 18
4
Datový typ Set - množina .................................................................................... 21
5
Práce s IDE C++ Builder ...................................................................................... 24
5.1
5.2
5.3
5.4
5.5
6
Knihovna VCL ...........................................................................................................24
Volací konvence: .......................................................................................................25
Rozšíření vlatností __property ..................................................................................26
Základy ovládání IDE ................................................................................................27
Nastavení projektu.....................................................................................................28
Základy funkce IDE C++ Builder ......................................................................... 30
6.1
6.2
7
Obsah důležitých souborů projektu po založení: ........................................................31
Základní vlastnosti komponent ..................................................................................32
Naše první programy ........................................................................................... 35
7.1
7.2
7.3
7.4
7.5
8
Aplikace obsahující jeden formulář ............................................................................35
Aplikace obsahující více formulářů ............................................................................38
Dynamická alokace formulářů....................................................................................42
Odalokování formuláře sebou samým .......................................................................43
SDI (Single Document Interface) a MDI (Multiple Document Interface)......................43
První příklady ....................................................................................................... 45
8.1
8.2
9
Příklad 1 ....................................................................................................................45
Příklad 2 ....................................................................................................................46
Základní komponenty I ........................................................................................ 48
9.1
9.2
9.3
9.4
9.5
9.6
9.7
9.8
9.9
Třída AnsiString .........................................................................................................48
Komponenta TLabel ..................................................................................................51
Komponenta TButton .................................................................................................51
Komponenta TEdit .....................................................................................................51
Komponenta TLabeledEdit ........................................................................................52
Společné ošetření stejné události u více komponent .................................................52
Kontrola číselných hodnot zadaných v textu ..............................................................52
Dynamická alokace komponent .................................................................................53
Nastavení desetinné tečky nebo čárky ......................................................................54
10
Začlenění vlastní třídy do projektu ................................................................. 57
11
Základní komponenty II ................................................................................... 62
11.1
11.2
11.3
11.4
11.5
Práce s obslužnými metodami navrhnutými IDE .......................................................62
Datový typ TAnchors .................................................................................................62
TBevel (skupina Additional) ......................................................................................63
TCheckBox ................................................................................................................63
TRadioButton ............................................................................................................63
11.6
11.7
11.8
12
12.1
12.2
12.3
12.4
12.5
12.6
12.7
12.8
13
13.1
13.2
13.3
13.4
14
14.1
14.2
14.3
15
15.1
15.2
15.3
16
16.1
16.2
16.3
16.4
17
17.1
17.2
17.3
18
18.1
18.2
18.3
19
19.1
19.2
19.3
20
TGroupBox ................................................................................................................64
TRadioGroup .............................................................................................................64
Modalni okna a ModalResult .....................................................................................65
Komponenty se seznamy I .............................................................................. 69
TList ..........................................................................................................................69
TObjectList ................................................................................................................71
TComponentList ........................................................................................................71
TStrings .....................................................................................................................71
TStringList .................................................................................................................71
TListBox ....................................................................................................................72
TCheckListBox ..........................................................................................................73
TComboBox ..............................................................................................................73
Komponenty se seznamy II ............................................................................. 76
TMemo ......................................................................................................................76
TRichEdit ...................................................................................................................77
TStringGrid - .............................................................................................................77
TUpDown ..................................................................................................................80
Komponety TabControl a PageControl .......................................................... 85
TTabControl ..............................................................................................................85
TPageControl ............................................................................................................86
TTabSheet.................................................................................................................86
Komponenty pro práci s menu ....................................................................... 89
TMainMenu ...............................................................................................................89
TPopupMenu .............................................................................................................89
TMenuItem ................................................................................................................90
Dialogové komponenty .................................................................................... 94
TOpenDialog .............................................................................................................95
TSaveDialog ..............................................................................................................96
TFontDialog ...............................................................................................................97
TColorDialog .............................................................................................................97
Práce s časem a datem .................................................................................. 101
TMonthCalendar ......................................................................................................103
TDateTimePicker .....................................................................................................103
TCCalendar .............................................................................................................104
Časovače ........................................................................................................ 107
TTimer .....................................................................................................................107
Performace Counter ................................................................................................108
TApplication::ProcessMessages() ...........................................................................108
Práce se soubory v IDE C++ Builder ............................................................ 112
Binární operace .......................................................................................................112
TFileStream .............................................................................................................113
Nejdůležitější pomocné funkce ................................................................................115
Příklady na práci se soubory ........................................................................ 116
21
Grafika I ........................................................................................................... 119
21.1
21.2
21.3
21.4
21.5
21.6
21.7
21.8
21.9
21.10
22
Grafika II .......................................................................................................... 127
22.1
22.2
23
TCanvas ..................................................................................................................119
Počítání souřadnic ...................................................................................................121
Překreslování ..........................................................................................................122
TColor .....................................................................................................................122
TBitmap ...................................................................................................................123
TPicture ...................................................................................................................124
TBrush.....................................................................................................................124
TPen........................................................................................................................125
TPaintBox ................................................................................................................125
TImage ................................................................................................................125
Přiklad 1. .................................................................................................................127
Příklad 2. .................................................................................................................129
Příklady práce s grafikou I ............................................................................ 132
23.1
23.2
23.3
23.4
23.5
23.6
23.7
23.8
23.9
23.10
Příklad 1. .................................................................................................................132
Příklad 2. .................................................................................................................134
Příklad 3. .................................................................................................................136
Příklad 4. .................................................................................................................139
Příklad 5. .................................................................................................................141
Příklad 6. .................................................................................................................142
Příklad 7. .................................................................................................................142
Příklad 8. .................................................................................................................142
Příklad 9. .................................................................................................................143
Příklad 10. ...........................................................................................................144
24
Souhrný příklad - bitmapový editor ............................................................. 145
25
Příklady práce s grafikou II ........................................................................... 146
25.1
25.2
26
26.1
26.2
26.3
26.4
26.5
Kreslení čárového grafu...........................................................................................146
Kreslení sloupcového grafu .....................................................................................150
Základy tisků .................................................................................................. 153
Třída TPrinter ..........................................................................................................153
TPrinterSetupDialog ................................................................................................154
TPrintDialog.............................................................................................................154
Postup při sestavování tiskové sestavy ...................................................................155
Ladění tiskových sestav...........................................................................................156
27
Uživatelské vykreslování buněk TStringGrid............................................... 159
28
Práce s inicializačními soubory .................................................................... 162
Inicializační soubory INI ......................................................................................................163
29
Práce s registrační databázi .......................................................................... 166
30
Některé další užitečné komponenty ............................................................. 169
30.1
30.2
30.3
Standard ..................................................................................................................169
Additional ................................................................................................................169
Win32 ......................................................................................................................170
30.4
30.5
30.6
30.7
31
31.1
31.2
31.3
32
32.1
32.2
System ....................................................................................................................171
Win 3.1 ....................................................................................................................172
Dialogs ....................................................................................................................172
Internet ....................................................................................................................172
Základy tvorby dynamicky linkovaných knihoven - DLL ............................ 174
Důvody vzniku dynamicky linkovaných knihoven - DLL ...........................................174
Postup tvorby jednoduché knihovny DLL .................................................................175
Použití cizí DLL knihovny.........................................................................................179
Práce se zprávami .......................................................................................... 181
Příjem zpráv ............................................................................................................181
Vysílání zpráv ..........................................................................................................184
33
Tvorba vlastních zpráv .................................................................................. 186
34
Tvorba potomků standardních komponent ................................................. 190
34.1
34.2
34.3
34.4
35
35.1
35.2
35.3
35.4
35.5
35.6
35.7
35.8
35.9
Vytvoření třídy TMujForm jako potomka třídy TForm ...............................................190
Rozšíření komponenty o reakci na další událost ......................................................192
Vytvoření nové komponenty jako potomka TCustomControl....................................193
Vytvoření nové komponenty a její zveřejnění v Tool Palette ....................................197
“Vychytávky“ .................................................................................................. 198
Pouze jedno spuštění aplikace ................................................................................198
Omezení změny velikosti formuláře .........................................................................198
Nastaveni velikosti formuláře na celou plochu .........................................................199
Tisk obsahu formuláře .............................................................................................199
Vytvoření formuláře s průhledným otvorem .............................................................199
Vytvoření formuláře vlastního tvaru podle bitmapy ..................................................199
Vytvoření eliptického formuláře regiónem ................................................................201
Vytvoření mnohoúhelníkového formuláře regiónem .................................................203
Vytvoření eliptického panelu pomocí regiónu...........................................................203
Použitá literatura a jiné zdroje ..................................................................................... 205
Vysvětlivky k používaným symbolům
Obsah hodiny – popisuje náplň hodiny
Cíl hodiny – specifikace dovedností a znalostí, které si studující osvojí během
hodiny
Klíčová slova – nové pojmy, specifické termíny či cizí slova, jejichž význam je
v textu vysvětlen
Definice – definování a vysvětlení nového pojmu či jevu
Příklad – objasnění nebo konkretizování problematiky na příkladu ze života,
z praxe, ze společenské reality apod.
Shrnutí – shrnutí probrané látky, shrnutí kapitoly
Kontrolní otázky a úkoly – prověřují, do jaké míry studující text a problematiku
pochopil, zapamatoval si podstatné a důležité informace a zda je dokáže
aplikovat při řešení problémů
Otázky k zamyšlení - úkoly rozšiřující úroveň základních znalostí
Literatura – literatura a zdroje pro doplnění a rozšíření poznatků kapitoly
9
Úvod
Smyslem tohoto modulu je uvést čtenáře do problematiky tvorby programů
s grafickým uživatelským rozhraním, řízených událostmi. Představiteli takových
programů jsou takřka všechny programy, s kterými se setkáváme v praktickém
životě, ať už je to např. v operačním systému Windows nebo GNU/Linux.
Jako představitel vývojového prostředí pro vývoj takových programů bylo zvoleno
osvědčené a oblíbené prostředí firmy Borland známé nejčastěji pod názvem C++
Builder. Firmě Borland se totiž podařilo vyvinou prostředek, který je pro uživatele
teprve začínajícího se seznamovat s touto problematikou, nejsnáze pochopitelný a
vyžadující nejmenší kvantum znalostí. Zároveň po prostudování tohoto materiálu
lze velmi snadno následně přejít na jiná vývojová prostředí jiných výrobců. Logika
tvorby programů v nich se od zásad tvorby v prostředí C++ Builder zase neliší
natolik, aby to muselo zájemci činit nepřekonatelné potíže. Nic ovšem uživateli
nebrání v tom, aby zůstal prostředí firmy Borland věrný a pokračoval ve své práci
pomocí nových verzí tohoto prostředí vyvíjených nyní firmou Embarcadero.
Pro prostudování tohoto modulu se od čtenáře očekávají základní znalosti
programování v jazyku C++ včetně základů objektového programování.
10
1
Historie IDE C++ Builder
Obsah hodiny
Historie vývojového prostředí, které budeme používat.
Klíčová slova
IDE, RAD, Delphi, C++ Builder (BCB), Borland Developer Studio (BDS), Turbo
C++.
Vývojové prostředí Turbo C++, ve kterém se budeme učit, pochází z dílny
softwarové firmy Borland, která je již desítky let známá hlavně svými vývojovými
prostředky.
Ještě v době šestnáctibitových PC a operačního systému DOS firma uvedla na
trh své proslavené překladače Turbo Pascal a Turbo C (později po obohacení o
vlastnosti jazyka C++ známého také pod názvem Borland C++), které si rychle
získaly oblibu mezi programátory.
Po nástupu operačního systému Windows firma Borland přišla s IDE
(integrované vývojové prostředí – Integrated Development Environment)
pracujícím na základě jazyka Pascal a nazvala ho Delphi. Tento svůj produkt také
označovala jako RAD (Rapid Application Development).
Delphi si záhy získalo mezi programátory obrovskou oblibu. Byla v něm
vyvinuta dlouhá řada známých aplikací a celá řada programátorů v něm pracuje
dodnes. Jedním z důvodů, proč Delphi bylo tak úspěšné, bylo mimo jiné také to, že
zde bylo hned ze začátku počítáno s prácí s databázemi. To tehdy žádné jiné
prostředí neumožňovalo. U všech ostatních bylo nutno ještě dokoupit další,
většinou velmi drahý, speciální software.
Jazyk Pascal však postupně začal ztrácet svou oblibu. Byla mu vytýkána hlavně
malá pružnost a obtížný zápis řešení některých problémů. Na jeho místo se
postupně začal prosazovat jazyk C a později C++.
Po nějaké době, když už bylo jasné, že jazyky C a C++ získávají převahu nad
jazykem Pascal, se Borland rozhodl již vyvinuté IDE na bázi Pascalu inovovat o
programování v jazyku C++. Nové prostředí bylo nazváno C++ Builder. Od té
doby probíhá vývoj obou prostředí souběžně a jsou spolu úzce spojeny. Pokud
budete hledat informace o prostředí C++ Builder, hledejte také pod zkratkou BCB
(text C++ Builder se do vyhledávače zadává obtížně).
Uživatelské rozhraní obou IDE je vlastně naprosto stejné. Software, který byl již
vyvinut v jazyce Pascal, byl použit i v prostředí C++ Builder a bylo pouze
11
doplněno softwarové rozhraní, které umožňuje to, co bylo již vyvinuto v jazyku
Pascal, používat i pod C++. Běžného uživatele preferujícího jazyk C++ to však
nemusí zajímat. Na druhé straně lze i v C++ Builderu s minimálními problémy
používat již vyvinuté části software psané v Pascalu pro Delphi.
IDE Delphi a C++ Builder byla nejdříve prodávána samostatně. Tato obchodní
strategie byla dodržována až do Delphi verze 7 a C++ Builder verze 6. Později
začala být obojí IDE prodávána jako jeden balík obsahující možnost programování
v jazyku Pascal, C++ a navíc ještě v jazyku C#. Toto prostředí bylo nazváno
Borland Developer Studio (BDS). První verze měla označení 2006. Později se
firma Borland na nějakou dobu přejmenovala na CodeGear a prostředí
přejmenovala na CodeGear RAD Studio. Pod touto hlavičkou vydala až verzi
2009.
Později Borland část firmy zabývající se vývojem vývojových prostředků prodal
firmě Embarcadero, která v současné době uvedla na trh poslední verzi IDE pod
názvem RAD Studio XE2. Lze ovšem zakoupit i samostatně Delphi XE2 a C++
Builder XE2.
V době, kdy se rozhodovalo o prodeji části firmy Borland někomu jinému,
vznikla ještě řada Turbo, která byla dodávaná buď jako freeware nebo velmi levně
jako plnohodnotné prostředí. Z tohoto období pochází verze, kterou používáme na
naší škole. Dnes se již filosofie spolupráce firmy Embarcadero se školami změnila.
(http://www.embt.cz/cs/stranky/16-nabidka-pro-skoly).
V dalším textu budeme naše vývojové prostředí raději nazývat všeobecně
zavedeným názvem C++Builder.
Seznamy aplikací napsaných v Delphi nebo C++ Builderu (Turbo C++)
naleznete zde:
http://www.embarcadero.com/rad-in-action/application-showcase
http://www.delphi.cz/page/zajimave-programy-napsane-v-delphi.aspx
http://www.delphi.wikia.com/wiki/Good_Quality_Applications_Built_With_CppBuilder
http://www.delphi.wikia.com/wiki/Good_Quality_Applications_Built_With_Delphi
12
2
Základy funkce Windows
Obsah hodiny
Seznámíme se, případně si zopakujeme, některé základní pojmy a poznatky o
operačním systému Windows, které budeme potřebovat pro naše další studium.
Klíčová slova
Sdílení procesoru, preemptivní multitasking, task, thread, virtuální paměť,
swapování, sdílení paměti, privilegovaný režim, události, zprávy, dynamicky
linkované knihovny, WIN32 API
DOS - jednoúlohový (jednotaskový) systém. Každý spuštěný program má pro sebe
k dispozici celý počítač s celou jeho pamětí. Nebylo nutné řešit sdílení procesoru,
HW nebo paměti.
Windows – víceúlohový (multitaskový) systém. Bylo nutné vyřešit sdílení
procesoru, HW a paměti mezi úlohami.
Sdílení procesoru – dnes řešeno pomocí preemptivního (vnuceného)
multitaskingu. Operační systém přiděluje jednotlivým úlohám (procesům, taskům)
časové úseky (časová kvanta), ve kterých je procesor k dispozici dané úloze. Po
uplynutí daného úseku jsou informace o tom, kde momentálně úloha skončila,
uloženy a jsou nataženy informace o tom, kde skončila úloha následující. Od
tohoto bodu se pak provádějí instrukce následující úlohy (tasku) až do ukončení
jejího časového úseku. Toto přepínání mezi úlohami probíhá tak rychle, že nám se
zdá, jako by všechny úlohy běžely současně. Předchůdcem preemptivního
multitaskingu byl multitasking kooperativní, ve kterém časová kvanta
nepřiděloval (nevnucoval) operační systém, ale úlohy si procesor předávaly mezi
sebou. Každá úloha vždy dokončila nějakou svou činnost a pak předala práci
úloze následující. Tento multitasking měl řadu nevýhod a proto se již nepoužívá.
Úlohám se ovšem nepřidělují časová kvanta vždy, ale pouze tehdy, když mají co
dělat (dodělává se činnost z jejího minulého časového kvanta nebo úloha dostala
ze systému zprávu, že má dělat něco nového). Tím se zbytečně neztrácí čas
přidělováním časového kvanta úloze, která momentálně stejně nemá co na práci.
Thready (vlákna) – Každá úloha se může rozpadnout na více paralelně
(souběžně) běžících threadů (vláken). Standardně je systémem pro každou úlohu
spuštěn jeden základní thread. Jestliže úloha potřebuje řešit více věcí, které se
mají provádět současně, může si založit thready další. V rámci preemptivního
multitaskingu jsou časová kvanta přidělována jednotlivým threadům, ne
táskům. Když už úloha nějaké vlákno nepotřebuje, může ho zase zrušit.
Samozřejmě kromě hlavního threadu. Ten běží tak dlouho, jak dlouho běží úloha.
13
Privilegia vláken – Časová kvanta nejsou threadům přidělována tak, že by
všechny thready byly v nějakém kolečku, ale uplatňuje se zde také priorita
threadů. Existuje 15 základních úrovní priorit a ty se ještě rozpadají na několik
podúrovní. Jestliže má systém vybrat thread, kterému bude přiděleno následující
časové kvantum, začíná vybírat od threadů s nejvyšší prioritou. Mezi nimi vybere
ale pouze ten thread, který má zrovna co dělat (má rozdělanou práci ze svého
minulého časového kvanta nebo je pro něho připravená zpráva, že má dělat něco
nového). Jestliže systém nenajde vhodný thread mezi těmi s nejvyšší prioritou,
hledá mezi tasky s nižší prioritou atd. Pokud vůbec nenajde thread, kterému by
přidělil čas, začne dělat věci, které jsou součástí základní čekací smyčky (IDLE).
Např. začne uklízet paměť.
Virtuální paměť – Každá nově spuštěná úloha (task) dostane přidělenu operační
paměť. U běžných Win XP a podobných dostane každá úloha 4GB paměti,
z toho 2GB pro vlastní úlohu a 2GB pro systém. To ovšem neznamená, že tolik
paměti musí být nainstalováno na motherboardu. Hardwarová paměť jakoby
pokračuje na harddisku takzvaným swapovacím souborem (pagefile.sys). Úloha
si pak „myslí“, že má pro sebe 4GB paměti, ale taková paměť ve skutečnosti
neexistuje. Proto mluvíme o virtuální paměti.
Celá virtuální paměť se rozpadá na stránky. Vždy, když potřebuje proces novou
paměť, je mu přidělená další stránka. Takže oněch 4GB je vlastně teoretická
velikost, kterou úloha takřka určitě skoro nikdy nevyužije.
V reálné paměti je od všech procesů uloženo pouze tolik stránek. Tolik, kolik se
jich tam vejde. Další jsou uloženy ve swapovacím souboru. Jestliže potřebuje
některý proces pracovat se stránkou, která momentálně není v reálné paměti, část
operačního systému, která je nazývána správce paměti, najde v reálné paměti
stránku, která už nebyla dlouho používaná, tu uloží na harddisk do swapovacího
souboru a na její místo ze swapovacího souboru umístí stránku, která je
momentálně potřeba. Tomuto procesu se říká swapování.
Sdílení paměti mezi procesy – Systém pracuje tak, že normálně si jednotlivé
úlohy navzájem nevidí do svých paměťových prostorů. Každá úloha se chová, jako
by žádná jiná paměť neexistovala, jen ta její. Potřebujeme-li předávat data z jedné
úlohy do jiné, je nutno na toto použít některé speciální postupy. Nejjednodušší, ale
asi nejméně vhodné, je např. předávání dat přes soubory. To je ovšem pomalé.
Proto obsahuje systém další prostředky pro předávání dat mezi úlohami. Normálně
ale platí, že úloha vidí pouze do svého paměťového prostoru, který je sdílen
všemi thready této úlohy. Tím je zajištěno, že úlohy si navzájem nemohou
přepisovat svá data.
Privilegovaný režim - je metoda, jak ochránit systém proti chybě hardware nebo
software. V privilegovaném režimu běží operační systém a drivery zařízení,
zatímco uživatelské úlohy jsou procesorem prováděny v neprivilegovaném režimu.
V neprivilegovaném režimu nelze provádět některé instrukce. Tím je zajištěno, že
běžná uživatelská úloha nemůže provést něco, co by způsobilo zhroucení celého
systému nebo by např. začala tisknout svá data do rozpracovaného tisku jiné
úlohy. Úroveň práv je každému procesu systémem přidělena už při jeho spouštění
a nedá se tak snadno změnit. To ovšem neznamená, že náš program nemůže
využívat služeb systému nebo driverů, které běží v privilegovaném režimu. Počet
14
úrovní privilegií může být různý a záleží na procesoru. Procesory fy. Intel a jejich
odvozeniny využívají 4 úrovně. Jiné procesory mají třeba úrovně pouze dvě.
Operační systém se pak s tím musí nějak vypořádat. V privilegovaném režimu běží
např. jádro operačního systému a drivery zařízení.
Zprávy – Všechny spuštěné procesy, jejich thready, pracují na základě zpráv.
Zpráva je malá struktura dat, která vzniká v systému na základě událostí. Např.
bylo klepnuto na klávesnici, přišla data z USB, uběhl hlídaný čas, nějaká úloha
vyslala zprávu s nějakým požadavkem atd.
Tyto zprávy jsou systémem umísťovány do fronty zpráv. V datech zprávy je vždy
také uvedeno, kdo je jejím příjemcem. Např. příjemcem zprávy o klepnutí na
klávesnici bude to okno, které je momentálně na obrazovce na povrchu a je
vybráno. Na základě toho systém tyto zprávy rozesílá do front zpráv jednotlivých
threadů. Pokud má thread ve své frontě zpráv nějakou neobslouženou zprávu je to
znamení pro operační systém, aby při nejbližší vhodné příležitosti přidělil tomuto
vláknu časové kvantum, ve kterém se začne tato zpráva obsluhovat. Zároveň bude
zpráva z fronty odstraněna. Pokud ve frontě zpráv zůstane nějaká zpráva, která
nikomu nepatří, je tato po nějaké době zrušena.
Struktura zprávy vypadá takto:
typedef struct tagMSG {
HWND
hwnd; // identifikátor okna, příjemce zprávy
UINT
message;
// číslo označující typ zprávy
WPARAM wParam;
// dodatečná informace ke zprávě
LPARAM lParam;
// dodatečná informace ke zprávě
DWORD time;
// čas kdy byla zpráva zaslána
POINT pt;
// pozice kurzoru v okamžiku odeslání
} MSG;
Čísla zpráv mají pro snazší zapamatování přidělena symbolické názvy. Například
existuje zpráva WM_SYSCOMMAND, která je hlášením např. o tom, že jsme oknu
klepli myší na ikonky v pravém horním rohu. Pod WM_SYSCOMMAND se
schovává číslo 0x0112. Číslo uložené ve wParam blíže určuje o jaký typ události
se chová (minimalizace, maximalizace, zavření okna atd.), lParam navíc obsahuje
souřadnice kurzoru v okamžiku kliku myší. Blíže viz nápovědný soubor
"C:\Program Files\Common Files\Borland Shared\MSHelp\win32.hlp".
Typů zpráv existuje desítky a v případě potřeby si může programátor vytvořit i své
další.
Zprávy lze zasílat z jednoho procesu do jiného procesu a tím ho ovládat nebo mu
zasílat malé objemy dat (jeden z dalších možných způsobu jak předávat data mezi
různými procesy).
Dynamicky linkované knihovny (dll) – V DOSu byly při překladu programu
všechny knihovny přilinkovány přímo do spustitelného souboru (statické linkování).
To už v dnešní době není možné. Předpokládejme např., že někdo ve svém
programu použije knihovnu funkcí, která je např. dodávána s operačním
systémem. Po nějaké době, ale výrobce této knihovny zjistí, že je v ní chyba.
V DOSu by to znamenalo, že ten, kdo tuto chybnou knihovnu použil, by musel svůj
program znovu s již opravenou knihovnou slinkovat a novou verzi svého programu
rozeslat všem svým zákazníkům. A při nějaké další nově opravené chybě opět
znovu, atd. To by bylo v tak rozsáhlém systému, jakým Windows jsou,
15
neudržitelné. Aby se toto nemuselo provádět, byly vyvinuty dynamicky linkované
knihovny. Jejich soubory mají příponu dll. Dynamicky linkované se tyto knihovny
nazývají proto, protože se k již hotovému programu připojují až v době jeho
spuštění nebo dokonce za běhu programu až v době, kdy jsou potřeba. Program,
který je používá, v sobě pouze obsahuje název souboru s danou knihovnou a
názvy a volání funkcí, které chce z knihovny použít. Tím se stane, že jestliže
dodavatel např. operačního systému uživateli dodá novou verzi dll, není nutné, aby
všichni dodavatelé SW, kteří tuto knihovnu použili, museli své programy znovu
linkovat a znovu rozesílat koncovým uživatelům.
Všechny dll knihovny se natahují do paměťového prostoru, který je společný všem
běžícím procesům. Jestliže tedy např. tři procesy používají tutéž dll knihovnu,
nachází se v paměti pouze jedna její kopie. A teprve až všechny tři procesy tuto
knihovnu opustí, je tato odstraněna z paměti. Dll knihovna bude ovšem pro svou
práci potřebovat také paměť s pracovními daty (proměnnými). Tyto paměťové
prostory jsou samozřejmě každému uživateli vytvořeny odděleně v jeho
paměťovém prostoru. Tím je zajištěno, že např. tři použití téže dll knihovny ve
třech různých procesech se navzájem neovlivňují.
Win32 API (Windows 32 bit aplication interface) - Jedná se o obrovský systém
různých funkcí sloužících programátorům pro práci s Windows. Obsahuje tisíce
různých funkcí, deklarací datových struktur, konstant a maker. Jeho
pokračovatelem je knihovna . NET (dot net). Win32 API je ještě psán neobjektově.
Místo objektů používá pro odkazování se např. na různá spuštěná okna, spuštěné
procesy a podobně systém handlů. Existují handly oken, procesů a mnoha jiných
věcí. .NET je již psán objektově.
Win32 API obsahuje stovky dll knihoven, které jsou převážně umístěny v adresáři
Windows\System32.
Základní struktura jednoduchého programu ve Windows
#include <windows.h>
HINSTANCE g_hInstance;
HWND g_hwndMain;
MSG msg;
// funkce, která je zavolána ve funkci DispatchMessage(&msg);
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch ( message ) {
case WM_DESTROY:
// zpráva s požadavkem na zavření okna
PostQuitMessage(0);// zašlu systému zprávu WM_QUIT,
breaků
// že chci aplikaci ukončit
case WM_SYSCOMMAND:
if(wParam==SC_MAXIMIZE) // při maximalizaci okna písknu
Beep(1000,100);
if(wParam==SC_MINIMIZE) // pří minimalizaci nepustím dále
return NULL;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
// hlavní funkce programu
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst,
16
LPSTR lpCmdLine, int nShow)
{
WNDCLASSEX wc;
// zaregistrování druhu mého okna v systému
wc.cbSize = sizeof(WNDCLASSEX);
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wc.hInstance = g_hInstance;
wc.lpfnWndProc = WindowProcedure;
wc.lpszClassName = "HlavniTrida";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
if ( !RegisterClassEx(&wc) )
return FALSE;
// vytvoreni hlavniho okna programu
g_hwndMain = CreateWindowEx(0, "HlavniTrida",
"Mùj první pokus",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 450, 350,
NULL, NULL, g_hInstance, NULL);
if( g_hwndMain == NULL ) // okno se nepodařilo vytvořit
return FALSE;
// končím
g_hInstance = hInstance; // zapamatuji si handle instance
// přidělené mému programu
// v GetMessage čekám až bude ve frontě nějaká zpráva
while( GetMessage(&msg, NULL, 0, 0) )
{
TranslateMessage(&msg);
// přepočet zprávy
DispatchMessage(&msg);
// pošlu zprávu fci WinProcedure
}
// v této smyčce zůstává program tak dlouho,
// dokud GetMessage nevrátí nulu, což znamená,
// že přišel požadavek na konec programu
return msg.wParam;
// vrátím wParam ze zprávy WM_QUIT
}
I ten nejjednodušší program ve Windows musí obsahovat dvě funkce. WinMain,
která nahrazuje funkci main v konzolových aplikacích a funkci typu CALLBACK,
kterou systém zavolá vždy, když se zjistí, že ve frontě zpráv je nějaká zpráva. Tato
funkce pak na zprávu případně zareaguje a pustí ji případně dále do systému pro
standardní zpracování. V našem příkladu je tato funkce nazvána
WindowProcedure.
Už z tohoto jednoduchého příkladu je zřejmé, že při psaní programu pouze pomocí
Win32 API je třeba znát veliký soubor různých funkcí a datových struktur. Do
něčeho takového velmi dlouho nutilo uživatele vývojové prostředí Visual C++ firmy
Microsoft. Firma Borland se rozhodla jít jinou cestou. Vytvořila vlastní objektovou
knihovnu VCL a do jednotlivých tříd knihovny zabalila celou onu titěrnou a
zdlouhavou práci s knihovnou Win32 API.
17
Uživatel knihovny VCL pouze nastaví některá členská data příslušného objektu,
kterými blíže určí co má objekt přesně dělat a naplnění všech struktur a volání
funkcí Win32 API už provede objekt za něj sám.
I přesto se může stát, že někdy potřebujeme provést činnost, která v knihovně
VCL nebyla vyřešena. Pak nám nic nebrání v tom, abychom na takových místech
použili volání standardních funkcí Win32 API.
Pro vyzkoušení ukázkového programu vytvořte novou konzolovou aplikaci.
V úvodním okně návrhu konzolové aplikace ale v pravém rámečku zrušte
všechny volby!
Do souboru Unit1.cpp nakopírujte ukázkový program, přeložte ho a spusťte.
18
3
Nová vlastnost tříd __property
Obsah hodiny
Ukážeme si novou vlastnost tříd v IDE C++ Builder, kterou můžeme využít i ve
svých třídách, které jsme cvičili při výuce objektového programování.
Klíčová slova
__property, vlastnosti třídy.
Autoři vývojového prostředí firmy Borland zavedli ve třídách nový pojem vlastnost
- property. Umožňuje nám zacházet s členskými daty naší třídy tak, jako bychom
do nich přímo psali nebo je přímo vyčítali. Vývojové prostředí už za nás samo
zavolá vhodné přístupové metody. Nemluvíme zde pak o členských datech, ale o
vlastnostech třídy.
Vlastnosti třídy deklarujeme pomocí klíčového slova __property. Příklad
deklarace vlastnosti může vypadat např. takto:
__property float Vyska = {read = GetVyska, write=SetVyska};
Vlastnost Vyska zde představuje hodnotu typu float. Pro vyčtení této vlastnosti
bude volána metoda GetVyska, která vyčte příslušný členský údaj např. FVyska.
Přiřazení do vlastnosti Vyska zavolá metodu SetVyska, která členský údaj FVyska
naplní hodnotou, kterou jsme přiřadili do vlastnosti Vyska.
Borland ve svých programech dodržuje pravidlo, že názvy členských dat začínají
písmenem F a odpovídající vlastnost má název stejný, pouze bez onoho úvodního
písmene F.
Příklad
class Datum
{
private:
int FRok,FMesic,FDen;
void SetRok(int r) {FRok=r;}
void SetMesic(int m) {FMesic=m;}
void SetDen(int d) {FDen=d;}
int GetRok() {return FRok;}
int GetMesic() {return FMesic;}
int GetDen() {return FDen;}
public:
__property int Rok = {read=GetRok, write=SetRok};
__property int Mesic = {read=GetMesic, write=SetMesic};
__property int Den = {read=GetDen, write=SetDen};
Datum() {Rok=1900; Mesic=1; Den=1;}
Datum(int r,int m,int d) {Rok=r;Mesic=m;Den=d;}
};
19
void main()
{
Datum d;
int rok=d.Rok, mesic=d.Mesic, den=d.Den;
d.Rok=2000;
d.Mesic=5;
d.Den=20;
}
Díky klíčovému slovu __property můžeme do jednotlivých datových členů třídy
zapisovat a z nich číst takřka jako bychom pracovali přímo s nimi i přesto, že jsou
v sekci private. Při takovémto čtení a zápisu ovšem vlastně voláme přístupové
metody a neporušujeme zde tedy známé pravidlo, že k datovým členům nemá mít
uživatel přímý přístup. Při zápisu můžeme tak provést např. i kontroly na správnost
zapisovaného údaje nebo současně zapisovat i do více datových členů.
Jestliže používáme vlastnosti třídy (__property), můžeme na členská data a
přístupové metody při používání třídy prakticky zapomenout a soustředíme
se pouze na práci s vlastnostmi. Zápis je pak jednodušší a čitelnější.
Obecný popis klíčového slova __property je tento:
__property type propertyName[index1Type index1][indexNType indexN]
= { attributes };
„Indexy“ zde představují parametry které budou posílány přístupovým metodám.
„Atributy“ představují příkazy read a write, tak jak jsme je použili v předcházejícím
příkladu.
Nemusíme však vždy používat read i write současně. Pokud uvedeme pouze
read, bude možné příslušnou vlastnost pouze číst a ne do ní i zapisovat.
Následující příklad ukazuje některé možnosti deklarací property.
Příklad
class Priklad
{
private:
int Fx,Fy;
float Fdata[100][100];
protected:
int getX() {return Fx;}
void setX(int x) {Fx = x;}
float pocitejZ() {
float pom;
// Nejaky výpočet……
return pom;
}
float hodnotaBunky(int r,int s) {return Fdata[r][s];}
public:
__property int
X = {read=getX, write=setX};
__property int
Y = {read=Fy};
__property float Z = {read=pocitejZ};
__property float Bunka[int r][int s] = {read=hodnotaBunky};
};
Tento příklad ukazuje několik deklarací vlastnosti-property.
20
Property X má přístup k datovému členu třídy pro čtení i psaní prostřednictvím
metod getX a setX.
Property Y přistupuje přímo ke členské proměnné Fy, která je pouze pro čtení.
Nehrozí zde proto nebezpečí, že by mohl uživatel hodnotu Fy nějak nevhodně
změnit.
Property Z je pouze pro čtení. Tato hodnota je počítána a není ukládána ve třídě
jako členský údaj.
Property Bunka představuje property se dvěmi indexy.
Následující příklad ukazuje jak budeme jednotlivé property používat:
Priklad MujPriklad;
MujPriklad.X = 42;
// vyhodnotí se takto: MujPriklad.setX(42);
int hodnota = MujPriklad.Y;
// vyhodnotí se takto: hodnota = MujPriklad.Fy;
float hodnota1 = MujPriklad.Z;
// vyhodnoti se takto: hodnota1 = MujPriklad.pocitejZ();
float hodnoat2 = MujPriklad.Bunka[3][7];
// vyhodnotí se takto: hodnota2 = MujPriklad.hodnotaBunky(3,7);
To, že jsme v našem příkladu použili „indexy“ v deklaraci __property jako
opravdové indexy v poli, ještě neznamená, že je nemůžeme použít jako obecné
parametry metody, kterou pomocí vlastnosti voláme. Ostatně i v našem příkladu
indexy 3 a 7 vlastně představují obecné parametry metody „hodnotaBunky“.
Kontrolní otázky a úkoly
Navrhněte a vyzkoušejte třídu „PoložkaSkladu“ která bude používat vlastnosti
(property) třídy.
21
4
Datový typ Set - množina
Obsah hodiny
Poznáme nový datový typ Set
Klíčová slova
Set, Continue
Mnoho komponent má vlastnosti, které mohou nabývat několika stavů. Jednotlivé
stavy se přitom navzájem nevylučují a mohou být nastaveny i současně. Pro tyto
potřeby byl navržen speciální datový typ Set – množina. V podstatě se za tímto
typem schovává řada bytů. Tyto jsou u typu Set využívány tak, že každý bit řady
bytů představuje jednu možnost. Má-li být určitá možnost nastavena, je nastaven
odpovídající bit do jedničky.
Chceme-li navrhnout novou množinu, pak postupujeme tak, že nejdříve
navrhneme výčtový typ, který představuje jednotlivé možnosti a pak pomocí
příkazu typedef definujeme novou množinu tak, že v příkazu do závorek < a >
uvedeme nejdříve název našeho výčtového typu a pak minimální a maximální
hodnotu výčtu. Za závorku > pak uvedeme název nové množiny.
Např. takto:
enum TSmer {tsVlevo, tsVpravo, tsNahoru, tsDolu};
typedef Set < TSmer, tsVlevo, tsDolu > TSmery;
Možná se budete podivovat zvláštní deklaraci datového typu s podivnými
závorkami < a >. Deklarace je správně! Je zde totiž použita tzv. šablona –
template. Templates jsou v podstatě pokračování maker. Někde na pozadí je
navržen obecný vzor pro Set, který má místo našich TSmer, tsVlevo, tsDolu
nějaké obecné názvy. Pomocí závorek < a > za ně dosadíme naše konkrétní
názvy. Zatímco makra nedokázala vlastně dělat žádné kontroly, tak Templates při
svém překladu provádějí i syntaktickou kontrolu.
Templates jsou ovšem mimo rozsah tohoto kurzu. Berte proto tento zápis jako vzor
a příliš nad ním nepřemýšlejte.
S datovým typem Set můžeme provádět různé operace. Jedná se o třídu, která
kromě konstruktoru obsahuje řadu metod a řadu přetížených operátorů.
Set
Clear
Contains
__fastcall Set();
implicitní konstruktor
__fastcall Set(const Set& s);
copy konstruktor
Set& __fastcall Clear();
odstraní všechny členy objektu Set
bool __fastcall Contains(const T prvek) const;
22
vrací true pokud Set obsahuje prvek element
bool __fastcall Empty() const;
vrací true pokud je Set prázdný
operator Set __fastcall operator - (const Set& s) const;
vrací Set, který obsahuje prvky, které nejsou zároveň součástí
množiny s
operator !=
bool __fastcall operator != (const Set& s) const ;
vrací true jestliže oba Set neobsahují přesně stejné prvky
operator *
Set __fastcall operator *(const Set& s) const;
provádí operaci and mezi prvky obou objektů Set a vrací
výsledek
operator *=
Set& __fastcall operator *= (const Set& s);
provádí operaci and mezi prvky obou objektů Set a výsledek
vrací zpět do objektu
operator +
Set __fastcall operator + (const Set& s) const;
provádí operaci or mezi prvky obou objektů Set a vrací výsledek
operator +=
Set& __fastcall operator += (const Set& s);
provádí operaci or mezi prvky obou objektů Set a výsledek vrací
zpět do objektu
operator -=
Set& __fastcall operator -=(const Set& s);
provádí operaci xor (negovaná ekvivalence) mezi prvky obou
objektů Set a výsledek vrací zpět do objektu
operator =
Set& __fastcall operator =(const Set& s);
přiřazení obsahu s do tohoto Set
operator ==
bool __fastcall operator ==(const Set& s) const;
porovnání obsahů obou Set
operator << (addition)
Set& __fastcall operator <<(const T prvek);
operator přidá prvek do Set
operator << (streaming) friend ostream& operator <<(ostream& os,
const Set& arg);
spřátelená funkce pro výpis nastavení Set do streamu ve formě
nul a jedniček
operator >> (subtraction) Set& __fastcall operator >>(const T prvek);
operator odebere prvek ze Set
operator >> (streaming) friend istream& operator >>(istream& is,
Set& arg);
spřátelená funkce pro vstup textu ve formě nul a jedniček
určujících nastavení Set
Empty
Vypsali jsme si zde všechny deklarace již z toho důvodu, protože tato třída je
vlastně výborným opakováním přetěžování operátorů třídy.
Příklad:
//
enum
//
//
deklarace výčtového typu určujícího názvy prvků Set
TSmer {tsVlevo, tsVpravo, tsNahoru, tsDolu};
deklarace nové množiny
mezi < > uvádíme název výčtu, minimální a maximální hodnotu
23
typedef Set < TSmer, tsVlevo, tsDolu > TSmery;
// nejčastější použití
TSmery smery; // deklarace nového objektu množiny TSmery
smery << tsVlevo << tsVpravo; // nastavení dvou prvků do true
smery >> tsVpravo; // nastavení prvku tsVpravo do false
if(smery.Contains(tsVpravo))
// dotaz zda je tsVpravo true
Kontrolní otázky a úkoly
Navrhněte datový typ Set pro dny v týdnu a vymyslete vhodný příklad, ve kterém
ho použijete.
24
5
Práce s IDE C++ Builder
Obsah hodiny
Vysvětlíme si základní filosofii práce IDE C++ Builder.
Klíčová slova
VCL, TObject, TComponent, TControl, TWinControl, TGraphicControl,
Exception, __published, __property, formulář, komponenta
5.1
Knihovna VCL
Základem všech verzí vývojových prostředků firmy Borland ať už v jazyku Pascal
nebo C++ je knihovna VCL (Visual Component Library). Jedná se o rozsáhlou
knihovnu obsahující složitou hierarchií tříd.
Základní třídy knihovny VCL jsou tyto
TObject
Základ a prapředek všech tříd knihovny VCL
TPersistent
přímý potomek třídy TObject, základ všech tříd, které jsou
schopné přiřazení a zápisu do streamů
TComponent
přímý potomek třídy TPersistent, předek všech komponent,
které se mohou objevit při návrhu formulářů (oken)
TControl
přímý potomek třídy TComponent, předek všech visualních
komponent
TWinControl
přímý potomek třídy TControl, předek všech komponent, které
můžeme na obrazovce vybrat, mohou získat focus
TGrapficControl přímý potomek třídy TControl, předek všech komponent, které
se mohou zobrazit na ploše formuláře, ale nemohou získat
focus
Exception
přímý potomek třídy TObject, předek všech vyjímek v knihovně
VCL – např. chyba převodu EConversionError
Knihovna VCL je ještě napsaná v Pascalu.
Takřka všechny objekty knihovny VCL musí být alokovány dynamicky, tedy
pomocí operátoru new.
Knihovna VCL nepodporuje implicitní parametry funkcí. To ovšem neznamená, že
bychom ve svých třídách, které nejsou potomky tříd knihovny VCL, nemohli
implicitní hodnoty parametrů používat!
25
Borland své třídy kromě standardních atributů private, protected, public rozšířil o
atribut __published.
S členy třídy, které jsou v sekci __published pracuje vývojové prostředí a
běžný programátor do této oblasti třídy nezasahuje. Jinak dojde
k nepředvídanému chování vývojového prostředí.
Další vlastností, kterou Borland přidal do svých tříd, je __property. Tato vlastnost,
jak jsme si již vysvětlili v minulé kapitole, dovoluje uživateli třídy snadno používat
přístupové metody ke členům třídy tak, jako bychom zapisovali nebo četli přímo
z nějaké členské proměnné pomocí operátoru přiřazení. Přitom ale tímto
způsobem voláme příslušné přístupové metody.
Všechna okna našeho programu jsou v IDE firmy Borland nazývány formuláře.
Všechny grafické objekty, které se používají v programech pod Windows pro jejich
ovládání nazýváme komponenty. V klasických programech pro Windows jsou
komponenty ovládány celou řadou různých funkcí knihovny WIN32 API. Firma
Borland ovládání komponent schovala do tříd knihovny VCL. Pomoci nich pak
můžeme komponenty zakládat a ovládat aniž musíme znát knihovnu WIN32 API.
Na druhé straně, protože jsou komponenty odvozeny od standardních ovládacích
prvků Windows, lze s nimi často pracovat i pomocí funkcí Win32 API.
Protože komponent je velké množství, jsou v IDE přehledně uspořádány do
skupin. Skupiny, s kterými budeme pracovat my, jsou hlavně Standard,
Additional, Win32, System, Dialogs a Win3.1. Kromě těchto skupin existují ještě
další, které obsahují komponenty pro práci s databázemi, internetem atd. I ve
skupinách, které budeme používat, je řada komponent, s kterými pracovat
nebudeme a které si v případě potřeby budete muset nastudovat sami.
5.2
Volací konvence:
Existuje několik způsobů jak předávat parametry funkci:
__cdecl, __pascal, __fastcall, __stdcall
Každý z těchto způsobů dodržuje jiná pravidla. Měli bychom dávat přednost
__fastcall. Tehdy se snaží kompilátor přeložit volání funkce tak, aby co nejvíce
parametrů uložil do registrů procesoru. Teprve když už nemá žádný registr volný,
ukládá zbývající parametry do stacku. Tento způsob volání funkcí je
nejrychlejší.
Pouze ve výjimečných případech používáme __cdecl, __pascal.
Např. __cdecl (je použito i tehdy, když neuvedeme žádné klíčové slovo) se
používá při volání funkcí Win32 API.
Příklady:
//deklarace funkce
void __fastcall FormClick(TObject *Sender);
// definice funkce
void __fastcall TForm1::FormClick(TObject *Sender)
26
5.3
Rozšíření vlatností __property
Pro objekty, které jsou potomky třídy VCL TPersistent k vlastnostem write a read
přibyly ještě index, default a nodefault.
Vytvořme třídu, která deklaruje tři vlastnosti. Klíčové slovo index umožňuje, aby
všechny tři vlastnosti měly pro read a write stejné přístupové metody.
class PACKAGE TKalendar : public TObject
{
private:
int __fastcall GetPrvekData(int Index);
void __fastcall SetPrvekData(int Index, int Hodnota);
public
__property int Den = {read=GetPrvekData, write=SetPrvekData,
index=3, nodefault};
__property int Mesic = {read=GetPrvekData, write=SetPrvekData,
index=2, nodefault};
__property int Rok = {read=GetPrvekData, write=SetPrvekData,
index=1, nodefault};
};
Protože všechny prvky data (den, mesic, rok) jsou typu int a protože čtení i
nastavení každého vyžaduje obdobnou činnost vytvoříme společné metody.
int __fastcall TKalendar::GetPrvekData(int Index)
{
unsigned short rok, mesic, den;
int vysledek;
// datum je uloženo v FDatum typu TDateTime
FDatum.DecodeDate(&rok, &mesic, &den);
switch (Index) {
case 1: vysledek = rok; break;
case 2: vysledek = mesic; break;
case 3: vysledek = den; break;
default: vysledek = -1;
}
return vysledek;
}
void __fastcall TKalendar::SetPrvekData(int Index, int Hodnota)
{
unsigned short rok, mesic, den;
if (Hodnota > 0) // všechny prvky musí být kladné
{
FDatum.DecodeDate(&rok, &mesic, &den);
switch (Index)
{
case 1: rok = Hodnota; break;
case 2: mesic = Hodnota; break;
case 3: den = Hodnota;
break;
default: return;
}
}
FDate = TDateTime(rok, mesic, den); // nastavím změněne datum
}
27
Jestliže deklarujeme property, můžeme uvést její default hodnotu. C++Builder
používá default hodnotu pro určení, zda tuto vlastnost uchovávat, když je objekt
ukládán do souboru. Jestliže není uvedena default hodnota property, C++Builder
takovou property vždy ukládá.
Default hodnotu zapisujeme takto:
__property int Hight = {default=100};
Deklarování default hodnoty ale výchozí hodnotu property nenastaví! To
musíme provést sami v konstruktoru. Je vhodné si ovšem uvědomit, že objekty své
datové členy vždy automaticky nastavují na nulu, řetězce na NULL a logické členy
na false. Nastavováni na nulu tudíž v konstruktoru provádět nemusíme.
Opakem ke klíčovému slovy default je nodefault. Tímto slovem můžeme
zdůraznit, že property nemá default hodnotu a že tedy při zápisu do souboru má
být ukládána.
5.4
Základy ovládání IDE
Při zakládání našeho projektu využívajícího knihovnu VCL zvolíme vytvoření
nového projektu typu VCL Application. IDE spustí návrh aplikace a na obrazovce
se objeví okno, které bude podobné shora uvedenému obrázku. Okno je rozděleno
na několik částí. Uprostřed se nachází návrh našeho prvního formuláře a na obou
stranách budou rozloženy části nazvané Object Inspector, Structure, Tool
Palete a Project Manager. Pokud nám tyto plochy momentálně překážejí,
28
můžeme je srolovat na levou a pravou stranu pomocí „špendlíku“ v jejich horním
pravém okraji. Z ploch zůstanou vidět pouze malé části. Pokud na ně ukážeme
kurzorem, plochy se na chvíli zvětší na původní velikost a my s nimi můžeme
pracovat.
Object Inspektor slouží pro nastavování Properties (vlastností) formuláře a
komponent na něm ležících a obslužných metod Events (událostí). Pokud nám
nedostačuje šířka Object Inspektoru, můžeme si ho kdykoliv tažením myší patřičně
zvětšit. Můžeme si vhodně pomocí tažení myší nastavit i polohu střední dělící čáry
mezi sloupci. Object Inspektor můžeme rovněž kdykoliv zneviditelnit kliknutím na
křížek v jeho pravém horním rohu. Znovu zobrazit ho opět můžeme v nabídce
View.
Structure je panel, ve kterém se zobrazují jednotlivé objekty našeho formuláře.
Kliknutím na ně si můžeme snadno zobrazit jejich data v Object Inspector.
Tool Palete je panel používaný pro výběr komponenty, kterou chceme položit na
plochu formuláře. Pro přehlednost jsou komponenty rozděleny do skupin.
Nejčastější komponenty jsou ve skupině Standard. Jestliže již máme otevřeno
hodně skupin komponent a hledání v nich začíná být nepřehledné, můžeme
kliknutím pravým tlačítkem myši na plochu panelu vyvolat kontextovou nabídku, ve
které vybereme položku Colapse All.
Project Manger je panel, ve kterém vidíme všechny soubory, ze kterých se skládá
náš projekt. Kliknutím na některou z položek můžeme tento soubor zobrazit. Na
spodním okraji střední plochy máme možnost zvolit, co o daném souboru chceme
vidět. Zda vzhled formuláře, jeho hlavičkový soubor, zdrojový soubor nebo stránku
dokumentace o něm.
Při překladu projektu se ještě ve spodní části objeví panel s hlášeními kompilátoru
a linkeru.
Je vhodné celý náš projekt během vývoje neustále ukládat. Zamezíme tak tomu,
že např. když dojde k nečekanému resetu počítače a celá naše práce je
nenávratně ztracena.
Se základním ovládáním vývojového prostředí se záhy seznámíme metodou
pokus-omyl. Celé ovládání je velmi intuitivní.
5.5
Nastavení projektu
Častým problémem bývá, jak to zařídit, aby program bylo možno spustit „tak jak je“
i na „čistém počítači“, tedy bez nainstalovaných balíčků a dalších runtimových
knihoven.
Pokud tedy chcete mít veškerý potřebný kód v jednom exe souboru, musíte vybrat
volbu „Project / Options“ a na záložce „Packages“ nesmíte mít zaškrtnutou volbu
„Build with runtime packages“. Znamená to, že program nebude používat
runtimové balíčky, což jsou v podstatě běžné DLL knihovny, pouze mají koncovku
29
BPL. Dále na záložce „Linker / Linking“ nesmíte mít zaškrtnutou volbu „Use
dynamic RTL“, tedy dynamické použití runtimové knihovny.
Výsledkem těchto nastavení je samozřejmě podstatně zvětšený exe soubor, který
však můžete spustit kdekoli na „čistých Windows“.
V nastavení „Project / Build Configurations“ lze nastavit dva typy výsledného
programu „Debug build“ a „Release build“. Verze Debug je samozřejmě určena
výhradně pro ladění, tedy spouštění uvnitř vývojového prostředí (IDE). Obsahuje
totiž v sobě vše to, co je potřebné pro ladění programu, jako krokování, definování
breakpointů a tak dále. Důsledkem je pochopitelně větší a pomalejší kód.
Poslední nastavení projektu, o kterém se zmíníme, je záložka „Application“ opět ve
volbě „Project / Options“. Zde je možné vybrat ikonu, která bude hlavní ikonou
aplikace, tedy tou ikonou, která se standardně zobrazí v systémové nabídce (vlevo
nahoře) formuláře a která reprezentuje aplikaci na panelu úloh. Dále tato ikona
většinou reprezentuje aplikaci (tedy její exe soubor) v průzkumníku.
30
6
Základy funkce IDE C++ Builder
Obsah hodiny
Seznámíme se stavbou jednoduchého projektu, se soubory, které obsahuje a
s jejich obsahem.
Po založení nového projektu získáme několik podstatných souborů:
Project1.bdsproj – projektový soubor. Zde je popsáno z kterých souborů se náš
projekt skládá a jak se má přeložit a slinkovat. Tento soubor spravuje IDE, my do
něho běžně nezasahujeme. Název tohoto souboru určuje zároveň název
výsledného spustitelného souboru.
Project1.cpp – hlavní soubor aplikace. Obsahuje funkci WinMain. Tento soubor
obyčejně mnoho neupravujeme, pracuje s nim hlavně IDE.
Project1.res – soubor se zdroji (resources) projektu. Obsahuje např. ikonu
programu a podobně. S resource zdroji pracuje hlavně vývojové prostředí firmy
Microsoft. Vývojové prostředí Borlandu preferuje soubory dfm. To ovšem
neznamená, že je nemůžeme používat i zde. Je to ale ve většině případů
zbytečné.
Unit1.h – hlavičkový soubor našeho formuláře. Obsahuje hlavně deklaraci třídy
našeho formuláře.
Unit1.cpp – zdrojový soubor našeho formuláře. Obsahuje hlavně definice metod
třídy našeho formuláře.
Unit1.dfm – soubor se zápisem grafického uživatelského rozhraní tohoto
formuláře (okna). Obsahuje instrukce jak vykreslit náš formulář. Náhrada souborů
zdrojů.
Debug_Build – adresář obsahující object moduly (.obj) všech zdrojových souborů
našeho projektu, předkompilované hlavičkové soubory (.tds) a spustitelný soubor
(.exe). Zde ve verzi potřebné pro debugger.
Release_Build - adresář obsahující object moduly (.obj) všech zdrojových
souborů našeho projektu, předkompilované hlavičkové soubory (.tds) a spustitelný
soubor (.exe). Zde ve verzi již bez informací pro debugger připravené k předání
uživateli.
__history – adresář se záložními verzemi našich souborů.
Pro uchování našeho projektu je nutné ukládat pouze soubory s příponami:
bdsproj, res, dfm, cpp, h. Vše ostatní lze smazat!
31
6.1
Obsah důležitých souborů projektu po založení:
Project1.cpp
#include <vcl.h>
#pragma hdrstop
// hlavní hlavičkový soubor knihovny VCL
// všechny hlav.soubory před tímto řádkem
// budou překládány do předkompilovaných
// hlavičkových souboru (*.tds) a jejich
// překlad příště bude podstatně rychlejší
//----------------------------------------------------------------USEFORM("Unit1.cpp", Form1);
//----------------------------------------------------------------// hlavní funkce celého programu
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
// sekce pro odchytávání vyjímek
{
Application->Initialize(); // počáteční inicializace aplikace
// vytvoření prvního a hlavního formuláře aplikace
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
// aplikace běží v této funkci
}
catch (Exception &exception)
// zobrazení případných vyjimek {
Application->ShowException(&exception);
}
catch (...)
// ošetřeni všech jiných vyjimek
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
Unit1.h
#ifndef Unit1H
// ochrana proti vícenásobnému vložení souboru
#define Unit1H
// deklarace pomocného makra
//----------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------class TForm1 : public TForm
// deklarace naší třídy TForm1,
// potomka třídy TForm
{
__published:
// IDE-managed Components – zde neměnit!!!!
private:
// User declarations
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
// deklarace konstruktoru
};
//-----------------------------------------------------------------
32
extern PACKAGE TForm1 *Form1; // exportujeme pointer pro uložení
// adresy instance našeho formuláře
//----------------------------------------------------------------#endif
// konec ochrany proti vícenásobnému vložení
Unit.cpp
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
// zde je popis, jak vytvořit formulář
TForm1 *Form1;
//----------------------------------------------------------------// definice konstruktoru třídy našeho formuláře
// včetně volání konstruktoru předka
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
Unit1.dfm
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 326
ClientWidth = 686
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
end
6.2
// zápis jak vytvořit formulář
Základní vlastnosti komponent
Prakticky u každé použité komponenty z knihovny VCL vždy sledujeme jaké má
základní vlastnosti (properties), metody a reakce na základní události (events).
Events jsou vlastně také metody, ale specializované na ošetření událostí. Názvy
těchto metod začínají On (např. OnClick). Můžeme jim dát samozřejmě i jiné
názvy, ale pro snadné odlišení je vhodné ono On na začátku ponechat.
Nejdříve se vždy soustředíme na vlastnosti samotné komponenty a pak
postupujeme k vlastnostem jejich předků.
Nejčastější properties:
33
Owner
určuje komponentu, která je zodpovědná za zrušení naší
komponenty. Nejčastěji to je samotný formulář. Pak zde
uvádíme pointer this.
Parent
určuje komponentu, na které naše komponenta leží. Pokud se
nachází přímo v klientské oblasti formuláře, píšeme zde opět
this. Pokud naše komponenta lež např. na panelu Panel1, pak
zde píšeme Panel1.
Name
název instance komponenty, u formuláře název třídy. IDE vám
výchozí název navrhne samo. Pro snadnou čitelnost programu
je ale vhodné ho změnit na takový, který by hned vypovídal
komu patří. Názvy typu Button1, Label10 a podobně nejsou
vhodné. Daleko vhodnější jsou názvy jako BOk, BPotvrzení,
LHodnota, EPrumer, kde první jedno nebo dvě písmena
vystihují o jaký typ komponenty se jedná. Nař. B pro Buton, L
pro Label, E pro Edit a podobně.
Caption
nadpis naší komponenty (pokud ho má)
Left, Top
souřadnice levého horního rohu v pixelech. Měřeno vůči
komponentě, která je Parent.
Width, Height
vnější šířka a výška
ClientWidth, ClientHeight šířka a výška vnitřní, pracovní oblasti
Font
určuje vlastnosti v komponentě použitého písma (znaková
sada, font, barva písma, velikost, styl atd.).
Velikost písma je zde uváděna dvěma údaji:
Size – určuje velikost písma v pixelech (uvádí se kladné)
Height – velikost i s místem nad a pod písmenem a je
tedy větší než Size. Zadává se jako záporné.
Styl písma určuje vlastnost Style. Je to množina.
Pracujeme s ním hlavně pomocí operátorů << a >> a
funkce Contains. Viz kapitola o datovém typu Set.
Edit1->Font->Style<<fsBold<<fsItalic;
if(Edit1->Font->Style.Contains(fsBold))
Color
většinou nastavení barvy pozadí pomocí určení RGB (na
každou barvu jeden byte). Hodnota 0x00FF0000 je čistě modrá,
0x0000FF00 je čistě zelená, 0x000000FF je čistě červená,
0x00000000 je černá, 0x00FFFFFF je bílá. Základní barvy mají i
své názvy (clRed, clWhite, clYellow, clBlack atd.) nebo názvy
dle použití ve Windows (clWindowText, clWindow, clMenu,
clGrayText atd.)
Tag
číslo typu int, do kterého si můžeme něco poznačit. Je jen pro
nás. IDE ho nemění.
Align
jak se má naše komponenta umístit v okně Parent
Visible
true znamená, že je naše komponenta viditelná
Enabled
je-li true, můžeme komponentu ovládat myší a klávesnicí
34
Anchors
vůči čemu jsou zakotveny souřadnice komponenty. Nejčastěji
akLeft a akTop, což znamená, že je ukotvena vůči levému a
hornímu oblasti Parent
BorderStyle
určuje styl chování formuláře, zda se jedná o dialogové okno
nebo o formulář, kterému lze měnit rozměry a podobně.
Hint
určuje pomocný text bublinkové nápovědy, který se objeví
v okamžiku, kdy chvíli zůstaneme s kurzorem nad naší
komponentou
ShowHint
jestliže má hodnotu true, bublinková nápověda je zobrazována
TabStop
true má-li být naše komponenta vybrána pomocí klávesy Tab
TabOrder
určuje pořadové číslo naší komponenty při přeskakování mezi
komponentami pomocí Tab
Některé properties lze nejen zapisovat, ale i vyčítat, některé jen číst, některé jen
zapisovat. Záleží na tom, o co se jedná.
Nejčastější events (události):
OnClick
kliknuli jsme myší na komponentu
OnKeyDown, OnKeyUp, OnKeyPress zmáčkli, pustili nebo stisknuli jsme
klávesu v době, kdy naše komponenta má focus
OnMouseDown, OnMouseUp
stiskli nebo pustili jsme levé tlačítko
myši na naší komponentě
OnEnter
komponenta dostala focus
OnExit
komponenta ztratila focus
OnCreate
formulář byl zrovna založen konstruktorem
OnClose
formulář je destruktorem rušen
OnShow
formulář je zrovna zobrazen
OnHide
formulář je zrovna skryt
OnPaint
formulář je překreslen
OnResize
formuláři byly změněny rozměry
Každý typ metody pro ošetření události má různý počet parametrů. Záleží na tom,
co tato metoda zachycuje.
Metody komponenty:
Metody jsou specifické pro každou komponentu.
Každá komponenta však obsahuje konstruktor a destruktor. Tyto volá IDE při
zakládání komponenty při spouštění programu nebo je voláme my, pokud
komponentu vytváříme dynamicky sami. V obou případech jsou používány
operátory new a delete. Z toho také plyne, že na komponentu se vždy
odvoláváme pomocí pointeru. Např.:
Form->Width=150;
x=Form1->Height;
Fromt1->OnClick=MojeObsluha;
Form1->Repaint();
35
7
Naše první programy
Obsah hodiny
Projekty s prvním ošetřením událostí, s jedním a více formuláři
Klíčová slova
SDI, MDI, modální formulář
7.1
Aplikace obsahující jeden formulář
Vytvoříme program, který bude obsahovat jeden formulář. Na ploše formuláře
budou umístěny dvě komponenty typu TPanel. Tyto komponenty jsou používány
pro členění klientské plochy formulářů na více dílů. Slouží jako podklad pro další
komponenty, které tak uzavírají opticky do logických celků a stávají se jejich
Parent. Horní panel bude zarovnán k hornímu okraji klientské oblasti (property
Align nastaveno na alTop) formuláře a spodní panel bude vyplňovat zbytek
klientské oblasti (property Align nastaveno na alClient). Při takovém nastavení
bude horní panel vždy vyplňovat horní část klientské oblasti a bude mít i při
změnách rozměrů formuláře stále stejnou výšku. Spodní panel bude při změnách
rozměrů formuláře měnit své rozměry tak, aby vyplňoval zbytek klientské oblasti.
Po kliknutí na horní panel dojde k zobrazení zprávy o kliknutí. Po dvojím kliknutí na
spodní panel dojde k pípnutí přes zvukovou kartu a ke zvětšení šířky a výšky
formuláře o 10 pixelů. Rovněž bude o 10 pixelů zvýšena výška horního panelu.
Unit1.dmf:
- předpis pro vytvoření formuláře
object Form1: TForm1
- název naší třídy a od čeho je
odvozena
36
Left = 192
- x-ová souřadnice levého horního rohu
formuláře na obrazovce
Top = 114
- y-ová souřadnice levého horního rohu
formuláře na obrazovce
Width = 382
- vnější šířka formuláře
Height = 289
- vnější výška formuláře
Caption = 'Form1'
- nadpis formuláře
Color = clBtnFace
- barva pozadí klientské oblasti
Font.Charset = DEFAULT_CHARSET
- vlastnosti písma formuláře
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
- událost OnCreate vznikne až po
konstruktoru okna
Position = poScreenCenter – formulář bude zobrazeno ve středu
obrazovky
PixelsPerInch = 96
TextHeight = 13
- velikost písma v pixelech
object Panel1: TPanel
- předpis pro vytvoření panelu ve
formuláři
Left = 0
Top = 0
Width = 374
Height = 121
Align = alTop
- panel při změnách velikosti okna bude
vyplňovat horní okraj okna
BevelInner = bvLowered - vnitřní hrana okraje bude vtlačena
BevelWidth = 2
- šířka okraje
Caption = 'Panel1'
- nápis panelu
Color = clBlue
- barva pozadí panelu
Font.Charset = DEFAULT_CHARSET - vlastnosti písma v panelu
Font.Color = clWhite
Font.Height = -20
Font.Name = 'MS Sans Serif'
Font.Style = [fsBold]
ParentFont = False
- panel nepoužije nastavení z Parent
v tomto případě z formuláře
TabOrder = 0
- pořadové číslo panelu při procházení
pomocí klávesy Tab
OnClick = Panel1Click
- název metody pro ošetření události
kliknutí myší na panel
end
- konec předpisu pro horní panel
object Panel2: TPanel
Left = 0
Top = 121
Width = 374
Height = 134
Align = alClient
BevelInner = bvLowered
BevelWidth = 2
Caption = 'Panel2'
Color = clRed
Font.Charset = DEFAULT_CHARSET
Font.Color = clWhite
Font.Height = -20
Font.Name = 'MS Sans Serif'
37
Font.Style
ParentFont
TabOrder =
OnDblClick
end
end
= [fsBold, fsItalic]
= False
1
= Panel2DblClick - název metody pro ošetření
dvojího kliknutí na panel
- konec předpisu pro dolní panel
- konec předpisu pro celý formulář
Unit1.h:
#ifndef Unit1H
#define Unit1H
//----------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//----------------------------------------------------------------class TForm1 : public TForm
{
__published:
// IDE-managed Components
TPanel *Panel1;
TPanel *Panel2;
void __fastcall Panel1Click(TObject *Sender);
void __fastcall Panel2DblClick(TObject *Sender);
private:
// User declarations
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------------------------------extern PACKAGE TForm1 *Form1;
// v tomto pointeru bude adresa
//----------------------------------------------------------------#endif
Unit1.cpp:
#include "Unit1.h"
//----------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------// konstruktor okna, zatím prázdný
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) {
}
//----------------------------------------------------------------void __fastcall TForm1::Panel1Click(TObject *Sender)
{
ShowMessage("Bylo kliknuto na Panel1");// zde se to zastaví
// a čeká se na potvrzení
}
//----------------------------------------------------------------void __fastcall TForm1::Panel2DblClick(TObject *Sender)
{
Beep(1000,100);
// pískání tónem 1000 Hz s délkou 100 ms
38
Height+=10;
Width+=10;
Panel1->Height+=10;
// výšku okna zvětšíme o 10 pixelů
// šířku okna zvětšíme o 10 pixelů
// výšku Panel1 zvýšíme o 10 pixelů
}
//-----------------------------------------------------------------
Project1.cpp:
hlavní soubor aplikace
#include <vcl.h>
#pragma hdrstop
//----------------------------------------------------------------USEFORM("Unit1.cpp", Form1);
// makro pro přidání formuláře do
// projektu
//----------------------------------------------------------------// hlavní funkce programu
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
// dynamické založení formuláře
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
// spuštění aplikace
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
Aplikace obsahující více formulářů
7.2
•
Založte nový projekt typu VCL Forms Application.
•
Hlavnímu formuláři dejte jméno HlavniOkno, dejte mu nadpis „Hlavní okno
projektu“, nastavte mu vhodné rozměry, nastavte barvu pozadí, nastavte
polohu okna do středu obrazovky a vytvořte obsluhu události OnClick na
klientskou oblast formuláře. Do funkce pro obsluhu události vložte zatím
pouze nějakou poznámku, aby nám IDE prázdnou metodu při ukládání
zase nevymazalo.
•
Pomocí nabídky File/New/Form vytvořte druhé okno projektu, nazvěte ho
DruheOkno, dejte mu nadpis „Pomocné okno“, nastavte mu vhodné
39
rozměry, a připravte si obsluhu události OnShow. Do funkce pro obsluhu
této události dejte náhodné nastavení barvy pozadí:
Color=random(0x1000000).
•
Do hlavního okna vložte hlavičkový soubor druhého okna. To uděláme
pomocí nabídky „File/Include Unit Hdr“ nebo přímo na začátek souboru
unit1.cpp hned za řádek #include “Unit1.h” prostě napište #include
“Unit2.h”.
•
V obsluze události OnClick hlavního formuláře zrušte vaši poznámku a
napište:
if(Form2->Visible)
Form2->Close();
else
Form2->Show();
•
Uložte celý project do nějakého pracovního adresáře (“File/Save all” nebo
Shift+F2 nebo ikonou v horní liště)
•
Přeložte a spusťte pomocí F9.
Project1.cpp:
#include <vcl.h>
#pragma hdrstop
//----------------------------------------------------------------USEFORM("Unit1.cpp", HlavniOkno);
USEFORM("Unit2.cpp", DruheOkno);
//----------------------------------------------------------------WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
// HlavniOkno je viditelné hned po spuštění programu
Application->CreateForm(__classid(THlavniOkno), &HlavniOkno);
// DruheOkno je v paměti založeno, ale není vidět
Application->CreateForm(__classid(TDruheOkno), &DruheOkno);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
40
}
Unit1.dmf:
object HlavniOkno: THlavniOkno
Left = 232
Top = 348
Width = 237
Height = 195
Caption = 'Hlavní okno projektu'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnClick = FormClick
PixelsPerInch = 96
TextHeight = 13
end
Unit1.h:
#ifndef Unit1H
#define Unit1H
//----------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------class THlavniOkno : public TForm
{
__published:
// IDE-managed Components
void __fastcall FormClick(TObject *Sender);
private:
// User declarations
public:
// User declarations
__fastcall THlavniOkno(TComponent* Owner);
};
//----------------------------------------------------------------extern PACKAGE THlavniOkno *HlavniOkno;
//----------------------------------------------------------------#endif
Unit1.cpp:
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"
//----------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
THlavniOkno *HlavniOkno;
//----------------------------------------------------------------__fastcall THlavniOkno::THlavniOkno(TComponent* Owner):Form(Owner)
41
{
randomize();
// spuštění generátoru náhodných čísel
}
//----------------------------------------------------------------void __fastcall THlavniOkno::FormClick(TObject *Sender)
{
if(!DruheOkno->Visible)
// jestliže není DruheOkno viditelné
DruheOkno->Show();
// zobrazím DruheOkno
else
DruheOkno->Close();
// schovám DruheOkno
}
Unit2.dmf:
object DruheOkno: TDruheOkno
Left = 706
Top = 322
Width = 226
Height = 184
Caption = 'Pomocné okno'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
end
Unit2.h:
#ifndef Unit2H
#define Unit2H
//----------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//----------------------------------------------------------------class TDruheOkno : public TForm
{
__published:
// IDE-managed Components
void __fastcall FormShow(TObject *Sender);
private:
// User declarations
public:
// User declarations
__fastcall TDruheOkno(TComponent* Owner);
};
//----------------------------------------------------------------extern PACKAGE TDruheOkno *DruheOkno;
//----------------------------------------------------------------#endif
Unit2.cpp:
#include "Unit2.h"
42
//----------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TDruheOkno *DruheOkno;
//----------------------------------------------------------------__fastcall TDruheOkno::TDruheOkno(TComponent* Owner) : TForm(Owner)
{
}
//----------------------------------------------------------------void __fastcall TDruheOkno::FormShow(TObject *Sender)
{
Color=(TColor)random(0x1000000); // náhodné nastavení barvy
// klientské oblasti DruheOkno
}
Jestliže se podíváme do souboru Project1.cpp, vidíme, že jsou založeny dva
formuláře:
Application->CreateForm(__classid(THlavniOkno), &HlavniOkno);
Application->CreateForm(__classid(TDruheOkno), &DruheOkno);
Formulář, který je založen jako první, je hlavním formulářem aplikace a jeho
zavřením celá aplikace končí. Po spuštění programu je vidět.
Adresy instancí obou formulářů jsou umístěny do pointerů HlavniOkno a
DruheOkno, které deklaruje už IDE.
Druhý formulář tedy existuje v paměti i v tu dobu, kdy není vidět. Oba
formuláře jsou z paměti odstraněny až při ukončování aplikace.
Pokud druhý formulář otevíráme pomocí metody Show(), pak s oběma formuláři
můžeme pracovat současně.
To nemusí být vždy výhodné. Často může druhé okno sloužit pro získání nějakých
údajů s kterými, až budou získány, první okno bude pracovat. Je pak vhodnější
otevřít druhý formulář pomocí metody ShowModal() jako modální. Tím získáme
přístup k druhému formuláři, ale první bude zablokován tak dlouho, dokud druhý
nezavřeme.
7.3
Dynamická alokace formulářů
Někdy nám nemusí vyhovovat, že druhé okno v paměti neustále překáží i v době,
kdy není potřeba. Pak nenecháme založení druhého okna na funkci WinMain, ale
založíme (a také odalokujeme) jeho instanci v paměti dynamicky sami.
Postupujeme následovně:
V souboru Project1.cpp zrušíme řádek
Application->CreateForm(__classid(TDruheOkno), &DruheOkno);
a obsluhu OnClick v hlavním formuláři upravíme následovně:
TDruheOkno *druhe=new TDruheOkno(this);
druhe->ShowModal();
// zde se můžeme dotázat na nastavení "druhé"
43
delete druhe;
// odblokuji formulář z paměti
Parametr „Owner“ při zakládání instance druhého formuláře naplním hodnotou
this, tedy adresou prvního formuláře. Pokud by tedy program končil a druhý
formulář by ještě nebyl zrušen, postará se o to první ve svém destruktoru.
Nastavení polohy a rozměrů druhého formuláře musíme ovšem umístit do o
konstruktoru. Jinak by se na začátku formulář zakládal s implicitními hodnotami.
7.4
Odalokování formuláře sebou samým
Někdy bych potřeboval mít druhých formulářů otevřených více a rušit je hned
po zavření. Pak využiji toho, že okno se umí odalokovat i samo.
V druhém formuláři vytvoříme obsluhu události OnClose. Ta má parametr
„TCloseAction &Action“. Pokud tento parametr neměníme, má hodnotu caHide,
což znamená, že se okno pouze skryje, ale je v paměti zůstává nadále. Pokud
tento parametr nastavíme na hodnotu caFree, pak se formulář nejen schová, ale i
sám sebe odstraní z paměti.
To ovšem také znamená, že hodnoty všech datových proměnných tohoto
formuláře jsou zapomenuty.
Program pak bude vypadat takto:
void __fastcall THlavniOkno::FormClick(TObject *Sender)
{
TDruheOkno *okno=new TDruheOkno(this);
okno->Show();
}
void __fastcall TDruheOkno::FormClose(TObject *Sender,
TCloseAction &Action)
{
Action=caFree;
}
Program vyzkoušejte tak, že klikem na hlavni formulář založíte druhé okno, pak
hlavní formulář odsuňte a dalším klikem na něho založte další, druhý formulář.
Pak zavřete hlavní formulář. Všimněte si, že se rovněž zavřou i všechny otevřené
druhé formuláře. To provede první formulář díky tomu, že při dynamickém
vytváření druhých formulářů jsme první uváděli jako Owner.
7.5
SDI (Single Document Interface) a MDI (Multiple Document
Interface)
Zatím jsme v příkladu měli jeden hlavní formulář projektu a různá pomocná okna
se otevírala jako další samostatné formuláře. Takový model se nazývá SDI. Někdy
by se mohlo hodit, aby se další okna otevírala jako podřízená okna hlavního přímo
na jeho klientské ploše. Tomuto modelu se říká MDI.
Upravme dosavadní příklad takto:
44
V hlavním formuláři nastavte property FormStyle na hodnotu fsMDIForm.
V návrhu druhého formuláře nastavte FormStyle na hodnotu fsMDIChild. Nastavte
rozměry druhého formuláře podstatně menší než je klientská oblast prvního.
Jinak nechte program tak jak byl v posledním pokusu, tedy takto:
void __fastcall THlavniOkno::FormClick(TObject *Sender)
{
TDruheOkno *druhe=new TDruheOkno(this);
druhe->Show();
}
//----------------------------------------------------------------void __fastcall TDruheOkno::FormShow(TObject *Sender)
{
Color=(TColor)random(0x1000000);
}
//----------------------------------------------------------------void __fastcall TDruheOkno::FormClose(TObject *Sender,
TCloseAction &Action)
{
Action=caFree;
}
Nyní otevírejte další a další formuláře kliknutím na klientskou oblast hlavního.
Program nám sám nabídne vhodnou velikost druhých oken. Pokud nám to
nevyhovuje, můžeme v konstruktoru nastavit rozměry okna.
__fastcall TDruheOkno::TDruheOkno(TComponent* Owner) : TForm(Owner)
{
Width=100;
Height=100;
}
V dnešní době se MDI model prakticky nepoužívá a takřka všechny programy
používají model SDI.
45
8
První příklady
Obsah hodiny
Vytvoříme dva jednoduché programy, na kterých si vyzkoušíme tvorbu
jednoduchých aplikací.
8.1
Příklad 1
Vytvořte program obsahující dva formuláře. Hlavní Form1 bude obsahovat tři
panely. Kliknutím na tyto panely ovládáme druhý formulář Form2. Kliknutím na
spodní panel ve Form1druhý formulář zobrazujeme a zhasínáme. Kliknutím na
levý panel přesuneme druhý formulář tak, aby byl k hlavnímu přilepen vlevo a měl
jeho výšku. Kliknutím na pravý panel přilepíme Form2 vpravo od Form1. Kliknutím
na plochu Form2 měníme náhodně barvu jeho klientské oblasti.
Vytvořte společnou obsluhu OnClick pro všechny tři panely. Který panel
obsluhu vyvolal zjistíme podle jeho hodnoty Tag, která bude obsahovat
identifikační číslo panelu. Na Tag se dotazujte takto:
TPanel *p=(TPanel*)Sender;
switch(p->Tag) {
}
Pří rozmisťování panelů v klientské oblasti využijte jejich vlastnosti Align.
Nejdříve umístěte na formulář panel, který bude spodní a jeho Align změňte na
hodnotu alBottom, pak položte na formulář panel, který bude levý a jeho Align
změňte na alLeft. Poslednímu panelu nastavte Align na alClient. Tímto postupem
dosáhnete toho, že panely budou i při změně rozměrů formuláře neustále
vyplňovat celou jeho clientskou oblast.
Vzhled našich formulářů
46
8.2
Příklad 2
Vytvořte program s jedním formulářem. Nastavte jeho property BorderStyle na
bsSingle. Tím znemožníme měnění rozměrů formuláře tažením myší za okraje
okna.
Fomulář nechť reaguje na tyto události:
1.
OnMouseWheel - pootočení kolečkem myši. V obsluze změňte náhodně
barvu pozadí okna.
2.
OnMouseUp - dle stavu parametru Shift měňte výšku nebo šířku okna. Jeli stiknut Shift ( if(Shift.Contains(ssShift)) ) měňte výšku, jinak měňte
šířku okna o 10 pixelů. Pravým tlačítkem myši (parametr Button)
zmenšujte rozměr, levým zvětšujte. Minimální rozměr okna budiž 50 pixelů
a maximální nesmí přesáhnout polovinu rozměru obrazovky (Screen>Width a Screen->Height).
Řešení:
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
randomize();
// spuštění generátoru náhodných čísel
}
//----------------------------------------------------------------void __fastcall TForm1::FormMouseWheel(TObject *Sender,
TShiftState Shift,int WheelDelta,
TPoint &MousePos, bool &Handled)
{
Color=(TColor)random(0x1000000);
// generování náhodné barvy
}
//----------------------------------------------------------------void __fastcall TForm1::FormMouseUp(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
if(Shift.Contains(ssShift)) {
switch(Button) {
case mbLeft: if(Height<Screen->Height/2)
Height+=10;
break;
case mbRight: if(Height>=60)
Height-=10;
break;
}
}
else {
switch(Button) {
47
case mbLeft: if(Width<Screen->Width/2)
Width+=10;
break;
case mbRight: if(Width>=60)
Width-=10;
break;
}
}
}
Pro správné pochopení našeho příkladu si uveďme ještě význam některých
datových typů:
TPoint - souřadnice bodu na obrazovce. Zjednodušená deklarace tohoto typu je
následující:
class TPoint
{
private:
int x, y;
public:
TPoint() {x=0; y=0;}
TPoint(int X, int Y) {x=X; y=Y;}
};
TShiftState – stav řídících kláves a tlačítek myši. Jedná se o množinu - Set. Její
deklarace je následující:
enum ShiftStates { ssShift, ssAlt, ssCtrl,
ssLeft, ssRight, ssMiddle, ssDouble };
typedef Set<ShiftStates, ssShift, ssDouble> TShiftState;
Jak jsme si už vysvětlili v kapitole o datovém typu Set, nejčastěji s tímto typem
pracujeme pomocí funkce Contains a přetížených operátorů >> a <<.
TMouseButton – výčet tlačítek myši. Deklarace tohoto výčtového typu je tato:
enum TMouseButton { mbLeft, mbRight, mbMiddle };
Screen – Objekt třídy TScreen umožňující práci s vlastnostmi obrazovky. Jedná se
o globální objekt, který je pro nás vždy automaticky založen v době spouštění
našeho programu. Sami ho již zakládat nemusíme. Je nám automaticky
k dispozici pro práci s vlastnostmi obrazovky.
48
9
Základní komponenty I
Obsah hodiny
Naučíme se používat nejjednodušší a také nejčastěji používané komponenty.
Cíl hodiny
Po této hodině budete schopni:
Napsat program, který bude obsahovat nápisy, tlačítka a editovací okénka.
Klíčová slova
AnsiString, TLabel, TButton, TEdit.
V této kapitole si ukážeme základy práce s komponentami pro psaní nápisů
(TLabel), pro zadávání údajů (TEdit) a ovládací tlačítko (TButton).
U všech tří komponent se setkáváme s prácí s textem. Ať už s textem jako
takovým nebo s textem, který představuje číselné údaje. Základní funkce pro práci
se stringy (textem), jak je známe z jazyka C, jsou ale poněkud nepraktické. (To
však neznamená, že nejsou nepoužitelné.) Borland proto za pomocí prostředků
jazyka C++ navrhl novou třídu AnsiString, která nám podstatně usnadňuje práci s
textem.
9.1
Třída AnsiString
Protože se třída používá opravdu často, lze tuto třídu používat i pod jednodušším
názvem String.
Výběr nejdůležitějších metod třídy:
AnsiString
~AnsiString
konstruktor, existuje 16 typů konstruktorů, viz help
destruktor. Samostatně nepoužíváme. Používáme operátor
delete.
AnsiCompare
porovnává svůj text se zadaným a zohledňuje velikost písma
AnsiCompareIC
porovnává svůj text se zadaným a ignoruje velikost písmen
AnsiLastChar
vrací adresu posledního písmena
Insert
vloží do vlastního textu zadaný text na zadanou pozici
Length
vrací délku text uloženého v objektu
Pos
vrací index kde se ve vlastním textu nachází začátek zadaného
textu (pozice je indexována od 1!)
49
printf
SubString
UpperCase
LowerCase
naformátování textu do objektu pomocí pravidel platících pro
printf
vrací část (substring) vlastního textu
převede text na velká písmena
převede text na malá písmena
c_str
funkce vracející adresu začátku stringu s textem
ToDouble
ToInt
FloatToStrF
IntToHex
převede text na double (při chybě generuje výjimku)
převede text na int (při chybě generuje výjimku)
převede zadané desetinné číslo na text v zadaném formátu
ze zadaného celého čísla vytvoří text s hexa výpisem čísla
Trim
TrimLeft
TrimRight
odříznutí zbytečných mezer na začátku a konci
odříznutí zbytečných mezer z leva
odříznutí zbytečných mezer z prava
operator !=
operator [ ]
operator +
operator +=
operator <
operator <=
operator =
operator ==
operator >
operator >=
vrací se true jestliže vlastní text je různý od zadaného
vrací písmeno jehož index zadáme (indexuje se od 1!)
spojení vlastního textu s jiným textem
připojení jiného textu k sobě samotnému
operátory pro porovnání vlastního textu se zadaným
Prostudujte si deklaraci třídy AnsiiString. Někam ve zdrojovém textu napište text
„AnsiString”, postavte kurzor myši na tento text a klepněte na něj pravým
tlačítkem. V kontextovém menu, které se objeví, vyberte „FindDeclaration“. Otevře
se hlavičkový soubor „dstring.h“, ve kterém si můžeme prostudovat deklaraci třídy.
V souboru „dstring.h“ nic neměňte!!!! Vyvolali byste chyby, které znemožní
funkci kompilátoru. Pro jistotu klepněte pravým tlačítkem myši na plochu
zdrojového textu a v kontextovém menu vyberte „Read only“. Pak nemusíte mít
obavy, že soubor nějak porušíte.
Příklady:
String s1("Alena"), s2("Novakova"), s3, s4, s5;
s3=s1+" "+s2;
// v s3 je "Alena Novakova"
s4=s1.SubString(2,3);
// v s4 je "len"
s4+="ka";
// v s4 je "lenka"
int i=s3.Pos("ena");
// i=3
s5.printf("Poloha je: %d, Pi je: %.3f",i,M_PI);
// v s5 je tento text: "Poloha je: 3, Pi je: 3.142"
50
char text[100];
strcpy(text,s5.c_str());
// strcpy nezná datový typ String
char posledniPismeno=*s5.AnsiLastChar(); // posledni pismeno je 2
s3.Insert("xxx",3);
int x=s3.Pos("xxx");
String s6=s1.UpperCase();
char xx=s6[2];
s6[2]='x';
//
//
//
//
//
v
v
v
v
v
s3 je Alxxxena Novakova
x je 3
s6 je ALENA
xx je pismeno L
s6 je AxENA
if(s2=="Novakova")
s6="souhlasi";
else
s6="nesouhlasi";
s6="123456";
int y=s6.ToInt();
// je-li v s6 text, který nelze převést,
// vznikne výjimka
String s7=IntToHex(123456,8);
// v s7 je text "0001E240"
Užitečné konverzní funkce třídy AnsiString:
IntToStr
StrToInt
FloatToStr
FloatToStrF
FloatToText
StrToFloat
převodní funkce mezi číslem typu int a AnsiString
převodní funkce mezi číslem typu foat a AnsiString
Tyto funkce jsou ve třídě AnsiString deklarovány jako statické. Lze je tudíž
používat i mimo objekty třídy AnsiString
Jestliže při převodu textu na číslo dojde k chybě, je generována vyjímka.
Příklady:
String s1,s2,s3;
// s1 a s2 jsou nějak naplněny
try
{
s3 = IntToStr(StrToInt(s1) * StrToInt(s2));
}
catch(...)
{
ShowMessage("Chyba při prevodech");
}
String s4=FloatToStrF(M_PI*100,ffFixed,7,4);
// 7 - počet platných cifer
// 4 – počet desetinných míst
51
9.2
Komponenta TLabel
Tato komponenta je jednou z nejčastěji používaných. Slouží pro vkládání různých
textů do formuláře.
Významné properties (kromě těch, o kterých jsme se již zmiňovali):
Caption
text, který se má zobrazit
Aligment
určuje způsob zarovnání textu v ploše zabírané komponentou
WordWrap je-li true, pak delší texty mohou být zalamovány na více řádků
9.3
Komponenta TButton
Tlačítko - nejzákladnější ovládací komponenta.
Významné properties (kromě těch, o kterých jsme se již zmiňovali):
ModalResult
určuje jaká hodnota se má vrátit, pokud tlačítkem zavíráme
modální okno
Významné metody
SetFocus
Významné události:
OnClick
9.4
tlačítko získá fokus (bude vybráno)
na tlačítko bylo klepnuto myší nebo pokud bylo vybráno byla
stisknuta klávesa Enter
Komponenta TEdit
Základní komponenta pro zadávání údajů formou textu.
Významné properties (kromě těch, o kterých jsme se již zmiňovali):
Alignment
určuje způsob zarovnání textu v okénku
CharCase
typ velikosti písma
ReadOnly
je-li true, nelze do okénka psát
Enabled
je-li false, nelze komponentu vybrat a ovládat
Text
AnsiString obsahující text v okénku
Významné metody
Clear
Focused
SetFocus
zrušení textu v okénku. Totéž jako Text=””.
true značí, že TEdit je vybrán
TEdit získá fokus (bude vybrán)
Významné events:
OnChange
text byl změněn
OnReturnPressed – byl stisknut Return
52
OnEnter
OnExit
OnKeyUp
OnKeyDown
9.5
TEdit získal focus
TEdit ztratil focus. Vhodné místo pro kontroly.
byla povolena klávesa. Vhodné místo pro zásahy podle
stisknuté klávesy
byla stisknuta klávesa. Vhodné místo pro zásahy podle
stisknuté klávesy
Komponenta TLabeledEdit
Tato komponenta se nachází ve skupině Additional a představuje komponentu
TEdit rozšířenou o Label s doprovodným nápisem.
S komponentou pracujeme stejně jako s TEdit.
Dodatečné properties:
EditLabel
Pomocí této vlastnosti nastavíme nadpis
LabelPosition určuje polohu nadpisu
LabelSpacing určuje vzdálenost nadpisu od okénka
9.6
Společné ošetření stejné události u více komponent
Máme-li více komponent a chceme u nich ošetřit stejnou událost, nemusíme pro
každou událost vytvářet samostatnou funkci. Často je výhodnější vytvořit pro
několik komponent společnou obsluhu a teprve v události se můžeme rozhodovat
(pokud je to nutné), která komponenta událost vyvolala. Můžeme se rozhodovat
podle Name, Caption, Tag, souřadnic levého horního rohu, rozměrů atd. Musíme
si ovšem parametr Sender vhodně přetypovat.
Příklad:
void __fastcall THlOkno::BClick(TObject *Sender)
{
TButton *b=(TButton*)Sender;
switch(b->Tag) {
case 0: Pocitadlo=0;
break;
case -1: Pocitadlo--;
break;
case +1: Pocitadlo++;
}
Label1->Caption=IntToStr(Pocitadlo);
}
9.7
Kontrola číselných hodnot zadaných v textu
Často se do editačního okénka TEdit vkládají texty, které představují číselné
údaje. Pak je ovšem nutné se ujistit, že vložený údaj nemá nějakou chybu. Pro tuto
kontrolu je asi nejvhodnější použít událost OnExit, která vzniká tehdy, když se
53
TEdit opouští (ztrácí focus). Např. přecházíme na ovládací tlačítko nebo na jiný
TEdit. Uvnitř obsluhy se pokusíme text převést na požadovaný údaj. Ani
nemusíme výslednou hodnotu někam ukládat. Jestliže vznikne výjimka jako
hlášení chyby, pak na ni zareagujeme tak, že TEdit vrátíme focus a uživatel může
hodnotu opravit.
Příklad:
void __fastcall THlOkno::EPrirustekExit(TObject *Sender)
{
try {
// kontroluji na vznik výjimky
StrToInt(EPrirustek->Text);
}
catch (...) {
// odchycení libovolné výjimky
Beep(1000,100);
// písknutí
ShowMessage("Chyba !");
// modální okno s hlášením
EPrirustek->SetFocus();
// dám focus zpět na EPrirustek
}
}
9.8
Dynamická alokace komponent
Komponenty nemusím vždy vytvářet při návrhu formuláře, ale mohu je podle
potřeby vytvářet a rušit i za chodu programu. Všechny podstatné properties musím
ovšem pak sám zadat po alokování komponenty. Nesmím zapomenou na property
Parent, které obsahuje jméno komponenty na jejímž povrchu se naše komponenta
nachází. Může to být např. TPanel v němž je naše tlačítko umístěno. Musíme zde
samozřejmě zadat i názvy metod pro ošetření událostí. Tyto metody deklaruji
nejčastěji jako private. Hlavičku takové funkce nejlépe okopíruji z nějaké statické
komponenty, kterou si na chvíli položím do formuláře.
Příklad:
// komponenty vytvářím v konstruktoru formuláře
__fastcall TForm3::TForm3(TComponent* Owner) : TForm(Owner)
{
Pocitadlo=0;
Label1->Caption=IntToStr(Pocitadlo);
TButton *b;
for(int i=1;i<=10;i++) { // dynamicky generuji 10 tlačítek
b=new TButton(this);
// this – Form3 je vlastníkem a je
// zodpovědný za dealokaci komponenty
b->Parent=Panel1;
// b bude ležet na ploše Panel1
b->Left=BNulovani->Left;
b->Top=BNulovani->Top+(BNulovani->Height+5)*i;
b->Width=BNulovani->Width;
b->Height=BNulovani->Height;
b->Caption=IntToStr(i);
b->OnClick=BClick;
// název metody pro ošetření události
// OnClick všech tlačítek
b->Tag=i;
// do Tag uložím číslo tlačítka
}
}
// obsluha OnClick pro všechna dynamická alokovaná tlačítka
54
void __fastcall TForm3::BClick(TObject *Sender)
{
TButton *b=(TButton*)Sender;
Pocitadlo+=b->Tag;
Label1->Caption=IntToStr(Pocitadlo);
}
9.9
Nastavení desetinné tečky nebo čárky
V programech pod Windows je v desetinných číslech standardně desetinná tečka
nebo čárka podle národního nastavení. Pro české prostředí to je čárka.
Někdy však chceme raději použít desetinnou tečku nebo případně nějaký jiný
znak. Pro to je určena speciální globální proměnná DecimalSeparator, která platí
v celé naší aplikaci. Znak, který do ní vložíme, bude znakem, který se bude
používat jako oddělovač mezi celočíselnou částí a desetinnou částí ve všech
funkcích, které provádějí konverzi mezi čísly s plovoucí desetinnou čárkou nebo
tečkou a stringy.
Příklad:
DecimalSeparotor = ’.’;
55
Kontrolní otázky a úkoly
Zadání 1.
Vytvořte program obsahující pouze hlavní formulář. Pomocí tlačítek „+“ a „-“ přičítejte
a odečítejte od své proměnné hodnotu zadanou v políčku „Přírůstek“. Tlačítkem
„NUL“ proměnnou nulujete.
Hodnotu proměnné zobrazujte v horním TLabel.
Číslo zadávané jako přírůstek kontrolujte na správnost. Při chybném zadání vyžádejte
od uživatele opravu.
Potřebné obsluhy události:
OnClick společné pro všechny tři tlačítka. Které tlačítko událost vyvolalo můžeme
zjistit např. podle vlastnosti Tag odesilatele.
OnExit pro TEdit. Vhodný okamžik pro zjištění, zda je obsah v editovacím okénku
opravdu číslo.
56
Zadání 2.
Ve formuláři založte TLabel pro zobrazování stavu vnitřního počítadla a tlačítko
„Nulování“. Zbývající tlačítka jsou vygenerována dynamicky v konstruktoru
formuláře, mají společnou obsluhu a přičítají svou hodnotu, která je uložená ve
property „Tag“ k hodnotě počítadla.
Potřebné obsluhy události:
OnClick společné pro všechny tři tlačítka. Které tlačítko událost vyvolalo ,můžeme
zjistit např. podle vlastnosti Tag odesilatele.
57
10
Začlenění vlastní třídy do projektu
Obsah hodiny
Připojování vlastní nevizuální třídy k projektu
Cíl hodiny
Po této hodině budete schopni:
●
připojit k projektu, jehož kostru nám navrhne IDE, své třídy, které řeší
problematiku, která nás zajímá
Do projektu můžeme začlenit i vlastní třídu.
V menu File zadáme New / Unit. IDE nám vytvoří dva soubory. Hlavičkový
(include) soubor s příponou h a vlastní soubor s příponou cpp. Do hlavičkového
souboru pak vložíme deklaraci naší třídy a do souboru cpp pak vlastní definice této
třídy.
Do všech souborů typu cpp, kde budeme potřebovat naši třídu, pak pomocí menu
File / Use unit vložíme hlavičkový soubor naší třídy.
Poznámka: Dáváme přednost vkládání do souborů cpp. Přemíra vkládání
hlavičkových souborů do jiných hlavičkových souborů může vést až k takové
nesprávné provázanosti souborů našeho projektu, že tento pak nelze slinkovat.
Příklad
Navrhněme program, který bude umět počítat výsledné hodnoty impedancí
sériových a paralelních zapojení odporů. Pro výpočty s impedancemi navrhněme
vlastní třídu Odpor.
58
Výpis podstatných částí hlavních souborů programu:
HlOkno.h
class THlavniOkno : public TForm
{
__published: // IDE-managed Components
TPanel *Panel1;
TPanel *Panel2;
TEdit *EParalel1;
TEdit *EParalel2;
TEdit *ESerial1;
TEdit *ESerial2;
TEdit *EParalelne;
TEdit *ESeriove;
TButton *BParalelne;
TButton *BSeriove;
void __fastcall BParalelneClick(TObject *Sender);
void __fastcall BSerioveClick(TObject *Sender);
private:
// User declarations
public:
// User declarations
__fastcall THlavniOkno(TComponent* Owner);
};
//----------------------------------------------------------------extern PACKAGE THlavniOkno *HlavniOkno;
//-----------------------------------------------------------------
HlOkno.cpp
#include "HlOkno.h"
#include "TridaOdpor.h"
// zde je deklarace mé třídy Odpor
THlavniOkno *HlavniOkno;
//----------------------------------------------------------------__fastcall THlavniOkno::THlavniOkno(TComponent* Owner) : TForm(Owner)
{
DecimalSeparator='.';
// nastavení desetinné tečky
}
//----------------------------------------------------------------void __fastcall THlavniOkno::BParalelneClick(TObject *Sender)
{
Odpor ov;
float imp1,imp2;
try {
// hlídání výjimky od špatně zadaného čísla
imp1=StrToFloat(EParalel1->Text);
}
catch(...) {
ShowMessage("Chyba");
EParalel1->SetFocus();
return;
}
try {
imp2=StrToFloat(EParalel2->Text);
}
catch(...) {
ShowMessage("Chyba");
EParalel2->SetFocus();
return;
}
59
try {
// hlídání výjimky od nesprávné hodnoty odporu
Odpor o1(imp1);
}
catch (...) {
ShowMessage("Chyba");
EParalel1->SetFocus();
return;
}
try {
Odpor o2(imp2);
}
catch (...) {
ShowMessage("Chyba");
EParalel2->SetFocus();
return;
}
// - je přetížený operátor pro paralelní řazení impedancí
Odpor ov=Odpor(imp1)- Odpor(imp2);
EParalelne->Text=FloatToStrF(ov.Impedance(),ffGeneral,7,2);
}
//----------------------------------------------------------------void __fastcall THlavniOkno::BSerioveClick(TObject *Sender)
{
float imp1,imp2;
try {
imp1=StrToFloat(ESerial1->Text);
}
catch(...) {
ShowMessage("Chyba");
ESerial1->SetFocus();
return;
}
try {
imp2=StrToFloat(ESerial2->Text);
}
catch(...) {
ShowMessage("Chyba");
ESerial2->SetFocus();
return;
}
try {
Odpor o1(imp1);
}
catch (...) {
ShowMessage("Chyba");
ESerial1->SetFocus();
return;
}
try {
Odpor o2(imp2);
}
catch (...) {
ShowMessage("Chyba");
ESerial2->SetFocus();
return;
}
// - je přetížený operátor pro paralelní řazení impedancí
ov=Odpor(imp1)-Odpor(imp2);
ESeriove->Text=FloatToStrF(ov.Impedance(),ffGeneral,7,2);
60
}
//-----------------------------------------------------------------
TridaOdpor.h
class Vyjimka
{
public:
Vyjimka(char *text) {strcpy(Hlaska,text);}
char Hlaska[120];
};
class Odpor
{
private:
float impedance;
public:
// implicitní konstruktor
Odpor() {impedance=1;}
// kontruktor se zadáním výchozí hodnoty
Odpor(float imp);
// copykonstruktor
Odpor(Odpor &vzor) {impedance=vzor.impedance;}
// přistupové metody
void Impedance(float imp) {impedance=imp;}
float Impedance() {return impedance;}
// přetěžování operátorů
friend ostream& operator<< (ostream& ostr,Odpor &odp);
// + je přetížen pro sériové řazení odporů
Odpor operator+ (const Odpor& o) const;
// - je přetížen pro paralelní řazení odporů
Odpor operator- (const Odpor& o) const;
// -= přetížení pro paralelní připojení odporu k mému
Odpor& operator-= (const Odpor& o);
// += přetížení pro sériové připojení odporu k mému
Odpor& operator+= (const Odpor& o);
};
TridaOdpor.cpp
Odpor::Odpor(float imp)
{
if(imp<=0)
throw Vyjimka("Zadani zaporne hodnoty");
impedance=imp;
}
ostream& operator<< (ostream& ostr,Odpor &odp)
{
ostr << odp.impedance;
return ostr;
}
Odpor Odpor::operator+ (const Odpor& o) const
{
Odpor pom;
pom.impedance=impedance+o.impedance;
return pom;
}
// sériově
61
Odpor Odpor::operator- (const Odpor& o) const
{
Odpor pom;
pom.impedance=1/(1/impedance+1/o.impedance);
return pom;
}
Odpor& Odpor::operator+= (const Odpor& o)
{
impedance+=o.impedance;
return *this;
}
Odpor& Odpor::operator-= (const Odpor& o)
{
impedance=1/(1/impedance+1/o.impedance);
return *this;
}
// paralelně
// sériově
// paralelně
Kontrolní otázky a úkoly
1) Vytvořte projekt umožňující základní operace s vektory. Vektory budeme
zapisovat a operace s nimi provádět v hlavním formuláři. Pro vlastní výpočty
s vektory navrhněte samostatnou třídu TVektor a umístěte ji do
samostatného modulu.
62
11
Základní komponenty II
Obsah hodiny
Seznámíme se s dalšími základními komponentami a s modálními formuláři.
Cíl hodiny
Po této hodině budete schopni:
●
vytvořit program obsahující pomocné okno pro nastavení např. výchozích
hodnot nebo parametrů
Klíčová slova
TAnchors, TBevel, TCheckBox, TRadioButton, TGroupBox, TRadioGroup,
Modální formulář
11.1
Práce s obslužnými metodami navrhnutými IDE
Nikdy tyto funkce sami nemazejte nebo jim nějak neupravujte hlavičky!
Zmatete tím IDE a při překladu budete dostávat různé záhadné chyby.
Potřebujete-li nějakou obsluhu události zrušit, pouze vymažte řádky nacházející se
uvnitř těla funkce (tělo musí být úplně prázdné).
IDE samo při ukládání souboru ať už na naše přání nebo před překladem tyto
funkce zruší.
Pokud nechceme, aby nám IDE funkci vymazalo ale zatím ji chceme ponechat
prázdnou, napište do ní třeba pouze dvě lomítka jako začátek poznámky. IDE už
tuto funkci nebude při ukládání mazat, protože není prázdná
11.2
Datový typ TAnchors
TAnchors je potomek datového typu Set (množina) a určuje vůči čemu se má náš
ovládací prvek „zakotvit“. Zda vůči levé straně rodiče, na kterém náš prvek leží,
nebo vůči spodnímu okraji apod. Může mít nastaveny čtyři hodnoty: akLeft,
akRight, akTop, akBottom.
Např.:
63
Button1->Anchors=Button1->Anchors<<akRight<<ak Bottom>>akLeft;
V tomto příkladu do množiny kotev našeho buttonu přidáme zakotvení vůči
pravému a spodnímu okraji např. panelu na kterém náš button leží a odebereme
zakotvení vůči levému okraji např. panelu.
Toto ukotvení se pak projeví při změně rozměrů vlastníka, tedy např. panelu, na
kterém náš ovládací prvek leží. Náš prvek se bude přemísťovat tak, aby
vzdálenost vůči zakotveným okrajům zůstala stále stejná. Existuje možnost ukotvit
jak vůči např. levému tak i vůči pravému okraji rodiče. Pak se bude měnit rozměr
našeho prvku.
11.3
TBevel (skupina Additional)
Tento ovládací prvek slouží pouze pro zlepšení vzhledu našeho formuláře.
Nemůže být rodičem žádného ovládacího prvku. Používá se pouze pro tvorbu
rámečků a různých dělících čar ve formuláři. Kromě konstruktoru a destruktoru
neobsahuje žádné metody ani události.
Užitečné vlastnosti (kromě standardních):
Anchors
ukotvení
Shape
co má prvek vykresli (obdélník, čáru)
Style
zda mají být okraje zanořené nebo vystupující
11.4
TCheckBox
Ovládací prvek pro zadávání voleb typu ano/ne.
Při rozhodování, zda je prvek zaškrtnut nebo ne, se můžeme dotazovat na
vlastnost Checked, která určuje, zda je prvek zaškrtnut (obsahuje fajfku) nebo ne.
Důležité vlastnosti:
Aligment určuje, zda nápis připojený k zaškrtávacímu okénku je vlevo nebo
vpravo
Checked zda je okénko zaškrtnuto nebo ne. Hodnotu můžeme nejen číst,
ale i měnit. Tím měníme nebo rušíme zaškrtnutí.
Enabled – utčení, zda lze prvek momentálně měnit nebo ne
Události:
OnClick – Na prvek jsme klikli myší nebo byl vybrán a stiskli jsme na
klávesnici mezeru
11.5
TRadioButton
Představuje navzájem se vylučující volby.
64
Simuluje vzájemně provázaná tlačítka na radiopřijímači. Je-li těchto prvků
umístěno ve formuláři nebo panelu více, automaticky se spolu prováží.
Máme-li ve formuláři více panelů a každý má své TRadioButtons, pak se spolu
prováží jen ty, které jsou na stejném panelu. Mezi panely se neovlivňují.
Důležité vlastnosti:
Aligment určuje polohu nadpisu tlačítka. Zda je vlevo nebo vpravo.
Checked zda je tlačítko “zamáčknuto“ nebo ne. Můžeme se programově
dotazovat, ale i měnit. V rámci provázaných tlačítek lze dát do
stavu Checked pouze jedno tlačítko. Ostatní přejdou automaticky
do vymáčknutého stavu
Události:
OnClick
Na prvek jsme klikli myší nebo byl vybrán na klávesnici pomocí
šipek
11.6
TGroupBox
Komponenta umožňuje v rámci formuláře nebo panelu umístit více skupin tlačítek
TRadioButton tak, aby se skupiny vzájemně neovlivňovaly.
`
Jak si uvnitř jeho rámečku tlačítka rozmístíme je zcela na nás.
Chová se vlastně jako TPanel. Můžeme do něho umístit i jiné ovládací prvky a
stává se jejich Parent.
Komponenta je užitečná i při návrhu formuláře, protože jejím přetažením
přetahujeme i všechny komponenty, které se na její ploše nacházejí.
11.7
TRadioGroup
Komponenta umožňuje rychle do formuláře nebo panelu umístit skupinu
provázaných tlačítek typu RadioButton.
Na jednotlivá tlačítka se už pak tak snadno nedostaneme. Celý rámeček i s tlačítky
se chová jako jeden prvek. Nic jiného nemůže obsahovat.
Důležité vlastnosti:
Columns
počet sloupců, do kterých budou tlačítka rozmístěna.
Máme-li pět tlačítek a nastavíme Columns na hodnotu 5, pak
65
ItemIndex
Items
Události:
OnClick
11.8
budeme mít tlačítka rozmístěna vodorovně, dáme-li 1, budou
svisle.
určení, které tlačítko bude při zahájení programu vybráno. Číslují
se od 0 a hodnota -1 znamená, že není vybráno žádné tlačítko.
seznam názvů jednotlivých tlačítek. Klepneme-li v Object
Inspektorovi na tlačítko s třemi tečkami, otevře se nám dialogové
okno, kde můžeme zadat názvy tlačítek. Co řádek, to jedno
tlačítko.
Na některé tlačítko jsme klikli myší nebo bylo vybráno na
klávesnici pomocí šipek. Které tlačítko je vybráno zjistíme
z ItemIndex.
Modalni okna a ModalResult
Modální okna nebo formuláře jsou okna, která otevíráme v programu jako
pomocná a u kterých nechceme, aby po dobu jejich zobrazení uživatel mohl
zasahovat i do jiných oken programu. Tím donutíme uživatele, aby se plně
soustředil na obsluhu našeho formuláře a např. vyplnil všechny požadované
hodnoty.
Potřebujeme-li v rámci projektu takové okno, pak ho nezobrazujeme pomocí
funkce Show, ale funkcí ShowModal.
U modálních formulářů je vhodné použít ještě dvě nastavení:
1. FormStyle nastavíme na hodnotu fsStayOnTop. Toto důležité nastavení
zajistí, že se při nevhodných manipulacích nemůže stát, že se otevřený
modální formulář dostane pod formulář, z kterého jsme modální otevřeli.
Aplikace by se nám tím zasekla, protože na modální formulář bychom se
nedostali z důvodu, že je překryt formulářem volajícím a na volající
formulář se nedostaneme proto, protože je zablokován voláním modálního
formuláře.
2. BorderStyle nastavíme na hodnoty bsToolWindow nebo bsSizeToolWin.
Získáme tak formulář, které neobsahuje ikonky minimalizace a
maximalizace. Tyto jsou u modálních oken z logiky věci spíše zbytečné.
Každý formulář má členskou hodnotu ModalResult, která může nabývat těchto
stavů:
mrNone, mrOk, mrCancel, mrAbort, mrRetry, mrIgnore, mrYes, mrNo
Nastavíme-li hodnotu ModalResult do jakéhokoliv jiného stavu, kromě mrNone,
dojde k uzavření okna. To se může stát na základě obsluhy libovolné události
vztahující se k formuláři. Můžeme tedy okno zavřít např. kliknutím na jeho plochu,
nebo na základě času otevření formuláře a podobně.
66
Nejčastěji okno zavíráme pomocí tlačítek. Každý button má vlastnost nazvanou
také ModalResult. Tu můžeme přednastavit již při návrhu do některé nahoře
uvedené hodnoty. Default hodnota je mrNone.
Po stisknutí tlačítka dojde před spuštěním obslužné metody OnClick k přepsání
jeho hodnoty ModalResult do stejnojmenné hodnoty formuláře. Pak je spuštěna
obslužná metoda, a pokud ta nezmění hodnotu ModalResult zase zpět na hodnotu
mrNone, dojde po skončení obsluhy k zavření okna. Hodnota ModalResult je pak
vrácena jako návratová hodnota metody ShowModal a my se pak podle ní
můžeme rozhodovat, zda uživatel stiskl v modálním formuláři např. tlačítko OK
nebo Cancel.
Pokud hodnotu ModalResult modálního formuláře v obsluze tlačítka změníme zpět
na mrNone, formulář se nezavře a my můžeme např. opravit některý špatně
zadaný údaj a podobně.
Příklad
// button BOK má ModalResult nastaven na mrOK
void __fastcall TZadani::BOKClick(TObject *Sender)
{
try {
A=StrToFloat(EKoefA->Text);
}
catch (...) {
Beep(1000,100);
// písknutí
ShowMessage("Chyba");
// zobrazení varování
EKoefA->SetFocus();
// dáme focus zpět na EKoefA
ModalResult=mrNone;
// okno se nezavře
return;
// musíme zadat, jinak by i při
// chybě došlo k přiřazení za závorkou
}
hodnota=StrToFloat(EKoefA->Text);
}
Použití:
// počítám jen když z modálního okna dostanu mrOK
if(Zadani->ShowModal()==mrOk)
{
float a=Zadani->A, b=Zadani->B,c=Zadani->C;
}
67
Kontrolní otázky a úkoly
Zadání 1.
Na plochu formuláře umístěte TCheckBox „Změna povolena“, kterým budete povolovat a
zakazovat změny svého počítadla. Stav počítadla bude zobrazován na TLabel umístěným
pod TCheckBoxem.
Přičítat a odečítat hodnotu 1 od počitadla budete pomocí tlačítek „+“ a „-“.
Velikost písma TLabel měňte pomocí TRadioGroup „Velikost písma“ a jeho barvu pomocí
tří TRadioButton umístěných v TGroupBox „Barva písma“.
Barvu přiřazenou jednotlivým TRadioButtons si pamatujte v jejich hodnotě „Tag“.
Potřebné obsluhy události:
OnClick společné pro všechny tři TRadioButton pro určení barvy. Které tlačítko funkci
vyvolalo můžeme zjistit např. podle vlastnosti Tag.
OnClick pro TRadioGroup.
OnClick společné pro obě tlačítka.
68
Zadání 2.
Vytvořte program pro výpočty obsahů základních obrazců. Druh obrazce volíme pomocí
TRadioGroup „Druh objektu“. Při přepínání vhodně měníme nápisy u jednotlivých TEdits a
druhý TEdit v případě kruhu zhasneme.
Zadání 3.
Vytvořte aplikaci pro výpočet kořenů kvadratické rovnice.
Koeficienty budeme zadávat pomocí speciálního modálního formuláře „Zadání koeficientů“.
Všechny informace budeme v hlavním formuláři zobrazovat řadou TLabels rozmístěných
kolem tlačítka „Zadáni“ .
Neopomeneme v modálním formuláři kontrolovat správnost zadání jednotlivých údajů a
v případě chyby hned vyžádáme opravu.
69
12
Komponenty se seznamy I
Obsah hodiny
Budeme se zabývat komponentami, které pro svou práci potřebují seznamy.
Cíl hodiny
Po této hodině budete schopni:
●
vytvořit programy se základními komponentami používajícími seznamy
Klíčová slova
TList, TObjectList, TComponentList, TStrings, TStringList, TListBox,
TComboBox, TCheckListBox
12.1
TList
Nevizuální třída obsahující seznam pointerů typu void*. Můžeme si v ní uchovávat
a spravovat pointery na cokoliv. Při jejich použití se ale samozřejmě musíme
postarat o jejich přetypování na požadovaný datový typ.
Užitečné vlastnosti:
int Count
počet položek v seznamu
void* Items[int Index] pole jednotlivých položek seznamu
Užitečné metody (návratová hodnota je uvedena pouze pokud existuje a má
nějaký význam):
TList()
~TList()
konstruktor. Nepoužívat! Při dyn. alokaci používat operátor new
destruktor. Nepoužívat! Při dynamickém alokování použít
operátor delete.
Pozor!! Před zrušením seznamu nejdříve, pokud je to
potřeba, odalokovat všechny objekty, na které jednotlivé
položky seznamu ukazují. Pak teprve zrušit vlastní seznam.
Add(void* Item) přidá položku na konec seznamu
Clear()
vymaže celý seznam (ale neodalokuje objekty na které pointery
ukazovaly!)
Delete(int Index) vymaže položku na pozici Index
70
Exchange(int Index1, int Index2) zamění položky na pozicích Index1 a
Index2
int IndexOf(void * Item) vrátí index položky Item
Insert(int Index, void * Item) vloží novou položku Item na pozici Index
void * Last() vrátí poslední položku seznamu. Dělá totéž jako
Items[Count-1]
Move(int CurIndex, int NewIndex) přesune položku z pozice CurIndex na
NewIndex
Remove(void * Item) vymaže položku Item (sama si najde její Index)
Sort(TListSortCompare Compare) setřídí položky seznamu pomocí
QuickSort za pomoci porovnávací funkce Compare. Hlavičku
porovnávací funkce navrhněte takto:
int __fastcall Nazev(void * Item1, void * Item2);
Funkci nedeklarujte jako metodu třídy vašeho formuláře, ale jako
lokální funkci, jejíž definice bude umístěna někde nad metodou,
která ji bude používat. Nesmí být tedy deklarovaná jako metoda
nějaké třídy!
Funkce vrací +1 jestliže jsou *Item1 a *Item2 ve špatném pořadí,
0 jestliže jsou hodnoty stejné a -1 jestliže jsou ve správném
pořadí.
Příklad
//-------------------------------------------------// porovnávací funkce
int __fastcall vzestupne(void * Item1, void * Item2)
{
int i1=*(int*)Item1, i2=*(int*)Item2;
if(i1>i2)
// pří vzestupně je nutno prohodit
return 1;
if(i1==i2)
// hodnoty jsou stejné
return 0;
return -1;
// hodnoty jsou ve správném pořadí
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
list=new TList;
// naplnění seznamu adresami náhodných čísel
for(int i=0;i<10;i++) {
int *p=new int;
*p=random(100);
list->Add(p);
}
// výpis hodnot z adres uložených v seznamu
for(int i=0;i<list->Count;i++)
Memo1->Lines->Add(IntToStr(*(int*)list->Items[i]));
// setřídění vzestupně
list->Sort(vzestupne);
// výpis hodnot z adres uložených v seznamu
Memo1->Lines->Add("Setříděné");
for(int i=0;i<list->Count;i++)
Memo1->Lines->Add(IntToStr(*(int*)list->Items[i]));
// zrušení seznamu
71
for(int i=0;i<list->Count;i++)
delete list->Items[i];
delete list;
}
//---------------------------------------------------
12.2
TObjectList
Potomek třídy TList. Slouží pro uchovávání seznamu adres objektů tříd TObject a
jejich potomků, tedy vlastně takřka všech tříd knihovny VCL. Na rozdíl od třídy
TList tedy neobsahuje seznam pointerů typu void*, ale pointerů TObject*. Jinak má
prakticky stejné vlastnosti jako TList.
12.3
TComponentList
Potomek třídy TObjectList. Slouží pro uchovávání seznamu adres objektů tříd
TComponent a jejich potomků, tedy vlastně všech vizuálních tříd knihovny VCL.
Na rozdíl od třídy TObjectList tedy neobsahuje seznam pointerů typu TObject*, ale
pointerů TComponent*. Jinak má prakticky stejné vlastnosti jako TObjectList.
12.4
TStrings
Předek všech tříd obsahujících seznamy stringů. Jedná se o abstraktní třídu.
Nemůžeme z ní tedy vytvořit instanci v paměti, ale můžeme si odvodit své
potomky této třídy a redefinovat všechny čistě virtuální metody, které jsou uvedeny
jako virtual.
Užitečné vlastnosti:
int Count
počet položek v seznamu
AnsiString Strings[int Index] pole jednotlivých položek seznamu
TObject* Objects[int Index]
pole ukazatelů na jednotlivé instance
AnsiString Text celý text uložený v seznamu (celý text najednou).
Jednotlivé řetězce jsou odděleny znaky \r\n.
12.5
TStringList
Potomek třídy TStrings. Má tedy všechny properties třídy TString a něco navíc.
Další užitečné vlastnosti:
bool Sorted true znamená, že řetězce budou setříděny podle abecedy
vzestupně, false znamená, že řetězce budou v tom pořadí, jak
byly zadávány pomocí Add a Insert.
Užitečné metody:
72
int Add(AnsiString S) přidá do seznamu další string a vrátí jeho pořadové
číslo v seznamu
AddStrings(TStrings* Strings) přidá do seznamu obsah jiného seznamu
Assign(TPersistent* Source) přiřadí obsah seznamu Source našemu
seznamu
Clear()
vyprázdní seznam
Delete(int Index) vymaže řetězec na pozici Index
Exchange(int Index1, int Index2) zamění položky na pozicích Index1 a
Index2
Insert(int Index, AnsiString S) vloží novou položku S na pozici Index
Move(int CurIndex, int NewIndex) přesune řetězec z pozice CurIndex na
NewIndex
int IndexOf(AnsiString S) vrátí pořadové číslo řetězce S v seznamu. Pokud
tam není, vrátí -1
LoadFromFile(AnsiString FileName) nahraje seznam ze souboru FileName.
Nelze-li, vyvolá výjimku.
SaveToFile(const AnsiString FileName) uloží seznam do souboru
FileName. Nelze-li, vyvolá výjimku.
12.6
TListBox
Komponenta obsahující seznam položek.
Seznam lze skrolovat pomocí scroll baru.
Užitečné vlastnosti:
int Columns Počet sloupců v každé položce. Výchozí hodnota je 0. Pokud
zadáme více jak 1, dostaneme více sloupců. Lze tak vytvořit
tabulku. Musí ale platit TabWidth>0.
int ItemIndex Pořadové číslo vybrané položky. Pokud není vybraná žádna,
je hodnota -1.
TStrings* Items Pointer na intanci třídy TStrings obsahující jednotlivé
řetězce, které se zobrazí.
bool MultiSelect Je-li true, pak lze vybrat více položek najednou.
int SelCount Počet vybraných položek při MultiSelect=true, jinak 0.
bool Selected[] Jedná se o pole. Má-li daná položka hodnotu true, je
odpovídající položka komponenty vybrána.
bool Sorted
Je-li true, položky setříděny podle abecedy.
int TabWidth Šířka sloupců, pokud je nastaveno Columns>1.
int TopIndex Pořadové číslo položky, která je zobrazena na vrcholu.
73
Další užitečné vlastnosti dědí od svých předků! Podívejte se o pár řádků
nahoru.
Užitečné metody:
Clear() vymaže všechny položky ListBoxu
12.7
TCheckListBox
Klasický ListBox doplněný o možnost označování (zaškrtávání) jednotlivých svých
položek.
Užitečné datové položky:
bool Checked[int Index] pole určující zaškrtnutí jednotlivých položek
Užitečné události:
OnClickCheck nastane po změně stavu zaškrtávacího políčka. Na rozdíl
od OnClick nastane pouze při kliknutí na zaškrtávací
políčko!
12.8
TComboBox
Spojení komponent TEdit a TListBox.
Nerozvinutá podoba
Rozvinutá podoba
Od TListBox a TEdit přejímá většinu vlastností, metod a událostí.
Další užitečné vlastnosti:
int DropDownCout Určuje, kolik položek bude zobrazovat list box.
bool DroppedDown True znamená, že list box bude zobrazován.
74
TComboBoxStyle Style Definuje 5 stylů combo boxu.
Metody a události jsou prakticky stejné jako u TListBox
Kontrolní otázky a úkoly
Zadání 1.
V konzolové aplikaci deklarujte strukturu údajů o žákovi. Vytvořte seznam TList
dynamicky alokovaných struktur o několika žácích.
Pokuste se žáky setřídit podle abecedy pomocí metody Sort třídy TList.
Zadání 2.
•
•
•
•
•
•
•
•
•
•
•
•
V konzolové aplikaci alokujte dynamicky objekt třídy TStringList
Naplňte seznam AnsiStringů zadanými z klávesnice
Vypište stringy zadané do seznamu pozpátku
Vložte do seznamu na pozici 2 nový AnsiString
Zrušte poslední položku v seznamu
Pro kontrolu vypište nesetříděný seznam
Setřiďte seznam vzestupně a znovu vypište
Uložte seznam stringů do souboru c:\vypis.txt
Zrušte všechny položky seznamu
Seznam opět naplňte ze souboru c:\vypis.txt
Pro kontrolu vypište seznam
Odalokujte seznam
75
Zadání 3.
Vytvořte program pro vyzkoušení vlastností ComboBoxu a ListBoxu.
Pomocí ComboBoxu volte, zda chcete počítat obsah trojúhelníku, obdélníku nebo kruhu.
Při změně upravte patřičně nápisy u TEdits na levé straně, případně prostřední zhasněte.
Kontrolujte správnost zadání!
Vyzkoušejte vlastnosti TListBox. Už při návrhu ho naplněte několika položkami.
Do TEdit „Popis vybrané položky“ vypisujte na základě ItemIndex komponenty TListBox.
Nový řádek do seznamu přidávejte pomocí ListBox1->Items->Add(text);
Vybraný řádek rušte takto:
ListBox1->Items->Delete(ListBox1->ItemIndex);
Vybraný řádek posouvejte nahoru a dolů pomocí metody Exchange, kterou TListBox dědí
z třídy TStringList. Pozor na meze!
Potřebné obsluhy události:
Levý panel:
OnClick pro TButton.
OnChange pro TComboBox.
OnExit společné pro horní dva TEdit. Využijeme pro kontrolu správnosti zadaných čísel.
Pravý panel:
OnClick pro TListBox. Použijeme pro výpis vybrané položky.
OnClick pro jednotlivá tlačítka. Pro každé můžeme mít samostatnou funkci obsluhy.
76
13
Komponenty se seznamy II
Obsah hodiny
Budeme se dále věnovat komponentám, které pro svoji práci potřebují seznamy.
Cíl hodiny
Po této hodině budete schopni:
●
●
●
vytvořit programy obsahující okno s textem
navrhnout nejjednodušší editor souborů typu txt
vytvořit program obsahující dvourozměrné tabulky s texty
Klíčová slova
TMemo, TRichEdit, TUpDown, TStringGrid.
13.1
TMemo
Komponenta sloužící pro zobrazení a editaci víceřádkového textu.
Užitečné vlastnosti:
TScrollStyle ScrollBars určuje kolik scroll barů se bude zobrazovat
bool WantReturns false znamená zákaz vkládání znaku ENTER do textu
bool Tabs false zakazuje vkládání TAB do textu
TCaretPos CaretPos určení polohy caretu (kurzoru v textu)
TStrings* Lines pointer na seznam řádků
bool Modified true znamená, že text v okně byl nějak změněn
bool ReadOnly true znamená, že do okna nelze psát
TMemoSelection Selection vrací strukturu údajů o rozsahu selektovaného
textu. Jen pro čtení.
int SelLength určení délky selektovaného textu
int SelStart pozice prvního selektovaného znaku v textu
AnsiString SelText vrací nebo mění text v selektované oblasti
TCaption Text celý text uložený v TMemo. Řádky jsou odděleny znaky \r a \n
77
bool WordWrap true znamená, že řádky budou zalamovány tak, aby se vešly
do okna. Nesmí být ovšem nastaveno ScrollBars na
ssHorizontal nebo ssBoth.
Užitečné metody:
Append(AnsiString Text) připojení textu Text na konec
Clear() vymazání celého textu
ClearSelection() vymazání vybraného textu
CopyToclipboard() zkopírování vybraného textu do schránky
CutToClipboard() vyříznutí vybraného textu do schránky
PasteFromClipboard () vložení textu ze schránky
Insert(const AnsiString Text, Boolean Mark = false)
Insert(const AnsiString Text, int Column, int Row, Boolean Mark = false)
dvě metody pro vložení textu
SelectAll () vyselektování celého textu
UnSelect() odselektování vybraného textu
Užitečné události:
OnChange – událost která vznikne, když je v textu něco změněno
13.2
TRichEdit
Obdoba TMemo, ale s většími možnostmi blížícími se vlastnostem Notepad.exe.
Texty jsou ukládány ve formátu rtf. Pokud komponentu budete potřebovat,
nastudujte k ní help.
13.3
TStringGrid -
komponenta umožňující pracovat s celou řadou AnsiStringů uspořádaných do
formy dvourozměrné tabulky.
Tabulka může mít několik pevných prvních sloupců a řádků pro např. nadpisy.
Tyto zůstávají pevné, při scrollování se nepřesouvají a umožňují uživateli mít stále
přehled o tom, na jaká data zrovna kouká. Buňky, které nejsou obsaženy
78
v pevných řádcích a sloupcích, mohou být po správném nastavení editovatelné
nebo ne.
Užitečné vlastnosti:
TScrollStyle ScrollBars určuje kolik scroll barů bude v komponentě
TGridSelection určuje obdélník buněk vybrané oblasti.
Používá tuto strukturu:
struct TGridCoord {
int X;
int Y;
};
struct TGridRect {
union {
struct {
TGridCoord TopLeft;
TGridCoord BottomRight;
};
struct {
int Left;
int Top;
int Right;
int Bottom;
};
};
};
Povšimněte si použití unionu!
Nejčastěji pomocí TGridSelection vybíráme jednu konkrétní buňku. Pak bude
platit, že TopLeft a BottomRight musí být stejné nebo musí být stejné Left a
Right a současně Top a Bottom.
Vybraná oblast je ohraničena buď čárkovaně v době, kdy má StringGrid focus
nebo modrou barvou v době, kdy focus nemá. Pokud toto modré zdůraznění
nechceme, musíme focus přestavit někam mimo rozsah mřížky:
void __fastcall TForm1::ZrusZvyrazneni()
{
TGridRect r;
r.Left=StringGrid1->ColCount+1;
// zruším modré pozadí
r.Top=StringGrid1->RowCount+1;
r.Bottom=r.Top;
r.Right=r.Left;
StringGrid1->Selection=r;
}
long ColCount počet sloupců mřížky
long RowCount počet řádků mřížky
int FixedCol počet pevných sloupců. Celkový počet sloupců musí být větší
než toto číslo.
int FixedRow počet pevných řádků. Celkový počet řádků musí být větší než
toto číslo.
int DefaultColWidth standardní šířka sloupců
int DefaultRowHeight standardní výška řádků
79
int ColWidths[ ] pole se šířkami jednotlivých sloupců
int RowHeights[ ] pole s výškami jednotlivých řádků
long Col číslo sloupce vybrané buňky (čísluje se od 0)
long Row číslo řádku vybrané buňky (čísluje se od 0)
long LeftCol první momentálně viditelný sloupec
long TopRow první momentálně viditelný řádek
int VisibleColCount počet momentálně viditelných sloupců
int VisibleRowCount počet momentálně viditelných řádků
int GridLineWidth šířka dělicích čar mezi buňkami v pixelech
AnsiString Cells[col][row] pole řetězců mřížky.
TObject* Objects[col][row] pole pointerů na nějaké naše objekty, které
spojíme s textem v buňce.
TStrings* Cols[col] seznam obsahující text.řetězce obsažené v daném
sloupci.
TStrings* Rows[col] seznam obsahující text.řetězce obsažené v daném
řádku.
bool TabStops[ ] pole o velikosti dané počtem sloupců. Určuje, zda na daný
sloupec můžeme přejít pomocí TAB nebo ne. Funguje to ovšem
pouze tehdy jestliže Options budou obsahovat prvek goTabs.
Jinak stisk TAB značí standardní přechod na další ovládací prvek
ve formuláři.
bool DefaultDrawing true znamená, že necháme vykreslování obsahů buněk
na samostatné komponentě. false znamená, že obsahy buněk
budeme kresli sami. Používá se např. na to, abychom text
v buňkách zarovnali podle svého (třeba na střed).
TGridOptions Množina hodnot (Set) určujících chování mřížky.
Důležitá je např. hodnota goEditing, která je-li v množině
obsažena, znamená, že mřížku lze editovat. Další volby viz. help.
Užitečné metody:
TRect CellRect(int ACol, int ARow) – vrátí obdélník buňky o souřadnicích
ACol a ARow v pixelech celé obrazovky. Dáme-li parametry nulové,
dostaneme obdélník levého horního rohu viditelné oblasti mřížky.
Jestliže adresovaná buňka není momentálně viditelná, dostaneme
prázdný obdélník. Tato funkce se může použít tehdy, když se
rozhodneme překreslovat obsahy buněk sami (DefaultDrawing=false).
void MouseToCell(int X, int Y, int &ACol, int &ARow) – Přepočítá
souřadnice v rámci celé obrazovky na číslo sloupce a řádku. Používá
se v událostech od myši.
TGridCoord MouseCoord(int X, int Y) – Přepočítá souřadnice v pixelech
celé obrazovky na souřadnice buňky v mřížce v číslech řádku a
sloupce. Používáme v obsluze události od myši abychom si určili buňku
nad kterou se zrovna nachází kurzor.
80
13.4
TUpDown
Komponenta, která zde logicky nepatří. Budeme ji ale potřebovat v následujícím
příkladu.
Umožňuje připojit k jiné komponentě šipky nahoru a dolů a pomocí nich zvětšovat
nebo zmenšovat číslo nacházející se např. v TEdit.
Užitečné vlastnosti:
Associate zde vybereme komponentu s kterou má být TUpDown svázán.
Increment přírůstek
Max
maximální hodnota
Min
minimální hodnota
81
Kontrolní otázky a úkoly
Zadání 1.
Na plochu formuláře umístěte komponentu TPanel a nastavte mu zarovnání alRight.
Vedle panelu umístěte komponentu TMemo a u ní nastavte Align na alClient.
Na panel umístěte TCheckListBox pro nastavování vlastností písma v komponentě
TMemo.
Dále na panel umístěte tlačítka „Uložení do souboru“, „Výmaz“ a „Načtení ze souboru“
pro práci s textem v TMemo. Umístění souboru s obsahem je zadáváno v TEdit „Cesta
na soubor“.
S jednotlivými řádky můžeme manipulovat pomocí TEdit „Číslo řádku“ a „Text vybraného
řádku“. Pokud text v tomto TEdit upravíme, můžeme ho zpět na stejné místo zapsat
tlačítkem „Zápis“.
Nakonec na plochu ještě umístěte dva TCheckBox pro rozsvěcení a zhasínání track barů
a pro určení způsobu zalamování slov.
82
Zadání 2.
Na plochu formuláře umístěte TPanel a nastavte mu zarovnání na alRight. Vlevo od něho
umístěte TMemo a nastavte mu zarovnání na alClient. Na panel umístěte tlačítko
„Generuj“.
V obsluze OnClick tlačítka proveďte následující činnosti:
•
•
•
•
•
•
Vymažte obsah TMemo
Dynamicky alokujte dvourozměrné pole čísel typu int o rozměrech 10x10.
Naplňte políčka pole náhodnými čísly z rozsahu 0 až 99.
Vypište obsah pole do TMemo po jednotlivých řádcích.
Vypište součet všech čísel v poli
Zrušte alokované pole.
Pro výpis si vytvořte pomocnou proměnnou typu AnsiString, do které vždy poskládejte
výpis jednoho řádku. Přitom s výhodou použijte metodu cat_printf třídy AnsiString, která
vám umožňuje pohodlně připojovat k danému objektu AnsiString text naformátovaný
stejně, jak jsme zvyklí z jazyka C ve funkci printf. Připojení jednoho údaje z pole
k proměnné str provedeme tedy takto:
str.cat_printf(“%4d”,pole[r][s]);
Abychom obdrželi správně seřazenou tabulku čísel, musíme ale v TMemo nastavit jiný
font písma! Musíme vybrat takový font, který je neproporcionální, u kterého tedy zabírají
stejnou šířku znak mezera nebo číslice 1 jako číslice 8. Takovým fontem je např. Courier
New!
83
Zadání 3.
Na plochu formuláře umístěte TPanel a nastavte mu zarovnání alRight. Vedle panelu
umístěte komponentu TStringGrid a u ní nastavte Align na alClient.
Na panel umístěte TCheckListBox pro výběr počítaných goniometrických funkcí. Dále tři
TEdit pro určování rozsahu tabulky goniometrických funkcí a kroku v tabulce. Tlačítko pro
spuštění výpočtu a TComboBox pro zadávání počtu desetinných míst (uvedené počty
budou odpovídat indexu jednotlivých položek).
Celá aplikace by mohla obsahovat obsluhu dvou událostí:
• OnExit pro kontrolu obsahu TEditů, zda se jedná o čísla
• OnClick pro spuštění výpočtu
Uvnitř obsluhy OnClick nejdříve spočítejte počet zaškrtnutých položek v TChecListBox.
Podle tohoto údaje nastavte počet sloupců ve TStringGrid. Nezapomeňte přičíst jeden
sloupec pro pevný sloupec s nadpisy. Pak spočítejte počet hodnot v zadaném intervalu.
Následně spočítejte počet položek v zadaném intervalu při daném kroku. Opět přičtěte
jedničku pro horní nadpis.
Naplňte jednotlivé pevné řádky a sloupce nadpisů.
Ve vhodných dvou cyklech naplňte okénka daty. Pozor funkce počítají s radiány!
84
Zadání 4.
Na plochu formuláře umístěte dvě komponenty TStringGrid. Levá bude určena pro
zadávání teplot v jednotlivých hodinách směny obsluhou. Proto jí nezapomeňte v Options
nastavit goEditing do true. Pravá bude používána pro výpočet průměrných teplot
v příslušné směně.
Přepočet spustíme tlačítkem „Přepočet“.
85
14
Komponety TabControl a PageControl
Obsah hodiny
Naučíme se vytvářet programy, ve kterých formuláře obsahují záložky.
Cíl hodiny
Po této hodině budete schopni:
●
vytvořit program, který bude mít vzhled kartotéky
Klíčová slova
TTabControl, TPageControl, TTabSheet
14.1
TTabControl
Sada karet.
Všechny karty mají ale stejný obsah. Přepínáme pouze záložky. Dá se říct, že
chování je podobné jako u TRadioGroup nebo TComboBox , ale vzhled je
elegantnější. Naše aplikace může vypadat jako opravdová kartotéka.
Užitečné vlastnosti:
int TabIndex index vybrané karty
TStrings* Tabs seznam nadpisů jednotlivých karet. Seznam lze v době
návrhu vytvořit editorem, který se objeví kliknutím na položku
Tabs v ObjectInspektoru.
bool Multiline true znamená, že pokud se všechny karty nevejdou do
jednoho řádku, budou ve více řádcích.
TTabStyle Style určuje vzhled záložek
TTabPosition TabPosition určují polohu záložek (nahoře, dole, vpravo,
vlevo)
int TabIndex index vybrané záložky (-1 znamená, že není vybrána žádná)
86
TCustomImageList* Images seznam obrázků, které se mohou na záložkách
objevit.
14.2
TPageControl
Sada stránek.
Každá stránka může obsahovat zcela jinou sadu prvků. Každá stránka je
reprezentována jinou položkou typu TTabSheet.
Sadu nejsnáze založíme tak, že pravým tlačítkem myši vyvoláme kontextové
menu, ve kterém se nachází položka New. Každým jejím stiskem založíme další
kartu.
Užitečné vlastnosti:
TTabSheet* ActivePage pointer na aktivní kartu
int ActivePageIndex index aktivní karty
TTabSheet* Pages[int Index] pole pointerů na jednotlivé karty
int PageCount počet karet
TTabPosition TabPosition určení polohy záložek – nahoře, dole,
vlevo,vpravo
TTabStyle Style určení stylu záložek
bool Multiline true – záložky mohou být ve více řádcích
Užitečné události:
OnChange vznikne když je vybrána nová karta
OnChanging vznikne těsně před tím než je vybrána nová karta a umožňuje
nám zakázat přechod na novou kartu. Uživatel má třeba ještě
něco dodělat na stávající kartě.
14.3 TTabSheet
Komponenta představující jednu kartu komponenty TPageControl. Komponentu
vkládáme do formuláře při konfiguraci této komponenty.
Užitečné vlastnosti:
TPageControl* PageControl pointer na TPageControl, který je vlastníkem
této karty
int PageIndex index karty v TPageControl
87
bool TabVisible true znamená, že karta je viditelná
int TabIndex pořadové číslo karty v rámci těch, které jsou viditelné. Jestliže
není vybrána žádná stránka TabIndex má hodnotu -1.
Kontrolní otázky a úkoly
Zadání 1.
Na plochu formuláře umístěte komponentu TTabControl. V Object Inpector
klepnutím na vlastnost Tabs otevřete editovací okno pro založení potřebných
záložek.
Na společnou plochu komponenty TTabControl umístěte tři komponenty
TLabeledEdit a jeden TButton.
Vytvořte u komponenty TTabControl obsluhu události OnChange, ve které budete
měnit nadpisy jednotlivých TEdit.
V obsluze události OnClick komponenty TButton umístěte switch, který se bude
přepínat podle TabIndex a v každé možnosti bude mít specifický výpočet podle
vybrané záložky.
88
Zadání 2.
Na plochu formuláře umístěte komponentu TPageControl.
Klepnutím na komponentu pravým tlačítkem myši vyvolejte kontextové menu a
v něm vyberte nabídku New. Pomocí ní nastavte parametry (hlavně nadpis)
příslušné TTabSheet.
Postup opakujte třikrát pro všechny stránky.
Každá stránka bude obsahovat své ovládací prvky podle toho, co se v ní řeší.
89
15
Komponenty pro práci s menu
Obsah hodiny
Jeden odstavec výstižně popisující, co bude náplní této hodiny.
Cíl hodiny
Po této hodině budete schopni:
●
●
zde krátce uveďte 2 nebo 3 body toho,
co se student naučí
Klíčová slova
Pojmy k zapamatování, jejichž význam je v textu vysvětlen.
15.1
TMainMenu
Komponenta spojující v sobě horní pruh ve formuláři a roletové menu.
Každá položka menu je tvořena jako instance třídy TMenuItem. Jednotlivé položky
můžeme vytvářet v editovacím okně, které se objeví po poklepání na
nezobrazovanou ikonu komponenty v době návrhu.
Užitečné vlastnosti:
TMenuItem* Items pointer na instanci jednotlivých položek menu
TBitmap* Bitmap bitmapa kreslená jako podklad menu
TColor Color barva menu
TCustomImageList* Images seznam obrázků, které lze použít jako pozadí
pro jednotlivé položky menu
15.2 TPopupMenu
Komponenta reprezentuje kontextové (popup) menu, které se zobrazí, když na
komponentě stiskneme pravé tlačítko myši. Každá komponenta formuláře může
90
mít své popup menu jehož název komponente určíme pomocí vlastnosti
PopupMenu. Různé komponenty mohou samozřejmě sdílet i stejné kontextové
menu.
Další používání je podobné práci s hlavním menu.
Užitečné vlastnosti:
TPopupAlignment Alignment určuje zarovnání popup menu vůči kurzoru
myši v okamžiku jeho vyvolání.
bool AutoPopup false zakazuje vynořování popup menu pomocí pravého
tlačítka myši.
TMenuItem* Items pointer na instanci jednotlivých položek menu
Užitečné události:
OnPopup událost vznikne v okamžiku, kdy je popup menu vyvoláno
15.3 TMenuItem
Komponenta reprezentující jednu položku TMainMenu a TPopupMenu.
Užitečné vlastnosti:
AnsiString Caption Nápis zobrazovaný v nabídce.
Chceme-li některou nabídku z menu vyvolat pomocí kláves ALT
a písmeno z nadpisu, pak před toto písmeno umístíme znak &.
Jestliže jako text nadpisu zvolíme znak mínus, pak tato položka
bude zobrazována jako dělící čára mezi nabídkami.
AnsiString Name Název položky. Název volíme výstižný, protože při
rozsáhlejších menu nám může vzniknout třeba i desítky položek.
Je např. vhodné, aby všechny položky menu ve formuláři měly
názvy začínající např. písmenem M.
bool Checked je-li true, přidá se před položku zaškrtnutí
bool Default je-li true, má položka menu titulek tučným písmem a je
vybrána
bool RadioItem true znamená, že položka patří do skupiny položek
spojených spolu jako TRadioGroup. V rámci jednoho sloupce
nabídek můžeme mít i více skupin pracujících jako TRadioGroup.
Do které skupiny příslušný TMenuItem patří, určujeme pomocí
vlastnosti GroupIndex.
GroupIndex číslo určující do které skupiny voleb navzájem se vylučujících
tato komponenta patří
TShortCut ShortCut určuje klávesovou zkratku, kterou lze použít pro
vyvolání této položky menu. Nastavíme-li např. ShortCut=Ctrl+S,
bude se stisk kláves CTRL a S chápat jako kliknutí myši na této
položce.
Break
tato položka startuje nový sloupec nabídky. Lze zde vybrat ze tří
možností jak budou sloupce seřazeny .
91
Užitečné metody:
Add(TMenuItem* Item) dynamicky přidává na konec menu novou položku
Postup je následující:
TMenuItem *nova=new TMenuItem();
nova->Caption=“Uložit jako”;
File->Add(nova);
Delete(int Index) zrušení položky dynamicky
Insert(int Index, TMenuItem* Item) vložení položky na polohu Index
Nejčastější událost:
OnClick
kliknutí na položku nabídky
Příklad nastavení TMainMenu:
Nabídky „Otevřít“ a „Zavřít“ jsou spolu svázány a
navzájem se vylučují. Totéž platí o nabídkách „Alfa“ a
„Beta“. Nabídka „Gama“ je samostatná a kliknutím na
ní ji zaškrtáváme nebo odškrtáváme.
Podívejme se na nastavení celého TMainMenu v dmf
souboru a pak si ve zdrojovém kódu ukažme, jak
budeme přepínat vylučující se nabídky a jak
zaškrtávací nabídku.
Část souboru Unit1.dmf
object MainMenu1: TMainMenu
Left = 16
Top = 16
object MSoubor: TMenuItem
Caption = 'Soubor'
object MOtevrit: TMenuItem
Caption = '&Otevrit'
Checked = True
GroupIndex = 1
RadioItem = True
OnClick = MOtevritClick
end
object MZavrit: TMenuItem
Caption = '&Zavrit'
GroupIndex = 1
RadioItem = True
OnClick = MZavritClick
end
object N1: TMenuItem
Caption = '-'
GroupIndex = 1
end
object MAlfa: TMenuItem
//
//
//
//
//
položku lze vybrat i pomocí Alt+O
tato položka má stav chcked
polozka patří do skupiny 1
položka se chová jako radiobutton
název obslužné metody OnClick
// položku lze vybrat i pomocí Alt+Z
// položka patří do skupiny 1
// položka se chová jako radiobutton
// položka použitá jako předěl
92
Caption = '&Alafa'
Checked = True
GroupIndex = 2
RadioItem = True
OnClick = MAlfaClick
end
object MBeta: TMenuItem
Caption = '&Beta'
GroupIndex = 2
RadioItem = True
OnClick = MBetaClick
end
object N2: TMenuItem
Caption = '-'
GroupIndex = 2
end
object MGama: TMenuItem
Caption = '&Gama'
GroupIndex = 2
OnClick = MGamaClick
end
end
object MKonec: TMenuItem
Caption = '&Konec'
OnClick = MKonecClick
end
end
// položka patří do skupiny 2
// položka patří do skupiny 2
// položka použita jako předěl
Zdrojový kód Unit1.cpp
void __fastcall TForm1::MOtevritClick(TObject *Sender)
{
MOtevrit->Checked=true;
// provázáno s MZavrit
}
void __fastcall TForm1::MZavritClick(TObject *Sender)
{
MZavrit->Checked=true;
// provázáno s MOtevrit
}
void __fastcall TForm1::MAlfaClick(TObject *Sender)
{
MAlfa->Checked=true;
// provázáno s MBeta
}
void __fastcall TForm1::MBetaClick(TObject *Sender)
{
MBeta->Checked=true;
// provázáno s MAlfa
}
void __fastcall TForm1::MGamaClick(TObject *Sender)
{
MGama->Checked=!MGama->Checked; // samostatná volba
}
void __fastcall TForm1::MKonecClick(TObject *Sender)
{
Close();
//zavření hlavního okna – program končí
}
93
Kontrolní otázky a úkoly
Zadání 1.
Na plochu formuláře umístěte dvě komponenty TLabeledEdit a TButton. Kromě
toho na plochu umístěte také komponenty TMainMenu a TPopupMenu.
Nabídky TMainMenu:
Nabidky TPopupMenu:
Kontextové menu připojte ke všem komponentám formuláře i k formuláři
samotnému.
Každá položka menu otevře speciální modální okno pro nastavení požadovaných
vstupních údajů. Zadání se pak vypíše do horního TLabeledEdit a výsledek
výpočtu do spodního.
Položky v menu „Výpočty“ se navzájem vylučují. U položek v menu „Nastavení“
upravte vždy po zadání jejich Caption tak, aby na konci byl nastavený znak
desetinného odělovače a počet desetinných míst.
Položky kontextového menu a menu „Nastavení“ budou mít stejnou obsluhu.
94
16
Dialogové komponenty
Obsah hodiny
Naučíme se pracovat se standardními dialogovými okny.
Cíl hodiny
Po této hodině budete schopni:
●
●
●
●
ve svých programech používat standardní dialogová okna
okna pro otevření a uložení souboru,
okno pro nastavení vlastností písma
okno pro nastavení barvy
Klíčová slova
TOpenDialog, TSaveDialog, TFontDialog, TColorDialog.
Při tvorbě programů často potřebujeme provádět takové činnosti jako je otevírání
nebo ukládání souborů, volba a změna vlastností fontů dle přání uživatele, změna
barev dle vkusu uživatele, volit a nastavovat vlastnosti tiskárny a podobně.
Pro tyto činnosti si můžeme samozřejmě vytvořit vlastní modální formuláře
(potřebujeme dobře znát Win32 API), ale je to pracné a zbytečné, protože
v prostředí Windows jsou tyto činnosti již vyřešeny. Stačí pouze nastudovat
vhodné funkce knihovny Win32 API.
Ve vývojovém prostředí C++ Builderu se nám autoři pokusili ještě dále zjednodušit
práci a vytvořili několik tzv. dialogových komponent, které za nás volají příslušné
služby Windows.
Tyto komponenty se nacházejí ve skupině Dialogs. Dnes se zmíníme o
komponentách pro práci se soubory, fonty a barvami. Později si ještě přibereme
komponenty pro práci s tiskárnami.
Práce se všemi dialogovými komponentami je obdobná. Před použitím
komponenty nejdříve v programu nastavíme některé výchozí parametry
dialogového formuláře, pak zavoláme členskou metodu Execute a jestliže tato
vrátí hodnotu true, která znamená, že jsme dialog ukončili správně (tlačítkem OK),
vyčteme z komponenty údaje, které jsme prací s komponentou získali (např. název
souboru, s kterým chceme pracovat).
95
Většina dialogů je modálních. To znamená, že do ukončení dialogu nemůžeme
přistupovat na jiné formuláře naší aplikace.
16.1
TOpenDialog
Dialog, který se používá pro volbu souboru, který chceme otevřít pro čtení nebo
psaní.
Po správném ukončení dialogu můžeme z komponenty vyčíst název souboru i
s celou cestou.
Před otevřením dialogu je vhodné nastavit některé vlastnosti a tím uživateli
zpříjemnit práci.
Užitečné vlastnosti:
AnsiString DefaultExt výchozí přípona souboru
(uvádíme pouze příponu skládající se ze tří písmen
a bez úvodní tečky, tedy např. txt nebo rtf)
AnsiString FileName při zápisu určuje výchozí název souboru, po ukončení
dialogu obsahuje název vybraného souboru i s cestou.
TStrings *Files obsahuje seznam souborů, které jsme vybrali. Můžeme totiž
vybrat i více souborů. Musíme to ovšem povolit v Options prvkem
ofAllowMultiSelect.
AnsiString InitialDir název výchozího adresáře
AnsiString Title titulek dialogu
AnsiString Filter obsahuje povolené přípony souborů včetně jejich popisů.
Např. takto:
SaveDialog1->Filter="Textový soubor (*.txt)|*.TXT|
Zdrojový soubor (*.cpp)|*.CPP|
96
Hlavičkový soubor (*.h)|*.H|
Všechny soubory (*.*)|*.*"
Zde jsou popsány čtyři možnosti, podle kterých budeme vybírat
soubor. Jsou odděleny znakem |. Každá možnost se skládá ze
dvou položek, které jsou rovněž odděleny znakem |. První
položka je popis typu souboru (bývá zvykem do popisu psát
v závorce i příponu) a druhá položka ukazuje jak vyfiltrovat
soubory, které nás zajímají.
Filter lze s výhodou vytvářet pomocí FilterEditoru, který otevřeme
klikem na tlačítko s třemi tečkami u položky Filter v Object
Inspektoru.
int FilterIndex pořadí řetězce z vlastnosti Filter, který je použit jako výchozí.
Číslujeme od 1!
TOpenOption Option množina (Set), která určuje vzhled a chování dialogu.
Můžeme plnit např. takto:
TOpenOptions oo=OpenDialog1->Options;
oo<<ofOldStyleDialog;
SaveDialog1->Options=oo;
Příklad
void __fastcall TForm1::Button1Click(TObject *Sender)
{
OpenDialog1->InitialDir="c:\\data";
OpenDialog1->DefaultExt="txt";
OpenDialog1->Title="Otevření souboru";
if(OpenDialog1->Execute())
{
Edit1->Text="Zvolený soubor: " + OpenDialog1->FileName;
}
}
16.2
TSaveDialog
Dialog používaný pro výběr názvu ukládaného souboru včetně cesty. Vlastnosti
jsou prakticky stejné jako u TOpenDialogu. Malinko se liší pouze Options.
97
16.3
TFontDialog
Dialog určení pro volbu fontu a jeho vlastnosti.
Po uzavření dialogu můžeme vyčíst vlastnost Font, která obsahuje font
s nastavenými vlastnostmi. My ho pak můžeme přiřadit např. k vlastnostem fontu
našeho TMemo.
Při té příležitosti si ještě jednou připomeňme, že většina fontů je proporcionálních,
to znamená, že šířka všech znaků není stejná. Pouze několik fontů je
neproporcionálních, tedy takových, u kterých všechny znaky mají stejnou šířku.
Takovým fontem je např. font Courier New, který se tedy hodí např. pro výpisy ve
formě tabulek.
16.4
TColorDialog
Dialog pro určení barvy.
Pomocné dialogové funkce
98
Kromě výše uvedených dialogových komponent existuje ještě celá řada
pomocných dialogových funkcí, které otevírají modální okna pro různé pomocné
činnosti.
Jejich seznam nejsnáze získáme tak, že si vyžádáme help k funkci ShowMessage
a pak z tohoto výběru přejdeme do stránky Dialogs Unit. Zde je pak najdeme
v odstavci Routines.
Nejčastěji používanými funkcemi jsou ShowMessage a MessageDlg.
Funkce ShowMessage se používá pro otevření modálního okna s hlášením pro
obsluhu. Uživatel má k dispozici pouze tlačítko „Ok“, kterým zprávu potvrdí a tím
okno zavře.
Funkce MessageDlg otevírá složitější dialog, kde již máme více možností a
funkce podle vybrané volby vrací příslušnou hodnotu.
Obě funkce mají pouze jednu nevýhodu. Popisy tlačítek jsou anglicky a nedají se
snadno změnit na české. Později si ukážeme funkce Win32 API, které vytvářejí
rovněž dialogová okna, ovšem jejich ovládací prvky už mohou mít české popisy.
Příklad
if(MessageDlg("Soubor zavřít?",mtConfirmation,
TMsgDlgButtons() << mbYes<<mbNo,0); ==mrYes)
{
}
Pro vytváření krátkých dialogů je místo MessageDlg vhodnější metoda třídy
TApplication MessageBox, u které si také můžeme volit, která tlačítka bude
náš dialog obsahovat a jaké ikony budeme chtít. Má však výhodu v tom, že
nápisy tlačítek jsou v češtině.
Deklarace metody je tato:
int __fastcall MessageBox(char* Text, char* Caption, int Flags = MB_OK);
99
Flags určují která tlačítka budou zobrazena a které ikony se použijí a
můžeme je kombinovat.
Nejpodrobnější informace k této metodě nalezneme v nápovědě k Win32
API, která se nainstaluje s instalací C++ Builderu. Nalezneme ji na tomto
místě: "c:\Program Files\Common Files\Borland Shared\MSHelp\win32.hlp"
v rejstříku pod heslem MessageBox.
Příklad
Application->MessageBox("Chybně zadané číslo", "Pozor",
MB_YESNOCANCEL | MB_ICONWARNING);
Svislá čára mezi flagy představuje binární součet. O jeho významu si
povíme později.
Dialogové okno pak vypadá takto:
100
Kontrolní otázky a úkoly
Zadání 1.
Na plochu formuláře položte komponentu TMemo a TMainMenu.
TMainMenu nastavte dle přiložených obrázků.
Při ošetření nabídek „Otevřít“ a „Uložit“ použijte dialogové komponentu TOpenDialog a
TSaveDialog, které rovněž položíte na plochu formuláře.
Pro vyřešení nabídek v menu „Úpravy“ si zopakujte práci s komponentou TMemo,
případně použijte nápovědu.
101
17
Práce s časem a datem
Obsah hodiny
Naučíme se pracovat s údaji typu čas nebo datum.
Cíl hodiny
Po této hodině budete schopni:
●
vytvářet programy pracující s časem a datem
Klíčová slova
TDateTime, TMonthCalendar, TDateTimePicker, TCCalendar
Při tvorbě programů velmi často potřebujeme pracovat s údaji obsahujícími čas a
datum. Pro usnadnění práce s takovými údaji byla v prostředí C++ Builderu
navržena třída TDateTime. Základem této třídy je hodnota deklarovaná takto:
double Val;
V tomto údaji je uchován jak datum, tak i čas. Celočíselná část hodnoty Val
představuje počet dnů od 30.12.1899. Desetinná část představuje čas ve dni.
Příklady některých hodnot:
0
2.75
-1.25
35065
30.12.1899
1.1.1900
29.12.1899
1.1.1996
00:00
18:00
06:00
00:00
Třída obsahuje mnoho přetížených operátorů a členských funkcí pro práci s těmito
údaji.
Různé verze konstruktoru:
TDateTime()
TDateTime(const TDateTime& src)
TDateTime(const TDateTimeBase& src)
TDateTime(const double src)
TDateTime(const int src)
enum TDateTimeFlag {Date, Time, DateTime}; - pomocný výčtový typ
TDateTime(const AnsiString& src, TDateTimeFlag flag = DateTime);
TDateTime(unsigned short year, unsigned short month, unsigned short day);
102
TDateTime(unsigned short hour, unsigned short min,
unsigned short sec, unsigned short msec);
Příklady použití konstruktorů
TDateTime dt;
// údaj s hodnotou 0 odpovídající
// datumu a času 30.12.1899 00:00
TDateTime dt(2010,8,12); // deklarace a naplnění datumem
dt=TDateTime(1999,2,20); // naplnění novou hodnotou
// naplnění datem a časem
dt= TDateTime(1999,2,20)+TDateTime(12,15,30,0);
Některé užitečné metody:
static TDateTime CurrentDate vrací dnešní datum
static TDateTime CurrentDateTime vrací momentální datum a čas
např.:
dt=TDateTime::CurrentDateTime(); - CurrentDateTime je statická
funkce,mohu volat i bez instance třídy jen s určením
jmenného prostoru (name space)
static TDateTime CurrentTime vrací momentální čas
AnsiString DateString převede uložené datum na textový řetězec
AnsiString DataTimeString převede datum a čas na řetězec
AnsiString TimeString převede čas na text. řetězec
int DayOfWeek vrátí pořadové číslo dne v týdnu. Neděle má číslo 1.
void DecodeDate() dekoduje datum na rok, měsíc, den
void DecodeTime() dekóduje čas na hodina, minuta, sekunda, milisekunda
int FileDate() dekóduje obsah instance na hodnotu s údajem času souboru
používaném v DOSu.
static FileDateToDateTime() – převede čas souboru z DOSu do TDateTime
AnsiString FormatString() určuje způsob formátovaní řetězce datu a času
Díky přetížení různých operátorů lze údaje typu TDateTime sčítat, odečítat,
přičítat k nim celá čísla a podobně.
Třída TDateTime může mít také zkrácené označení TDate.
Mnoho dalších zajímavých funkcí pro práci s časem a datem v prostředí
C++Builderu získáme v helpu. Vyvoláme si help např. k funkci Date a pak
přejdeme do kategorie „date/time routines“. Tyto funkce většinou vyžadují
#include <DateUtils.hpp>
Opravdu tam je přípona hpp !
Nejlepší přehled získáte ve starém helpu ještě k C++ Builderu v.6 !
103
Příklad
Zjištění času a data vzniku souboru:
// funkcí FileAge zjistíme čas souboru podle zásad DOSu
int casSouboru=FileAge(OpenDialog1->FileName);
// pomocí metody FileDateToDateTime převedeme na tvar TDateTime
TDateTime dt=TDateTime::FileDateToDateTime(casSouboru);
17.1
TMonthCalendar
Komponenta pro výběr dne v měsíci.
Komponenta se nachází ve skupině Win32 a umožňuje obsluze vybrat konkrétní
den v kalendáři. Důležitá vlastnost je Date, ze které můžeme vyčíst vybrané
datum. Čas vybraného data je nastaven na nulu, což představuje půlnoc.
17.2
TDateTimePicker
Komponenta představující spojení TComboBox s komponentou TMonthCalendar.
Nachází se ve skupině komponent Win32.
TDateTimePicker je vizuální komponenta navržena speciálně pro zadávání data
nebo času.
Užitečné vlastnosti:
DateMode dvě možnosti nastavení zobrazení
dmComboBox připomíná TListBox nebo TComboBox,
104
s výjimkou toho, že zobrazovaný seznam je nahrazen obrázkem
kalendáře. Viz. levý obrázek. Uživatel může vybírat datum
z tohoto kalendáře.
dmUpDown vzhled se změní dle pravého obrázku. Datum nebo
čas mohou být vybírány šipkami nahoru a dolů nebo přímo
vpisovány do políčka.
DateFormat
Format
obě nastavení ve vzájemné kombinaci určují, co se bude
v políčku komponenty zobrazovat. Viz. help.
TDateTime Date vlastnost určená pro nastavování a vyčítání data
TDateTime Time vlastnost určená pro nastavení a vyčtení času
Kind
nastavení, zda komponenta bude používána pro výběr času
nebo data
17.3
TCCalendar
Jiná užitečná komponenta pro práci s kalendářem. Nachází se ve skupině
Samples.
Je to potomek třídy TCustomGrid, který je rodičem také komponenty TStringGrid.
Ke komponentě není help. Nejvíce se o ní dozvíme tak, že na deklaraci
komponenty v hlavičkovém souboru našeho formuláře klepneme pravým tlačítkem
myši a necháme si zobrazit její deklaraci, která se nachází v hlavičkovém souboru
ccalendr.h. Soustředíme se na sekce public a __published. Další přehled
získáme prohlídkou seznamu vlastností a událostí v Object Inspektoru.
105
Kontrolní otázky a úkoly
Zadání 1.
Na plochu formuláře umístěte dvě komponenty TDateTimePicker, dva TLabeledEdit a
TButton. Dále ještě budete potřebovat dvě TLabel pro nadpisy.
Veškerá činnost se provádí pouze v obsluze OnClick tlačítka.
Počet dnu získáme odečtením vlastnosti Date z levého TDateTimePicker od Date
pravého. Jak jsme si řekli, celočíselná část představuje počet celých dnů.
Celočíselnou část získáme přiřazením do proměnné typu int.
Den v týdnu získáme pomocí metody DayOfWeek třídy TDateTime.
Podrobnosti získáte v nápovědě.
Zadání 2.
Na plochu formuláře umístěte šest komponent TLabeledEdit.
Všechnu činnost můžete provádět v konstruktoru formuláře nebo v jeho obsluze
události OnShow.
Do „Dnešní datum“ zapište dnešní datum, které získáte funkcí
TDateTime::CurrentDateTime() vracející momentální datum. Toto momentální datum
také rozpočítejte pomocí metody DecodeDate třídy TDateTime na rok, měsíc, den.
Tyto hodnoty pak použijte pro vytváření datumů před měsícem, před rokem, po
měsíci a po roce. Z těchto datumů pak získejte název dne.
106
Zadání 3.
Na plochu formuláře umístěte dva TLabeledEdit a jeden TButton. Dále ještě
neviditelnou komponentu TOpenDialog.
Po stisku tlačítka otevřete dialog pro otevření souboru a vyberte požadovaný soubor.
Pomocí funkce FileAge získejte čas vybraného souboru jako číslo typu int, což je
záznam data souboru ve standardu DOS. Toto číslo pak pomocí funkce
TDateTime::FileDateToDateTime převeďte na formát TDateTime. Pak cestu na
soubor a získaný čas vypište do editovacích políček
107
18
Časovače
Obsah hodiny
Naučíme se vytvářet programy, které dokáží v pravidelných intervalech provádět
nějakou činnost.
Cíl hodiny
Po této hodině budete schopni:
●
●
vytvořit např. hodiny zobrazující čas počítače,
změřit čas mezi dvěma událostmi
Klíčová slova
TTimer, PerformanceCounter, ProcessMessages()
18.1
TTimer
Často potřebujeme v pravidelných intervalech vykonávat nějakou činnost. Pro tyto
účely existuje nevizuální komponenta TTimer.
Vlastnosti:
bool Enabled true znamená, že je běh časovače povolen
unsigned int Interval čas v milisekundách, po jehož vypršení dojde
k události OnTimer.
Důležité události:
OnTimer
Událost, která vznikne po uplynutí nastavené doby časovače.
Jestliže chceme, aby časovač běžel hned od spuštění programu, nastavíme
Enabled do true.
Jinak nastavíme Enabled do true až potřebujeme spustit odměřování a máme
možnost ho kdykoliv zase zastavit. Můžeme si také udělat počítadlo události
OnTimer a po dosažení požadovaného počtu události OnTimer časovač přímo
v této události zastavit. Uvnitř události můžeme také měnit délku následujícího
kroku časovače.
I přesto, že délku intervalu nastavujeme v milisekundách, tak vlastní rychlost
časovače záleží na vlastnostech PC a operačního systému (viz. příklad „Pokus
108
TTimer“). Na dnešních PC a v operačním systému Windows XP můžeme např.
dostat počet události OnTimer maximálně asi 64 za sekundu. Můžeme tedy
časovač TTimer použít pouze pro odměřování pomalejších dějů a musíme počítat
s tím, že u něho může dojít i ke chvilkovým zastavením nebo zpožděním podle
momentálního stavu a vytížení systému.
18.2
Performace Counter
Potřebujeme-li odměřovat čas s větší přesností máme možnost využít tak zvaného
Performace Counteru, který se u dnešních procesorů nachází přímo v nich. Tyto
čítače mají podstatně větší rychlost a kromě toho běží stále pravidelně nezávisle
na stavu systému.
Při práci s Performace Counterem pracujeme se 64-bitovými datovými typy. Vždy
si nejdříve voláním funkce QueryPerformanceCounter zjistíme, zda náš procesor
Performance Counter vlastní, pak voláním funkce QueryPerformanceFrequency
zjistíme počet hodin čítače za sekundu a tím jsme připraveni k odměřování časů.
Na začátku odměřování si zjistíme počáteční stav čítače, na konci koncový stav.
Délku intervalu pak zjistíme odečtením těchto stavů a podělením frekvencí čítače.
Příklad
bool performanceCounterInstalovan;
__int64 clockyZacatek;
// 64-bitový integer !
__int64 clockyKonec;
__int64 fregvence;
double sekundy
performanceCounterInstalovan=QueryPerformanceCounter((LARGE_INTEGER
*)&clockyZacatek);
if(performanceCounterInstalovan)
QueryPerformanceFrequency((LARGE_INTEGER *)&fregvence);
if(performanceCounterInstalovan)
QueryPerformanceCounter((LARGE_INTEGER *)&clockyZacatek);
if(performanceCounterInstalovan) {
QueryPerformanceCounter((LARGE_INTEGER *)&clockyKonec);
sekundy=((double)(clockyKonec-clockyZacatek))/fregvence;
}
18.3
TApplication::ProcessMessages()
V systému Windows je celý program řízen na základě obsluh událostí. Někdy se
může stát, že některá obsluha bude trvat hodně dlouho. V tom případě než dojde
k ukončení této obsluhy přestane celý program reagovat na zásahy uživatele .
Nelze ani přesouvat okno programu, dokonce nelze program ani ukončit.
109
Můžeme to vyzkoušet tak, že v obsluze nějaké události vytvoříme nekonečnou
smyčku. Program pak můžeme ukončit pouze z vývojového prostředí nebo přes
Task Manager. Naprosto se přestane s námi bavit.
Aby k takovému absolutnímu „zamrznutí“ programu nedocházelo, má třída
TApplication, která je základní třídou našeho programu, metodu
ProcessMessages. Zavoláním této metody způsobíme to, že se chod obsluhy
události zastaví a provedou se obsluhy všech požadavků, které mezitím vznikly
(tedy i požadavku na ukončení programu). Pak se program vrátí zpět do přerušené
obsluhy a pokračuje v jejím provádění.
Metodu Application->ProcessMessages() voláme tak často, aby to na jedné
straně příliš nezdržovalo provádění dlouhotrvající obsluhy, ale na druhé straně,
aby zase uživatel příliš nepostřehl, že program nějak neochotně reaguje. Záleží
tedy na konkrétní situaci jak často budeme funkci volat
Příklad
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
Label1->Caption="Cyklus neběží";
}
//-------------------------------------------------------------------void __fastcall TForm1::bStartClick(TObject *Sender)
{
unsigned i;
Ukoncit=false;
Label1->Caption="Cyklus běží";
for(i=0;;i++) {
if(i%1000000==0) {
Beep(1000,10);
Application->ProcessMessages();
// nekonečný cyklus
// vždy po 1000000 cyklech
// krátce písknu
// ošetření zpráv,
// které mezi tím přišly
if(Ukoncit) {
// v ošetření zpráv mohl být
// nahozen i Ukoncit
Label1->Caption="Cyklus neběží";
return;
}
}
}
}
//--------------------------------------------------------------------void __fastcall TForm1::bKonecClick(TObject *Sender)
{
Ukoncit=true;
}
110
Kontrolní otázky a úkoly
Zadání 1.
Vytvořte si jednoduchou střelnici. Snažite se kliknout na TButton s velkým písmenem X.
Butonu ale pomoci TTimer co vteřinu změňte náhodně polohu.
Na formuláři jsou umístěny dva TPanel. Horní slouží jako hrací pole.
Do spodního panelu umístěte TLabeledEdit a TButton.
Kamkoliv do formuláře umístěte ještě dvě nevizuální komponenty TTimer. Jednu pro
odměřování doby hry (10 sec) a druhou pro určování okamžiků posunu tlačítka (1 sec).
Oba timery spusťte až stiskem tlačítka „Start“ a uplynutím doby hry zase oba zastavte..
Zadání 2.
Na plochu formuláře umístěte TPanel a do něj TLabel a TTimer.
Časovači nastavte Interval na hodnotu 500 ms.
V obsluze události OnTimer vyčtěte pomocí funkce TDateTime::CurrentTime aktuální
čas počítače a ten zobrazte vhodně v komponentě TLabel.
111
Zadání 3.
Na plochu formuláře umístěte TLabeledEdit a dva TButton.
V obsluze události OnClick tlačítka „Start“ pomocí nekonečné řady počítejte čtvrtinu
hodnoty π .
Výpočet provádějte pomocí této řady:
π
4
=
1
1
1
1
(−1) n
−
+
−
+ ........ +
2 * 0 + 1 2 *1 + 1 2 * 2 + 1 2 * 3 + 1
2*n +1
Vždy po 1000000 krocích proveďte výpis do editovacího okénka a překreslete okno
pomocí funkce
Application->ProcessMessages();
Cyklus probíhá tak dlouho, pokud nestiskneme tlačítko Stop.
Počítejte s čísly typu long double.
Zadání 4.
Vytvořte program, který změří dobu mezi stiskem tlačítka „Start“ a tlačítka
„Stop“. Pro odměřování použijte Performance Counter.
112
19
Práce se soubory v IDE C++ Builder
Obsah hodiny
Naučíme se pracovat se soubory pomocí třídy TFileStream.
Cíl hodiny
Po této hodině budete schopni:
●
●
Napsat programy pracující se soubory pomocí třídy TFileStream,
Používat bitové operace
Klíčová slova
Bitové operace, TFileStream, .
19.1
Binární operace
Binární operace jsou takové operace, které nepracují s celými byty, ale přímo
s jednotlivými bity a jsou používány již v jazyku C. My jsme se s nimi zatím
neseznámili, protože jsme je nepotřebovali.
O binárních operacích se nejvíce a nejpřehledněji dozvíte z helpů ke starému C++
Builver v.6. Vyhledejte heslo “Bitwise operators”.
Jsou možné tyto operace:
|
Binární součet. Sčítané údaje položíme jako pod sebe a jednotlivé bity
sčítáme dle pravidel pro logické součty. Stačí tedy jedna jednička a výsledek
je jednička.
Operace se hodí pro nastavení konkrétních bitů do jedničky.
Např. ve výrazu x |= 0x01; dojde v x k nastavení nultého bitu do jedničky ať
už byl předtím jakýkoliv.
&
Binární součin. Násobené údaje položíme jako pod sebe a jednotlivé bity
vynásobíme dle pravidel pro logické součty. Stačí tedy jedna nula a výsledek
je nula.
Operace se hodí pro nastavení konkrétních bitů do nuly.
Např. ve výrazu x &= 0xFE; dojde v x k nastavení nultého bitu do nuly ať už
byl předtím jakýkoliv.
^
Bitová operace typu XOR (negovaná ekvivalence). Zpracovávané údaje
položíme jako pod sebe a jednotlivé bity zpracujeme dle pravidel pro XOR.
113
Jsou-li tedy bity stejné, je výsledek nula, jsou-li různé, je výsledek jedna.
Operace se hodí k negování konkrétních bitů.
Např. ve výrazu x ^= 0x01; dojde v x k negaci nultého bitu, ostatní zůstanou
tak jak byly.
~
Bitová negace. Všechny bity vstupního údaje jsou znegovány.
Např. jestliže byl na začátku obsah proměnné x 0x55, tedy 0101 0101, po
provedení výrazu x = ~x; bude mít hodnotu 0xAA, tedy 1010 1010;
<<
Posuv vlevo o zadaný počet bitů. Zprava se doplňuje údaj nulou, zleva se
bity ztrácí.
Např. jestliže na začátku byla v x hodnota 0x02, tedy 0000 0010, po
provedení příkazu x << 2; bude v x hodnota 0x08, tedy 0000 1000.
>>
Posuv vpravo o zadaný počet bitů. Zleva se doplňuje údaj pro datové typy
unsigned nulou, pro typy signed hodnotou znaménkového bitu (nejlevější
bit).
Např. jestliže na začátku byla v unsigned x hodnota 0xAA, tedy 1010 1010,
po provedení příkazu x >> 2; bude v x hodnota 0x2A, tedy 0010 1010.
Jestliže provedeme operaci ^ (exclusive or) mezi naším byte a nějakým jiným
bytem, pak tam, kde byly v pomocném bytu jedničky, dojde v našem byte k negaci
bitů. Na místech, kde jsou v pomocném bytu nuly, zůstanou bity v našem byte
nezměněny. Toho se často používá k jednoduchému šifrování. První operací ^
našeho byte se zvoleným klíčem dojde u nás k negaci bitů na místech, kde jsou
v klíči jedničky. Tím se stane text nečitelný. Druhou operací ^ se stejným klíčem se
náš byte vrátí do původního stavu.
Operace binárního součtu | se často používá pro skládání hodnot, ve kterých
každý bit něco znamená.
Příklad na použití binárního součtu máme např. v parametrech funkce
TApplication::MessageBox,
kde pomocí binárního součtu vytváříme příznaky, která tlačítka a které ikony má
modální dialogové okno obsahovat. Např.
Application->MessageBox("Chybně zadané číslo", "Pozor",
MB_OK | MB_ICONWARNING);
(základní informace o funkci naleznete v nápovědném souboru o třídě
TApplication. Nápověda ovšem není kompletní. Přesnější informace o příznacích
naleznete v nápovědě k funkci MessageBox z Win32 API).
19.2
TFileStream
Třída TFileStream představuje zapouzdření činností se soubory do jedné třídy.
Práce s ní je daleko přehlednější a jednodušší než s funkcemi z knihoven stdio a
iostream.Kromě toho plně využívá výjimek, což nám pomůže s přehlednějším
zpracováním výjimečných stavů.
114
Třída je navržena hlavně pro práci se soubory obsahujícími struktury dat, tedy
s takovými soubory, u kterých můžeme předem přesně stanovit, na kterém místě
začíná další skupina údajů.
Práci s textovými soubory si musíme naprogramovat sami. Např. čtení řádku
představuje cyklické vyčítání po jednom znaku s tím, že znaky \r (návrat na
začátek řádku) vynecháváme a na znaku \n (nový řádek) končíme a nahradíme ho
ve výstupním stringu zakončovací nulou. Při zápisu řádku naopak zakončovací
nulu ze vstupního stringu nahradíme dvojicí znaků \r\n.
Užitečné metody:
TFileStream(AnsiString FileName, Word Mode) – konstruktor třídy.
Zadáváme zde název souboru i s cestou a mód v jakém chceme se
souborem pracovat. Mód vytváříme skládáním jednotlivých příznaků
pomocí binárního součtu.
Např. fmOpenRead | fmOpenWrite | fmShareDenyNone. Viz. help.
Jestliže se nepodaří soubor otevřít, je generována výjimka.
int Read(void *Buffer, int Count) – čtení Count bytů do pole Buffer. Vrací
kolik se ji podařilo opravdu přečíst bytů.
int Write(const void *Buffer, int Count) - zápis Count bytů z pole Buffer do
souboru. Vrací kolik se ji podařilo opravdu bytů zapsat.
int Seek(int Offset, Word Origin) – posun ukazovátka v souboru o Offset bytů
vzhledem k místu určenému pomocí Origin (soFromBeginning,
soFromCurrent,soFromEnd)
Užitečné vlastnosti:
__int64 Position – poloha ukazovátka v souboru
__int64 Size – momentální délka souboru. Pokud tuto délku přepíšeme na
menší, zbytek dat v souboru se ztratí.
int Handle – handle soubou, který můžeme používat pro další práce se
souborem pomocí standardních funkcí WIN32 API.
Příklad
// vyčítání textového souboru po řádcích a výpis do TMemo
void __fastcall TForm1::VypisClick(TObject *Sender)
{
TFileStream *fs=new TFileStream(OpenDialog1->FileName,
fmOpenRead|fmShareDenyNone);
fs->Seek(PolohaVSouboru,soFromBeginning);
String radek;
while(CtiRadek(fs,radek)) {
Memo1->Lines->Add(radek);
}
delete fs;
}
//-------------------------------------------------------------------bool __fastcall TForm1::CtiRadek(TFileStream *s, String &str)
115
{
char znak;
bool nacteno=false;
str=””;
while(s->Read(&znak,sizeof(char))!=0) {
nacteno=true;
if(znak=='\n')
// na znaku \n končím se čtením řádku
break;
if(znak!='\r')
// znak \r přeskakuji
str += znak;
}
return nacteno;
}
19.3
Nejdůležitější pomocné funkce
Nápovědu k dále uvedeným funkcím získáte pod heslem „file management
routines“.
FindFirst
FindNext
FindClose
skupina funkcí pro hledání souborů v adresáři v programu
ChDir
změna aktuálního adresáře
CreateDir
založení nového adresáře
RemoveDir odstranění adresáře
DirectoryExists dotaz na existenci adresáře
ForceDirectories vytvoření adresáře i s celou cestou
GetCurrentDir dotaz na aktuální adresář
SetCurrentDir nastavení aktuálního adresáře
DiskFree
DiskSize
dotaz na velikost volného místa na disku
dotaz na velikost disku
FileAge
získání data souboru ve formátu DOS
FileGetDate
získání data otevřeného souboru ve formátu DOS
FileDataToDateTime převod data souboru z fotmátu DOS do TDateTime
RenameFile
přejměnování suboru
DeleteFile
vymazání souboru
FileExists
dotaz na existenci souboru
FileGetAttr
zjištění atributů souboru
FileSetAttr
nastavení atributů souboru
Kontrolní otázky a úkoly
1) Vytvořte program, který bude umět zašifrovat a odšifrovat vybraný soubor
pomocí operace XOR se zadaným klíčem. Vzhled vašeho programu
navrhněte sami.
2) Navrhněte program pro zjištění co nejvíce informací o vybraném souboru.
.
116
20
Příklady na práci se soubory
Obsah hodiny
Na konkrétních příkladech si procvičíme práci se soubory.
Kontrolní otázky a úkoly
Zadání 1.
Vytvořte program, který:
1. Vypíše vybraný textový soubor do TMemo.
2. Vyčítá z vybraného souboru postupně řádky a hledá v nich zadaný text.
Každým stiskem tlačítka „Hledej“ vypíše jeden nalezený řádek.
Když už nebylo nic nalezeno, vypíše prázdné okénko a dalším stiskem začne
hledání znovu od začátku.
Nápověda:
1. Pro vyčítání řádku ze souboru využijte postup uvedený ve funkci „CtiRadek“, která
je v příkladu z minulé kapitoly.
2. Pamatujte si, kde se při vyčítání minule skončilo a odsud začněte číst další řádek.
3. Při každém stisku tlačítka „Hledej“ soubor znovu otevřete, přesuňte se na místo,
kde se minule skončilo, vyčtěte řádek a soubor opět zavřete.
4. Existenci textu v řádku zjistěte pomocí metody AnsiString::Pos.
117
Zadání 2.
Vytvořte program pro práci se soubory obsahující struktury udajů o žácích.
O každém žákovi si pamatujte jméno, příjmení, věk a váhu.
Význam ovládacích prvků:
Založit soubor: Pomocí dialogové komponenty TSaveDialog zjistěte cestu a
název nového souboru.
Otevřít soubor: Pomocí dialogové komponenty TOpenDialog zjistěte cestu na
existující soubor.
Vpred: V otevřeném souboru vyčteme strukturu údajů následujícího žáka
a vypíšeme v editovacích okénkách.
Vzad: V otevřeném souboru vyčteme strukturu údajů předcházejícího žáka
a vypíšeme v editovacích okénkách.
Upravit aktuální záznam: Pointer v souboru přesuneme zpět před minule
vyčtený záznam a přepíšeme ho strukturou s novými údají z editovacích
okének.
Připojit jako nový záznam: Pointer v souboru posuneme na konec souboru a
zapíšeme nové údaje z editovacích okének.
118
Zadání 3.
Vytvořte jednoduchý program pro práci se souborem obsahujícím údaje o
výrobcích na skladu.
Formulář obsahuje jednu komponentu TPageControl, která obsahuje tři stránky.
Každá stránka pracuje se soubory obsahujícími údaje o výrobcích jiným
způsobem.
119
21
Grafika I
Obsah hodiny
Popíšeme si základní struktury a třídy, které budeme používat v aplikacích
pracujících s grafikou.
Cíl hodiny
Po této hodině budete schopni:
●
pochopit smysl datových struktur a objektů, které se při práci s grafikou
používají
Klíčová slova
TCanvas, TColor, TBitmap, TPicture, TBrush, TPen, TPaintBox, TImage
Práce s grafikou pod Win32 API představuje poměrně náročnou práci, při které je
nutná také řádná dávka opatrnosti. Pro práci s grafikou si Windows drží řadu
různých datových struktur (tzv. prostředky), které jsou společné pro celé Windows.
Prostor pro tyto struktury není ovšem neomezený. Proto je třeba si pro nějakou
svou činnost v rámci grafického systému vždy z těchto prostředků vypůjčit to, co
zrovna potřebujeme (např. strukturu pro popis štětce nebo pera). Až už tyto
prostředky nepotřebujeme, musíme je opět uvolnit, aby je mohly Windows
nabídnout jiným programům. Jestliže si nedáme pozor, může se stát, že tyto
grafické prostředky Windows vyčerpáme a znemožníme tak práci celých Windows.
21.1
TCanvas
Pro zjednodušení práce s grafikou byla v rámci knihovny VCL zavedena třída
TCanvas (plátno), která za nás provede potřebné zabírání a opětné uvolňování
prostředků a zjednoduší nám volání různých funkcí Win32 API. Třída obsahuje
poměrně málo datových položek, ale tyto položky většinou představují ukazatele
na objekty jiných tříd.
V následující tabulce jsou běžně používané vlastnosti objektu TCanvas.
Vlastnosti:
TFont *Font Určuje font použítý při psaní textu do obrázku. Pomocí objektu
TFont můžeme určit písmo, barvu, velikost a styl písma.
TBrush *Brush Určuje barvu a vzor, které TCanvas používá pro vyplňování
grafických tvarů a pozadí. Pomocí objektu TBrush určujeme barvu,
120
vzor nebo bitmapu, které budou použity při vyplňování mezer na
plátně.
TPen *Pen
Určuje druh pera použitého pro kreslení čar a tvarů. Pomocí
objektu TPen určujeme barvu, styl, šířku a druh pera.
TPoint PenPos Určuje aktuální pozici pera na plátně.
TRect ClipRect Určuje okraje ohraničujícího obdélníku.
Handle Čtyřbytový handle, který můžeme použít při volání funkcí Win32API
pro kreslení do našeho Canvasu.
TColor Pixels[int X][int Y] zjištění nebo změna barvy konkrétního pixelu
(nemusí pro všechna zařízení fungovat, pro velký počet pixelů je tento
postup dosti pomalý)
Důležité metody:
DrawPoint Vykreslí bod
DrawPoints Vykreslí řadu bodů
MoveTo Změna aktuální pozice ve výkresu na bod X, Y
LineTo Kreslení čáry na plátno z aktuální pozice na místo určené pomocí X,
Y a nastavení pozice pera na X, Y.
Polyline kreslí aktuálním perem řadu úseček, jejichž body jsou uvedeny v poli
Points
Polygon kreslí aktuálním perem řadu úseček, jejichž body jsou uvedeny
v poli Points, poslední bod spojí s prvním a takto vzniklou plochu vyplní
aktuálním štětcem.
FrameRect Nakreslí aktivním perem obrys obdélníka
Rectangle Vykreslí pomocí aktuálního pera na plátno zadaný obdélník a
vyplní ho pomocí aktuáního štětce.
RoundRect Vykreslení obdélníku se zaokrouhlenými rohy
FillRect Vyplní zadaný obdélník na plátně pomocí aktuálního štětce.
DrawFocusRect Kreslí obdélník označující objekt na který se máme zaměřit
CopyRect Kopie části obrazu z jednoho plátna na jiné plátno.
Ellipse Kreslí na plátno elipsu definovanou ohraničujícím obdélníkem. Okraj
je určen nastavením pera, vnitřek elipsy pak nastavením štětce.
Arc Kreslí na obrázek oblouk tvořený obvodem elipsy ohraničené zadaným
obdélníkem.
Chord Kreslí uzavřenou část elipsy zadanou obdélníkem a utnutou zadanou
úsečkou..
Pie Kreslí na plátno část koláče určeného elipsou.
TextOut Vypíše na zadanou pozici aktuálním fontem zadaný text
Funkce nevymaže pozadí pod textem. K jeho vymazání musíme
nejdříve použít funkci FillRect.
TextRect Vypíše do zadaného obdélníka na zadané souřadnice aktuálním
fontem zadaný text.
TextWidth Vrátí v pixelech šířku zadaného textu pro aktuální font.
TextHeight Vrátí v pixelech výšku zadaného textu pro aktuální font.
Draw Vykreslí grafický objekt určený parametrem Graphic na plátně v místě
daném souřadnicemi X, Y.
121
StretchDraw Nakreslí grafický objekt do zadaného obdélníka v canvasu.
Případně provede smrštění nebo roztažení obrazu tak, aby vyplnil
přesně zadaný obdélník.
21.2
Počítání souřadnic
Ve Windows se obvykle souřadnice počítají od levého horního rohu, který má
souřadnice (0,0).
Uvnitř každého okna se provádí přečíslování, takže se čísluje od (0,0) do
(ClientWidth-1,ClientHeight-1).
Pro přepočítání souřadnic obrazovky (Screen) na souřadnice okna (Client) a
opačně používáme funkce ScreenToClient a ClientToScreen.
Ve většině nahoře uvedených metod je potřeba často určovat souřadnice bodu
nebo souřadnice obdélníku. Proto byly vytvořeny dvě třídy: TPoint a TRect.
typedef struct
{
LONG x;
LONG y;
} POINT, *PPOINT;
// struktura definována ve Win32 API
// typedef long LONG;
struct TPoint : public POINT
{
TPoint() {}
TPoint(int _x,int _y) { x=_x; y=_y; }
TPoint(POINT &pt) { x = pt.x; y = pt.y; }
};
typedef struct
{
LONG
left;
LONG
top;
LONG
right;
LONG
bottom;
} RECT, *PRECT;
// struktura definována ve Win32 API
struct TRect : public RECT
{
TRect() {}
TRect(const TPoint& TL, const TPoint& BR)
{ left=TL.x; top=TL.y; right=BR.x; bottom=BR.y; }
TRect(int l, int t, int r, int b)
{ left=l; top=t; right=r; bottom=b; }
TRect(RECT& r)
{ left=r.left; top=r.top; right=r.right; bottom=r.bottom; }
int Width () const { return right-left; }
int Height() const { return bottom-top; }
bool operator ==(const TRect& rc) const
{ return left == rc.left && top==rc.top &&
right == rc.right && bottom==rc.bottom;
}
122
bool operator !=(const TRect& rc) const
{ return !(rc==*this); }
__property
__property
__property
__property
LONG
LONG
LONG
LONG
Left
Top
Right
Bottom
=
=
=
=
{
{
{
{
read=left, write=left };
read=top, write=top };
read=right, write=right };
read=bottom, write=bottom };
};
21.3
Překreslování
Okna se musí často překreslovat. Jedním z důvodů může být např. jejich
překrývání. Windows to řeší zasíláním zprávy WM_PAINT. Okno, které obdrží
zprávu WM_PAINT, se musí překreslit a obnovit tak část, která byla zničena
předchozí akcí.
Na základě přijmutí zprávy WM_PAINT je v knihovně VCL generována událost
OnPaint. Navíc je v datové položce Canvas->ClipRect specifikován obdélník,
který je nutno překreslit. Často není nutné překreslit celé okno, ale pouze jeho
výřez.
Nestačí tedy pouze kreslit metodami canvasu, ale je nutné také zajistit i
obnovování nakresleného obrazu při příjmu zprávy WM_PAINT.
Překreslování se ve Win32 API řeší pomocí funkcí InvalidateRect a
UpdateWindow. Tyto funkce umožňují překreslení nejen celého okna, ale i jen její
části.
V knihovně VCL jsou definovány podobné funkce Invalidate a Update. Nelze zde
ovšem určit překreslovanou oblast, tudíž jako invalidní se vždy označí celá plocha.
Navíc zde vždy dochází k jejímu vymazání (u funkcí Win32API si můžeme určit).
Ve VCL existují ještě dvě takřka shodné funkce Repaint a Refresh. Obě dělají
totéž, tedy volají nejdříve Invalidate() a pak Update(). Tím se zajistí okamžité
překreslení celé plochy okna.
21.4
TColor
Datový typ TColor je používán pro určování barvy objektu.
Deklarace TColor:
enum TColor {clMin=-0x7fffffff-1, clMax=0x7fffffff};
TColor tedy představuje čtyřbytové číslo se znaménkem - int (každá dvojice
hexadecimálních číslic představuje jeden byte). Význam jednotlivých bytů je
následující:
První byte zprava
Druhý byte zprava
Třetí byte zprava
Čtvrtý byte zprava
velikost červené složky 0x00 až 0xFF
velikost zelené složky
velikost modré složky
pro naše potřeby ponecháme na hodnotě 0x00
123
Hodnota 0xFF0000 představuje tedy sytou modrou, 0x00FF00 sytou zelenou,
0x0000FF sytou červenou, 0x000000 černou a 0xFFFFFF bílou. Měněním
jednotlivých složek v rozsahu od 0x00 do 0xFF pak měníme výslednou barvu a
jas.
Nejčastěji používané barvy mají přiděleny své názvy. Tyto začínají vždy písmeny
„cl“ a za nimi následuje název barvy. Máte tedy barvy clWhite, clBlack, clBlue,
clGreen, clRed, clYellow atd.
Kromě toho důležité barvy jsou ještě pojmenovány podle jejich použití v systému
Windows. Máte tedy např. tyto barvy: clBtnFace, clBtnHighlight, clBtnShadow,
clBackground, clActiveCaption atd.
Příklady použití TColor:
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
// deklarace ve Windows již existuje
// deklarace ve Windows již existuje
// deklarace ve Windows již existuje
// změna červené složky existující barvy
// jsou použity bitové operace!
TColor NastavCervenou(TColor c,BYTE hodnota)
{ return (TColor)((c&0xffffff00) | (hodnota)); }
TColor NastavZelenou(TColor c,BYTE hodnota)
{ return (TColor)((c&0xffff00ff) | (hodnota<<8)); }
TColor NastavModrou(TColor c,BYTE hodnota)
{ return (TColor)((c&0xff00ffff) | (hodnota<<16)); }
// vytvoření barvy zadáním složek
TColor NastavBarvu(BYTE r, BYTE g, BYTE b)
{ return (TColor)( b<<16 | g<<8 | r); }
// získání složek existující barvy
BYTE DejCervenou(TColor c) {return c&0x000000ff;}
BYTE DejZelenou(TColor c) {return (c&0x0000ff00)>>8;}
BYTE DejModrou(TColor c)
{return (c&0x00ff0000)>>16;}
21.5
TBitmap
Třída TBitmap představuje obdélník pixelů – bitovou mapu. Metodami třídy
provádíme operace nad touto bitovou mapou. Bitová mapa může být uložena jak
na obrazovce, tak třeba v paměti. Představuje základní grafický prvek.
Existují dvě deklarace třídy TBitmap. Jedna je deklarována ve windows.hpp pro
operace ve Win32 API a druhá v graphics.hpp pro bitmapu pod knihovnou VCL.
Při práci s TBitmap musíme tedy vždy použít operátor příslušnosti, abychom
určili, s kterým typem TBitmap chceme pracovat.
Při deklaraci objektu knihovny VCL píšeme:
Graphics::TBitmap *Bitmapa;
Objekty typu TBitmap můžeme používat pro úschovu bitových map v paměti pro
jejich další použití.
124
Důležité vlastnosti:
int Height výška bitmapy
int Width šířka bitmapy
TCanvas *Canvas pointer na TCanvas, který komponenta obsahuje
bool Transparent transparentní (průhledný) mód
TColor TransparentColor určení průhledné barvy. Používáme např. při
kreslení kulatých rohů, kdy kulatý roh vyplníme právě uvedenou
barvou.
TransparentMode je-li tmAuto, bere se automaticky jako transparentní barva
barva leveho spodního rohu. Je-li tmFixed, pak transparentní barvu
musíme určit sami.
Důležité metody:
LoadFromFile natažení obrázku ze souboru s příponou BMP.
Save to File uložení do souboru
SetSize nastavení rozměrů bitmapy, pokud plníme ze souboru, nastaví se
automaticky podle rozměrů načítaného obrázku
21.6
TPicture
Třída TPicture může v sobě obsahovat bitmapy (obrázky), icony nebo
uživatelem definovanou grafiku.
Důležité vlastnosti:
int Height výška
int Width šířka
TBitmap *Bitmap pointer na objekt třídy TBitmap, který je v TPicture uložen
Důležité metody:
LoadFromFile natažení obrázku ze souboru s příponou BMP.
Save to File uložení do souboru
21.7
TBrush
Třída zachycující vlastnosti štětce.
Důležité vlastnosti:
TColor Color Barva štětce.
TBitmap *Bitmapa Bitová mapa, která se použije jako tvar štětce.
HRUSH Handle Handle objektu brush (lze použít ve funkcích Win32 API)
TBrushStyle Style styl štětce (bsSolid, bsClear, bsHorizontal, bsVertical …)
125
21.8
TPen
Třída zachycující vlastnosti pera.
Důležité vlastnosti:
TColor Color barva pera
TPenStyle Style styl pera (psSolid, psClear, psDash, psDot …)
TPenMode Mode mód kreslení pera na Canvas
int Width šířka pera v pixelech
21.9
TPaintBox
Komponenta TPaintBox představuje plátno, které mohou využívat další
komponenty pro vykreslování obrazu.
Např. TPanel nemá přístupný Canvas. Proto si vypomůžeme tím, že do panelu
vložíme TPaintBox a kreslíme do jeho Canvasu.
Komponenta nezajišťuje překreslování! To si musíme ošetřit sami.
Důležité události:
OnPaint ošetření okamžiku, kdy se má komponenta překreslit
Příklad
void __fastcall TForm1::Button1Click(TObject *Sender)
{
PaintBox1->Canvas->Brush->Color = clRed;
// vymazáním starého obsahu PaintBox1
PaintBox1->Canvas->FillRect(PaintBox1->Canvas->ClipRect);
// nakreslení elipsy
PaintBox1->Canvas->Ellipse(0,0,100,100);
}
// ošetření události OnPain
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
// elipsu je nutno znovu překresli
PaintBox1->Canvas->Ellipse(0,0,100,100);
}
21.10 TImage
Komponenta TImage používáme k zobrazování obrázků. Pro zadání aktuální
bitové mapy použijeme objekt TPicture, který vlastní TImage. Vlastnosti a metody
TPicture lze použít pro takové věci, jako je načítání obrázku ze souboru, vymazání
obrazu v TImage a k přenosu obrázku do dalších komponent. TImage má několik
vlastností určujících, jak se má obraz zobrazit v rámci hranic TImage objektu.
Důležité vlastnosti
TCanvas *Canvas pointer na TCanvas, který komponenta obsahuje
126
TPicture *Picture obrázek, který se objeví na ploše komponenty.
bool Stretch hodnota true znamená, že se mají rozměry obrázku přepočítat
tak, aby obrázek zaplnil plochu komponenty
bool Transparent true znamená, že obrázek bude průsvitný. Průsvitná barva
je většinnou určena barvou levého spodního rohu.
TImage si na rozdíl od TPaintBox řídí překreslování sama. Při např. prohlížení
obrázků je to výhodné. Nemusíme se starat o překreslení obrazu při např. překrytí
a následném odkrytí našeho okna jiným oknem.
Na druhé straně to má nevýhodu, protože si již sami nemůžeme tak snadno
určovat kdy překreslení provést a v některých situacích pak zobrazení selže.
Otázky k zamyšlení
1) K čemu je třída TBitMap.
2) U které z komponent TPaintBox a TImage se musíme sami postarat o
překreslování?
3) Vytvořte aplikaci, ve které budete barvu pozadí formuláře měnit pomocí
zadávání velikosti jednotlivých základních barev.
127
22
Grafika II
Obsah hodiny
Vytvoříme jednoduché příklady pracující s grafikou.
Cíl hodiny
Po této hodině budete schopni:
●
Vytvořit jednoduchý prohlížeč obrázků.
Na jednoduchých příkladech si ukážeme základy práce s grafikou.
22.1
Přiklad 1.
Vytvořme aplikaci, ve které budeme na pozadí formuláře kreslit vybraný obrázek.
Budeme mít možnost volit, zda se má obrázek nakreslit jen jednou v původní
velikosti nebo se mají jeho rozměry změnit tak, aby plně vyplňoval klientskou
plochu formuláře, případně aby ji vyplnil kreslením malého obrázku vícekrát vedle
sebe tak, aby plocha byla opět celá zaplněna.
Budeme kreslit do Canvas klientské oblasti našeho formuláře. Ta se ale nestará o
překreslování, takže ho budeme muset zajistit sami.
Na plochu formuláře umístíme tyto komponenty: TButton, TComboBox
a TOpenDialog. V deklaraci naší třídy formuláře si deklarujeme ještě pointer na
TBitmap „Bitmapa“.
128
Připravíme si obsluhy těchto událostí: OnClick pro TButton, OnChange pro
TComboBox, OnPaint a OnResize pro náš formulář.
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
TOpenDialog *OpenDialog1;
TComboBox *ComboBox1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall ComboBox1Change(TObject *Sender);
void __fastcall FormPaint(TObject *Sender);
void __fastcall FormResize(TObject *Sender);
private:
// User declarations
Graphics::TBitmap *Bitmapa;
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
V obsluze události OnClick otevřeme dialog pro volbu souboru jenž budeme
zobrazovat. Příponu souboru, který budeme hledat, zvolíme pouze „bmp“.
S jiným typem grafického formátu neumí TCanvas pracovat.
TForm1 *Form1;
//--------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
Bitmapa=NULL;
}
//--------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(Bitmapa!=NULL) {
delete Bitmapa;
// odalokuji bitmapu starého obrázku
Bitmapa=NULL;
}
OpenDialog1->Filter="Bitmapovy soubor (*.bmp)|*.bmp";
if(OpenDialog1->Execute()) {
Bitmapa=new Graphics::TBitmap;
Bitmapa->LoadFromFile(OpenDialog1->FileName);
}
Repaint();
// vyvolam prekresleni formuláře
}
//--------------------------------------------------------------------// bitmapu různými způsoby zkopíruji do Canvas formuláře
void __fastcall TForm1::FormPaint(TObject *Sender)
{
if(Bitmapa!=NULL) {
switch(ComboBox1->ItemIndex) {
case 0: Canvas->Draw(0,0,Bitmapa);
// kreslím jen jednou
break;
case 1: Canvas->Brush->Bitmap=Bitmapa;
// kreslim jako dlazdice
// vyplnim celou plochu vzorem stetce
Canvas->FillRect(TRect(0,0,ClientWidth,ClientHeight));
break;
// jiná možnost, opravdu kreslím jednotlivé obrázky vedle sebe
129
//
//
//
//
case 1: for(int y=0;y<ClientHeight;y+=Bitmapa->Height
for(int x=0;x<ClientWidth;x+=Bitmapa->Width)
Canvas->Draw(x,y,Bitmapa);
break;
case 2: Canvas->StretchDraw(TRect(0,0, // jeden obrázek přes celé
ClientWidth,ClientHeight),Bitmapa);
break;
}
}
}
//--------------------------------------------------------------------// při změně stylu kreslení musím překreslit
void __fastcall TForm1::ComboBox1Change(TObject *Sender)
{
Repaint();
// vyvolám překreslení - FormPaint
}
//--------------------------------------------------------------------// při změně rozměrů formuláře musím překreslit
void __fastcall TForm1::FormResize(TObject *Sender)
{
Repaint();
// vyvolám překreslení
}
22.2
Příklad 2.
Na plochu formuláře umístěte dvě komponenty TPanel. Do Canvas levého panelu
budeme kreslit obrázek (jako dlaždice nebo se změněnými rozměry dle velikosti
panelu), pravému budeme barvu měnit podle barvy levého horního rohu obrázku.
Způsob zobrazení obrázku volíme pomocí TCheckBox.
Protože TPanel nemá přístupnou vlastnost TCanvas, umístíme do obou
panelů komponenty TPaintBox, které již Canvas mají přístupný, nastavíme
jim Aligen=alClient a kreslíme do nich. U TPaintBox se ovšem musíme
postarat o překreslování!
Deklarace třídy našeho formuláře bude vypadat takto:
class TForm1 : public TForm
{
130
__published: // IDE-managed Components
TOpenDialog *OpenDialog1;
TPanel *Panel1;
TPanel *Panel2;
TButton *Button1;
TPaintBox *PaintBox1;
TPaintBox *PaintBox2;
TCheckBox *CheckBox1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall PaintBox1Paint(TObject *Sender);
void __fastcall PaintBox2Paint(TObject *Sender);
void __fastcall CheckBox1Click(TObject *Sender);
private:
// User declarations
Graphics::TBitmap *Bitmapa;
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
Unit1.cpp:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
Bitmapa=NULL;
}
//--------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(Bitmapa!=NULL) {
delete Bitmapa;
Bitmapa=NULL;
}
OpenDialog1->Filter="Bitmapový soubor (*.bmp)|*.bmp";
if(OpenDialog1->Execute()) {
Bitmapa=new Graphics::TBitmap;
Bitmapa->LoadFromFile(OpenDialog1->FileName);
Bitmapa->Dormant();
// uvolním nepotřebné zdroje
Bitmapa->FreeImage();
// obrázek už nepotřebuji, uvolním ho
}
PaintBox2->Repaint();
// vyvolám překresleni PaintBoxu
PaintBox1->Repaint();
}
//--------------------------------------------------------------------// překreslení TPaintBox pravého panelu
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
// nastavím barvu štětce Canvasu pravého panelu
// podle barvy levého horního rohu levého panelu
PaintBox1->Canvas->Brush->Color=PaintBox2->Canvas->Pixels[0][0];
// vyplním plochu pravého panelu
PaintBox1->Canvas->FillRect(TRect(0,0,
PaintBox1->Width,PaintBox1->Height));
CheckBox1->Color=PaintBox1->Canvas->Brush->Color;
}
//--------------------------------------------------------------------// překreslení TPaintBox levého panelu
void __fastcall TForm1::PaintBox2Paint(TObject *Sender)
{
if(CheckBox1->Checked) {
// změnit rozměry obrázku
131
PaintBox2->Canvas->StretchDraw(TRect(0,0,PaintBox2->Width,
PaintBox2->Height),Bitmapa);
}
else {
// kreslit obrázek jako dlaždice
PaintBox2->Canvas->Brush->Bitmap=Bitmapa;
PaintBox2->Canvas->FillRect(TRect(0,0,PaintBox2->Width,
PaintBox2->Height));
}
}
//--------------------------------------------------------------------void __fastcall TForm1::CheckBox1Click(TObject *Sender)
{
// při změně způsobu kreslení musím překreslit
PaintBox2->Repaint();
}
Kontrolní otázky a úkoly
1) Dodělejte příklady uvedené v kapitole
2) Prostudujte v nápovědě jak pracuje metoda FillRect třídy Canvas a vlastnost
Style třídy TBrush a příklady vhodně předělejte pro šrafování místo obrázků.
132
23
Příklady práce s grafikou I
Obsah hodiny
Budeme cvičit složitější příklady pro práci s grafikou.
Cíl hodiny
Po této hodině budete schopni:
●
23.1
vytvořit program, ve kterém budeme opravdu kreslit, ne jen zobrazovat
Příklad 1.
Vytvořme program, který bude umět zobrazovat obrázky typu bmp.
Použijme nyní TImage, takže o překreslování se nemusíme starat.
Část zdrojového kódu programu:
void __fastcall TForm1::bNacistObrazekClick(TObject *Sender)
{
OpenPictureDialog1->Filter="Bitmapové soubory (*.bmp)|*.bmp";
if(OpenPictureDialog1->Execute()) {
Image1->Picture->LoadFromFile(OpenPictureDialog1->FileName);
}
}
//---------------------------------------------------------------------void __fastcall TForm1::cbStretchClick(TObject *Sender)
{
Image1->Stretch=cbStretch->Checked;
}
//---------------------------------------------------------------------void __fastcall TForm1::cbTransparentClick(TObject *Sender)
{
Image1->Transparent=cbTransparent->Checked;
}
133
//---------------------------------------------------------------------void __fastcall TForm1::cbCenterClick(TObject *Sender)
{
Image1->Center=cbCenter->Checked;
}
Následující příklady jsou založeny na existenci několika základních metod
třídy TCanvas. Popišme si je tedy blíže.
void __fastcall FillRect(const TRect &Rect);
Vyplní zadaný obdélník Canvas pomocí momentálního štětce. Oblast je
vyplněna včetně horního a levého okraje obdélníku, ale bez dolního a
pravého okraje.
Čím bude obdélník vyplněn, záleží na našem nastavení štětce. Uplatní se
zde nastavení vlastností Style, Color a Bitmap.
Pokud se Bitmap nerovná NULL, tedy obsahuje adresu nějaké bitmapy, pak
se pro vyplnění obsahu obdélníka použije tato bitmapa. Pokud je menší
než obdélník, bitmapa se dlaždicovitě opakuje.
Graphics::TBitmap *BrushBmp = new Graphics::TBitmap;
try
{
BrushBmp->LoadFromFile("Obrazek.bmp");
Form1->Canvas->Brush->Bitmap = BrushBmp;
Form1->Canvas->FillRect(Rect(0,0,100,100));
}
__finally
{
Form1->Canvas->Brush->Bitmap = NULL;
delete BrushBmp;
}
Pokud je Bitmap rovno NULL, je obdélník naplněn zvoleným vzorkem dle
Style v barvě zadané v Color.
Příklady nejčastějších Style jsou tyto:
bsSolid
bsClear
bsCross
bsDiagCross
bsHorizontal
bsVertical
bsBDiagonal
bsFDiagonal
obdélník je celý vyplněn danou barvou (default)
prázdný obdélník
svislá a vodorovná mřížka
mřížka pod úhlem 45 stupňů
vodorovné čáry
svislé čáry
šikmé šrafování zdola nahoru
šikmé šrafování shora dolů
134
Další styly naleznete v nápovědě. Zde můžete také nalézt tuto poznámku:
Nastavení Style na bsClear omezuje blikání při překreslování objektu.
void __fastcall CopyRect(const TRect &Dest,
TCanvas* Canvas, const TRect &Source);
Zkopírování části bitmapy z Canvas zadaném jako parametr do našeho
Canvas. Nejčastěji jsou obdélníky Dest a Source stejně velké, ale nemusí.
Jsou-li různé, dojde k úpravě bitmapy tak, aby vyplnila obdélník Dest.
Tato metoda je v grafických aplikacích velmi často používána. Představuje
rychlý přenos ze zdroje do cíle.
23.2
Příklad 2.
Příklad na použití metod Canvas MoveTo a LineTo.
Vytvořme program, který bude zobrazovat analogové hodiny.
Budeme kreslit do Canvas klientské oblasti formuláře. Budeme muset
tedy ošetřit i překreslování!
class TForm1 : public TForm
{
__published:
// IDE-managed Components
TTimer *Timer1;
void __fastcall Timer1Timer(TObject *Sender);
void __fastcall FormResize(TObject *Sender);
void __fastcall FormPaint(TObject *Sender);
private: // User declarations
TRect Ohraniceni;
int Polomer;
TPoint Stred;
TDateTime MinulyCas;
void __fastcall KresliBod(TPoint stred, int velikost);
void __fastcall KresliRucicky(TDateTime cas,TColor barva);
135
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
MinulyCas=Time();
}
//---------------------------------------------------------------------void __fastcall TForm1::FormPaint(TObject *Sender)
{
Canvas->Brush->Style=bsSolid;
Canvas->Brush->Color=clWhite;
Canvas->FillRect(TRect(0,0,ClientWidth,ClientHeight));
int velikost=ClientHeight;
if(ClientWidth<velikost)
velikost=ClientWidth;
Ohraniceni=TRect(velikost/10,velikost/10,
velikost-velikost/10,velikost-velikost/10);
Polomer=Ohraniceni.Width()/2;
Stred=TPoint(Ohraniceni.Left+Ohraniceni.Width()/2,
Ohraniceni.Top+Ohraniceni.Height()/2);
Canvas->Pen->Color=clBlack;
Canvas->Pen->Style=psSolid;
Canvas->Pen->Width=5;
Canvas->Ellipse(Ohraniceni);
// nakreslím ohraničující kruh
// nakreslím kroužky za pětiminuty
for(float uhel=90;uhel!=-270;uhel-=30){
int x=Stred.x + (Polomer-10)*cos(uhel*M_PI/180);
int y=Stred.y + (Polomer-10)*sin(uhel*M_PI/180);
KresliBod(TPoint(x,y),5);
}
Timer1->Interval=20;
// urychlím překreslování ručiček
}
//---------------------------------------------------------------------void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Timer1->Interval=1000;
// vratim interval timeru na 1 sec
KresliRucicky(MinulyCas,clWhite);
TDateTime cas=Time();
KresliRucicky(cas,clRed);
MinulyCas=cas;
// vymažu staré ručičky
// nakreslím nové ručičky
}
//---------------------------------------------------------------------void __fastcall TForm1::FormResize(TObject *Sender)
{
Repaint();
}
//---------------------------------------------------------------------void __fastcall TForm1::KresliBod(TPoint stred, int velikost)
{
Canvas->Ellipse(TRect(stred.x-velikost/2,stred.y-velikost/2,
stred.x+velikost/2,stred.y+velikost/2));
136
}
void __fastcall TForm1::KresliRucicky(TDateTime cas,TColor barva)
{
unsigned short hod,min,sec,msec;
cas.DecodeTime(&hod,&min,&sec,&msec);
if(hod>12)
hod-=12;
}
Dodělejte funkci „KresliRucicky“. Pro výpočet koncového bodu ručiček
použijte goniometrické funkce podobně jako ve funkci FormPaint pro
kreslení kroužků pětiminut.
23.3
Příklad 3.
Příklad na použití metod Canvas MoveTo a LineTo.
Vytvořme program, ve kterém budeme moci pomocí myši kreslit do
klientské oblasti formuláře. Musíme tedy sami zajistit překreslování.
Tlačítkem „Zpět“ budeme mít možnost zrušit poslední krok.
Pro to, aby program fungoval správně, musíme v programu použít dvě
pomocné bitové mapy.
Jednu budeme používat při překreslování. Během kreslení budeme
kreslit čáru od minulé polohy myší do nové. Po puštění tlačítka
zkopírujeme obsah Canvas klientské plochy do bitmapy. Při
překreslování pak obsah této mapy kopírujeme zpět do Canvas.
Druhou bitmapu budeme používat pro zapamatování stavu po
ukončení kreslení v minulém kroku. Zkopírujeme do ní vždy obsah
první bitmapy. Toto nám bude sloužit pro návrat o krok zpět.
Nastavení TPen a TBrush proveďte v ObjectInspektoru.
class TForm2 : public TForm
{
__published: // IDE-managed Components
137
TButton *Button1;
void __fastcall FormShow(TObject *Sender);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall FormPaint(TObject *Sender);
void __fastcall FormCanResize(TObject *Sender, int &NewWidth,
int &NewHeight, bool &Resize);
void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y);
void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall Button1Click(TObject *Sender);
private:
// User declarations
Graphics::TBitmap *bitmapa;
Graphics::TBitmap *bitmapaKopie;
bool Kresleni;
public:
// User declarations
__fastcall TForm2(TComponent* Owner);
};
__fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner)
{
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormShow(TObject *Sender)
{
Kresleni=false;
bitmapa=new Graphics::TBitmap;
// založím si pracovní bitmapu
bitmapa->SetSize(800,600);
// nastavím maximální rozměry obrázku
bitmapa->Canvas->Brush->Color=Color; // vezmu barvu stětce z barvy okna
// vyplním bitmapu barvou okna
bitmapa->Canvas->FillRect(TRect(0,0,bitmapa->Width,bitmapa->Height));
bitmapaKopie=new Graphics::TBitmap;
// založím si bitmapu pro kopii
// nastavím rozměry kopie
bitmapaKopie->SetSize(bitmapa->Width,bitmapa->Height);
bitmapaKopie->Canvas->Brush->Color=Color;
// vyplním bitmapu kopie barvou okna
bitmapaKopie->Canvas->FillRect(TRect(0,0,bitmapa->Width,
bitmapa->Height));
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Kresleni=true;
// začínáme kreslit
Canvas->Pen->Color=clRed;
Canvas->Pen->Width=5;
Canvas->Pen->Style=psSolid;
Canvas->MoveTo(X,Y);
// přesunu polohu bodu v Canvasu na polohu mysi
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormMouseMove(TObject *Sender,
TShiftState Shift, int X, int Y)
138
{
if(Kresleni)
Canvas->LineTo(X,Y);
// kreslím čáru z minulé polohy na novou polohu
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
// vytvořím kopii záložní bitmapy
bitmapaKopie->Canvas->CopyRect(TRect(0,0,bitmapa->Width,
bitmapa->Height),bitmapa->Canvas,
TRect(0,0,bitmapa->Width,
bitmapa->Height));
// uložím obraz plochy okna pro budoucí překreslování
bitmapa->Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
Canvas,TRect(0,0,ClientWidth,ClientHeight));
Kresleni=false;
// kreslení končí
}
//-----------------------------------------------------------------------void __fastcall TForm2::Button1Click(TObject *Sender)
{
// do bitmapy natáhnu obraz posledního stavu před tímto krokem
bitmapa->Canvas->CopyRect(TRect(0,0,bitmapa->Width,
bitmapa->Height),
bitmapaKopie->Canvas,
TRect(0,0,bitmapa->Width,bitmapa->Height));
// kpírují obsah bitmapy do klientské oblasti
Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
bitmapa->Canvas,
TRect(0,0,ClientWidth,ClientHeight));
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormPaint(TObject *Sender)
{
// oživím obraz plochy okna ze zálohy
Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
bitmapa->Canvas,
TRect(0,0,ClientWidth,ClientHeight));
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormCanResize(TObject *Sender, int &NewWidth,
int &NewHeight, bool &Resize)
{
NewWidth = (NewWidth>bitmapa->Width) ? bitmapa->Width : NewWidth;
NewHeight = (NewHeight>bitmapa->Height) ? bitmapa->Height : NewHeight;
Resize=true;
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormClose(TObject *Sender, TCloseAction &Action)
{
// uvolním pomocné bitmapy z paměti
delete bitmapa;
delete bitmapaKopie;
}
139
23.4
Příklad 4.
Příklad na použití metod Canvas MoveTo a LineTo.
Vytvořme program, ve kterém budeme moci opět pomocí myši kreslit
do klientské oblasti formuláře. Tentokrát ale přímky.
Pomocnou bitmapu budeme nyní používat už při ukládání přímky. Při
přesunu myši obnovím obrázek z pomocné bitmapy, tím se stará
přímka ztratí a vytvoří se nová.
class TForm2 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall FormPaint(TObject *Sender);
void __fastcall FormShow(TObject *Sender);
void __fastcall FormCanResize(TObject *Sender, int &NewWidth,
int &NewHeight, bool &Resize);
void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y);
void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall Button1Click(TObject *Sender);
private:
// User declarations
Graphics::TBitmap *bitmapa;
Graphics::TBitmap *bitmapaKopie;
bool Kresleni;
int XZac, YZac;
// pro zapamatování souřadnic v okamžiku OnMouseDown
public:
// User declarations
__fastcall TForm2(TComponent* Owner);
};
__fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner)
{
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormShow(TObject *Sender)
140
{
Kresleni=false;
bitmapa=new Graphics::TBitmap;
// založím si pracovní bitmapu
bitmapa->SetSize(800,600);
// nastavím maximální rozměry obrázku
bitmapa->Canvas->Brush->Color=Color; // vezmu barvu štětce z barvy okna
// vyplním bitmapu barvou okna
bitmapa->Canvas->FillRect(TRect(0,0,bitmapa->Width,bitmapa->Height));
bitmapaKopie=new Graphics::TBitmap;
// založím si bitmapu pro kopii
// nastavím rozměry kopie
bitmapaKopie->SetSize(bitmapa->Width,bitmapa->Height);
bitmapaKopie->Canvas->Brush->Color=Color;
bitmapaKopie->Canvas->FillRect(TRect(0,0,
bitmapa->Width,bitmapa->Height));
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Kresleni=true;
// začínáme kreslit
Canvas->Pen->Color=clRed;
Canvas->Pen->Width=5;
Canvas->Pen->Style=psSolid;
Canvas->MoveTo(X,Y);
// přesunu polohu bodu v Canvasu na polohu myši
XZac=X;
// zapamatuji si počáteční bod
YZac=Y;
bitmapa->Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
Canvas,TRect(0,0,ClientWidth,ClientHeight));
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y)
{
if(Kresleni) {
// uvedu plochu do stavu z okamžiku FormMouseDown
Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
bitmapa->Canvas,TRect(0,0,ClientWidth,ClientHeight));
Canvas->MoveTo(XZac,YZac); // přesunu se zpět na začátek
Canvas->LineTo(X,Y);
// kreslím čáru ze začátku na novou polohu
}
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
// vytvořím kopii záložní bitmapy
bitmapaKopie->Canvas->CopyRect(TRect(0,0,bitmapa->Width,
bitmapa->Height),bitmapa->Canvas,
TRect(0,0,bitmapa->Width,bitmapa->Height));
// uložím obraz plochy okna pro budoucí překreslování
bitmapa->Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
Canvas,TRect(0,0,ClientWidth,ClientHeight));
Kresleni=false;
}
//-----------------------------------------------------------------------void __fastcall TForm2::Button1Click(TObject *Sender)
{
141
bitmapa->Canvas->CopyRect(TRect(0,0,bitmapa->Width,bitmapa->Height),
bitmapaKopie->Canvas,
TRect(0,0,bitmapa->Width,bitmapa->Height));
Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
bitmapa->Canvas,TRect(0,0,ClientWidth,ClientHeight));
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormPaint(TObject *Sender)
{
// oživím obraz plochy okna ze zálohy
Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),bitmapa->Canvas,
TRect(0,0,ClientWidth,ClientHeight));
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormCanResize(TObject *Sender,
int &NewWidth, int &NewHeight, bool &Resize)
{
NewWidth = (NewWidth>bitmapa->Width) ? bitmapa->Width : NewWidth;
NewHeight = (NewHeight>bitmapa->Height) ? bitmapa->Height : NewHeight;
Resize=true;
}
//-----------------------------------------------------------------------void __fastcall TForm2::FormClose(TObject *Sender, TCloseAction &Action)
{
delete bitmapa;
// uvolním pomocnou bitmapu z paměti
}
Porovnejte tento příklad s příkladem 3. Soustřeďte se na používání
pomocné bitmapy.
23.5
Příklad 5.
Příklad na použití metod Canvas MoveTo a Rectangle.
Navrhněte program, pomocí něhož budete moci kreslit šrafované
obdélníky.
142
Logika programu je stejná jako u přímek, pouze místo funkce LineTo
použijte funkci Rectangle. Rozměry obdélníka odvodíme od souřadnic
bodu začátku kreslení a od momentální polohy myši.
23.6
Příklad 6.
Příklad na použití metod Canvas MoveTo a Ellipse.
Obdoba předcházejícího programu. Tentokrát kreslíme elipsy pomocí
funkce Ellipse.
23.7
Příklad 7.
Příklad na použití metod Canvas MoveTo a Ellipse.
Vyzkoušíme funkci Ellipse, ale oba rozměry dáme stejné.
23.8
Příklad 8.
Příklad na použití metod Canvas MoveTo a TextOut.
143
Kreslíme pomocí funkce TextOut. V místě stisku tlačítka myši
zobrazíme text, který můžeme se stále stisknutým tlačítkem
přesouvat. Logika používání pomocných bitmap je stále stejná.
23.9
Příklad 9.
Opět příklad na použití metod Canvas MoveTo a LineTo. Tentokrát si
ovšem budeme sami pomocí funkce WIN32 API definovat styl naší čáry.
Jestliže bychom použili místo standardní vlastnosti TPen Style=psSolid jiný
styl, čára musí mít tloušťku pouze 1!
void __fastcall TForm2::FormMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Kresleni=true;
// začínáme kreslit
int pocetSekci = 4;
// délka první čáry, délka první mezery,
// délka druhé čáry, délka druhé mezery - cyklicky se opakuje
unsigned long delkyCarek[4]={30, 30, 10, 10};
unsigned long tloustka=8;
TLogBrush logBrush;
// pokud chci určovat délky sám, musím dát BS_SOLID
logBrush.lbStyle=BS_SOLID;
144
logBrush.lbColor=RGB(255,100,50);
// pomoci RGB si namíchám barvu
// volani API funkce, která mi předá Handle vytvořeného pera,
// viz. help
// |- binární logický součet
Canvas->Pen->Handle=ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE,
tloustka, &logBrush, pocetSekci, delkyCarek);
Canvas->MoveTo(X,Y);
// přesunu polohu bodu v Canvasu na polohu myši
XZac=X;
// zapamtuji si počáteční bod
YZac=Y;
bitmapa->Canvas->CopyRect(TRect(0,0,ClientWidth,ClientHeight),
Canvas,TRect(0,0,ClientWidth,ClientHeight));
}
23.10
Příklad 10.
Příklad na použití metod Canvas MoveTo a Ellipse. Tentokrát ovšem
změníme vlastnost TPen TBitmas a uložíme do ní adresu naší bitmapy.
V tomto příkladu si štětec vytvoříme z vlastního obrázku bmp.
Obrázek načteme v konstruktoru do pomocné bitmapy „vzorek“ ze souboru
a v okamžiku stisku myši nastavíme štětec takto:
Canvas->Brush->Bitmap=vzorek;
Vše ostatní je jako v minulém příkladu.
145
24
Souhrný příklad - bitmapový editor
Obsah hodiny
V této kapitole sloučíme všechny naše pokusy s grafikou do jednoho programu.
Kontrolní otázky a úkoly
Všechno to, co jsme vyzkoušeli v kapitole 23, nyní pospojujte do
jednoduchého bitmapového editoru.
Výsledný obrázek při ukončování programu uložte do zadaného souboru
typu bmp.
146
25
Příklady práce s grafikou II
Obsah hodiny
Naučíme se vytvářet programy zobrazující grafy.
Cíl hodiny
Po této hodině budete schopni:
●
25.1
vytvářet programy zobrazující jednoduché grafy
Kreslení čárového grafu
Při návrhu programu musíme vycházet z toho, že vždy můžeme zobrazit
pouze určité „okno“ z celého grafu. Řekněme, že toto „okno“ bude dáno
hodnotami MinX, MaxX, MinY, MaxY. Tyto hodnoty jsou uvedeny
v jednotkách vodorovné a svislé osy. Toto „okno“ grafu pak zobrazujeme na
obrazovce v obdélníku, který má své rozměry dány hodnotami Sirka, Vyska.
Tyto jsou uvedeny v pixelech.
Nejdříve si musíme určit jakou hodnotu představuje jeden pixel ve
vodorovném a svislém směru:
147
float hodnotaPixeluX=(MaxX-MinX)/Sirka;
float hodnotaPixeluY=(MaxY-MinY)/Vyska;
Při kreslení každého bodu nejdříve určíme jeho vzdálenost od horního a
levého okraje „okna“ grafu v jednotkách obou os. Bereme hodnotu od
horního okraje, protože takto budeme pracovat i s pixely. Pixel se
souřadnicemi 0,0 se nachází v levém horním rohu.
xVOkne=x-MinX;
yVOkne=MaxY-y;
// v tomto pořadí dostaneme kladné číslo
Vzdálenosti v „okně“ přepočítáme na pixely.
pixelyX=(x-MinX)/hodnotaPixeluX;
pixelyY=(MaxY-y)/hodnotaPixeluY;
Přesunu pero na vypočítané souřadnice
Canvas->MoveTo((x-MinX)/hodnotaPixeluX,(MaxY-y)/hodnotaPixeluY);
Vykreslení jedné čárky z minulé pozice na novou
y=sin(x*M_PI/180);
// funkce sin pracuje s radiány!
xVOkne=x-MinX;
yVOkne=MinY-y;
pixelyX=(x-MinX)/hodnotaPixeluX;
pixelyY=(MinY-y)/hodnotaPixeluY;
Canvas->LineTo((x-MinX)/hodnotaPixeluX,(MaxY-y)/hodnotaPixeluY);
Pro vykreslování si můžeme vytvořit samostatnou funkci zobrazující
požadovaný čárový graf:
void __fastcall KresliSin(TCanvas *Can,int Sirka, int Vyska,
float MinX, float MaxX, float Krok,
float MinY, float MaxY);
Parametr „Krok“ představuje velikost přírůstku na vodorovné ose.
148
Příklad
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
PaintBox1->Color=clWhite;
PaintBox1->Canvas->Pen->Color=clBlack;
KeyPreview=true;
}
//-----------------------------------------------------------------------void __fastcall TForm1::FormShow(TObject *Sender)
{
LabeledEditExit(NULL);
}
//-----------------------------------------------------------------------void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
TCanvas *can=PaintBox1->Canvas;
// smažu starý obrázek
can->FillRect(TRect(0,0,PaintBox1->Width,PaintBox1->Height));
// kreslení osy Y
can->Pen->Color=clBlack;
can->MoveTo((0-Vlevo)/HodnotaPixeluX,0);
can->LineTo((0-Vlevo)/HodnotaPixeluX,PaintBox1->Height);
// kreslení osy X
can->MoveTo(0,(Nahore-0)/HodnotaPixeluY);
can->LineTo(PaintBox1->Width,(Nahore-0)/HodnotaPixeluY);
Kresli(can,PaintBox1->Width,PaintBox1->Height,
Vlevo, Vpravo, (Vpravo-Vlevo)/200,Dole,Nahore);
}
//-----------------------------------------------------------------------void __fastcall TForm1::LabeledEditExit(TObject *Sender)
{
149
float vlevo,vpravo,nahore,dole;
TLabeledEdit *le;
try
{
le=leVlevo;
Vlevo=StrToFloat(le->Text);
le=leVpravo;
Vpravo=StrToFloat(le->Text);
le=leNahore;
Nahore=StrToFloat(le->Text);
le=leDole;
Dole=StrToFloat(le->Text);
}
catch (...)
{
ShowMessage("Chyba");
le->SetFocus();
return;
}
HodnotaPixeluX=(Vpravo-Vlevo)/PaintBox1->Width;
HodnotaPixeluY=(Nahore-Dole)/PaintBox1->Height;
PaintBox1->Repaint();
// překreslím
}
//-----------------------------------------------------------------------void __fastcall TForm1::EditKeyPress(TObject *Sender, char &Key)
{
if(Key==VK_RETURN)
// přechod na následující ovládací prvek
this->SelectNext((TLabeledEdit*)Sender,true,true);
}
150
25.2
Kreslení sloupcového grafu
sirkaSloupce=(float)Image1->Width/PocetHodnot;
hodnotaPixeluY=(MaxY-MinY)/Image1->Height;
yOsyXVOkne=0-MinY;
pixelyYOsyX=Image1->Height-yOsyXVOkne/hodnotaPixeluY;
Jeden obdélník:
zacSloupce=n*sirkaSloupce;
yVOkne=y-MinY;
pixelyY=Image1->Height-yVOkne/hodnotaPixeluY;
Canvas->FillRect(Rect(zacSloupce,pixelyY,
zacSloupce+sirkaSloupce,pixelyYOsyX));
151
Kontrolní otázky a úkoly
Zadání 1.
Vytvoříme program pro zobrazení průběhu goniometrických funkcí. Kresleme do
Canvas komponenty TPaintBox. Budeme se tudíž muset postarat o
překreslování při překrytí nebo změně rozměru okna. Funkci pro kreslení budeme
tedy volat uvnitř obsuhy události OnPaint našeho TPaintBox.
152
Zadání 2.
Nakreslete sloupcový graf z pěti zadaných celočíselných hodnot.
Ošetřete také změnu rozměrů okna a tedy i komponenty TImage. Změňte
nejdříve rozměry bitmapy, pak ji vyplňte bílou barvou a pak překreslete graf.
U komponenty TImage se nemusíme starat o překreslování (OnPaint) po překrytí
okna jiným oknem. Komponenta se postará o překreslení již sama.
153
26
Základy tisků
Obsah hodiny
Naučíme se v našich programech tisknout.
Cíl hodiny
Po této hodině budete schopni:
●
Vytvářet jednoduché tiskové sestavy
Klíčová slova
TPrinter, PrinterSetupDialog, PrintDialog.
Při vytváření tiskových sestav a dokumentů použijeme znalosti, které jsme
získali při pokusech s grafikou. V knihovně VCL existuje třída TPrinter,
která slouží pro ovládání tiskárny. Instance této třídy je pro nás automaticky
založena při spuštění programu. Adresu této instance získáme jako
návratovou hodnotu funkce Printer(). Třída TPrinter obsahuje vlastnost
Canvas, takže práce při tiscích se vlastně převádí na kreslení do tohoto
Canvasu.
26.1
Třída TPrinter
Důležité vlastnosti:
Canvas malířské plátno, do kterého se kreslí dokument
PageHeight výška stránky v pixelech
PageWidth šířka stránky v pixelech
Title Titulek, který se zobrazuje v tiskové frontě při tisku
PageNumber pořadové číslo právě tisknuté stránky
Orientation orientace dokumentu (poPortrait, poLandscape)
Copies počet kopií, které se mají vytisknout
Fonts seznam názvů fontů, které jsou pro vybranou tiskárnu dostupné
Printers seznam názvů tiskáren instalovaných ve Windows
PrinterIndex index vybrané tiskárny (standardní tiskárna má index 1)
Printing indikuje zda právě probíhá tisk (true) nebo už byl dokončen
Aborted jestliže byl přerušen tisk pomocí metody Abort() má hodnotu true
Handle handle na objekt tiskárny
154
Důležité metody:
void BeginDoc() založí tiskovou úlohu
void Abort() zrušení tisku, zbývající část dokumentu se vyjme z tiskové fronty
void NewPage() přechod na novou stránku, PageNumber se zvýší o 1, pozice
pera se nastaví na souřadnice 0,0
void EndDoc() ukončí dokument, začne se tisknout
26.2
TPrinterSetupDialog
Dialog pro výběr aktivní tiskárny a parametrů tisku. S tímto dialogem
pracujeme stejně jako s jinými standardními dialogy.
26.3
TPrintDialog
Zobrazuje dialogové okno pro nastavení parametrů tisku.
155
26.4
Postup při sestavování tiskové sestavy
Při programování tiskové sestavy začínáme vždy zavoláním funkce
BeginDoc.
Následně si pomocí vlastností PageWidth a PageHeight zjistíme rozměry
stránky v pixelech. Tyto rozměry mohou jít např. u laserových tiskáren do
několika tisíc. A každá tiskárna je může mít jiné. Proto nemá smysl udávat
např. šířky pera přímo v pixelech, ale nejraději počet pixelů odvozujeme ze
šířky stránky (např. 1 setina).
Podobně určujeme i např. okraje tisku. Také je bereme jako zlomek
celkových rozměrů stránky a zapamatujeme si je ve vhodných proměnných.
Tiskneme-li text na více řádků, pak si založíme ukazovátko, které bude
ukazovat na horní okraj následujícího řádku. Toto ukazovátko pak při
přechodu na další řádek zvyšujeme o velikost písma. Tento údaj pro
vybraný font a jeho vlastnosti zjistíme pomocí funkce Canvas->TextHeight,
které zadáme zkušební text obsahující písmena se spodním ocáskem (j,y)
a velká písmena s háčky a čárkami. Tedy např. text „ČÁjy“.
Potřebujeme-li usadit text např. na střed nebo vpravo, s výhodou použijeme
funkci Canvas->TextRect, které zadáme obdélník, do kterého má text
vepsat. Tato funkce může mít jako poslední parametr také určení zarovnání
jak horizontálního, tak vertikálního. Flagy pro horizontální a vertikální
zarovnání můžeme sčítat (např. AlignLeft+AlignVCenter).
Po každém řádku zkontrolujeme, zda už ukazovátko polohy příštího řádku
neukazuje za spodní okraj, který jsme si zvolili. Pokud ano, pak pomocí
funkce NewPage odstránkujeme a ukazovátko řádku nastavíme na horní
okraj následující stránky.
Na stránce nemusíme postupovat při tisku důsledně shora dolů, ale
můžeme se např. i vracet. Lze např. vytisknout text na celé stránce a pak
kolem něho nakreslit rámeček nebo dokonce něco nakreslit přes text.
Hlavně nezapomeňme všechno počítat ve zlomcích rozměrů stránky,
ne přímo v pixelech! Pokud bychom pracovali přímo v pixelech, pak
bychom pro každou tiskárnu mohli získat jiný vzhled stránky.
Celá navržená tisková sestava se začne tisknout teprve tehdy, až zavoláme
funkci EndDoc.
156
26.5
Ladění tiskových sestav.
Při návrhu tiskové sestavy musíme často zkoušet, zda to, co jsme
naprogramovali, pracuje podle našich představ. Používat k tomu reálnou
tiskárnu představuje velké zdržení a velké mrhání papírem a tonerem.
Výhodnější je proto tiskové sestavy ladit na některé fiktivní tiskárně a
tisknout např. do PDF souboru. K tomu můžeme s výhodou použít např.
freeware software PDFCreator. Teprve, když máme pocit, že vše funguje
tak, jak má, vyzkoušíme tisk na reálné tiskárně.
Stále musíme ale mít na mysli, že uživatel, kterému náš program předáme,
může mít tiskárnu o zcela jiných parametrech než je naše. Proto při návrhu
sestavy nikdy nepracujte přímo v pixelech, ale potřebné pixely si odvoďte
jako zlomek rozměrů stránky!
Příklad
Vytvořme program, který bude do komponenty TPaintBox vypisovat totéž
co na tiskárnu.
Ony nesmyslné texty byly zvoleny proto, protože obsahují hodně háčků a
čárek nad písmeny a obsahují znaky s „ocáskem“ pod čarou.
Při změně rozměrů okna se bude měnit i velikost písma tak, aby výpis
vypadal vždy stejně.
157
// funkce je volána při zobrazení okna a při změně rozměrů okna
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
VypisTextu(PaintBox1->Canvas,PaintBox1->Height,PaintBox1->Width);
}
//-----------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender)
{
PrintDialog1->Execute();
TPrinter *pr=Printer();
pr->BeginDoc();
VypisTextu(pr->Canvas,pr->PageHeight,pr->PageWidth);
pr->EndDoc();
}
//-----------------------------------------------------------------------void __fastcall TForm1::VypisTextu(TCanvas *Canvas,int Vyska,int Sirka)
{
TCanvas *can=Canvas;
int VyskaStranky=Vyska;
int SirkaStranky=Sirka;
int HorniOkraj=VyskaStranky/50;
int SpodniOkraj=VyskaStranky-HorniOkraj;
int LevyOkraj=SirkaStranky/40;
int PravyOkraj=SirkaStranky-LevyOkraj;
can->Font->Name="Times New Roman";
can->Font->Style= can->Font->Style<<fsBold<<fsItalic;
can->Font->Height=VyskaStranky/15;
int PolohaY=HorniOkraj;
int PolohaX=LevyOkraj;
can->TextOut(PolohaX,PolohaY,"Čau světe, jak si žiješ");
PolohaY+=can->Font->Height;
can->TextOut(PolohaX,PolohaY,"Jak si žiješ, člověče");
PolohaY+=can->Font->Height;
can->TextOut(PolohaX,PolohaY,"Čau světe, jak si žiješ");
PolohaY+=can->Font->Height;
can->Pen->Width=SirkaStranky/500;
can->Pen->Color=clRed;
can->Brush->Style=bsClear;
// průhledný štětec
TSize s=can->TextExtent("Čau světe, jak si žiješ");
can->Rectangle(LevyOkraj,HorniOkraj,LevyOkraj+s.cx,PolohaY);
PolohaY+=can->Font->Height;
can->Font->Style=can->Font->Style>>fsItalic;
// zruším Italic
// Canvas by měl mít i funkci TextRect, ta ale jaksi nefunguje
// místo ní použijeme funkci WinAPI DrawText
String str="Čau, jaro !";
// obdélník, do kterého tisknu
TRect r(LevyOkraj,PolohaY, PravyOkraj, PolohaY+can->Font->Height);
DrawText(can->Handle,str.c_str(),str.Length(),&r,DT_VCENTER|DT_CENTER);
// nakreslím kolem toho obdélník
can->Rectangle(LevyOkraj,PolohaY,PravyOkraj,PolohaY+can->Font->Height);
}
158
Kontrolní otázky a úkoly
Zadání 1.
Program pro kreslení grafů goniometrických funkcí z minulé kapitoly
rozšiřte o tisk.
Začněte nejdříve jednotlivými jednoduššími úlohami tak, jak jsou v minulé
kapitole uvedeny. Do všech přidělejte tlačítko tisk, které nejdříve zobrazí
dialog pro výběr tiskárny a pak provede vlastní tisk. Pokusy provádějte
pomocí „tisku“ do virtuální tiskárny PDFCreatror.
Zadání 2.
Vytvořte program pro tisk vybraného obrázku.
Zadání 3.
V souboru „sklad.dat“ jsou data o položkách skladu.
Každá položka obsahuje tato dat:
class TVyrobek
{
public:
char NazevVyrobku[30];
int Pocet;
float CenaKusu;
};
Pomocí třídy TFileStream si nejdříve vytvořte soubor obsahující data
několika výrobků. Pak položky vyčítejte a každou vytiskněte na
samostatný řádek. Při tisku nastavte font na neproporcionální "Courier
New" a jeho výšku zvolte na 1/70 výšky stránky. Řádky formátujte s
výhodou pomocí členské funkce třídy AnsiString printf. Pro zarovnání
názvu vlevo použijte formátovací řetězec "%-30s".
159
27
Uživatelské vykreslování buněk TStringGrid
Obsah hodiny
Naučíme se vykreslovat texty v buňkách komponenty TStringGrid např.
různobarevně.
Cíl hodiny
Po této hodině budete schopni:
●
●
každou buňku TStringGrid vykreslovat jiným fontem,
každou buňku vypisovat jinou barvou a s jinou barvou pozadí
Klíčová slova
DefaultDrawing, OnDrawCell.
Při seznamování se s komponentou TStringGrid jste si možná všimli vlastnosti
„DefaultDrawing“, která se implicitně nastavená na false. To znamená, že texty
v jednotlivých buňkách budou vykreslovány způsobem, který si předem nastavíme
a dále ho již nemůžeme změnit. Všechny buňky budou tedy vykreslovány stejnou
barvou na stejném pozadí a stejným fontem.
Toto chování komponenty se nám někdy nemusí hodit. Můžeme např. požadovat,
aby některé buňky v závislosti na jejich obsahu nebo poloze byly vypisovány jinou
barvou na jiném pozadí a jiným fontem.
V těchto okamžicích použijeme právě vlastnost „DefaultDrawing“ a událost
„OnDrawCell“, která vzniká před vykreslováním každé buňky. V obsluze této
události se můžeme rozhodnout, zda příslušnou buňku necháme vykreslit
samotnou komponentou nebo zda si ji vykreslíme dle našich speciálních
požadavků. Podle toho nastavíme vlastnost „DefaultDrawing“.
Pokud budeme vykreslovat ve vlastní režii, pak kreslíme do Canvas komponenty.
A to do obdélníku „Rect“, který v obsluze události obdržíme jako parametr.
Souřadnice zrovna vykreslované buňky získáme z parametrů „ARow“ a „ACol“.
Dále se můžeme ještě rozhodovat podle parametru „State“, který je typu
„TGridDrawState“. Jedná se o množinu nabývající těchto hodnot: gdSelected –
buňka je vybraná, gdFocused – buňka je vybraná pro zápis, gdFixed – jedná se o
buňku typu „fixed“. Na jednotlivé možnosti se dotazujeme metodou Contains.
160
Příklad:
Vytvořme aplikaci, kde v komponentě TStringGrid budeme zobrazovat náhodně
vygenerovaná celá čísla. Sudá čísla zobrazujme v buňkách s červeným pozadím,
lichá v buňkách s modrým pozadím. Buňky určené pro nadpisy nechme
zobrazovat „default“.
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
for(int r=1;r<StringGrid1->RowCount;r++)
StringGrid1->Cells[0][r]="Radek "+IntToStr(r);
for(int s=1;s<StringGrid1->ColCount;s++)
StringGrid1->Cells[s][0]="Sloupec "+IntToStr(s);
// aby byla i buňka [0][0] šedá, jinak bude bílá
StringGrid1->Color=clBtnFace;
randomize();
Button1Click(NULL);
}
//-------------------------------------------------------------------------// naplnění náhodnými čísly z rozsahu 0 až 99
void __fastcall TForm1::Button1Click(TObject *Sender)
{
for(int r=1;r<StringGrid1->RowCount;r++)
for(int s=1;s<StringGrid1->ColCount;s++)
StringGrid1->Cells[s][r]=random(100);
}
//-------------------------------------------------------------------------void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender,
int ACol, int ARow, TRect &Rect, TGridDrawState State)
{
if(State.Contains(gdFixed)) {
// překresluje se buňka s nadpisem
StringGrid1->DefaultDrawing=true;
return;
}
else
// překresluje se datová buňka
StringGrid1->DefaultDrawing=false;
bool sude=false;
if(StrToInt(str)%2==0)
161
sude=true;
if(sude)
StringGrid1->Canvas->Brush->Color=clRed;
else
StringGrid1->Canvas->Brush->Color=clBlue;
StringGrid1->Canvas->Brush->Style=bsSolid;
StringGrid1->Canvas->FillRect(Rect);
StringGrid1->Canvas->Brush->Style=bsClear;
StringGrid1->Canvas->Font->Color=clWhite;
StringGrid1->Canvas->TextOut(Rect.Left+10,Rect.Top+5,str);
}
//--------------------------------------------------------------------------
Pro přesné vykreslování v obdélníku buňky se kromě metody „TextOut“ nabízí
ještě „TextRect“, bohužel ve VCL je nějaká chyba a metoda prostě nefunguje.
Proto je nutné místo ní použít WIN32 API funkci „DrawText“, která nám umožní
usadit text např. přesně na střed obdélníka jak vodorovně, tak i svisle.
Např. takto:
TRect Rec=Rect;
unsigned int Format=DT_SINGLELINE|DT_VCENTER|DT_CENTER;
DrawText(StringGrid1->Canvas->Handle,
StringGrid1->Cells[ACol][ARow].c_str(),
-1,
(RECT*)&Rec,
Format
);
Podrobnosti o této funkci, která má velké možnosti nastavení, si zjistěte
v nápovědném souboru k WIN32 API, který se nachází v "C:\Program
Files\Common Files\Borland Shared\MSHelp\win32.hlp".
Je vhodné si ještě uvědomit, že pokud umíme pomocí Canvas do jednotlivých
buněk psát a vyplňovat je barvou, tak do nich můžeme samozřejmě vkládat i
obrázky a vytvořit si tak náhradu další komponenty, kterou jsme sice neprobírali,
ale jejíž použití je zřejmé, a to „TDrawGrid“.
162
28
Práce s inicializačními soubory
Obsah hodiny
Naučíme se jak uložit důležitá data našeho programu do speciálních
inicializačních souborů tak, abychom podle nich mohli příště náš program uvést
do takového stavu, v jakém jsme ho minule opustili.
Cíl hodiny
Po této hodině budete schopni:
●
●
vytvářet inicializační soubory,
vyčítat inicializační soubory
Klíčová slova
Inicializační soubor, TIniFile
Často potřebujeme někam uložit různá potřebná nastavení pro spouštění a
provoz našich programů. Potřebujeme také při ukončování programu uložit
koncový stav programu tak, aby při příštím najetí program pokračoval
v takovém stavu, v jakém minule skončil.
Máme v podstatě čtyři možnosti:
1. Ukládat si všechna potřebná data do speciálních souborů se
speciální strukturou. Tento způsob má ovšem nevýhodu v tom, že
musíme takové soubory navrhovat vždy znovu a nesnadno se
udržují.
2. Ukládat si data do inicializačních textových souborů (mívají
většinou příponu ini). Tento způsob má výhodu v tom, že tyto
soubory mají standardizovanou, snadno čitelnou a snadno
upravitelnou strukturu. Můžeme je dokonce upravovat i před
spuštěním programu pomocí vhodného textového editoru.
3. Ukládat data do textového souboru se speciální strukturou
nazývanou jazykem XML - eXtensible Markup Language.
4. Ukládat data do systémové databáze nazývané Registry. Toto
řešení se dnes používá nejčastěji i přesto, že má mnoho nevýhod.
163
Registry je obrovská hromada dat, ve kterých něco najít je často
velmi nesnadné. Nastavení zde uložená se těžko přenášejí
s programem na jiný počítač a prakticky to vždy znamená novou
instalaci programu a ztrátu všech nastavení z předcházejícího
počítače.
Inicializační soubory INI
Jedná se o textový soubor nejčastěji s příponou „ini“. Tato přípona ovšem
není povinná a můžeme klidně použít nějakou svou vlastní.
Soubor je členěn do jednotlivých sekcí. Každá sekce začíná názvem sekce
uzavřeným do hranatých závorek. Uvnitř každé sekce se nacházejí
jednotlivé položky. Každá položka začíná názvem následovaným
rovnítkem a pak hodnotou. Hodnoty mohou byt čísla, texty, písmena,
logické hodnoty (0,1). Dále ještě můžeme do takového souboru vždy
na samostatné řádky uvést poznámky uvozené středníkem.
Příklad inicializačního souboru:
; inicializační soubor popisující strukturu dat
[Nastaveni]
ArchivBinaru=c:\EnergoWinC\archiv\binar
; Každý binární vstup ma svou skupinu s názvem např. [Vstup5]
[Vstup1]
Stanice=1
Vstup=1
Nazev=Birar1
; Časy se uvádějí v sekundách
MinCas=5
MaxCas=60
[Vstup2]
Stanice=1
Vstup=2
Nazev=Binar2
MinCas=20
MaxCas=120
Se soubory INI se snadno pracuje pomoci třídy TIniFile, která je
deklarovaná v <IniFiles.hpp>. Třída obsahuje řadu metod pro čtení a zápis
hodnot mnoha typů. Názvy metod pro čtení začínají slovem Read, metody
pro zápis slovem Write. Lze pracovat s hodnotami typu int, float, bool, time,
date, datetime.
164
Metody pro čtení hodnot mají vždy tři parametry: název sekce, název
hodnoty, default hodnotu, která se vrátí v tom případě, když se daná
hodnota v uvedené sekci nenalezne.
Příklad:
void __fastcall TForm1::FormShow(TObject *Sender)
{
TIniFile *ini = new TIniFile("c:\\MyApp\\bin\\MyApp.ini");
// ze sekce AutoLoad čteme položku FormProperties typu bool
// není-li položka nalezena, bere se to jako by měla hodnotu false
if (ini->ReadBool("AutoLoad", "FormProperties", false) == true)
{
Visible = ini->ReadBool("FormOptions", "Visible", true);
Color = (TColor)ini->ReadInteger("FormOptions", "Color", clWindow);
Caption = ini->ReadString("FormOptions", "Caption", "Main");
}
delete ini;
}
Jestliže při zakládání instance třídy TIniFile soubor ještě neexistuje, je
automaticky založen.
Metody pro zápis mají rovněž tři parametry: název sekce, název hodnoty,
vlastní hodnotu. Nevracejí však nic.
Příklad:
// ini soubor bude ve stejném adresáři jako soubor exe
String str=ExtractFilePath(Application->ExeName);
str=str+"fungame.ini";
TIniFile *ini = new TIniFile (str);
// do sekce Options zapíši položku Sound typu bool s hodnotou true
ini->WriteBool("Options", "Sound", true);
ini->WriteInteger("Options", "Level", 3);
ini->WriteBool("Configuration", "ShowToolBar", false);
delete ini;
Při práci se soubory, jejichž struktura se mění, se nám mohou hodit funkce
SectionExists, ValueExists.
Často můžeme potřebovat, aby inicializační soubor byl uložen ve stejném adresáři
jako vlastní program a měl stejný název, jen příponu jinou.
Pak postupujeme takto:
// zjistím cestu na exe soubor aplikace
String str=Application->ExeName;
// zjistím adresu tečky před příponou exe
165
char *p=strrchr(str.c_str(),'.');
// na tuto adresu nakopíruji ".ini"
// to mohu, protože text je stejně dlouhý jako ".exe"
strcpy(p,".ini");
if(!FileExists(str)) {
Application->MessageBox("Nelze otevřít konfigurační soubor",
"Chyba inicializace",MB_OK|MB_ICONWARNING);
return;
}
TIniFile *ini=new TIniFile(str);
Poznámka:
Název našeho programu i s cestou můžeme získat také pomocí globální
proměnné _argv, což je vlastně obdoba parametru argv ve funkci main
v konzolových aplikacích. _argv je pole textových řetězců deklarovaných stejně
jako argv, tedy takto:
char **_argv;
Velikost pole (počet stringů) nalezneme v globální proměnné _argc. V prvním
stringu nalezneme cestu na náš program. V dalších jsou pak parametry, které byly
při spouštění našeho parametru použity.
Takže cestu na náš program můžeme získat také takto:
String str=_argv[0];
Kontrolní otázky a úkoly
Vytvořte jednoduchý program s jedním formulářem.
V obsluze události FormClose zapište do inicializačního souboru se stejným
názvem jako má váš spustitelný soubor, ale s příponou ini a nacházející se i ve
stejném adresáři polohu a rozměry vašeho formuláře.
V obsluze události FormShow vyčtěte z inicializačního souboru, pokud existuje,
polohu a rozměry formuláře z minulého spuštění programu a obnovte je. Pro
zjištění existence souboru použijte funkci FileExists.
166
29
Práce s registrační databázi
Obsah hodiny
Ukážeme si základy práce s registrační databázi Registry.
Cíl hodiny
Po této hodině budete schopni:
●
●
zakládat v Registry nové klíče, hodnoty
používat uložené hodnoty pro najetí programu do takového stavu, jak jsme
ho minule opustili
Klíčová slova
Registrační databáze, klíče, hodnoty, kořenové klíče, TRegistry.
Se systémovou databází můžeme pracovat pomocí třídy TRegistry, která
je deklarovaná v hlavičkovém souboru Registry.hpp.
Registry je databáze, kterou může aplikace použít pro ukládání
konfiguračních informací. Tyto informace jsou ukládány do hierarchické
stromové struktury. Každý uzel v tomto stromu se nazývá klíč. Každý klíč
může obsahovat podřízené klíče a data, která reprezentují část
konfiguračních informací aplikace.
Všechny klíče, které aplikace vytváří, otevírá, čte nebo do nich píše, jsou
podřízenými klíči předdefinovaných kořenových klíčů. Ve výchozím stavu
je objekt (instance) třídy TRegistry vytvářen s kořenovým klíčem
HKEY_CURRENT_USER. V daném okamžiku je vždy přístupný pouze
jeden klíč. Pro zjištění klíče, který je momentálně přístupný, lze vyčíst
vlastnost CurrentKey.
Metody třídy TRegistry umožňují aplikaci s klíči provádět tyto činnosti:
otevírání, zavírání, ukládání, přesouvání, kopírování, mazání.
Do klíče může být uložena jedna nebo více datových hodnot.
Pozor: Při vytváření nových klíčů, případně i při jejich vyčítání musíme mít
vždy na paměti, že ne do všech kořenových klíčů máme přístup. Případně
máme přístup omezený pouze pro čtení. Mohou se vyskytnout i takové
167
situace, že do kořenového klíče můžeme vstupovat, ale můžeme v něm
pracovat pouze s některými podřízenými klíči. Zde je nutné získat podrobné
informace z literatury nebo to prostě vyzkoušet.
Důležité vlastnosti:
Access Určuje užitou úroveň přístupu při otevírání klíče
RootKey Určuje použitý kořenový klíč. Výchozí hodnota je
HKEY_CURRENT_USER
CurrentPath Obsahuje cestu v syst. databázi spojenou s aktuálním klíčem
CurrentKey Určuje klíč, který je v současné době otevřen
Důležité metody:
bool CreateKey(AnsiString Key) – Založení nového klíče
void CloseKey() - Zápis aktuálního klíče do Registry a jeho zavření
bool DeleteKey(AnsiString Key) - Odstranění klíče a souvisejících dat
z Registry
bool ReadBool(AnsiString Name) - Vyčtení logické hodnoty ze zadané
datové hodnoty spojené se současným klíčem
ReadBinaryData, ReadCurrency, ReadDate, ReadTime, ReadDateTime,
ReadFloat, ReadInteger, ReadString
void WriteBool(AnsiString Name, bool Value) – Zápis logické hodnoty se
jménem Name a hodnotou Value.
WriteBinaryData, WriteCurrency, WriteDate, WriteTime, WriteDateTime,
WriteFloat, WriteInteger, WriteString
bool KeyExists(AnsiString Key) – Zjištění, zda klíč existuje
bool ValueExists(AnsiString Name) – Zjištění, zda hodnota existuje
Při práci s TRegistry jsou chyby ošetřeny pomocí generování výjimek. Proto
je vhodné si v nápovědě přečíst také reakci dané metody na chybu.
Příklad:
void __fastcall TForm1::FormClose(TObject *Sender)
{
TRegistry *Reg = new TRegistry;
try {
Reg->RootKey = HKEY_CURRENT_USER;
if (Reg->OpenKey("\\Software\\MujPokus", true)) {
Reg->WriteInteger("Sirka",Width);
Reg->CloseKey();
}
}
__finally {
delete Reg;
}
}
168
Kontrolní otázky a úkoly
Vytvořte jednoduchý program s jedním formulářem.
V obsluze události FormClose zapište do Systémové databáze polohu a
rozměry vašeho formuláře.
V obsluze události FormShow vyčtěte ze systémové databáze polohu a
rozměry formuláře z minulého spuštění programu a obnovte je. Pro zjištění
existence dat použijte funkce KeyExists a ValueExists.
169
30 Některé další užitečné komponenty
Obsah hodiny
Uvedeme si seznam některých dalších komponent některých skupin,
které by se mohly hodit pro tvorbu vašich aplikací.
30.1
Standard
TScrollBar
grafický objekt, u nějž můžeme měnit polohu palce
(thumb) a odečítat ji. Používá se hlavně pro plnění proměnných
celým číslem.
TActionList
komponenta, kterou lze používat podobně jako
TMainMenu nebo TPopupMenu. Každá akce, kterou tato
komponenta zastupuje, je tvořena instancí třídy TAction, která
obsahuje vše potřebné pro zahájení této akce stejně jako to měly
jednotlivé položky Items např. u TMainMenu.
30.2
Additional
TBitBtn
komponenta, která je rozšířením vlastností komponenty
TButton. Na její plochu lze kromě Caption přidat také bitové mapy
pro polohy Up, Disabled a Down.
TSpeedButton na rozdíl od TButton a TBitBtn tato komponenta
nepřijímá focus a nereaguje na klávesu TAB. Po jejím stisku zůstane
tedy i nadále vybrána ta stejná komponenta jako doposud.
TMaskEdit
odpovídá komponentě TEdit se vstupním filtrem.
Můžeme si tedy předem určit v jakém tvaru má být napsán text
v okénku. Kde mají být např. písmena, kde číslice a podobně.
TDrawGrid
komponenta, která umožňuje zobrazení libovolných dat
ve formě tabulky do Canvas komponenty. Jedná se o předchůdce
komponenty TStringGrid, která je již specializována na zobrazování
stringů.
TShape
komponenta představuje jeden grafický obrazec, který
se nakreslí na plochu formuláře. Umí tyto obrazce: obdélník, čtverec,
zaokrouhlený obdélník, elipsa, kruh. Zná pouze události, které se
týkají DragAndDrop a myši.
TBevel
komponenta vytvářející vizuální efekt vtlačených nebo
vystouplých čar. Třída nedefinuje žádnou událost, slouží pouze jako
podklad pro jiné komponenty.
TScrollBox
komponenta slouží pro umístění jiných komponent a
jejich skrolování.
170
TCheckListBox klasický TListBox doplněný o možnost označování
(zaškrtávání) jednotlivých položek.
TSplitter
slouží pro rozdělování pracovní plochy na dvě části.
Předěl mezi částmi můžeme tažením pomocí myši přemísťovat a tím
si zvětšovat tu část pracovní plochy, která nás zrovna zajímá.
TStaticText
má prakticky stejné vlastnosti jako TLabel. Rozdíl je
pouze v tom, že TStaticText je klasická komponenta Windows, tedy
že má např. položku Handle a můžeme jí tedy posílat zprávy. TLabel
vlastnost Handle nemá. Podívejte se na kapitoly o zprávách.
TControlBar
představuje plochu pro umístění komponent TToolBar
(karta Win 32).
TValueListEditor komponenta představuje snadný způsob pro
editování seznamu stringů obsahujícího dvojice název/hodnota.
TColorBox
představuje combo box se seznamem, který uživateli
umožňuje vybrat si barvu z nabídnutých možností.
TColorListBox představuje list box se seznamem, který uživateli
umožňuje vybrat si barvu z nabídnutých možností.
TTrayIcon
komponenta umí zobrazit animovanou či statickou
ikonu v pravé části systemové lišty.
Viz. http://edn.embarcadero.com/article/33415
TFlowPanel
komponeta, která automaticky rozmístí jiné
komponenty, které do ní umístíme. Můžeme si určit podle jakých
pravidel bydou komponenty rozmístěny. Při změně rozměrů panelu
dojde k automatickému novému rozmístění.
Viz. http://edn.embarcadero.com/article/33421
TGridPanel
umožňuje rozmístění komponent, které jsou v něm
umístěny, do mřížky.
Viz. http://edn.embarcadero.com/article/33421
30.3
Win32
TImageList
komponenta obsahující seznam bitmapových obrázků
stejné velikosti. Slouží jako zásobník obrázků nebo ikon, z kterého si
vybíráme pomocí indexu.
TTrackBar
komponenta má stejné použití jako TScrollBar, liší se
pouze vzhledem.
TProgressBar se používá pro grafické znázornění vývoje nějaké akce.
Napodobuje vzhledem klasický teploměr.
TUpDown
umožňuje zvýšit nebo snížit hodnoty numerického
vstupu. Komponenta je obvykle asociována pomocí položky
„Associate“ s jiným objektem.
THotKey
slouží pro editaci akcelerátoru (horké klávesy). Tento
akcelerátor lze pak např. v události OnExit přiřadit položce
171
TMainMenu nebo TPopupMenu (např. Ctrl + 4) a vyvolat pak tuto
položku nejen kliknutím na ní myší, ale i stiskem zvolené kombinace
kláves. Pomocí této komponenty měníme u položek menu vlastnost
ShortCut.
TAnimate
komponenta slouží pro snadné přehrávání AVI klipů.
Lze použít vlastní AVI klip nebo si vybrat z několika
předdefinovaných pro vyhledávání souborů, vyhledávání počítače,
přenos souborů atd.
TTreeView
představuje strom s uzly. Každý uzel figuruje jako
instance třídy TTreeNode. Uzly jsou dosažitelné přes seznam
TTreeNodes, datové položky pomocí Items.
TListView
zobrazuje seznam položek. Každá položka je na
jednom řádku v jednom nebo více sloupcích a figuruje jako instance
třídy TListItem. Položky jsou dosažitelné přes TListItems
představované vlastností Items.
THeaderControl komponenta představuje množinu sekcí s měnitelnou
šířkou, kterou lze umístit na okraje klientské oblasti formuláře. Každá
sekce je instancí třídy THeaderSection, do které můžeme umísťovat
texty nebo obrázky.
TStatusBar
komponenta představující stavový pruh aplikace. Může
mít buď charakter jednoduchého textu nebo se jedná o několik
panelů.
TToolBar
komponenta obsahující několik tlačítek (TToolButton),
které umí automaticky na své ploše naaranžovat. Každé tlačítko
může mít obrázek ze sady obrázků uložených v TImageList.
TCoolBar
komponenta představuje kontainer, do kterého se
umísťují ovládací komponenty k nimž jsou automaticky připojeny
pásky, pomoci kterých můžeme komponenty za chodu programu po
ploše TCoolBar přemíťovat a měnit jejich rozměry. Podobně se
chovají ovládací lišty např. u webových prohlížečů.
TcomboBoxEx komponenta representuje combo box s rozšířenými
vlastnostmi. K jednotlivým položkám lze přiřadit obrázky
z příslušného TImageList.
30.4
System
TMediaPlayer vytváří uživatelské rozhraní pro použití multimedií
(přehrávání, tvoření záznamu atd.) Bohužel tato komponenta nemá
kodeky na nové verze multimedií. Proto je vhodnější raději používat
jiné přehrávací komponenty jako jsou např. DSPack
[http://www.progdigy.com] nebo VideoLab [http://www.mitov.com].
172
30.5
Win 3.1
Většina zde obsažených komponent je dnes už zastaralých, ale hlavně
poslední čtyři zde uvedené by se někdy přece jen mohly hodit.
TOutline
obdoba novější komponenty TTreeView.
TTabbedNotebook
obdoba novější komponenty TPageControl.
TNotebook
obdoba novější komponenty TPageControl.
THeader
obdoba novější komponenty THeaderControl.
TFileListBox
speciální list box zobrazující všechny soubory
z vybraného adresáře. Uživatel může vybrat požadovaný soubor.
TDirectoryListBox
list box zobrazující strukturu složek na aktuálním
disku. Uživatel si může vybrat požadovanou složku (adresář).
TDriveComboBox
speciální combo box zobrazující všechny
dostupné disky. Umožňuje uživateli přepnutí na jiný disk.
TFilterComboBox
speciální combo box umožňující uživateli výběr
mezi různými filtry pro výběr souborů.
30.6
Dialogs
TOpenPictureDialog obdoba TOpenDialog přednastavená pro výběr
obrázků.
TSavePictureDialog obdoba TSaveDialog přednastavená pro
ukládání obrázků.
TOpenTextFileDialog obdoba TOpenDialog přednastavená pro výběr
textových souborů.
TSaveTextFileDialog obdoba TSaveDialog přednastavená pro
ukládání textových souborů.
TFindDialog
dialogová komponenta pro hledání textu v textových
souborech.
TreplaceDialog dialogová komponenta pro vyhledání a náhradu textu
v textových souborech.
TpageSetupDialog
dialogová komponenta pro nastavení vlastností
stránky textového souboru.
30.7
Internet
TtcpClient
komponenta pracující jako TCP/IP client při
komunikacích mezi počítači na internetu.
TtcpServer
komponenta pracující jako TCP/IP server při
komunikacích mezi počítači na internetu. Komponenta může
pracovat v módu, kdy je schopna v daném okamžiku komunikovat
pouze s jedním TCP klientem nebo v módu, kdy se pro každého
klienta, který se přihlásí, vytvoří samostatné vlákno (thread).
173
TudpSocket
komponenta, která v aplikaci může zajišťovat
komunikaci přes UDP/IP protokol.
TwebBrowser zajišťuje přístup ke schopnostem webového prohlížeče
Internet Explorer. Můžeme si tak ve svém formuláři vytvořit okno se
zobrazenou stránkou z internetu včetně toho, že můžeme pomocí
kliku na požadovaný odkaz přecházet mezi jednotlivými stránkami.
174
31
Základy tvorby dynamicky linkovaných
knihoven - DLL
Obsah hodiny
Seznámíme se základy tvorby jednoduchých dynamicky linkovaných knihoven.
Cíl hodiny
Po této hodině budete schopni:
●
●
vytvořit vlastní knihovnu DLL,
používat cizí knihovny DLL
Klíčová slova
Dynamicky linkovaná knihovna, DLL.
31.1
Důvody vzniku dynamicky linkovaných knihoven - DLL
V době operačního systému DOS, probíhala tvorba programu tak, že velkou
část programu jsme napsat sami. Některé základní funkce jazyka C, jako
např. funkci printf jsme pak k našemu programu připojili tak, že jsme od
jiných autorů nebo od prodejce kompilátoru získali jejich části programu
přeložené již jako soubory typu OBJ popřípadě LIB. K těmto částem jsme
samozřejmě potřebovali ještě hlavičkové soubory (např. stdio.h).
Pokud souborů OBJ bylo mnoho, bylo je možno spojit do souborů typu LIB.
LIB je více souborů OBJ poskládaných do jednoho.
My jsme pak v našem projektu uvedli, které soubory OBJ nebo LIB jsou
součásti programu.
Po překladu pak pomocí linkeru došlo ke spojení našich souborů OBJ
s cizími soubory typu OBJ nebo LIB do jednoho souboru EXE.
Pokud později dodavatel cizích souborů OBJ nebo LIB zjistil, že v nich má
chybu, musel všem uživatelům dodat opravené soubory a uživatelé musili
své programy s novými soubory znovu přeložit a slinkovat.
175
V době operačního systému Windows by byl výše popsaný způsob
strašně nepraktický. Systém je totiž složitý a tudíž aktualizací a oprav je
v něm velké množství.
Proto byly vymyšleny dynamicky linkované knihovny – DLL. Při
používání těchto knihoven se v našem programu vyskytují pouze odvolávky
na funkce obsažené v cizích souborech DLL. K propojení těchto odvolávek
s konkrétními funkcemi v příslušných DLL souborech dojde až za chodu
programu buď na základě projektu (statická vazba) nebo na přání autora až
v okamžiku potřeby (dynamická vazba).
Operační systém natáhne do paměti náš spustitelný soubor typu EXE a
když už při natahování nebo později zjistí, že se program odvolává na
funkci z nějaké knihovny DLL, tak tuto natáhne do paměti také a nahradí
ony odvolávky konkrétními adresami žádané funkce v natažené knihovně
DLL. Proto se tyto soubory nazývají dynamicky linkované. K propojení
našeho volání funkce a vlastního uložení požadované funkce dojde až za
běhu programu. Tedy ne jako v programech DOSu, kde propojení došlo již
v době linkování a již se neměnilo.
Pokud se během používání knihovny uživateli nalezne v knihovně DLL
chyba, pak se pouze do systému umístí nová verze knihovny. Žádný nový
překlad našeho programu není nutný.
31.2
Postup tvorby jednoduché knihovny DLL
Nejjednodušší postup tvorby spustitelného souboru EXE a knihovny DLL,
kterou EXE používá, je tento:
1. Vytvoříme nový projekt typu „Project Group“. Tento typ projektu je u
Turbo C++ Builderu ve skupině „Other Files“. Naše skupina projektů
bude obsahovat jednak projekt typu „Dynamik-link Library“ pro
vytvoření naší knihovny DLL a dále projekt klasického programu typu
„VCL Forms Application“, který bude naši knihovnu DLL používat.
Tento program může sloužit buď jako prostředek pro odladění knihovny
DLL nebo může být našim opravdovým finálním výrobkem.
Oba projekty vytvoříme nejsnáze tak, že v Projekt Manager klepneme
pravým tlačítkem myši na název skupiny projektů a zvolíme volbu „Add
New Project“. První projekt zvolíme typu Dynamik-link Library a druhý
typu VCL Forms Application. Pak vše uložíme do společného adresáře
pod vhodnými názvy, které vystihují komu který soubor patří (viz.
vzorové příklady).
Pro náš pokus se domluvme, že všechny soubory knihovny DLL budou
začínat slovem Knihovna a všechny soubory aplikace používající naše
DLL budou mít názvy začínající slovem Aplikace. Celou skupinu
176
projektů pojmenujte např. PokusDLL.
Zde máme příklad nově založené skupiny projektu v Project Manager:
PokusDll
Knihovna.dll:
Knihovna .bdsproj
Knihovna .bpf
Knihovna.res
KnihovnaHlavniSoubor.cpp
Aplikace.exe:
Aplikace.bdsproj
Aplikace.cpp
Aplikace.res
AplikaceOkno.cpp
AplikaceOkno.dfm
AplikaceOkno.h
Vše pro jednoduchost uložme pomocí nabídky „File / Save All“ do
společného adresáře a přeložme pomocí nabídky „Project / Build All
Projects“.
2. V Project Manager klepněte pravým tlačítkem myši na název naší
aplikace, která bude DLL používat, a zvolte „Add…“. Při vyhledávání
zvolte soubory typu LIB. V adresáři „Debudg_Build“ zvolte knihovní
soubor našeho projektu DLL nazvaný „Knihovna.lib“. Pomocí tohoto
souboru linker vytvoří vazbu našich budoucích volání funkcí knihovny
DLL s jejich výskytem v knihovně DLL.
V Project Manager poklepáním vyberte projekt DLL a přidejte do něj
pomocí nabídky „File / New“ nový soubor typu „Header File“ a
pojmenujte ho „Knihovna.h“. Zapište do něj tento výchozí text:
#ifndef KNIHOVNAH
#define KNIHOVNAH
#ifdef PREKLADMOJEDLL
#define EXPORTIMPORT __declspec(dllexport)
#else
#define EXPORTIMPORT __declspec(dllimport)
#endif
#ifdef __cplusplus
extern "C" {
#endif
// zde uvedeme deklarace všeho, co naše dll knihovna exportuje
float EXPORTIMPORT __cdecl PovrchValce(float r, float v);
#ifdef __cplusplus
}
#endif
#endif
177
Hlavičkový soubor obsahuje deklaraci funkce PovrchValce, který bude
naše knihovna vyvážet (dllexport) a aplikace dovážet (dllimport). Která
z možností se má při překladu zrovna vkládat, je určeno existencí
nebo neexistencí makra PREKLADMOJEDLL, které budeme
deklarovat ve všech souborech cpp v projektu knihovny dll. Do
všech takových souborů na začátek uvedeme na začátek tyto řádky:
#ifndef PREKLADMOJEDLL
#define PREKLADMOJEDLL
#endif
Pozor, do souborů typu cpp v projektu „Aplikace“ tyto řádky
neuvádíme! Tím dosáhneme toho, že funkce „PovrchValce“ bude
v projektu „Aplikace“ vedená jako „dllimport“.
3. Obsah souboru KnihovnaHlavniSoubor.cpp
#ifndef PREKLADMOJEDLL
#define PREKLADMOJEDLL
#endif
//--------------------------------------------------------------------#include <vcl.h>
#include <windows.h>
#pragma hdrstop
#include "Knihovna.h" // diky makru PREKLADMOJEDLL se vkládá dllexport
//--------------------------------------------------------------------#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason,
void* lpReserved)
{
// přepínač není běžně potřeba, stačí jen return 1;
switch(reason)
{
// příkazy vykonávané při natažení DLL poprvé do paměti
case DLL_PROCESS_ATTACH:
break;
// příkazy vykonávané při připojení threadu k DLL
case DLL_THREAD_ATTACH:
break;
// příkazy vykonávané při odpojení threadu od DLL
case DLL_THREAD_DETACH:
break;
// příkazy vykonávané při uvolňování dll z paměti
case DLL_PROCESS_DETACH:
break;
}
return 1;
}
4. Do souboru KnihovnaHlavniSoubor.cpp umístíme definici vyvážené
funkce PovrchValce:
float EXPORTIMPORT __cdecl PovrchValce(float r, float v)
{
return 2*M_PI*r*r+2*M_PI*r*v;
178
}
Proti běžné deklaraci zde přibylo „EXPORTIMPORT __cdecl“.
Přeložíme projekt „Knihovna“.
5. V Project Manager poklepáním vyberme projekt „Aplikace“ upravme
mu soubor „AplikaceOkno.cpp“ takto:
#include <vcl.h>
#pragma hdrstop
#include "AplikaceOkno.h"
#include "Knihovna.h"
//--------------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TForm2 *Form2;
//--------------------------------------------------------------------__fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner)
{
}
//--------------------------------------------------------------------void __fastcall TForm2::Button1Click(TObject *Sender)
{
float r=StrToFloat(lePolomer->Text);
float v=StrToFloat(leVyska->Text);
lePovrch->Text=FloatToStrF(PovrchValce(r,v),ffNumber,7,2);
}
//---------------------------------------------------------------------
Přeložíme projekt Aplikace a spustime ho.
6. V Project Manager se pak při dalším vývoji vždy poklepáním přepneme
do toho projektu, který chceme upravovat.
Pracujeme-li na projektu dll „Knihovna“, pak by bylo výhodné, aby při
každém zkoušení knihovny se spustila Aplikace, která vyvíjenou
knihovnu natáhne do paměti. To provedeme v projektu Knihovna tak,
že v nabídce „Run / Parameters“ zadáme v položce „Host
application“ cestu na náš projekt „Aplikace“. Ten se pak vždy
automaticky spustí, když při vývoji dll zvolíme „Run / Run“. V takto
spuštěné aplikaci pak můžeme normálně např. krokovat jak v projektu
179
dll, tak i exe. Pouze to, co provedeme v projektu, který momentálně
nemáme zvolený, se přeloží až po překladu tohoto projektu samostatně.
Naše knihovna může samozřejmě obsahovat další soubory typu cpp. To
provedeme přidáním nejjednodušeji pomocí nabídky „File / New / Unit –
C++Builder“.
Knihovna nemusí vyvážet pouze jednoduché funkce, ale i třídy. Např.
v „příkladu 2“ knihovna vyváží kromě funkce „PovrchValce“ ještě třídu
„KonvInt“ pro výpis celočíselných čísel v různých soustavách.
Při deklaracích funkcí rozhraní knihovny DLL (tedy ty funkce, které
z knihovny vyvážíme) nepoužíváme paměťovou třídu __fastcall. Místo
ní používáme třídu _cdecl, která je běžně používána ve funkcích rozhraní
všech DLL knihoven. Funkce, které používáme uvnitř knihovny a které
nevyvážíme, mohou i nadále používat __fastcall.
Uvnitř knihovny můžeme používat i formuláře. Ty přidáme do projektu
knihovny pomocí nabídky File / New / Form. Musíme si ale uvědomit, že
naši knihovnu budeme moci používat i v jiných prostředích (např. firmy
Microsoft). Proto při návrhu rozhraní knihovny nepoužívejme funkce a
třídy, které se odkazují na objekty knihovny VCL. Vytvoříme si rozhraní
skládající se z klasických funkcí neobsahujících odkazy na objekty knihovny
VCL. Tyto pak už ale uvnitř projektu knihovny mohou volat objekty knihovny
VCL. Viz. příklad 3. Zde knihovna exportuje třídu „TGraf“ obsahující
metody, které teprve uvnitř sebe pracují s klasickou třídou formuláře
TForm. Deklarace třídy TGraf se nachází v souboru „KnihovnaGraf.h“ a
definice metod této třídy, které vlastně představují soubor funkcí pomocí
nichž lze s knihovnou dll pracovat, v souboru „KnihovnaGrafHlavniSoubor.
cpp“.
Další informace o tvorbě knihoven dll hledejte v helpu k C++Builderu 6
v odstavci „Programming with C++Builder / Bulding applications,
components and libraries / Using DLLs in C++Builer“ a následujících
stránkách.
31.3
Použití cizí DLL knihovny
Pokud v našem programu chceme požívat cizí knihovnu DLL, pak je nutno
si sehnat k této knihovně příslušný hlavičkový soubor. Pokud k této
knihovně dostaneme i soubor LIB, pak se může stát, že tento nebude
s našim projektem pracovat. Microsoft má totiž strukturu tohoto souboru
jinou než Borland. Proto si musíme vytvořit nový soubor LIB pomocí
programu implib, nacházejícím se v adresáři
"c:\Program Files\Borland\BDS\4.0\Bin\IMPLIB.EXE".
Program „implib.exe“spouštíme z příkazového řádku nebo konzolového
okna otevíraného příkazem „cmd“. Příklad volání pro vytvoření LIB souboru
180
z knihovny MojeDLL.dll:
implib MojeDLL.lib MojeDLL.dll
Teprve tento soubor lib uvedeme do našeho projektu.
V době, kdy máme spuštěné vývojové prostředí, není nutno psát za
program implib cestu, ta je automaticky přidána do standardních cest
vývojovým prostředím. Pokud si nejsme jisti, zda se požadované soubory
najdou, nakopírujeme si program „implib.exe“ přímo do složky, kde se
nachází dll, které chceme použít. Oba soubory dll i lib pak nakopírujeme
do adresáře Dubug_Build našeho projektu.
181
32
Práce se zprávami
Obsah hodiny
Ukážeme si, jak v programu reagovat na zprávy, které nejsou ošetřeny
standardními událostmi.
Cíl hodiny
Po této hodině budete schopni:
●
●
používat ve svém programu reakce na méně časté typy zpráv
řešit situace, které pomocí standardních události nejsme schopni
postihnout.
Klíčová slova
Fronta zpráv, smyčka ošetřování zpráv, TWndMethod.
32.1
Příjem zpráv
Už v úvodních kapitolách jsme si vysvětlovali na základě čeho naše programy
používající GUI vlastně pracují. Říkali jsme si, že náš program, nebo přesněji
řečeno jeho jednotlivá vlákna - thready, v době, kdy nemusí řešit žádnou událost,
od operačního systému ani nedostává strojový čas a naše aplikace vlastně stojí.
„Ožívá“ až v okamžiku, kdy má vyřešit nějakou událost, kterou v IDE nazýváme
event. Po ošetření této události naše aplikace opět „usíná“ až do vzniku další
události. Tyto události vznikají na základě zpráv, kterých je v systému Windows
desítky, možná i stovky. Mohou vznikat zprávy o událostech typu kliknutí myší,
posunutí myší, posunutí okna, zanoření okna, překrytí okna, stisku klávesy a
mnoha, mnoha dalších. Dokonce si můžeme jako uživatelé ve svých programech i
vytvářet své zprávy. Můžeme zprávy nejen přijímat, ale i vysílat třeba i jiným
současně běžícím programům a tak zajistit jejich spolupráci s našim programem.
Jak jsme již psali v kapitole 2, struktura zprávy vypadá takto:
typedef struct tagMSG {
HWND
hwnd;
// identifikátor okna, příjemce zprávy
UINT
message;
// číslo označující typ zprávy
WPARAM wParam;
// dodatečná informace ke zprávě
LPARAM lParam;
// dodatečná informace ke zprávě
DWORD time;
// čas kdy byla zpráva zaslána
POINT pt;
// pozice kurzoru v okamžiku odeslání
} MSG;
182
Nejdůležitější položky struktury jsou zdůrazněny červeně. Čísla zpráv mají pro
snazší zapamatování přiděleny symbolické názvy. Například existuje zpráva
WM_SYSCOMMAND, která je hlášením o tom, že jsme oknu klepli myší na ikonky
v pravém horním rohu. Pod WM_SYSCOMMAND se schovává číslo 0x0112.
Číslo uložené ve wParam blíže určuje o jaký typ události se jedná (minimalizace,
maximalizace, zavření okna atd.), lParam navíc obsahuje souřadnice kurzoru
v okamžiku kliku myší. Za datovými typy WPARAM a LPARAM se schovávají
normální čísla typu int. U jiných typů zpráv mohou znamenat něco zcela jiného.
V C++ Builder je tato struktura uložena ve struktuře TMessage. Blíže viz
nápovědný soubor "C:\Program Files\Common Files\Borland
Shared\MSHelp\win32.hlp".
Zprávy, které vznikají v systému, jsou systémem zasílány do hlavní fronty zpráv a
odsud jsou pak zasílány do fronty příjemce zprávy. Z této fronty jsou vybrány
v okamžiku vzniku události pro ošetření zprávy. Poté je zpráva zrušena.
Zprávy jsou z fronty zpráv vybírány pomocí cyklu, který často nazýváme smyčka
zpráv nebo také Message Pump. Tento cyklus je zastaven tak dlouho, dokud
nepřijde nějaká zpráva. Po příchodu zprávy se v cyklu zpráva z fronty vybere,
zpracuje a pak se přejde opět na začátek cyklu a čeká se na další zprávu.
Každá komponenta, která je potomkem třídy TControl, obsahuje metodu, která se
nazývá WindowProc. Tato metoda slouží pro ošetření zpráv o událostech, které
jsou pro danou komponentu důležité. Neumí však ošetřit jiné typy zpráv. Metoda je
zavolána vždy, když přijde nějaká zpráva.
Abychom mohli reagovat i na jiné typy zpráv, máme možnost definovat svou funkci
typu TWndMethod, která bude ošetřovat zprávy, které chceme ošetřit navíc. Na
konci této metody pak voláme původní funkci WindowProc, které pošleme
informaci o zprávě, kterou jsme obdrželi jako parametr. Pokud toto neprovedeme,
pak naše komponenta na příslušnou zprávu nebude reagovat.
Příklad: Ošetření zprávy typu WM_SYSCOMMAND
Okno obdrží tuto zprávu když uživatel zvolí volbu z menu okna (toto menu
otevřeme kliknutím na ikonku okna v levém horním rohu), které se také nazývá
Systémové nebo Řídící menu, nebo když uživatel klikne na tlačítko minimalizace
nebo maximalizace v pravém horním rohu. Tyto události standardními událostmi
třídy TForm ošetřeny nejsou.
Vytvořme program, u kterého můžeme měnit chování tlačítek minimalizace a
maximalizace.
183
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *BNoveChovani;
TButton *BStareChovani;
void __fastcall BNoveChovaniClick(TObject *Sender);
void __fastcall BStareChovaniClick(TObject *Sender);
private:
// User declarations
// úschova adresy původní funkce WindowProc
TWndMethod PuvodniWndProc;
// deklarace moje funkce ošetření zpráv typu TWndMethod
void __fastcall MojeWndProc(Messages::TMessage &Message);
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//--------------------------------------------------------------------------void __fastcall TForm1::BNoveChovaniClick(TObject *Sender)
{
PuvodniWndProc=WindowProc;
// schovám adresu původní smyčky zpráv
WindowProc= MojeWndProc;
}
//--------------------------------------------------------------------------void __fastcall TForm1::BStareChovaniClick(TObject *Sender)
{
WindowProc=PuvodniWndProc;
// obnovím adresu původní smyčky zpráv
}
//--------------------------------------------------------------------------void __fastcall TForm1:: MojeWndProc(TMessage &Msg)
{
if(Msg.Msg==WM_SYSCOMMAND) {
if(Msg.WParam==SC_MAXIMIZE)
Beep(1000,500); // PuvodniWndProc se provede - standardní reakci provedu
if(Msg.WParam==SC_MINIMIZE) {
Beep(400,500);
return;
// PuvodniWndProc se neprovede - standardní reakci neprovedu
}
}
PuvodniWndProc(Msg);
// zavolám původní smyčku zpráv
}
V tomto jednoduchém programu dosáhneme toho, že při zvolení nového chování
tlačítek v levém horním rohu okna, stisk tlačítka maximalizace bude doprovozen
pípnutím tónem s výškou 1000Hz a délkou 500ms a na stisk tlačítka minimalizace
bude reagováno pouze pípnutím, ale již ne minimalizací okna.
Příklad: Ošetření WM_NCLBUTTONDOWN a WM_WINDOWPOSCHANGING
Odměřování posunutí okna. Reakce na kliknutí v neklientské oblasti formuláře.
184
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabeledEdit *ePosunVodorovne;
TLabeledEdit *ePosunSvisle;
void __fastcall FormResize(TObject *Sender);
private:
// deklarace mé fce pro obsluhu zprav
void __fastcall MojeWndProc(TMessage &Message);
TPoint PocatecniPoloha;
TPoint KoncovaPoloha;
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
WindowProc=MojeWndProc;
// nastavím volání mé funkce pro obsluhu zpráv
}
//--------------------------------------------------------------------------void __fastcall TForm1::FormResize(TObject *Sender)
{
ePosunVodorovne->Text="0";
ePosunSvisle->Text="0";
}
//--------------------------------------------------------------------------void __fastcall TForm1::MojeWndProc(TMessage &Message)
{
switch(Message.Msg) {
case WM_NCLBUTTONDOWN:
// stiskli jsme myš na horním okraji formuláře
PocatecniPoloha.x=Left;
PocatecniPoloha.y=Top;
break;
case WM_WINDOWPOSCHANGING:
// změnila se pozice okna
ePosunVodorovne->Text=IntToStr(Left-PocatecniPoloha.x);
ePosunSvisle->Text=IntToStr(Top-PocatecniPoloha.y);
break;
}
WndProc(Message);
// zavolám původní obsluhu zpráv,
// aby se provedla původní reakce na zprávy
}
32.2
Vysílání zpráv
Zprávy můžeme nejen přijímat, ale i vysílat. Vysílat můžeme nejen sámi
sobě, což se může někdy hodit, když máme aplikaci složenou z více vláken
(threadů) a potřebujeme mezi nimi zasílat bezpečně malý objem dat, ale i
jiným programům. Tento způsob se dá s výhodou použít pro ovládání jiných
programů z našeho programu nebo pro zasílání malého objemu dat do
jiných programů. Prosté zapisování dat do nějakých společných
proměnných v tomto případě nelze použít, protože každý běžící program
má svou virtuální paměť, která není druhým programům přístupná. Pak je
nutno hledat různé obezličky, jak data zaslat. Jednou z možnosti je použít
pro data parametry wParam a lParam obsazené ve struktuře zprávy. Tímto
způsobem můžeme každou zprávou zaslat jiné aplikaci dvě čtyřbytová
čísla.
Zprávu většinou zasíláme pomocí funkcí WIN32 API. Jsou to tyto funkce:
185
SendMessage – pomocí této funkce zašleme zprávu jiné aplikaci a funkce
čeká na návrat z obsluhy zprávy.
LRESULT SendMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
//
//
//
//
handle cílového okna
zpráva, která se má odeslat
první parametr zprávy
druhý parametr zprávy
Tato funkce je ovšem nebezpečná právě v tom, že čeká na ukončení
zpracování zprávy. Pokud volaná aplikace zprávu zpracuje špatně, vede to
z zaseknutí i naší aplikace.
PostMessage – Tato funkce také odešle zprávu, ale nečeká na její
zpracování. Nedozvíme se tedy, jak zpracování dopadlo, ale zbavíme se
rizika zaseknutí naší aplikace. Funkce se proto používá se proto častěji než
předcházející.
BOOL PostMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
//
//
//
//
handle cílového okna
zpráva, která se má odeslat
první parametr zprávy
druhý parametr zprávy
);
PostThreadMessage – Funkce pro zasílání zpráv mezi thready. Tato
funkce je mimo rozsah vysvětlované látky.
U obou dvou prvních funkcí je prvním parametrem handle cílového okna.
Pro jeho zjištění často používáme funkci Win32 API FindWindow.
HWND FindWindow(
LPCTSTR lpClassName, // pointer na string s názvem třídy okna
LPCTSTR lpWindowName // pointer na string s nadpisem okna
);
Nemusíme znát obsahy obou dvou stringů. Stačí pouze jeden. Pokud bude
okno nalezeno, vrátí funkce handle okna. Pokud ne, funkce vrátí NULL.
Název třídy okna můžeme u cizích programů zjistit různými pomocnými
aplikacemi. Jedna z nich je např. WinDowse (Free Advanced Windows
Analyzer od firmy Greatis Software).
Pokud budeme uvádět oba stringy, máme větší jistotu, že se opravdu
dovoláme na žádané okno a ne na nějaké jiné.
Příklad: Zavření programu Total Commander z našeho programu
void __fastcall TForm1::Button1Click(TObject *Sender)
{
String str="Total Commander";
// začátek nadpisu okna programu
HWND hwnd=FindWindow(NULL,str.c_str());
if(hwnd!=NULL)
PostMessage(hwnd,WM_CLOSE,0,0);
}
Zpráva WM_CLOSE představuje požadavek na zavření okna programu.
Pokud je toto okno hlavním oknem programu (to v našem případě je),
program se ukončí. Parametry wParam a lParam u této zprávy nemají
význam.
186
33
Tvorba vlastních zpráv
Obsah hodiny
Naučíme se vytvářet vlastní zprávy.
Cíl hodiny
Po této hodině budete schopni:
●
●
navrhnout vlastní zprávu,
pomocí naší zprávy ovládat z jedné aplikace aplikaci jinou
Klíčová slova
WM_USER.
Kromě zpráv, které již byly v operačním systému Windows navrženy, si
můžeme pro své potřeby navrhnout i zprávy vlastní.
Jak jsme si již řekli v minulé kapitole, tak za názvy jednotlivých typů zpráv
se schovávají celá čísla. Pokud potřebujeme vytvořit vlastní zprávu, pak
musíme mít jistotu, že toto číslo již není v systému používáno. K tomuto
účelu slouží zpráva s názvem WM_USER, která představuje nejvyšší
systémem používané číslo zprávy. Již tuto zprávu můžeme používat volně
pro své potřeby. Všechny další naše zprávy pak navrhujeme jako
WM_USER+x, kde x je číslo 1 a výše. Tím máme jistotu, že nedojde ke
kolizi čísla naší zprávy s nějakou zprávou, kterou používá systém.
Příklad
Vytvořme si pro vyzkoušení skupinu dvou aplikací, kde první aplikace bude
pomocí zpráv ovládat aplikaci druhou. Aplikaci, která bude řídit nazvěme
„Ridici“ a aplikaci, která bude řízena nazvěme „Rizena“. Obě budou
součástí skupiny aplikací „Project Group“, kterou nazvěme např.
„MojeZpravy“.
Řídící aplikace bude do řízené zasílat čtyři typy zpráv: WM_USER,
WM_USER+1, kterou nazvěme např. WM_ROZMERY, WM_USER+2,
budeme nazývat WM_CISLA a WM_USER+3 pod názvem WM_FLOAT.
187
MojeZpravy.h
#ifndef MOJEZPRAVYH
#define MOJEZPRAVYH
#define
#define
#define
WM_ROZMERY
WM_CISLA
WM_FLOAT
WM_USER+1
WM_USER+2
WM_USER+3
#endif
RidiciHlOkno.cpp
#include <vcl.h>
#pragma hdrstop
#include "RidiciHlOkno.h"
#include "MojeZpravy.h"
#include <math.h>
//-------------------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TRidiciHlavniOkno *RidiciHlavniOkno;
//-------------------------------------------------------------------------__fastcall TRidiciHlavniOkno::TRidiciHlavniOkno(TComponent* Owner) :
TForm(Owner)
{
randomize();
}
//-------------------------------------------------------------------------HWND __fastcall TRidiciHlavniOkno::ZiskaniHandle()
{
HWND hwnd=FindWindow("TRizenaHlavniOkno","Řízená");
if(hwnd==NULL)
ShowMessage("Řízená aplikace není spuštěná");
return hwnd;
}
//-------------------------------------------------------------------------void __fastcall TRidiciHlavniOkno::bZmenaGriduClick(TObject *Sender)
{
HWND hwnd=ZiskaniHandle();
if(hwnd==NULL)
return;
try {
PostMessage(hwnd,WM_USER,StrToInt(ePocetSloupcu->Text),
StrToInt(ePocetRadku->Text));
}
188
catch (...) {
ShowMessage("Zkontrolujte zadání rozměrů mřížky");
}
}
//-------------------------------------------------------------------------void __fastcall TRidiciHlavniOkno::bZmenaRozmeruClick(TObject *Sender)
{
HWND hwnd=ZiskaniHandle();
if(hwnd==NULL)
return;
try {
PostMessage(hwnd,WM_ROZMERY,StrToInt(eVyskaOkna->Text),
StrToInt(eSirkaOkna->Text));
}
catch (...) {
ShowMessage("Zkontrolujte zadání rozměrů okna");
}
}
//-------------------------------------------------------------------------void __fastcall TRidiciHlavniOkno::bZaslaniCiselClick(TObject *Sender)
{
HWND hwnd=ZiskaniHandle();
if(hwnd==NULL)
return;
TMessage msg;
msg.Msg=WM_CISLA;
msg.WParamHi=random(65000); // vyšší dvoubyte z WParam
msg.WParamLo=random(65000); // nižší dvoubyte z WParam
msg.LParamHi=random(65000); // vyšší dvoubyte z LParam
msg.LParamLo=random(65000); // nižší dvoubyte z LParam
PostMessage(hwnd,WM_CISLA,msg.WParam,msg.LParam);
}
//-------------------------------------------------------------------------void __fastcall TRidiciHlavniOkno::bZaslaniFloatClick(TObject *Sender)
{
HWND hwnd=ZiskaniHandle();
if(hwnd==NULL)
return;
float pom=M_PI*(random(10)-5);
PostMessage(hwnd,WM_FLOAT,*((unsigned int*)(&pom)),0);
}
Poslední z příkladů zasílání zprávy demonstruje jak je možné přes
parametry WParam a LParam zasílat i např. čísla typu float.
Vezmeme adresu našich čtyř byte, ve kterých je uloženo číslo typu float,
přetypujeme tento pointer na pointer na unsigned int, což je datový typ
WParam a z této adresy vezmeme naše čtyři byte, které jsou teď ale
chápány jako unsigned int. To znamená, že získáme číslo, které je sice teď
nepoužitelné, ale po zpětném převedení na float opačným postupem
získáme na straně řízené aplikace opět naše původní číslo typu float.
189
#include <vcl.h>
#pragma hdrstop
#include "RizenaHlOkno.h"
#include "MojeZpravy.h"
//-------------------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TRizenaHlavniOkno *RizenaHlavniOkno;
//-------------------------------------------------------------------------__fastcall TRizenaHlavniOkno::TRizenaHlavniOkno(TComponent* Owner) :
TForm(Owner)
{
WindowProc=MojeWndProc;
// nastavím volání mé funkce pro obsluhu zpráv
}
//-------------------------------------------------------------------------void __fastcall TRizenaHlavniOkno::MojeWndProc(TMessage &Message)
{
switch(Message.Msg) {
case WM_USER:
StringGrid1->ColCount=Message.WParam;
StringGrid1->RowCount=Message.LParam;
break;
case WM_ROZMERY: Height=Message.WParam;
Width=Message.LParam;
break;
case WM_CISLA:
StringGrid1->Cells[1][1]=Message.WParamHi;
StringGrid1->Cells[2][1]=Message.WParamLo;
StringGrid1->Cells[3][1]=Message.LParamHi;
StringGrid1->Cells[4][1]=Message.LParamLo;
break;
case WM_FLOAT:
// WParam převedeme zpět na float
float pom=*((float*)(&Message.WParam));
StringGrid1->Cells[1][2]=FloatToStrF(pom,ffFixed,10,3);
}
WndProc(Message); // zavolám původní obsluhu zpráv,
// aby se provedla původní reakce na zprávy
}
Po spuštění obou aplikací budeme moci pomocí hodnot nastavených
v řídící aplikaci měnit v aplikaci řízené rozměry komponenty StringGrid,
roměry okna aplikace, zasílat čtyři náhodná celá čísla do prvního řádku
StringGridu a jeden náhodný násobek čísla Pi do první buňky druhého
řádku.
190
34
Tvorba potomků standardních komponent
Obsah hodiny
Ukážeme si vytváření našich vlastních komponent odvozených od komponent
Borlandu.
Cíl hodiny
Po této hodině budete schopni:
●
●
34.1
vytvářet jednoduché komponenty,
upravovat vlastnosti existujících komponent
Vytvoření třídy TMujForm jako potomka třídy TForm
Vytvoříme třídu formuláře, kterým na rozdíl od klasického budeme moci
pohybovat i uchopením za jeho klientskou plochu. Tento problém můžeme
řešit u každého formuláře, u kterého chceme toto chování, vždy znovu.
Lepší tedy bude vytvořit si potomka třídy TForm a formuláře aplikace pak
vytvářet jako potomky této třídy.
TMujForm.h
class TMujForm : public TForm
{
__published:
void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X,
int Y);
void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
private:
bool Stisk;
TPoint Minule;
public:
__fastcall TMujForm(TComponent* Owner);
};
TMujForm.cpp
__fastcall TMujForm::TMujForm(TComponent* Owner) : TForm(Owner)
{
Stisk=false;
}
//--------------------------------------------------------------------------void __fastcall TMujForm::FormMouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
Stisk=true;
Minule=ClientToScreen(TPoint(X,Y));
}
//---------------------------------------------------------------------------
191
void __fastcall TMujForm::FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y)
{
if(Stisk) {
TPoint Ted=ClientToScreen(TPoint(X,Y));
Left+=Ted.x-Minule.x;
Top+=Ted.y-Minule.y;
Minule=Ted;
}
}
//--------------------------------------------------------------------------void __fastcall TMujForm::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Stisk=false;
}
//---------------------------------------------------------------------------
Unit1.h
#ifndef Unit1H
#define Unit1H
//--------------------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <TMujForm.h>
//--------------------------------------------------------------------------class TForm1 : public TMujForm
{
__published: // IDE-managed Components
private:
// User declarations
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------------------------------------extern PACKAGE TForm1 *Form1;
//--------------------------------------------------------------------------#endif
Unit1.cpp
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//--------------------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TMujForm(Owner)
{
}
//---------------------------------------------------------------------------
Project1.cpp
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
192
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
34.2
Rozšíření komponenty o reakci na další událost
U komponenty TTrackBar nejsou uživateli přístupné události OnMouseUp
a OnMouseDown. Někdy by se nám ovšem tyto události mohly hodit pro
reagování na nastavenou hodnotu v okamžiku např. puštění tlačítka myši.
Podíváme-li se do deklarace třídy, pak zjistíme, že tyto události jsou
definovány už v předkovi, kterým je TControl, ale, bohužel, jako protected.
Nemůžeme se tedy na ně odvolávat při zakládání instance třídy TTrackBar.
Vytvoříme tedy potomka této třídy a tyto události v něm deklarujeme už jako
public. Více nemusíme dělat. Tuto naší komponentu si musíme ale při
použití deklarovat dynamicky sami.
Unit1.h
//-----------------------------------------------------------------------// deklaruji potomka komponenty TTrackBar
// který má navíc ošetření události OnMouseDown a OnMouseUp
class TMyTrackBar : public TTrackBar
{
public:
__fastcall TMyTrackBar(TComponent* AOwner) : TTrackBar(AOwner) {};
// v předkovi TControl jsou jako private, změním na public
// stačí uvést pouze __property a název vlastnosti
__property OnMouseDown;
__property OnMouseUp;
};
//-----------------------------------------------------------------------class TForm1 : public TForm
{
__published:
TMemo *Memo1;
TPanel *Panel1;
private:
void __fastcall MyTrackBarMouseUp(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall MyTrackBarMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y);
TMyTrackBar *MyTrackBar;
public:
__fastcall TForm1(TComponent* Owner);
};
extern PACKAGE TForm1 *Form1;
193
Unit1.cpp
TForm1 *Form1;
//-----------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
// musím si MyTrackBar vygenerovat dynamicky
MyTrackBar=new TMyTrackBar(this); // Owner bude tento formulář
MyTrackBar->Parent=Panel1; // nesmím zapomenout nastavit Parent
MyTrackBar->Left=5;
MyTrackBar->Top=30;
MyTrackBar->Width=200;
MyTrackBar->Max=100;
MyTrackBar->Position=20;
// nastavim funkce pro OnMouseUp a OnMouseDown
MyTrackBar->OnMouseUp=MyTrackBarMouseUp;
MyTrackBar->OnMouseDown=MyTrackBarMouseDown;
}
//----------------------------------------------------------------------void __fastcall TForm1::MyTrackBarMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
// Memo1->Lines->Add(MyTrackBar->Position);
}
//-----------------------------------------------------------------------void __fastcall TForm1::MyTrackBarMouseUp(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Memo1->Lines->Add(MyTrackBar->Position);
}
34.3
Vytvoření nové komponenty jako potomka TCustomControl
Při vytváření komponenty obecně si vždy nejdříve najdeme některou
komponentu standardní, která už má co nejvíce vlastností, které
potřebujeme, hotové. Nejčastěji budeme jako předka používat třídy
TControl, TCustomControl nebo TWinControl. Někde ve zdrojovém kódu
napíšeme název této třídy a klepneme na něj pravým tlačítkem myši.
V kontextovém menu pak zvolíme „Find Declaration“. Objeví se nám
deklarace této třídy, kterou si prostudujeme. Soustředíme se na metody,
které jsou deklarované jako virtuální, na události a podobně. Prostudujeme
stejně i jeho předky. Podle toho zjistíme, zda se nám komponenta hodí jako
předek naší třídy.
Demonstrujme si tvorbu naší komponenty na příkladu našeho tlačítka.
194
Tlačítko budeme deklarovat jako potomka třídy TCustomControl.
MujButton.h
//--------------------------------------------------------------------------#ifndef MujButtonH
#define MujButtonH
//--------------------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
//--------------------------------------------------------------------------class TMujButton : public TCustomControl
{
private:
TColor FBarva;
String FText;
TFont *FFont;
bool StiskKlavesy;
void __fastcall NastavBarvu(TColor AColor) {FBarva=AColor; Invalidate();}
void __fastcall NastavText(String AText) {FText=AText; Invalidate();}
void __fastcall NastavFont(TFont *AFont) {FFont->Assign(AFont);
Invalidate();}
protected:
virtual void __fastcall Paint();
DYNAMIC void __fastcall Click();
// DYNAMIC je totéž jako virtual
DYNAMIC void __fastcall DoEnter();
// virtual optimalizováno na rychlost,
// DYNAMIC na velikost
DYNAMIC void __fastcall DoExit();
// DYNAMIC se používá u metod,
// které se nepoužívají tak často
DYNAMIC void __fastcall KeyDown(Word &Key, Classes::TShiftState Shift);
DYNAMIC void __fastcall KeyUp(Word &Key, Classes::TShiftState Shift);
DYNAMIC void __fastcall MouseDown(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall MouseUp(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y);
public:
__fastcall TMujButton(TComponent* Owner);
__fastcall ~TMujButton();
// nové vlastnosti, které v předcích vůbec nejsou
__property TColor Color={read=FBarva,write=NastavBarvu};
__property String Caption={read=FText,write=NastavText};
__property TFont *Font={read=FFont,write=NastavFont};
__property
__property
__property
__property
__property
__property
__property
// vlastnosti, které v předcích existují, ale nejsou přístupné
OnClick;
// __property v TControl jako private, změním na public
OnEnter;
// __property v TWinControl protected, změním na public
OnExit;
OnMouseDown;// __property v TControl private,změním na public
OnMouseUp;
OnKeyDown; // __property v TWinControl protected,změním na public
OnKeyUp;
};
#endif
MujButton.cpp
//--------------------------------------------------------------------------#pragma hdrstop
#include "MujButton.h"
#pragma package(smart_init)
//--------------------------------------------------------------------------__fastcall TMujButton::TMujButton(TComponent* Owner) : TCustomControl(Owner)
{
Name="MujButton";
195
Caption="MujButton";
FFont=new TFont;
Top=20;
Left=20;
Width=75;
Height=45;
Color=clRed;
}
//--------------------------------------------------------------------------__fastcall TMujButton::~TMujButton()
{
delete FFont;
}
//--------------------------------------------------------------------------void __fastcall TMujButton::Paint()
{
TCustomControl::Paint();
// zavolal by se OnPaint, kdyby se zde používal
Canvas->Pen->Color=FFont->Color;
Canvas->Pen->Width=2;
Canvas->Brush->Color=FBarva;
Canvas->Ellipse(1,1,Width-1,Height-1);
Canvas->Font=FFont;
int v=Canvas->TextHeight(Caption);
int s=Canvas->TextWidth(Caption);
Canvas->TextOut((Width-s)/2,(Height-v)/2,Caption);
}
//--------------------------------------------------------------------------void __fastcall TMujButton::Click()
{
TControl::Click();
// zavolá případně OnClick
SetFocus();
// zavolá můj DoEnter
}
//--------------------------------------------------------------------------void __fastcall TMujButton::DoEnter()
{
TWinControl::DoEnter();
// zavolá případně OnEnter
TFontStyles st=Canvas->Font->Style;
st<<fsUnderline;
FFont->Style=st;
Invalidate();
}
//--------------------------------------------------------------------------void __fastcall TMujButton::DoExit()
{
TWinControl::DoExit();
// zavolá případně OnExit
TFontStyles st=Canvas->Font->Style;
st>>fsUnderline;
FFont->Style=st;
Invalidate();
}
//--------------------------------------------------------------------------void __fastcall TMujButton::MouseDown(TMouseButton Button, Classes::TShiftState
Shift, int X, int Y)
{
TControl::MouseDown(Button,Shift,X,Y);
// zavolá případně OnMouseDown
Left+=1;
Top+=1;
Invalidate();
}
//--------------------------------------------------------------------------void __fastcall TMujButton::MouseUp(TMouseButton Button, Classes::TShiftState
Shift, int X, int Y)
{
TControl::MouseUp(Button,Shift,X,Y);
// zavolá případně OnMoseUp
Left-=1;
Top-=1;
Invalidate();
}
//--------------------------------------------------------------------------void __fastcall TMujButton::KeyDown(Word &Key, Classes::TShiftState Shift)
196
{
TWinControl::KeyDown(Key,Shift);
if(Key==VK_RETURN || Key==' ') {
if(!StiskKlavesy) {
StiskKlavesy=true;
Left+=1;
Top+=1;
Invalidate();
}
}
// zavolá případně OnKeyDown
}
//--------------------------------------------------------------------------void __fastcall TMujButton::KeyUp(Word &Key, Classes::TShiftState Shift)
{
TWinControl::KeyUp(Key,Shift);
// zavolá případně OnKeyUp
if(StiskKlavesy) {
StiskKlavesy=false;
Left-=1;
Top-=1;
Invalidate();
}
}
HlOkno.h
//--------------------------------------------------------------------------#ifndef HlOknoH
#define HlOknoH
//--------------------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include "MujButton.h"
//--------------------------------------------------------------------------class TForm1 : public TForm
{
__published:
TButton *Button1;
TButton *Button2;
private:
TMujButton *MujButton1;
void __fastcall MujButton1Click(TObject *Sender);
void __fastcall MujButton1KeyDown(TObject *Sender, WORD &Key,
TShiftState Shift);
public:
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------------------------------------extern PACKAGE TForm1 *Form1;
//--------------------------------------------------------------------------#endif
HlOkno.cpp
TForm1 *Form1;
//--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
MujButton1=new TMujButton(this);
MujButton1->Parent=this;
MujButton1->Name="MujButton1";
MujButton1->Color=clGreen;
MujButton1->Caption="Tlačítko";
MujButton1->TabStop=true;
MujButton1->TabOrder=2;
MujButton1->Font->Size=10;
TFontStyles st;
st<<fsBold;
MujButton1->Font->Style=st;
MujButton1->Width=100;
197
MujButton1->Hint="Moje tlacitko";
MujButton1->ShowHint=true;
MujButton1->OnClick=MujButton1Click;
MujButton1->OnMouseDown=MujButton1MouseDown;
MujButton1->OnKeyDown=MujButton1KeyDown;
}
//--------------------------------------------------------------------------void __fastcall TForm1::MujButton1Click(TObject *Sender)
{
Beep(400,200);
}
//--------------------------------------------------------------------------void __fastcall TForm1::MujButton1KeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{
Beep(1000,300);
}
34.4
Vytvoření nové komponenty a její zveřejnění v Tool Palette
Tato látka již přesahuje rozsah této učebnice.
Podle názoru autora je ovšem s používáním a instalováním dalších a
dalších komponent postupovat opatrně. Jestliže tvoříme nějaký program
s nestandardními komponentami můžeme se dočkat různých potíží při
přenosu vývoje na nový počítač nebo při přeinstalování vývojového
prostředí.
Může se klidně stát, že najednou zjistíme, že náš program se odvolává na
nějakou komponentu, na kterou jsme již dávno zapomněli a ani jsme
netušili, že ji máme nainstalovanou. Dokonce se nám může stát (stalo se
často i autorovi), že už jsme dávno zapomněli odkud jsme ji vůbec vzali.
Z toho hlediska se autorovi jeví jako výhodnější uchovávat naše
komponenty ve zdrojovém tvaru a vždy je udělat součástí projektu. Toto
jsme si předvedli na předcházejících příkladech.
Nevýhodou tohoto postupu je ovšem to, že se připravíme o jednoduchý
návrh formuláře pouhým kladením komponent na její plochu. Na druhé
straně ale získáme jistotu, že vše potřebné budeme mít i po létech vždy po
ruce.
Na závěr je rovněž nutno připomenout, že školní verze vývojového
prostředí má instalaci nových komponent stejně zablokovanou.
198
35
“Vychytávky“
Obsah hodiny
Zde uvedeme některé programátorské postupy, které se nám mohou hodit.
Zároveň si ukážeme, jak lze v našem programu používat funkce WIN32 API.
35.1
Pouze jedno spuštění aplikace
// úprava klasického hlavního souboru aplikace navrženého pomocí IDE
#include <vcl.h>
#pragma hdrstop
//--------------------------------------------------------------------------USEFORM("Unit1.cpp", MujFormular);
//--------------------------------------------------------------------------WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
// „TMujFormular“ – název třídy mého okna,
// „Můj formulář“ – začátek Caption mého formuláře
HWND hwnd=FindWindow("TMujFormular","Muj Formular");
// okno se nenašlo, aplikace tedy ještě neběží
if(hwnd==NULL) {
Application->Initialize();
Application->CreateForm(__classid(TMujFormular), &Můjformulář);
Application->Run();
}
else {
// okno se našlo, aplikace tedy už běží
// pokud bylo neviditelné, tak ho zobrazím
ShowWindow(hwnd,SW_RESTORE);
// pokud bylo na pozadí, převedu ho do popředí
SetForegroundWindow(hwnd);
}
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
35.2
Omezení změny velikosti formuláře
// obsluha události OnCanResize našeho formuláře
// okno nelze zmenšit pod zadané meze
// a nelze ho také zvětšit nad zadané meze
void __fastcall TForm1::FormCanResize(TObject *Sender, int &NewWidth,
int &NewHeight, bool &Resize)
{
if(NewWidth>500 || NewWidth<200 || NewHeight>500 || NewHeight<200)
199
Resize=false;
else
Resize=true;
}
35.3
Nastaveni velikosti formuláře na celou plochu
// příklad použití instance třídy TScreen
void __fastcall TForm1::FormShow(TObject *Sender)
{
// použijeme instanci Screen třídy TScreen popisující vlastnosti obrazovky
Width=Screen->WorkAreaWidth;
// podle rozměrů primárního monitoru
Height=Screen->WorkAreaHeight;
Top=Screen->WorkAreaTop;
Left=Screen->WorkAreaLeft;
}
35.4
Tisk obsahu formuláře
// tisk klientské části formuláře
if(PrinterSetupDialog1->Execute())
Form1->Print();
35.5
Vytvoření formuláře s průhledným otvorem
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
TransparentColor=true;
// nastavíme vlastnosti formuláře
TransparentColorValue=clRed;
// zvolíme průhlednou barvu
}
//--------------------------------------------------------void __fastcall TForm1::FormPaint(TObject *Sender)
{
Canvas->Brush->Color=clRed;
// vytvoříme otvor barvy
Canvas->Pen->Color=clRed;
// která je uvedena jako průhledná
Canvas->Ellipse(50,50,300,300);
}
35.6
Vytvoření formuláře vlastního tvaru podle bitmapy
Využijeme průhledných barev ve formuláři stejně jako v minulém příkladu. Dále
nastavíme náš formulář na „bez okrajů“. Protože formulář nebude mít rámeček,
budeme muset formulář posouvat myší uchopením za jeho klientskou plochu. Za
tím účelem si vytvoříme obsluhu zprávy WM_NCHITTEST, která se používá jako
200
hlášení formuláři, že na jeho ploše došlo k nějaké „myší“ události. Hlášení, že
došlo ke stisku levého tlačítka na klientské ploše, změníme na hlášení, že došlo ke
stisku na horní liště. Tím můžeme formulář přesouvat.
class TForm1 : public TForm
{
__published:
TButton *bKonec;
TButton *bMinimalizuj;
void __fastcall FormPaint(TObject *Sender);
void __fastcall bKonecClick(TObject *Sender);
void __fastcall bMinimalizujClick(TObject *Sender);
private:
Graphics::TBitmap *Maska;
void __fastcall MojeWndProc(TMessage &Message);
public:
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();
};
//--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
Maska=new Graphics::TBitmap;
Maska->LoadFromFile("maska.bmp");
// načtu masku pro tvar okna
TransparentColor=true;
TransparentColorValue=clFuchsia;
// můžeme uvést i takto 0x00FF00FF
// průhlednou barvu nesmíme použít ani v komponentách, byly by z nich díry
BorderStyle=bsNone;
WindowProc=MojeWndProc;
// nastavíme WindowProc na svou funkci
}
//--------------------------------------------------------------------------__fastcall TForm1::~TForm1()
{
delete Maska;
}
//--------------------------------------------------------------------------void __fastcall TForm1::FormPaint(TObject *Sender)
{
// v OnPaint formuláře musíme náplň pozadí klientské plochy překreslit
Canvas->StretchDraw(Rect(0,0,ClientWidth,ClientHeight),Maska);
}
//--------------------------------------------------------------------------void __fastcall TForm1::bKonecClick(TObject *Sender)
{
Close();
// zavřeme hlavní okno a tím ukončíme i aplikaci
}
//--------------------------------------------------------------------------void __fastcall TForm1::bMinimalizujClick(TObject *Sender)
{
//zasláním zprávy WM_SYSCOMMAND s SC_MINIMIZE sámi sobě minimalizujeme formulář
PostMessage(Handle,WM_SYSCOMMAND,SC_MINIMIZE,0);
}
//--------------------------------------------------------------------------void __fastcall TForm1::MojeWndProc(TMessage &Message)
{
switch(Message.Msg)
{
case WM_NCHITTEST:
// zpráva od myši, viz. help nebo internet
TForm::WndProc(Message);
// zavolám funkci předka
if((Message.Result == HTCLIENT) &&
// stisk v klientské oblasti
(GetAsyncKeyState(MK_LBUTTON) < 0)) // stisknuto levé tlačítko
{
Message.Result=HTCAPTION;
// "předstíráme" stisk v horní liště
return;
}
break;
}
201
TForm::WndProc(Message);
// zavolam funkci předka
}
V nějakém grafickém editoru vytvoříme obraz masky, která bude určovat tvar
našeho formuláře. Mohla by vypadat třeba takto:
Fialová barva bude průhledná.
Výsledný vzhled formuláře:
35.7
Vytvoření eliptického formuláře regiónem
Pro vytváření formulářů s netradičním tvarem lze také využít tzv. regiónů. Pro
práci s regióny existuje několik funkcí z WIM32 API.
Zde máme příklad využití regiónů pro změnění tvaru klasického obdélníkového
formuláře.
Zároveň je zde ukázáno jiné řešení, jak formulářem pohybovat uchopením za jeho
plochu (červenou) pomocí ošetření událostí OnMouseDown, OnMouseMove a
202
OnMouseUp. Toto řešení jsme si ukázali už v minulé kapitole při tvorbě našeho
potomka třídy TForm.
Návrh formuláře ve vývojovém prostředí:
Vzhled formuláře po spuštění:
class TForm1 : public TForm
{
__published:
TPanel *Panel1;
TButton *Button1;
TLabel *Label1;
void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y);
void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall Button1Click(TObject *Sender);
private:
bool Stisk;
TPoint Minule;
public:
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
HRGN rgn;
BorderStyle=bsNone;
// formulář bude bez okrajů
// vytvoříme eliptický region o velikosti klientské části formuláře
rgn=CreateEllipticRgn(0,0,Width,Height); // vytrvoříme elipt. región
SetWindowRgn(Handle,rgn,true);
// přiřadíme región našemu formuláři
DeleteObject(rgn);
// už region nepotřebuji
}
//--------------------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender)
{
Close();
// zavřením hlavního formuláře končí i aplikace
}
//--------------------------------------------------------------------------// protože okno není za co uchopit, budu ho posouvat zachycením plochy okna
203
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Stisk=true;
// přepočtu souřadnice myši z fomuláře na polohu na obrazovce
Minule=ClientToScreen(TPoint(X,Y));
}
//--------------------------------------------------------------------------void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y)
{
if(Stisk) {
TPoint Ted=ClientToScreen(TPoint(X,Y));
Left+=Ted.x-Minule.x;
// o posunutí myši změníme polohu formuláře
Top+=Ted.y-Minule.y;
Minule=Ted;
}
}
//--------------------------------------------------------------------------void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Stisk=false;
}
35.8
Vytvoření mnohoúhelníkového formuláře regiónem
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
HRGN rgn;
BorderStyle=bsNone;
Width=200;
Height=100;
TPoint body[5]={TPoint(0,0),TPoint(150,0),TPoint(200,100),
TPoint(50,100),TPoint(0,0)};
rgn=CreatePolygonRgn(body,5,ALTERNATE);
SetWindowRgn(Handle,rgn,true);
DeleteObject(rgn);
}
35.9
Vytvoření eliptického panelu pomocí regiónu
Region lze použít i na tvarování některých komponent s Handla. Klasický tvar:
204
Nový tvar:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
HRGN rgn;
rgn=CreateEllipticRgn(0,0,Panel1->Width,Panel1->Height);
SetWindowRgn(Panel1->Handle,rgn,true);
DeleteObject(rgn);
// už region nepotřebuji
}
Pro práci s regiony existují ještě další funkce WIN32 API. Např.
CreatePolyPolygonRgn
CreateRectRgn
SetRectRgn
CreateRoundRectRgn
CombineRgn
Další podrobností naleznete v případě zájmu na internetu:
http://msdn.microsoft.com/en-us/library/dd162913(v=vs.85)
205
Použitá literatura a jiné zdroje
[1]
www.cplusplus.com
[2]
Kadlec V.; Učíme se programovat v Borland C++ Builder a jazyce C++;
Computer Press; 2002
[3]
Matoušek David; C++ Builder, vývojové prostředí, Nakladatelství BEN,
1999
[4]
Holan T., Neruda R.; C++ Builder v příkladech; Nakladatelství Ben, 2002
[5]
Svoboda L., Voneš P.; 1001 Tipů a triků pro Delphi; Computer Press; 2002
[6]
Simon R., Gouker M., Barnes B.; Win32 API 1, 2, 3; Unis publishing; 1997
[7]
Reisdorph K.; C++ Builder 3 in 14 Days; Borland Press; 1998

Podobné dokumenty

Počítačové modely pro výuku elektroniky

Počítačové modely pro výuku elektroniky prvkům. Programy byly vytvořeny za účelem počítačové podpory výuky digitální elektroniky na katedře obecné fyziky a byly úspěšně zařazeny do několika předmětů. Práce rigorózní se zabývala tvorbou m...

Více

C++ Builder 4.0 - Tvorba software

C++ Builder 4.0 - Tvorba software Následuje deklarace ukazatele na instanci třídy TForm1, která v programu reprezentuje hlavní okno aplikace. Zbytek není nic jiného než deklarace konstruktoru třídy TForm1. Jediná věc, kterou tento ...

Více

1. Seznámení s C++ Builderem

1. Seznámení s C++ Builderem zobrazený v této komponentě byl „Edit1“, je to hodnota vlastnosti Text editačního ovladače. Zrušíme hodnotu této vlastnosti a v ovladači nebude nic zobrazeno. 17. V naši aplikaci budeme zjišťovat p...

Více

Bakalářská práce - Vysoké učení technické v Brně

Bakalářská práce - Vysoké učení technické v Brně 1. Autor touto smlouvou poskytuje nabyvateli oprávnění (licenci) k výkonu práva uvedené dílo nevýdělečně užít, archivovat a zpřístupnit ke studijním, výukovým a výzkumným účelům včetně pořizovaní v...

Více

Stáhnout materiál Úvod do Deplhi

Stáhnout materiál Úvod do Deplhi Vlevo – inspektor objektů , vpravo navrhář formuláře a editor kódu. V novějších verzích Delphi bývají další okna. (Project Manager. Object Tree View…) Inspektor objektů Obsahuje dvě záložky – Prope...

Více

Programování 3

Programování 3 Velká písmena označují neterminály –v programu rekurzivní procedury, malá písmena jsou znaky –terminály. Postup: pomocí syntaktických diagramů odvodíme vývojový diagram a přepíšeme ho do programova...

Více

Programování v jazyce C++ pro 4. ročník

Programování v jazyce C++ pro 4. ročník Šablona: III/2 - Inovace a zkvalitnění výuky prostřednictvím ICT Sada: VY_32_INOVACE_EL_10 - Programování v jazyce C++ pro 4. ročník DUM: VY_32_INOVACE_EL_10_04 Anotace: Postup vytvoření aplikace n...

Více

Počítače a programování 2 - UTEE

Počítače a programování 2 - UTEE Poté co se v našem kursu podrobně seznámíme s programováním v jazyce C, přejdeme k programu Matlab. Matlab je speciální matematický program pro inženýrské a vědecké výpočty. Většina matematických o...

Více

Diagnostika a léčba komunitní pneumonie dospělých

Diagnostika a léčba komunitní pneumonie dospělých močoviny použivá věkový parametr. Hodnotou rizika je věk nad 65 let. Tento systém je standardně používán v německých nemocnicích napojených na síť CAPNETZ. Systém PSI Americké hrudní společnosti (A...

Více

Cvicen´ı 8: Delphi - unita Graph2d, propojen´ı formulár˚u

Cvicen´ı 8: Delphi - unita Graph2d, propojen´ı formulár˚u • nakopı́rujte soubory Graph2d.pas a Graph2d.dfm do adresáře, kde máme uložen náš program • pomocı́ Project - Add To Project přidáme novou unitu Graph2d.pas do našeho projektu • v oblasti ...

Více