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
#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 #include #include <StdCtrls.hpp> #include //----------------------------------------------------------------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 #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<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 #include #include <StdCtrls.hpp> #include #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 #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 #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 #include #include <StdCtrls.hpp> #include //----------------------------------------------------------------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 #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 #include #include <StdCtrls.hpp> #include //----------------------------------------------------------------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 #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<>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<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 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;yHeight for(int x=0;xWidth) 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(ClientWidthPen->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<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 . 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 #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 #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 #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 #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 #include #include <StdCtrls.hpp> #include #include //--------------------------------------------------------------------------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 #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 #include #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<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 #include #include <StdCtrls.hpp> #include #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<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 #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