JON BATES TIM TOMPKINS POZNAJ VISUAL C++ 6 Przekład z języka angielskiego: Marek Korbecki (1-17), Sławomir Dzieniszewski (18-28, słowniczek) Największemu i najlepszemu ze wszystkich programów, genetycznemu kodowi życia - DNA. O M 1 KO~M© Polska wersja okładki: Grzegorz Ławniczak Redakcja: Justyna Domasłowska-Szulc Skład komputerowy: Małgorzata Dąbkowska-Kowalik O au' Kolejna książka z serii „Poznaj..." dotyczy programu Visual C++ 6, najlepszej, jak dotąd, wersji kompilatora C/C++ autorstwa firmy Microsoft. Wpr` Książka jest zbiorem użytecznych informacji na temat programowania ~" w w języku C++, z którego można korzystać zarówno wybiórczo, jak C, i systematycznie. Można się z niej nauczyć wielu nowych funkcji, takich jak tworzenie aplikacji "' SDI i MDI, użycie wskaźnika postępu, korzystania z list, drzew, opcji Częśt formatowania i podglądu HTML, tworzenie, importowanie i edytowanie ikon oraz rysunków bitmapowych. Rozd Książka powstała w oparciu o wieloletnie doświadczenie autorów, którzy w najbardziej przystępny sposób wyjaśniają i opisują na konkretnych przykładach wszystkie zagadnienia związane z tworzeniem nowoczesnych, wielodokumentowych aplikacji. Książka jest adresowana do użytkowników, którzy mają już pewną wiedzę w zakresie programowania w tym języku. Authorized translation from English language edition published by QUE CORPORATION Copyright © 1999. All rights reserved. No part of this book may be reproduced or transmitted in any form or any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Polish language edition published by Wydawnictwo MIKOM, Copyright © 1999. Tytuł oryginału: Using Visual C++ 6. Wszystkie prawa zastrzeżone. Reprodukcja bez zezwolenia zabroniona. Wydawca nie ponosi odpowiedzialności za ewentualne szkody powstałe przy wykorzystaniu zawartych w książce informacji. Wydawca: MIKOM, ul. Andrzejowska 3, 02-312 Warszawa, tel. 823-70-77 Rozd; Pr Druk: Wyd. Archidiecezji Warszawskiej, ul. Rybnicka 27, 02-405 Warszawa. pl ISBN 83-7158-190-4 Warszawa, sierpień 1999 Spis treści O autorach .................................................12 Wprowadzenie...........................................15 Czym jest Visual C++ 6.0 i dlaczego warto go używać?.................................. 15 Cóż nowego jest w tej książce?............. 15 Czy to jest odpowiednia książka? ......... 16 Konwencje zastosowane w książce....... 16 Część I. Tworzenie aplikacji Visual C++ 17 Rozdział 1. Projektowanie i tworzenie programu w Visual C++..............19 Uruchomienie Visual C++ .................... 19 Tworzenie nowego projektu.................. 20 Wybór rodzaju projektu ....................... .21 Nadawanie nazwy projektowi i wybór lokalizacj i ............................................. . 21 Używanie AppWizard........................... 21 Korzystanie z podstawowych opcji AppWizard............................................ 22 Generowanie i uruchamianie aplikacji.. 24 Wybór konfiguracji procesu generowania pliku................................ .24 Przeprowadzanie procesów kompilacji i łączenia ............................................... 24 Uruchomienie aplikacji......................... 25 Zgodność z interfejsem Windows......... 26 Modyfikacja interfejsu aplikacji ........... 26 Umieszczanie przycisków sterujących..26 Dołączanie kodu.................................... 30 Sprawdzenie zmian wprowadzonych do aplikacji............................................ 32 Zapisanie i zamknięcie projektu............ 34 Rozdział 2. Środowisko programistyczne 35 Praca z Developer Studio ...................... 35 Otwieranie istniejącego projektu........... 35 Okno obszaru roboczego projektu ........ 37 Korzystanie z panelu C1assView........... 38 Ikona ..................................................... 39 Znaczenie .............................................. 39 Używanie panelu ResourceView...........46 Używanie panelu FileView ...................51 Zarządzanie projektami ......................... 53 Konfigurowanie projektu.......................54 Dodatkowe możliwości konfiguracyjne54 Część II. Okna dialogowe i kontrolki.......55 Rozdział 3. Projektowanie i tworcenie okien dialogowych.................................... 57 Tworzenie szablonu okna dialogowego 58 Przydzielanie identyfikatora oknu dialogowemu .........................................62 Korzystanie z karty Dialog General Properties ...............................................63 Nadawanie stylu oknu dialogowemu.....64 Dodawanie i pozycjonowanie kontrolek. 65 Zmiana rozmiarów kontrolek ................70 Jednoczesne zaznaczanie kilku obiektów .71 Wyrównywanie pozycji obiektów.........72 Użycie prowadnic..................................73 Organizacja kontrolek w oknie dialogowym ........................................... 74 Grupowanie obiektów ...........................74 Ustalenie porządku dostępu do okien przy użyciu klawisza Tab ......................76 Ustalanie skrótów klawiaturowych .......78 Rozdział 4. Używanie przycisków sterujących..................................... 79 Użycie przycisków ................................ 79 Dodawanie funkcji obsługi zdarzenia użycia przycisku ................................... .81 Istota map komunikatów ...................... .82 Modyfikacja przycisków w czasie pracy programu ............................................... 84 Użycie przełączników............................ 90 Dodawanie grupy przełączników .......... 91 Określenie wybranego przycisku .......... 93 Użycie pól wyboru................................. 96 Dodawanie pól wyboru.......................... 96 Pobieranie i ustalanie stanu pól wyboru.... 97 -W _ , . _.. _~. ... ... ~ ... ._ _ _ ... ~ ~ ..._ .,. _ 1~ I 4 Spis treści Sl Rozdział 5. Stosowanie kontrolek tekstowych ........................,..........101 Stosowanie statycznych pól tekstowych 101 Formatowanie tekstów w oknach dialogowych........................................102 Łączenie statycznych pól tekstowych z polami edycji....................................102 Zmiana zawartości statycznego pola tekstowego w czasie pracy programu . 103 Umieszczanie statycznych pól tekstowych w oknie dialogowym...........................103 Używanie pól edycji............................ 108 Umieszczanie pól edycji .....................109 Umieszczanie i pobieranie tekstu z pola edycji ........................................111 Ustalanie reakcji na komunikaty powiadamiające, dotyczące pól edycji..112 Wyprowadzanie podklas dla pól edycji 116 Stosowanie wieloliniowych pól edycji 122 Stosowanie pasków przewijania..........155 Dodawanie pasków przewijania do okna dialogowego ........................................155 Przypisanie zmiennych paskom przewijania ..........................................156 Inicjalizowanie pasków przewijania....157 Obsługa powiadomień pasków przewijania ..........................................160 Stosowanie suwaków kontrolnych ......164 Umieszczenie suwaka kontrolnego w oknie dialogowym ...........................164 Prcypisanie zmiennej obiektowi suwaka.166 Inicjalizacja obiektu suwaka.............:..166 Odpowiedzi na powiadomienia obiektu suwaka .................................................168 Stosowanie próbników daty i godziny.169 Umieszczanie próbników daty i godziny w oknie dialogowym ...........................169 Przypisywanie zmiennych do obiektów próbników daty i godziny....................171 Inicjalizowanie próbników daty i godziny ..............................................172 Powiadomienia o zmianie daty............176 Stosowanie kalendarza miesięcznego..178 Umieszczenie kalendarza miesięcznego w oknie dialogowym ............................178 Przypisanie zmiennej do obiektu kalendarza............................................179 Inicjalizacja obiektu kalendarza ..........180 Ustalanie zakresu dostępnych dat w kalendarzu........................................181 Reakcje na powiadomienia o zmianie wyboru daty ........................183 Ro; Rozdział 6: Używanie list........................ 123 Tworzenie list...................................... 123 Dodawanie list kombinowanych (rozwijanych) ...................................... 124 Dodawanie list drzewiastych .............. 126 Wykorzystanie pól listy ...................... 128 Użycie list szczegółowych.................. 129 Zapełnianie list.................................... 131 Umieszczanie elementów na liście kombinowanej ..................................... 132 Reakcja programu na komunikaty powiadamiające listy kombinowanej.. 135 Wypełnianie listy drzewiastej ............. 136 Wypełnianie pola listy ........................ 139 Reakcja programu na komunikaty powiadamiające pola listy................... 141 Wypełnianie listy szczegółowej.......... 143 Rozdział 7. Stosowanie wskaźników postępu, pasków prcewijania, suwaków oraz obiektów pobierających datę i godzinę ..... 149 Obiekty zorientowane na zakres wartości 149 Użycie wskaźnika postępu .................. 149 Umieszczenie wskaźnika postępu w oknie dialogowym........................... 150 Przypisanie zmiennej do wskaźnika postępu ................................................ 152 Manipulowanie i zmiany wskazań wskaźnika postępu .............................. 153 Rozdział 8. Obsługa zdarceń wywoływanych myszą.................185 Obsługa powiadomień klawiszy myszy 185 Obsługa zdarzeń naciśnięcia i zwolnienia klawisza myszy ...............186 Przechwytywanie zdarzeń dwukrotnego kliknięcia .............................................191 Śledzenie ruchów i położenia wskaźnika myszy...................................................192 Obsługa zdarzenia przesunięcia wskaźnika myszy.................................192 Przechwytywanie wskaźnika myszy ...196 Ustalanie obszaru testowania ..............198 Stosowanie klasy CRectTracker..........199 Roz Spis treści 5 Rozdział 9. Stosowanie kontrolek ActiveX ........................................ 205 Wybór kontrolek ActiveX z galerii Component Gallery ............................. 206 Przeglądanie kontrolek ActiveX ......... 206 Włączanie nowego obiektu do bieżącego projektu ..........,.................................... 207 Wybór, skalowanie i testowanie kontrolek ActiveX z paska narzędziowego Controls ............................................... 209 Zmiana właściwości kontrolki w edytorze zasobów ............................ 211 Ustalanie właściwości standardowych 211 Korzystanie z kart właściwości........... 211 Stosowanie klas dostarczonych z kontrolką........................................... 212 Dodanie zmiennej składowej dla kontrolki .............................................. 213 Programowe pobieranie i ustalanie właściwości kontrolki ......................... 213 Implementacja funkcji obsługi zdarzeń ActiveX za pomocą C1assWizard ....... 216 Rozdział 10. Tworcenie własnych okien dialogowych i ich klas................. 221 Tworzenie klasy okna dialogowego.... 221 Dodawanie szablonu nowego okna dialogowego ....................................... . 222 Wyprowadzanie klas z klasy CDialog za pomocą C1assWizard......................223 Inicjalizowanie nowej klasy okna dialogowego........................................ 225 Wyświetlanie modalnych okien dialogowych........................................ 226 Dodawanie zmiennej składowej dla zachowywania danych z okna dialogowego........................................ 227 Wymiana i zatwierdzanie danych z okien dialogowych ........................... 229 Stosowanie funkcji Data Exchange (DDX) ................................................. 230 Stosowanie funkcji Data Validation (DDV) ................................................ . 232 Tworzenie własnych funkcji zatwierdzających................................ . 234 Używanie niemodalnych okien dialogowych ........................................ 235 Tworzenie i usuwanie niemodalnego okna dialogowego .............................. . 236 Przesyłanie i pobieranie danych z niemodalnego okna dialogowego .....239 Obsługa komunikatu o zamknięciu niemodalnego okna dialogowego........242 Usunięcie przycisku zamykającego ......243 Część III. Elementy aplikacji ................245 Rozdział 11. Praca z rysunkami, bitmapami i ikonami...................247 Stosowanie edytora graficznego .......... 248 Tworzenie i edycja ikon ......................250 Modyfikacja domyślnej ikony MFC....250 Umieszczanie nowego zasobu ikony...251 Umieszczanie nowego zasobu bitmapy.252 Ustalanie rozmiarów i liczby kolorów dla bitmapy ..........................................253 Importowanie rysunków......................254 Stosowanie rysunków w oknach dialogowych......................................... 255 Ustalanie właściwości graficznego kontrolki ..............................................256 Ładowanie zasobów graficznych podczas pracy programu............,.......................257 Tworzenie graficznych przycisków..... 261 Tworzenie bitmap dla stanów przycisku.262 Użycie klasy przycisku graficznego....264 Używanie rysunków w obiektach sterujących ........................................... 265 Istota list obrazów................................265 Tworzenie i stosowanie list obrazów...268 Rozdział 12. Dokumenty, widoki, ramki oraz ich stosowanie..................... 273 Tworzenie aplikacji SDI...................... 274 Klasy aplikacji SDL............................ .278 Elementy wizualne aplikacji SDI ....... .280 Istota szablonów dokumentów SDI..... 282 Stosowanie funkcji osnowy w architekturze dokument/widok ........... .284 Używanie dokumentów i widoków ..... 289 Inicjalizacja danych dokumentu......... .290 Dodawanie do dokumentu zmiennych składowych ......................................... .291 Dostęp widoku do danych dokumentu. .292 Używanie standardowych zasobów szablonu.............................................. .294 Odświeżanie widoku .......................:.. .296 6 Spis treści Rozdział 13. Praca z menu ..................... 299 Tworzenie i edycja zasobów menu ..... 299 Dodawanie nowych zasobów menu....300 Dodawanie do menu pozycji nagłówków ..........................................301 Dodawanie pozycji menu....................302 Przypisywanie identyfikatorów do poleceń ........................................... 303 Zmiany właściwości pozycji menu.....303 Umieszczanie separatorów.................. 304 Tworzenie pozycji menu podrzędnego. 304 Umieszczanie znaczników ..................305 Umieszczanie skrótów klawiaturowych305 Obsługa poleceń menu ........................ 306 Dodawanie funkcji obsługujących polecenia menu ................................... 306 Dodawanie funkcji obsługi poleceń interfejsu użytkownika........................308 Aktywowanie i dezaktywowanie pozycji menu....................................................309 Umieszczanie i usuwanie znaczników 309 Dynamiczna zmiana treści pozycji menu.................................................... 310 Dodawanie menu skrótów................... 311 Uruchamianie menu skrótów .............. 31 I Obsługa poleceń menu skrótów .......... 315 Tworzenie i dostęp do obiektów menu 316 Inicjalizacja obiektu CMenu ............... 316 Dynamiczne dodawanie pozycji menu . 317 Dynamiczne modyfikowanie pozycji menu....................................................320 Dynamiczne usuwanie pozycji menu..321 Rozdział 14. Praca z paskami narcędzi i stanu........................................... 323 Dostosowywanie standardowych pasków narzędziowych..................................... 323 Istota paska narzędziowego ................324 Umieszczanie nowych przycisków na pasku narzędziowym za pomocą edytora zasobów..................................329 Przemieszczanie i usuwanie przycisków oraz dodawanie separatorów............... 331 Aktywacja i dezaktywacja przycisków paska narzędziowego ..........................331 Dodawanie własnych pasków narzędziowych..................................... 332 Umieszczanie paska narzędziowego w oknie głównym................................333 Ukrywanie i wyświetlanie paska narzędziowego.....................................334 Lokalizowanie paska narzędziowego z aplikacji.............................................335 Zachowywanie i załadowywanie pozycji paska narzędziowego...........................335 Stosowanie pasków dialogowych........336 Dodawanie zasobu paska dialogowego.337 Przyłączenie paska dialogowego do okna głównego ....................................338 Obsługa kontrolek pasków dialogowych 340 Dostosowywanie paska stanu ..............342 Istota standardowego paska stanu .......343 Dodawanie indykatorów i separatorów .......................................345 Dynamiczne zmiany wymiarów, stylu i tekstu w panelach ..............................349 Istota pasków kontenerowych w programie Internet Explorer ............ 353 Tworzenie paska kontenerowego za pomocą AppWizard.............................354 Nadawanie tytułu i przydzielanie bitmapy tła paska kontenerowego .......356 Część IV. Tworzenie grafiki....................357 Rozdział 15. Rysowanie w kontekście urządzenia....................................359 Wprowadzenie do pojęcia kontekstu urządzenia............................................ 359 Typy kontekstów urządzenia.............. .360 Stosowanie klasy CDC ....................... .360 Stosowanie kontekstu urządzenia Client .................................................. .366 Stosowanie kontekstu urządzenia Paint . .367 Stosowanie kontekstu urządzenia pamięci ............................................... .372 Stosowanie trybów odwzorowania...... 375 Tryby odwzorowania swobodnego skalowania.......................................... .379 Badanie możliwości urządzenia......... .381 Rozdział 16. Stosowanie piór i pędzli.....387 Tworzenie piór.....................................387 Stosowanie klasy CPen........................387 Zmiana szerokości pióra......................388 Zmiana koloru pióra ............................388 Użycie stosu piór.................................390 Wybór pióra do kontekstu urządzenia...390 Usuwanie piór......................................392 Spi Roz Spis treści 7 Rysowanie linii i kształtów za pomocą pióra..................................................... 393 Tworzenie kontekstu urządzenia do rysowania ............................................394 Zmiana położenia pióra....................... 394 Rysowanie linii ...................................396 Rysowanie w oparciu o współrzędne.. 398 Rysowanie okręgów i elips ................. 400 Rysowanie krzywych..........................402 Rysowanie wielokątów .......................404 Tworzenie pędzli................................. 405 Stosowanie klasy CBrush.................... 405 Tworzenie pędzli rysujących kolorem pełnym i wzorem kreskowanym ......... 406 Zmiana tła okna................................... 406 Tworzenie pędzli z rysunków i wzorów.............................................. 408 Używanie stosu pędzli ........................ 410 Wybieranie pędzla do kontekstu urządzenia ........................................... 412 Usuwanie pędzli.................................. 413 Malowanie wypełnionych figur za pomocą Pędzli ..................................... 413 Rysowanie prostokątów i prostokątów z zaokrąglonymi narożnikami............. 413 Rysowanie elips i okręgów ................. 415 Rysowanie cięciw ................:.............. 415 Rysowanie wielokątów ....................... 416 Rozdział 17. Korzystanie z czcionek...... 421 Funkcje rysujące tekst......................... 421 Odrysowywanie prostego tekstu ........ .422 Ustalanie wyrównania tekstu ............. .423 Zmiana koloru czcionki i jej tła ......... .425 Rysowanie tekstu przezroczystego i nieprzezroczystego........................... .427 Obcinanie tekstu................................. .428 Tworzenie czcionek ............................ 429 Stosowanie klasy CFont..................... .430 Tworzenie czcionek za pomocą funkcji CreatePointFont()............................... .430 Tworzenie czcionek za pomocą funkcji CreateFontQ ....................................... . 431 Wybór czcionek .................................. 437 Wyliczanie czcionek.......................... .437 Używanie okna dialogowego wyboru czcionki .............................................. . 442 Rysowanie tekstu formatowanego i wieloliniowego................................. .445 Usuwanie czcionek ............................ . 447 Część V. Zaawansowane dokumenty i techniki dla widoków................449 Rozdział 18. Rozciąganie i prcewijanie widoków .......................................451 Obsługa rozciągania okna................... .451 Obsługa zdarzeń rozciągania............. ..452 Obsługa zdarzenia Sizing .................. ..453 Obsługa zdarzenia Size...................... ..456 Definiowanie ograniczeń rozmiarów okna ................................................... ..462 Okna dialogowe dające się rozciągać ..464 Przewijanie zawartości okna .............. .464 Definiowanie rozmiarów przewijanego obszaru............................................... ..465 Zmienianie małego i dużego skoku przewijania ........................................ ..469 Do czego służy bieżąca pozycja widocznego obszaru .......................... ..470 Obsługa komunikatów paska przewijania ........................................ ..473 Rozdział 19. Widoki List, Tree, Rich Edit i HTML ........................................ 477 Czym są widoki List, Tree i Rich Edit? .. 477 Tworzenie i korzystanie z widoku List . 477 Tworzenie aplikacji z widokiem List za pomocą kreatora AppWizard ......... .478 Dodawanie elementów do listy .......... .478 Zmiana stylu listy ............................... .483 Kolumny i nagłówki kolumn.............. .486 Odnajdywanie wybranych przez użytkownika elementów na liście....... .491 Widok Tree .......................................... 494 Tworzenie widoku Tree za pomocą kreatora AppWizard ........................... .494 Style widoku Tree............................... .495 Dodawanie elementów do drzewa ....... .496 Odnajdywanie wybranego węzła drzewa ................................................ .499 Ręczne edytowanie elementów drzewa .501 Widok Rich Edit .................................. 503 Tworzenie widoku Rich Edit.............. .504 Zachowywanie i ładowanie tekstu...... .505 Formatowanie akapitów tekstu........... .505 Dodawanie obiektów OLE ................. .508 HTML - widok do budowania przeglądarki ......................................... 508 Tworzenie widoku HTML.................. .509 Definiowanie adresu URL.................. .509 rr 8 Spis treści Spis Obsługa zdarzeń przeglądarki.............510 Odpowiedni stosunek długości do szerokości strony .................................573 Stronicowanie i orientowanie wydruku ..577 Definiowanie pierwszej i ostatniej Rozdz Rozdział 20. Tworzenie okien z kilkoma widokami ..................................... 511 Jak działa okno z kilkoma widokami? 511 Korzystanie z okna dzielonego ........... 512 Tworzenie dynamicznych okien dzielonych.......................................... .512 Inicjalizacja dynamicznych okien dzielonych ........................................... 515 Tworzenie statycznych okien dzielonych 518 Inicjalizacja statycznego okna dzielonego ........................................... 519 Tworzenie aplikacji podobnej do Eksploratora Windows........................ 524 Inne sposoby tworzenia okien z wieloma widokami ........................... 525 Dodawanie i usuwanie widoków ........ 525 Zarządzanie tworzeniem i aktywacją widoku ............................. 526 strony ...................................................577 Okno dialogowe Print..........................581 Opcje orientacji strony ........................585 Dodawanie obiektów GDI za pomocą funkcji OnBeginPrinting( ) ....................586 Modyfikowanie kontekstu urządzenia.588 Przerywanie wydruku..........................589 Drukowanie bez pomocy funkcji szkieletu aplikacji ...............................................589 Bezpośrednie przyzywanie okna dialogowego Print................................589 Funkcje StartDoc ( ) i EndDoc ( )........592 Funkcje StartPage ( ) i EndPage ( ).........593 Część VI. Eksportowanie danych aplikacji .......................................595 Rozdział 21. Aplikacje Rozdział 23. Zachowywanie, ładowanie wielodokumentowe..................... 533 i transferowanie danych..............597 Tworzenie aplikacji wielodokumentowej ............................ 534 Hierarchia klas w aplikacji MDI ......... 538 Wizualne elementy aplikacji MDI ...... 540 Szablon dokumentu MDI .................... 543 Porządek tworzenia dokumentu, widoku i okna obramowującego MDI ............. 545 Poruszanie się między elementami architektury dokument/widok ............. 548 Przykładowa aplikacja MDL............... 550 Dodawanie do dokumentu zmiennych składowych ......................................... 551 Sięganie do danych dokumentu z klasy widoku .................................... 552 Modyfikowanie danych dokumentu i uaktualnianie widoku........................ 553 Dodawanie nowych szablonów dokumentów........................................ 556 Rozdział 22. Drukowanie i podgląd wydruku....................................... 563 Drukowanie za pomocą funkcji szkieletu aplikacji................................ 563 Korzystanie ze standardowych funkcji obsługujących drukowanie..................564 Pokrywanie funkcji OnPrintQ.... .......568 Kontekst urządzenia drukarki ............. 571 Serializacja...........................................597 Tworzenie aplikacji SDI obsługującej operacje na plikach..............................598 Tworzenie obiektów przystosowanych do serializacji.......................................599 Deklarowanie klasy pozwalającej na serializację ...........................................601 Przechowywanie danych dokumentu ..606 Serializowanie obiektów .....................610 Lista najczęściej używanych plików ...613 Rejestrowanie typów dokumentów ........613 Obsługa plików.................................... 614 Klasa CFile ..........................................614 Otwieranie plików ...............................615 Odczytywanie i zapisywanie pliku........616 Bieżąca pozycja pliku..........................621 Odnajdywanie informacji o wykorzystywanym pliku ..................622 Zmienianie nazwy pliku i usuwanie pliku.....................................................624 Klasy pochodne klasy CFile................624 Transferowanie danych za pomocą schowka ............................................... 625 Definiowanie formatów danych schowka ...............................................626 Kopiowanie danych do schowka.........627 Wklejanie danych ze schowka.............630 Rozd; Część Rozd Spis treści 9 Rozdział 24. Bazy danych i widok Record 633 Bazy danych........................................ 633 Relacyjne bazy danych........................ 634 ODBC ................................................ . 634 Konfigurowanie źródła danych.......... . 636 Tworzenie aplikacji korzystającej z bazy danych...................................... 639 Załączanie do programu bazy danych za pomocą kreatora AppWizard......... . 639 Lączenie się z bazą danych................ . 641 Zapytania do bazy danych ................. . 643 Zmienianie danych zapisanych w bazie ............................................... . 646 Wiązanie pól z tablicami bazy danych .647 Widok Record ..................................... 648 Edytowanie szablonu widoku Record .648 Wiązanie kontrolek z polami zbioru rekordów ............................................ . 650 Rozdział 25. Zasady programowania OLE i COM .......................................... 653 Programowanie w oparciu o komponenty...................................... 653 Interfejsy COM ..............:.................... 655 Identyfikatory interfejsów, identyfikatory klas i identyfikatory GUID ................. 657 Tworzenie egzemplarzy obiektów COM 659 Pośredniczące biblioteki DLL i szeregowanie..................................... 661 Wersje interfejsów .............................. 662 Automatyzacja OLE............................ 663 Jak działa interfejs przesyłający.......... 663 Warianty.............................................. 664 Tworzenie serwem automatyzacji....... 666 Tworzenie klienta automatyzacji ........ 673 Kontenery OLE, serwery i miniserwery ...................................... 677 Część VII. Zaawansowane zagadnienia programowania ........................... 681 Rozdział 26. Tworzenie kontrolek ActiveX ........................................ 683 Tworzenie szkieletu kontrolki ActiveX za pomocą kreatora ActiveX Control Wizard ................................................. 683 Definiowanie liczby kontrolek, obsługi licencjonowania i pomocy .................. 684 Definiowanie nazw klas i dodatkowych opcji............................684 Budowanie kontrolek ActiveX w oparciu o kontrolki już istniejące ........686 Zaawansowane możliwości kontrolek ActiveX................................................686 Tworzenie kodu kontrolki ................... 687 Malowanie kontrolki na ekranie..........688 Zarządzanie zdarzeniami wywołanymi przez użytkownika i obsługa wykonywanych przez niego operacji ................................................691 Szybki sposób testowania kontrolki........693 Uruchamianie zdarzeń.........................695 Tworzenie interfejsu właściwości .......697 Właściwości wyposażenia...................698 Dodawanie karty właściwości definiujących kolory............................700 Dodawanie własnych właściwości ......701 Dodawanie kontrolek kart właściwości dla własnych właściwości....................704 Utrwalanie ustawień właściwości........706 Kompilowanie i rejestrowanie kontrolki. 709 Różne pliki źródłowe...........................709 Tworzenie biblioteki typów i plików licencji...................................710 Rejestrowanie kontrolki.......................710 Program testujący ActiveX Control Test Container ............................................. 711 Wybieranie testowanej kontrolki.........711 Testowanie właściwości kontrolki ........711 Obserwowanie uruchomionych zdarzeń .713 Rozdział 27. Korcystanie ze zintegrowanego debugera...........715 Usuwanie błędów z programu i narzędzia debugera ............................ 715 Tryby Debug i Relase......................... .715 Opcje trybu Debug i wyświetlane w nim komunikaty ostrzegawcze ....... .717 Tworzenie i korzystanie z informacji programu Source Browser.................. .719 Debugowanie przez sieć oraz opcja Just in time Debugging....................... .722 Makroinstrukcje TRACE, ASSERT i przeglądanie programu krok po kroku.. 723 Makroinstrukcja TRACE.................... .724 Makroinstrukcje ASSERT i VERIFY............................................ .727 Punkty kontrolne i przeglądanie kodu programu krok po kroku..................... .730 Edit and Continue............................... .733 10 Spis treści Śledzenie zmiennych programu......... .733 DirectPlay................................. ...........763 Inne okna debugera............................ .735 DirectInput............. ..............................763 Inne narzędzia debugowania............... 736 Directsetup........................ ..................764 Spy++................................................. . 736 Tworzenie wiadomości i programowanie Process Viewer................................... .739 obsługi poczty za pomocą MAPL............ 764 OLE/COM Object Viewer .................. 739 Korzystanie z prostszej wersji MAPI ..765 MFC Tracer......................................... 740 Dodawanie programu pocztowego MAPI za pomocą kreatora AppWizard...767 Rozdział 28. Interfejsy API i zestawy Biblioteki multimedialne i interfejs SDK.............................................. 741 MCL.....................................................772 Krótkie wprowadzenie API i SDK...... 741 Interfejs multimedialny MCI...............772 Tworzenie szybkich aplikacji Komunikaty powiadamiające MCL.....776 dźwiękowych i graficznych za pomocą Dodawanie do aplikacji okna MCI......780 interfejsów DirectX............................. 742 Directsound ........................................ 743 Słownik ..................................................... 785 DirectDraw.......................................... 752 Skorowidz ........................... 799 Direct3D.............................................. 762 ...................... w se kons dow syste asse~ razu nikó Gdy lub c rząd zacz nycl anal jętnc racy giełc (kor tem O autorach Jon Bates przez ostatnie 15 lat pracował nad szeroko zakrojonymi projektami w sektorach komercyjnych, przemysłowych i militarnych. Obecnie jest samodzielnym konsultantem i programistą specjalizującym się w tworzeniu aplikacji dla systemów Win- dows NT/95 i 98, w języku C++. Swą karierę rozpoczynał od pisania gier dla mikrokomputerów. Pracował w wielu systemach operacyjnych, takich jak CPM, DOS, TRIPOS, UNIX i Windows oraz różnych assemblerach, językach trzeciej generacji i zorientowanych obiektowo. Napisał wiele rozmaitych aplikacji dla obsługi poczty e-maik obsługi wideo, analizy ob- razu, obsługi sieci i urządzeń telekomunikacyjnych, systemów kontrolnych, a także sterow- ników urządzeń. Opublikował również kilka artykułów w pismach informatycznych. Jonathan mieszka z żoną Ruth i psem Chaosem w samym środku chłodnej Brytanii. Gdy nie pracuje na komputerze, lubi spać i śnić sny pełne fraktali. Kontakt z Jonathanem można nawiązać pisząc na adres j on@chaosi . demon . co . uk lub odwiedzając jego stronę WWW, pod adresem www. chaosl . demon. co.uk. Tim Hopkins jest menedżerem do spraw oprogramowania w europejskiej firmie two- rzącej oprogramowanie, specjalizującej się w opracowywaniu zintegrowanych systemów zarządzania informacjami. Odpowiedzialny jest za projektowanie i wdrażanie komercyj- nych aplikacji, pisanych w języku C++. Kariera programistyczna Tima rozpoczęła się w roku 1986 od pisania aplikacji do analizy statystycznej dla komputerów mainframe IBM. Od tamtego czasu rozwinął umie- jętności i zdobył doświadczenie w używaniu C i C++, pracując z różnymi systemami ope- racyjnymi, takimi jak CPM, DOS, UNIX, XENIX, Windows NT/95 oraz 98. Tim zaprojektował i napisał wiele aplikacji wspomagających planowanie produkcji, giełdowych, kosztorysujących oraz kontrolujących czas pracy. Mieszka w zielonej i miłej części Anglii, ze swą żoną Tracey i jej koniem Oliverem (koń przebywa na zewnątrz domu). Kiedy tylko może, jeździ swym sportowym kabriole- tem (nawet gdy pada deszcz). Można kontaktować się z Timem pod adresem tinytim@globalnet . co . uk. Wx Podziękowania Specjalne podziękowania dla Matta Purcella i Kelly Marshall za ich nieustające słowa otuchy i wielką dbałość o szczegóły, co pomogło nam wykonać tą ciężką pracę. K Składamy podziękowania również Mattowi Buttlerowi, Tonyi Simpson wszec Kate Givens , i funk oraz Kate Talbot za ich nieocenioną pracę edytorską i wskazówki, a także wszystkim ciężko pracującym za kulisami MCP ludziom. szybci szkod: Dziękujemy naszym rodzinom i przyjaciołom za wyrozumiałość, gdy byli zaniedbywani przez kilka ostatnich miesięcy. CZyI Powiedz nam, co myślisz v Micrc Jako czytelnik, jesteś naszym najważniejszym krytykiem i komentatorem. Bardzo ce- wiem nimy twoją opinię i chcielibyśmy poznać, co zrobiliśmy dobrze, co powinniśmy poprawić, i przy dodać oraz wszelkie mądre słowa, które chciałbyś nam przekazać. złożo~ Jako redaktor naczelny działu programistycznego w Macmillan Computer Publishing, eleme oczekuję na twoje komentarze. Możesz przesłać je faksem, pocztą elektroniczną lub zinteg w formie zwykłego listu. Napisz o tym, co ci się spodobało w tej książce, a co przeszka- grafie dzało. Pomoże nam to pisać książki jeszcze lepsze. zinteg spraw Chciałbym jednak, abyś wiedział, iż nie jestem w stanie udzielać porad technicznych posiai dotyczących tematyki opisanej w niniejszej książce. Dodatkowo, z uwagi na ogromną ilość mie, l przychodzącej korespondencji, być może nie będę mógł w ogóle odpowiedzieć na twój list. Gdy zdecydujesz się napisać do mnie, koniecznie pamiętaj o podaniu tytułu oraz na- CÓŻ zwisk autorów książki, jak również swojego imienia i nazwiska oraz numeru telefonu lub faksu. Uważnie przejrzę twój list, a zawartymi w nim uwagami podzielę się z autorami oraz redaktorami, którzy nad określoną książką pracowali . opisy Fax: 317-817-7070 Micrc mych E-maik adv_prog@mcp.com P Adres pocztowy: Executive Editor Programming równi Macmillan Computer Publishing P 201 West 103N Street czone aplika Indianapolis, IN 46290 USA i pysk Wprowadzenie Komputery używające różnych wersji systemu operacyjnego Windows stały się po- wszechnym narzędziem w domach i biurach na całym świecie. Popularyzacja Internetu i funkcji multimedialnych powoduje wzrost zapotrzebowania na różnorodne i silne, coraz szybciej działające oprogramowanie. Wygląda na to, że wedle obecnej mody próbuje się szkodzić firmie Microsoft za jej ogromny sukces. Czym jest Visual C++ 6.0 i dlaczego warto go używać? Visual C++ 6.0 jest ostatnią i najlepszą jak dotąd wersją kompilatora C/C++ autorstwa Microsoftu. Produkt ten to jednak coś znacznie więcej niż tylko kompilator. Zawiera bo- wiem obszerną. bibliotekę Microsoft Foundation Classes, która znacznie upraszcza i przyspiesza tworzenie aplikacji. W bibliotece tej znajdują się narzędzia do tworzenia złożonych okien dialogowych, menu, pasków narzędziowych, obrazków i wielu innych elementów nowoczesnych aplikacji dla systemów Windows. Zawarte jest także doskonale zintegrowane środowisko programistyczne, zwane Developer Studio, które w sposób graficzny prezentuje całą strukturę aplikacji, już w momencie jej tworzenia. Całkowicie zintegrowane narzędzie do debugowania (usuwania błędów) pozwala na natychmiastowe sprawdzenie każdego szczegółu podczas biegu programu. Oprócz tego, Visual C++ 6 posiada ogromną liczbę funkcji umożliwiających pisanie aplikacji o najwyższym pozio- mie, przy wykorzystaniu wszelkich stosowanych obecnie technik. Cóż nowego jest w tej książce? Visual C++ 6 zawiera wiele nowych funkcji, z których tak dużo jak było to możliwe, opisaliśmy w niniejszej książce. Nowe obiekty sterujące, pola wstawiania daty, które znamy z takich programów, jak Microsoft Outlook, są teraz w zasięgu ręki i mogą być wykorzystywane w naszych wła- snych aplikacjach (zostały one opisane w rozdziale 7) Dzięki zastosowaniu rozszerzonych list rozwijanych, listy te mogą obecnie zawierać również rysunki, widoczne jako pozycje listy (mówi o tym rozdział 11). Paski narzędziowe znane z Microsoft Office 98 lub Internet Explorera 4, zostały włą- czone do biblioteki Microsoft Foundation Class, a stąd dostępne są również dla waszych aplikacji (możecie o tym przeczytać w rozdziale 15 o używaniu pasków narzędziowych i pasków stanu). 14 Poznaj Visual C++ 6 Wpr Możliwe jest również użycie Internet Explorera „spod" aplikacji przez nas napisa- KOII nych, co umożliwia przeglądanie stron WWW i kodu HTML (rozdział 20). Potężne narzędzie AppWizard tworzy szeroki wybór złożonych aplikacji, włączając dzy it w to aplikacje w stylu Windows Explorer, zarządzające kilkoma widokami jednocześnie. Działająca aplikacja powstaje zanim jeszcze umieścimy w kodzie programu choćby jedną S linię (tworzenie aplikacji wielowidokowych opisano w rozdziale 21). ś' Programowanie zorientowane obiektowo oraz mechanizm Distributed Component Object Model (DCOM) są obecnie standardami w tworzeniu nowoczesnych aplikacji (mówimy o nich w rozdziale 26). Najświeższe wersje bibliotek API oraz SDK, na przykład DirectX Game SDK, omó- wione zostały w rozdziale 32. . Opis wszystkich z powyższych oraz wielu innych funkcji nowego Visual C++ 6, sta- nowi właśnie treść tej książki. rzyli Czy to jest odpowiednia książka? Pisząc tę książkę założyliśmy, iż czytelnicy posiadają pewną wiedzę i doświadczenie z zakresu programowania w języku C++. Niekonieczna przy tym jest znajomość budowy środowiska Windows ani też biblioteki Microsoft Foundation Class, ale na pewno będzie pomocna. Czytelnik powinien jednak móc poruszać się w systemie Windows 95/98 lub NT i jego graficznym interfejsie. Książki niniejszej można użyć jako zbioru podstawowych wiadomości na temat pro- gramowania w języku C++, od samego początku, rozdział po rozdziale lub skorzystać jedynie z wybranych rozdziałów, opisujących konkretny temat. Tim i ja posiadamy pewne doświadczenie w pisaniu krótkich książeczek opisujących pewne istotne dla programistów zagadnienia. Postanowiliśmy postawić krok dalej, korzy- stając z własnych doświadczeń i wyjaśniając na konkretnych przykładach pewne problemy i wymagania stawiane nowoczesnym, komercyjnym aplikacjom. Czytelnicy wyposażeni w tę książkę, nauczą się pisać wyszukane aplikacje, używając do tego celu najbardziej produktywnego i uniwersalnego środowiska programistycznego: Visual C++ 6.0. Wprowadzenie 15 Konwencje zastosowane w książce Aby zwiększyć czytelność informacji przedstawionych w książce, zastosowano mię- dzy innymi następujące zasady ich oznaczania: • Słowa i zwroty użyte w tekście po raz pierwszy wyróżnione są kursywą, a także obja- śniane w słowniczku. Wszystkie słowa, które pojawiają się na ekranie są pogrubione. Skróty klawiszowe oraz "gorące klawisze" są oznaczane poprzez podkreślenie zna- czącego klawisza. • Wiersze kodu oraz nazwy klas, które użyte są w kodzie, są drukowane czcionką co- urier. • Informacje powiązane z omawianym tematem są drukowane w ramkach, żeby nie zakłócały głównego toku wypowiedzi i można je było łatwo znaleźć. Każda z tych in- formacji ma krótki tytuł, pomagający w jej szybkim zidentyfikowaniu. Życzymy czytelnikom sukcesów i mamy nadzieję, że z pomocą tej książki będą two- rzyli udane aplikacje w Visual C++ 6.0. Część I Tworzenie aplikacji Visual C++ Rozdział l Projektowanie i tworzenie programu w Visual C++ Generowanie aplikacji dla Windows przy użyciu AppWizard Modyfikowanie wyglądu aplikacji przy wykorzystaniu edytora pli-ków źródłowych___________________________ Dodawanie kodu w celu zmiany reakcji aplikacji Ci, którzy nigdy nie próbowali napisać aplikacji dla Windows sądzą, że jest to bardzo ciężkie i żmudne zadanie. Jednakże odpowiednie narzędzia sprawiają, iż zadanie takie staje się łatwiejsze, a najlepszym tego rodzaju narzędziem jest Visual C++ 6. Używając go, możemy stworzyć działającą aplikację dla Windows w mniej niż minutę, zacząć jednak powinniśmy od nieco wolniejszego tempa. Inne określenia Visual Studio Developer Studio jest nazywane również Visual Studio. Oprócz tego można się spotkać również z terminem IDĘ (Integrated Development Enviroment). Uruchomienie Visual C++ Aby uruchomić Visual C++, musimy z menu Start na pasku zadań wybrać Microsoft Visual C++ 6.0. Gdy program zostanie uruchomiony, na ekranie ukaże się okno Microsoft Visual Studio. Uruchomienie Visual C++ wywołuje zatem pojawienie się okna Visual Studio. Visual Studio jest nazwą nadaną interfejsowi użytkownika w Visual C++, a pokazano go na rysunku 1.1. Visual Studio jest właściwym obszarem roboczym. Gdy widok okna w naszym monitorze nie będzie identyczny z zaprezentowanym, nie należy się tym przejmować. 20 Poznaj Visual C++ 6 Czytając niniejszą książkę nauczymy się jak wywoływać i korzystać z koniecznych funkcji programu. Rysunek 1.1. Okno Visual Studio Tworzenie nowego projektu Aby rozpocząć pisanie nowej aplikacji, musimy najpierw utworzyć projekt. Jest on przeznaczony do zarządzania wszystkimi elementami składającymi się na program Visual C++ oraz gotową aplikację Windows. W celu utworzenia nowego projektu wybieramy pozycję New z menu File. Ukazujące się w tym momencie okno dialogowe pokazane jest na rysunku 1.2. Filei PrciecL- Wo!kspac"s J Othe; Docirnerds, I ^ojset aame; ^BWWff 3 CIu^łei Re;iJUice T^ Wi;aid ^]Cu-,[omAppWeaid ifIDatabaseProject :: 8iDevSludBAdd-inWi;ard 1: S; Eitended Stmed Proc Wzad 811SAF1 EHlendonWeafd ^Makelh 8SMFCAclivB>:C(mln]f*fctld [gMFCAppWBardlljl) gJMFCAppWiardlene) jS:NfwDalaba"Wizaid nu""Pio|ect <)Wrl32Aililfcatk"l 3Wn32CmsoleAp(fcation 3wn32 D>tiarr";4.inkLtmi" ^^M-ii^jllBtiSal^r 1' -11- - :-' : :":la .ctsaEion;. : .: CAProgfam Files'^ic>osoH Vsue J ^ Create new workspace r •.' .i',:,.,, .• . ; . r i:...:-.-.. . l d Wonni: ,-'l'v.'ró2 i:"^:i::::':::::i^|^:^^;fc". • ^s^^aii | Cancd | Rysunek 1.2. Okno dialogowe New Projektowanie i tworzenie programu w Visual C++ 21 Wybór rodzaju projektu Na początku należy określić rodzaj projektu, który chcemy utworzyć. W tym celu trzeba kliknąć kartę Projects w oknie dialogowym New, o ile nie jest aktualnie wybrana, a na wyświetlonej liście zaznaczyć typ projektu, który będzie tworzony. W przykładzie służącym w niniejszym rozdziale będzie to MFC AppWizard (exe). Wybór tej opcji oznacza, że owocem naszych działań będzie typowy, wykonywalny program dla Windows. Do czego służy MFC? Visual Studio zostało wyposażone w bibliotekę Microsoft Foundation Ciass. Jest to zestaw predefiniowanych klas języka C++. AppWizard tworzy szkielet projektu, używając klas wyprowadzonych z klas MFC. Używanie biblioteki MFC jest zilustrowane właśnie w niniejszej książce. Nadawanie nazwy projektowi i wybór lokalizacji Pole Locatiori służy do ustalenia katalogu dyskowego, w którym umieszczony zostanie projekt. Lokalizacja może pozostać w tym przykładzie niezmieniona. Lokalizacja bieżąca, wyświetlana w polu Location, zależy od naszego wyboru poczynionego podczas instalacji Visual C++. Aby ją zmienić, musimy wpisać nową bezpośrednio w polu lub kliknąć klawisz znajdujący się obok pola. Pozwoli to nam na przejrzenie drzewa katalogów na dysku i wybór jednego z nich. Nazwa katalogu, w którym zostanie umieszczony projekt, wynika z nazwy samego projektu, na przykład: C:\Program Fi-les\Microsoft Visual Studio\My Projects\Minute. Nadawanie nazw projektom Swoim projektom można nadawać nazwy odmienne od sugerowanych tutaj. Aby jednak uniknąć nieporozumień, wskazane jest używanie tych samych nazw, jakie występują w omawianych przykładach. W aplikacjach należy używać takich nazw, które określają czynności wykonywane przez daną aplikację. Obecnej aplikacji nadany został tytuł Minutę, gdyż jak już wspomnieliśmy wcześniej, nową aplikację można utworzyć w czasie krótszym niż minuta. Używanie AppWizard Po dokonaniu wszystkich ustawień w oknie dialogowym New klikamy OK, by rozpocząć tworzenie nowego projektu. AppWizard rozpocznie ten proces za użytkownika. Zadaniem tego kreatora jest utworzenie kośćca programu, który następnie będziemy rozbu- 22 Poznaj Visual C++ 6 dowywać o kolejne elementy. Tworzenie szkieletu projektu odbywa się poprzez wybór odpowiedniej klasy spośród Microsoft Foundation Ciass, dla utworzenia wszystkich plików koniecznych w projekcie Visual Studio. PATRZ TAKŻE " Więcej szczegółów dotyczących tworzenia aplikacji opartych o okna dialogowe znajduje w rozdziale 3 Korzystanie z podstawowych opcji AppWizard Okno dialogowe AppWizard (pokazane na rysunku 1.3) pozwala nam wybrać interfejs aplikacji spośród trzech dostępnych. W projekcie Minutę interfejsem będzie okno dialogowe. Należy więc wybrać opcję Dialog Based. Możliwy jest także wybór z rozwijanej listy języka, który zostanie użyty w plikach źródłowych. W niniejszym przykładzie możemy posłużyć się bieżącym wyborem. Wszystkie informacje, których AppWizard wymaga i które wykorzysta w tworzeniu nowego projektu, są w tym punkcie już ustalone. Należy więc kliknąć Finish, po czym pojawi się nowe okno - The New Project Information, jak na rysunku 1.4. MFC AppWizald Sień 1 Whdl type ot appficafion wouldyou like to cieate? f Stf^gle document f Mufcpte docuftients 17 fil*^"1! f? ;.""".:;1,,/,:.-..-1 -- - What ianguage wouid you iike youi reiources in? [Engirch [Uniled Siałeś] [APPAZENU.DLL^ < Ba* l Ma* > Rysunek 1.3. AppWizard MFC - krok l, ustalający typ nowego projektu Okno to zostaje wyświetlone przez AppWizard w celu zatwierdzenia szczegółów dotyczących tworzonego projektu. Wewnątrz tego okna ujawnione są nazwy klas użytych w projekcie oraz nazwy plików, które zostaną utworzone. Z listy tej możemy się dowiedzieć również o innych składnikach funkcyjnych, jakie kreator włączy do projektu. Aby zamknąć okno The New Project Information, klikamy OK. Projektowanie i tworzenie programu w Visual C++ 23 New Pioleci Inionoation AppWizard wH create a new $kefeton ploject w^h the following specificalions: Applicattón t^pe c^ Minutę: D iatog-B ased Appfcahon targetng: Win32 Ciasses to be cteated; Applicafion: CMinuteApp in Minutę h and Minule.cpp Dialog: CMhuteOlg in MinuteOlg h and MinuteDig.cpp ^edtules: + About bon on sysleiri menu " 3D Controls * Uses shared DLL impletnenitlion (MFC42.DLL) + Acriya^ Conffols supporó enabied + LocaS^abfe text in' Engis-hlUnitedS(a)es) ProlectDiFectofy: C;\P]i)gia[n FlesSMiaosof t Visual Sludni'\M>Pro|ecl8\Minute Rysunek 1.4. Okno dialogowe The New Project Information Na tym etapie zadanie AppWizard jest zakończone, a nowy projekt o nazwie Minutę zostaje utworzony i otwarty w oknie Visual Studio. Jeśli nawet nie widzieliśmy dotąd ani jednej linii kodu źródłowego C++, możemy być pewni, że w pełni funkcjonująca aplikacja dla Windows jest już gotowa. Panel po lewej stronie okna Visual Studio zwany jest panelem roboczym. Jeśli projekt został już utworzony, panel ów zawiera trzy karty: ClassView, ResoureeView oraz Fi-leView. Karty te umożliwiają nam dostęp do każdej części składowej projektu. Możemy zmieniać rozmiar tego panela, jak też wszystkich innych okien, które pojawiają się w Visual Studio, poprzez uchwycenie jego krawędzi i przesunięcie w żądane miejsce, z wciśniętym lewym klawiszem myszy. Ponowne uruchamianie AppWizard AppWizard używany jest tylko do tworzenia nowego projektu. Nie można powrócić do niego, gdy projekt został już utworzony. Jeśli programista zorientuje się, że ustalenia dokonane przez niego są niewłaściwe, chcąc powrócić do AppWizard musi uprzednio usunąć istniejący projekt. Aby tego dokonać, wystarczy usunąć z dysku katalog, w którym umieszczony został dany projekt. Dla przykładu, aby ponownie opracować projekt Minutę, należałoby używając Eksploratora Windows usunąć katalog C:\Program Files\ Microsoft Visual Studio\My Projects\Minute. 24 Poznaj Visual C++ 6 i1 Generowanie i uruchamianie aplikacji Teraz przeprowadzimy proces generowania wykonywalnego pliku, na podbudowie utworzonego projektu. W przykładzie Minutę plik ten będzie nosił nazwę Minute.exe. Gdy plik zostanie wygenerowany, będziemy mogli uruchomić go wewnątrz Visual Studio. Wykonywalne pliki C++ są standardowymi plikami .exe systemu Windows Minute.exe jest plikiem wykonywalnym w systemie Windows, podobnie jak inne pliki .exe. Dlatego można go uruchomić z Eksploratora, a nawet utworzyć skrót z pulpitu. Wybór konfiguracji procesu generowania pliku Visual Studio może tworzyć pliki wykonywalne .exe w dwóch trybach: debug version oraz release version. Ustalenie tego trybu nazywane jest konfiguracją generatora. W tym przypadku wykorzystamy tryb debug version. Używany on będzie we wszystkich przykładach do czasu, gdy napotkamy instrukcje nakazujące jego zmianę. W przykładzie Minutę nie należy zmieniać tej konfiguracji. Wynikiem tego będzie zawarcie informacji debugera wewnątrz właściwego pliku aplikacji Minutę. Owe informacje umożliwiają prześledzenie kodu podczas wykonywania i sprawdzenie zawartości kolejnych zmiennych. Jednak włączanie tych informacji do pliku wykonywalnego zwiększa jego rozmiary, a także powoduje obniżenie szybkości działania. Konfiguracja release nie umieszcza takich informacji, a stosuje się ją, gdy generowany plik jest ostateczny i ma trafić do końcowego odbiorcy. PATRZ TAKŻE • By dowiedzieć się więcej o debugowaniu projektów, przejdź do rozdziału 27. Przeprowadzanie procesów kompilacji i łączenia Proces generowania pliku wykonywalnego obejmuje kompilację pojedynczych plików utworzonych w ramach projektu, a następnie łączenie ich, czyli łączenie kodów wynikowych w jeden plik wykonywalny. By przeprowadzić ten proces, musimy kliknąć przycisk Build w pasku narzędziowym albo z menu Build wybrać pozycję Build Minute.exe lub też użyć skrótu klawiaturowego (F7). Plik Minute.exe umieszczony jest w podkatalogu /Debug, w katalogu przechowującym cały projekt, czyli /Minutę. Podkatalog /Debug zawiera również pliki obiektowe. Utworzony podkatalog został opatrzony taką nazwą /Debug, ponieważ właśnie taką konfiguracją posłużyliśmy się dla operacji wygenerowania pliku. Gdyby wybrana została konfiguracja release, pliki te znalazłyby się w podkatalogu /Release. Karta Build w panelu Output wyświetla szczegóły dotyczące procesu generowania pliku. Jeśli kod źródłowy zawierałby błędy, zostałyby one wykazane w tym panelu, który można zobaczyć na ry- Projektowanie i tworzenie programu w Visual C++ 25 sunku 1.5. Ponieważ jednak w mniejszym przykładzie kod źródłowy był przygotowywany przez AppWizard, powinien być wolny od błędów. i^BBSBfilffilSJI^^ Rysunek 1.5. Proces generowania pliku wykonywalnego w projekcie Minutę Uruchomienie aplikacji Aby uruchomić aplikację, należy kliknąć przycisk Execute znajdujący się na pasku narzędziowym. Można również z menu Build wybrać pozycję Execute Minute.exe lub też użyć skrótu Ctrl+F5. Główne okno aplikacji zostanie wyświetlone, jak na rysunku 1.6. & Minutę "OK""""" Cancel TODO; Place dialog contols here Rysunek 1.6. Uruchomiona aplikacja Minutę Zgodność z interfejsem Windows Gratulacje! Utworzyliśmy właśnie aplikację dla Windows przy użyciu C++. Jak można się przekonać, zawiera ona standardowe składniki funkcyjne, używane we wszystkich 26______________________________________ Poznaj Visual C++6 aplikacjach dla systemu Windows. Aplikacja Minutę posiada dwa przyciski, OK oraz Cancel, a także wyświetla określony tekst. Wyposażona jest również w pasek tytułowy, wyświetlający przypisaną aplikacji ikonę oraz przycisk zamykający. Pasek tytułowy zawiera menu systemowe i można za jego pomocą (klikając na nim i przytrzymując lewy klawisz myszy) przesuwać całe okno. Gdy klikniemy ikonę w lewym górnym rogu okna Minutę, pojawi się menu systemowe, z którego wybierzmy pozycję About Minutę. Aplikacja wyświetli kolejne okno dialogowe, zawierające podstawowe informacje o aplikacji. Obrazuje to rysunek 1.7. Kliknięcie OK w oknie dialogowym About Minutę spowoduje jego zamknięcie. Kuknięcie OK tym razem w oknie głównym aplikacji Minutę sprawi, iż zostanie zamknięta cała aplikacja. About Minutę MinuteVerrion1.0 [[J3K" ir*Jui i "i;'"";'i ,^|!3 Copr[ight(C)1998 Rysunek 1.7. Okno dialogowe About Minutę Modyfikacja interfejsu aplikacji Wizualne elementy projektu zwane są zasobami (ang. resources). Okna dialogowe, ikony, menu są właśnie takimi zasobami. Zawarty w Visual Studio edytor zasobów jest narzędziem pozwalającym nam projektować elementy zaliczające się do różnych rodzajów zasobów, a także modyfikować wygląd aplikacji. Umieszczanie przycisków sterujących Zanim dodamy nowy przycisk sterujący, najpierw musimy otworzyć szablon głównego okna dialogowego aplikacji. Otwieranie szablonu 1. Wybierz kartę ResourceView w panelu roboczym projektu, a ujrzysz zwiniętą listę zasobów pod nazwą Minutę resources. 2. Rozwiń tę listę, klikając znak + po lewej stronie napisu Minutę resources, a następnie rozwiń katalog Dialog. Widoczne są tutaj dwa identyfikatory: IDD_ABOUTBOX oraz IDD_MINUTE_DIALOG, jak na rysunku 1.8. Projektowanie i tworzenie programu w Visual C++ 27 Rysunek 1.8. Identyfikatory w katalogu Dialog, w panelu ResourceYiew 3. Kliknij dwukrotnie identyfikator IDD_MINUTE_DIALOG, po czym powinien zostać wyświetlony szablon głównego okna dialogowego aplikacji Minutę. Jest to pokazane na rysunku 1.9. Okno po uruchomieniu aplikacji będzie wyglądało tak jak teraz wygląda jego szablon. Możesz teraz modyfikować szablon, używając edytora zasobów. Teraz dodamy nowy przycisk sterujący. ^Efa LA JfBW l>ag.t &t,ed fiAf i.aw- lód! ^ cf as • -r" ::a- -- ,[o ^nito" Uałp la ,!i'-ta1'' ! ;M 1" [S] %• ^|COMBOBU^LX1TEM ^J ^ : i:Mr"B:l ':,";;;^^^a;.S-:."". -""J.d l •"*'' l J|,CM,nu"eDlg d 3.-' i j ,, i.,,, i.,,..,,,. i,,., i,,,, i,.,, i.,,,: : - D ABOUTBOX : ! iDb MINUTĘ Dgml 3: LJ lewi t _|S>r,|Tltlt tTeBS: Plawffateg Mrtirii hae. T1'"11;:1 /ll"l:l:t"' '\ Caned l '':'jClailVawj ^{Beloucey.Tł [g Fleyuy l: s :1:.1: "•'.; •'.•i •' | B) mi" ;'i; Readv Rysunek 1.10. Usuwanie tekstu z okna dialogowego 2. Wybierz z paska narzędziowego Controls ikonę przycisku, jak na rysunku 1.11. Rysunek 1.11. Wybieranie ikony przycisku z paska narzędziowego 3. Przenieś wskaźnik myszy nad obszar szablonu. Gdy już to uczynisz, wskaźnik przyjmie postać krzyżyka wskazującego miejsce umieszczenia nowego przycisku. Ustal jego pozycję w pewnej odległości od OK i Cancel i kliknij myszą. Nowy przycisk, opatrzony napisem Buttoni, pojawi się na szablonie; widać to na rysunku 1.12. Projektowanie i tworzenie programu w Visual C++ 29 ^Ete iS •^w Irewt &c"=d BAl 1.<"1 loołi a"k" Li* 1,'a, t* H a * na. •-f . ~33..':|i<- a f. i "i - -.J Mnute lafOlMCDf • - •_aK^ SalDDABGUTBCK lisa '' j Cl.lir/gw j f-j ReiuuraVie" | j] F^/BW s • •11 • : '.as i lr R Handlers lor ciass l.MinutrDlg OK Cancef j | fidd Handler | Ądd and Edit | 0ais es obfeci to handle: IDCANCEL IDOK ~3 BN_CUCKEO: Indicateł Ihl uss cfcked a bulion Rysunek 1.14. Dodawanie kodu do zdarzenia naciśnięcia przycisku 3. Z listy New Windows messages/events wybierz pozycję BN_CLICKED. Zauważ, że pierwsza pozycja na liście jest traktowana jako domyślna. Projektowanie i tworzenie programu w Visual C++ 31 4. Kliknij Add and Edit. Otworzy to kolejne okno dialogowe Add Member Function, widoczne na rysunku 1.15. W tym miejscu nadasz nazwę funkcji programu, która będzie wywoływana zawsze, gdy okno dialogowe aplikacji odbierze komunikat BN_CLICKED, świadczący o naciśnięciu przycisku Press Me. 5. Kliknij OK, aby zaakceptować bieżącą nazwę OnPressMe. Zapis nowej funkcji pojawi się w oknie edytora. Spójrz na rysunek 1.16. Funkcja ta została dodana jako składowa klasy CMinuteDlg, a klasa ta została automatycznie utworzona i nazwana, kiedy AppWizard tworzył szkielet projektu. W chwili obecnej funkcja nie ma jeszcze przydzielonych zadań, należy więc dopisać porcję kodu. Rysunek 1.15. Nadawanie nazwy funkcji, która będzie obsługiwać zdarzenie :':ff*BK-lWa-;.B"hlt:oii,- roid CHinuteDig: :OitPrwstfa() iti-OlMt ItC.lStiiT.U. ; sit; DV";CT> •;a"a-,|^i"""K">-|.(j(<*A"i Rysunek 1.16. Szkielet funkcji OnPressMe() 6. Dodaj wpis do funkcji OnPressMe () .identyczny z listingiem 1.1. 32______________________________________Poznaj Visual C++ 6 Listing 1.1. LST01_1.CPP-implementacja obsługi komunikatu o naciśnięciu przycisku 1 void CMinuteDlg::OnPressMe() 2 ( 3 // TODO: Dodaj tu swój własny kod obsługi komunikatów sterowania 4 MessageBox("Thanks, I needed that!"); 5 }______________________________________________________ Została tu użyta predefiniowana funkcja MessageBox () w celu wyświetlenia okna zawierającego komunikat Thanks, l needed That!, gdy zostanie naciśnięty przycisk Press Me. Sprawdzenie zmian wprowadzonych do aplikacji Za każdym razem, gdy zostają wprowadzone zmiany, należy ponownie przeprowadzić operację generowania i uruchomić aplikację, by zobaczyć, jaki rezultat przyniosły te zmiany. Musimy w takiej sytuacji kliknąć przycisk Build lub wybrać pozycję Build Mi-nute.exe z menu Build bądź też skorzystać z klawisza funkcyjnego F7. Spowoduje to wygenerowanie nowego pliku wykonywalnego, z uwzględnieniem zmian wprowadzonych w kodzie plików źródłowych lub zasobowych. Jeśli w kodzie tkwi błąd, zostanie on zidentyfikowany podczas procesu kompilacji, i wykazany na karcie Build w panelu Output, u dołu okna Visual Studio. Ilustruje to rysunek 1.17. W sytuacji, gdy zostaną podane komunikaty o wystąpieniu błędów, należy dokładnie sprawdzić, czy kod wpisany do funkcji OnPressMe () jest identyczny z zapisem w listingu 1.1. Jeżeli kontrola wykaże błędność wpisu, musimy przepisać ten fragment kodu i ponownie kliknąć przycisk Build. Należy postępować w ten sposób do momentu, kiedy po wykonaniu kompilacji nie pojawią się już żadne komunikaty o błędach. Skoro kod źródłowy jest "czysty", należy kliknąć przycisk Execute Program albo wybrać pozycję Execute Minute.exe z menu Build lub użyć skrótu klawiaturowego Ctrl+F5. Powinno zostać wyświetlone okno dialogowe Minutę, jak na rysunku 1.18. Jeśli teraz zostanie kliknięty przycisk Press Me, wywoła to pojawienie się okna wyświetlającego komunikat Thanks, l needed that!, widać to na rysunku 1.19. Po kliknię-ciu OK, okno zostanie zamknięte. Ponowne kliknięcie przycisku Press Me w oknie głównym spowoduje ponowne otwarcie okna ze znanym już komunikatem. Dzieje się tak dlatego, gdyż za każdym kuknięciem Press Me, program wywołuje działanie funkcji OnPressMe (). Projektowanie i tworzenie programu w Visual C++ ltVi"ual c+* • IMmuteDtg.cppl --^ ". !^ L"" L'" Yiew !nw( Plowct fimki loob ^BiAw y^p l^lis^a' ns •1! ' 1- n s;?,' 1*1- ~3'<. ^iSS " ! 'l CDialog::OnPaint(ł; ^J Minule letouce* M ^a i)"*n : SilDDABOyTBrK )'MIHUTE DW.061 / '.•iiii- -';'!t"ysi':'^ .c-J i .fwi^-^y, Rozdział 2 ^ Środowisko programistyczne Poznawanie środowiska Deyeloper Studio Poruszanie się w oknie roboczym projektu Zasady zarządzania konfiguracją i ustawieniami projektu Praca z Developer Studio Środowisko pracy Developer Studio, które wykorzystuje Visual C++ może wydawać się na pierwszy rzut oka nieco złożone. Przeliczywszy szybko, możemy stwierdzić, iż korzysta ono z ponad stu różnorodnych opcji i prawie tylu przycisków kontrolnych. Wiele spośród nich powoduje komplikacje w użytkowaniu okien dialogowych poprzez wielość możliwości wyboru. Taka złożoność jest jednak konieczna, gdyż dzięki temu Developer Studio pozwala pisać bardzo rozbudowane, profesjonalne aplikacje do każdego zastosowania. Nie trzeba się go jednak bać. Wystarczy zrozumieć tylko część całości, aby rozpocząć działanie. Dopasowywanie środowiska Developer Studio do swoich preferencji Środowisko pracy w Developer Studio można modyfikować w celu dopasowania do własnych upodobań, na kilka sposobów. Wybierając pozycję Customize umieszczoną w menu Tools, dokonać można zmian dotyczących pasków narzędziowych oraz menu. Za pomocą opcji Options z tego samego menu, zmienić można czcionkę używaną w edytorze plików, kolory oraz inne elementy środowiska. Otwieranie istniejącego projektu Jeśli w tym momencie Visual C++ nie jest jeszcze uruchomiony, należy zrobić to teraz, z menu Start na pulpicie. W rozdziale l "Projektowanie i tworzenie programu w Visual C++", utworzony został projekt Minutę. 36 Poznaj Visual C++ 6 Najszybszym sposobem na otwarcie tego projektu jest skorzystanie z listy Recent Workspaces, z menu File. Aby to uczynić, należy kliknąć menu Filc, a następnie wybrać odpowiednią pozycję z listy Recent Workspaces. Objętość tej listy jest jednak ograniczona. W celu otwarcia projektu nie widniejącego na liście, trzeba kliknąć menu File i wybrać opcję Open Workspaces. Wywołane zostanie okno Open Workspaces, widoczne na rysunku 2.1. Rysunek 2.1. Otwieranie istniejącego projektu poprzez wybór pliku .dsw Teraz należy przeszukać katalogi, aby odnaleźć plik projektu Minutę (jeśli lokalizacja domyślna została zaakceptowana, projekt powinien znajdować się w katalogu C:\Program Files\Microsoft Visual StudioNMy ProjectsSMinute). Zaznaczenie pliku Minute.dsw i klik-nięcie Open spowoduje otwarcie projektu. Automatyczne otwieranie ostatnio opracowywanego projektu Możliwe jest nakazanie Developer Studio, aby w trakcie uruchamiania automatycznie otwierał ostatnio opracowywany projekt. Aby osiągnąć ten cel, należy wybrać kolejno: menu Tools, pozycję Options, a następnie kartę Workspace. Na karcie tej trzeba odszukać oraz zaznaczyć pole wyboru Reload Last Workspace on Startup. Którakolwiek z tych metod zostanie użyta, w efekcie projekt zostanie otwarty, a jego nazwa pojawi się na pasku tytułowym okna Developer Studio. Developer Studio automatycznie otworzy ostatni plik źródłowy, nad którym pracowano (patrz rysunek 2.2). Aby ukryć Środowisko programistyczne 37 okno Output zlokalizowane u dołu ekranu, wystarczy kliknąć mały symbol krzyżyka w lewym górnym rogu tego okna (wskazuje go wskaźnik myszy, rysunek 2.2). B-ifflxi ^lflJ.xj : E3 Eite EA Vew [mwt Elojeel gutd Tools Wyidiw UB^ iliaiisH(r) ' -; s' i.-. - [D @)Ę|' Cjl(|CT,e"Clll . ...... . ... .. . . .."•..'. :-,.s",y,^ssy,"f,;^^,ss;^";. "nr ~an 5R [_][ Minutę JMOurcBij CDialog;:OnPaint(); HCURSÓR CMinuteDlg:^OnÓueryDragIcoi^) : retaiT. (HCORSOR) "_hlcon; i:} void CMinuteDlg: :OnPresstIe() lIessageBoKC-Thanks. I needed that!"); Rysunek 2.2. Developer Studio otwiera projekt w punkcie, w którym przerwano pracę Można by się zastanawiać, jaka istnieje różnica pomiędzy obszarem roboczym (ang. Workspace) a projektem. Otóż obszar roboczy obejmować może wiele projektów. We wszystkich przykładach podanych w tej książce, każdy obszar roboczy będzie obejmował tylko jeden projekt. Jednakże podczas pracy nad większymi aplikacjami, bardzo często wygodne jest zawarcie większej liczby projektów w jednym obszarze roboczym. Użycie podprojektów Możliwe jest utworzenie podprojektów dla projektu głównego i powiązanie ich ze sobą. W takiej sytuacji wprowadzenie zmian w jednym z projektów powoduje odświeżenie zawartości pozostałych. Okno obszaru roboczego projektu Okno robocze projektu jest oknem (lokowanym. W programie występuje ich kilka (okno Output jest jednym z takich okien), a niektóre pojawiają się w trybie debug. Aby zmienić charakter okna z dokowanego na pływające, wystarczy podwójnie kliknąć pasek tytułowy tego okna. Jednak praca z oknami dokowanymi będzie prawdopodobnie wygodniejsza. Okno robocze projektu można zamknąć, klikając mały przycisk zamykający w górnym rogu okna. 38 Poznaj Visual C++ 6 Gdy zachodzi potrzeba ponownego otwarcia okna, musimy wybrać pozycję Workspace, znajdującą się w menu View. Każde okno dokowano zachowuje się podobnie. Gdy okno robocze projektu jest otwarte, pozwala na przemieszczanie się pomiędzy elementami projektu. Kiedy otworzymy projekt, u dołu okna widoczne są zazwyczaj trzy karty: CIassView, ResourceView oraz FileView. W przypadku opracowywania aplikacji służącej do obsługi baz danych może pojawić się jeszcze jedna karta, Data View. Owe karty u dołu okna, reprezentują logiczne części projektu. Wystarczy kliknąć wybraną kartę, by był widoczny odpowiadający jej panel. Każdy spośród tych paneli zawiera graficzną reprezentację drzewa elementów składowych projektu. Kolejne pozycje możemy w miarę potrzeb rozwijać lub zamykać wewnątrz drzewa, klikając symbol (+) lub (-), widniejący po lewej stronie pozycji, do której się odnosi. Korzystanie z panelu CIassView Po kliknięciu karty CIassView, zostanie odsłonięty właściwy panel, taki jak na rysunku 2.3. Na tym panelu uwidocznione są wszystkie klasy wchodzące w skład projektu. App Wizard utworzył owe klasy automatycznie w początkowej fazie tworzenia projektu. Każdy element drugiego poziomu (z wyjątkiem pozycji Globals) reprezentuje jedną podklasę i podaje jej nazwę. Rozwinięcie takiej pozycji powoduje wyświetlenie kilku następnych, podrzędnych elementów, z których każdy symbolizuje funkcję składową lub zmienną składową owej klasy. Dla przykładu, podrzędnym elementem podklasy CMinuteDlg, jest funkcja OnPressMeO, która została umieszczona w przykładowym projekcie z rozdziału l. Małe ikony widniejące tuż obok nazwy elementu dostarczają nam o nim dodatkowych informacji (tabela 2.1). S W Minutę danei E-"f CAboutDln ' ; " CAbciulDIad ; L S" DoOataEulshansiefCOataElichange "pOX) B "i" CMnuteApp \ t "CMinuliAppl) i '-- ^ ln"lnriancB() B "i; CMinmeDlg l l " CMinule01g(CWnd "pParenI - MULL) ' . % DoDataE"change(COalaEa:hange •pDX| . l ^ OnIntDiabgn l ' '8"OnPainl(l l , 'JOlOnPreBMeflI ; . %> OnC|ueiiiDraglcon() 1 l %" OnSysComniandlUINT nID. LRARAM Parain) l ; %> m_hlcon ffl Cl Globali " jClałsyeHfsgj Besouci"aij;|ffl^i||i||"j Rysunek 2.3. Wybór karty CIassView umożliwia manipulację klasami wewnątrz projektu Środowisko programistyczne________________________________ 39 Tabela 2. l. Ikony wyświetlane z elementami ClassView podają dodatkowe szczegóły Ikona Znaczenie Klasa Funkcja publiczna Funkcja chroniona Funkcja prywatna Zmienna publiczna Zmienna chroniona ^1 Zmienna prywatna B Większa część procesu programowania projektu w Visual C++^ polega na wykorzystaniu klas. Z tego powodu panel ClassView jest z reguły najczęściej używany. Dzięki niemu bowiem łatwo jest poruszać się pomiędzy klasami, funkcjami składowymi oraz zmiennymi zawartymi w projekcie. Podwójne kuknięcie którejkolwiek z tych pozycji, powoduje otwarcie właściwego kodu źródłowego w oknie edytora, z kursorem ustawionym w linii, w której rozpoczyna się kod danego elementu. Dwukrotne kliknięcie pozycji reprezentującej klasę ustawia kursor w miejscu, w którym znajdziemy definicję danej klasy. Podobnie podwójne kliknięcie zmiennej składowej przenosi kursor do miejsca, w którym została ona zdefiniowana. Natomiast gdy dwukrotnie klikniemy nazwę funkcji składowej, to z kolei spowoduje skok do pierwszej linii definicji danej funkcji, w pliku implementacji klasy. Pasek tytułowy w Developer Studio Nazwę aktualnie otwartego w edytorze pliku można odczytać z paska tytułowego Developer Studio. Aby jeszcze bardziej ułatwić poruszanie się pomiędzy klasami projektu, panel ClassView posiada dodatkowe udogodnienia, dostępne z menu skrótów. Menu skrótów otwarte zostaje obok znacznika myszy, gdy odpowiednia pozycja jest kliknięta jej prawym klawiszem. Aby więc otworzyć menu skrótów, należy wybrać właściwy element i kliknąć prawym klawiszem myszy. Wybrana pozycja zostanie podświetlona, a obok otworzy się menu skrótów. Rodzaj tego menu zależy od typu wskazanego elementu listy. W celu wyświetlenia menu skrótów dla klasy CMinuteDlg, musimy najechać na nią wskaźnikiem myszy, następnie kliknąć jej prawym klawiszem, co w konsekwencji otworzy menu skrótów pokazane na rysunku 2.4. 40 Poznaj Visual C++ 6 !: XŁ.^(tm)"^^.(tm)^Ąti^<.^^.t.ywwwBS^^ i? ES • S^ Minutę cianei l B "I" CAboutDlg a l l " CAboulDlg() : 'a" DiiDataE"changB(COalaEiichange •pDX) l B ••f CMinuteApp : : • 6 CMinuleAppl) ; ^ , "- ^ Initlmtancell l ś-t-Brner 5,1,0^^ : : ^ CMiriL f l L ^ nnUa SoToOiabgMtoi fe ; ^ Oninit ^m^^^^,^gj^3jj^V^|j^BJ^J l l !• S$ OnPa AddM"mt"]yaraUs.. ^ , -1^ OnPrf AddViilualFunctitin... l l ~%Or,Qu AddWindowsMessaga Handler... ••-pa OnSy; :# l L^m.hicl'^""'6"- i SB ••LI Blobals - D"iv"l Ctatsw... V fiasa Ctes"et... l Add to Gafcii! QJ N esi Folder,. i GlOupbyAccess i V DockingVtew i Htie i f^K' " , S'P,spaM, 3ftBaWw|^fiKMBiV"!w ^)FteVew] ii iiii 11' Rysunek 2.4. Menu skrótów otwarte dla elementu z listy ClassView Menu skrótów daje nam możliwość przeprowadzania pewnych operacji związanych z wybraną klasą. Wybór pozycji Go to Definition odnosi skutek taki sam, jak podwójne kliknięcie nazwy klasy, czyli ustawienie kursora na początku jej definicji w pliku źródto-wym, otwartym w oknie edytora. Cztery opcje spośród widocznych w menu służą dodawaniu funkcji oraz zmiennych. Użyjemy ich nieco później, gdyż najpierw omówione zostaną w skrócie pozostałe opcje menu. Gdy klikniemy opcję References, wyświetlony zostanie komunikat widoczny na rysunku 2.5. Microsoft Vnual C-M C:\PJog(am FileAMicrosolt Vtsual Studio\M^Proiect!'A1inute\Defaug\MinuFe. Bfowse infotTn-atiofi is ncrf avai)abte for friis pfc^sct Do you wani you? fc^ild s^lings attefed lit neccssafł'J and yws ptoject refauiR to fleferate bfowse hfofnłation? Rysunek 2.5. W przypadku niektórych opcji menu skrótów jest wymagany plik dla przeglądarki Kliknięcie Yes spowoduje wygenerowanie pliku dla przeglądarki i automatyczne przetworzenie projektu. Po wykonaniu tych czynności należy zamknąć okno Output, klikając mały krzyżyk w lewym górnym rogu. Wygenerowanie informacji dla przeglądarki sprawia, że stają się dostępne dodatkowe ułatwienia w menu skrótów. Opcja References wyświetla wszystkie miejsca w projekcie, w których następują odwołania do wybranej pozycji z Usty. Dla każdego wskazanego odwołania możemy odczytać Środowisko programistyczne 41 również nazwę pliku źródłowego i numer linii, w której następuje wywołanie. Możliwy jest także bezpośredni skok do tej konkretnej linii w oknie edytora. Deriyed Ciasses pokazuje szczegóły dotyczące wybranej klasy, a także klas z niej wyprowadzonych. Base CIasses z kolei wyświetla, jak możemy się domyśleć, szczegóły wybranej klasy oraz wyszczególnienie klas bazowych, włączając w to klasy pochodzące z biblioteki MFC (rysunek 2.6). Gdy z menu skrótów wybierzemy pozycję dotyczącą funkcji i zmiennych składowych, opcja Calls wyświetli wszystkie funkcje wywoływane przez wybrane funkcje składowe, natomiast opcja Called By poda wszystkie miejsca w projekcie, z których dana funkcja jest wywoływana. Umieszczona w menu skrótów opcja Group By Access używana jest do usystematyzowania elementów składowych funkcji. Gdy opcja Group By Access nie jest zaznaczona, składowe pojawiają się na liście w porządku alfabetycznym. W przeciwnym wypadku porządek odpowiada dostępności konkretnej składowej: prywatne, chronione i na końcu publiczne. [5"?J Functiora: | :'^| Al ~3i Public: ;-i -,_J CPalog !;; ;_i CWnd 3 L3 CCmdTaiget ! QCObiecl CMinuteDlo::CMinul"Dlolcto CWnd -l Prolecled: ^:CMinu(eDlfl':_6elBaseMessageMap(TOid) M, CMinuteDlg::DoOalaE"change(claat CDalaEuchange •) ^6 CMinuteDlg::GelMesiageMaplvtiidj CMinuteDlg,:m_hlcon d •S; struci AB<_MSaMAPconsl CMrote01g;:mesaaeMap iW, CMnmeDlg;:Onln"Dialog(void) CMnuleDlg::OnPainl("oid) CMinu>eDlg::Onfi8SsM6(TOid) CMnuteDlg"OnQueiliOraglcofudio\nyipfoiect3\ininute\iTiriute.cpp(57) Rysunek 2.6. Wybranie opcji Base CIasses ukazuje hierarchię klas Możemy teraz przystąpić do modyfikowania klasy CMinuteDIg. Pierwszym krokiem będzie wybranie klasy CMinuteDIg i otwarcie dla niej menu skrótów. Dla przypomnienia: otwiera się je klikając prawym klawiszem myszy nazwę wybranej klasy. Z otwartego menu kontekstowego wybieramy następnie pozycję Add Member Variable. Otworzymy w ten sposób okno dialogowe Add Member Variable pokazane na rysunku 2.7. Jak wskazuje nazwa tego okna, służy ono do dodawania nowych zmiennych do klasy. Musimy ustalić typ nowej zmiennej, nadać jej nazwę oraz zadeklarować tryb dostępu do niej. Dodawanie do klasy nowych elementów składowych bez edycji kodu źródłowego Okna dialogowe Add Member Variable oraz Add Member Function, dostępne z menu skrótów panelu ClassView automatycznie umieszczają niezbędne linie kodu w plikach źródłowych klasy, nagłówkowym i implementacyjnym. 42 Poznaj Visual C++ 6 W oknie Variable Type należy wpisać i n t, a w pozycji Variable Declaration wprowadzić m_nClickCount. Tryb dostępu pozostawić należy bieżący, czyli Public. W ten oto sposób dodajemy do klasy CMinuteDlg zmienną typu całkowitego, o nazwie m_ndickCount. Owa zmienna używana będzie jako licznik kliknięć przycisku Press Me. Kiedy już dokonaliśmy niezbędnych wpisów, należy kliknąć OK. Add Membei Variable Variable łype: jQy~ Cancel m_nClickCount rAccess - •"-' -'"--•--•.-....-......--.-- l (r Public t~ Protected i" Pr.iv.ate Rysunek 2.7. Dodawanie nowej zmiennej poprzez ClassView Konwencja stosowana w nazewnictwie zmiennych składowych Normalną, aczkolwiek niekonieczną praktyką jest nadawanie zmiennym składowym funkcji nazw z prefiksem m_. Pomaga to podczas czytania kodu rozpoznawać, które zmienne są składowymi danej klasy, a które należą do funkcji. Również zwyczajowo stosuje się oznaczenia typu danej zmiennej w jej nazwie. Przykładowo, litera n w pre-fiksie nazwy oznacza, że zmienna jest zmienną liczbową. Nowa zmienna powinna pojawić się w panelu ClassView jako element podrzędny klasy CMinuteDlg. Podwójnym kliknięciem wywołujemy okno edytora, z kursorem umieszczonym w miejscu, w którym należy wpisać kod definiujący nową zmienną. Ponieważ zmienna ta służyć będzie do zliczania kliknięć przycisku Press Me, jej początkowa wartość musi wynosić zero. Właściwym miejscem na zainicjowanie tej zmiennej jest konstruktor klasy CMinuteDlg. Aby dodać kod inicjujący, musimy dwukrotnie kliknąć konstruktor funkcji składowej (jest to pierwsza podrzędna pozycja w klasie CMinuteDlg). Edytor wykona skok do stosownego pliku źródłowego. By zainicjować zmienną zliczającą, wpiszemy kod z listingu 2.1. Listing 2.1. LST02_1 .CPP - inicjowanie zmiennej składowej w konstruktorze klasy 1 CMinuteDlg::CMinuteDlg(CWnd* pParent /*=NULL*/) 2 : CDialog(CMinuteDlg::IDD, pParent) 3 { 4 //{(AFX_DATA_INIT(CMinuteDlg) Środowisko programistyczne 43 5 // NOTĘ: the CIassWizard will add member initialization here 6 //}}AFX_DATA_INIT 7 // Notę that Loadlcon does not require a subsequent Destroylcon in Win32 8 m_hlcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); 9 10 m_nClickCount =0; O 11 } O Zmienna m_ndickcount jest inicjowana z wartością O w oknie dialogowym konstruktora klasy. Początkowa wartość zmiennej wynosi O, teraz musi zwiększać się za każdym razem, kiedy przycisk Press Me zostanie kliknięty. Musimy więc kliknąć dwukrotnie funkcję składową klasy CMinuteDlg, OnPressMe (). W oknie edytora wyświetlona będzie funkcja, która została dodana w przykładzie z rozdziału l. Trzeba pamiętać, że funkcja ta wykonywana jest, gdy komunikat BN_CLICKED zostaje wysyłany do okna dialogowego, po kliknięciu przycisku IDC_PRESS_ME. By spowodować inkrementację wartości zmiennej, trzeba dodać kod z listingu 2.2. Listing 2.2. LST02_2.CPP - zliczanie kliknięć przycisku Press Me 1 void CMinuteDlg::OnPressMe()O 2 ( 3 MessageBox("Thanks. I needed that."); 4 5 m_nClickCount++; @ 6 } O OnPressMe () jest funkcją obsługi komunikatu, wywoływaną po każdym kliknię-ciu przycisku l DC PRESS ME. @ W celu wyświetlenia odpowiedniego komunikatu funkcja inkrementuje wartość zmiennej, zliczającej kliknięcia przycisku. Zmienna zliczająca zostaje powiązana z kuknięciem przycisku. Następnym krokiem, który wykonamy, będzie dodanie nowej funkcji składowej. Należy więc kliknąć prawym klawiszem myszy klasę CMinuteDlg, aby otworzyć menu skrótów, a z niego wybrać pozycję Add Mem- 44 Poznaj Visual C++ 6 ber Function. Pojawi się okno dialogowe z pozycją Add Member Function, jak na rysunku 2.8. Okna tego użyjemy do umieszczenia w klasie CMinuteDlg nowej funkcji składowej. Add Membet Function Function Type: OK BOOL Cancel jClickCountMessage ; Access • ---..-, [ '? Public C Protected C- p,jvate r 5>afc r Virtual Rysunek 2.8. Dodawanie nowej funkcji składowej poprzez menu skrótów ClassView W oknie tym musimy określić typ funkcji (jest to typ zmiennej, którą funkcja ma zwracać). W polu Function Type należy umieścić wpis BOOL. Oznacza to, iż zmienna zwracać będzie jedną z dwóch możliwych wartości, TRUE lub FALSE. Funkcja musi również posiadać odrębną nazwę. W polu Function Declaration należy więc wpisać ClickCountMessage. Jeśli zmienna ta wymaga podania parametrów, możemy to uczynić również w polu Function Declaration. Jako tryb dostępu do funkcji powinniśmy wskazać Public. Okienka wyboru Static oraz Virtual należy pozostawić nieoznaczone. Po dokonaniu zmian klikamy OK. Od tego momentu nowa funkcja powinna być widoczna w panelu ClassView, jako podrzędna pozycja w klasie CMinuteDlg, a zapis funkcji w oknie edytora. Teraz należy wpisać kod zgodny z listingiem 2.3. Ostrzeżenie: dodanie składowej nie powoduje automatycznego zatwierdzenia jej zawartości Zatwierdzanie zawartości składowej (funkcji lub zmiennej) nie jest przeprowadzane, gdy zostaje ona wpisana do klasy poprzez okno dialogowe Add Member. Możliwe jest popełnienie pomyłki podczas wpisywania. Przykładowo, wpisanie funkcji, której przypisana została wartość zwracana BOO (zamiast BOOL), spowoduje błędy podczas kompilacji. Należy więc dokładnie sprawdzić składnię przed kuknięciem OK. Środowisko programistyczne_____________________ ___ 45 Listing 2.3. LST02_3.CPP - w zależności od liczby kliknięć, wyświetlony zostanie odpowiedni komunikat 1 BOOL CMinuteDlg::ClickCountMessage() 2 { 3 if (m_nClickCount == 0) 4 ( 5 MessageBox("You haven't clicked Press Me", "Don't Leave", MB_ICONSTOP); O 6 return FALSE; @ 7 } 8 if (m_nClickCount > l) 9 ( 10 CString str; 11 str.Format("Press Me was clicked %d times", m nClickCount (c)) ; 12 MessageBox(str); 13 } 14 else 15 { • 16 MessageBox("Press Me was clicked once"); 17 } 18 return TRUE; O 19 } O Funkcja MessageBox () może używać różnych stylów. W tym przykładzie umieszczony jest znacznik MB_ICONSTOP, który powoduje wyświetlanie znaku stop obok treści komunikatu. (c) Zwrócona zostaje wartość FALSE, ponieważ wartość zmiennej zliczającej wynosi zero. (c) Funkcja CString'sFormat () użyta jest do sformułowania tekstu komunikatu, przekazanego następnie do MessageBox (). O Zwrócona zostaje wartość TRUE, gdy wartość zmiennej zliczającej osiągnie l lub więcej. Wartość zmiennej m_nClickCount będzie sprawdzana i używana do wyświetlania odpowiedniego komunikatu. W pierwszym odwołaniu do funkcji MessageBox () w linii 5 umieszczone są dwa dodatkowe argumenty. Pierwszy z nich, "Don' t Leave", jest tytułem okna komunikatu. Znacznik MB_ICONSTOP powoduje wyświetlanie ikony w kształcie znaku stop. 46 Poznaj Visual C++ 6 Pojawiająca się w linii 10 zmienna str należy do klasy cstring, która jest jedną z najbardziej użytecznych klas spośród zawartych w bibliotece MFC. Klasa cstring służy bowiem do obsługi wielu prostych operacji programistycznych, polegających na manipulowaniu tekstem. Funkcja CStringFormat () użyta w linii 11, spełnia to samo zadanie, co funkcja printf () w języku C. Warto zauważyć, że funkcja zwraca FALSE (linia 6), gdy stan licznika równy jest zeru lub TRUE (linia 18) dla każdej innej wartości licznika. Nowa funkcja jest zatem ukończona, lecz nie używana jeszcze przez program. Powiemy o tym nieco dalej. Używanie panelu ResoureeYiew W celu korzystania z panelu ResourceView musimy kliknąć kartę o tej nazwie, widoczną u dołu okna roboczego projektu. Panel otwiera listę zasobów używanych w projekcie, jak widać na rysunku 2.9. Rysunek 2.9. Wybór karty ResourceView umożliwi operowanie zasobami projektu Panelu tego użyliśmy już wcześniej, do umieszczenia nowego przycisku. Teraz zostaną omówione inne rodzaje zasobów. Okno wyświetla wszystkie zasoby użyte w bieżącym projekcie. Termin zasoby (ang. resources odnosi się do wszelkich elementów wizualnych projektu. Przykładowo, zasobami takimi są okna dialogowe, listy menu, a także ikony. Środowisko programistyczne 47 Rozdzielność plików zasobowych od kodu programu Przyczyną, dla której zasoby programu nie są jego integralną częścią, jest umożliwienie ich niezależnej obróbki. Pozwala to na tworzenie kilku wersji językowych danej aplikacji, poprzez zmiany zawartości samych plików zasobowych, nie zaś przeróbkę całego kodu źródłowego programu. Podobnie jak w przypadku ClassView, w panelu ResourceView możemy posługiwać się menu skrótów, klikając prawym klawiszem myszy. Teraz z menu skrótów Minutę Resources wybierzmy pozycję Insert. Okno dialogowe Insert Resources pokazane jest na rysunku 2.10. W oknie tym wyświetlane są różne typy zasobów dostępnych dla projektu w Visual C++. Tablica akceleratorów jest listą skojarzeń kombinacji klawiszy na klawiaturze z poleceniami programu. Terminem zwykle używanym na określenie akceleratora jest skrót. Skrót stanowi szybszy sposób wywoływania poleceń, niż ich wybieranie z menu. Na przykład, podczas pisania tej książki bardzo często używany był skrót Ctrl+X i było to znacznie wygodniejsze niż wybieranie polecenia Cut z menu Edit. Inscit Resouice New ;i^) Bitmap l^i f+! El Cursor IJi Dialog iH HTML Q Icon ^) Menu S SIring Table S Toolbar 151 Vs["ion Import,. Gustom.. Cancel Rysunek 2.10. Wyświetlone poprzez użycie opcji Insert w menu skrótów ResourceView okno dialogowe wykazuje rodzaje zasobów możliwych do włączenia do projektu Zasób bitmap jest obrazkiem, o pewnych rozmiarach, złożonym z kropek. Każda taka kropka może posiadać odmienną od innych barwę. Bitmapy używane są między innymi do wyświetlania obrazków na przyciskach, które widzimy w paskach narzędziowych. Ekran powitalny, wyświetlany podczas uruchamiania Visual C++ również jest bitmapą. Kursor także jest zasobem graficznym. Służy on przede wszystkim jako wskaźnik myszy (standardowy kursor w systemie Windows ma postać strzałki). 48 Poznaj Visual C++ 6 Ikona oczywiście również jest zasobem graficznym. Projekt Minutę posiada ikonę o nazwie IDR_MAINFRAME. Podwójne kliknięcie pozycji IDR_MAINFRAME spowoduje otwarcie ikony w oknie edytora. Zasób dialogowy jest szablonem okna, które zawierać może zasoby innych typów, jak menu lub przyciski. Edycja tego rodzaju zasobów omówiona zostanie w rozdziale 3, "Tworzenie i projektowanie okien dialogowych". Zasób menu reprezentuje to, na co wskazuje nazwa, czyli menu. Zwykle aplikacja posiada menu umieszczone u góry okna głównego (na przykład Developer Studio), może ich mieć jednak kilka. Przykładowo nowy zasób menu może zostać dodany do projektu i być używanym przez aplikację jako menu skrótów. Tablica łańcuchów znakowych powinna zawierać wszystkie zwroty tekstowe używane przez aplikację. Każdy z tych zwrotów posiada odrębną nazwę, która służy do ich powiązania z kodem źródłowym (rysunek 2.11). Powodem umieszczenia wszystkich zwrotów w osobnej tabeli, a nie bezpośrednio w pliku źródłowym, jest ułatwienie tłumaczenia ich na inne języki. Gdy wszystkie zwroty użyte w aplikacji znajdują się w takiej tabeli, inna tabela może zawierać te same zwroty, lecz w innym języku. W momencie startu aplikacji można w takiej sytuacji wybrać odpowiednią tabelę. SBFind.ic - Stron Table (Strinn Table) 57632 i tsase m teledKimnErase Rysunek 2.11. Przykładowa tablica łańcuchów Zasób pasków narzędziowych zawiera zestaw przycisków. Zwykle każdy z nich reprezentuje polecenie, dostępne również z menu. Na przyciskach tych mogą być umieszczane symbole bitmapowe. Do przycisków można również przypisywać podpowiedzi, pojawiające się w "dymkach", gdy wskaźnik myszy znajdzie się ponad przyciskiem. Zasób wersji zawiera dane o aplikacji, na przykład nazwę firmy autorskiej, numer wersji i tak dalej. Informacje te zapisane są w standardowym formacie, który umożliwia innym aplikacjom dostęp do nich. Środowisko programistyczne 49 Nowe zasoby możemy dodawać w każdej chwili. Gdy już zostaną dodane, automatycznie otrzymają odrębne nazwy. Dla przykładu, nowe okno dialogowe umieszczone na szablonie mogłoby otrzymać nazwę IDD_DIALOGI. Jednak programiści zwykle zmieniają wygenerowane przez program nazwy, na bardziej wymowne, wyrażające w kontekście programu rodzaj zastosowanego zasobu. Tak było też w momencie, gdy dokonywaliśmy zmiany nazwy IDC_BUTTONI na IDC_PRESS_ME. Aby obejrzeć nazwy wszystkich zawartych w projekcie zasobów, wybrać należy pozycję Resource Symbols z menu View. Otworzy to okno dialogowe Resource Symbols, pokazane na rysunku 2.12. Możliwe jest zlokalizowanie określonego zasobu, gdy na przykład jego nazwa niewiele nam mówi, poprzez kliknięcie nazwy w oknie dialogowym Resource Symbols, dostępnego z menu View, a następnie kliknięcie przycisku View Use. Relouice Symboli Name IDD_ABOUTBOX ' ~ IDD_M1NUTE_DIALOG ffi IDM_ABOUTBOX I IDR.MAINFRAME ff IDS ABOUTBOX Value In Use 100 rf 102 v 0x0010 128 ^ 101 v (~ Show read-oniy symbols Used by: Rysunek 2.12. Okno Resource Symbols wyświetla wszystkie zasoby użyte w projekcie Każdy zasób użyty w projekcie możemy poddać edycji, poprzez podwójne kliknięcie jego nazwy w panelu Resource View. Narzędzia i opcje dostępne w edytorze zmieniają się w zależności od rodzaju edytowanego zasobu. Tworzenie i edytowanie zasobów różnych typów omówione zostanie w następnych rozdziałach. Tymczasem jednak dokończymy rozwijany przykład. Minutę. Używając panelu Resource View należy postąpić według niżej przedstawionych instrukcji. Określanie zachowań przycisków w oknie dialogowym 1. Kuknij dwukrotnie pozycję IDD_MINUTE_DIALOG, co otworzy szablon okna dialogowego. 2. Dwukrotnie kliknij przycisk OK. 50______________________________________Poznaj Visual C++ 6 3. Zostanie otwarte okno dialogowe Add Member Function. Kliknij OK, aby zaakceptować bieżącą nazwę funkcji: OnOK. Szkielet nowej funkcji zostanie utworzony i wyświetlony w oknie edytora, w gotowości na wprowadzenie kodu. 4. Wpisz zawartość funkcji OnOK, według listingu 2.4. 5. Dwukrotnie kliknij IDD_MINUTE_DIALOG, by ponownie otworzyć szablon. 6. Kliknij dwa razy przycisk Cancel. 7. Kliknij OK w celu zaakceptowania bieżącej nazwy funkcji: OnCancel. 8. Wprowadź do tej funkcji kod zgodny z listingiem 2.5. Listing 2.4. LST02_4.CPP - zastąpienie bieżącej funkcji OnOK 1 void CMinuteDlg::OnOK() O 2 { 3 if ( ClickCountMessageO == TRUE ) 4 ( 5 CDialog::OnOK() ; @ 6 ' } 7 } O Ta funkcja jest nadrzędną wobec aktualnie zaimplementowanej funkcji CDialog:: OnOK () i stanowi procedurę obsługi komunikatu dla przycisku OK. @ Wywoływana zostaje klasa bazowa dla funkcji OnOK, co powoduje zamknięcie okna dialogowego. Listing 2.5. LST02_5.CPP - zastąpienie bieżącej funkcji OnCancel 1 void CMinuteDlg: .•OnCancel () O 2 { 3 if ( ClickCountMessageO == TRUE ) 4 { 5 CDialog::OnCancel();@ 6 } 7 ) O Umieszczenie funkcji nadrzędnej nad obecnie zaimplementowaną, CDialog: : OnCancel (). Funkcja nadrzędna służy do obsługi komunikatu skojarzonego z przyciskiem Cancel. , , : :- Środowisko programistyczne 51 @ Wywołanie klasy bazowej funkcji OnCancel (), które zamyka okno dialogowe. Dodaliśmy zatem dwie nowe funkcje. Pierwsza z nich jest nadrzędną dla funkcji CDialog: :OnOK(), druga natomiast dla funkcji CDialog: : OnCancel (). Obie wywołują ten sam efekt, czyli zamknięcie okna dialogowego aplikacji. Teraz jednak będą wywoływane tylko wtedy, gdy funkcja ClickCountMessage () zwróci argument TRUE. Jeśli nie jesteśmy do końca świadomi tego, co przed chwilą uczyniliśmy, powinniśmy wygenerować nowy plik wykonywalny i uruchomić go. Spróbujmy kliknąć OK lub Cancel, nie dotykając przycisku Press Me. W takiej sytuacji ujrzymy komunikat widoczny na rysunku 2.13, a aplikacja nie zostanie zamknięta, lecz będzie kontynuowała działanie. Don'" Leave You haven't ctcked Press Me Rysunek 2.13. Komunikat wyświetlany, gdy zostanie kliknięty przycisk OK lub Cancel Teraz kliknijmy kilkukrotnie przycisk Press Me, a następnie OK albo Cancel. Powinno zostać otwarte okno komunikatu, mówiącego nam, ile razy klikęliśmy Press Me (rysunek 2.14). Po naciśnięciu OK lub Cancel aplikacja powinna zostać zamknięta. Press Me was clicked 4 limes LOK: Rysunek 2.14. Komunikat o liczbie kliknięć Press Me Używanie panelu FileView Kliknijmy kartę FileView u dołu okna roboczego projektu. Wywoła to okno zawierające listę plików, które składają się na całość projektu (rysunek 2.15). 52 Poznaj Visual C++ 6 -~^--""-^ udioAdd-inWi2ad ,: C: Program Files\Micr«BoftVisua J ^'ElltendedStoiedPfocWizaid ''i,:'':, i:' filSAPIEntensionWizałd ^ .'l'" :-!";::; '^: E-IMakefile (T Craate new worisspace 8gMFCActiveXControlWizard r -;;j3 •.-;„!—i .::•!,p^.; :: §|MFCAppWi2ard(dll) 1" !:';:,.:-ns••,; . ..f ^airaft^fflJffl „—-„.- ...„—„„, ...^, jS: N ew D atabase Wisard —• y j Utiiity PtojecI 3Wi"32Application , „;, .„:.::l,i,:1;!1:.-:^ ,„„:1::!, 'i;:.:' '.,:;:„ „,,• ; ^]Win3-.r,nTOlpApplirafcn ^EI^W^a^liiailsfi&^Jiffliaaa^ s •Ł]Win32Dynamic-Link.Library fdWin32 - - i; Eontrols Would you like to include WOSA support? r" Windows Sockets -l»si» pjeasa enter a title for youi dialog; Person Next > Einish Cancel Rysunek 3.2. Wybór opcji dotyczących funkcjonalności okna 7. Kliknij Next. Otwarte zostanie okno dialogowe MFC AppWizard - Step 3. W ramach trzeciego kroku AppWizard zapyta nas, czy ma umieścić wewnątrz kodu swoje komentarze. Ułatwiają one późniejszą orientację, w którym miejscu powinniśmy dopisać własną część kodu. Wskazane jest udzielenie odpowiedzi Yes, Please. AppWizard tworzy program wykorzystując bibliotekę MFC w postaci plików DLL (Dynamie Link Library). Sprawia to, że powstałe na bazie projektu utworzonego w ten sposób pliki wykonywalne mogą mieć mniejszą objętość, ponieważ odczytują z dysku wszelkie potrzebne do działania funkcje MFC na bieżąco. 8. Kliknij Next, by przejść do kroku czwartego - MFC AppWizard - Step 4. 60 Poznaj Visual C++ 6 MFC AppWiZdłd - Step 4 ol 4 AppWizatd createt l.he (ollowing dasses for you: 'KiSSSiy^ttSSSSIUHIUSSSttUS^^ CPeisonDlg CIass narne: Headet (ile: yle$ {D: JIDOK3 Caption: pfidT llt?P' Visible Jgr Disabled r" Group 17 Tabstop r HelpID Rysunek 3.4. Edycja właściwości istniejącego przycisku kontrolnego Wprowadziliśmy zatem pewne, niewielkie modyfikacje w oknie dialogowym utworzonym przez AppWizard, ale jak postąpić, gdy chcemy otrzymać zupełnie nowe okno? Tworzenie nowego okna dialogowego 1. Otwórz panel ResourceView w oknie roboczym projektu. 2. Kliknij prawym klawiszem myszy katalog Dialog, by otworzyć menu skrótów. 3. Wybierz z niego pozycję Insert Dialog. Wywoła to otwarcie w edytorze zasobów szablonu.nowego okna dialogowego. Szablon ten standardowo wyposażony jest w przyciski OK oraz Cancel. PATRZ TAKŻE • Więcej informacji o panelu ResourceView znajduje się w rozdziale 2. Przydzielanie identyfikatora oknu dialogowemu Po umieszczeniu w projekcie nowego okna dialogowego, program automatycznie nadaje temu oknu odrębny identyfikator ID. Będzie on miał postać IDD_DIALOG, z dołączoną na końcu cyfrą. Ów identyfikator odnaleźć można następnie w katalogu Dialog, w panelu ResourceView. Pierwszą rzeczą, jaką być może będziemy chcieli zrobić, jest zmiana automatycznie przydzielonego identyfikatora na inny, mówiący o tym, czego dotyczy. Przykładowo, jeśli nasze nowe okno dialogowe służyć będzie do wprowadzania informacji na temat sprzedaży książek, jego identyfikator mógłby mieć postać IDD_BOOK_SALES, będąc w ten sposób bardziej rozpoznawalnym, niż na przykład IDD_DIALOG9. Konwencja stosowana przy nadawaniu identyfikatorów oknom dialogowym Normalną praktyką programistyczną jest stosowanie w identyfikatorach okien dialogowych prefiksu IDD_ dla odróżnienia ich od innych typów zasobów. Projektowanie i tworzenie okien dialogowych 63 Zmiana identyfikatora nowego okna dialogowego 1. Kliknij prawym klawiszem myszy w obszarze szablonu okna dialogowego, z dala od przycisków sterujących. Możesz kliknąć również pasek tytułowy okna. Otworzysz w ten sposób menu skrótów. Kliknięcie musi odbyć się w pewnej odległości od przycisków sterujących, aby mieć pewność, że otwarte zostanie menu skrótów dla okna dialogowego, a nie zawartych w nim elementów kontrolnych. 2. Z menu skrótów wybierz pozycję Properties. Zobaczysz okno dialogowe, widoczne tutaj na rysunku 3.5. Dialog Piopeities -W f Generał Stylet j MnieStyles | Extended S(ytes ~gl ID; jliaa^iMiBHIiH^Capfon: JDialog Font ramę: MS SansSerif Menu; Font tize: B Fan).., j X Pos: (O^ Pos: (O Rysunek 3.5. Nadawanie identyfikatora oknu dialogowemu 3. Wybierz kartę Generał. 4. W polu ID zastąp bieżący wpis, IDD_DIALOGI, nowym: IDD_PERSONALITY. Korzystanie z karty Dialog Generał Properties Modyfikowanie właściwości elementów projektu jest jedną z najprostszych czynności wykonywanych podczas projektowania okna dialogowego. Korzystanie z okna dialogowego Dialog Properties może nam ułatwić możliwość „przypięcia" go za pomocą pinezki, której wizerunek widnieje w lewym górnym rogu tego okna. Zostało to pokazane na rysunku 3.6. Taka czynność powoduje, iż okno Dialog Properties pozostaje otwarte, a informacje zawarte w nim podlegają zmianom i odnoszą się do aktualnie wskazanego elementu. Developer Studio daje taką możliwość również w stosunku do innych okien dialogowych. Dialog Piopeities ~|® T Generał | S tyłeś | Mnie S tyłeś | Entended Stytes ID: |lDD_PERSONALITY^C*'^ |Pe'sonaBy Dialog Fontname: M S S ans Serif ,, Menu; Font size: 8 Font... | X Pos: |o^ Pos: |5 Rysunek 3.6. Przypinanie okna dialogowego 64 __ ___ ______ __ Poznaj Visual C++ 6 O Pinezka została użyta w celu utrzymania okna w stanie otwartym. W ten sposób możemy dokonywać modyfikacji szablonu okna dialogowego, jak też wszelkich umieszczonych na nim elementów, bez konieczności ciągłego zamykania i otwie- rania nowych okien właściwości. Rodzaj wybranego zasobu determinuje zawartość tego okna, od niego też zależy, jakie karty zostaną udostępnione. Drugą czynnością, którą wykonamy w nowo utworzonym oknie dialogowym, będzie zmiana jego tytułu. Jest to napis widoczny na pasku tytułowym. Zmiany tej dokonamy poprzez zastąpienie bieżącego wpisu w polu Caption, wpisując zamiast niego Personality Dialog. Zmiana ta powinna być natychmiast widoczna - na pasku tytułowym szablonu okna dialogowego pojawi się tytuł, który przed momentem wprowadziliśmy. Na karcie Generał mamy do dyspozycji jeszcze inne opcje. Jedną z nich jest wielkość czcionki używanej do wyświetlania napisów wewnątrz okna dialogowego. Jeśli dokonamy zmiany bieżącej wielkości na większą od 8, cały szablon okna dialogowego również ulegnie powiększeniu. Dzieje się tak, ponieważ rozmiary całego okna obliczane są na podstawie wysokości oraz szerokości przypisanej mu czcionki. W naszym przykładzie, czcionką bieżącą jest MS Sans Serif o rozmiarze 8, co jest wielkością zwykle stosowaną w oknach dialogowych. Parametry X Pos oraz Y Pos określają położenie okna dialogowego na ekranie, po jego pierwszym wywołaniu. Współrzędne te odnoszą się do okna głównego aplikacji. Jednostka miary okien dialogowych Jednostka miary stosowana w projektowaniu okna dialogowego oparta jest o rozmiar wykorzystywanej w tym oknie czcionki. Można spotkać się z terminem DLU, co oznacza Dialog Unit (dosł. jednostka dialogowa). Wskazanie tych współrzędnych jako 0,0 powoduje umieszczenie okna dialogowego pośrodku okna-rodzica. Nadawanie stylu oknu dialogowemu W oknie Dialog Properties znajdujemy trzy karty odnoszące się do stylu okna dialo- gowego. Kliknijmy zatem pierwszą z nich, czyli Styles (rysunek 3.7). Projektowanie i tworzenie okien dialogowych 65 Dialog Propertie* •' " '-•- :'-: •/:-,», 1,:,/!,1::,;^:111 ^ ,1 ^ l® t Genet a' S tyłeś i Mole S tyłeś Extended Styles Style: F F lilie bar System meny r r CliĘ siblings ClipAildten Popu p d ::::1^ iorderi' y'" p- r Minimize box Manimize bon r r Horizontal sctoll Yeltical tcroll Dial og Fiame .I J Rysunek 3.7. Wybór stylu okna dialogowego Ostrożnie ze stylami! Pewne ustawienia stylu mogą powodować, że inne przestaną obowiązywać. Jest również możliwym zaordynowanie takich ustawień, które spowodują całkowitą bezuży-teczność okna dialogowego. Musimy być świadomi, iż zmiany stylu dotyczące pewnych elementów okna dialogowego mogą wywierać wpływ również na inne jego elementy. Przykładowo, jeśli usuniemy znacznik dla pozycji Title Bar, spowoduje to nie tylko usunięcie z szablonu paska tytułowego, ale jednocześnie brak dostępu do menu systemowego oraz zatarcie tytułu nadanego oknu. Jeżeli ponownie zaznaczymy Title Bar, będziemy musieli na nowo wprowadzić tytuł okna. Podobne problemy mogą pojawić się również w przypadku zmian dokonywanych na ramce okna. Należy także zdać sobie sprawę, że bardzo łatwo jest uczynić okno całkowicie nieużytecznym lub wręcz niewidocznym, dokonując nieprzemyślanych zmian jego stylu. Może to stanowić później problem ciężki do rozwiązania. Jeśli mamy podejrzenie, że popełniliśmy taki błąd, najprostszą metodą sprawdzenia tego jest porównanie ustawień przez nas poczynionych z ustawieniami odnoszącymi się do okna w pełni funkcjonalnego. Generalnie, w większości przypadków zaleca się stosowanie bieżących ustawień stylu. Dodawanie i pozycjonowanie kontrolek Okna dialogowe nie przedstawiałyby żadnej wartości, gdyby nie zawarte w nich kon-trolki. Gdy w oknie edytora otwarty jest szablon okna dialogowego, pasek menu Develo-per Studio zawiera dodatkowe menu, Layout. Powinniśmy widzieć również dodatkowe paski narzędziowe. Jeden z nich, Dialog (rysunek 3.8), służy edycji okna, natomiast drugi, czyli Controls (rysunek 3.9), dodawaniu kontrolek. 13 11 |ci«- -»a| oa + + |o+- -łoi t+ o a M X i^ ffl l &-14 26 10 4 8 12 Rysunek 3.8. Pasek narzędziowy Dialog wspomaga pozycjonowanie kontrolek 1. Test 2. Wyrównanie do lewej 3. Wyrównanie do prawej 4. Wyrównanie od góry 5. Wyrównanie od dołu 6. Środkowanie pionowe 7. Środkowanie poziome 8. Spacja w poziomie 9. Spacja w pionie 10. Ujednolicenie szerokości 11. Ujednolicenie wysokości 12. Ujednolicenie wymiarów 13. Włączenie / wyłączenie siatki 14. Włączenie / wyłączenie prowadnic l [33 —1^ KU 14 2———— abl- 3——————— —Aa —Q a- —————15 ————————16 4————————— K, (»- 5 l" l H —m E§- ——————————17 1 0 6 133 B tg - 19 Jk EE] - 8 ir blonu okna, to właśnie prowadnice. Są one dobrze widoczne na rysunku 3.13. Gdy krawędź kontrolki pokryje się z prowadnicą, przesunięcie prowadnicy powoduje jednoczesne przesuwanie całego obiektu. Efekty takiego działania prowadnic możemy zaobserwować zwiększając szerokość okna dialogowego. Przyciski OK oraz Cancel ulegają przesunięciu wewnątrz szablonu, ponieważ ich pozycja jest powiązana z marginesem wyznaczonym przez prowadnice. Nie jesteśmy jednak ograniczeni tylko do prowadnic stanowiących margines szablonu. Możemy umieszczać na nim dodatkowe prowadnice, klikając linijki widoczne u góry i po lewej stronie szablonu. Gdy zatem klikniemy linijkę górną, możemy dodać prowadnicę pionową, poziomą zaś klikając linijkę po lewej. Pojawienie się strzałki na linijce po jej kliknięciu sygnalizuje dodanie nowej prowadnicy. Umieszczenie jej w pobliżu krawędzi jednej z kontrolek spowoduje przyciągnięcie prowadnicy do obiektu. Ppdobnie dodanie obiektu i umieszczenie go w okolicach prowadnicy wyrówna do niej położenie nowego elementu. Obiekty stykające się z prowadnicami są przesuwane automatycznie Gdy krawędź kontrolki pokrywa się z prowadnicą, jej przesunięcie powoduje zmianę pozycji obiektu. Prowadnice wyświetlane są tylko podczas edycji okna dialogowego i nie są widoczne podczas testowania okna lub działania aplikacji. Organizacja kontrolek w oknie dialogowym Poza kwestiami związanymi z umieszczaniem i pozycjonowaniem kontrolek, istnieją również inne, które powinniśmy rozważyć podczas projektowania okna dialogowego. Na przykład, zgrupowanie pewnych obiektów może nam pomóc w utrzymaniu przejrzystej formy okna. Jeszcze inną kwestią jest ustalenie porządku, w jakim użytkownik będzie mógł przechodzić pomiędzy obiektami, używając klawisza Tab. Możemy również ustalić skróty klawiaturowe. Grupowanie obiektów Jedną z metod uzyskiwania przejrzystego wyglądu okna dialogowego jest grupowanie występujących w nim kontrolek. Grupom obiektów możemy nadawać nazwy, a także obejmować je wspólną ramką. Jak zwykle, najlepszą metodą zrozumienia danego zagadnienia jest sprawdzenie go w praktyce. Dodajmy więc kilka nowych kontrolek do naszego okna dialogowego. Projektowanie i tworzenie okien dialogowych 75 Grupowanie obiektów służy nie tylko ładnemu wyglądowi okna Grupowanie obiektów ma znaczenie nie tylko dla wyglądu okna dialogowego. Gdy zawartością grupy są kontrolki typu pole wyboru lub przełącznik opcji, pozostają one w ścisłym związku ze sobą. Stan poszczególnych obiektów wpływa na inne. Dodanie grupy obiektów oraz zawarcie w niej kilku pól wyboru 1. Wybierz grupę obiektów z paska narzędziowego Controls. 2. Umieść ją na szablonie, poniżej obecnych już obiektów. 3. Zmień rozmiar grupy, używając uchwytu w prawym dolnym rogu ramki formatującej, by uzyskać efekt widoczny na rysunku 3.14. Rysunek 3.14. Grupowanie kontrolek nadaje oknu dialogowemu schludny wygląd 4. Zmień nazwę okna grupy na Anałysis. 5. Z paska narzędziowego wybierz pole wyboru. 6. Dodaj do szablonu cztery kolejne pola wyboru. Ustal ich pozycje wewnątrz okna grupującego, jak widać na rysunku 3.15. 76 Poznaj Visual C++ 6 iloa Fifst name Sumame Edit , •Anałytis •-••-•••-: r" Talkatiye l r" Likes Animals l r" Watche»theNews r" Plays Sport Rysunek 3.15. Pozycjonowanie kontrolek wewnątrz okna grupy 7. Wprowadź nazwy dla kolejnych pól wyboru (w przykładzie powyższym są to określenia cech charakteru). 8. Użyj metod opisanych w niniejszym rozdziale, aby uporządkować i wyrównać nowe elementy okna Anałysis. PATRZ TAKŻE • Więcej informacji o grupowaniu obiektów znajdziesz w rozdziale 4. Dodawanie kilku obiektów tego samego typu Nie istnieje możliwość umieszczania na szablonie kilku obiektów za jednym razem. Po umieszczeniu jednego, aby umieścić następny, należy wybrać go z paska narzędziowego Controls i powtórzyć procedurę. Ustalenie porządku dostępu do okien przy użyciu klawisza Tab Poszczególne kontrolki okna dialogowego mogą być udostępniane kolejno, według pewnej sekwencji. Użytkownik przemieszcza się pomiędzy obiektami, według tej sekwencji, używając klawisza Tab. Jednoczesne użycie klawiszy Shift+Tab powoduje ruch wsteczny. Obiekt, na którym użytkownik zatrzymał sekwencję, staje się obiektem bieżącym. Jeśli przykładowo obiektem tym jest przycisk sterujący, widoczny będzie na nim prostokąt narysowany przerywaną linią. Oznacza to, że przycisk ten jest aktywny. Kontrolka może zostać wyłączona z sekwencji poprzez usunięcie znacznika w polu wyboru, na karcie Generał w oknie dialogowym właściwości danego obiektu. Projektowanie i tworzenie okien dialogowych 77 Ustalanie sekwencji dostępu do kontrolek l. Wybierz pozycję Tab Order z menu Layout. Na szablonie przy każdej z kontrolek pokazane zostaną liczby, odwzorowujące sekwencję, w jakiej kolejne obiekty są udostępniane (rysunek 3.16). Wałysis •Młalkatrró ikrlikesAnimals :V'"A'atehes the Ne^ Płlays Sport Rysunek 3.16. Modyfikowanie sekwencji dostępu 2. Kliknij obiekt, który ma być aktywny w momencie otwierania okna dialogowego. Obiekt ten otrzyma numer l. 3. Klikaj kolejne obiekty w takim porządku, w jakim mają być udostępniane klawiszem Tab. Obiekty otrzymają kolejne numery. 4. By sprawdzić prawidłowość dokonanych ustaleń sekwencji, użyj Test z menu Layout lub skrótu klawiaturowego Ctri+T. Następnie naciskając klawisz Tab, prześledź ustaloną sekwencję. Przemieszczanie się pomiędzy kontrolkami okna dialogowego Porządek tabulatora jest sekwencją, wedle której kolejne kontrolki umieszczone w oknie dialogowym stają się dostępne, gdy użytkownik przyciska klawisz Tab. Tylko jedna kontrolka może być dostępna w danym momencie, W trybie testowym nasze okno wygląda dokładnie tak, jak w gotowej działającej aplikacji, co pozwala sprawdzić funkcjonalność i prawidłowość działania poszczególnych elementów. Wyjście z trybu testowego następuje po naciśnięciu klawisza Escape. 78______________________________________Poznaj Visual C++ 6 Ustalanie skrótów klawiaturowych Skrót klawiaturowy zostaje przypisany do kontrolki poprzez umieszczenie znaku & przed literą stanowiącą mnemonikę skrótu, w nazwie obiektu. Przykładowo, gdy przycisk sterujący posiada nazwę E&xit, napis na przycisku będzie wyglądał tak: Exit. Efekt jednakowy z kuknięciem tego przycisku da naciśnięcie kombinacji klawiszy Alt+X lub AIt+x. Jeśli w naszym oknie dialogowym znajdzie się kilka obiektów o takich samych mnemonikach, to użycie skrótu klawiaturowego odnosić się będzie do obiektu występującego najwcześniej w sekwencji dostępu. By zapobiec zaskoczeniu, edytor zasobów pozwala nam sprawdzić, czy taka sytuacja występuje. Aby z tej możliwości skorzystać, z menu skrótów edytora zasobów wybierzmy pozycję Check Mnemonics. Jeśli nie występują duplikaty mnemonik, otrzymamy komunikat „No duplicate mnemonics found". Gdyby jednak duplikaty^takie występowały, zostaniemy zapytani o zaznaczenie obiektów posługujących się tymi duplikatami, co ma na celu usunięcie konfliktów. Skróty używające mnemonik Mnemonika jest podkreśloną literą, występującą w nazwie obiektu kontrolnego, służącą do wywoływania za pomocą klawiatury efektu tożsamego z wybraniem danego obiektu za pomocą myszy. Skrót jest aktywowany kombinacją klawisza Alt i litery wskazanej jako mnemonika. Mnemonikę można określić wstawiając w nazwie obiektu znak & przed wybraną literą. ^ , .,: :: :, PATRZ TAKŻE • Więcej informacji o określaniu skrótów klawiaturowych znajdziesz w rozdziale 5. Rozdział 4 Używanie przycisków sterujących Wykorzystywanie przycisków różnych typów Operowanie przyciskami podczas wykonywania programu_____ Mapowanie komunikatów do funkcji C++ W swoich aplikacjach możemy wykorzystywać trzy typy przycisków sterujących. Są to: przyciski „zwykłe", przełączniki oraz pola wyboru. Każdy typ przycisku wykonuje inną funkcję. Zwykły przycisk wywołuje określoną funkcję, w momencie gdy zostanie kliknięty. Przełączniki umożliwiają dokonanie wyboru jednego i tylko jednego elementu z listy dostępnych opcji. Pole wyboru natomiast zachowuje się w sposób odmienny od przełącznika, umożliwiając wybór więcej niż jednej, a nawet wszystkich pozycji dostępnych na liście lub żadnej. Przyciski sterujące służą do sterowania pracą okna dialogowego. Spotykamy je we wszystkich oknach tego rodzaju. Właściwe ich użycie powoduje, że aplikacja staje się prostsza i łatwiejsza do zrozumienia. Klasa CDialog implementuje funkcje obsługi przycisków OK oraz Cancel Edytor zasobów umieszcza w każdym nowym szablonie okna dialogowego standardowe przyciski OK oraz Cancel. Klasą bazową dla tych obiektów jest CDialog, która implementuje funkcje obsługi komunikatu BN_CLICKED pochodzącego od tych właśnie przycisków: OnOK () oraz OnCancel (). Można jednak wymusić zmianę ich normalnego funkcjonowania. Użycie przycisków Przyciski są najprostszymi kontrolkami w systemie Windows. Każdy z nich reprezentuje pojedyncze polecenie, a po kliknięciu wywołuje przypisaną do niego procedurę. Prawie każde okno dialogowe posiada co najmniej dwa takie przyciski: OK oraz Cancel. 80 Poznaj Visual C++ 6 AppWizard automatycznie umieszcza oba te przyciski w projekcie aplikacji opartej na architekturze okna dialogowego. Niektóre okna wymagają oczywiście dodatkowych przy- cisków. O tym, jak je umieścić mówiliśmy przy okazji edytora zasobów. Początkowe właściwości i style przycisków ustalane są również w tym edytorze. Ale co mamy zrobić, jeśli chcemy by właściwości lub styl przycisku zmieniały się w trakcie pracy programu? Gdybyśmy chcieli, by napis na przycisku zmieniał się w zależności od pewnych warunków, które mogą zaistnieć w czasie pracy aplikacji, musimy uzyskać dostęp do niego przez aplikację oraz metodę, która pozwoli zdefiniować nowy napis. W naszym przykładzie wykorzystamy funkcję GetDlgitem() do pobierania wskaźnika obiektu cwnd, który reprezentuje dany przycisk. Następnie nauczymy się, jak użyć wskaźnika obiektu cwnd do zmiany napisu oraz innych atrybutów przycisku. Wywołamy teraz AppWizard w celu utworzenia nowego projektu o nazwie Buttons, opartego o okno dialogowe. Kiedy projekt będzie gotowy, umieśćmy na jego szablonie pięć nowych przycisków. Być może będziemy musieli w tym celu zwiększyć szerokość szablonu oraz wymiary przycisków. Umieszczenie na szablonie nowych przycisków 1. Otwórz panel ResourceView, rozwiń katalog Dialog i dwukrotnie kliknij pozycję IDD_BUTTONS_DIALOG. W oknie edytora umieszczony zostanie szablon okna dialo- gowego. Kliknij tekst widoczny pośrodku, a następnie usuń go, naciskając klawisz Delete. 2. Kliknij pozycję Buttons na pasku narzędziowym Controls i dodaj do szablonu u góry po lewej stronie, dwa nowe przyciski, jak na rysunku 4.1. soft D«vdcper Shidjo • IBultoM.lc - IDD BUTTONS DIALOG IEngll*hlU.S.)l(D*at«g11 jlkippiw) "El es;1' " u , •te, ^."i 3 łtłf •*>% M8- ./.^iBtaB""! t ^ fi~n»"e Cont.?nt^ ^j ^ M : SS ^w ^ F'* '^ ''h ^'^•fi' JButlol .•.JOitbB Rysunek 4. l. Układ okna dialogowego Buttons Używanie przycisków sterujących 81 3. Zaznacz pierwszy z przycisków, kliknij prawym klawiszem myszy, by wywołać menu skrótów i wybierz pozycję Properties. Otworzy się okno dialogowe Push Button Properties. Możesz przypiąć to okno za pomocą pinezki widocznej w lewym górnym rogu, by pozostawało otwarte przez cały czas. 4. Wprowadź nowy identyfikator w polu listy rozwijanej; niech to będzie na przykład: IDC_SHOW_HIDE. Nadaj nową nazwę przyciskowi: Hide, korzystając z pola edycji Caption. 5. Zaznacz drugi przycisk. Jego identyfikator zapisz jako IDC_ENABLE_DISABLE, a napis na przycisku wprowadzony do pola Caption niech brzmi Disable. 6. Umieść jeszcze trzy następne przyciski i rozmieść je u dołu szablonu, jak widać na rysunku 4. l. 7. Zaznacz wszystkie trzy nowe przyciski, klikając je po kolei z wciśniętym klawiszem CtrI. 8. Wywołaj menu skrótów (o ile nie przypiąłeś okna właściwości) i wybierz kartę Extended Styles w oknie dialogowym Multiple Selection Properties. 9. Zaznacz teraz przycisk pierwszy od lewej. Wywołaj okno właściwości. Nadaj identyfikator: IDC_LEFT i Left jako nazwę w polu Caption. Dla środkowego przycisku wprowadź odpowiednio: IDC_CENTER i Center, natomiast dla prawego: IDC_RIGHT oraz Right. 10. Zamknij okno Push Button Properties. Szablon okna powinien wyglądać teraz tak, jak na rysunku 4.1. PATRZ RÓWNIEŻ ^ Szczegóły dotyczące tworzenia aplikacji w oparciu o okno dialogowe znajdziesz w rozdziale 3. * Więcej informacji o projektowaniu okien dialogowych znajduje się w rozdziale 3. Dodawanie funkcji obsługi zdarzenia użycia przycisku Przycisk, jak każdy inny obiekt sterujący aplikacji Windows, wysyła do swego okna-przodka komunikat o wystąpieniu określonego zdarzenia. Gdy zdarzeniem tym jest klik-nięcie przycisku, komunikatem wysłanym do okna dialogowego jest BN_CLICKED. Aby program mógł wykonać odpowiednią czynność po odebraniu takiego komunikatu, musi zostać uprzednio utworzona funkcja, która odpowiedzialna będzie za obsługę odebranego komunikatu. Każdy z pięciu nowych przycisków sterujących otrzyma teraz własną funkcję obsługującą wysyłany przez niego komunikat. Funkcja przycisku IDC_SHOW_HIDE będzie powodowała wyświetlenie bądź ukrycie trzech przycisków u dołu okna oraz zmianę napisu na przycisku z Hide na Show. Przycisk IDC_ENABLE_DISABLE będzie dzięki przypisanej 82 Poznaj Visual C++ 6 mu funkcji powodował włączenie lub wyłączenie działania dolnych przycisków, a także zmianę napisu z Disable na Enable, który na nim widnieje. Kiedy przycisk zostaje postawiony w stan wyłączony (Disable) napis na nim widniejący zostaje przyszarzony, a on sam nie może odbierać poleceń wprowadzanych za pomocą myszy lub klawiatury. Działanie przycisków Left, Center oraz Right polegać będzie na zmianie tytułu okna dialogowego. Teraz dodamy funkcję obsługi komunikatu BN_CLICKED dla pierwszego z przycisków, natomiast nieco później uczynimy to samo w stosunku do pozostałych. Dodanie funkcji obsługi kliknięcia przycisku 1. Kliknij dwukrotnie przycisk Hide, co spowoduje otwarcie okna dialogowego Add Member Variable. 2. Kliknij OK, by zaakceptować bieżącą nazwę funkcji (Member Function Name), OnShowHide. Otrzymasz w ten sposób szkielet nowej funkcji. Jej zapis zostanie otwarty w oknie edytora, gotowy na umieszczenie twojego kodu. Funkcja OnShowHide () będzie wywoływana za każdym klinięciem przycisku lDC_SHOW_HiDE. Jakie mają być jednak następstwa jej działania? Aby odpowiedzieć na to pytanie, uczynimy małą dygresję i wyjaśnimy pojęcie mapy komunikatów. Istota map komunikatów Struktura MFC wykorzystuje system zwany mapowaniem komunikatów dla utworzenia pomostu pomiędzy systemem Windows i funkcjami języka C++. Mapa komunikatów może zostać dołączona do każdej klasy wyprowadzonej z będącej elementem biblioteki MFC klasy cCmdTarget. Hierarchia klasy CButtonsDlg pokazana jest na rysunku 4.2. CButtonDlg CDialog CWnd CCmdTarget CObject Rysunek 4.2. Hierarchia klasy CButtonsDlg Używanie przycisków stemjących 83 Większość kodu stanowiącego zawartość map komunikatów, ujęta jest w formę makr, które są dodawane i dostosowywane do potrzeb programu przez narzędzia Visual C++. Definicja klasy CButtonsDlg w pliku ButtonsDIg.h zawiera następującą linię: DECLARE_MESSAGE_MAP() AppWizard umieścił to wyrażenie w kodzie podczas tworzenia klasy. Makro to deklaruje kilka dodatkowych zmiennych w celu wykorzystania zawartości mapy komunikatów i funkcji, które kierują komunikaty do właściwych funkcji C++. Mapy komunikatów są dostarczane przez klasę ccmdTarget Mapa komunikatów jest mechanizmem, który powoduje wywoływanie odpowiednich funkcji C++, w odpowiedzi na komunikaty systemu Windows. W celu zaim-plementowania mapy komunikatów należy wyprowadzić klasę z CCmdTarget. Z utworzeniem funkcji obsługi OnShowHide() wiążą się trzy inne modyfikacje w plikach: 1. Następuje implementacja funkcji za pomocą wpisu: void CButtonsDlg::OnShowHide() ( // W tym miejscu wpisz kod funkcji obsługi } 2. Funkcja zostaje zadeklarowana w pliku CBUttonsDIg.h. Wpis ma postać: afx_msg void OnShowHide(); Prefiks afx_msg informuje nas, iż jest to funkcja obsługi komunikatu. 3. W makrze BEGIN_MESSAGE_MAP zostaje umieszczony wpis łączący komunikat z funkcją. Makro jest zawarte w pliku CButtonsDIg.cpp i ma następującą postać: BEGIN_MESSAGE_MAP(CButtonsDlg, CDialog) //{(AFX_MSG_MAP(CButtonsDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYICON() ON_BN_CLICKED(IDC_SHOW_HIDE, OnShowHide) //}}AFX_MSG_MAP END_MESSAGE_MAP W linii 6 widzimy wpis dotyczący funkcji OnShowHide () wprowadzony do makra mapy komunikatów. Kiedy system Windows wysyła do okna dialogowego komunikat BN_CLICKED odnoszący się do przycisku IDC_SHOW_HIDE, program szuka odpowiednich wpisów. Gdy odnajdzie właściwą funkcję obsługi komunikatu, wywołuje ją. 84______________________________________Poznaj Visual C++ 6 AppWizard utworzył również inne wpisy, dotyczące innych rodzajów komunikatów. Komunikaty te zostaną omówione w dalszej części książki. Specjalne komentarze CIassWizard Specjalne komentarze //{ (AFX_MSG_MAP są używane przez narzędzia C++ dla ułatwienia zrozumienia struktury kodu. Jeśli zostaną one usunięte lub kod pomiędzy nimi będzie niewłaściwie zmodyfikowany, CIassWizard może przestać funkcjonować. PATRZ TAKŻE • Aby dowiedzieć się więcej na temat reakcji na komunikaty Windows, przejdźmy do rozdziału 8. Modyfikacja przycisków w czasie pracy programu Edytor zasobów jest elastycznym narzędziem, jednakże elastyczność ta nie zawsze bywa wystarczająca. Gdybyśmy przykładowo chcieli napisać aplikację w formie formularza elektronicznego, jednym z wymagań, jakie musiałaby spełniać ta aplikacja, byłaby możliwość zmiany zachowania przycisków. Aby móc zmieniać właściwości lub styl przycisku podczas pracy programu, niezbędny jest szerszy dostęp do obiektu tworzącego dany przycisk. Jest to łatwe do wykonania w przypadku kontrolek okna dialogowego. Osiąga się to poprzez wywołanie funkcji GetDlgltem() i podstawienie identyfikatora przycisku. Funkcja zwraca nam wskaźnik obiektu CWnd, który reprezentuje dany przycisk. Wskaźnik ten może być zastąpiony innym, odpowiedniej klasy i służy wtedy do pobrania i ustawienia atrybutów przycisku. Funkcja GetDIgitem () zwraca ten wskaźnik, ponieważ przycisk jest oknem sam w sobie. Mimo iż różni się stylem w sposób zasadniczy od okna dialogowego, oba elementy są oknami i dlatego obiekt cwnd może zostać użyty dla reprezentacji obu. Wprowadźmy kod zawarty w listingu 4.1, by dzięki funkcji OnShowHide () uzyskać możliwość zmiany stanu przycisków u dołu okna dialogowego. Kod ten musimy umieścić za linią zawierającą wpis:// TODO comments. Listing 4. l. LST04_1 .CPP - przełączanie widoczności przycisków przy użyciu funkcji Show^-Window() 1 void CButtonsIDlg::OnShowHide() 2 { 3 // TODO: Tutaj umieść kod funkcji obsługi 4 5 // ** Sprawdzenie aktualnego stanu lewego przycisku Używanie przycisków sterujących_______________________________85 6 BOOL bVisible = GetDIgItem(IDC_LEFT)-> O IsWindowVisible() ; 7 8 // ** Przełączanie widoczności kolejnych przycisków 9 GetDIgItem(IDC_LEFT)->ShowWindow(bVisible @ ? SW_HIDE : SW_SHOW) ; 10 GetDIgItem(IDC_CENTER)->ShowWindow(bVisible ? SW_HIDE : SW_SHOW) ; 11 GetDIgItem(IDC_RIGHT)->ShowWindow(bVisible ? SW_HIDE : SW_SHOW) ; 12 13 // ** Przełączanie napisu na przycisku IDC_SHOW_HIDE 14 GetDIgItem(IDC_SHOW_HIDE)->SetWindowText(bVisible © ? "Show" : "Hide") ; 15 } O Funkcja GetDIgItem () zwraca wskaźnik obiektu cwnd. IsWindowVisible ojest funkcją składową cwnd, która zwraca TRUE, gdy okno jest widoczne. @ Widoczność każdego przycisku jest ustalana poprzez wywołanie funkcji show-window () i ustawienie znacznika SW_SHOW lub SW_HIDE. © setWindowText()jest jeszcze jedną użyteczną funkcją składową CWnd, która służy w tym przykładzie do zmiany napisu na przycisku. Argumentem funkcji GetDIgItem () w wywołaniu z linii 6 jest identyfikator IDC_LEFT, dlatego też funkcja ta zwraca wskaźnik obiektu cwnd dla lewego przycisku. Zwrócony wskaźnik jest następnie użyty do wywołania funkcji IsWindowVisible (), która z kolei zwraca odpowiednio TRUE lub FALSE. Wynik ten zostaje umieszczony w lokalnej zmiennej typu BOOL: bvisibie. Ponieważ stan wszystkich trzech przycisków jest zawsze jednakowy, wystarczy sprawdzić stan tylko jednego z nich. W liniach 9,10 oraz 11 funkcja GetDIgItem () jest wywoływana dla pobrania wskaźników dla wszystkich przycisków po kolei. Następnie dla każdego z nich wywoływana jest funkcja ShowWindow (). W zależności od zawartości zmiennej bVisible przekazany zostaje parametr SW_HIDE, co powoduje ukrycie przycisku lub SW_SHOW, co powoduje powrót przycisków do normalnego widoku. W wyniku otrzymujemy efekt jednoczesnego ukrycia wszystkich trzech przycisków za pomocą jednego kuknięcia i odkrycia ich za pomocą drugiego kuknięcia. W linii 14 funkcja GetDIgItem () zostaje wywołana ponownie, tym razem jej argumentem jest IDC_SHOW_HIDE, będący identyfikatorem przycisku, którego użycie powoduje ukrywanie i odsłanianie trzech przycisków u dołu okna dialogowego. SetWindowText Ojest natomiast funkcją składową klasy cwnd i użyta została do zmiany napisu na przycisku. Umieszczony napis to, w zależności od wartości zmiennej bVisible, Show lub Hide. 86 ____________________ __ __ ____ Poznaj Visual C++ 6 Na pierwszy rzut oka wydawać by się mogło, iż napis Show występuje, gdy przyciski są widoczne, a Hide, gdy są ukryte. Tymczasem jest na odwrót. Pamiętajmy, że zmienna bvisible została umieszczona na szczycie funkcji, w linii 6. Jeśli przyciski były w tym momencie widoczne, to po wykonaniu linii 14, zostaną ukryte, a napis na przycisku ukrywającym zmieni się na Show. Wpiszemy zatem ten kod, skompilujemy i uruchomimy program. Klikniemy przycisk Hide. Jego użycie spowoduje ukrycie trzech dolnych przycisków, a napis umieszczony na nim zmieni się z Hide na Show. Ponowne kliknięcie tego przycisku sprawi, iż ukryte przyciski ponownie staną się widzialne. Obecnie potrzebna nam jest podobna funkcja obsługi do wprowadzania przycisków w stan dostępności lub niedostępności. Dodamy zatem funkcję obsługi komunikatu BN_CLICKED dla przycisku IDC_ENABLE_ DISABLE. Procedura ta jest identyczna z procedurą opisaną w podpunkcie „Dodanie funkcji obsługi kliknięcia przycisku", w tym rozdziale. Musimy jedynie pamiętać o wybraniu przycisku Disable. Otrzymaliśmy szkielet funkcji OnEnableDisable (). Musimy jeszcze wpisać kod zawarty w listingu 4.2, aby przypisać funkcji działanie w postaci zmiany stanu przycisków. Od razu możemy zauważyć podobieństwa pomiędzy poniższym listingiem a listingiem 4.1, dotyczącym funkcji OnShowHide (). Zajmiemy się więc jedynie różnicami. Listing 4.2. LST04_2.CPP - przełączanie stanu przycisków przy użyciu funkcji Enablewindow () 1 void CButtonsDlg::OnEnableDisable() 2 { 3 // TODO: Tutaj umieść kod funkcji obsługi 4 5 // ** Sprawdzenie stanu lewego przycisku 6 BOOL bState = GetDIgItem(IDC_LEFT)->IsWindowEnabled(); O 7 8 // ** Przełączenie stanu wszystkich przycisków 9 GetDIgItem(IDC_LEFT)->EnableWindow(IbState); @ 10 GetDIgItem(IDC_CENTER)-->EnableWindow(!bState) ; 11 GetDIgItem(IDC_RIGHT)->EnableWindow(!bState) ; 12 13 // ** Zmiana napisu Enable / Disable 14 GetDIgItem(IDC_ENABLE_DISABLE)->SetWindowText(bState ® ? "Enable" : "Disable") ; 15 } Używanie przycisków stemjących 87 O Funkcja GetDlgItem() zwraca wskaźnik obiektu CWnd. IsWindowEnabledO jest funkcją składową klasy CWnd, zwracaną przez nią wartością jest TRUE, jeśli przycisk jest dostępny. @ Stan dostępności wszystkich przycisków jest zmieniany poprzez wywołanie funkcji EnableWindow (), zawierającej TRUE lub FALSE. © Napis umieszczony na przycisku IDC_ENABLE_DISABLE jest ustalany przy użyciu funkcji SetWindowText () . Wywołana w linii 6 funkcja IsWindowEnabledO zwraca aktualny stan przycisku IDC_LEFT. W liniach 9, 10 i 11 następuje wywołanie funkcji EnableWindow (), która ustala stan przeciwny do obecnego. Daje nam to oczekiwany efekt przełączania stanu przycisków, które z dostępnych stają się niedostępne i na odwrót W linii 14 wskaźnik przycisku IDC_ENABLE_DISABLE zostaje odebrany przez funkcję GetDlgltem()i użyty do wywołania funkcji SetWindowText () w celu zmiany napisu na przycisku. Spróbujmy skompilować i uruchomić nasz program. Po uruchomieniu, kuknięcie przycisku Disable spowoduje przejście dolnych przycisków w stan niedostępności; jest to sygnalizowane poprzez zatarcie napisów znajdujących się na nich. Przyciski znajdujące się w tym stanie nie mogą odbierać poleceń wydawanych za pośrednictwem klawiatury lub myszy. Stan przycisków u dołu naszego okna dialogowego możemy zmieniać nawet wówczas, gdy przyciski są ukryte. Enable'1 OK Cancel Rysunek 4.3. Dolne przyciski są niedostępne By nadać ostateczny kształt naszej aplikacji, musimy jeszcze umieścić w niej funkcje obsługi pozostałych trzech przycisków. Każda z tych funkcji obejmować będzie jedną linię kodu, zawierającą wywołanie kolejnej funkcji ChangeDialogTitle (). Funkcja ta posłuży do zmiany tytułu okna dialogowego, w zależności od tego, który z trzech dolnych przycisków zostanie kliknięty. Przechowywać będzie identyfikator tegoż przycisku, 88______________________________________Poznaj Visual C++ 6 w związku z czym okno, oprócz zmiany tytułu, powiadamiać będzie, który z przycisków został naciśnięty dwukrotnie. Cały niezbędny kod podany jest na listingu 4.3. Musimy dodać jeszcze funkcje obsługi komunikatu BN_CLICKED, pochodzącego od tych przycisków. Możemy posłużyć się tą samą metodą, co w przypadku przycisków Show oraz Disable. Dodanie zmiennych i funkcji składowych przy użyciu panela CIassYiew 1. Otwórz panel ClassView w oknie roboczym projektu. 2. Kliknij prawym klawiszem myszy klasę CButtonsDlg, co wywoła menu skrótów. 3. Wybierz pozycję Add Member Variable, by wyświetlić okno dialogowe. 4. W polu Variable Type wpisz UINT, następnie użyj klawisza Tab, by przejść do następnego pola. 5. Wprowadź m_nlDofLastButton jako Variable Declaration i kliknij OK. 6. Ponownie kliknij prawym klawiszem klasę CButtonsDlg. 7. Z menu skrótów wybierz teraz Add Member Function. 8. Umieść void w polu Function Type, a następnie przejdź do następnego pola. 9. Jako Function Declaration podaj ChangeDialogTitle (UINT nID). Kliknij OK. Dysponujemy teraz zmienną, która będzie przechowywała identyfikator klikniętego przycisku. Posiadamy również funkcję, która posługiwać się będzie zmiennymi typu liczbowego bez znaku, czyli UINT. Dodajmy więc kod według listingu 4.3. Listing 4.3. LST04_3.CPP - modyfikacja powodująca zmianę tytułu okna dialogowego 1 void CButtonsDlg::OnLeft()O 2 { 3 // TODO: Tutaj umieść kod funkcji obsługi 4 ChangeDialogTitle(IDC_LEFT); 5 } 6 7 void CButtonsDlg::OnCenter()@ 8 ( 9 // TODO: Tutaj umieść kod funkcji obsługi 10 ChangeDialogTitle(IDC_CENTER) ; 11 } 12 13 void CButtonsDlg::OnRight()© 14 { 15 // TODO: Tutaj umieść kod funkcji obsługi 16 ChangeDialogTitle(IDC_RIGHT); 17 } 18 19 void CButtonsDIgChangeDialogTitle(UINT nID) O 20 { 21 CString strCaption; 22 23 // ** Pobranie napisu od kukniętego przycisku 24 GetDlgItem(nID)->GetWindowText(strCaption) ; 25 26 strCaption += " was pressed"; © 27 28 // ** Sprawdzenie, czy przycisk kliknięty został dwukrotnie 29 if(m_nIDofLastButton == nID) 30 strCaption += " again"; 31 32 // ** Nadanie nowego tytułu oknu dialogowemu 33 SetWindowText(strCaption) ; © 34 35 //. ** Przechowanie identyfikatora kukniętego przycisku 36 m_nIDofLastButton = nID; ® 37 } O OnLeft () jest funkcją obsługi komunikatu BN_CLICKED wysyłanego po kliknię-ciu lewego przycisku. @ OnCenter ojest z kolei funkcją obsługi komunikatu BN_CLICKED wysyłanego po kliknięciu przycisku środkowego. © OnRightO jest funkcją obsługi komunikatu BN_CLICKED wysyłanego po kliknięciu prawego przycisku. O changeDialogTitle ()nie jest funkcją obsługi, a jedynie wywoływaną przez trzy wyżej wymienione. Jej argumentem jest identyfikator klikniętego przycisku. © Operator CString+= powoduje doklejenie frazy „was pressed" na końcu łańcucha znakowego, będącego zawartością zmiennej strCaption. © Funkcja SetWindowText () wyświetla tekst w pasku tytułowym okna dialogowego. Q Identyfikator klikniętego przycisku zostaje przechowany jako zawartość zmiennej składowej m_nlDofLastButton w celu dalszego wykorzystania Jak widzimy w liniach 4, 10 oraz 16, każda z funkcji obsługi wywołuje funkcję ChangeDialogTitle () . Każda z nich przekazuje wywołanej funkcji identyfikator przycisku, do którego przynależy. 90______________________________________Poznaj Visual C++ 6 W linii 24 identyfikator zostaje podany funkcji GetDlgitem(), która zwraca wskaźnik do obiektu cwnd kukniętego przycisku. Zwrócony wskaźnik służy do wywołania funkcji GetWindowText () w celu pobrania napisu z przycisku. Napis ten zostaje zapisany jako wartość zmiennej strCaption. W linii 26 operator += klasy cstring zostaje użyty do dołączenia tekstu „was pressed" na końcu napisu pobranego z przycisku. Identyfikator użytego przycisku zostaje w linii 29 porównany z zawartością zmiennej składowej m_nlDofLastButton, która przechowuje identyfikator ostatnio wciśniętego przycisku. Jeśli oba identyfikatory są takie same, w linii 30 na końcu zawartości zmiennej strCaption zostaje dołączony dodatkowy tekst: „again". W linii 33 aktualna zawartość zmiennej strCaption zostaje przekazana funkcji Set-WindowText (). Ponieważ obiekt CButtonsDialog jest wyprowadzony z klasy CWnd, wywołana zostaje funkcja SetWindowText Odia okna dialogowego, która powoduje zmianę napisu w pasku tytułowym okna. Identyfikator ostatnio wciśniętego przycisku zostaje w linii 36 zapisany w zmiennej składowej m_nIDofLastButton. Skompilujemy teraz i uruchomimy naszą aplikację. Przetestujemy ją, klikając przycisk Center. W odpowiedzi, na pasku tytułowym okna dialogowego powinniśmy ujrzeć napis: „Center was pressed". Po ponownym kliknięciu tego samego przycisku, napis powinien zostać rozszerzony o słowo „again". Użycie przełączników Istnieje wiele sytuacji, w których jedynym logicznym rozwiązaniem jest wybór tylko jednej opcji spośród kilku dostępnych. Przykładowo, gdy w programie występuje pytanie o płeć użytkownika, logicznym się staje zablokowanie wyboru opcji Małe, jeśli wybrana została odpowiedź Female. Przełączniki, inaczej przyciski opcji służą do dokonywania takiego rodzaju rozstrzygnięć właśnie, a deselekcja przycisków innych niż wybrany, przeprowadzane jest automatycznie. Przyciski tego rodzaju stosowane są zwykle w grupach, co pozwala na konstruowanie ich zestawów, których w oknie dialogowym może zostać umieszczonych kilka. Grupy te są od siebie niezależne. Ponieważ selekcja i deselekcja przycisków opcji zachodzi wewnątrz grupy jednocześnie i automatycznie, jedynym wymaganiem w stosunku do programu jakie opcje są przypisane (gdy jest to konieczne). W tym celu do przycisków przyłączona zostaje zmienna. Nie powinno to nastręczyć nam zbyt wielu kłopotów. Aby zobaczyć, jak się to odbywa, utworzymy nowy program. Użyjemy nam już narzędzia AppWizard i za jego pomocą utworzymy nowy projekt oparty o okno dialogowe, o nazwie CityBreak. PATRZ TAKŻE • Zasady tworzenia aplikacji o architekturze okna dialogowego znajdziemy w rozdziale 3. Używanie przycisków sterujących 91 Dodawanie grupy przełączników Najważniejszą rzeczą, o której musimy pamiętać przy tworzeniu przycisków opcji, jest upewnienie się co do prawidłowości przypisanych im właściwości oraz stylów. Jeśli są one właściwe, przyciski same zadbają o własną funkcjonalność. Każdy z przełączników umieszczonych wewnątrz grupy musi posiadać ustawiony atrybut Auto Style, by możliwa była jego automatyczna deselekcja. Opcja ta jest uaktywniana automatycznie, w momencie umieszczania przycisku w grupie. Nie musimy się zatem o to martwić. Metodą, dzięki której przyciski zostają zgrupowane jest, użycie właściwości Group. Element, który posiada aktywną opcję Group, traktowany jest jako pierwszy w grupie; każdy następny (w porządku tabulacji) bez zaznaczenia tej opcji, należy do tej samej grupy. Zasięg danej grupy kończy się na kolejnym obiekcie, mającym uaktywnioną opcję Group. Pomiędzy elementami jednej grupy przemieszczamy się używając klawiszy strzałek. Projekt CityBreak posługuje się dwoma zestawami przełączników; pierwszy z nich określa miasto docelowe, drugi zaś klasę hotelu. Wykonując poniższą procedurę, będziemy być może zmuszeni do zwiększenia rozmiaru okna dialogowego oraz przycisków. Umieszczenie zestawu przełączników w oknie dialogowym 1. W oknie edytora zasobów otwórz szablon okna dialogowego IDC_CITYBREAK _DIALOG. Kliknij widoczny pośrodku napis i klawiszem Delete usuń go z szablonu. 2. Wybierz grupę obiektów z paska narzędziowego Controls i umieść ją po lewej stronie szablonu okna. Wzorem niech będzie rysunek 4.4. 3. Zaznacz dodaną grupę i prawym klawiszem myszy otwórz dla niej menu skrótów. Wybierz następnie pozycję Properties. Otwarte zostanie okno dialogowe Group Box Properties. Możesz przypiąć okno, by pozostawało otwarte przez cały czas. 4. W polu Caption wpisz Destination, a następnie zaznacz opcję Tab Stop. 5. Z paska narzędziowego Controls wybierz teraz symbol przełącznika, a następnie umieść cztery przyciski tego rodzaju wewnątrz dodanej przed momentem grupy. 6. Zaznacz przycisk pierwszy od góry. W oknie Properties w polu ID umieść wpis IDC_LONDON, a w polu Caption napisz London. Zaznacz również opcję Group. 7. Wybierz kolejny przycisk. Jako identyfikator wpisz IDC_PARIS, a nazwę ustal jako Paris. Dla trzeciego przycisku wprowadź odpowiednio IDC_NEWYORK oraz New York. Czwarty przycisk opatrzony będzie wpisami: IDC_MUNICH oraz Munich. 8. Ponownie wybierz z paska narzędziowego Controls symbol grupy obiektów i umieść nową grupę po prawej stronie grupy Destination, jak na rysunku 4.4. 9. Nadaj nowej grupie nazwę Hotel i zaznacz opcję Tab Stop. 10. W grupie Hotel umieść trzy przełączniki. 92 Poznaj Visual C++ 6 11. Zaznacz te trzy przyciski, klikając je kolejno i przytrzymując klawisz Ctri. 12. Otwórz kartę Styles w oknie właściwości. Zaznacz w niej opcję Push-like. 13. Wybierz najwyższy przycisk. Nadaj mu identyfikator IDC_LUXURY oraz nazwę Luxury. Zaznacz także opcję Group. 14. Dla drugiego przycisku wprowadź: IDC_STANDARD oraz Standard w polu Caption. Trzeciemu przyciskowi przypisz natomiast identyfikator IDC_ECONOMY i nazwę Economy. Okno dialogowe powinno mieć postać zgodną z rysunkiem 4.4. OK i-Destinalion •-| <" London |rr Pans 'C New York C Munich Hotel - Cancel Luxury Standald Rysunek 4.4. Układ okna dialogowego CityBreak Bardzo istotny dla funkcjonowania przełączników jest porządek tabulacji, który w tym przypadku służy do odnajdywania następnego przycisku w logicznym porządku. Aby sprawdzić obecny porządek tabulacji w naszym oknie, musimy wybrać pozycję Tab Order z menu Layout i porównać go z przedstawionym na rysunku 4.5. W celu sprawdzenia prawidłowości działania umieszczonych przez nas przycisków, przeprowadzimy test okna, naciskając Ctri+T lub wybierając pozycję Test z menu Layout, bądź też uruchamiając go z paska narzędziowego Dialog. W czasie testu powinno być możliwe wybranie tylko jednego z miast i tylko jednej klasy hotelu. Tryb testu zamykamy klawiszem Esc. PATRZ TAKŻE • Więcej informacji na temat projektowania okien dialogowych uzyskamy w rozdziale 3. Rysunek 4.5. Porządek tabulacji w oknie dialogowym CityBreak Używanie przycisków sterujących 93 Określenie wybranego przycisku Zadaniem grupy przełączników jest umożliwienie użytkownikowi dokonania wyboru jednej opcji spośród kilku dostępnych. Związana z tym jest konieczność powiadomienia programu, który z przycisków użytkownik wybrał. Na podstawie tej informacji, program wykonuje określone zadania. Aby utworzyć właściwy mechanizm, do grupy przypisana zostaje zmienna. Zmienna ta jest obecnie związana z pierwszym przyciskiem w grupie (tym, który posiada zaznaczoną opcję Group). W naszym przykładzie są to przyciski: IDC_LONDON oraz IDC_LUXURY. Użyjmy teraz narzędzia CIassWizard, by dodać te zmienne i połączyć je z właściwymi przyciskami. W przykładzie CityBreak obie grupy muszą posiadać swoje zmienne. Łączenie zmiennych z przyciskami za pomocą CIassWizard Naciśnij Ctri+W by uruchomić CIassWizard lub z menu View wybierz odpowiednią pozycję. Otwarte zostanie okno MFC CIassWizard, widoczne na rysunku 4.6. l. FC CIastWaald Message Maps Membec Yanabies | Au Proiecl'. omatio n ;lass nam ActiveX Events ei Ctoalnfo AddClaK... ••• E-\CityBieak\CityBreakDlg,h.E:- \aii'Breal<\CitvBieakDlg ConIrollDs: Typa kDlg cpp Member d fiddYariable... iDr l.nNDftN ::: :: :: ^^HHIH^^^^BiB IDC LLKURY •-.: •-,. . ..: '. ,; . .• IDCANCEL IDOK 3esctiption- : Ilill^l^llte^^^ t' OK | Cancel l Rysunek 4.6. Okno dialogowe CIassWizard 2. Kliknij kartę Member Variables. 3. Wybierz z listy Ciass name klasę okna dialogowego; w bieżącym przykładzie będzie to CCityBreakDIg. 4. Z listy Control ID wybierz identyfikator przycisku IDC_LONDON. 5. Kliknij przycisk Add Yariable. Spowoduje to wyświetlenie okna dialogowego o tym tytule, widocznego na rysunku 4.7. Ten sam efekt osiągniesz klikając dwukrotnie pozycję identyfikatora na liście. 94 Poznaj Visual C++ 6 6. W polu Member Yariable name wpisz nazwę zmiennej: m_nDestination. Kliknij następnie OK, by powrócić do karty Member Yariables kreatora CIassWizard. 7. Wybierz z listy identyfikator IDC_LUXURY i ponownie wywołaj okno dialogowe Add Member Yariable. 8. W polu nazwy zmiennej wpisz m_nHotel i kliknij OK, by dodać nową zmienną. 9. W oknie CIassWizard kliknij OK, co spowoduje jego zamknięcie. Rysunek 4.7. Okno dialogowe Add Member Variable Skoro już zdefiniowaliśmy nowe zmienne, musimy je zainicjować. CIassWizard tworząc kod zapisał początkową wartość zmiennych jako -l, co oznacza, że podczas uruchomienia programu i wyświetlenia okna dialogowego po raz pierwszy żaden z przycisków nie będzie wybrany. Efekt, do którego dążymy, jest jednak inny; zwykle w takich sytuacjach okno otwiera się z zaznaczonym pierwszym przyciskiem każdej grupy. Aby uzyskać to w naszej aplikacji musimy zmienić kod utworzony przez CIassWizard w konstruktorze klasy ccityBreakDig. Otworzymy zatem panel ClassView, rozwiniemy klasę CCityBreakDlg i klikniemy dwukrotnie pierwszą pozycję. Zapis konstruktora funkcji zostanie wyświetlony w oknie edytora. Musimy w nim zmienić dwie linie, zamieniające wartości zmiennych z -l na 0. Edytowane linie powinny wyglądać następująco: m_nDestination = 0; m_nHotel = 0; Pozostało jeszcze tylko jedno do zrobienia: umieszczenie kodu umożliwiającego stwierdzenie, który z przełączników został wybrany. Kodem tym będzie funkcja obsługi przycisku OK oraz wyświetlenia komunikatu „Bon Voyage". Dodamy zatem funkcję obsługi komunikatu BN_CLICKED dla przycisku IDOK. Możemy to zrobić według procedury zawartej w punkcie „Dodanie funkcji obsługi kliknięcia przycisku" znajdującym się we wcześniejszej części niniejszego rozdziału. Używanie przycisków sterujących_________ ______ 95 W wyniku przeprowadzenia tej procedury dysponujemy funkcją składową OnOk (). Wprowadzimy więc kod zawarty na listingu 4.4, aby wykorzystać tę funkcję do utworzenia i wyświetlenia komunikatu mówiącego, który z przycisków został wybrany. Listing 4.4. LST04_4.CPP - kod służący do identyfikowania przycisku wybranego przez użytkownika 1 void CCityBreakDlg::OnOK() 2 { 3 // TODO: Add extra validation here 4 CString strMessage; 5 CString strHotel; 6 CString strDest; 7 8 // ** Przeniesienie danych z przycisków do zmiennych 9 UpdateDataO; O 10 11 // ** Pobranie napisu z wybranych przycisków 12 GetDlgItem(IDC_LUXURY + m_nHotel)->GetWindowText(strHotel); @ 13 GetDlgItem(IDC_LONDON + m_nDestination)->GetWindowText(strDest); © 14 15 // ** Utworzenie i wyświetlenie komunikatu 16 strMessage = "Bon Voyage to a " + strHotel + " Hotel in " O + strDest; 17 MessageBox(strMessage); 18 19 CDialog::OnOK() ; 20 } O Funkcja UpdateData () pobiera dane z przycisków okna dialogowego i zmienia wartość stowarzyszonych z nimi zmiennych. © Pobranie napisu opisującego wybrany przełącznik opcji, z grupy Hotel. © Pobranie takiego samego napisu z grupy Destination. O Sformułowanie komunikatu z zawarciem nazw zaznaczonych przycisków. Funkcja UpdateData () z linii 9 umieszcza wartość przycisku w zmiennej z nim stowarzyszonej. Po przejściu przez linię 11, wartość zmiennych m_nDestination oraz m_nHotel będzie liczbą pozostającą w relacji z wartością pierwszego przycisku. Na przykład, jeśli wybrany zostanie przycisk London, wartością zmiennej m_nDestination będzie 0. Gdy użytkownik dokona wyboru przycisku Paris, wartością będzie l. Wartości te użyte są do 96______________________________________Poznaj Visual C++ 6 pobrania nazw zaznaczonych przycisków w liniach 12 i 13. Operator CString + zapisany w linii 16 służy do utworzenia treści komunikatu w zmiennej strMessage. Komunikat ten zostaje następnie wyświetlony za pomocą funkcji MessageBox () w linii 17. Możemy teraz skompilować i uruchomić program. Przetestujmy działanie kodu, wybierając kilka kombinacji przycisków z obu grup. Gdy wybierzemy New York oraz Luxu-ry, po kliknięciu OK ujrzymy komunikat pokazany na rysunku 4.8. PATRZ TAKŻE • By dowiedzieć się jak funkcjonuje wymiana danych z oknami dialogowymi, przejdź do rozdziału 10. CityBieak BonYoyage to a Lunury Hotel in New York p———, Rysunek 4.8. Komunikat Bon Voyage Użycie pól wyboru Pola wyboru są jednym z najszerzej stosowanych kontrolek. Używaliśmy ich już dokonując zmian właściwości i styli podczas pracy w edytorze zasobów. Pola wyboru pozwalają użytkownikowi na dowolne zaznaczanie dostępnych opcji. Możemy wybrać jedną opcję, więcej niż jedną, wszystkie lub też nie wybrać żadnej z dostępnych. Pola te występują najczęściej w standardowym stylu, który nadaje im postać małego okienka, obok którego znajduje się napis objaśniający. Dokonanie wyboru danego pola jest oznaczone znakiem zaznaczenia ptaszek (ang. tick) lub w przypadku Windows NT krzyżykiem. Jeśli pole nie zostało wybrane, pozostaje puste, bez zaznaczenia. Ten rodzaj kontrolek może występować jako obiekt trójstanowy. Trzecim stanem, poza obecnością lub brakiem zaznaczenia jest stan, w którym pole zostaje przyciemnione. Dzieje się tak, gdy stan pola nie może być jednoznacznie określony. Przykładowo, gdy w edytorze zasobów zaznaczymy kilka spośród kontrolek okna dialogowego, mających przypisane różne style, pola wyboru odnoszące się do stylu zostaną właśnie przyciemnione. Dodawanie pól wyboru Aby nauczyć się, jak używać pól wyboru, rozbudujemy nieco nasz bieżący projekt, CityBreak. W ramach tej rozbudowy umieścimy w oknie dialogowym dwa pola wyboru. Dzięki nim będą pobierane informacje, czy osoba planująca wyjazd do danego hotelu, chce skorzystać z dancingu, a jeśli tak, czy chciałaby napić się szampana. Używanie przycisków sterujących___________________________ 97 Dodanie pól wyboru do okna dialogowego w projekcie CityBreak 1. Otwórz okno dialogowe IDD_CITYBREAK_DIALOG w edytorze zasobów. 2. Wybierz z paska narzędziowego Controls symbol pola wyboru i umieść dwa takie pola w oknie dialogowym, jak widać na rysunku 4.9. 3. Zaznacz pierwsze z nich i klikając prawym klawiszem myszy wywołaj menu skrótów, z którego wybierz pozycję Properties. Wyświetlone zostanie okno dialogowe Check Box Properties. 4. W polu listy rozwijanej ID wpisz identyfikator IDC_DANCE. Jako nazwę w polu Caption umieść Dinner Dance. 5. Zaznacz pole wyboru Group, co spowoduje „wyjęcie" Dinner Dance spod wpływu grupy Hotel. 6. Zaznacz drugie świeżo umieszczone pole wyboru. 7. Jako identyfikator dla niego podaj IDC_CHAMPAGNE, a nazwę określ jako Cham- pagne. Okno dialogowe powinno po tych zabiegach wyglądać, jak na rysunku 4.9. - Destinałion • ••• :• Hotel ••••-••• l r" London ; ; {.uwy •'" !:Sii|i®i:'i:'ifc,'. • ^^SisiMt l P DinnefOance l r" Champagne IIIIIHS^ijs^l OK | Cancel | Ipr Pa.-s : •A:> , btandald "t l~ NewYork ! • —--——————i : i F Mmch l \ Ecmm' Rysunek 4.9. Okno dialogowe CityBreak z dodanymi dwoma polami wyboru Pobieranie i ustalanie stanu pól wyboru Podobnie jak przełączniki, pola wyboru służą do określania preferencji użytkownika. Pro- gram musi w pewnych sytuacjach sprawdzać stan tych pól. Aby umożliwić mu dostęp do nich, musimy posłużyć się klasą CButton. Klasa ta posiada funkcję składową GetCheckO, którą wykorzystuje do zbadania stanu pola wyboru oraz funkcję SetCheck (), ustalającą ten stan. Przykładowy kod demonstruje nam jak uzależnić dwa pola wyboru, jedno od drugiego. Nie możemy zamówić szampana, nie idąc na dancing. Możemy tam oczywiście pójść i nie pić szampana. Dlatego też, gdy pole wyboru Champagne zostanie zaznaczone, automatycznie stanie się to również z polem Dinner Dance. Jeśli natomiast usuniemy znacznik z pola Dinner Dance, zniknie on również z pola wyboru Champagne. Aby uzyskać takie efekty, musimy dodać funkcję obsługi komunikatu BN_CLICKED dla obu pól wyboru. Procedura jest podobna, jak opisana w punkcie „Dodanie funkcji obsługi kliknięcia przycisku" we wcześniejszej części tego rozdziału. 98______________________________________Poznaj Visual C++ 6 Do otrzymanego szkieletu funkcji musimy teraz dodać kod zawarty na listingu 4.5. Listing 4.5. LST04_5.CPP - ustalenie dostępu do stanu pola wyboru 1 void CCityBreakDlg::OnChampagne()O 2 ( 3 // TODO: Tutaj umieść kod zgłoszenia funkcji obsługi 4 5 // ** Pobranie wskaźników do obiektów pól wyboru 6 CButton* pDance = (CButton*)GetDIgItem(IDC_DANCE); @ 7 CButton* pChamp = (CButton*)GetDIgItem(IDC_CHAMPAGNE); © 8 9 // ** Zaznaczenie pola wyboru Dinner Dance, jeśli pole Champagne jest zaznaczone 10 if (pChamp->GetCheck())0 11 pDance->SetCheck(l); 12 } 13 14 void CCityBreakDlg::OnDance()© 15 { 16 // TODO: Tutaj umieść kod zgłoszenia funkcji obsługi 17 18 // ** Pobranie wskaźników do obiektów pól wyboru 19 CButton* pDance = (CButton*)GetDIgItem(IDC_DANCE); © 20 CButton* pChamp = (CButton*)GetDIgItem (IDC_CHAMPAGNE); ® 21 22 // ** Usunięcie znacznika z pola Champagne, jeśli pole Dinner Dance nie jest zaznaczone 23 if (!pDance->GetCheck()) © 24 pChamp->SetCheck(0); 25 } O OnChampagne () jest funkcją wywoływaną, gdy następuje wybranie pola Champagne. © Ustawienie wskaźnika CButton do pola wyboru IDC_DANCE. © Tutaj ustawiony zostaje wskaźnik klasy CButton do pola wyboru IDC_CHAMPAGNE. O Jeśli pole Champagne jest zaznaczone, Dinner Dance również musi być zaznaczone. © OnDance () jest funkcją obsługi komunikatu, wywoływaną gdy użytkownik zaznaczy pole Dance. Używanie przycisków sterujących 99 © Ponowne umieszczenie wskaźnika CButton dopolaiDC DANCE. Q Ponowne umieszczenie wskaźnika do pola wyboru Champagne. © Jeśli użytkownik nie zaznaczył pola Dance, również pole Champagne nie może być zaznaczone. : : : : :,,,,:,: - : W liniach 6, 7, 19 oraz 20 widzimy, jak wskaźnik cwnd zwrócony przez funkcję Get-Digitem () zostaje zastąpiony przez wskaźnik CButton. CButton jest klasą MFC, która reguluje funkcjonalność wszystkich typów przycisków. Nie było konieczne stosowanie tej klasy w przypadku przycisków, ponieważ wskaźnik klasy cwnd mógł być zastosowany bezpośrednio. Różnica pomiędzy przyciskiem a polem wyboru jest taka, że ten pierwszy nie łączy się z pojęciem stanu. W związku z tym, zastosowano wskaźnik CButton, by umożliwić wywoływanie funkcji składowych GetCheck () oraz SetCheck (). Wyrażenie i f pojawiające się w linii 10 służy sprawdzeniu stanu pola wyboru Champagne. Jeżeli zostało ono zaznaczone, wywołana jest funkcja SetCheck () dla pola Dance z parametrem l, co powoduje z kolei zaznaczenie Dance. Wyrażenie i f, widoczne w linii 29 służy odwrotnej czynności — usuwa zaznaczenie pola Champagne, gdy pole Dance nie jest wybrane. Po skompilowaniu i uruchomieniu programu, sprawdzimy działanie kodu poprzez zaznaczanie i usuwanie zaznaczenia obu pól, by zaobserwować interakcje pomiędzy nimi. Aby dopełnić konstrukcję programu, rozszerzymy jeszcze obszar działania funkcji OnOK (), dodając kod widoczny na listingu 4.6 pomiędzy liniami 19 i 29. Listing 4.6. LST04_6.CPP - umieszczenie dodatkowych informacji w komunikacie Bon Voyage 1 void CCityBreakDlg::OnOK() 2 { 3 // TODO: Add extra validation here 4 CString strMessage; 5 CString strHotel; 6 CString strDest; 7 8 // ** Przekazanie danych z pól wyboru do zmiennych funkcji UpdateData() 9 UpdateData() ; 10 11 // ** Pobranie nazw zaznaczonych przełączników 12 GetDlgItem(IDC_LUXURY + m_nHotel)->GetWindowText(strHotel); O 13 GetDlgItem(IDC_LONDON + m_nDestination)->GetWindowText(strDest); © 14 15 100_____________________________________Poznaj Visual C++ 6 16 // ** Uformowanie i wyświetlenie komunikatu 17 strMessage = "Bon Voyage to a " + strHotel + " Hotel in " + strDest; 18 19 // ** Pobranie wskaźników do obu pól wyboru 20 CButton* pDance = (CButton*)GetDlgItem(IDC_DANCE) ; 21 CButton* pChamp = (CButton*)GetDIgItem (IDC_CHAMPAGNE); 22 23 // ** Rozszerzenie komunikatu, w zależności od stanu pól wyboru 24 if(pDance->GetCheck()) 25 { 26 strMessage += " and a Dinner Dance"; 27 if(pChamp->GetCheck()) 28 strMessage += " with Champange"; 29 } 30 MessageBox(strMessage) ; 31 32 CDialog::OnOK(); 33 } O Pobranie nazwy zaznaczonego przycisku opcji z grupy Hotel. @ Pobranie nazwy przycisku z grupy Destination. Funkcja OnOK () sprawdza stan pól wyboru i na tej podstawie umieszcza dodatkową in- formację w komunikacie Bon Voyage. W liniach 19 i 20 wskaźnik obiektu cwnd zwrócony przez funkcję GetDIgitemO zostaje zastąpiony wskaźnikiem obiektu CButton. Od tego miejsca pDance wskazuje pole wyboru IDC_DANCE, a wskaźnik pChamp pole Cham-pagne. Wskaźniki te zostają następnie użyte do sprawdzenia stanu pól wyboru, co następuje w liniach 24 i 27. Jeśli pole IDC_DANCE zostało zaznaczone przez użytkownika, do komunikatu zawartego w zmiennej strMessage zostanie dołączona za pomocą operatora CString += fraza „and a dinner dance". Jeśli natomiast użytkownik również dokona wyboru pola Champagne, komunikat ze zmiennej strMessage zostanie dodatkowo rozszerzony o frazę „with Champagne". W skompilowanym i uruchomionym programie kliknięcie OK spowoduje wyświetlenie komunikatu, rozszerzonego o dodatkowe elementy, w zależności od stanu obu pól wyboru. Wynik możemy obejrzeć na rysunku 4.10. Bon Ytyage to a limify Hotel in Pafi-s and a Dinner Dance with Champange Rysunek 4.10. Rozszerzony komunikat Bon Voyage Rozdział 5 Stosowanie kontrolek tekstowych Wyświetlanie informacji i komunikatów w oknach dialogowych Manipulowanie tekstem w czasie pracy programu Zatwierdzanie wprowadzanych danych Rozszerzanie użyteczności kontrolek za pomocą podklas Przez wiele lat przed pojawieniem się systemu Windows i jego graficznego interfejsu komputery miały ograniczone możliwości wyświetlania tekstu. Tekst był prezentowany w maksymalnie 80 liniach, po 80 znaków. Królowały monitory wyświetlające litery złożone z pojedynczych pikseli, w zielonym kolorze na czarnym tle. Istniała również odmiana posługująca się kolorem bursztynowym. W przeciwieństwie do tego, co niektórzy sądzą, nie wyszły one całkiem z użycia. Dzieje się tak dlatego, ponieważ znakomita większość informacji wprowadzanych do komputera i z niego wyprowadzanych występuje w formie tekstu, co pozwala przedłużyć żywot aplikacjom pracującym w trybie tekstowym. Niezależnie od trybu, aplikacje pobierające informacje tekstowe i wyświetlające je w tym samym formacie, wciąż są potrzebne. W oknach dialogowych teksty informacyjne wyświetlane są za pośrednictwem sta- tycznych pól tekstowych, a teksty wprowadzane do komputera pobierane za pomocą pól edycyjnych. Stosowanie statycznych pól tekstowych Gdybyśmy zapytali kogoś, ile słów wart jest jeden obraz, odpowiedź brzmiałaby prawdopodobnie „tysiąc". Ale gdyby dwojgu ludziom pokazać ten sam obraz, prosząc o określenie jednym słowem jego treści, padłyby zapewne określenia odmienne. Słowa są więc najlepszym sposobem przekazywania informacji, pozwalającym na dokładne ich precyzowanie. Ścisłość informacji pożądana jest na przykład w przypadku okien dialogowych. Tekst wykorzystywany jest w nich jako opis kontrolek, opcji, a czasem nawet służy do wyświetlania pewnych treści informacyjnych, takich jak zastrzeżenia praw autorskich. 102 Poznaj Visual C++ 6 Formatowanie tekstów w oknach dialogowych Gdy zachodzi potrzeba umieszczenia prostego tekstu wewnątrz okna dialogowego, posłużyć się możemy statycznym polem tekstowym. Każde takie pole zawierać może do 255 znaków. Poprzez użycie znaków nakazujących \n wymuszać możemy przejście do nowej linii, tekst może być wyrównany do lewej lub prawej strony bądź wypośrodkowa-ny. Stosowanie stylów umożliwia nam umieszczanie wokół pól tekstowych ramek o różnym wyglądzie. Użycie statycznych pól tekstowych przy odrobinie pomysłowości, pozwala na konstruowanie wyrafinowanych okien dialogowych, jak na rysunku 5.1. MourtainJourney The (oothilb with theił genlle stopes Point to grandeut yet to come Whete mountains lear theit towering heads; Ascend in splendioin ali theit owi. Theif forms arę mirrored in the depths of watets blue. that [ound them lie. And ali arę meiged where lushing slieams crash out their mulic to the sky. Rysunek 5.1. Przykład statycznego pola tekstowego Łączenie statycznych pól tekstowych z polami edycji Najpowszechniejszym zastosowaniem statycznych pól tekstowych w oknach dialogowych jest użycie ich do opisu pól edycji. Pola edycji są obiektami, które umożliwiają użytkownikowi wprowadzanie informacji. Statyczne pola tekstowe umieszczane są tuż obok pól edycji, gdyż te ostatnie nie posiadają opisujących je nagłówków. Jeśli statyczne pole tekstowe umieścimy przed polem edycji, umożliwi nam to utworzenie skrótu klawiaturowego do tegoż pola. Skrót ten występuje w postaci mnemoniki zawartej w treści pola tekstowego. Mnemonika pozwala na szybszy dostęp do kontrolki, poprzez wciśnięcie jednego klawisza. Litera, która pełni tę rolę, jest określona poprzez poprzedzenie jej znakiem & w nagłówku kontrolki. Na przykład, nagłówek zapisany Fo-ot&ball określa literę „b" lub „B" jako mnemonikę dla danego obiektu. W nagłówku tym, litera „b" zostanie podkreślona: Football. Każda mnemonika powinna być w danym oknie dialogowym niepowtarzalna. Czy tak jest, sprawdzić możemy wybierając pozycję Check Mnemonics z menu skrótów w edytorze zasobów. Statyczne pole tekstowe nie może być obiektem aktywnym, więc użycie wskazanego skrótu klawiaturowego powoduje uaktywnienie następnego obiektu według porządku tabulacji. Gdy pole edycji staje się aktywne, automatycznie umieszczony zostaje w nim kursor, w gotowości na przyjęcie danych. Stosowanie kontrolek tekstowych 103 Działanie mnemonik Jeśli zostanie użyta mnemonika obiektu, który nie jest dostępny, aktywną staje się kontrolka następna w kolejności tabulacji. Zmienić to zachowanie programu można poprzez utworzenie podklasy dla statycznego pola tekstowego. PATRZ TAKŻE « O tworzeniu pól edycji można przeczytać dalej w tym rozdziale. Zmiana zawartości statycznego pola tekstowego w czasie pracy programu Statyczne pole tekstowe może okazać się przydatne, gdy chcemy wyświetlić komunikat. Na przykład, użytkownik posługujący się aplikacją sieciową, mógłby obserwować okno dialogowe stanu podające liczbę użytkowników aktualnie korzystających z danej aplikacji lub systemu. Statyczne pole tekstowe idealnie nadaje się do takiego zastosowania. Zważywszy jednak, że liczba aktywnych użytkowników może ulegać zmianie, zawartość pola tekstowego również musi się zmieniać podczas pracy programu. Teraz zajmiemy się tworzeniem informacyjnych okien dialogowych, w których pola tekstowe wykorzystamy do wyświetlania takich informacji, jak nazwa komputera, imię użytkownika oraz ilość pamięci, całkowitej i wolnej. Zaczniemy od utworzenia w AppWizard nowego projektu w oparciu o okno dialogowe o nazwie Sysinfo. Kiedy szkielet nowej aplikacji mamy już gotowy, musimy do jego okna dialogowego dodać osiem statycznych pól tekstowych oraz jeden przycisk sterujący. Umieszczanie statycznych pól tekstowych w oknie dialogowym 1. W edytorze zasobów otwórz okno dialogowe IDD_SYSINFO_DIALOG. Kliknij napis TODO: Place Dialog Controls Herę, a następnie usuń go za pomocą klawisza Delete. 2. Na pasku narzędziowym Controls wybierz symbol statycznego pola tekstowego, a następnie nanieś cztery takie pola na szablon okna dialogowego. Umieść je po lewej stronie okna, jak na rysunku 5.2. 3. Zmień nagłówki pól tekstowych, wzorując się na rysunku 5.2. 4. Z paska narzędziowego Controls wybierz pozycję statycznego pola tekstowego, następnie umieść takie pole po prawej stronie Computer Name. 5. Zmień identyfikator nowego pola, wpisując IDC_COMPUTER_NAME. 6. Umieść po prawej stronie Total Memory kolejne statyczne pole tekstowe. 104 Poznaj Visual C++ 6 7. Nowemu polu tekstowemu nadaj identyfikator IDC_TOTAL_MEMORY. 8. Z prawej strony Free Memory umieść następne pole tekstowe. 9. Jako jego identyfikator ID wpisz IDC_FREE_MEMORY. 10. Umieść na szablonie jeszcze jedno statyczne pole tekstowe, po prawej stronie Memory Load. 11. Wpisz IDC_MEMORY_LOAD jako jego identyfikator. 12. Zaznacz wszystkie cztery dodane pola, klikając je kolejno i przytrzymując klawisz CtrI. Z pola edycji zwierającego nagłówek Caption usuń wpis Static. Kliknij kartę Styles, a na niej zaznacz opcję Sunken. 13. Wybierz z paska narzędziowego Controls symbol przycisku wciskanego i umieść na szablonie jeden taki przycisk, w prawym dolnym rogu okna dialogowego. 14. Nadaj mu identyfikator: IDC_REFRESH, a jako nagłówek wpisz Refresh. Okno powinno wyglądać jak na rysunku 5.2. OK ^Computef Name' flotal Memory: ;Free Memory: Memory Load: Cancel Reftesh Rysunek 5.2. Układ okna dialogowego Sysinfo Cztery pola tekstowe, w prawej kolumnie będą wyświetlały informacje o komputerze, odświeżane za każdym kuknięciem Refresh. Aby jednak umożliwić aplikacji odświeżanie, należy obiektom kontrolnym przypisać zmienne, co uczynimy według poniższej procedury. IDC_STATIC jest identyfikatorem specjalnym Domyślnie każdemu statycznemu polu tekstowemu przypisywany jest jednakowy identyfikator: IDC_STATIC. Jest to specjalny identyfikator o wartości -l. Chcąc przypisać zmienną oknu tekstowemu, trzeba jego wartość zmienić. Przypisanie zmiennych statycznym polom tekstowym za pomocą CIassWizard 1. Naciśnij CtrI+W lub wybierz pozycję CIassWizard z menu View, by uruchomić to narzędzie. 2. Kliknij kartę Member Variables. Stosowanie kontrolek tekstowych 105 3. W oknie listy rozwijanej CIass Name wybierz klasę okna dialogowego; w naszym przykładzie CSysinfoDIg. 4. Z listy Control ID wybierz identyfikator statycznego pola tekstowego IDC_COMPUTER_NAME. Kliknij Add Variable, co wywoła okno dialogowe Add Member Variable. Okno to możesz wywołać również poprzez dwukrotne kliknięcie identyfikatora z listy. 5. Upewnij się, że w oknie listy rozwijanej Category widnieje wpis Value, a w oknie Variable Type znajduje się CString. 6. W polu nazwy zmiennej, czyli Member Variable Name, wpisz m_strComputerName, następnie kliknij OK. 7. Z listy identyfikatorów wybierz IDC_TOTAL_MEMORY i kliknij Add Variable. 8. W polu Member Variable Name wpisz m_strTotalMemory. Kliknij OK. 9. Wybierz identyfikator IDC_FREE_MEMORY, kliknij Add Variable. 10. Wpisz m_strFreeMemory jako nazwę zmiennej, a następnie kliknij OK. 11. Wybierz z listy IDC_MEMORY_LOAD i naciśnij Add Variable. 12. Nowej zmiennej nadaj nazwę m_strMemoryLoad, po czym kliknij OK. 13. Na koniec kliknij OK w oknie CIassWizard. Musimy jeszcze dodać funkcję obsługi komunikatu BN_CLICKED dla przycisku IDC_REFRESH. Procedura jest identyczna z wykonaną w punkcie „Dodanie funkcji obsługi kliknięcia przycisku", zawartym w rozdziale 4. W jej wyniku otrzymamy nową funkcję składową OnRefresh (). Jej działanie polegać będzie na odnawianiu zawartości zmiennych przypisanych do kolejnych statycznych pól tekstowych. Aby uzyskać takie działanie, musimy dopisać kod zawarty na listingu 5.1. Listing 5.1. LST05_1.CPP — zmiana zawartości statycznych pól tekstowych podczas pracy programu 1 void CSysinfoDIg::OnRefresh() 2 ( 3 // TODO: Tutaj umieść kod funkcji obsługi 4 II ** Zmienne użyte do pobrania nazwy komputera 5 TCHAR szBuffer[256] ; 6 DWORD dwSize = 256; 7 8 // ** Pobranie od systemu Windows nazwy komputera 9 GetComputerName(szBuffer, sdwSize); O 10 11 // ** Przekazanie nazwy komputera do składowej zmiennej 12 13 m_strComputerName = szBuffer; 14 15 // ** Umieszczenie struktury służącej pobraniu danych o pamięci 16 MEMORYSTATUS mem_stat; @ 17 18 // ** Sprawdzenie aktualnego stanu pamięci 19 GlobalMemoryStatus(&mem stat) ; 20 21 // ** Przekazanie danych dotyczących pamięci do odpowiednich 22 II** zmiennych składowych 23 m_strTotalMemory.Format("%ld KB", @ 24 mem_stat.dwTotalPhys / 1024); 25 m_strFreeMemory.Format("%ld KB", 26 meni_s t at.dwAvai.lPh y s / 1024) ; 27 m_strMemoryLoad.Format("%d %%", 28 mem_stat.dwMemoryLoad); 29 30 //. ** Uaktualnienie danych do wyświetlenia w polach tekstowych 31 UpdateData(FALSE); 32 } O Nazwa komputera zostaje pobrana przez funkcję GetComputerName () i zapisana w zmiennej typu s t r. © Struktura MEMORYSTATUS przechowuje szczegóły dotyczące fizycznej oraz wirtualnej pamięci komputera. © Zmienna znakowa użyta zostaje do sformułowania komunikatów dotyczących pamięci komputera, które następnie zostaną wyświetlone w oknie dialogowym. W liniach 5 i 6 zostały zadeklarowane dwie zmienne lokalne, a następnie w linii 9 przypisane do funkcji GetComputerName O jako jej argumenty. Tymi argumentami będą: bufor znakowy, w którym funkcja przechowuje nazwę komputera oraz adres zmiennej DWORD, określającej maksymalną wielkość bufora znakowego. Zmienna reprezentująca bufor została zadeklarowana w linii 5 przy użyciu makra TCHAR. Makro to jest wykorzystywane podczas pisania aplikacji kompatybilnych z zestawami znaków ANSI oraz Uni-code. Zestaw znaków Unicode używa dwóch bajtów dla reprezentacji każdego znaku i jest standardem używanym do kodowania wszelkich możliwych znaków narodowych. W linii 13 zawartość bufora znakowego przekazywana jest do zmiennej m_str-ComputerName, przypisanej do statycznego pola tekstowego IDC_COMPUTER_NAME. Stosowanie kontrolek tekstowych 107 Użyteczne funkcje systemowe Oprócz funkcji GetComputerName (), wykorzystać można również funkcję Get-UserName () dla pobrania imienia lub nazwiska użytkownika, a także funkcję Get-VersionEx () w celu określenia systemu operacyjnego, w którym działa aplikacja. W ramach struktury MEMORYSTATUS zostaje zadeklarowana w linii 16 zmienna mem_stat. Wszystkie elementy składowe struktury MEMORYSTATUS są pokazane na listingu 5.2. W linii 19 adres zmiennej mem_stat zostaje podany funkcji GlobalMemorySta-tus (), która zapisuje w zmiennych struktury bieżące dane dotyczące pamięci komputera. Użyta w liniach 23, 25 i 27 funkcja CString:: Format () służy do wyodrębnienia ze zmiennych mem_stat informacji, które z kolei posłużą do uaktualniania zawartości zmiennych klasy CString, przypisanych do statycznych pól tekstowych, wyświetlających dane dotyczące pamięci komputera. Odwołanie do funkcji UpdateData () odbywa się w linii 31 z parametrem FALSE, by odświeżyć treść wszystkich czterech statycznych pól tekstowych. Po skompilowaniu i uruchomieniu programu każde kliknięcie przycisku Refresh powodować będzie odświeżenie informacji. Spróbujmy zatem uruchomić kilka innych aplikacji, a klikając Refresh zaobserwować zmiany zachodzące w polach tekstowych, odnoszących się do pamięci komputera. Nasze okno dialogowe powinno wyglądać jak na rysunku 5.3. Zastanawiającym może być, dlaczego wartość wyświetlana w polu tekstowym Memo-ry Load, identyfikującym obciążenie pamięci, nie zwiększa się w miarę uruchamiania kolejnych aplikacji. Otóż dzieje się tak dlatego, ponieważ pole wyświetla dane dotyczące sumy pamięci fizycznej oraz wirtualnej, która występuje w postaci pliku używanego jako część pamięci. Listing 5.2. LST05_2.CPP - struktura MEMORYSTATUS 1 typedef struct _MEMORYSTATUS ( 2 DWORD dwLength; 3 DWORD dwMemoryLoad; 4 DWORD dwTotalPhys; 5 DWORD dwAvailPhys; 6 7 DWORD dwAvailPageFile; 8 DWORD dwTotalVirtual; 9 DWORD dwAvailVirtual; 10 } MEMORYSTATUS; 108 Poznaj Visual C++ 6 Pamięć wirtualna Aby aplikacje w systemie Windows mogły korzystać z większej ilości pamięci, niż wynosi jej fizyczna wielkość, pewna część dysku zostaje wykorzystana jako pamięć dodatkowa. Ta część pamięci nazywana jest pamięcią wirtualną. Computer Name: i1'1*1-2 TolalMemory: ,65052 KF FieeMemory: [13588 KB"" Memory Load: l53 ^ Rysunek 5.3. Okno wyświetlające dane dotyczące komputera PATRZ TAKŻE ł O tym, jak tworzyć aplikacje o architekturze okna dialogowego mówimy w rozdziale 3. 4 Więcej informacji na temat projektowania okien dialogowych jest w rozdziale 3. * Pomoc w zakresie dodawania funkcji obsługi znajduje się w rozdziale 3. Używanie pól edycji Ile liter i cyfr jest wprowadzanych do komputerów na całym świecie każdego dnia? Jak wielu ludzi dokonuje operacji wprowadzania danych bankowych czy też zamówień? Ile by ich nie było, w znacznej wielkości w środowisku Windows odbywa się to za pomocą pól edycji. Pola edycji służą do wprowadzania tekstu i w odpowiedzi wyświetlania go na ekranie. Litery, cyfry oraz zwykłe znaki pisarskie, które widnieją na klawiaturze komputera, mogą być wprowadzone i wyświetlone właśnie w polu edycji. Pola te posiadają pewne możliwości w zakresie formatowania wprowadzonego tekstu. Na przykład, część lub całość tekstu widniejącego w polu edycji może zostać zaznaczona za pomocą myszy lub klawiatury, po czym może być wycięta lub skopiowana do innego pola edycji. Dodatkowo, możemy skorzystać z menu skrótów, z którego mamy dostęp do takich czynności jak: Undo (cofnięcie operacji), Cut (wycięcie zaznaczonego fragmentu wraz ze skopiowaniem do schowka), Copy (skopiowanie fragmentu do schowka). Pastę (wklejenie fragmentu przechowywanego w schowku), Delete (usunięcie danego fragmentu, oraz Select Ali (zaznaczenie całości tekstu). Stosowanie kontrolek tekstowych 109 Ograniczenie pojemności jednoliniowych pól edycji Pojemność jednoliniowych pól edycji jest ograniczona do maksymalnie 32 kB, co oznacza możliwość umieszczenia w nim około 32 000 znaków. Do dyspozycji mamy różne użyteczne style, które możemy przypisywać polom edycji. Styl Password powoduje ukrycie tekstu wprowadzonego przez użytkownika, zwykle zastępując wszystkie wprowadzone znaki symbolem gwiazdki (*). Style Uppercase oraz Lowercase automatycznie zamieniają wszystkie umieszczone w polu edycji znaki według ustalonego trybu. Z kolei styl Number nie dopuszcza do wpisywania w polu edycji znaków, które nie są cyframi. Styl nie zezwala również na użycie znaku kropki (.). Kolejne punkty rozdziału będą mówić o tym, jak możemy spożytkować obiekty edycyjne. Najpierw jednak musimy utworzyć nowy projekt. Użyjemy zatem znanego nam już narzędzia AppWizard, by utworzyć projekt w oparciu o architekturę okna dialogowego. Nowemu projektowi nadamy nazwę Edits. PATRZ TAKŻE • Szczegółowy opis tworzenia projektów znajduje się w rozdziale 3. Umieszczenie pól edycji Podobnie jak w przypadku pozostałych kontrolek pola edycji umieścimy na szablonie okna dialogowego, otwartym w edytorze zasobów. Po wykonaniu tych poleceń powinniśmy otrzymać okno dialogowe podobne do widocznego na rysunku 5.4. Dodanie pól edycji do pola dialogowego 1. Otwórz w edytorze zasobów okno dialogowe IDD_EDITS_DIALOG. Usuń widniejący pośrodku napis, zaznaczając go, a następnie naciskając klawisz Delete. 2. Z paska narzędziowego Controls wybierz symbol statycznego pola tekstowego, następnie umieść takie pole w lewym górnym rogu okna dialogowego, jak na rysunku 5.4. 3. Upewniwszy się, że zaznaczyłeś dodane okno, kliknij prawym klawiszem myszy, a z otwartego menu skrótów wybierz pozycję Properties. Otwarte zostanie okno dialogowe Text Properties, które za pomocą pinezki w lewym górnym rogu możesz przypiąć, tak że będzie otwarte przez cały czas. 4. Jako nagłówek wpisz w polu Caption: &First. 5. Z paska narzędziowego Controls wybierz symbol pola edycji, a następnie umieść takie pole po prawej stronie First. 6. W polu ID otwartego okna Properties wpisz identyfikator nowego pola edycji: IDC_ EDIT FIRST. 110 Poznaj Visual C++ 6 7. Po wybraniu z paska Control symbolu przycisku, umieść jeden przycisk po prawej stronie dodanego przed chwilą pola edycji. Jego położenie widać na rysunku 5.4. 8. Wpisz IDC_GET_TEXT jako identyfikator, a jako nagłówek przycisku wpisz Get Text. 9. Z paska Controls wybierz statyczne pole tekstowe i umieść je poniżej okna First. 10. Wprowadź &Second jako jego nazwę. 11. Umieść nowe pole edycji po prawej stronie Second. 12. Ponownie wybierz symbol pola edycji i umieść nowe pole edycji po prawej stronie ostatnio dodanego. 13. Wpisz lDC_EDZT_SHOwjako identyfikator, a na karcie Styks zaznacz opcję Read-oniy. 14. Rozciągnij ostatnio dodane pole edycji, aby całe okno dialogowe wyglądało jak na rysunku 5.4. OK ; First Edit Cance) ; Second Edit Rysunek 5.4. Końcowy wygląd okna dialogowego Edits Skróty klawiaturowe do pól edycji Jeśli chcemy wykorzystać mnemonikę do utworzenia skrótu do pola edycji, pole to musi być następne w kolejności tabulacji po statycznym polu tekstowym, w którym musimy umieścić żądaną mnemonikę. Okno dialogowe Edits pokazuje działanie mnemonik użytych w statycznych polach tekstowych. Możemy więc przetestować działanie tego mechanizmu, naciskając Ctri+T lub wybierając pozycję Test z menu Layout. Gdy uruchomimy tryb testowy, naciśnięcie kombinacji Alt+F powinno spowodować ustawienie kursora w polu edycji po prawej stronie First. Podobnie naciśnięcie Alt+S powinno wywołać pojawienie się kursora w polu edycji Second. PATRZ TAKŻE • Szczegóły dotyczące tworzenia projektów w oparciu o okno dialogowe znajdują się w rozdziale 1. Stosowanie kontrolek tekstowych 111 Umieszczanie i pobieranie tekstu z pola edycji W przypadku pól edycji stanowiących element okna dialogowego wprowadzony do nich tekst pobierany jest na żądanie, najczęściej na skutek kliknięcia przycisku OK. W tym momencie tekst jest pobierany z pola edycji i przechowany wewnątrz programu lub zapisany na przykład w bazie danych. Użyjemy teraz narzędzia CIassWizard, by przypisać zmienną klasy CString do pola edycji. Narzędzie to deklaruje zmienną oraz wywołuje makra, które umożliwiają obsługę wymiany danych pomiędzy zmiennymi i polami edycji. Przypisanie za pomocą CIassWizard zmiennej klasy CString polu edycji 1. Naciśnij CtrI+W lub wybierz odpowiednią pozycję z menu View, by uruchomić CIassWizard. 2. Wybierz kartę Member Variables. 3. Z listy rozwijanej Ciass Name wybierz klasę okna dialogowego: CEditDIg. 4. Na liście identyfikatorów Control ID zaznacz identyfikator IDC_EDIT_FIRST. 5. Kliknij Add Variable. Otwarte zostanie okno dialogowe Add Member Variable. Podwójne kliknięcie identyfikatora z listy wywoła ten sam efekt. 6. Upewnij się, że wybraną pozycją na liście rozwijanej Category jest Value, a Variable Type wskazuje CString. 7. W polu Member Variable Name wpisz nazwę zmiennej: m_strFirst, a później kliknij OK. 8. W polu Maximum Characters, znajdującym się u dołu okna dialogowego CIassWizard wpisz liczbę 15. Parametr ten ogranicza długość łańcucha znakowego akceptowanego przez pole edycji. 9. Kliknij OK, by zamknąć CIassWizard. Przycisk Get Text będzie służył do: pobierania łańcucha znakowego z pola edycji First, odwracania tego łańcucha, a następnie wyświetlania go w nowej formie z powrotem w polu edycji. Aby jednak osiągnąć taki efekt, musimy jeszcze dodać funkcję obsługi komunikatu BN_CLICKED dla przycisku IDC_GET_TEXT. Posłużyć się możemy wielokrotnie już wykorzystywaną procedurą, opisaną w rozdziale 4. Nowej funkcji powinniśmy nadać nazwę OnGetText(), a kod uzupełnić w sposób pokazany na listingu 5.3. Listing 5.3. LST05_3.CPP - umieszczanie i pobieranie tekstu z pola edycji 1 void CEditsDlg::OnGetText() 2 ( 3 // TODO: Tutaj wpisz zawartość funkcji 4 // ** pobranie danych z.pola edycji 112_____________________________________Poznaj Visual C++6 5 UpdateData() ; 6 7 // ** Sprawdzenie, czy został wpisany tekst S if (m_strFirst.IsEmpty() == FALSE) O 9 { 10 // ** Wyświetlenie wprowadzonego tekstu 11 MessageBox (m strFirst) ; 12 13 // ** Odwrócenie kolejności znaków w łańcuchu 14 m_strFirst.MakeReverse(); @ 15 16 // ** Przepisanie danych ze zmienne-] do pola edycji 17 18 UpdateData(FALSE); © 19 } 20 } O Sprawdzenie, czy w polu edycji dokonano wpisu. @ Kolejność znaków zawartych w zmiennej zostaje odwrócona. © Odwrócony tekst zostaje umieszczony w polu edycji. Wywołanie funkcji UpdateData () w linii 5 następuje z parametrem FALSE, co powoduje przekazanie treści z pole edycji do zmiennej m_strFirst. W linii 8 funkcja isEmp-ty() klasy CString zwraca FALSE, jeśli zmienna m_strFirst nie jest pusta. W linii 13 MakeReverse (), funkcja składowa CString, po prostu odwraca kolejność znaków łańcucha stanowiącego treść tekstu wpisanego w polu edycji. Dalej następuje ponowne wywołanie funkcji UpdateData (), z argumentem FALSE, co powoduje przekazanie łańcucha znakowego w formie odwróconej z powrotem do pola edycji. Ustalanie reakcji na komunikaty powiadamiające, dotyczące pól edycji Mechanizmy opisane powyżej pozwalają na pobieranie tekstu po jego wprowadzeniu. Jeśli jednak wykorzystamy funkcje obsługi komunikatów, które wysyłane są za każdym razem, gdy tekst w polu edycji ulega zmianie, możliwe się staje pobieranie tekstu już w trakcie jego wprowadzania. System Windows posługuje się ośmioma komunikatami po- wiadamiającymi, dotyczącymi pól edycji. Są one wymienione w tabeli 5.1. Przykładowo, komunikat EN_CHANGE zostaje wysłany po każdej zmianie tekstu wewnątrz pola edycji. Stosowanie kontrolek tekstowych 113 Tabela 5.1. Komunikaty powiadamiające dotyczące pól edycji Komunikat Znaczenie EN_CHANGE Wysyłany, gdy tekst został zmieniony po jego wyświetleniu EN_UPDATE Wysyłany, gdy tekst został zmieniony przed wyświetleniem EN_SETFOCUS Wysyłany, gdy pole edycji staje się obiektem bieżącym EN_KI LLFOCUS Wysyłany, gdy pole edycji przestaje być obiektem bieżącym EN_MAXTEXT Wysyłany, gdy wprowadzony tekst był za długi i został obcięty EN_HSCROLL Wysyłany, gdy użytkownik użył paska przewijania poziomego (tylko w przypadku pól wieloliniowych) EN_VSCROLL Wysyłany, gdy użytkownik użył paska przewijania pionowego (tylko w przypadku pól wieloliniowych) EN_ERRSPACE Wysyłany, gdy nie można było przydzielić pamięci Dopiszemy teraz funkcję obsługi komunikatu EN_CHANGE dla pola edycji Second. Dzięki temu tekst będzie pobierany z pola Second, a następnie wypisywany w polu po prawej stronie Second. Będzie to sprawiało wrażenie wypełniania dwóch okien jednocześnie. Posłużymy się narzędziem CIassWizard dla utworzenia nowej funkcji obsługi komunikatu. Funkcje tego rodzaju były dotąd dodawane przy użyciu okna dialogowego New Win-dows Message and Event Handler. Jak każda szanująca się aplikacja dla Windows, również Developer Studio, umożliwia osiągnięcie danego wyniku za pomocą różnych metod. CIassWizard może być w tym wypadku narzędziem wygodniejszym, albowiem nie jest ograniczony do działań z komunikatami i nie wymaga otwierania szablonu okna dialogowego. Utworzenie funkcji obsługi komunikatu przy użyciu CIassWizard 1. Naciśnij CtrI+W lub wybierz pozycję CIassWizard z menu View, aby wywołać to narzędzie. 2. Wybierz kartę Message Maps. 3. Z listy Ciass Name wybierz pozycję CEditDIg. 4. Zaznacz identyfikator IDC_EDIT_SECOND, widoczny na liście Control IDs. 5. Wybierz komunikat EN_CHANGE spośród komunikatów wypisanych na liście Messages. 6. Kliknij Add Function. 7. W oknie dialogowym Add Member Function kliknij OK, aby zaakceptować nazwę nowej funkcji składowej: OnChangeEditSecond. Okno dialogowe CIassWizard powinno wyglądać jak na rysunku 5.5. 114 Poznaj Visual C++ 6 8. Kliknij Edit Code, aby przejść do edycji treści nowej funkcji. Dysponujemy zatem szkieletem nowej funkcji OnChangeEditSecondO; musimy teraz dopisać kod z listingu 5.4. Nowa funkcja będzie wywoływana za każdym razem, gdy użytkownik dokona zmian tekstu wprowadzonego w polu edycji IDC_EDIT_SECOND, włączając w to kasowanie i wstawianie znaków. MFC CIaisWiMid Message Maps Project MemberVatiabl e» Automatio n Oass nam Adivei«; Events j e: Ctednb AddClaa.., •' Edits C:\„.\EdJts\E cBsO Objęci IDs: d la.h. C:^...\Edite\EditsDlg .cp CEdit sDlg P vłessa aes EN ERR SPA CE ROL L FOC US TEX T FOC US ATE ' -H -i" ,''-.':1:,1 d I Oelete Function CEditsDlg IDC EDIT FJRS1 IDC EDIT SWv, IDC GET TEXT IDCANCEL IDOK 'lemberfuncti ore; ^ S Edit Code EN HSC EN KILL E N MAX EN SET EMU PD ;'' i ••' :•:1:•::• V DoOataEmshange ^- W OnGetTent ONJDCGEUE:XT:Bt. W OnInBDialog ON.WMJNITDIALOG W OnPaint ON WM PAINT 3esciiption: Indicales Ihe display is upddted aftef te 'LCLICKED t changes . :: | OK ] Cancel j Rysunek 5.5. Wygląd okna dialogowego CIassWizard po dodaniu funkcji obsługi komunikatu EN CHANGE Listing 5.4. LST05_4.CPP - przechwytywanie komunikatu EN_CHANGE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void CEditsDlg::OnChangeEditSecond() { // TODO: Jeśli to jest kontrolka RICHEDIT, // n-ie wyśle ona tego komunikatu II dopóki nie zastąpisz funkcji CDialog::OnInitDialog (), II aby wysłać .komunikat EM_SETEVENTMASK II do kontrolki z flagą. ENM_CHANGE ORed II do maski IParam. II TODO: dodaj tu własny kod obsługi komunikatu II ** Pobranie wskaźnika do każdego obiektu pól edycji CEdit* pEdit = (CEdit*)GetDIgItem(IDC_EDIT_SECOND); O CEdit* pEditShow = (CEdit*)GetDIgItem(IDC_EDIT_SHOW); Stosowanie kontrolek tekstowych 115 16 // ** Deklaracja zmiennej przechowującej wprowadzony tekst 17 CString strText; 18 19 // ** Pobranie tekstu z pola edycji Second 20 pEdit->GetWindowText(strText); © 21 22 // ** Zmiana zawartości pola edycji IDC_EDIT_SHOW 23 pEditShow->SetWindowText(strText); © 24 } O Przydzielenie wskaźnika każdemu polu edycji. © Pobranie zawartości pierwszego pola edycji i zapisanie jej w zmiennej. © Umieszczenie w drugim polu edycji zawartości identycznej z pierwszym polem. CIassWizard dodaje na początku listingu kilka linii dodatkowego komentarza. Komentarz ten odnosi się jednak do pól tekstu formatowanego. Ponieważ my nie zastosowaliśmy tego rodzaju pola, komentarz może zostać zignorowany. W liniach 13 i 14 funkcja GetDIgitem () jest wywoływana dwukrotnie w celu pobrania wskaźników osobno dla każdego pola edycji. Jak wszystkie inne klasy dotyczące kontrolek, również CEdit jest klasą wywodzącą się z cwnd. Klasa CEdit dostarcza dodatkowych funkcji w stosunku do cwnd. Kilka najbardziej użytecznych, opisujemy w tabeli 5.2. Wywołana w linii 20 funkcja GetWindowText () pobiera bieżącą zawartość pola edycji Second i zapisuje ją w zmiennej lokalnej strText. Zmienna ta służy do skopiowania tekstu do pola edycji IDC_EDIT_SHOW, które pracuje w trybie read-oniy. Dzieje się to w linii 23. Możemy więc skompilować i uruchomić nasz program. Podczas, gdy umieszczamy tekst w polu edycji Second, identyczny tekst powinien pojawiać się w polu po jego prawej stronie. Zauważmy, że jeśli klikniemy pole read-oniy pojawi się w nim kursor, co jednak nie umożliwi nam dokonywania wpisów. Możemy zmienić ten stan, dodając funkcję obsługi komunikatu powiadamiającego EN_SETFOCUS dla pola IDC_EDIT_SHOW. Możemy dokonać tego wykorzystując procedurę opisaną w punkcie „Utworzenie funkcji obsługi komunikatu przy użyciu CIassWizard". W utworzonej w ten sposób funkcji OnSetFocu-sEditShow () należy umieścić następujący wpis: GetDIgitem(IDC_EDIT_SHOW)->SetFocus() ; Po ponownym skompilowaniu i uruchomieniu programu, kuknięcie w polu edycji IDC_EDIT_SHOW spowoduje ustawienie kursora w polu Second. 116_____________________________________Poznaj Visual C++6 Tabela 5.2. Funkcje składowe klasy CEdit Nazwa funkcji Opis Create () Pozwala na tworzenie nowych pól edycji podczas pracy programu GetSel () Ustala pozycje pierwszego i ostatniego znaku w zaznaczonym tekście S e t s e l () Ustala zakres selekcji ReplaceSeK) Usuwa zaznaczony tekst LimitText () Ustala maksymalną liczbę znaków PATRZ TAKŻE • O dodawaniu funkcji obsługi mówimy w rozdziale 4. • Więcej informacji na temat wymiany i zatwierdzania danych w kontrolkach podajemy w rozdziale 10. • Więcej informacji o klasie cnnd uzyskasz w rozdziale 4. Wyprowadzanie podklas dla pól edycji Bardzo często tekst wprowadzany w polu edycji musi zachować specyficzną formę. Na przykład kody pocztowe wielu państw składają się tylko i wyłącznie z cyfr, a w niektórych są to kombinacje cyfr i liter. Aplikacje Windows, które wymagają wprowadzania tekstu w takim formacie, muszą sprawdzać poprawność tekstu oraz jego zgodność z zasadami określonymi dla danego pola edycji. Najlepszym momentem dla takiego sprawdzenia i zatwierdzenia jest chwila, w której użytkownik wprowadza dane. Aby wykorzystać taki mechanizm, należy rozszerzyć funkcjonalność okna dialogowego poprzez wyprowadzenie z klasy CEdit podklasy. Wyprowadzona podklasa dziedziczy od klasy bazowej jej zmienne i funkcje. Nowa klasa może wykorzystywać mechanizmy zawarte w klasie bazowej, a także zamieniać ich funkcjonalność, gdy zajdzie taka potrzeba. Czym są podklasy? Podklasy są klasami wyprowadzonymi z klas bazowych, dziedziczącymi elementy składowe, takie jak funkcje, po klasach bazowych. W niniejszym punkcie wyprowadzimy podklasę o nazwie Clnitials z klasy CEdit. Zadaniem nowej klasy będzie wymuszenie specyficznego formatu dla wprowadzanych inicjałów użytkownika. Przykładowo, znakami akceptowalnymi będą jedynie litery, które zostaną automatycznie zamienione, jeśli zajdzie potrzeba, na litery wielkie. Klasa Clnitials będzie ponadto powodowała wprowadzanie odstępu pomiędzy literami, oraz usuwała je, gdy użytkownik naciśnie klawisz Backspace. Stosowanie kontrolek tekstowych 117 Zacznijmy więc od umieszczenia dodatkowych pól edycji w naszym oknie dialogowym Edits. Dodanie nowych pól edycji do okna dialogowego 1. Otwórz w edytorze zasobów okno dialogowe IDDJEDITSJDIALOG. 2. Z paska narzędziowego Controls wybierz symbol statycznego pola tekstowego, a następnie umieść takie okno w lewym dolnym rogu okna dialogowego, jak widać na rysunku 5.6. 3. W oknie właściwości w polu Caption wprowadź nagłówek Initials. 4. Po prawej stronie okna tekstowego Initials umieść nowe pole edycji. 5. Identyfikator nowego pola edycji umieszczony w polu ID okna właściwości powinien wyglądać następująco: IDCJEDITJNITIALS. 6. Ponownie wybierz z paska Controls i umieść w oknie dialogowym Edit statyczne pole tekstowe, po prawej stronie pola IDC_EDIT_INITIALS (rysunek 5.6). 7. Jako nagłówek tego okna wpisz Sumame. 8. Umieść w oknie dialogowym jeszcze jedno pole edycji, z prawej strony okna tekstowego Sumame. 9. Zwiększ szerokość ostatni umieszczonego pola edycji. Okno dialogowe Edits powinno wyglądać jak na rysunku 5.6. Rysunek 5.6. Rozszerzone okno dialogowe Edits Skoro dodaliśmy już nowe pola edycji, utworzymy klasę cinitials. Warto jeszcze zauważyć, że narzędzie CIassWizard może zostać użyte do wyprowadzania klas z innych klas biblioteki MFC, nie tylko CEdit. Tworzenie nowej podklasy przy użyciu CIassWizard 1. Wywołaj CIassWizard poprzez naciśnięcie Ctri+W lub wybranie pozycji CIassWizard z menu View. 2. Kliknij kartę Message Maps. 118 Poznaj Visual C++ 6 3. Wybierz pozycję CEditsDlg z listy klas Ciass name. 4. Kliknij przycisk Add Ciass, a następnie z wyświetlonego menu wybierz pozycję New. Otwarte zostanie okno dialogowe New Ciass, widoczne na rysunku 5.7. Ciass info(mation Marne: jCInitials File name: :lnitiab.cpp Cancel Change... fiase ciass: [[•Edit "3 The base ciass does not require a dialog resomce. Automation - • •• ••-• - ••••"•" •-• ••—•••••• ' (•' None : ^" Autorriation s C Ciealeable by type ID: l"""""" Rysunek 5.7. Okno dialogowe New Ciass 5. W polu Name wpisz nazwę nowej klasy: CInitials. 6. Wybierz CEdit spośród klas bazowych, Base Ciass. 7. Kliknij OK, co spowoduje utworzenie nowej klasy. 8. Z listy Ciass Name wybierz pozycję CEditDIg. 9. Z listy Object IDs wybierz pozycję IDC_EDIT_INITIALS. 10. Kliknij Add Variable. Otworzysz w ten sposób okno dialogowe Add Member Yariable. 11. Jako nazwę nowej zmiennej składowej w polu Member Variable Name wpisz: m_edtlnitials. 12. Z listy Category wybierz pozycję Control. 13. Z listy Variable Type wybierz CInitials. Opis umieszczony u dołu okna dialogowego powinien informować o przypisaniu zmiennej do zdefiniowanej przez użytkownika klasy CInitials. 14. Kliknij OK, by dodać nową zmienną do klasy. Komunikat, który zostanie wyświetlony, poinformuje, że koniecznym jest umieszczenie dyrektywy #include w pliku na- główkowym EditsDIg.h. Kliknij OK. 15. Kliknij OK w oknie dialogowym CIassWizard, aby je zamknąć. Stosowanie kontrolek tekstowych 119 Nowa klasa cinitials powinna być widoczna w panelu ClassView. Musimy jeszcze dopisać dyrektywę, o której mówił komunikat CIassWizard. Kliknijmy więc dwukrotnie pozycję CEditsDlg widoczną w panelu ClassView. W oknie edytora zostanie otwarty plik nagłówkowy klasy CEditsDlg. Zmodyfikujmy treść tego pliku, umieszczając jedną linię ponad linią, w której klasa CEditsDlg zostaje zadeklarowana. Wpisana przez nas linia musi mieć postać: #include "Initials.h" ciass CEditsDlg : public CDialog { CIassWizard tworząc nową klasę, utworzył wraz z nią dwa nowe pliki i dodał je do projektu: Initials.h oraz Initials.cpp. Dyrektywa łinclude jest konieczna w pliku EditsDIg.h, ponieważ klasa CEditsDlg posiada obecnie zmienną składową typu cinitials, która jest zadeklarowana w pliku Initials.h. Nieumieszczenie tej dyrektywy spowoduje błędy kompilacji. Możemy więc wykorzystać już klasę cinitials do wypełnienia określonych zadań związanych z wprowadzaniem przez użytkownika inicjałów. Pierwszym z tych zadań będzie odrzucanie wszelkich znaków, nie będących literami, konwertowanie wprowadzonych liter na wielkie oraz automatyczne wstawianie kropek po każdym wprowadzonym znaku. Osiągniemy to poprzez dodanie funkcji obsługi komunikatu WM_CHAR. Komunikat taki zostaje wysłany za każdym razem, gdy użytkownik wprowadzi znak z klawiatury, jeśli pole edycji jest obiektem bieżącym. Odmiennie od komunikatu EN_CHANGE, funkcja obsługi komunikatu WM_CHAR pobiera znaki wprowadzane przez użytkownika, zanim zostaną one wyświetlone, dlatego też jest szczególnie przydatna do zatwierdzania poprawności wprowadzanych znaków. Jeśli funkcja ustali, że znak jest właściwy, znak zostaje umieszczony w polu edycji. W przeciwnym wypadku zostaje po prostu zignorowany. Utworzenie funkcji obsługi komunikatu w podklasie przy użyciu CIassWizard 1. Wywołaj CIassWizard poprzez naciśnięcie Ctri+W lub wybranie pozycji CIassWizard z menu View. 2. Kliknij kartę Message Maps. 3. Z listy klas Ciass Name wybierz Cinitials. 4. Wybierz WM_CHAR z listy komunikatów Messages. 5. Kliknij Add Function. Na liście funkcji składowych, u dołu okna dialogowego CIass- Wizard pojawi się pozycja OnChar. 6. Kliknij przycisk Edit Code. Ustanowiliśmy zatem nową funkcję składową OnChar() wewnątrz klasy cinitials. Funkcja ta będzie wywoływana za każdym razem, gdy użytkownik będzie wprowadzał do pola edycji Initials nowy znak. Argument nChar przechowuje wartość wprowadzonego znaku. 120_____________________________________Poznaj Visual C++6 Dodatkowe argumenty nRepCnt oraz nFlags nie są związane z omawianym zagadnieniem, nie musimy zatem zaprzątać sobie nimi głów. Wpiszmy teraz kod zawarty na listingu 5.5. Listing 5.5. Zapis funkcji obsługi OnChar 1 void CInitials::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 2 ( 3 // TODO: Tutaj wpisz kod funkcji 4 // ** Sprawdzenie, czy wprowadzony znak jest literą 5 if( isalpha(nChar) ) O 6 ( 7 // ** Zamiana małych liter na wielkie 8 if( islower(nChar) ) @ 9 nChar -= 32; 10 11 // ** Wywołanie domyślnej procedury sprawdzania wartości 12 // ** zmian nChar 13 DefWindowProc(WM_CHAR, nChar, ® 14 ' MAKELONG(nRepCnt, nFlags)); 15 16 // ** Ponowne wywołanie domyślnej procedury 17 // ** aby dodać znak kropki 18 nChar = '.' ; 19 DefWindowProc(WM_CHAR, nChar, 20 MAKELONG(nRepCnt, nFlags)); 21 } 22 // ** Jeśli użyto klawisza Backspace 23 // ** następuje dwukrotne wywołanie funkcji OnChar 24 // ** w celu usunięcia kropki oraz kasowanego znaku 25 if( nChar == VK_BACK ) O 26 { 27 CEdit::OnChar(nChar, nRepCnt, nFlags); 28 CEdit::OnChar(nChar, nRepCnt, nFlags); 29 } 30 ) O Sprawdzenie, czy wprowadzony znak jest literą alfabetu. © Konwersja liter małych na wielkie. Stosowanie kontrolek tekstowych 121 © Wywołanie domyślnej procedury dla przeprowadzenia standardowej obróbki tekstu. Wprowadzony znak zostaje zamieniony na kropkę, a następnie ponownie zostaje wywołana ta sama procedura. O Po naciśnięciu klawisza Backspace program dokonuje podwójnego kasowania: kropki oraz wprowadzonego znaku. Wyrażenie i f umieszczone w linii 5 służy sprawdzeniu, czy znaki przekazane funkcji poprzez argument nChar są literami. W linii 8 sprawdzane są wielkości liter. Jeśli wpisano litery małe, zostają one zamienione na wielkie poprzez odjęcie liczby 32 od kodu danej litery w zestawie ANSI. W linii 13 wywołana zostaje funkcja Defwindowproc () (domyślna procedura okna). Wywołuje się ją zamiast funkcji składowej klasy bazowej CEdit: : OnChar (), ponieważ ta ostatnia nie odnotowuje zmian wprowadzanych w argumencie nChar. Funkcja ta posługuje się bowiem wyłącznie oryginalnymi parametrami komunikatu, ignorując wszelkie inne. W linii 18 następuje zamiana zawartości nChar na znak kropki, który zostaje wysłany do pola edycji, przy ponownym wywołaniu funkcji Defwindowproc (). W linii 25 umieszczona jest procedura sprawdzająca, czy użytkownik naciskał klawisz Backspace. VK_BACK jest symboliczną nazwą, która definiuje wirtualny kod tego klawisza. Definicje kodów wirtualnych istnieją dla wszystkich klawiszy. Tabela 5.3 wymienia kilka przykładów tych definicji. Jeśli zatem użytkownik użyje klawisza Backspace, spowoduje tym samym dwukrotne wywołanie funkcji składowej CEdit: :OnChar() w celu usunięcia kropki oraz znaku po niej następującego. Skompilujemy i uruchomimy teraz program, by przetestować funkcjonalność klasy CInitials. Tabela 5.3. Wirtualne kody klawiszy Definicja Opis klawisza VK_BACK Klawisz Backspace VK_TAB Klawisz tabulacji VK_LEFT Strzałka w lewo VK_RI GHT Strzałka w prawo VK_RETURN Klawisz Enter VK_ESCAPE Klawisz Esc VK CONTROL Klawisz Ctrl 122_____________________________________Poznaj Visual C++6 Stosowanie wieloliniowych pól edycji Pola edycji nie muszą być ograniczone tylko do jednej linii. W celu zastosowania wieloliniowego pola, wszystko co musimy uczynić, polega na zwiększeniu wysokości pola edycji, umieszczonego na szablonie okna dialogowego oraz zaznaczeniu stylu Multiiine. Konieczne będzie również narzucenie stylu Want Retum, co spowoduje śledzenie przy- padków użycia klawisza Enter. To z kolei wywoływać będzie przeniesienie kursora do nowej linii. Wieloliniowe pola edycji obsługują pionowe paski przewijania, które mogą uaktywniać się automatycznie, gdy objętość wprowadzonego tekstu przekroczy pojemność pola edycji. Dzięki ogólnym zasadom wykorzystywania wieloliniowych pól edycji, używa się ich podobnie jak pól jednoliniowych. Możemy w nich wykorzystać klasę CEdit, która posiada kilka funkcji składowych, specyficznych dla pól wieloliniowych, wymienionych w tabeli 5.4. Ograniczenia pojemności wieloliniowych pól edycji Wieloliniowe pola edycji, tak jak i jednoliniowe, mają ograniczoną pojemność. Dla pól wieloliniowych wynosi ona maksymalnie 64 kB, co zapewnia możliwość wpisania do około 64 000 znaków. Tabela 5.4. Funkcje składowe klasy CEdit dla wieloliniowych pól edycji Nazwa funkcji Opis GetLineCount() Sprawdza liczbę linii tekstu GetLine() Pobiera linię tekstu FmtLines() Włącza i wyłącza zawijanie tekstu SetTabStops() Umożliwia wyznaczenie punktów tabulacji Ostatnim typem pól edycji są pola tekstu formatowanego. Pola te są o wiele bardziej rozbudowane niż proste pola edycji, przez co umożliwiają formatowanie zarówno znaków, jak i akapitów. Tego typu pola rzadko są stosowane w oknach dialogowych, częściej w widoku ogólnym aplikacji. Z tego też powodu nie zostały opisane na tym etapie. PATRZ TAKŻE • Szczegóły dotyczące pól tekstu formatowanego znajdują się w rozdziale 19. Rozdział 6 Używanie list Wyświetlanie list w oknach dialogowych Organizowanie danych w strukturze drzewiastej Obsługa wyboru jednej lub kilku pozycji Implementacja zależności pomiędzy listami w oknie dialogowym Sposób prezentacji informacji ma ogromny wpływ na sposób odbioru aplikacji jako mniej lub bardziej przyjaznej. Aplikacje bardziej przyjazne użytkownikowi mają większe szansę na sukces. Wiele spośród tych informacji podawanych jest w formie list: katalogów i plików, nazw czcionek i ich stylów a nawet wykazu samolotów podchodzących do lądowania na lotnisku w Los Angeles. Tworzenie list Listy mogą występować w czterech różnych odmianach: listy kombinowane (rozwijane), pola listy, drzewa oraz listy szczegółowe. Każda z tych list spełnia określone zadania programistyczne. Wraz z umieszczeniem obiektu listy w oknie dialogowym należy również określić właściwy styl, ponieważ może on wpływać w sposób radykalny na wygląd oraz działanie całego obiektu. Przykładowo, dokonywanie wyboru wielopozycyj-nego może być dozwolone w przypadku pól listy oraz list szczegółowych, podczas gdy w przypadku listy kombinowanej styl Drop List uaktywnia funkcjonalność pola edycji, natomiast styl Dropdown tego nie czyni. W niniejszym rozdziale poznamy zagadnienia związane ze stosowaniem różnego ro- dzajów list. Zaczniemy jak zwykle, od utworzenia nowego projektu o budowie okna dia- logowego, o nazwie Lists. Projekt będzie obejmował wszystkie cztery rodzaje list, co ma na celu wyświetlenie struktury katalogów oraz zawartych w nich plików. PATRZ TAKŻE • Informacje na temat tworzenia aplikacji wykorzystujących jako podstawę okno dialogowe, znajdują się w rozdziale 3. 124_____________________________________Poznaj Visual C++ 6 Dodawanie list kombinowanych (rozwijanych) Lista kombinowana zawdzięcza nazwę swojej budowie. Składa się bowiem z trzech kontrolek: pola edycji, pola listy oraz przycisku. Lista tego typu umożliwia dokonanie wyboru tylko jednej pozycji. Od pozostałych rodzajów list odróżnia ją to, iż wybrana pozycja jest zawsze widoczna u góry listy. Tabela 6.1 wymienia trzy rodzaje, na jakie podzielić można listy kombinowane. O tym, który spośród tych typów ma zostać zastosowany, decydujemy przy użyciu karty Styles, dostępnej w oknie dialogowym Combo Box Properties. Okno to możemy wywołać po umieszczeniu listy na szablonie okna dialogowego. W nowej przykładowej aplikacji lista kombinowana użyta zostanie do dokonywania wyboru głównego katalogu, a następnie umieszczeniu w innych obiektach okna dialogowego informacji o podkatalogach oraz plikach. Tabela 6. l. Rodzaje list kombinowanych Opis Rodzaj Opis Simple ' Łączy pole edycji z polem listy. Lista jest zawsze widoczna, wybrana pozycja natomiast zostaje wyświetlona w polu edycji Dropdown Łączy pole edycji wyposażone w przycisk oraz pole listy. Lista zostaje wyświetlona dopiero po kliknięciu przycisku Drop List Zawiera statyczne okno tekstowe, posiadające przycisk rozwijania listy oraz pole listy. Jest to typ podobny do Dropdown z tą różnicą, że użytkownik nie może dokonywać wpisów z klawiatury Umieszczenie listy kombinowanej w oknie dialogowym Lists 1. Otwórz w edytorze zasobów szablon okna dialogowego, klikając dwukrotnie pozycję IDD_LIST_DIALOG, widoczną w panelu ResoureesYiew, a następnie usuń znajdujący się pośrodku okna napis TODO. 2. Usuń z szablonu przycisk Cancel, a OK przesuń w prawy dolny róg. Ponieważ w nowym oknie umieścimy kilka innych obiektów, prawdopodobnie konieczne będzie powiększenie jego rozmiarów. Na obecnym etapie okno powinno mieć proporcje jak okno pokazane na rysunku 6. l. 3. Z paska narzędziowego Controls wybierz statyczne pole tekstowe i umieść je w lewym górnym rogu okna dialogowego. 4. Nadaj mu nagłówek: Main Directory. 5. Z paska Controls wybierz symbol listy kombinowanej i umieść taką listę po prawej stronie Main Directory. 6. Zwiększ szerokość listy, rozciągając ją do po prawego marginesu okna dialogowego. Używanie list 125 Rysunek 6.1. Lista kombinowana umieszczona w oknie dialogowym Lists 7. Nadaj liście identyfikator IDC_MAIN_DIR. 8. Na znajdującej się w oknie właściwości karcie Styles, z listy Type wybierz pozycję Drop List. Umieszczona w oknie dialogowym lista kombinowana posiada domyślnie zaznaczoną opcję Sort. Oznacza to, że wszystkie elementy występujące na liście, uporządkowane będą alfabetycznie. Aby zrezygnować z tej opcji, należy na karcie Styles usunąć znacznik widniejący w polu wyboru Sort. Cechą listy kombinowanej jest możliwość oglądania listy w rozwiniętym oknie. Owo okno rozwijamy klikając przycisk widoczny w obrębie pola edycji. Wokół rozwiniętego pola listy widoczna jest ramka formatująca, umożliwiająca zwiększenie wysokości pola za pomocą uchwytów wymiarowania. Skoro dodaliśmy do naszego okna dialogowego listę kombinowaną, za pomocą Ciass-Wizard przypiszemy jej teraz zmienną, wykonując poniższą procedurę. Przypisanie zmiennej CcomboBox do listy kombinowanej 1. Wywołaj CIassWizard, naciskając kombinację klawiszy CtrI+W lub wybierając właściwą pozycję z menu View. 2. Kliknij kartę Member Yariables. 3. Z listy Ciass Name wybierz pozycję ClistDIg. 4. W polu listy Control IDs zaznacz pozycję IDC_MAIN_DIR. 5. Kliknij przycisk Add Yariable, po czym otwarte zostanie okno Add Member Yariable. 6. Upewnij się, że z listy Category wybrana jest pozycja Control, a z listy Variable Type pozycja CcomboBox. 7. Wpisz w polu Member Variable Name nazwę nowej zmiennej: m_ncbMainDir, a następnie kliknij OK. 8. W oknie CIassWizard również kliknij przycisk OK, co zakończy działanie narzędzia. • O nowym rodzaju list, listach rozszerzonych, mówimy w rozdziale 11. Rozszerzona funkcjonalność list kombinowanych Visual C++ w swojej szóstej wersji pozwala na umieszczanie elementów graficznych jako pozycji listy rozwijanej. Jest to tzw. rozszerzona lista rozwijana. Dodawanie list drzewiastych Lista drzewiasta jest unikalną listą, jedyną, która może wyświetlać informacje hierar- chiczne. Wynika z tego sposób przedstawienia elementów listy. Element wysunięty najdalej w lewą stronę nazywany jest root node (odsł. węzeł korzenia) — węzłem początkowym. Po przeciwnej stronie grafu występują elementy nie posiadające następujących po nich podelementów, zwane leafnode (dosł. węzeł liścia) - węzłami końcowymi. Pomiędzy węzłami krańcowymi występują węzły pośrednie branch node (dosł. węzeł gałęzi) - węzły wewnętrzne.'Na wykresie węzły połączone są liniami, reprezentującymi zachodzące pomiędzy nimi relacje. Domyślnie, lista drzewiasta zezwala na wyselekcjonowanie tylko jednego elementu. Celem umożliwienia dokonywania wyboru kilku elementów jednocześnie trzeba wpisać pewną porcję kodu. W naszym przykładzie lista drzewiasta użyta zostanie do wyświetlania zawartości kata- logów, którą stanowią pliki uporządkowane alfabetycznie. Utworzymy zatem węzły począt- kowe dla każdej litery alfabetu, a następnie przydzielimy pliki do właściwych węzłów. Używając po raz kolejny narzędzia CIassWizard, umieścimy w oknie dialogowym listę drzewiastą, wykonując podane poniżej instrukcje, a następnie przydzielimy jej zmienną, co opisuje punkt „Przydzielenie zmiennej CTreeCtrl liście drzewiastej". Dodanie listy drzewiastej do szablonu okna dialogowego Lists 1. Otwórz w edytorze zasobów szablon okna dialogowego IDC_LISTS_DIALOG kli-kając dwukrotnie właściwą pozycję w panelu ResourcesView. 2. Umieść na szablonie statyczne pole tekstowe, nazywając je Files. Będzie ono pełniło rolę nagłówka listy drzewiastej, co widać na rysunku 6.2. 3. Z paska narzędziowego Controls wybierz pozycję symbolizującą listę drzewiastą, a później umieść ją po lewej stronie okna dialogowego. 4. Wprowadź identyfikator IDC_FILES_TREE dla nowej listy. 5. Po przejściu na kartę Styles zaznacz opcje: Has Buttons, Has Lines, Lines at Root. Okno dialogowe powinno wyglądać obecnie dokładnie jak na rysunku 6.2. Używanie list 127 Rysunek 6.2. Okno dialogowe Lists z umieszczoną w nim listą drzewiastą Przydzielenie zmiennej CTreeCtri liście drzewiastej 1. Wywołaj narzędzie CIassWizard, naciskając CtrI+W lub aktywując właściwą pozycję menu View. 2. Kliknij kartę Member Variables. 3. Z listy CIass Name wybierz pozycję CListsDIg. 4. Wybierz IDC_FILES_TREE z listy Control IDs. 5. Kliknij przycisk Add Variable, co spowoduje otwarcie okna dialogowego Add Member Variable. 6. Z listy Category musi być wybrana pozycja Control, natomiast jako typ zmiennej, czyli Variable Type, musisz wybrać CTreeCtri. 7. Jako nazwę zmiennej wpisz w polu Member Variable Name: m_treeFiles, po czym kliknij OK. 8. Zamknij okno CIassWizard, klikając obecny w nim przycisk OK. PATRZ TAKŻE • Więcej informacji na temat stylów i używania list drzewiastych w widokach dokumentów znajduje się w rozdziale 19. Listy drzewiaste a wybór wielopozycyjny Klasa CTreeCtri nie pozwala wprawdzie na wybór więcej niż jednej pozycji z listy, lecz można wyprowadzić z niej podklasę i wyposażyć ją w tę funkcję. 128_____________________________________Poznaj Visual C++ 6 Wykorzystanie pól listy Pole listy jest najprostszą jej formą, przedstawiającą w sposób bezpośredni zawarte na liście elementy, lecz w przeciwieństwie do list kombinowanych i drzewiastych, umożliwia jednoczesny wybór więcej niż jednego elementu. Lista tego rodzaju występuje w czterech wariantach dotyczących możliwości dokonywania wyboru pozycji. Warianty te opisane są w tabeli 6.2. Tabela 6.2. Typy selekcji w polach listy Typ Opis Single Można wybrać tylko jeden element listy. Wybranie któregoś z nich unieważnia ostatnio dokonaną selekcję Multiple Można dokonać selekcji kilku elementów listy, klikając je myszą, w połączeniu z klawiszem Shift lub Ctrl Extended Spełnia funkcje typu Multiple, pozwalając dodatkowo na dokonywanie wyboru poprzez przeciągnięcie myszą z wciśniętym jej lewym klawiszem, ponad wybranymi elementami None Nie zezwala na wybór żadnego elementu Podobnie jak w przypadku listy kombinowanej, porządkowanie alfabetyczne jest przyjęte jako domyślne. Aby zmienić ten stan rzeczy, musimy usunąć znacznik opcji Sort, na karcie Styles. W naszym projekcie Lists użyjemy pola listy do wyświetlenia podkata-logów. Będziemy mogli wybrać kilka spośród nich, a z listy szczegółowej odczytać dodatkowe szczegóły. Zaczniemy od umieszczenia pola listy typu Multiple w oknie dialogowym, co uczynimy według przedstawionej poniżej procedury. Odnosi się ona do opcji stylu No Integral Height, która niezaznaczona powoduje obliczenie wysokości pola listy w celu pomieszczenia konkretnej liczby elementów. Jeśli pozostawimy tę opcję aktywną, pole będzie wyświetlało listę partiami. Umieszczenie pola listy w oknie dialogowym Lists 1. Otwórz szablon okna dialogowego IDD_LISTS_DIALOG w edytorze zasobów. 2. Dodaj statyczne pole tekstowe o nagłówku Sub Directories, które będzie opisywało zawartość pola listy. 3. Z paska narzędziowego Controls wybierz pole listy i umieść je wzorując się na rysunku 6.3. 4. Jako identyfikator pola listy wpisz: IDC_SUB_DIRS. 5. Otwórz kartę Styles, a w niej z listy Selection wybierz pozycję Extended. Używanie list 129 6. Na tej samej karcie usuń znacznik z pola wyboru No Integral Height. Okno dialogowe powinno obecnie wyglądać jak na rysunku 6.3. Teraz, skoro pole listy jest już dodane, należy przydzielić mu zmienną. Rysunek 6.3. Okno dialogowe Lists z dodanym polem listy Przydzielenie zmiennej CListBox do pola listy 1. Wywołaj narzędzie CIassWizard, naciskając Ctri+W lub aktywując właściwą pozycję menu View. 2. Kliknij kartę Member Variables. 3. Z listy Ciass Name wybierz pozycję CListsDIg. 4. Z pola listy Control IDs wybierz identyfikator IDC_SUB_DIRS. 5. Kliknij przycisk Add Variable, co spowoduje otwarcie okna dialogowego Add Member Variable. 6. Z listy Category musi być wybrana pozycja Control, natomiast jako typ zmiennej, czyli Variable Type, musisz wybrać CListBox. 7. Jako nazwę zmiennej wpisz w polu Member Variable Name: m_lbSubDirs, po czym kliknij OK. 8. Zamknij okno CIassWizard, klikając obecny w nim przycisk OK. Użycie list szczegółowych Lista tego typu jest najbardziej złożonym obiektem spośród list i stosowana jest raczej jako samodzielny widok, niż element okna dialogowego. Może ona wyświetlać symbole graficzne wraz z towarzyszącymi im napisami. Posiada cztery tryby wyświetlania, wymie- nione w tabeli 6.3. 130 Poznaj Visual C++6 Eksplorator Windows wykorzystuje listę szczegółową Najbardziej znamiennym przykładem zastosowania listy szczegółowej jest Eksplorator Windows, Menu View pozwala na wybór jednego z czterech trybów wyświetlania. Tabela 6.3. Typy widoku listy szczegółowej Tryb widoku Opis Icon Wyświetla duże ikony (32 X 32 piksele) z podpisami umieszczonymi pod spodem, ikony porządkowane są w rzędach Smali Icon Tryb podobny do poprzedniego z tą różnicą, że wyświetlane są małe ikony (16 x 16pikseli). List Wyświetla w sposób podobny do trybu Smali Icon, ikony porządkowane są w kolumnach Report Wyświetla w kolumnach informacje o plikach. Kolumny posiadają swoje nagłówki W przykładzie Lists lista szczegółowa wykorzystana będzie do wyświetlania szczegółów katalogów, wyselekcjonowanych w polu listy leżącym powyżej. Informacje na tej liście umieszczone będą w trzech kolumnach. Zawartością pierwszej z nich będzie nazwa katalogu, drugiej liczba plików znajdujących się w katalogu, a trzecia kolumna posłuży do podania rozmiaru katalogu w megabajtach. Po dodaniu listy szczegółowej do okna dialogowego, co uczynimy kierując się podaną niżej procedurą, przydzielimy jej za pomocą CIassWizard zmienną CListCtri. Umieszczenie listy szczegółowej w oknie dialogowym 1. Otwórz szablon okna dialogowego IDD_LISTS_DIALOG w edytorze zasobów. 2. Umieść w nim nowe statyczne pole tekstowe, nazwij je Selected Directory Details, aby pełniło rolę nagłówka listy i porównaj z rysunkiem 6.4. 3. Wybierz pozycję listy szczegółowej z paska narzędziowego Controls i umieść ją posługując się rysunkiem 6.4. Używanie list 131 Rysunek 6.4. Okno dialogowe z dodaną listą szczegółową 4. Nadaj nowej liście identyfikator IDC_SELECTED_DIRS. 5. Po kliknięciu karty Styks z widocznej tam listy View wybierz pozycję Report. Okno dialogowe powinno wyglądać teraz dokładnie jak na rysunku 6.4. Przydzielenie liście szczegółowej zmiennej CListCtrl 1. Jednym ze znanych już sposobów otwórz CIassWizard. 2. Kliknij kartę Member Yariables. 3. Wybierz pozycję CListsDlg spośród obecnych na liście Ciass Name. 4. Następnie zaznacz pozycję IDC_SELECTED_DIRS z listy Control IDs. 5. Kliknij Add Variable. 6. Upewnij się, że lista rozwijana Category wyświetla Control, a Variable Type pokazuje pozycję CListCtri. 7. W polu Member Variable Name wpisz m_lcDirsDetails, a następnie kliknij OK. 8. Kliknij OK, aby zamknąć CIassWizard. PATRZ TAKŻE ^ Więcej informacji o listach szczegółowych (kontrolkach list), ich stylach i wykorzystaniu znajduje się w rozdziale 19. Zapełnianie list Ponieważ listy szczegółowe służą wyświetlaniu i dokonywaniu selekcji różnych ele- mentów, należy tę listę nimi wypełnić. Każdy wpis wprowadzony na listę staje się jej 132 Poznaj Visual C++ 6 elementem. Jakkolwiek mechanizm ten jest podobny dla wszystkich typów list, to podczas dalszej lektury przekonamy się, że każda z list posiada specyficzne właściwości. Umieszczanie elementów na liście kombinowanej Lista kombinowana jest jedyną spośród list, które można wypełniać za pośrednictwem edytora zasobów. Przeprowadzamy ten proces, wykorzystując kartę Data w oknie dialogowym właściwości listy. Przykład widoczny jest na rysunku 6.5. Każda pozycja listy może zostać wprowadzona poprzez wpis w polu Enter Listbox Items. Jeśli zachodzi potrzeba dopisywania kolejnych pozycji w ten sposób, należy wciskać kombinację CtH+Enter po każdym wpisie, ponieważ naciśnięcie samego klawisza Enter spowoduje zamknięcie okna. Praktyka taka nie jest jednakże stosowana. Zwykle listę wypełnia się w trakcie pracy programu, często poprzez funkcję OninitDialog (). Osnowa MFC wywołuje tę funkcję przed otwarciem okna dialogowego. s® t Entet listboK iterns: Generał s Data i Slytes | Exlended Stytes Disdnction *J „, Meri( ' Credit " :' Pass --Fail ::- ,., , , ,, , ^,tf| Rysunek 6.5. Karta Data w oknie właściwości listy kombinowanej Klasą MFC, obsługującą listy kombinowane jest CComboBox. Klasa ta posiada kilka zaimplementowanych funkcji odpowiedzialnych za umieszczanie na liście nowych elementów i usuwanie obecnych. Każdy nowo dodany element opatrzony zostaje numerem, który pozwala później na odwoływanie się do niego. Tabela 6.4. Funkcje klasy CComboBox związane z zawartością listy Nazwa funkcji Opis AddString Dodaje nowy element na końcu listy lub w miejscu wynikającym z ustalonego porządku sortowania DeleteString Usuwa element z listy InsertString Umieszcza element w wybranym punkcie listy ResetContent Usuwa całą zawartość listy Dir Specjalny tryb służący umieszczaniu na liście nazw plików jako elementów listy Używanie list_______________________________________ 133 W przykładowym programie Lists lista kombinowana zostanie wypełniona za pośrednictwem funkcji okna dialogowego OninitDialog () wykazem katalogów pobranym za pomocą pewnych funkcji globalnych systemu Windows. Najpierw musimy jednak zadeklarować nową zmienną PopulateCombo()o typie zwracanej wartości void. Następnie należy zmodyfikować zawartość funkcji poprzez wpisanie kodu zawartego na listingu 6. l, od linii 28. Na koniec będziemy musieli jeszcze wpisać kod zawarty w liniach 34-53 tego samego listingu. Listing 6. l. LST06_1 .CPP — wypełnianie listy kombinowanej 1 BOOL CListsDlg::OninitDialog() 2 ( 3 CDialog::OninitDialog(); 4 5 // Dodaj pozycję "About..." do menu systemowego. 6 // IDM ABOUTBOX musi pozostawać w zasięgu poleceń systemu. 1 ASSERTf(IDM_ABOUTBOX & OxFFFO) == IDM_ABOUTBOX); 8 ASSERT(IDM_ABOUTBOX < OxFOOO); 9 10 CMenu* pSysMenu = GetSystemMenu(FALSE); 11 if (pSysMenu != NULL) 12 ( 13 CString strAboutMenu; 14 strAboutMenu.LoadString(IDS_ABOUTBOX) ; 15 if (!strAboutMenu.IsEmpty() ) 16 { 17 pSysMenu->AppendMenu(MF_SEPARATOR) ; 18 pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu) ; 19 } 20 } O 21 // Ustal ikonę dla okna dialogowego II Odbywa się to automatycznie 22 II gdy okno główne aplikacji // nie jest oknem dialogowym 23 Setlcon(m_hlcon, TRUE); // Set big icon 24 Setlcon(m_hlcon, FALSE); // Set smali icon 25 26 // TODO: Tutaj zainicjuj nową funkcję 27 // ** Inicjalizacja listy kombinowanej dla głównego katalogu 28 PopulateCombo(); Używanie list 135 cymi w buforze szBuffer argumenty w postaci określonych ścieżek dyskowych. Definicja MAX_PATH służy określeniu maksymalnej liczby znaków w nazwie ścieżki. W większości wypadków wynosi ona 260. Po pobraniu nazw ścieżek, zawartość bufora szBuffer przekazywana jest zmiennej m_cbMainDir funkcji AddString (), która odpowiada za umieszczenie tych nazw w polu listy. Ponieważ lista kombinowana w naszym przykładzie posiada aktywną opcję Sort, nowe elementy listy zostaną ułożone w porządku alfabetycznym. Reakcja programu na komunikaty powiadamiające listy kombinowanej Zadaniem listy kombinowanej jest umożliwienie użytkownikowi dokonanie wyboru jednego spośród dostępnych w niej elementów. Gdy przebiega to normalnym trybem, program musi dostać właściwą wiadomość, natychmiast po dokonaniu wyboru, a następnie zareagować na fakt zmiany selekcji w określony sposób. Możliwe to jest poprzez prze-chwytywanie komunikatu powiadamiającego CBN_CHANGE, wysyłanego przez listę do okna dialogowego w momencie zmiany wybranej pozycji. Dodamy zatem funkcję obsługi komunikatu CBN_CHANGE dla listy kombinowanej IDC_MAIN_DIR. Powinna zostać utworzona jednocześnie funkcja składowa OnSelchan-geMainDir (). Będziemy musieli także powołać nową zmienną składową klasy CString: m_strMainDir, która będzie przechowywała nazwę ścieżki. Ta sama zmienna posłuży nam w dalszym toku działań do wypełniania listy drzewiastej oraz pola listy. Teraz jednak wpiszemy kod zawarty na listingu 6.2. Sprawdzanie błędów powstałych podczas wypełniania listy kombinowanej Należy sprawdzać wartości zwracane przez funkcje AddString()oraz insert-StringO. Jeśli podczas wypełniania listy pojawią się błędy, wartości zwracane przyjmą postać CB__ERR lub CB_ERRSPACE. Listing 6.2. LST06_2.CPP - pobieranie zapisu pozycji wybranej z listy kombinowanej 1 void CListsDlg::OnSelchangeMainDir()O 2 ( 3 // TODO: Tutaj umieść kod funkcji obsługi komunikatu 4 // ** Pobranie numeru porządkowego wybranej pozycji 5 int nlndex = m_cbMainDir.GetCurSel();@ 6 7 // ** Sprawdzenie poprawności numeru porządkowego 8 if(nlndex != CB_ERR) 9 { 10 // ** Pobranie zapisu wybranej pozycji oraz przechowanie 11 // ** jej w zmiennej oraz wywołanie funkcji dla 12 // ** wypełnienia pozostałych list 134_____________________________________Poznaj Visual C++ 6 i 29 return TRUE; // Zwraca TRUE dopóki okno listy nie jest ( // aktywne l 30 } 31 ' l 32 void CListsDlg::PopulateCombo() ' 33 ( • 34 TCHAR szBuffer[MAX_PATH]; 35 , @ 36 // ** Pobierz katalog systemu Windows, zwykle C:\Windows 37 // ** i umieść na liście kombinowanej 38 GetWindowsDirectory(szBuffer, MAX_PATH); '. 39 m_cbMainDir.AddString(szBuffer) ; 40 © 41 // ** Odrzuć katalog i pozostaw tylko identyfikator dysku C: 42 // ** i umieść go w oknie listy kombinowanej 43 szBuffer[2]=0; 44 m_cbMainDir.AddString(szBuffer) ; 45 46 // ** Pobierz katalog System, t l zwykle C:\Windows\System 47 // ** i umieść w oknie listy 48 GetSystemDirectory(szBuffer, MAX_PATH); 0 49 m_cbMainDir.Add3tring(szBuffer) ; 50 l 51 // ** Pobierz katalog bieżący i umieść w oknie listy 52 GetCurrentDirectory(MAX_PATH, szBuffer); 53 m_cbMainDir.AddString(szBuffer); © 54 )__________________,___________________________________ , O Wywołanie funkcji PopulateCombo () z funkcji OninitDialog (). © Umieszczenie katalogu Windows w oknie listy. © Odrzucenie katalogu i pozostawienie jedynie identyfikatora dysku oraz umieszczenie go w oknie listy. O Umieszczenie katalogu systemowego w oknie listy. © Umieszczenie bieżącego katalogu w oknie listy. Funkcje GetWindowsDirectoryO, GetSystemDirectory() oraz GetCurrent-DirectoryO wywołane kolejno w liniach 28, 48 i 52 są globalnymi funkcjami zapisują- Używanie list 135 cymi w buforze szBuffer argumenty w postaci określonych ścieżek dyskowych. Definicja MAX_PATH służy określeniu maksymalnej liczby znaków w nazwie ścieżki. W większości wypadków wynosi ona 260. Po pobraniu nazw ścieżek, zawartość bufora szBuffer przekazywana jest zmiennej m_cbMainDir funkcji AddString (), która odpowiada za umieszczenie tych nazw w polu listy. Ponieważ lista kombinowana w naszym przykładzie posiada aktywną opcję Sort, nowe elementy listy zostaną ułożone w porządku alfabetycznym. Reakcja programu na komunikaty powiadamiające listy kombinowanej Zadaniem listy kombinowanej jest umożliwienie użytkownikowi dokonanie wyboru jednego spośród dostępnych w niej elementów. Gdy przebiega to normalnym trybem, program musi dostać właściwą wiadomość, natychmiast po dokonaniu wyboru, a następnie zareagować na fakt zmiany selekcji w określony sposób. Możliwe to jest poprzez prze-chwytywanie komunikatu powiadamiającego CBN_CHANGE, wysyłanego przez listę do okna dialogowego w momencie zmiany wybranej pozycji. Dodamy zatem funkcję obsługi komunikatu CBN_CHANGE dla listy kombinowanej IDC_MAIN_DIR. Powinna zostać utworzona jednocześnie funkcja składowa OnSelChan-geMainDir (). Będziemy musieli także powołać nową zmienną składową klasy CString: m_strMainDir, która będzie przechowywała nazwę ścieżki. Ta sama zmienna posłuży nam w dalszym toku działań do wypełniania listy drzewiastej oraz pola listy. Teraz jednak wpiszemy kod zawarty na listingu 6.2. Sprawdzanie błędów powstałych podczas wypełniania listy kombinowanej Należy sprawdzać wartości zwracane przez funkcje AddString () oraz insert-StringO. Jeśli podczas wypełniania listy pojawią się błędy, wartości zwracane przyjmą postać CB_ERR lub CB_ERRSPACE. Listing 6.2. LST06_2.CPP - pobieranie zapisu pozycji wybranej z listy kombinowanej 1 void CListsDlg::OnSelchangeMainDir()0 2 { 3 // TODO: Tutaj umieść kod funkcji obsługi komunikatu 4 // ** Pobranie numeru porządkowego wybranej pozycji 5 int nlndex = m_cbMainDir.GetCurSel();@ 6 7 // ** Sprawdzenie poprawności numeru porządkowego 8 if(nlndex != CB_ERR) 9 { 10 // ** Pobranie zapisu wybranej pozycji oraz przechowanie 11 // ** jej w zmiennej oraz wywołanie funkcji dla 12 // ** wypełnienia pozostałych list 136_____________________________________Poznaj Visual C++ 6 13 m_cbMainDir.GetLBText(nlndex, m_strMainDir); ® 14 PopulateTree();O 15 } 16 } O Wywołanie następuje, gdy użytkownik zmieni dotychczasowy wybór pozycji z listy. © Program pobiera numer porządkowy wybranej pozycji na liście. © Następuje pobranie i przechowanie nazwy katalogu. O Wywołanie funkcji w celu wypełnienia listy drzewiastej. W momencie otrzymania komunikatu o zmianie wybranej pozycji widoczna w linii 5 funkcja GetCurSel () i zwraca numer porządkowy wybranej pozycji do zmiennej nlndex. Jeśli nie została wybrana natomiast żadna z pozycji listy, co sprawdzone zostaje w linii 8, wtedy zwracana jest specjalna wartość CB_ERR. Jeżeli wartość nlndex zostaje uznana, zostaje przekazana w linii 13 funkcji GetLBText () wraz ze zmienną m_strMainDir. W linii 14 natomiast widzimy wywołanie nowej funkcji Populatetree (), którą zajmiemy się w następnym punkcie. Wypełnianie listy drzewiastej Podczas tego procesu często zachodzi konieczność przechowania informacji o strukturze drzewa, by móc określić, które elementy listy są podrzędne, a które nadrzędne w stosunku do nowej, umieszczanej na liście pozycji, i Klasą biblioteki MFC odpowiedzialną za funkcjonowanie list drzewiastych jest CTree-Ctri. Funkcja ta wyposażona jest w wymienione w tabeli 6.5 funkcje, które służą dodawaniu nowych lub usuwania istniejących elementów listy. Funkcja lnsertltem() występuje w kilku odmianach przeciążonych, których zadaniem jest zaspokojenie potrzeb programu i w zakresie umieszczania wskaźników do obiektów graficznych innych obiektów zewnętrznych. Każda pozycja wprowadzana na listę otrzymuje indywidualny wskaźnik HTREEITEM, który może zostać przekazany funkcji lnsertltem(), co umożliwia tworzenie hierarchii drzewa. Tabela 6.5. Funkcje klasy CTreeCtrl wypełniające listę Nazwa funkcji Opis Insertitem Umieszcza nowe elementy jako nadrzędne lub podrzędne wobec innych, zależnie od parametrów Deleteltem Usuwa pozycję listy DeleteAllItems Usuwa wszystkie pozycje listy Używanie list 137 By wypełnić listę drzewiastą, musimy powołać do życia nową funkcję składową, o nazwie PopulateTree (), o typie zwracanej wartości void. Pliki umieszczone na liście jako jej pozycje, pokazane są w oknie listy w porządku alfabetycznym. Funkcja umieszcza na liście najpierw pozycje organizujące porządek alfabetyczny, a później dodatkową 27 pozycję dla plików, których nazwy zaczynają się od znaku innego niż litera. Następnie ta sama funkcja, posługując się innymi obecnymi w systemie Windows funkcjami, pobiera nazwy plików umieszczonych w danym katalogu i według klucza alfabetycznego wstawia te nazwy we właściwe miejsca listy. Kiedy mamy gotową funkcję PopulateTree (), musimy dopisać kod umieszczony na listingu 6.3. Listing 6.3. LST06_3.CPP - wypełnianie listy drzewiastej 1 void CListsDlg::PopulateTree() 2 { 3 // ** Usuń wszystkie pozycje z listy 4 m treeFiles.DeleteAllItems(); 5 // ** Umieść tablicę HTREEITEMS 6 HTREEITEM hLetter[27]; O 7 // ** Umieść węzły początkowe dla liter A-Z S for(int nChar = 'A'; nChar <= 'Z'; nChar++) 9 hLetter[nChar - 'A'] = m_treeFiles.InsertItem((TCHAR*)SnChar) ; 10 11 // ** Umieść węzeł "Other" 12 hLetter[26] = iri_treeFiles . Insertitem ("Other") ; 13 14 HANDLE hFind; 15 WIN32_FIND_DATA dataFind; 16 BOOL bMoreFiles = TRUE; 17 CString strFile; 18 19 // ** Znajdź pierwszy plik w katalogu 20 hFind = FindFirstFile(m_strMainDir + "\\*.*", &dataFind); @ 21 22 // ** Kontynuuj przeszukiwanie katalogu, aż do odnalezienia II ** wszystkich plików 23 while(hFind != INVALID_HANDLE_VALUE && bMoreFiles == TRUE) ® 24 { 25 // ** Sprawdź, czy znaleziony plik nie jest katalogiem 26 if(dataFind.dwFileAttributes == FILE_ATTRIBUTE_ARCHIVE) 27 { 28 // ** Pobierz pierwszą literę nazwy pliku 138 Poznaj Visual C++ 6 29 int nChar = dataFind.cFileName[0]; 30 // ** Dokonaj konwersji małych liter na wielkie 31 if(islower(nChar)) 32 nChar -= 32; 33 // ** Jeśli nazwa pliku zaczyna się od litery, 34 // ** wyodrębnij 'A' by ustalić jej miejsce w tablicy 35 // ** hLetter 36 if(isalpha(nChar)) 37 nChar -= 'A' ; 38 else 39 nChar =26; 40 // ** Umieść nazwę pliku na liście 41 m_treeFiles.Insertitem(dataFind.cFileName, O hLetter[nChar]) ; 42 } 43 // ** Znajdź kolejny plik w katalogu 44 bMoreFiles = FindNextFile(hFind, SdataFind); 45 } 46 // ** Zakończ poszukiwanie 47 FindClose(hFind); 48 l O Umieszczenie węzłów początkowych dla wszystkich liter alfabetu. @ Odszukanie pliku w katalogu zapisanym w m strMainDir. © Iteracja wszystkich plików znalezionych w katalogu. O Umieszczenie nazwy pliku na liście. W linii 4 powyższego listingu następuje wywołanie funkcji DeleteAllltems (), usuwającej wszystkie pozycje listy. Jest to konieczne, ponieważ wypełnianie listy odbywa się za każdym razem, gdy zmienimy katalog bieżący. Pętla for umieszczona w liniach 8 i 9 wywołuje funkcję lnsertltem() podając jako parametr kolejne litery alfabetu. Ponieważ nie ma innych zdefiniowanych parametrów, litery te automatycznie dołączane są do listy. Po każdym wywołaniu funkcji insertitemO zwrócony wskaźnik HTREEITEM zostaje zapisany w tablicy hLetter. Dlatego pozycja tablicy hLetter [0] przechowuje wskaźnik litery A, hLetter [l] litery B itd. Węzeł początkowy Others umieszczony w linii 12, przechowywać będzie nazwy plików nie zaczynające się od liter. Pobieranie nazw plików rozpoczyna się od wywołania w linii 20 funkcji FindFirst-itemO. Jako argument podana jest zmienna m_strMainDir + "\\*.*", co służy przeszukaniu wybranego katalogu. Następnie w linii 44 wywołana zostaje funkcja Find-Nextltem(), której zadaniem jest pobranie nazw kolejnych plików. Zwraca ona PALSE Używanie list 139 gdy już pobrane zostaną nazwy wszystkich plików, a to powoduje zatrzymanie działania pętli. Obie funkcje Find zapisują pobrane nazwy w zmiennej dataFind, która należy do struktury WIN32_DATA_FIND. Struktura owa przechowuje informacje o wszystkich odnotowanych pozycjach. Poprzez linię 26 sprawdzone zostają atrybuty wszystkich odnalezionych plików. Ma to na celu stwierdzenie, że mamy do czynienia z plikiem, a nie podkatalogiem. W linii 29 pobrany zostaje pierwszy znak nazwy pliku i jeśli to konieczne, w linii 32 zamieniony z litery małej na wielką. Linia 36 kontroluje, czy nazwa pliku rozpoczyna się od znaku alfabetycznego. Jeżeli tak jest, wartość danego znaku skonwertowana zostaje na numer indeksu w tablicy hLetter. Wywołana natomiast w linii 41 funkcja lnsertltem() powoduje przypisanie nazwy pliku do odpowiedniego węzła. Po skompilowaniu i uruchomieniu aplikacji jej okno dialogowe powinno wyglądać jak na rysunku 6.6. Widać na nim rozwiniętą listę katalogów wraz z zawartością pierwszego z nich. Rysunek 6.6. Widok okna dialogowego Lists z funkcjonującą listą drzewiastą i listą rozwijaną Wypełnianie pola listy Wypełnianie pola listy przebiega praktycznie identycznie z wypełnianiem listy kombinowanej. Najważniejszymi różnicami pomiędzy tymi dwoma typami obiektów jest sposób wyświetlania listy oraz to, iż pola listy umożliwiają dokonywanie wyboru kilku pozycji jednocześnie. Klasą MFC, która odpowiada za obsługę pól listy jest CListBox. Funkcje w niej zawarte, służące umieszczaniu nowych i usuwaniu istniejących pozycji listy, są identyczne z funkcjami wymienionymi w tabeli 6.4, znajdującej się nieco wcześniej w tym rozdziale. W przykładzie Lists ten rodzaj obiektu wykorzystamy do wyświetlania podkatalogów zawartych w katalogu, który jest podany w polu edycji listy kombinowanej. Zaczniemy od 140_____________________________________Poznaj Visual C++ 6 dodania nowej funkcji składowej, o nazwie PopulateListBox()i typie zwracanej wartości void. Następnie poddamy edycji zapis funkcji OnSelChangeMainDir (), która wywoływana będzie po wywołaniu PopulateTree (). Wpiszemy teraz kod zawarty na listingu 6.4, by spowodować wypełnienie pola listy nazwami podkatalogów. Listing 6.4. LST06_4.CPP - wypełnianie pola listy 1 void CListsDlg::PopulateListBox() 2 { 3 // ** Usunięcie wszystkich dotychczasowych elementów listy 4 m_lbSubDirs.ResetContent() ;O 5 6 HANDLE hFind; 7 WIN32_FIND_DATA dataFind; 8 BOOL bMoreFiles = TRUE; 9 10 //• ** Odnalezienie pierwszego pliku w głównym katalogu 11 hFind = FindFirstFile(m_strMainDir + "\\*.*", SdataFind); @ 12 13 // ** Pętla wyszukująca wszystkie pozostałe pliki 14 while(hFind != INVALID_HANDLE_VALUE && © bMoreFiles == TRUE) 15 { 16 // ** Sprawdzenie obecności podkatalogów 17 if(dataFind.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) 18 { 19 // ** Umieszczenie w polu listy nazw podkatalogów 20 II ** z pominięciem pozycji "." oraz ".." 21 if(strcmp(dataFind.cFileName, ".")) 0 22 iffstrcmp(dataFind.cFileName, "..")) O 23 m_lbSubDirs.Add3tring(dataFind.cFiieName) ; 24 } 25 // ** Odnalezienie następnego pliku w katalogu głównym 26 bMoreFiles = FindNextFile(hFind, SdataFind); 27 } 28 // ** Zakończenie pobierania nazw plików 29 FindClose(hFind); 30 ) O Umieszczenie węzłów początkowych dla wszystkich liter alfabetu. Używanie list 141 O Umieszczenie węzłów początkowych dla wszystkich liter alfabetu. © Odszukanie pliku w katalogu zapisanym w m_strMainDir. © Iteracja wszystkich plików znalezionych w katalogu. O Umieszczenie nazwy pliku na liście. Widoczne w linii 4 wywołanie funkcji ResetContent () powoduje usunięcie dotychczasowej zawartości listy. Jest to niezbędne, ponieważ pole listy jest wypełniane za każdym razem, gdy dokonamy zmiany katalogu wybranego za pośrednictwem listy kombinowanej. Aby przypomnieć sobie szczegóły dotyczące funkcji FindFirstitemO oraz FindNext-ltem(), musimy się nieco cofnąć, do punktu „Wypełnianie listy drzewiastej", w tym rozdziale. Atrybuty każdej odnalezionej pozycji są sprawdzane w linii 17 w celu upewnienia się, iż dana pozycja jest podkatalogiem. Dwa wywołania funkcji strcmpOw liniach 21 i 22 powodują zignorowanie pozycji . oraz .. . Funkcji AddStringO wywołanej w linii 23 podana zostaje nazwa pozycji (w tym wypadku jest to nazwa podkatalogu), która jest wyświetlona w polu listy. Jeśli skompilujemy i uruchomimy aplikację, stwierdzimy, że wybranie katalogu z listy kombinowanej spowoduje jego rozwinięcie listy drzewiastej oraz pola listy. Reakcja programu na komunikaty powiadamiające pola listy Działania, które użytkownik wykonuje w obrębie pola listy, powodują wysyłanie do okna dialogowego aplikacji komunikatów powiadamiających. Komunikat LBN_SELCHANGE zostaje wysłany, gdy użytkownik dokona zmiany wyboru pozycji z listy. Sposób traktowania poczynionego przez użytkownika wyboru zależy od tego, czy lista pozwala wybierać kilka pozycji jednocześnie oraz od funkcji składowych klasy CListBox. Pole listy użyte w przykładzie Lists zezwala na wybór wielopozycyjny. Funkcja obsługi komunikatu tworzy listę wybranych katalogów i zapisuje ją w nowej zmiennej składowej klasy cstringList, m_strList. Musimy zatem dodać tę zmienną, korzystając z okna dialogowego Add Member Variable. Dodamy także funkcję obsługi komunikatu LBN_SELCHANGE dla pola listy IDC_SUB_DIRS. W ten sposób utworzona również zostanie funkcja składowa OnSelChan-geSubDirs (). Funkcja ta określać będzie, ile pozycji wybrał użytkownik, a także będzie pobierać ich nazwy i zapisywać je w zmiennej m_strList. Na koniec ta sama funkcja wywoływać będzie inną, populateListControl (), która z kolei użyje zawartości zmiennej m_strList do wyświetlenia szczegółowego wykazu pozycji na liście szczegółowej. Dopiszemy teraz kod z listingu 6.5. Listing 6.5. LST06_5.CPP - reakcja aplikacji na komunikat powiadamiający pola listy 1 void CListsDlg::OnSelchangeSubDirs() O ' 2 { 3 // TODO: Tutaj wpisz kod funkcji obsługi komunikatu 4 // ** Sprawdzenie liczby wybranych pozycji 5 int nSelCount = m_lbSubDirs.GetSelCount(); @ 6 7 // ** Wyczyszczenie zawartości m_strList 8 m_strList.RemoveAll() ; 9 if(nSelCount) 10 ( > 11 CString str; 12 // ** Utworzenie tablicy typu int przechowującej indeksy 13 // ** oraz jej inicjalizacja z indeksami wybranych pozycji 14 LPINT pitems = new int[nSelCount]; l 15 m_lbSubDirs.GetSelItems(nSelCount, pitems); 16 17 for(int i = 0; i < nSelCount; i++) @ 18 ( 19 // ** Pobranie nazw wybranych pozycji 20 // ** oraz zapisanie ich w zmiennej 21 m_lbSubDirs.GetText(pitems[i], str); O 22 m_strList.AddTail(str) ; O 23 ) ) 24 // ** Uporządkowanie zawartości tablicy 25 delete [] pitems; 26 } 27 // ** Wypełnienie listy szczegółowej 28 PopulateListControl(); © 29 } O Funkcja jest wywołana, gdy użytkownik dokona zmiany wyboru. © Pobranie liczby wybranych pozycji. © Wypełnienie tablicy. O Zapisanie nazwy wybranego katalogu w zmiennej m_strList. © Wywołanie funkcji wypełniającej listę szczegółową. Używanie list 143 W linii 14 ustanowiona zostaje tablica wartości i n t. Tablica ta zawierać będzie indeksy wybranych z listy pozycji i z tego powodu jej rozmiar musi być równy nSelCount. Zmienna pitems jest wskaźnikiem wartości w tej tablicy i zainicjowana zostaje ze wskazaniem jej początku. W linii 25 tablica zostaje usunięta poprzez użycie delete [ ], kiedy nie jest dłużej potrzebna. W linii 15 funkcja GetSelltems () pobiera listę wybranych elementów. Lista owa posiada indeks początkowy 0. Drugim parametrem tej funkcji jest wskaźnik tablicy i n t, którą funkcja wypełnia. Pętla for rozpoczęta w linii 17 dokonuje iteracji dla wszystkich wybranych elementów. Za każdym przebiegiem wywoływana jest funkcja GetText (), pobierająca kolejny indeks z tablicy (pitems [l]) i zapisując nazwę w zmiennej str. Następnie w linii 22 nazwa ta jest dołączana na końcu zawartości m_strList. Ponieważ za każdym razem, kiedy lista wybranych podkatalogów ulega zmianie, elementy wykazane na liście szczegółowej są wywoływane w linii 28, na końcu funkcji PopulateListControl (). Wypełnianie listy szczegółowej Wypełnianie listy szczegółowej przebiega nieco inaczej niż w przypadku pozostałych typów list, opisanych w tym rozdziale. Zależy ono od sposobu przedstawienia danych na tej liście. Istnieją cztery sposoby wypełniania: Icon, Smali Icon, List oraz Report, o czym mówiliśmy wcześniej w tym rozdziale. Prawdopodobnie najczęściej używanym (i najbardziej użytecznym) trybem jest Report. W trybie tym lista wyświetla dane dotyczących plików pogrupowane w kolumny. Na przykład, w Eksploratorze Windows tryb Report powoduje użycie czterech kolumn reprezentujących nazwę pliku, jego rozmiar oraz typ, a także datę ostatniej modyfikacji. Kategorie danych określone są w nagłówkach kolumn. Klasą MFC zawierającą mechanizmy obsługi list szczegółowych jest CListCtri. W tabeli 6.6 wymienione są funkcje składowe tejże klasy, którymi posługiwać się możemy w celu dodawania nowych i usuwania elementów listy. Tabela 6.6. Funkcje klasy CListCtri Nazwa funkcji Opis insertColumn Umieszcza nową kolumnę we wskazanej pozycji DeleteColumn Usuwa kolumnę z listy Insertitem Umieszcza na liście nową pozycję Deleteltem Usuwa z listy istniejącą pozycję DeleteAllItems Usuwa z listy wszystkie pozycje SetltemText Wstawia nagłówek podelementu 144_____________________________________Poznaj Visual C++ 6 , Przykładowy program Lists wykorzystywać będzie listę szczegółową, pracującą właśnie w trybie Report. Danymi wyświetlanymi w jej kolumnach będą: nazwa katalogu, liczba plików zawartych wewnątrz oraz całkowita objętość plików. Pierwszą konieczną czynnością jest umieszczenie kolumn. Ponieważ kolumny nie ulegają zmianom, możemy ' je dodać wewnątrz funkcji ininitDialogO. Aktualne dane dotyczące tych pozycji zostaną uwidocznione na liście poprzez funkcję PopulateListCtrl (), jak zostało to opisane w poprzednim punkcie. Teraz zajmiemy się implementacją tej funkcji, a jako typ zwracanej wartości obierzemy void. i Dopiszemy teraz fragment kodu umieszczony w liniach od 8 do 11 w listingu 6.6. Listing 6.6. LST06_6.CPP - mixcjalizacja pola listy kolumn —————————————————————————————————————————————— ———— ^ 1 BOOL CListsDlg::OnInitDialog() 2 { 3 4 ... l 5 // TODO: Inicjalizacja pola listy wyświetlającego katalog główny 7 PopulateCombo() ; 8 // ** Inicjalizacja kolumn listy szczegółowej 9 m_lcDirDetails.InsertColumn(0, "Directory", LVCFMT_LEFT, 70); O 10 m_lcDirDetails.InsertColumn(l, "Files", LVCFMT_RIGHT, 50); O 11 m_lcDirDetails.InsertColumn(2, "Size KB", LVCFMT_RIGHT, 60); O 12 13 return TRUE; // zwraca TRUE jeśli nie ustawisz focusu // na kontrolce ) 14 > O Inicjalizacja listy szczegółowej, zorganizowanej w trzech kolumnach. Pierwszy parametr funkcji insertColumn ojest numerem kolumny. Numeracja rozpoczyna się od zera. Możemy określić położenie poziome kolumny. Do wyboru mamy trzy możliwości: LVCFMT_LEFT (po lewej), LVCFMT_CENTER (na środku) oraz * LVCFMT_RIGHT (po prawej). Ostatnim parametrem tej funkcji jest początkowa szerokość kolumn mierzona w pikselach. Wymiar ten może zostać zmieniony przez użytkownika, za pomocą myszy podczas pracy programu. Dopiszemy teraz kod z listingu 6.7, by wypełnić listę szczegółową. > Używanie list 145 Listing 6.7. LST06_7.CPP - wypełnianie listy szczegółowej 1 void CListsDlg::PopulateListControl() 2 { 3 // ** Usunięcie dotychczasowej zawartości listy 4 ni_lcDirDetails.DeleteAllItems(); 5 6 POSITION pos; 7 // ** Iteracja katalogów wybranych Q II ** w polu listy 9 for(pos = m_strList.GetHeadPosition(); pos != NULL;) 10 ( 11 int nitem; 12 HANDLE hFind; 13 WIN32_FIND_DATA dataFind; 14 BOOL bMoreFiles = TRUE; 15 CString str; 16 CString strFind; 17 18 str = m_strList.GetAt(pos); 19 // ** Dodaj wiersz do listy (kolumna 0) 20 nitem = m_lcDirDetails.Insertltem(0, str); 21 22 strFind = m_strMainDir + "\\" + str + "\\*.*"; 23 hFind = FindFirstFile(strFind, &dataFind); 24 25 int nFileCount = 0; 26 double nFileSize = 0; 27 28 // ** Pobranie nazw wszystkich plików w katalogu 29 II ** liczby plików oraz całkowitej objętości 30 while(hFind != INVALID_HANDLE_VALUE && O bMoreFiles == TRUE) 31 ( 32 if(dataFind.dwFileAttributes === FILE_ATTRIBUTE_ARCHIVE) 33 { 34 nFileCount++; 35 nFileSize += (dataFind.nFileSizeHigh * @ MAXDWORD) 36 + dataFind.nFileSizeLow; 37 } 38 bMoreFiles = FindNextFile(hFind, &dataFind) ; 39 } 146 Poznaj Visual C++ 6 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 // ** Zakończenie obsługi plików FindClose(hFind) ; // ** Formatowanie informacji o liczbie plików II ** i umieszczenie ich w kolumnie l str.Format("%ld", nFileCount); ® m_lcDirDetails.Set!temText(nitem, l, str); // ** Formatowanie informacji o rozmiarach plików f l ** i umieszczenie ich w kolumnie 2 str.Format("%-1.2f", nFileSize / 1024.0); O m_lcDirDetails.Set!temText(nitem, 2, str); m_strList.GetNext(pos) ; } O Iteracja plików ze wszystkich podkatalogów. © Inkrementacja liczby plików i ogólnej objętości. © Umieszczenie szczegółów w kolumnie podającej liczbę plików. O Umieszczenie danych w kolumnie podającej objętość plików. Funkcja DeleteAllltems () występująca w linii 4 usuwa całą dotychczasową zawartość listy, ale nie usuwa kolumn. Pętla for rozpoczęta w linii 9 dokonuje iteracji poprzez zmienną m_strList, która utworzona została w funkcji OnSelChangeSubDirs (). Każda pozycja typu cstring obecna na liście reprezentuje wybrany katalog. W linii 20 nazwa katalogu zostaje przekazana funkcji lnsertltem() i umieszczona w kolumnie 0. Funkcja ta zwraca wartość ostatnio dołączonej pozycji, poczynając od zera oraz zapisuje ją w nitem. Ta wartość z kolei przekazywana jest funkcji SetltemText (), wywoływanej w liniach 46 i 51, która umieszcza odpowiednie informacje w pozostałych dwóch kolumnach. Dla przypomnienia szczegółów dotyczących funkcji FindFirstItemO oraz Find-Nextltem() możemy cofnąć się do punktu mówiącego o wypełnianiu list drzewiastych w tym samym rozdziale. Skompilujemy i uruchomimy zatem program Lists. Aby sprawdzić poprawność jego działania, zmienimy wybór katalogów. Spróbujemy dokonać wyboru wielopozycyjnego w polu listy. Okno programu powinno wyglądać podobnie do tego na rysunku 6.7. Używanie list 147 g "łain Difectóły C:WIMDOWS ^J|i, . .:::•:•:. ...^vi^. Files SubDitectories 'B^:,,,!' •-•s ^. IFORMS AJ.:; ; : AODLFNPR.REG „ .JHELP^^^^^^^^""' (i, : !. ACCSTAT.EXE ^ i^l^BBBBIHHr-1 ^ ', i ASP12HLP.SYS ,:|i ^ MEDIA .::: ł : i AWEMAN.OLL -J uB^lISHHHBBBHBHLiJ: |J i ;-AWEMAN32.DLL B : i- - ACCESSOR GRP Selected Directory Delails li ; ; Aclive Setup Log.BAK Dfectoły Files SizeKB , '• Ac>ive SeSup Log.tKt OPTIONS 12 163.31 l IS-B JA','A 1 0.17 , ii B C „„ A IMAGE B 7.79 B Ig D ^r COMMAND 33 1065.77 : OK l Rysunek 6.7. Ostateczny wygląd okna programu Lists PATRZ TAKŻE 4 Więcej szczegółów na temat stylów list szczegółowych znajduje się w rozdziale 19. Rozdział 7 Stosowanie wskaźników postępu, pasków przewijania, suwaków oraz obiektów pobierających datę i godzinę Użycie wskaźnika postępu do wyświetlania stopnia wykonania zadania Zastosowanie suwaków oraz pasków przewijania do ustalania pozycji wewnątrz obszaru Używanie obiektów pobierających dane czasowe Obiekty zorientowane na zakres wartości Programy często muszą umożliwiać użytkownikowi określenie pewnego zakresu spośród dostępnych wartości, zdefiniować punkty graniczne tego zakresu, a także wskazywać stopień wykonania zadania zleconego przez użytkownika. Zakresy, które określać ma użytkownik, mogą reprezentować różne rodzaje jednostek i danych, takie jak jednostki numeryczne, data, godzina, a także jednostki specyficzne dla danej aplikacji, jak metry lub mile. Visual C++ 6 dostarcza nam szerokiego wyboru obiektów tego rodzaju, które możemy umieszczać w aplikacjach za pomocą edytora zasobów i dostępnego w nim paska narzędziowego Controls. Biblioteka MFC zapewnia dostęp do mechanizmów umożliwiających aplikacji podejmowanie interakcji z użytkownikiem. Rozdział niniejszy mówi o tego rodzaju obiektach sterujących i objaśni sposoby integracji tych mechanizmów z aplikacją. Użycie wskaźnika postępu Wskaźnik postępu jest zwykle obiektem umożliwiającym użytkownikowi kontrolę stopnia wykonania zleconego zadania. Ma on postać okna, stopniowo wypełnianego przez niebieski pasek lub ciąg prostokątów. Jego przykładowy wygląd widzimy na rysunku 7.1. Obiekt tego typu możemy wykorzystać również w innych dziedzinach. Możemy się nim posłużyć w celu reprezentowania wartości z określonego zakresu. Przykładowo, rysunek 150 Poznaj Visual C++ 6 7.2 przedstawia sposób wykorzystania wskaźników postępu jako wskaźników korektora graficznego, spotykanego w sprzęcie Hi-Fi. OK Caneel Rysunek 7.1. Typowe zastosowanie wskaźnika postępu do identyfikacji stopnia wykonania zadania Caneet l. Rysunek 7.2. Zespół wskaźników postępu wykorzystanych do odwzorowania wybranych wartości w danym zakresie Umieszczenie wskaźnika postępu w oknie dialogowym Wskaźnik postępu możemy umieścić w oknie dialogowym w sposób analogiczny do wszelkich innych obiektów sterujących: przeciągając go z paska narzędziowego Controls, wywoływanego podczas pracy z edytorem zasobów i ustalając jego pozycję wewnątrz okna. Po umieszczeniu wskaźnika, możemy także zmieniać jego wymiary. Na rysunku 7.3 ikona wskaźnika postępu jest wskazana myszą i widoczny jest również „dymek" opisujący znaczenie ikony. Kopiowanie i wklejanie obiektów sterujących Obiekty sterujące każdego typu mogą być kopiowane do schowka, a następnie wklejane z niego do szablonu okna. Do skopiowania używa się skrótu klawiaturowego CtrI+C, do wklejania natomiast Ctrl+V. Wklejona kopia obiektu zachowuje wymiary i cechy pierwowzoru. Stosowanie wskaźników postępu, pasków przewijania, suwaków... 151 Rysunek 7.3. Umieszczenie wskaźnika postępu w oknie dialogowym za pomocą edytora zasobów Na rysunku tym widzimy również okno właściwości Progress Properties z otwartą kartą Styles. Okno to możemy wywołać zaznaczając wskaźnik umieszczony na szablonie, a następnie naciskając klawisze Alt+Enter lub też wybierając pozycję Properties z otwartego kliknięciem prawego klawisza myszy menu skrótów. Okno właściwości na karcie Generał umożliwia nam zmianę identyfikatora ID przypisanego wskaźnikowi. Powinniśmy zmienić ten identyfikator na jak najjaśniej opisujący zastosowanie wskaźnika oraz ustawić pozostałe jego parametry według potrzeb. Karta Styles pozwala na dokonanie zmian wizualnych cech wskaźnika poprzez zaznaczenie lub usunięcie zaznaczenia następujących opcji: • Border. Jeśli zaznaczymy tę opcję, wokół wskaźnika wyświetlana będzie wąska czarna ramka. • Vertical. Zaznaczenie tej opcji spowoduje zmianę orientacji wskaźnika, z poziomej na pionową. • Smooth. Jeżeli wybierzemy tę pozycję, wskaźnik będzie miał formę paska, zamiast oddzielnych prostokątów. Domyślne ustawienia stylu wskaźnika postępu Jeśli nie zostaną dokonane zmiany opcji stylu wskaźnika postępu, domyślnie ustalonymi będą opcje: Border, Vertical oraz Smooth. Przykład pokazany na rysunku 7.1 prezentuje sposób wykorzystania wskaźnika postępu w aplikacji utworzonej za pomocą AppWizard, w oparciu o architekturę okna dialogo- 152_____________________________________Poznaj Visual C++ 6 wego. Wartość wskazywana jest inkrementowana za każdym razem, gdy użytkownik kliknie przycisk Step It. Chcąc wykonać ten przykład, musimy zacząć od wywołania AppWizard i utworzenia w nim aplikacji o budowie okna dialogowego, a następnie umieścić w nim wskaźnik postępu, jak widać na rysunku 7.13. Wskaźnik ten w naszym przykładzie otrzyma identyfikator IDC_MY_PROGRESS, a przycisk widniejący po jego lewej stronie IDC_STEPIT. Kolejne punkty pokażą jak połączyć klasę MFC ze wskaźnikiem i jak odświeżać jego wskazania. PATRZ TAKŻE • Więcej szczegółów na temat edycji okien dialogowych i właściwościach obiektów sterujących znajduje się w rozdziale 3. Przypisanie zmiennej do wskaźnika postępu Przypisania zmiennej do wskaźnika postępu dokonać możemy za pomocą CIassWi-zard. Wskaźniki tego rodzaju są obsługiwane przez klasę CProgressCtrI. Przypisanie zmiennej składowej do wskaźnika postępu 1. W edytorze zasobów zaznacz wskaźnik, któremu będziesz przypisywać zmienną. 2. Naciśnij Ctri+W lub wybierz z menu View pozycję uruchamiającą CIassWizard. W jego otwartym oknie zaznacz kartę Member Yariables. Upewnij się, że została wybrana właściwa klasa; w tym przypadku CProgressDIg. 3. Poprzez dwukrotne kliknięcie pozycji IDCJMY PROGRESS lub kliknięcie przycisku Add Variable otwórz okno dialogowe Add Member Variable. 4. Jako nazwę nowej zmiennej w polu Member Variable Name wpisz: m_MyProgress. 5. Zauważ, że lista Category wyświetla pozycję Control, natomiast Variable Type wskazuje CProgressCtri. Są to jedyne dostępne opcje w przypadku wskaźnika postępu. 6. Kuknij OK w celu zatwierdzenia nowej zmiennej i zamknięcia okna Add Member Variable. Nowa zmienna powinna być teraz widoczna na liście Control IDs. 7. Kliknij OK, by zamknąć CIassWizard i dodać nową zmienną do wybranej klasy. Gdy posiadamy już zmienną przypisaną do wskaźnika, manipulując jej zawartością możemy powodować zmiany w jego wskazaniach. Jest to opisane w następnym punkcie. Wskaźnik postępu jest nowym rodzajem obiektu sterującego Ponieważ wskaźnik postępu to jeden z nowych rodzajów obiektów sterujących, użytkownik musi pracować w systemie Windows 95/NT 3.51 (lub wersji późniejszej), aby móc stosować te obiekty. rM i ru. i Mrv.c • Bardziej szczegółowe wyjaśnienie przypisywania zmiennych składowych znajduje się w rozdziale 10. Manipulowanie i zmiany wskazań wskaźnika postępu Operacji tych dokonywać możemy wywołując metody klasy cprogressCtrl, wykorzystującej nową zmienną. Wskaźnik postępu operuje na pewnym zakresie wartości całkowitych, które reprezentują pusty wskaźnik (0% wykonania) i na przeciwległym końcu, wskaźnik wypełniony (100% wykonania). Wewnątrz tego zakresu znajdują się wartości reprezentujące aktualny stan wykonania zadania, co determinuje stopień wypełnienia wskaźnika. Możemy również określić wartość kroku, o którą następować będzie inkrementacja wskazań w momentach wywoływania funkcji składowej s tęp 11 (). Używanie 32-bitowych zakresów wartości we wskaźnikach postępu Funkcja SetRange () ograniczona jest do posługiwania się tylko 16-bitowymi wartościami. Oznacza to, iż wartości przez nią wykorzystywane mieszczą się w zakresie od -32 768 do 32 767. Jeśli zadanie wymaga większego zakresu tych wartości, użyć należy funkcji SetRange32 (), posługującej się wartościami 32-bitowymi, czyli od -2 147 483 648 do 2 147 483 647. Ustalanie zakresu wartości dla wskaźnika postępu Powinniśmy teraz ustalić zakres wartości, który wykorzystywany będzie przez wskaźnik. Możemy to osiągnąć wywołując funkcję składową SetRange (), podając jej dwa parametry w postaci wartości całkowitych, które określać będą wartość najniższą i najwyższą. Oczywiście wartości te powinny korespondować z zadaniem, którego stopień wykonania będzie identyfikował wskaźnik. Przykładowo, gdy dokonujemy obliczeń liczb pierwszych z zakresu od 3000 do 7000, zakres wartości dla wskaźnika postępu powinien być ograniczony tymi właśnie dwoma liczbami. Zakres ten określać będziemy zazwyczaj w momencie inicjalizacji wskaźnika, jednakże możemy go zmienić w każdym momencie. Najlepszym miejscem na inicjalizację tego obiektu jest końcówka zapisu funkcji okna dialogowego OninitDialog (). W przykładowym programie Progress wartościami granicznymi dla wskaźnika będą O i 10. Liczba O reprezentować będzie całkowicie pusty wskaźnik, 10 natomiast wypełniony. Poprzez kliknięcie karty ClassView, a następnie rozwinięcie kolejnych pozycji, uzyskamy dostęp do wszystkich klas wykorzystanych w programie, ich funkcji składowych oraz zmiennych. Skoro aplikację zbudowaliśmy na bazie okna dialogowego i nazwaliśmy ją Progress, w panelu ClassView powinna być obecna klasa cprogressDlg, która służy do obsługi głównego okna dialogowego aplikacji. Powinniśmy widzieć tam również funkcję składową tej klasy, OninitDialog (), której zapis ujrzymy w oknie edytora po dwukrot- 154 _____ ____ __ Poznaj Visual C++ 6 nym jej kliknięciu. Inicjalizacji zakresu wartości wskaźnika dokonać możemy wpisując poniższą linię na samym końcu zapisu funkcji, tuż przed rozkazem powrotu. Linia, którą musimy wpisać, wygląda następująco: m_MyProgress.SetRange(O, 10); Od tego momentu zakres wartości wskaźnika zawierał się będzie pomiędzy O a 10. Mamy do dyspozycji jeszcze jedną funkcję GetRange (), która pobiera dwie wartości poprzez odwołania i ustawia je jako wartość minimalną i maksymalną. Możemy wykorzy- stywać tę funkcję do dokonywania zmian aktualnego zakresu wartości. PATRZ TAKŻE • Więcej informacji o inicjowaniu okien dialogowych znajduje się w rozdziale 6. Przykładowa aplikacja Fire Przykładowa aplikacja Fire demonstruje współdziałanie wskaźnika postępu z suwakiem. Aplikację można pobrać z dysku MSDN CD bądź ściągnąć ze strony WWW pod adresem:www.microsoft.com/msdn. Ustalanie pozycji wskaźnika Gdy ustaliliśmy już zakres wartości wskaźnika, możemy określić również wyświetlaną wartość poprzez wywołanie funkcji składowej SetPos O . Jej użycie spowoduje ustawienie wartości identyfikowanej aktualnie przez wskaźnik na wartość równą przekazanej jako argument i odświeżenie wskazań wizualnych. Jeśli przekazana do SetPos ((wartość będzie większa od maksymalnej, wskaźnik zostanie narysowany jako całkowicie wypełniony, gdy zaś mniejsza, wskaźnik nie będzie wskazywał nic. W programie Progress możemy posłużyć się funkcją SetPos () do ustalenia wartości początkowej na 0. Uczynimy to poprzez wpisanie kolejnej linii, tuż pod poprzednią. Wpi- szemy: m_MyProgress.SetPos(0) ; Taka deklaracja nie jest co prawda konieczna, gdyż wskaźnik postępu automatycznie przyjmuje jako wartość początkową minimalną wartość określonego zakresu. Możemy jednak spowodować ustalenie wskazania początkowego na 50%, co wymaga zmiany ostatnio wpisanej linii na: m_MyProgress.SetPos(5) ; Zamiast poprzedniej metody, określającej wskazanie początkowe jako wartość absolutną wewnątrz danego zakresu, możemy wskazanie ustalić za pomocą wartości relatywnej, używając w tym celu funkcji OffsetPos (). Gdy podamy tej funkcji jako parametr Stosowanie wskaźników postępu, pasków przewijania, suwaków... 155 pewną wartość, zostanie ona dodana do obecnie wskazywanej, a wskaźnik zostanie odry-sowany tak, by odzwierciedlić aktualną wartość. Ustalanie i używanie wartości kroku Krok inkrementacji wartości wskazań może być ustalony automatycznie. Możemy jednak wpłynąć na wartość, o którą będzie się przesuwał wskaźnik postępu po każdym sygnale do odświeżenia wskazań. Funkcją, która nam to umożliwi, jest SetStep (). Jako argument możemy podać jej wartość inkrementacji. Po jej zaimplementowaniu będziemy mogli powodować zmiany wskazań poprzez wywołania funkcji Steplt(), bez żadnych parametrów. W programie Progress ustalmy wartość inkrementacji na l, umieszczając jeszcze jedną linię kodu na końcu OninitDialog (); m_MyProgress.SetStep(l) ; Teraz możemy już dodać funkcję obsługi dla przycisku Step 11. Po jego dwukrotnym kliknięciu w edytorze zasobów poproszeni zostaniemy o zatwierdzenie nazwy nowej funkcji OnStepit (), a gdy go dokonamy, dopisany zostanie kod inicjujący nową funkcję: void CProgreeDlg::OnStepIt() ( m_MyProgress.Step!t() ; } Po skompilowaniu i uruchomieniu programu za pomocą przycisku Step it będziemy zwiększać wartość wyświetlaną na wskaźniku, powiększając ją za każdym razem o zadaną wartość inkrementacji. Gdy wskazanie osiągnie wartość maksymalną, wskaźnik zostanie opróżniony i cała operacja będzie mogła zostać powtórzona. Stosowanie pasków przewijania Paski przewijania są częstym elementem ramki okna, co pozwala przesuwanie widoku, który fizycznie nie może zostać wyświetlony w obrębie. Zagadnienie to omówione jest szerzej w rozdziale 18. Paski mogą być jednakże zdefiniowane jako odrębne obiekty, służące do poruszania się w określonym obszarze. Do zadań tych częściej stosuje się obiekty sterujące w postaci suwaków. Musimy jednak poznać zasady funkcjonowania pasków przewijania, gdyż suwaki wykorzystują wiele ich cech funkcjonalnych. PATRZ TAKŻE 4 Więcej informacji o wykorzystaniu pasków przewijania znajduje się w rozdziale 18. Dodawanie pasków przewijania do okna dialogowego Edytor zasobów umożliwia wyposażenie okna dialogowego w paski przewijania, które pobierane są z paska narzędziowego Controls. Na palecie tej znajdziemy dwie ikony, 156_____________________________________Poznaj Visual C++ 6 symbolizujące paski przewijania poziome oraz pionowe (rysunek 7.4). W tradycyjny sposób możemy umiejscowić takie paski na szablonie okna dialogowego, zwiększyć ich wymiary oraz korzystając z okna dialogowego właściwości Scrollbar Properties (wywoływanego przez naciśnięcie Alt+Enter po zaznaczeniu pasków), nadać im odpowiednie identyfikatory. ^ H Aa ab| Q o lx c 1§ IH EB j i •••i |ni7| JxSLlEa_J_________. ^ iHonzontalScrollBarI EJ R= C3" Rysunek 7.4. Dodawanie pasków przewijania z paska narzędziowego Controls Na karcie Styles dostępnej w oknie właściwości pasków dokonać możemy ustalenia tylko jednego parametru, czyli wyrównania paska. Parametr ten określony jest jako Align i daje nam do dyspozycji trzy możliwości: • None. Ustawienie domyślne, umieszczające pasek przewijania w takim położeniu i z takimi wymiarami, jak na szablonie okna dialogowego. • Top/Left. Przy tym wyborze pasek przewijania otrzymuje wymiary zgodne ze standardowo stosowanymi w systemie Windows (niezależnie od ustawień na szablonie) i zostaje wyrównany do lewej i górnej krawędzi paska umieszczonego na szablonie okna. • Bottom/Right. Podobnie jak poprzednio, z tą różnicą, że pasek wyrównany jest do prawej i dolnej krawędzi wzorca na szablonie PATRZ TAKŻE • Więcej informacji na temat edycji okien dialogowych i ustalania właściwości obiektów sterujących znajduje się w rozdziale 3. Przypisanie zmiennych paskom przewijania Czynność tę możemy wykonać w taki sam sposób jak w przypadku wskaźnika postępu, o czym mówiliśmy wcześniej w tym samym rozdziale. Jedyną różnicą jest ustalenie kategorii oraz typu zmiennej Variable Type, co wówczas następowało w 5 kroku. Pasek przewijania może być wyłącznie obiektem typu Control, podczas gdy obiekty w postaci suwaków pozwalają na stosowanie typów zarówno Control jak i Value. Stosowanie wskaźników postępu, pasków przewijania, suwaków. 157 Jeśli wybierzemy opcję Value, typ zmiennej zostanie ustalony jako int. Zmienna tego typu przypisana do obiektu suwaka zostaje wpisana do wybranej klasy. Zawartość tej nowej zmiennej składowej będzie zmieniała się w zależności od położenia suwaka w sposób podobny do stosowanego w przypadku obiektów edycyjnych, co omówione zostanie w punkcie „Dodawanie zmiennych składowych, przechowujących dane okna dialogowego", w rozdziale 10. Jeżeli natomiast wybierzemy jako kategorię nowej zmiennej pozycję Control, jako jej typ zostanie automatycznie wskazana klasa cscrollBar. PATRZ TAKŻE « Więcej szczegółów dotyczących przypisywania zmiennych składowych znajduje się w rozdziale 10. Kategorie przypisywanych zmiennych Zawsze, gdy typ zmiennej przypisywanej do obiektu sterującego ustalony jest jako Control, właściwą dla niej klasą będzie cscrollBar, wywodząca się z klasy CWnd. Oznacza to, że wszelkie operacje przeprowadzane na oknie, takie jak zmiana wymiarów lub położenia, będą się odbywały przy użyciu przypisanej zmiennej składowej. Przykład takiej sytuacji znaleźć można w punkcie rozdziału 18. Inicjalizowanie pasków przewijania Paski przewijania możemy inicjalizować wewnątrz funkcji OninitDialog (), tak jak w przypadku wskaźników postępu. Paski te również posiadają określony zakres działania, który określać możemy za pomocą funkcji SetScroilRange () z klasy cscrollBar, podając jej wartości minimalną i maksymalną jako argumenty. Możliwe jest również umieszczenie jako trzeciego argumentu, znacznika wywołującego przerysowywanie (domyślnie jest przyjmowana wartość TRUE, jeśli nie podamy tego parametru). Ograniczenia działania pasków przewijania Zakres wartości, jakimi posługują się paski przewijania, jest ograniczony. Określać go można używając liczb całkowitych (int) przestrzegając zasady, iż różnica pomiędzy wartością minimalną i maksymalną nie może przekroczyć 32767. Przykładowo, jeśli przypisaliśmy dwie zmienne składowe klasy cscrollBar, nazwane m_ScrollBarl oraz m_ScrollBar2 umieszczonym w oknie dialogowym paskom przewijania pionowego i poziomego, to ich inicjalizację możemy przeprowadzić umieszczając na końcu funkcji OninitDialog () następujące dwie linie kodu: 158_____________________________________Poznaj Visual C++ 6 m_ScrollBarl.SetScrollRange(O, 100) ; m_ScrollBar2.SetScrollRange(O, 200) ; W ten sposób zakres wartości dla paska pionowego zawiera się pomiędzy O a 100, natomiast poziomego w granicach O do 200. Ponieważ nie zapisaliśmy trzeciego parametru, domyślnie paski będą przerysowywane (moglibyśmy wpisać wartość FALSE, aby tę czynność zablokować). Funkcja GetScrolIRange () może nam posłużyć do pobierania wartości granicznych dla zakresów z wykorzystaniem wskaźników do właściwych wartości: int nMin, nMax; m ScrollBar2.GetScrolIRange(&nMin, &nMaxO; TRACĘ("Rangę = (%d to %d)\n", nMin, nMax); Gdybyśmy chcieli wyłączyć widok strzałek na końcach paska przewijania, możemy to zrealizować poprzez wywołanie funkcji paska EnableScrollBar(), przekazując jej parametr w jednej z postaci wymienionych w tabeli 7.1. Tabela 7.1. Wartości parametru podawanego funkcji EnableScrollBar () w celu wyłączenia widoku strzałek Wartość Opis ESB_DI SABLE_BOTH Wyłącza widok strzałek na obu końcach paska ESB_DISABLE_LTUP Wyłącza widok lewej lub górnej strzałki (w zależności od orientacji paska przewijania) ESB_DISABLE_RTDN Wyłącza strzałki po przeciwnych w stosunku do poprzedniej pozycji stronach paska. ESB_ENABLE_BOTH Włącza widok obu strzałek - jest to domyślna wartość, jeśli żaden parametr nie zostanie podany funkcji EnableScrollBar () Podobnie jak w wypadku wskaźników postępu, również i tutaj możemy określić początkowe położenie uchwytu do przesuwania wewnątrz paska. Osiągniemy to wywołując funkcję paska przewijania SetScrollPos (), przekazując jej parametr w postaci liczby całkowitej zawartej wewnątrz określonego wcześniej zakresu wartości. Korespondująca z tą funkcją inna, GetScrollPos (), zwraca aktualne położenie uchwytu przesuwającego.- Jeśli chcemy ustawić rozmiar tego uchwytu na wartość wynikającą z pewnej proporcji w stosunku do całego zakresu (na przykład, by równał się jednej stronie tekstu), wyznaczyć go możemy za pomocą funkcji SetScrollinfo (), podając jej jako parametr wskaźnik do struktury SCROLLINFO. Struktura ta w dużej mierze duplikuje działanie funkcji służących do określania zakresu wartości paska i pozycji początkowej uchwytu przesuwającego. Stosowanie wskaźników postępu, pasków przewijania, suwaków... 159 Struktura SCROLLINFO GetScroliinfo () oraz SetScrollInfo () są dwiema korespondującymi funkcjami, których można użyć do zapisywania wewnątrz struktury SCROLLINFO danych dotyczących pasków przewijania. GetScroliinfo () wymaga podania dwóch parametrów - pierwszy jest wskaźnikiem struktury, w której będą zapisane dane, drugi parametr natomiast stanowi maskę bitową wartości znaczników SCROLLINFO, które należy pobrać. Wartości te mogą być zestawem SIF_RANGE, SIF_POS, SIF_TRACKPOS oraz SIF_PAGE, ustalającym wartość zmiennych składowych SCROLLINFO odpowiednio na: nMin, nMax, nPos, nTrackpos i nPage. Znacznik SIF_ALL dokonuje ustawienia wszystkich zmiennych. Związaną z tym zagadnieniem składową zmienną struktury SCROLLINFO jest nPage. Jej wartość powinniśmy określić liczbą całkowitą, stanowiącą rozmiar strony w proporcji do całego dokumentu czy też zakresu działania. Posłużyć się możemy przykładem programu do edycji tekstu. Zakres działania paska przewijania, reprezentujący całość dokumentu ustalić możemy w granicach od O do 100, a ekran może wyświetlać tylko jedną stronę tekstu. Dla dwustronicowego dokumentu zatem zmiennej nPage moglibyśmy nadać wartość 50, co powodowałoby wyświetlanie jednej strony jako połowy dokumentu. Z kolei dla dokumentu o objętości 20 stron wartość zmiennej powinna wyrażać się liczbą 5, co determinuje podział dokumentu na 20 widoków (1/20 zakresu od O do 100). Musimy jednocześnie określić wartość zmiennej f Ma s k na SIF_PAGE w celu powiadomienia funkcji SetScrollInfo ()o zatwierdzeniu zmiennej nPage, a także ustalić rozmiar struktury w zmiennej cbSize (jest to normalne działanie w strukturach Win32). Przykładowo, aby określić rozmiar przesunięcia widoku dokumentu za pomocą uchwytu o 30, powinniśmy za wywołaniem funkcji SetScrolIRange () umieścić następujący kod inicjalizujący: SCROLLINFO s i; si.cbSize = sizeof(SCROLLINFO); si.nPage = 30; si.fMask = SIF_PAGE; m_ScrollBarl.SetScrollInfo(&si) ; Po uruchomieniu aplikacji z tym wpisem rozmiar uchwytu pionowego będzie wyraźnie większy od domyślnie zwymiarowanego uchwytu na pasku przewijania poziomego. PATRZ TAKŻE • Więcej informacji o stosowaniu makra TRACĘ znajduje się w rozdziale 27. 160_____________________________________Poznaj Visual C++ 6 Obsługa powiadomień pasków przewijania Zawsze, gdy użytkownik klika przycisk strzałki, naciska klawisz PgDn/PgUp lub też klawisze strzałek na klawiaturze, pasek przewijania wysyła powiadomienie do okna rodzica. Powiadomienia te są komunikatami systemu Windows: WM_HSCROLL w wypadku paska poziomego oraz WM_VSCROLL w przypadku paska pionowego. Funkcje obsługujące te komu- nikaty możemy umieścić w programie korzystając z okna dialogowego New Windows Messages/Events. Klasa głównego okna dialogowego aplikacji będzie obsługiwała okno rodzica w zakresie wykorzystywania pasków przewijania, zatem funkcja obsługi powiadomień również powinna być w niej zlokalizowana. Dla przykładu: jeśli aplikacja oparta o okno dialogowe ma nazwę Scroll, klasa dla tego okna będzie nosiła nazwę cscroliDig. Klasa ta powinna zostać wskazana na liście Ciass or Object to Handle znajdującej się w oknie dialogowym New Windows Messages/Events (jeżeli ktoś ma z tym kłopoty, może posłużyć się procedurą opisaną w punkcie „Dodawanie funkcji obsługi OnHScroll() w celu przechwytywania komunikatu WM_HSCROLL lub WM_VSCROLL"; odwołania do klasy widoku trzeba zastąpić odwołaniami do klasy głównego okna dialogowego. Dołączenie funkcji obsługi komunikatu WM_VSCROLL, spowoduje dopisanie przez CIassWizard następującego kodu: void CScrollDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) ( // TODO: dodaj tu własny kod obsługi komunikatów i/lub przywołaj CDialog::OnVScroll(nSBCode, nPos, pScrollBar); } Funkcja obsługi komunikatu WM_HSCROLL jest identyczna z powyższą w definicji i strukturze, oprócz nazwy, którą jest w tym wypadku OnHScroll (). Jeśli okno wyposażymy w paski przewijania w obu płaszczyznach, będziemy musieli dołączyć dwie osobne funkcje obsługi powiadomień. Ujemne wartości określające pozycję paska przewijania Jeśli początkowo ustawiony zakres wartości umożliwia posługiwanie się wartościami ujemnymi, możliwe, iż nPos wyrażona jest właśnie taką wartością. Ponieważ jednak jest ona przekazywana jako UINT (wartość całkowita nieoznaczona), do reprezentacji wartości ujemnych użyte zostaną większe wartości dodatnie. Jeśli zostanie stwierdzona taka sytuacja, należy zmienić typ zmiennej nPos na int, co pozwoli jej reprezentować wartości ujemne. Pierwszym parametrem podawanym funkcji (nSBCode) jest znacznik identyfikujący rodzaj wykonanej przez użytkownika czynności. Chodzi tu o to, w jaki sposób dokonał on przesunięcia widoku na ekranie (czy przesunął go za pomocą uchwytu, skorzystał z przycisków strzałek na pasku, czy też nacisnął klawisz Page Up lub Page Down). War- Stosowanie wskaźników postępu, pasków przewijania, suwaków...____________161 tości, które znacznik ten może przybierać, opisane są w tabeli 7.2. Przede wszystkim określić musimy, o który pasek nam chodzi (w omawianym wypadku funkcją obsługi będzie OnVScroll (), dla której klasą bazową jest CDialog). Następnie za pomocą tego znacznika należy ustalić, w jaki sposób uchwyt przesuwania ma zmienić swoje położenie. Jeśli nie określimy tego w sposób jednoznaczny w wywołaniu funkcji SetScrollPos (), uchwyt po przesunięciu go przez użytkownika jak sprężyna powróci do położenia początkowego! Tabela 7.2. Wartości znacznika przekazywane poprzez parametr cSBCode do funkcji obsługi Wartość znacznika Znaczenie SB_THUMBTRACK SB_THUMBPOSITION SB_ENDSCROLL SB_LINEUP SB_LINELEFT SB_LINEDOWN SB_LINERIGHT SB_PAGEUP SB_PAGELEFT SB_PAGEDOWN S B PAGELEFT Użytkownik przesunął uchwyt w określone położenie. Położenie to odczytać można z drugiego parametru, npos Użytkownik przeciągnął uchwyt w określone położenie, zwalniając klawisz myszy. Położenie można pobrać z parametru nPos Użytkownik zwolnił klawisz myszy po przesunięciu widoku za pomocą przycisków strzałek lub kliknięciu wewnątrz paska przewijania (bez użycia uchwytu) Pozycja uchwytu zmniejsza się o jednostkę Tak samo jak w przypadku SB_LINELEFT, lecz w odniesieniu do paska poziomego Pozycja uchwytu zwiększa się o jednostkę Tak samo jak w przypadku SB_LINEDOWN, lecz w odniesieniu do paska poziomego Pozycja uchwytu zmniejsza się o jedną stronę, w proporcji do całego dokumentu (wielkość zależna od wcześniejszych ustaleń) Pozycja uchwytu zmniejsza się o jedną stronę, jak w poprzednim przypadku, ale w zastosowaniu do paska poziomego Pozycja uchwytu zwiększa się o jedną stronę, w proporcji do całego dokumentu (wielkość zależna od wcześniejszych ustaleń) Pozycja uchwytu zmniejsza się o jedną stronę, jak w poprzednim przypadku, ale w zastosowaniu do paska poziomego Możemy sprawdzić, który z pasków przewijania wysłał komunikat powiadamiający. Wykorzystać do tego należy trzeci parametr funkcji OnVScroll (). Jest on wskaźnikiem paska, w którym zaszły zmiany. Prawdopodobnie najlepszą i najprostszą zarazem metodą zidentyfikowania paska jest użycie jego identyfikatora ID. Możemy to osiągnąć wywołując funkcję GetDlgCtrllD() z użyciem wskaźnika pScrollbar. Funkcja ta zwraca identyfikator przypisany paskowi, który następnie zostaje porównany z identyfikatorami pasków, aby określić, czy i w jaki sposób powinien zostać potraktowany zidentyfikowany pasek. 162_____________________________________Poznaj Visual C++ 6 Kod zapisany na listingu 7.1 jest przykładowym zapisem funkcji obsługi komunikatu WM_VSCROLL. Po zidentyfikowaniu paska zostaje zbadany rodzaj przeprowadzonej przez użytkownika operacji i na tej podstawie określony jest sposób zmiany położenia uchwytu przesuwania. Następnie zmieniona zostaje również pozycja uchwytu na pasku poziomym. Gdy więc użytkownik przesunie uchwyt na pasku pionowym, przesunięcie nastąpi również na pasku poziomym. Powiadomienia WM_VSCROLL oraz WMJHSCROLL Pamiętać należy, iż pasek przewijania pionowego wysyła powiadomienie WM_VSCROLL, obsługiwane przez funkcję OnYScroll (). Pasek poziomy natomiast wysyła powiadomienie WM_HSCROLL, wymagające oddzielnej funkcji obsługi OnHScroll (). Częstym błędem jest implementacja tylko jednej funkcji, obsługującej tylko jeden z pasków przewijania. Byłoby z pewnością lepiej, gdyby Windows wysyłał tylko jeden rodzaj powiadomienia ze znacznikiem orientacji paska. Niestety, wcześniejsze 16- bitowe ograniczenia wymusiły stosowanie dwóch komunikatów. Listing 7.1. LST07_1.CPP - obsługa powiadomień pasków przewijania oraz zmiana położenia uchwytów 1 void CScrollDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 2 { 3 if (pScrollBar->GetDlgCtrlID() == IDC_SCROLLBAR1) O 4 { 5 int nCurrentPos = pScrollBar->GetScrollPos () ; 6 switch(nSBCode) 7 { 8 case SB_THUMBTRACK: 9 case SB_THUMBPOSITION: 10 pScrollBar->SetScrollPos(nPos); @ 11 break; 12 case SB_LINEUP: 13 pScrollBar->SetScrollPos(nCurrentPos-1) ; 14 break; 15 case SB_LINEDOWN: 16 pScrollBar->SetScrollPos(nCurrentPos+1) ; 17 break; - 18 case SB_PAGEUP: 19 pScrollBar->SetScrollPos(nCurrentPos-5) ; 20 break; 21 case SB PAGEDOWN: Stosowanie wskaźników postępu, pasków przewijania, suwaków... 163 22 pScrollBar->SetScrollPos(nCurrentPos+5) ; 23 break; 24 } 25 m_ScrollBar2.SetScrollPos( © 26 2 * pScrollBar->GetScrollPos()); 27 } 28 29 CDiaiog::OnVScroll(nSBCode, nPos, pScrollBar) ; 30 } O Sprawdzenie identyfikatora paska. © Zmiana położenia uchwytu w oparciu o komunikat wysłany przez pasek przewijania po dokonaniu zmian przez użytkownika. © W linii tej następuje zmiana położenia w drugim pasku, o wartość dwukrotnie większą od zmian w pierwszym, gdyż zakres wartości drugiego paska jest dwa razy większy od zakresu paska pierwszego. W linii 3 powyższego listingu sprawdzony zostaje identyfikator zwrócony przez funkcję GetDigCtriiD () w celu upewnienia się, iż jest to identyfikator paska pionowego (IDC_SCROLLBARI). Jeśli tak jest rzeczywiście, określone zostaje aktualne położenie uchwytu poprzez wywołanie funkcji GetScrollPos (), a pobrana wartość zapisana zostaje w zmiennej nCurrentPos w linii 5. Wyrażenie switch, które widzimy w linii 6, oddziela kody opisujące poszczególne rodzaje działań użytkownika z parametru nSBCode. Jeśli zmiana położenia uchwytu nastąpiła wskutek przeciągnięcia myszą lub przeciągnięcia i zwolnienia klawisza myszy, pozycja uchwytu zostaje ustalona zgodnie z życzeniem użytkownika na wartość npos, poprzez wywołanie funkcji SetScrollPos () w linii 10. Jeśli użytkownik dokonał przesunięcia o jedną linię w górę lub w dół (za pomocą przycisków strzałek), zmiana pozycji uchwytu wyniesie jedną jednostkę, co zapisane jest w liniach 12-17. Jeżeli natomiast użytkownik kliknie wewnątrz paska przewijania, nakazując tym samym przesunięcie widoku w oknie o jedną stronę, uchwyt przesunięty zostanie o 5 jednostek, co zostało określone jako równoważnik jednej strony dokumentu. Dzieje się to w liniach 18-23. Na koniec, poziomy pasek przewijania przypisany do zmiennej m_ScrollBar2 od- wzorowuje przesunięcia następujące na pasku pionowym przez wywołanie funkcji Set- ScrollPos (). Pobranie informacji o aktualnym położeniu uchwytu na pasku pionowym następuje w liniach 25 oraz 26. Zauważmy, iż w linii 26 wartość zmiany położenia zostaje pomnożona przez 2. Dzieje się tak dlatego, gdyż rozmiar paska poziomego jest dwukrotnie 164 Poznaj Visual C++ 6 większy niż pionowego, który ustaliliśmy przy użyciu funkcji SetScrolIRange (O, 200), wywołanej z OnInitDialog (). Wywołanie funkcji obsługi z klasy bazowej okna dialogowego CDialog: :OnV-Scroll () widniejące w linii 29 pozwala temu oknu na obsługę jego pasków, służących do przesuwania widoku w nim samym (jest to szerzej omówione w rozdziale 18). PATRZ TAKŻE • Na temat powiadomień wysyłanych przez paski przewijania mówimy więcej w rozdziale 18. Stosowanie suwaków kontrolnych Suwaki kontrolne pozwalają określać użytkownikowi pewne wartości poprzez prze- suwanie wskaźnika wzdłuż podziałki i ustawienie go w żądanej pozycji (jak na rysunku 7.5), podobnie do potencjometrów suwakowych, na przykład w sprzęcie HI-FI. Rysunek 7.5. Suwak kontrolny z widocznymi u góry punktami podziałki; wskaźnik postępu widoczny u dołu wskazuje aktualne położenie wskaźnika. Obiekt tego typu jest w pewnym sensie rozwinięciem paska przewijania. Obydwa te rodzaje obiektów sterujących łączy stosowanie określonego zakresu działania oraz pojęcie pozycji wskaźnika. Jednakże suwaki kontrolne pozwalają na dużo elastyczniejszą konfigurację niż paski przewijania. Umieszczenie suwaka kontrolnego w oknie dialogowym Podobnie jak w przypadku pozostałych obiektów sterujących, suwak kontrolny również możemy umieścić w oknie dialogowym przeciągając go z paska narzędziowego Con-trols na szablon okna. Suwak także można wymiarować, ustalać jego położenie w oknie, a także nadawać mu charakterystyczny identyfikator ID na karcie Generał, w oknie dialogowym Slider Properties. Karta Styles (pokazana na rysunku 7.6) z kolei umożliwia nam określenie kilku aspektów działania oraz wyglądu suwaka. Stosowanie wskaźników postępu, pasków przewijania, suwaków... 165 Slldei Propeities -W ? Generał Slyles j Entended Styles ) Orientation: F Tickmaiks r" Enableselection | Honzontal3 1^ Autoticks :;:jEoint.: ::5: ::^ •'•) r Botttet Rysunek 7.6. Ustalanie właściwości suwaka na karcie Style w oknie Slider Properties Na karcie tej znajdujemy dwie listy rozwijane. Pierwsza z nich to Orientation, w której umieszczone zostały dwie pozycje: Horizontal oraz Vertical. Pozwalają one na ustalenie orientacji suwaka, poziomej lub pionowej. Domyślnie ustawiona jest orientacja pozioma. Jeżeli ją zmienimy, może okazać się konieczna także zmiana wymiarów okna dialogowego, aby zmieścić suwak w żądanym rozmiarze. Druga lista obecna na karcie Styks to lista Point. Początkowo wybraną pozycją jest Both, co oznacza, iż wskaźnik suwaka będzie miał postać prostokąta. Zmiana wyboru na Top/Left temu wskaźnikowi ostre zakończenie, skierowane ku górze w przypadku suwaka poziomego lub w lewo, gdy suwak ma orientację pionową. Alternatywnie możemy ustalić kierunek wskaźnika na prawy lub dolny, wybierając pozycję Bottom/Right. Poza tymi dwoma listami, na karcie Styles znajdują się jeszcze cztery pola wyboru, w których możemy wstawiać znaczniki w zależności od dokonanego wyboru. • Tick Marks. Jeśli zaznaczymy tę opcję, na suwaku pojawi się podziałka ułatwiająca wybór pozycji znacznika, umieszczona w zależności od ustawienia znacznika, po prawej lub lewej stronie suwaka (gdy stylem wybranym jest Tol/Left lub Bottom/Right), albo po obu jego stronach, jeśli wybierzemy styl Both. • Auto Ticks. W przypadku zaznaczenia tej opcji punkty podziałki zostają umieszczone zgodnie z przyjętą wartością inkrementacji wartości określającej położenie wskaźnika, w odniesieniu do całego zakresu działania. • Enable Selection. Ta opcja zmienia wnętrze suwaka ze „szczeliny" na biały pasek, co umożliwia programowi wyświetlenie zakresu działania za pomocą małych trójkąci-ków. • Border. Wybór tej opcji spowoduje umieszczenie czarnej obwódki wokół suwaka. PATRZ TAKŻE • Więcej szczegółów dotyczących edycji okien dialogowych oraz właściwości obiektów ste- rujących znajduje się w rozdziale 3. 166_____________________________________Poznaj Visual C++ 6 Przypisanie zmiennej obiektowi suwaka Procedura przypisywania zmiennej temu obiektowi jest bardzo podobna, jak w wypadku wskaźnika postępu. Możemy więc posłużyć się procedurą opisaną w punkcie „Przypisanie zmiennej składowej do wskaźnika postępu" wcześniej w tym rozdziale. Musimy jedynie zwrócić uwagę na wskazania listy Category oraz Variable Type, w kroku 5. Podobnie jak pasek przewijania, suwak pozwala na wybór kategorii Control lub Value. Jeśli spośród dwóch dostępnych wybierzemy pozycję Value, typ zmiennej zostanie określony jako int. Zmienna ta, przypisana obiektowi suwaka, zostanie następnie wpisana do wybranej klasy. Zawartość zmiennej będzie odświeżana w oparciu o zmiany położenia wskaźnika, podobnie jak w przypadku paska przewijania, gdy przypisana mu zmienna jest typu int. W pozostałej części tego podrozdziału zajmiemy się przypadkiem, gdy kategorię zmiennej ustalimy na Control. Wybór ten determinuje automatyczną zmianę typu zmiennej na zmienną klasy MFC CSliderCtrI. Po dokonaniu ustaleń co do nowej zmiennej, możemy przeprowadzić procedurę opisaną w punkcie „Przypisanie zmiennej składowej do wskaźnika postępu". Dynamiczne uaktywnianie i dezaktywowanie obiektów sterujących Przy użyciu przypisanej obiektowi zmiennej (jak csliderCtrl) możliwe jest programowe aktywowanie i dezaktywowanie obiektów sterujących. Można to osiągnąć stosując funkcję EnableWindow(), podając jej parametr TRUE lub FALSE (na przykład m_mySliderCtrl.EnableWindow(FALSE)). Może się to okazać przydatne do uniemożliwienia użytkownikowi wprowadzania zmian za pomocą danego obiektu sterującego w pewnych okolicznościach. PATRZ TAKŻE 4 Więcej na temat przypisywania zmiennych mówimy w rozdziale 10. Inicjalizacja obiektu suwaka Zakres działania ustalamy przy użyciu funkcji SetRangeO, podając jej jako dwa pierwsze argumenty wartość minimalną oraz maksymalną. Opcjonalnie możemy także podać trzeci argument o wartości FALSE, co zablokuje automatyczne odświeżanie widoku suwaka. Zakres działania określić mogą również dwie funkcje: SetRangeMin ()oraz Se-tRangeMax (), dla których parametrami są odpowiednio wartość minimalna i maksymalna. Korespondujące z nimi dwie inne funkcje GetRangeMin () oraz GetRangeMax (), służą do pobierania aktualnie ustalonych wartości granicznych. Stosowanie wskaźników postępu, pasków przewijania, suwaków.. 167 Pozycję wskaźnika możemy ustalić używając funkcji SetPos () i podając jej w charakterze parametru nową wartość reprezentującą żądane położenie. Wartość opisującą położenie aktualne można pobrać z funkcji składowej GetPos (), która tę wartość zwraca. Tak jak w przypadku pasków przewijania, użytkownik może przesuwać widok na ekranie o linię lub stronę, naciskając klawisze strzałek lub Page Up/Page Down. Jednakże nie musimy tu stosować specjalnych powiadomień dla odświeżania widoku suwaka, jak to się dzieje w przypadku pasków przewijania. Kontrolę nad wartością przesunięcia widoku o linię lub stronę zapewniają nam funkcje obiektu suwaka SetLineSize () oraz SetPage-Size(), którym przekazać należy odpowiednie wartości. Bieżące ustawienia możemy pobrać z wartości zwróconych przez dwie korespondujące z powyższymi funkcje składowe: GetLineSize () iGetPageSize(). Ustalić można również wartość odstępów pomiędzy punktami podziałki. Użyć w tym celu należy funkcji SetTickFreq (), podając jej jako parametr wartość pojedynczego odstępu. Gdybyśmy chcieli na przykład ustalić ten odstęp na 5 jednostek, powinniśmy wpisać następujący kod: m_Slider.SetTickFreq(5) ; Programowe usuwanie znaczników Aby programowo usunąć z widoku suwaka znaczniki podziałki, należy posłużyć się funkcją clearTics (). Odświeżenie widoku suwaka nastąpi po niej automatycznie, gdy przekazany jej zostanie parametr TRUE. Aby funkcja ta mogła działać, musimy wcześniej ustalić właściwy styl, wybierając opcję Auto Ticks z okna dialogowego Slider Properties. Wywołanie funkcji składowej GetNumTicks () pomoże nam określić, ile punktów znajduje się obecnie na podziałce suwaka. Jeżeli na karcie stylów zaznaczyliśmy opcję Enable Selection, możemy użyć funkcji składowej SetSelection (), by zaznaczyć pewien obszar zakresu działania. Obszar ten będzie zaznaczony niebieskim kolorem na białym pasku pod wskaźnikiem (widać to na rysunku 7.5). Aby ustalić granice tego obszaru musimy podać funkcji SetSelection () wartości minimalną oraz maksymalną wewnątrz obecnego zakresu działania. W celu określenia wartości granicznych obecnie dokonanej selekcji musimy wywołać funkcję GetSe-lectionO, podając jej odwołania do zmiennych przechowujących bieżące wartości. Użycie funkcji ciearSel () natomiast powoduje usunięcie selekcji. PATRZ TAKŻE » Więcej informacji na temat inicjalizacji okien dialogowych znajduje się w rozdziale 10. 168 Poznaj Visual C++ 6 Odpowiedzi na powiadomienia obiektu suwaka Obiekty tego typu stosują ten sam rodzaj komunikatów powiadamiających, jakich używają paski przewijania, czyli WM_VSCROLL oraz WM_HSCROLL. Jedyną różnicą jest to, że nie musimy nakazywać odświeżenia pozycji wskaźnika, gdyż dzieje się to automatycznie. Możliwe jest jednakże wykorzystanie tych komunikatów do podejmowania innych, specyficznych działań. Przykładowo, za pomocą suwaka możemy sterować wskazaniami wskaźnika postępu, co pokazaliśmy na rysunku 7.5. Należałoby w tym celu przypisać suwakowi (o identyfikatorze IDC_SLIDERI) zmienną składową m_siider klasy CSliderCtrl, a wskaźnikowi postępu (o identyfikatorze IDC_PROGRESSI) zmienną o nazwie m_Progress klasy CProgressCtrI. Aby spowodować zmiany wskazań na wskaźniku postępu wraz ze zmianami położenia wskaźnika suwaka, należy dodać funkcję obsługi OnHScroll () oraz poniższy kod: void CSliderDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) ( if (pScrollBar->GetDlgCtrlID() == IDCJ3LIDER1) m_Progress.SetPos(m_Slider.GetPos() ) ; CDialog::OnHScroll(nSBCode, nPos, pScrollBar); } Użyta tu funkcja GetDIgCtrIlDO służy do identyfikacji suwaka, następnie pozycja wskaźnika postępu zostaje ustalona przez funkcję składową SetPos () w oparciu o wartość położenia wskaźnika suwaka pobraną za pomocą funkcji suwaka GetPos (). Jeżeli chcemy poznać wartość przesunięcia o linię lub stronę, powinniśmy sprawdzić wartość parametru nSBCode i porównać ją z tabelą 7.2. Odnajdywanie obiektu sterującego na podstawie identyfikatora ID GetDIgitemO oraz GetDIgCtrl () są funkcjami zwracającymi wskaźnik cwnd do obiektu, którego identyfikator ID zostanie im podany jako pierwszy parametr. Jeżeli żaden obiekt o podanym identyfikatorze nie zostanie odnaleziony, zwrócona zostanie wartość NULL. Jeżeli zachodzi potrzeba wykorzystania zwróconego wskaźnika, należy włączyć go do klasy MFC, obsługującej dany rodzaj obiektu sterującego. PATRZ TAKŻE « Informacje o identyfikatorach obiektów sterujących oraz komunikatach powiadamiających znajdują się w rozdziale 4. Stosowanie wskaźników postępu, pasków przewijania, suwaków... 169 Stosowanie próbników daty i godziny Do czasu pojawienia się Visual C++ 6.0 programiści musieli konstruować własne mechanizmy pozwalające użytkownikowi na określanie daty i godziny bądź też korzystać z kontrolek ActiveX. Obecnie Microsoft dołączył do paska narzędziowego Controls dwa obiekty temu służące, czyli próbnik daty oraz godziny i kalendarz miesięczny. Próbnik daty automatycznie otwiera kalendarz (omówiony w dalszej części rozdziału), po kliknię-ciu przez użytkownika przycisku rozwijającego w celu określenia dnia i miesiąca. Ten rodzaj obiektu możemy zobaczyć w aplikacji poczty elektronicznej programu Microsoft Outlook. Próbnik może nam posłużyć do pobierania zarówno daty, jak i godziny (przy użyciu dwóch obiektów tego rodzaju). Próbnik pobierający datę ma zwykle wygląd listy kombinowanej i wyświetla w polu edycji datę w skróconej formie. Gdy użytkownik kliknie przycisk rozwijania, pojawi się kalendarz miesięczny (widoczny na rysunku 7.7), który pozwoli na wybór miesiąca i dnia. Próbnik godziny natomiast (po prawej stronie, na rysunku 7.7) otwarty zostaje z podaną bieżącą godziną, którą można edytować bezpośrednio w polu edycji lub poprzez użycie przycisków ze strzałkami. Rysunek 7.7. Użycie próbników daty oraz godziny PATRZ TAKŻE • Przykład kontrolki ActiveX, służącej do pobierania daty znajduje się w rozdziale 9. Umieszczanie próbników daty i godziny w oknie dialogowym Tak jak wszystkie obiekty sterujące, również próbniki daty czy godziny mogą być umieszczane na szablonie okna dialogowego poprzez przeciągnięcie ich z paska narzędziowego Controls bezpośrednio na szablon okna. Na szablonie mogą być przesuwane oraz wymiarowane. W ich wypadku również powinniśmy ustalić identyfikatory, korzystając z karty Generał w oknie dialogowym Datę Time Picker Properties (wywołujemy je z menu skrótów lub poprzez naciśnięcie klawiszy Alt+Enter), jak widać to na rysunku 7.8. 170 Poznaj Visual C++ 6 M.illplellM.MiciOKilWiaMCc.lIdliKeltit.lc-IDD.DTPICKERJlIAUlGIOIllagll":1 !':::!::,-;:: .l,«.»i„, s'?- ::,,,. HBQ ^f.ile EA1 V.sw InMft Pr0)ec Bi.tó Laiwl I wiś Window łje!p „ {ffj x iii B; V ^ X. ^ ^ . . '..; ' !r,y5%-' ^:^'^r~~''~~"'~'''''^] -^ ' ' ^ -:"' ! ''S-i '/ • j i-i-"P.'.:i.eiL^ 'Jit[/C.L',Ałt.ilMfcPil:rŁ.Hl ^J|D NJ.UKEUP J^J ^ - : ^ -^ '1 Y11!,!:. ^,. ;1:1:1;..,.'.,. ;.1,11:1,,..., ,;:';:"!!ll;iiBS'7''ylljgfiT:;fl"l!: ii ^ 11111:1 ^:' ., i dlpk.k.1 .n«J e, Ł.atlMtc -^ B^^IWilKi^^r^W"® : i- gl.OO.Ml :-BScii l BSS^flBB^I lit N A> "K •^5/4/33 TJII 1021 51 AM — i OK :;; ,,„:, , Q a [x r. ; a!Qv«idFrom) @ 15 ( 16 case IDCJMYDATE: @ 17 m_myDate.GetTime(dtSel); 18 SetWindowText("Datę:"+dtSel.Format("%#x")) ; 19 break; 20 case IDC_MYTIME: @ 21 m_myTime.GetTime(dtSel); 22 SetWindowText("Time:"+dtSel.Format("%H:%M:%S")) ; 23 break; 24 } 25 *pResult = 0; 26 } O Drugie odwołanie do mapy komunikatów należy wprowadzić samodzielnie. © W zależności od tego, jakiego rodzaju próbnik wysłał komunikat, czy był to próbnik daty (linia 16), czy też godziny (linia 20) pasek tytułowy okna dialogowego wyświetli wybraną datę lub godzinę. Pozycja mapy komunikatów wypisana w linii 6 jest pozycją standardową, wpisywaną przez CIassWizard po umieszczeniu w programie funkcji obsługi powiadomienia o zmianie daty. Jednakże drugą pozycję mapy, widoczną w linii 8, musimy wprowadzić samodzielnie, by mogła współużytkować tę samą funkcję obsługi (OnDatetimechangeMyDate()). Implementacja powyższej funkcji odbywa się w liniach od 11 do 26. Zmienna obiektu COleDateTime (dtSel) zostaje zadeklarowana w linii 13 i służy do przechowywania bieżącej daty i godziny. Wyrażenie switch natomiast, widoczne w linii 14, musi określić próbnik, który wysłał komunikat. Jeśli próbnikiem tym będzie IDC_MYDATE, ze zmiennej przypisanej temu próbnikowi, m_myDate, zostanie pobrana nowa data (linia 17) i wyświetlona w pasku tytułowym głównego okna dialogowego (linia 18). Jeżeli natomiast próbnik IDC_MYTIME będzie nadawcą komunikatu, z przypisanej mu zmiennej m_myTime odczytana zostanie ustalona przez użytkownika godzina (w linii 21), a następnie wyświetlona w pasku tytułowym okna dialogowego (linia 22). 178 Poznaj Yisuał C++ 6 Ręczne wpisanie pozycji mapy komunikatów Podczas wpisywania z klawiatury pozycji mapy komunikatu należy uważać, aby nowy wpis umieścić za komentarzem // { {AFX_MSG_MAP. Komentarz ten używany jest przez CIassWizard, a gdy odnajdzie za nim nieoczekiwany kod, może powstać zamieszanie. Najlepsze miejsce na wpisanie tego kodu znajduje się za drugim komentarzem // { (AFX_MSG_MAP, lecz przed linią makro END_ME s SAGĘ MAP . : PATRZ TAKŻE • Więcej o identyfikatorach obiektów sterujących oraz komunikatach powiadamiających mówimy w rozdziale 4. Stosowanie kalendarza miesięcznego Kalendarz miesięczny możemy stosować także jako oddzielne okno dialogowe. Dotych- czas zajmowaliśmy się tym obiektem w połączeniu z próbnikiem daty, gdy wyświetlany był po kuknięciu przez użytkownika przycisku rozwijania. Próbnik pokazywał wtedy okno ka- lendarza, z którego użytkownik mógł wybrać żądaną datę, zmieniając wyświetlany miesiąc i rok. Kalendarz ten w normalnych sytuacjach zezwala na wybór tylko jednej daty, możliwe jednak jest skonfigurowanie go w sposób umożliwiający wybór większego zakresu dat. Umieszczenie kalendarza miesięcznego w oknie dialogowym Kalendarz umieszczamy na szablonie okna dialogowego w znany nam już sposób, czyli przeciągając jego ikonę z paska narzędziowego Controls, bezpośrednio na szablon. Po wykonaniu tej czynności możemy przystąpić do edycji właściwości nowego obiektu. Okno dialogowe Month Calendar Properties wywołujemy poprzez naciśnięcie Alt+Enter. Na karcie Generał dokonujemy zmiany identyfikatora. Karta Styles (widoczna na rysunku 7.10) pozwala na wybór opcji związanych ze sposobem działania i zachowania się kalendarza. Oto możliwości wyboru: • Day States. Po zaznaczeniu tej opcji możemy określić datę, która będzie wyświetlana w kalendarzu w negatywie (biała data na czarnym tle). W przeciwnym wypadku, w ten sposób wyświetlana będzie bieżąca data systemowa. " Multi Select. Zaznaczenie tej opcji sprawi, iż możliwe będzie oznaczanie pewnego zakresu dat, zamiast pojedynczej daty, w przypadku niezaznaczenia. • No Today. W przypadku wyboru tej opcji bieżąca data systemowa nie będzie otaczana czerwoną linią, jak ma to miejsce zazwyczaj. • Week Numbers. Gdy oznaczymy tę opcję jako aktywną, po lewej stronie każdej linii reprezentującej tydzień umieszczony zostanie numer danego tygodnia w aktualnie wy- świetlanym roku, w numeracji od l do 52. Stosowanie wskaźników postępu, pasków przewijania, suwaków... 179 Znaczniki stylu kalendarza miesięcznego Opcje stylu nadane kalendarzowi są równoznaczne ze znacznikami stylu: MCS_DAY-STATE, MCS_MULTISELECT, MCS__NOTODAY, MCS_NOTODAYCIRCLE Oraz MCS_WEEK- NUMBERS. Znaczników tych można użyć w trakcie dynamicznego tworzenia obiektu kalendarza, zamiast w edytorze zasobów. •IDD MONTHCAL DIALOG (Diatogn ;3tile ŁAI YKW jraell Flopef Bu B) lSS9[t aa. : a - . r, 13 %' CjhICMonIhCaioir ^ [**i ^ l ^ '^ !.jCMot'B^a!Lil.3^]; IDC_1''ON l h&LfcMDńftl_>J|MCU_l^ l DAł'.' !A s Ł ^..^ ~33- •••-•••—- •••^ 'm..,,....,....,,...,,,.,,.........,.,.. :* •-; "-J Btonihcal tetoucce* ' ;-:"..:it Dialog : : ^| iDDA60UTBOX ; S^ «. ,^^^^^B^riiBi::a : ' •^itjD- MgNTH.CAL^D.lAigC. ;' !;S"Q] lccr. 1 : :S..^JSIhi- .9Ttbte :; l|^|B!FJ|iC^ 4^-1 p*"fflmHr^ It U AHM Q 0 K s ! H5-.(JV»-riw : :^ 3 40! S 6 7 6 9 , iB a as a l l $ BCT -ł~ ^ i l || - t9"''- 10 11 12 13 t4 15 f. ' 17 18 19 20 21 22 -K : 24 25 36 37 28 29 3CI • . !ll. B Hi l ; 31 ; '•: :••• i "-. • UL 9 Ęi'~ l | l- l^ • ••.••..••••.. ••. .•.••••*••. ••••• .•••• ••• • • • • • •• • C 68 |S^c„h.i«| : -» f Gweidl [Sty!es,J E^iwdedS^te;. | 1^ r E3;,Siate? F UoTctIdj. r {-'utiSdeGi r Węch Nun-.be-s 1^ :^ "r MoIcriayC,.j^F!eMi»„. t]FiteViw.| :'S /. '• 7; ' lalE53 •• Cwales ai^twmoolhcateridaicwilioi. Rysunek 7.10. Kalendarz miesięczny umieszczony w oknie dialogowym jako odrębny obiekt PATRZ TAKŻE t Więcej szczegółów dotyczących edycji okien dialogowych oraz właściwości obiektów sterujących znajduje się w rozdziale 3. Przypisanie zmiennej do obiektu kalendarza Procedura przypisania zmiennej do obiektu jest w tym wypadku bardzo podobna do pozostałych, opisanych już w tym rozdziale. Możemy przypisać zmienną typu CTime lub COleDateTime bezpośrednio do obiektu kalendarza, gdy wybraną dla niego w oknie dialogowym Add Member Yariable kategorią będzie Value. Jeżeli jednak zależy nam na pełniejszej kontroli obiektu, powinniśmy jako jego kategońę wybrać Control, co automatycznie ustali typ zmiennej na CMonthCalCtrl jako zmienną składową klasy obsługującej wybrane okno dialogowe. • Więcej słów wyjaśnienia na temat przypisywania zmiennych składowych znajduje się w rozdziale 10. Inicjalizacja obiektu kalendarza Obiekt ten można zainicjalizować w taki sposób, aby zezwalał na wybór daty z pewnego zakresu, poprzez przekazanie funkcji SetRangeO klasy CMonthCalCtrl parametrów wskazujących najwcześniejszą oraz najpóźniejszą dostępną datę w postaci wskaźników obiektów COleDateTime (lub CTime), jakimi są na przykład próbniki daty. Funkcja korespondująca, GetRange (), posłużyć może do zapisania we wskazanych obiektach dat granicznych. Domyślnie przyjmowanym pierwszym dniem tygodnia jest niedziela. Możemy to jednak zmienić, przekazując funkcji SetFirstDayOfWeek() numer dnia bazujący na numeracji rozpoczynającej się od zera, w której poniedziałek będzie nosił indeks O, niedziela natomiast indeks 6. W tym celu na końcu funkcji OninitDialog () powinniśmy umieścić następujący wpis (gdzie m_myMonthCal jest zmienną klasy CMonthCalCtrl, przypisaną obiektowi kalendarza): m_niyMonthCal. SetFirstDayOfWeek (0) ; Możemy również dokonać zmiany daty wskazywanej jako „dziś" (otoczonej czerwoną kreską), przekazując funkcji SetTodayO wskazanie obiektu COleDateTime, w którym zapisana jest żądana data. Bieżąca data „dzisiejsza" pobierana jest poprzez przekazanie funkcji GetToday () zmiennej COleDateTime. Owa data będzie zapisana w zmiennej. Domyślne kolory użyte do wyświetlania kalendarza możemy zmienić, wywołując funkcję SetColor (), której przekażemy jako pierwszy parametr znacznik spośród opisanych w tabeli 7.4 oraz kolor jako drugi parametr. Ustalenie kroku przewijania kalendarza Gdy użytkownik naciśnie któryś z umieszczonych w oknie kalendarza przycisków przewijania, przesunięcie „w czasie" równe będzie jednemu miesiącowi. Można zmienić wielkość tego kroku wywołując funkcję SetMonthDelta () i podając jej w charakterze parametru liczbę miesięcy, o jaką ma następować przesunięcie kalendarza po każdym użyciu przycisków przewijania. Powiązana z powyższą funkcja GetMonthDelta () zwraca obecną wartość tego kroku. Stosowanie wskaźników postępu, pasków przewijania, suwaków... 181 Tabela 7.4. Znaczniki zmian kolorystyki kalendarza miesięcznego Znacznik Opis MCSC_TEXT Zmiana koloru czcionki wyświetlającej daty MCSC_TITLETEXT Zmiana koloru napisu w pasku tytułowym MCSC_TITLENBK Zmiana koloru tła paska tytułowego MCSC_TRAILINGTEXT Zmiana koloru dni zachodzących na bieżący miesiąc z miesiąca poprzedniego oraz następnego MCSCJBACKGROUND Zmiana koloru tła, na którym kalendarz jest wyświetlany PATRZ TAKŻE • O wartościach kodowych kolorów mówimy w rozdziale 16. Ustalanie zakresu dostępnych dat w kalendarzu Umożliwienie użytkownikowi dokonywania wyboru kilku następujących po sobie dat uwarunkowane jest zaznaczeniem opcji Multi Select na karcie Styles w oknie dialogowym właściwości obiektu kalendarza. Zakres dat wyświetlanych w kalendarzu Zakres dat, jakie wyświetlić może kalendarz, można ograniczyć używając funkcji setRange (), podając jej jako parametry wskaźniki do obiektów coleDateTime, jak miało to miejsce w przypadku obiektów CDateTimeCtrI. Powiązana funkcja Get-Range () zwraca obecnie obowiązujący zakres. Gdy kalendarz pracuje w trybie zezwalającym na wybór tylko jednej daty, możliwe jest wskazanie początkowo wskazywanej daty poprzez przekazanie funkcji kalendarza SetCurSel () wskazania obiektu COleDateTime, jak poniżej: m_MyMonthCal.SetCur(C01eDateTime(1967, l, 18, 18, 2, 0) ) ; Data wpisana lub wskazana przez użytkownika może zostać pobrana w każdym momencie, poprzez przekazanie obiektu typu COleDateTime do funkcji kalendarza GetCur-Sel (), jak poniżej: COleDateTime dtChosenTime; m_MyDate.GetCur3el (dtChosenTime) ; AfxMessageBox(dtChosenTime, Format("%#x")) ; Ostatnia linia służy do wyświetlenia wybranej daty w oknie komunikatu. 182_____________________________________Poznaj Visual C++ 6 Jeżeli chcemy umożliwić dokonywanie wyboru kilku dat, powinniśmy użyć funkcji GetSeIRange () oraz SetSeIRange () w celu sprawdzenia aktualnie określonego i ustalenia nowego zakresu dostępnych dat. Obie te funkcje wymagają przekazania im obiektów COleDateTime jako parametrów. Pierwszy z nich określa najwcześniejszą możliwą do wyboru datę, drugi natomiast najpóźniejszą. Domyślnie ustalona maksymalna liczba dni, które użytkownik może zaznaczyć, wynosi siedem. Stan ten można zmienić, podając dowolną liczbę maksymalną poprzez podanie funkcji kalendarza SetMaxSelCount () tej liczby w postaci wartości int. Jeśli przykładowo, chcemy zaznaczyć pierwsze 14 dni stycznia 1998 oraz umożliwić dokonanie wyboru maksymalnie 15 następujących po sobie dni wewnątrz tego zakresu, dodać musimy poniższy kod: m_MyMonthCal.SetMaxSelCount(15) ; m_MyMonthCal.SetSeIRange(COleDateTime(l 998, l, l, O, O ,0), COleDateTime(1998, l, 14, O, O, 0)) ; Po uruchomieniu programu na kalendarzu zaznaczone zostaną daty pomiędzy l a 14 stycznia i użytkownik będzie mógł dokonać selekcji maksymalnie 15 spośród nich. Po dokonaniu przez użytkownika wyboru, można sprawdzić liczbę wybranych dni, a także daty, pomiędzy którymi nastąpiła selekcja. W tym celu dopisać należy kolejny kod: COleDateTime dtMin, dtMax; m_MyMonthCal.GetSeIRange(dtMin, dtMax) ; COleDateTimeSpan dtSpan = dtMax - dtMin; AfxMessageBox(dtSpan.Format("You Selected %D Days")) ; Liczba wybranych dni obliczona zostaje poprzez odjęcie daty najpóźniejszej od najwcześniejszej i zapisanie różnicy w obiekcie COleDateTimeSpan, dtSpan. Okno komunikatu wyświetli wtedy otrzymaną różnicę, jako liczbę wyselekcjonowanych dni. Manipulowanie obiektami COleDateTimeSpan Wartość odstępów pomiędzy datami można ustalać przekazując funkcji SetDateTi-meSpanO parametry w postaci liczb całkowitych, reprezentujących liczbę dni, godzin, minut oraz sekund. Obecne wartości zapisane w obiekcie COleDateTimeSpan pobrać można za pomocą funkcji: GetDays (), GetHours (), GetMinutes () oraz Get-SecondsO, które zwracają cząstkowe informacje zawarte w obiekcie, w postaci liczb typu long. Chcąc uzyskać informacje całościowe, w postaci wartości typu do- uble wywołać należy odpowiednie funkcje: GetTotalDays (), GetTotalHours (), GetTotalMinutes() oraz GetTotalSeconds(). Stosowanie wskaźników postępu, pasków przewijania, suwaków... 183 Reakcje na powiadomienia o zmianie wyboru daty Możemy dodać funkcję obsługi, która będzie wywoływana, gdy użytkownik dokona zmiany wyboru zakresu dat. Przebiega to w ten sam sposób, jak w punkcie „Dodanie funkcji obsługi komunikatu o zmianie daty". Jedyną różnicą jest wskazywany komunikat, którym w tym przypadku jest MCD_SELCHANGE. Po umieszczeniu w programie funkcji obsługi tego komunikatu, będzie ona miała następującą postać: void CMonthcalDlg::OnSelchangeMonthCalendarl(NMHDR* pNMHDR, LRESULT* pResult) ( // TODO: ... *pResult = 0; } Teraz należy dopisać właściwy kod funkcji, a następnie dokonać sprawdzenia struktury NMHDR, jak na listingu 7.2 dla próbnika daty. Możemy również umieścić kod sprawdzający bieżący wybór, wykorzystując funkcje GetCurSel () oraz GetSeiRange () opisane w poprzednim punkcie rozdziału. PATRZ TAKŻE • Więcej o identyfikatorach obiektów sterujących oraz komunikatach powiadamiających mówimy w rozdziale 4. Rozdział 8 Obsługa zdarzeń wywoływanych myszą Obsługa zdarzeń naciśnięcia i zwolnienia klawisza myszy Wykorzystanie własnych funkcji do obsługi mchów myszy Wykorzystywanie informacji o współrzędnych wskaźnika myszy oraz obszarów testowania Obsługa powiadomień klawiszy myszy Częstokroć zachodzi potrzeba sprawdzenia, czy użytkownik nacisnął bądź zwolnił klawisz myszy i miejsca, w którym to nastąpiło — bez użycia klawiszy kontrolnych. Naj- lepszym przykładem takiej sytuacji może być aplikacja graficzna, w której ekran służy użytkownikowi jako tablica do rysowania, a kuknięcia myszą powodują określone reakcje w postaci rysowania kresek lub punktów. Jak już wiemy, wszelkie akcje podjęte przez użytkownika docierają do aplikacji w formie określonych komunikatów (lub zdarzeń). Komunikaty pochodzące od klawiszy myszy nie są wyjątkiem od tej reguły. Aktualna pozycja wskaźnika zostaje połączona z komunikatami klawiszy, co sprawia, że są one bardzo łatwe do wykorzystania w połączeniu z funkcjami graficznymi systemu Windows. Kontrolowanie długości czasu naciśnięcia klawisza myszy Komunikaty Windows przekazują informacje o tym, jak długo użytkownik naciskał klawisz myszy. Informacja ta może zostać pobrana ze składowej zmiennej struktury MSG poprzez wymuszenie wykonania wirtualnej funkcji PreTranslate- Message (). Funkcja ta wywoływana jest po otrzymaniu komunikatu przez aplikację, a przed przeprowadzeniem wszelkich operacji korzystających z mapy komunikatów. 186 Poznaj Visual C++ 6 Obsługa zdarzeń naciśnięcia i zwolnienia klawisza myszy System Windows posiada mechanizmy obsługi trzech klawiszy, w jakie wyposażona może być mysz. Część myszy jednak posiada tylko dwa klawisze; w takim przypadku mamy do czynienia z wydarzeniami związanymi z lewym bądź prawym klawiszem, lecz w pewnych okolicznościach wskazana jest obsługa również środkowego klawisza. System Windows wysyła do aplikacji dwa rodzaje informacji o zdarzeniach: gdy użytkownik naciśnie klawisz myszy oraz go zwolni. Czasami obsługa tych zdarzeń określana jest terminem catching (łapanie), ponieważ zdarzenia są „łapane" czy przechwytywane i wywoływany jest odpowiedni kod przy przekazywaniu zdarzeń klasie bazowej okna. Aby poznać ten proces w praktyce, utworzymy za pomocą AppWizard nową aplikację okna dialogowego i nazwijmy ją MouseMsg. Po utworzeniu okna dialogowego aplikacji IDD_MOUSEMSG_DIALOG, powiększmy w edytorze zasobów jego rozmiary do około 300 pikseli szerokości i 200 wysokości. Da nam to wystarczająco dużą powierzchnię do przeprowadzenia kontroli zwracanych przez mysz współrzędnych. Dokonując edycji okna dialogowego aplikacji, musimy także usunąć widoczny pośrodku napis TODO :. . ., pozostawiając w obrębie okna jedynie przyciski OK oraz Cancel. Za pomocą poniższej procedury ustanowimy funkcję obsługi przechwyconego komu- nikatu, informującego o naciśnięciu przez użytkownika lewego klawisza myszy. Dodanie funkcji obsługi komunikatu lewego klawisza myszy 1. Kliknij kartę CIassView w panelu Project Workspace. 2. Kliknij symbol +, by rozwinąć widok użytych w aplikacji klas. 3. Prawym klawiszem kliknij pozycję klasy CMouseMsgDlg, po czym wyświetlone zostanie menu skrótów. 4. Otwórz okno dialogowe New Windows Message and Event Handlers, klikając pozycję menu Add Windows Message Handler. 5. Z listy komunikatów i zdarzeń (messages/events) wybierz WM_LBUTTOMDOHN. 6. Kliknij przycisk Add and Edit, co umożliwi edycję zawartości nowej funkcji. Otworzyliśmy zatem funkcję obsługi OnLButtonDown () dla zdarzenia związanego z lewym klawiszem myszy. Funkcja ta będzie wywoływana za każdym razem, gdy użytkownik naciśnie ten klawisz. Edytując kod funkcji, możemy nakazać wykonywanie określonych czynności po wywołaniu tej funkcji. Przykładowo, dodając kod zapisany na listingu 8.1, spowodujemy, że po każdym naciśnięciu lewego klawisza myszy wyświetlana będzie ikona z wykrzyknikiem. Obsługa zdarzeń wywoływanych myszą 187 Listing 8.1. LST8_1.CPP - przypisanie funkcji OnLButtonDown () reakcji w postaci wyświetlania ikony z wykrzyknikiem, w miejscu naciśnięcia lewego klawisza myszy void CMouseMsgDlg::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Dodaj tu kod kod obsługi komunikatu lub/i II ** Wyświetl komunikat w pasku tytułu okna CString strMessage; strMessage.Format("Left Button Pressed at (%d,%d)", O point.x,point.y) ; SetWindowText(strMessage) ; // ** Wybór ikony w zależności od użycia klawisza Ctrl char* pszlcon; if (nFlags & MK_CONTROL) @ pszlcon = IDI_EXCLAMATION; el-se pszlcon = IDI_HAND; // ** Wyświetlenie ikony z wykrzyknikiem w miejscu naciśnięcia II ** klawisz myszy CDC* pDC = GetDCO ; pDC->Draw!con(point, AfxGetApp()->LoadStandardIcon(pszlcon)); ® ReleaseDC(pDC) ; CDialog::OnLButtonDown(nFlags, point) ; O Współrzędne wskaźnika myszy zostają sformatowane w łańcuch zmiennych i wyświetlone w pasku tytułowym okna dialogowego. @ Jeśli użytkownik jednocześnie z naciśnięciem lewego klawisza myszy użyje klawisza Ctrl, wybrana zostanie inna spośród standardowych ikon. © Wybrana ikona zostaje wyświetlona w miejscu, w którym nastąpiło naciśnięcie klawisza myszy. Zauważmy, iż w powyższym listingu funkcji OnLButtonDown () zostają przekazane dwa parametry. Pierwszy z nich, nFlags, informuje o tym, który z klawiszy myszy został 188_____________________________________Poznaj Visual C++ 6 i naciśnięty oraz czy jednocześnie nastąpiło użycie klawisza Ctrl lub Shift (wartości tego parametru są przedstawione w tabeli 8.1). Są to znaczniki bitowe i każda ich kombinacja może zostać wykorzystana, w każdym momencie. Dla sprawdzenia działania poszczególnych znaczników można posłużyć się operatorem logicznym AND (w języku C++ operator &), jak w linii 13. Zostaje tutaj sprawdzona wartość znacznika MK_CONTROL, którą będzie TRUE, jeśli wraz z lewym klawiszem myszy zostanie naciśnięty kalwisz Ctrl. Wskaźnik pszlcon jest wykorzystany do wskazania w linii 14 ikony IDI_EXCLA-MATION lub IDI_HAND, w zależności od wyniku sprawdzenia wartości nFlags pod kątem użycia klawisza Ctri. Ikony te są standardowymi ikonami systemowymi i zostają użyte w liniach 20 i 21 przez funkcję kontekstu urządzenia DrawiconO (nie będziemy tutaj rozprawiać o kontekście urządzenia; zagadnienie to zostanie opisane w rozdziale 15). Funkcja klasy aplikacji LoadStandardlcon () pobiera wskaźnik pszlcon w celu załado- i wania określonej ikony systemowej (linia 21). Inne tego rodzaju ikony wyszczególnione są w tabeli 8.2. Drugim parametrem przekazywanym funkcji OnLButtonDown () jest obiekt point klasy c Point. Jest to klasa przechowująca współrzędne jako składowe x oraz y. W linii 7 zostaje sformułowany na ich podstawie łańcuch znakowy, który następnie jest wyświetlany przez funkcję SetWindowText () na pasku tytułowym okna dialogowego (linia 9). Punkt o podanych współrzędnych służy następnie umiejscowieniu wyświetlanej ikony. Tabela 8.1. Znaczniki przekazywane funkcji OnLButtonDown () jako parametr nFlags Postać znacznika Opis MK_LBUTTON Informuje o naciśnięciu lewego klawisza myszy MK_MBUTTON Informuje o naciśnięciu środkowego klawisza MK_RBUTTON Informuje o naciśnięciu klawisza prawego MK_CONTROL Informuje o naciśnięciu klawisza Ctrl MK_SHIFT Informuje o naciśnięciu klawisza Shift Tabela 8.2. Standardowe ikony systemowe dostępne poprzez funkcję LoadStandardlcon () Znacznik ikony Opis IDI_EXCLAMATION Znak wykrzyknika wewnątrz trójkąta IDI_HAND Znak X wewnątrz czerwonego kółka IDI_APPLICATION Bieżąca ikona przypisana aplikacji I DI_ASTERI SK Litera i oznaczająca informację IDI_QUESTION Znak zapytania Obsługa zdarzeń wywoływanych myszą 189 Po dodaniu do funkcji obsługi kodu umieszczonego na listingu 8.1, skompilowaniu i uruchomieniu programu, otrzymamy w rezultacie najprostszy na świecie program do rysowania. Po naciśnięciu lewego klawisza myszy powinny ukazywać się ikony z wykrzyknikiem, a gdy dodatkowo naciśniemy klawisz Ctrl, rysować będziemy ikony typu IDI_HAND, jak to zostało pokazane na rysunku 8.1. Standardowe ikony Windows 95 i NT 4.0 Używając standardowych ikon systemowych Windows 95 i NT 4.0, zauważyć można, iż nie zawsze wizerunek, który przedstawiają odpowiada ich nazwie. Przykładowo, ikona IDI_HAND (ręka) przedstawia biały krzyżyk na czerwonym tle. Nazwy te, to pozostałość po systemach Windows 3.11 oraz NT 3.51. Zmienione zostały elementy graficzne, nazwy jednak zachowano. Rysunek 8.1. Rysowanie ikon poprzez naciskanie lewego klawisza myszy O Ikona z krzyżykiem rysowana jest po kliknięciu lewym klawiszem myszy, z jednoczesnym użyciem klawisza Ctrl. @ Ikona z wykrzyknikiem rysowana jest na skutek naciśnięcia lewego klawisza myszy. Zauważmy, iż na pasku tytułowym okna wyświetlane są koordynaty punktu, w którym nastąpiło ostatnie kliknięcie. Gdy klikniemy w pobliżu lewego górnego rogu, wyświetlane współrzędne będą się odnosiły do lewego górnego rogu okna dialogowego, a nie ekranu. Badanie współrzędnych ekranowych możemy wykonać poprzez wywołanie 190_____________________________________Poznaj Visual C++ 6 funkcji GetCursorPos (), która może zostać wywołana w każdym momencie dla określenia położenia wskaźnika myszy. Funkcja ta wykorzystuje tylko jeden parametr. Jest nim wskaźnik obiektu cpoint. Aby sprawdzić jej działanie, dodajmy do funkcji obsługi OnL-ButtonDown () bezpośrednio za linią 3 następujący wpis: GetCursorPos(&point) ; Po ponownym skompilowaniu i uruchomieniu programu zauważymy, że obecnie wyświetlane w pasku tytułowym okna współrzędne są koordynatami ekranowymi, a nie okna dialogowego. Ikony również będą wyświetlane według tych współrzędnych. Jednakże funkcja rysująca oczekuje koordynat leżących w obrębie okna, należy zatem usunąć powyższą linię po sprawdzeniu jej działania. Komunikaty o zwolnieniu klawisza myszy mogą być w taki sam sposób przechwyty-wane, jak komunikaty o jego naciśnięciu. Funkcję obsługi takiego komunikatu dodajemy więc w taki sam sposób jak poprzednio, z tą różnicą, iż wybranym komunikatem będzie w tym przypadku WM_LBUTTONUP zamiast WM_LBUTTONDOWN. Po przeprowadzeniu stosownej procedury otrzymamy nową funkcję OnLButtonUp (). Możemy umieścić w niej kod sprawiający wyświetlenie informacji w pasku tytułowym okna, mówiącej o zwolnieniu klawisza: SetWindowText( "Left Button Released") ; Po umieszczeniu tej linii wewnątrz kodu funkcji OnLButtonUp (), skompilowaniu i uruchomieniu programu stwierdzimy, iż nowa funkcja wywoływana jest za każdym razem, gdy wciśnięty klawisz myszy (lewy) zostanie zwolniony. Takie zachowanie pozwala nam na tworzenie aplikacji, w których naciśnięcie klawisza myszy będzie wywoływało określony tryb działania, zwolnienie klawisza natomiast powodować będzie wyłączenie danego trybu. Najlepszym przykładem może być czynność przesuwania okna na pulpicie Windows. Naciśnięcie lewego klawisza myszy, gdy jej wskaźnik znajduje się ponad paskiem tytułowym okna, wywołuje tryb przesuwania. Po zwolnieniu klawisza ustalona zostaje nowa pozycja okna, a jego normalne funkcje powracają do działania. Podobne funkcje istnieją dla klawiszy prawego oraz środkowego. Tabela 8.3 przedstawia wszystkie możliwe zdarzenia związane z użyciem tych klawiszy. Funkcje obsługi dodajemy w taki sam sposób, jak w przypadku lewego klawisza. Funkcje obsługi zdarzeń środkowego klawisza myszy Umieszczanie tych funkcji wymaga nieco zachodu, gdyż Microsoft nie umieścił w narzędziach Visual C++ mechanizmu ich tworzenia. Nie oznacza to oczywiście, że nie można tych funkcji umieścić, należy jednak najpierw utworzyć własnoręcznie mapę komunikatów oraz definicję funkcji. Powoduje to niechęć programistów do pisania aplikacji wykorzystujących środkowy klawisz myszy. Obsługa zdarzeń wywoływanych myszą 191 Tabela 8.3. Komunikaty Windows dotyczące klawiszy myszy Komunikat Funkcja obsługi Opis w AppWizard Naciśnięto lewy klawisz Lewy klawisz został zwolniony Naciśnięto środkowy klawisz Zwolniono środkowy klawisz Naciśnięto prawy klawisz Zwolniono prawy klawisz WM_LBUTTONDOWN OnLButtonDown() WM_LBUTTONUP OnLButtonUp() WM_MBUTTONDOWN Brak WM_MBUTTONUP Brak WM_RBUTTONDOWN OnRButtonDown() WM RBUTTONUP OnRButtonUp() PATRZ TAKŻE • Więcej informacji o rysowaniu w kontekście urządzenia znajduje się w rozdziale 15. * O projektowaniu i edytowaniu okien dialogowych mówimy w rozdziale 3. Przechwytywanie zdarzeń dwukrotnego kliknięcia Podwójne kliknięcie posiada w aplikacjach Windows specjalne znaczenie. Często jest wykorzystywane jako skrót przy wybieraniu pozycji z listy. Gdy użytkownik naciśnie lewy klawisz myszy dwukrotnie w krótkim odstępie czasu, następuje wysłanie do aplikacji komunikatu o zdarzeniu podwójnego klinięcia. System Windows dokonuje oceny odstępu pomiędzy obydwoma kliknięciami i jeśli był to wystarczający okres, wysyła komunikat WM_LBUTTONDBLCLK. Równoznaczne komunikaty obowiązują w przypadku klawiszy pra- wego i środkowego: WM_RBUTTONDBLCLK oraz WM_MBUTTONDBLCLK. Funkcje obsługujące te zdarzenia umieszcza się w kodzie programu w taki sam sposób jak dla lewego klawisza, czyli według procedury w punkcie „Dodanie funkcji obsługi komunikatu lewego klawisza myszy". Spróbujemy teraz zapisać funkcję obsługi zdarzenia podwójnego kliknięcia lewym klawiszem myszy i skojarzyć je z otwarciem okna komunikatu: void CMouseMsgDlg::OnLButtonDblClk(UINT nFlags, CPoint point) ( AfxMessageBox("Left Button Double Clicked"); CDialog::OnLButtonDblClk(nFlags, point) ; ) Po skompilowaniu i uruchomieniu aplikacji, po każdym dwukrotnym kliknięciu lewym klawiszem myszy wyświetlone zostanie okno zawierające komunikat o zaistnieniu tego zdarzenia. Zauważmy, że w przypadkach pozostałych klawiszy wykorzystywane są te same parametry nFlags oraz point, co pozwala na ustalanie wielu różnych kombinacji klawiszy myszy z klawiszami kontrolnymi na klawiaturze. 192 Poznaj Visual C++ 6 Ustalanie prędkości dwukrotnego kliknięcia Odstęp czasu pomiędzy dwoma kliknięciami, który powoduje ich interpretację jako kliknięcie podwójne ustala się za pomocą apletu Mysz w Panelu Sterowania. Wyświetlony jest tam suwak regulujący odstęp oraz pole testowe, służące sprawdzeniu ustawienia. Śledzenie ruchów i położenia wskaźnika myszy Badanie aktualnego położenia wskaźnika myszy jest bardzo przydatne, jednak istnieją sytuacje, gdy należy prześledzić trajektorię jego ruchów wykonywanych myszą przez użytkownika. Program rysunkowy wymaga istnienia takiej procedury, by umożliwić użytkownikowi wykonywanie rysunków „z wolnej ręki". System Windows wychodzi tej potrzebie naprzeciw, wysyłając komunikaty po każdym wykonanym myszą ruchu i przekazujące aktualne położenie wskaźnika. Ograniczenie przetwarzania informacji o ruchach myszy Twórcy oprogramowania zwykle starają się ograniczać przetwarzanie informacji o ruchach myszy. Dzieje się tak dlatego, że ogromna liczba tych komunikatów, generowana podczas wykonywania przez użytkownika wielu szybkich ruchów, powodowałaby obniżenie sprawności działania systemu i rozbieżności pomiędzy aktualnym położeniem wskaźnika myszy a zadaniami wykonywanymi na ekranie. Obsługa zdarzenia przesunięcia wskaźnika myszy Podobnie jak informacje o użyciu klawiszy myszy, informacje o przesunięciu wskaźnika wysyłane są do aplikacji za pomocą odpowiedniego komunikatu: WM_MOUSEMOVE. Funkcję, która obsługiwać będzie ten komunikat, umieszczamy w programie w taki sam sposób jak funkcje dla komunikatów klawiszy. Wykonajmy więc procedurę z punktu „Dodanie funkcji obsługi komunikatu lewego klawisza myszy" opisaną wcześniej, jako komunikat docelowy wybierając WM_MOUSEMOVE. CIassWizard wygeneruje kod nowej funkcji obsługi OnMouseMove (), wypisanej na listingu 8.2. Listing 8.2. LST8_2.CPP - wykorzystanie funkcji OnMouseMove () do wyświetlenia i przechowania pozycji wskaźnika myszy po wykonaniu jej przesunięcia 1 void CMouseMsgDlg::OnMouseMove(UINT nFlags, CPoint point) 2 { 3 // TODO: Dodaj tu kod obsługi komunikatu lub/i 4 5 // ** Wyświetl aktualne współrzędne wskaźnika myszy 6 CString strMessage; 7 strMessage.Format(„Mouse Position = (%d,%d)", 8 point.x,point.y); 9 SetWindowText(strMessage) ; 10 11 // ** PrzecAowaj współrzędne punktu l odśwież okno 12 m ptMouse = point; 13 Invalidate();0 14 15 CDialog::OnMouseMove(nFlags, point); 16 } O Pozycja wskaźnika myszy zostaje zachowana w składowej zmiennej, do późniejszego wykorzystania w funkcji OnPaint (). Należy zauważyć, iż parametry przekazywane funkcji OnMouseMove () są tymi samymi parametrami, co w przypadku funkcji obsługi komunikatów o naciśnięciu klawiszy myszy. Wartość wskaźnika nFlags można tu porównać ze wartościami wymienionymi w tabeli 8.1, w ten sam sposób, jak czyni to funkcja obsługi na listingu 8.1. Drugim parametrem jest point, w którym zapisane są aktualne koordynaty wskaźnika myszy; są one wyświetlane w pasku tytułowym okna dialogowego, co następuje w liniach 6-9 listingu 8.2. Funkcja lnvalidate (), wywołana w linii 13, wymusza odświeżenie widoku okna. Pozycję wskaźnika myszy przechować można w zmiennej składowej CMouseMsgDlg, jak to zostało wykonane w linii 12 listingu 8.2. Po utworzeniu właściwej funkcji musimy jeszcze umieścić w definicji klasy okna dialogowego zmienną składową m_ptMouse. Jest to proste zadanie, które wykonamy według poniższej procedury. Dodanie składowej zmiennej do klasy okna dialogowego 1. Kliknij kartę ClassView w panelu Project Workspace. 2. Kliknij znak + widoczny przy jedynej wyświetlonej pozycji, po czym wyświetlone zostaną klasy użyte w bieżącej aplikacji. 3. W celu otwarcia menu skrótów, kliknij prawym klawiszem myszy pozycję CMouse- MsgDlg. 4. Wybierz opcję Add Member Variable, co wywoła okno dialogowe o tej samej nazwie. 5. W polu Variable Type wprowadź CPoint. 194_____________________________________Poznaj Visual C++ 6 6. Wpisz nazwę zmiennej: m_ptMouse w oknie Variable Declaration. Pozycję Access Type pozostaw niezmienioną, czyli Public Access (umożliwi to dostęp do zmiennej wszystkim obiektom). Gdyby wybrana została pozycja Protected bądź Private, dostęp do nowej zmiennej miałyby tylko obiekty klasy CMouseMsgDIg. 7. Kliknij OK, co zakończy działanie CIassWizard i doda nową zmienną. Dodana właśnie zmienna m_ptMouse może zostać użyta przez funkcję OnPaint () do odświeżenia widoku okna z nowymi współrzędnymi wskaźnika myszy. Dopiszemy kod zamieszczony na listingu 8.3, by umieścić w oknie dialogowym parę oczu, które będą „obserwowały" ruchy wskaźnika myszy na ekranie. Aby dodać ten kod, musimy dwukrotnie kliknąć pozycję Onpaint () na karcie CIassYiew w panelu Project Workspace. Być może konieczne będzie również kliknięcie znaku + widocznego obok pozycji CMo-useMsgDlg, by uzyskać dostęp do funkcji tej klasy. Funkcja Onpaint () zawiera już kod rysujący ikonę aplikacji, gdy funkcja lslconic() zwraca wartość TRUE (okno aplikacji jest zminimalizowane). Powinniśmy to jednak zignorować i dodać własny kod wewnątrz instrukcji else pomiędzy klamrami, tuż za komentarzem // TODO :. Listing 8.3. LST8_3.CPP - rysowanie pary oczu śledzących ruchy wskaźnika myszy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 else ( // ** Utwórz kontekst urządzenia CPaintDC dc(this); O // ** Określ wymiary okna dialogowego CRect rcDIg; GetCIientRect(SrcDIg) ; // ** Wykonaj pętlę osobno dla każdego oka for(int i=0;i<2;i++) ( • // ** Określ pozycję środka oka CPoint ptEye = rcDIg.CenterPoint() ; // ** Ustaw pozycję obu oczu ptEye.x += i==0 ? -80 : +80; @ // ** Mąkę an eye rectangle CRect rcEye(ptEye,ptEye); rcEye.InflateRect(40,60) ; // ** Wypełnij oko białym kolorem dc.SelectStockObject(WHITE_BRUSH) ; dc.Ellipse(rcEye); © Obsługa zdarzeń wywoływanych myszą 195 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 rcEye.DeflateRect(20,40) ; // ** Pobierz pozycję wskaźnika myszy w celu zmiany II ** położenia źrenicy CPoint ptRel = m_ptMouse - rcEye.CenterPoint (); double dX = (double)ptRel.x * (rcEye.Width() / (double)rcDIg.Width()); double dY = (double)ptRel.y * (rcEye.HeightO / (double)rcDIg.Height());© // ** Przesuń źrenicę 2 narysuj ją rcEye.OffsetRect(CPoint((int)dX,(int)dY)) ; dc.SelectStockObject(BLACK_BRUSH) ; dc.Ellipse(rcEye); © CDialog::OnPaint () ; } O Zainicjalizowany zostaje kontekst urządzenia rysującego. @ Początkowa pozycja każdego oka zostaje określona na 80 pikseli od pionowej osi symetrii okna dialogowego. © Funkcja Ellipse () wykreśla białe elipsy oczu. O Bieżąca pozycja wskaźnika myszy zostaje użyta do obliczenia przesunięcia źrenic. © Źrenice zostają odrysowane w oparciu o pozycję wskaźnika myszy. Na powyższym listingu w linii 4 spotykamy się z obiektem kontekstu urządzenia cpa-intDC. Zagadnienia związane z kontekstem urządzenia zostaną omówione w rozdziale 15. Wywołana w linii 8 funkcja GetdientRect () służy do określenia wymiarów okna dialogowego, które zostają zapisane w obiekcie rcDig zadeklarowanym w Unii7, a należącym do CRect. CRect to klasa przechowująca dwa obiekty CPoint, jeden dla koordynat górnej i lewej, drugi natomiast dla dolnej i prawej strony. Dostarcza również kilku przydatnych funkcji służących do określania punktu środkowego, szerokości i wysokości oraz zmniejszania i zwiększania wymiarów okna. W linii 11 rozpoczęta zostaje pętla, wykonywana dwukrotnie, osobno dla każdego oka. Określa ona rozmiary prostokąta mieszczącego każde oko (w linii 14) i umieszcza je po obu stronach osi symetrii okna dialogowego (linia 17). W linii 20 z kolei określony jest ostateczny rozmiar, a w liniach 24-25 narysowane są białe elipsy. W linii 26 określone zostają wymiary źrenicy. 196 _Poznaj Visual C++ 6 Bardzo interesująca jest linia 29. W linii tej bowiem aktualne położenie wskaźnika myszy użyte zostaje do wyliczenia pozycji źrenicy każdego z oczu. Przesunięcie ich zostaje obliczone w liniach 30-36, a linie 37-38 powodują narysowanie źrenic w nowym położeniu. Nie zajmowaliśmy się tutaj funkcjami graficznymi, gdyż omówione zostaną one w rozdziale 16. Po skompilowaniu i uruchomieniu aplikacji w oknie dialogowym widoczne będzie dwoje oczu ze źrenicami przesuwającymi się wraz ze wskaźnikiem myszy (rysunek 8.2). Rysunek 8.2. Dwoje oczu podąża za wskaźnikiem myszy podczas przesuwania go wewnątrz okna dialogowego PATRZ TAKŻE • Więcej informacji o działaniu funkcji OnPain t () znajduje się w rozdziale 15. ^ Jak rysować za pomocą piór i pędzli mówimy w rozdziale 16. Przechwytywanie wskaźnika myszy Po uruchomieniu aplikacji zawierającej kod z listingu 8.3 zorientujemy się, iż pojawia się pewien problem. Wszystko pracuje świetnie do momentu, gdy wskaźnik myszy znajdzie się poza obszarem okna dialogowego aplikacji. Dzieje się tak, ponieważ komunikaty o wykonaniu ruchu myszą wysyłane są tylko wtedy, gdy jej wskaźnik pozostaje w obrębie okna. Kiedy jednak wskaźnik wykroczy poza ten obszar, komunikaty odbierane będą przez okno innej aplikacji. Istnieje pewna technika umożliwiająca w takiej sytuacji kierowanie komunikatów do właściwej aplikacji, zwana (ang. mouse capturing) przechwytywaniem myszy. Przechwytywanie owo realizuje się bardzo prosto, za pomocą funkcji SetCapture () z okna aplikacji, które ma przechwycenie wykonać. Jednakże ten proces jest „aspołeczny" Obsługa zdarzeń wywoływanych myszą 197 w stosunku do innych aplikacji, które w normalnych warunkach przechwytywałyby mysz. W związku z tym, należy za każdym razem wywoływać funkcję ReleaseCapture (), która powoduje powrót do normalnej sytuacji. Zasady rządzące przechwytywaniem myszy Tylko okno znajdujące się na najwyższym poziomie może przechwytywać mysz. Jeśli któreś z okien niższego poziomu próbuje przechwycenia, otrzyma informacje dotyczące tylko widocznej jego części, nieprzesłoniętej przez inne okna. Tylko jedna aplikacja może w danym momencie wykorzystywać ten mechanizm. Najwłaściwszymi miejscami na umieszczenie obu funkcji są odpowiednio funkcje obsługi zdarzeń naciśnięcia klawisza myszy oraz jego zwolnienia. Do listingu 8.3 musimy więc wprowadzić pewne zmiany. Do funkcji OnLButtonDown () dopiszemy taki kod: void CMouseMsgDlg::OnLButtonDown(UINT nFlags, CPoint point) ( SetCaptureO ; CDialog::OnLButtonDown(nFlags, point) ; } Po wprowadzeniu tej poprawki mysz będzie przechwytywana przez okno dialogowe aplikacji za każdym razem, gdy naciśnięty zostanie lewy klawisz. Odwołanie przechwy-tywania następować będzie z kolei po wywołaniu funkcji ReleaseCapture (), umieszczonej w funkcji obsługi OnLButtonUpO . To również wymaga wprowadzenia dodatkowego kodu: void CMouseMsgDlg::OnLButtonUp(UINT nFlags, CPoint point) { ReleaseCapture () ; CDialog::OnLButtonUp(nFlags, point) ; } Można już skompilować i uruchomić program. Kiedy naciśniemy lewy klawisz myszy, gdy jej wskaźnik znajduje się w obszarze okna dialogowego, możemy tenże wskaźnik przesuwać poza obszar okna, po całym ekranie. Oczy będą cały czas podążały za wskaźnikiem. Oznacza to, iż okno dialogowe wciąż odbiera komunikaty WM_MOUSEMOVE, nawet gdy wskaźnik myszy znajduje się poza jego obrębem. Zauważmy również, że koordynaty wskaźnika będą wyświetlane jako ujemne, gdy znajdzie kursor się po lewej stronie lub powyżej okna dialogowego. Dzieje się tak dlatego, ponieważ pozycja wskaźnika jest cały czas obliczana w odniesieniu do lewego, górnego rogu okna dialogowego aplikacji. Po zwolnieniu klawisza wywołana zostaje funkcja ReleaseCapture (), a oczy powracają do zwykłego trybu pracy, co oznacza, iż przechwytywanie myszy zostało odwołane. 198_____________________________________Poznaj Visual C++ 6 Ustalanie obszaru testowania Jednym z normalnych warunków swobodnego operowania myszą jest umożliwienie sprawdzenia, czy w ustalonym obszarze nastąpiło kliknięcie. Przykładowo, aby spowodować kuknięciem zmianę koloru wypełniającego owal oka w bieżącym przykładzie, musimy ustanowić procedurę sprawdzającą. Punkt wskazujący kursora Tylko jeden piksel spośród składających się na obraz kursora (wskaźnika myszy) reprezentuje aktualne jego położenie. Jest to piksel stanowiący wierzchołek grota strzałki. Punktem tym może być każdy inny piksel ustanowiony podczas tworzenia kursora. Gdy piksel ten znajdzie się w obszarze testowania, procedura sprawdzająca zwraca TRUE. Procedura ta nie jest niczym więcej, jak tylko sprawdzeniem, czy punkt, w którym nastąpiło kliknięcie leży wewnątrz określonego obszaru. Po pierwsze, zachowane muszą zostać współrzędne punktu, w którym użytkownik ostatnio kliknął. Jest to przypadek przypisania parametru point, przekazanemu funkcji obsługi OnLButtonDown (), do zmiennej składowej: void CMouseMsgDlg::OnLButtonDown(UINT nFlags, CPoint point) ( SetCapture() ; m ptButton = point; // **Przechowanie przekazanych współrzędnych } Musimy dodać zmienną m_ptButton do klasy CMouseMsgDlg w taki sam sposób, jak w przypadku określania położenia wskaźnika myszy, wykonując procedurę dodawania zmiennej składowej do klasy okna dialogowego, nadając zmiennej nazwę m_ptButton (typ zmiennej to CPoint). Mając zachowane współrzędne ostatniego kliknięcia, możemy sprawdzić, czy m_pt-Button leży wewnątrz jednego z oczu, zmieniając zapis Onpaint () zaraz za linią komentarza // ** Wypełnij oko białym kolorem w podany poniżej sposób: if (rcEye.PtInRect(m_ptButton)) dc.SelectStockObject(GRAY_BRUSH) ; else dc.SelectStockObject(WHITE_BRUSH) ; Należąca do CRect funkcja składowa ptlnRect () zwraca wartość TRUE, gdy punkt kliknięcia (w obecnym przypadku zapisany w m_ptButton) wypada wewnątrz sprawdzanego prostokąta rcEye. Jeśli warunek ten jest spełniony, zostaje użyty obiekt GRAY_BRUSH w miejsce WHITE_BRUSH i oko wypełnione zostaje szarym kolorem. Obsługa zdarzeń wywoływanych myszą 199 Gdy skompilujemy i uruchomimy program, kliknięcie wewnątrz któregoś z oczu po- wodować będzie zmianę koloru jego wypełnienia na szary, jak zostało to pokazane na rysunku 8.3. Jeśli klikniemy wewnątrz drugiego oka, stanie się ono szare, a pierwsze powróci do koloru pierwotnego, gdyż punkt kliknięcia wypada wewnątrz innego obszaru. Z kolei kliknięcie gdziekolwiek poza obszarem oczu spowoduje ponowne wypełnienie ich białym kolorem, gdyż w tym wypadku punkt kliknięcia znajduje się poza ich obszarami. Rysunek 8.3. Kliknięcie wewnątrz obszaru oka powoduje zmianę koloru wypełnienia z białego na szary PATRZ TAKŻE • O stosowaniu pędzli (brush) mówimy więcej w rozdziale 16. Stosowanie klasy CRectTracker W wielu aplikacjach do zaznaczania większej liczby elementów posłużyć się możemy tzw. „gumową taśmą". Klasą, która umożliwia nam taką operację, jest CRectTracker. Używa ona kodu, dzięki któremu sama przeprowadza przechwytywanie myszy, pozwalając użytkownikowi na oznaczanie dowolnych obszarów, a aplikacji na sprawdzenie, czy punkty kontrolne znajdują się wewnątrz tego obszaru. Wykorzystamy obiekt tej funkcji w naszym przykładowym programie, w którym posłuży ona do zakreślania za pomocą gumowej taśmy obszaru wokół oczu, następnie do sprawdzenia, które oko znajduje się wewnątrz wykreślonego prostokąta. Aby dodać zmienną składową CRectTracker, o nazwie m_RectTracker, do klasy CMouseMsgDlg, musimy wykonać poznaną już procedurę dodawania nowej zmiennej do klasy okna dialogowego. 200 Poznaj Visual C++ 6 Pierwszą rzeczą, którą musimy wykonać, jest inicjalizacja obiektu rect, z parametrami początkowymi wykreślanego prostokąta oraz stylu. Właściwy kod umieścimy w miejscu inicjalizacji klasy CMouseMsgDlg, w konstruktorze funkcji: CMouseMsgDlg::CMouseMsgDlg(): CMouseMsgDlg::CMouseMsgDlg(CWnd* pParent /*=NULL*/ ) , : CDialog(CMouseMsgDlg::IDD, pParent) , m_RectTracker(CRect(O,0,0,0) , // **Prostokąt początkowy CRectTracker::hatchedBorder + // * * Krę skowanie obwódki CRectTracker::resizeOutside) // **Uchwyty wymiarowania na zewnątrz obszaru Dostęp do tej funkcji uzyskamy klikając dwukrotnie pozycję CMouseMsgDlg (), należącą do klasy CMouseMsgDlg widocznej na karcie ClassView, w panelu Project Workspace. Dodatkowe linie komentarza pokazują, jak zostaje zainicjalizowana zmienna składowa m_RectTracker z początkowymi współrzędnymi i wymiarami prostokąta równymi zero, w związku z czym nic nie jest zaznaczone. Drugim parametrem konstruktora CRectTracker jest wyznacznik stylu, który może być kombinacją znaczników wymienionych w tabeli 8.4. Styl CRectTracker: :hatchedBorder oznacza wykreślanie grubej, kreskowanej obwódki i jest połączony z CRectTracker::resizeOutside. Tabela 8.4. Znaczniki stylu CRectTracker Znacznik stylu Opis CRectTracker: : solidLine Podczas zaznaczania obszaru za pomocą „gumowej taśmy" wykreśla prostokąt linią ciągłą CRectTracker: : dottedLine Jak wyżej, jednak z użyciem linii kropkowanej CRectTracker: : hatchedBorder Rysuje grubą kreskowaną linię CRectTracker: : hatchinside Wypełnia kreskowaniem zaznaczony obszar CRectTracker:: resizeinside Uchwyty wymiarowania umieszczone zostają wewnątrz wykreślonego prostokąta CRectTracker: : resizeOutside Uchwyty wymiarowania umieszczone zostają na zewnątrz wykreślonego prostokąta Operacja zaznaczania za pomocą „gumowej taśmy" rozpoczynana jest zwykle od naciśnięcia przez użytkownika lewego klawisza myszy, zatem funkcja obsługi OnLButton-Down () jest właściwym miejscem rozpoczęcia tego procesu. Cały proces użycia „gumowej taśmy" obsługiwany jest przez funkcję składową klasy CRectTracker, Trac-kRubberBand (). Dodajmy więc poniższy kod do treści funkcji OnLButtonDown (): Obsługa zdarzeń wywoływanych myszą 201 void CMouseMsgDlg::OnLButtonDown(UINT nFlags, CPoint point) ( m_RectTracker.TrackRubberBand(this, point, TRUE); CDialog::OnLButtonDown(nFlags, point); ) Jak widzimy, funkcja TrackRubberBand () wymaga podania trzech parametrów. Pierwszy z nich jest wskaźnikiem okna, w którym użyty zostanie mechanizm „gumowej taśmy". Ponieważ oknem tym będzie okno dialogowe naszej przykładowej aplikacji, należy przekazać funkcji specjalny wskaźnik C++, this, by wskazać obiekt MouseMsgDlg (wyprowadzony z klasy cwnd, będący w związku z tym obiektem okna). Drugim parametrem jest lewy górny punkt prostokąta wykreślanego za pomocą „gumowej taśmy" jako punkt początkowy. W tym przypadku przypisać możemy punkt kliknięcia klawiszem myszy. Trzeci parametr jest opcjonalny. Jeśli jego wartość wynosi TRUE, za pomocą „gumowej taśmy" można zaznaczać również obszary leżące po lewej stronie i powyżej punktu początkowego. W przeciwnym wypadku zaznaczać można obszary leżące jedynie po prawej stronie i poniżej punktu początkowego. Wykorzystanie „gumowej taśmy" do własnych zadań Normalne operacje wykonywane za pomocą klasy CRectTracker można zamienić na inne poprzez wyprowadzenie z niej własnej, zmienionej wersji tej klasy. Istnieje kilka funkcji wirtualnych, takich jak DrawTrackerRect (), powodujących określony sposób obrysowania oznaczanego obszaru, które można wbudować we własną klasę. Poprzednio umieszczaliśmy wewnątrz funkcji OnLButtonDown () funkcję przechwy-tywania myszy setCapture (), którą musimy w tej chwili usunąć. Obiekt rect bowiem ustanawia własny mechanizm przechwytywania, który pozostawałby w konflikcie z poprzednim. Musimy usunąć również powiązany wpis w funkcji OnLButtonUp (), czyli ReleaseCapture(). void CMouseMsgDlg::OnLButtonUp(UINT nFlags, CPoint point) ( CDialog::OnLButtonUp(nFlags, point) ; } Na koniec należy jeszcze zmodyfikować w podany poniżej sposób treść funkcji OnPa-int (), by mogła sprawdzać wartość składowej obiektu rect. Klasa CRectTracker posiada własną funkcję HitTestO, która może zostać wywołana poprzez przekazanie obiektu CPoint do sprawdzenia: 202 Poznaj Visual C++ 6 // ** Wypełnij oko białym kolorem if (m_RectTracker.HitTest(rcEye.CenterPoint() ) !=CRectTracker::hitNothi ng)/ dc.SelectStockObject(GRAY_BRUSH) ; else dc.SelectStockObject(WHITE_BRUSH) ; Poprzez przekazanie rcEye. CenterPoint () do funkcji HitTestO dokonujemy sprawdzenia, czy punkt środkowy oka wypada wewnątrz wykreślonego prostokąta. Funkcja HitTest () zwróci CRectTracker: :hitNothing (-1), jeśli prostokąt nie obejmuje żadnego z tych punktów. W przeciwnym wypadku wartość zwrócona może przyjąć jedną z postaci wymienionych w tabeli 8.5, identyfikując położenie sprawdzanego punktu w wykreślonym prostokącie. Normalnie jednak używane są dwie z poniższych wartości: CRectTracker: :hitMiddle, gdy prostokąt obejmie swym zasięgiem punkt środkowy oka oraz CRectTracker:: hitNothing, kiedy tak nie jest. Tabela 8.5. Wartości zwracane przez funkcję HitTest () Zwracana wartość Wartość Znaczenie CRectTracker::hitNothing - l CRectTracker::hitTopLeft Q CRectTracker;:hitTopRight l CRectTracker::hitBottomRight 2 CRectTracker::hitBottomLeft 3 CRectTracker::hitTop 4 CRectTracker::hitRight 5 CRectTracker::hitBottom 6 CRectTracker::hitLeft 7 CRectTracker::hitMiddle g Punkt nie został objęty zasięgiem prostokąta Punkt wypada w lewym górnym rogu prostokąta Punkt wypada w prawym górnym rogu prostokąta Punkt wypada w prawym dolnym rogu prostokąta Punkt wypada w lewym dolnym rogu prostokąta Punkt wypada u góry prostokąta Punkt wypada po prawej stronie prostokąta Punkt wypada u dołu prostokąta Punkt wypada po lewej stronie prostokąta Punkt wypada pośrodku prostokąta Po skompilowaniu i uruchomieniu programu z wprowadzonymi zmianami można wy- kreślać prostokąt za pomocą „gumowej taśmy", przy użyciu myszy z wciśniętym lewym klawiszem. Prostokąt ten, służący do selekcji obiektów wewnątrz jego obszaru, możemy Obsługa zdarzeń wywoływanych myszą 203 wymiarować do żądanych rozmiarów przytrzymując klawisz myszy. Po jego zwolnieniu rozmiary prostokąta zostaną zatwierdzone, a obiekty wewnątrz prostokąta zaznaczone. W bieżącym przykładzie, gdy punkty środkowe oczu lub jednego z nich, znajdą się wewnątrz wykreślonego prostokąta, zostaną wyselekcjonowane, a kolor ich wypełnienia zmieni się na szary, jak na rysunku 8.4. Rysunek 8.4. Użycie CRectTracker do zaznaczania obszarów za pomocą „gumowej taśmy" Rozdział 9 Stosowanie kontrolek ActiveX Umieszczanie zaawansowanych kontrolek ActiveX we własnym projekcie Konfigurowanie właściwości i stylu kontrolek Użycie w programie danych pochodzących z kontrolek Cały wysiłek programistów ubiegłej dekady skoncentrowany był w dużej mierze na rozwiązywaniu problemu ponownego wymyślania koła i trwał przez całe lata 80-te. Techniki takie jak programowanie obiektowe oraz COM/OLE (Component Object Model/ Object Linnking Embedding) są wynikiem tych wysiłków. W obecnej chwili możemy zakupić bardzo zaawansowane, gotowe obiekty w postaci kontrolek ActiveX i w łatwy sposób zintegrować je z własną aplikacją, zaoszczędzając tym samym długie godziny, dni, a może nawet i miesiące pracy programistycznej. Dostępnych jest również wiele kontrolek za darmo, a niektóre rozprowadzane są jako oprogramowanie typu shareware. Jeśli jednak ktoś chciałby pisać własne kontrolki, nie ma problemu, można je później rozpowszechnić jako darmowe lub sprzedać innym twórcom oprogramowania. PATRZ TAKŻE • O tworzeniu własnych kontrolek ActiveX możesz przeczytać w rozdziale 26. • By dowiedzieć się więcej na temat COM oraz OLE przejdź do rozdziału 25. Kontrolki ActiveX a Intemet Dzięki rozwojowi technologii Active Server Pages, kontrolki ActiveX oraz skrypty serwera mogą być umieszczane na stronach Web w celu zwiększenia ich atrakcyjności i rozszerzenia możliwości przeglądarek. 206 Poznaj Visual C++ 6 Wybór kontrolek ActiveX z galerii Component Gallery Component Gallery jest kolekcją gotowych komponentów, które możemy pobrać i włączyć do własnej aplikacji w celu rozszerzenia jej możliwości. Visual C++ dostarczany jest wraz z wieloma obiektami ActiveX, które dołączać można do swej aplikacji w każdym momencie. Kontrolki ActiveX oparte są o technikę COM, w związku z czym można je wy- korzystywać nie tylko w Visual C++, ale także w Visual Basie czy Visual J++, jak również w każdym innym języku programowania, który umożliwia współpracę z obiektami COM. Z tego powodu dokumentacja tych kontrolek posługuje się najczęściej przykładami zaczerpniętymi z Visual Basica. Jednakże w Visual C++ obowiązują dokładnie te same metody. Przeglądanie kontrolek ActiveX Aby sprawdzić działanie kontrolek ActiveX w działaniu, utworzymy nową aplikację opartą o architekturę okna dialogowego, w którym będziemy mogli następnie umieścić takie obiekty. Naszą nową aplikację, utworzoną za pomocą AppWizard nazwiemy ActiveX. Po utworzeniu aplikacji możemy obejrzeć listę zarejestrowanych w systemie kontrolek ActiveX, wykonując poniższe czynności: Przeglądanie kolekcji kontrolek ActiveX w Component Gallery 1. Kuknij menu Project, a z niego wybierz pozycję Add to Project. 2. Z menu podrzędnego wybierz Components and Controls. 3. Kliknij dwukrotnie katalog Registered ActiveX Controls. 4. Powinieneś ujrzeć okno listy podobne do widocznego na rysunku 9. l. -3 Ej a-J fiijHJ Ctioose a component to inserttntoyourproject Look )n: j -^ Registered Actr^eX Cantrols llffiDBGnd Central S3Marqu@eCtl Obje O IsllMicrosoflAnimation Confrol vefsion 5.0 SSMIcrosottCommon Dialog CorHrol.version 5.0 ^Miciosott Communicotions Control, veision 5.0 BMicrosott DBCombo Control. ',/er^.ion c BMiaosott DBList Control. version 5.0 : iMicrosott Fle?(Grid ConIroL yersion 5.0:; l KMicrosofl Forms 2.0 CheckBon 1 |)MicrosoftFoiTns2.0 ComboBox :| .iMicfosoflFormsS.OComrriaridBu tton s ^MicrosoftFDrms2.0Frame '.. fiMicrosoft Folms 2.0 Image Filenome^ |CalendafConfrol B.O.Ink O Rysunek 9.1. Zarejestrowane w systemie kontrolki ActiveX wyświetlone w Component and Controls Gallery Stosowanie kontrolek ActiveX 207 O Wykonywalny kod kontrolek ActiveX zapisywany jest w plikach .ocx (widoczny tu kalendarz dołączany jest do pakietu MS Office 97, lecz może to być również wersja wcześniejsza lub późniejsza, w zależności od zainstalowanego oprogramowania), Zauważmy, iż w oknie Components and Controls Gallery wylistowane obiekty są wyświetlane jako typowe skróty Windows Explorera. Nazwa pliku, w którym zapisany jest kod danego obiektu, wyświetlona zostaje w polu Path to Control u dołu okna. Kody kontrolek ActiveX zapisywane są obecnie w plikach .ocx, jak również .DLL. W przypadku dystrybucji naszego oprogramowania musimy pamiętać zatem o dołączeniu tych plików do innych, zawierających kod aplikacji i zarejestrowaniu ich w systemie. Obiekty kontrolne mogą zostać zarejestrowane przez program regsvr32.exe, wywołanym poleceniem Run z menu systemowego Start lub z linii poleceń: regsvr32 C:\Windows\System\mscal.ocx Procedura ta dokona zarejestrowania obiektu w systemie i wymagana jest w przypadku instalowania nowego oprogramowania. Obiekty widoczne na liście Components and Controls Gallery są obiektami już zarejestrowanymi. Po wybraniu interesującego nas obiektu, skorzystać możemy z uaktywnionego przycisku Morę Info. Jego użycie spowoduje wyświetlenie systemowego okna pomocy, zawierającego informacje o obiekcie i sposobie jego wykorzystania. Z informacji tam zawartych dowiedzieć się możemy, iż kontrolki ActiveX, podobnie do innych obiektów sterujących, posiadają właściwości, metody i zdarzenia. Konfiguracja właściwości umożliwia zmianę wizualnych aspektów obiektu, metody pozwalają na wymianę informacji z innymi obiektami, a zdarzenia wywołują nasze własne kody, które współdziałają z kontrolkami (np. w przypadku naciśnięcia przycisku). Włączanie nowego obiektu do bieżącego projektu Aby umieścić obiekt kalendarza w oknie dialogowym naszej aplikacji, musimy w oknie Components and Controls kliknąć reprezentującą go pozycję Calendar Control 8.0. Po zaznaczeniu właściwej pozycji, należy kliknąć przycisk Insert, po czym ukaże się okno z zapytaniem Insert This Component? (Czy umieścić ten komponent?), na co odpowiadamy kuknięciem OK. Kolejne okno dialogowe, które zostanie wyświetlone, przedstawione jest na rysunku 9.2. Podaje ono nazwy plików, które zostaną włączone do projektu, w celu zaimplemen-towania klas interfejsu niezbędnych do działania kontrolki. Wewnątrz tego okna możemy zmieniać nazwy zarówno klas, jak i dołączanych plików. W normalnych sytuacjach jednak nie czyni się tego; jedynie gdy dołączane pliki lub klasy pozostają w konflikcie nazw z innymi, już wykorzystywanymi. Możemy także zmienić selekcję dołączanych klas, lecz w większości wypadków należy pozostawić wszystko w stanie, jaki zaproponuje program. 208 Poznaj Visual C++ 6 Rysunek 9.2. Okno dialogowe Confirm CIasses umożliwia zmianę nazw plików implementacyjnych Gdy zatwierdzimy bieżący wybór i klikniemy OK, wymienione klasy wraz z ich plikami implementacyjnymi zostaną dołączone do projektu. Od tej chwili nowe klasy powinny być uwidocznione na karcie ClassView w panelu Project Workspace. Klasy ccalendar i coleFont dostarczają nam jedynie atrap funkcji dla obiektu kalendarza i użytych w nim czcionek. Czym są atrapy funkcji? Jeśli klikniemy znak plusa widoczny obok pozycji ccalen-dar w celu wyświetlenia funkcji składowych tej klasy, a następnie funkcję GetBackCo-lor (), zobaczymy że jej implementację stanowi zaledwie kilka linii w edytorze: unsigned long CCalendar::GetBackColor() ( unsigned long result ; InvokeHelper(DISPID_BACKCOLOR , DISPATCH_PROPERTYGET , VT_I4, (void*)&result , NULL) ; return result ; Pozostałe funkcje mają postać podobną do powyższej. Są to właśnie atrapy funkcji lub inaczej funkcje odsyłające. Możemy je wywoływać w taki sam sposób jak zwykłe funkcje, jednakże ich właściwy kod implementacyjny zapisany jest w plikach .OCX lub .DLL. Te atrapy funkcji używają interfejsu COM do wywoływania właściwego kodu, za pomocą funkcji invokeHelper(). Parametry przekazywane funkcji lub z niej pobierane zostają skon-wertowane na parametry variant i wysyłane do lub odbierane z właściwego kodu. Takie rozwiązanie przynosi nam trzy korzyści. Po pierwsze, umożliwia zmniejszenie rozmiarów naszej aplikacji, gdyż do własnego kodu nie musimy włączać pełnego zapisu funkcji, a jedynie ich trzon. Po drugie, zapewnia naszemu kodowi kompatybilność z przyszłymi wersjami użytych kontrolek (zakładając, iż ich autor utrzyma dotychczasowy interfejs). Możemy w związku z tym uaktualniać użyte kontrolki bez konieczności tworzenia nowej wersji aplika- cji. Po trzecie wreszcie, zapewnia to ochronę własności intelektualnej autora obiektu, gdyż właściwy kod jest dla nikogo niewidoczny. r mm. \ HIV.C ^ By dowiedzieć się, co oznaczają skróty COM oraz OLE, przejdź do rozdziału 25. Kontrolki VBX i Visual Basie są kompatybilne Kontrolki Visual Basie są przodkami kontrolek OCX i choć funkcjonują odmiennie, mogą być użyte przez 16-bitowy kompilator Visual C++. Kontrolki VBX nie są jednak kompatybilne z 32-bitowym C++ i programy 32-bitowe muszą korzystać z kontrolek OCX (ActiveX). Wybór, skalowanie i testowanie kontrolek ActiveX z paska narzędziowego Controls Oprócz umieszczenia w projekcie klas interfejsu dla nowych obiektów, dodana zostaje do paska narzędziowego Controls w edytorze zasobów nowa ikona, reprezentująca kon-trolki ActiveX. Umieszczenie kontrolki ActiveX w oknie dialogowym Kontrolka kalendarza jest już gotowa do użycia w oknie dialogowym naszej aplikacji. Otwórzmy zatem panel ResourceView, w którym widoczne są rodzaje zasobów użytych w projekcie ActiveX. Po otwarciu katalogu Dialog, dwukrotnym kuknięciem pozycji IDD_ACTIVEX_DIALOG w oknie edytora zasobów otwarty zostanie szablon okna dialogowego aplikacji. Zwiększamy wymiary szablonu i usuwamy widoczny pośrodku napis TODO: Place dialog controls here. Na pasku narzędziowym Controls powinna być widoczna nowa ikona, wskazana na rysunku 9.3. 1 >t @ Aa abl Q a lx (?. II m a § ^ EE3 fl- E^ ^ F:' Q H o————^S— Rysunek 9.3. Nowa pozycja, ActiveX Calendar, umieszczona na pasku narzędziowym Controls O Ikona kontrolki Calendar Control 8.0. 210_____________________________________Poznaj Visual C++ 6 Kontrolkę tę możemy umieścić na szablonie w taki sam sposób, jak wszelkie inne kontrolki, czyli przeciągając ją z paska narzędziowego bezpośrednio w obręb szablonu. Także operacja wymiarowania przebiega tak samo, jak w wypadku innych obiektów. Prawdopodobnie będzie konieczne zwiększenie rozmiarów nowego obiektu w bieżącym przykładzie. Dodawanie kontrolek ActiveX z edytora Zamiast wybierać kontrolki ActiveX z kolekcji Component Gallery, jeśli znamy obiekt, o który nam chodzi, możemy go pobrać bezpośrednio z edytora. Poprzez kliknięcie prawym klawiszem myszy w obrębie szablonu okna dialogowego otwieramy menu skrótów, w którym znajdujemy pozycję Insert ActiveX Control. Jej wybranie spowoduje otwarcie okna dialogowego o tym samym tytule. Okno to wyświetla listę kontrolek ActiveX zainstalowanych w systemie, które możemy wybrać stąd bezpośrednio i umieścić w oknie dialogowym tworzonej aplikacji. Z listy tej wybierzmy Microsoft MCI Control, version 5 (pamiętajmy, iż numer wersji mógł ulec zmianie). Gdy klikniemy OK, na szablonie okna dialogowego powinien pojawić się nowy obiekt. Będzie to obiekt typu MCI (Media Control Interface), który podczas prac projek- towych nad 'oknem dialogowym ma postać białego prostokąta. Przesuniemy go w lewy dolny róg okna. Przy użyciu tej metody interfejs dla tego obiektu nie został dodany. Możemy to jednak nadrobić za pomocą CIassWizard, przypisując obiektowi zmienną składową, co zostanie opisane nieco dalej. Kontrolka typu MCI daje nam do dyspozycji zestaw przycisków, które możemy połączyć z urządzeniami multimedialnymi, na przykład waveaudio dla odtwarzania plików ,WAV, cdaudio dla odtwarzania CD bądź digitalvideo, odtwarzający sekwencje wideo. Testowanie kontrolki w edytorze okna dialogowego Funkcjonowanie obydwóch umieszczonych w oknie dialogowym kontrolek możemy przetestować korzystając ze zwykłego trybu testującego edytora. Z menu Layout wybierzemy zatem pozycję Test lub użyjemy skrótu klawiaturowego Ctri+T. Powinniśmy ujrzeć okno dialogowe takie jak na rysunku 9.4, zawierające umieszczone działające kontrolki ActiveX. PATRZ TAKŻE • Więcej szczegółów na temat edycji okien dialogowych znajdziesz w rozdziale 3. • By dowiedzieć się więcej o MCI i stosowaniu bibliotek API, zajrzyj do rozdziału 28. Stosowanie kontrolek ActiveX 211 Zmiana właściwości kontrolki w edytorze zasobów Tak jak w przypadku innych obiektów sterujących, właściwości kontrolek ActiveX mogą być zmieniane w panelu właściwości, podzielonym na kilka kart. Właściwości można zmieniać również w sposób programowy, używając do tego funkcji klasy interfejsu, gdy do obiektu zostanie przypisana zmienna. Okno właściwości możemy otworzyć naciskając Alt+Enter lub wybierając pozycję Properties z menu skrótów. Ustalanie właściwości standardowych Korzystając z karty Generał podobnie jak w przypadku innych obiektów, możemy ustalić identyfikator dla nowej kontrolki oraz dokonać kilka innych ustawień, korzystając z dostępnych pól wyboru. Klikniemy zatem prawym klawiszem myszy kontrolkę kalendarza, a z menu skrótów wybierzmy pozycję Properties Calendar Object, co otworzy okno właściwości tego obiektu. Zmienimy identyfikator ID z IDC_CALENDARI na IDC_CALENDAR. Podobnie w oknie właściwości obiektu MCI zmienimy jego identyfikator na IDC_MMCONTROL. Korzystanie z kart właściwości Pozostałe karty umożliwiają dokonanie zmian właściwości specyficznych dla danego typu obiektu. W oknie właściwości kalendarza wybierzemy kartę Control. Ujrzymy na niej kilka charakterystycznych dla tego obiektu opcji, widocznych na rysunku 9.5. Rysunek 9.5. Właściwości kontrolki kalendarza Opcje te wpływają na wygląd i styl kontrolki. Spróbujmy zatem pozmieniać kilka z nich, obserwując zachodzące w obiekcie zmiany (być może trzeba będzie przesunąć nieco okno właściwości, aby nie przesłaniało widoku kontrolki). Następnie wybierzemy kartę Font. Zauważmy, iż umożliwia ona zmianę zarówno kroju, jak i rozmiaru stosowanej czcionki użytej w obiekcie. Po wybraniu karty Color, stwierdzimy, iż wpływać możemy na użyte kolory. Ostatnia z kart Ali, zawiera listę właściwości. Listę taką posiada każda kontrolka Ac- tiveX. Lista generowana jest na podstawie zamieszczonych właściwości OLĘ, a wymie- 212 Poznaj Visual C++ 6 nione opcje dostępne są w klasach obiektu przy użyciu funkcji Get i Set dla każdego typu właściwości. W związku z tym na przykład właściwość BackColor posiada dwie kore- spondujące funkcje GetBackColor () oraz SetBackColor (), których możemy użyć do zmiany wartości na drodze programowej. Zamkniemy okno właściwości kalendarza i klikniemy prawym klawiszem myszy obiekt MCI, a następnie z menu skrótów wybierzemy Properties MMControl Objęci, co otworzy okno właściwości tego obiektu. Otwierając karty Generał i Ali stwierdzimy, że są one podobne do tych samych kart właściwości obiektu kalendarza, pozostałe karty mają jednak zdecydowanie inną zawartość. Gdy otworzymy kartę Control właściwości obiektu MCI, zobaczymy, iż każdy przycisk użyty w tym obiekcie może być indywidualnie uaktywniany i ukrywany. Usuwamy znaczniki z pól wyboru EjectVisible i RecordVisible (jak na rysunku 9.6). Zamykamy okna właściwości i naciskamy CtrI+T dla ponownego przetestowania okna dialogowego aplikacji. Zauważmy, iż z zestawu przycisków zniknęły dwa spośród nich, a cały panel stał się odpowiednio mniejszy. Microsoft MCI Ctintrol. versian 5,0 Propertifis •W 1 ? Ga fiefal | Controt ^ Contiols j F PrevEnabled ff Pre\Msible F He^Enobled f? Neyf/tsible r StepEnabted f7 SlepV'isibIe r &ackEnabled ^ Bad-Yisible r PflUseEnabled p' PauseYisible PiCt ure Ali r pis 17 Pla FBe r RS r so [y sio rEje r Eje lyEnabled styYisibte cordEnabled cofdYisible pEnabled pVisibie c1En obleci cłV] Eible K >• >1 9 1^ • -<1 A 11 Rysunek 9.6. Karta Controls właściwości obiektu MCI pozwala na ustalenie widocznego zestawu przycisków Stosowanie klas dostarczonych z kontrolką Dokonaliśmy zatem pewnych zmian w obrębie kontrolek ActiveX, nie dopisując ani jednej linii kodu, a mimo to możemy skompilować i uruchomić program oraz przetestować sposób wyświetlania obiektów oraz ich interakcje z użytkownikiem. Jak w przypadku innych kontrolek, musimy jednak programowo określić rodzaj danych wysyłanych do kontrolki i z niej pobieranych. Normalnie robi się to poprzez przypisanie do obiektu zmiennej składowej klasy obiektu i włączenie jej do klasy okna dialogowego. W wypadku kontrolek ActiveX czyni się dokładnie tak samo, możemy zatem użyć CIassWizard do dodania niezbędnego kodu. Stosowanie kontrolek ActiveX 213 Dodanie zmiennej składowej dla kontrolki CIassWizard wywołujemy z okna edytora zasobów naciskając Ctri+W lub wybierając pozycję CIassWizard z menu View. Mając otwarte okno CIassWizard otwieramy kartę Member Variable, gdzie znajdziemy listę identyfikatorów kontrolek, którym możemy przydzielić zmienne. Stwierdzimy tutaj, iż obiekty IDC_CALENDAR oraz IDC_MMCONTROL mogą posiadać przydzielone zmienne klasy okna dialogowego CActiveXDlg. Gdy oznaczymy pozycję IDC_CALENDAR i klikniemy przycisk Add Variable, otwarte zostanie zwykłe okno dialogowe Add Member Variable. Kontrolkom ActiveX przydzielać możemy zmienne wyłącznie ich klasy przesyłającej lub interfejsu. Dlatego też pole listy Category dopuszcza jedynie wybór Control, a pole Variable Type nie daje wyboru innego niż klasy CCalendar, oczywiście w przypadku obiektu kalendarza. Wprowadzamy m_Calendar jako nazwę nowej zmiennej i klikamy OK, aby ją zatwierdzić. Następnie wybieramy identyfikator IDC_MMCONTROL i ponownie klikamy przycisk Add Variable. Tym razem ukaże się okno dialogowe, które poinformuje o tym, że kontrolka Microsoft MCI nie została włączona do projektu i zapyta o zezwolenie na wykonanie tego zadania (rysunek 9.7). Microsoft Developer Studio Trie ActiveX Control "Microsoft MCI Control. version 5.0" has not been inserted into the project. Developer Studio will do this nów and generale aC++wrapperclassforit. OK Cancel Rysunek 9.7. CIassWizard proponuje utworzenie klasy dla nowej kontrolki Kliknijmy OK, po czym otwarte zostanie okno dialogowe Confirm Ciasses podobne do okna widocznego podczas pobierania obiektu kalendarza z kolekcji Component Galle-ry, lecz tym razem dla kontrolki MCI. Możemy tu dokonać zmian nazw klas i plików implementacyjnych. Po kuknięciu OK będziemy mogli kontynuować proces przydzielania zmiennych klasy Cmci do klasy okna dialogowego, posługując się oknem dialogowym Add Member Variable. Wprowadzamy nazwę zmiennej składowej: m_Md. Mając już przydzielone zmienne do kontrolek możemy wywoływać ich funkcje w celu wysyłania i pobierania z nich wartości. Programowe pobieranie i ustalanie właściwości kontrolki Zainicjalizujemy teraz kontrolki ActiveX dopisując kod z listingu 9. l do treści funkcji OninitDialogO. Kontrolka kalendarza posiada funkcję TodayO służącą do ustalania bieżącej daty. Kontrolkę MCI możemy z kolei skonfigurować do odtwarzania pliku .WAV. 214_____________________________________Poznaj Visual C++ 6 Listing 9.1. LST9_1.CPP - inicjalizacja kontrolek Calendar i MCI w funkcji OninitDialog () 1 // TODO: Tu umieść dodatkowy kod incjalizacyjny 2 3 II ** Ustaw kalendarz na datę bieżącą 4 m_Calendar.Today() ; 5 6 // ** Skonfiguruj obiekt mci waveaudio do odtwarzania 7 // ** pliku Microsoft Sound.wav 8 m_MCI.SetNotify(FALSE); 9 m_MCI.SetWait(TRUE); 10 m_MCI.SetShareable(FALSE); 11 m_MCI.SetDeviceType("waveaudio") ; 12 m_MCI.SetFileName("C:\\Windows\\Media\\TheMic~l.wav"); O 13 m_MCI.SetCommand("Open") ; 14 15 return TRUE; 16 } O Kontrolka MCI może zostać skojarzona z plikami multimedialnymi, w związku z czym jej przyciski będą służyły do obsługi odtwarzania: startowania, zatrzymywania i wstrzymywania. Na powyższym listingu kontrolka ActiveX Calendar przyjmuje bieżącą datę poprzez wywołanie jej funkcji Today (). Kontrolka MCI jest nieco trudniejsza do konfigu-rowania, albowiem może być używana przez kilka urządzeń i stąd opcje konfiguracyjne są rozleglejsze. W liniach od 8 do 13 następuje skonfigurowanie tej kontrolki do zadania odtwarzania pliku o nazwie The Microsoft Sound, za pomocą urządzenia MCI waveaudio. Zauważmy, iż w specyfikacji ścieżki dostępu do pliku występuje podwójny odwrotny uko-śnik. Dzieje się tak dlatego, gdyż ukośnik ma w języku C++ specjalne znaczenie w łańcuchach znakowych. Natomiast podwójny odwrorny ukośnik służy do ustanowienia znaku odwotnego ukośnika w znaczeniu, do którego jesteśmy przyzwyczajeni. Nazwa pliku jest skrócona do postaci dosowej, czyli TheMic~l.wav. Za pomocą Eksploratora Windows plik ten możemy odnaleźć w katalogu C:\Windows\Media pod nazwą The Microsoft Sound. W przypadku kontrolki MCI musimy wykonać jeszcze jedną procedurę. Otóż urządzenie waveaudio, które zostaje otwarte w linii 13 listingu 9.1, musi zostać zamknięte po wykonaniu zadania, by przekazać systemowi kontrolę nad nim. Najlepszym momentem na zamknięcie urządzenia jest usunięcie okna dialogowego. W tym celu posłużyć się należy wirtualną funkcją składową DestroyWindow () według poniższego schematu. Stosowanie kontrolek ActiveX 215 Nazwy katalogów w Windows NT Należy zauważyć, iż nazwa katalogu C:\WINDOWS powinna być zastąpiona przez C:\WINNT w Windows NT, ponieważ jest to domyślny katalog przechowujący dane systemowe. Dodanie wirtualnej funkcji składowej do klasy okna dialogowego 1. Kliknij kartę ClassView w panelu Project Workspace. 2. Kliknij znak + u góry karty, by wyświetlić użyte w projekcie klasy. 3. Prawym klawiszem myszy kliknij pozycję CActiveXDlg, by otworzyć menu skrótów dla tej klasy. 4. Wybierz pozycję Add Virtual Function, po czym otwarte zostanie okno dialogowe New Virtual Override. Na liście New Virtual Function powinna być widoczna funkcja wirtualna DestroyWindow. 5. Kliknij przycisk Add and Edit, by rozpocząć edycję funkcji. Funkcja ta wywoływana będzie w momencie usuwania okna dialogowego. Możemy umieścić jeszcze jedną funkcję odwołującą się do obiektu ActiveX, powodującą zamknięcie urządzenia wraz z wywołaniem funkcji DestroyWindow (): BOOL CActiveXDlg::DestroyWindow() { // Tutaj umieść specjalny kod i/lub wywołaj m_MCI.SetComaand("Close") ; return CDialog::DestroyWindow() ; } Urządzenie MCI zostanie prawidłowo zamknięte, możemy zatem skompilować i uruchomić aplikację. Zauważmy, iż kontrolka Calendar wyświetla datę bieżącą (jeśli data systemowa ustawiona jest prawidłowo), a przycisk Play (w postaci strzałki 4) jest dostępny do użycia. Gdy klikniemy ten przycisk, usłyszymy The Microsoft Sound. Podczas jego odtwarzania, dostępny staje się również przycisk Pause (pauza), pozwalający wstrzymywać odtwarzanie i wznawiać od miejsca zatrzymania. Po odtworzeniu całego dźwięku, możemy odtworzyć go jeszcze raz, przyciskając najpierw przycisk przewijania 9. Przeglądając funkcje składowe w ramach klas ActiveX CCalendar oraz Cmci, stwierdzimy obecność znacznie większej liczby funkcji, niż możemy wywołać w ramach dwóch obecnych kontrolek. Pamiętajmy również, iż pomoc dotycząca kontrolek dostępna jest w oknie dialogowym Component and Controls Gallery, po naciśnięciu przycisku Morę Info. Obecnie użyliśmy jedynie dwóch kontrolek spośród dostępnych w oknie Component Gallery; o wiele więcej dostępnych jest Internecie na istniejących stronach WWW — moż- 216_____________________________________Poznaj Visual C++ 6 na je stamtąd pobierać. Powinniśmy zająć się więc wykorzystaniem tej potęgi, wykorzystując kontrolki dostępne poprzez COM. Tworzenie kontrolek ActiveX w klasie odsyłającej Studiując definicje klas CCalendar oraz Cmci (w modułach Calendar.h oraz mci .h) możemy zauważyć, że obie wyprowadzone są z klasy cwnd. Pozwala to wykorzystującej je aplikacji traktować je jako osobne okna (inne kontrolki). Cała sztuczka tkwi w funkcji create (), zdefiniowanej jako funkcja wirtualna klasy cwnd i wykorzystywanej w klasach odsyłających do wywołania funkcji CreateControl () klasy CWnd. To z kolei służy tworzeniu kontrolek ActiveX zamiast zwykłych okien. Implementacja funkcji obsługi zdarzeń ActiveX za pomocą CIassWizard Kontrolka ActiveX będzie w znacznym stopniu ograniczona w działaniu, jeśli nie jest w stanie wykorzystać informacji o zmianie jej stanu przez użytkownika. Należy zatem dodać funkcje obsługujące zdarzenia wywołane przez kontrolki. Czyni się to w taki sam sposób, jak w przypadku innych kontrolek, czyli za pomocą CIassWizard lub poprzez okno dialogowe New Windows Messages/Event Handlers. Utwórzmy zatem funkcję obsługi, stwierdzającej fakt kliknięcia przez użytkownika kontrolki Calendar. Dodanie funkcji obsługi zdarzenia ActiveX za pomocą CIassWizard 1. Wywołaj CIassWizard naciskając Ctri+W lub wybierając pozycję CIassWizard z menu View. 2. Upewnij się, iż wybraną kartą jest Message Maps. 3. Z pola listy Ciass Name wybierz CActiveXDlg, jeśli jeszcze ta pozycja nie jest wybrana. 4. Z listy identyfikatorów Object IDs wybierz IDC_CALENDAR. 5. Z listy Messages wybierz i dwukrotnie kliknij pozycję Click, co otworzy okno dialogowe Add Member Function. 6. Zaakceptuj domyślną nazwę funkcji OnClickCalendar() poprzez kliknięcie OK. 7. Kliknij przycisk Edit Code, by przejść do edycji treści nowej funkcji. W oknie edytora umieszczona zostanie treść funkcji obsługi OnClickCalendarO, która będzie wywoływana za każdym razem, gdy użytkownik kliknie w obrębie kontrolki w celu zmiany daty. Celem funkcji obsługi będzie wyświetlenie wybranej daty w pasku tytułowym okna dialogowego. Musimy zatem wpisać odpowiedni kod, przedstawiony na listingu 9.2. Stosowanie kontrolek ActiveX 217 Listing 9.2. LST9_2.CPP - dodanie funkcji obsługi zdarzenia Click wysyłanego przez kon-trolkę ActiveX, Calendar 1 void CTestcalDlg::OnClickCalendar() 2 ( 3 // TODO: Tutaj umieść treść funkcji obsługi 4 5 // ** Skonwertuj wartość VARIANT kalendarza 6 COleDateTime dtChosenDate(m_Calendar.GetValue()); O 7 8 // ** Sformatuj datę Ole do wyświetlenia 9 II ** w pasku tytułowym okna dialogowego. 10 SetWindowText(dtChosenDate.Format("You Chose %#x")) ; 11 l O Struktura VARIANT zamieniona zostaje przez konstruktor na COleDateTime. Na powyższym listingu w linii 6 funkcja składowa GetValue () obiektu m_Calendar zostaje użyta do pobrania aktualnie wyznaczonej daty w obiekcie Calendar. Gdy klik-niemy dwukrotnie pozycję funkcji GetValue () wyświetloną jak składowa klasy CCalen-dar w panelu Project Workspace, zobaczymy, iż funkcja owa zwraca wartość VARIANT: VARIANT CCalendar::GetValue() { VARIANT result; InvokeHelper(Oxc, DISPATCH_PROPERTYGET, VT_VARIANT, (void*)Sresult, (NULL) ; return result; } VARIANT jest strukturą, w której przechowywane mogą być różne rodzaje danych. Przechowuje ona dane w składowych, odrębnych dla każdego typu danych. Składowe te z kolei zebrane są w unię (unia w języku C++ służy do przechowywaniu wszystkich jej składowych pod tym samym adresem, w celu zaoszczędzenia przestrzeni). Struktura posługuje się także składową VARTYPE vt, która zawiera znacznik informujący o typie danych. Możemy zobaczyć, iż struktury te oraz klasy MFC C0levariant zawierają nieco z technik COM/OLE. Pozwalają aplikacjom na wymianę danych, które mogą mieć różne znaczenia w różnych językach programowania, dostarczając jednocześnie prostych mechanizmów przechowywania i pobierania danych. Na szczęście, nie musimy operować bezpośrednio na strukturach VARIANT, albowiem wiele spośród klas MFC rozpoznaje je i potrafi skonwertować do postaci znacznie przyjaźniejszych klas MFC. Na listingu 9.2 w linii 6 zostaje przeprowadzona taka właśnie konwersja, gdzie wartość VARIANT zwraca- 218_____________________________________Poznaj Visual C++ 6 na przez funkcję GetValue () przekazana zostaje bezpośrednio do konstruktora obiektu dtChosenDate, który występuje jako obiekt COleDateTime. Tworzenie kontrolek ActiveX w klasach odsyłających MFC dostarcza czterech klas umożliwiających manipulowanie i przechowywanie danych dotyczących daty i godziny. Oryginalnie istniały tylko dwie klasy, CTime oraz CTimeSpan bazujące na zmiennej systemu UNIX time_t (4-bajtowa wartość typu long), zliczającej sekundy, które upłynęły od początku 1970 roku. Jednakże dokładność jednej sekundy i ograniczony zakres obsługiwanych dat (1970 — 2038) stanowi dla wielu aplikacji przeszkodę. Dlatego też powstały dwie nowe klasy, COleDateTime oraz COleDateTimeSpan, używane w nowszych aplikacjach OLE. COleDateTime opiera się na strukturze DATĘ (która reprezentuje wartości double). Możliwości przechowywania pozwalają tej klasie pokrywać zakres dat od l stycznia 100 do 31 grudnia 9999, z dokładnością do l milisekundy. Odstęp pomiędzy dwoma wartościami reprezentowana jest przez obiekt COleDateTimeSpan, który może również manipulować danymi. Jest to obiekt przechowujący i operujący danymi dotyczącymi dat i godzin. Posiada przydatną funkcję Format (), pozwalającą na utworzenie z posiadanych danych łańcucha znakowego CString, na podstawie przekazanych jej znaczników formatowania. Znacznik %#x użyty w linii 10 powoduje utworzenie z danych zapisanych w obiekcie COleDateTime łańcucha znakowego zawierającego informacje: dzień tygodnia, dzień miesiąca oraz rok (na przykład: Poniedziałek, 21 kwietnia, 1998). Tak utworzony łańcuch CString może zostać następnie przekazany funkcji SetWin-dowText () w celu wyświetlenia wybranej przez użytkownika daty na pasku tytułowym okna dialogowego. Po skompilowaniu i uruchomieniu programu zawierającego podane kody kontrolka kalendarza powinna pozwalać na wybór daty, wyświetlając ją na pasku tytułowym okna dialogowego, jak na rysunku 9.8. PATRZ TAKŻE • O ustanawianiu obsługi komunikatów i zdarzeń czytaj w rozdziale 4. • By nauczyć się tworzenia własnych kontrolek ActiveX, zajrzyj do rozdziału 26. • Aby dowiedzieć się więcej na temat technik COM i OLE, przejdź do rozdziału 25. Stosowanie kontrolek ActiveX 219 leSfeYouChQseThursday.March19.1998 i& E3| Ms »r c h1 < i)8 - OK 998 March .•J |l9 Cancel Suń Mon Tu e Wed Thu Fri Sat 1 2 3 4 5 6 7 8 9 10 11 12 13 •14 15 16 17 13 2G 20 21 22 23 24 25 27 2B 29 30 31 !-< M > I —j Rysunek 9.8. Obsługa zdarzeń w kontrolce ActiveX pozwala na wyświetlenie wybranej daty Rozdział 10 Tworzenie własnych okien dialogowych i ich klas Stosowanie modalnych okien dialogowych do pobierania danych Tworzenie niemodalnych okien dialogowych Wymiana i zatwierdzanie danych z okien dialogowych Zdobywanie wiedzy jak używać i dostosowywać okna dialogowe wymiany i potwierdzania danych Tworzenie klasy okna dialogowego Możemy tworzyć nowe okna dialogowe, które będą wywoływane z głównego okna aplikacji w odpowiedzi na czynności wykonane przez użytkownika. Dla każdego nowego okna dialogowego będziemy musieli utworzyć jego szablon i obsługującą je klasę, wyprowadzoną z klasy CDialog. Szablon okna utworzyć można za pomocą edytora zasobów, natomiast klasę dostarczającą oknu funkcjonalności przy użyciu CIassWizard. Następnie możemy dodać do owej klasy inne funkcje obsługujące kontrolki nowego okna, w taki sam sposób jak rozszerzaliśmy możliwości głównego okna dialogowego. Okna dialogowe występują najczęściej w formach, które większość z nas dobrze zna. Standardową procedurą jest wyświetlenie takiego okna z pewnymi ustawieniami początkowymi, pozwalającymi użytkownikowi dokonywać ich zmian, a następnie zatwierdzić owe zmiany za pomocą przycisku OK lub też z nich zrezygnować naciskając Cancel. Programista musi pamiętać o tym, iż okno dialogowe powinno zachowywać lokalną kopię danych, które ulegają zmianie. Gdy użytkownik kliknie OK, zmiany te zostają wprowadzone w życie, w przeciwnym wypadku nie dzieje się nic. Typowym przypadkiem okna dialogowego jest okno modalne. Podczas wyświetlania takiego okna reszta aplikacji jest niedostępna, co zmusza użytkownika do zamknięcia 222 Poznaj Visual C++ 6 okna, zanim odzyska kontrolę nad aplikacją. Okna modalne mogą otwierać kolejne okna, a okno najwyższe zawsze przejmuje kontrolę, zwracając ją po zamknięciu oknu, z którego zostało wywołane. Okna niemodalne są alternatywą w stosunku do okien modalnych. Okna niemodalne nie blokują dostępu do aplikacji podczas ich wyświetlania. Pływające paski narzędziowe, takie jak pasek Controls, są przykładem niemodalnych okien dialogowych. Istota niemodalnych okien dialogowych zostanie objaśniona nieco dalej w tym rozdziale, jednak obydwa rodzaje okien wymagają klasy źródłowej, z której pozyskają swoją funkcjonalność, co zostanie wykorzystane przy tworzeniu szablonu okna. PATRZ RÓWNIEŻ • O tworzeniu szablonu okna dialogowego czytaj w rozdziale 3. Dodawanie szablonu nowego okna dialogowego Zanim przystąpimy do tworzenia klasy nowego okna, musimy najpierw utworzyć jego szablon i umieścić w nim kontrolki. Umieszczenie w projekcie szablonu nowego okna dialogowego 1. Otwórz kartę ResourceView w panelu Project Workspace. 2. Kliknij prawym klawiszem najwyższą pozycję i wybierz z menu Insert, co wywoła okno Insert Resource (rysunek 10.1). Inieil Resouice Resource type: t^ Acceterator II Bitmap l gt Curspr B! IDD_DIALOGBAfl IDD_FORMVIEW IDD_OLE_PROPPAGE_LARGE IDD_OLE_PROPPAGE_SMALL IDD_PROPPAGE_LARGE IDD_PRDPPAGE_MEDIUM H IDD_PROPPAGE_SMALL ig) HTML 03 Icon Rysunek 10. l. Okno Insert Resource wyświetlające opcje dla szablonu nowego okna dialogowego 3. Wybierz pozycję Dialog, by utworzyć typowe okno dialogowe. Możesz wybrać również inny typ okna, klikając widoczny obok znak plusa i wybrać typ z listy. 4. Kliknij New, co spowoduje otwarcie szablonu nowego w edytorze zasobów. Tworzenie własnych okien dialogowych i ich klas______________________223 5. Kliknij prawym klawiszem myszy w obrębie szablonu i wybierz z menu skrótów pozycję Properties. 6. W oknie właściwości wprowadź w polu ID nazwę dla nowego okna. PATRZ TAKŻE • O tworzeniu szablonów okien dialogowych czytaj w rozdziale 3. * Więcej informacji o tworzeniu aplikacji na bazie okien dialogowych znajdziesz w rozdziale 3. Wyprowadzanie klas z klasy CDialog za pomocą CIassWizard Po dodaniu szablonu nowego okna dialogowego, możemy umieścić w nim wszelkie potrzebne kontrolki, a także zmienić właściwości okna. By móc korzystać z nowego okna, należy najpierw utworzyć dla niego nową klasę, która odpowiedzialna będzie za wyświetlenie okna wraz z kontrolkami oraz obsługującą komunikaty pochodzące od okna i jego obiektów. Klasa bazowa CDialog zawiera procedury ładujące i wyświetlające okno dialogowe. Z klasy tej wyprowadzić można własną klasę, dziedziczącą te funkcję. CIassWizard w pewnym stopniu automatyzuje to zadanie. Tworzenie klasy dla nowego okna dialogowego za pomocą CIassWizard 1. Zaznacz pozycję reprezentującą na panelu ResourceView nowe okno. 2. Naciśnij CtrI+W lub wybierz pozycję CIassWizard z menu View, by wywołać CIassWizard. 3. CIassWizard automatycznie wykryje nowe okno dialogowe i wyświetli okno Adding a Ciass, widoczne na rysunku 10.2. IDD_MYNEWDLG is a new rasource. Sińce ii is a dialog i&source you probab^i want to oeate a new das^ for it. You ean alio select an eidsting r- , f" Cteale a new ciass f ^eW. an eniating dass Rysunek 10.2. CIassWizard wyświetla okno Adding a Ciass po wykryciu nowego okna dialogowego. 4. Kliknij OK, by zaakceptować domyślną funkcję Create a New Ciass. 5. CIassWizard wyświetli kolejne okno dialogowe New Ciass, widoczne na rysunku 10.3. 224 Poznaj Visual C++ 6 :•• CIass information • ••••••••••• •••••••••••••••••• ...•••.•..•••••................ ..........; ?^ : Mamę: jCMyNewDlg ; i • , -.,...........,....„„„...„. ^„..-....„/.J-;..^.„^^,....„.„. l Cance! l Fite narne: jMyNewDIg.cpp • •:. l ' """ | . .Change,,. | l Basectas^ ^Dialog ^j | , . Dialog !D; IDDMYNEWDLG j^i III"^!^..^^!^:^ - Automation •••• •••• ••.......•••• .. ........................ ................ ....... l f7 None ; ; f" ^utowation : ^r„..„„,,^..,i. |,:,,,„-,.^...,-, | Rysunek 10.3. Okno dialogowe New Ciass pozwala nadać nazwę nowej klasie 6. Możesz wprowadzić nazwę dla nowej klasy w polu Name. Obowiązującą w przypadku klas wyprowadzanych z klas MFC konwencją jest umieszczanie jako pierwszego znaku nazwy litery C. 7. Po wprowadzeniu nazwy w polu File name powinna zostać wyświetlona nazwa pliku implementującego nową klasę. Tę nazwę również możesz zmienić naciskając przycisk Change. 8. W polu Base Ciass pozostaw wyświetloną pozycję CDialog, dopóki nie zostanie zaimplementowana strona właściwości. 9. W polu Dialog ID powinien być wyświetlony identyfikator nowo utworzonego okna dialogowego. Jeśli tak nie jest, możesz wybrać właściwy identyfikator rozwijając listę. 10. Kliknij OK, by zatwierdzić utworzenie nowej klasy wraz z jej plikami implementacyjnym (.cpp) oraz nagłówkowym (.h). 11. CIassWizard wyświetli następnie kartę Message Maps, co pozwoli na dodanie funkcji obsługujących zdarzenia związane z oknem dialogowym. Możesz tutaj zdefiniować funkcje obsługi oraz przypisać zmienne oknu i jego kontrolkom. 12. Gdy zakończysz tworzenie funkcji i przypisywanie zmiennych, kliknij OK, aby zamknąć CIassWizard. PATRZ TAKŻE • Więcej informacji na temat tworzenia aplikacji na bazie okna dialogowego znajdziesz w rozdziale 3. • Aby dowiedzieć się więcej na temat obsługi komunikatów, przejdź do rozdziału 4. Tworzenie własnych okien dialogowych i ich klas 225 Inicjalizowanie nowej klasy okna dialogowego Po utworzeniu nowa klasa powinna być widoczna na liście użytych w projekcie klas, w panelu CIassYiew. Po kliknięciu widocznego obok znaku plusa, powinniśmy zobaczyć nazwę konstruktora (o takiej samej nazwie jak nazwa nowej klasy). Gdy klikniemy dwukrotnie pozycję konstruktora, w oknie edytora powinien znaleźć się poniższy kod: CMyNewDlg::CMyNewDlg(CWnd* pParent /*=NULL*/ : CDialog(CMyNewDlg::IDD, pParent) { //{(AFX_DATA_INIT(CmyNewDlg) // CIassMizard zainicjalizuje zmienną 11}}AFX_DATA_INIT > Zauważmy, iż zależna od okna dialogowego składowa CMyNewDlg:: IDD zostaje przekazana konstruktorowi klasy bazowej CDialog. W definicji nowej klasy powinna znaleźć się definicja tej składowej jako enumerator przyjmujący wartość w postaci identyfikatora okna dialogowego: enum ( IDD = IDD_MYNEWDLG } ; Klasa bazowa CDialog używa tej wartości do załadowania właściwego okna dialogowego, gdy klasa CMyNewDlg jest już w użyciu. Kiedy za pomocą CIassWizard dodamy nowe zmienne składowe, pomiędzy komentarzami AFX_DATA_INIT zapisany zostanie kod inicjalizujący te zmienne. Możemy również dodać zmienne składowe do definicji klasy bez pomocy CIassWizard. Zmienne te musimy wtedy zainicjalizować w konstruktorze z właściwymi dla nich wartościami początkowymi. Kod generowany przez CIassWizard Podczas modyfikacji kodu generowanego przez CIassWizard należy zachować ostrożność. Kreator ten może przerwać działanie nie znajdując oczekiwanego identyfikatora. Dodając własny kod inicjalizujący, należy umieścić również odpowiedni komentarz. : Wywołując CIassWizard możemy wybrać interesującą nas klasę z listy Ciass Name. Po dodaniu nowego okna dialogowego do aplikacji utworzonej także na bazie okna dialogowego, oprócz klasy głównego okna, powinna być widoczna także klasa nowego okna. Za każdym razem, gdy dodajemy do programu nową zmienną składową lub funkcję obsługi komunikatu dla kontrolek umieszczonych w oknie, musimy upewnić się, iż z listy Ciass Name wybraliśmy właściwą pozycję. W przeciwnym wypadku CIassWizard umieści te elementy w aktualnie wybranej klasie (niekoniecznie tam, gdzie chcemy!). 226_____________________________________Poznaj Visual C++ 6 Wyświetlanie modalnych okien dialogowych W tym miejscu rozważyć warto, w jaki sposób wywoływane są nowe okna dialogowe. Generalnie rzecz biorąc, są one otwierane na skutek działania użytkownika, takiego jak wybór pozycji menu lub kliknięcie przycisku w oknie-rodzicu. Okno potomne możemy wyświetlić korzystając z funkcji obsługi obiektu sterującego okna-rodzica. Odbywa się to w dwóch krokach. W pierwszym należy utworzyć model klasy okna. Można to uczynić poprzez lokalną deklarację obiektu tejże klasy, wpisując w funkcji obsługi następującą linię: CMyCustomDlg dlgMyCustom(this) ; W powyższej linii CmyCustomDlg jest klasą okna dialogowego, utworzoną przez CIassWizard. Konstruktorowi przekazany zostaje specjalny wskaźnik języka C++, this. Ponieważ wywoływana funkcja obsługi jest składową klasy wyprowadzonej z klasy typu CWnd (jak Cdialog, Cview itd.), nowe okno otrzyma informacje od właściwego okna-przodka. Jak wiemy z poprzedniego punktu, szablon okna dialogowego automatycznie inicjalizuje klasę bazową Cdialog. Aby poinformować kompilator o utworzeniu wyprowadzonej klasy, co jest niezbędne podczas kompilacji modułu ją powołującego (CmyCustomDlg w linii powyżej), musimy pamiętać o dołączeniu pliku nagłówkowego (.h) na samym początku modułu powołującego, łącząc z rozkazem preprocesora łinclude. Przykładowo, jeśli klasa utworzona dla nowego okna zapisana została w pliku MyCu-stomDIg.h i dodajemy linię powołującą tę klasę, która odwołuje się do definicji nowej klasy w pliku implementacyjnym CustomDIg.cpp, powinniśmy na początku tego pliku dopisać linię: #include "MyCustomDIg.h" Drugim etapem jest wyświetlenie i rozpoczęcie modalnej operacji okna dialogowego. Należy w tym celu wywołać funkcję okna dialogowego DoModal (). Wyświetlone zostanie w ten sposób okno dialogowe wraz z kontrolkami, oczekujące w lokalnej pętli komunikatów. Okno to będzie kierowało komunikaty kontrolek do własnej implementacji, do czasu zamknięcia go przez użytkownika. Zamykanie modalnego okna dialogowe za pomocą funkcji EndDialog() Do zamykania modalnych okien dialogowych powinno się stosować funkcję End-Dialog (), a nie DestroyKindow (). EndDialog () daje pewność, iż wszystkie zasoby GDI zostaną zwolnione przed zamknięciem okna. Funkcji EndDialog () użyć można nawet wewnątrz funkcji OninitDialogO, co spowoduje zamknięcie okna, jeszcze przed jego wyświetleniem. Tworzenie własnych okien dialogowych i ich klas 227 Jeśli podczas wyświetlania nowego okna dialogowego wystąpi błąd, funkcja DoMo-dal () bezzwłocznie zwróci kod w postaci liczby całkowitej -l lub IDABORT. Gdy jednak okno zostanie otwarte bez przeszkód, funkcja DoModa l () kończy działanie wraz z wywołaniem funkcji EndDialogO, która powoduje zamknięcie okna dialogowego. Funkcji EndDialogO przekazana zostaje wartość całkowitoliczbowa, zwrócona przez DoModal() jako kod wyjściowy. Domyślna implementacja OnOK() oraz OnCancel () powoduje automatyczne wywoływanie funkcji EndDialogO, wraz z przekazaniem kodów wyjściowych l DOK oraz IDCANCEL zwróconych przez funkcję DoModal (). Przykładowo, możemy zapisać następujące wywołanie DoModal () dla otwarcia okna dialogowego: int nRetCode = dIgMyCustom.DoModal() ; Kiedy okno dialogowe zostaje zamknięte (zwykle za pomocą przycisku OK lub Can-cel), zwrócony kod (zwykle IDOK lub IDCANCEL) zostanie przypisany do nRetCode. Powinniśmy w tym miejscu sprawdzić zawartość nRetCode by wywołać odpowiednią reakcję programu, zależną od czynności wykonanej przez użytkownika, taką jak modyfikacja danych aplikacji, gdy zwróconą wartością jest IDOK. Uzyskać to możemy poprzez dokonanie następującego wpisu: if (nRetCode == IDOK) { // Zachowaj zmiany ] Możemy spowodować również programowe zamknięcie okna poprzez wywołanie z własnego kodu funkcji EndDialog () i przekazanie jej wymaganego kodu. Dodawanie zmiennej składowej dla zachowywania danych z okna dialogowego Większość aplikacji wymaga, aby okna dialogowe były inicjowane, wtedy posiadając kopię aktualnych danych (lub ustawień). Możliwe jest wówczas dokonywanie w sposób bezpośredni lub pośredni modyfikacji lokalnej kopii danych, będącej w dyspozycji okna dialogowego. Gdy użytkownik kliknie przycisk OK, co spowoduje zamknięcie okna, zmiany te powinny zostać wprowadzone w głównych danych aplikacji. Oznacza to, iż musimy przekazać obiektowi okna dialogowego bieżące wartości pobrane od aplikacji przed wywołaniem funkcji DoModal (), a następnie wprowadzić zmiany w wartościach, gdy w momencie zakończenia działania funkcji DoModal () zostanie spełniony warunek kliknięcia OK. W wielu wypadkach lokalną kopię danych dla okna dialogowego przechować możemy w zmiennych (dodanych za pomocą CIassWizard) przypisanych do kontrolek, a następnie pobrać z nich nowe wartości, gdy okno zamykamy. Przykładowo, możemy wyświetlić proste okno dialogowe, w którym dokonuje się modyfikacji danych dotyczących 228 Poznaj Visual C++ 6 imienia i nazwiska oraz wieku, za pomocą dwóch pól edycji. Takie przykładowe okno widzimy na rysunku 10.4. Rysunek 10.4. Proste okno dialogowe do pobierania informacji Do pola edycji, w którym wpisywane są dane imienia i nazwiska, przypisać możemy zmienną CString (m_strName), do drugiego pola natomiast zmienną typu int (m_Age), w której zostanie zapisany wiek osoby wpisującej. Przypisania dokonać możemy za pomocą CIassWizard i karty Member Yariable dla klasy okna dialogowego (CMyCustomDlg),jaknarysunku 10.5. Zmienne inicjujemy po zadeklarowaniu obiektu okna dialogowego, lecz przed wyświetleniem okna. Oto odpowiedni kod: CMyCustomDlg dIgMyCustom(this) ; dlgMyCustom.m_nAge = 31 ; dlgMyCustom.m_strName = "Jon Bates" ; digMyCustom.DoModal() ; Użytkownik może od tej chwili modyfikować wyświetlone wartości, po czym można sprawdzić, czy funkcja DoModal () zwróciła l DOK oraz odczytać wartości obecnie zapisane w zmiennych przydzielonych oknu. Wymaga to wpisania następującego kodu: if ( (digMyCustom.DoModal() == IDOK) CString strMsg ; strMsg.Format("New Name •%s' , Agę = %d", dIgMyCustom.m nAge) dIgMyCustom.m strName AfxMessageBox(strMsg) ; Po zakończeniu działania funkcji DoModal (), dzięki powyższemu kodowi, zmieniona zawartość pola edycji imienia i nazwiska zostanie zapisana jako zawartość dIgMyCustom.m strName, nowo wprowadzony wiek natomiast w dlgMyCustom.m_nAge. Wartości te zostaną następnie użyte do sformatowania łańcucha znakowego CString i wyświetlone w oknie komunikatu. W rzeczywistej aplikacji dane te mogą zostać przekazane do obszaru danych głównych. Tworzenie własnych okien dialogowych i ich klas 229 aaa Boj«± ICuitomDig CIsss oame; •^j jcMyCu:>ornDlg Add Cja»... c:\...\Cu.!tomDlg\M^Cu3tomD!g h, e:\„ \MyCustofnD!g.cpp ConttoliDs: Type Membef 1&C_NA ME IDCAMCE L IDOK CSlling Desciiption: intwith'/. yinKWJmVabe', MaKimumYaiue^ Rysunek 10.5. CIassWizard wyświetlający przydzielone oknu dialogowemu zmienne PATRZ TAKŻE • Więcej o dodawaniu i przydzielaniu obiektom sterującym składowych zmiennych mówimy w rozdziale 5. Wymiana i zatwierdzanie danych z okien dialogowych Zajmiemy się teraz przekazywaniem wartości zapisanych w zmiennych składowych z oraz do pól edycji. Jakkolwiek zmienne przydzielamy do kontrolek za pomocą CIassWizard, funkcja okna dialogowego DoDataExchange () zawierająca kod konieczny dla przeprowadzenia wymiany danych zostaje wygenerowana automatycznie. Funkcja ta odpowiedzialna jest za transfer danych w obu kierunkach. Proces transferu danych nosi nazwę Data Exchange i inicjowany jest poprzez wywołanie funkcji okna dialogowego UpdateDataO. Funkcji tej może zostać przekazany parametr typu Boolean, zwany b3aveAndValidate. Po przekazaniu FALSE, wartość zapisana w zmiennej składowej zostanie pobrana i posłuży do ustawienia wartości wyświetlanej w polu edycji. Gdy natomiast przekazany zostanie parametr TRUE, nastąpi proces odwrotny, czyli pobranie wartości z pola edycji i zapisanie w zmiennej składowej. Funkcja UpdateData () wywoływana jest automatycznie przez funkcję klasy bazowej CDialog w celu ustawienia początkowych wartości w polach edycji, po otwarciu okna dialogowego. Inna funkcja klasy bazowej, onOK() (wywoływana, gdy użytkownik kliknie przycisk OK), powoduje przekazanie parametru TRUE, co z kolei determinuje przeniesienie danych z pól edycji do zmiennych składowych. 230 Poznaj Visual C++ 6 Wymiana danych nie ogranicza się do okien dialogowych Technika stosowania funkcji UpdateData () w połączeniu z DoDataExchange () ma zastosowanie nie tylko w przypadku okien dialogowych. Obie funkcje są składowymi klasy cwnd, co oznacza, że możemy zaimplementować mechanizm wymiany i zatwierdzania danych dla każdej klasy wyprowadzonej z cwnd. Okno dialogowe może również dokonywać zatwierdzania danych wprowadzonych przez użytkownika. Procedury sprawdzające wykonywane są poprzez wpis dokonany w funkcji DoDataExchange (), która uruchamiana jest poprzez wywołanie UpdateDa-ta(), gdy jako wartość parametru b3aveAndValidate przekazana zostanie TRUE. Gdy wprowadzone przez użytkownika dane są poprawne, UpdateData () zwraca TRUE (a bieżąca implementacja CDialog: :OnOK() zamyka okno dialogowe). Gdy dane są niewłaściwe, do użytkownika wysłane zostaje ostrzeżenie, a UpdateData zwraca FALSE. Stosowanie funkcji Data Exchange (DDX) Przyjrzyjmy się bliżej wpisowi powodującemu zatwierdzanie danych, przez przeglądnięcie zawartości funkcji DoDataExchange (). Weźmy na przykład funkcję DoDataExchange () użytą w oknie dialogowym opisanym wcześniej - jej zapis wygląda następująco: void CMyCustomDlg::DoDataExchange(CDataExchange* pDX) ( CDialog::DoDataExchange(pDX) ; //{(AFX_DATA_MAP(CMyCustomDlg) DDX_Text(pDX, IDC_AGE, m_nAge) ; DDX_Text(pDX, IDC_NAME, m_strName) ; //}}AFX_DATA_MAP } Zauważmy, iż funkcji zostaje przekazany wskaźnik obiektu CDataExchange, (pDX). Obiekt CDataExchange przechowuje szczegółowe informacje dotyczące kierunku transferu danych oraz okna docelowego. Wskaźnik ten zostaje dalej przekazany wraz z identyfikatorem pola edycji i nazwą przydzielonej zmiennej do wywołania funkcji DDX_Text (wygenerowanej przez CIassWizard) w celu zaimplementowania transferu danych. Istnieje kilka typów funkcji DDX_, służących do obsługi różnych kontrolek i rodzajów danych. Kilka z najczęściej wykorzystywanych przedstawia tabela 10.1. CIassWizard umieszcza powyższy kod automatycznie pomiędzy komentarzami AFX_DATA_MAP, gdy przydzielamy zmienne obiektom sterującym za pomocą karty Member Yariable w CIassWizard. Mo- żemy umieścić własne wpisy, dokonując ich na końcu kodu funkcji DoDataExchange (), za wpisami dokonanymi przez CIassWizard. Tworzenie własnych okien dialogowych i ich klas 231 Tabela 10.1. Różne typy funkcji DDX Nazwa funkcji Typ obiektów sterujących Rodzaje obsługiwanych danych DDX_Text DDX_Check DDX Radio Pola edycji Pola wybom Grupy przełączników alter- natywnych BYTE, short, int, UINT, long, DWORD, String, LPTSTR, float, double, COleCurrency, COleDa- teTime int int CString - CString CString CString int int int DDX_LBString Listy rozwijane DDX_LBStringExact Listy rozwijane DDX_CBString Listy kombinowane DDX_CBStringExact Listy kombinowane DDX_LBlndex Indeks listy rozwijanej DDX_CBlndex Indeks listy kombinowanej DDX_Scroll paski przewijania Funkcje powyższe sprawdzają przekazany wskaźnik obiektu CDataExchange i na podstawie jego składowej m_bSaveAndValidate ustalają kierunek przepływu danych. Dopisując linię if (pDX-> m_bSaveAndValidate TRUE) możemy sprawdzić ten znacznik i spowodować wykonanie określonych zadań, w zależności od kierunku transferu danych. Funkcje DDX implementują kod WIN32, który ustawia lub pobiera wartości z kontro-lek, używając komunikatów Windows (pamiętajmy, iż każdy obiekt sterujący jest osobnym oknem). Możemy zaimplementować własne funkcje DDX_ lub dopisać kod do DoDa-taExchange () dla wywołania interakcji z kontrolkami okna dialogowego. Może okazać się konieczne wywołanie funkcji UpdateDataO z własnego kodu, by w ten sposób wymusić kierunek transferu. Może się tak zdarzyć, gdy implementujemy funkcje obsługujące komunikaty okien dialogowych, które wykorzystują bieżące wartości wprowadzone do kontrolek. Przypuśćmy, iż chcemy przechwycić zmiany dokonywane przez użytkownika w polu edycji, by na bieżąco wyświetlać w pasku tytułowym okna zachodzące zmiany. Moglibyśmy w tym celu użyć CIassWizard do dodania obsługi powiadomienia pola edycji EN_CHANGE i skorzystać z funkcji SetWindowText () dla aktualizacji napisu w pasku tytułowym. Jednakże, bez dodatkowego wywołania UpdateData(TRUE), zwartość przypisanej zmiennej składowej nie będzie uaktualniana wraz ze zmianami zachodzącymi w polu 232_____________________________________Poznaj Visual C++ 6 edycji. Poprzez wpisanie odpowiedniego kodu w funkcji obsługi, zyskamy pewność wywołania funkcji DoDataExchange (), zapisującej zawartość pola edycji w zmiennej CString. Zatem funkcja obsługi (o nazwie opartej o nazwę przykładowego okna dialogowego) powinna mieć następujący zapis: void CMyCustomDlg::OnChangeName() ( UpdateData(TRUE) ; SetWindowText(m strName ; } Po naciśnięciu klawisza, wywołana zostanie OnChangeName (), a updateData (TRUE) zaktualizuje zawartość zmiennej składowej m_strName, poprzez wywołanie DDX_Text. Zmieniona zawartość zmiennej zostanie następnie przesłana do paska tytułowego okna, za pośrednictwem SetWindowText (). Gdy wywołamy UpdateData (FALSE) , zawartość pól edycji zostanie zastąpiona zawartością przydzielonych zmiennych składowych. Możemy wykorzystać tę możliwość w celu przywrócenia domyślnych wartości, gdy użytkownik kliknie przycisk anulowania operacji. PATRZ TAKŻE • O różnych rodzajach kontrolek możesz przeczytać w rozdziale 5, 6 oraz 7. Stosowanie funkcji Data Yalidation (DDV) Gdy za pomocą CIassWizard przypisujemy kontrolkom okna dialogowego zmienne składowe, przechowujące dane różnego typu, na przykład CString czy int, musimy jednocześnie dostarczyć parametry umożliwiające zatwierdzenie wprowadzonych wartości. Parametry te możemy ustalić przez wywołanie CIassWizard i zaznaczenie na karcie Member Variables docelowej zmiennej, a następnie wpisanie odpowiednich wartości w okienkach umieszczonych u dołu okna CIassWizard (oznaczonych na rysunku 10.6). Dla zmiennych typu CString określić możemy maksymalną liczbę znaków, jaka może znaleźć się w łańcuchu znakowym wprowadzanym przez użytkownika. Zmienne typu int natomiast mogą posiadać ograniczenia wpisywanej przez użytkownika wartości. Program oczekiwał będzie na wpisanie wartości mieszczącej się w wyznaczonym przedziale. Tworzenie własnych okien dialogowych i ich klas 233 O ^K MessageMapa MeflibetYatiabtes Au Piwd . omahon J ACHYS^ Ev'ents J CIassInio.i ^"C^: AddOas... •- Cu,^mD!g ^ c:\,.AClBtOBDIa'>MiCu»loinOl B.h. c\.\M>Cu CiirSsIłBKi:'. Typ LMrCurtoni&lg -^j """1 1" 1 —i AddVaiiabte... e Merr-ber Oetete Yafiable !'^ IDC NAWĘ C11! IOCANCEL IDOK: '• •:v i:1!:!';;'!*'*!*'' lescrplion, ni wahyalidaiiópl;^: i^^^ ^dmum Vatue: | RS •r^ rn sttNarne ...,,•.. -y'/'- :tl.f;' .•»-- Mastnium Value: | [65 :&:'• ' |0^j Ca"cs! Rysunek 10.6. Ustalanie zakresu dopuszczalnych wartości dla zmiennej typu int O Wartości dopuszczalne Kiedy ograniczenia powyższe zostaną wprowadzone, CIassWizard wygeneruje odpowiednie linie DDV_ i zapisze je w funkcji DoDataExchange (). Dla przykładu, ograniczenia zilustrowane na rysunku 10.6 spowodują wygenerowanie następującej linii: DDV_MinMaxInt(pDX, m_nAge , 18 65) Wywołanie funkcji DDV nastąpi bezpośrednio po wywołaniu funkcji DDX, która spowoduje uaktualnienie wartości zapisanej w zmiennej m_nAge. Uaktualnione dane zostaną poddane zatwierdzeniu. Jeśli wprowadzona przez użytkownika wartość wykracza poza dopuszczalny zakres wartości, wyświetlone zostanie ostrzeżenie, a funkcja UpdateData (), inicjująca wymianę danych zwróci FALSE, co oznacza niepowodzenie procedury zatwierdzenia (rysunek 10.7). CUSTOMDLG Please enter an integer between 18 and 65. Rysunek 10.7. Okno nakazujące użytkownikowi wprowadzenie właściwej wartości Wygenerowany przez CIassWizard kod służący zatwierdzaniu wprowadzonej liczby znaków wygląda w ten sposób: DDV_MaxChars(pDX , m_strName , 15); 234 Poznaj Visual C++ 6 Funkcje zatwierdzające DDV, z których możemy skorzystać, wymienione są w tabeli 10.2. Procedury w nich zawarte mogą zostać użyte dla każdego typu danych, pobieranych z obiektów sterujących, takich jak pola edycji, paski przewijania czy też przełączniki alternatywne (opcji). Generalnie rzecz biorąc, ograniczają one wprowadzane wartości numeryczne w ten sposób, by mieściły się w zadanym zakresie numerycznym. Funkcje DDV możemy dodawać samodzielnie, musimy być jednak ostrożni i przestrzegać zasady umieszczania tych wpisów w sekcji AFX_DATA_MAP. W przeciwnym razie, może to zakłócić pracę CIassWizard. Tabela 10.2. Standardowe funkcje DDV, generowane przez CIassWizard Funkcja DDV Obsługiwany typ Opis danych DDV MaxChars CString Ogranicza liczbę wprowadzanych znaków DDV MinMaxByte BYTE Ogranicza wartości liczbowe DDV MinMaxDouble double Ogranicza wartości liczbowe DDVMinMaxDWord DWord Ogranicza wartości liczbowe DDVMinMaxFloat float Ogranicza wartości liczbowe DDVMinMaxInt int Ogranicza wartości liczbowe DDV MinMaxLong long Ogranicza wartości liczbowe DDV MinMaxUnsigned unsigned Ogranicza wartości liczbowe PATRZ TAKŻE • Informacje o różnych rodzajach kontrolek są w rozdziałach 5, 6 oraz 7. Tworzenie własnych funkcji zatwierdzających W szczególnych przypadkach utworzyć możemy funkcje zatwierdzające, na wzór funkcji DDV_. Konieczne jest w tym celu napisanie funkcji akceptującej wskaźnik do obiektu CDataExchange (pDX), zawierającej odwołanie do zmiennej, której zawartość ma zostać zatwierdzona i posiadającej ewentualne parametry dodatkowe. Pierwszym warunkiem, jaki spełniać musi taka funkcja, jest sprawdzenie, czy obiekt CDataExchange znajduje się w trybie zatwierdzania. Sprawdzenie to wykonane może zostać poprzez zbadanie wartości wyrażenia pDX->m_nbSaveAndValidate, która musi wskazywać TRUE. Jeśli tą zwróconą wartością nie jest TRUE, oznacza to, że kontrolki zainicjowane są przez zmienne składowe, a procedura zatwierdzania nie jest konieczna i funkcja może zakończyć działanie. W innym przypadku musimy dokonywać zatwierdzania wartości zapisanych w zmiennych według założonych kryteriów. Tworzenie własnych okien dialogowych i ich klas 235 Jeśli wartości będące przedmiotem zatwierdzania zostaną przyjęte, funkcja może zakończyć działanie w sposób normalny, w przeciwnym razie wyświetlone powinno zostać okno komunikatu. Funkcja Fail () powinna zostać wywołana poprzez wskaźnik obiektu CDataExchange, w celu poinformowania wywoływanej funkcji UpdateData (), iż proces zatwierdzania nie powiódł się. Gdybyśmy na przykład chcieli rozszerzyć funkcjonalność procedury zatwierdzającej z bieżącego przykładu o wykluczenie możliwości wprowadzenia wartości 31, utworzona funkcja powinna mieć postać: void DDV_ValidateAge(CDataExchange* pDX, int& nCheckAge) { if(pDX->m_bSaveAndValidate && (nCheckAge < 18 || nCheckAge > 65 || nCheckAge == 31)) ( AfxMessageBox( "Ages must be between 18 and 65, but not 31") ; pDX->Fail() ; } } Ponieważ parametr nCheckAge stanowi odwołanie do zmiennej, opcjonalnie możemy dokonać zresetowania jego wartości do wartości dozwolonej, jeśli wprowadzona przez użytkownika wartość okazała się niewłaściwa. Zatwierdzanie danych okien dialogowych W przypadku umieszczania makr DDV_. . . w nadpisanej funkcji DoDataExchange () powinny one znaleźć się tuż za makrem DDX_ ... danych składowych, które zatwierdzają. Kompletną listę makr do wymiany danych zawiera plik nagłówkowy afxdd_.h. Następnie z funkcji DoDataExchange () wywołana może zostać nasza funkcja DDV_ValidateAge, po uaktualnieniu zawartości składowej zmiennej przez odpowiednią funkcję DDX. Wywołanie to powinno mieć postać: DDV_ValidateAge(pDX, m_nAge) ; W ten sposób zatwierdzone mogą zostać jedynie wartości z zakresu od 18 do 65, z wyłączeniem 31. Używanie niemodalnych okien dialogowych Okna dialogowe niemodalne, w przeciwieństwie do okien modalnych, nie przejmują kontroli nad działaniem aplikacji. Zamiast tego, używane są zwykle podczas normalnej pracy aplikacji, przekazując jej z powrotem komunikaty i szczegółowe odpowiedzi. Okna te występują zwykle w postaci pływających pasków narzędziowych, pozwalając użytkow- 236_____________________________________Poznaj Visual C++ 6 nikowi na dokonanie wyboru i klikanie przycisków w celu dokonania zmian ustawień lub wyboru narzędzia. Niemodalne okna dialogowe konstruuje się także za pomocą otwartego w edytorze zasobów szablonu. Możemy usunąć zbędne w ich przypadku przyciski OK oraz Cancel. Klasy obsługujące te okna wyprowadzane są również z klasy CDialog. Różnice pomiędzy oknami obu rodzajów polegają na sposobie ich otwierania i zamykania. Tworzenie i usuwanie niemodalnego okna dialogowego Zamiast uruchamiania pętli modalnej za pomocą funkcji DoModal () do wyświetlania modalnego okna dialogowego, niemodalne okno wyświetlić możemy przy użyciu funkcji Create () klasy CDialog. Możemy umieścić wywołanie funkcji Create () w konstruktorze wyprowadzonej klasy okna dialogowego w celu utworzenia egzemplarza klasy, gdy jej obiekt został już utworzony. Możemy również oddzielić egzemplarz klasy i wywołać funkcję Create w celu dołączenia kodu inicjalizującego. Funkcja Create () posługuje się dwoma parametrami. Pierwszym jest identyfikator okna dialogowego, które ma zostać utworzone, drugi parametr zaś jest opcjonalnym wskaźnikiem do obiektu okna-rodzica (domyślnie głównego okna aplikacji). Powodzenie procedury utworzenia okna funkcja Create () stwierdza zwróceniem wartości TRUE, a FALSE w wypadku niepowodzenia. Gdy funkcja Create () zwraca TRUE, niemodalne okno dialogowe jest już utworzone, lecz nie może jeszcze być wyświetlone. Aby je ujrzeć, należy wywołać funkcję okna ShowWindow (), przekazując jej znacznik SW_SHOW. O ile modalne okno dialogowe może pozostawać w zasięgu jedynie funkcji obsługi, dostęp do okna niemodalnego musi być możliwy również dla innych części programu. W związku z tym niemodalne okno dialogowe powinno być tworzone dynamicznie, przez użycie operatora new, a jego adres powinien być zapisany w globalnym lub składowym wskaźniku. Okno dialogowe możemy zamknąć stosując operator C++, delete. Destruktor klasy bazowej CDialog dokona usunięcia obiektu okna. Możemy wypróbować tę technikę, tworząc za pomocą AppWizard kolejny przykładowy program bazujący na konstrukcji okna dialogowego. Po jego utworzeniu, dodamy szablon nowego, niemodalnego okna dialogowego, nadając mu identyfikator IDD_MODELESS (opis odpowiedniej procedury znajdziemy w punkcie „Umieszczenie w projekcie szablonu nowego okna dialogowego"). Następnie utworzymy klasę obsługującą nowe okno, którą nazwiemy CModeless. Operację tę przeprowadzić należy według algorytmu w podrozdziale „Tworzenie klasy dla nowego okna dialogowego za pomocą CIassWizard". Po utworzeniu klasa powinna być widoczna na liście klas, w panelu CIassYiew. Gdy klikniemy widoczny obok znak plusa, w rozwiniętej gałęzi ujrzymy konstruktor funkcji CModeless (). Klikniemy więc dwukrotnie tę pozycję, by rozpocząć edycję konstruktora. Wprowadzimy następnie treść listingu 10.1, by umożliwić programowi wywoływanie funkcji Create () oraz ShowWindow (). Tworzenie własnych okien dialogowych i ich klas___________________ 237 Listing 10.1. LST10_1.CPP - dodanie funkcji Create() i ShowWindow() do konstruktora niemodalnego okna dialogowego 1 CModeless::CModeless(CWnd* pParent /*=NULL*/) 2 : CDialog(CModeless::IDD, pParent) 3 { 4 //((AFX_DATA_INIT(CModeless) 5 // NOTĘ: CIassWizard umieści tu kod inicjalizujący 6 //}}AFX_DATA_INIT 7 8 // ** Utworzenie niemodalnego okna dialogowego 9 if (Create(CModeless::IDD,pParent)) O 10 ( 11 // ** Wyświetlenie okna 12 ShowWindow(SW_SHOW); @ 13 } 14 } O Okno dialogowe zostaje utworzone przez funkcję Create () © Funkcja showMindow () powoduje wyświetlenie okna Dodatkowe linie (8 do 13) ustawiają okno dialogowe do funkcjonowania jako niemo-dalne. Linia 9 wywołuje funkcję Create () i przekazuje jej wygenerowany przez CIassWizard enumerator CModeless: : IDD, przechowujący identyfikator okna oraz wskaźnik pParent do okna-rodzica. Gdy funkcja Create () poinformuje o powodzeniu swego działania zwracając wartość TRUE, wywołana w linii 12 funkcja showWindow () ze znacznikiem SW_SHOW spowoduje wyświetlenie okna dialogowego. Aby otworzyć nowe niemodalne okno dialogowe, powinniśmy umieścić obiekt przycisku w klasie okna. Przycisk ten otrzyma identyfikator IDC_START_MODELESS i będzie służył do wywoływania okna. Drugi przycisk natomiast, o identyfikatorze IDC_STOP_ MODELESS będzie to okno zamykał. Obydwa przyciski widoczne są na rysunku 10.8. Rysunek 10.8. Dodane do okna głównego aplikacji przyciski, otwierający i zamykający niemodalne okno dialogowe Dodajmy także funkcje obsługi komunikatów BN_CLICKED dla każdego z tych przycisków, a później kod implementujący owe funkcje, co pozwoli nam przywoływać i zamykać niemodalne okno (listing 10.2). Funkcja OnStartModeless () będzie służyła do otwierania okna, a OnStopModeless () do jego zamykania. 238_____________________________________Poznaj Visual C++ 6 Listing 10.2. LST10_2.CPP - tworzenie i usuwanie niemodalnego okna dialogowego przez funkcje obsługi przycisków głównego okna dialogowego 1 // ** Umieszczenie definicji żądanej klasy 2 łinclude „modeless.h" 3 4 // ** Zadeklarowanie globalnego wskaźnika do obiektu okna niemodalnego 5 CModeless* g_pDlgModeless = NULL; O 6 7 void CMLaunchDlg::OnStartModeless() 8 { 9 // ** Utworzenie nowego okna dialogowego, jeśli globalny wskaźnik 10 // ** wskazuje NULL, co oznacza iż okno dialogowe 11 // ** nie zostało jeszcze utworzone 12 i f (!g_pDlgModeless) @ 13 g_pDlgModeless = new CModeless(this); @ 14 ) 15 16 void CMLaunchDlg::OnStopModeless() 17 { 18 // ** Jeśli wskaźnik niemodalnego okna zawiera wskazanie 19 // ** okno dialogowe może zostać zamknięte i usunięte 20 if (g_pDlgModeless) 21 ( 22 delete g pDIgModeless; @ 23 g_pDlgModeless = NULL; 24 } 25 } O Dostęp do globalnego wskaźnika uzyskać można z każdego miejsca w aplikacji, jednak wymagana może być deklaracja extern © Jeśli okno dialogowe nie jest uaktywnione, utworzone zostaje przez ustanowienie nowego obiektu klasy CModeless © Destruktor automatycznie dokona usunięcia okna Linia 2 powyższego listingu powoduje wczytanie z pliku modeless.h niezbędnych definicji klasy CModeless. W linii 5 zadeklarowany zostaje globalny wskaźnik obiektu okna dialogowego CModeless (g_pDlgModeless) i zainicjalizowany z wartością NULL. Warunkowe sprawdzenie wartości NULL wskaźnika g_pDlgModeless przeprowadzone zostaje przez funkcję OnStartModeless () w celu upewnienia się, iż okno nie jest aktywne. Jeśli zostanie to potwierdzone, w linii 13 tworzone jest nowe okno, które wskazuje poprzez Tworzenie własnych okien dialogowych i ich klas 239 wskaźnik this gtówne okno aplikacji jako okno-rodzica. Nowe okno dialogowe zostanie wyświetlone natychmiast po przeprowadzeniu procedury jego utworzenia poprzez funkcje Create () oraz ShowWindow () zaimplementowane w konstruktorze okna dialogowego. W wyniku kliknięcia przycisku Stop Modeless w linii 16 następuje wywołanie funkcji OnStopModeless (). Wartość wskaźnika g_pDlgModeless sprawdza się w linii 20 w celu stwierdzenia, iż okno dialogowe istnieje (gdy wartość wskaźnika jest różna od NULL). Okno dialogowe zostaje zamknięte w linii 22 (usunięte przez destruktor). Na koniec w linii 23 wartość wskaźnika ustawiona zostaje jako NULL, co pozwala na ponowne utworzenie nowego okna dialogowego. Po skompilowaniu i uruchomieniu aplikacji, będziemy mogli dynamicznie tworzyć i zamykać okno dialogowe poprzez klikanie przycisków. PATRZ TAKŻE • By otrzymać informacje o tworzeniu funkcji obsługi zdarzeń i komunikatów o kuknięciu przycisków, zajrzyj do rozdziału 5. Przesyłanie i pobieranie danych z niemodalnego okna dialogowego Ustalanie wartości czy wywoływanie funkcji składowych niemodalnego okna dialogowego wykonywać możemy w każdym momencie istnienia obiektu okna, uzyskując dostęp do nich poprzez wskaźnik. Przekazywanie danych pomiędzy kontrolkami okna a przydzielonymi zmiennymi składowymi odbywa się na tych samych zasadach, co w przypadku okien niemodalnych, poprzez funkcje UpdateDataO oraz DoDataExchange (). Zmiennym składowym możemy nadawać wartości z aplikacji głównej, a poprzez wywołanie funkcji UpdateData () z parametrem FALSE spowodować transfer zawartości zmiennych do obiektów sterujących. Z okna niemodalnego możemy także wywoływać funkcje lub ustalać zawartość zmiennych innych obiektów aplikacji w odpowiedzi na czynności wykonane przez użytkownika. Aby uzyskać tę możliwość, musimy przekazać oknu niemodalnemu wskaźnik do żądanego obiektu (lub użyć wskaźnika globalnego), przez co uzyskuje ono dostęp do obiektu. Konstruktor niemodalnego okna dialogowego jest doskonałym miejscem na umieszczenie tych wskaźników. Podczas tworzenia okna możemy modyfikować listę parametrów konstruktora w celu ich umieszczenia. Wskaźniki te przechować można w lokalnych zmiennych składowych niemodalnego okna dialogowego, dzięki czemu wszystkie jego funkcje mają dostęp do żądanych obiektów. Musimy także pamiętać o umieszczeniu rozkazu preprocesora # indu de na początku pliku definiującego klasy niemodalnego okna dialogowego, by kompilator mógł rozpoznać deklaracje zmiennych. Dodamy zatem do przykładowego programu kod, który zademonstruje opisane wyżej działanie. Do głównego okna aplikacji dodamy pole listy, a w nim umieścimy komunikaty wysyłane przez dwa nowe przyciski, umieszczone w niemodalnym oknie dialogowym. 240 Poznaj Visual C++ 6 Pole edycji dodane do tego samego okna pobierać będzie tekst wyznaczony w oknie głównym. Opisane zmiany spowodują, iż aplikacja będzie wyglądała jak na rysunku 10.9. •Bl Start Modeiess Stop Modclc» OK Cancel "POP™ i "POP" ;:;. "POP-ilS "POPIS-POP" i^ "pop-'si; "pow" |:,|: "POW" 1:5 "POW" lis TOW" la-fow" |K "POW" Rysunek 10.9. Interakcja pomiędzy aplikacją a jej niemodalnym oknem dialogowym Po pierwsze, musimy zmodyfikować konstruktor niemodalnego okna dialogowego w taki sposób, aby wskaźnikiem okna-rodzica był CMLaunchDlg, zamiast domyślnego wskaźnika cwnd. Zmianę tę możemy wprowadzić w definicji konstruktora w pliku nagłówkowym definicji klasy CModeless (Modeless.h). Wpis ten będzie powodował akceptację wyłącznie wskaźnika CMLaunchDlg, a powinien mieć następującą postać: CModeless(CMLaunchDlg* pParent) ; // konstruktor standardowy Edycję pliku Modeless.h rozpoczynamy dwukrotnym kliknięciem pozycji CModeless na liście klas w panelu ClassView. Skoro kompilator jest już poinformowany o zastosowaniu CMLaunchDlg, musimy jeszcze umieścić na początku pliku Modeless.h następujący rozkaz preprocesora: łinciude "MLaunchDIg.h" Do przechowania przekazanego wskaźnika będziemy potrzebowali korespondującej zmiennej składowej. Dodać ją możemy jako składową chronioną po operatorze protec-ted, wpisując linię: CMLaunchDlg* m pParent Aby zapisać przekazany wskaźnik w zmiennej składowej, musimy także zmienić funkcję konstruktora w taki sposób, aby inicjalizowała wskaźnik z przekazanego parametru pParent. Oto treść wprowadzonych zmian: CModeless::CModeless(CMLaunchDig* pParent) : CDialog(CMoldeless::IDD, pParent) , m pParent(pParent) Tworzenie własnych okien dialogowych i ich klas 241 Zauważmy, iż okno gtówne pparent może zostać przekazane jako okno-rodzic klasie bazowej CDialog. pparent można użyć także do zainicjalizowania lokalnej składowej W sekcji m_pParent (pparent). W oknie głównym aplikacji umieścić możemy pole listy, w którym wyświetlane będą komunikaty wysyłane przez modalne okno dialogowe - widać to na rysunku 10.9. Polu temu przydzielić możemy zmienną klasy CListBox, m_DisplayList za pomocą kreatora CIassWizard, jak pokazano na rysunku 10.10. Następnie dodajmy do niemodalnego okna dialogowego dwa przyciski i pole edycji. Przyciski będą uruchamiały dwie funkcje obsługi, wysyłające do pola listy w głównym oknie aplikacji dwa różne komunikaty. Stosując CIassWizard należy utworzyć owe dwie funkcje, obsługujące komunikaty BN_CLICKED pochodzące od dwóch nowych przycisków i wyświetlające określone łańcuchy znakowe w polu listy głównego okna. Zamykanie niemodalnych okien dialogowych przez funkcję DestroyWindow() Gdy w oknie niemodalnym znajdują się przyciski OK oraz Cancel należy zmienić postać domyślnej implementacji OnOK() i OnCancel (), ponieważ powodują one wywołanie funkcji EndDialogO , co w efekcie daje ukrycie okna, zamiast jego destrukcji. Aby osiągnąć właściwy rezultat, należy spowodować wywołanie funkcji DestroyWindow (), które może nastąpić z OnOK () lub OnCancel (). Dodajmy zatem następujący kod: void CModeless::OnPop() ( m pParent ->m_DisplayList.AddString(„**POP**") ; } void CModeless::OnPow () ( m_pParent ->m_DisplayList.AddString(„**POW**") ; } Nowe funkcje wykorzystują nowy, osadzony wskaźnik CMLaunchDlg, (m_pParent) w celu uzyskania dostępu do zmiennej głównego okna aplikacji, m_DisplayList. Funkcja pola listy AddString () umieszcza następnie w tym polu łańcuch znakowy informujący, który z przycisków kliknął użytkownik w niemodalnym oknie dialogowym. Aby zademonstrować możliwości dostępu do niemodalnego okna dialogowego z głównego okna aplikacji, umieścimy w oknie niemodalnym pole edycji, za pomocą edytora zasobów. Następnie, korzystając z CIassWizard, dodać możemy zmienną składową CString, m_DispMsg, którą przydzielimy polu edycji. 242 Poznaj Visual C++ 6 Kiedy stosować systemowe modalne okna dialogowe Jedna z opcji stylu okna dialogowego umożliwia utworzenie systemowego modalne-go okna dialogowego. Zapobiega to możliwości przeniesienia się użytkownika do innego okna lub aplikacji przed zamknięciem okna dialogowego. Okien tego typu należy używać jedynie w przypadku okien alarmujących, że operacje wykonane przez inną aplikację mogłyby uszkodzić konfigurację systemu. Dostęp do owej zmiennej uzyskać można z każdego miejsca aplikacji, gdy niemodalne okno jest aktywne poprzez wskaźnik g_pDlgModeless. Aby wyświetlić tekst w polu edycji, należy wymusić transfer danych, wywołując funkcję UpdateDataO ze znacznikiem FALSE. Wpisać należy, co następuje: if (g_pDlgModeless) // Upewnienie się, czy okno dialogowe jest aktywne { g pDIgModeless ->m DispMsg=CString ("I'm a modeless dialog") ; g_pDlgModeless ->UpdateData(FALSE) ; } PATRZ TAKŻE • O tworzeniu funkcji obsługi zdarzeń / komunikatów o kuknięciu przycisków czytaj w rozdziale 5. t Więcej szczegółów o stosowaniu pól listy uzyskasz w rozdziale 6. Obsługa komunikatu o zamknięciu niemodalnego okna dialogowego Pozostała jeszcze jedna rzecz do omówienia w związku ze stosowaniem niemodalnych okien dialogowych. Pomimo iż usunęliśmy z niego przyciski OK oraz Cancel, w prawym górnym rogu okna pozostał przycisk zamykający z widocznym nań krzyżykiem. Jeśli zamkniemy niemodalne okno za pomocą tego przycisku, okno zostanie zamknięte, lecz jeśli aplikacja nie przechwyci komunikatu, pozostawiony zostanie globalny wskaźnik do okna oraz zajęty obszar pamięci. Może to spowodować niedostatek pamięci, co z kolei będzie przyczyną problemów z otwarciem nowego niemodalnego okna już po zamknięciu i poprzedniego. Aby rozwiązać ten problem, konieczne jest przechwycenie komunikatu WM_CLOSE, by możliwe było usunięcie obiektu okna i wyzerowanie wskaźnika. Za pomocą okna New Windows Message and Event Handlers możemy utworzyć funkcję obsługi komunikatu f o zamknięciu okna przez kliknięcie przycisku zamykającego. Okno New Windows... wywołujemy klikając prawym klawiszem myszy pozycję CModeless w panelu ClassView, a następnie wybierając pozycję Add Windows Message Handler. Tworzenie własnych okien dialogowych i ich klas 243 Poniższe linie kodu służą do uruchomienia funkcji obsługi OnCiose (), aby mogła ona powodować usunięcie obiektu okna i zresetowanie globalnego wskaźnika: extern CModeless* g pDIgModeless; void CModeless::OnClose() ; { CDialog::OnClose () ; delete this ; g_pDlgModeless = NULL; Deklaracja extern użyta została do zadeklarowania wskaźnika globalnego. Jest to ten sam wskaźnik g_pDlgModeless, który zadeklarowany został w MLaunchDIg.cpp. Funkcja klasy bazowej OndoseO wywołana zostaje dla przeprowadzenia procesu zamknięcia okna. Gdy funkcja kończy działanie, obiekt niemodalnego okna dialogowego (this) zostaje usunięty z pamięci. Wskaźnik g_pDlgModeless ulega następnie wyzero-waniu, przyjmując wartość NULL, a główne okno aplikacji może powołać do życia nowe niemodalne okno dialogowe. Usunięcie przycisku zamykającego Zamiast obsługi komunikatu WM_CLOSE, możemy uniemożliwić użytkownikowi korzystanie z przycisku zamykającego przez usunięcie zaznaczenia opcji System Menu z karty Styks, znajdującej się wśród kart właściwości niemodalnego okna dialogowego w edytorze zasobów (rysunek 10.11). Brak tej opcji spowoduje usunięcie ikony zamykania okna oraz menu, w związku z czym użytkownik nie będzie mógł zamknąć okna. Stosuje się to w sytuacjach, gdy niemodalne okno dialogowe musi być otwarte przez cały czas. l Dialog Ptoperties : : -W ?„ Geneia Style s Morę S tyłeś Entended Styles Style: p litle bar r Clifi siblings ^ Popup d r ISystem meny r Clip chiidren ' gotdec "': : : r Mininize bon r Horizontalscroll Dialog Fiame d r Maximi;eb ox r Vertical taoll Rysunek 10.11. Usunięcie opcji System Menu dla niemodalnego okna dialogowego Część III Elementy aplikacji Rozdział 11 Praca z rysunkami, bitmapami i ikonami Tworzenie, importowanie i edytowanie ikon oraz rysunków bitma-powych Wyświetlanie ilustracji w oknach dialogowych Implementacja przycisków graficznych Umieszczanie rysunków na listach, w drzewach i listach rozwijanych Obiekty graficzne są bardzo ważne dla systemu Windows. Istnieje po temu kilka powodów: są to znaki międzynarodowe, zajmują mniej miejsca niż opisy słowne i po prostu ładnie wyglądają. Głównym powodem ich stosowania jest umożliwienie użytkownikowi szybkiego odnajdywania interesujących go programów i funkcji, bez konieczności czytania tekstu. Aplikacje Windows korzystają z kilku typów rysunków. • Ikona jest rysunkiem przypisanym do aplikacji i wyświetlana w Eksploratorze Windows. Wykorzystywana jest również do tworzenia skrótów do aplikacji z Pulpitu. • Rysunki bitmapowe wykorzystywane są do tworzenia ekranów powitalnych, przycisków na paskach narzędziowych i również mogą być umieszczane w oknach dialogowych i systemowych. • Kursor jest rysunkiem stosowanym do reprezentowania wskaźnika myszy. Różne odmiany kursora pojawiają się w aplikacjach graficznych i informują o możliwym sposobie edycji danego obiektu. Edytor zasobów znajdujący się w Developer Studio pozwala tworzyć i edytować każdy z wyżej opisanych obiektów graficznych. W projekcie możemy umieścić tyle ikon, bitmap i kursorów, ile tylko chcemy. Później jednak musimy zaimplementować odpowiednie kody pozwalające na korzystanie z tych zasobów. 248_____________________________________Poznaj Visual C++ 6 Stosowanie edytora graficznego Edytor zasobów graficznych pozwala na projektowanie różnego rodzaju tych zasobów (bitmapy, kursory, ikony i paski narzędziowe). Rysować możemy „wolną ręką" lub korzystając z dostępnych opcji i narzędzi do rysowania kształtów i wypełnień. Okno edytora podzielone jest zwykle na dwa panele. Panel po lewej stronie przedstawia edytowany obiekt w naturalnej skali, prawy panel natomiast w powiększeniu. W panelu zawierającym powiększony obraz możemy przeprowadzać operacje na pojedynczych pikselach. Widok w obu panelach jest automatycznie odświeżany podczas edycji. Bez względu na to, jaki rodzaj zasobów edytujemy, wiele spośród funkcji graficznych jest identycznych. Gdy otwarty jest edytor graficzny, dostępne staje się menu Image. Dostarcza ono kilku opcji. Opcja Invert Colors umożliwia odwrócenie kolorów w oznaczonym obszarze rysunku. Zaznaczony obszar może być obracany zarówno w poziomie, jak i w pionie, l Używając menu Image możemy także definiować i zpisywać własne palety kolorów. Narzędzia rysujące dostępne są z paska narzędziowego Graphics, kolory wybieramy natomiast z paska Colors, widocznego na rysunku 11.1. Jeśli paski narzędziowe nie są widoczne, musimy wykonać poniższe czynności. Otwieranie pasków narzędziowych Graphics i Colors 1. Wybierz pozycję Customize z menu Tools. Otwarte zostanie okno Customize. 2. Wybierz kartę Toolbars. 3. Na liście Toolbars kliknij pozycje Graphics i Colors. 4. Kliknij Close. 5. Alternatywnie ustaw wskaźnik myszy w pasku menu u góry okna i kliknij prawym klawiszem myszy. 6. Z menu skrótów wybierz Graphics lub Colors. Pasek narzędziowy Graphics zawiera narzędzia, które spotkać można w programach do obróbki grafiki rastrowej. Przykładowo, jest natrysk farby, próbnik koloru, szablony kształtów regularnych i nieregularnych, wypełnionych i obrysowych itd. U dołu paska Graphics znajduje się sekcja, w której dokonać możemy wyboru stylu pędzla, grubości linii oraz innych parametrów, zależnych od rodzaju wybranego narzędzia. Podczas projektowania rysunku, korzystać możemy z opcji Undo (Cofnij) i Redo (Ponów) z menu Edit, a stosując skróty klawiaturowe, odpowiednio Ctri+Z oraz Ctri+Y. Paleta Colors umożliwia wybór koloru rysowania pierwszego planu i tła, poprzez kliknięcie odpowiednio lewego lub prawego klawisza myszy. Wybrane narzędzie rysuje kolorem pierwszego planu, gdy naciśnięty jest lewy klawisz myszy, a kolorem tła, gdy naciśniemy klawisz prawy. Praca z rysunkami, ikonami i bitmapami 249 Rysunek 11.1. Edycja ikony O Narzędzia rysowania i edycji © Rysunek nieprzezroczysty © Rysunek przezroczysty O Kolor ekranu © Odwrócenie kolorów @ Kolor pierwszego planu © Kolor tła Podczas edycji ikon i kursorów paleta Colors umożliwia korzystanie z dwóch dodatkowych opcji, koloru ekranu i koloru odwrotnego (rysunek 11.1). Piksele narysowane kolorem ekranu stają się przezroczyste podczas wyświetlania kursora lub ikony, tworząc rysunek o nieregularnym kształcie. Piksele narysowane kolorem odwrotnym, przybierają kolor odwrotny podczas przeciągania ikony. W edytorze graficznym możemy korzystać również ze schowka, przenosząc doń i wklejając z niego fragmenty obrazu. Jeśli przykładowo chcemy mieć kilka podobnych ikon, możemy skopiować pewną część obrazu jednej z nich, a następnie wkleić go do innych. Wklejanie fragmentu możemy przeprowadzić w trybie przezroczystym lub nieprzezroczystymi, wybierając właściwą opcję z paska Graphics albo zaznaczając lub usuwając zaznaczenie opcji Draw Opaque z menu Image. • Szczegóły dotyczące projektowania i edycji zasobów pasków narzędziowych znajdziesz w rozdziale 14. Tworzenie i edycja ikon Wizualnie odróżnienie ikony od bitmapy jest trudne, istnieją jednakże pewne istotne cechy różnicujące. Bitmapa jest tablicą danych, które reprezentują kolor każdego piksela. Ikona tworzona jest z dwóch bitmap, przy czym pierwsza jest kolorową bitmapą, druga natomiast maską bitmapową. Poprzez połączenie informacji zawartych w obu bitmapach, możliwe jest określanie kolorów przezroczystych i odwrotnych ikony. Bardzo często spo- tykamy ikony, które mają najróżniejsze kształty, są na przykład okrągłe. W rzeczywistości, wszystkie ikony są kwadratowe; przezroczysta maska umożliwia im przybieranie nieregularnych kształtów. Każda ikona może zawierać kilka rysunków, których wymiary i liczba użytych kolorów zależą od aktualnego urządzenia wyświetlającego. Modyfikacja domyślnej ikony MFC Aplikacje utworzone przez kreator AppWizard opatrzone są domyślną ikoną. Jest to trójwymiarowy rysunek piramidki MFC. Identyfikatorem tej ikony jest IDR_MAINFRAME i jest to ikona przypisana aplikacji. Widać ją także w oknie dialogowym About. Dla przetworzenia tej ikony według własnych potrzeb należy wykonać procedurę z podrozdziału "Modyfikacja ikony MFC". Typowe ikony przypisane aplikacjom lub dokumentom posiadają dwa 16-kolorowe rysunki: standardowy (32x32 piksele) i małe (16x16 pikseli). Rysunki te są zarejestrowane w systemie Windows, mogą zatem być wyświetlane w Eksploratorze Windows, menu Start, a także na pasku zadań. Modyfikacja ikony MFC 1. Rozwiń katalog Icon w panelu ResourceView i kliknij dwukrotnie pozycję IDR_ MAINFRAME. Domyślna ikona MFC wyświetlona zostanie w otwartym edytorze (rysunek 11.1). 2. Edytuj obrazek Standard (32x32) używając pasków narzędziowych Graphics i Colors. Edycja graficzna opisana została w punkcie „Stosowanie edytora graficznego", w tym samym rozdziale. 3. Z listy rozwijanej Device wybierz pozycję Smali (16x16) i edytuj otwarty obrazek ikony. Jeśli ta mniejsza wersja ikony nie została dotąd utworzona, system Windows utworzy ją automatycznie, poprzez pomniejszenie ikony standardowej, w celu wyświetlania w Eksploratorze Windows i na pasku zadań. fi\im. iAr\^c • Więcej o rejestrowaniu ikon aplikacji dowiesz się w rozdziale 15. Umieszczanie nowego zasobu ikony Każdy projekt może posiadać wiele ikon. Mogą to być ikony narysowane przez nas lub pobrane z istniejących projektów lub plików typu .ico. Nowo tworzone ikony przyjmują tryb przezroczystości i domyślne urządzenie wyświetlające VGA, co determinuje ich wymiary (32x32). Aby utworzyć nową ikonę, należy uczynić co następuje: Tworzenie nowego zasobu ikony l. Naciśnij Ctri+R lub wybierz pozycję Resource z menu Insert. Otwarte zostanie okno dialogowe Insert Resource, widoczne na rysunku 11.2. Rysunek 11.2. Okno dialogowe Insert Resource 2. Z listy Resource Type wybierz pozycję Icon i kliknij przycisk New. W edytorze graficznym otwarta zostanie pusta ikona. W celu utworzenia nowej ikony można także skorzystać ze skrótu klawiaturowego Ctrl+4. 3. Ustal wymiary rysunku za pomocą listy rozwijanej Deyices. 4. Aby nadać rysunkowi nowe wymiary, kliknij przycisk New Device Image po prawej stronie listy Devices lub naciśnij klawisz Ins. Otwarte zostanie okno dialogowe New Icon Image widoczne na rysunku 11.3. 252 Poznaj Visual C++ 6 ^^^^^^^^^^^^^^HNew Icon Image BOI ^ ^arget device: p^ 48n43. 256 colors Cancel Monochrome (32x32] ——————— Smalili 6x1 E) Custom,.. Rysunek 11.3. Okno dialogowe New Icon Image 5. Wybierz żądane wymiar z listy Target Device lub kliknij przycisk Custom w celu określenia własnych wymiarów i liczby kolorów. 6. Z menu View wybierz pozycję Properties. Wyświetlone zostanie okno dialogowe Icon Properties, które przedstawione jest na rysunku 11.4. Okno to można wywołać także klikając dwukrotnie w oknie edytora, poza obszarem rysowania ikony. 7. 8. Wprowadź identyfikator nowej ikony. Tradycyjnie identyfikatory te poprzedzane są prefiksem IDI_. Wprowadź nazwę pliku w polu edycji File Name. Jest to czynność opcjonalna, gdyż Developer Studio generuje własne nazwy dla plików ikon, które przechowuje w podkatalogu /res, głównego katalogu projektu. Icon Piopeities -la T Genaal [ Palette | ID: |lDI_MYICON~-\ Width: 32 Height: 32 Colots: 16 File narne: res'irnyicon.jco Rysunek 11.4. Okno dialogowe właściwości ikony By usunąć ikonę z projektu, należy zaznaczyć jej identyfikator w panelu Resour-ceView, a następnie nacisnąć klawisz Delete. Czynność ta nie usuwa jednakże pliku .bmp z dysku, co musimy wykonać własnoręcznie. Aby usunąć ikonę specyficzną dla danego urządzenia, powinniśmy z listy Devices wybrać właściwy jej rozmiar i z menu Image wybrać pozycję Delete Device Image. Umieszczanie nowego zasobu bitmapy Zasoby tego rodzaju wykorzystywane są na wiele sposobów. Można ich użyć do tworzenia na przykład wizerunku kraciastej flagi, który widoczny jest w ostatnim kroku podczas pracy z kreatorem AppWizard. Możemy użyć bitmap również do umieszczenia symboli zamiast napisów na przyciskach, ale o tym powiemy później. Praca z rysunkami, ikonami i bitmapami 253 Odmiennie niż ikony, bitmapy mogą posiadać wymiary aż do 2048x2048 pikseli. W odróżnieniu od ikon nie posiadają atrybutów przezroczystości i odwrotnych kolorów. W projekcie można umieścić dowolną liczbę bitmap. Można narysować je samodzielnie w edytorze graficznym lub też zaimportować z innych aplikacji pliki z rozszerzeniem .bmp. W celu utworzenia nowej bitmapy wykonajmy procedurę z podrozdziału „Tworzenie nowego zasobu bitmapy". Bitmapę możemy usunąć z projektu w taki sam sposób, co ikonę, czyli naciskając klawisz Delete po zaznaczeniu jej identyfikatora w panelu ResourceView. Tworzenie nowego zasobu bitmapy 1. Naciśnij CtrI+R lub wybierz pozycję Resource z menu Insert. Otwarte zostanie okno dialogowe Insert Resource (rysunek 11.2). 2. Z listy Resource Type wybierz typ Bitmap, a następnie kliknij przycisk New. Utworzona zostanie pusta bitmapa i otwarta w edytorze zasobów graficznych. Możesz skorzystać również ze skrótu klawiaturowego Ctrl+5. 3. Z menu View wybierz pozycję Properties, po czym wyświetlone zostanie okno dialogowe Bitmap Properties, widoczne na rysunku 11.5. Okno właściwości możemy wywołać także klikając dwukrotnie w obszarze edytora graficznego i wyłączając obszar samej bitmapy. Bitmap Piopeitiet Rysunek 11.5. Okno właściwości bitmapy 4. Wprowadź identyfikator bitmapy. Identyfikatory zasobów tego rodzaju poprzedzane są zwyczajowo prefiksem IDB_. 5. Ustal żądane wymiary i liczbę kolorów dla bitmapy. Szczegóły dotyczące tej czynności opisane są w podrozdziale „Ustalanie rozmiarów i liczby kolorów dla bitmapy". 6. W polu edycji Filename wpisz nazwę pliku. Czynność ta nie jest konieczna, gdyż Developer Studio generuje własne nazwy dla tych plików. Pliki przechowywane są w podkatalogu /res. Ustalanie rozmiarów i liczby kolorów dla bitmapy Domyślnie dla nowego zasobu bitmapy przyjmowane są wymiary 48x48 pikseli, liczba kolorów natomiast to 16. Dla ustalenia własnych wartości należy wykonać czynności opisa- 254 Poznaj Visual C++ 6 ne w podpunkcie „Modyfikacja atrybutów bitmapy". Gdy bitmapa zostaje zmniejszona, odrzucone są piksele leżące po prawej dolnej stronie rysunku. Podczas powiększania bitmapy nowe piksele dodawane są w tych samych obszarach i wypełniane kolorem tła. Modyfikacja atrybutów bitmapy 1. Rozwiń folder Bitmap w panelu ResourceView i dwukrotnie kliknij identyfikator bitmapy, którą chcesz edytować. 2. Z menu View wybierz pozycję Properties. Otwarte zostanie okno właściwości bitmapy, czyli Bitmap Properties. Okno to możesz wywołać również poprzez dwukrotne kliknięcie w obszarze edytora, lecz poza obszarem samej bitmapy. 3. Kliknij kartę Generał. Wprowadź żądaną szerokość bitmapy (Width) oraz jej wysokość (Height). Zmiany wymiarów możesz dokonać także poprzez kliknięcie i przeciągnięcie jednego z uchwytów wymiarujących, widocznych na krawędzi bitmapy. 4. 5. Z listy rozwijanej Colors wybierz żądaną liczbę kolorów. Aby dopasować do własnych oczekiwań paletę kolorów, kliknij kartę Palette, a następnie dwukrotnie kolor, który chcesz zmienić. Otwarte zostanie okno Custom Color Selector, widoczne na rysunku 11.6. Wybierz odpowiedni kolor i kliknij OK. SBR HiJe:|il" fled:[?55" ^•K::::@i;Sw , . __—————^ ,________ ! ^i!!,",. c-i. hsil r...— ,.,...„„„,„.„, Sacfzio" 6(een:j Color l,utn:[Tio" ^:filiie:J255'|i^ | OK | ^g^ll}111"^' Rysunek 11.6. Okno dialogowe Custom Color Selector Importowanie rysunków Jeśli ktoś posiada zacięcie do rysowania, prawdopodobnie będzie chciał tworzyć własne obrazki. Można jednakże importować tego rodzaju zasoby z istniejących plików i projektów, czy też nawet z plików wykonywalnych, obecnych na dysku komputera. Aby zaimportować ikonę lub bitmapę z plików .ico i .bmp, wykonać należy czynności opisane w podpunkcie "Importowanie zasobów z pliku". Kiedy wygenerujemy własny plik wyko- Praca z rysunkami, ikonami i bitmapami_________________________ 255 nywalny, w jego wnętrzu zapisany zostanie również włączony do projektu zasób graficzny. Odwracając zatem sytuację, możemy pobierać takie zasoby z plików wykonywalnych innych autorów. Importowanie zasobów z pliku 1. Wybierz pozycję Import z menu skrótów panelu ResourceView lub wybierz Resour-ce z menu Insert i kliknij przycisk Import w oknie dialogowym Insert Resouree. Program wyświetli okno dialogowe Import Resouree. Okno to jest podobne do systemowego okna File Open. 2. Przejrzyj katalogi dyskowe i wybierz plik zawierający interesujący cię zasób. Kliknij następnie przycisk Import. Aby określić konkretny typ poszukiwanego pliku, posłuż się listą rozwijaną File of Type. 3. Jeśli wybrany plik jest plikiem zgodnym z oczekiwaniami programu, zasób zostanie dodany do projektu. Nadany mu zostanie odrębny identyfikator, a jego kopia umieszczona będzie w podkatalogu /res katalogu aktualnego projektu. Importowanie zasobów z plików wykonywalnych 1. Z menu File wybierz pozycję Open. Wyświetlone zostanie standardowe systemowe okno otwierania plików. 2. Z listy rozwijanej Open As wybierz Resources. 3. Odnajdź właściwy katalog i wybierz z niego plik wykonywalny (.exe, .dli lub .ocx), a następnie kliknij przycisk Open. Zasoby graficzne osadzone w pliku zostaną wyli-stowane w oknie edytora (rysunek 11.7). Dla przykładu wybierz plik Cards.dll, znajdujący się zwykle w katalogu C:\Windows\System. Wskazane jest zaznaczenie opcji Open as Read-OnIy (Otwórz tylko do odczytu). 4. W celu przejrzenia zasobów graficznych danego pliku otwórz okno właściwości i przypnij je pinezką, aby utrzymać w stanie otwartym. Po zaznaczeniu kolejnej pozycji w ramce Preview widoczny będzie wizerunek danego zasobu. 5. Aby pobrać określony zasób, zaznacz go myszą i przytrzymując wciśnięty klawisz Ctrl przeciągnij do panelu ResourceView. Zwolnij klawisz myszy, gdy obok przeciąganego obiektu pojawi się znak plusa. Zasób zostaje włączony do projektu. 6. Z menu File wybierz Close. Stosowanie rysunków w oknach dialogowych Istnieją różne sposoby wyświetlania rysunków w oknach dialogowych. Ikony oraz bitmapy są łatwe do wyświetlenia w oknach za pomocą obiektów rysunkowych i ustalenia właściwości wskazujących właściwy zasób graficzny. Rysunkowe obiekty użyte mogą być do tworzenia wypełnionych bądź obrysowych ramek, grupujących kontrolki okna dialogowego. 256 Poznaj Visual C++ 6 Rysunek 11.7. Importowanie zasobów rysunkowych z pliku wykonywalnego Niniejszy rozdział zajmuje się wykorzystaniem zasobów graficznych. Aby umożliwić tworzenie rysunków nie będących zasobami w obszarze użytkownika, musimy nadpisać funkcję OnPaint. Jest to opisane szczegółowo w rozdziale 15. PATRZ TAKŻE • Szczegóły dotyczące wykorzystania funkcji OnPaintO znajdziesz w rozdziale 15. Ustalanie właściwości graficznego kontrolki AppWizard automatycznie dodaje do projektu zasób ikony IDR_MAINFRAME, który przedstawia piramidkę MFC. W celu pokazania tej ikony AppWizard umieszcza graficzny obiekt w oknie dialogowym About. Rysunek 11.8 ukazuje okno właściwości tego obiektu. Lista rozwijana Type określa, z jakim rodzajem rysunku mamy do czynienia i gdy ustalonym typem jest Icon lub Bitmap, w oknie listy rozwijanej Image pojawia się identyfikator zasobu. Tabela 11.1 opisuje dostępne typy rysunków, które mogą zostać wyświetlone przez kontrolki graficzne. |Pic»uie Ptopeities -ta ? j Generał ' j S tyłeś | EntendedStyles j ID: IDCSTATIC W Yisible r ,, r Disabled F ||r uelp ID »J Taić Eroup Ifflage: T abstop Icon ^j IDRMAIHFRAME ^J 11 J Rysunek 11.8. Okno dialogowe właściwości Picture Properties Praca z rysunkami, ikonami i bitmapami 257 Tabela 11.1. Typy rysunków używanych przez kontrolki Typ rysunku Opis Frame Rectangle Icon Bitmap Enhanced Metafile Wyświetla czarną, białą, szarą lub zagłębioną ramkę. Używany do wizualnego grupowania obiektów w oknie Wyświetla czarny, biały, szary lub zagłębiony kwadrat Wyświetla rysunek ikony Wyświetla rysunek bitmapowy Wyświetla rysunek zawarty w rozszerzonym metapliku Co to jest rozszerzony metaplik? Metaplik przechowuje obraz w formacie niezależnym od urządzenia. Poza przechowywaniem samego obrazu, metaplik zawiera strukturę opisującą obraz pod względem wymiarów, używanych obiektów graficznych i zastosowanej palety kolorów. Rozszerzony metaplik stosowany jest tylko w aplikacjach Win32. Ładowanie zasobów graficznych podczas pracy programu Aby załadować ikonę lub bitmapę podczas pracy programu, należy przydzielić zmienną klasy CStatic do kontrolki graficznej. Musimy w tym celu zmienić identyfikator obiektu z domyślnie nadanego IDC_STATIC. Tabela 11.2 wykazuje funkcje składowe klasy CStatic używane do wyznaczania rysunków, które mają zostać wyświetlone przez kontrolkę. Każda z tych funkcji posiada odpowiednik Get służący do pobierania rysunku. Tabela 11.2. Funkcje składowe klasy CStatic Nazwa funkcji Opis działania Setlcon SetBitmap SetCursor SetEnhMetaFile Definiuje ikonę do wyświetlenia Definiuje bitmapę do wyświetlenia Definiuje kursor do wyświetlenia Definiuje metaplik do wyświetlenia Przed wywołaniem którejkolwiek z funkcji wymienionych powyżej, musi zostać zała- dowany właściwy zasób. Procedura ładowania zależy od typu zasobu. Rysunek 11.9 ukazuje przykład programu Images ładującego podczas działania bitmapę oraz kilka ikon. Aby prześledzić działanie tego programu, utworzymy za pomocą kreatora AppWizard nową aplikację na bazie okna dialogowego, którą nazwiemy Images. Kiedy projekt zosta- 258 Poznaj Visual C++ 6 nie utworzony, wykonajmy procedurę z podrozdziału "Umieszczanie obiektów graficznych w oknie dialogowym". Po jej wykonaniu, za pomocą CIassWizard przydzielimy każdej z kontrolek zmienną składową klasy CStatic, wykonując kolejny algorytm z podrozdziału "Przydzielenie zmiennej do kontrolki graficznej". Rysunek 11.9. Przykładowy program Images Umieszczanie obiektów graficznych w oknie dialogowym 1. Rozwiń katalog Dialog w panelu ResourceYiew i kliknij dwukrotnie okno dialogowe, które zamierzasz edytować. W obecnym przykładzie będzie to IDD_IMAGES _DIALOG. Usuń widniejący pośrodku szablonu napis TODO: Place Dialog Box Controls Herę, zaznaczając go kuknięciem, a następnie naciskając klawisz Delete. 2. Wybierz z paska narzędziowego Controls pozycję rysunku i umieść pole rysunku w lewym górnym rogu szablonu okna. 3. Z menu View wybierz pozycję Properties. Wyświetlone zostanie okno dialogowe Picture Properties. Używając pinezki widocznej w lewym górnym rogu tego okna, możesz utrzymać je w polu widzenia. 4. Wprowadź identyfikator ID dla nowego obiektu. W tym przypadku niech będzie to IDC Bl. 5. Dla umieszczonego właśnie obiektu ustal na liście rozwijanej Type jego typ, Bitmap. Praca z rysunkami, ikonami i bitmapami 259 IW^^oSMABESJIAiS^EllBlitllB^rroBlw)^^^^^^^^""™^^^^^^^ "SBk EdU y<-w Iniul Pl ifl i^Ha ' pet Buitó La^ouł Ws s . • .. 0, -^IfijA p] E,' |Cjlt|<,pcn ^ H: DnagesDfc] ^j! Al cliii -- Jji) .^„„ ^ ^J| S CI».ige>Dlg ^j 3. * :!:--1 1:3 ;';: ! M-: -•: '.„i lmag« lelouicu *, U Bfrnap T ~ 1 a 'a Diatoa • ' S IDO ABDIJTB DX[ En Bi - tiiu .s Łr ot e - ^ J.JJJ.J^R^ -^-J ——1 ——^ -^-J -™ Cancd j' ai-Tilc«n ^ Y Q Slling TaUe nnaTi j ai-JVafyon ,i( B j ; ..te »bl j 0° j \ J SX a B'. FT3 l e3 EB g ^ in^ l -. ^ E;? l Bfr l ~ : Q H «1 •l :' i ' "' '... . „.. . .... ......... . „ . . . . ,., .... . .... . : „ . . ........ s6 ^Q : T I ~ 1 V H Bt:! CIassYBW j S^Relource. JpFiteYi eW roi Rysunek 11.10. Układ okna dialogowego programu Images 6. Wybierz kolejno pięć razy tę samą pozycję z paska Controls i umieść nowe obiekty po prawej strome pierwszego, jak na rysunku 11.10. 7. Zaznacz pięć nowych obiektów, klikając je kolejno z przytrzymanym klawiszem Ctri. Gdy zaznaczysz wszystkie, w oknie Properties z listy Type wybierz pozycję Icon. 8. Zaznaczaj po kolei każdy z pięciu obiektów Icon i nadaj im kolejne identyfikatory: IDCJ1, IDC 12 i tak dalej. Przydzielenie zmiennej do kontrolki graficznej 1. Naciśnij CtrI+W lub z menu View wybierz pozycję CIassWizard, aby uruchomić ten kreator. 2. Otwórz kartę Member Variables. 3. Z listy rozwijanej Ciass Name wybierz CImagesDIg. 4. Z listy Control IDs wybierz IDC_B1, a następnie kliknij przycisk Add Yariable. Kreator wyświetli okno dialogowe Add Member Variable. Możesz otworzyć to samo okno klikając dwukrotnie identyfikator obiektu. 5. Wprowadź nazwę nowej zmiennej, w tym przykładzie m_bl. 6. Z listy Category wybierz pozycję Control. Kliknij przycisk OK. 7. Powtórz czynności 4-6 dla każdego z pozostałych obiektów, nadając przydzielanym zmiennym kolejne nazwy: m_il, m_i2 itd. 260 Poznaj Visual C++ 6 8. Okno CIassWizard powinno wyglądać jak na rysunku 11.11. Meaage Maps 'miect: MembetYariables | Au omation Active^ Eyents CIass name: Clas» Info AddClass... » ^:S..,ymages \lrfł Control l D s: i—————L'J iage®Dlg.h. C:\„Almage3\!rria Typ Clmag esC gesDIg .cp e 3lg p MBtnber d AddVariable.,. fielelaYariable IDC 11 IDC 12 IDC 13 IDC 14 IDC 15 IDCAMCEL IDOK ^Bscr^ion" CS te CS t cs > CS tc CS t map lo CS tafie member atic Btl C alic 3>I C atic m.i1 mi2 mi3 mJ4 mi5 | OK "j Cancal | Rysunek 11.11. Okno dialogowe MFC CIassWizard Ponieważ pierwszy z graficznych obiektów wyświetlał będzie bitmapę, musimy umieścić w projekcie zasób właśnie tego rodzaju. Rysunek 11.9 pokazuje bitmapę zaimportowaną z pliku Cards.dll. Procedura importowania opisana została w podpunkcie "Importowanie zasobów z plików wykonywalnych" w niniejszym rozdziale. Bitmapa użyta w tym przykładzie figuruje pod numerem 64. Gdy zaimportujemy tę bitmapę, nadajmy jej identyfikator IDB_BEACH. Bitmapa zostaje załadowana poprzez użycie obiektu CBitmap oraz wywołanie funkcji LoadBitmap z przekazanym jej identyfikatorem właściwego zasobu. Teraz dodamy do klasy cirnagesDlg zmienną składową typu CBitmap i nadajmy jej nazwę m_bmp. Dodamy również do treści funkcji OninitialUpdate kod zapisany na listingu 11.1. Umieśćmy go za komentarzem TODO. Listing 11.1. LST11_1 .CPP - ładowanie zasobów rysunkowych i ustalanie kontrolek graficznych l // * * Załadowanie zasobu bitmapy 2 11 ** i ustalenie wyświetlającej ją kontrolki 3 VERIF Y(mbmp.LoadBitmap(IDBBEACH)) ; 4 m bl. SetBitmap(m bmp); 5 6 CWinA pp* pApp = AfxGetApp(); 7 HICON hicon; 8 Praca z rysunkami, ikonami i bitmapami 261 9 // ** Załadowanie zasobu ikony aplikacji 10 // ** -i ustalenie wyświetlającej ją. kontrolki 11 hicon = pApp->LoadIcon(IDR_MAINFRAME); 12 m_il.Setlcon(hicon); 13 14 // ** Załadowanie kilku standardowych ikon 15 // ** i ustalenie wyświetlających je kontrolek 16 hicon = pApp->LoadStandardIcon(IDI_HAND); 17 m_i2.Setlcon(hicon); 18 19 hicon = pApp->LoadStandardIcon(IDI_QUESTION); 20 m_i3.Setlcon(hicon); 21 22 hicon = pApp->LoadStandardIcon(IDI_EXCLAMATION); 23 m i4.Setlcon(hicon); 24 25 hicon = pApp->LoadStandard!con(IDI_ASTERISK); 26 m_i5.Setlcon(hicon); Wywołana w linii 3 funkcja LoadBitmap pobiera bitmapę IDB_BEACH i łączy ją ze zmienną CBitmap, m_bmp. Obiekt CBitmap zostaje przekazany w linii 4 przekazany funkcji CStatic: :SetBitmap, która informuje kontrolkę powiązaną ze zmienną m_bl, która bitmapa ma zostać wyświetlona. Uruchomiona w linii 11 funkcja cwinApp: :Loadlcon pobiera zasób ikony IDR_MAINFRAME i zwraca HICON (uchwyt graficznego obiektu ikony). Uchwyt ten przekazany zostaje następnie funkcji CStatic: :Setlcon, informując kontrolkę, którą ikonę ma wyświetlić. Przy użyciu funkcji cwinApp: :LoadStandardlcon w liniach 16, 19, 22 i 25, załadowane zostają cztery standardowe ikony systemu Windows. Po przekazaniu identyfikatora wybranej funkcja zwraca HICON. Po skompilowaniu i uruchomieniu aplikacji, jej okno powinno wyglądać jak na rysunku 11.9. Tworzenie graficznych przycisków Ponieważ obrazki są najczęściej rozpoznawalne szybciej niż tekst, czasem wskazane jest zastosowanie graficznego oznaczenia przycisku zamiast umieszczania na nim napisu. Biblioteka MFC dostarcza nam specjalnej klasy CBitmapButton, służącej do realizacji takich zadań. Tworzenie bitmapy oraz umieszczanie w oknie dialogowym przycisku sterującego przebiega w sposób stosowany dotychczas. Istnieją jednak dwie rzeczy, o których musimy pamiętać tworząc zasoby graficzne dla przycisków. Pierwsza, to nadanie bitmapie w polu ID okna właściwości nazwy w postaci łańcucha znakowego, zamiast 262_____________________________________Poznaj Visual C++ 6 numeru łdefine. Druga to uaktywnienie opcji Owner Draw w oknie właściwości przycisku. Zasób bitmapy zostaje skojarzony z przyciskiem poprzez funkcję CBitmapBut-ton::AutoLoad. Używając AppWizard utworzymy nowy projekt na bazie okna dialogowego, nadając mu tytuł Smile. W przykładzie tym utworzymy własne bitmapy do umieszczenia na przycisku. Widniejący na nim obrazek będzie się zmieniał, gdy zmieni się stan przycisku, czyli gdy zostanie kliknięty. Zaczniemy od usunięcia z szablonu okna IDD_SMILE_DIALOG obecnego na jego środku napisu TODO. Dodamy następnie do szablonu przycisk, który umieścimy w miejscu, w którym widniał usunięty napis. Nowemu obiektowi sterującemu nadamy identyfikator IDC_SMILE i nagłówek SMILE. Zauważmy, iż aktualne wymiary przycisku nie grają żadnej roli, gdyż będzie on zwymiarowany tak, by pasował wielkością do przydzielonej do niego bitmapy. Tworzenie bitmap dla stanów przycisku Przycisk znajdować się może w jednym z czterech stanów: wciśnięty, zwolniony, niedostępny lub aktywny (użycie Enter powoduje wciśnięcie przycisku). Stan przycisku zmienia się, gdy użytkownik go kliknie lub użyje sekwencji tabulatora, ale może też zostać zmieniony na drodze programowej. Zwykle przyciski zawierają tekst nagłówka, którego postać identyfikuje stan przycisku. Przykładowo, napis na przycisku jest przyciemniony, gdy przycisk jest niedostępny dla użytkownika. Wygląd przycisków graficznych zależy od autora. Trzeba dostarczyć osobnych rysunków dla każdego ze stanów przycisku. Gdy ograniczymy się tylko do jednego rysunku, nie będziemy odbierać informacji zwrotnej po kuknięciu przycisku. Powinniśmy zatem utworzyć co najmniej dwa rysunki, dla przycisku wciśniętego i zwolnionego. Identyfikatory tych bitmap muszą być łańcuchami znakowymi (włączając cudzysłowy) opartymi o nagłówek (Caption) przycisku. Dla przykładu, jeśli nagłówek przycisku brzmi SMILE, identyfikatory bitmap mogłyby wyglądać: SMILEU - przycisk zwolniony, SMILE D - przycisk wciśnięty, SMILEX - przycisk niedostępny oraz SMILEF - przycisk aktywny. Potrzebne są więc cztery odrębne bitmapy dla każdego ze stanów przycisku. W celu ich wykonania, przeprowadźmy poniższy algorytm "Tworzenie bitmapowych rysunków dla przycisku". Tworzenie bitmapowych rysunków dla przycisku 1. Naciśnij Ctrl+5, by otworzyć edytor graficzny z otwartą w nim pustą bitmapą. 2. Z menu View wybierz pozycję Properties. Wyświetlone zostanie okno dialogowe Bitmap Properties. 3. W polu listy ID wpisz "SMILEU" (wraz ze znakami cudzysłowu). 4. Przeprowadź edycję bitmapy, aby wyglądała jak jedna z widocznych na rysunku 11.12. Praca z rysunkami, ikonami i bitmapami 263 ^MiISB^Ł^^SIliItSHfe.-^SsaBals^SM^^^^ Rysunek 11.12. Bitmapy dla przycisku zwolnionego i wciśniętego 5. Powtórz kroki 1-4 w celu utworzenia trzech pozostałych bitmap. 6. Wprowadź identyfikatory dla bitmap: "SMILED", "SMILEF" oraz "SMILEX". Dokonaj edycji bitmap, aby otrzymać zestaw widoczny na rysunkach 11.12 oraz 11.13. Rysunek 11.13. Bitmapy dla przycisku aktywnego i niedostępnego 264 Poznaj Visual C++ 6 Użycie klasy przycisku graficznego Jak wspomniano wcześniej, biblioteka MFC dostarcza klasę CBitmapButton umożliwiającą nakładanie bitmap na przyciski. Klasa ta jest pochodną klasy CButton i obejmuje jedynie kilka funkcji składowych. Implementuje ona wirtualną funkcję Drawitem, która wywoływana jest dla obiektów narysowanych przez programistę. Jedyną funkcją, którą sami musimy wywołać, jest AutoLoad. Dodamy zmienną składową typu CBitmapButton do klasy CSmileDIg. Nadajmy jej nazwę m_bbsmile. Następnie w funkcji Oninitialupdate, tuż za komentarzem // TODO, umieśćmy następujący wpis: VERIFY(m_bbSmile.AutoLoad(IDC_SMILE, this) ; Makro VERIFY użyte zostaje do sprawdzenia kodu zwróconego przez funkcję AutoLoad. Jeśli wartością tą jest FALSE, twierdzenie jest ustawione w tryb Debug. Jak widzimy, funkcji AutoLoad przekazany zostaje identyfikator zasobu przycisku, IDC SMILE. Stąd pobiera ona nagłówek i ładuje bitmapy o nazwach bazujących na nagłówku. Ostatnią rzeczą, którą musimy zrobić przed wygenerowaniem ostatecznego kodu wykonywalnego, jest ustalenie sposobu na postawienie przycisku w stan niedostępności. Uczynić to możemy przez modyfikację przycisku Cancel. Pierwszą modyfikacją będzie zmiana nagłówka na Dis/Enable i identyfikatora na ID_DISENABLE. Następnie powinniśmy utworzyć funkcję obsługi komunikatu BN_CLICKED dla tego przycisku. W funkcji tej należy wpisać poniższy kod, umieściwszy go za komentarzem // TODO: m_bb3mile.EnableWindow( !m_bbSmile.IsWindowEnabled() ) ; Teraz możemy już skompilować i uruchomić program Smile. Spróbujmy wykorzystać sekwencję tabulatora. Zobaczymy, że rysunek na przycisku zmienia się, gdy przycisk staje się i przestaje być aktywny. Spróbujmy również przełączać go w stan niedostępności. Jeśli klik-niemy przycisk i przytrzymamy klawisz myszy, powinien on wyglądać jak na rysunku 11.14. Rysunek 11,14. Program Smile fiMKŁ IAIU.E • Więcej informacji na temat makra VERIFY uzyskasz w rozdziale 27. • Pomocy odnośnie dodawania funkcji obsługi szukaj w rozdziale 4. Używanie rysunków w obiektach sterujących Omówiliśmy już użycie bitmap na przyciskach, lecz wykorzystanie rysunków wspiera także kilka innych obiektów sterujących, takich jak listy, drzewa oraz nowy typ obiektu — rozszerzone listy rozwijane. Obiekty te służą do przechowywania spisu określonych elementów. Każdy element posiada zwykle opis tekstowy, ale może być również skojarzony z dwoma rysunkami: jednym, gdy element jest zaznaczony, drugim dla sytuacji przeciwnej. Rysunki owe mogą być bitmapami oraz ikonami. Modyfikowanie listy obrazów Możliwe jest dodawanie, usuwanie i zastępowanie obrazów wewnątrz listy, a także umożliwienie rysowania przezroczystego poprzez określenie maski. Istota list obrazów Rysunki wymieniane na listach, drzewach lub listach rozwijanych muszą znajdować się na liście obrazów. Lista ta jest obiektem klasy cirnageList. Po utworzeniu takiego obiektu umieszczane w nim są bitmapy i ikony. Wszystkie elementy listy muszą mieć te same rozmiary. Wskaźnik do obiektu cirnageList jest przekazywany do kontrolki poprzez wywołanie funkcji składowej SetlmageList. Gdy do listy zostaje dodany nowy element, kojarzony jest z rysunkiem na liście rysunków i określany jego indeks, począwszy od zera. Ponieważ lista obrazów jest obiektem odseparowanym od kontrolki, umożli- wia to innym kontrolkom korzystanie z niej. Aby przestudiować konstruowanie listy obrazów oraz jej użycie przez kontrolkę, utworzymy za pomocą kreatora AppWizard nowy projekt na bazie okna dialogowego, który nazwiemy Shapes. Po utworzeniu projektu przyszła pora na rysunki. Gdy chcemy dodać do listy kilka rysunków, najprostszą metodą jest skonstruowanie jednej bitmapy zawierającej je wszystkie. Przykład obecny wymaga ośmiu rysunków zawartych w jednej bitmapie, która widoczna jest na rysunku 11.15. Rysunek 11.15. Bitmapa z elementami listy rysunkowej Utworzymy zatem nowy zasób bitmapy, nadając mu identyfikator IDB_SHAPES i wymiary: Width (szerokość) 118 i Height (wysokość) 16 pikseli. Następnie przeprowadzić musimy edycję bitmapy, aby wyglądała jak na rysunku 11.15. Jeśli mamy wątpliwości, jak utworzyć nową bitmapę, musimy cofnąć się do podpunktów "Tworzenie nowego zasobu bitmapy" oraz "Używanie edytora graficznego", obecne wcześniej, w niniejszym rozdziale. Następnie na szablonie okna dialogowego IDD_SHAPES_DIALOG umieśćmy pole listy, listę drzewiastą oraz listę kombinowaną, jak na rysunku 11.16. Identyfikatorem pola listy będzie IDC_LISTI. Z listy kombinowanej View na karcie Styles wybrać musimy List. Lista drzewiasta otrzyma identyfikator IDC_TREEI. Na karcie Styles dodatkowo zaznaczyć musimy opcje Has Buttons, Has Lines oraz Lines as Root. W oknie właściwości listy kombinowanej wprowadzamy identyfikator IDC_COMBOBOXEXI i z karty Styles wybieramy pozycję Drop List, którą znajdziemy na liście Type. Praca z rysunkami, ikonami i bitmapami 267 Rysunek 11.16. Układ okna dialogowego programu Shapes Teraz musimy użyć CIassWizard w celu dodania do klasy cshapesDlg zmiennej składowej dla każdej kontrolki. Zmiennym nadajmy nazwy: m_list, m_tree oraz m_combo. Dla wszystkich wybieramy również typ Control z listy Category. Po dodaniu zmiennych, karta Member Variables w kreatorze CIassWizard powinna wyglądać jak na rysunku 11.17. FC CIauWizald BE31 MessageMaps Membef Variables Automation | ActiveX Events | CIassInfo l Ploject; a,wst)»me: Add CIas- s... • auBUa^^^^——^—^^^^—l l-- - — AddYaiable... CortiollOs; Type Membel 2elcleVaiiable IDP r:nMROBnXEX1 CComboBoxE» m combo ^HMM——M«fl IDFII^TI CLiitCtlI m ll;t .„„:..:•„:.;..„<.. IDC TREE1 CTreeCtiI m tlee IDCANCEL '":::- 1DOK : : : | OK 1 Cancel | Rysunek 11.17. OknoMFC CIassWizard PATRZ TAKŻE • Informacji na temat umieszczania pól listy w oknach dialogowych szukaj w rozdziale 6. • Więcej informacji o używaniu list drzewiastych znajdziesz w rozdziale 19. » Więcej informacji o używaniu pól listy znajdziesz w rozdziale 19. 268_____________________________________Poznaj Visual C++ 6 Tworzenie i stosowanie list obrazów Lista obrazów przechowuje kolekcję bitmap używanych przez kontrolki. Lista ta zawarta jest w obiekcie klasy cirnageList. Dodajmy więc do klasy cshapesDlg zmienną składową m_imageList klasy cirnageList. Cały kod konieczny do utworzenia listy obrazów i umieszczenia elementów na liście drzewiastej można umieścić w funkcji Oninitia-lUpdate. Wprowadźmy zatem kod z listingu 11.2, umieszczając go w tejże funkcji, za komentarzem // TODO. Listing 11.2. LST11_2.CPP - tworzenie i użycie listy rysunków 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // ** Utworzenie nazw dla rysunków static char* shape[] = { "Square", "Rectangle", "Parallelogram", "Trapezoid", "Triangle", "Octagon"}; int nShapes = 6; // ** Załadowanie zasobu bitmapy CBitmap bitmap; VERIFY(bitmap.LoadBitmap(IDB_SHAPES)) ; // ** Utworzenie listy obrazów i dodanie bitmapy m_imagelist.Create(IDB_SHAPES, 16, l, 0) ; m_imagelist.Add(&bitmap, (COLORREF)OxFFFFFF) ; // ** Utworzenie pola listy, korzystającego z listy obrazów m_list.Set!mageList(&m_imagelist, LVSIL_SMALL) ; // ** Umieszczenie w polu listy 6 pozycji l / ** i nadanie im indeksów for(int i = 0; i < nShapes; i++) m list.Insertltem(i, shape[i], i); // ** Utworzenie listy drzewiastej, korzystającej z listy obrazów m_tree.Set!mageList(&m_imagelist, TVSIL_NORMAL) ; // ** Umieszczenie dwóch pozycji najwyższego poziomu listy II ** Ustalenie rysunków normalny/wybrany dla katalogu // ** zamkniętego/otwartego HTREEITEM hTetragons, hOther; hTetragons = m tree.InsertItem("Tetragons", 6, 7) ; hOther = m tree.Insertitem("Other", 6, 7); Praca z rysunkami, ikonami i bitmapami 269 32 // ** Umieszczenie pierwszych trzech kształtów jako podelementów 33 // ** "Tetragons" oraz trzech ostatnich jako podelementów "Other" 34 for (i =0; i < nShapes; i++) 35 m tree.Insertltem(shape[i], i, i, 36 i < 3 ? hTetragons : hOther) ; 37 38 // ** Utworzenie listy kombinowanej, korzystającej z listy obrazów 39 m combo.SetImageList(&m imagelist); 40 41 // ** Alokacja struktury elementów listy i ustawienie maski 42 COMBOBOXEXITEM cbitem; 43 cbitem.mask = CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE; 44 45 // ** Umieszczenie 6 elementów na liście kombinowanej 46 // ** Ustawienie tego samego rysunku dla trybu normalny/wybrany 47 ford = 0; i < nShapes; i++) 48 ( 49 cbitem.pszText = shape[i]; 50 ,cbitem.iltem = i; 51 cbitem.ilmage = cbitem.iSelectedImage = i; 52 m_combo.Insertitem(Sebitem); 53 }______________________________________________________ Na pierwszy rzut oka, powyższy listing wydaje się bardzo złożony, ale w rzeczywistości zawiera jedynie 28 linii kodu; reszta to komentarze. W linii 2 utworzona zostaje tablica łańcuchów znakowych, nazwana shape. Nazwy w niej zawarte dotyczą pierwszych sześciu rysunków z bitmapy IDB_SHAPES. W linii 9 lokalna zmienna CBitmap użyta zostaje do załadowania zasobu IDB_SHAPES. Tworzenie listy obrazów Dostępnych jest kilka przeciążonych wersji cirnageList: :Create. Umożliwiają one przekazywanie łańcucha znakowego identyfikatora zasobu lub wiązanie dwóch list w jedną. Obiekt klasy cimageList zainicjalizowany zostaje w linii 12 z wywołaniem funkcji Create. Pierwszym parametrem jest identyfikator zasobu, a drugim szerokość każdego rysunku, podana w pikselach. Trzeci parametr może być maską koloru, umożliwiającą zastosowanie przezroczystości. Kiedy lista obrazów jest zainicjalizowana, rysunki dodawane są poprzez wywołanie funkcji Ado. (linia 13). Przekazany zostaje adres bitmapy, w związku z czym następuje załadowanie wszystkich ośmiu rysunków. 270_____________________________________Poznaj Visual C++ 6 Po zainicjalizowaniu listy obrazów mogą z niej korzystać kontrolki. W tym celu dla każdego obiektu kolejno wywołuje się funkcję SetlmageList, jak widać w liniach 16, 24 i 39. Niezależnie od adresu rysunku, polu listy oraz liście drzewiastej przekazywane są dodatkowe parametry określające typ listy obrazów. Tabele 11.3 i 11.4 wymieniają możliwe typy. Inicjalizacja listy obrazów W celu dodania ikony do listy obrazów można posłużyć się przeciążoną wersją Add, która pobiera HICON jako parametr. Aby dodać do listy rysunki bezpośrednio z dysku, należy wywołać funkcję Read. Tabela 11.3. Typy listy obrazów dla pola listy Typ listy rysunków Opis typu LVS I L_NORMAL Lista z dużymi ikonami LVS l L_SMALL Lista z małymi ikonami LVSIL_STATE Lista z rysunkami dla różnych stanów Tabela 11.4. Typy listy obrazów dla listy drzewiastej Typ listy rysunków Opis typu TVS IL_NORMAL Lista rysunków dla pozycji zaznaczonej i niezaznaczonej TVSIL_STATE Lista rysunków dla stanów określonych przez użytkownika Elementy listy są na niej umieszczane poprzez pętlę for w linii 20. Funkcji Inser-tltem przekazane są trzy parametry: pierwszym jest pozycja elementu na liście, drugim tekst opisujący element, trzeci natomiast określa pozycję rysunku na liście. W obecnym przykładzie parametr i podlega inkrementacji, a więc pozycje listy obrazów umieszczane są na liście kolejno. Lista drzewiasta posiada dwa elementy najwyższego poziomu, czyli roots, "korzenie"; oba posiadają po trzy podelementy. Elementy najwyższego poziomu umieszczane są na liście w liniach 29 i 30. Wywoływana tam funkcja insertitem pobiera tekst opisu jako pierwszy parametr. Drugi określa pozycję rysunku dla elementu nie wybranego (teczka zamknięta) - w tym przypadku jest to numer 6 na liście obrazów. Trzeci z parametrów z kolei określa pozycję rysunku dla wybranego elementu (opisany numerem 7). Podelementy listy drzewiastej umieszczane są w niej poprzez pętlę for w linii 34. Występuje tu przeciążenie funkcji Insertitem posługującej się czterema parametrami. Pierwszym jest opis podelementu. Drugi i trzeci określają pozycje rysunków dla podele- Praca z rysunkami, ikonami i bitmapami 271 mentów, odpowiednio niewybranego oraz wybranego (w przykładzie Shapes oba stany reprezentowane są przez ten sam rysunek). W czwartym parametrze określa się docelowy katalog: hTetragons lub hOther. Umieszczenie elementów na liście kombinowanej odbywa się inną metodą. Należy w tym przypadku zapisać w zmiennej cbitem struktury COMBOBOXEXITEM dane elementu, a następnie przekazać adres zmiennej funkcji Insertitem. Umieszczony w linii 43 znacznik ma s k określa, które elementy struktury są ważne lub inaczej, które zmienne struktury zostaną użyte. Jak widać w linii 51, obu zmiennym, ilmage oraz iSelectedl-mage przydzielona zostaje ta pozycja tego samego rysunku. Po skompilowaniu i uruchomieniu program powinien wyglądać jak na rysunku 11.18. Rysunek 11.18. Program Shapes w pełnej krasie Rozdział 12 Dokumenty, widoki, ramki oraz ich stosowanie Tworzenie aplikacji jednodokumentowych Rozbudowa aplikacji przy użyciu architektury MFC dokument /widok_______________________________ Istota klas dokumentu, widoku, ramki i szablonu dokumentu Umieszczanie dodatkowych elementów standardowego menu Miliardy dolarów wydane podczas projektowania samochodów, pozwoliły ujednolicić współpracę człowieka z maszyną. Dzięki temu, przesiadając się z jednego samochodu do drugiego, jesteśmy w stanie w ciągu kilku minut opanować jego obsługę. Umiejętność prowadzenia można z łatwością przenieść na inny samochód, oferujący wirtualnie takie same elementy sterowania, różniące się głównie pod względem estetycznym. Popularność aplikacji Windows opiera się na jednolitym interfejsie użytkownika. Typowa taka aplikacja wyposażona jest w pasek tytułowy u góry okna, umieszczone pod nim rozwijane menu oraz jeden lub więcej pasków narzędziowych, których ikony stanowią skróty do możliwych do wykonania operacji. Poniżej pasków narzędziowych znajduje się główny obszar wyświetlania, a całe okno zamyka od dołu pasek stanu. Przeglądając wiele spośród aplikacji zauważymy podobieństwa pozycji menu oraz przycisków kontrolnych opatrzonych wizerunkami, które ujednolicają ich znaczenie. Dzięki temu w łatwy sposób przenieść się można z edytora tekstu do programu e-mail i w ciągu kilku minut zredagować wiadomość. Dla użytkowników Visual C++ tworzenie aplikacji Windows, z ich elementami gra- ficznymi i użytkowymi jest zajęciem w ogóle niewymagającym wysiłku. Biblioteka MFC wspiera ich w ogromnym stopniu podczas tworzenia szkieletu aplikacji, dzięki kreatorowi AppWizard, automatycznie umieszczającemu paski menu, narzędziowy oraz stanu, a także inne komponenty i pozwalającemu na dostosowywanie ich do własnych oczekiwań. Klasy utworzone przez AppWizard współpracują ze sobą, tworząc zorganizowaną strukturę, zwaną potocznie architekturą dokument/widok. 274 Poznaj Visual C++ 6 Prawdziwe znaczenie terminu „dokument" Pierwszą aplikacją korzystającą z techniki dokument/widok był MS Word, pozwalający tworzyć dokumenty tekstowo-graficzne. Słowo dokument jest jednak znacznie szerszym pojęciem, niż zwykło się sądzić. Klasa Document może zawierać dane każdego typu, czyniąc architekturę dokument/widok dostępną dla przeróżnych aplikacji. Podstawowym założeniem takiej architektury jest oddzielenie danych wymaganych dla działania aplikacji od danych przeznaczonych dla użytkownika. Osiąga się to poprzez przechowywanie danych aplikacji w jednej klasie (klasa dokumentu), podczas gdy informacje dla użytkownika prezentowane są poprzez inną (klasę widoku), stąd nazwa dokument/widok. Rozwiązania proponowane przez MFC dostarczają jednak o wiele więcej ponad tę podstawową organizację aplikacji. MFC zawiera także wiele rozwiązań dotyczących funkcjonalności interfejsu użytkownika, a także metod ładowania i przechowywania danych w plikach. Istnieją dwie kategorie aplikacji typu dokument/widok, SDI (Single Document Interface — aplikacja je,dnodokumentowa oraz MDI (Multiple Document Interface — aplikacja wielo- dokumentowa). Niniejszy rozdział koncentruje się na aplikacjach typu SDI. PATRZ TAKŻE • Więcej szczegółów dotyczących aplikacji MDI znajdziesz w rozdziale 21. Tworzenie aplikacji SDI Zadanie utworzenia aplikacji Windows wraz ze wszystkimi elementami jej interfejsu, takimi jak paski narzędzi, stanu, mogłoby być zniechęcające, gdyby nie kreator AppWi-zard, wykonujący całą pracę za programistę. W rzeczywistości AppWizard daje nam znacznie więcej, niż tylko podstawowe elementy aplikacji i włącza do niej kilka opcjonalnych dodatków. Na przykład połączenie aplikacji z bazą danych jest takim właśnie dodatkiem; innym może być włączenie podstawowych funkcji do obsługi poczty elektronicznej. W tym punkcie utworzymy aplikację typu SDI, która posłuży nam do prześledzenia wygenerowanych klas oraz metod działania architektury dokument/widok. Utworzymy zatem aplikację, nadając jej nazwę SDICoin. Tworzenie aplikacji SDI przy użyciu AppWizard 1. Wybierz pozycję New z menu File. Otwórz kartę Projects okna dialogowego New, a następnie wybierz MFC AppWizard (exe). 2. Wpisz nazwę projektu (SDICoin w tym przykładzie) i kuknij OK. Dokumenty, widoki, ramki oraz ich stosowanie 275 3. W oknie kroku l (MFC AppWizard Step l) kliknij przełącznik Single Document i upewnij się, czy zaznaczona została opcja Document/View Architecture Support? (patrz rysunek 12.1). Kliknij Next. MFC AppWizaid. Stępi What type of applicafion would you like to create? ^ ISingtedocumetili ^ ^ititiple documents (~ fiialog based P Document/Yiew architecture support? Whatjanguage wouldyou likeyoui resources in? English [Uniled Slates] [APPWZENLLDLL^ Finish Cancel Rysunek 12.2. Krok 4 kreatora AppWizard New Pioiect Infolinalion AppWizard will create a new ®Rdeton project with the following specifications: Application type of SDICcnn: Single Document InterfaceApptication targeting: Win32 CIasses to be cieated: Application: CSDICoinApp in SDlCoin.h and SDICoincpp Frame; CMairiFrame in MainFrm.h and MainFrm.cpp Document CSDICoinDoc in SDICoinDoe.h and SDICoinDoc.cpp View: CSDICoirf/iew in SDICoinYiew.h and SDICohView.cpp Features: + initiat tootbaf in rnain fiame + ReBaf ?^le menus and toolbafs + Initiaf status bar in rnain frame + Pnnting and Print Prevtew suppoft in view + 3D Conhols + Uses shated DLL implemenlation (MFC4ŁDLL) + ActiveX Conirob support enabled ł Localizable teKt in: English [United Stałeś] Project Directory: C:'>Ptogran> Files\Miciosoft Visual StudB',MyPiojec[s\SDICoin Cancel Rysunek 12.3. Okno dialogowe The New Project Infonnation Aktualnie tworzoną aplikację będziemy rozbudowywać, tak by w efekcie końcowym wyświetlała stos monet. Opcje menu będą umożliwiały dodawanie i zabieranie monet ze stosu. Dane, które będą obejmowały jedynie aktualną liczbę monet, przechowywane będą w klasie dokumentu, a klasa widoku będzie miała do nich dostęp w celu wyświetlenia stosu. Dokumenty, widoki, ramki oraz ich stosowanie 277 Jakkolwiek jest to przykład bardzo prosty, powinien rzucić nieco światła na pewne fundamentalne założenia architektury dokument/widok, takie jak hermetyzacja danych. Dzięki hermetyzacji dane przechowywane są tylko w klasie dokumentu, natomiast funkcje dostępu pozwalają klasie widoku na dostęp do nich w celu przekazania użytkownikowi informacji w wybrany sposób. Przykładowo, zamiast wyświetlania obrazu stosu monet, klasa widoku może przekazać informacje o nim w postaci aktualnej sumy monet, bez potrzeby wprowadzania zmian w klasie dokumentu. Jednakże przed dokonaniem rozbudowy aplikacji, konieczne są pewne wyjaśnienia dotyczące klas wygenerowanych przez AppWizard. Dokumenty zawierają dane aplikacji Klasa dokumentu jest wyprowadzana z CDocument, a jej zmienne składowe przechowują dane aplikacji. Funkcje składowe wyprowadzonej z CDocument klasy operują na tych danych. Na przykład, funkcja Serialize () służy do ładowania i zapisywania danych na dysku. Aplikacja jednodokumentowa posiada jedną klasę dokumentu wyprowadzoną z klasy MFC CDocument oraz drugą, wyprowadzoną z jednej z klas widoku MFC. Te klasy widoku znajdują zastosowanie w różnych typach aplikacji opartych na architekturze dokument/widok. Tabela 12.1 wymienia dostępne klasy. Aplikacja SDICoin korzysta z klasy domyślnej cview. Tabela 12. l Bazowe klasy widoku dostępne dla AppWizard Nazwa klasy Opis cview Klasa domyślna, implementująca wszelkie najważniejsze funkcje związane z widokiem, włączając w to interakcje z klasą dokumentu CSrollview Wyprowadzona z CView i rozszerzona o możliwość przewijania, gdy logiczny obszar wyświetlania jest większy od fizycznie dostępnego CListView Używa listy jako widoku, umożliwiając aplikacjom wyświetlanie ikon lub tekstu w kolumnach CTreeView Używa listy drzewiastej jako widoku, umożliwiając aplikacjom wyświetlanie informacji hierarchicznych CEditView Używa wieloliniowego pola edycji jako klasy widoku, rozszerzonego o możliwości przewijania i szukania CRichEditView Używa pola tekstu formatowanego jako widoku, umożliwiając wszechstronniejszą edycję tekstu, niż oferuje to CEditView CFormView Używa szablonu okna dialogowego jako widoku, umożliwiając aplikacji przybranie formy bazy danych 278 Poznaj Visual C++ 6 CRecordView CHtmlView Wyprowadzona z CFormView, rozszerzona o możliwość połączenia widoku z określonym rekordem bazy danych. Klasa ta dostępna jest jedynie wtedy, gdy zostało ustalone wsparcie obsługi baz danych Używa Intemet Explorera jako widoku, pozwalając na tworzenie aplikacji przeglądarek WWW PATRZ TAKŻE Informacji o klasie CListYiew szukaj w rozdziale 19. Informacji o klasie CTreeYiew szukaj w rozdziale 19. Informacji o klasie CRichEdi t szukaj w rozdziale 19. Informacji o klasie CHtmlView szukaj w rozdziale 19. Informacji o klasie CSrollYiew szukaj w rozdziale 18. Informacji o klasach CFormView i CRecordView szukaj w rozdziale 24. Informacji o obsłudze plików w architekturze dokument/widok szukaj w rozdziale 23. Informacji o ustalaniu wsparcia baz danych szukaj w rozdziale 24. Informacji na temat automatyzacji OLE szukaj w rozdziale 25. Klasy aplikacji SDI Struktura klas jednodokumentowej aplikacji różni się od istniejącej w aplikacjach budowanych na bazie okien dialogowych. Przeglądając zawartość panelu ClassView, zobaczymy utworzone przez AppWizard w projekcie SDICoin cztery klasy stanowiące strukturę aplikacji. Możemy to zobaczyć na rysunku 12.4. COocTemplateN CWhApp H L;CD«wmen(;,,|J |" "^liSlli^sCWnifI.^' ^"'ag, CSDICoinApp J CSDICoinDoc J CSDICoinView J CMainFrame Rysunek 12.4. Hierarchia klas w aplikacji SDI Dokumenty, widoki, ramki oraz ich stosowanie 279 Wyprowadzona z cwinApp klasa cSDlCoin spełnia kilka funkcji i różni się w implementacji od klasy obiektu w projekcie zbudowanym na bazie okna dialogowego. Zarządza inicjalizacją aplikacji i odpowiedzialna jest za tworzenie powiązań pomiędzy klasami dokumentu, widoku i ramki. Odbiera również komunikaty od systemu Windows, kierując je następnie do odpowiedniego okna docelowego. Jak napisano wcześniej, w aplikacji jednodokumentowej utworzony zostanie tylko jeden egzemplarz klasy csoiCoinDoc, wyprowadzonej z CDocument. Klasa csoiCoinDoc pełni rolę kontenera zawierającego dane aplikacji. Dane te mogą występować w każdej formie. W programie SDICoin będą miały formę po prostu pewnej liczby monet. Zwykle jednak dane te bywają o wiele bardziej złożone. W programie do edycji tekstu, danymi będą zarówno treść tekstu, jak i informacje o jego formatowaniu. Program typu CAD potrzebował będzie z kolei danych o współrzędnych i innych informacji dotyczących tworzonego rysunku. Sposób implementacji mechanizmu przechowywania danych zależy od programisty. Idealnie, gdy zostanie to rozwiązane poprzez obiektowe podejście do sprawy, przez co dane mogą być przechowywane w sposób całkiem niezależny od interfejsu użytkownika. Stosowanie się do takiej metodologu nadaje kodowi zwartość i elastyczność. W przykładzie SDICoin wykorzystywany jest tylko jeden widok, lecz być może będziemy chcieli korzystać z kilku widoków, operujących na tych samych danych dokumentu. Widoki mogą występować w różnych typach; jeśli jednak tak będzie, musimy zachować pewną ostrożność w stosunku do mechanizmu stosowanego przez klasę dokumentu, dostarczającego danych do każdego widoku. Przodkiem jest CObject Klasa CObject jest klasą bazową dla wszystkich klas MFC, poza kilkoma wyjątkami. Wszystkie wyprowadzone z niej klasy dziedziczą zdolność do sprawdzania typu klasy obiektu podczas pracy programu, udzielania informacji diagnostycznych oraz wsparcia serializacji. Klasa C3DlCoinView odpowiedzialna jest za formowanie wizualizacji danych dokumentu w celu przedstawienia ich użytkownikowi oraz umożliwienia mu interakcji z owymi danymi. Każdy widok może być połączony z jednym tylko dokumentem, natomiast każdy dokument może zostać połączony z wieloma obiektami widoku. Klasa widoku uzyskuje dostęp do danych, których wymaga, za pośrednictwem funkcji składowych dostarczanych przez dokument. Modyfikowanie stylu ramki okna Poprzez zmianę treści funkcji preCreateWindowO można za pomocą znaczników stylu modyfikować ramkę okna głównego przed jego wyświetleniem. Funkcji tej przekazuje się odwołanie do struktury CREATESTRUCT zawierającej atrybut style, który można modyfikować zanim zostanie przekazany klasie bazowej funkcji. 280 Poznaj Visual C++ 6 Rolą klasy CMainFrame jest dostarczenie aplikacji okna roboczego. Jest to klasa wy- prowadzona z CFrameWnd, dostarczającej prostego okna. Domyślne znaczniki stylu okna obejmują WS_OVERLAPPEDWINDOW, który umieszcza w oknie głównym pasek tytułowy zawierający przyciski minimalizowania, maksymalizowania oraz zamykania okna, menu systemowe i umożliwia zmianę położenia i wymiarów okna. Znaczniki stylu obejmują także znacznik FWS_ADDTOTITLE, który powoduje automatyczne wyświetlanie w pasku tytułowym okna tytułu dokumentu po jego załadowaniu. Klasa CMainFrame obsługuje również procesy kreacji, inicjalizacji i usuwania pasków narzędziowych i stanu. PATRZ TAKŻE • Aby poznać inne różnice pomiędzy klasami SDI a MDI, zajrzyj do rozdziału 21. Elementy wizualne aplikacji SDI Akceptując domyślne ustawienia zaproponowane przez AppWizard w 4 kroku, utwo- rzyliśmy aplikację posiadającą pasek tytułowy z przyciskami minimalizacji, maksymalizacji i zamykania, rozwijanym menu zawierającym polecenia wydruku i podglądu wydruku, paskiem narzędziowym oraz paskiem stanu. Niektóre z tych elementów są jednak opcjonalne. Więcej opcji możemy wybierać, klikając przycisk Advanced w oknie dialogowym wyświetlanym w czwartym kroku kreatora AppWizard. Okno Advanced pokazane jest na rysunku 12.5. Wykazane tu opcje pozwalają na zmianę stylu okna głównego. Advanced Optioni Docurnent Template Strings Window Styles F" U sesplit aindow -Mainftamestyles • • •••••—•-••••.•---.••- . l F ll-lick frame P |Syrfern mew F M.inimizebon r" Mirumized F Manimize bon r" Maumized Close Rysunek 12.5. Okno dialogowe Advanced Options dostępne w 4 kroku kreatora AppWizard Dokumenty, widoki, ramki oraz ich stosowanie 281 Komunikaty ramki okna Niektóre komunikaty wysyłane są tylko do okien posiadających ramki. Obejmują one komunikaty dotyczące przemieszczania, zmiany wymiarów, minimalizacji oraz maksymalizacji okna aplikacji. Obszar roboczy okna głównego nie implementuje widoku. Widok posiada własne okno, bez ramki i menu, które jest oknem potomnym okna głównego. Okno widoku nakładane jest na obszar roboczy okna głównego (rysunek 12.6). W ten sposób widok zostaje zaimplementowany, co powoduje, iż ten sam model stosowany może być w aplikacjach zarówno jedno-, jak i wielodokumentowych, z różnicą tkwiącą w klasach okna głównego i potomnego. Rysunek 12.6. Graficzne elementy aplikacji SDI O CMainFrame @ CToolbar © CSDICoinYiew O CStatusBar © Okno widoku 282_____________________________________Poznaj Visual C++ 6 Poniżej widzimy objaśnienia dotyczące poszczególnych elementów okna: " CMainFrame klasa wyprowadzona z CFrameWnd; tworzy okno główne aplikacji • CToolBar implementuje pasek narzędziowy dokowany w oknie głównym • CSDlCoinView implementuje widok, jako okno potomne okna głównego • CStatusBar implementuje pasek stanu dołączony do okna głównego • View window nakłada się na obszar roboczy okna głównego. Można się zastanawiać, skąd właściwie biorą się takie elementy wizualne jak menu, czy paski narzędziowe. Są one zasobami dodawanymi do projektu przez AppWizard podczas jego tworzenia. Gdy otworzymy panel ResourceView, zobaczymy cztery rodzaje zasobów (tablica akceleratorów, ikona, menu i pasek narzędziowy) opatrzone tym samym identyfikatorem, czyli IDR_MAINFRAME. Każdy z tych zasobów możemy wywołać do edycji, klikając dwukrotnie jego pozycję. Identycznym identyfikatorem oznaczona jest również specjalna tablica. Zawiera ona łańcuch znakowy w specyficznym formacie. Łań- cuch jest podzielony na siedem oddzielnych łańcuchów ograniczonych znakiem \n (przejście do nowej linii). Każdy z łańcuchów opisuje odrębny element dokumentu. Na przykład, pierwszy z nich jest tytułem okna aplikacji. Istota szablonów dokumentów SDI Rozdział ten w dalszym ciągu skoncentrowany będzie na indywidualnych klasach aplikacji dokument/widok oraz elementach wizualnych. Wszystkie te elementy zebrane są i zarządzane przez klasę szablonu dokumentu. Gdy spojrzymy na rysunek 12.4 ukazujący hierarchię klas aplikacji SDI, zobaczymy znajdującą się tam klasę CSingleDocTemplate, która jest klasą szablonu dokumentu, używaną w aplikacjach jednodokumentowych. Utworzenie egzemplarza klasy CSingleDocTemplate i jego wykorzystanie następuje wewnątrz funkcji CSDlCoinApp: :lnitlnstance (listing 12.1). Funkcję tę wywołuje osnowa MFC do inicjalizacji aplikacji w chwili jej startu. Listing 12.1. LST13_4.CPP- funkcja Initinstance 1 BOOL CSDlCoinApp::Initinstance () O 2 { 3 // ** Uwaga: pewne linie usunięto dla zwięzłości 4 5 // .Rejestracja szablonu dokumentu. 6 // Szablony tę służą połączeniu dokumentów, okien i widoków. 7 8 CSingleDocTemplate* pDocTemplate; 9 pDocTemplate = new CSingleDocTemplate( 10 IDR_MAINFRAME, @ 11 RUNTIME CLASS(CSDICoinDoc), Dokumenty, widoki, ramki oraz ich stosowanie 283 12 RUNTIME_CLASS(CMainFrame), // Główne okno SDI 13 RUNTIME_CLASS(CSDICoinYiew)) ; 14 AddDocTemplate(pDocTemplate) ; 15 16 // Parse command linę for standard snęli commands, DDE, file open 17 CCommandLineInfo cmdinfo; 18 ParseCommandLine(cmdinfo); ® 19 20 // Wysłanie komend, określonych w linii poleceń 21 if (!ProcessShellCommand(cmdinfo)) O 22 return FALSE; 23 24 // Tylko jedno okno zostaje zalnicjalizowane i następuje jego otwarcie 25 m_pMainWnd->ShowWindow(SW_SHOW) ; 26 m_pMainWnd->UpdateWindow(); 27 28 return TRUE; 29 t 21 if (!ProcessShellCommand(cmdinfo)) 22 return FALSE; 23 24 // Tylko jedno okno zostaje zainicjalizowane i następuje jego otwarcie 25 m_pMainWnd->ShowWindow(SW_SHOW); ® 26 m_pMainWnd->UpdateWindow(); © 27 28 return TRUE; 29 ) O Wywołanie przez MFC w momencie startu programu. @ Inicjalizacja szablonu dokumentu z identyfikatorem zasobów, klasami dokumentu, okna i widoku. © Analiza składni argumentów linii poleceń. O Przetworzenie argumentów linii poleceń i utworzenie nowego dokumentu lub otwarcie pliku. © Wyświetlenie głównego okna aplikacji. 284_____________________________________Poznaj Visual C++ 6 Jak widzimy, w linii 9 utworzony został obiekt CSingleDocTemplate, któremu przekazano cztery parametry. Pierwszym jest identyfikator zasobów IDR_MAINFRAME. W przykładowym programie SDICoin jest to identyfikator wspólny dla zasobów czterech typów: ikony aplikacji, menu, paska narzędziowego oraz tablicy akceleratorów. Następne trzy parametry są wskaźnikami klas wykonawczych dla odpowiednio: dokumentu, okna głównego oraz widoku. Wskaźniki te generowane są przez makro RUNTIME_CLASS. Jest to możliwe za sprawą AppWizard, który włączył do projektu makra DECLARE_DYNCREATE i IMPLEMENT_DYNCREATE, pozwalające na dynamiczne tworzenie klas. Obiekty dokumentu, widoku i okna nie są jeszcze utworzone na tym etapie. Powyższy kod inicjalizuje obiekt CSingleDocTemplate, zawierający wszelkie informacje niezbędne w chwili ładowania zasobów, alokacji dokumentów, widoków i okien. Klasa szablonu dokumentu nazywana jest ciass factory, co można przetłumaczyć jako fabryka klas. Fabryka klas Klasa szablonu dokumentu jest przykładem „fabryki klas". Jest to klasa definiująca proces tworzenia innych klas. Zna ona zatem zasady tworzenia klas charakterystycznych dla danej aplikacji. Obiekt szablonu dokumentu przetrzymywany jest w klasie aplikacji. Wywołana w linii 14 funkcja AddDocTemplate rejestruje nowo tworzony obiekt szablonu dokumentu w klasie cwinApp. Klasa cwinApp przechowuje obiekt CSingleDocTemplate do czasu jego destrukcji, która następuje dopiero podczas zamykania aplikacji. Wykonując operację destrukcji, klasa cwinApp uwalnia pamięć przydzieloną dokumentowi, dzięki czemu nie musimy zawracać sobie tym głowy. PATRZ TAKŻE • Więcej informacji o klasie wykonawczej znajdziesz w rozdziale 23. Stosowanie funkcji osnowy w architekturze dokument/widok Być może zagadnieniem najtrudniejszym do zrozumienia, a co za tym idzie, do stosowania w praktyce, dotyczącym architektury dokument/widok jest przebieg sekwencji tworzenia obiektów oraz funkcji wirtualnych. Trudność polega nie na złożoności, ale na tym, iż większość procesów implementacyjnych przebiega bez naszego udziału. Jest to jednak rozwiązanie korzystne, gdyż zwalnia nas z wykonania dużej części pracy. Aby w pełni zrozumieć istotę architektury dokument/widok, musimy poznać te zakulisowe procesy. Dokumenty, widoki, ramki oraz ich stosowanie 285 Analizowanie argumentów linii poleceń Najprostszym sposobem dostarczania dodatkowych argumentów linii poleceń jest wyprowadzenie klasy z CommandLineinfo i zmiana treści funkcji ParseParam. Podczas tworzenia i inicjalizacji obiektu CSingleDocTemplate w liniach 8-14 listingu 12.1 powiązane z nim są parametry linii poleceń i wtedy okno aplikacji zostaje wyświetlone. Klasa ccommandLineinfo (linia 17) jest pomocna przy obsłudze parametrów przekazywanych aplikacji z linii poleceń. Funkcja CWinApp: : ParseCommandLine (linia 18) pobiera odwołanie do tej klasy jako parametr. Wywołanie funkcji ParseParam następuje dla wszystkich argumentów linii poleceń, zapisywanych w zmiennych obiektu ccomman-dLineinfo. Tabela 12.2 wymienia domyślne opcje linii poleceń oraz ich znaczenia. Tabela 12.2. Argumenty linii poleceń Argument Działanie NONE Tworzy nowy dokument f i l e Otwiera wskazany plik /p f ile Drukuje wskazany plik na drukarce domyślnej /p t f ile Drukuje wskazany plik na wskazanej drukarce printer driver port /dde Rozpoczyna sesję DDE /automation Uruchamia aplikację jako serwer OLE /embedding Uruchamia aplikację w celu edycji obiektu OLE osadzonego w innej ____ aplikacji_________ _________________________ Z powyższej tabeli możemy wyczytać, że jeżeli z linii poleceń nie zostaną przekazane żadne argumenty, podczas startu aplikacji tworzony jest nowy dokument. Jest to oczywiście działanie domyślne. Wraz z dokumentem, utworzone zostaje okno główne aplikacji oraz widok. Ma to miejsce w funkcji CWinApp: :ProcessShellCommand, widocznej w linii 21. Listing 12.2 służy zapoznaniu się z sekwencją tworzenia kolejnych elementów aplikacji. Musimy jednak pamiętać, iż jest listing uproszczony, podany jedynie w celu zobrazowania owej sekwencji. 286_____________________________________Poznaj Visual C++ 6 Listing 12.2. LST13_3.CPP — sekwencja tworzenia dokumentu, widoku oraz okna głównego 1 BOOL CWinApp::ProcessShellCommand( O CCommandLineInfo& rCmdInfo) 2 ( 3 if(rCmdInfo.NewFile) 4 ( 5 OnFileNewO; 6 } 7 if(rCmdInfo.OpenFile) 8 ( 9 OpenDocumentFile(rCmdInfo.strFileName); 10 } 11 12 ) 13 void CWinApp::OnFileNewO 14 { 15 m pDocManager->OnFileNew() ; 16 } 17 void CDocManager::OnFileNew() 18 ( 19 pTemplate->OpenDocumentFile(NULL); @ 20 } 21 CSingleDocTemplate::OpenDocumentFile (LPCTSTR IpszPathName) 22 { 23 if(m_pOnlyDoc != NULL) 24 pDocument->SaveModified(); ® 25 else 26 ( 27 pDocument = CreateNewDocument() ; 28 pFrame = CreateNewFrame(pDocument); O 29 ) 30 31 pDocument->OnNewDocument(); © 32 InitialUpdateFrame(pFrame, pDocument); © 33 O Wywołanie to następuje z funkcji initinstance @ pTemplatejest wskaźnikiem do zarejestrowanego obiektu CSingleDocTemplate Dokumenty, widoki, ramki oraz ich stosowanie 287 © Jeśli dokument istnieje, zostaje zainicjalizowany ponownie O Obiekty dokumentu i okna konstruowane są w szablonie, za pomocą klasy wykonawczej. CreateNewFrame konstruuje również widok. © Inicjalizacja dokumentu przez funkcję wirtualną © Inicjalizacja okna głównego poprzez wywołanie LoadFrame. Aktywowanie widoku i wysłanie komunikatu WM_INITIALUPDATE, w celu wywołania cview: :0nlni-tialUpdate. Funkcja cwinApp zapisana w linii l powoduje serię wywołań funkcji osnowy aplikacji, zależnie od wyników analizy argumentów linii poleceń. Jeśli nie zostały tam określone żadne argumenty, wywołana zostanie funkcja CWinApp: :OnFileNew (linia 13). Ta sama funkcja wywołana będzie również wtedy, gdy użytkownik wybierze pozycję New z menu File, które jest automatycznie umieszczane w projekcie przez AppWizard. Listing 12.2 obrazuje logikę kreacji nowego dokumentu podczas startu aplikacji, lecz różnica pomiędzy tą sekwencją, a procedurą otwierania pliku nie jest duża. Leży ona na samym początku sekwencji, gdzie CWinApp: :processShellCommand odnajduje wskazany plik lub wzywa użytkownika do wskazania go poprzez standardowe okno dialogowe. Po zlokalizowaniu pliku, jego nazwa oraz ścieżka dojścia przekazane zostają tej samej funkcji CSingleDocTemplate: :OpenDocumentFile (linia 21) zamiast argumentu NULL, który powoduje utworzenie nowego dokumentu. CWinApp: :OnFileNew bezzwłocznie przekazuje kontrolę funkcji CDocManager: :OnFileNew (linia 15), która pobiera wskaźnik do utworzonego wcześniej obiektu csin-gleDocTemplate. Używając wskaźnika szablonu, wywołuje OpenDocumentFile z parametrem NULL. W tym miejscu rozpoczyna się właściwa praca. CSingleDocTemplate:: OpenDocumentFile Obiekt dokumentu w aplikacji SDI tworzony jest tylko raz (w pierwszym wywołaniu OpenFileDocument). Kolejne wywołania funkcji w odpowiedzi na wybór opcji New lub Open z menu File powodują reinicjalizację dokumentu, przez co może on być użyty ponownie. Reinicjalizacja następuje w funkcji SaveModified. Funkcja ta sprawdza fakt wprowadzenia zmian w dokumencie. Klasa CDocument, będąca klasą bazową wszystkich klas dokumentu, posiada zmienną składową typu BOOL, której wartość świadczy o obecności zmian w dokumencie. Zawartość tej zmiennej ustalana jest przez funkcję SetModifiedFIag. Jeśli w dokumencie nastąpiły zmiany, użytkownik zostaje wezwany do zapisania dokumentu przed kontynuacją pracy. Okno komunikatu umożliwia użytkownikowi kontynuację z zapisaniem zmian, bez zapisywania lub cofnięcie funkcji OpenDocumentFile. Zapisanie dokumentu odbywa się poprzez wywołanie OnSaveDocument, a dalej następuje jego reinicjalizacja poprzez kolejną funkcję DeleteContents. Obie wymienione funkcje opisane są dalej, w tym rozdziale. 288_________________________________ Poznaj Visual C++ 6 Dokumenty OLE Jeśli AppWizard tworzy aplikację serwera lub kontenera OLE, jej klasa dokumentu wyprowadzona zostanie z jednej spośród podklas CDocument: COleDocument, co-leLinkingDoc lub C01eServerDoc. Klasy te umożliwiają traktowanie dokumentów jako obiekty OLE. Jeśli zachodzi potrzeba utworzenia nowego obiektu dokumentu, wywołana zostaje funkcja szablonu dokumentu CreateNewDocument (linia 27). Używa ona informacji klasy wykonawczej do skonstruowania obiektu dokumentu, specyficznego dla danej aplikacji. Funkcja ta dodaje również nowy dokument do listy dokumentów bieżących. Wraz z nowym dokumentem, utworzony zostaje również widok oraz okno. Wykonuje to kolejna funkcja, CreateNewFrame. Funkcja CreateNewFrame (linia 28) korzysta z informacji klasy wykonawczej szablonu do skonstruowania obiektów okna i widoku. Po utworzeniu okna, następuje wywołanie funkcji CFrameWnd: :LoadFrame, której przekazuje się identyfikator zasobów, określony w konstruktorze szablonu. Przypomnijmy sobie, iż identyfikatorem tym jest IDR_MAIN-FRAME. 'LoadFrame ładuje każdy z zasobów (menu, pasek narzędziowy, ikona, tablice akceleratorów i łańcuchów znakowych) i dołącza je do okna. Jeśli załadunek któregokolwiek z zasobów zakończy się niepowodzeniem, funkcja LoadFrame zakończy działanie, wysyłając komunikat: Warning: CDocTemplate couldn't create a f ramę (Ostrzeżenie: CDoc-Template nie mógł utworzyć okna). Kiedy wszystkie obiekty są już skonstruowane, następuje inicjalizacja dokumentu poprzez funkcję OnNewDocument (linia 31), opisaną nieco dalej. Na końcu sekwencji wywoływana jest funkcja InitialUpdateFrame (linia 32). Uaktywnia ona nowo utworzony widok, wysyłając następnie komunikat WM_INITIAL-UPDATE do okien potomnych okna głównego, po którym wywołana zostaje funkcja wirtualna CView: :OnlnitialUpdate. Normalną praktyką jest rozszerzenie treści tej funkcji w celu specyficznej inicjalizacji widoku. Konieczne zwykle jest również upewnienie się, iż wywołano również właściwą klasę bazową. CDocument:: OnNewDocument Funkcja ta wywoływana jest podczas tworzenia obiektu dokumentu. Ma to miejsce podczas startu aplikacji lub po wydaniu przez użytkownika polecenia New z menu File. Dalej następuje wywołanie funkcji DeleteContents w celu uporządkowania zawartości dokumentu, ustalenie stanu modyfikacji na FALSE (niezmodyfikowany). Funkcja ta opróżnia również łańcuch, w którym zapisana była nazwa pliku zawierającego dokument. CDocument: :OnOpenDocument Funkcję tę wywołuje się podczas otwierania istniejącego pliku. Może się to odbyć podczas startu aplikacji, jeśli plik został umieszczony w wierszu poleceń, jako jego argu- Dokumenty, widoki, ramki oraz ich stosowanie 289 ment lub w odpowiedzi na polecenie Open z menu File, wydane przez użytkownika. Wywołana zostaje następnie funkcja DeleteContents. Implementacja domyślna otwiera plik, a następnie wywołuje funkcję Serialize dla załadowania danych dokumentu. Po załadowaniu danych, plik zostaje zamknięty, a stan modyfikacji otrzymuje wartość FALSE (niezmodyfikowany). CDocument: :OnSaveDocument Funkcja wywoływana jest podczas zapisywania pliku. Dzieje się to po wydaniu przez użytkownika polecenia Save lub Save As z menu File. Wywołuje się ją również, gdy użytkownik zapytany w momencie zamykania zmodyfikowanego dokumentu wyda decyzję o zapisaniu pliku. Funkcja ta wykorzystuje jeden parametr, będący stałym łańcuchem znakowym, zawierającym nazwę pliku. Implementacja domyślna otwiera plik, a następnie wywołuje funkcję Serialize w celu zapisania danych dokumentu. Gdy dane zostaną już zapisane, plik ulega zamknięciu, a stan modyfikacji przybiera wartość FALSE. CDocument: :DeIeteContents Funkcja ta wywoływana jest przez funkcje OnNewDocument, OnOpenDocument oraz OnCloseDocument. Jest odpowiedzialna za usunięcie zawartości bieżącego dokumentu. Implementacja domyślna nie czyni niczego, gdyż dane dokumentu są specyficzne dla danej aplikacji. Musimy rozszerzyć treść tej funkcji, aby powodowała zwolnienie zawartości zmiennych dokumentu i ich reinicjalizację. CDocument:: OnCloseDocument Funkcja ta wywoływana jest w momencie zamykania dokumentu. W aplikacjach SDI ma to miejsce, gdy użytkownik nakazuje otwarcie innego lub utworzenie nowego dokumentu, jak również w chwili zamykania aplikacji. Funkcja OnCloseDocument zamyka najpierw wszystkie widoki należące do dokumentu, a następnie wywołuje DeleteContents przed destrukcją obiektu dokumentu. PATRZ TAKŻE • Więcej informacji na temat automatyzacji OLE znajdziesz w rozdziale 25. • Informacji o obsłudze plików i serializacji szukaj w rozdziale 23. Używanie dokumentów i widoków Implementacja architektury dokument/widok dostarcza podstawowych sposobów rozwijania aplikacji. Oddzielenie kodu obsługującego dane od kodu obsługującego interfejs użytkownika daje w efekcie aplikację modułową, która jest łatwiejsza do rozbudowy. Przykładowo, zmiana sposobu prezentacji istniejących danych na nowy jest kwestią zbu- 290 Poznaj Visual C++ 6 dowania nowej klasy widoku, używającej interfejsu istniejącego dokumentu dla uzyskania dostępu do żądanych informacji. Zaprojektowanie interakcji pomiędzy parą klas, dokumentu i widoku, jest najważniejszym zadaniem. Zwykle, największą trudnością, nawet dla doświadczonych programistów, jest ustalenie, które informacje powinny znaleźć się w klasie dokumentu, a które w klasie widoku. Nie istnieją tutaj twarde reguły i wszystko zależy w największym stopniu od specyfiki aplikacji. Optymalnie sam widok nie powinien przechowywać danych, tylko pobierać je w razie potrzeby z dokumentu. Tym sposobem zmiany w danych zachodzą tylko w jednym miejscu, skąd wprowadzane są do wszystkich widoków dokumentu. Jednakże takie rozwiązanie nie zawsze jest wygodne lub wystarczające. Weźmy jako przykład edytor tekstu, w którym często kopia wszystkich lub części danych dokumentu przechowywana jest w klasie widoku, gdyż w przeciwnym wypadku, widok musiałby mieć stały dostęp do klasy dokumentu (nawet za każdym naciśnięciem klawisza), co spowalniałoby pracę aplikacji. Istnieje wiele sytuacji, w których wskazane jest umieszczanie pewnych porcji danych w widoku w celu przyspieszenia działania. Nie stanowi to problemu - mamy pełną swobodę w tworzeniu własnego kodu w tym zakresie. Jednak niepotrzebne duplikowanie danych skomplikuje nam pracę, powinniśmy zatem takich sytuacji unikać. Inicjalizacja danych dokumentu Klasa CDocument jest klasą abstrakcyjną, musimy zatem wyprowadzić z niej własną klasę. Składowe klasy dokumentu służą do przechowywania danych aplikacji. W każdej, nawet najprostszej aplikacji, dokumenty wymagają licznych zmiennych składowych, często w formie tablic lub list łączonych. Klasy kolekcji MFC Biblioteka MFC dostarcza kilku klas kolekcji, m.in. CArray i CObList, do projektowania złożonych struktur. Pamiętajmy, iż w aplikacji SDI, obiekt dokumentu tworzony jest tylko raz i używany dalej dla kolejnych dokumentów. Z tego też powodu konstruktor dokumentu nie zawsze jest właściwym miejscem dla inicjalizacji zmiennych składowych, ponieważ byłyby one wywołane tylko raz, a nie dla każdego nowego dokumentu. W podrozdziale „Stosowanie funkcji osnowy w architekturze dokument/widok" widzieliśmy, że wirtualna funkcja dokumentu DeleteContents wywoływana jest automatycznie z funkcji OnNewDocument, OnOpenDocument oraz OnCloseDocument. Są to sytuacje, w których zawartość dokumentu ulega zmianie. Funkcja DeleteContents odpowiedzialna jest za usunięcie zawartości dokumentu i może z powodzeniem inicjalizo-wać zmienne dokumentu, po wykonaniu podstawowego zadania. Dokumenty, widoki, ramki oraz ich stosowanie 291 Dodawanie do dokumentu zmiennych składowych Klasa dokumentu jest klasą jak każda inna w projekcie, możemy więc dodawać do niej zmienne stosując menu skrótów panelu ClassView wybierając z niego pozycję Add Member Variable. Przy dodawaniu wielu zmiennych może się okazać, iż wygodniejszym sposobem jest bezpośrednia edycja kodu. Możemy wejść w ten tryb klikając dwukrotnie pozycję klasy dokumentu w panelu ClassView. Ochrona zmiennych dokumentu Klasa dokumentu powinna zawierać zmienne chronione, do których dostęp uzyskuje się poprzez publiczne funkcje składowe. Aby zapewnić integralność danych, zmienne dokumentu powinny być zmiennymi chronionymi (protected). Zmienna chroniona może być zmieniona jedynie przez funkcje własnej klasy lub klasy z niej wyprowadzonej. Zakazanie wszystkim innym klasom dokonywania zmian w danych dokumentu oznacza, iż istnieje tylko jeden punkt, z którego zmienne mogą być zmienione; stąd wszystkie widoki powiązane z dokumentem są zawiadamiane o zmianie danych. Aby umożliwić widokom wgląd w chronione dane dokumentu, klasa dokumentu musi dostarczyć funkcji dostępowych, zwracających odwołania do zmiennych składowych. Teraz, gdy poznaliśmy już architekturę dokument/widok, zajmiemy się rozbudową programu SDICoin. W obecnym punkcie zajmiemy się klasą dokumentu, CSDICoinDoc. Klasa dokumentu wymaga liczby monet, jaką można zapisać w pojedynczej zmiennej składowej. Chociaż do klasy widoku należy jedynie narysowanie stosu monet, klasa ta wymaga dostępu do tej liczby. Najprostszą metodą zapewnienia jej tego, będzie określenie publicznej zmiennej składowej dokumentu przechowującej tę liczbę, umożliwiającej klasie widoku bezpośredni dostęp do tej liczby. Pewną niedogodnością takiego rozwiązania jest możliwość dokonania zmiany zawartości zmiennej przez klasę widoku, nawet przez przypadek. Preferowaną metodą jest posiadanie chronionej zmiennej składowej przechowującej liczbę monet dla klasy dokumentu oraz dostarczenie klasie dokumentu funkcji dostępowej, zapewniającej klasie widoku dostęp do danych. Aby dodać niezbędny ku temu kod, należy wykonać poniższą procedurę. Implementacja przechowywania danych dokumentu oraz metod dostępu 1. W panelu ClassView wybierz klasę CSDICoinDoc. Następnie z menu skrótów wybierz Add Member Variable. Otwarte zostanie okno dialogowe Add Member Variable. 2. W polu Variable Type wprowadź typ int. W polu Variable Name wpisz nazwę zmiennej: m_nCoins, a także zaznacz przełącznik alternatywny Protected Access. Kliknij OK. 292_____________________________________Poznaj Visual C++ 6 3. Mając cały czas podświetloną pozycję CSDICoinDoc, otwórz menu skrótów, a z niego wybierz tym razem Add Member Function. 4. Wprowadź int jako Function Type. Wpisz GetCoinCount w polu Function Decla- ration i wybierz przełącznik Public Access. 5. Wprowadź następujący kod do treści funkcji GetCoinCount: return m_nCoins ;. 6. Naciśnij Ctri+W lub wybierz CIassWizard z menu View, aby uruchomić kreator. 7. Wybierz kartę Message Maps. 8. Z listy Ciass Name wybierz CSDICoinDoc. 9. Z listy Object IDs wybierz taką samą pozycję. 10. Z listy Messages wybierz DeleteContents, a następnie kliknij przycisk Add Function. 11. Kliknij OK, by zamknąć okno CIassWizard. 12. Wprowadź do kodu funkcji DeleteContents po komentarzu TODO następujący wpis: m_nCoins = l ; csDlCoin posiada w tej chwili chronioną zmienną składową m_nCoins, inicjalizowa-ną w funkcji DeleteContents. Dodaliśmy również dostępową funkcję składową GetCoinCount, która będzie służyła do pobierania wartości zmiennej m_nCoins. Dostęp widoku do danych dokumentu Aby widok miał możliwość pobierania danych z dokumentu, musi mieć dostęp do jego obiektu. Osnowa MFC dostarcza takiego rozwiązania, automatycznie dodając funkcję GetDocument do klasy widoku aplikacji. Funkcja ta zwraca wskaźnik do dokumentu, z którym widok jest związany poprzez szablon dokumentu. W rzeczywistości dostarczane są dwie funkcje GetDocument. W bieżącym przykładzie zobaczymy, że klasa CSDICO-inView posiada implementacje GetDocument w plikach SDlCoinView.cpp oraz SDlCo-inView. h. Obie funkcje wykonują to samo zadanie. Jedyną różnicą pomiędzy nimi jest to, iż pierwsza użyta jest w trybie projektu Debug, druga natomiast w trybie Release. Powodem takiego rozwiązania jest fakt, że funkcje iniine są skuteczniejsze i zastępują wielokrotne wywoływanie podczas pracy programu. SDlCoinView.cpp łifdef _DEBUG CSDICoinDoc* CSDICoinView::GetDocument() ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSDICoinDoc))) ; return (CSDICoinDoc*)m_pDocument ; łendif // _DEBUG Ol/H-^Ulll V ICYT.ll #ifdef _DEBUG // wersja debug w pliku SDICoinView.cpp iniine CDSICoinDoc* CSDICoinView::GetDocument() ( return (CSDICoinDoc*)m_pDocument; } #endif Klasa CSDICoinView odpowiada za wyświetlenie stosu monet. Kod rysujący imitację monet jest bardzo prosty i znajduje się wewnątrz funkcji CSDICoinView:: OnDraw. Dzięki kreatorowi AppWizard mamy już w programie szkielet tej funkcji. Osnowa MFC wywołuje tę funkcję zawsze, gdy widok musi zostać wysłany na ekran - na przykład, gdy wymiary okna głównego uległy zmianie. Funkcja ta wykorzystywana jest również podczas drukowania i podglądu wydruku. Zależnie od tego, gdzie widok ma zostać wyrysowany, funkcji OnDraw przekazywane są różne konteksty urządzeń. Teraz zajmiemy się edycją funkcji OnDraw, korzystając z listingu 12.3. Listing 12.3. LST13_1.CPP - dostęp do danych dokumentu w celu wyświetlenia rysunku 1 void CSDICoinView::OnDraw(CDC* pDC) 2 { 3 // ** Pobranie wskaźnika do dokumentu 4 CSDICoinDoc* pDoc = GetDocument(); O 5 ASSERT_VALID(pDoc) ; 6 7 // TODO: add draw code for native data here 8 // ** Zapisanie bieżącego pędzla 9 CBrush* pOldBrush = pDC->GetCurrentBrush(); @ 10 11 // ** Utworzenie pełnego, żółtego pędzla 12 CBrush br; 13 br.CreateSolidBrush(RGB(255,255,0) ) ; @ 14 15 // Wybór żółtego pędzla w kontekście urządzenia 16 pDC->SelectObject(&br); O 17 18 // Pobranie liczby monet z dokumentu 19 // -i rysowanie dwóch elips dla każdej z monet 20 for(int nCount = 0; © nCount < pDoc->GetCoinCount(); nCount++) 21 { 22 int y = 200 - 10 * nCount; 23 pDC->Ellipse(40, y, 100, y-30); ® 24 pDC->Ellipse(40, y-10, 100, y-35); 25 } 26 27 II** Przywróć pędzel bieżący 28 pDC->SelectObject(p01dBrush); O 29 } O Wywołanie funkcji GetDocument widoku w celu pobrania wskaźnika do klasy dokumentu aplikacji. © Pobranie wskaźnika do pędzla bieżącego, ustalonego w kontekście urządzenia p DC. © Utworzenie pędzla malującego pełnym, żółtym kolorem. O Wybór nowego pędzla w kontekście urządzenia. © Pętla wykonywana do momentu narysowania właściwej liczby monet. Ich liczba pobrana została poprzez funkcję dostępu dokumentu, GetCoinCount (). © Rysowanie dwóch elips w ustalonych odstępach dla każdej monety. O Przywrócenie oryginalnego pędzla kontekstu urządzenia. Pierwsze dwie linie (4 i 5) zawarte w funkcji są automatycznie tworzone przez AppWizard. Funkcja GetDocument zwraca wskaźnik do obiektu dokumentu. Wskaźnik ten zapisany jest w pDoc. Zostaje on użyty w pętli for (w linii 20) do pobrania bieżącej liczby monet poprzez wywołanie metody GetCoinCount. W linii 9 następuje przechowanie w pOldBrush wskaźnika do bieżącego pędzla w kontekście urządzenia. Pędzel ten przywracany jest na końcu funkcji, w linii 28. Linie 12, 13 oraz 16 tworzą żółty pędzel w lokalnej zmiennej br i ustawiają go w kontekście urządzenia, w gotowości do rysowania monet. Każda z monet składa się z dwóch elips, umieszczonych jedna nad drugą w celu utworzenia efektu trójwymiarowości. Do tego stosowana jest funkcja CDC :: Ellipse, co możemy zobaczyć w liniach 23 i 24. PATRZ TAKŻE • Informacji na temat kontekstów urządzenia szukaj w rozdziale 15. * Więcej informacji o rozwiązaniach graficznych znajdziesz w rozdziale 16. Używanie standardowych zasobów szablonu Jak mówiliśmy wcześniej, w projekcie umieszczane są automatycznie pewne zasoby, wspierające aplikację SDI od strony wizualnej. Są to: menu, pasek narzędziowy, ikona, tablica akceleratorów oraz łańcuch znakowy z nazwą dokumentu. Wszystkie te standardowe zasoby są w pełni funkcjonalne. Przykładowo, wybór pozycji New z menu File spowoduje wywołanie funkcji CWinApp: : OnFileNew, co pociąga Dokumenty, widoki, ramki oraz ich stosowanie 295 za sobą utworzenie nowego dokumentu. Podobne efekty daje użycie ikony New z paska narzędziowego. Nie oznacza to jednak, iż nie możemy zmienić domyślnego sposobu działania tych opcji. Możemy zmienić treść funkcji zarządzających i powodować wykonywanie dowolnych operacji. Wolno nam również usunąć menu lub pasek narzędziowy, jeśli nie są przydatne w naszej aplikacji. Jednak stosowniejszym sposobem postępowania jest pozostawienie tych elementów w ustalonym porządku i dodanie do nich własnych, specyficznych dla danej aplikacji. Jest to bardzo proste zadanie, gdyż każdy zasób może być edytowany przy użyciu edytora zasobów i łączony za pomocą CIassWizard z funkcjami przetwarzającymi polecenia. W przykładzie SDICoin musimy umieścić dwie nowe pozycje menu: jedna będzie dodawać monetę do stosu, a druga zabierać ją stamtąd. Po wybraniu jednej z nich, wywołana zostanie funkcja w klasie dokumentu, która powiększy lub pomniejszy liczbę monet na stosie, a następnie spowoduje odświeżenie widoku. W celu umieszczenia dodatkowych pozycji menu wykonamy poniższy algorytm. Dodawanie akceleratorów dla opcji menu Podczas edycji właściwości opcji menu można określić skrót klawiaturowy, dopisując go na końcu treści nagłówka. Przykładowo, nagłówek &Toolbar\tCtrl+T oznacza, że kombinacja klawiszy CtrI+T będzie wywoływała widok paska narzędziowego. Można dodać również wpis ID_VIEW_TOOLBAR w tablicy akceleratorów, definiując taką samą kombinację klawiszy. Dodawane nowych opcji menu 1. Rozwiń katalog Menu w panelu ResourceView i kliknij dwukrotnie IDR_MAINFRAME. Zasób menu wyświetlony zostanie w edytorze zasobów. 2. Kliknij menu Edit edytowanego zasobu. Ukaże się menu rozwijane. 3. Kliknij dwukrotnie pustą pozycję u dołu listy opcji. Wyświetlone zostanie okno dialogowe The Menu Item Properties, jak na rysunku 12.7. 4. Wprowadź identyfikator w polu ID. W obecnym przykładzie niech to będzie ID_EDIT_ ADD_COIN. 5. Wpisz w polu Caption nazwę nowej pozycji menu: Add a Coin (Dodaj monetę). 6. W polu Prompt wpisz treść informacyjną dla użytkownika: Increase the number of coins (Zwiększ liczbę monet). 7. Kliknij dwukrotnie kolejną pustą pozycję menu. 8. Wpisz identyfikator: ID_EDIT_REMOVE_COIN. 296 Poznaj Visual C++ 6 '^i Fte ^ift V»w intwt gicwct fiutó Jod ^a c? a9 x. sss i; a- • s ^'"do^ y^p ^lai-i ! ,'t ;,1 • . " 33 ^%' .qi»ja"FH<- N^ -d: 'n; - •ii^... ....^':. ••—• -^l*"--- J: ^•\';|iS'S1^ :~—„-—,—„—;„;^..„...„„—••-- ^J.5i • •.::^•;;.•. BtaSDICoinnmiuc..- '':• * ^JAcMtealCT l »j.L3 Diaku i mOkm i la -a MOTU | S 11 SUllM* ; a; C) Toobaf : ; B-C.jVetiiwt Undo Ctrk2 C^ Ctrtó Sopy Ctfl.C AchlaCoR MemoyflaL on' -» t Geiie^ | Efiend&jSl^EŁ | 11.): jlLi^[iirALi^l:fJ H ^•'J i;^iW JAddaCw ,.r;E.e[>.yala • rpgMip r l[3aclr^ Sieak-. JN^ne~^:„. i^.i^w^^ Ofi*^:: • r ^e '^ll^lll: l I^R^tfcI^Incieate iht omniw olcans W l 11 11111 11"1111 1'111" 11" ' '11 "iiiiii "llll'lf """" "iili iiiiiiiiii 1""1111 iiiii ,|[ ®Ma»M^«httfc»^A:B«rt(i.teteKiiA*Łjllat: il.yiSBSM^iSffii^^-i^sPs^a^i-^ii^asi Rysunek 12.7. Okno właściwości pozycji menu 9. Wpisz w polu Caption treść nagłówka nowej pozycji: Remove a Coin (Usuń monetę). 10. W polu Prompt wpisz: Decrease the number of coins (Zmniejsz liczbę monet). 11. Wywołaj kreator CIassWizard, naciskając Ctri+W lub wybierając stosowną pozycję menu View. 12. Wyświetl kartę Message Maps. 13. Z listy Ciass Name wybierz CSDICoinDoc. 14. Z listy Object IDs wybierz pozycję ID_EDIT_ADD_COIN. 15. Z listy Messages wybierz COMMAND, a następnie kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 16. Wybierz pozycję ID_EDIT_REMOVE_COIN z listy Object IDs. 17. Z listy Messages wybierz COMMAND, a następnie kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 18. Kliknij przycisk Edit Code. Odświeżanie widoku Po wprowadzeniu każdej modyfikacji danych dokumentu, prawie zawsze zachodzi konieczność odzwierciedlenia tych zmian w widoku. Przeprowadza się to poprzez wywołanie funkcji, które powodują przerysowanie widoku. W poprzednim punkcie dodaliśmy dwie nowe pozycje menu i utworzyliśmy dla nich w klasie dokumentu funkcje obsługi poleceń. Teraz musimy jeszcze przeprowadzić edycję dwóch funkcji, według listingu 12.4. Dokumenty, widoki, ramki oraz ich stosowanie 297 Listing 12.4. LST13_2.CPP — odświeżanie widoku po zmianach w dokumencie 1 void CSDICoinDoc::OnEditAddCoin()0 2 { 3 // TODO: Tutaj umieść kod funkcji obsługi polecenia 4 5 // ** Inkrementacja liczby monet 6 m nCoins++; @ 7 8 // ** Odświeżenie widoku stosu 9 UpdateAllYiews(NULL); © 10 } 11 12 void CSDICoinDoc::OnEditRemoveCoin() 13 { 14 // TODO: Tutaj umieść kod funkcji obsługi polecenia 15 16 // ** Dekrementacja liczby monet 17 if(m_nCoins > 0) 18 m nCoins—; 19 20 // ** Odświeżenie widoku stosu 21 UpdateAllViews(NULL); 22 } O Funkcja obsługi polecenia wywołana zostaje po wybraniu z menu Edit pozycji Add a Coin. © Inkrementacja wartości zmiennej składowej dokumentu, przechowującej liczbę monet. © Wywołanie funkcji OnUpdate () powodującej odświeżenie wszystkich widoków danego dokumentu. Jak widzimy, funkcje powodują zwiększenie lub zmniejszenie wartości zmiennej m_nCoins, należącej do dokumentu. Następnie wywołana zostaje funkcja UpdateAllViews z parametrem NULL. Parametr ten nakazuje odświeżenie wszystkich widoków związanych z dokumentem. Każdy z widoków jest odświeżany po wywołaniu funkcji OnUpdate. Funkcja ta unieważnia obszar roboczy okna widoku i powoduje wywołanie funkcji OnDraw. Skompilujemy i uruchomimy teraz nasz program. Spróbujemy użyć poleceń Add a Coin oraz Remove a Coin z menu Edit. Ekran programu powinien wyglądać jak na rysunku 12.8. 298 Poznaj Visual C++ 6 ^^^^^^^^^^^Hrr Untilleil - SUlLom ; miStt fte El yiew Het. : D .:'•- Re(nove a Coio ? ' lir Rysunek 12.8. Gotowa aplikacja SDICoin Rozdział 13 Praca z menu Tworzenie i edycja zasobów menu Tworzenie funkcji obsługi dla poleceń menu Nauka implementacji menu skrótów_____ Dynamiczne tworzenie opcji menu Tworzenie i edycja zasobów menu Menu są kręgosłupem interakcji użytkownika z aplikacjami Windows, pozwalając mu na szybkie wyszukiwanie poleceń w strukturze interfejsu. Menu posiadają nagłówki (widoczne na pasku menu) oraz pozycje (opcje w rozwiniętym menu). Pozycje mogą posiadać menu podrzędne, kierując użytkownika poprzez hierarchię kodu aplikacji. Można je dezaktywować, zaznaczać lub traktować jako przełączniki alternatywne. Menu skrótów umożliwiają dodawanie nowych, specyficznych opcji dla obiektów aplikacji, udostępnianych na życzenie użytkownika wyrażone poprzez kliknięcie prawego klawisza myszy. Zapewnia to użytkownikowi szybszą i bardziej przejrzystą formę kontroli aplikacji. Menu skrótów możemy w łatwy sposób tworzyć i obsługiwać przy użyciu edytora za- sobów. Edytor ten pozwala tworzyć nowe nagłówki menu, jak też ich nowe pozycje, a wszystko w trybie WYSIWYG, What You See Is What You Get (dostajesz to, co widzisz). PATRZ TAKŻE f Więcej informacji o edytorze zasobów znajdziesz w rozdziale 2. 300 Poznaj Visual C++ 6 Dynamiczne tworzenie menu Jeśli nie używa się zasobów menu, można tworzyć menu dynamicznie i dodawać do nich nowe opcje podczas pracy programu, co będzie opisane w dalszej części tego rozdziału. , Dodawanie nowych zasobów menu Menu wyświetlane są zwykle z zasobów menu (takich jak szablony okien dialogowych), które przechowują wszystkie nagłówki i pozycje menu. Nowe zasoby menu możemy dodawać poprzez edytor zasobów. Tworząc nowy projekt typu SDI lub MDI, AppWizard dodaje automatycznie do projektu domyślne menu, o identyfikatorze IDR_MAINFRAME. Umieszczanie własnych menu wykonuje się według przedstawionej poniżej procedury. Dodawanie nowych zasobów menu 1. Otwórz panel ResourceView wyświetlający zasoby projektu. 2. Kliknij prawym klawiszem pozycję najwyższego poziomu, a z otwartego menu wybierz pozycję Insert, po czym wyświetlone zostanie okno dialogowe Insert Resource. 3. Wybierz pozycję Menu z listy typów zasobów. 4. Kliknij przycisk New, aby umieścić nowy zasób menu. Powinieneś ujrzeć szablon nowego menu pod nagłówkiem Dialog Resource. 5. Kliknij prawym klawiszem okno dialogowe i wybierz pozycję Properties z otwartego menu skrótów. 6. Możesz teraz zmienić identyfikator nowego zasobu na bardziej jednoznaczny, zastępując domyślny IDD_. 7. Możesz dalej rozszerzyć zasoby projektu, klikając nagłówek Menu, co pozwoli ci wybrać Insert Menu i dodać nowy zasób, a następnie ustalić jego identyfikator, powtarzając kroki od 5 do 7. Używanie klawisza skrótu Insert Menu Naciskając kombinację klawiszy Ctrl+2 można pominąć kroki 1-4 powyższej procedury. Skrót ten jest efektywny w każdym punkcie działania Visual Studio. Po naciśnięciu tej kombinacji w panelu zasobów pojawi się pozycja nowego menu, a ono samo wyświetlone zostanie w głównym oknie edytora. fAIK& IAt\Z.t • Węcę/' informacji o tworzeniu aplikacji SD l znajdziesz w rozdziale 12. « Więcej informacji o tworzeniu aplikacji MDI znajdziesz w rozdziale 21. « Więcej informacji o edytorze zasobów znajdziesz w rozdziale 34. Dodawanie do menu pozycji nagłówków Po włączeniu do projektu nowego zasobu lub wybraniu istniejącego, możemy edyto-wać ten zasób w oknie edytora zasobów. Menu wyświetlane jest w edytorze w postaci, w jakiej pojawi się w gotowej aplikacji. Widoczne są zatem nagłówki paska menu. Tutaj możemy dodawać nagłówki do nowego lub istniejącego zasobu menu. Dodanie nagłówka nowego menu 1. Wybierz żądany zasób menu z panelu ResourceView, a w oknie edytora wyświetlony zostanie pasek menu. 2. Kliknij pusty prostokąt widoczny po prawej stronie istniejących nagłówków. Wokół prostokąta powinna pojawić się biała obwódka. 3. Wpisz nazwę nowego menu. Podczas jej wpisywania, tekst pojawiać się będzie jednocześnie w oknie właściwości (Menu Item Properties) oraz wewnątrz oznaczonego prostokąta. Pod nowym nagłówkiem pojawi się również pusta pozycja menu, jak widać na rysunku 13.1. MFC AuuWizald - Ślep 4 ul E Edi*rq Coktial: | Record ; P? Cfccck Boi (*• Radio BWUOB |i| F (bdio BMI<» X Wha> featmes would you like to include? P (Docking tooiba; 1^ Iniliaistatu^baf 17 Printing and print pieview r ConteKt-sensitive Hglp 17 30 conttols r MAPI(MessagingAR) r ^/indows Sockete H ów do you want your toolbars to look? ff Noimal (~ Intetnet Euplorei ReBars Howmany files would you like on youtrecent (ile list? ró^j Advanced... | Einish Cancel Rysunek 13.1. Dodawanie do paska nagłówka nowego menu 302 Poznaj Visual C++ 6 4. Oznacz według uznania inne, widoczne w oknie właściwości opcje (ich znaczenie objaśnione zostanie w dalszej części tego rozdziału, możliwe będzie także dokonanie poprawek). 5. Naciśnij Enter, by zakończyć proces dodawania nagłówka nowego menu. 6. Możesz teraz przemieszczać nowy nagłówek wewnątrz paska menu, ustalając własny porządek. Dodawanie pozycji menu Wstawianie nowej pozycji menu 1. Kliknij nagłówek menu, które chcesz rozbudować o nową pozycję. 2. Kliknij pusty prostokąt widoczny pod wybranym nagłówkiem. Jeśli dodajesz pozycję niższego poziomu, kliknij ją, by wyświetlić listę pozycji tego poziomu. Wokół oznaczonej pozycji pojawi się biała obwódka. 3. Wpisz nazwę nowej pozycji. Pojawi się ona wewnątrz oznaczonego prostokąta oraz oknie właściwości (rysunek 13.2). 4. Oznacz według uznania inne widoczne w oknie właściwości opcje (ich znaczenie objaśnione zostanie w dalszej części tego rozdziału, możliwe będzie także dokonanie poprawek). 5. Kliknij pole edycji Prompt. Możesz tu wprowadzić tekst, który pojawi się w pasku stanu, gdy dana pozycja menu zostanie oznaczona. Możesz również dodać drugi łańcuch znakowy, który pojawiać się będzie w formie „dymku", gdy wskaźnik myszy znajdzie się ponad odpowiadającą pozycji menu ikoną w pasku narzędziowym. Ten łańcuch oddzielasz od poprzedniego znakiem przejścia do nowej linii, czyli \n. Przykład pokazany jest na rysunku 12.2. Rysunek 13.2. Umieszczanie w menu nowej pozycji Praca z menu 303 6. Kliknij pole ID. Powinieneś ujrzeć domyślny identyfikator menu, oparty o nazwy nagłówka i pozycji menu. Jeśli chcesz, zmień ten identyfikator na bardziej sugestywny lub odpowiedniejszy dla specyfiki danego menu. 7. Możesz zmienić porządek menu, przeciągając nową pozycję w nowe położenie (nawet pod inny nagłówek). Stosowanie identyfikatorów poleceń Identyfikatory poleceń przypisane poszczególnym pozycjom menu są wykorzystywane do powiadamiania o wydaniu polecenia przez użytkownika. Windows wysyła komunikat WM_COMMAND do okna zawierającego menu. Identyfikator zostaje następnie odnaleziony na mapie komunikatów przez makro ON_COMMAMD w celu ustalenia właściwej funkcji obsługi. Przypisywanie identyfikatorów do poleceń Identyfikator pozycji menu będzie używany do przydzielenia jej funkcji obsługi, co opisane jest w punkcie „Dodawanie funkcji obsługi polecenia menu", w dalszej części rozdziału. Jeśli chcemy przypisać pozycji menu istniejący już identyfikator (np. przycisku na pasku narzędziowym), musimy go wybrać z listy kombinowanej ID, którą zobaczymy po kliknięciu przycisku rozwijania. Listę tę znajdziemy w oknie właściwości. Możemy również dzielić identyfikator pomiędzy różnymi zasobami menu. Przykładowo, gdybyśmy chcieli utworzyć dwa różne widoki, oba wyświetlające menu File i wywołujące tę samą funkcję obsługi, możemy w nowym widoku utworzyć duplikat interesującego nas menu. W obu widokach pozycje menu będą miały te same identyfikatory, w związku z tym wywoływane będą te same funkcje. Do czasu utworzenia właściwych funkcji obsługi dla poszczególnych pozycji menu, będą one nieaktywne. Po wpisaniu łańcucha znakowego w polu Prompt (krok 5), łańcuch zostanie zapisany w tablicy łańcuchów z odnoszącym się do niego identyfikatorem. Po skompilowaniu i wygene- rowaniu pliku wykonywalnego w programie łańcuch ten będzie wyświetlany w pasku stanu. Zmiany właściwości pozycji menu Okno właściwości danej pozycji menu możemy wyświetlić klikając ją dwukrotnie w oknie edytora lub oznaczając i naciskając Alt+Enter. W otwartym oknie możemy dokonywać zmian wcześniej ustalonych właściwości. Każda pozycja menu może zostać przesunięta poprzez przeciągnięcie jej w nowe poło- żenie. Pojawiająca się podczas przeciągania linia pomocnicza wskazuje miejsce „zrzutu". 304_____________________________________Poznaj Visual C++ 6 Niepotrzebną pozycję menu możemy usunąć zaznaczając ją, a następnie naciskając klawisz Delete. Jeśli potraktujemy w ten sposób pozycję nagłówkową, usuniemy również wszystkie pozycje menu, ale zostaniemy o tym uprzedzeni i poproszeni o akceptację. PATRZ TAKŻE • Wyjaśnień dotyczących tablic łańcuchów znakowych szukaj w rozdziale 2. Umieszczanie separatorów Separatory służą do dzielenia menu na mniejsze podgrupy. Są to poziome kreski usytuowane w poprzek kolumny menu (jak w menu File programu Developer Studio). Aby umieścić separator, musimy dwukrotnie kliknąć pustą pozycję menu, a następnie w oknie właściwości zaznaczyć opcję Separator. Po zaznaczeniu tej opcji, wszelkie inne staną się niedostępne, z wyjątkiem pola edycji Caption. W celu zamknięcia okna właściwości należy nacisnąć Enter. Ujrzymy wstawiony separator, dzielący kolumnę menu. Separator ten możemy później przesunąć, gdybyśmy zmienili decyzję co do jego położenia. Zamiana separatorów na pozycje menu Separatory mogą być przekształcane w pozycje menu poprzez wpisanie tekstu w polu Caption okna właściwości. Tworzenie pozycji menu podrzędnego Pozycje menu mogą stanowić pozycje nagłówkowe dla menu podrzędnych, otwieranych po najechaniu myszą na nagłówek (rysunek 13.3). Aby utworzyć takie menu, musimy dodać nową pozycję menu nadrzędnego, a następnie w oknie właściwości zaznaczyć opcję Pop-up. Gdy to uczynimy, pole ID zostanie zaciemnione, a obok pozycji menu, która ma otwierać menu podrzędne, pojawi się mała, skierowana w prawo strzałka, wskazująca pustą pozycję podrzędną. Pozycje menu podrzędnego możemy ustanawiać w taki sam sposób jak nadrzędnego. W razie potrzeby z menu podrzędnego wyprowadzać możemy dalsze menu, jeszcze niższego poziomu. W ten sposób możliwe jest tworzenie złożonych struktur hierarchicznych menu. Praca z menu 305 a Et- y '/••i ''iii ^ 3@ Eldiec' fimtó Irols Wndow Beto .=,s===;=Bg;g3iiiaa 'Wii/St Bw ŁM) M>H«a Sit My 9f r"---" ::!:!:^"" • .:.:. '•-\ ~3 idintenu lelouicef ' *• UAcceletatoi 35 j Dialog L-fc- LJ icon °) -a Menu Menu Irem | opup » One ;--------| | • ^(iłDR MAlHFRAHEl : B>-IStil«lTaUe : iB Cj Toota : -la ? G»wal EitłendedSylss 1 : | j I;aH«»r |ailiflas r • . p Pgp-uc r' }oacl!ve &ieak |rjune•*| r Chtked r SiW) r Hełp "t® -. l :,:.'^',!1:' Rysunek 13.3. Tworzenie menu podrzędnego Umieszczanie znaczników Pozycje menu mogą posiadać znaczniki - „ptaszki", umieszczone po ich lewej stronie. Znaczniki możemy wprowadzać programowo lub ustalać ich domyślne wyświetlanie, posługując się edytorem zasobów. Aby ustalić wyświetlanie domyślne, należy zaznaczyć opcję Checked w oknie właściwości pozycji. Umieszczanie skrótów klawiaturowych Możemy ustanowić skróty klawiaturowe dla poszczególnych pozycji menu, które pozwolą użytkownikowi wywoływać polecenia menu przy użyciu klawiatury. Skrót klawiaturowy reprezentowany jest przez literę z podkreśleniem, stanowiącą skrót dla danej pozycji menu. Aby przydzielić skrót do treści pozycji menu, musimy wstawić znak & (ang. amper-sand) przed literą, którą na skrót wytypowaliśmy. W wyświetlanym menu litera ta będzie podkreślona, a naciśnięcie odpowiadającego jej klawisza spowoduje wywołanie polecenia. Przykładowo, jeśli chcemy uczynić literę S skrótem klawiaturowym dla pozycji My Shortcut, w nagłówku tej pozycji (Caption w oknie właściwości) musimy dokonać wpisu: My &Shortcut, w wyniku czego napis w menu będzie miał postać My Shortcut. Gdybyśmy jednak chcieli wstawić do nazwy pozycji znak &, musimy wtedy umieścić w nagłówku dwa takie znaki obok siebie, na przykład My && Am&persand Napis w menu będzie wtedy wyglądał tak: My & Amfiersand. 306 Poznaj Visual C++ 6 Klawisze mnemonik Klawisze skrótów bywają nazywane klawiszami mnemonik i mogą być ustalane dla kontrolek w oknach dialogowych. PATRZ TAKŻE • Więcej informacji na temat skrótów klawiaturowych znajdziesz w rozdziale 3. Obsługa poleceń menu Obsługa poleceń menu jest zorganizowana na podobnych zasadach, jak przyciski w oknach dialogowych, czyli poprzez utworzenie funkcji obsługi. Możemy zatem użyć kreatora CIassWizard, by utworzyć funkcje obsługi poleceń menu. Dodana zostanie również pozycja mapy dla komunikatu Windows, WM_COMMAND, wysyłanego wraz z identyfikatorem przez pozycję menu. Gdy użytkownik wybierze określoną pozycję menu, system wyśle komunikat WM_COMMAND do okna aplikacji będącego w posiadaniu danego menu. Gdy odnaleziona zostanie odpowiednia pozycja mapy komunikatów, nastąpi wywołanie właściwej funkcji obsługi. Możemy także dodać funkcje obsługi poleceń interfejsu użytkownika w celu zapewnienia możliwości aktywowania / dezaktywowania określonych menu, poprzez oddzielną, wygenerowaną przez CIassWizard mapę komunikatów i funkcję obsługi. PATRZ TAKŻE « Więcej o mapach komunikatów mówimy w rozdziale 4. Dodawanie funkcji obsługujących polecenia menu Po umieszczeniu nowego menu, możemy przystąpić do tworzenia funkcji obsługujących poszczególne pozycje menu i wywołujących wykonanie związanych z nimi kodów. Owe funkcje mogą być implementowane przez każdą klasę, której obiekt związany jest z wywoływanym poleceniem. W normalnych warunkach będzie to klasa dokumentu lub określonego widoku implementującego menu. Jednakże możemy również umieścić takie funkcje w klasach aplikacji bądź okna głównego. Najlepszym sposobem na dokonanie właściwych ustaleń jest określenie miejsca najdogodniejszego dla dostępu do danych i metod, z punktu widzenia różnych obiektów aplikacji. W celu dodania funkcji posłużymy się kreatorem CIassWizard. Dodanie za pomocą CIassWizard funkcji obsługi poleceń menu l. Wybierz pozycję menu, dla którego chcesz dodać funkcję obsługi. Praca z menu 307 2. Wywołaj CIassWizard, naciskając Ctri+W lub wybierając odpowiednią pozycję z menu View. W oknie listy Object IDs powinieneś ujrzeć zaznaczony identyfikator wybranej pozycji menu, jak na rysunku 13.4. FC Cla»Wizaid Messaga Maps 'roject: MemberVafiab!es | Automation Act!Ve^ CMain Ffd Frm.cp p ^lessas es UPDA TE me ^J CO M MAŃ C' Ul AddFunction,.. ID FILE OPEN ID FILE PRINT J PREVIEW SETUP | S iBiBH——Lil y^MKi:/ •-,.•:::.• .:„i:^«':':'::i:::;^- '.:" Edii Coda ID FILE PRINT ID FILE PRINT 10 FILE SAVE ID FILE SAVE A lil'A!T«i1J:1 M embef jun^ions W OnCreate ONWMCREATE V PieCieateWindow Detcription: Handle a command (ftom menu. accel. cmd buttoni | OK l Cancel j Rysunek 13.4. Wskazany w CIassWizard identyfikator polecenia menu 3. Lista kombinowana Ciass Name wskazywać będzie klasę docelową dla nowej funkcji. Gdy klikniesz przycisk rozwijania listy, będziesz mógł wybrać inną niż aktualnie wybrana, klasę spośród zdolnych do zaimplementowania nowej funkcji. Możesz wybrać klasę widoku, która implementuje menu lub klasę dokumentu. Wszystko zależy od tego, która z nich będzie odpowiedniejsza. 4. Kliknij dwukrotnie pozycję komunikatu COMMAND widoczną na liście Messages, po czym wyświetlone zostanie okno dialogowe Add Member Function (rysunek 13.5). Jeśli nie lubisz podwójnych kliknięć, zaznacz pozycję komunikatu, a następnie kliknij przycisk Add Function. Add Membel Function Membet function nanie: l^^asgiaaEa E Message: COMMAND ObiectlD: ID_MYHEADER_MYSUBONE Rysunek 13.5. Okno dialogowe Add Member Function umożliwia zmianę nazwy funkcji 5. Domyślnie ustalona nazwa funkcji oparta jest o identyfikator nagłówka dla menu, w którym znajduje się dana pozycja. Możesz zmienić tę nazwę, wpisując nową w polu Member Function Name, zanim funkcja zostanie utworzona. 308_____________________________________Poznaj Visual C++ 6 6. Kliknij Ok, co spowoduje dodanie nowej funkcji. 7. W CIassWizard nowa funkcja powinna być widoczna w oknie Member Functions. 8. Możesz teraz edytować treść funkcji, zaznaczywszy jej pozycję i klikąwszy przycisk Edit Code. Po dodaniu nowej funkcji obsługi, możemy dodać również własny kod implementacyjny. W bieżącym przykładzie będzie to funkcja (implementowana w klasie dokumentu) jedynie wyświetlająca okno komunikatu informujące, iż użytkownik wybrał określoną pozycję menu: void CSdimenuDoc::OnMyHeaderMysubone() ( AfxMessageBox("You picked the l'" menu item") ; } PATRZ TAKŻE • Aby zrozumieć komunikaty przekazywane w aplikacjach SDI, zajrzyj do rozdziału 12. ^ Szczegółowe informacje na temat map komunikatów znajdziesz w rozdziale 4. Dodawanie funkcji obsługi poleceń interfejsu użytkownika Funkcja obsługi polecenia interfejsu użytkownika odpowiedzialna jest za wygląd, styl oraz sposób zachowania danej pozycji menu. Przed wyświetleniem menu wywoływane są odpowiednie funkcje obsługi, umożliwiające aplikacji aktywowanie lub dezaktywowanie poszczególnych pozycji, dodawanie lub usuwanie znaczników lub zmianę treści opisu pozycji. Wymienione atrybuty interfejsu użytkownika możemy zmienić poprzez wywołanie funkcji dostępu do obiektu ccmdUl, związanego z pozycją menu. Gdy następuje wywołanie funkcji obsługi interfejsu użytkownika, przekazany jej zostaje wskaźnik do obiektu CCmdui. Na przykład, poniższa funkcja aktywuje pozycję menu, w związku z czym użytkownik może ją wybrać. Czyni to wywołując funkcję Enable () wskazanego obiektu poprzez wskaźnik pCmdUl: void CSdimenuDoc::OnUpdateMyheaderMysubone(CCmdUl* pCmdUl) { pCmdUl - >Enable(TRUE) ; } Kolejny punkt rozdziału opisze te aspekty interfejsu użytkownika szczegółowo. Funkcje, jak powyższa możemy dodawać do aplikacji w taki sam sposób, jak miało to miejsce w przypadku funkcji obsługujących pozycje menu, czyli za pomocą CIassWizard. Trzeba jednak wybrać UPDATE_COMMAND_UI zamiast COMMAND, co miało miejsce w kroku 4. Praca z menu 309 Kiedy wywoływane są funkcje obsługi poleceń interfejsu użytkownika? Funkcje te wywoływane są pomiędzy kliknięciem nagłówka menu przez użytkownika a wyświetleniem samego menu. System uaktualniania opcji menu na żądanie przyczynia się do zwiększenia szybkości pracy aplikacji, jeśli atrybuty tych opcji wymagają uaktualniania wskutek zmian stanu aplikacji. Aktywowanie i dezaktywowanie pozycji menu Poszczególne pozycje menu mogą być aktywowane lub dezaktywowane poprzez użycie funkcji Enable ((obiektu CCmdUl. Jeśli przekazany jej zostanie parametr TRUE, pozycja jest aktywna, w przeciwnym wypadku jest nieaktywna i przybiera szarą barwę. Możemy regulować ten stan ustanawiając zmienną składową typu BOOL. Będzie ona osadzona w klasie i przekazywana poprzez poniższą linię funkcji Enable () obiektu CCmdUl, gdy nastąpi wywołanie funkcji obsługi interfejsu użytkownika: pCmdUl ->Enable (in_bMySubItemEnableStatus) ; Zależnie od stanu m_bMySubltemEnable3tatus, pozycja menu będzie aktywna lub nieaktywna. Umieszczanie i usuwanie znaczników Obok każdej pozycji menu, mogą być umieszczane znaczniki identyfikujące stan logiczny danej pozycji w kontekście aplikacji. Umieszczanie tych znaczników odbywać się może poprzez wywołanie funkcji SetCheck (), należącej do obiektu CCmdUl i przekazanie jej jako parametr liczby zero dla braku znacznika lub jeden, gdy znacznik ma zostać umieszczony. Aby zaimplementować możliwość przełączania przez użytkownika stanu pozycji menu, poprzez jej kuknięcie, możemy posłużyć się kodem zawartym na listingu 13.1. Listing 13.1. LST14_1.CPP - implementacja przełącznika znacznika dla pozycji menu 1 void CSdimenuDoc::OnMyheaderMysubone() 2 { 3 II** Przełączenie obecnego stanu 4 m_nToggleState = m_nToggleState==0 ? l : 0; O 5 } 6 7 void CSdimenuDoc::OnUpdateMyheaderMysubone (CCmdUl* pCmdUl) 8 ( 9 // ** Uaktywnienie pozycji menu 10 pCmdUI->Enable(TRUE) ; 11 12 // ** Ustalenie bieżącego stanu 13 pCmdUI->SetCheck(m_nToggleState) ; @ 14 } O W odpowiedzi na dokonanie przez użytkownika wyboru pozycji menu, następuje zmiana wartości m_nToggleState z O na l lub odwrotnie. © Wyświetlenie znacznika, jeśli wartość m_nToggleState wynosi l lub usunięcie znacznika w przypadku wartości 0. Na powyższym listingu w celu zaimplementowania przełącznika użyte zostały funkcje obsługi poleceń menu oraz poleceń interfejsu użytkownika. Funkcja obsługi polecenia menu OnMyheaderMysubone() zaimplementowana w liniach l do 5, ustanawia stan przełącznika poprzez wartość zmiennej m_nToggleState. Zmienna ta może zostać zadeklarowana w definicji klasy, jako prosta zmienna całkowitoliczbowa: int m_nToggleState ; W linii 4 użyty został operator warunkowy ? w celu sprawdzenia, czy obecna wartość zmiennej m_nToggleState wynosi zero. Jeśli tak, wartość ta zostaje zmieniona na l, w przeciwnym wypadku zaś, dzieje się odwrotnie. Funkcja obsługi interfejsu użytkownika zaimplementowana jest w liniach 7-14 poprzez funkcję OnUpdateMyheaderMysubone (). Linia 10 zapewnia stałą dostępność pozycji menu za pomocą wywołania funkcji Enable () obiektu ccmdui. Bieżący stan przełącznika zostaje uwidoczniony za pomocą ustawienia lub usunięcia znacznika obok pozycji menu przez wywołaną w linii 13 funkcję SetCheckO obiektu CCmdUl. Funkcji SetCheckO przekazana zostaje wartość zmiennej m_nToggleState, co decyduje o wyświetleniu lub usunięciu znacznika. Dynamiczna zmiana treści pozycji menu Funkcję polecenia interfejsu użytkownika możemy wykorzystać również do zmiany treści pozycji menu, przekazując funkcji SetText () obiektu ccmdui nowy łańcuch znakowy. Pozycja menu otrzyma wówczas nowe określenie, wraz z przypisanymi jej skrótami klawiaturowymi. Moglibyśmy w tym miejscu skorzystać z ostatniego przykładu, opisującego wstawianie znaczników i zastąpić je tekstem On i Off, w zależności od stanu przełącznika, a na końcu treści funkcji obsługi interfejsu użytkownika dodać następującą linię: Praca z menu 311 pCmdUl ->SetText(m_nToggleState?"&On":"0&ff") ; W powyższej linii operator warunkowy ? przekazuje jeden z dwóch łańcuchów, w zależności od wartości zmiennej m_nToggleState. Dodawanie menu skrótów Menu skrótów jest to menu wyświetlane w dowolnym miejscu ekranu (zwykle), gdy użytkownik kuknie prawym klawiszem myszy obiekt aplikacji. Pozwala to użytkownikowi wybrać z menu pozycję specyficzną dla danego obiektu aplikacji. Menu skrótów dodajemy do projektu jako nowy zasób. Powinniśmy to zrobić za po- średnictwem edytora zasobów w sposób opisany wcześniej, w tym samym rozdziale, w punkcie „Tworzenie i edycja zasobów menu". Komunikat WM CONTEXTMENU Komunikat ten generowany jest przez procedurę okna, gdy odebrany zostaje komunikat WM RBUTTONUP. Jeśli nastąpi przechwycenie WM_RBUTTONUP bez wywołania funkcji obsługi klasy bazowej, aplikacja nie będzie mogła odebrać komunikatu WM CONTEXTMENU. , : : Menu skrótów możemy zaimplementować w aplikacji dodając funkcję obsługi komu- nikatu Windows WM_CONTEXTMENU, który przesyłany jest aplikacji, gdy użytkownik klik-nie prawym klawiszem myszy gdziekolwiek w oknie aplikacji. Poprzez funkcję obsługi załadowany zostanie odpowiedni zasób menu i nastąpi rozwinięcie menu poprzez wywołanie funkcji TrackPopupMenu (). Funkcje obsługujące polecenia tego menu mogą być dodawane w znany nam już sposób. Uruchamianie menu skrótów Poniżej przedstawiona jest procedura dodania funkcji obsługi komunikatu WM_CONTEXTMENU. Dodanie funkcji obsługi menu skrótów 1. Otwórz panel ClassView w oknie Project Workspace i w razie konieczności rozwiń listę klas klikając znak plusa widoczny obok pozycji najwyższego poziomu. 2. Kliknij prawym klawiszem myszy pozycję klasy, do której chcesz dodać nową funkcję obsługi i wybierz z rozwiniętego menu opcję Add Windows Message Handler. Otwarte zostanie okno dialogowe New Windows Message and Event handlers. 3. Z listy New Windows Messages/Events wybierz WM_CONTEXTMENU, a następnie kliknij przycisk Add and Edit, co spowoduje dodanie nowej funkcji. 312_____________________________________Poznaj Visual C++ 6 4. Powinna być widoczna nowa wygenerowana przez CIassWizard funkcja, nazwana domyślnie OnContextMenu (). Teraz możesz dodać własny kod do implementacji domyślnej. Tworzenie dynamicznych menu skrótów Zamiast ładowania predefiniowanych menu z zasobów, można wyświetlić tekst związany z obiektem kliniętym przez użytkownika (a nie typem obiektu). W tym przypadku korzystniejszym rozwiązaniem może się okazać wywołanie funkcji Cre-atePopupMenu () klasy CMenu dla utworzenia menu, a następnie użycie AppendMenu w celu dodania pozycji do nowego menu. W ten sam sposób można użyć funkcji TrackPopupMenu () do wyświetlenia menu. Po dodaniu funkcji obsługi możemy dodać kod powodujący wyświetlanie menu skrótów. Możemy zadecydować, aby menu było otwierane tylko wtedy, gdy wskaźnik myszy znajdzie się nad określonym obiektem lub aby wyświetlało różne polecenia, w zależności od specyfiki wybranego obiektu. Osiągnąć to możemy dzięki parametrowi Cpoint point, określającemu, który z obiektów aplikacji lub jaki obszar okna został kliknięty, od czego z kolei zależeć będzie wyświetlenie menu skrótów lub jego zawartość. Techniki stosowane w takich sytuacjach opisane zostały w rozdziale 8. Aby menu skrótów mogło zostać wyświetlone, musimy najpierw zadeklarować obiekt CMenu, implementujący menu rozwijane (klasa CMenu jest klasą MFC, która może zawierać i posiadać dostęp do obiektu HMENU). Nowy obiekt CMenu zainicjować możemy wywołując funkcję LoadMenu () i przekazując jej identyfikator zasobu. W takiej sytuacji obiekt ten zostanie zainicjowany z nagłówkiem nowego zasobu menu. Potrzebujemy jeszcze pozycji menu, które będą wyświetlane po jego otwarciu. Pobranie zawartości menu (submenu) następuje poprzez wywołanie funkcji GetSubMenu () z przekazanym parametrem zero, który określa pierwszą pozycję menu. Z menu możemy wywoływać kolejną funkcję, TrackpopupMenu (), wyświetlającą menu i określającą jego położenie. Funkcja ta wymaga czterech parametrów. Pierwszym z nich jest znacznik służący do określania położenia menu w stosunku do zadanej pozycji. Jeśli wskaźnik myszy powinien znajdować się po lewej stronie rozwiniętego menu, możemy przekazać TPM_LEFTALIGN jako pierwszy parametr oraz pozycję wskaźnika myszy jako pozycję menu. Inne możliwe wartości znacznika wymienione są w tabeli 13.1. Drugi i trzeci parametr to pozioma i pionowa współrzędne dla określenia położenia menu. Jako te parametry mogą występować wartości point, x oraz point, y przekazywane funkcji OnContextMenu (). Ostatnim parametrem jest wskaźnik do obiektu okna, który zwykle jest operatorem this, wskazującym bieżący obiekt widoku. Praca z menu 313 Tabela 13.1. Wartości znacznika położenia menu dla funkcji TrackPopupMenu () Wartość TPM_LEFTALIGN TPM_RIGHTALIGN TPM_TOPALIGN TPM_BOTTOMALIGN TPM_CENTERALIGN TPM_VCENTERALIGN TPM_HORIZONTAL TPM VERTICAL Opis Umieszcza menu po lewej stronie wskazanej pozycji Umieszcza menu po prawej stronie wskazanej pozycji Umieszcza menu poniżej wskazanej pozycji Umieszcza menu powyżej wskazanej pozycji Wyśrodkowuje menu w osi poziomej, w stosunku do wskazanej pozycji Wyśrodkowuje menu w osi pionowej, w stosunku do wskazanej pozycji Jeśli obszar wyświetlania jest ograniczony, istotniejsza jest oś pozioma Jeśli obszar wyświetlania jest ograniczony, istotniejsza jest oś pozioma Po wywołaniu funkcji menu zostaje wyświetlone, a użytkownik może dokonywać wyboru poleceń. Gdy już wybór zostanie dokonany lub nastąpi kliknięcie poza obrębem menu, zostaje ono usunięte. Przykładowo, zaimplementujemy menu skrótów umożliwiające użytkownikowi dokonanie wyboru spośród pozycji North, East, South i West (Północ, Wschód, Południe, Zachód) z menu w aplikacji SDI, utworzonej za pomocą AppWizard. Pierwszą czynnością będzie dodanie zasobu dla menu rozwijanego (rysunek 13.6). MenuPioperties -(•l l? Resource ID: jlil.KMiUiEii »IP'eview W, PW-D Waf-u l 1'W SCT. ff Langysge: |English(U.S.) ^J CtOndition; | :-;^.::.::•::::.:^;v:- Rysunek 13.6. Umieszczanie nowego zasobu menu dla rozwijanego menu skrótów Po dodaniu zasobu dla nowego menu dodać należy funkcję obsługi komunikatu WM_CONTEXTMENU, wykonując procedurę opisaną wcześniej, w punkcie „Dodanie funkcji obsługi menu skrótów". Funkcję tę powinniśmy umieścić w klasie widoku aplikacji SDI, implementując kod służący załadowaniu i pozycjonowaniu menu. Kod ten znajduje się na listingu 13.2. 314_____________________________________Poznaj Visual C++ 6 Listing 13.2. LST14_2.CPP - funkcja OnContextMenu () obsługująca komunikat WM_CONTEXTMENU, ładująca i pozycjonująca rozwijane menu skrótów 1 void CSdimenuView::OnContextMenu(CWnd* pWnd, CPoint point) 2 { 3 // ** Zadeklarowanie obiektu CMenu 4 CMenu menuPopup; 5 6 // ** Inicjalizacja zasobu menu skrótów 7 menuPopup.LoadMenu(IDR_MYCONTEXT) ; 8 9 // ** Wyświetlenie i pozycjonowanie menu 10 menuPopup.GetSubMenu(0)->TrackPopupMenu( O 11 TPM_LEFTALIGN,point.x,point.y,this) ; 12 } O Linia wyświetla i rozpoczyna śledzenie wyboru menu przez użytkownika. W linii 4 listingu 13.2 zadeklarowany zostaje obiekt CMenu jako zmienna menupopup. Zmienna ta zainicjalizowana zostaje w linii 7 wraz z zawartością menu z zasobu IDR_MY- CONTEXT. W linii 10 następuje wywołanie funkcji TrackPopupMenu (), nakazującej wyświetlenie menu po lewej stronie punktu wyrównania oraz przekazanie jej położenia wskaźnika myszy, które przekazane zostało również funkcji obsługi menu skrótów. Przekazany zostaje także wskaźnik do bieżącego obiektu widoku CSdimenuView, przy użyciu słowa kluczowego this. Aby wyświetlone zostało właściwe menu, funkcja TrackPopupMenu () musi być wywołana z głównego zasobu menu, zawierającego owo menu. Wskaźnik do tego menu zwrócony zostaje z menu głównego przez funkcję menuPopup. GetSubMenu (0) wywołaną na początku linii 10. Okno dialogowe dodawania klas w CIassWizard Podczas dołączania nowego zasobu menu i następującego po tym wywołania CIassWizard, kreator wyświetla okna dialogowe Adding a Ciass wskutek wykrycia nowego zasobu. Ponieważ nie trzeba tworzyć ani wybierać klasy obsługującej, okno można zamknąć przyciskiem Cancel. • Więcej informacji o procedurze sprawdzającej kuknięcie w określonych obszarach znaj- dziesz w rozdziale 8. Obsługa poleceń menu skrótów Po dodaniu do projektu menu skrótów powinniśmy dodać funkcje obsługi jego poleceń, w taki sam sposób, jak robiliśmy to w przypadku normalnego menu (według opisu w punkcie „Dodawanie funkcji obsługi poleceń interfejsu użytkownika"). Możemy posłużyć się w tym celu kreatorem CIassWizard, dodając funkcje obsługi poleceń menu oraz interfejsu użytkownika, dla każdej pozycji menu. Oto przykład dla opcji North: void CSdimenuView::OnMypopupmenuNorth() { AfxMessageBox("North") ; ) Funkcja obsługi polecenia powodować będzie wyświetlanie słowa North w oknie komunikatu, co nastąpi w wyniku wybrania tej pozycji przez użytkownika. void CSdimenyView::OnUpdateMypopupmenuNorth(CCmdUl* pCmdUl) ( pCmdUl ->Enable(TRUE) ; > Funkcja obsługi interfejsu użytkownika powoduje aktywację pozycji menu poprzez wywołanie funkcji Enable () dla obiektu CmdUl przekazanego funkcji obsługi. Zmiany te możemy zastosować w standardowej aplikacji SDI, skompilować ją i uruchomić. Kliknięcie prawym klawiszem myszy w obrębie głównego widoku spowoduje wyświetlenie menu skrótów, jak widać na rysunku 13.7. a?» Uniltled - adimenu File E dii View Hełp MyHeadet Radio Menu • D "'WH'. ' ', r •igi "f •gH South West Rysunek 13.7. Rozwijane menu skrótów, widoczne po kliknięciu prawym klawiszem myszy fIMKt. IAIU.C « Więcej informacji na temat obsługi zdarzeń kuknięcia znajdziesz w rozdziale 8. Tworzenie i dostęp do obiektów menu Biblioteka MFC dostarcza nam klasę CMenu, opakowującą uchwyt menu Windows (HMENU) i upraszczającą operacje wykonywane na menu. Możemy z niej korzystać podczas konstruowania własnych menu (zamiast ich ładowania). Umożliwia ona również dynamiczne dodawanie i usuwanie pozycji z istniejącego menu. Klasa CMenu zawiera szereg funkcji składowych, zapewniając obsługę każdego aspektu wyświetlania i manipulacji. Kolejne punkty rozdziału opisywać będą kilka najważniejszych spośród tych funkcji. Uzyskiwanie dostępu do uchwytu menu Dostęp do uchwytu obiektu menu CMenu można zdobyć poprzez jego zmienną składową m_hMenu. Może to być użyteczne, gdy konieczne jest zastosowanie funkcji Win32 zamiast funkcji MFC. .'.,', : ,,: Inicjalizacja obiektu CMenu Zanim będziemy mogli skorzystać z zadeklarowanego obiektu CMenu, musimy go za- inicjalizować, ładując menu z zasobu menu, tworząc nowe menu lub dołączając obiekt menu do innego już istniejącego. Każda z funkcji inicjalizujących zwraca wartość TRUE, gdy operacja zakończona zostaje pomyślnie zainicjalizowaniem menu lub FALSE w wypadku niepowodzenia. • Ładowanie menu z szablonu zasobu Menu możemy załadować za pomocą funkcji LoadMenu (), z przekazanym jej identy- fikatorem żądanego menu, co było zademonstrowane na listingu 13.2. Szablon menu zostanie wówczas załadowany z zasobu i użyty do utworzenia pozycji menu. • Tworzenie nowych menu Funkcja CreateMenu () dynamicznie tworzy pusty zasób menu Windows, bez żadnych zawartych w nim pozycji. Do tego menu możemy dynamicznie dodawać nowe pozycje, co opisane jest w następnym punkcie rozdziału. Wywołanie funkcji CreatePopupMenu () pozwala zaś utworzyć menu rozwijane, które może być otwierane z pozycji innego menu lub jako rozwijane menu skrótów. • Dołączanie do istniejącego menu Za pomocą funkcji Attach () możemy dołączać istniejące menu Windows do obiektu CMenu, przekazując funkcji uchwyt HMENU istniejącego menu. Uchwyt ten można po- Praca z menu_________________________________________317 brać z CWnd (lub z wyprowadzonej CView) poprzez wywołanie funkcji obiektu okna GetMenu (). Po zakończeniu tych operacji, możemy odłączyć menu od obiektu CMenu, wywołując funkcję Detach (). Po zainicjalizowaniu obiektu CMenu możemy dodawać, usuwać lub modyfikować pozycje menu, by dostosować je do potrzeb aplikacji. Jest to opisane w następnych podrozdziałach. Dynamiczne dodawanie pozycji menu Nowe pozycje mogą być umieszczane w menu poprzez wywołanie funkcji AppendMe-nu () lub insertMenu (), w zależności od tego, czy nowa pozycja ma się znaleźć na końcu, czy też w określonym miejscu menu pomiędzy innymi jego pozycjami. Funkcja AppendMenu () wymaga trzech parametrów. Pierwszym jest znacznik określający typ nowej pozycji kombinacji znaczników stylu. Normalnie powinniśmy wpisać znacznik MF_STRING, co oznaczałoby, iż nowa pozycja menu przechowuje wskaźnik do zwykłego łańcucha znakowego (możemy użyć obiektów cstring). Możemy jednak utworzyć separator przekazując MF_SEPARATOR. Można wówczas łączyć podstawowe znaczniki typu z innymi znacznikami, wymienionymi w tabeli 13.2, korzystając z operatora dodawania (+) lub logicznego OR (l). Drugim parametrem, jaki możemy przekazać, jest identyfikator komunikatów poleceń lub uchwytu menu rozwijanego HMENU, o ile został przekazany znacznik MF_POPUP. Jako trzeci parametr występuje wskaźnik łańcucha znakowego, stanowiącego treść opisu pozycji menu (pozostawiamy domyślne NULL dla pozycji separatorów). Sprawdzanie odrębności identyfikatora menu Zawsze należy sprawdzać, czy identyfikator menu jest niepowtarzalny i nie powoduje konfliktu z innym identyfikatorem zasobu menu. Można to sprawdzić otwierając plik resource.h (na karcie FileYiew) i odnajdując nowy identyfikator. Jeśli go tam nie ma i nie jest powtórzony w innym menu dynamicznym, można go bezpiecznie zastosować. Tabela 13.2. Znaczniki pozycji menu dla funkcji AppendMenu () oraz InsertMenu () Znacznik Opis MF_CHECKED Ustawia znacznik („ptaszek") obok pozycji menu MF_UNCHECKED Usuwa znacznik pozycji menu MF_ENABLED Uaktywnia pozycj ę menu 318 Poznaj Visual C++6 MF_DISABLED Dezaktywuje pozycję menu MF_GRAYED Działa identycznie jak MF_DISABLED MF_POPUP Pozycja menu posiada własne podmenu rozwijane, określone identyfikatorem MF_MENUBARBREAK Pozycja menu umieszczona zostaje w nowej linii (lub w nowej kolumnie menu rozwijanego, oddzielonej pionową linią) MF_MENUBREAK Działa podobnie do MF_MENUBARBREAK, lecz bez wstawiania pionowej linii Funkcja insertMenuO pozwala na umieszczanie nowych pozycji menu w określonym miejscu lub przed pozycją o wyznaczonym identyfikatorze. Jeśli chcemy określić pozycję poprzez podanie indeksu, należy przekazać indeks, w porządku zaczynającym się od zera, jako pierwszy parametr i dołączyć znacznik MF_BYPOSITION do znaczników umieszczonych wcześniej. Aby umieścić nową pozycję menu przed inną istniejącą o określonym identyfikatorze, należy podać ten identyfikator jako pierwszy parametr i dołączyć znacznik MF_BYCOMMAND. Pozostałe trzy parametry są identyczne z użytymi w funkcji AppendMenu (). Przykłado- wo, zamiast ładować menu skrótów (jak na listingu 13.2 w poprzednim punkcie), możemy utworzyć menu rozwijane i dołączyć do niego nowe pozycje, co pokazane jest na listingu 13.3. Technika ta daje nam większą kontrolę nad dołączaną pozycją oraz umożliwia dynamiczne dodawanie innych pozycji, specyficznych dla danej aplikacji. Listing 13.3 pokazuje również inne sposoby dodawania pozycji menu, a także efekty łączenia znaczników. Listing 13.3. LST14_3.CPP - dynamiczne tworzenie i wyświetlanie rozwijanego menu skrótów 1 łdefine ID_MENU_RED 5001 O 2 łdefine ID_MENU_GREEN 5002 O 3 łdefine ID_MENU_BLUE 5003 O 4 łdefine ID_MENU_YELLOW 5004 O 5 6 void CSdimenuView::OnContextMenu(CWnd* pWnd, CPoint point) 7 { 8 // Deklaracja obiektu CMenu 9 CMenu menuPopup; 10 11 // Dodanie pozycji do menu 12 if (menuPopup.CreatePopupMenu() )@ 13 { 14 // Dodanie prostych pozycji menu 15 menuPopup.AppendMenu(MF_STRING,ID_MENU_RED,"SRed"); 16 17 // Dodanie pozycji na początku menu 18 menuPopup.InsertMenu(O, MF_BYPOSITION | MF_STRING, © 19 ID_MENU_GREEN,"SGreen"); 20 21 22 menuPopup.AppendMenu(MF_SEPARATOR) ; 23 24 // Dodanie pozycji oznaczonej 25 menuPopup.AppendMenu(MF_STRING | MF_CHECKED, 26 ID_MENU_BLUE,"SBlue") ; 27 28 // MF_MENUBARBREAK 29 menuPopup.AppendMenu(MF_STRING | MF_MENUBARBREAK, O 30 ID_MENU_YELLOW,"SYellow") ; 31 menuPopup.TrackPopupMenu(TPM_LEFTALIGN, 32 point.x,point.y,this); 33 } 34 } O Zdefiniowane zostają odrębne identyfikatory. @ Jeśli menu rozwijane może zostać utworzone, bezpiecznie można dodać do niego nowe pozycje. © Należy zauważyć, że pozycja ta umieszczona zostaje z indeksem O (ponad dotychczasową pierwszą pozycją menu). O Znacznik w tym miejscu powoduje umieszczenie pionowej linii, dzielącej menu rozwijane na dwie kolumny. Linie od l do 4 definiują wartości identyfikatorów dla czterech dodawanych do menu pozycji. W linii 9 następuje zadeklarowanie obiektu CMenu jako menuPopup, natomiast jego inicjalizacja jako menu ma miejsce w linii 12, poprzez wywołanie funkcji CreatePo-pupMenu(). W liniil5 za pomocą funkcji AppendMenu () dodana zostaje nowa pozycja menu. Red. Kolejna pozycja, Green, umieszczona zostaje przed pozycją Red, poprzez funkcję in-sertMenu () wywołaną w linii 18. W linii 22 za pomocą dodania pozycji menu ze znacznikiem MF_SEPARATOR, do menu dodany zostaje separator. Pozycja Blue dodana zostaje jako oznaczona, co ma miejsce w liniach 25 i 26, natomiast w liniach 29 i 30 pozycja Yellow umieszczona zostaje po prawej stronie pionowego separatora. Na koniec, wywołana w linii 31 funkcja TrackPopupMenu () wyświetla nowe menu. 320 Poznaj Visual C++ 6 Funkcje obsługi dla poszczególnych pozycji dynamicznego menu możemy uzyskać dodając odpowiednie wpisy do mapy komunikatów na końcu mapy komunikatów widoku, dla określonych identyfikatorów: ON_COMMAND(ID_MENU_YELLOW,OnYellow) Musimy mieć przy tym pewność, że linie łdefine dla tych identyfikatorów znajdują się w kodzie powyżej mapy komunikatów, by kompilator mógł je odnaleźć. Funkcja obsługi OnYeliowf) będzie wywoływana, gdy użytkownik dokona wyboru pozycji menu o identyfikatorze ID_MENU_YELLOW. Implementacji tej funkcji obsługi doko- nujemy w zwyczajny sposób: void CSdimenuView::OnYellow() { AfxMessageBox("Yellow") ; } Po uruchomieniu skompilowanej aplikacji nasze rozwijane menu skrótów będzie wy- glądało jak na rysunku 13.8. rV Undtled - sdimenu Fie Edit View Hełp MyHeader Radio Menu rD^Hn^iij^'^''?7"""""' Green | ^ellow •/ Blue Rysunek 13.8. Dynamicznie utworzone menu rozwijane, z kilkoma włączonymi opcjami Dynamiczne modyfikowanie pozycji menu Możemy również modyfikować pozycje menu podczas ich wyświetlania poprzez wy- wołanie funkcji ModifyMenu (). Funkcja ta wymaga czterech parametrów. Pierwszy parametr funkcjonuje jak parametr funkcji insertMenu () określający pozycję. Jeżeli chcemy określić pozycję poprzez jej indeks, powinniśmy podać indeks w relacji do pierwszej pozycji oznaczonej jako zero oraz dołączyć znacznik MF_BYPOSITION do innych przekazanych znaczników. Możemy także modyfikować określoną pozycję przekazując jej identyfikator jako pierwszy parametr. Musi to być połączone z dodaniem znacznika MF BYCOMMAND. Praca z menu 321 Drugim parametrem jest zestaw nowych znaczników dla wybranej pozycji menu, zmieniających dotychczasowe ustawienia. Użyć przy tym możemy znaczników omówionych w tabeli 13.2. Opcjonalnie możemy jako trzeci parametr przekazać nowy identyfikator menu (lub obecny, co pozostawi go niezmienionym), a jako czwarty parametr wskaźnik łańcucha znakowego, stanowiącego nową treść opisu pozycji menu. Przykładowo, aby oznaczyć pozycję menu połączoną z identyfikatorem ID_MENU_ YELLOW (opisanym w poprzednim punkcie), umieścić należy poniższy wpis za linią dodającą nową pozycję menu: menuPopup.ModifyMenu(ID_MENU_YELLOW, MF_BYCOMMAND | MF_CHECKED | MF_STRING | MF_MENUBREAK , ID_MENU_YELLOW, "SYellow") ; Bardzo prosto zmieniać możemy atrybuty pozycji, takie jak oznaczenie jej lub stan aktywności, poprzez wywołanie funkcji CheckMenultem() lub EnableMenultemO. Funkcje te pobierają dwa parametry. Pierwszy określa poprzez identyfikator lub indeks pozycję menu podlegającą zmianie. Drugi parametr jest znacznikiem MF_BYCOMMAND lub MF_BYPOSITION, co determinuje sposób identyfikacji pozycji. Funkcji CheckMenultem() przekazywać możemy znacznik MF_CHECKED lub MFJJNCHECKED, by oznaczyć lub usunąć oznaczenie pozycji. Funkcja EnableMenultemO przyjmuje znaczniki MF_ENABLED lub MF_DISABLED, które powodują uaktywnianie bądź dezaktywację pozycji menu. Dla przykładu, możemy uprościć stosowanie ModifyMenuO dla oznaczenia pozycji menu, korzystając z funkcji CheckMenultem (): menuPopup.CheckMenuItem(ID_MENU_YELLOW, MF_BYCOMMAND i MF_CHECKED) ; Dynamiczne usuwanie pozycji menu Usuwanie pozycji menu z obiektu CMenu odbywa się poprzez wywołanie funkcji Re-moveltem(), pobierającej dwa parametry. Pierwszym jest identyfikator lub indeks pozycji mającej ulec usunięciu. Drugim natomiast jest znacznik MF_BYCOMMAND lub MF_BYPOSITION, identyfikujący typ wartości przekazanej jako parametr pierwszy. Rozdział 14 Praca z paskami narzędzi i stanu Tworzenie, konfigurowanie i stosowanie pasków narzędziowych Stosowanie pasków dialogowych_________________ Konfigurowanie paska stanu i wyświetlanie sygnalizatorów zwią-zanych z aplikacją___________________________ Nowe paski kontenerowe w stylu Intemet Explorer 4 Dostosowywanie standardowych pasków narzędziowych Większość aplikacji używa pasków narzędziowych dla ułatwienia użytkownikowi wydawania często używanych poleceń. Ich przyciski pełnią zazwyczaj rolę skrótów do określonych pozycji menu i prezentują graficzny związek z poleceniem. Na przykład, jeśli użytkownik chce wykonać wydruk dokumentu, przycisk z ikoną drukarki będzie właściwym wyborem. Zmiana standardowego paska narzędziowego szkieletu aplikacji Standardowy pasek narzędziowy jest dołączany do aplikacji przez AppWizard. Istnieje pełna swoboda uzupełniania, usuwania i modyfikowania jego przycisków, zgodnie ze specyfiką aplikacji. Możliwe jest również całkowite usunięcie paska, jeśli jest zbędny. Jeśli utworzymy projekt typu SDI lub MDI, akceptując domyślne ustawienia dotyczące pasków narzędziowych w kroku 4 kreatora AppWizard, jak widać na rysunku 14.1, do szkieletu aplikacji dodany zostanie normalny, dokowany pasek narzędziowy. 324 Poznaj Visual C++ 6 MFC AppWizard - Step 4 Ol 6 F JDock^Jooibal F Inilial status bat ^ &inling and pfint prew^i r" Coritent- ®ensitive Hełp f7 3D controls r MAPI(MessagingAPI) r Windows Sockets How do you want youi toolbare to look? (T Norrol <^ Intefnet E^piofer ReBar? How many liles would you like on your tecent file lisi? pi::3 6dvanced... | Nexl> Finish Cancel Rysunek 14.1. W kroku 4 AppWizard włącza do projektu dokowany pasek narzędziowy AppWizard generuje kod tworzący okno paska narzędziowego, załadowujący ikony jego przycisków i wiążący go z głównym oknem aplikacji. Pasek narzędziowy generowany przez AppWizard zawiera przyciski odpowiadające pozycjom menu File, Edit oraz Hełp, co widać na rysunku 14.2. PATRZ TAKŻE ł Więcej informacji na temat tworzenia aplikacji SDI znajdziesz w rozdziale 12. ł Aby utworzyć aplikację MDI, przejdź do rozdziału 21. A-UntiIIed - Toolbar File Edi( yiew Hełp j"D"ci H' 7 E Rysunek 14.2. Standardowy pasek narzędziowy, generowany przez AppWizard Istota paska narzędziowego Szkielet aplikacji domyślnie implementuje pasek narzędziowy poprzez dodanie obiektu m_wndToolbar klasy Ctoolbar, do definicji klasy CMainFrame poprzez linię: Ctoolbar m wndToolbar ; Praca z paskami narzędzi i stanu 325 Klasa CToolbar jest klasą pochodną od CControiBar, która z kolei wyprowadzana jest z CWnd. Klasa CControiBar rozszerza możliwości CWnd o wsparcie dokowania okien w oknie głównym lub umożliwienia im swobodnego pływania. Paski narzędziowe, paski stanu oraz dialogowe rozszerzają klasę CControiBar na kilka sposobów, by wspomóc wsparcie okien dokowanych dla każdego typu pasków sterujących. Klasa CToolbar dodaje natomiast wsparcie dla bitmap i tablic przycisków w celu właściwego wyświetlenia użytkownikowi paska, takiego jak standardowy, widoczny na rysunku 14.2. PATRZ TAKŻE * Szczegółów dotyczących klasy CMainFrame szukaj w rozdziale 12. Tworzenie standardowego paska narzędziowego Przypomnijmy sobie, iż klasa CMainFrame odpowiedzialna jest za obsługę okna głównego aplikacji SDI oraz MDI. Gdy utworzone zostaje okno główne, wywoływana jest funkcja składowa OnCreate () klasy CMainFrame. Generowany przez AppWizard kod używa tej funkcji do utworzenia paska narzędziowego jako okna potomnego i załadowania zasobu paska narzędziowego (IDR_MAINFRAME), co czyni poprzez następujące linie: if (!m_wndToolbar.CreateEx(this, TBSYTLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP ] CBRS_GRIPPER | CBRSJTOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) l| m_wndToolbar.LoadToolBar(IDR_MAINFRAME)) { TRACEO("Failed to create toolbar\n") ; return -l ; ) Kod ten znajdziemy w module implementacyjnym MainFrame.cpp. Operacje utworzenia okna i załadowania zasobu połączone są wyrażeniem warunkowym i f, w związku z czym funkcja okna głównego OnCreate () zwraca wartość -l w wypadku niepowodzenia którejś z tych operacji. Tworzenie pasków narzędziowych w stylu Intemet Explorer 4 za pomocą funkcji CreateEx() Funkcja CreateEx() umożliwia nadawanie paskom narzędziowym nowych znaczników stylu. W celu uzyskania stylu Internę! Explorer 4 należy nadać dwCtrIStyle wartość TBSTYLE FLAT | TBSTYLE TRANSPARENT. Funkcja CreateEx () użyta jest do utworzenia okna paska narzędziowego. Ta funkcja składowa jest nową funkcją implementacji pasków narzędziowych w Visual C++ 6.0 i stosuje się ją do ustalania obramowań płaskich przycisków paska. Pierwszym przekazy- 326_____________________________________Poznaj Visual C++ 6 wanym jej parametrem jest wskaźnik okna przodka; przekazując tu słowo kluczowe C++, this powodujemy, iż wywołujący funkcję obiekt okna głównego określony zostaje jako rodzic. Implementacja wygenerowana przez AppWizard przekazuje jako drugi parametr znacznik TBSTYLE_FLAT, definiujący styl paska. Możemy zmienić ten parametr, nadając mu wartość O, co spowoduje nadanie przyciskom paska „wypukłości". Znaczniki stylu przekazywane jako trzeci parametr możemy modyfikować korzystając z ich opisów zawartych w tabeli 14.1. Musimy jedynie pamiętać o dołączeniu znaczników WS_CHILD oraz WS_VISIBLE. Tabela 14.1. Znaczniki stylu pasków narzędziowych Wartość znacznika Opis CBRS_TOP Dokuje pasek narzędziowy u góry okna głównego i umieszcza pod nim obramowanie CBRS_BOTTOM Dokuje pasek narzędziowy u dołu okna głównego i umieszcza ponad nim obramowanie CBRS_LEFT Dokuje pasek narzędziowy po lewej stronie okna głównego i dodaje obramowanie z prawej strony CBRS_RIGHT Dokuje pasek narzędziowy po prawej stronie okna głównego i dodaje obramowanie z lewej strony CBRS_ALIGN_ANY Dokuje pasek narzędziowy gdziekolwiek CBRS_FLOAT_MULTI Umożliwia otwarcie kilku pasków sterujących w jednej miniramce CBRS_TOOLT I PS Wyświetla podpowiedzi („dymki") dla ikon paska narzędziowego CBRS_FLYBY Odświeża treść komunikatu, gdy zmieniona została treść podpowiedzi CBRS_GRIPPER Dodaje uchwyt, umożliwiający przemieszczanie paska CBRS_SIZE_DYNAMIC Zezwala użytkownikowi na zmianę rozmiarów paska CBRS_S I ZE_FIXED Uniemożliwia zmianę rozmiarów paska przez użytkownika Gdy pasek narzędziowy jest już utworzony, następna część instrukcji warunkowej za- ładowuje zasób menu poprzez wywołanie funkcji składowej LoadToolbar (), posługując się identyfikatorem paska, który ma zostać załadowany. Zasób ów przechowuje ikony bitmapowe dla przycisków paska, a znaleźć go możemy w panelu ResourceView, pod pozycją Toolbar (patrz rysunek 14.3). Praca z paskami narzędzi i stanu 327 ^-* fie L(» ^i»w IWMI Piosci guitó looti ^i-idow Hei ^ :(^BS iJt ^^i -.;• ' • : - •COisi r ^Iffl- S-^ICBBSJionnM ^ „ 1:•^:JI: ' E1 :i -'——...„^ ^l"--:-—— ... ..dl<^—.. „„„.,.,, . ,, ^••\- .. ........„.„....^....„„..„.^^^ig^ I^MaBal^iBhaitislflsfl r--. -' S;.CJAcctłti*tói : ^0 Dialog : S-"Jlcon : iS LJMenu : Sl CasahgTabte : S-^ Toolbal : : ^'^BSBSESBEE^ : a: ClYettioo | | l ssagi^^B^al D ^l^^11'^^ 0 Q •s'1: 0 0 as- Rysunek 14.3. Domyślny pasek narzędziowy aplikacji SDI/MDI otwarty w edytorze zasobów Kiedy załadowanie zasobu przez funkcję LoadToolbarO przebiegnie bez zakłóceń, zwróci ona wartość TRUE, po czym funkcja okna głównego OnCreate () może kontynuować pracę. PATRZ TAKŻE • Więcej informacji na temat edytora zasobów znajdziesz w rozdziale 2. Dokowanie standardowego paska narzędziowego Po utworzeniu domyślnego paska stanu funkcja OnCreate () przystępuje do dokowania standardowego paska narzędziowego. Paski tego rodzaju mogą pływać, jak normalne okna lub być dokowane do ramki okna głównego. Dokowanie oznacza połączenie paska z ramką okna głównego, w związku z czym pasek nie posiada własnej ramki. Pierwszą linię służącą wykonaniu tego zadania ujrzymy w implementacji funkcji okna głównego OnCreate (), jak poniżej: m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY) ; Powyższa linia umożliwia dokowanie paska przy dowolnej krawędzi okna głównego. Gdybyśmy chcieli, aby miał on postać okna pływającego (tak jak paleta Controls), powinniśmy tę linię poprzedzić znakiem komentarza //. Oprócz tego, możemy ograniczyć możliwość dokowania paska tylko do określonych krawędzi, łącząc znaczniki wymienione w tabeli 14.2. Łączenie tych znaczników odbywa się za pomocą operatora logicznego OR (|). Na przykład, aby zezwolić paskowi narzędziowemu na dokowanie tylko do lewej lub prawej krawędzi okna głównego, powinniśmy napisać poniższą linię kodu: m_wndToolBar.EnableDocking(CBRS ALIGN_LEFT | CBRS_ALIGN RIGHT); 328_____________________________________Poznaj Visual C++ 6 Tabela 14.2. Znaczniki dokowania paska narzędziowego Wartość znacznika Opis CBRS_ALIGN_ANY Umożliwia dokowanie na każdej z krawędzi okna głównego CBRS_ALIGN_TOP Umożliwia dokowanie jedynie na górnej krawędzi okna głównego CBRS_ALIGN_BOTTOM Umożliwia dokowanie jedynie na dolnej krawędzi okna głównego CBRS_ALIGN_LEFT Umożliwia dokowanie jedynie na lewej krawędzi okna głównego CBRS_ALIGN_RIGHT Umożliwia dokowanie jedynie na prawej krawędzi okna głównego Zauważmy, iż w standardowej implementacji OnCreate () wywołana zostaje funkcja składowa okna głównego EnableDocking (): EnableDocking(CBRS_ALIGN_ANY) ; Linia ta zezwala wszystkim krawędziom okna głównego na dokowanie paska sterującego, jako strona dekująca. Możemy się tu posłużyć znacznikami, wymienionymi w tabeli 14.2, by zezwolić określonym krawędziom okna głównego na dokowanie pasków. W ostatniej linii standardowej implementacji OnCreate () wywołana zostaje funkcja składowa okna głównego DockControlBarO, służąca do początkowego zadekowania paska sterującego w oknie głównym. Linia ta ma postać: DockControlBar(&m wndToolBar) ; Funkcji tej możemy przekazać trzy parametry. Pierwszym z nich jest wskaźnik paska sterującego, który ma zostać zadekowany. Drugi parametr determinuje miejsce dokowania paska, do czego służą znaczniki wymienione w tabeli 14.3. Przekazaną wartością może być również zero, co oznacza, iż pasek może być dokowany do każdej krawędzi okna (jest to wartość domyślna). Trzeci parametr umożliwia określenie dokładnej pozycji dokowania poprzez przekazanie wskaźnika do struktury RECT, przechowującej współrzędne ekranowe pozycji dokowania w oknie głównym. Tabela 14.3. Wartości znacznika określającego w funkcji DockControlBar () stronę dokują-cą pasek narzędziowy Wartość znacznika Opis AFX_I DW_DOCKBAR_TOP Dokuje pasek sterujący u góry okna głównego AFX_I DW_DOCKBAR_BOTTOM Dokuje pasek sterujący u dołu okna głównego AFX_IDW_DOCKBAR_LEFT Dokuje pasek sterujący po lewej stronie okna głównego AFX_IDW_DOCKBAR_RIGHT Dokuje pasek sterujący po prawej stronie okna głównego Praca z paskami narzędzi i stanu 329 Zamiast dokowania paska sterującego, możemy określić go jako pływający, zastępując wywołanie funkcji DockControlBarO wywołaniem funkcji składowej okna głównego FloatControlBar (). Funkcja ta również wymaga podania trzech parametrów (dwa obowiązkowe i jeden opcjonalny). Pierwszy jest wskaźnikiem obiektu paska sterującego. Jako drugi parametr możemy wskazać obiekt cpoint, który określi współrzędne lewego górnego rogu paska w odniesieniu do koordynat ekranowych. Opcjonalny trzeci parametr określa orientację paska sterującego. Przekazanie CBRS_ALIGN_TOP jako wartość trzeciego parametru powoduje pionową orientację paska (jest to domyślna wartość) poziomą natomiast wywołuje wartość CBRS_ALIGN_LEFT. Przykładowo, umieszczenie pływającego paska sterującego w punkcie o współrzędnych (50, 50), wymaga wpisania następującej linii kodu zastępującego wywołanie funkcji DockControlBar () w funkcji okna głównego OnCreate (): FloatControlBar(&m wndToolBar,CPoint (50,50)) ; Niezależne klasy rozszerzeń pasków narzędziowych Twórcy oprogramowania tworzą własne klasy uzupełniające MFC i rozszerzające funkcjonalność pasków narzędziowych, przez co umożliwiają odmienne sposoby ich dokowania oraz konfigurowania. Przykładowo, nie ma możliwości przeciągania przycisków w normalnej bibliotece MFC. Jednak cecha ta bywa dostępna dzięki niezależnym programistom. Po wywołaniu funkcji DockControlBarO lub FloatControlBar (), wyświetlony zostaje pasek narzędziowy i inicjalizowanie aplikacji przebiega dalej. Umieszczanie nowych przycisków na pasku narzędziowym za pomocą edytora zasobów W oparciu o edytor zasobów można umieszczać w standardowym pasku narzędziowym IDR_MAINFRAME nowe przyciski, wykonując podaną niżej procedurę. Dodawanie przycisków do paska narzędziowego 1. Otwórz panel ResourceYiew w oknie Project Workspace. 2. Rozwiń drzewo zasobów oraz gałąź Toolbar, a następnie kliknij dwukrotnie pozycję paska, który chcesz edytować. 3. Pasek ten powinien zostać wyświetlony w oknie edytora zasobów, co zilustrowane jest rysunkiem 14.3. 4. Aby dodać nowy przycisk, wybierz pusty, który położony jest po prawej stronie paska, a następnie rozpocznij edycję bitmapy. 330 Poznaj Visual C++ 6 5. Podczas edycji zauważysz, iż po prawej stronie edytowanego przycisku pojawia się kolejny, pusty. W celu dodania kilku nowych przycisków, powtórz instrukcję 4. 6. Dla każdego nowego przycisku możesz ustalić identyfikator oraz tekst objaśniający. Można to zrobić poprzez dokonanie wpisów w oknie dialogowym właściwości przycisku, Toolbar Button Properties (rysunek 14.4), które wywołuje się naciskając Alt+Enter lub wybierając pozycję Properties z menu View. Rysunek 14.4. Ustalanie właściwości przycisku 7. Możesz ustalić identyfikator polecenia wpisując go w pole listy ID. Możliwe jest również połączenie przycisku z innym identyfikatorem polecenia innego obiektu (takim jak identyfikator polecenia menu) poprzez wybór odpowiedniej pozycji z listy kombinowanej ID. 8. Tekst pojawiający się w pasku stanu wpisz w polu edycji Prompt. Podpowiedz, która będzie wyświetlana pod postacią „dymku", dopisuje się do poprzedniego łańcucha, po znaku końca linii \n. 9. Nowy przycisk będzie wyświetlony na pasku narzędziowym po skompilowaniu i uruchomieniu programu. Jednakże będzie on niedostępny (zaciemniony), dopóki nie powstanie funkcji dla jego obsługi. Funkcję taką możesz utworzyć w taki sam sposób, jak dla poleceń menu, wykonując algorytm z podrozdziału „Dodawanie funkcji obsługi poleceń menu", który znajdziesz w rozdziale 13. Praca z paskami narzędzi i stanu 331 Przypisywanie akceleratorów do przycisków paska narzędziowego Skrót klawiaturowy dla przycisku można utworzyć wpisując nową pozycję tablicy akceleratorów w edytorze zasobów, kojarząc identyfikator przycisku ze skrótem klawiaturowym. Na przykład, szkielet aplikacji domyślnie łączy kombinację Ctri+N z identyfikatorem ID_FILE_NEW (identyfikator pierwszego przycisku standardowego paska narzędziowego). Gdy dodaliśmy już nowy przycisk oraz funkcję jego obsługi, możemy dopisać własny kod, który będzie wywoływany w momencie naciśnięcia przycisku. Funkcja obsługi może być funkcją współużytkowaną z odpowiadającym przyciskowi poleceniem menu, co jest często stosowane. Przycisk stanowi wówczas skrót dla określonego polecenia. Przykładowo, prosta funkcja obsługi paska narzędziowego, wyświetlająca okno komunikatu, po kuknięciu odpowiadającego jej przycisku, może wyglądać następująco: void CToolBarView::OnCreature() ( AfxMessageBox("Creature") ; } PATRZ TAKŻE • Szczegóły dotyczące edycji bitmap znajdziesz w rozdziale 11. 4 Więcej szczegółów dotyczących tablic akceleratorów możesz znaleźć w rozdziale 2. Przemieszczanie i usuwanie przycisków oraz dodawanie separatorów Przesuwanie przycisków wykonuje się poprzez zaznaczenie ich i przeciągnięcie na nową pozycję. Separatory pojawiają się, gdy przycisk zostanie przesunięty w lewo lub w prawo jedynie odrobinę. Przycisk może zostać usunięty, kiedy go zaznaczymy i przeciągniemy w dół, poza obszar paska narzędziowego, po czym zwolnimy klawisz myszy. Aktywacja i dezaktywacja przycisków paska narzędziowego Wpływ na stan aktywności przycisku możemy uzyskać dodając funkcję obsługi interfejsu użytkownika. Funkcja interfejsu użytkownika dla przycisku funkcjonuje w taki sam sposób jak podobna funkcja obsługi dla polecenia menu. W tym przypadku mamy jednak do dyspozycji kilka funkcji ccmdul, jak na przykład setText (). 332 Poznaj Visual C++ 6 Dlaczego przyciski paska narzędziowego są czasem niedostępne? Konstrukcja MFC przewiduje automatyczną dezaktywację przycisku, jeśli nie posiada on korespondującej z nim funkcji obsługi COMMAND lub UPDATE_COMMAND_UI. W celu sprawowania kontroli nad stanem aktywności przycisku możemy przekazać kontrolną zmienną składową funkcji Enablef) obiektu ccmdul, wskazanego korespondującej funkcji obsługi interfejsu użytkownika poprzez następujący kod: void CToolBarView::OnUpdateCreature(CCmdUl* pCmdUl) ( pCmdUl - >Enable(m_bEnableCreature) ; } Efekt wciśnięcia i wyciśnięcia przycisku możemy kontrolować przekazując funkcji SetRadiot) obiektu ccmdul wartość TRUE lub FALSE. Wartość TRUE powoduje wciśnięcie przycisku, FALSE natomiast jego zwolnienie. PATRZ TAKŻE * Informacji na temat funkcji obsługi interfejsu użytkownika szukaj w rozdziale 13. Dodawanie własnych pasków narzędziowych W przypadku mniejszych aplikacji modyfikowanie paska narzędziowego generowanego przez AppWizard może być pozbawione sensu. Jednak gdy tworzymy aplikacje większe, koniecznością może się okazać umieszczenie w niej dodatkowych pasków narzędziowych, ułatwiających korzystanie z dobrodziejstw z wielu funkcji. Możemy pokusić się również o umożliwienie użytkownikowi niezależnego wyświetlania i ukrywania tych pasków, w dogodnym dla niego momencie. Bitmapy dla przycisków tworzymy w edytorze, tworząc najpierw zasób nowego paska. Zasób ten będzie ładowany do nowego obiektu CToolbar osadzonego w oknie głównym. Dodawanie nowego zasobu paska narzędziowego 1. Otwórz panel ResourceView, wyświetlający zasoby projektu. 2. Kliknij prawym klawiszem myszy pozycję najwyższego poziomu i wybierz z rozwiniętego menu pozycję Insert. Otwarte zostanie okno dialogowe Insert Resource. 3. Z listy typów wybierz pozycję Toolbar. 4. Kliknij przycisk New, by umieścić w projekcie nowy zasób paska narzędziowego. 5. W gałęzi Toolbar powinieneś ujrzeć nowa pozycję i czysty pasek narzędziowy w oknie edytora. Praca z paskami narzędzi i stanu 333 6. Kliknij prawym klawiszem pasek narzędziowy i wybierz pozycję Properties, by wyświetlić okno dialogowe właściwości. 7. Możesz teraz zmienić domyślny identyfikator IDD_ na bardziej odpowiedni dla twojego okna dialogowego. 8. Zmień rozmiary pustego przycisku widocznego na pasku (jeśli zachodzi taka potrzeba), co wpłynie na rozmiary wszystkich przycisków całego paska. 9. Możesz już przystąpić do edycji pustego przycisku i dodawać kolejne przyciski, co było opisane w punkcie „Dodawanie przycisków do paska narzędziowego", wcześnie w tym samym rozdziale. Umieszczanie paska narzędziowego w oknie głównym Po utworzeniu zasobu paska musimy dodać obiekt CToolbar w celu włączenia nowego paska narzędziowego do klasy okna głównego (CMainFrame). Deklarację nowego paska możemy umieścić w definicji klasy CMainFrame (w module MainFrm.h), za wpisami dotyczącymi innych pasków. Przykładowo, po umieszczeniu takiej deklaracji dla paska nazwanego m_wndcolor- SwipeBar, część deklaracji klasy CMainFrame będzie wyglądała następująco: protected: CStatusBar m_wnd3tatusBar ; CToolBarm_wndToolBar ; CToolBar m wndColorSwipeBar ; Teraz możemy dodać do funkcji okna głównego OnCreateO kod formatujący nowy pasek, umieszczając go za kodem dla paska standardowego, korzystając z funkcji CreateEx (): if (!m_wndColorSwipeBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_LEFT | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndColorSwipeBar.LoadToolBar(IDR_COLORSWIPE)) { TRACEO("Failed to create toolbar\n"); return -l; // nie udało się utworzyć } Umieszczone w kodzie powyższym znaczniki powodują wyświetlenie paska o płaskim wyglądzie, zadekowanego po lewej stronie okna głównego i zawierającego uchwyt pozwalający na jego przemieszczanie. Przyciski i ich ikony załadowane zostają z zasobu paska IDR_COLORSWIPE. 334 Poznaj Visual C++ 6 Tworzenie pływających pasków narzędziowych Zamiast dokowanych pasków narzędziowych, tworzyć możemy również paski pływające, czego przykładem jest panel Controls widoczny w edytorze zasobów. Paski takie tworzy się za pomocą funkcji FloatControlBar (). Gdy utworzenie nowego paska przebiegnie bez zakłóceń, możemy wpisać kolejne linie kodu, które umożliwią dokowanie paska do krawędzi okna: m_wndColor3wipeBar.EnableDocking(CBRS_ALIGN_ANY) ; DockControlBar(&m_wndColorSwipeBar) ; Teraz możemy dopisać funkcje obsługi poleceń i interfejsu użytkownika. Rysunek 14.5 pokazuje okno główne aplikacji, posiadające zadekowany po lewej stronie dodatkowy pasek narzędziowy. Zmodyfikowany, standardowy pasek narzędziowy zadekowany jest u góry okna głównego. „^ Untitled - Toolbal Fie Edit View Uelp 1 D^GssH1 "J '[ir$ri'^"f"7^f Rysunek 14.5. Aplikacja SDI wyświetlająca drugi, większy i zadekowany po lewej stronie pasek narzędziowy PATRZ TAKŻE • Więcej informacji na temat klasy CMainFrame uzyskasz w rozdziale 12. Ukrywanie i wyświetlanie paska narzędziowego Za pomocą funkcji okna głównego showControlBarO możemy pasek narzędziowy ukrywać lub sprawić, by był widoczny. Funkcja ta wymaga od nas podania trzech para- Praca, z paskami narzędzi i stanu 335 metrów. Pierwszym z nich jest wskaźnik paska, który chcemy ukryć lub wyświetlić. Drugim parametrem jest wartość BOOL, która powinna mieć postać TRUE, by pasek był widoczny lub FALSE, jeśli gdy pasek ma być ukryty. Po przekazaniu w trzecim parametrze wartości FALSE powodujemy, iż pasek będzie wyświetlany natychmiast, wartość TRUE natomiast wprowadza pewne opóźnienie w wyświetlaniu paska. Lokalizowanie paska narzędziowego z aplikacji Zwykle paski narzędziowe są osadzone jako publiczne zmienne klasy CMainFrame. W celu zlokalizowania takiego obiektu można posłużyć się funkcją globalną AfxGet-MainWndO. Dla przykładu umieścimy w menu opcję ukrywania paska, przy której umieszczony będzie znacznik w przypadku widoczności paska. Funkcja obsługi dla tego polecenia może wyglądać mniej więcej tak: void CMainFrame::OnViewColorswipebar() ( m_bColorVisible = !m_bColorVisible ; ShowControlBar(&m_wndColorSwipeBar, m_bColorVisible, FALSE) ; ) Stan osadzonej w obiekcie CMainFrame zmiennej m_bcolorvisible jest przełączany pomiędzy TRUE i FALSE za pomocą kombinacji operatorów = !. Pasek narzędziowy będzie widoczny lub nie w wyniku przekazywania zmiennej m_bColorVisible do funkcji ShowControlBar(). Następną zaimplementowaną funkcją będzie funkcja obsługi interfejsu użytkownika umieszczająca lub usuwająca znacznik obok pozycji menu, w zależności od stanu widoczności paska narzędziowego: void CMainFrame::OnUpdateViewColorswipebar(CCmdUl* pCmdUl) ( pCmdUl ->Enable() ; pCmdUl ->SetCheck(m_bColorVisible) ; ) PATRZ TAKŻE • Więcej informacji o funkcjach obsługi interfejsu użytkownika w menu uzyskasz w rozdziale 13. Zachowywanie i załadowywanie pozycji paska narzędziowego Pozycje wszystkich użytych w aplikacji pasków możemy zachować i załadować stosując dwie funkcje: SaveBarState () oraz LoadBarState (). Funkcje te wymagają tylko jednego parametru, którym jest łańcuch opisujący nazwę klucza inicjalizującego, dla wpisu w Rejestrze systemu Windows. Możemy więc przekazać którykolwiek z łańcuchów do 336_____________________________________Poznaj Visual C++ 6 zapisania pozycji paska narzędziowego (zwykle podczas zamykania aplikacji). W celu załadowania tych ustawień podczas ponownego uruchomienia aplikacji przekazać musimy ten sam łańcuch. Ustawienia te zapisane zostają pod domyślnym kluczem Rejestru dla danej aplikacji, pomiędzy innymi dotyczącymi jej szczegółami (HKEY_CURRENT_USER\ Software\nazwafirmy autorskiej\tytułaplikacji). Skróty klawiaturowe funkcjonują nawet wówczas, gdy przyciski są niedostępne Akcelerator wysyła komunikat polecenia niezależnie od stanu przycisku, do którego się odwołuje. W takiej sytuacji należy zatroszczyć się o wywołanie funkcji obsługi, mimo iż przycisk jest niedostępny. Przykładowo, zapisanie stanu paska narzędziowego podczas destrukcji okna głównego zapewnimy dodając funkcję obsługi komunikatu WM_DESTROY i zapisując stan zanim wywołana zostanie funkcja OnDestroy () klasy bazowej: void CMainFrame::OnDestroy() { SaveBarState(„MyToolBarSettings") ; CFrameWnd::OnDestroy() ; } Aby dodać tę funkcję, zajrzyj do podrozdziału „Dodawanie funkcji obsługi komunikatu WM_DESTROY", w rozdziale 15. Aby załadować uprzednio zachowane ustawienia paska po jego utworzeniu, na końcu funkcji OnCreate () umieszczamy linię: LoadBarState("MyToolBarSettings") ; Po ponownym uruchomieniu aplikacji przywrócone zostaną wszystkie ustawienia dotyczące dokowania i pozycjonowania paska narzędziowego. Stosowanie pasków dialogowych Paski dialogowe są paskami zbudowanymi w oparciu o niemodalne okna dialogowe. Możemy umieścić na szablonie okna dialogowego kontrolki każdego rodzaju, a następnie użyć szablonu do wyświetlenia paska dialogowego. Domyślnym identyfikatorem jest zwykle IDD_DIALOGBAR, gdyż występuje w postaci szablonu paska, bez modalnych przycisków OK i Cancel. ... .. -. .. . '-i .„-: .-.•;,, '/—.^^i ^^--.<.—.i ^ ^••w^Ł-5^ •^wilirfUi3^-"'^ E»'.ł^J ^-,--i-t i^fn-f.-.^L^f,'^ .'^^'JV^O''ź -Ali/: yB^Bi-i-^^^i.so.^oa^ •,4^.^^?6^)a?J.•'y''t() iU^-W^^^yf^^t^W^^iW."8 -if]^ 4L^q^^p^tlć(;^^^I^^n^/j^ flaK^^J^|Jqj^^;,in^i^Qfrpga^t3s. ".V ^.Aa.-^.of.r^ ',. ^. „f-t-^,.^ - '1?,;':'': fj7—-" .y,'"".'1^,,'^';.^ '""'*<>, ••"»• '•.f\'^" ^.^,. '." '• .'.''^':. r-'.';'' Praca z paskami narzędzi i stanu 337 Użycie bitmap w paskach dialogowych Dodanie przycisku z bitmapą jest trudniejsze w przypadku paska dialogowego, niż narzędziowego. Jeśli zachodzi potrzeba umieszczenia takiego przycisku, należy to zrobić stosując klasę CBitmapButton, co omówione jest w rozdziale 11. Przykład paska dialogowego możemy obejrzeć w standardowej aplikacji po wydaniu polecenia podglądu wydruku. Pasek ten wyposażony jest w przyciski Print, Next Page, Prev. Page itd. Obiekt CDialogBar może zostać osadzony w oknie gtównym w taki sam sposób jak pasek narzędziowy. Klasa CDialogBar jest pochodną klasy CControlBar i dziedziczącą możliwości dokowania paska i inne cechy, którymi charakteryzują się paski narzędziowe. Główną przesłanką stosowania pasków dialogowych jest prostota umieszczania w nich różnych kontrolek i łatwość ich obsługi. PATRZ TAKŻE • Aby dowiedzieć się więcej na temat niemodalnych okien dialogowych zajrzyj do rozdziału 10. 4 Więcej szczegółów dotyczących procesów drukowania i podglądu wydruku znajdziesz w rozdziale 12. Dodawanie zasobu paska dialogowego Aby dodać do projektu szablon okna dialogowego dla paska dialogowego, możemy posłużyć się procedurą dotyczącą tej czynności, opisaną w rozdziale 10 i utworzyć zasób IDD_DIALOGBAR za pomocą okna dialogowego Insert Resource. Po wykonaniu koniecznych do utworzenia szablonu czynności, zmianie jego identyfikatora oraz właściwości według potrzeb, możemy przystąpić do umieszczania na szablonie kontrolek, jak ma to miejsce w przypadku zwykłych okien dialogowych. Wszystkim umieszczanym kontrolkom powinniśmy przydzielić identyfikatory, co umożliwi nam programowy do nich dostęp. Wyłączanie prowadnic podczas edycji paska dialogowego W trakcie używania domyślnego szablonu IDD_DIALOGBAR można wyłączyć prowadnice (Rulers and Guides), by maksymalnie wykorzystać ograniczoną przestrzeń w pasku dialogowym. Można to zrobić wybierając menu Layout, a z niego pozycję Guide Settings, co otworzy okno dialogowe o tym samym tytule. Wybór opcji None dla pozycji Layout Guides spowoduje wyłączenie prowadnic. 338 Poznaj Visual C++ 6 Rysunek 14.6 przedstawia szablon dialogu dla paska dialogowego. W szablonie tym umieszczono dwa obiekty; jeden przycisk (opisany Appły Color) oraz listę kombinowaną, inicjalizowaną z zestawem barw. PATRZ TAKŻE • O edytowaniu szablonów okien dialogowych przeczytasz w rozdziale 3. & " » fc EAI y» ssaa a^ s w hseil Pio l ^ Ha Kt Buitó Layout j^oc „ - ^ •- [T DIAŁOSBAB IDmIoall ^Jffj-' m_wndColorDlgBar.GetDIgItem(IDC_COLOR) if (pColSel) m nColor = pColSel->GetCurSel (); BOOL CDlgbarView::OnEraseBkgnd(CDC* pDC) ( COLORREF rf = RGB(255,255,255); switch(m_nColor) { case 0: rf = RGB(O,O,255); break; © case l: rf = RGB(O,255,0); break; © case 2: rf = RGB(255,O,0); break; © case 3: rf = RGB(255,255,0); break; ® } CRect rcCIient; OetCIientRect (srcdient) ; pDC->Fill3olidRect(&rcClient,rf) ; return TRUE; O Ręcznie dodana pozycja mapy komunikatów dotycząca zmiany wyboru z listy kombinowanej. @ Lista kombinowana zostaje dynamicznie przyłączona do klasy obiektu w celu pobrania bieżącego wyboru. © W zależności od bieżącego wyboru, ustalona zostaje wartość COLORREF w celu wypełnienia tła widoku, po wysłaniu komunikatu WM_ERASEBKGND. W linii 9 powyższego listingu widzimy pozycję mapy komunikatów, dotyczącą przycisku (o identyfikatorze IDC_APPLY_COLOR). Wpis ten łączy komunikat polecenia przycisku z funkcją obsługi AppłyColor () (jak w przypadku zwykłego przycisku na pasku narzędziowym). Pozycja mapy komunikatów widoczna w linii 10 demonstruje jak połączyć funkcję obsługi ColorChange () z komunikatem CBN_SELCHANGE, który wysyłany jest, gdy użytkownik dokona zmiany wyboru pozycji z listy kombinowanej. Po umieszczeniu tego wpisu w mapie komunikatów (linia 10) powiadomienie o zmianie wyboru, wysyłane przez listę kombinowaną powoduje wywołanie funkcji ColorChange (). 342 Poznaj Visual C++ 6 Funkcja AppłyColor () implementowana w liniach od 13 do 16 wymusza odświeżenie okna poprzez wywołanie w linii 15 funkcji lnvalidate (), powodującej odświeżenie widoku. Funkcja obsługi ColorChange (), której implementacja odbywa się w liniach 19-27, zmienia wartość zmiennej składowej m_nColor przechowującej indeks koloru, w zależności od wyboru użytkownika. Zauważmy, iż moduł MainFrame.h jest dołączony w linii 18 w celu poinformowania kompilatora o zadeklarowaniu obiektu m_wndColorDlgBar w definicji klasy CMainFrame. W liniach 29-43 znajdujemy funkcję obsługi komunikatu WMERASEBKGND. W liniach 31-38 ustawiony zostaje właściwy kolor i wywołanie funkcji FilISolidRect () w linii 41 powoduje wymazanie tła. Rysunek 14.7. Aplikacja SDI wyświetlająca pasek dialogowy z listą kombinowaną PATRZ TAKŻE • Aby zrozumieć istotę komunikatów powiadamiających listy kombinowanej, przeczytaj rozdział 6. • Więcej szczegółów dotyczących kolorów i makra RGB znajdziesz w rozdziale 16. • Więcej informacji na temat odmalowywania prostokątów za pomocą pędzli znajdziesz w rozdziale 16. Dostosowywanie paska stanu Tworząc aplikację SDI lub MDI możemy dodawać własne znaczniki paska stanu. Szkielet standardowej aplikacji dostarcza indykatory sygnalizujące włączenie CAPS, NUM LOCK oraz SCROLL LOCK. Znajdują się one u dołu głównego okna aplikacji. Są to małe „wgłębione" prostokąty, zwane panelami indykatorów. Praca z paskami narzędzi i stanu 343 Konwencje paska stanu Pasek stanu występuje z reguły jako pasek dotowany, tak że nie ma możliwości zmiany jego położenia, które wyznaczone jest u dołu okna. Cecha ta jest nadawana automatycznie i funkcjonuje do momentu zmiany domyślnych znaczników funkcji tworzącej. Istnieje jednak możliwość obejścia konwencji przy odpowiednim wykorzystaniu mechanizmów związanych z paskami sterującymi. Możemy tworzyć, uzyskiwać dostęp, a także zmieniać stan tych paneli, używając obiektu cstatusBar zadeklarowanego w klasie okna głównego. CStatusBar jest jeszcze jedną klasą wyprowadzaną z CControlBar, przez co dziedziczy ona wszelkie cechy pasków sterujących. PATRZ TAKŻE • Więcej szczegółów dotyczących aplikacji SDI oraz MDI znajdziesz w rozdziale 21. Istota standardowego paska stanu Standardowy generowany przez AppWizard kod szkieletowy aplikacji, w zakresie wsparcia dla paska stanu jest podobny do kodu paska narzędziowego. W definicji klasy CMainFrame możemy zobaczyć deklarację obiektu CStatusBar, oznaczonego jako m_wndStatusBar: CStatusBar m_wndStatusBar ; Okno paska stanu tworzone jest poprzez wywołanie z funkcji OnCreate () okna głównego funkcji klasy CStatusBar, Create (). Funkcji tej przekazać możemy trzy parametry, przy czym jedynie pierwszy jest niezbędny, gdyż stanowi wskaźnik okna rodzica. Drugi parametr umożliwia ustalanie stylu, wliczając pozycję dokowania oraz inne wymagane przez system Windows znaczniki. Jako wyznaczniki pozycjonowania przekazywać można wartości podane w tabeli 14.4 (dotyczyły one pasków dialogowych). Jeśli pominiemy ten parametr, domyślnie zostaną przekazane znaczniki: WS_CHILD, WS_VISIBLE oraz CBRS_BOTTOM. Trzeci z parametrów pozwala na ustalenie identyfikatora paska stanu, jak miało to miejsce w przypadku paska dialogowego, domyślnie nadając identyfikator AFX_IDW_STATUS_BAR. W szkieletowym kodzie aplikacji możemy zobaczyć, iż pasek stanu tworzony jest po pasku narzędziowym w funkcji okna głównego OnCreate () poprzez następujący wpis: if (!m_wndStatusBar.Create(this) || !m wndStatusBar.Setindicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACEO("Failed to create status bar\n") ; return -l // fail to create ) 344 Poznaj Visual C++ 6 Jeśli funkcja Create () przebiegnie prawidłowo, wywołana zostaje funkcja Setindi-cators () inicjalizująca panele znaczników. Funkcji tej należy przekazać jako pierwszy parametr wskaźnik tablicy wartości UINT oraz liczbę znaczników paska stanu jako parametr drugi. Kod szkieletowy wskazuje tablicę nazwaną indicators i używa operatora sizeof () do określenia liczby pozycji w tablicy, poprzez podzielenie rozmiaru całej tablicy indicators przez rozmiar jednego elementu. Włączanie podpowiedzi dla elementów paska stanu W celu włączenia wyświetlania podpowiedzi dla paska stanu należy ustalić odpowiedni znacznik stylu cstatusBarCtri. Można to zrobić stosując funkcję Create-Ex() w miejsce Create(). Drugim parametrem CreateEx() jest dwCtrIStyie, któremu trzeba przekazać znacznik SBT_TOOLTIPS, co spowoduje włączenie wyświetlania podpowiedzi. Tablica indicators zdefiniowana jest na początku modułu implementacyjnego MainFrm.cpp; przed funkcją OnCreate () i funkcjami konstruktora okna głównego. Kod domyślny, generowany przez AppWizard, deklarujący tablicę indicators ma postać następującą: static UINT indicators[] ( ID_SEPARATOR , // wskaźnik stanu linii ID_INDICATOR_CAPS , ID_INDICATOR_NUM , ID_INDICATOR_SCRL , } ; Pozycja pierwsza, ID_SEPARATOR ustanawia separator generujący płaską część paska stanu. Domyślnie zostaje on rozciągnięty, by wypełnić przestrzeń nie zajętą przez indykatory. Ponieważ pierwsza pozycja tablicy jest separatorem, aplikacja wyświetla pusty kawałek normalnego okna, rozciągnięty do indykatorów. Panel ten używany jest do wyświetlania opisów pozycji menu oraz przycisków paska narzędziowego, gdy użytkownik wskazuje je myszą. Druga pozycja, czyli ID_INDICATOR_CAPS, jest identyfikatorem generowanym przez AppWizard. Obsługiwany jest domyślnie w ramach aplikacji, służąc sygnalizacji stanu klawisza Caps Lock. Pozycje ID_INDICATOR_NUM oraz ID_INDICATOR_SCRL pełnią takie samo zadanie w odniesieniu do klawiszy Num Lock i Scroll Lock. Identyfikatory powyższe możemy znaleźć w tablicy łańcuchów aplikacji. Każda z pozycji posiada przydzielony tekst (widać to na rysunku 14.8) wyświetlany w panelu właściwego indykatora, gdy jest dostępny. Panele te mogą być udostępniane lub nie po zastosowaniu funkcji obsługi polecenia interfejsu użytkownika, w podobny sposób, jak w przypadku menu lub przycisków Praca z paskami narzędzi i stanu 345 paska narzędziowego. Gdy panel jest dostępny, wyświetlony w nim zostaje odpowiedni tekst; w przypadku niedostępności zaś, tekst jest ukryty. Szkielet aplikacji automatycznie udostępnia lub nie panele, wyświetlając w nich przydzielony tekst (CAPS, NUM lub SCRL), w zależności od stanu klawiszy Caps Lock, Num Lock oraz Scroll Lock. dilool • Miciofdll Vnua) C.» • liwlitool ic • SInng Tubie ISIrimi Tllbtell !UJNUIWUR.,EXT : iB,INDi»łOR.CAW • Ul WIKTOR NUM. . ID IWICłTOR SCRLi it).inniaioR OVB -t TWSK{Wfiar~ S3136 EX1 58137 CH> : S133: NUM : WB SCBL ; Rysunek 14.8. Oznaczone pozycje tablicy łańcuchów wskazują wygenerowane przez AppWizard indykatory paska stanu Te pozycje tablicy możemy usuwać, dodawać, a także modyfikować zmieniając zestaw domyślnych indykatorów paska stanu, co opisane jest w kolejnym punkcie. Gdy funkcji Setindicators () przekazana zostanie tablica indicators, zawarte w niej wartości służą zmianie wyglądu i funkcjonalności paska stanu. Po wywołaniu funkcji Cre-ate() oraz Setindicators (), pasek stanu zostaje wyświetlony, dokując u dołu okna głównego poprzez domyślne znaczniki w funkcji Create (). PATRZ TAKŻE • Objaśnień dotyczących tablicy łańcuchów szukaj w rozdziale 2. Dodawanie indykatorów i separatorów Aby dodać do paska stanu własny indykator, należy najpierw umieścić odrębny identyfikator w tablicy łańcuchów. Tekst zapisany w tej pozycji tablicy wyświetlany będzie w pasku stanu, gdy indykator będzie dostępny. Może on zostać udostępniony poprzez przekazanie TRUE funkcji Enable () obiektu CCmdUl, przekazanej funkcji polecenia interfejsu użytkownika dla indykatora. Możemy również zmienić tekst pojawiający się w pasku stanu wywołując funkcję setText () obiektu CCmdUl (jak w przypadku polecenia menu). CIassWizard nie daje niestety możliwości utworzenia tych funkcji obsługi poleceń interfejsu użytkownika, musimy zatem własnoręcznie wpisać nowe pozycje do mapy łańcuchów, a także zadeklarować i zaimplementować kod owych funkcji. Funkcjonujący przykład, w którym wyświetlana na pasku stanu będzie aktualna pozycja wskaźnika myszy, może uczynić te powyższe procedury nieco jaśniejsze. Utworzymy zatem za pomocą AppWizard szkielet typowej aplikacji SDI, nazywając ją StatusMouse. 346 Poznaj Visual C++ 6 Po pierwsze, otworzymy tablicę łańcuchów i wprowadzimy do niej wpis dla nowego indykatora, którego identyfikator będzie brzmiał ID_INDICATOR_MOUSE (rysunek 14.9). -lal xi -ri .^Rtf EAt. Vew Ifiseil Ptoiec! fiuifcf Joofs ^tidow tNp ""lĘ^I':!1^^1 " ^•i. ^6 \CSQS ^ E& ES . ;. - •;-••• Ola?,' CjltjIDJNOICATOR.MOUSE3; i» | ;,$« ca ^: • w al-sl, JB. ; •_j SlatulMoule re :+;• UAcceteatot cł; Cl Datog EB O Icon »l _)M«r»l Ś aSłłaTaHł •• Sfej SIrino Tab C+1 LJToobal »!• _iV»«o" ID PREV_PANE - iB'lŃDiiaTOFi:Rr'— — ID INDICMOR CAPS ID INDICATOH NUM IDJNDICATOn_SC BL IDJNDICATOR OVR ID.INOICATOR^HE C IDJNDfCATOR^MD USE iD_V!EW_TOOLBA R"" "" ^ 3 IS l=! ^ \^ fi | Vahl» | Caplnn RepMt łhe last ariiCTiSnRcpeat^] Reptece sptciłic lexl w»l dllfetont >eKt\nReplace Scled »» eriie *«:l»»nl\łlS«lecl Al Undo the b;l aciionWJndo Redo the fitwwJy undone action\nRedo "ScillThe actiw iwidowrto paneJ^nSpA^~ Dispi^ pogram rrfonnatCTl. '/etsion liumbei and cop^ighłYAb Quit Ihe applicfition; plompts to save docimnsrtl\rfłiit Swiichto^naxrwndowpwe\riŃeidPaiw^~^ 57E40 ID EDIT_nEPLACE i 57641 ID_EDIT SELECI_ALL 1 57642 ID EDITJJNDO l 57643 10_EDIT REDO i 57644 'VffmEeW3ffn__ J WST ID_APP""ABOUf"""' " I^STiS&i^ ID APP DII l 57665 'ItrNEKrPANE""""""""""""!'""^ ^" Switch back to Iha oEnable() ; ) Funkcja Enable () domyślnie ustawia stan TRUE, powodując pojawienie się skojarzonego tekstu oraz wyświetlenie w pasku stanu napisu Mouse Position (łańcuch z tablicy łańcuchów). Sterowanie widzialnością nowego indykatora odbywa się poprzez przekazywanie funkcji Enable () wartości zmiennej składowej typu Boolean (podobnie jak w podrozdziale „Aktywacja i dezaktywacja przycisków paska narzędziowego"). Zamiast szarzenia, które ma miejsce w przypadku przycisków paska narzędziowego lub pozycji menu, w chwili dezaktywacji indykatora tekst w pasku stanu po prostu znika. Ponieważ funkcja obsługi interfejsu użytkownika dodana została ręcznie, musimy jeszcze dopisać deklarację funkcji w definicji klasy CMainFrame (zawartej w module MainFrm.cpp). Deklaracja ta dla dodanej przed momentem funkcji obsługi wyglądać powinna następująco: afx_msg void OnUpdateMousePos(CCmdUl* pCmdUl) ; Prefiks afx_msg używany jest przez makra mapy komunikatów. Nowy panel indykatora powinien być widoczny na pasku stanu, wyświetlając napis Mouse Position (rysunek 14.10). 348 Poznaj Visual C++ 6 ^ Uniitled - StatusMouse File gsSt VBW Hełp "D|^JH|'7ji-|fe| ,fl|fj Rysunek 14.10. Nowy indykator na pasku stanu, wyświetlający napis Mou s e Position O Indykator z wyświetlonym napisem Poprzez dopisanie nowych pozycji ID_SEPARATOR możemy spowodować wstawienie separatorów pomiędzy poszczególne panele. Jeśli przykładowo dodamy do mapy indykatorów wpis ID_SEPARATOR za wpisem ID_INDICATOR_MOUSE, spowodujemy umieszczenie separatora pomiędzy nowym panelem a pozostałymi: ID_SEPARATOR , // status indykatora ID_INDICATOR_MOUSE , ID_SEPARATOR , // nowy separator ID_INDICATOR_CAPS , Po wprowadzeniu tej zmiany, nowy panel oddzielony zostanie od pozostałych poprzez umieszczenie separatora pomiędzy panelami indykatorów Mouse Position i CAP (rysunek 14.11). Untitled • SlatutMoute Ffc Edt ';iew tjtlp 'D|e?lMl ••I -l -Q Rysunek 14.11. Efekt dodania separatora za panelem Mouse Position O Nowy panel separatora Prawdopodobnie jednak będziemy chcieli zmienić początkowy domyślny rozmiar se- paratora, a także jego wygląd, co opisane jest w następnym podrozdziale. fl\\KŁ l AIY.I: « Więcej na temat funkcji obsługi interfejsu użytkownika w menu dowiesz się z rozdziału 13. • Wyjaśnień dotyczących tablic łańcuchów szukaj w rozdziale 2. Dynamiczne zmiany wymiarów, stylu i tekstu w panelach Klasa cstatusBar posiada kilka funkcji umożliwiających zmianę wymiarów, stylu oraz tekstu elementów paska stanu. Funkcjami tymi są SetPaneinfo (), SetPaneStyle () oraz SetPaneText(). Każda funkcja identyfikuje panel na podstawie indeksu pozycji w tablicy indykatorów (przekazanej do Setindicators ()). Nie zawsze najlepszym wyjściem jest bezpośrednie użycie tego indeksu, ponieważ kod może ulec zmianie, gdy wprowadzimy do paska stanu kolejne indykatory (istniejące indeksy ulegną wówczas zmianie). Z pomocą w takiej sytuacji przychodzi nam funkcja składowa CommandTolndex (), zwracająca indeks indykatora na podstawie przekazanego jej, jako jedyny parametr, identyfikatora. Przykładowo, poniższa linia przypisuje l do zmiennej nindex po wyjściu z funkcji CommandTolndex () (używając tablicy indicators podanej wcześniej). int nldnex m_wnd3tatusBar.CommandTolndex(ID_INDICATOR_MOUSE) ; Teraz możemy już umieszczać nowe panele przed ID_INDICATOR_MOUSE, gdyż wartość indeksu będzie się zmieniała odzwierciedlając aktualną pozycję identyfikatora, który możemy w dalszej kolejności przekazywać funkcjom SetPane... (). Funkcja SetPaneText () pobiera jako pierwszy parametr indeks pozycji, której tekst wyświetlany na pasku ma ulec zmianie. Drugim parametrem jest wskaźnik łańcucha lub obiektu cstring, w którym znajduje się nowy tekst. Trzecim opcjonalnym parametrem jest wartość Boolean. Wartość ta decyduje, czy nowy tekst ma pojawić się na pasku bezzwłocznie (TRUE), czy po kolejnym odświeżeniu okna (FALSE). Model linii zmieniającej tekst przydzielony identyfikatorowi wygląda następująco: m_wnd3tatusBar.SetPaneText(nindex , "My New Pane Text") ; Korespondująca funkcja GetPaneText () zwraca zmienną cstring. Przechowywany w niej jest tekst aktualnie używany przez panel, którego indeks podany jest funkcji jako parametr. Styl panelu indykatora zmienia się za pomocą funkcji SetPaneStyle () i przekazując jej jak drugi parametr znacznik stylu spośród wymienionych w tabeli 14.5. Pierwszym parametrem jest oczywiście indeks panelu, którego dotyczy działanie funkcji. Przykładowo, aby panel Mouse Position stał się wypukły należy dodać następujący kod: int nindex = CommandTolndex(ID_INDICATOR_MOUSE) ; m_wndStatusBar.SetPaneStyle(nindex, SBPS_POPOUT) ; 350_____________________________________Poznaj Visual C++ 6 Tabela 14.5. Znaczniki stylu paneli indykatorów paska stanu Znacznik Opis SBPS_NORMAL panel jest „odciśnięty" w pasku stanu SBPS_POPOUT panel jest płaski SBPS_DISABLED Tekst indykatora nie jest wyświetlany SBPS_STRETCH Rozciągnięcie panelu w taki sposób, aby wypełnił pozostałą część paska stanu (tylko jeden panel może posiadać ten znacznik) SBPS_NOBORDERS Wokół panelu nie pojawi się trójwymiarowa ramka Korespondująca funkcja GetPaneStyle () zwraca wartość typu UINT, przechowującą bieżący znacznik stylu panelu identyfikowanego przez przekazany indeks. Dzięki funkcji SetPaneinfoO za jednym zamachem możemy zmienić identyfikator, styl oraz szerokość panelu. Pierwszym parametrem jest, jak zwykle, indeks pozycji panelu. Jako drugi parametr możemy podać nowy identyfikator indykatora (lub istniejący, by go zachować). Trzecim parametrem jest znacznik stylu, który możemy wybrać z tabeli 14.5. Czwarty parametr wreszcie, umożliwia zmianę domyślnej szerokości panelu poprzez podanie wartości całkowitoliczbowej, określającej nową szerokość panelu w pikselach (parametr ten jest ignorowany jeśli wstawimy znacznik stylu SBPS_STRETCH). Na przykład, by zmienić szerokość panelu Mouse Position na 100 pikseli, musimy wpisać poniższy kod: int nlndex = CommandToIndex(ID_INDICATOR_MOUSE) ; m_wndSatusBar.SetPaneInfo(nlndex,ID_INDICATOR_MOUSE, GetPaneStyle(), 100) ; Zauważmy, iż funkcja GetPaneinfo () użyta jest dla zachowania stylu przekazanego jako trzeci parametr funkcji SetPaneinfo (). Listing 14.2 zamyka te funkcje w jednej funkcji okna głównego, która powoduje wyświetlanie w panelu Mouse Position określonego łańcucha. Szerokość panelu jest dopasowywana do szerokości tekstu, a styl ustalony jest jako normalny. Tekst, który ma się pojawiać w panelu indykatora, informując o aktualnym położeniu wskaźnika myszy, generowany będzie przez funkcję widoku, która umieszczona jest na listingu 14.3. Po skompilowaniu i uruchomieniu programu, pozycja ta będzie wyświetlana w panelu i zmieni wskazywane wartości wraz ze zmianą położenia wskaźnika (rysunek 14.12). .' :-'." A•.';;i•l"7T.3SetMousePosText(strMousePosition); @ CView::OnMouseMove(nFlags, point) ; O Pozycja wskaźnika myszy sformułowana zostaje jako zestaw współrzędnych x, y. @ Gdy pozycja wskaźnika myszy jest już sformatowana w łańcuch, wywołana zostaje funkcja utworzona przez kod na listingu 14.2, zmieniająca zawartość panelu. Linia l listingu 14.3 dołącza definicję klasy CMainFrame. Funkcji składowej OnMo-useMove() wywołanej w odpowiedzi na ruch wskaźnika myszy, przekazana zostaje Praca z paskami narzędzi i stanu 353 zmienna point, zawierająca aktualną pozycję wskaźnika. W linii 7 następuje sformułowanie łańcucha strMousePosition w postaci (x, y), który zadeklarowany jest w linii 6. Linie 11 i 12 pobierają wskaźnik do okna przodka bieżącego obiektu widoku. Nowa funkcja składowa okna głównego SetMouseposText () (z listingu 14.2) może wtedy pobrać nowo sformatowany łańcuch strMousePosition w celu odświeżenia wskazań w panelu indykatora. Implementując tę funkcję, musimy pamiętać również o utworzeniu pozycji mapy komunikatów dla tej funkcji, a także definicji klasy, w oparciu o CIassWizard lub okno dialogowe New Windows Message and Event Handlers. PATRZ TAKŻE » Więcej szczegółów dotyczących obsługi komunikatów o zmianie pozycji myszy znajdziesz w rozdziale 8. • Więcej informacji na temat kontekstów urządzeń znajduje się w rozdziale 15. Istota pasków kontenerowych w programie Intemet Explorer Pasek kontenerowy jest jedną z nowych kontrolek, funkcjonującą w programie Inter-net Explorer jako kontener pasków narzędziowych, pasków dialogowych oraz innych kontrolek. Po uruchomieniu programu zobaczymy taki pasek u góry okna. Pasek kontenerowy pozwala na przemieszczanie wewnątrz niego pasków narzędziowych, a także innych umieszczonych tam obiektów. Te obszary, które możemy przesuwać, zwane są taśmami. Pasek kontenerowy może posiadać wiele taśm, ułożonych poziomo. Pasek kontenerowy można udekorowany bitmapą. Pomimo iż użytkownik może przesuwać paski narzędziowe i dialogowe wewnątrz paska kontenerowego, bitmapą pozostaje niezmieniona. Biblioteka MFC zawiera klasę obudowująca CReBar dla dokowanych pasków kontenerowych, która dziedziczy z klasy CControlbar, dostarczając w ten sposób możliwości dokowania. Klasa CReBarCtrl obudowuje zwykły obiekt paska, implementujący pasek dekujący. Odwołanie do obiektu CReBarCtrl możemy pobrać z obiektu CReBar poprzez wywołanie jego funkcji składowej GetReBarCtrl (). Paski kontenerowe stanowią obszerny i złożony temat. Niestety, z powodu braku miejsca, opiszemy tylko ich podstawowe możliwości oraz wsparcie szkieletu aplikacji. PATRZ TAKŻE • By dowiedzieć się więcej na temat używania wewnętrznego widoku Intenet Explorer 4, zajrzyj do rozdziału 10. 354_____________________________________Poznaj Visual C++ 6 Tworzenie paska kontenerowego za pomocą AppWizard Jeśli chcemy użyć paska kontenerowego do zgrupowania pasków narzędziowych oraz dialogowych, do jego wygenerowania możemy użyć kreatora AppWizard, ustanawiając go poprzez wybór opcji Intemet Explorer Rebar, którą mamy do dyspozycji w kroku 4 tego kreatora (patrz rysunek 14.1). AppWizard doda wówczas obiekt o nazwie m_wndReBar do definicji klasy CMainFrame i wygeneruje w funkcji OnCreate () okna głównego poniższy kod, tworzący i inicjalizujący pasek kontenerowy: if (!m_wndReBar.Create(this) || !m wndReBar.AddBar(&m wndToolBar) || !m wndReBar.AddBar(&m wndDłgBar)) { TRACEO("Failed to create rebar\n") ; return -l ; // niepowodzenie Funkcja Create () obiektu CReBar pobiera cztery parametry. Pierwszy jest obowiązkowym wskaźnikiem okna rodzica. Jako drugi parametr umieścić możemy znacznik (tabela 14.6), by nadać paskowi odpowiedni styl. Domyślnym wskaźnikiem przekazywanym funkcji w tym miejscu jest RBS_BANDBORDER, który powoduje obrysowanie taśm obwódką. Trzeci parametr charakteryzuje okno oraz sposób dokowania paska kontenerowego. Domyślnymi znacznikami są tutaj: WS_CHILD, WS_VISIBLE, WS_CLIPSIBLINGS oraz CBRS_TOP. W rezultacie otrzymujemy pasek zadekowany u góry widocznego okna potomnego, z dopasowanymi do jego obszaru zawartymi w nim paskami narzędziowym i dialogowym. Ostatni parametr określa identyfikator paska, który domyślnie ma postać AFX IDW REBAR. Do czego służą paski kontenerowe? Pasek tego typu jest obiektem sterującym, który pojawił się wraz z programem Iner-net Explorer 4. Służy po prostu do zmiany wymiarów pasków narzędziowych. Czasem określa się go mianem coolbar. Tabela 14.6. Znaczniki stylu pasków kontenerowych Znacznik Opis RBS_AUTOSIZE Układ każdej z taśm jest automatycznie zmieniany, gdy zmianie ulegną wymiary lub położenie paska kontenerowego RBS_BANDBORDERS Każda z taśm otoczona zostaje oddzielną obwódką RBS_FIXEDBORDER Utrzymuje stały porządek taśm Praca z paskami narzędzi i stanu 355 RBS VARHEIGHT RBS VERTICALGRIPPER RBS DBLCLKTOGGLE RBS REGISTERDROP Zwykle wszystkie taśmy przyjmują jednakową wysokość, równą wysokości najwyższej spośród nich. Przekazując ten znacznik powodujemy, iż każda z taśm przyjmuje wysokość odpowiednią dla kontrolek w niej zawartych Uchwyt wymiarowania taśmy wyświetlany jest jako pionowy, gdy znajduje się w pionowym pasku kontenerowym Minimalizuje i maksymalizuje taśmy paska kontenerowego na skutek podwójnego kliknięcia. Domyślnie efekt ten wywołuje kliknięcie pojedyncze Umożliwia wysyłanie powiadomień OLE drag-and-drop za pośrednictwem komunikatu RBN_GETOBJECT Zauważmy, iż po utworzeniu paska kontenerowego przez funkcję AddBar() zostają dodane do niego wygenerowane przez AppWizard paski, narzędziowy i dialogowy. Wytyczne dotyczące dokowania paska narzędziowego są pomijane, a całą odpowiedzialność za dokowanie przejmuje pasek kontenerowy, wyświetlając oba paski sterujące w swoim obszarze, co, widać na rysunku 14.13. File Edit View Hełp ^D E? H T, ^fi5' jtfc: TODO: layout dialog bal Rysunek 14.13. Standardowa aplikacja wygenerowana przez AppWizard, zawierająca pasek kontenerowy Komunikaty kontrolek osadzonych w pasku kontenerowym Komunikaty wysyłane przez kontrolki osadzone w pasku kontenerowym trafiają do okna rodzica. Można więc zastosować funkcje obsługi tych obiektów w klasach okna głównego, widoku lub dokumentu, podobnie jak w przypadku obiektów pasków dialogowych. 356_____________________________________Poznaj Visual C++ 6 Konfigurację paska narzędziowego, a także dialogowego możemy przeprowadzić zgodnie ze wskazówkami z poprzednich punktów tego rozdziału. Korzystając z funkcji AddBar () możemy dodawać kolejne paski sterujące. PATRZ TAKŻE • Więcej informacji dotyczących korzystania z AppWizard i tworzeniu szkieletów aplikacji SDI znajdziesz w rozdziale 12. • Aby dowiedzieć się więcej o procedurze obcinania, zajrzyj do rozdziału 15. Nadawanie tytułu i przydzielanie bitmapy tła paska kontenerowego Funkcja AddBar () obiektu CReBar wymaga tylko jednego obowiązkowego parametru. Jest nim wskaźnik kontrolki lub paska narzędziowego, który ma zostać dodany. Drugi, lecz opcjonalny, parametr umożliwia umieszczenie napisu w pasku kontenerowym, poprzez przekazanie wskaźnika łańcucha znakowego lub wskaźnika NULL (NULL jest wskaźnikiem domyślnym). Możemy również opatrzyć tło paska bitmapą, przekazując wskaźnik CBitmap do załadowanej bitmapy. Czynimy to w parametrze trzecim. Dla przykładu, poniższy kod umieszcza napis w pasku narzędziowym oraz dodaje tło bitmapowe dla obu dodanych do paska kontenerowego pasków sterujących: static CBitmap myBitmap ; myBitmap.LoadBitmap(IDB_MYREBAR_BM) ; if (!m_wndReBar.Create(this) || !m_wndReBar.AddBar(&m_wndToolBar,"My Toolbar",SmyBitmap) || !m_wndReBar.AddBar(&m_wndDlgBar,NULL,SmyBitmap)) Zauważmy, iż w pierwszej linii obiekt myBitmap zadeklarowany został jako statyczny (static), w związku z czym pozostaje w pamięci po zakończeniu działania funkcji On-Create (). Moglibyśmy użyć zmiennej składowej zamiast niego, lecz bitmapą musi pozostać legalna przez cały czas istnienia paska kontenerowego. Druga linia ładuje zasób bitmapy IDB_MYREBAR_BM. Utworzenie tego zasobu jest możliwe za pomocą edytora zasobów. Wskaźnik do obiektu CBitmap został przekazany funkcjom AddBar () jako trzeci parametr. Po dodaniu obiektu m_wndToolBar w czwartej linii jako drugi parametr przekazano tekst, który będzie wyświetlany w pasku kontenerowym. Po uruchomieniu aplikacji tekst ten widniał będzie na tej samej taśmie, w której umieszczono pasek narzędziowy, po jego lewej stronie. Pasek dialogowy nie został opatrzony napisem, ponieważ jako drugi parametr przekazano w jego przypadku wartość MULL. PATRZ TAKŻE • Jak utworzyć i użyć bitmapy dowiesz się z rozdziału 11. CzęŚĆ IV Tworzenie grafiki Rozdział 15 Rysowanie w kontekście urządzenia Kontekst urządzenia i jego użycie Ustalanie i stosowanie trybów rysowania i możliwości urządzeń Stosowanie trybów odwzorowywania podczas rysowania przy użyciu cali lub centymetrów Wprowadzenie do pojęcia kontekstu urządzenia Nie dajmy się zwieść określeniu kontekst urządzenia. Jest to jeden z technicznie brzmiących terminów, określających prosty mechanizm, stanowiący centralny punkt systemu Windows. Jak wyglądałby ten system, gdyby nie kontekst urządzenia? Nie wiadomo. Kontekst to swego rodzaju płótno, na którym rysowane są wszystkie punkty, linie, kwadraty, czcionki, kolory oraz wszystko to, co widzimy. Urządzenie oznacza zaś, że możemy rysować na ekranie, drukarce, ploterze lub jakimkolwiek innym urządzeniu wytwarzającym dwuwymiarowy obraz, bez głębszej wiedzy na temat sprzętu, z którego korzystamy. Struktury kontekstu urządzenia Kontekst urządzenia jest strukturą Windows, w której przechowywane są atrybuty domyślnych ustawień używanych podczas wykonywania operacji graficznych, takich jak chociażby rysowanie linii. Odmiennie niż w przypadku większości struktur Windows, program może nie mieć bezpośredniego dostępu do struktury kontekstu urządzenia. Ustawienia mogą być zmieniane jedynie poprzez zestaw standardowych funkcji dostępu. Jedną z najważniejszych rzeczy, jakie Microsoft dał przemysłowi programistycznemu, jest standaryzacja wsparcia dla urządzeń różnego typu, zawarta w systemie Windows. Wraz z rozpędem, którego nabrał przemysł informatyczny, aplikacje graficzne powstają 360_____________________________________Poznaj Visual C++ 6 w wielkiej obfitości. Jest to najprawdopodobniej najważniejszy i najbardziej niedoceniany rezultat powstania systemu Windows. Możemy pójść do sklepu z komputerami, kupić najnowszą drukarkę firmy XYZ, zainstalować w systemie jeden jedyny sterownik dostarczony przez producenta, a każdy punkt utworzony w aplikacji, zostanie natychmiast przeniesiony na drukarkę. Kontekst urządzenia jest tym, co łączy i wspiera proces rysowania. PATRZ TAKŻE • O rysowaniu w kontekście urządzenia czytaj w rozdziale 16. 4 Jak używać kontekstu urządzenia dla drukarki dowiesz się w rozdziale 22. Graficzne biblioteki Windows Kod systemu operacyjnego odpowiedzialny za przeprowadzanie operacji graficznych zaimplementowany jest w wyniku interakcji dwóch bibliotek DLL. Pierwszą z nich jest GDI.DLL, która dostarcza programom zestawu funkcji graficznych, niezależnych od urządzenia. Drugą biblioteką jest biblioteka DLL zależna od urządzenia, która użyta jest w odniesieniu do monitora, drukarki lub plotera. Na przykład, podczas rysowania na zwykłym monitorze VGA, używana jest biblioteka VGA.DLL dostarczająca kodu implementacyjnego właściwych funkcji rysujących. Typy kontekstów urządzenia Istnieją konteksty urządzeń dla specjalnych zastosowań i określonych zadań. Są one obiektami GDI (Graphical Device Interface, interfejs urządzeń graficznych) i stanowią zesiaw funkcji umieszczonych w bibliotece DLL, w samym sercu systemu. Funkcje te zapewniają łączność pomiędzy wywoływanymi funkcjami graficznymi, a sterownikami urządzeń, które powodują wyświetlenie obrazu lub jego wydruk. Biblioteka MFC dostarcza klas osłonowych dla kontekstów urządzeń, które upraszczają interakcje z obiektami GDI. Zasadniczą klasą kontekstu urządzenia jest klasa CDC. Klasa ta dostarcza ogromną liczbę funkcji rysujących, odwzorowujących współrzędne i wycinających. Wszystkie pozostałe, bardziej wyspecjalizowane klasy są z niej wyprowadzane i ją poszerzają. Kolejne punkty rozdziału będą poświęcone właśnie tym klasom. Stosowanie klasy CDC Klasą kontekstu urządzenia, którą będziemy stosować zawsze jako klasę bazową, jest klasa CDC. Klasa ta przechowuje uchwyty dwóch obiektów GDI; m_hDC oraz m_hAttri-bDC. m_hoc jest uchwytem kontekstu urządzenia łączącym klasę z obiektem GDI w celu obsługi danych wyjściowych z funkcji rysujących. m_hAttribDC natomiast jest uchwy- Rysowanie w kontekście urządzenia 361 tem, który łączy wszelkie informacje o atrybutach, takie jak kolor czy tryb rysowania. Nie musimy zanadto zawracać sobie głowy tymi atrybutami. Musimy jednak pamiętać, iż korzystając z funkcji GetDCO lub ReleaseDCO (służących do pobierania i usuwania kontekstu urządzenia z okna), uchwyty te są przyłączane i odłączane od klasy CDC. Zdolność klasy CDC do przyłączania i rysowania w kontekście urządzenia może zostać zademonstrowana na prostym przykładzie. Każde okno posiada własny kontekst pokrywający jego obszar roboczy, włączając w to pulpit pokrywający cały ekran. Do obecnej chwili zajmowaliśmy się aplikacjami, które zachowywały się bardzo zdyscyplinowanie. Teraz jednak postąpimy bardziej brawurowo, by zaprezentować moc i łatwość użycia kontekstu urządzenia. Uchwycimy kontekst urządzenia pulpitu i będziemy na nim bezpośrednio rysować. Jedyną szkodą, jaką możemy w ten sposób wyrządzić, jest chwilowe zaśmiecenie pulpitu, który jednakże zostanie odświeżony po zamknięciu programu. Przeprowadzając poniższą procedurę, utworzymy aplikację opartą o okno dialogowe, chwytającą kontekst urządzenia. Funkcje rysujące CDC Klasa CDC implementuje wszystkie dostępne w Windows funkcje rysujące. Każda z tych implementacji wywołuje korespondującą funkcję rysującą Win32 niższego poziomu (w GDI.DLL), przekazując obiektowi CDC uchwyt osadzonego kontekstu urządzenia (m_hDC lub m_hAttribDC). Tworzenie aplikacji chwytającej kontekst urządzenia 1. Kliknij pozycję New z menu File. 2. W oknie dialogowym New wybierz kartę Projects, wyświetlającą typy projektów. 3. Wybierz MFC AppWizard i wpisz nazwę projektu DCDraw. 4. Kliknij OK, otwierając w ten sposób okno dialogowe l kroku AppWizard. 5. Spośród typów aplikacji wybierz Dialog Based Application. 6. Kliknij Finish, po czym wyświetlone zostaną informacje dotyczące szkieletu aplikacji. 7. Kliknij OK, a AppWizard wygeneruje konieczne pliki należące do projektu. Mamy zatem szkielet aplikacji utworzonej w oparciu o architekturę okna dialogowego. Dodamy teraz zwykły przycisk oraz funkcję obsługi kliknięcia, by zaimplementować kod chwytający kontekst urządzenia. 362 Poznaj Visual C++ 6 Plik ReadMe.txt generowany przez AppWizard AppWizard generuje plik ReadMe.txt umieszczony w katalogu nowego projektu. Ten plik tekstowy objaśnia zadania każdego z utworzonych modułów źródłowych, wykonywane w aplikacji. Utworzenie przycisku 1. Otwórz panel ResourceView w widoku Project Workspace. 2. Klinij znak plusa, by rozwinąć drzewo zasobów projektu. 3. Kliknij znak plusa obok pozycji Dialog otwierając okno dialogowe zasobów. 4. Kliknij dwukrotnie IDD_DCDRAW_DIALOG, co rozpocznie edycję głównego okna dialogowego aplikacji. 5. Kliknij widoczny pośrodku napis TODO: Place Dialog Controls Herę, a następnie usuń go, używając klawisza Delete. 6. Z paska narzędziowego Controls wybierz przycisk i umieść go na szablonie okna, a następnie powiększ jego wymiary. 7. Naciśnij Alt+Enter aby wyświetlić okno dialogowe właściwości przycisku. Wpisz w polu Caption nagłówek: Draw!. 8. Zmień identyfikator przycisku na IDC_DRAWIT. 9. Naciśnij Enter, by zamknąć okno właściwości. Mając już w oknie umieszczony przycisk, musimy utworzyć funkcję obsługi zdarzenia kliknięcia tego przycisku. Utworzenie za pomocą CIassWizard funkcji obsługi BN_CLICKED 1. Kliknij przycisk umieszczony w oknie dialogowym. 2. Naciśnij Ctri+W uruchamiając w ten sposób CIassWizard. 3. Powinna zostać otwarta karta MFC CIassWizard Message Maps. Powinna być również zaznaczona pozycja IDC_DRAWIT. Jeśli tak nie jest, wybierz tę pozycję z listy Object IDs. 4. Kliknij dwukrotnie komunikat BN_CLICKED z listy Messages, czym spowodujesz dodanie nowej funkcji obsługi. 5. Kliknij OK w oknie dialogowym Add Member Function, przez co zatwierdzisz nazwę funkcji: OnDrawit. 6. Kliknij przycisk Edit Code, po czym możesz przystąpić do edycji kodu obsługującego kliknięcie przycisku. Rysowanie w kontekście urządzenia 363 Możemy teraz zaimplementować kod chwytający kontekst urządzenia i rysujący w nim, wewnątrz funkcji obsługi kliknięcia przycisku, wzorując się na listingu 15.1. Skrót dodający funkcję obsługi komunikatu BN_CLICKED Szybszym sposobem dodania tej funkcji obsługi jest dwukrotne kliknięcie przycisku w edytorze zasobów. Powoduje to dodanie kodu funkcji i pozycji mapy komunikatów lecz pomija pierwsze cztery kroki kreatora CIassWizard i otwiera kod w oknie edytora. Listing 15.1. LST16_1.CPP - rysowanie w kontekście urządzenia pulpitu 1 void CDCDrawDlg::OnDrawit() 2 { 3 // TODO: Dodaj kod obsługi ... 4 5 // ** Pobranie wskaźnika pulpitu 6 'CWnd* pDeskTop = GetDesktopWindow(); O 7 8 // ** Pobranie wskaźnika do kontekstu urządzenia 9 CDC* pDC = pDeskTop->GetWindowDC(); © 10 11 // ** Pętla poprzez 300 pikseli w poziomie 12 for(int x=0;x<300;x++) 13 ( 14 // ** Pętla przez 300 pikseli w pionie 15 for(int y=0;y<300;y++) 16 { 17 // ** Ustalenie odmiennego koloru dla każdego piksela 18 pDC->SetPixel(x,y,x*y); © 19 ) 20 } 21 pDeskTop->ReleaseDC(pDC); 22 } O W tej linii ustalone zostaje okno pulpitu (lub cały ekran). © Ta linia odnajduje kontekst urządzenia pulpitu. © Każdy piksel wewnątrz wyznaczonego prostokąta przyjmuje inny kolor, wyliczony w oparciu o współrzędne. 364 Poznaj Visual C++ 6 Na powyższym listingu w linii 6 pobraliśmy wskaźnik do okna głównego pulpitu Windows, stosując w tym celu funkcję GetDesktopWindow(). Z tego wskaźnika cwnd możemy uchwycić kontekst urządzenia okna głównego używając funkcji GetWindowDC (), co widać w linii 9. Linie od 12 do 20 są odpowiedzialne za utworzenie efektu artystycznego, widocznego na rysunku 15.1. Linie od 9 do 12 powtórzone są w pętli for przez 300 pikseli w poprzek oraz 300 pikseli w dół dla współrzędnych x oraz y. W wywoływanej dla każdego piksela funkcji SetPixel () pierwsze dwa parametry stanowią jego współrzędne (piksel O, O leży w lewym górnym rogu ekranu). Trzecim parametrem jest kolor piksela wyrażony jako odwołanie (omówione szerzej w rozdziale 16 „Stosowanie piór i pędzli"). Kolor ten zależy od współrzędnych x i y danego piksela, w wyniku czego otrzymujemy zaskakująco piękny, kolorowy efekt określany mianem moire (mora). Na koniec musimy zwolnić kontekst urządzenia, stosując funkcję ReleaseDC (), wywołaną linii 21. Zjawisko mory Mora powstaje w wyniku nakładania się dwóch wzorów o podobnym układzie i miejscach przezroczystych. Zostało to zaobserwowane w bardzo cienkiej, mokrej tkaninie jedwabnej o nazwie moire, stąd nazwa zjawiska. Podobny efekt można uzyskać obracając względem siebie dwa kawałki tkaniny. Interferencja taka stanowi podstawę holografii. :jgTx| ^^^^JliBiŁ. ^ . , . ^ |lS S? qit|GelDesktopWindow~^ m w ReleaseDC (pDC); Rysunek 15.1. Uchwycenie i rysowanie w kontekście urządzenia pulpitu Rysowanie w kontekście urządzenia 365 Ponieważ program DCDraw jest tak nonszalancki w działaniu, rysując bezpośrednio na pulpicie, musimy naprawić tego skutki i oczyścić pulpit. Za pomocą funkcji obsługi OnDestroyO możemy przechwycić komunikat Windows WM_DESTROY. Funkcja OnDe-stroyO wywoływana jest w momencie destrukcji okna dialogowego, w związku z tym jest dobrym miejscem na umieszczenie kodu czyszczącego. Dodanie funkcji obsługi komunikatu WM_DESTROY 1. Otwórz panel ClassView w Project Workspace. 2. Rozwiń drzewo klas DCDraw, klikając znak plusa. 3. Kliknij prawym klawiszem pozycję CDCDrawDlg, wywołując w ten sposób menu skrótów. 4. Wybierz opcję Add Windows Message Handler. 5. Wybierz komunikat WM_DESTROY z listy New Windows Messages/Events. 6. Kliknij przycisk Add and Edit, dodając tym samym funkcję obsługi do klasy CDCDrawDlg i przechodząc do edycji implementacji. Teraz możemy dodać kod oczyszczający pulpit do funkcji obsługi OnDestroyO. Aby uzyskać wyczyszczenie pulpitu, musimy nakazać oknu pulpitu odświeżenie oraz wydanie rozkazu odświeżenia zawartości wszystkim oknom potomnym (aplikacjom). Kod wykonujący to zadanie zawarty jest na listingu 15.2. W linii 8 funkcja składowa okna pulpitu RedrawWin-dow () (odnalezionego przez wywołanie funkcji GetDesktopWindow ()) wymusza odświeżenie zawartości pulpitu. Funkcji tej przekazane są trzy parametry. Pierwsze dwa są wskazaniami odświeżanego prostokąta i obszaru. Na podstawie przekazanych w obu parametrach wartości NULL, funkcja RedrawWindow () słusznie zakłada, iż odświeżony ma być cały obszar pulpitu. Trzeci parametr składa się z kilku znaczników: RDW_ERASE (wymazujący wszystko), RDW_INVALIDATE (nakazujący odświeżenie), RDW_ALLCHILDREN (nakazujący odświeżenie okien wszystkim aplikacjom) oraz RDW_ERASENOW (nakazujący natychmiastowe wykonanie). Listing 15.2. LST16_2.CPP - odświeżanie pulpitu i okien wszystkich aplikacji przez funkcję RedrawWindow() 1 void CDCDrawDlg::OnDestroy() 2 { 3 CDialog::OnDestroy(); 4 5 // TODO: Add your message handler code here 6 7 // ** Odświeżenie pulpitu i wszystkich okien potomnych 8 GetDesktopWindow()->RedrawWindow(NULL,NULL, O 9 RDW_ERASE+RDW_INVALIDATE+ 10 RDW_ALLCHILDREN+RDW_ERASENOW) ; 11 } 366 Poznaj Visual C++ 6 O Wymuszenie odświeżenia zawartości całego pulpitu Windows oraz okien wszystkich aplikacji Po skompilowaniu i uruchomieniu programu z uwzględnieniem powyższych zmian otrzymamy kolorowy prostokąt, jak na rysunku 15.1. Oczywiście klasa kontekstu urządzenia zawiera o wiele więcej funkcji rysujących. Są one dalece bardziej wyrafinowane niż SetPixel (). Jednak istnieje zbyt wiele złożonych tematów, by opisywać je w tym miejscu. Uczynimy to w szerokim zakresie w rozdziałach 16 i 17. PATRZ TAKŻE « O rysowaniu z wykorzystaniem piór i pędzli czytaj w rozdziale 16. • O pisaniu tekstu w kontekście urządzenia czytaj w rozdziale 17. Stosowanie kontekstu urządzenia Client Klasa cćlientDC automatyzuje użycie funkcji GetDC oraz ReleaseDCO. Podczas konstruowania obiektu cćlientDC przekazujemy wskaźnik okna jako parametr, a klasa używa tego wskaźnika do przyłączenia kontekstu urządzenia okna. Podczas destrukcji klasy wywołuje ona automatycznie funkcję ReleaseDCO, nie musimy więc zawracać sobie tym głowy. Client odwołuje się do obszaru roboczego okna. Istnieje natomiast korespondująca klasa cwindowDC, umożliwiająca dostęp zarówno do paska tytułowego, jak i ramki okna. Stosuje się ją jednak rzadko, gdyż „przyzwoite" aplikacje działają tylko w obszarze klienckim (roboczym). Zmienimy teraz projekt, by zastąpić wskaźnik CDC obiektem cćlientDC. Jeśli klik-niemy dwukrotnie pozycję funkcji OnDrawit () w panelu ClassView, edytor wykona skok bezpośrednio do kodu funkcji obsługi przycisku. Wprowadzimy zmiany w funkcji OnDrawit () według listingu 15.3. Spowoduje to użycie jako pola rysunkowego kontekstu urządzenia okna dialogowego, zamiast okna pulpitu. Aby to osiągnąć, przekazujemy obiektowi cćlientDC, dIgDC wskaźnik this (do przechowującej go klasy) do własnego okna dialogowego. Ponieważ okno dialogowe jest formą cwnd, obiekt dIgDC zawiera obecnie uchwyt kontekstu urządzenia tego okna. W liniach 9 - 17 następuje procedura rysowania jak poprzednio, z jedną różnicą; w linii 15 bowiem obiekt dIgDC stosuje wywołanie obiektu zamiast wskaźnika do SetPixel () .Warto również zauważyć, iż na końcu funkcji OnDrawit () nie zostaje wywołana ReleaseDC (). Zamiast tego, uchwyt kontekstu urządzenia będzie automatycznie zwalniany przez destruktor cćlientDC w chwili destrukcji obiektu dIgDC, na zakończenie działania funkcji. Rysowanie w kontekście urządzenia 367 Listing 15.3. LST16_3.CPP - zastosowanie klasy CCIientDC do uchwycenia kontekstu urządzenia okna dialogowego 1 void CDCDrawDlg::OnDrawit() 2 ( 3 // TODO: Add your control notification handler code 4 5 // ** Skonstruuj DC klienta z okna dialogowego 6 CCIientDC dlgDC(this); O 7 8 // loop through 300 pixels horizontally 9 for(int x=0;x<300;x++) 10 ( 11 // loop through 300 pixels vertically 12 for(int y=0;y<300;y++) 13 ( 14 // ** set each pixel to a different color 15 dlgDC.SetPixel(x,y,x*y); 16 • } 17 } 18 } O Skonstruuj kontekst urządzenia, aby użyć prawidłowego obszaru klienta w oknie dialogowym. Po skompilowaniu aplikacji z wprowadzonymi zmianami i jej uruchomieniu zobaczymy, że rysowanie odbywa się tylko w kontekście urządzenia okna dialogowego, kiedy użyty zostanie przycisk Draw!, co widać na rysunku 15.2. Wymuszanie odświeżania okna Komunikat WM_PAINT jest zwykle wysyłany wtedy, gdy pewna część okna wymaga uaktualnienia. Jednakże stosując funkcję Redrawwindow () klasy CWnd można wymusić wysłanie do okna komunikatu WM_PAINT. Parametry tej funkcji określają obszar, który ma ulec odświeżeniu, a także decydują, czy ma nastąpić wymazanie tła. Stosowanie kontekstu urządzenia Paint Klasa cpaintDC jest specjalną klasą osłonową kontekstu urządzenia, która wspomaga obsługę komunikatu WM_PAINT. Komunikat WM_PAINT wysłany zostaje do okien, gdy 368 Poznaj Visual C++ 6 część lub całość ich obszaru jest odsłonięta przez inne okno. Nakazuje on aplikacji odma- lowanie odsłoniętego obszaru. Rysunek 15.2. Przechwycenie kontekstu urządzenia okna dialogowego przez CCIientDC Zamiast odmalowywania całości okna za każdym razem, gdy jego część zostanie od- słonięta, Windows przekazuje informacje o współrzędnych odsłoniętego kawałka. Możemy użyć tych informacji do odmalowania tylko tej części okna, oszczędzając w ten sposób cenny czas procesora, który byłby stracony na wykonywanie czynności dla użytkownika niezauważalnych. Jeśli próbowalibyśmy rysować poza wyznaczonym prostokątem, nie stałoby się nic, ponieważ kontekst urządzenia powoduje również okrajanie obszaru roboczego do obszaru właśnie tego prostokąta. Jednakże całkowite poleganie na procedurze obcinania nie jest mądrym posunięciem, gdyż cały kod rysujący będzie wykonywany non stop, spowalniając w znacznym stopniu działanie całego komputera. Czasami jednak jest to nieuniknione - odrysowując złożony tekst lub diagram, zbyt trudno ocenić, w którym miejscu przerwać tekst lub linię. W tej sytuacji możemy pozwolić Windows zając się obcinaniem. Próbną aplikację możemy przekształcić w ten sposób, by rysowanie odbywało się z funkcji OnPaint () i tylko na odsłoniętym obszarze. Klikamy zatem dwukrotnie pozycję funkcji OnPaint () na panelu ClassView. Przechodzimy w ten sposób do edycji tej funkcji. Zauważmy, iż nie musimy tworzyć tej funkcji poprzez CIassWizard. Zawdzięczamy to kreatorowi AppWizard, który automatycznie umieścił jej kod w szkielecie aplikacji. Treść funkcji widoczna jest na listingu 15.4. Linię 24 wywołującą funkcję OnDrawit (), musimy jednak dopisać sami. Na listingu znajdujemy również inne ciekawostki utworzone przez AppWizard. W linii 3 wywołana zostaje funkcja lslconic() zwracająca TRUE, gdy okno aplikacji jest zminimalizowane (czyli wyświetlone na pasku zadań). Jeśli tak jest, ikona aplikacji zostaje umieszczona wewnątrz prostokąta wyświetlonego w pasku zadań poprzez linię 18. Możemy użyć wartości FALSE w celu zaimplementowania kodu OnPaint () i wywołania OnDrawit () w linii 24. Rysowanie w kontekście urządzenia______________________ __ 369 Listing 15.4. LST16_4.CPP - modyfikacja funkcji OnPaint () 1 void CDCDrawDlg::OnPaint() 2 { 3 if (IsIconicO) 4 { 5 CPaintDC dc(this); // kontekst urządzenia do rysowania 6 7 SendMessage(WM_ICONERASEBKGND, O (WPARAM) dc.GetSafeHdcO, 0) ; 8 9 // Wy środkowa n i e ikony w prostokącie klienta 10 int cxlcon = GetSystemMetrics(SM_CXICON) ; 11 int cylcon = GetSystemMetrics(SM_CYICON); 12 CRect rect; 13 GetCIientRect(Srect); 14 int x = (rect.WidthO - cxlcon + l) / 2; 15 int y = (rect.Height() - cylcon + l) / 2; 16 17 // Narysowanie ikony 18 dc.DrawIcon(x, y, m_hlcon); @ 19 20 } 21 else 22 ( 23 // ** Wywołanie funkcji rysującej 24 OnDrawit();© 25 } 26 } O Usunięcie tła zminimalizowanego okna © Narysowanie ikony pośrodku zminimalizowanego okna © Dodatkowa linia, wywołująca funkcję wypełniającą Wprowadźmy teraz zmiany do funkcji OnDrawitO, które spowodują użycie CPa-intDC. Zmiany te ukazane są na listingu 15.5. Zauważmy, iż w linii 6 klasa CPaintDC użyta jest do skonstruowania obiektu paintDC z okna dialogowego. Mechanizm pracuje poprawnie jedynie wtedy, gdy okno odpowiada na komunikat WM_PAINT. W tym przypadku wywołujemy tę funkcję z OnPaint (), wszystko zatem jest w porządku. Ponieważ jest to wciąż kod obsługi przycisku, możemy go wywoływać klikając przycisk Drawit, lecz 370_____________________________________Poznaj Visual C++ 6 obecnie nie wywoła to żadnego efektu, gdyż okno nie posiada legalnego obszaru - jest całkowicie odcięte od funkcji rysującej. Następna zmiana tkwi w linii 9, zostaje zadeklarowany wskaźnik RECT i ustawiony na adres prostokąta rcPaint, po czym zapisany w składowej obiektu paintDC. Prostokąt ten zawiera obszar do odmalowania i użyty zostaje w liniach 12 i 15, w związku z czym pętla wywoływana jest tylko dla prostokąta nieważnego. Podnosi to znacząco prędkość rysowania, gdyż tylko niewielka część okna wymaga odmalowania. SetPixel () spowalnia działanie aplikacji, gdy jest wywoływana wielokrotnie. Obszar o wymiarach 300x300 pikseli wywołuje tę funkcję 90 000 razy! Jeżeli natomiast odsłonięty obszar ma wymiary 30x50 pikseli, SetPiksel () będzie wywoływana jedynie 1500 razy, co oznacza 60-krotne skrócenie procesu. Jest więc o co walczyć. Zauważmy jeszcze jedną wprowadzoną zmianę. Otóż SetPiksel () jest obecnie wywoływana z obiektu paintDC w linii 18. Funkcja zaimplementowana jest w klasie CDC, w związku z czym wszystkie funkcje rysujące dostępne są ze wszystkich wyspecjalizowanych wyprowadzanych klas kontekstu urządzenia. Listing 15.5. LST16_5.CPP - użycie klasy CPaintDC do obsługi żądania WM_PAINT l void CDCDrawDlg::OnDrawit() 2 ( 3 // TODO: Add your control notification handler code 4 5 // ** Skonstruowanie kontekstu urządzenia okna dialogowego 6 CPaintDC paintDC(this); 7 8 // ** Utworzenie wskaźnika prostokąta 9 RECT* pRect = SpaintDC.m ps.rcPaint; 10 11 // ** Pętla wykonywana w liniach poziomych 12 for(int x=pRect->left; x < pRect->right ;x++) 13 { 14 // ** Pęt-Za wykonywana w liniach pionowych 15 for(int y=pRect->top; y < pRect->bottom ;y++) 16 ( 17 // ** Nadanie każdemu pikselowi innego koloru 18 paintDC.SetPixel(x,y,x*y) ; 19 } 20 l 21 } Wygenerujemy i uruchomimy aplikację z wprowadzonymi zmianami i zaobserwujmy zachowanie się kontekstu urządzenia. Najpierw powinniśmy ujrzeć okno dialogowe z wielokolorowym tłem i widocznymi przyciskami. Podczas wyświetlania okna wysłany Rysowanie w kontekście urządzenia 371 zostaje komunikat WM_PAINT w celu całkowitego narysowania okna, następnie zaś rysowane są przyciski, przesłaniające wymalowany pod nimi obszar. By zaobserwować działanie tego komunikatu w praktyce, przesłonimy połowę okna dialogowego innym oknem (na przykład Kalkulatora Windows). Uruchomić go możemy klikając przycisk Start, a następnie przechodząc przez Programy do karty Akcesoria, skąd wybieramy Kalkulator. Efekt działania widać na rysunku 15.3. Rysunek 15.3. Eksperymentowanie z komunikatem WM_PAINT poprzez odsłanianie okna Gdy ustawimy okno Kalkulatora tak, by przesłaniało w połowie okno dialogowe DCDraw, kukniemy wewnątrz okna dialogowego, by wysunąć je na pierwszy plan. Zobaczymy, iż odsłonięta jego część została wypełniona. Zajmuje to bardzo krótką chwilę. Teraz ponownie klikamy Kalkulator i przesuwamy go nieco w lewo i w górę. Kolejna odsłonięta część okna dialogowego powinna zostać wypełniona trochę szybciej. Wykonując kilkakrotnie te czynności zauważymy, iż wypełnianie odbywa się coraz szybciej. Gdy Kalkulator będzie przesłaniał już tylko niewielki skrawek okna dialogowego w jego górnym lewym rogu, klikamy ponownie okno dialogowe i próbujemy zaobserwować, jak długo trwa jego odświeżanie - zdecydowanie szybciej. Dzieje się tak, ponieważ odsłonięty obszar jest bardzo mały i odświeżaniu ulega Jedynie ten kawałek. Funkcja SetPiksel () wywoływana jest mniej intensywnie, dlatego cały proces przebiega o wiele szybciej. Możemy eksperymentować dalej przesuwając okna względem siebie, obserwując działanie komunikatu WM_PAINT. Na komunikat ten reagować muszą wszystkie aplikacje Windows, lecz ponieważ najczęściej zapewniają to klasy widoku lub obiektu sterującego, nie musimy się tym przejmować. Przypomnijmy sobie teraz składową m_ps klasy cpaintDC. Związana jest ze strukturą PAINTSTRUCT opisującą prostokąt rcPaint i określającą obszar wymagający odmalowania. Struktura ta zawiera kilka użytecznych składowych. Definicja struktury PAINTSTRUCT wygląda następująco: typedef struct tagPAINTSTRUCT { HDC hdc ; 372_____________________________________Poznaj Visual C++ 6 BOOL f E ras e ; RECT rcPaint ; BOOL fRestorę ; BOOL fIncUpdate ; BYTE rgbReserved[16] ; } PAINTSTRUCT ; Składowa hdc jest uchwytem podstawowego kontekstu urządzenia GDI. Znajdujemy tu również znacznik fErase, którego wartość Windows ustawia jako TRUE, gdy zachodzi potrzeba wymazania tła przed rozpoczęciem rysowania. Ze składową rcPaint spotkaliśmy się na listingu 15.5. Ostatnie trzy składowe określone w dokumentacji Microsoftu są jako zarezerwowane przez Windows, w związku z tym najlepiej zostawić je w spokoju. Zastanawiać nas może, skąd pochodzą te wszystkie informacje, które przekazywane są obiektowi CPaintDC. Odpowiedzią jest funkcja BeginPaintO klasy CWnd. Pamiętajmy, iż klasa CDialog, jak większość klas obiektów sterujących i widoków, wyprowadzona jest właśnie z klasy CWnd. Dlatego też cała funkcjonalność okna dostępna jest dla tych klas i wszystkie one stosują funkcję BeginPaint () dla powołania struktury PAINTSTRUCT podczas deklarowania obiektu CPaintDC. Gdy obiekt CPaintDC ulega destrukcji, wywoływana jest funkcja klasy CWnd EndPaint () w celu powiadomienia Windows o przetworzeniu komunikatu WM_PAINT i wykonaniu odświeżenia uszkodzonego obszaru okna. Obsługa komunikatu WM_ERASEBKGND Inną metodą pozwalającą podjąć decyzję o wymazaniu tła jest dodanie funkcji obsługi komunikatu Windows WM ERASEBKGND. Nowa funkcja obsługi, OnEraseBkGnd (), wywoływana będzie za każdym razem, gdy zajdzie potrzeba wymazania tła. Wskaźnik do odpowiedniego obiektu kontekstu urządzenia (obiektu coc) przekazywany jest funkcji OnEraseBkGnd () jako jej pierwszy parametr. Stosowanie kontekstu urządzenia pamięci Kontekst urządzenia pamięci jest kontekstem bez przydzielonego urządzenia. Może wydać się to dziwne, lecz jest to bardzo pożyteczne rozwiązanie. Normalnie używamy kontekstu przyłączonych urządzeń, by kopiować i wklejać fragmenty obrazu na ekranie. Możemy jednak utworzyć kontekst urządzenia pamięci kompatybilny z kontekstem urządzenia wyświetlającego. Można go wykorzystać do skopiowania obrazu do pamięci, gdzie nie będzie wyświetlany, a następnie przesłać z powrotem do kontekstu urządzenia wyświetlającego. Nie istnieje żadna klasa osłonowa dla kontekstu pamięci. Nie jest ona zresztą potrzebna, gdyż CDC doskonale radzi sobie sama. Ponieważ jest to zwykła klasa kontekstu urządzenia, możemy jej użyć do rysowania w sposób normalny, z tą różnicą, że rysowanie Rysowanie w kontekście urządzenia 373 odbędzie się w pamięci, w związku z czym użytkownik nie będzie widział tworzonego obrazu do momentu przesłania go do kontekstu urządzenia wyświetlającego. Wymiary bitmapy w kontekście urządzenia pamięci Bitmapa znajdująca się w kontekście pamięci może być większa lub mniejsza niż w kontekście określonego urządzenia. Tworząc dużą bitmapę można osiągnąć znacznie wyższą rozdzielczość, niż na standardowym ekranie. Należy jednak pamiętać, iż duża bitmapa wymaga użycia większej ilości pamięci systemowej. Jeśli bitmapa przekroczy objętością rozmiary pamięci RAM, komputer znacznie spowolni działanie poprzez intensywne korzystanie z pamięci wirtualnej dla uzupełnienia braków RAM. Pamięć wirtualna jest powolna z uwagi na fakt, iż następuje ciągła wymiana danych pomiędzy dyskiem i pamięcią. Możemy zatem utworzyć kontekst urządzenia pamięci, dzięki któremu rysunek wykonany zostanie w pamięci zamiast na ekranie. Po wykonaniu tego rysunku, za pomocą funkcji BitBitO możemy skopiować go na ekran. Modyfikacje dokonane na listingu 15.6 są obszerne, gdyż utworzyć musimy nie tylko kontekst urządzenia pamięci, lecz także bitmapę dla niego. Inaczej niż kontekst urządzenia wyświetlającego, który jest powiązany z oknem, kontekst pamięci nie jest automatycznie wyposażany w kompatybilną z ekranem bitmapę. Kontekst urządzenia rysującego zamieniony zostaje z powrotem na kontekst obszaru roboczego, a kontekst pamięci zadeklarowany jest w linii 9 listingu 15.6 jako obiekt memDC. Funkcja składowa kontekstu urządzenia CreateCompatibleDC () wywołana jest w linii 10. Funkcji tej przekazano wskaźnik obiektu clientDC, który posłuży jako wskazówka dotycząca wymiarów oraz innych atrybutów podczas tworzenia kontekstu urządzenia pamięci, kompatybilnego z kontekstem okna. W linii 17 zapisana jest deklaracja bitmapy, która użyta będzie przez kontekst pamięci do wykonania rysunku. Bitmapa owa utworzona zostaje przez funkcję składową Cre-ateCompatibleBitmap () w linii 18. Także tej funkcji przekazany zostaje obiekt clientDC dla wskazania jej, w którym kontekście użyta będzie bitmapa. Jest to również wykorzystane do określenia liczby kolorów, jaką bitmapa musi wykorzystywać. Szerokość i wysokość prostokąta roboczego ustalone poprzez linię 14 przekazane zostają jako parametry określające wymagane wymiary bitmapy. W linii 22 bitmapa zostaje umieszczona w kontekście urządzenie przez funkcję składową SelectObjectO. Każda czynność rysowania wykonana w kontekście urządzenia pamięci odbywać się będzie z wykorzystaniem dołączonej bitmapy. Możemy teraz utworzyć tło w sposób podobny do dotychczasowych, wszystkie czynności są jednak wykonywane w pamięci zamiast na ekranie. W linii 36 następuje wywołanie funkcji BitBit (), która kopiuje bitmapę do kontekstu ekranu. Funkcja ta jest funkcją kopiującą obraz, a nazwę swą przejęła ze starego terminu bit blitting oznaczającego układ scalony umożliwiający szybkie kopiowanie zawartości pamięci z jednego miejsca w inne. 374___________________ __ ___ __ __ __ Poznaj Visual C++ 6 Pewne karty graficzne wykonują to zadanie po wywołaniu funkcji BitBit (), jednakże zajmuje się tym głównie procesor. BitBit () kopiuje dane z memDC do clientDC, gdy przekażemy jej szerokość, wysokość i punkt początkowy jako parametry. Interesującym parametrem dostarczanym tej funkcji jest znacznik SRCINVERT, który nakazuje jej odwrócenie kolorów obrazu. Powstaje w ten sposób odmienny zestaw kolorów w oknie docelowym, co udowadnia fakt rysowania w pamięci i kopiowania obrazu do kontekstu okna dialogowego. Możemy stosować zamiennik tego znacznika SRCCOPY, gdy obraz skopiowany z kontekstu pamięci ma pozostać niezmieniony. Wprowadźmy zatem zmiany widoczne na listingu 15.6 i usuńmy wywołanie funkcji OnDrawitO z funkcji obsługi OnPaintO (w przeciwnym wypadku uzyskamy bardzo dziwne efekty). Następnie skompilujemy i uruchomimy aplikację. Po kliknięciu przycisku Drawit zauważymy krótkie opóźnienie związane z wykonywaniem rysunku w pamięci, po czym zostanie on skopiowany z użyciem odwróconych kolorów do kontekstu urządzenia okna dialogowego. Listing 15.6. LST16_6.CPP - rysowanie w kontekście urządzenia pamięci 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void CDCDrawDlg::OnDrawit() ( // TODO: tu dodaj własny kod obsługi komunikatu II ** Utworzenie kontekstu paintDC okna dialogowego CCIientDC clientDC(this); // ** Utworzenie kompatybilnego kontekstu pamięci C DC memDC; memDC.CreateCompatibleDC(SclientDC) ; O // ** Ustalenie prostokąta roboczego CRect rcdient; GetCIientRect(srcCIient) ; // ** Utworzenie kompatybilnej bitmapy CBitmap memBitmap; memBitmap.CreateCompatibleBitmap(&clientDC, @ rcCIient.Width () , rcdient .Height () ) ; // ** Wybranie bitmapy dla kontekstu pamięci memDC.SelectObject(SmemBitmap) ; // ** Pętla wykonywana przez linie poziome prostokąta roboczego for(int x=0; x < rcdient .Width () ;x++) { Rysowanie w kontekście urządzenia 375 27 // ** Pętla wykonywana przez linie pionowe prostokąta II roboczego 28 for(int y=0; y < rcCIient.Height() ;y++) 29 ( 30 // ** Nadanie każdemu pikselowi innego koloru 31 memDC.SetPixel(x,y,x*y); © 32 } 33 ) 34 • 35 // ** Skopiowanie obrazu z pamięci do kontekstu urządzenia II okna dialogowego 36 clientDC.BitBlt(0,0, O 37 rcdient.WidthO , rcCIient .Height () , 38 &memDC,0,0,SRCINVERT); 39 } O Utworzony zostaje kontekst urządzenia dla pamięci kompatybilny z kontekstem urządzenia wyświetlającego. @ Bitmapa musi być również kompatybilna z kontekstem wyświetlania. © Rysunek w pamięci jest już wykonany. Poza tym nie dzieje się nic. O Na koniec cały obraz zostaje skopiowany z pamięci na ekran, dzięki czemu staje się widoczny dla użytkownika. Stosowanie trybów odwzorowania Tryby odwzorowania są kolejnym użytecznym narzędziem dostarczanym przez konteksty urządzenia. Ich stosowanie umożliwia rysowanie na ekranie lub drukarce przy użyciu w celach pomiarowych centymetrów lub cali zamiast pikseli. Zapewniają one również dokładność odwzorowania skali. Układ współrzędnych i osie Każda funkcja rysująca kontekstu urządzenia wymaga parametrów określających pozycję w układzie współrzędnych. Układ współrzędnych jest dwuwymiarową powierzchnią, opartą na geometrii kartezjańskiej. Każdy punkt posiada określone wartości obu współrzędnych, przekazywane funkcji rysującej jako odrębne parametry. Gdy stosowany jest tryb MM_TEXT, niższe wartości współrzędnej pionowej znajdują się u góry ekranu, a dla osi poziomej po lewej stronie. Dostępne są również inne tryby, które powodują umieszczenie niskich wartości osi pionowej u dołu ekranu. W przypadku osi poziomej układ pozostaje niezmienny. 376 Poznaj Visual C++ 6 Podczas korzystania z trybów odwzorowania spotkamy się z pojęciami jednostek lo- gicznych oraz jednostek urządzenia. Mówiąc prościej, jednostką urządzenia jest piksel lub inna najmniejsza jednostka, jaką dane urządzenie jest w stanie zaprezentować. Jednostkami logicznymi natomiast mogą być cale, centymetry lub jednostki do wyliczania wielkości czcionki. Domyślnie kontekst urządzenia używa trybu odwzorowania zwanego MM_TEXT. Ta dziwnie brzmiąca nazwa oznacza po prostu, że jedna jednostka logiczna odpowiada jednej jednostce urządzenia. W związku z tym, nie zachodzi żadna konwersja i współrzędne określane poleceniami rysowania, będą odwzorowane przez adekwatną liczbę pikseli. Istnieje kilka trybów odwzorowania, a są one wymienione w tabeli 15.1. Tabela 15.1. Wspierane tryby odwzorowania Znacznik trybu Jednostka logiczna równa jest MM_TEXT jednemu pikselowi MM_LOMETRIC 0,1 milimetra MMJHMETRJC 0,01 milimetra MM_LOENGLISH 0,01 cala MM_HIENGLISH 0,001 Cala MM_TW l P S 1/440 cala lub 1/20 punktu (w przypadku czcionek) MM_ISOTROPIC jednostce ustalonej przez użytkownika, lecz zawsze przy zachowa- niu proporcji pomiędzy X i Y MM_ANISOTROPIC jednostce ustalonej przez użytkownika Powyższe tryby odwzorowania ustawiane są przez funkcję kontekstu urządzenia Set- MapMode (), która po prostu pobiera jeden spośród tych znaczników. Po dokonaniu ustalenia trybu, koordynaty, jakie przekażemy funkcjom rysującym, skonwertowane zostaną do postaci jednostek urządzenia (lub pikseli) dla każdego z trybów, poprzez wewnętrzne odwzorowanie GDI. Jeśli zatem wybierzemy tryb MM_LOMETRIC i przekażemy funkcji rysującej wartość 100, tryb odwzorowania wyliczy, że wskazaną wartością jest 100X0,1 milimetra, czyli l centymetr. Na tej podstawie z kolei, skalkulowana zostanie liczba pikseli ekranu lub drukarki, jaką urządzenie użyje do odwzorowania l centymetra. Znaczniki MM_ISOTROPIC oraz MM_ANISOTROPIC umożliwiają ustalenie skali za pomocą funkcji SetWindowExt () oraz SetViewPortExt (). Do dyspozycji mamy również dwie funkcje SetwindowOrgO i SetViewPortOrg() pozwalające na zmianę punktu początkowego (lub O, 0) układu współrzędnych. Ujemne współrzędne są normalną rzeczą, zwłaszcza gdy zmienimy punkt początkowy. Wykorzystajmy zatem zdobytą wiedzę do utworzenia małego programu typu SDI. Utworzymy szkielet aplikacji, korzystając z procedury opisanej w rozdziale 12. Powinni- Rysowanie w kontekście urządzenia 377 śmy przy tym zaakceptować wszystkie ustawienia domyślne poza wybraniem opcji aplikacji SDI w kroku l i nadaniem jej nazwy MapMode. Klasa widoku MapMode posiada funkcję składową OnDraw (), która jest zwykle wykorzystywana do implementacji kodu rysującego. By przeprowadzić edycję kodu tej funkcji, wykonamy opisaną poniżej procedurę. Odnajdywanie i edycja funkcji OnDraw () 1. Otwórz kartę ClassView w widoku Project Workspace. 2. Kliknij znak plusa, by zobaczyć klasy projektu. 3. Kliknij znak plusa widoczny obok pozycji MapMode View. 4. Kliknij dwukrotnie funkcję OnDrawO klasy widoku i rozpocznij edycję. Nowe linie, jakie umieścimy w funkcji OnDraw (), znajdziemy na listingu 15.7. Po pierwsze, zauważmy, że trybem odwzorowania jest MM_LOMETRIC, co znaczy, że wszystkie wartości współrzędnych podawane są w jednostkach równych 0,1 mm. W linii 10 odnaleziony zostaje prostokąt roboczy przez funkcję GetCIientRect (). Nie ma znaczenia, jakiego trybu .odwzorowania używamy, ponieważ współrzędne zawsze podawane są w pikselach, a zatem w linii 16 użyta jest funkcja kontekstu urządzenia DPtoLP () dla dokonania konwersji na jednostki logiczne. DPtoLp () oznacza Device Points to Logical Points (jednostki urządzenia na jednostki logiczne) i może konwertować obiekty cpoint lub CRect, jeśli przekazane jej zostaną ich wskaźniki. Istnieje również funkcja odwrotna LPtoDP () przeprowadzająca konwersję w przeciwnym kierunku. Pomiędzy liniami 19 i 21 zmienna x wykonuje pętlę w poziomych liniach prostokąta, od lewej do prawej, po dokonaniu konwersji jednostek na logiczne. Pętla przeskakuje każdą setną jednostkę logiczną, co daje nam jeden centymetr przy trybie odwzorowania MM_LOMETRIC. Trzykrotnie wywołana funkcja SetPixel() umieszcza wewnątrz okna znaki w odstępach l cm. Listing 15.7. LST16_7.CPP - zastosowanie trybu odwzorowania MMJLOMETRIC 1 void CMapModeView::OnDraw(CDC* pDC) 2 { 3 CMapModeDoc* pDoc = GetDocument() ; 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: tutaj dodaj kod rysowania dla własnych danych 7 8 // ** Ustalenie trybu odwzorowania na 0,1 mm 9 pDC->SetMapMode(MM_LOMETRIC); O 10 378 Poznaj Visual C++ 6 11 12 CRect rcCIient; 13 GetCIientRect(&rcClient) ; 14 15 16 // ** Konwersja koordynat urządzenia na koordynaty logiczne pDC- >DPtoLP(&rcClient); @ 17 18 // ** Pętla dla każdych 100 jednostek logicznych 19 for(int x=rcClient.TopLeft().x; 20 xSetPixel(x,rcCIient.CenterPoint().y-1,0); © 25 pDC->SetPixel(x,rcCIient.CenterPoint().y,0) ; 26 pDC->SetPixel(x,rcCIient.CenterPoint().y+1,0); 27 } 28 } . O Ustalony zostaje metryczny tryb odwzorowania, w związku z tym jednostka współrzędnych równa jest O, l milimetra. © Funkcja DPtoLP () dokonuje konwersji punktów urządzenia na logiczne (aktualnie O, l mm). © Trzykrotnie wywołana funkcja setpixel () umieszcza małe pionowe znaki. Zauważmy, iż funkcji OnDraw () przekazano wskaźnik kontekstu urządzenia, który używany jest podczas wszystkich operacji rysowania. Szkielet aplikacji przyjmuje ten kontekst automatycznie. W rozdziale 22 zobaczymy jak ten kontekst ustawia ekran lub drukarkę. Gdy skompilujemy i uruchomimy aplikację, powinniśmy ujrzeć rząd regularnie roz- mieszczonych znaków. Jeśli przyłożymy do ekranu miarkę zobaczymy, iż są one w odstę- pach jednego centymetra. Spróbujemy teraz zmienić znacznik MM_LOMETRIC na MM_LOENGLISH. Gdy ponownie skompilujemy i uruchomimy program, znaki będą rozmieszczone w odstępach równych jednemu calowi. Rysowanie w kontekście urządzenia 379 Dokładność odwzorowania na ekranie Odwzorowywanie na ekranie monitora cechuje się matą dokładnością. Na tę sytuację składają się różnice w wymiarach monitorów i ustawieniach szerokości i wysokości obrazu. Drukarki są znacznie dokładniejsze i wiarygodniejsze. Tryby odwzorowania swobodnego skalowania Możemy ustalać własną skalę odwzorowania, co toruje nam drogę do łatwego tworzenia trybów powiększania poprzez zastosowanie trybu MM_ANISOTROPIC. Oznacza to możliwość nadania odmiennych proporcji, w różnych kierunkach. Przykładowo, pies jest istotą anisotropiczną, choć nie zmienia swoich wymiarów tak łatwo, jak robi to tryb odwzorowania. Ten tryb odwzorowania jest potężnym narzędziem, umożliwiającym ustalenie wzajemnej konwersji jednostek logicznych i urządzenia. Listing 15.8 demonstruje taką konwersję, ustalając poprzez funkcję SetViewPort-Ext() wymiary prostokąta roboczego: szerokość na 500 pikseli wysokości. W linii 19 funkcja SetWindowExt () ustala szerokość na 10000 jednostek logicznych, przy ciągle podanej w pikselach wysokości. Pętla w liniach 23-30 rysuje dokładnie 100 znaków w poprzek prostokąta, czyli co 100 jednostek. Jednakże w wymiarze pionowym jednostka logiczna jest równa jednemu pikselowi (MM_TEXT). Jeśli skompilujemy kod ze zmianami wprowadzonymi do funkcji OnDraw (), które widzimy na listingu 15.8, ujrzymy 100 równomiernie rozstawionych znaków, rozmieszczonych wszerz okna. Gdy spróbujemy zmienić jego skalę zauważymy, że znaki są rozciągane lub zwężane, lecz zawsze odstęp pomiędzy nimi wynosi 100. Listing 15.8. LST16_8.CPP - zastosowanie trybu odwzorowania MM_AMISOTROPIC 1 void CMapModeView::OnDraw(CDC* pDC) 2 ( 3 CMapModeDoc* pDoc = GetDocument() ; 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: add draw code for native data here 7 8 pDC->SetMapMode(MM_ANISOTROPIC) ; 9 10 CRect rcdient; 11 GetClientRect(SrcClient) ; 12 13 // ** Ustalenie szerokości urządzenia równej szerokości 14 // ** obszaru roboczego oraz wysokości na 500 pikseli 380___________________ Poznaj Visual C++ 6 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 pDC->SetViewportExt(CSize(rcCIient.BottomRight(). x,500)); // ** Ustalenie szerokości logicznej na 10 000 jednostek // ** l 500 pikseli wysokości pDC->SetWindowExt(CSize(10000,500)) ; pDC->DPtoLP(&rcClient) ; forfint x=rcClient.TopLeft(). x;xSetPixel(x,rcCIient.CenterPoint().y-1,0) ; pDC->SetPixel(x,rcCIient.CenterPoint() .y,0) ; pDC->SetPixel(x,rcCIient.CenterPoint().y+1,0) ; O Tryb MM_ANISOTROPIC pozwala każdej z osi na swobodny wybór sposobu skalowania. @ Funkcja SetViewportExt () ustala szerokość i wysokość urządzenia w pikselach. © Funkcja SetWindowExt() ustala wymiary logiczne okna we współrzędnych logicznych. Tryb odwzorowania MM_ISOTROPIC oznacza nadanie takich samych właściwości skali we wszystkich kierunkach. Możemy używać tych samych funkcji SetViewportExt () oraz SetWindowExt () dla ustalania skali, lecz GDI zawsze dostosowuje odmienne stopnie skali, w związku z czym jednostka logiczna w osi poziomej równa jest jednostce logicznej w pionie. Tryb ten przydaje się, gdy chcemy zapobiec zmianom proporcji rysunku. PATRZ TAKŻE • O tym jak stosować tryby odwzorowania podczas drukowania czytaj w rozdziale 22. Stosowanie funkcji SetWindowExt () i SetViewportExt () Funkcje SetWindowExt () oraz SetViewportExt () działają jedynie w trybach MM_ISOTROPIC i MM_ANISOTROPIC. W pozostałych trybach są ignorowane. Rysowanie w kontekście urządzenia 381 Badanie możliwości urządzenia Kontekst może zostać użyty do zbadania możliwości przyłączonego urządzenia. W większości przypadków wspiera ono wszystkie standardowe czynności rysowania grafiki oraz czcionek. Jednakże niektóre urządzenia - na przykład plotery pisakowe - wspierają jedynie rysowanie liniowe. Drukarki natomiast mogą być czarno-białe lub wykorzystywać ograniczony zestaw kolorów. Dzięki funkcji GetDeviceCaps () możemy dowiedzieć się wszystkiego, czego potrzebujemy o określonym urządzeniu. Skoro funkcja ta może dostarczyć tylu informacji, to jak można się domyśleć, istnieje długa lista znaczników, które możemy jej przekazać w celu zbadania konkretnych aspektów technologicznych. Funkcja kontekstu urządzenia GetDeviceCaps () pobiera jeden parametr, będący jednym z tych właśnie znaczników, określający rodzaj żądanej informacji. Znacznik TECHNOLOGY informuje o rodzaju przyłączonego urządzenia, zwracając jedną spośród wartości wymienionych w tabeli 15.2. Specyficzne możliwości graficzne Istnieje cały zestaw znaczników pozwalających na stwierdzenie posiadania przez urządzenie możliwości rysowania krzywych (CURYECAPS), linii (LINECAPS), wielo-kątów (POLYGONCAPS) oraz tekstu (TEXTCAPS). Znaczniki te odnoszą się do ploterów, które mogą nie posiadać możliwości rysowania pewnych elementów. Tabela 15.2. Wartości zwracane przez funkcję GetDeviceCaps () dla znacznika TECHNOLOGY Zwracana wartość Opis DT_RASDISPLAY Zwykła karta graficzna lub monitor DT_RASPRINTER Drukarka rastrowa DT_PLOTTER ploter pisakowy DT_RASCAMERA Rastrowe urządzenie wejściowe DT_CHARSTREAM Sekwencja znaków, na przykład z klawiatury DT_METAFILE plik zawierający informacje dotyczące rysowania DT_DISPFILE Plik do wyświetlenia Wersja sterownika może zostać sprawdzona poprzez użycie znacznika DRIVEVERSION. Kilka kolejnych znaczników dostarcza informacji o fizycznych wymiarach urządzenia. Znaczniki te wymienia tabela 15.3. 382 Poznaj Visual C++ 6 Parametry funkcji GetDeviceCaps () Oczywiście istnieje o wiele więcej znaczników, niż przedstawione w powyższej tabeli. Znaczniki, które nie zostały wymienione, służą w większości do sprawdzenia, czy dane urządzenie jest w stanie wykonać określoną operację. Dla większości zwykłych monitorów i drukarek operacje rysowania są dostępne i dają dobre wyniki. Tabela 15.3. Znaczniki przekazywane funkcji GetDeviceCaps () w celu określenia fizycznych wymiarów urządzenia Przekazany znacznik Opis zwracanej wartości HORZRES Szerokość urządzenia podana w pikselach VERTRES Wysokość urządzenia podana w pikselach HORS IZĘ Szerokość urządzenia podana w milimetrach VERTSI ZE Wysokość urządzenia podana w milimetrach ASPECTX Relacja wysokości do szerokości podana w pikselach ASPECTY Relacja szerokości do wysokości podana w pikselach ASPECTXY Długość przekątnej podana w pikselach Kolejne znaczniki służą do zdobycia informacji o liczbie odtwarzanych kolorów oraz możliwościach rysowania przez urządzenia. Wymienia je tabela 15.4. Tabela 15.4. Znaczniki przekazywane funkcji GetDeviceCaps () do określenia liczby kolorów i możliwości rysowania przez urządzenie Przekazywany znacznik Opis zwracanej wartości NUMCOLORS Liczba kolorów, jaką wykorzystuje urządzenie PLANES Liczba płaszczyzn kolorów obsługiwanych przez urządzenie BITSPIXEL Liczba bitów reprezentujących jeden piksel NUMPENS Liczba piór wykorzystywanych przez urządzenie NUMBRUSHES Liczba pędzli wykorzystywanych przez urządzenie NUMFONTS Liczba czcionek wspieranych przez urządzenie COLORRES Rozdzielczość kolorów urządzenia (jeśli da się zastosować) SIZEPALETTE Liczba pozycji palety w palecie systemowej (jeśli da się zastoso- wać) Rysowanie w kontekście urządzenia 383 Zrobimy teraz użytek z funkcji GetDeviceCaps (), by ustalić właściwości karty graficznej. Okno dialogowe About będzie świetnym miejscem na umieszczenie tych informacji w postaci listy. Dodanie pola listy do okna dialogowego About 1. Wybierz kartę ResourceYiew z widoku Project Workspaces. 2. Odnajdź pozycję IDD_ABOUTBOX należącą do okna dialogowego. 3. Kliknij dwukrotnie tę pozycję, by rozpocząć jej edycję. 4. Rozciągnij okno i umieść w nim pole listy. 5. Naciśnij Alt+Enter, by zmienić właściwości pola listy. 6. Na karcie Generał zmień identyfikator ID na IDC_DEVCAPS. 7. Kliknij kartę Styles i usuń znacznik opcji Sort. 8. Naciśnij Enter, by zamknąć okno właściwości. 9. Dodaj pole Static ponad polem listy i wpisz Device Capabilities. Szablon okna dialogowego About powinien wyglądać obecnie jak na rysunku 15.4. sm*^.; v^i^S. '^^w^wS^^ ;::.;tó< :^H]Fiie ^dif View Snsert grojeci fiuild f„oyoul laota ^indOT/ tiełp ^•SfiSSSS * ą» l?" •a- :.: - ;[aia?i' IftlGetDesktopWindow ijCAtunrtDlg3|lDC_DEVCAPS^liSi^^OBiST IJChapl6P2 j^|Win32 Debug "r] S a^"»©Eia'««, l "'El S.^^iOBlAESlitiiia •^aSt l> •t 4- ISl |EnlireC(inter_J •|K f» •,\ Chap16P2Varsion1 O Copyrighf(C)1998 Devics Capabilities ^, .-^•^ >t B A< aM Q a 9 a Są as S » B!I o- Ę" 3 & a H *6 C la-.a ChapłBPS $ LJAcceleri B a Dialog l a|iDp_; iłs iJlcon in SMenu BO String Ti EB OToolbar: la i_|Version "^JM-SIU |l^ i;1' •;" ,: ": iSEBIj", 'J Fteady ^a ^ Generał | Styl&s | Erfended Słyles | D; [TDCJIEYCAFS3 17 Visible r firoup r aełp 10 r Disabled 17T»bstop Rysunek 15.4. Edycja okna dialogowego About aplikacji MapMode Teraz musimy jeszcze przydzielić polu listy zmienną, dzięki której pole to będzie mogło wyświetlić łańcuch składający się na informację dla użytkownika. 384 Poznaj Visual C++ 6 Przydzielenie zmiennej do pola listy 1. Kliknij pole listy w oknie edytora. 2. Naciśnij Ctri+W, by uruchomić CIassWizard. Powinien on zostać otwarty z zaznaczoną pozycją IDC_DEVCAPS w polu Control IDs na karcie Member Yariables. 3. Kliknij przycisk Add Variable, co spowoduje wyświetlenie okna dialogowego Add Member Variable. 4. Rozwiń listę Category i zastąp domyślną pozycję Value pozycją Control. 5. Wpisz w polu Member Variable nazwę nowej zmiennej, m_listDevCaps. 6. Kliknij OK, by zamknąć okno dialogowe. 7. Kliknij OK, by zamknąć CIassWizard. Mamy zatem przydzieloną zmienną klasy CListBox do pola listy, wcieloną do klasy CAboutDig. Teraz musimy wpisać kod wysyłający zapytanie do kontekstu urządzenia, poprzez funkcję GetDeviceCaps () i wyświetlający otrzymane informacje. Aby ten mechanizm zadziałał, należy dodać do klasy CAboutDig funkcję obsługi OninitDia-log () inicjalizującą pole listy podczas jego otwierania. Dodanie funkcji obsługi OninitDialogO 1. Wybierz panel CIassView z widoku Project Workspace. 2. Kliknij prawym klawiszem myszy pozycję CAboutDig, by otworzyć menu skrótów. 3. Wybierz opcję Add Windows Message Handler. 4. Z listy New Windows Messages/Events wybierz pozycję WM_INITDIALOG. 5. Kliknij przycisk Add and Edit, co pozwoli przejść do edycji treści funkcji. Wpiszmy teraz treść listingu 15.9, dzięki czemu rezultaty działania funkcji GetDeviceCaps () zostaną umieszczone w polu listy okna dialogowego About. W linii 8 tego listingu użyto CdientDC dla uchwycenia kontekstu urządzenia okna dialogowego About. Kontekst ten przechowuje informacje o urządzeniu wyświetlającym. Poprzez linie 14 i 15 wartość związana ze znacznikiem rozdzielczości poziomej HORZRES sformatowana zostaje do postaci łańcucha tekstowego. W linii 16 łańcuch ten przekazany zostaje do pola listy, po czym wyświetlony. Proces ten powtórzony jest dla wszystkich żądanych atrybutów urządzenia. Listing 15.9. LST16_9.CPP — wyświetlenie w polu listy informacji pobranych przez Get- DeviceCaps () 1 BOOL CAboutDig::OninitDialogO 2 { 3 CDialog: :OnInitDialog.() ; 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 } // TODO: Tutaj dodaj dodatkową inicjallzację II ** Przechwycenie kontekstu okna dialogowego CCIientDC dcDev(this); // ** Deklaracja łańcucha CString strCap; // ** Pobranie otrzymanych informacji i sformatowanie ich strCap.Format("Horizontal Resolution = %d pixels", dcDev.GetDeviceCaps(HORZRES)) ; m_listDevCaps.AddString(strCap) ; strCap.Format("Vertical Resolution = %d pixels", dcDev.GetDeviceCaps(YERTRES)) ; m_listDevCaps.AddString(strCap) ; strCap.Format("Horizontal Size = %d cm", dcDev.GetDeviceCaps(HORZSIZE) / 10); m_listDevCaps.AddString(strCap) ; strCap.Format("Vertical Size = %d cm", dcDev.GetDeviceCaps(VERTSIZE) / 10); m_listDevCaps.AddString(strCap) ; strCap.Format("Supported Colors = %d colors", dcDev.GetDeviceCaps(NUMCOLORS)) ; m_listDevCaps.AddString(strCap) ; return TRUE; // Zwraca TRUE do chwili ustawienia fokusu na obiekcie sterującym 11 WYJĄTEK: Strony właściwości OCX powinny zwracać TRUE Po skompilowaniu aplikacji z uwzględnieniem opisanych zmian i jej uruchomieniu oraz wybraniu pozycji About Map Modę... z menu Hełp powinniśmy ujrzeć informacje o atrybutach używanego urządzenia wyświetlającego, jak widać na rysunku 15.5. Nie należy się martwić, że urządzenie powinno wykorzystywać większą liczbę kolorów, niż wynika to z otrzymanej informacji. Podawana liczba jest liczbą kolorów palety systemowej kontekstu urządzenia, która domyślnie wynosi 20. Paleta może zostać rozszerzona poprzez przydzielenie większej palety do kontekstu urządzenia, w ramach możliwości naszego sprzętu. 386 Poznaj Visual C++ 6 Vj» f* Chapl6P2 Version 1.0 HorizontsI Resolulion - 600 pixels Vertfcol Resolution • 600 pixels Morilontoi Sile " 16 cm YerticaJ Size -12 cm Supported Cotere - EO colors Rysunek 15.5. Wyświetlone informacje otrzymane są przez funkcję GetDeviceCaps () 4 O możliwościach drukarki przeczytasz w rozdziale 22. Rozdział 16 Stosowanie piór i pędzli Wykorzystanie piór do rysowania linii i kształtów Malowanie pędzlami wypełnionych kształtów różnymi kolorami i teksturami Stosowanie funkcji rysujących dla uzyskiwania złożonych kształtów Tworzenie piór Pióra to jeden z podstawowych obiektów GDI. Mają swoje miejsce głęboko w otchłani Windows i są tam od samego początku. Zanim cokolwiek narysujemy, musimy utworzyć lub wybrać jedno z piór, odpowiednich do wykonania określonego zadania. Pióra i figury wypełnione Pióra wykorzystywane są do rysowania linii i krzywych. Podczas rysowania wypełnionej figury, pióro używane jest do rysowania krawędzi figury, do wypełniania jej zaś służy pędzel. Figura nie musi być wypełniana, narysowany będzie jedynie jej obrys. W takim przypadku stosuje się pędzel przezroczysty lub pusty, co omówione jest w dalszej części rozdziału. Stosowanie klasy CPen MFC posiada klasę osłonową nazwaną CPen znacznie ułatwiającą korzystanie z piór (obiektów rysujących). Klasa ta przechowuje podstawowy obiekt GDI, zajmując się jego dostarczaniem i usuwaniem. By utworzyć pióro, musimy je zadeklarować, przekazując pewne parametry inicjali- zujące. Poniższy kod tworzy pióro rysujące czerwoną linię ciągłą: CPen penRed(PS_SOLID,3,RGB(255,0,0)) ; Rozdział 16 Stosowanie piór i pędzli Wykorzystanie piór do rysowania linii i kształtów Malowanie pędzlami wypełnionych kształtów różnymi kolorami i teksturami Stosowanie funkcji rysujących dla uzyskiwania złożonych kształtów Tworzenie piór Pióra to jeden z podstawowych obiektów GDI. Mają swoje miejsce głęboko w otchłani Windows i są tam od samego początku. Zanim cokolwiek narysujemy, musimy utworzyć lub wybrać jedno z piór, odpowiednich do wykonania określonego zadania. Pióra i figury wypełnione Pióra wykorzystywane są do rysowania linii i krzywych. Podczas rysowania wypełnionej figury, pióro używane jest do rysowania krawędzi figury, do wypełniania jej zaś służy pędzel. Figura nie musi być wypełniana, narysowany będzie jedynie jej obrys. W takim przypadku stosuje się pędzel przezroczysty lub pusty, co omówione jest w dalszej części rozdziału. Stosowanie klasy CPen MFC posiada klasę osłonową nazwaną CPen znacznie ułatwiającą korzystanie z piór (obiektów rysujących). Klasa ta przechowuje podstawowy obiekt GDI, zajmując się jego dostarczaniem i usuwaniem. By utworzyć pióro, musimy je zadeklarować, przekazując pewne parametry inicjali-zujące. Poniższy kod tworzy pióro rysujące czerwoną linię ciągłą: CPen penRed(PS_SOLID,3,RGB(255,0,0) ) ; 388 Poznaj Visual C++ 6 Ustalanie typu pióra Pierwszy parametr, PS_SOLID określa styl pióra, który może być wybrany spośród kilku dostępnych. Tabela 16.1 prezentuje owe style. Tabela 16. l. Typy piór Typ pióra Typ rysowanej linii PS_SOLID PS_DASH PS_DOT PS DASHDOT Linia ciągła Linia przerywana Linia kropkowana Linia składająca się z kropek i kresek na przemian Zmiana szerokości pióra Poprzez drugi parametr decydujemy o szerokości (grubości) pióra. Wartość l powoduje rysowanie kreski o szerokości jednego piksela, czyli najmniejszej możliwej. Jeśli ustawimy ten parametr na 30, otrzymamy pióro naprawdę bardzo grube. Bis Edi« Yiew ijelp NB| ^: aj c Solid Dash Dot DashDot WIdth = 1 Width=15 WIdth = 30 i Rysunek 16. l. Style i szerokości piór Zmiana koloru pióra Trzeci parametr podawany podczas konstruowania pióra określa jego kolor. Wartością, którą musimy przekazać, jest COLORREF. Jest to 32-bitowa liczba reprezentująca zawartość poszczególnych komponentów barwy: czerwonego, zielonego i niebieskiego. Bezpośrednie ustalanie wartości COLORREF opiera się na pewnej sztuczce. Otóż wartością Stosowanie piór i pędzli 389 barwy jasnopurpurowej na przykład jest 16711935. Na szczęście istnieje makro RGB ułatwiające wybór kolorów. Oto przykład wykorzystania tego makra: COLORREF rgbPurple = RGB(255,O,255) ; CPen penDashDotPurple(PS_DASHDOT,l,rgbPurple) ; Makro RGB pobiera trzy parametry dla kolorów czerwonego, zielonego oraz niebieskiego, które wyznaczają jasność tych komponentów w żądanej barwie. Wartości tych parametrów możemy ustalać dowolnie w granicach od O do 255, przy czym O oznacza brak koloru, a 255 pełną jego jasność. Dysponujemy zatem ogromną liczbą barw (16,7 miliona), dzięki czemu możemy tworzyć kombinacje kolorów składowych, jeśli tylko pozwala na to karta graficzna. Listing 16.1 prezentuje kilka przykładów wykorzystania kolorowych piór i różnych, wykorzystywanych przez nie wartości RGB. Sprzętowe ograniczenia użycia kolorów Jakkolwiek 24-bitowy tryb koloru (zwany True Color) pozwala na uzyskanie 16,7 miliona kolorów i odcieni, właściwa liczba wyświetlanych kolorów zależy od możliwości karty graficznej i monitora. Jeśli sprzęt wspiera wyświetlanie mniejszej liczby kolorów (przykładowo 16, 256 lub 65 536), Windows będzie próbował zniwelować te niedostatki, dopasowując paletę do jak najlepszego odwzorowania kolorów używanych obecnie w systemie. Czasami mieszane są dwa kolory z palety w celu osiągnięcia barwy jak najbardziej zbliżonej do żądanej. Listing 16.1. LST17_1.CPP - przykłady używania kolorów i piór 1 // Ciągła ciemnozielona kreska 2 CPen pen3olidDullGreen(PS_SOLID,l,RGB(0,128,0)); O 3 4 // Gruba żółta kreska 5 CPen pen3olidYellow30(PS_SOLID,30,RGB(255, 255,0) ); @ 6 7 // Czerwona kropkowana kreska 8 CPen penDashRed(PS_DOT,l,RGB(255,0,0) ) ; ® 9 10 // Przerywana niebieska kreska 11 CPen penDotBlue(PS_DASH,l,RGB(0,0,255)); O 12 O Utworzone zostaje pióro rysujące ciągłą kreskę o szerokości l zielonym kolorem. @ Utworzone zostaje pióro rysujące ciągłą kreskę o szerokości 30 żółtym kolorem. 390 Poznaj Visual C++ 6 © Utworzone zostaje pióro rysujące kropkowaną kreskę o szerokości l czerwonym kolorem. O Utworzone zostaje pióro rysujące przerywaną kreskę o szerokości l niebieskim kolorem. Użycie stosu piór Nie musimy za każdym razem określać atrybutów pióra, gdyż Windows przechowuje kilka z nich na stosie. Są to pióra prekonfigurowane, zwykle stosowane do rysowania pulpitu i obiektów setrujących. Aby użyć któregoś z nich, deklarujemy obiekt CPen, lecz nie musimy przekazywać żadnych parametrów. Musimy jednak wywołać funkcję Cre-ateStockObject (), która przekazuje ustawienia domyślne pióra do obiektu stosu. Poniższe linie prezentują użycie funkcji CreateStockObject () do utworzenia czarnego pióra: CPen stockBlackPen ; stockBlackPen.CreateStockObject(BLACK_PEN) ; Dostępne są również inne style pióra, wymienione w tabeli 16.2. Tabela 16.2. Style piór ze stosu Nazwa pióra Efekt rysowania BLACK_PEN Rysuje czarną kreskę WH l TE_PEN Rysuje białą kreskę NULL_PEN Rysuje kreskę bieżącym kolorem tła Wybór pióra do kontekstu urządzenia Zanim skorzystamy z utworzonych piór, musimy wybrać je do kontekstu urządzenia. W rozdziale 15 wspominaliśmy o tym, iż metoda OnDraw klasy view dostarcza takiego mechanizmu. Kontekst urządzenia posiada zaczep pióra, w którym w jednym czasie umieścić można tylko jedno pióro. Jest to domyślne pióro rysujące. Gdy wybierzemy do kontekstu inne pióro, poprzednie zostaje z kontekstu usunięte. Do umieszczenia pióra we właściwym miejscu używamy metody SelectObject (). Po wykonaniu tej czynności stare pióro zostaje usunięte z kontekstu, lecz musi pozostać w zasięgu działania. Listing 16.2 pokazuje typowe rozwiązanie tego problemu. Stosowanie piór i pędzli____________________________________391 Listing 16.2. LST17_2.CPP - wybór pióra 1 void MyView::OnDraw(CDC* pDC) 2 ( 3 CPen penSolid(PS_SOLID,5,RGB(0,200,0)); // Green pen 4 CPen *p01dPen = NULL; 5 6 pOldPen = pDC->Selectdbject(SpenSolid); O 7 8 II... Rysowanie piórem zielonym 9 10 pDC->SelectObject(p01dPen); ® 11 } 12 O W linii 6 następuje wybór nowego, zielonego pióra i przechowanie poprzednio znajdującego się w kontekście urządzenia. @ Linia l O przywraca pióro poprzednie. Przekazujemy SelectObject () wskaźnik nowego pióra (zauważmy użycie & w linii 6), a zwrócony zostaje wskaźnik poprzednio wybranego. Możemy w tej sytuacji dokonać czynności rysowania w kontekście urządzenia. Po zakończeniu musimy przywrócić poprzednie pióro, gdyż w przeciwnym wypadku Windows zacznie rysować przyciski i inne obiekty sterujące aktualnie wybranym piórem! Jeśli używamy dwóch piór, do używania ich na przemian również stosujemy metodę SelectObject (), co zilustrowane jest na listingu 16.3. Listing 16.3. LST17_3.CPP - używanie dwóch piór 1 void MyView::OnDraw(CDC* pDC) 2 ( 3 CPen penGreen(PS_SOLID,5,RGB(0,255,0)); // Pióro zielone 4 CPen penBlue(PS_SOLID,3,RGB(0,0,255) ) ; // Pióro niebieskie 5 CPen *p01dPen = NULL; 6 7 pOldPen = pDC->SelectObject(SpenGreen); // Zamiana piór B 9 II... Rysowanie piórem zielonym 10 11 pDC->SelectObject(SpenBlue); // Zamiana piór O 12 // Zwrócone zostaje pióro zielone. 13 14 II... Draw with the blue pen 15 pDC->SelectObject(pOldPen); // Przywrócenie pióra 16 } O Linia 11 zamienia nowe zielone pióro (wybrane w linii 7) na niebieskie. Gdy chcemy rysować piórami innymi niż na listingu 16.3, przechować musimy jedynie pióro domyślne. Nie musimy przechowywać wskaźników do piór stosowanych do rysowania, używamy bowiem jedynie odwołań do nich. Na koniec musimy przywrócić pióro oryginalne dla upewnienia się , iż wszystkie nasze pióra zostały usunięte z kontekstu urządzenia. Usuwanie piór Po zakończeniu rysowania własnymi piórami, musimy je usunąć. Uwalniamy w ten sposób obiekt GDI i odzyskujemy drogocenne zasoby systemu. Klasa CPen robi to automatycznie, lecz możemy również zrobić to ręcznie, jeśli chcemy użyć tego samego obiektu CPen z innymi ustawieniami (poprzez wywołanie Createpen ()). Możemy również, jeśli utworzyliśmy wiele piór i niektóre z nich są niepotrzebne, uwolnić nieco zasobów systemowych przed usunięciem piór na końcu funkcji. DeleteObject () jest funkcją składową, usuwającą obiekt GDI. Listing 16.4 pokazuje przykład, w którym obiekt pióra jest tworzony, użyty, usunięty i utworzony ponownie. Listing 16.4. LST17_4.CPP - użycie funkcji DeleteObj ect () oraz CreatePen () 1 void MyView::OnDraw(CDC* pDC) 2 { 3 CPen penMainDraw(PS_DASH,l,RGB(128,255,255)) ; 4 CPen *p01dPen = NULL; 5 6 pOldPen = pDC->SelectObject(&penMainDraw); 7 8 // Rysowanie kreską przerywaną... 9 10 pDC->SelectObject(p01dPen); 11 12 penMainDraw.DeleteObject(); O 13 penMainDraw.CreatePen(PS_SOLID,5,RGB(200,2 O,5) ) ; 14 15 pOldPen = pDC->SelectObject(&penMainDraw); 16 17 // Rysowanie kreską ciągłą... 18 19 pDC->SelectObject(p01dPen); 20 } 21 // DeleteObject wywołana zostaje automatycznie, gdy 22 // penMainDraw jest usunięty na zakończenie funkcji Q Linia 12 usuwa obiekt GDI, w związku z tym można ponownie użyć klasy CPen do utworzenia nowego pióra, co ma miejsce w linii 13. Na powyższym listingu w linii 3, czyli na początku funkcji OnDraw (), utworzone zostaje pióro penMainDraw, a następnie użyte do rysowania. Nigdy nie wolno wywoływać DeleteObject () w celu usunięcia pióra aktualnie używanego przez kontekst urządzenia. Na listingu poprzednie pióro zostaje z powrotem wybrane do kontekstu urządzenia, w związku z czym możemy wywołać DeleteObject () dla pióra rysującego przerywaną kreskę. Po jego usunięciu obiekt GDI zostaje zwolniony i możemy ponownie użyć klasy CPen do skonstruowania kolejnego pióra. Wywołana jest więc funkcja CreatePenO, która tworzy pióro rysujące kreskę ciągłą. Ta funkcja składowa pobiera identyczne parametry, co konstruktor: CreatePen(nPenStyle, nwidth, crColor) dla określenia stylu, grubości oraz koloru pióra. Ostrożnie z funkcją Delete0bject() Funkcja DeleteObject () wywołana dla pióra znajdującego się w kontekście urządzenia spowoduje zniszczenie obiektu GDI. Dalsze używanie pióra może doprowadzić do upadku aplikacji i innych nieprzewidywalnych skutków. Rysowanie linii i kształtów za pomocą pióra Abyśmy mogli narysować cokolwiek piórem (lub innym obiektem GDI), musi istnieć kontekst urządzenia, w którym rysowanie będzie przebiegało. Jak pisaliśmy w rozdziale 15, kontekst urządzenia dostarcza mechanizm, dzięki któremu rysowanie tymi samymi obiektami przy użyciu szerokiego spektrum urządzeń pozwala osiągać jednakowe wyniki. 394_____________________________________Poznaj Visual C++ 6 Tworzenie kontekstu urządzenia do rysowania Aplikacja z widokiem SDI będzie doskonałym polem działania dla naszych czynności graficznych. Poniższa procedura pomoże nam utworzyć taką aplikację, używającą prostego, zorientowanego na rysowanie, widoku (klasą bazową będzie cview). Wybór kontekstu urządzenia rysującego Funkcji rysujących można używać do rysowania w legalnym kontekście urządzenia. Oznacza to możliwość uchwycenia okna innej aplikacji, wywołania funkcji GetDC () i rozpoczęcia rysowania w tym oknie. Można uchwycić także kontekst pulpitu Win-dows i rysować bezpośrednio na nim. Jednakże większość „przyzwoitych" aplikacji ogranicza możliwości rysowania jedynie do własnych okien. Utworzenie kontekstu urządzenia w aplikacji SDI 1. Kuknij menu File i wybierz pozycję New. 2. Wybierz .kartę Projects. Z listy dostępnych typów projektów wybierz MFC AppWi-zard (exe). 3. Teraz kliknij pole Project Name i wpisz nazwę nowego projektu (MyDraw jak na omawianych listingach). 4. Kliknij OK. Powinieneś ujrzeć okno dialogowe pierwszego kroku AppWizard. 5. Wybierz Single Document, a następnie kliknij Finish. Zostanie tym samym utworzony szkielet aplikacji SDI, używający klasy bazowej CView do wyprowadzenia nowej klasy widoku aplikacji. 6. Kliknij OK w oknie dialogowym New Project Information, a AppWizard utworzy nowy projekt wraz z koniecznymi plikami źródłowymi. Jesteśmy już gotowi do rysowania. Zmiana położenia pióra Kontekst urządzenia przechowuje aktualne współrzędne pióra. Linia będzie rysowana od pozycji bieżącej do innej, po czym nowa pozycja stanie się bieżącą. Kontekst urządzenia posiada funkcję składową MoveTo () która ustala nową pozycję bieżącą. Funkcję tę wywołuje się, przekazując jej dwa parametry, którymi są nowe współ- rzędne x oraz Y. Stosowanie piór i pędzli __ __ 395 Badanie aktualnej pozycji rysowania Stwierdzenie bieżącej pozycji, w której odbywa się rysowanie, możliwe jest dzięki funkcji GetCurrentPosition (), zwracającej tę pozycję w obiekcie CPoint. Otworzymy teraz kartę ClassView w widoku Project Workspace. Powinniśmy tam odnaleźć klasę CMyDrawView. Kuknijmy widoczny obok niej znak plusa, co pozwoli nam dotrzeć do składowych tej klasy. Używając funkcji składowej OnDraw () możemy zaimple-mentować własny kod rysujący. Kliknijmy zatem dwukrotnie funkcję OnDraw (), a w oknie edytora ujrzymy kod implementacyjny, jak na listingu 16.5. Listing 16.5. LST17_5.CPP - standardowa funkcja OnDraw () 1 // rysowanie CMyDrawView 2 3 void CMyDrawView::OnDraw(CDC* pDC) 4 t 5 CMyDrawDoc* pDoc = GetDocument(); 6 ASSERT_VALID(pDoc); 7 8 // TODO: tu dodaj kod rysowania dla własnych danych 9 } Jak widzimy, funkcji tej przekazany został wskaźnik kontekstu urządzenia, który reprezentuje obszar roboczy głównego widoku SDI. W linii 5 pobrany zostaje wskaźnik do klasy dokumentu, ponieważ większość aplikacji wykonuje rysowanie w oparciu o dane przechowywane w dokumencie. Chwilowo nie narysowaliśmy jeszcze nic. Jeśli wydamy polecenie Execute MyDraw.exe z menu Build, aplikacja zostanie uruchomiona, wyświetlając pusty widok. Możemy rozpocząć dodawanie kodu rysującego przesuwając kursor graficzny. Aby to zrobić, musimy dopisać linię MoveTo () za linią komentarza//TODO: add draw code, jak w linii 8 listingu 16.6. Listing 16.6. LST17_6.CPP - zastosowanie funkcji MoveTo() 1 void CMyDrawView::OnDraw(CDC* pDC) 2 { 3 CMyDrawDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 396_____________________________________Poznaj Visual C++ 6 8 pDC->MoveTo(50,100); // ** Nowa linia MoveTo ** O 9 } O W linii 8, poprzez funkcję MoveTo (), następuje zmiana pozycji kursora graficznego w kontekście urządzenia. Jakkolwiek nie ma jeszcze żadnego rysunku, nastąpiła zmiana pozycji kursora graficznego. Obecnie jest to 50 pikseli w osi poziomej i 100 w osi pionowej widoku, poprzez nową funkcję MoveTo () w linii 8. Rysowanie linii Kontekst urządzenia posiada, oprócz funkcji MoveTo () funkcję składową LineTo (). Pobiera ona te same parametry, co poprzednia i rysuje linię od ostatniej pozycji pióra do nowej. Jeśli dodamy do funkcji OnDrawO kod widoczny na listingu 16.7, przez funkcję LineTo () narysowany zostanie kreskowaną linią trójkąt. Listing 16.7. LST17_7.CPP - rysowanie linii 1 void CMyDrawView::OnDraw(CDC* pDC) 2 { 3 CMyDrawDoc* pDoc = GetDocument() ; 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 8 // Utworzenie pióra 9 CPen penRed(PS_DOT,l,RGB(255,0,0)); 10 // Deklaracja wskaźnika poprzedniego pióra 11 CPen *p01dPen = NULL; 12 13 // Wybór pióra czerwonego 14 pOldPen = pDC->SelectObject(SpenRed); 15 16 pDC->MoveTo(50,100); 17 18 // Narysowanie podstawy trójkąta 19 pDC->LineTo(100,100); O 20 // Narysowanie pierwszego boku 21 pDC->LineTo(75,50); Stosowanie piór i pędzli 397 22 23 24 25 26 27 // Wary s owa n-i e drugiego boku pDC->LineTo (50,100) ; // Przywrócen-ie poprzedniego pióra pDC- >SelectObject(pOldPen) ; O W liniach 19 do 23 funkcja LineTo () rysuje trzy boki trójkąta. Jak poprzednio, musimy utworzyć pióro (czerwone, rysujące kreskę przerywaną) i wybrać je do kontekstu urządzenia. W linii 16 następuje ustawienie pióra we współrzędnych (50, 100), a czyni to funkcja MoveTo(). W następnym kroku (linia 19) funkcja LineTo () rysuje linię od punktu (50, 100) do punktu (100, 100). Wynikiem tej operacji jest pozioma linia od punktu 50 do 100 w osi X. Wartość współrzędnej Y pozostaje natomiast taka sama (50). Następne dwa wywołania funkcji powodują wykreślenie linii pomiędzy punktami (100, 100) i (75,50) oraz drugiej pomiędzy współrzędnymi (75,50) a (50,100). Aby skompilować po wprowadzeniu zmian, skonsolidować i uruchomić aplikację, wybrać należy polecenie Execute z menu Build, a na pytanie postawione przez program odpowiedzieć Yes. Jeśli wszystko przebiegnie bez przeszkód, na ekranie narysowany zostanie mały trójkąt, podobny do widocznego na rysunku 16.2. File Edrt yiew yefp Rysunek 16.2. Prosty przykład użycia funkcji LineTo () 398_____________________________________Poznaj Visual C++ 6 Rysowanie w oparciu o współrzędne W rozdziale 8 poznaliśmy klasę służącą do przechowywania i przekazywania współrzędnych. Użycie klasy cpoint, bo o niej mowa, jest funkcjonalnym sposobem manipulowania współrzędnymi X oraz Y. Funkcje składowe kontekstu urządzenia MoveTo () oraz LineTo () odbierają obiekty cpen jako parametry, możemy je zatem wykorzystywać do rysowania zamiast osobnego przekazywania współrzędnych. Inną użyteczną funkcją jest GetCurrentPosition () zwracająca bieżącą pozycję kursora graficznego (gdzie ustawiła go jedna z funkcji MoveTo () lub LineTo ()). Pośród klas MFC do dyspozycji oddano klasę CRect przechowującą dwa obiekty CPoint, jeden dla lewego górnego rogu prostokąta, drugi dla prawego dolnego. Możemy użyć funkcji GetCIientRect () do zdobycia informacji o rozmiarach okna widoku, a rezultaty zapisać w klasie CRect. Klasa ta posiada pewne użyteczne funkcje składowe, które wykorzystywać można do manipulowania informacjami o prostokącie. Jedną z tych funkcji składowych jest CenterPoint (), która zwraca obiekt CPoint przechowujący wartości współrzędnych środkowego punktu prostokąta. Prostokątem tym jest w naszym przykładzie okno widoku aplikacji SDI. CRect dostarcza również informacji o wysokości i szerokości prostokąta, stosując funkcje Width () oraz Height (). Usuniemy teraz z funkcji OnDraw () kod rysujący trójkąt, a w jego miejsce wpiszemy treść listingu 16.8, co pozwoli nam uzyskać efekt ekranowy zaprezentowany na rysunku 16.3. Listing 16.8. LST17-8.CPP - rysowanie z wykorzystaniem klas CPoint oraz CRect 1 void CMyDrawYiew::OnDraw(CDC* pDC) 2 { 3 CMyDrawDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 8 // Utworzenie pióra 9 CPen penRed(PS_DOT,l,RGB(255,0,0)); 10 CPen *p01dPen = NULL; 11 12 pOldPen = pDC->SelectObject(SpenRed); 13 14 // Określenie współrzędnych obszaru rysowania 15 CRect rcCIient; 16 GetCIientRect(SrcCIient) ; 17 18 for(int x=0; x < rcCIient.Width(); x+=5) O 19 forfint y=0; y < rcCIient.Height(); y+=5) 20 ( 21 CPoint ptNew(x, y); 22 pDC->MoveTo (rcdient.CenterPoint () ) ; 23 pDC->LineTo(ptNew) ; 24 } 25 26 pDC->SelectObject(p01dPen) ; 27 } O Linie od 18 do 24 wykonują w obszarze roboczym, poczynając od jego środka, rysowanie linii pomiędzy węzłami siatki. Zauważmy, w jaki sposób nakłania się widok do przekazania zmiennej rcdient informacji o obszarze roboczym: przez za pośrednictwem funkcji GetCIientRect () (linie 15 i 16). Funkcja ta ułatwia wykonanie w szerokości i wysokości prostokąta dwóch pętli for. Każde pięć pikseli w osi poziomej przyjętych zostaje jako x, następnie zaś dla każdego z punktów x ustawione zostaje y, czyli co piąty piksel w osi pionowej. W ten sposób uformowana jest siatka. Linie rysowane są od środka (ustalonego za pomocą centerpo-int () w linii 22) do punktu zmiany pozycji utworzonego jako ptNew w linii 21 i przekazanego funkcji LineTo () w linii 23. Skompilujemy i uruchomimy teraz program. Dzięki wprowadzonemu przed chwilą kodowi, otrzymamy artystyczny efekt, widoczny na rysunku 16.3. Określanie wielkości prostokąta całego okna Jeśli zachodzi potrzeba określenia współrzędnych dla całego okna, można posłużyć się funkcją GetWindowRectO, przekazując wskaźnik do obiektu CRect. Współrzędne ekranowe okna zostaną zapisane we wskazanym obiekcie CRect. Jeśli jednak rysowanie odbywa się w kontekście urządzenia przekazanym funkcji OnDrawO, operacje rysowania będą ograniczone do powierzchni obszaru roboczego. 400 Poznaj Visual C++ 6 ':EI« Eilil Yiaw tlslP "oMal_&IMU ^DltfH ' :: e »l?n ^ » j; > .• ' s - ,-••'.. •' ' s' f i "'•" '•' * ;l ^^^^^;^^^^^'^ j j l tóllllr ' 1- l | f-—, •: ;. ^ ^ 1*31 ; j j "———; s—— 'Z.ll"'^''1 •"""""""- ""•——-•"- •"•S l aSSInnI ayMicro5o«Wof[l..,| e9Chiip17P1-Ml,:| ajEiliiIonng-iSfr^lBitJuJjtetf-gny-i: Rysunek 16.3. 'Rysowanie za pomocą punktów i prostokątów Rysowanie okręgów i elips Okrąg jest w zasadzie elipsą o takiej samej szerokości co wysokości, w związku z tym obie te figury wykreślane są za pomocą tej samej funkcji składowej kontekstu urządzenia. Funkcja EllipseO występuje w dwóch formach. W pierwszej z nich funkcja pobiera prostokąt określony przez przekazany obiekt CRect. W drugiej formie natomiast funkcja wymaga czterech parametrów. Są to współrzędne x oraz y górnego lewego oraz dolnego prawego rogu prostokąta. Zmienimy teraz treść funkcji OnDrawO według listingu 16.9. Zmiana spowoduje wykonanie rysunku przedstawionego na rysunku 16,4. Listing 16.9. LST17_9.CPP - rysowanie okręgów za pomocą funkcji Ellipse 1 2 3 4 5 6 7 8 9 10 void CMyDrawView::OnDraw(CDC* pDC) { CMyDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: tu dodaj kod rysowania dla własnych danych II Utworzenie pióra CPen penRed(PS_DOT,l,RGB(255,0,0)) ; CPen *p01dPen = NULL; 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 pOldPen = pDC->SelectObject(SpenRed); // Bez wypełniania elipsy pDC- >Select3tockObject(NULL_BRUSH) ; CRect rcCIient; GetCIientRect(SrcCIient) ; // Deklaracja wyśrodkowanego obiektu prostokąta CRect rcEllipse(rcCIient.CenterPoint(), rcCIient.CenterPoint ()) ; // Wykonywanie funkcji do czasu całkowitego wypełnienia obszaru roboczego O while(rcEllipse.Width() < rcCIient.Width() && rcEllipse.Height () < rcCIient.Height ()) { // Zwiększenie wymiarów elipsy o 10 piksell rcEllipse.InflateRect(10,10); pDC->Ellipse(rcEllipse) ; } pDC->SelectObject(pOldPen) ; } O Pętla whiie w linii 25 powtarzana jest poprzez linie 28-30, powększając za każdym przebiegiem wymiary figury do momentu, gdy nie osągną one wartości równej szerokości lub wysokości obszaru roboczego. Pierwszą zmianą, jaką powinniśmy zauważyć, to pDC ->SelectStockObject (NULL_BRUSH) w linii 15. Wywołanie takie ma miejsce, gdyż domyślnie funkcja Ellip-se () dokonałaby w linii 30 wypełnienia figury bieżącym pędzlem (pędzle omówione są dalej, w tym rozdziale). Zauważmy, iż funkcja ta instruuje kontekst urządzenia, aby nie wypełniał elips, a jedynie rysował krawędzie. Następnie utworzony zostaje prostokąt dla elipsy (rcEllipse) i użyty do jej rysowania. Prostokąt ten zainicjalizowany zostaje jako pokrywający się z obszarem roboczym. Pętla while w liniach 25, 26 oraz 27-31 wykonywana jest dopóty, dopóki prostokąt elipsy nie zrówna się w szerokości lub wysokości z wymiarami okna. W pętli (linia 30) wykorzystana jest kolejna funkcja składowa CRect, InflateRect () służąca do powiększania prostokąta o 10 pikseli w szerokości i wysokości. Punk- 402 Poznaj Visual C++ 6 tem odniesienia jest punkt środkowy. Wewnątrz rosnących prostokątów rysowane są kolejne elipsy. Gdy skompilujemy i uruchomimy program, ujrzymy na ekranie efekt wprowadzonych zmian, który przedstawia rysunek 16.4. 51e Edil yiew Uelp D Mai "n7 Rysunek 16.4. Rysowanie okręgów za pomocą funkcji Ellipse() Rysowanie krzywych połyBezierO jest funkcją nazwaną od nazwiska matematyka Beziera, który obmyślił redukcję krzywej do postaci zwanej cubic spline. Spline to krzywa, natomiast wyrażenie cubic oznacza, iż linia posiada punkty początkowy i końcowy oraz dwa punkty położone po przeciwnych stronach krzywej, które rozciągają ją ku sobie, poprzez interpolację pomiędzy punktami. Część poły w nazwie funkcji oznacza, iż możemy narysować wiele krzywych łączących się ze sobą. Funkcji przekazać należy tablicę obiektów cpoint (co najmniej czterech) i liczbę punktów w tablicy. Zmienimy więc postać funkcji OnDraw () pomiędzy wywołaniami GetCIientRect () oraz SelectObject (), zgodnie z listingiem 16.10, co pozwoli rysować krzywe Beziera podobne do widocznej na rysunku 16.5. Listing 16.10. LST17_10.CPP - rysowanie krzywych za pomocą funkcji PołyBezier () GetCIientRect(&rcClient); Stosowanie piór i pędzli___________________________________403 3 // Utworzenie tablicy punktów 4 CPoint ptBezierAr[4]; 5 CPen penBlue(PS_SOLID,3,RGB(0,0,255)); 6 7 pDC->SeiectObject (SpenBlue); O 8 for(int i=0; i < 4 ;i++) 9 ( 10 // Ustalenie losowej tablicy punktów 11 ptBezierAr[i] = 12 CPoint(rand()%rcClient.Width() , 13 rand()%rcClient.Height()); 14 15 // Narysowanie etykiety @ 16 CString strPointLabel; 17 strPointLabel.Format("%d.",i+l) ; 18 pDC->TextOut(ptBezierAr[i].x, 19 ptBezierAr[i].y, 20 strPointLabel) ; 21 22 '// Narysowanie współrzędnych w postaci niebieskich kropek 23 CRect rcDot(ptBezierAr[i],ptBezierAr[i]); 24 rcDot.InflateRect(2,2) ; 25 pDC->Ellipse(rcDot) ; 26 } 27 28 // Wary s owa n-Ł e czerwonej krzywej 29 pDC->SelectObject(SpenRed); 30 pDC->PolyBezier(ptBezierAr,4); © 31 32 pDC->SelectObject(pOldPen); 33 O W linii 7 wybrane zostaje niebieskie pióro w celu narysowania niebieskich punktów (w linii 26). © Poprzez linie 16-20 utworzony zostaje obiekt es t ring, przedstawiający numer punktu. Zawartość łańcucha jest wyświetlana obok punktu jako jego etykieta przez funkcję Text0ut () w linii 18. © Linia 30 rysuje krzywą używając funkcji PołyBezier () i czerwonego pióra, wybranego w linii 29. W linii 4 powyższego listingu zadeklarowana jest tablica obiektów CPoint jako ptBezierAr, a wszystkim czterem zawartym w niej punktom przydzielone zostają losowe 404 Poznaj Visual C-H- 6 współrzędne w obszarze roboczym (linie 12 i 13). Następnie w linii 18 funkcja kontekstu urządzenia Text0uf () wyświetla etykiety punktów (będziemy o tym pisać więcej w rozdziale 17). Funkcja Ellipse () użyta w linii 25 doskonale nadaje się do narysowania niebieskich plamek (niebieskim piórem) symbolizujących poszczególne punkty. Na koniec, po wybraniu pióra rysującego czerwoną przerywaną kreskę, w linii 30 wywołana zostaje funkcja PołyBezier (). Przekazano jej tablicę punktów, informując, że do narysowania krzywej ma posłużyć się czterema punktami. Krzywa zostaje narysowana pomiędzy punktami l i 4, a jej formę ustalają punkty 2 oraz 3. Aby obejrzeć efekty działania PołyBezier (), możemy skompilować i uruchomić program. Rysunek 16.5. przejrzyście ilustruje rolę każdego z punktów. Za każdym razem, gdy zmienimy wymiary okna, rysowana krzywa zmieni swą postać. Rysunek 16.5. Rysowanie krzywych za pomocą PołyBezier () Rysowanie wielokątów Funkcja Polyline() jest identyczna pod względem zapotrzebowania na parametry z funkcją PołyBezier O. Pobiera zatem tablicę punktów obiektów cpoint oraz liczbę punktów w tablicy. Zamiast rysować krzywe, łączy podane punkty prostymi odcinkami. Możemy określić dowolną liczbę punktów (jednak najmniej dwa). Istnieją również funkcje polyLineTo() oraz PołyBezierTo (), które ustalają bieżące koordynaty w ostatnio rysowanym punkcie. Jeśli w treści funkcji OnDraw () zastąpimy PołyBezier () funkcją połyLine (), a następnie skompilujemy i uruchomimy program, wyznaczone punkty połączone zostaną prostymi odcinkami zamiast krzywych Beziera (listing 16.11). Stosowanie piór i pędzli___________________________________405 Listing 16.11. LST17_11.CPP-rysowanie linii za pomocą funkcji Połyline () 1 // Wybór czerwonego pióra 2 pDC->SelectObject(SpenRed); 3 4 // Rysowanie linii pomiędzy czterema punktami 5 pDC->Polyline(ptBezierAr,4) ; 6 7 // Wybór poprzedniego pióra 8 9 pDC->SelectObject(pOldPen) ; 10 Jeśli naszym celem jest narysowanie wielokąta, współrzędne ostatniego punktu muszą być jednakowe z koordynatami pierwszego. Tworzenie pędzli Pióra doskonale nadają się do rysowania krawędzi figur, lecz do ich wypełniania po- trzebujemy pędzla. Większość funkcji rysujących GDI używa jednocześnie pióra i pędzla. Pióro użyte zostaje do wykonania obrysu figury, a pędzel do wymalowania wnętrza. Pozwala to zestawiać pióra o określonym stylu i kolorze z pędzlami innego koloru i prezentującymi inny styl, dzięki temu możemy rysować kompletną figurę, wykorzystując jedną funkcję. Stosowanie klasy CBrush Klasą osłonową dla obiektów pędzli GDI jest CBrush. Pędzel może malować kolorem pełnym, wzorem kreskowym lub pobranym z bitmapy bądź innego wzoru. Dlatego też posiada konstruktor, który potrafi pobrać parametry odmiennego typu. Możemy jednak utworzyć obiekt nie inicjalizując go, lecz czyniąc to później poprzez wywołanie odpowiedniej funkcji. Klasa CBrush oraz HBRUSH Klasa CBrush jest kolejną klasą osłonową MFC hermetyzującą uchwyt obiektu GDI HBRUSH. Użycie uchwytu Windows jest możliwe wprost poprzez włączenie HBRUSH do obiektu CBrush. 406_____________________________________Poznaj Visual C++ 6 Tworzenie pędzli rysujących kolorem pełnym i wzorem kreskowanym Najprostszym do utworzenia jest pędzel malujący pełnym kolorem. Odbywa się to poprzez prostą deklarację obiektu CBrush z przekazaniem jednego parametru, czyli kodu żądanego koloru. Oto przykład właściwej deklaracji: CBrush brYellow(RGB(192, 192, 0)) ; Możemy utworzyć pędzel malujący wzór krzyżykowy, konstruując go i nadając mu znacznik kreskowania krzyżykowego jako pierwszy parametr. Drugim parametrem jest kolor pędzla, jak w przykładowej linii: CBrush brYellowHatch(HS_DIAGCROSS, RGB(192, 192, 0)) ; Tabela 16.3 prezentuje znaczniki stylu malowanego wzoru. Tabela 16.3. Znaczniki stylu wzoru pędzla Znacznik wzoru Efekt HS_CROSS Siatka z linii pionowych i poziomych HS_DIAGCROSS Siatka z linii ukośnych HS_HORIZONTAL Linie poziome HS_VERTICAL Linie pionowe HS_BDIAGONAL Linie ukośne (od strony górnej lewej do dolnej prawej) HS_FDIAGONAL Linie ukośne (od strony dolnej lewej do górnej prawej) Zmiana tła okna Pędzel może zostać użyty do zmiany tła okna poprzez zapewnienie obsługi komunikatu Windows o wymazaniu tła (WM_ERASEBKGND). i Dodanie funkcji obsługi powodującej zmianę koloru tlą okna 1. Kliknij prawym klawiszem pasek Wizard - to pasek u góry okna Visual Studio, wyświetlający trzy listy kombinowane. Pole edycji pierwszej z list powinno wyświetlać nazwę klasy widoku aplikacji (w obecnym przykładzie CMyDrawYiew), w polu edycji drugiej listy zaś powinien widnieć napis (Ali ciass members). 2. Powinieneś ujrzeć menu skrótów zawierające pozycję Add Windows Message Hań- l dier, którą należy wybrać. Otwarte zostanie okno dialogowe widoczne na rysunku 16.6. \ 3. Wybierz pozycję komunikatu WM_ERASEBKGMD i kliknij przycisk Add and Edit. W ten sposób dodasz funkcję obsługi o nazwie OnEraseBkgnd (). Stosowanie piór i pędzli 407 4. Poprzez zmianę domyślnej implementacji funkcji możemy dostosować tło ekranu do własnych oczekiwań. Listing 16.12 prezentuje sposób modyfikacji niezbędnej do po- malowania tła żółtym kreskowanym wzorem. Add Hnndl tiewWindows messoges/eyents: WM_CAFTURECHANGED WM_CHAR WM_CONTEXTMENU WM.COFr-DATA WM_CREATE WM_DESTROY WM_DROPFILES WM HELPINPO WM HSCflOLL WhLKEYDOWN WMJ-EYUP WM_KILLFOCUS WM.LBLnTONDBLCI-K WM.LBUTTONDCWN Oass orobjacł ta handle: |Ci_APF_ABOUT ID_APP_E:-1T ID EDIT_COFY IDlEDIT_ClJT BHefformessoges evBi}abletodass: WM_LBLTTTONUP WM MOUSEMOYE WM MOUSEWHEEL •WM_MOVE WM_PAINT WMJ=lBUTTONDBLCU< WM_RBI-nTnNDOWN wu_RBi.nronup ^J ChildWindow V^/M_CA^>1CELMQDE• Nołifies o Lindów to cancelinte mai modę' Rysunek 16.6. Okno dialogowe Message and Event Handler Listing 16.12. LST17_12.CPP - modyfikacja tła poprzez użycie funkcji OnEraseBkgnd () 1 2 3 4 5 6 7 8 9 10 BOOL CMyDrawView::OnEraseBkgnd(CDC* pDC) { CBrush brYellowHatch(HS_DIAGCROSS,RGB(192,192,0)), CRect rcCUent; GetCIientRect(SreClient) ; pDC->FillRect(rcCIient,&brYellowHatch); O return TRUE; O Podczas obsługi komunikatu WM_ERASEBKGND należy wymazać tło używając funkcji FilIRect (), widocznej w linii 7. Zwracana wartość TRUE oznacza, iż zadanie funkcji obsługi zostało wykonane. Po skompilowaniu z uwzględnieniem powyższych zmian i uruchomieniu programu tło będzie pomalowane żółtym, kreskowanym wzorem. 408 Poznaj Visual C++ 6 Poprzez zmianę implementacji OnEraseBkgnd () utworzyliśmy kreskowany żółty wzór pędzla (linia 3), określiliśmy prostokąt obszaru roboczego (linia 50), a następnie używając funkcji FillRect() (w linii 7) wypełniliśmy tło wzorem. Wpis return TRUE w linii 9 informuje Windows, iż przejęliśmy zadanie wymazania tła i system nie musi tego robić. Tworzenie pędzli z rysunków i wzorów Możliwość tworzenia pędzli z rysunków jest bardzo interesującą cechą Windows. Za jej pomocą możemy malować na ekranie „rozpylając" obrazki. Niektóre aplikacje korzystają z tej możliwości do nadania paskom niecodziennego wyglądu. Utworzenie obrazka dla pędzla 1. Wybierz kartę ResourceView z widoku Project Workspace. 2. Kliknij znak plusa, by rozwinąć drzewo zasobów projektu (MyDraw w obecnym przykładzie). 3. Kliknij prawym klawiszem nagłówek i wybierz pozycję Insert z menu skrótów. 4. W oknie dialogowym Insert Resource wybierz Bitmap. 5. Nowy zasób bitmapy nazwany IDBJBITMAPI powinien się ukazać w oknie edytora. Kliknij w obszarze okna edycji i naciśnij Alt+Enter lub wybierz pozycję Properties z menu View, by wywołać okno właściwości. 6. Zmień nazwę bitmapy na wybraną przez siebie (obecnie proponujemy IDB INYADER), a oba wymiary na 8. Następnie narysuj obrazek podobny do widocznego na rysunku 16.7. -JAłjiJ ~~^ ^ ••„ (p :•„, es Q a » l "•^-iliSia] tS« iSSw •^«««i 'L3 Ffle Edit ^iew insert Profea. guild Image Tools y^mdow Het(?|:| si^aa s, s; e a- „• taisę,' i^icpe.^-cpeii "31. ;: - dn ||Cliapl7Pl3|Win3Z Oebiig '-:. •ćaChap17P1 resources * CJAccelerator w ^ Bitmap______ : a|IDBJNVADER| feC:iniJ"- '^ ~' _ ~ ;_] lec' -" ? Generat Pałał K! CJMi A "J t1 JD JIDB irjYADER ffi _lTo 1 - »._IVe ^^ ^ Flis gsnie ires\invodBr.bmp "3 i eS ^ B l ! Sitlfflia- t * l»|ir>.„.CM»r_.J :« ?».;» —ris?'—'—————— -1---^ a ;.; f a •"is p f <"^ \ 7 A a EI : < O ED ^s iiSw^a-MattKKiiMK Rysunek 16.7. Projektowanie bitmapy dla pędzla Stosowanie piór i pędzli 409 Bitmapy skompilowane Po skompilowaniu aplikacji użyte zasoby bitmapy włączone zostają do pliku wykonywalnego (.exe). Otwierając plik tego typu poleceniem Open As można dotrzeć do zawartych w nim bitmap. Jeśli w programie używa się dużej liczby bitmap, lepiej ładować je z plików lub umieścić wszystkie razem (np. w pliku DLL), co pozwoli ograniczyć rozmiary pliku wykonywalnego. Teraz musimy zaimplementować kod konstruujący pędzel z nowej bitmapy. Aby to zrobić, powinniśmy zamknąć okno edytora bitmapy i zmienić kod funkcji OnEraseBkgnd na zgodny z listingiem 16.13. Listing 16.13. LST17_13.CPP-użycie pędzla bitmapowego 1 BOOL CMyDrawView::OnEraseBkgnd(CDC* pDC) 2 { 3 // Deklaracja bitmapy 4 CBitmap bmliwader; 5 6 // Deklaracja bitmapy 7 8 bmliwader.LoadBitmap(IDB_INVADER); O 9 // Utworzenie pędzla z bitmapy 10 11 CBrush brirwader(&bmlnvader); @ 12 13 CRect rcCIient; 14 GetCIientRect(SrcCIient) ; 15 16 // Wypełnienie prostokąta pędzlem bitmapowym 17 pDC->FillRect(rcCIient,&brlnvader); @ 18 19 return TRUE; 20 } O Funkcja LoadBitmap () ładuje bitmapę z zasobu IDB_INVADER. © Obiekt CBrush może być skonstruowany z obiektu CBitmap w celu teksturowego wypełnienia przy użyciu bitmapy. © Do wymazania tła obszaru roboczego można użyć funkcji FiliRect (). 410 Poznaj Visual C++ 6 Ograniczenia pędzla bitmapowego Windows 95 ogranicza wielkość pędzla do wymiarów 8x8 pikseli. Windows NT natomiast nie nakłada takich ograniczeń. Po skompilowaniu i uruchomieniu programu, w który dokonaliśmy zmian z listingu 16.13, zobaczymy tło wypełnione wzorem zbudowanym z poukładanych obok siebie egzemplarzy utworzonej bitmapy. Do przechowania jej służy klasa CBitmap (linia 4). Bitmapa załadowana zostaje przez funkcję LoadBitmapO w linii 8. Gdy obiekt CBrush jest skonstruowany z zainicjalizowaną bitmapą (linia 11), pędzla można użyć do wykonania każdej operacji malowania, takiej jak funkcja FiliRect () wywołana w linii 17. Używanie stosu pędzli Podobnie jak pióra, również pewna liczba pędzli jest dostępnych na stosie Windows. Możemy posłużyć się nimi w wybranym momencie. Tabela 16.4 wymienia te pędzle. Tabela 16.4. Pędzle stosu Pędzel Efekt malowania BLACK_BRUSH Maluje czarnym kolorem WHITE_BRUSH Maluje białym kolorem DKGRAY_BRUSH Maluje kolorem ciemnoszarym LTGRAY_BRUSH Maluje kolorem jasnoszarym GRAY_BRUSH Maluje szarym kolorem NULL_BRUSH Pędzel przezroczysty (nie wypełnia figur) Przykładem bezpośredniego wybrania pędzla ze stosu za pomocą funkcji Select- StockObject () jest pędzel NULL_BRUSH w linii 15 listingu 16.9. Pędzlowi można nadać kolor z szerszej palety systemowej poprzez wywołanie funkcji CreateColorBrush(), przekazując tej funkcji znacznik żądanego koloru. Lista dostępnych kolorów zawarta jest w tabeli 16.5. Stosowanie funkcji CreateSysColorBrush () Próbując emulować pewne kolory Windows powinno się korzystać z funkcji CreateSysColorBrush O. Jeśli emuluje się kolor poprzez ustaloną wartość COLORREF korespondującą z jednym z kolorów Windows, może się okazać, iż podczas działania aplikacji kolory pulpitu uległy zmianie. Stosowanie piór i pędzli 411 Tabela 16.5. Znaczniki kolorów systemowych dostępnych dla funkcji CreateColorBrush () Znacznik koloru Opis COLOR_DESKTOP COLOR_BTNTEXT COLOR_GRAYTEXT COLOR_HILIGHT COLOR_HILIGHTTEXT COLOR_ACTIVEBORDER COLOR_INACTIVEBORDER COLOR_INFOBK COLOR_WINDOW COLOR_WINDOWFRAME COLOR_WnMDOWTEXT COLOR_3DDKSHADOW COLOR_3DFACE COLOR_3DHILIGHT COLOR 3DLIGHT Kolor tła pulpitu Kolor opisów przycisków Kolor opisów funkcji niedostępnych Kolor podświetlonego tła wybranej pozycji Kolor tekstu opisu zaznaczonej pozycji Kolor obramowania okna aktywnego Kolor obramowania okna nieaktywnego Kolor tła „dymków" podpowiedzi Kolor tła okna Kolor ramki okna Kolor tekstu w oknie Kolor cienia przycisków Kolor powierzchni przycisków Kolor przycisku zaznaczonego Kolor krawędzi przycisku Spróbujmy zmienić treść funkcji OnEraseBkgnd O , by skonstruować jeden z wymienionych pędzli, wzorując się na listingu 16.14. Listing 16.14. LST17_14.CPP - tworzenie pędzla z kolorem systemowym l BOOL CMyDrawYiew::OnEraseBkgnd(CDC* pDC) 2 ( 3 CBrush brDesktop; 4 5 brDesktop.CreateSysColorBrush(COLORDESKTOP); 0 6 7 CRect rcCIient; 8 GetCIientRect(SrcCIient) ; 9 pDC->FillRect(rcCIient,SbrDesktop) ; 10 11 return TRUE; 12 } 412 Poznaj Visual C++ 6 O W celu utworzenia pędzla malującego jednym ze standardowych kolorów systemowych, można posłużyć się funkcją składową CreateColorBrush () Po skompilowaniu i uruchomieniu programu tło okna będzie wymalowane takim samym kolorem, jak tło pulpitu. Wybieranie pędzla do kontekstu urządzenia Do wybrania nowego pędzla posługujemy się funkcją składową kontekstu urządzenia SelectObject (). Funkcjonuje ona tak samo jak w przypadku pióra, zwracając na końcu wskaźnik do pędzla poprzedniego. Także w tej sytuacji powinniśmy zachować ten wskaźnik, by móc przywrócić poprzedni pędzel. Jest to zademonstrowane na listingu 16.15, zmieniającym treść funkcji OnEraseBkgnd (). Listing 16.15. LST17_15.CPP - wybór pędzla do kontekstu urządzenia 1 BOOL CMyDrawYiew: .•OnEraseBkgnd (CDC* pDC) 2 ( 3 CBrush brDesktop; 4 brDeskt9p.CreateSysColorBrush(COLOR_DESKTOP) ; 5 6 CRect rcCIient; 7 GetCIientRect(SrcCIient) ; 8 9 CBrush* pOldBrush = pDC->SelectObject(SbrDesktop) 10 11 pDC->Eliipse(rcCIient); O 12 13 pDC->SelectObject(pOldBrush) ; 14 return TRUE; 15 } O Elipsa narysowana w linii 11 wypełniona zostaje standardowym kolorem tła pulpitu. Przykład powyższy używa wypełnionej elipsy. Możemy zaobserwować pewną dziwną rzecz podczas pracy programu. Otóż wybrany pędzel wypełnia elipsę kolorem pulpitu, lecz ponieważ nie cały jej obszar jest wypełniony, poprzez przezroczyste miejsca widzimy pulpit leżący pod spodem. Stosowanie piór i pędzli 413 Po skompilowaniu i uruchomieniu programu będziemy mogli obejrzeć opisany efekt. Efekt przezroczystości pojawiłby się również w przypadku użycia do wypełnienia elipsy pędzla NULL_BRUSH. Usuwanie pędzli Jak w przypadku piór, możemy również usuwać pędzle, uwalniając zasoby systemowe. Kolejne pędzle tworzymy używając jednej z funkcji Create. Dostępnymi funkcjami są: CreateSolidBrush(), CreateHatchBrush(), CreateBrushindirect(), której przekazuje się strukturę LOGBRUSH, CreatePatternBrush () do użycia bitmapy oraz Creaye-SysColorBrush (), z którą zapoznaliśmy się przed momentem. Wszystkim funkcjom przekazywane są parametry adekwatne do ich konstruktora. Usuwanie bitmap pędzla Usuwając pędzel bitmapowy należy pamiętać również o usunięciu przydzielonej mu bitmapy (która nie jest usuwana automatycznie). Trzeba także upewnić się, iż bitma-pa jest usuwana po pędzlu, by nie usunąć obiektu, który jest w użyciu. Malowanie wypełnionych figur za pomocą pędzli Cały zestaw funkcji rysujących tworzy wypełnione figury. Możemy malować różnego typu wielokąty, półkola, wycinki koła, a także prostokąty. Rysowanie prostokątów i prostokątów z zaokrąglonymi narożnikami Możemy użyć funkcji RectangleO oraz RoundRectO do rysowania prostokątów (podobnie jak funkcji FiliRect (), którą już poznaliśmy). Funkcja Rectangle () posługuje się parametrami identycznymi z Ellipse (), to znaczy pobranymi od CRect współrzędnymi narożników. Funkcja RoundRect () potrzebuje jeszcze jednego zestawu koordynat, które pobiera od CPoint lub jako dwie liczby całkowite. Te dodatkowe parametry określają wymiary elips, wyświetlanych na każdym narożniku prostokąta. Zmienimy treść funkcji OnEraseBkgndO w taki sposób, aby wywoływała RectangleO zamiast Ellipse (). Właściwy kod podany jest poniżej. Następnie tak jak w przypadku funkcji FiliRect () kolor tła ustawiony jest jako zgodny z kolorem pulpitu. pDC->Rectangle(rcCIient) ; // * Narysowanie prostokąta kolorem pulpitu Zauważmy, że jakkolwiek FiliRect () używa wskaźnika pędzla, RoundRectO odnosi się do bieżącego pędzla kontekstu urządzenia. 414_____________________________________Poznaj Visual C++ 6 Funkcja RoundRect () umożliwia utworzenie prostokąta z zaokrąglonymi narożnikami. Jeśli jeszcze raz dokonamy modyfikacji OnDraw() i usuniemy kod połyline () posługując się listingiem 16.16, uzyskamy prostokąt o zaokrąglonej formie. Listing 16.16. LST17_16.CPP - rysowanie za pomocą funkcji RoundRect () 1 void CMyDrawYiew::OnDraw(CDC* pDC) 2 ( 3 CMyDrawDoc* pDoc = GetDocument (); 4 ASSERT_VALID(pDoc); 5 6 // TODO: tu dodaj kod dla własnych danych 7 8 CPen penRed(PS_SOLID,5,RGB(255,0,0) ) ; 9 CPen *p01dPen = NULL; 10 11 pOldPen = pDC->SelectObject(&penRed); 12 13 .CRect rcdient; 14 GetCIientRect(&rcClient) ; 15 16 CBrush brBlue(RGB(O,O,255)); 17 CBrush *p01dBrush = NULL; 18 19 pOldBrush = pDC->SelectObject(sbrBlue); 20 21 rcdient.DeflateRect (50, 50) ; 22 pDC->RoundRect (rcdient, CPoint (15,15) ); O 23 24 pDC->SelectObject(pOldBrush) ; 25 pDC->SelectObject(pOldPen) ; 26 } O W linii 22 widać jak funkcja RoundRect () wykonuje zaokrąglone narożniki o średnicy 15 pikseli.. Po skompilowaniu i uruchomieniu programu, ujrzymy narysowany prostokąt o zaokrąglonych narożnikach, wypełniony kolorem pulpitu i obrysowany grubą, czerwoną obwódką. Stosowanie piór i pędzli 415 Rysowanie elips i okręgów Rysowanie elips poznaliśmy na listingu 16.15. Funkcja E l lip s e (), której użyliśmy wraz z piórem, działa tak samo z pędzlem. Wyjątkiem jest tylko użycie pędzla NULL_BRUSH, które powoduje wstrzymanie rysowania. Dodajmy teraz za linią RoundRect () poniższy kod, powodujący rysowanie elipsy: // Zmniejszenie wymiarów prostokąta roboczego rcdient.DeflateRect (25,25) ; // Narysowanie elipsy wewnątrz prostokąta pDC->Ellipse(rcClient) ; Przydatna sztuczka związana z rysowaniem elips Często tworzy się elipsy o różnej średnicy i w różnych punktach. Sztuczka polega na skonstruowaniu obiektu CRect z tego samego obiektu CPoint dla współrzędnej lewej górnej oraz prawej dolnej. Przykładowo: CRect rcEilipse (ptCenter, ptCenter);. Następnie należy użyć funkcji inflateRect () dla określenia średnicy koła i przekazania prostokąta w celu wykonania rysunku. Po skompilowaniu i uruchomieniu programu, wewnątrz narysowanego wcześniej prostokąta pojawi się elipsa. Ona również będzie miała czerwoną obwódkę, gdyż jest rysowana tymi samymi kolorami. Rysowanie cięciw Cięciwa to elipsa z narysowaną w poprzek linią, dzielącą elipsę na dwie części. Każda z tych części jest cięciwą. Dokładnie na tej zasadzie działa funkcja chord (). Wymaga ona prostokątnego obiektu (CRect) dla określenia współrzędnych, podobnie jak elipsa. Dostarczając jej tych koordynat, wyznaczamy punkty, między którymi przebiegać będzie linia dzieląca elipsę na części. Kształty otrzymanych części zależą od tego, które współrzędne podamy jako pierwsze. Wprowadźmy kolejne modyfikacje w funkcji OnDraw() pomiędzy liniami wyboru pędzla, wzorując się na listingu 16.17. Listing 16.17. LST17_17.CPP - rysowanie cięciw za pomocą funkcji Chord () 1 pOldBrush = pDC->SelectObject(&brBlue); 2 3 CRect rcChord = rcCIient; 4 rcChord.DeflateRect(50,50); 5 6 pDC->Chord(rcChord, O 416 Poznaj Visual C-H- 6 7 8 9 10 rcCIient.TopLeft(), rcdient.BottomRight () ) ; pDC->SelectObject(pOldBrush) ; O Cięciwy powstają w wyniku przeprowadzenia linii dzielącej od lewej górnej współrzędnej do prawej dolnej Pierwszą rzeczą, którą powinniśmy zauważyć, jest fakt inicjalizacji deklaracji obiektu rcchord (w linii 3) z prostokąta roboczego. Następnie wymiary prostokąta zmniejszone zostają o 50 pikseli za pomocą funkcji Deflate () (linia 4), po czym poprzez linie 5,6 i 7 narysowana jest cięciwa. Linia przekątna, dzieląca elipsę przebiega od lewego górnego do prawego dolnego rogu prostokąta roboczego. Po skompilowaniu i uruchomieniu programu otrzymamy obraz widoczny na rysunku 16.8. Rysunek 16.8. Wypełniona cięciwa Rysowanie wielokątów Funkcja PołygonO wykorzystuje takie same parametry jak funkcje PołyBezierf) oraz Połyline (). Wymaga zatem współrzędnych i liczby punktów w zestawie. Rysowanie wielokąta odbywa się poprzez połączenie odcinkami wyznaczonych punktów. Odmiennie niż w przypadku Połyline (), nie musimy pokrywać ostatniego punktu z pierwszym, gdyż obiekt wielokąta zamykany zostaje automatycznie. Stosowanie piór i pędzli 417 Badanie bieżącego trybu wypełniania wielokąta Tryb wypełniania wielokąta można sprawdzić poprzez wywołanie funkcji kontekstu urządzenia GetPołyFillMode (). W zależności od wykorzystanego trybu, funkcja ta zwraca wartość ALTERNATE lub WINDING. Istnieje jednak pewna komplikacja, o której musimy pamiętać - tryb wypełniania. Funkcja o nazwie SetPołyFillMode () pozwala na wybór jednego z dwóch trybów, ALTERNATE lub WINDING. Jeśli wybierzemy ALTERNATE, wielokąt wypełniany będzie liniami od lewej do prawej. W trybie WINDING natomiast kierunek rysowanej linii odnosi się do kierunku jej poprzedniczki. Wprowadzimy teraz modyfikacje funkcji OnDraw (), powodujące rysowanie wieloką-tów, widoczne na listingu 16.18. Listing 16.16. LST17_18.CPP - rysowanie gwiazd za pomocą funkcji Połygon () 1 łiriclude "math.h" 2 void CMyDrawView::OnDraw(CDC* pDC) 3 { 4 CMyDrawDoc* pDoc = GetDocument(); 5 ASSERT_VALID(pDoc); 6 7 CPen penRed(PS_SOLID,5,RGB(255,0,0)); 8 CPen *p01dPen = NULL; 9 10 pOldPen = pDC->SelectObject(SpenRed); 11 12 CRect rcCIient; 13 GetCIientRect(SrcCIient); 14 15 CBrush brBlue(RGB(O,O,255)); 16 CBrush *p01dBrush = NULL; 17 18 pOldBrush = pDC->SelectObject(SbrBlue); 19 20 for(int w=0;w<2;w++) 21 { 22 const int nPoints = 5; 23 double nAngle = (720.0/57.295)/(double)nPoints; 24 int x0ffset = (w ? l : -l)*rcClient.Width()/4; 25 pDC->SetPolyFillMode(w ? ALTERNATE : WINDING); O 26 CPoint ptPołyAr[nPoints]; 27 for (int i=0; KnPoints; i++) ® 28 { 29 ptPolyAr[i].x = x0ffset + 30 (long)(sin((double)i * nAngle) * 100.0); 31 ptPolyAr[i].y = 32 (long)(cos((double)i * nAngle) * 100.0); 33 ptPolyAr[i] += rcCIient.CenterPoint (); 34 } 35 pDC->Polygon(ptPolyAr,nPoints); © 36 37 } 38 39 pDC->SelectObject(pOldBrush) ; 40 pDC->SelectObject(pOldPen) ; 41 } O W pierwszej iteracji w jest równe zero, w związku z tym użyty zostaje tryb WINDING. Podczas drugiej iteracji, gwiazda jest rysowana w trybie ALTERNATE. @ Punkty wierzchołków zostają obliczone i zachowane w tablicy punktów ptPołyAr. © Wielokąt rysowany jest na podstawie tablicy punktów i ich liczby w tablicy przez funkcję Połygon(). Musimy również dodać na początku wskazówkę #include "math.h", dzięki czemu funkcjonowały będą wyrażenia sin oraz cos. Dwie gwiazdy zostają narysowane przez funkcję Połygon () (linia 35) na podstawie punktów zawartych w tablicy (linia 26). Pierwsza gwiazda po lewej używa trybu wypełnienia WINDING, druga natomiast trybu ALTERNATE. Ponieważ linie tworzące kształty gwiazd krzyżują się, tryb WINDING powoduje całkowite wypełnienie figury, tryb ALTERNATE natomiast pozostawia niewypełnioną część środkową. Po skompilowaniu i uruchomieniu programu, narysowane wielokąty powinny wyglądać jak na rysunku 16.9. Stosowanie piór i pędzli Rysunek 16.9. Wielokąty narysowane z użyciem trybów wypełniania ALTERNATE iWINDING Rozdział 17 Korzystanie z czcionek Wyświetlanie tekstu w różnych kolorach i stylach Użycie czcionek do rozszerzania aplikacji Od czasu wynalezienia maszyny drukarskiej przez Williama Caxtona słowo drukowane stało się nieodłącznym elementem naszego życia. Powstała ogromna liczba krojów czcionek, które obecnie dostępne są w niezliczonej obfitości. Właściwy wybór czcionki jest ważny dla obrazu i stylu aplikacji. Windows wspiera wyświetlanie tekstu szerokim wachlarzem krojów i stylów czcionek. Funkcje rysujące tekst Głównym zadaniem projektantów GDI Windows było opracowanie mechanizmu wspierającego wyświetlanie wysokiej jakości grafiki przy użyciu dostępnych urządzeń. Terminem, który zrobił furorę w latach osiemdziesiątych, jest WYSIWYG (What You See Is What You Get). Obecnie jest on w powszechnym użyciu. Modelowanie tekstu w całym zakresie krojów czcionek za pomocą wszelkiego rodzaju monitorów czy drukarek jest jednym z najpoważniejszych zagadnień tego tematu. Szczęśliwie praca projektantów zakończyła się sukcesem. Mimo szerokiego wyboru czcionek, dzięki funkcjom wyświetlania tekstu możemy modelować tekst, mając pewność, iż otrzymają one dokładnie taką sama formę na różnych urządzeniach. PATRZ TAKŻE • Więcej informacji na temat kontekstów urządzeń znajdziesz w rozdziale 15. • Jak określić możliwości danego urządzenia dowiesz się w rozdziale 15. 422_______________________________ Poznaj Visual C++ 6 Inne funkcje Text0ut () Istnieją funkcje podobne do Text0ut (). TabbedTextOut () rozszerza tabulację w formie serii pozycji tabulatora określonych w tablicy. PolyTextOut () wyświetla tablicę łańcuchów znakowych w jednym wywołaniu. ExtTextOut() pozwala określać rozszerzone opcje modelowania tekstu. Odrysowywanie prostego tekstu Funkcja składowa kontekstu urządzenia Text0ut () jest prawdopodobnie najszybszą i najłatwiejszą metodą rysowania tekstu. Wymaga ona podania współrzędnych x oraz y, a także wskazania obiektu CString przechowującego treść wyświetlanego tekstu. Posłużymy się ponownie przykładem aplikacji SDI, zawierającej pusty obszar roboczy. Utworzymy szkielet aplikacji, korzystając z kreatora AppWizard i akceptując wszystkie opcje domyślne, jak miało to miejsce w rozdziale 12. Użyjemy tej aplikacji do wypróbowania działania kilku funkcji rysowania tekstu. Otworzymy panel ClassView w widoku Project Workspace. Pośród innych ujrzymy pozycję klasy CSimpTextView. Klikniemy umieszczony obok symbol plusa, by otworzyć listę składowych tej klasy. Następnie klikniemy dwukrotnie pozycję OnDraw (), a do kodu implementacyjnego, otwartego w oknie edytora, dopiszemy zawartość listingu 17.1. Linie, które wpiszemy posłużą do narysowania prostego tekstu. Listing 17.1. LST18_1.CPP- rysowanie tekstu przez funkcję Text0ut () 1 void CSimpTextView::OnDraw(CDC* pDC) 2 { 3 CSimpTextDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 8 CString strText = "Water under the bridge."; 9 pDC->TextOut(100,100,strText) ; O 10 } O Funkcja Text0ut () rysuje tekst pobrany z łańcucha w zmiennej, począwszy od punktu o współrzędnych (100,100). Korzystanie z czcionek 423 Na listingu 17.1 wyświetlany tekst zapisany jest w łańcuchu strText i przekazany funkcji Text0ut () w celu wyświetlenia na ekranie. Text0ut () również wymaga zdefiniowania współrzędnych pozycji tekstu, która w tym przypadku określona została w linii 9 na 100 pikseli w obu osiach. Po skompilowaniu programu ujrzymy tekst wyświetlony domyślną czcionką (czarna na białym tle), w pobliżu lewego górnego rogu okna. Ustalanie wyrównania tekstu Tekst możemy ustawiać na kilka sposobów względem wyznaczonego punktu odniesienia, korzystając z funkcji SetTextAlign (). Ta funkcja składowa kontekstu urządzenia posługuje się kilkoma znacznikami do ustalenia wyrównania oraz sposobu ustawienia kursora po odrysowaniu tekstu. Znaczniki wyznaczają miejsce w odniesieniu do tekstu, w którym powinien znajdować się wyznaczony punkt. Można by sądzić, iż znacznik TA_RIGHT powoduje wyrównanie do punktu po prawej stronie, lecz w rzeczywistości tekst zostaje wyrównany do lewej strony, mając punkt po prawej. Tabela 17.1 wymienia znaczniki, które możemy przekazać funkcji SetTextAlign () . Ustalanie bieżącego trybu wyrównywania Bieżące ustawienia dla kontekstu urządzenia, dotyczące wyrównywania mogą zostać zbadane za pomocą funkcji GetTextAlign (). Zwraca ona wartość składającą się z takich samych znaczników, jakich używa się do ustawienia wyrównywania poprzez funkcję SetTextAlign(). Tabela 17.1. Znaczniki wyrównania tekstu przekazywane funkcji SetTextAlign () Znacznik Sposób oddziaływania na tekst TA_LEFT Tekst wyrównany po prawej stronie punktu TA_RIGHT Tekst wyrównany po lewej stronie punktu TA_CENTER Tekst wyśrodkowany względem punktu TA_TOP Tekst umieszczony poniżej punktu TA_BOTTOM Tekst umieszczony powyżej punktu TA_BASELINE Linia podstawy tekstu przechodzi przez punkt TAJJPDATECP po wykonaniu funkcji Text0ut () pozycja kursora graficznego zostaje uaktualniona, bez względu na wskazane współrzędne, a nowy tekst wyświetlany jest bezpośrednio za poprzednią pozycją TA_NOUPDATECP Pozycja kursora graficznego nie jest uaktualniana 424_____________________________________Poznaj Visual C++ 6 Zapoznajmy się bliżej z działaniem znaczników wyrównywania rysując, krzyż w punkcie o współrzędnych (200,200), a następnie umieszczając tekst w różnych położeniach względem niego i przekazując odpowiednie znaczniki. Zmienimy w ten sposób treść funkcji OnDraw (), wzorując się na listingu 17.2. Listing 17.2. LST18_2.CPP - ustalanie wyrównania tekstu 1 void CSimpTextView::OnDraw(CDC* pDC) 2 ( 3 CSimpTextDoc* pDoc = GetDocument() ; 4 ASSERT_VALID(pDoc); 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 8 // ** Wyrównanie tekstu do prawej strony punktu 9 pDC->SetTextAlign(TA_RIGHT); O 10 11 // ** Wyświetlenie tekstu 12 pDC->TextOut(200,200,"<-Right->") ; 13 14 // ** Wyrównanie tekstu do lewej strony punktu 15 pDC->SetTextAlign(TA_LEFT) ; 16 pDC->TextOut(200,200,"<-Left->") ; 17 18 // ** Wyśrodkowanie tekstu względem punktu, leżącego poniżej tekstu 19 pDC->SetTextAlign(TA_CENTER + TA_BOTTOM); @ 20 pDC->TextOut(200,200,"<-Center->") ; 21 22 pDC->MoveTo(150,200); // ** Przesuń 23 pDC->LineTo(250,200); // ** Narysuj poziomą linię 24 pDC->MoveTo(200,150); // ** Przesuń 25 pDC->LineTo(200,250); // ** Narysuj pionową linię 26 27 } O Tekst domyślnie wyrównywany jest ze znacznikiem RIGHT, lecz użyto go tutaj dla zwiększenia przejrzystości. © Znaczniki mogą być łączone według potrzeb. W tym przypadku tekst jest wy-środkowany i umieszczony powyżej wyznaczonego punktu odniesienia. Korzystanie z czcionek 425 Każde wywołanie Text0ut () odnosi się do tych samych współrzędnych, lecz tekst pojawia się w różnych pozycjach. Jeśli skompilujemy i uruchomimy program, naszym oczom ukaże się widok jak na rysunku 17.1. gle Edit Mew Usip ..^ <-L< ft-> <-Right-X-Left-> Rysunek 17.1. Różne warianty wyrównania tekstu PATRZ TAKŻE • Szczegóły dotyczące rysowania linii i figur znajdziesz w rozdziale 16. Zmiana koloru czcionki i jej tła Domyślnie czcionki wyświetlane są w czarnym kolorze na białym tle - możemy to jednak zmienić w kontekście urządzenia za pomocą dwóch funkcji, SetTextColor () oraz SetBkColor (). Obie funkcje pobierają tylko jeden parametr, czyli wartość COLORREF żądanego koloru dla czcionki lub tła. Zwracają one również wartości COLORREF, określające kolory użyte poprzednio. Sprawdzanie bieżących kolorów tekstu Bieżące kolory użyte w tekście można zbadać za pomocą funkcji GetTextColor() oraz GetBkColor (), które zwracają wartości kolorów czcionki i tła. Wartości podawane są w postaci COLORREF. 426_____________________________________Poznaj Visual C++ 6 Wykorzystajmy te funkcje do zmiany koloni czcionki i tła poprzez dopisanie do implementacji OnDraw() wywołań funkcji, przed wywołaniem Text0ut (). Możemy posłużyć się w tym celu listingiem 17.3. Listing 17.3. LST18_3.CPP - ustalenie kolorów za pomocą funkcji SetTextColor () oraz SetBkColorO 1 // ** Wybór niebieskiego koloru dla czcionki 2 pDC->SetTextColor(RGB(O,O,255)) ; 3 pDC->TextOut(200,200,"<-Right->") ; 4 5 pDC->SetTextAlign(TA_LEFT); 6 7 // ** Wybór czerwonego koloru dla czcionki 8 pDC->SetTextColor(RGB(255,0,0)); O 9 pDC->TextOut(200,200,"<-Left->") ; 10 11 // Punkt odniesienia pośrodku i poniżej tekstu 12 pDC->SetTextAlign(TA_CENTER + TA_BOTTOM); 13 14 // ** Wybór żółtego koloru dla czcionki 15 pDC->SetTextColor(RGB(255,255, 0) ) ; 16 17 // ** Wybór ciemnoniebieskiego koloru dla tlą 18 pDC->SetBkColor(RGB(0,0,128) ) ; @ 19 pDC->TextOut(200,200,"<-Center->") ; O Ta linia ustala kolor czcionki, którym ma zostać narysowana. @ Ta linia ustala kolor tła. Na powyższym listingu funkcja SetTextOut () wywołana w linii 2 zmienia kolor tekstu na niebieski, linia 8 na czerwony, a linia 15 na żółty. W linii 18 następuje zmiana koloru tła na ciemnoniebieski. Po skompilowaniu i uruchomieniu programu zobaczymy te same napisy, co poprzednio lecz w różnych kolorach. PATRZ TAKŻE • Więcej informacji na temat wyboru kolorów i makra RGB znajdziesz w rozdziale 16. Korzystanie z czcionek 427 Rysowanie tekstu przezroczystego i nieprzezroczystego Rysowany tekst zwykle przesłania obszar pod nim umieszczony. Czasami jednak zachodzi potrzeba zlania tekstu z rysunkiem. Funkcja SetBkMode () umożliwia przełączanie pomiędzy dwoma trybami. Pobiera jeden parametr, będący znacznikiem żądanego trybu. Dostępne tryby to domyślny OPAQUE oraz TRANSPARENT (dla tekstu z przezroczystym tłem). Jeśli wywołamy funkcję SetBkMode () ze znacznikiem OPAQUE, do narysowania tekstu użyte zostaną oba wybrane kolory, czcionki i tła. Podczas rysowania w trybie TRANSPARENT użyty zostanie tylko kolor czcionki. Aby zademonstrować działanie tych trybów, wprowadzimy zgodnie z listingiem 17.4 modyfikacje funkcji OnDraw (). Listing 17.4. LST18_4.CPP - ustalanie trybów TRANSPARENT i OPAQUE 1 void CSimpTextView::OnDraw(CDC* pDC) 2 ( 3 CSimpTextDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 1 8 pDC->Ellipse(160,160,240,240) ; 9 pDC->SetTextAlign(TA_CENTER) ; 10 11 // ** Wybranie trybu Transparent 12 pDC->SetBkMode(TRANSPARENT); 13 14 // ** Wyświetlenie tekstu 15 pDC->TextOut(200, 180, "Transparent"); O 16 17 // ** Wybranie trybu Opaque 18 pDC->SetBkMode(OPAQUE); @ 19 20 // ** Wy s wie t Jen-i e tekstu 21 pDC->TextOut(200, 220, "Opaque"); 22 } O W trybie TRANSPARENT okrąg widoczny jest pod napisem. @ W trybie OPAQUE tekst jest rysowany wraz z tłem przesłaniającym okrąg. 428_____________________________________Poznaj Visual C++ 6 Na powyższym listingu każdy tekst wyświetlany za linią 12 będzie posiadał przezroczyste tto. W linii 18 następuje zmiana trybu na OPAQUE, co powoduje rysowanie tła nieprzezroczystego. Jeśli skompilujemy i uruchomimy program zawierający opisane modyfikacje zobaczymy, że powierzchnia okręgu jest przesłonięta tłem napisu Opaque. W przypadku napisu Transparent, okrąg nie jest przesłaniany jego tłem. Ustalony jest kolor tła nie jest brany pod uwagę podczas rysowania w trybie TRANSPARENT. Obcinanie tekstu Często zachodzi potrzeba obcięcia tekstu nie mieszczącego się w wyznaczonym prostokącie. Mimo iż możemy dokonać brutalnego cięcia, rozsądniejszym rozwiązaniem jest umożliwienie tekstowi wypłynięcie poza wyznaczony obszar. Użycie znacznika TA UPDATECP w funkcji ExtTextOut () Znacznik wyrównania tekstu TA_UPDATECP może być użyty przez funkcję setAli-gnText () w połączeniu z ExtTextOut (). Gdy znacznik ten jest ustawiony, współrzędne pobierane w pierwszym wywołaniu Text0ut () lub ExtTextOut () są wykorzystywane w zwykły sposób. Kolejne wywołania jednakże ignorują podane współrzędne i rysują tekst za bieżącą pozycją kursora. Funkcja ExtTextOut () jest bardziej wyrafinowana niż Text0ut (). Przekazując jej znacznik ETO_CLIPPED powodujemy obcięcie tekstu. ExtTextOut (), podobnie jak Text0ut () pobiera jako dwa pierwsze parametry współrzędne x oraz y. Jako trzeci parametr podawać możemy znaczniki, takie jak ETO_CLIPPED. Możemy również połączyć znaczniki ETO_CLIPPED oraz ETO_OPAQUE, co pozwoli użyć tekstu nieprzezroczystego, nawet jeśli ustawiony jest tryb TRANSPARENT. Jeśli nie życzymy sobie obcinania ani nie-przezroczystości tekstu, jako trzeci parametr podajemy wartość zero (jak w linii 13 listingu 17.5). Czwarty parametr wymaga obiektu CRect i jeśli ustawiony jest znacznik obcinania, obcinanie wykonane zostanie równo z krawędziami prostokąta. Piątym parametrem jest treść wyświetlanego tekstu umieszczona w obiekcie CString. Na koniec, jako szósty parametr przekazujemy tablicę odstępów międzyznakowych lub wybieramy odstępy domyślne, przekazując wartość NULL. Dokonamy teraz kolejnej modyfikacji treści OnDraw, według listingu 17.5. Listing 17.5. LST18_5.CPP - obcinanie tekstu przez funkcję ExtTextOut () 1 void CSimpTextView::OnDraw(CDC* pDC) 2 ( 3 CSimpTextDoc* pDoc = GetDocument() ; 4 ASSERT_VALID(pDoc); 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 8 CRect rcClipBox(CPoint(100,100),CPoint(250,120)); 9 pDC->Rectangle(rcClipBox); 10 pDC->SetBkMode(TRANSPARENT) ; 11 12 // ** Tekst nieobcięty 13 pDC->ExtTextOut(100,100,O,rcClipBox, 14 _"This text won't fit in there!",NULL); 15 16 rcClipBox.OffsetRect(O,40); 17 pDC->Rectangle(rcClipBox); 18 19 // ** Tekst obcięty 20 pDC->ExtTextdut(100,140,ETO_CLIPPED ,rcClipBox, O 21 _"This text won't fit in there!",NULL); 22 } O Funkcja ExtTextOut() obcina tekst wewnątrz wyznaczonego prostokąta, o ile przekazano znacznik ETO_CLIPPED. W linii 8 powyższego listingu zadeklarowany zostaje prostokąt. Będzie on użyty do pozycjonowania i obcięcia tekstu. Wywołana w linii 13 funkcja ExtTextOut () nie posiada znacznika ETO_CLIPPED, w związku z czym tekst wylewa się poza obszar prostokąta. Drugie wywołanie funkcji w linii 20 posiada ten znacznik i dlatego tekst będzie obcięty wewnątrz prostokąta. Po skompilowaniu i uruchomieniu programu zobaczymy dwa prostokąty. Pierwszy będzie zawierał tekst wylewający się na zewnątrz, w drugim natomiast ten sam tekst zostanie obcięty. PATRZ TAKŻE • Szczegółów na temat rysowania i obcinania szukaj w rozdziale 15. Tworzenie czcionek Jak dotąd, tekst wyświetlany był przy użyciu domyślnej czcionki kontekstu urządzenia. Podobnie jak pędzle i pióra, czcionki również reprezentowane są przez obiekty GDI i mogą być umieszczane oraz usuwane z kontekstu urządzenia. Czcionki możemy tworzyć 430_____________________________________Poznaj Visual C++ 6 korzystając z różnych funkcji dostarczanych przez GDI Windows oraz klas osłonowych MFC. Stosowanie klasy CFont CFont jest klasą osłonową dla obiektów GDI czcionek. Inaczej niż niektóre klasy osłonowe, CFont nie może być konstruowana z ustawieniami domyślnymi. Musimy zadeklarować obiekt CFont, a następnie użyć jednej spośród funkcji Create dla określenia wielu możliwych parametrów. Funkcje Create nie zawsze są w stanie dostarczyć czcionki zgodnej z oczekiwaniami. Wtedy program zarządzający czcionkami stara się znaleźć czcionkę najbardziej zbliżoną do żądanej (zwykle wywiązując się z zadania całkiem dobrze). Tworzenie czcionek za pomocą funkcji CreatePointFont() Zastosowanie funkcji CreatePointFont () jest najłatwiejszym sposobem tworzenia czcionek. Funkcja ta wymaga trzech parametrów: rozmiaru czcionki podanego w dziesiątych częściach punktu, nazwy kroju czcionki (na przykład Arial) oraz wskazania kontekstu urządzenia, w którym czcionka ma zostać umieszczona. Posługując się listingiem 17.6 zmodyfikujmy treść OnDraw (), w wyniku czego wyświetlony zostanie tekst za pomocą dużej czcionki Arial. Listing 17.6. LST18_6.CPP - tworzenie czcionek za pomocą funkcji CreatePointFont () 1 void CSimpTextView::OnDraw(CDC* pDC) 2 { 3 CSimpTextDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc); 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 1 8 // ** Utworzenie czcionki Arial o rozmiarze 36 punktów 9 CFont fnBig; 10 fnBig.CreatePointFont(360,"Arial",pDC); O 11 12 CFont* pOldFont = pDC->SelectObject(SfnBig); 13 14 pDC->TextOut(50,50,"** 36 Pt Arial Font **"); 15 16 pDC->SelectObject(p01dFont); 17 ) Korzystanie z czcionek 431 O Funkcja CreatePointFontt) daje możliwość szybkiego i łatwego tworzenia czcionek o określonym kroju i rozmiarze. Zauważmy, iż w linii 10 przekazano funkcji wartość 360. Wielkość czcionki została bowiem podana w dziesiątych częściach punktu. Daje to większą elastyczność w ustalaniu rozmiaru czcionki. Po uruchomieniu skompilowanego programu ujrzymy na ekranie tekst (ustalony w linii 14), wyświetlony za pomocą czcionki Arial o rozmiarze 36 punktów. Tworzenie czcionek za pomocą funkcji CreateFont() Funkcja CreateFont () jest prawdopodobniej jedną z najsprytniejszych funkcji, jakie możemy znaleźć w MFC. Jest również jedną z najobszerniejszych, gdyż wymaga 14 parametrów, którymi są w większości złożone kombinacje znaczników. Prototyp funkcji widoczny jest na listingu 17.7. Listing 17.7. LST18_7.CPP - parametry dla funkcji CreateFont () 1 BOOL CreateFont( int nHeight, int nWidth, 2 int nEscapement, int nOrientation, int nWeight, 3 BYTE bitalic, BYTE bUnderline, BYTE cStrikeOut, 4 BYTE nCharSet, BYTE nOutPrecision, 5 BYTE nClipPrecision, BYTE nQuality, 6 BYTE nPitchAndFamiły, LPCTSTR IpszFacename ) ; Ustalanie wysokości i szerokości czcionki Dwa pierwsze parametry funkcji CreateFont () służą do ustalenia wysokości i szerokości żądanej czcionki. Pierwszy parametr, nHeight, może być ujemny lub dodatni. Jeśli ustalimy wartość dodatnią, program zarządzający będzie poszukiwał czcionki zbliżonej wysokością do oczekiwanej, odnosząc się do wymiaru komórki czcionki. Podanie wartości ujemnej powoduje poszukiwanie czcionki w oparciu o wysokość znaku. Różnica pomiędzy komórką czcionki i znakiem jest taka, iż komórka posiada pewne puste obszary pod i ponad znakiem, natomiast wysokość znaku to wysokość komórki po odjęciu tych pustych obszarów. Wartość zero komunikuje programowi zarządzającemu, iż powinien poszukiwać czcionki według założeń domyślnych. Parametr nWidth określa średnią szerokość żądanej czcionki (czcionki proporcjonalne posiadają znaki szersze i węższe). Przekazując wartość zero powodujemy, iż program zarządzający będzie poszukiwał czcionek według domyślnych zasad, bazując na wysokości oraz proporcjach czcionki. 432 ____________ Poznaj Visual C++ 6 Zasady dopasowywania wysokości czcionki Podczas dopasowywania czcionki poszukiwana jest czcionka najwyższa, jednak niższa niż określona wysokość. Ustalanie pochylenia i orientacji czcionki Za pomocą parametrów Escapement i Orientation możemy ustalać pochylenie i orientację tekstu, a także obracać znaki. • Parametr nEscapement pozwala rysować kolejne znaki pochylone pod podanym w dziesiątych częściach stopnia kątem względem osi x. Wartość 900 daje tekst pionowy. • Parametr nOrientation określa kąt, pod jakim przebiega podstawa znaku, względem osi x, podawana również w dziesiątych stopnia. Ustalanie pogrubienia, kursywy, podkreślenia i przekreślenia Kolejne cztery parametry odnoszą się do grubości czcionki (która może być pogrubiana) i umożliwia wybór dodatkowych stylów, takich jak kursywy, podkreślenia oraz przekreślenia. • Parametr nWeight pozwala określić grubość czcionki, od O (bardzo cienka) do 1000 (bardzo gruba). Predefiniowane wartości podane są w tabeli 17.2. Tabela 17.2. Znaczniki wagi czcionki Znacznik Wartość wagi FW_DONTCARE Q FWJTHIN 100 FW_EXTRALIGHT 200 FW_LIGHT 300 FW_NORMAL 4QO FW_MEDIUM 500 FWJSEMIBOLD 600 FW_BOLD 700 FW_EXTRABOLD g00 FW_HEAVY 900 Korzystanie z czcionek___________________________ __ 433 • Parametr bitalic powoduje zastosowanie kursywy, jeśli ustaloną jego wartością jest TRUE. • Parametr bUnderline powoduje użycie czcionki z podkreśleniem, jeśli jego wartością jest TRUE. • Parametr bStrikeOut natomiast powoduje użycie czcionki przekreślonej, jeśli jego wartość równa jest TRUE. Równoważne znaczniki wagi czcionki Oprócz wymienionych, spotkać można również inne znaczniki, takie jak FWJJLTRALIGHT, FW_REGULAR, FW_DEMIBOLD, FWJJLTRABOLD, FW_BLACK, które równoważne są ze znacznikami odpowiednio FW_EXTRALIGHT, FWJMORMAL, FW_SEMI- BOLD,FW EXTRABOLD oraz FW HEAVY. Ustalenia dotyczące jakości i precyzji Parametry jakości i precyzji pozwalają ustalić, w jaki sposób czcionki będą wybierane i modelowane na urządzeniu docelowym. Istnieją poza tym możliwości dodatkowych ustaleń, takich jak stałe lub proporcjonalne odległości między znakami, a także użycie szeryfów. • Parametr nCharSet pozwala wybrać zestaw znaków. Zwykle będzie to ANSI_CHAR-SET, ustalający zestaw ANS l. Czasami jednak niezbędne jest wybranie SYMBOL_CHAR-SET, co pozwala korzystać z zestawu symboli zamiast zwykłych liter. • Parametr nOutPrecision określa, w jaki sposób program zarządzający czcionkami będzie wybierał jedną czcionkę w zastępstwie innej. Zwykle stosowanym jest OUT_DEFAULT_PRECIS, lecz jeśli koniecznie chcemy korzystać z czcionek TrueType, możemy wybrać OUT_TT_PRECIS. • Parametr ndipprecision ustala precyzję obcinania. Zwykle wybranym trybem jest CLIP_DEFAULT_PRECIS. • Parametr nQuality pozwala określić kompromis pomiędzy jakością wyświetlanych znaków a dopasowaniem ich do innych, ustalonych parametrów. Używanymi trybami Są DEFAULT_QUALITY, DRAFT_QUALITY Oraz PROOF_QUALITY. • Parametr nPitchAndFamiły umożliwia ustalenie dwóch rzeczy jednocześnie. Może to być powodem komunikatu o błędzie kompilatora, który powie o niemożności przyjmowania przez CreateFont 15 parametrów. Jeśli otrzymamy taki komunikat, powinniśmy sprawdzić, czy podane wartości są połączone ze sobą (operatorem + lub II), a nie oddzielone przecinkiem. 434__________________________________ Poznaj Visual C++ 6 Alternatywne znaczniki precyzji obcinania Znaczniki te zmieniają sposób obcinania czcionki, gdy tekst nie mieści się w obszarze rysowania. Znacznik CLIP_EMBEDDED może być użyty w stosunku do osadzonych czcionek tylko do odczytu. Znacznik CLIP_ANGLES natomiast zastosować można, gdy zmieniona została orientacja tekstu. W starszych kodach można spotkać inne znaczniki, lecz większość z nich nie znajduje się już w użyciu i może być zignorowana. Wartości możliwe do ustalenia dla odstępów międzyznakowych podane są w tabeli 17.3. Tabela 17.3. Ustawienia odstępów Znacznik DEFAULT_PITCH VARIABLE_PITCH FIXED PITCH Opis Typowy dla danej czcionki Odstępy zmienne Odstępy stałe, użyteczne dla emulatorów terminali Znaczniki wymienione w tabeli 17.3 możemy łączyć ze znacznikami rodzin typograficznych, wymienionymi w tabeli 17.4. Dla przykładu, można połączyć ze sobą znaczniki DEFAULT_PITCH | | FF_SCRIPT w celu uzyskania czcionki o domyślnych odstępach i stylu pisma ręcznego. Stosowanie czcionek Tnie Type Używając znacznika TMPF_TRUETYPE w połączeniu ze znacznikiem rodziny czcionki można wybierać czcionki True Type danej rodziny. Tabela 17.4. Znaczniki rodziny typograficznej Znacznik rodziny Opis FF_DECORATIVE Czcionki oryginalne FF_DONTCARE Bez znaczenia FF_MODERN Stałe odstępy lub szerokość kreski FF_ROMAN Proporcjonalne odstępy, zmienna grubość kreski FF_SCRI PT Styl pisma ręcznego FF_SWISS Odstępy proporcjonalne, lecz bez szeryfów TMPFJTRUETYPE Wymagana wersja TrueType Korzystanie z czcionek____________________________________435 Czcionki TrueType rysowane są za pomocą serii krzywych. Z tego też względu po powiększeniu wyglądają znacznie lepiej niż czcionki rastrowe. Określanie nazwy czcionki Parametr IpszFacename umożliwia określenie nazwy jednej spośród zainstalowanych w systemie czcionek, na przykład Times New Roman. Tworzenie czcionki za pomocą funkcji CreateFont() Spróbujmy zatem utworzyć czcionkę, którą następnie użyjemy w kolejnym przykładzie. Wprowadzimy zmiany w treści funkcji składowej widoku OnDraw (), widoczne na listingu 17.8. Listing wyświetli kilka linii tekstu, obróconych wokół punktu środkowego poprzez zmianę orientacji kolejnych linii w zakresie kąta pełnego, czyli 360 stopni. Listing 17.8. LST18_8.CPP - tworzenie czcionek za pomocą funkcji CreateFont () 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void CSimpTextView::OnDraw(CDC* pDC) { . CSimpTextDoc* pDoc = GetDocument() ; ASSERT_VALID(pDoc) ; // TODO: tu dodaj kod rysowania dla własnych danych II ** Deklaracja obiektu CRect dla obszaru roboczego CRect rcCIient; GetCIientRect(SrcCIient) ; // ** Ustalenie punktu środkowego CPoint ptCentre = rcCIient.CenterPointO; pDC->SetBkMode(TRANSPARENT) ; // ** Pęt-la przez 360 stopni for(int i=0 ; i <360 ; i+=18) ( CFont fnBig; // ** Wysokość czcionki 30 punktów, szerokość domyślna fnBig.CreateFont(30,0, O // ** Zmiana orientacji i*10,i*10, // ** Zwiększenie wagi czcionki i/4, FALSE, TRUE, //** Podkreślenie 436_____________________________________Poznaj Visual C++ 6 29 FALSE, 30 ANSI_CHARSET, 31 OUT_DEFAULT_PRECIS, 32 CLIP_DEFAULT_PRECIS, 33 PROOF_QUALITY, 34 DEFAULT_PITCH + FF_DONTCARE, 35 "Arial" 36 ); 37 38 CFont* pOldFont = pDC->SelectObject(SfnBig); 39 40 // ** Rysowanie tekstu 41 pDC->TextOut(ptCentre.x,ptCentre.y, @ 42 ".....Beautiful Fonts") ; 43 44 pDC->SelectObject(pOldFont) ; 45 ) 46 } O CreateFont () zezwala na pasowanie i utworzenie kroju, lecz może on nie spełniać warunków określonych parametrami. © Funkcja Text0ut () wyświetla kolejne, obrócone linie tekstu. Na powyższym listingu w liniach 17-44 następuje obracanie kolejnych linii tekstu wokół punktu środkowego, określonego w linii 13. Potężna funkcja CreateFont () rozpoczyna działanie w linii 22. W linii 26 przekazany jej jest znacznik nakazujący zwiększanie wagi czcionki. Kąt obrotu kolejnych linii tekstu przekazany jest poprzez parametry Escapement oraz Orientation w linii 24, nakazując przechylanie czcionki o stałą wartość kątową, w kolejnych powtórzeniach pętli. Gdy skompilujemy i uruchomimy program, ujrzymy kilkanaście linii tekstu, owiniętych wokół punktu środkowego, jak na rysunku 17.2. Korzystanie z czcionek 437 518 Erfil 5M"J. Rysunek 17.2. Obracanie tekstu za pomocą funkcji CreateFont () Jednokrotne tworzenie czcionek Utworzenie czcionek potrzebnych danej aplikacji i zapisanie ich w formie składowych zmiennych pozwala na przyspieszenie działania aplikacji, gdyż czcionki nie muszą być tworzone do każdorazowego użycia. Wybór czcionek Użytkownik Windows może wybierać czcionki spośród szerokiego zakresu krojów. Wiele aplikacji, nie tylko do edycji tekstu, oferuje taką możliwość jako standardową opcję. By nadać tę cechę własnej aplikacji, musimy uzyskać dostęp do listy zainstalowanych czcionek. GDI umożliwia to poprzez funkcje wyliczające czcionki oraz specjalne okna dialogowe. Wyliczanie czcionek W jaki sposób program może pobrać listę czcionek wraz z ich ustawieniami? Odpowiedzią na to pytanie jest funkcja EnumFont Familie s () oraz stowarzyszona z nią funkcja zwrotna (ang. calibackfunction). Funkcja zwrotna ustanawia funkcję wywoływaną dla każdej pozycji listy, przekazującą jej nazwę czcionki. Funkcja wyliczająca przegląda całą listę (w tym przypadku czcionek) i wywołuje funkcję zwrotną dla każdej odnalezionej pozycji. 438 Poznaj Visual C++ 6 Możemy nakazać funkcji wyliczającej (ang. enumerator) przejście przez całą listę czcionek i pobranie wszelkich dostępnych informacji. Funkcja wyliczająca przekazuje następnie owe informacje funkcji zwrotnej. Możemy wyświetlić listę zainstalowanych czcionek z ich domyślnymi ustawieniami, co widoczne jest na rysunku 17.3. Kod listingu 17.9 i związane z nim objaśnienia, pokazują, w jaki sposób można wykorzystać funkcję EnumFontFamilies () do tego zadania. W celu wyświetlenia listy czcionek (takiej jak na rysunku 17.3) musimy przed kodem funkcji onDraw () umieścić kod funkcji zwrotnej. Zauważmy, iż jest to funkcja globalna, dlatego też nie widzimy nazwy żadnej klasy przed jej nazwą własną. Musimy ją umieścić powyżej OnDraw (), gdyż w przeciwnym wypadku, musielibyśmy dodać deklarację funkcji przed OnDraw (). Wywołanie EnumFontFamilies () możemy natomiast umieścić wewnątrz OnDraw (), co widać na listingu 17.9. „..,,_..„.....„ , ,„„ ......,,,. .,._,......„.._.....„.........,, System Fixtdsys Teminal MS Serit MS Sons Serrt Courier &W3* Roman lOC/lipt Modern D^xrr^^ Arial Courier New E\^jJ,po^ Times New Roman tXBV)o^X«^» iSt/WK:. i:i:fli1 ,:il!lilF SSSSSSsf^ Rysunek 17.3. Czcionki wyhstowane przez EnumFontFamilies () Listing 17.9. LST18_9.CPP - wyliczenie i wyświetlenie zainstalowanych czcionek // ** Funkcja zwrotna int CALLBACK FontCallBack(ENUMLOGFONT FAR* Ipelf, O NEWTEXTMETRIC FAR *lpnt, int FontType, LPARAM IParam) ( // ** Pobranie z IParam wskaźnika kontekstu urządzenia Korzystanie z czcionek 439 CDC* pDC = (CDC*)lParam; CFont fnEnum; // ** Pośrednie utworzenie czcionki fnEnum.CreateFontIndirect(&lpelf->elfLogFont); ® CFont* pOldFont = pDC->SelectObject (SfnEnuin) ; // ** Ustalenie bieżącej pozycji kursora int nYPos = pDC->GetCurrentPosition().y; // ** Wyświetlenie nazwy czcionki pDC- >TextOut(5,nYPos,CString(lpelf->elfLogFont. IfFaceName)) ; //** Move down by the height of the font pDC->MoveTo(0,nYPos + lpelf->elfLogFont.IfHeight); © pDC->SelectObject(p01dFont) ; // ** Zwrócenie TRUE dla kontynuacji return TRUE; l l l l l l l l l l l l l l l l l l l l l l / l l l l l l l l l l l l l l l l l l l l l l l l l l l t l l l II CSimpTextView drawing void CSimpTextView::OnDraw(CDC* pDC) ( CSimpTextDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc) ; // TODO: tu dodaj kod rysowania dla własnych danych II ** Ustawienie funkcji wyliczającej EnumFontFamilies(pDC- >Get3afeHdc(),NULL, O (FONTENUMPROC)FontCallBack,(LPARAM)pDC) ; } O Funkcja zwrotna będzie wywoływana dla każdej czcionki zainstalowanej w systemie. Informacje o czcionce przekazywane będą jako parametry. 440_____________________________________Poznaj Visual C++ 6 @ Funkcja CreateFontindirect () jest tożsama z CreateFont(), lecz parametry przekazywane są jej jako składowe struktury LOGFONT. © Funkcja MoveTo () jest wywoływana w celu ustawienia kursora w gotowości do wyświetlenia kolejnej czcionki. O Adres funkcji zwrotnej przekazywany jest do EnumFontFamilies (), a zatem może ona wywoływać tę funkcję dla każdej czcionki. Na listingu 17.9 pierwszą funkcją, której treść mieści się w liniach 1-29, jest funkcja zwrotna. Jest ona wywoływana dla każdej odnalezionej w systemie czcionki, a jej parametrami są informacje o danej czcionce. Co się więc tutaj dzieje? W funkcji onDraw() ustanowiona została funkcja wyliczająca. Parametr l param jest wartością definiowaną przez użytkownika, która została ustanowiona w linii 44 jako wskaźnik kontekstu urządzenia. Kontekst ten użyty jest do narysowania wszystkich kompatybilnych z nim czcionek (w tym przypadku urządzeniem jest monitor). Funkcja CreateFontindirect () wywołana w linii 12 działa w taki sam sposób jak CreateFont (), lecz pobiera strukturę przechowującą wszelkie parametry, co widać z listingu 17.11. Drugim parametrem funkcji wyliczającej w linii 44 jest rodzina czcionki. Przekazując wartość NULL spowodujemy wyświetlenie wszystkich możliwych czcionek. Jeśli natomiast wskażemy określoną rodzinę, na przykład Arial lub Courier, na ekranie ujrzymy tylko czcionki należące do tej rodziny. Trzecim parametrem jest nazwa funkcji zwrotnej, którą możemy ukryć w (FONTENUM-PROC), zapobiegając ostrzeżeniom kompilatora. Na koniec możemy przekazać pewne informacje użytkownika. Ponieważ czcionki mają być rysowane, wskazanie kontekstu urządzenia (w którym będzie się to odbywało) jest prawdopodobnie najużyteczniejszą spośród informacji. Jak widzimy, funkcja wyliczająca odnajduje listę zainstalowanych czcionek, rozpoczyna wywoływanie funkcji zwrotnej FontCallBack () i przekazuje jej kilka parametrów. Pierwszym jest struktura ENUMLOGFONT. Listing 17.10 przedstawia jej definicję. Listing 17.10. LST18_10.CPP- struktura ENUMLOGFONT 1 typedef struct tagENUMLOGFONT { 2 LOGFONT elfLogFont; 3 BCHAR elfFullName[LF_FULLFACESIZE]; 4 BCHAR elfStyle[LF_FACESIZE] ; 5 } ENUMLOGFONT; Korzystanie z czcionek 441 ENUMLOGFONT przechowuje nazwę elfFullName oraz styl elfstyle czcionki lecz prawdopodobnie najużyteczniejszą częścią jest przechowywana tu struktura LOGFONT. Jest to struktura wszystkich parametrów, jakie można przekazać funkcji CreateFont (). Jak widzieliśmy w linii 12 listingu 17.9, może być użyta również przez CreateFontindirect (). Typ danych BCHAR Typ danych BCHAR można napotkać w strukturach Win32. Typ ten jest definiowany jako BYTE podczas kompilacji w środowisku nie będącym środowiskiem Unicode lub jako WCHAR w środowisku Unicode. Unicode reprezentuje każdy znak w formie dwubajtowej sekwencji, pozwalając na utworzenie do 65 536 znaków, co umożliwia tworzenie zestawów znaków narodowych, a także symboli. Listing 17.11 przedstawia sposób definiowania tej struktury. Listing 17.11. LST18_11.CPP-struktura LOGFONT 1 typedef struct tagLOGFONT ( // If 2 LONG IfHeight; 3 LONG IfWidth; 4 LONG IfEscapement; 5 LONG IfOrientation; 6 LONG IfWeight; 7 BYTE Ifitalic; 8 BYTE IfUnderline; 9 BYTE IfStrikeOut; 10 BYTE IfCharSet; 11 BYTE IfOutPrecision; 12 BYTE IfCIipPrecision; 13 BYTE IfOuality; 14 BYTE IfPitchAndFamiły; 15 TCHAR !fFaceName[LF_FACESIZE] ; 16 } LOGFONT; Dostęp do szczegółów struktury uzyskujemy poprzez wskaźnik struktury ENUMLOGFONT ipelf przekazany funkcji zwrotnej (co następuje w linii 2 listingu 17.9). Jest on użyty po raz pierwszy w liniil2 (poprzez kod &lpelf->elfLogFont, służący do określenia adresu struktury LOGFONT). Kolejnym parametrem przekazanym funkcji zwrotnej w listingu 17.9 jest struktura NEWTEXTMETRIC. Struktura ta służy do przechowania szczegółów dotyczących rozmiarów znaków i sposobu prezentacji w określonym kontekście urządzenia. 442 Poznaj Visual C++ 6 Dalej przekazywany jest parametr FontType informujący nas, czy dana czcionka jest typu TRUETYPE_FONTTYPE, RASTER_FONTTYPE bądź DEVICE_FONTTYPE. Na końcu znaj- duje się wskaźnik kontekstu urządzenia pod postacią LPARAM. Po utworzeniu i wybraniu czcionki odnajdujemy bieżącą pozycję kursor graficznego w kontekście urządzenia, stosując funkcję GetCurrentPosition (), co widać w linii 17 listingu 17.9. Nazwę czcionki możemy pobrać ze struktury LOGFONT (linia 20 listingu 17.9) i wyświetlić ją poprzez wywołanie funkcji Text0ut (). Pozycja kursora może być zmieniona o wysokość czcionki i ustalona za pomocą funkcji MoveTo (), jak w linii 23. Po usunięciu wyboru starej czcionki należy zwrócić TRUE do funkcji wyliczającej, co pozwoli jej kontynuować działanie. Przekazanie FALSE zinterpretowane zostanie jako zakończenie działania. Struktury TEXTMETRICS Struktury NEWTEXTMETRICS są potomnymi struktur TEXTMETRICS. Istnieje wiele odmiennych struktur Win32 i wiele spośród funkcji może wymagać tych odmiennych wersji. Napotkać można również struktury NEWTEXTMETRICSEX, TEXTMETRICW oraz TEXTMETRICA, które zależą od sygnatur czcionek środowiska Unicode i środowisk odmiennych. Używanie okna dialogowego wyboru czcionki Windows dostarcza standardowego okna dialogowego pozwalającego na dokonanie wy- boru czcionki. Większość aplikacji używa tych okien i każdy korzystał z nich wielokrotnie. Możemy umieścić takie okno we własnej aplikacji, dodając pozycję menu umożliwiającą jego otwarcie. Musimy więc utworzyć pozycję menu z nagłówkiem Choose Font (jak na rysunku 17.4.) i identyfikatorem ID_CHOOSEFONT. Algorytm „Dodawanie nowej pozycji menu" opisany w rozdziale 13 opisuje sposób wykonania tego zadania. Następnie należy utworzyć funkcję obsługi komunikatu MESSAGE o nazwie OnChoose- Font () dla nowej pozycji menu (ID_CHOOSEFONT) wykonując algorytm „Dodanie za pomocą CIassWizard funkcji obsługi poleceń menu", znajdujący się również w rozdziale 13. Menu Item PropBrties Generał Extended Styles 10: IlIJMihMddiMgiii3 caption: S~ Separator t~ Pop-up T lnactive Break: F Checked r Grayed F Hełp Proropt Rysunek 17.4. Dodawanie do menu pozycji wyboru czcionki Korzystanie z czcionek 443 Add Member Function Rysunek 17.5. Tworzenie funkcji obsługi OnChooseFont Skoro mamy nową pozycję menu i obsługującą ją funkcję, możemy dodać i użyć klasę CFontDialog, co pokazane jest na listingu 17.12. Listing 17.12. LST18_12.CPP - implementacja funkcji obsługi OnChooseFont () 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void CSimpTextView::OnChoosefont() ( // TODO: Add your command handler code here // ** Deklaracja CFontDialog CFontDialog dIgChooseFont; O • if (dIgChooseFont.DoModal() == IDOK) { m fnCustom.DeleteObject(); m fnCustom.CreateFontIndirect( dIgChooseFont.m_cf.IpLogFont) ; Invalidate() ; O CFontDialog jest klasą osłonową dla standardowego okna dialogowego CHOOSE-FONT i dostarcza prostej lecz wyrafinowanej metody wybierania określonej czcionki i jej stylu. Na powyższym listingu, w linii 6 zadeklarowany zostaje obiekt CFontDialog jako dIgChooseFont. Możemy więc otworzyć okno dialogowe poprzez wywołanie w linii 7 funkcji DoModal (). Użytkownik może dokonać wyboru czcionki. Po kliknięciu OK, musi zostać wywołana funkcja DeleteObject () dla obecnej czcionki w celu uwolnienia obiektu GDI. Następnie tworzona jest przez składową CFontDialog, m_cf która posiada wskaźnik do struktury LOGFONT. Dalej następuje wywołanie Invalidate () (linia 12), co wymusza odświeżenie widoku. Musimy również dodać do klasy widoku zmienną czcionki, m_fnCustom. Klikniemy więc kartę ClassView w widoku Project Workspace, a następnie podwójnym kliknięciem 444___________________________________Poznaj Visual C++ 6 otworzymy kod deklaracji klasy CSimpTextView. Dodamy nową zmienną, umieszczając ją za komentarzem // Attributes, co widać na listingu 17.13. Listing 17.13. LST18_13.CPP - dodanie składowej zmiennej do klasy widoku 1 // Atrybuty 2 public: 3 // ** Nowa dodana zmienna czcionki 4 CFont m fnCustom; 5 6 CSimpTextDoc* GetDocument(); Na koniec powinniśmy zmienić implementację OnDraw (), by wyświetlała tekst przy użyciu wybranej czcionki. Listing 17.14 prezentuje właściwy kod. Listing 17.14. LST18_14.CPP - wyświetlanie wybranej czcionki 1 void CSimpTextView::OnDraw(CDC* pDC) 2 { 3 CSimpTextDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 8 CFont* pdldFont = pDC->SelectObject(&m_fnCustom); 9 pDC->TextOut(20,20, 10 "Bań bah black sheep, have you any wool") ; 11 pDC->SelectObject(pOldFont); 12 } Czcionka wybrana zostaje w linii 8 listingu 17.14, a funkcja Text0ut() wyświetla próbny tekst w liniach 9 i 10. Teraz skompilujemy i uruchomimy program. Jeśli wszystko przebiegnie pomyślnie, będziemy mieli możliwość zmiany czcionki, którą wyświetlany jest tekst na ekranie. Dostęp do okna wyboru uzyskać możemy wybierając pozycję Choose Font z menu Edit. Zmienimy teraz ustawienia w tym oknie, a tekst zostanie wyświetlony ponownie inną czcionką, jak na rysunku 17.6. Korzystanie z czcionek 445 w. ':'''• "'^^^ysip ^"•^ Eii'e"&tt yTw atip'" D|e!|a| ^l^M a|'j?[ Bah bah black sheep. have you any wool RS- 'TArielBlai 'l- Aliol Noirow t BookAntique "*t Bookrnan Old Style ''f Bool-yhell Symboli OK Cance! ••EtiBCiE •••••• • r S&ikaout y iyndertin^ i^olor: AaBbYyZ Rysunek 17.6. Okno wyboru czcionki PATRZ TAKŻE • O dodawaniu i implementowaniu menu oraz ich stylach czytaj w rozdziale 13. Rysowanie tekstu formatowanego i wieloliniowego Ponieważ odmienne wymiary czcionek powodują zmiany w wykorzystywanym obszarze widoku, ważnym zadaniem jest utworzenie kodu powodującego prawidłowe sformatowanie tekstu względem określonego okna. Na szczęście Windows posiada funkcję, która wspomaga nas w tym zadaniu. DrawText () pobiera trzy parametry. Pierwszym jest obiekt cstring, w którym znajduje się łańcuch znakowy przeznaczony do wyświetlenia. Drugi parametr stanowi wskaźnik prostokąta, w którym tekst ma zostać umieszczony. Ostatni parametr (co nie znaczy najmniej ważny) jest znacznikiem, którego wartość determinuje sposób wyświetlenia i formatowania tekstu. Znaczniki możemy łączyć na różne sposoby, ustalając w ten sposób odmienne opcje. Znaczniki są przedstawione w tabeli 17.5. Stosowanie funkcji DrawTextEx () Istnieje rozszerzona wersja funkcji DrawText (), zwana DrawTextEx (). Jest ona dostępna jedynie jako globalna funkcja Win32, należy zatem przekazać jej jako pierwszy parametr, uchwyt urządzenia docelowego. Forma rozszerzona posługuje się tymi samymi parametrami, zezwalając jednakże na przekazanie wskaźnika struktury DRAWTEXT PARAMS w postaci ostatniego parametru. Struktura ta może być wykorzystana do ustalania pozycji tabulacji, jak również marginesów. 446_____________________________________Poznaj Visual C++ 6 Tabela 17.5. Znaczniki formatowania przekazywane funkcji DrawText () Wartość znacznika Opis DT_LEFT Wyrównanie tekstu do lewej strony okna DT_RIGHT Wyrównanie tekstu do prawej strony okna DT_CENTER Wysrodkowanie tekstu w oknie DT_VCENTER Wysrodkowanie tekstu w pionie DT_WORDBREAK Nie dzieli wyrazów (jak edytor tekstu) DT_CALCRECT Nie rysuje tekstu. Oblicza obramowanie okna DT_EXPANDTABS Poszerza tabulację (domyślna wartość 8) DT_NOPREFIX Wytacza stosowanie prefiksów. W przeciwnym wypadku znak & oznacza skrót klawiaturowy DT_TOP Wyświetla tekst u góry okna (tylko w przypadku pojedynczej linii) DT_BOTTOM Wyświetla tekst u dołu okna (tylko w przypadku pojedynczej linii) Możemy teraz zmodyfikować funkcję OnDraw (), by korzystała z DrawText () zamiast Text0ut (). Wzór stanowi listing 17.15. Listing 17.15. LST18_15.CPP- rysowanie tekstu formatowanego przez DrawText () 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void CSimpTextView::OnDraw(CDC* pDC) { CSimpTextDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc) ; // TODO: tu dodaj kod rysowania dla własnych danych CFont* pOldFont = pDC->SelectObject(&m_fnCustom) ; CRect rcSmall(CPoint(20,20),CPoint(200,100)) ; pDC->Rectangle(rcSmall) ; pDC->SetBkMode(TRANSPARENT) ; // ** Wywołanie DrawText w celu odrysowania tekstu II ** Przenoszenie wyrazów i Wysrodkowanie tekstu pDC->DrawText( "Bah bah black sheep, have you any wool", rcSmall, DT WORDBREAK + DT CENTER); Korzystanie z czcionek 447 20 pDC->SelectObject(p01dFont); O 21 } O Przekazując znaczniki DT_WORDBREAK i DT_CENTER funkcji DrawText () powodujemy przenoszenie wyrazów w całości oraz wyśrodkowanie tekstu w określonym prostokącie. W liniil6 listingu 17.15 widzimy wywołanie funkcji DrawText (), użytej do narysowania przykładowego tekstu w prostokącie rcSmall. Jeśli teraz skompilujemy i uruchomimy program, zobaczymy tekst pośrodku prostokąta, w którym przenoszone do nowej linii są całe wyrazy, co widać na rysunku 17.7. Zmieniając wielkość czcionki możemy obserwować wpływ tej czynności na format tekstu wewnątrz prostokąta. PATRZ TAKŻE • O wprowadzaniu tekstu do pól edycji czytaj w rozdziale 5. Usuwanie czcionek Podobnie jak w przypadku innych klas osłonowych, czcionki GDI są automatycznie usuwane, gdy obiekty osłonowe wychodzą z użycia. Możemy je usuwać własnoręcznie, wywołując funkcję DeleteObject () (tylko wtedy, gdy dana czcionka nie jest aktualnie umieszczona w kontekście urządzenia). Po jej wykonaniu ponownie możemy użyć klasy osłonowej wywołując jedną z jej funkcji Create. &le Edit yiew tfelp Q|a|H| : 'r;) g"! a|? J Boh bah black sheep, have you any wool ^B^ft^s-^BiiiHIlgII^ a" s»e; :^|Centuiy Gothic ,|i.| =teguler JH j OK ] ;>,: IT Bookshelf Symbol 2 ^: :^ ^Bookshelf Symbol 3 ..^ 1^ ^ BookshBlI Symbol 4 —i^ :^ TtBook-shBlf Symbol 5 l; Ita lie Bold Bold Italie 1 0 1 ] 1 - 1 -'- Canctl | •J—— w. WEmss^^m i 1 6 : l 'T Cenimy Schoolbook 1 3 'T Cumie SansMS ^j J 2 0 JJ Ettecte Sample r SU^eo'Jt . r iJnrisilina AoBbYyZz Qoloi : ^•Eilack ^j Sci-ipt: :.. ........ .... ..„..,...„,^...,.^, W eslern 4::„. „-, .•.•;-^iiil^^iyi^ »|:||: ll^.,....... ^^^N^liS ^i^iiS^^^ii^l^^ ^^^ Rysunek 17.7. Tekst sformatowany przez funkcję DrawText () 448_____________________________________Poznaj Visual C++ 6 Spośród wszystkich obiektów GDI czcionki są najprawdopodobniej najbardziej „pamięciożerne", dlatego wskazane jest, o ile to możliwe, usuwanie wszystkich nieużywanych czcionek. Jeśli zamierzamy użyć ich ponownie, najlepszym rozwiązaniem będzie utworzenie zmiennych składowych klasy widoku, które będziemy mogli wybierać i usuwać, zamiast tworzyć je za każdym razem, gdy dokonujemy rysowania, co spowalnia działanie programu. PATRZ TAKŻE • Więcej szczegółów dotyczących obiektów GDI znajdziesz w rozdziale 15. Część V Zaawansowane dokumenty i techniki dla widoków Rozdział 18 Rozciąganie i przewijanie widoków Zmiana wymiarów okna i programowanie obsługi rozciągania okna Definiowanie minimalnych i maksymalnych rozmiarów okna Tworzenie widoków większych niż rozmiary okna lub ekranu i oglądanie ich z pomocą pasków przewijania Operacja polegająca na rozciąganiu okna jest w środowisku Windows jedną z najczęściej wykonywanych przez użytkownika czynności. Zazwyczaj traktujemy to udogodnienie jako coś naturalnego, nie zastanawiając się, jaki wpływ rozciągnięcie okna będzie miało na działanie aplikacji. Jeśli przyjrzeć się jednak tej operacji z punktu widzenia aplikacji (lub programisty), zmiana wymiarów okna nie jest wcale taka prosta. Być może oprócz zmiany wymiarów okna, będziemy musieli również przemieścić lub zmienić wymiary kontrolek, dodać lub usunąć paski przewijania, ponownie odmalować czy dopasować wymiary tekstu lub diagramu wyświetlanego w oknie. Obsługa rozciągania okna Jak ustalić bieżący status i pozycję okna na ekranie Informacje na temat bieżącego statusu i położenia okna można uzyskać przyzywając funkcję obiektu CWnd, GetWindowPlacement (). Funkcja sięga do struktury WINDOW-PLACEMENT (wskaźnik do niej jest pierwszym parametrem funkcji), która zawiera informacje o położeniu okna i o jego bieżącym statusie (czy okno jest zminimalizowane, zmaksymalizowane, ukryte, czy wyświetlone normalnie). Odpowiadająca jej funkcja SetWindowPlacement () pozwala zmieniać status okna z poziomu programu. Kiedy chwytając brzeg okna myszą rozpoczynamy jego rozciąganie lub zmniejszanie, w systemie uruchamianych jest kilka procesów. Po pierwsze, gdy chwytamy myszą obra- 452 Poznaj Visual C++ 6 mowanie okna, system Windows blokuje wszelkie operacje wewnątrz okna. Natomiast podczas rozciągania do okna obramowującego przesyłana jest seria komunikatów (WM_SIZING) informujących o postępach rozciągania. Obsługa zdarzeń rozciągania Aby przyjrzeć się niektórym z problemów związanych z rozciąganiem, skorzystamy z aplikacji SDI i widoku Form. Widok Form wyświetla szablon dialogowy, który pozwala nam dodawać do widoku kontrolki w taki sam sposób jak przy okazji okna dialogowego. Poniższa instrukcja pokazuje jak krok po kroku można utworzyć za pomocą kreatora AppWizard aplikację opartą na widoku Form. Tworzenie aplikacji SDI bazującej na widoku Form 1. Kliknij menu File i wybierz polecenie New. 2. Wybierz kartę Projects i z listy typów projektów uruchom MFC AppWizard (exe). 3. Następnie kliknij pole Project Name i wpisz nazwę projektu: SizeForm. 4. Kliknij OK. Na ekranie powinno pojawić się pierwsza strona kreatora AppWizard. 5. Wybierz opcję Single Document i klikaj Next dotąd, aż pojawi się ostatnia strona kreatora z wielką kraciastą flagą. 6. W panelu zawierającym listę klas tworzonych przez kreator AppWizard kliknij klasę CSizeFonnView. 7. W tym momencie powinna uaktywnić się lista kombinowana Base CIass. Kliknij wskazującą w dół strzałkę, aby rozwinąć listę możliwych klas bazowych tworzonego widoku. 8. Jak pokazano na rysunku 18.1, wybierz klasę CFormView. 9. Kliknij Finish. 10. Kliknij OK w oknie dialogowym New Project Information. AppWizard utworzy teraz projekt i odpowiednie pliki źródłowe. Rozciąganie i przewijanie widoków 453 Rysunek 18.1. W kreatorze AppWizard jako gtówny widok wybieramy klasę CFormYiew Obsługa zdarzenia Sizing Przechwytując windowsowy komunikat związany ze zdarzeniem rozciągania okna (WM_SIZING) możemy na pasku tytułowym okna wyświetlić informacje o tym jak zmieniają się jego wymiary. Na początku rozciągania okno zostaje zablokowane, dlatego w trakcie wpisywania nowych informacji na pasku tytułowym trzeba je będzie odblokować, a potem zablokować ponownie. Przedstawiony poniżej przykład (SizeForm) pokazuje jak należy to zrobić w aplikacji SDI bazującej na widoku Form. Dodajemy w tym celu do programu funkcję obsługi OnSize (). Dodawanie za pomocą kreatora Event Wizard funkcji obsługi komunikatu WM_SIZING 1. Kliknij znak plus przy haśle SizeForm Ciases w panelu ClasView okna Project Workspace. Powinna pojawić się lista klas projektów. 2. Komunikatami rozciągania zajmuje się obramowanie okna, dlatego odpowiednią procedurę obsługi należy dodać do klasy CMainFrame. Kliknij prawym klawiszem myszy klasę CMainFrame, aby otworzyć menu skrótów, a następnie wybierz w nim opcję Add Windows Message Handler. 3. Powinno pojawić się okno dialogowe New Windows Message and Event Handlers for Ciass CMain Frame. 454 Poznaj Visual C++ 6 4. Przewiń w dół komunikaty wyświetlone w liście New Windows Messages/Eyents, aby odszukać zdarzenie WM_SIZING. 5. Zaznacz to zdarzenie i kuknij przycisk A,dd and Edit, tak jak pokazano na rysunku 18.2. 4e*Windcws messoges/e^nts: l a g ——————————————•^ril WM MOU3EWMEEL ^ WM.MOYE WM MOYING WM PAINT WM.PALETTECHANGED WM PALETTEISCHANGING WM OUERYENDSESSION WMQUEFWIEWPALETTE WM RBinTONOBLO-K WM RBUTTONDOWN WM.RBUTTONUP WM SETCURSOR WM SETFODJS WM.SETTINGCHWGE WM SHOWWINDOW WM SIZE ^^ ^^ WM SPOOLERSTATUS WMSYSCOLORO-IANGE WM TCARD WM TIMECHANGE WMTIMER VvM V3CRCILL •' Cancel Add Handler | AddandEdit ] EdrtExisltng glass or objectto handie' ID~WP~E)ST -^^ '.^^.^..^ ID EDIT copY 'aiii&^ateM1^!^:: IDEDITCUT yrtlBJilBK&iiSS EitetfwTOttBgatSJilftla^iiiBilMIlte^lliłBl WMŚIZIŃ& Indicatesftal liia usara ~mm TopmostFrame »J .::.•;.': '..•' ••.:^.:::•.; ; eniiyresizing the window Rysunek 18.2. Dodawanie procedury obsługującej dla zdarzenia WM_SIZING W tym momencie powinien pojawić się kod funkcji obsługi OnSizingO. Aby wyświetlić informacje o operacji rozciągania na pasku tytułowym, należy dodać do niej fragment kodu przedstawiony na listingu poniżej. Listing 18.1. LST19_1.CPP- wyświetlanie rozmiarów rozciąganego okna 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void CMainFrame::OnSizing(UINT fwSide, LPRECT pRect) ( CFrameWnd::OnSizing(fwSide, pRect) ; // TODO: Dodaj tu kod obsługi komunikatów l/ ** Skonstruuj CRect przekształcając wskaźnik struktury CRect rcSize(pRect); O // ** Utwórz łańcuch przechowujący komunikat CString sizeMsg; // ** Wyświetl informacje o wymiarach okna II ** zaczerpnięte ze struktury sizeMsg.Format("Sizing: width = %d, height = %d", @ rcSize.WidthO , rcSi.ze. Height () ) ; 17 18 // ** Wyłącz blokadę okna 19 UniockWindowUpdateO ; © 20 21 // ** Uaktualnij tekst na pasku tytułowym 22 SetWindowText(sizeMsg); 23 24 // ** Ponownie zablokuj okno 25 LockWindowUpdate(); 26 } O Ze struktury RECT będzie łatwiej korzystać, jeśli przekonwertujemy ją w klasę CRect. © Bieżące rozmiary okna zostają wformatowane w łańcuch cstring, który już bez większych problemów można umieścić na pasku tytułowym © Ponieważ system Windows blokuje okno na czas rozciągania, w celu zmiany tekstu paska, musimy je chwilowo odblokować Powyższa procedura obsługi będzie wzywana za każdym razem, gdy podczas rozciągania okna użytkownik poruszy myszą. Jej parametry to znacznik informujący, która krawędź okna jest rozciągana (fwside) i wskaźnik do struktury przechowującej koordynaty (pRect). W pierwszej kolejności należy przekształcić strukturę RECT w wygodniejszy w użyciu obiekt CRect, co też zrobiliśmy w linii 8. Teraz możemy umieścić wysokość i szerokość rozciąganego prostokąta okna w odpowiednim łańcuchu. Następnie aby wyświetlić odpowiednie współrzędne na ekranie, w linii 22 przypisujemy ten łańcuch tytułowi okna za pomocą funkcji SetWindowText (). Warto zwrócić uwagę na dwie funkcje: UnIockwindowUpdate () (linia 19) i LockWindowUpdate () (linia 25) otaczające funkcję SetWindowText (). Odwołanie się do nich jest tutaj niezbędne, ponieważ system Windows podczas operacji rozciągania automatycznie blokuje wszystkie operacje zmieniające zawartość okna. Takie posunięcie jest całkowicie uzasadnione; w przeciwnym wypadku podczas przesuwania przez użytkownika prostokąta rozciągania po ekranie system starałby się rozpaczliwie odmalować ciągle zmieniające się okno. Program Size Form a pakiet Plus! Microsoftu Jeśli mamy zainstalowany na komputerze pakiet Microsoft Plus!, musimy przed uru- chomieniem przedstawionego tutaj programu wyłączyć opcję Show Window Con-tents While Dragging. Opcję tę można znaleźć w applecie Control Panel Display na karcie Plus! Jeśli pozostawimy tę opcję włączoną, okno nie zostanie zablokowane i aplikacja zacznie wariować. 456_____________________________________Poznaj Visual C++ 6 Jeśli teraz zbudujemy i uruchomimy aplikację, będziemy mogli na pasku tytułowym obejrzeć aktualną wysokość i szerokość rozciąganego okna. Obsługa zdarzenia Size Bardziej przydatne niż zdarzenie Sizing jest zdarzenie size związane z zakończeniem operacji rozciągania. Zdarzenie Sizing możemy, jak właśnie pokazaliśmy, wykorzystać do manipulowania prostokątem definiującym docelowe rozmiary okna, jednak w praktyce zdarzenie to wykorzystuje się bardzo rzadko. Znacznie bardziej przydatne jest zdarzenie size. Informuje ono, że użytkownik zdecydował, jaki ma być ostateczny rozmiar okna i zwolnił przycisk myszy. Możemy przechwycić to zdarzenie w momencie, gdy dociera do okna obramowującego, jak zrobiliśmy to w przykładzie powyżej, jednak zdarzenie to jest przesyłane również dalej, do okna widoku, gdzie jego rola jest nawet większa. Dodamy teraz do programu odpowiednią funkcję obsługi. Dodawanie procedury obsługi widoku dla zdarzenia Size 1. W panelu ClassView w oknie Project Workspace wybierz klasę CSizeFormView. 2. Kliknij wybraną klasę prawym klawiszem myszy i z menu skrótów wybierz polecenie Add Wińdows Message Handler. 3. Powinno pojawić się okno dialogowe New Wińdows Message and Event Handlera dla klasy widoku. 4. Z listy New Wińdows Message/Events wybierz zdarzenie WM_SIZE. 5. Aby dodać kod odpowiedniej funkcji obsługi, kliknij przycisk Add and Edit. Aby wyświetlić na pasku tytułowym końcową wysokość i szerokość okna, możemy dodać do funkcji fragment kodu z przykładu 18.2. Listing 18.2 LST_2.CPP - kod funkcji obsługującej zdarzenie Size 1 void CSizeFormView::OnSize(UINT nType, int ex, int cy) 2 { 3 CFormView::OnSize(nType, ex, cy); 4 5 // TODO: dodaj tu kod obsługi komunikatów 6 7 // ** Zadeklaruj obiekt łańcucha 8 CString strTitle; 9 10 // ** Zdefiniuj i wyświetl tytuł dokumentu 11 strTitle.Format("Finał Width = %d, O Height = %d",cx,cy); 12 GetDocument()->SetTitle(strTitle) ; 13 } Rozciąganie i przewijanie widoków 457 O Szerokość jest formatowana, a następnie dołączana do tytułu dokumentu (który zostanie wyświetlony w pasku tytułowym okna). Funkcja obsługi OnSizeO, przedstawiona w przykładzie 18.2, jest przywoływana, gdy proces rozciągania okna zostaje zakończony. System Windows definiuje wartość znacznika nType będącego pierwszym parametrem funkcji OnSize (). Znacznik ten informuje, z jakiego powodu okno jest rozciągane. W tym przypadku może przyjąć jedną z trzech wartości podanych poniżej w tabeli 18.1. Tabela 18.1. Znaczniki zdarzenia sizing Wartość znacznika Opis S l ZE_RESTORED okno zostało rozciągnięte S I ZE_MAXIMI ZED Okno zostało zmaksymalizowane SIZE_MINIMIZED Okno zostało zminimalizowane Pozostałe dwa parametry funkcji OnSize () to liczby całkowite definiujące nową szerokość (ex) i wysokość (cy) okna. Możemy sformatować, tak jak to zrobiliśmy w linii 8, odpowiedni łańcuch wyświetlający te wartości, a następnie wykorzystać go w funkcji SetTitle () dokumentu, aby wpisać nowy tekst w pasku tytułowym okna (wiersz 9). Po uruchomieniu program będzie w trakcie rozciągania wyświetlał na pasku tytułowym aktualne wymiary, a gdy zakończymy rozciąganie, wyświetli końcowe wymiary okna. Pojawia się pytanie: do czego potrzebne są nam komunikaty informujące o rozciąganiu okna? Zazwyczaj potrzebne są nam wtedy, gdy użytkownik zmienia rozmiary kontro-lek w widoku Form. Załóżmy, ze napisaliśmy prosty edytor tekstu zawierający rozbudowaną wielowierszową kontrolkę Edit. Kiedy użytkownik zmienia wymiary okna, wymiary kontrolki powinny się również odpowiednio zmienić. Poniżej przedstawiamy postępowanie, w ramach którego dodamy do projektu kontrolkę Edit. Dodawanie do istniejącego już projektu z widokiem Form wielowierszowej przewijal-nej kontrolki Edit 1. Kliknij panel Resources w oknie Project Workspace. 2. Kliknij znak plus przy zasobach (Resources) odpowiedniego projektu, aby je otworzyć. 3. Kliknij znak plus przy haśle Dialog, aby otworzyć okno dialogowe Project. 4. Pojawi się nazwa okna dialogowego utworzona w oparciu o nazwę projektu (na przykład IDD_SIZEFORM). To okno dialogowe jest standardowym szablonem dla widoku Form. 458 Poznaj Visual C++6 5. Kliknij dwukrotnie tą nazwę, a pojawi się czyste okno dialogowe zawierające tylko komunikat TODO: Place Controls Herę (Tutaj umieść kontrolki). 6. Usuń ten komunikat klikając go przy wciśniętym klawiszu Delete. 7. Przeciągnij z Controls Palette pole edycji i umieść je na czystym szablonie. 8. Teraz rozciągnij pole edycji tak, aby pokryło całkowicie niebieski kropkowany obszar widoczny w szablonie. 9. Wciśnij Alt+Enter, aby przywołać okno dialogowe Edit Properties. 10. Kliknij kartę Styles, a następnie wybierz znaczniki Multiiine, Hori^ontal Scroll, Auto HScroll, yertical Scroll, Auto YScroll i want Retum. 11. Wybierz kartę Generał i tak, jak to pokazano na rysunku 18.3 zmień identyfikator zasobów (resource ID) na IDC_SIZEABLE_EDIT. 12. Aby zamknąć okno dialogowe Edit Properties, wciśnij Enter. Opcje przewijania Opcje Horizontal Scroll i Vertical Scroll dodają do pola edycji odpowiednio poziomy i pionowy pasek przewijania. Opcje Auto HScroll i AutoVScroll sprawiają, że kontrolka przewija automatycznie zawartość odpowiednio: po dotarciu do końca linii i po wciśnięciu przez użytkownika klawisza Enter. Jeśli opcja Auto HScroll nie zostanie ustawiona, po dotarciu do prawego brzegu kontrolki tekst będzie automatycznie przenoszony do następnej linii. Dodaliśmy kontrolkę Edit, ale nadal nie mamy odpowiedniej zmiennej umożliwiającej programowi komunikację z kontrolka. Kolejnym zadaniem będzie więc przypisanie (ang. map) kontrolce odpowiedniej zmiennej za pomocą kreatora CIassWizard. -^ T Generał j Sfr/les j Eytended Styles | ~3 ID: IDC_SIZEABLE_EDIT P Visible r" iSroup r Halp ID l:l;r Disabled P Tabstop Rysunek 18.3. Dodajemy do programu wielowierszową kontrolkę Edit Przypisywanie zmiennej do kontrolki za pomocą kreatora CIassWizard l. Kliknij obramowanie kontrolki Edit. Rozciąganie i przewijanie widoków 459 2. Wciśnij CtrI+W, aby uruchomić kreator CIassWizard (lub kliknij menu View i wybierz w nim polecenie CIassWizard). 3. Pojawi się okno dialogowe CIassWizard, w którym wybrana będzie karta Member Variables. 4. Z listy Control IDs wybierz odpowiedni identyfikator (taki jak IDC_SIZEABLE_ EDIT). Kliknij Ądd Variable, aby przywołać okno dialogowe Add Member Variable. 5. Teraz możesz dodać zmienną, która reprezentować będzie kontrolkę Edit. 6. W liście kombinowanej Category wybierz kategorię Control; w polu Variable Type pojawi się automatycznie klasa CEdit. 7. Kliknij pole edycji Member Variable ;Name i wpisz nazwę zmiennej przypisanej kontrolce (tutaj m_SizeableEdit), tak jak na rysunku 18.4. lAdd Member Variable ? Ł' .-' " ""s ,;s, IB————IR'7!."1 ill ll! Member yariablenB.me: s, , OK fflfcłH%'iaB=Btli Ctttegory;;^^^^^;!^!^!!!; :1 Cancel ,:.::,:1^',l;l:\s^i!:::!l !il|| '•'.•••: „'.l1"^. • ^.^sl^ Control ^j : CEdit ijlls; -E/lili ^M^i:^':'.''^'. :^: : "•;;•:"::i::T:i."•::::•.•l^. "::i-::l:i::.::!;hi:::..• ^ :^^ M^|J^|„|l„!Kt,:„'^,. ^-1:11-:••':'•"1^•:-,•^' ^i,;:'::1!.,!:;!:: ,-;::':. 1 •-l;1-!: 3escription: map to CEdit memher Rysunek 18.4. Dodajemy zmienną klasy CEdit przypisaną kontrolce Edit 8. Kliknij OK, aby dodać do klasy widoku zmienną składową. W ten sposób tworzymy zmienną m_SizeableEdit symbolizującą kontrolkę. Dzięki temu będziemy mogli przesyłać tekst między programem a kontrolką. Zmienna tę można również wykorzystać do przesyłania do kontrolki komunikatów związanych ze zmianą rozmiaru. Następną rzeczą, którą należy zrobić, jest obsłużenie komunikatu WM_SIZE w taki sposób, aby było wiadomo, kiedy i w jakim stopniu należy zmienić wymiary kontrolki. Mamy już gotową funkcję obsługi. Możemy dotrzeć do niej za pomocą kreatora CIassWizard, tak jak to pokazaliśmy poniżej. 460 Poznaj Visual C++ 6 Odnajdywanie funkcji składowej za pomocą panelu CIasView Innym sposobem jest odnalezienie funkcji onSizef) w panelu CIasView w oknie Project Workspace. Funkcja powinna być podana w klasie csizeFormView i zostanie automatycznie wyświetlona w oknie głównego edytora po dwukrotnym kliknię-ciu funkcji OnSize (). Można również kliknąć funkcję prawym klawiszem myszy, by poprzez menu skrótów sięgnąć do definicji lub deklaracji funkcji za pomocą poleceń Go to Definition i Go to Declaration. Odnajdowanie funkcji obsługi za pomocą kreatora CIassWizard 1. Wybierz kartę Message Maps w kreatorze CIassWizard i odpowiednią nazwę klasy: Ciass Name (tutaj CsizeFormView). 2. Przewiń w dół listę Member Functions, aby odnaleźć odpowiedni komunikat (taki jak ON_WM_SIZE) lub funkcję składową (OnSize ()). 3. Jeśli teraz klikniesz dwukrotnie nazwę funkcji, znajdziesz się wewnątrz implementacji funkcji składowej (OnSize ()) w klasie CsizeFormView widoku Form. Wewnątrz funkcji nie ma w tej chwili jeszcze żadnego kodu, który umożliwiałby zmianę rozmiarów kontrolki, kiedy okno będzie rozciągane. Jeśli uruchomimy aplikację i spróbujemy zmienić wymiary okna, zauważymy, że rozmiary kontrolki nie zmienią się. Kontrolka Edit pełni w tym programie role prostego edytora tekstu, nie będzie jednak warta wiele, jeśli nie będzie zmieniać swych wymiarów dopasowując się do wymiarów okna. Zamykamy aplikację i wracamy do funkcji OnSizef) w widoku Form (csizeFormView). Do funkcji obsługi OnSize () należy dodać linie przedstawione w przykładzie 18.3. Listing 18.3. LST19_3.CPP - rozciąganie kontrolki Edit, kiedy zmieniają się wymiary okna 1 void CsizeFormView::OnSize(UINT nType, int ex, int cy) 2 ( 3 CFormYiew::OnSize(nType, ex, cy) ; 4 5 // TODO: Dodaj tu kod obsługi komunikatów 6 7 CString strTitle; 8 strTitle.Format("Finał Width = %d, Height = %d",cx,cy) ; 9 GetDocument()->SetTitle(strTitle) ; 10 11 // ** Sprawdź, czy pole edycji zostało „ożywione" 12 if (m_SizeableEdit.GetSafeHwnd() ) 13 Rozciąganie i przewijanie widoków 461 14 II** Dopasuj jego wymiary do wymiarów okna 15 m_SizeableEdit.SetWindowPos(this,0,O, O 16 cx-40,cy-40, 17 II** Zmień tylko wymiary 18 SWP_NOMOVE+SWP_NOZORDER+SWP_SHOWWINDOW+ 19 SWP_NOACTIVATE); 20 } O Kontrolka Edit jest rozciągana do odrobinę mniejszych rozmiarów niż zawierające ją okno. Znaczniki na końcu informują, że kontrolka ma być tylko rozciągana. W linii 12 listingu 18.3 musimy przyzwać funkcję składową getSafeHwndO, aby sprawdzić, czy okno zostało inicjalizowane (na oknie, które nie zostało inicjalizowane nie wolno nam wykonywać żadnych operacji). Pojawiająca się w linii 15 funkcja składowa SetWindowpos () jest wszechstronną funkcją pozwalającą na jednoczesne przesuwanie, rozciąganie, aktywowanie lub ukrywanie okna. W tym wypadku tylko rozciągamy okno, dlatego przesyłamy funkcji nowe parametry rozmiarów kontrolki - wymiary ex i cy okna pomniejszone o 40 pikseli. Znaczniki przesłane w ostatnim parametrze funkcji informują ją, jakiego rodzaju akcję ma podjąć i co w związku z tym oznaczają parametry przesłane wcześniej. Dostępne wartości znaczników można znaleźć w tabeli 18.2. Tabela 18.2. Znaczniki funkcji SetWindowPos() Znacznik Opis SWP_NOMOVE Nie przesuwaj okna; ignoruj drugi i trzeci (x i y) parametr SWP_NORESIZE Nie rozciągaj okna; ignoruj czwarty i piąty (ex i cy) parametr rozmiarów okna SWP_NOZORDER Nie umieszczaj okna na wierzchu przed wszystkimi innymi (ignoruj pierwszy parametr pWndInsertAfter) SWP_NOACTIVATE Nie aktywuj okna SWP_SHOWWINDOW Spraw, aby okno było widoczne Jeśli po dodaniu do programu linii z listingu 18.3 zbudujemy i uruchomimy aplikację, otrzymamy dający się rozciągać edytor tekstu widoczny na rysunku 18.5. 462 Poznaj Visual C++ 6 Eiie Edif Ylew yelp D|ig|H| ^[\ M a|' ~3 yoidCChapl9P1View:.OnSii:e(UINTriType CForm''/iew..OnSi;e(nTyps. ex cy); // TODO; Add your messaga handler code here CString strTiHe; // Declare a string object strT[tle.FonT>at("FinalWidlh - 5;d, Height - %d".o.cy); //Setupthetrtle stting QetDocumentO->SetTitle(strTrtle); // Setthe DocumentTitle it (m_Sizeab!eEdit.GetSafeHwndO) //" Check the Edil BOK is 'Alrve- m_Si^eaJ^ieEdit.Se^VindowPos(^his,0,0,cl<-40.cy-40. // " Size to Ihe newwindow sire SWP_NO MOVE»SWP^NOZ ORDER* SWP_SHO'AW1N DOW* SWP_NOAC1 | Build and run eflei adding tfiese lines and you should have a resizable te?.1 editor as in figurę 19.5. Rysunek 18.5. Aplikacja z prostym, dającym się rozciągać oknem edytora tekstu Definiowanie ograniczeń rozmiarów okna W pewnych sytuacjach może nam zależeć, aby użytkownik nie mógł za bardzo powiększyć albo za bardzo pomniejszyć okna. Można ustalić takie ograniczenia obsługując win-dowsowy komunikat WM_GETMINMAXINFO za pomocą funkcji obsługi OnGetMinMaxlnfo () okna obramowującego. Możemy przechwycić komunikat, gdy dociera do okna obramowują-cego i zdefiniować własne minimalne i maksymalne ograniczenia rozmiaru okna. Dodawanie funkcji obsługi za pomocą paska WizardBar Opcja menu Add Windows message jest również dostępna poprzez wciśnięcie wskazującej w dół strzałki, którą znaleźć można w prawym końcu paska narzędziowego WizardBar. Jeśli korzystamy z paska narzędziowego zamiast z panelu Cia-sView, należy upewnić się, że w polu pierwszej po lewej listy kombinowanej widnieje klasa CMainFrame, tak aby dodawana funkcja obsługi została załączona właśnie do tej klasy. Dodawanie funkcji obsługi pozwalającej kontrolować maksymalne i minimalne rozmiary rozciąganego okna 1. Kliknij prawym przyciskiem myszy klasę CMainFrame w panelu ClassView w oknie ProjectWorkspace. Pojawi się menu skrótów. 2. W menu wybierz polecenie New Windows Message Handler. Powinno pojawić się okno dialogowe New Windows and event Handlers for Ciass CMainFrame. 3. W polu listy New windows Messages/Events znajdź komunikat MMJSETMINMMCINFO, a następnie kliknij przycisk Add and Edit, aby dodać nową funkcję obsługi. Rozciąganie i przewijanie widoków ____ ____ 463 Teraz możemy dołączyć do nowej funkcji obsługi okna obramowującego, OnGetMin-Maxlnfo (), fragment kodu z listingu 18.4. W ten sposób wyznaczone zostaną maksymalne i minimalne rozmiary okna, poza które nie da się go rozciągnąć. Listing 18.4. LST19_4.CPP - definiowanie minimalnych i maksymalnych dopuszczalnych rozmiarów okna za pomocą funkcji OnGetMinMaxInfo() 1 void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR* IpMMI) 2 ( 3 // TODO: Tu dodaj własny kod funkcji obsługi i/lub kod C 4 5 // ** Zdefiniuj minimalne rozmiary 6 lpMMI->ptMinTrackSize = CPoint(200,200); 7 8 // ** Zdefiniuj maksymalne rozmiary 9 !pMMI->ptMaxTrackSize = CPoint(500,400); 10 11 CFrameWnd::OnGetMinMaxInfo(IpMMI) ; 12 } Jeśli po dodaniu tych linii i zbudowaniu aplikacji uruchomimy program i spróbujemy zmienić wymiary okna, zauważymy, ze nie da się go rozciągnąć tak by był mniejszy niż 200x200 pikseli lub większy niż 500x400 pikseli. Zmieniając w linii 5 i 6 parametry pt-MinTrackSize i ptMaxTrack3ize struktury MINMAXINFO możemy definiować minimalne i maksymalne dopuszczalne rozmiary okna. Struktura MINMAXINFO została zdefiniowana na listingu 18.5. Listing 18.5 LST19_5.CPP - struktura MINMAXINFO______________________ 1 typedef struct tagMINMAXINFO ( 2 POINT ptReserved; 3 POINT ptMaxSize; 4 POINT ptMaxPosition; 5 POINT ptMinTrackSize; 6 POINT ptMaxTrackSize; 7 } MINMAXINFO; Typ POINT Typ POINT jest strukturą WIN32 zawierającą zmienne składowe x i y, przechowującą współrzędne pojedynczego punktu. Powszechniej znana jest klasa CPoint, obudowująca strukturę POINT i zawierająca kilka własnych funkcji składowych i operatorów. 464_____________________________________Poznaj Visual C++ 6 Dodatkowo zdefiniowaliśmy tutaj jeszcze dwie rzeczy. Pierwszą jest parametr pt-Maxsize w linii 3, który definiuje wysokość i szerokość okna po zmaksymalizowaniu. Drugą jest parametr ptMaxPosition z linii 4, definiujący pozycję lewego górnego rogu zmaksymalizowanego okna. Okna dialogowe dające się rozciągać Również okno dialogowe można bardzo łatwo zdefiniować, tak aby można było zmieniać jego wymiary. Poniższa instrukcja pokazuje jak to zrobić na przykładzie okna About. Jak uczynić okno About rozciągalnym 1. Kliknij panel Resource View w oknie Project Workspace. 2. Otwórz Dialog Resources klikając znak plus w kategoriach Resourees i Dialog projektu (takiego jak SizeFonn). 3. Kliknij dwukrotnie nazwę okna dialogowego IDD_ABOOTBOX, aby rozpocząć jego edycję. 4. Wciśnij klawisze Alt+Enter, żeby zmodyfikować właściwości okna dialogowego. 5. Rozwiń listę stylów obramowania Border i wybierz opcję Resizing. 6. Zbuduj i uruchom aplikację. 7. W aplikacji kliknij menu Hełp i wybierz opcję zaczynającą się od słowa About (na przykład About Size Form). Teraz będziemy mogli rozciągać okno dialogowe chwytając przeznaczony do tego celu uchwyt w prawym dolnym rogu obramowania. Możemy również odpowiednio obsłużyć komunikaty związane z funkcją cmsize (), dokładnie tak jak to robiliśmy w poprzednim przykładzie. Przewijanie zawartości okna Zdarzyć się może, że okno będzie za małe, by wyświetlić w nim cały rysunek lub całą zawartość okna dialogowego. Czasami może zaistnieć również potrzeba utworzenia okna większego niż rozmiary ekranu komputera, którego ukryte fragmenty użytkownik będzie mógł oglądać przewijając jego zawartość. Biblioteka MFC dostarcza w tym celu klasy cscrollYiew umożliwiającej przewijanie zawartości okna za pomocą pasków przewijania. Zapewne korzystaliście z wielu aplikacji, które wykorzystywały paski przewijania. Aplikację SDI z paskami przewijania można bez trudu utworzyć za pomocą kreatora AppWizard i klasy CScrollYiew. Algorytm postępowania jest tu dokładnie taki sam jak w przypadku klasy CFormView, tak jak to pokazaliśmy w instrukcji „Tworzenie aplikacji SDI bazującej na widoku Form". Rozciąganie i przewijanie widoków 465 Zamiast klasy CFormYiew, z listy kombinowanej Base Ciass na ostatniej stronie kreatora AppWizard wybieramy klasę CscrollView. Przykłady tutaj przedstawione odnosić się będą do różnych aspektów przewijania w aplikacji PanSDI. Definiowanie rozmiarów przewijanego obszaru Aplikacja SDI oparta na klasie CscrollView utworzona przez kreator AppWizard nie będzie się na pierwszy rzut oka niczym różnić od aplikacji SDI opartej na klasie cview. Dzieje się tak dlatego, że domyślnie ustawione rozmiary przewijanego obszaru wynoszą 100x100 pikseli i nie wykraczają poza domyślne rozmiary widoku. Można zwiększyć wymiary przewijanego obszaru zmieniając domyśle ustawienia funkcji OninitialUpdate (). Funkcja OninitialUpdate() Funkcja OninitialUpdate () jest dobrym miejscem na dokonywanie inicjalizacji widoku, jako że przyzywana jest w momencie inicjalizacji okna. Funkcja ta jest podobna do występującej w oknach dialogowych funkcji OninitDialog (). Należy jednak uważać z odwoływaniem się w funkcji OninitialUpdate () do obiektu dokumentu aplikacji, ponieważ czasami widok jest inicjalizowany wcześniej niż dokument. Mogłoby to być przyczyną okresowych awarii programu w momencie uru- chamiania aplikacji. Jak odnaleźć funkcję OninitialUpdate (), by za jej pomocą zmienić wymiary przewi- janego obszaru 1. W oknie Project Workspace wybierz panel ClassView. 2. Otwórz klasy projektu (na przykład PanSDI) klikając symbol plus. 3. Otwórz klasę wywiedzioną z klasy CscrollView (przykładowo CPanSDlview) klikając na jej znaku plus. Na dole listy funkcji składowych powinna znajdować się funkcja OninitialUpdate(). 4. Kliknij dwukrotnie tę funkcję, a pojawi się kod funkcji (implementacja) widoczny na listingu 18.6. 5. Domyślne wymiary przewijanego obszaru możesz zmienić, zmieniając wartości size-Total.ex i sizeTotal.cy. 466_____________________________________Poznaj Visual C++ 6 Listing 18.6. LST19_6.CPP - standardowa implementacja funkcji OnInitialUpdate() w klasie CScrollView 1 void CPanSDIView::OnInitialUpdate() 2 { 3 CScrollView::OnInitialUpdate() ; 4 CSize sizeTotal; 5 6 // TODO: oblicz całkowite wymiary widoku 1 sizeTotal.ex = sizeTotal.cy = 100; 8 SetScrollSizes(MM_TEXT, sizeTotal); O 9 ) O Domyślne rozmiary przewijanego obszaru wynoszą 100x100 pikseli i są zbyt małe, aby warto było go przewijać Funkcja OninitialUpdate () jest wzywana tylko raz, chwilę przed tym nim widok zostanie wyświetlony po raz pierwszy, ale już po jego podłączeniu do dokumentu. Można ją wykorzystać do wstępnej inicjalizacji widoku. W linii 7 listingu 18.6 klasie csize są przypisywane wstępnie wymiary 100x100 pikseli, po czym klasa jest przesyłana do funkcji SetScrollSizes (). Funkcja ta definiuje całkowite rozmiary widoku w specyficznym trybie odwzorowywania. W trybie MM_TEXT wymiary podawane są w pikselach (tryby odwzorowywania opisane są dokładniej w rozdziale 15). Rozmiar widoku jest definiowany domyślnie jako 100x100 pikseli, co oznacza, że jest on mniejszy niż rozmiary okna i nie ma potrzeby wyświetlania pasków przewijania. Może się zdarzyć, że będziemy potrzebowali bardzo dużego widoku, większego niż rozmiary ekranu. W tym celu możemy, tak jak to widać na listingu 18.7, zmienić jego rozmiary ze 100x100 na 2000x2000 pikseli. W ten sposób rozmiary widoku będą znacznie większe niż rozmiary ekranu (chyba że mamy ekran o bardzo wysokiej rozdzielczości!). Listing 18.7. LST19_7.CPP - przypisywanie widokowi rozmiarów większych niż wymiary ekranu 1 void CPanSDIView::OnInitialUpdate() 2 ( 3 CScrollView::OnInitialUpdate() ; 4 CSize sizeTotal; 5 6 // TODO: wylicz całkowite rozmiary widoku 1 sizeTotal.ex = sizeTotal.cy = 2000; // ** Nowe wymiary 467 Rozciąganie i przewijanie widoków 8 SetScrollSizes(MM_TEXT, sizeTotal); O 9 > O 2000x2000 pikseli to obszar większy znacznie niż rozmiary ekranu, dlatego widok Scroll musi pozwolić na przewijanie, żeby użytkownik mógł dotrzeć do dowolnej części widoku. „ Aby zobaczyć jak działa przewijanie widoku, powinniśmy narysować w oknie duży obiekt, na przykład elipsę, tak jak na listingu 18.8. Implementację funkcji onDraw można znaleźć klikając dwukrotnie jej nazwę w panelu ClasView w oknie Project Workspace. Listing 18.8. LST19_8.CPP - malowanie rysunku większego niż rozmiary ekranu w widoku ________Scroll__________________________________________ 1 void CPanSDIView::OnDraw(CDC* pDC) 2 { . 3 CPanSDIDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc); 5 6 // TODO: tu dodaj kod rysowania dla własnych danych 7 8 II** Wybierz szary pędzel 9 CBrush* pOldBrush = 10 (CBrush*)pDC->SelectStockObject(LTGRAY_BRUSH) ; 11 12 // ** Zdefiniuj widoczny prostokąt CRect 13 CRect rcTotal(CPoint(0,0),GetTotalSize()); 14 15 II** Namaluj elipsę 16 pDC->Ellipse(rcTotal); O 17 18 II** Przywróć dawny pędzel 19 pDC->SelectObject(pOldBrush) ; 20 } O Elipsa jest większa niż rozmiary ekranu i dlatego zostanie odmalowana tylko ta jej część, która mieści się w widocznej części okna. 468 Poznaj Visual C++ 6 Na listingu 18.8 warto zwrócić uwagę na to, że funkcja GetTotalSize () widoku Scroll jest wykorzystywana (linia 13) do uzyskiwania informacji o całkowitych rozmiarach przewijanego obszaru. W oparciu o te informacje można skonstruować obiekt CRect i wykorzystać go do narysowania elipsy o rozmiarach 2000x2000 pikseli. Elipsa rysowana jest (w linii 10) szarym pędzlem (LTGRAY_BRUSH). Jeśli po wprowadzeniu tych zmian zbudujemy i uruchomimy aplikację, zobaczymy okno zawierające fragment wielkiej elipsy (takiej jak na rysunku 18.6). Za pomocą pasków przewijania możemy obejrzeć jej ukryte fragmenty. Możemy również rozciągnąć okno, aby móc jednorazowo oglądać większy fragment rysunku. Rysunek 18.6. Wyświetlanie obiektów większych niż rozmiary ekranu za pomocą widoku Scroll Zmienianie małego i dużego skoku przewijania Jeśli klikniemy strzałkę znajdującą się na końcu paska przewijania, zauważymy, że rysunek przesunie się w o pewną odległość. Odległość tę nazywamy małym skokiem przewijania (ang. linę scroll amount). Jeśli klikniemy szczelinę pomiędzy strzałką a listwą paska przewijania, rysunek zostanie przewinięty o większą odległość. Odległość ta nosi nazwę dużego skoku przewijania (ang. page scroll amount). Angielskie nazwy mówiące o linii i stronie odwołują się do roli pasków przewijania w edytorach tekstu, niemniej reguły rządzące przewijaniem są takie same we wszystkich aplikacjach. Na czym oparte są wartości małego i dużego skoku przewijania? Zazwyczaj rozmiary skoku przewijania definiowane są w pikselach, za co odpowiada znacznik trybu odwzorowywania MM_TEXT. Jednakże skoki możemy również zdefiniować w oparciu o dowolny inny tryb odwzorowywania, tak aby były one lepiej dopasowane do miar, z których korzystamy na co dzień. Przykładowo, możemy zdecydować, aby mały skok przewijania wynosił l cm, a duży 10 cm. Rozciąganie i przewijanie widoków 469 Rozmiary tych skoków możemy zmienić, przesyłając odpowiednie parametry funkcji SetScrollSizes (). Jeśli zdecydujemy się ich nie zmieniać, skoki przyjmą rozsądne domyślne wartości. Do zmiany wartości skoków zarówno w pionowym, jak i w poziomym pasku przewijania możemy wykorzystać obiekt csize. Obiekt csize przechowuje zmienne składowe ex i cy. Zmienna ex definiuje wartość małego lub dużego skoku dla poziomego paska przewijania, a zmienna cy dla paska pionowego. Funkcja SetScrollsizes() może zostać inicjalizowana w momencie pierwszego wyświetlenia widoku przez funkcję OninitialUpdate (). Aby edytować kod tej funkcji dla odpowiedniej klasy widoku, wystarczy kliknąć dwukrotnie funkcję OninitialUpda-te () w panelu ClassView okna Project Workspace. Skoki przewijania możemy zmienić dodając do parametrów funkcji SetScrollsi-zes () odpowiednie obiekty CSize (listing 18.9). Listing 18.9 LST19_9.CPP - definiowanie małego i dużego skoku przewijania za pomocą funkcji SetScrollSizes() 1 void CPanSDIView::OninitialUpdate() 2 { 3 CScrollView::OninitialUpdate() ; 4 CSize sizeTotal; 5 // TODO: oblicz całkowite wymiary tego widoku 6 sizeTotal.ex = sizeTotal.cy = 2000; // nowe rozmiary 1 SetScrollSizes(MM_TEXT, sizeTotal, 8 CSize(200,10), // ** duży skok (X, Y) O 9 CSize(20,l));// ** mały skok (X, Y) 10 } O Funkcja SetScrollSizes () pozwala zmieniać wartości dużego i małego skoku paska przewijania. Warto zauważyć, że pionowe skoki przewijania są znacznie większe niż te zdefiniowane dla paska poziomego. Jeśli teraz zbudujemy i uruchomimy aplikację, będziemy mogli obserwować różnice między przewijaniem w pionie i w poziomie. Do czego służy bieżąca pozycja widocznego obszaru Czasami może nam być potrzebna wiedza o tym, która część dużego rysunku jest widoczna na ekranie i jakie są jej współrzędne względem całości. Załóżmy, że chcemy narysować pośrodku okna poruszający się wraz z widocznym obszarem ukośny krzyżyk. Wydawać by się mogło, że powinniśmy skorzystać podobnie jak we wcześniejszych przykładach z funkcji GetCIientRect () i funkcji CenterPoint () klasy CRect. Niestety 470 Poznaj Visual C++ 6 w ten sposób otrzymamy pożądany efekt tylko wtedy, gdy widoczny obszar znajduje się dokładnie w lewym górnym rogu widoku. Jeśli jednak widoczny obszar znajdować będzie się na przykład w środkowej części widoku, prostokąt obszaru roboczego (ang. client rectangle), którego wymiary podaje funkcja GetdientRect () nadal będzie zorientowany do lewego górnego rogu widoku i rysowany krzyżyk wcale nie musi znaleźć się pośrodku widocznego obszaru. Współrzędne lewego górnego rogu widocznego obszaru można zdobyć za pomocą funkcji GetScrollPosition (). Aby uzyskać współrzędne środka widocznego obszaru, należy do współrzędnych uzyskanych z funkcji GetScrollPosition () dodać wartości uzyskane za pomocą funkcji CenterPoint () zastosowanej na prostokącie obszaru roboczego. Jak uzyskać współrzędne zdefiniowane w pikselach Funkcja GetScrollPosition () podaje współrzędne wyrażone w jednostkach zależnych od zdefiniowanego trybu odwzorowywania. Jeśli chcemy zdobyć współrzędne wyrażone w jednostkach zrozumiałych dla komputera (pikselach), powinniśmy skorzystać z funkcji GetDeviceScrollPosition (). Funkcja ta podaje współrzędne wyrażone w pikselach, tak jak gdyby ustawiony był tryb odwzorowy- wania MM-TEXT. Wstawienie kodu przedstawionego na listingu 18.10 do funkcji OnDrawO zaraz po funkcji Ellipse () spowoduje, że program narysuje ukośny krzyżyk pośrodku widocznego obszaru okna. Listing 18.10. LST19_10.CPP - rysowanie krzyżyka pośrodku widocznego obszaru za pomocą funkcji GetScrollPosition() 1 // Utwórz obiekt CRect 2 CRect rcTotal(CPoint(0,0),GetTotalSize()); 3 4 // Narysuj elipsę 5 pDC->Ellipse(rcTotal); 6 7 // ** Weź rozmiary obszaru roboczego 8 CRect rcCIient; 9 GetdientRect (SrcCIient) ; 10 11 // ** Zdobądź współrzędne widocznego obszaru 12 rcCIient += GetScrollPosition(); O 13 14 // ** Znajdź jego środek 15 CPoint ptCenter = rcCIient.CenterPointO; 16 Rozciąganie i przewijanie widoków 471 17 // ** Rysuj linię na ukos w prawo w dół 18 pDC->MoveTo(ptCenter + CPoint(-30,-30) ) ; 19 pDC->LineTo(ptCenter + CPoint(+30,+30)); 20 21 // ** Rysuj -linię na ukos w lewo w dół 22 pDC->MoveTo(ptCenter + CPoint(+30,-30) ); 23 pDC->LineTo(ptCenter + CPoint(-30,+30)); 24 25 // Przywróć poprzedni pędzel 26 pDC->SelectObject(pOldBrush) ; 27 } O Prostokąt obszaru roboczego (client RECT) przechowuje rozmiary widocznego obszaru. Aby jednak narysować linie we właściwym miejscu, należy dodać bieżące współrzędne lewego górnego rogu widocznego obszaru uzyskane za pomocą funkcji GetScrollPos (). • Poza linią 12, w której dodajemy współrzędne uzyskane z funkcji GetScrollPosi-tion () do rozmiarów prostokąta obszaru roboczego, reszta kodu funkcji jest stosunkowo prosta. Teraz możemy wykorzystać funkcję rcdient. CenterPoint () (z linii 15) w funkcjach MoveTo () i LineTo () rysujących ukośny krzyż wewnątrz widocznego obszaru okna. Program wygląda całkiem ładnie, zawiera jednak pewien błąd. Po jego uruchomieniu okaże się, że gdy tylko przewiniemy obszar widoczny w oknie, ukośny krzyż zniknie. Dzieje się tak z powodu techniki systemu zwanej odcinaniem (ang. clipping, polegającej na odcinaniu obszarów okna, które nie muszą być odświeżone). System Windows, aby przyśpieszyć odmalowywanie okna, stara się maksymalnie ograniczyć ilość pracy, którą musi wykonać. System mówi sobie: „Aha, wystarczy, że odmaluję tylko nowo odkryty fragment okna i wszystko będzie w porządku". Dlatego odmalowuje na ekranie tylko wąski pasek widoku, który pojawia się po stronie, w którą przewijaliśmy, a resztę obrazu po prostu odpowiednio przesuwa. Technika ta działa sprawnie, gdy malujemy na ekranie nieruchomą elipsę, ale narysowane przed chwilą ukośne linie powinny przesuwać się wraz z przewijanym obszarem. Dlatego należy poinformować system, że jego technika odświeżania zawartości okna jest nieodpowiednia i powinien odmalować ponownie również pozostałe części widocznego w oknie obszaru. Służy do tego funkcja invalidate (). Musimy wywołać funkcję invalidate (), zachodzi jednak pytanie, w którym miejscu? Nie można jej przyzwać wewnątrz funkcji OnDraw (), bowiem system Windows właśnie funkcję OnDraw () wykorzystuje do odmalowywania obszaru zaznaczanego do odświeżenia przez funkcję Invalidate (). W ten sposób program odmalowywałby zawartość okna bez końca. 472 Poznaj Visual C++ 6 Odmalowywanie określonego obszaru okna Możemy odmalowywać zdefiniowany przez nas obszar okna korzystając z funkcji irwalidateRect (), której pierwszym parametrem są współrzędne odmalowywanego prostokąta. Możemy również skorzystać z funkcji lnvalidateRgn(), która odmalowuje wnętrza bardziej skomplikowanych obszarów zdefiniowanych dla niej przez przesyłany jako pierwszy parametr obiekt CRgn. Obie te funkcje zmieniają zasady odmalowywania stosowane przez Windows, tak że za każdym razem odświeżany jest dokładnie taki obszar, jaki sobie zażyczymy. Zamiast tego musimy sprawdzić, kto właściwie jest odpowiedzialny za odmalowywanie (komunikat, funkcja programu) i przyzwać funkcję lnvalidate () za pośrednictwem sprawcy odmalowywania. W tym przypadku zawartość okna jest odświeżana, kiedy klik-nięty zostanie pasek przewijania. Dlatego należy przechwycić komunikat, który informuje o konieczności przewinięcia zawartości okna i za jego pośrednictwem przyzwać funkcję lnvalidate (). Obsługa komunikatów paska przewijania Wirtualna funkcja OnScroll () jest przyzywana przy każdej operacji na pasku przewijania. Aby zdefiniować własny kod związany z przewijaniem okna, należy przechwycić i pokryć (ang. override) tę funkcję jej zmodyfikowaną przez nas wersją. Dodawanie pokrywającej wersji funkcji On Scroll () 1. W oknie Project Workspace w panelu ClasView kliknij prawym klawiszem myszy klasę wywiedzioną z klasy CScrollView (takiej jak CPanSDIView). Powinno pojawić się odpowiednie menu skrótów. 2. W menu wybierz opcję Add Virtual Function. Pojawi się okno dialogowe The New Virtual Override for Ciass CPanSDIView. 3. Przewiń w dół listę New Virtual Functions, aż znajdziesz funkcję OnScroll ( ), tak jak widać na rysunku 18.7. 4. Kliknij funkcję OnScroll ( ), a następnie przycisk Add and Edit, aby dodać nową funkcję pokrywającą funkcję wirtualną. 5. Dodaj swoje modyfikacje do kodu funkcji OnScroll ( ) (na przykład odwołanie do funkcji lnvalidate znajdujące się na listingu 18.11 zaraz pod komentarzem // TODO :. Rozciąganie i przewijanie widoków 473 ^ewVirtualFunction3 ^xisfingV]rtuaSiur(Ct]un o^/erfides ni;' OftDrayit-iave ^J OnDraigOver OnDraw Cancel OnDrop UnEndPnntirłg 0 nEndPrintPreview OnFinaIReleos e OnInilislUpdate ^dd Handler OnNoW/ "inPrpnflfpPnntii- irj PreCieateWindow fAddandiditi OnPrepareDC OnPrinI EdifE)c:]shng CinScrollBy U: OnUpdate :.| PostNcDestroy l1 PreSubdassWindow ); PreTranslaleMessage |^ ,u i:i ^iń^^^^:!!!!!!1!!^^ Serialize •^ ^^ [l '^^i^l^jw^^^l^ll^ll:!!^!' WindowProc : : • • ••: ••:•.•.• •;;;";.: ••:;:•:•••:•:•:•:.^^^•. Winhelo ^ OnScrollO: Calledto determiriewhethe scfoltmgispossibie Rysunek 18.7. Dodawanie funkcji pokrywającej wirtualną funkcję OnScroll za pomocą okna dialogowego New Virtual Ovemde Listing 18.11. LST19_11.CPP - wymuszanie na systemie Windows odmalowywania całego obszaru okna za pomocą funkcji Invalidate() BOOL CPanSDIView::OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll) 2 3 4 5 6 7 8 9 10 // ** Zaznacz do odświeżenia całe okno Invalidate() ; return CScrollView::OnScroll(nScrollCode, nPos, bDoScroll) W ten sposób kod odpowiedzialny za rysowanie krzyżyka jest kompletny. W linii 6 poinformowaliśmy system Windows, że cały widoczny obszar okna został unieważniony i należy go odmalować ponownie. W ten sposób zmodyfikowane zostają stosowane przez system Windows zasady odświeżania obrazu i narysowany krzyżyk pojawiać się będzie tam, gdzie chcemy, czyli zawsze pośrodku okna. Po zbudowaniu i uruchomieniu programu będziemy mogli swobodnie przewijać okno, by oglądać narysowaną w nim elipsę, a ukośny krzyżyk będzie zawsze znajdować się pośrodku widocznego obszaru, co można zobaczyć na rysunku 18.8. 474 Poznaj Visual C++ 6 Rysunek 18.8. Krzyżyk odmalowywany jest za każdym razem, gdy obraz w oknie zostanie przewinięty Możemy również przechwytywać bardziej szczegółowe komunikaty związane z przewijaniem tylko w pionie lub tylko w poziomie i modyfikować ich funkcje obsługi. Te komunikaty to WM_HSCROLL i WM_VSCROLL, a ich funkcje obsługi to odpowiednio OnHScroll () iOnVScroll (). Dodawanie funkcji obsługi OnHScroll () przechwytującej komunikat WM_VSCROLL 1. W panelu ClasView w oknie Project Workspace kliknij prawym klawiszem myszy klasę wywodzącą się z klasy CScrollView (tutaj CPanSDIView). Powinno pojawić się odpowiednie menu skrótów. 2. Wybierz opcję Add Windows Message Handler. Pojawi się okno dialogowe The New Windows Message and Event Handlers for Ciass CPanSDIView. 3. Z listy New Windows Messages/Events wybierz komunikat WM_HSCROLL. 4. Aby dodać do programu funkcję obsługi komunikatu informującego o przewijaniu w poziomie, kliknij przycisk Add and Edit. Pojawi się funkcja OnHScroll () obsługująca komunikat WM_HSCROLL. Komunikat ten wysyłany jest przez system zawsze, gdy zostanie poruszony poziomy pasek przewijania. Analogicznie, funkcja Onvscroll () obsługująca komunikat WM_VSCROLL wykorzystywana jest, gdy poruszony zostanie pionowy pasek przewijania. W linii 6 listingu 18.12 współrzędne pozycyjne otrzymane z funkcji OnHScroll () są przed wysłaniem do widoku Scroll odwracane. Rozciąganie i przewijanie widoków 475 Listing 18.12. LST_12.CPP- odwracanie kierunku przewijania w funkcji OnHScroll () 1 void CPanSDIView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 2 { 3 // TODO: tu dodaj własny kod obsługi komunikatów 4 5 II** Odwróć współrzędne widoku 6 nPos = GetScrollLimit(SB_HORZ) - nPos; 7 8 CScrollView::OnHScroll(nSBCode, nPos, pScrollBar); 9 } 10 O Zmieniając parametr npos zanim zostanie przyzwana funkcja klasy bazowej OnHScroll () możemy odwrócić kierunek przewijania. Przywołana w linii 6 funkcja GetScrollLimit () wykorzystywana jest do wydobycia długości paska przewijania (znacznik SB_HORZ informuje, że chodzi o poziomy pasek przewijania). Możemy odwrócić kierunek przewijania odejmując aktualną pozycję nPos od długości paska. Następnie zmodyfikowany parametr nPos przesyłany jest do bazowej funkcji OnScrolK) w linii 8, która obsługuje przewijanie w normalny sposób. Jednak dzięki zmianie wartości parametru, poziomy pasek przewijania działa na odwrót! Teraz możemy zbudować i uruchomić program, by przyjrzeć się dziwacznemu zachowaniu poziomego paska przewijania. Pionowy pasek przewijania natomiast działa normalnie (możemy jednak również i jego zachowanie odwrócić w podobny sposób, prze-chwytując komunikat WM_VSCROLL). Rozdział 19 Widoki List, Tree, Rich Edit i HTML Obsługa danych zapisanych w formie listy za pomocą widoku List Hierarchiczne organizowanie danych za pomocą widoku Tree Zaawansowane edytowanie tekstu za pomocą widoku Rich Edit Aplikacja z elementami przeglądarki sieciowej Czym są widoki List, Tree i Rich Edit? Widoki List, Tree i Rich Edit bazują na kontrolkach, które mogą być wykorzystywane w oknach dialogowych. W praktyce większość ich funkcji wykonywanych jest przez te właśnie, osadzone wewnątrz kontrolki. Trzy wspomniane tutaj widoki są jednymi z bardziej wszechstronnych i częściej wykorzystywanych narzędzi. Tworzenie i korzystanie z widoku List Wiele aplikacji wykorzystuje jako centralny element różnego rodzaju listy. Oczywistym przykładem jest tu Eksplorator Windows, który wykorzystuje widok List w prawym panelu okna. Widok List dostarcza licznych narzędzi do zarządzania lista danych, którą wyświetla. Poszczególne elementy mogą być wyświetlane w postaci małych lub dużych ikon albo rozbudowanej listy podającej bardziej szczegółowe informacje na temat każdego z elementów. Widok List pozwala również na automatyczne sortowanie danych i wybieranie większej liczby elementów. Wszystkie te funkcje zostały zautomatyzowane, dzięki czemu możemy się skoncentrować na tworzeniu aplikacji zamiast na żmudnej pracy pisania kodu odpowiedzialnego za wyświetlanie listy. PATRZ TAKŻE • Jak korzystać z kontrolki List w oknie dialogowym mówimy w rozdziale 6. • Informacje o tworzeniu okna z kilkoma widokami znaleźć można w rozdziale 20. • Jak zmienić domyślną czcionkę korzystając z funkcji SetFon t () pisaliśmy w rozdziale 17. 478_______________________________________Poznaj Visual C++ 6 Tworzenie aplikacji z widokiem List za pomocą kreatora AppWizard Za pomocą kreatora AppWizard można utworzyć aplikację SDI opartą na widoku List. Zbudujemy tutaj taki projekt, aby pokazać jak działa widok List. Tworzenie aplikacji SDI bazującej na widoku List 1. Kliknij menu File i wybierz polecenie New. 2. Wybierz kartę Projects; w liście typów projektów wybierz MFC AppWizard (exe). 3. Kliknij pole Project Name i wprowadź nazwę projektu, ListV. 4. Kliknij OK. Pojawi się pierwsze okno dialogowe kreatora AppWizard. 5. Kliknij Single Document i wciskaj przycisk Next, aż dotrzesz do końcowej strony z charakterystyczną flagą. 6. W liście klas utworzonych przez kreator AppWizard kliknij klasę CListView. 7. Lista kombinowana Base Ciass stanie się teraz aktywna. Kliknij strzałkę wskazującą w dół, aby rozwinąć listę możliwych bazowych klas widoków. 8. Wybierz jako klasę bazową klasę CListView. 9. Kliknij przycisk Finish. 10. Kliknij przycisk OK w oknie dialogowym New Project Infonnation, a kreator AppWizard utworzy nowy projekt i pliki źródłowe. Powinniśmy otrzymać teraz szkielet aplikacji SDI bazującej na widoku CListView. Moglibyśmy już teraz zbudować i uruchomić aplikację, jednak nie będzie się ona jeszcze w tej chwili niczym różnić od aplikacji SDI bazującej na standardowym widoku cview. Aby zauważyć różnicę, trzeba dodać od listy odpowiednie elementy. Dodawanie elementów do listy Każda normalna aplikacja powinna przechowywać swoje dane osobno od kodu widoku, w klasie dokumentu (lub w innej klasie zdefiniowanej dla potrzeb przechowywania danych). Klasa widoku powinna być wykorzystywana przede wszystkim do przechowywania operacji ściśle związanych z działaniem widoku. Najlepszym miejscem do przechowywania danych zawartych na liście jest wywodząca z klasy CDocument klasa CLi-stVDoc. Utwórzmy przykładową listę złożoną z łańcuchów, której poszczególne elementy będą nazwami pierwiastków chemicznych. W bibliotece MFC znajduje się specjalnie przystosowana do przechowywania takich list klasa CStringList. Poniżej podajemy odpowiedni algorytm. Widoki List.Tree, Rich Edit i HTML 479 Klasy kolekcji Klasa CStringList z biblioteki MFCjest przykładem klas kolekcji (ang. collection). Kolekcje zarządzają zbiorami danych określonego typu. W przypadku klasy CStringList danymi przechowywanymi w kolekcji będą oczywiście łańcuchy, obiekty CString. Dodawanie zmiennych składowych z karty ClassView 1. W oknie Project Workspace wybierz kartę ClassView. 2. Otwórz klasy ListV, aby sięgnąć do klasy CListVDoc. 3. Kliknij tę klasę prawym klawiszem myszy, aby wywołać menu skrótów. 4. Wybierz opcję Add Member Variable, aby wywołać okno dialogowe Add Member Variable. 5. W polu Variable Type wpisz CStringList, następnie, aby przenieść się do kolejnego pola wciśnij klawisz Tab. 6. W polu Variable Declaration wpisz m_listElements. 7. Wybierz opcję Pri^ate Access i kliknij OK, by dodać nową zmienną składową klasy listy łańcuchów. W ten sposób mamy już obiekt listy łańcuchów osadzony w klasie dokumentu. W punkcie siódmym zdefiniowaliśmy zasady dostępu do zmiennej, deklarując ją jako prywatną (ang. private), dzięki czemu jej wartość można będzie modyfikować tylko za pomocą funkcji składowych dokumentu. Zapobiegnie to przypadkowym, niepożądanym modyfikacjom zmiennej przez funkcje składowe innych klas. Oczywiście jest bardzo mała szansa, że coś takiego przydarzy się w tak prostym programie jak ten, jednak w dużych aplikacjach należy bezwzględnie udostępniać dane tylko określonej grupie zaufanych funkcji danej klasy. Zdefiniowanie zmiennej jako publicznej (ang. public) oznacza, że sięgnąć mogą do niej funkcje dowolnej innej klasy. Natomiast zdefiniowanie zmiennej jako chronionej (ang. protected) działa mniej więcej tak samo jak zadeklarowanie jej jako prywatnej, z tym że w przypadku zmiennej chronionej będzie ona dostępna również dla wszystkich klas wywiedzionych z klasy, w której została zdefiniowana. Temat ten jest związany z programowaniem obiektowym i nie będziemy się tutaj nad nim zbytnio rozwodzić, warto jednak wiedzieć, że definiowanie rodzaju dostępu do zmiennych ma znaczenie, gdy sięgamy z klasy CListView do osadzonej w widoku kon-trolki List. Tak samo jak w przypadku zmiennej składowej, zmienna nie będzie dostępna spoza klasy dokumentu. Może to sprawiać pewne kłopoty, ponieważ będziemy zmuszeni zapełniać listę w widoku List z wnętrza listy łańcuchów. Rozwiązaniem tego problemu jest zdefiniowanie odpowiedniej funkcji dostępu. Zadaniem funkcji dostępu jest bezpiecz- ne zarządzanie odczytywaniem i modyfikowaniem odpowiedniej zmiennej lub zmiennych 480_____________________________________Poznaj Visual C++ 6 składowych. Jeśli do zmiennej można sięgnąć tylko za pomocą jednej ściśle określonej funkcji, w razie problemów z tą zmienną będziemy mogli ograniczyć poszukiwania odpowiedzialnego za te problemy błędu tylko do tej jednej funkcji. Zapobieganie niepożądanym modyfikacjom danych poprzez deklarowanie zmiennych jako prywatne Należy pamiętać, że piszemy program komputerowy, czyli rzecz, która jeśli będzie miała szansę, na pewno nawali i to najpewniej wtedy, gdy będziemy demonstrować jego działanie szefowi lub potencjalnemu klientowi. Ograniczanie dostępu do zmiennych poprzez deklarowanie ich jako prywatne lub chronione oraz obsługiwanie ich tylko za pomocą specjalnie w tym celu zdefiniowanych funkcji dostępu (ang. access function), chroni nas w znacznej mierze przed takimi wypadkami. Dodamy teraz odpowiednią funkcję składową, która odpowiedzialna będzie za odczytywanie i modyfikowanie danych w liście łańcuchów. Poniższa instrukcja pokazuje jak utworzyć funkcję dostępu zwracającą listę łańcuchów m_listElements. Dołączanie do programu funkcji składowej poprzez ClassView 1. Kliknij prawym klawiszem myszy klasę CListVDoc, aby wyświetlić menu skrótów klasy. 2. Aby wyświetlić okno dialogowe Add Member Function, kliknij polecenie menu Add JMember Function. 3. W polu Function Type wpisz const CStringList&, a następnie za pomocą klawisza Tab przejdź do kolejnego pola. 4. W polu Function Declaration wpisz GetElements (). 5. Aby zakończyć dodawanie funkcji składowej GetElements (), kliknij OK. Kiedy klikniemy OK w programie pojawi się nowa funkcja GetElements (). Musimy jeszcze dodać do niej poniższą linię, aby wprowadzić odwołanie do listy elementów: return m_listElements; Ponieważ funkcja ta zwraca stałą, const CStringList&, funkcje spoza dokumentu sięgające do listy będą mogły tylko odczytywać jej zawartość. Status tylko do odczytu jest efektem użycia słowa kluczowego const. Oznacza to, że klasa widoku może modyfikować listę, ale nie jest w stanie zmieniać jej zawartości. I oto właśnie chodzi - zależało nam bowiem na tym, aby zawartość listy można było modyfikować tylko z klasy dokumentu. Aby przyjrzeć się widokowi List, potrzebować będziemy przykładowych danych, dlatego w konstruktorze klasy dokumentu dodamy kilka elementów do obiektu listy. Aby rozpocząć edycję funkcji konstruktora, klikamy dwukrotnie klasę CListVDoc w klasie Widoki List,Tree, Rich Edit i HTML_____________________________481 CListVDoc (konstruktor nosi tę samą nazwę co klasa, którą konstruuje). Jak dodawać elementy do listy pokazaliśmy na listingu 19.1. Listing 19.1. LST20_1.CPP - dodawanie nazw pierwiastków do klasy konstruktora dokumentu CListVDoc ____ ____ •l CListVDoc::CListVDoc() 2 ( // TODO: Tu dodaj kod konstruowania klasy II ** Dodaj nazwy pierwiastków do listy łańcuchów m_listElements.AddTail("Carbon") ; m_listElements.AddTail("Uranium") ; m_listElements.AddTail("Gold") ; m_listElements.AddTail("Osmium") ; m_listElements.AddTail("Oxygen") ; 10 m_listElements.AddTail("Lead") ; 11 }___________________________________________________ W liniach 5-10 listingu 19.1 dodajemy do listy m_listElements nazwy pierwiastków wpisując jako parametr funkcji składowej klasy cstringList odpowiednie teksty łańcuchów. Funkcja AddTail () dodaje przesłany jej łańcuch na końcu listy. Kolejną rzeczą, która trzeba tutaj zrobić, jest załadowanie składających się na listę danych do widoku List, w momencie, gdy jest on wyświetlany po raz pierwszy. Do listy łańcuchów sięgać możemy tylko za pomocą funkcji dostępu GetElements (). Dlatego musimy odwołać się do niej w metodzie OninitialUpdateO widoku. Funkcja Onini-tialUpdate () przyzywana jest, gdy widok jest wyświetlany po raz pierwszy. Funkcję OninitialUpdateO można znaleźć klikając w panelu CIasYiew okna Project Works-pace funkcję składową klasy CListWiew, OninitialUpdate (). Kod zawarty na listingu 19.2 kopiuje elementy listy z dokumentu do widoku List. Listing 19.2. LST20_2.CPP - dodawanie elementów do widoku List za pomocą funkcji Insert-Item() 1 void CListWiew:: OninitialUpdate () 2 { 3 CListView::OninitialUpdate() ; 4 5 // TODO: możemy zapełnić LlstView elementami 6 // jego kontrolki List za pomocą, funkcji GetListCtrl () 7 8 // ** Zdobądź wskaźnik dokumentu 9 CListVDoc* pDoc = GetDocument(); O 10 // ** Upewnij się, że dokument został inicjowany 11 ASSERT_VALID(pDoc); 12 13 II** Ustal pozycje nagłówka listy łańcuchów 14 POSITION pos = 15 pDoc->GetElements().GetHeadPosition() ; 16 17 // ** Jeśli pozycja jest NULL, dodawaj elementy 18 while(pos) 19 ( 20 // ** Weź następny element z listy 21 CString strEiement SB 22 pDoc->GetElements().GetNext(pos) ; 23 24 // ** Dodaj go do widoku 25 GetListCtrl().Insertltem(0.strEiement); ® 26 } 27 ) O Dokument można zawszę .odaaleźć za.pomocąnależącej do widoku funkcji Get- DocuroentO. ' .,;^, : '. ^ ' :. . - . .' . '...•:., .„.-•1'11/;--:..:,:^.'1 @ Tutaj dodajemy elementy do listy za pomocą funkcji Insertitęms (). Tak jak to pokazaliśmy w linii 9, wskaźnik dokumentu można zdobyć za pomocą funkcji GetDocument (), zwracającej zawsze dokument będący własnością widoku. Makroinstrukcja ASSERT_VALID w linii 11 sprawdza, czy dokument (w tym przypad- ku CListVDoc) istnieje i czy można się do niego bezpiecznie odwołać. Jak za pomocą makroinstrukcji ASSERT rozpoznawać problemy zanim się pojawią Makroinstrukcje ASSERT i ASSERT_VALID pozwalają wykrywać błędy zaraz po ich po- jawieniu się. Makroinstrukcję ASSERT można wykorzystać do sprawdzenia, czy zdefi- niowany przez nas warunek jest prawdziwy (True). Przykładowo makroinstrukcja ASSERT (a>l0) ostrzeże nas, jeśli wartość a będzie mniejsza niż .10. W liniach 14 i 15 przyzywamy funkcję dostępu GetElements (), aby sięgnąć do listy łańcuchów i należącą do klasy CStringList funkcję GetHead Position (), aby przypisać nagłówkowi listy zmienną definiującą pozycję (POSITION) pos. Pętla while w Uniach 17-23 wykonywana jest dopóty, dopóki zmienna pos nie przyjmie wartości O, informując, że dotarliśmy do końca listy. Wewnątrz pętli w linii 21 łańcuchowi strEiement przypisywana jest wartość łańcu- cha uzyskanego za pomocą funkcji GetNext (). Funkcja ta podaje wartość następnego Widoki ListTree, Rich Edit i HTML 483 łańcucha listy (lub pierwszego, gdy jesteśmy jeszcze w nagłówku listy). Przy ostatnim elemencie listy zmiennej pos przypisywana jest wartość zero i wykonywanie pętli zostaje zakończone. Na koniec nowy element łańcucha dołączany jest za pomocą funkcji insertltem() do kontrolki List. Parametry tej funkcji to pozycja (numer kolumny, tutaj zero) i łańcuch, który ma zostać dodany. Warto zauważyć, że do osadzonej wewnątrz kontrolki sięgamy tutaj za pomocą funkcji GetListCtriO, która podobnie jak funkcja GetElements() zwraca odwołanie do prywatnego elementu składowego (ang. private member) osadzonego wewnątrz, chroniąc jego zawartość przed nieodpowiednimi modyfikacjami. Jeśli po wprowadzeniu kodu przedstawionego na listingu 19.2 zbudujemy i uruchomimy aplikację, powinniśmy zobaczyć widok List, taki jak na rysunku 19.1, wyświetlający w bardzo nieskomplikowany sposób zawartość listy. Lead 0«ygen Osmium Gold Uranium Carbon Rysunek 19.1. Prosty widok List, wyświetlający listę danych Zmiana stylu listy Lista w przykładzie powyżej została wyświetlona w sposób bardzo prymitywny. Do- myślny styl rozmieszcza elementy w poziomych rzędach. Wybranie innego stylu spowoduje, że lista będzie wyglądać atrakcyjniej. Przykładowo, aby uzyskać pionową listę, należy wybrać styl LVS_LIST. Cztery dostępne style listy podane są w tabeli 19.1. Podświetlanie całego wiersza honirolki List;' ''•/:•,'':'.;' . ^.K^";:^, : \:1\"^ ^?^•:^:'•1, Standardowo gdy w trybie raportu (ang. repori modę) wybierzemy element, listy, podświetlony zostanie tylko tekst w pierwszej kolumnie. Aby podświetlić także i pozostałe informacje o elemencie, należy ustawić znacznik LVS_EX_FULLROWSEI,ECT. W tym przypadku wiersz zostanie podświetlony również, gdy kiikniemy któryś z po-delementów opisujących element listy. 484___________________________________Poznaj Visual C++ 6 Tabela 19.1. Style widoku List Styl widoku List Opis stylu LVS_LIST Prosty styl pionowej listy LVS_REPORT Podobny do poprzedniego, tylko z nagłówkami nad poszczególnymi kolumnami podelementów LWS_ICON Duże ikony poukładane w rzędach od lewej do prawej LVS_SMALLICON Małe ikony poukładane w rzędach od lewej do prawej Listing 19.3 pokazuje zmiany, które należy wprowadzić, by zmienić domyślny styl LVS_ICON na styl LVS_LIST. W tym celu pobieramy wartość stylu przypisaną oknu, zmieniamy ją i zmienioną zwracamy temu oknu. Listing 19.3. LST20_3.CPP - zmieniamy domyślny styl widoku List za pomocą funkcji Get- WindowLong() i SetWindowLong() 1 GetListCtrK).Insertltem(0,strElement); 2 } . 3 4 // ** Pobierz bieżący znacznik stylu 5 DWORD dwStyle = 6 GetWindowLongfGetListCtrl().GetSafeHwnd(), O 7 GWL_STYLE) ; 8 9 // ** Usuń bieżący znacznik stylu 10 dwStyle &= ~LVS_TYPEMASK; 11 12 // ** Dodaj styl pionowej listy 13 dwStyle 1= LVS_LIST; @ 14 15 // ** Zwróć znacznik do widoku List 16 SetWindowLong(GetListCtrl().GetSafeHwnd(), © 17 GWL_STYLE,dwStyle); 18 19 // ** Odmaluj widok 20 SetRedraw(TRUE); 21 } O W linii 6 pobieramy z kontrolki List bieżący styl. @ Tutaj modyfikujemy styl. © W linii 16 odsyłamy zmodyfikowany styl do kontrolki List. Widoki List.Tree, Rich Edit i HTML 485 Kod przedstawiony na listingu 19.3 należy dodać na końcu funkcji OninitialUpda-te() po pętli while. Aby zdefiniować nowy styl, należy najpierw pobrać bieżący styl okna za pomocą funkcji GetWindowLongO. Parametr GWL_STYLE informuje, że chcemy pobrać z parametrów stylu okna bity ustawień stylu. Funkcja GetWindowLong () zwraca różne znaczniki związane z oknem, a my chcemy zmienić tylko znacznik związany ze stylami LVS_, pozostałe pozostawiając nietknięte. Uzyskana wartość przechowywana jest (linia 5) w zmiennej dwStyle typu DWORD. W Unii 10 usuwany jest znacznik stylu za pomocą operatorów bitowych C++: &= i ~. Za pomocą operatora ~ przed LVS_TYPEMASK odwracamy wartość bitów maski (zera stają się jedynkami, a jedynki zerami). Ponieważ bity maski przechowują wartości wszystkich stylów widoku List, odwrócenie wartości bitów spowoduje, że wszystkie style oznaczone zostaną jako niewybrane. Teraz za pomocą operatora &= wykonujemy operację logiczną AND na parametrach dwStyle i masce, w wyniku czego bieżący styl widoku List zostaje wymazany. Następnie za pomocą operatora l = wykonujemy w linii 13 operację logiczną OR, dodając w ten sposób nowy znacznik do bieżących ustawień znaczników. Gdy już odpowiedni styl zostanie ustawiony, należy go za pomocą funkcji SetWindowLong () przestać z powrotem do zapisanych w oknie ustawień stylów. Parametrami funkcji SetWindow-LongO są. identyfikator obsługi okna (ang. window handle), znacznik kontekstowy (GWL_STYLE) i zmodyfikowany styl. Funkcja ta jest przyzywana w linii 16. Na koniec w linii 20 za pomocą funkcji SetRedraw () polecamy widokowi List, żeby odmalował się ponownie na ekranie. Zmienianie kolorów w widoku List • ' ;' .. ': Kolor tekstu w widoku List zmienić można za pomocą funkcji 3etTextColor (), kolor Ha tekstu za pomocą funkcji setTxtBkColor (), a kolor tła całego widoku List za pomocą funkcji SetBkColor'(),-.;: ;'•.;;.-.,. ^L' :' . Po zbudowaniu i uruchomieniu programu pojawi się Usta elementów taka jak na rysunku 19.2. yEtKWf^SISPWWSfS . Fiie Eciit Yiew Ue!p f,D]^lyj'Jj^f@l^ Lead Oxygen Osmium Gold Uranium Carbon Rysunek 19.2. Widok List po zastosowaniu stylu LVS_LIST « Informacje o dodawaniu do widoku List ikon i innych elementów graficznych znaleźć można w rozdziale 11. Kolumny i nagłówki kolumn Styl LVS_REPORT pozwala dodać do listy kolumny podelementów i nagłówki kolumn. Prezentowany tutaj program byłby ciekawszy, gdyby do każdego z pierwiastków chemicznych występujących w liście dodać jego symbol chemiczny i liczbę atomową. Pierwszą zmianą, którą należy wprowadzić, jest uzupełnienie listy o dodatkowe in- formacje. W dużych aplikacjach może pojawić się potrzeba przechowywania poszczególnych informacji w ich własnych klasach. W tym przypadku aby program pozostał możliwie prosty, dodatkowe informacje dodane zostaną na końcu każdego łańcucha, oddzielone od siebie przecinkami (patrz listing 19.4). Aby edytować funkcję konstruktora dokumentu, należy w panelu ClasView okna Project Workspace kliknąć dwukrotnie funkcję składową CListYDoc klasy CListYDoc. Listing 19.4. LST20_4.CPP - dodawanie do danych listy symbolu chemicznego i liczby ato- mowej pierwiastka 1 CListYDoc::CListVDoc() 2 ( 3 // TODO: tutaj dodaj kod konstruujący dokument 4 5 // ** Nazwy pierwiastków z symbolami chemicznymi i liczbami atomowymi 6 m_listElements.AddTail("Carbon,C,6") ; 7 m_listElements.AddTail("Uranium,U,92") ; 8 m_listElements.AddTail("Gold,Au,79") ; 9 m_listElements.AddTail("Osmium,0s,76") ; 10 m_listElements.AddTail("Oxygen,O,8") ; 11 mJ.istElements.AddTail("Lead,Pb,82") ; 12 }_________________________________________________________ Teraz, gdy dane zawarte w liście zostały zmienione, trzeba dodać nagłówki do nowych kolumn listy. Można to zrobić wewnątrz funkcji Oninitialupdate () klasy CList-Wiew wywiedzionej z klasy CListView. Jak widać z listingu 19.5, funkcja ta zrobiła się już całkiem długa. Widoki List/Tree, Rich Edit i HTML____________________________487 Listing 19.5. LST20_5.CPP - funkcja OninitialUpdate () dodająca do listy nowe kolumny 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 void CListWiew: :OnXnitialUpdate () ( CListView::OninitialUpdate() ; // TODO: Tutaj zapełniamy ListView odpowiednimi danymi II z jego kontrolki List za pomocą funkcji GetListCtrl () II ** Dodaj kolumny i nagłówki GetListCtrl().InsertCoiumn(O,"Element Name", LVCFMT_LEFT,120); GetListCtrl().InsertCoiumn(l,"Symbol", LVCFMT_CEMTER,70); GetListCtrl().InsertCoiumn(2,"Atomie Number", LVCFMT_RIGHT,130); // Pobierz wskaźnik dokumentu CListYDoc* pDoc =• GetDocument(); // Upewnij się, ze dokument jest legalny ASSERT_VALID(pDoc) ; // Ustal pozycję nagłówka listy łańcuchów POSITION pos » pDoc->GetElements().GetHeadPosition () ; // Póki pozycja nie jest NULL, dodawaj elementy while(pos) ( // Pobierz następny element listy CString strElement = pDoc->GetElements(),GetNext(pos) ; // ** Wydobądź z łańcucha nazwę pierwiastka CString strName = strElement.Left(strElement.Find(",")) ; // ** Wydobądź symbol chemiczny i liczbę atomową CString strSymbol = strElement.Mid(strElement.Find(",")+1); O // ** Wydobądź liczbę atomową CString strAtomicNumber = strSymbol.MidfstrSymbol.Find(",")+!); 488______________ Poznaj Visual C++ 6 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 } // ** Odetnij liczbę atomową na końcu strSymbol = strSymbol.Left(strSymbol.Find(",")); // ** Dodaj do widoku listy na pozycji O GetListCtrl().Insertitem(O,strName) ; // ** Do drugiej kolumny wpisz symbol GetListCtrl().SetItemText(O,l, strSymbol) ; // ** Do trzeciej kolumny wpisz liczbę atomową GetListCtrl().SetItemText(O,2, strAtomicNumber) ; // Pobierz bieżący znacznik stylu DWORD dwStyle == GetWindowLong(GetListCtrl().GetSafeHwnd(), GWL_STYLE) ; // Usuń bieżący znacznik stylu dwStyle &= ~LVS_TYPEMASK; // ** Dodaj styl raportu dwStyle |= LVS_REPORT; @ // Prześlij nowy styl do widoku List SetWindowLong(GetListCtrl().GetSafeHwnd(), GWL_STYLE,dwStyle) ; // Odmaluj ponownie widok SetRedraw(TRUE) ; O Tutaj rozcinamy łańcuch w miejscu przecinków, aby uzyskać osobne łańcuchy dla każdej z kolumn. © Styl LVS_REPORT dodaje do widoku List dodatkowe kolumny z dającymi się rozciągać nagłówkami. Widoki List,Tree, Rich Edit i HTML 489 Na listingu 19.5 kolumny dodawane są za pomocą funkcji składowej insertColumn () kontrolki List w liniach 9,11 i 13. Parametrami tej funkcji są: numer kolumny, tytuł nagłówka, znacznik formatowania i szerokość kolumny w pikselach. Każda z kolumn może być sformatowana tak, aby tekst był wyjustowany do lewej, do prawej lub centralnie. Służą do tego odpowiednio znaczniki: LVCFMT_LEFT, LVCFMT_RIGHT i LVCFMT_ CENTER. Usuwanie kolumn Po dołączeniu kolumn można usuwać je za pomocą funkcji DeieteColumn () podając numer kolumny, którą należy usunąć. Po dołączeniu do listy nagłówków kolumn trzeba dodać do każdej kolumny odpowiedni tekst. W tym celu, tak jak to pokazaliśmy w liniach 29-46, należy najpierw wykonać odpowiednie krojenie łańcucha na mniejsze części: strName, strSymbol i strAto-micNumber, które zostaną później wpisane do odpowiednich kolumn. Warto zauważyć, że pierwsza kolumna jest zapełniana w linii 49 za pomocą funkcji lnsertltem(), ale następne kolumny już za pomocą funkcji SetlfcemText (). Parametrami tej funkcji są: pozycja elementu,' numer kolumny i dodawany tekst. Na koniec trzeba skorzystać ze stylu LVS_REPORT, aby wyświetlić nagłówki kolumn. Dzieje się to w linii 70 po usunięciu poprzedniego stylu LVST_LIST. Po zbudowaniu i uruchomieniu programu pojawi się lista taka jak na rysunku 19.3. fyS -t:.; !•". •••'".{<••» .'•• •^••.^••'.iWiKi iWW.^t^i^.^.i:;;.^,^^^:..;.;,^.,.:^';',^'1 .^w& Fils Edit Yiew He!p Symbol Atomie Numtaer Lead Ocygań Osmiu m Gold Ulanium Carbon Pb O Os Au U C 62 Rysunek 19.3. Widok List z trzema kolumnami PATRZ TAKŻE ^ Ręczne edytowanie elementów widoku List opisane zostało w rozdziale 18 490___________________________________Poznaj Visual C++ 6 Odnajdywanie wybranych przez użytkownika elementów na liście Jedną z typowych funkcji dostępnych w widoku List jest możliwość wybierania przez użytkownika poszczególnych elementów na listy. Elementy oznaczone specjalnym znacz- nikiem można odnajdywać za pomocą funkcji GetNextltem(). Funkcja ta ma dwa parametry - pierwszy jest indeksem elementu, od którego funkcja rozpoczyna poszukiwania. W ten sposób możemy zacząć szukać elementu poczynając od innego arbitralnie ustalonego elementu albo jeśli przypiszemy temu parametrowi wartość -l, od początku listy. Drugi parametr jest znacznikiem informującym, do jakiej kategorii należy element poszukiwany przez funkcję. Lista znaczników podana jest w tabeli 19.2. Jak widać z tabeli 19.2, dostępnych jest kilka znaczników, takich jak LVNI_TOLEFT pozwalających poszukiwać element według pewnych kryteriów geometrycznych. Znaczniki te przydają się w przypadku, gdy widok List korzysta z jednego z dwóch stylów wyświetlających ikony elementów. Funkcja GetNextltem() obsługuje wszystkie te nietypowe przypadki, potrafi również odnaleźć element listy wybrany przez użytkownika. Ten ostatni przypadek związany jest ze znacznikiem LVNI_SELECTED. Jeśli wezwiemy funkcję GetNextltem() podając jej jako punkt wyjściowy ostatni zwrócony indeks, funkcja odnajdzie następny element spełniający kryteria definiowane przez znacznik. Mając indeks poszukiwanego elementu możemy użyć funkcji kontrolki List Getitem-Text (), żeby zdobyć tekst tego elementu. Funkcja ta wymaga jako parametrów indeksu i numeru kolumny poszukiwanego tekstu, a zwraca obiekt cstring zawierający poszukiwany tekst elementu. Tabela 19.2. Znaczniki wykorzystywane w funkcji GetNextltem () Znacznik Opis kategorii LVNI_SELECTED Element wybrany przez użytkownika LVNI_FOCUSED Element, na którym w danym momencie się koncentrujemy (obramowany kropkowanym prostokątem) LVNI_ALL Następny element, znacznik domyślny LVNI_ABOVE Element ponad tym, od którego zaczynamy poszukiwania LVNI__BELOW Element znajdujący się pod nim LVNI_TOLEFT Element na lewo od niego LVNI_TORIGHT Element na prawo od niego W ten sposób można na przykład wyświetlić na pasku tytułowym listę aktualnie wy- branych przez użytkownika elementów. W tym celu należy najpierw ustalić, kiedy tekst na pasku tytułowym będzie uaktualniany. Najlepiej wykonać to, kiedy użytkownik kliknie w obrębie listy. W tym momencie najpewniej zmieni się zestaw wybranych elementów. Należy teraz przechwycić odpowiedni komunikat informujący o kliknięciu i za pomocą kreatora CIassWizard utworzyć szkieletową funkcję obsługi. Widoki List/Irce, Rich Edit i HTML 491 Dodawanie do programu funkcji obsługi zmieniającej tekst paska tytułowego 1. Wciśnij Ctri+W, aby uruchomić kreatora CIassWizard. 2. Wybierz kartę MessageMaps, upewniając się uprzednio, że lista kombinowana Cłass Name wyświetla nazwę odpowiedniej klasy (tutaj CListWiew). Jeśli nie, wybierz odpowiednią klasę. 3. Upewnij się, czy klasa CListWiew została wybrana w polu listy Object IDs. 4. Teraz znajdź w liście komunikatów Messages komunikat =NM_CLICK. 5. Kliknij dwukrotnie odnaleziony komunikat, a pojawi się okno dialogowe Add Mem-ber Function z funkcją składową OnClick. 6. Aby dodać do programu nową funkcję obsługi, kliknij OK. 7. Kliknij Ędit Code, aby rozpocząć edycję nowej funkcji. Do programu dołączona została w ten sposób nowa funkcja obsługi OnClick (), która będzie wzywana za każdym razem, gdy użytkownik kliknie myszą w obrębie widoku List. Żeby program wyświetlał w pasku tytułowym listę wybranych przez użytkownika ele- mentów, należy umieścić wewnątrz funkcji fragment kodu przedstawiony na listingu 19.6. Listing 19.6. LST20_6.CPP - odnajdywanie wybranych przez użytkownika elementów na liście za pomocą funkcji GetNextItem () 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void CListWiew: : OnClick (NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Tutaj dodaj własny kod obsługi kontrolki *pResult = 0; // ** Łańcuch przechowujący wybrane elementy CString strSelectedItems; // ** Startowy indeks funkcji GetNextItem() powinien być zerem int nSelected=-l; do ( // ** Znajdź następny wybrany element nSelected = GetListCtrl().GetNextItem( O nSelected,LVNI_SELECTED); O // ** Sprawdź, czy jakiś element został wybrany if (nSelected != -l) ( 492_____________________________________Poznaj Visual C-K- 6 21 II ** Dodaj jego tekst do łańcucha 22 strSelectedItems += _" " + 23 GetListCtrl().GetItemText(nSelected,0); @ 24 } 25 } while(nSelected != -l); 26 27 // ** Przypisz tytułowi dokumentu wartość łańcucha 28 GetDocument()->SetTitle( 29 "Selected:" + strSelectedItems); 30 } O Funkcja GetNextitem() poszukuje elementów określonej kategorii. Tutaj znacznik LVNI_SELECTED informuje ją, że ma poszukiwać elementów, które zostały wybrane przez użytkownika. : @ Dodajemy wybrany element do łańcucha z listą wybranych elementów. : : Łańcuch strSelectedItems zadeklarowany w linii 8 wykorzystywany jest do prze- chowywania listy wybranych przez użytkownika elementów. Warto zauważyć, że zmiennej nSelected przypisana została w linii 11 wartość -l. Zmienna ta jest następnie wykorzystywana jako indeks w przywoływanej w linii 15 funkcji GetNextltem(). Dodatkowo funkcji GetMextltem() przesyłany jest znacznik LVNI_SELECTED informujący, że ma poszukiwać elementów wybranych przez użytkownika. Za pierwszym razem zmienna całkowita nSelected jest definiowana jako -l, aby funkcja rozpoczęła poszukiwania od początku listy. Jeśli lista zawiera jeszcze wybrane elementy, indeks kolejnego z nich jest przypisywany zmiennej nSelected. Wyrażenie warunkowe if z linii 19 sprawdza, czy zmienna nSelected nie przyjęła ponownie wartości -l (wartość -l oznacza, że w liście nie ma już więcej wybranych elementów). Po odnalezieniu kolejnego wybranego elementu, program przyzywa w linii 23 funkcję GetItemText przesyłając jej jako parametry indeks odnalezionego parametru i kolumnę, w której się znajduje (w tym przypadku O, czyli pierwszą kolumnę). Pobrany przez funkcję tekst jest dodawany razem z oddzielającą go od poprzedniego elementu spacją do łańcucha strSelectedItems. Warunek while w linii 25 powtarza całą pętlę zaczynającą się od słowa kluczowego do dopóty, dopóki udaje się nam odnajdywać kolejne wybrane elementy. Gdy już wszystkie zostaną odnalezione, w linii 28 definiujemy tytuł dokumentu. Po zbudowaniu i uruchomieniu programu korzystającego z przedstawionej powyżej funkcji, zobaczymy okno widoku List wyświetlające na pasku tytułowym listę wybranych przez użytkownika elementów (rysunek 19.4). Widoki ListTree, Rich Edit i HTML 493 »nt>Wi^M•tllral^nłi»m:^&h^nR1»":^^!^lN Eii ? Q E &' ;d il Q yie W U e K6 P Element Name Symbol AtomicNumber Lead ^'b 82 0 8 •, Os 76 l Gold Au 79 iBE iB B l u 92 Carbon c 6 | t8 Rysunek 19.4. Nazwy wybranych elementów wyświetlane są na pasku tytułowym Widok Tree Widoki List są bardzo przydatne, gdy trzeba wyświetlać i operować prostymi listami elementów. Jednak czasami zachodzi potrzeba wyświetlenia danych ułożonych w hierar- chiczną strukturę, w tym przypadku należy skorzystać z widoku Tree. Znakomitym przy- kładem wykorzystania widoku Tree jest lewy panel Eksploratora Windows. W panelu tym wyświetlona jest lista folderów, które można otwierać, by uzyskać listę folderów zawartych wewnątrz nich. PATRZ TAKŻE • O kontrolce Tree wewnątrz okna dialogowego pisaliśmy w rozdziale 6. • Informacje o tworzeniu okien z kilkoma widokami, takich jak okno Eksploratora Windows, można znaleźć w rozdziale 20. ^ Informacje o tym jak zmienić domyślną czcionkę za pomocą funkcji Set Font () znaleźć można w rozdziale 17. Tworzenie widoku Tree za pomocą kreatora AppWizard Kreator AppWizard pozwala na automatyczne utworzenie szkieletu aplikacji SDI opartej na widoku Tree. Postępujemy w tym przypadku dokładnie tak samo jak przy tworzeniu aplikacji opartej na widoku List, z tą tylko różnicą, że zamiast klasy CListview wybieramy klasę CTreeView. Dlatego możemy bez obaw skorzystać z algorytmu „Tworzenie aplikacji SDI bazującej na widoku List" wspomnianego wcześniej w tym rozdziale, 494 Poznaj Visual C++ 6 zmieniając tylko klasę CListview na CTreeYiew. Projekt wykorzystywany w prezento- wanych w tym rozdziale przykładach nosi nazwę TreeV. Style widoku Tree Podobnie jak widok List, widok Tree również posiada odpowiedni zbiór znaczników definiujących style. Jednak inaczej niż w widoku List, znaczniki te nie definiują sposobu wyświetlania danych, a raczej różne komponenty, które można dodawać do wyświetlanego drzewa, by wzbogacić jego wygląd. Te komponenty to linie między starszymi i potomnymi elementami hierarchii i przyciski pozwalające zwijać lub rozwijać fragmenty hierarchii. Istnieją również znaczniki pozwalające na zablokowanie możliwości przeciągania elementów między różnymi punktami hierarchii i umożliwiające na ich ręczną edycję. Ważniejsze ze znaczników podane są w tabeli 19.3. Tabela 19.3. Znaczniki stylów widoku Tree Znacznik TVS_HASLINES TVS_LIMESATROOT TVS_HASBUTTONS TVS_SHOWSELALWAYS TVS DISABLEDRAGDROP Opis działania Rysuje linie między elementami potomnymi i ich rodzicami Rysuje linie między bazowym elementem całej hierarchii a jego elementami potomnymi Rysuje przyciski pozwalające na zwijanie i rozwijanie fragmentów hierarchii Wybrany element pozostaje wybrany, nawet gdy inne okno stanie się oknem aktywnym Wyłącza możliwość przeciągania elementów w hierarchii Większość aplikacji korzystających z widoku Tree korzysta z kilku z wymienionych tutaj stylów, aby utworzyć interfejs podobny do tego, który oglądać możemy w Eksploratorze Windows. Tak samo jak w widoku List, style te mogą być zmieniane poprzez pobranie bieżących ustawień stylów za pomocą funkcji GetWindowLong (), zmodyfikowanie odpowiednich bitów i zwrócenie ich oknu za pomocą funkcji SetWindowLongO. Na listingu 19.7 w liniach 56-63 ustawione zostały znaczniki TVS_HASLINES + TVS_HAS-BUTTONS + TVS_LINESATROOT odpowiedzialne odpowiednio: za rysowanie linii do elementów potomnych, wyświetlanie przycisków rozwijających gałęzie i rysowanie linii do głównego elementu hierarchii (ang. root item). Podświetlanie rzędu kontrolki Tree Standardowo element drzewa jest podświetlany, gdy zostanie wybrany. Cały rząd włącznie z elementami graficznymi można podświetlić ustawiając znacznik TVS_ FULLROW-SELECT. Styl ten nie może występować równocześnie ze stylem TVS_ HASLINES. Widoki List,Tree, Rich Edit i HTML 495 Łączenie elementu drzewa z danymi przechowywanymi w aplikacji Czasami wygodnie jest połączyć każdy z elementów drzewa ze wskaźnikiem do obiektu. Stużą do tego funkcje SetitemData ()',i GetItemData O. Dodawanie elementów do drzewa O widoku Tree najlepiej myśleć jako o liście zawierającej jako elementy dające się rozwijać podlisty. Do wydobywania z widoku Tree odpowiedniej kontrolki służy funkcja GetTreeCtrl (). Kiedy już wydobędziemy kontrolkę, możemy polecić funkcji insertl-tem() dodanie do drzewa nowego elementu. Funkcja lnsertltem() występuje w kilku odmianach wymagających przestania różnych parametrów, a w najprostszej wersji posiada trzy parametry. Te parametry to: dodawany łańcuch, identyfikator (ang. handle) rodzica nowego elementu i identyfikator elementu, po którym nowy element ma zostać dodany. Możemy przestać funkcji tylko jeden parametr, wtedy dwa pozostałe zostaną zdefiniowane domyślnie tak, aby nowy element został dodany pod elementem głównym i na końcu listy. Niemniej, takie dodawanie elementów jest raczej bezcelowe, jako że otrzymamy w ten sposób zwykłą listę. Funkcja lnsertltem() zwraca identyfikator dodanego elementu. Można go potem użyć w zależności od potrzeb albo jako identyfikator rodzica, albo jako identyfikator elementu, po którym nowy element ma zostać dodany. Rozpoczynając tworzenie drzewa należy pierwszemu elementowi zadeklarować rodzica za pomocą znacznika TVI_ROOT. Znacznik ten określa, że jego rodzicem będzie element główny. Dodając następny element możemy zdefiniować jako rodzica pierwszy dołączony element, w ten sposób drzewo uzyska nową gałąź. Trzeci parametr definiuje pozycję w gałęzi, w której dodany ma być nowy element. Domyślnie parametrowi temu przypisywana jest wartość TVI__LAST, polecająca dołączyć go na końcu listy. Można jednak przypisać mu identyfikator elementu listy, po którym chcemy dodać nowy element lub znacznik TVI_SORT polecający funkcji wstawienie elementu do listy w porządku alfabetycznym. Usuwanie elementów drzewa Do usuwania elementów drzewa służy funkcja składowa Deleteltem (). Funkcja ta pobiera identyfikator HTREEITEM z dodawanego elementu. Korzystając z wspomnianych znaczników można, tak jak pokazaliśmy na listingu 19.7, dodawać nowe elementy w różnych miejscach hierarchii widoku Tree. Funkcja Oninitialupdate () dodawana jest do programu przez kreator AppWizard w momencie 496___________________________________Poznaj Visual C++ 6 tworzenia projektu. Korzystając z niej można dodać odpowiednie elementy do drzewa tuż przed wyświetleniem widoku. Listing 19.7. LST20J7.CPP - dodawanie elementów do widoku Tree 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void CTreeWiew: :OnInitialUpdate () { CTreeView::OnInitialUpdate() ; // TODO: TreeView można zapełnić elementami / / z kontrolki Tree wzywając funkcję GetTreeCtrl () 11 ** Zadeklaruj skrót do kontrolki Tree CTreeCtrl& tree = GetTreeCtrlO; // ** Dodaj element na poziomie bazowym HTREEITEM hAnimals = tree.Insertitem("Animals"); // ** Dodaj podelement do elementu z poziomu bazowego HTREEITEM hVerts = tree.Insertitem("Yertibrates",hAnimals) ; // ** Dodaj pode Jemen ty do elementu hVerts tree.Insertitem("Whales",hVerts,TVI_SORT) ; tree.Insertitem("Dogs",hVerts,TVI_SORT) ; tree.Insertitem("Humans",hVerts,TVI_SORT) ; // ** Dodaj podelement do elementu z poziomu bazowego HTREEITEM hlnverts = // ** Dodaj podelementy do elementu hlnverts tree.Insertitem("Jellyfish",hlnverts,TVI_SORT) ; tree.Insertitem("Worms",hlnverts,TVI_SORT) ; tree.Insertltem("Snails",hlnverts,TVI SORT) ; // ** Dodaj element na poziomie bazowym po hAnimals HTREEITEM hPlants = tree.Insertitem("Plants",TVI_ROOT,hAnimals) ; // ** Dodaj podelement do elementu z poziomu bazowego HTREEITEM hFruit = tree.Insertitem("Fruit",hPlants) ; // ** Dodaj podelementy do elementu hFruit Widoki List,Tree, Rich Edit i HTML 497 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 ) tree.Insertitem("Apples",hFruit,TVI__SORT) ; tree.Insertitem("Plums",hFruit,TVI_SORT); tree.Insertitem("Pears",hFruit,TVI_SORT) ; // ** Dodaj poćtelement do elementu z poziomu bazowego HTREEITEM hCereal = tree.Insertitem("Cereal",hPlants); // ** Dodaj podelementy do elementu hCreal tree.InsertItem("Wheat",hCereal,TVI_SORT) ; tree.Insertitem("Rye",hCereal,TVI_SORT) ; tree.Insertitem("Rice",hCereal,TVI_SORT) ; // ** Pobierz bieżący znacznik stylu DWORD dwStyle = GetWindowLong(GetTreeCtrl().GetSafeHwndO, GWL_STYLE) ; // ** Dodaj własne style O dwStyle |= TVS_HASLINES + TVS_HASBUTTONS + TVS_LINESATROOT; // ** Zwróć je z powrotem do widoku Tree SetWindowLong(GetTreeCtrK).GetSafeHwndO, GWL_STYLE,dwStyle) ; // ** Odmaluj widok SetRedraw(TRUE); ® Ustawianie znaczników styli. W linii 9 listingu 19.7 deklarowany jest obiekt tree będący odwołaniem do klasy CTreeCtris. W ten sposób tworzymy skrót, który oszczędza nam wzywania funkcji Get- TreeCtrl () za każdym razem, gdy musimy sięgnąć do osadzonej wewnątrz widoku kon- trolkiTree. Dla pierwszego elementu dodawanego w linii 12 musimy zdefiniować tylko jeden pa- rametr, łańcuch „Animals" (zwierzęta). Drugi parametr zdefiniowany jest domyślnie jako TVI_ROOT. Trzeci parametr również domyślnie jest definiowany jako TVI_AFTER, jednak ponieważ jest to pierwszy element, kolejność nie ma tutaj znaczenia. 498 Poznaj Visual C++ 6 W linii 16 dodawany jest element „Vertibrates" (kręgowce) definiujący jako swojego rodzica hAnimals, w ten sposób staje się elementem potomnym (ang. child item) elementu „Animals". Zwierzęta będące kręgowcami są następnie dodawane w liniach 19-21. Warto zauważyć, że podają one jako identyfikator rodzica hverts i korzystają ze znacznika TVI_SORT, aby ułożyć się w porządku alfabetycznym. W liniach 23-52 dodawane są w podobny sposób pozostałe elementy drzewa. W ten sposób powstanie hierarchiczny, złożony z kilku gałęzi widok Tree. Odpowiednie znaczniki styli drzewa definiowane są w liniach 56-66. Po zbudowaniu i uruchomieniu aplikacji pojawi się okno z widokiem Tree, takim jak na rysunku 19.5. Żeby zobaczyć wszystkie dołączone elementy, należy rozwinąć gałęzie za pomocą przycisków. fr» fcaf., „ns* -aap - ,1- !' ' , .• ./ • !- ., . ^'"i^Mftói''11 ' •11 : ^"''^ :: 1! .-. Wlir- a^ '-, V»rtbw«i>« : : ,-Ooyf li i : :• Hwnans :: i ' •••Whale» \ | B Im/ertibrates I: '• i-Jell^tsh : ' !- Snails : ^ '-Womu : S Plants l SFruit : i \ Apples : : i-Pears i • - Rums ' B-Cereal i '••• Rice 1 j.-Rye . Wheat : Rysunek 19.5. Widok Tree z rozwiniętymi gałęziami Odnajdywanie wybranego węzła drzewa Wybrany węzeł drzewa odnaleźć można za pomocą funkcji składowej kontrolki Get- SelectedltemO zwracającej identyfikator HTREEITEM wybranego przez użytkownika elementu. Można również skorzystać z funkcji GetNextltem(), która działa tak samo jak analogiczna funkcja GetNextitem() widoku List. Wersja funkcji dostępna w widoku Tree ma dwa parametry - pierwszym jest identyfikator HTREEITEM, a drugim znacznik definiujący kategorię elementów, które mają być poszukiwane. Możliwe wartości tego znacznika podane zostały w tabeli 19.4. Funkcja GetNextitem() zwraca elementy należące do poszukiwanej kategorii lub jeśli nie odnajdzie żadnego elementu, wartość NULL. Widoki ListJree, Rich Edit i HTML____________________________499 Tabela 19.4. Znaczniki funkcji GetNextltem () widoku Tree Znacznik Opis efektu TVGN_CARET Znajduje elementy wybrane przez użytkownika TVGN_ROOT Zwraca element poziomu bazowego odpowiadający danemu elementowi TVGN_PAREMT Zwraca rodzica danego elementu TVGN_CHILD Zwraca pierwszy element potomny; bieżącemu elementowi trzeba przypisać wartość NULL TVGN_NEXT Znajduje poprzedni element potomny TVGNJ?REVIOUS Znajduje następny element potomny TVGN_PREVIOUS_VI s IBLE Zwraca poprzedni widoczny element TVGN_FIRST_VXSIBLE Znajduje następny widoczny element Przywołując funkcję SelectItemO i podając identyfikator HTREE l TEM elementu, który chcemy wybrać, możemy wybrać element z poziomu programu. Zdobywszy odpowiedni identyfikator HTREEITEM elementu za pomocą funkcji GetSelectitemO lub GetNextltem () można korzystając z funkcji GetltemText () zdobyć tekst wybranego elementu. Na listingu 19.8 przedstawiona jest funkcja ustalająca, który element drzewa został wybrany przez użytkownika i pobierająca tekst tego elementu. Podobnie jak w widoku List, funkcja obsługi OnSelchanged () wykorzystywana jest do przechwytywania odbitego komunikatu powiadamiającego (ang. reflected nofification message) TVN_SELCHANGED w klasie widoku. Funkcję tę można dodać do programu z karty CIassWizard Message Maps. Listing 19.8. LST20_8.CPP - pobieranie tekstu z elementu wybranego przez użytkownika 1 void CTreeWiew: : OnSelchanged (NMHDR* pNMHDR, LRESULT* pResuit) 2 ( 3 NM_TREEVIEW* pNMTreeYiew = (NM_TREEVIEW*)pNMHDR; 4 5 // ** Znajdź identyfikator wybranego elementu 6 HTREEITEM hSelected = 7 GetTreeCtrl().GetSelectedItem() ; 8 9 // ** Sprawdź, czy identyfikator został znaleziony 10 if (hSelected >= 0) 11 ( 12 // ** Pobierz tekst wybranego elementu 13 CString strSelected = 500_____________________________________Poznaj Visual C++ 6 14 GetTreeCtrl().GetItemText(hSelected) ; 15 16 // ** Przypisz ten tekst tytułowi dokumentu 17 GetDocument()->SetTitle(strSelected) ; 18 } 19 20 *pResult = 0; 21 } W linii 6 listingu 19.8 wykorzystujemy funkcję GetSelectedItemO, by uzyskać identyfikator wybranego przez użytkownika elementu. Następnie w linii 9 upewniamy się, że w ogóle jakiś element został wybrany, czyli czy udało nam się pobrać identyfikator. Jeśli tak, w linii 14 za pomocą funkcji GetItemText () pobieramy w linii 14 tekst wybranego elementu i w linii 17 przypisujemy go tytułowi dokumentu. Po uruchomieniu programu, do którego została załączona przedstawiona tu funkcja, zobaczymy okno widoku Tree, w którym na pasku tytułowym pojawiać się będzie nazwa wybranego elementu. Ręczne edytowanie elementów drzewa Niektóre aplikacje pozwalają na modyfikowanie tekstu elementów widoku Tree poprzez kliknięcie wybranego elementu i ręczne wpisanie nowego tekstu. Operację taką możemy nazwać ręcznym edytowaniem (ang. iniine editing). Obsługę ręcznego edytowania można bardzo łatwo zaimplementować w programie korzystając ze znacznika stylu TVS_EDITLABELS i obsługując kilka komunikatów powiadamiających (ang. notification messages). Znaczniki tych komunikatów to TVS_BEGINLABELEDIT i TVN_ENDLABELEDIT. Za pomocą kreatora CIassWizard można im przypisać odpowiednie funkcje obsługi OnBe-ginLabeledit() i OnEndLabeledit(). Ręczne edytowanie w widoku List Aby wprowadzić możliwość ręcznego edytowania do widoku List. należy skorzystać Ze Stylu LVS_EDITLABELS. Gdy dodamy już do widoku Tree styl TVS_EDITLABELS, użytkownik będzie mógł uruchomić ręczne edytowanie tekstu elementu po prostu klikając odpowiedni element drzewa. Następnie wzywana jest funkcja obsługi OnBeginLabeledit (). Wewnątrz funkcji obsługi wzywana jest funkcja GetEditControl (), która umożliwia modyfikowanie dynamicznie tworzonej kontrolki Edit dodawanej do drzewa, aby możliwa była ręczna edycja elementów. Uzyskany w ten sposób wskaźnik kontrolki można wykorzystać na przykład do zmieniania koloru czy ograniczania długości wprowadzanego tekstu. Widoki List,Tree, Rich Edit i HTML 501 Po zmianie tekstu elementu aby zakończyć edytowanie, należy wcisnąć Enter lub kliknąć klawiszem myszy poza obszarem kontrolki Edit. W tym momencie przyzywana jest funkcja obsługi OnEnabledit (). Teraz za pomocą funkcji GetEditControl () ponownie sięgamy do kontrolki Edit, tym razem zwracając zmodyfikowany tekst. W tym momencie można zatwierdzić zmieniony tekst i wysłać go z powrotem do drzewa. Jeśli w tym momencie tekst nie zostanie odesłany do drzewa, wprowadzane zmiany zostaną utracone i element powróci do stanu, w jakim był przed rozpoczęciem edycji. Do odsyłania tekstu elementu do drzewa służy funkcja SetltemText (). Styl TVS_EDITLABELS można dodać do bieżących styli drzewa w następujący sposób: // ** Dodaj listę stylów dwStyle |= TVS_HASLINES + TVS_HASBUTTONS + TVS_LINESATROOT + TYSJEDITLABELS; // ** Dodaj możliwość edytowania etykiety Funkcje obsługi OnBeginLabeledit () i OnEndLabeledit () pokazane zostały na listingu 19.9. Uruchomienie ręcznej edycji,' • . •" ' ' •; . • Uruchomienie edycji dementu.jest odrobinęskomplikowane. Należy kUknąć element drzewa dwa razy, jednak z krótką pauzą między kolejnymi tdiknię.cianzi. . Po odpowiednim zdefiniowaniu stylu, dodaniu przedstawionych tutaj funkcji obsługi, zbudowaniu i uruchomieniu aplikacji otrzymamy widok Tree, w którym po dwukrotnym kuknięciu można będzie edytować tekst dowolnego elementu, tak jak to pokazano na rysunku 19.6. Listing 19.9. LST20_9.CPP - funkcje OnBeginLabeledit () i OnEndLabeledit () umożli- wiające ręczną edycję elementów 1 void CTreeWiew: :OnBeginlabeledit (NMHDR* pNMHDR, 2 LRESULT* pResult) 3 { 4 TV_DISPINFO* pTVDisp!nfo = (TV_DISPINFO*)pNMHDR; 5 // TODO: Tu dodaj własną, funkcję obsługi komunikatów 6 7 GetTreeCtrl() .GetEditControl()->LimitText(20); O 8 9 *pResult = 0; 10 } 11 12 void CTreeWiew: :OnEndlabeledit (NMHDR* pNMHDR, LRESULT* pResult) 13 ( 14 TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR; 15 // TODO: Tu dodaj własną funkcję obsługi komunikatów 16 17 // ** Pobierz zmodyfikowany tekst z kontrolki Edit 18 CString strText; 19 GetTreeCtrl().GetEditControl()-> 20 GetWindowText(strText); 21 22 // ** Tutaj można sprawdzić i zatwierdzić tekst kontrolki 23 24 // ** Sprawdź, czy łańcuch nie jest pusty 25 if (strText.GetLength()>0) 26 { 27 // ** Pobierz identyfikator wybranego elementu 28 HTREEITEM hSelected = pTVDispInfo->item.hitem; 29 30 // ** Odeślij zmodyfikowany tekst @ 31 GetTreeCtrl().SetItemText(hSelected,strText) ; 32 '} 33 34 *pResult = 0; 35 } ® Ograniczanie długości teksiu edytowanego w kontrolce Edit. @ Zwracanie zmodyfikowanego tekstu z powrotem do elementu drzewa. Kod funkcji obsługi OnBeginLabeleditO jest dość prosty. Jedyny fragment kodu nie dodany przez kreatora CIassWizard znajduje się w linii 7, gdzie rozmiar tekstu przyj- mowanego przez kontrolkę Edit ograniczany jest do 20 znaków. Funkcja OnEndLabeleditO jest bardziej skomplikowana. W liniach 19 i 20 tekst elementu pobierany jest z kontrolki Edit. Jeśli długość łańcucha jest różna od zera (co sprawdzamy w linii 25), z przesłanego funkcji wskaźnika struktury TV_DISPINFO pobierany jest identyfikator edytowanego elementu. Identyfikator ten jest następnie w linii 31 wykorzystywany do odsyłania zmodyfikowanego tekstu do wybranego elementu. Widok Rich Edit Widok Rich Edit pozwala przy stosunkowo małym wysiłku włożonym w programowanie aplikacji utworzyć zaawansowany i dający się dostosowywać do naszych potrzeb interfejs użytkownika. Widok ten jest całkowicie OLE-aktywny (ang. OLE-enabled), co Widoki ListJree, Rich Edit i HTML 503 oznacza, że możemy swobodnie dołączać do tekstu rysunki, filmy oraz efekty dźwiękowe. Jest to dość obszerny temat, dlatego w tym miejscu tylko krótko go zarysujemy. A/Wi.3f„ •Płonił eFiuB . AppłBianilOrangit :. -Psa?— ? • jpiums im B-Celem - Ric» . Rye •Mnal Rysunek 19.6. Ręczne edytowanie w widoku Tree Tworzenie widoku Rich Edit Aplikację SDI bazującą na widoku Rich Edit tworzy się za pomocą kreatora AppWi-zard w ten sam sposób jak aplikacje SDI bazujące na widokach Tree i Edit. Trzeba tylko na ostatniej karcie kreatora wybrać w kombinowanej liście widoków klasę CRichEditView. Utworzymy teraz przykładowy projekt RichV demonstrujący możliwości widoku Rich Edit. Kiedy kukniemy przycisk Finish, pojawi się okno pytające, czy chcemy dołączyć do aplikacji obsługę kontenera OLE (ang. OLE container). W tym miejscu należy kliknąć OK, ponieważ widok Rich Edit działa jak kontener OLE. Sprawę można yałatwić wcześniej wybierając na trzeciej stronie kreatora AppWizard, pod pytaniem What Compound Document Support Would You Like to Include?, opcję fiontainer. Widok Edit • , , • .. • , .: , - . -' , - ^\ : Istnieje również prostszy służący do edycji tekstu widok Edit, bazujący na wielo-wierszowej kontrolce Edit. Nie ma on lych możliwości edycyjnych i nie obsługuje technologii OLE tAjak widok Rich Edit, może się jednak przydać, jeśli potrzebujemy stosunkowo prostego i łatwego do zaprogramowania edytora. Ten widok jest reprezentowany przez klasę CEdifcView, którą definiujemy na ostatniej stronie kreatora AppWizard./• ',,;, - , ^ 1, „ 1"':' ^'-^ ^•.;1 ''\„;:-;^,-,:1 ':;;''';../ ' 1 504___________________________________Poznaj Visual C++ 6 Po wciśnięciu przycisku OK wystarczy zbudować i uruchomić aplikację. Otrzymamy w ten sposób zaawansowany edytor tekstu, bez konieczności żadnego dodatkowego pro- gramowania. PATRZ TAKŻE • Kontenery OLE opisane zostały dokładnie w rozdziale 25. Zachowywanie i ładowanie tekstu Jeśli w utworzonej aplikacji wpiszemy do widoku Rich Edit tekst, możemy zachować go klikając w menu File polecenie Save i wpisując nazwę pliku. Po zamknięciu i ponownym uruchomieniu aplikacji zachowany tekst będzie można odtworzyć klikając w menu File polecenie Open, następnie wybierając nazwę zachowanego pliku i klikając OK. Jak widać, edytor obsługuje zachowywanie i ładowanie plików bez żadnego wkładu z naszej strony. Podobnie drukowanie i podgląd wydruku implementowane są do aplikacji automatycznie. Dzieje się tak dlatego, że do programu załączone zostały funkcje plikowe kontenera OLE. Gdy widok Rich Edit jest aktywny związane z nim odpowiednie opcje obsługi plików są uaktywniane i automatycznie implementowane do programu. PATRZ TAKŻE • Więcej informacji na temat ładowania plików znaleźć można w rozdziale. 23. • Przegląd kontenerów OLE znaleźć można w rozdziale25. FornialoYi.ynif akapiió','.' tckylis Warto zauważyć, że wśród załączonych opcji me ma obstugi formatowania akapitów tekstu. Widok Rich Edit dostarcza licznych bardziej podstawowych funkcji edytora tekstu, ale pozostawia programiście możliwość dostosowywała ik- w^isnych pon/A b;«-dziej zaawansowanych opcji interfejsu użytkownika. Możemy oczywiście dodać odpowiednie elementy mscriejsu pozwalające iomiatować wybrane akapity tekstu. Do paska narzędziowego dodaliśmy trzy nowe przyciski pozwalające formatować do prawej, do lewej lub wycentrowywać tekst akapitu. Przyciski te dodajemy do paska narzędziowego, tak jak to opisaliśmy w instrukcji w rozdziale 14. Można zdefiniować własną oprawę graficzną tych trzech dołączanych przycisków. Moje niezdarne wysiłki w tym kierunku obejrzeć można na pasku narzędziowym widocznym na rysunku 19.7. Mój bardziej uzdolniony artystycznie przyjaciel stworzył wspaniałą mapę bitową (.bmp), która dołączona do tekstu wewnątrz widoku Rich Edit pokazuje możliwości edycyjne utworzonego w ten sposób edytora. Rysunki można dołączać do tekstu za pomocą polecenia Insert New Object znajdującego się w menu Edit. Widoki ListJree, Rich Edit i HTML 505 This Image was Inserted trom the Edlt menu by selecting the Insert New Object menu option. The powerful Rich Edlt View In ActlonI Rysunek 19.7. Widok Rich Edit w czasie pracy Po dodaniu ikon przycisków należy każdemu z nich przypisać odpowiedni identyfikator definiujący ich funkcję: ID_ALIGNLEFT, ID_ALIGNCENTER i ID_ALIGNRIGHT. Teraz za pomocą kreatora CIassWizard dodajemy funkcje obsługi poleceń przycisków: OnAlign-Left(), OnAlignCenter () i OnAlignRight (). Funkcje te należy utworzyć w klasie CRichWiew. Kod funkcji formatujących akapity tekstu jest banalnie prosty (listing 19.10). Widok Rich Edit korzysta ze struktury PARAFORMAT, aby zdefiniować kilka operacji formatowania. Zmienna składowa dwMask będąca maską dla znaczników operacji jest w linii 6 wykorzystywana do definiowania, która z nich ma być wykonana. Dostępne operacje podane zostały w tabeli 19.5. Większość z nich związana jest z definiowaniem odstępów. Maski znaczników mogą reprezentować każdą ze zmiennych składowych struktury PARAFORMAT, która ma zastosowanie dla danego akapitu. W ten sposób możemy definiować zmienne pojedynczo (jak w tym przykładzie) lub kilka naraz, dodając odpowiednie znaczniki przy- pisywane masce. 506___________________________________Poznaj Visual C++ 6 Tabela 19.5. Wartości masek zmiennej składowej dwMask struktury PARAFORMAT Ustawienie znacznika Wykonywana operacja maski PFM_ALIGNMENT Akapit tekstu jest justowany odpowiednio w zależności od znacznika zapisanego w zmiennej wAlignment PFM_NUMBERING Akapit tekstu jest numerowany wartością zapisaną w zmiennej wNumbering PFM_TABSTOPS Za pomocą cTabStop i tablicy rgxTabs można zdefiniować rozkład tabulatorów PFM_OFFSET Zmienna dx0ffset definiuje szerokość odstępu dla akapitu PFM_OFFSETINDENT Zmienna dx3tart0ffset definiuje szerokość odstępu w czasie formatowania PFM_RIGHTINDENT Zmienna dxRight!ndent definiuje odstęp względem prawego marginesu PFM_STARTINDENT Zmienna dxStartlndex definiuje szerokość dodawaną do każdego zmienianego akapitu Listing 19.10. LST20_10.CPP - formatowanie akapitów tekstu w widoku Rich Edit 1 void CRichWiew: :OnAiigncenter () 2 { 3 // TODO: Tutaj dodaj własną funkcję obsługi polecenia 4 5 PARAFORMAT pf; 6 pf.dwMask = PFM_ALIGNMENT; 7 pf.wAiignment = PFA_CESTER; @ 8 SetParaFormat(pf) ; 9 } 10 11 void CRichWiew: :OnAlignlef t () 12 ( 13 // TODO: Tutaj dodaj własną funkcję obsługi polecenia 14 15 PARAFORMAT pf; 16 pf.dwMask = PFM_ALIGNMENT; 17 pf.wAiignment = PFA__LEFT; @ ( 18 SetParaFormat(pf) ; 19 } 20 21 void CRichWiew: :OnAlignright () 22 ( Widoki List.Tree, Rich Edit i HTML 507 23 // TODO: Tutaj dodaj własna funkcję obsługi polecenia 24 25 PARAFORMAT p f; 26 pf.dwMask = PFM_ALIGNMENT; 27 pf.wAlignment = PFA_RIGHT; @ 28 SetParaFormat(pf) ; 29 } O Przypisywanie zmiennej znacznika centrowania. @ Przypisywanie zmiennej znacznika justowania do lewej. © Przypisywanie zmiennej znacznika justowania do prawej. W liniach 6 i 7 definiowany jest tylko znacznik PFM_ALIGNMENT i zmienna składowa struktury wAlignment. Następnie struktura jest przesyłana do funkcji składowej SetParaFormat (), która formatuje akapit w zależności od wartości zapisanych w strukturze (linia 8). Funkcje'OnAlignLef t () i OnAlignRight () są prawie identyczne, z tą tylko różnicą, że znacznik PFA_CENTER jest zastępowany odpowiednio znacznikami PFA_LEFT i PFA_RIGHT. Po dodaniu tych funkcji, zbudowaniu i uruchomieniu programu będziemy mogli ju-stować akapity tekstu klikając odpowiednie przyciski paska narzędzi. Dodawanie obiektów OLE Ponieważ widok Rich Edit obsługuje kontener OLE, możemy dodawać do tekstu rysunki i efekty medialne, korzystając ze standardowych opcji menu OLE. Aby to przetestować, wystarczy kliknąć menu Edit i wybrać opcję Insert New Object. Pojawi się wtedy standardowe okno dialogowe Insert Object. W oknie będzie dostępna szeroka gama różnych typów OLE. Typy te można swobodnie załączać do widoku Rich Edit. PATRZ TAKŻE • Informacje na temat technik OLE i COM można znaleźć w rozdziale 25. HTML - widok do budowania przeglądarki Podobnie jak widok Rich Edit, widok HTML jest bardzo rozbudowanym widokiem pozwalającym udostępnić tworzonej przez nas aplikacji pełnię możliwości przeglądarki sieciowej Microsoft Intemet Explorer 4. Tworząc ten widok należy zaprogramować od- powiednie elementy interfejsu i połączyć je z przeglądarką - resztę pracy wykona za nas 508___________________________________Poznaj Visual C++ 5 kompilator. Tak samo jak poprzednio, temat ten jest dość rozległy i tutaj tylko opiszemy go pobieżnie. Co oznacza skrót HTML? HTML jest skrótem nazwy języka Hypertext Markup Laneuage. Język ten jest standardowym językiem wykorzystywanym do prezentowania zawartości stron interne-towych. Język ten wykorzystuje do definiowania informacji dotyczących formatu znaczniki (ang. tags), a ponadto pozwala na stosowanie grafiki i tabulacji. Tworzenie widoku HTML Aplikację opartą na widoku HTML inożn;.i utworzyć ::i\ pomoc;' kreato':;'- AppWiz-ird, wybierając na ostatniej stronie jako klasę bazową (B^se ciass) klasę CHtmlView. Kreator AppWizard utworzy standardowy szkielet aplikacji, w którym główna klasa widoku wy- wiedziona będzie z klasy CHtmlView. Tutaj pokażemy jak korzystać z różnych możliwości i funkcji oferowanych przez widok HTML. Co to jest URL? URL (Universal Resource Locator) może być adresem strony internetowej na przykład hktp: .//chaos l .demon.co.uk. Może być również lokalną stroną internetową utworzoną w naszym programie, plikiem tekstowym, archiwum FTP lub dowolnym innym akceptowanym przez przeglądarkę zasobem. : Definiowanie adresu URL Aby rozpocząć przeglądanie, należy wezwać funkcję Navigafce2 () klasy CHtmlView przesyłając jej odpowiedni URL. Przeglądarka znajdzie teraz, jeśli to tylko możliwe, poszukiwaną stronę i wyświetli ją wewnątrz widoku. Poprzez zdefiniowany przez nas pasek narzędzi można wzywać różne funkcje usprawniające działanie przeglądarki: GoFoward (), GoBack (), GoSearch (), GoHo- me () czy Refresh (). Funkcje te wykonują te same czynności co odpowiednie przyciski w większości przeglądarek. Funkcja ExecWB () umożliwia korzystanie z różnych poleceń OLE (część z nich została przedstawiona w tablicy 19.6), pozwalających zmieniać rozmiar czcionki, czy drukować bieżącą stronę. Funkcja ta korzysta z kilkunastu znaczników poleceń i zmiennej typu CO!eVariant, w której przesyłany jest odpowiedni parametr. Widoki List.Tree, Rich Edit i HTML_____________________________509 Przykładowo, by zmienić rozmiar czcionki, trzeba odwołać się do niej w następujący sposób: C01eVariant vaFont3ize(9); ExecWB(OLECMDID_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, & vaFontSize, NULL) ; Typ C01eVariant jest typem definiującym obiekt, który może przechowywać w jednej zmiennej zarówno łańcuchy, liczby całkowite, daty, jak i liczby zmiennoprzecinkowe. Obiekty takie są dość często wykorzystywane w COM i OLE do przesyłania do wspólnych funkcji danych niezdefiniowanego typu. Funkcji ExecWB () można przesłać różnorodne polecenia umożliwiające wykonanie dowolnej funkcji przeglądarki. Tabela 19.6. Znaczniki poleceń OLE dla funkcji ExecWB () Znacznik polecenia OLE Opis OLECMDID_SAVEAS Zachowaj bieżącą stronę OLECMDID_PRINT Wydrukuj bieżącą stronę OLECMDID_ZOOM Zdefiniuj wartość powiększenia OLECMDID_FIND Znajdź tekst bieżącej strony OLECMDID_STOP Przerwij sprowadzanie strony OLECMDI D_COPY Kopiuj stronę do schowka OLECMDID_ENABLE_INTERACTION Włącz lub wyłącz interakcję z użytkownikiem Obsługa zdarzeń przeglądarki Prawdopodobnie w tworzonej aplikacji pojawi się potrzeba zareagowania na różne zdarzenia przeglądarki, takie jak kliknięcie przez użytkownika hipertącza. Zdarzenia obsługujemy pokrywając odpowiednie funkcje wirtualne. Funkcji OnBeforeNavigate2 () przesyłany jest łańcuch z adresem URL. Można tutaj sprawdzić legalność łańcucha lub wykorzystać funkcję do uruchomienia animacji (takiej jak obracająca się kula ziemska w Intemet Explorerze). Prototyp tej funkcji (ang. prototype) wygląda tak: void OnBeforeNavigate2(LPCTSTR IpszURL, DWORD nFlags, LPCTSTR IpszTargetFrameName, CByteArrayS baPostedData, LPCTSTR IpszHeaders, BOOL* pbCancel); Inaczej niż w przypadku innych wirtualnych funkcji, pokrywanie tej funkcji nie wymaga odwołania do klasy bazowej. Przyzywając tę funkcje można przerwać nawigowanie przypisując zmiennej *pbCancel wartość True. Odpowiednie zdarzenie kończące nawigację to OnNawigateComplete2 (), którego prototyp wygląda tak: 510___________________________________Poznaj Visual C-H- 6 virtual void OnNavigateComplete2(LPCTSTR strURL) ; Jeśli w funkcji OnBeforeNavigate () uruchomiliśmy animacje lub inny proces, w tym właśnie miejscu możemy go przerwać. Tekst informacji o statusie normalnie wyświetlany na dole okna przeglądarki można pobrać za pomocą funkcji OnStatusTextchanged (), która jest definiowana tak jak poniżej: virtual void OnStatusTextChange(LPCTSTR lpszText); Parametr lpszText przechowuje wskaźnik do tekstu nowej informacji o statusie. Funkcja OnProgressChange () przywoływana jest przy kolejnych postępach ładowania strony. Funkcja wirtualna jest zdefiniowana w następujący sposób: virtual void OnProgressChange(long nProgress, long nProgressMax) ; Zmienna nProgress określa jaka część operacji ładowania (nProgressMax) została wykonana do tej pory. Rozmiar ładowanego obiektu (nProgressMax) można zdefiniować za pomocą funkcji ExecWB (), przesyłając razem z definiowanym rozmiarem znacznik OLECMDID_SETPROGRESSMAX. Funkcje OnDownIoadBeginf) i OnDownIoadComplete () są przyzywane, kiedy odpo- wiednio rozpoczyna się lub kończy proces ściągania pliku. Można wykorzystać je do wyświe- tlania odpowiednich komunikatów ostrzegawczych lub zawieszenia procesu ściągania pliku. Inne dostępne w widoku pokrywalne funkcje obsługi zdarzeń powiadamiających po- zwalają na dołączenie do przeglądarki możliwości przechwytywania lub sprawdzania legalności poleceń, zanim zostaną wykonane. Pozwalają również ustalić, kiedy polecenia zostaną wykonane. Niestety jest to temat zbyt obszerny, by go tutaj omawiać i wspomnieliśmy o nim tylko po to, żeby pokazać możliwości klasy CHtmlview. Rozdział 20 Tworzenie okien z kilkoma widokami Statyczne i dynamiczne okna dzielone Tworzenie interfejsu aplikacji naśladującego Eksplorator Windows Tworzenie i obsługa aplikacji z kilkoma widokami bez korzystania z okien dzielonych Jak działa okno z kilkoma widokami? Architektura dokument/widok ma kilka istotnych zalet. Najważniejszą z nich są stan- dardowe funkcje wbudowane w klasy MFC, znacznie ograniczające ilość pracy, którą programista musi wykonać przy tworzeniu aplikacji. Tak jak to mówiliśmy we wcześniejszych rozdziałach, klasy te obsługują tworzenie i działanie dokumentów, dokowanie pasków narzędziowych, wyświetlanie paska narzędziowego i wiele innych rzeczy. Dodatkowo oddzielenie kodu obsługującego dane (dokument) od kodu obsługującego interfejs użytkownika (widok) czyni program bardzo plastycznym, pozwalając bez trudu dostosowywać jego strukturę do naszych potrzeb. Dzięki temu można na przykład wyświetlać te same dane w kilku widokach. W ten sposób można na przykład wyświetlać dwie różne sekcje tego samego dokumentu lub schematu lub dane liczbowe w jednym widoku i oparty na nich wykres w drugim. Nie jesteśmy przy tym ograniczeni do dwóch widoków i pojedynczemu dokumentowi możemy przypisać dowolną liczbę widoków. Okna z kilkoma widokami można utworzyć na kilka sposobów. Możemy w tym celu wykorzystać okno dzielone (ang. splitter window) dzielące okno obramowujące na kilka paneli, z których każdy zawiera osobny widok. Wiele aplikacji korzysta z tego sposobu. Eksplorator Windows korzysta z okna dzielonego, dzieląc okno obramowujące na dwie części: drzewo dysków i folderów po lewej stronie i listę plików po prawej. Niektóre typy kontrolek z zakładkami również mogą być wykorzystywane do dzielenia okna na części umożliwiając przełączanie się między poszczególnymi kartami. Z tego sposobu korzysta Excel. 512 Poznaj Visual C++ 6 Korzystanie z okna dzielonego Wykorzystanie okien dzielonych (ang. splitter window) jest jedną z najpowszechniejszych metod prezentowania kilku widoków wewnątrz jednego okna obramowującego. Okno dzielone osadzone jest w oknie obramowującym i wykorzystuje się je do tworzenia oddzielnych paneli. Każdy panel zawiera własny widok. Widoki te mogą być tej samej klasy lub też zupełnie różnych klas. Wewnątrz aplikacji MDI (wielodokumentowej) każdy widok umieszczony jest w osobnym oknie obramowującym; w tym przypadku okna dzielone mogą być implementowane osobno w każdym oknie potomnym. Okno obramowujące może być dzielone zarówno w pionie, jak i w poziomie, także liczba paneli nie jest niczym ograniczona. Użytkownik może dowolnie zmieniać wymiary paneli przeciągając pasek dzielący (ang. splitter bar) za pomocą myszy. Istnieją dwa typy okien dzielonych: statyczne i dynamiczne. Klasa csplitterWnd pozwala tworzyć oba rodzaje. Tworzenie dynamicznych okien dzielonych Okno obramowujące z dynamicznym oknem dzielonym wyświetla dodatkowe panele dopiero wtedy, gdy użytkownik wybierze w pasku narzędziowym okna pole dzielące (ang. splitter box). Początkowo cały obszar roboczy (ang. client area) jest zajęty przez pierwszy widok (lewy górny panel). Po przeciągnięciu pola dzielącego w obręb tego widoku pojawia się pasek dzielący i okno obramowujące dzielone jest na panele, z których każdy zawiera osobny widok. Podzielenie okna obramowującego powoduje, że konstruowane są niezbędne, wypełniające panele widoki. Panele mogą zostać usunięte poprzez przeciągnięcie paska dzielącego do jednego z końców paska przewijania. Po usunięciu panelu, odpowiedni obiekt widoku jest niszczony. Panele dynamicznego okna dzielonego są zazwyczaj wykorzystywane do przedstawiania dwóch różnych obszarów tego samego widoku i dlatego zazwyczaj przypisana jest im ta sama klasa widoku. Przykładem może być okno edytora tekstu dostępne w Developer Studio, które zawierać może okna dzielone zarówno w pionie, jak i w poziomie. Są trzy podstawowe sposoby dodawania do aplikacji dynamicznych okien dzielonych. Jeśli chcemy od razu dołączyć okna dzielone do aplikacji, możemy je wprowadzić za pomocą kreatora AppWizard. Aby natomiast dołączyć dynamiczne okna dzielone do istniejącej już aplikacji można albo napisać odpowiedni kod ręcznie, albo dołączyć komponent Splitter Bar dostępny w Components and Controls Gallery. Najprościej jest oczywiście skorzystać z kreatora AppWizard. Rozwiązanie to ma tę przewagę, że w ten sposób automatycznie dodajemy do menu ,View opcję Sfilit uaktywniającą pole dzielące, które pozwala później użytkownikowi dowolnie regulować rozmiary dodawanych dynamicznie paneli widoków. Tworzenie okien z kilkoma widokami 513 AppWizard i polecenie Split Kiedy w kreatorze AppWizard wybierzemy opcję Use Split Window, do menu Window aplikacji dodawane jest polecenie Split (Podziel). Jeśli natomiast zamierzamy dodawać okna dzielone do już istniejącej aplikacji, identyfikator (J_D) tego polecenia będzie miał postać: ID_WINDOW_SPLIT. Teraz wykorzystamy kreator AppWizard do utworzenia nowego projektu DSplit. Na czwartej stronie kreatora klikamy przycisk Advanced i wybieramy kartę Window Styles. Następnie, tak jak pokazaliśmy na rysunku 20.1, zaznaczamy opcję Use Split Window. Wreszcie na stronie szóstej kreatora wybieramy jako klasę bazową widoku klasę CScrollview. D ocument T emplate S trings Windom S tyłeś |7 lOse split windom 1^ ;use splitwini •Mainframe styles 17 Ihickhame F? Mjnimize box F Mamase bo^ P' System menu T Mirwnized F Maymized r 'n r M: r •^ r w r M. Rysunek 20. l. Okno dialogowe Advanced Options kreatora AppWizard Aby dodać do gotowej aplikacji SDI lub MDI dynamiczne okna dzielone, należy wykonać czynności opisane w przedstawionym niżej algorytmie. Algorytm ten zamieszczony został tylko w celach poglądowych i nie jest częścią przykładu DSplit. Dodawanie do programu komponentu Splitter Bar 1. W kompilatorze Developer Studio otwórz projekt, do którego chcesz dołączyć dynamiczne paski dzielące. 2. W menu Project wybierz polecenie Add to Project, a w odpowiednim podmenu Comnpnents and Controls. Pojawi się okno dialogowe Components and Controls Gallery. 3. Kliknij dwukrotnie folder Visual C^ Components. 514 Poznaj Visual C++ 6 4. Tak jak to pokazano na rysunku 20.2, z listy komponentów wybierz Splitter Bar. Klikając w tym momencie przycisk Morę Info wyświetlisz dodatkowe informacje na temat dodawanego komponentu. 5. Kliknij przycisk Insert. W oknie dialogowym Insert the Splitter Bar Component kliknij OK. Pojawi się okno dialogowe Splitter Bar pozwalające wybrać odpowiednie opcje paska dzielącego (rysunek 20.3). 6. Wybierz pożądany typ paska dzielącego i kliknij OK. 7. Zamknij okno dialogowe Components and Controls Gallery. PATRZ TAKŻE • Algorytm opisujący tworzenie aplikacji SDI znaleźć można w rozdziale 12. • Szczegółowe informacje na temat klasy cscroliview znaleźć można w rozdziale 18. • Op/s galerii Components and Controls znaleźć można w rozdziale 9. Rysunek 20.2. Okno dialogowe Components and Controls Gallery iliKei Bal ^^^HHx] W sptttar bar to: OK o--- J łype of split bar: <" Honzonta! F yertical P- gotK Cance) Hełp Rysunek 20.3. Okno dialogowe opcji komponentu Splitter Bar Tworzenie okien z kilkoma widokami 515 Inicjalizacja dynamicznych okien dzielonych Niezależnie od tego, czy dodajemy dynamiczne okno dzielone do aplikacji za pomocą kreatora AppWizard, czy galerii Components and Controls, dołączony do programu kod będzie identyczny. Obiekt klasy csplitterWnd jest wbudowywany jako element składowy do klasy CMainFrame (w aplikacji MDI cchildFrame). Obiekt okna dzielonego pokrywa cały obszar roboczy okna obramowującego i zarządza tworzeniem i niszczeniem odpowiednich okien widoków. W przykładzie DSplit znajduje się następująca chroniona zmienna składowa klasy CMainFrame: CSplitterWnd m_wndSplitter; Okno dzielone jest tworzone w pokrywanej funkcji CMainFrame: :OnCreateClient, tak jak to pokazaliśmy na listingu 20.1. Funkcja ta jest przyzywana w procesie tworzenia okna wewnątrz funkcji CFrameWnd: :OnCreate. Domyślnie, wpisywany przez kompilator kod funkcji tworzy obiekt widoku dopasowując jego rozmiary tak, aby pokrył cały obszar roboczy okna obramowującego. Funkcja pokrywająca najpierw tworzy okno dzielone, a następnie inicjalizuje je tworząc obiekt View. Następne widoki tworzone będą wówczas, kiedy użytkownik zdecyduje się podzielić okno obramowujące. Listing 20.1. LST21_1.CPP - tworzenie dynamicznego okna dzielonego wewnątrz funkcji _________OnCreateClient_________________________________________ 1 BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/, 2 CCreateContext* pContext) 3 ( 4 return m wndSplitter.Create(this, 5 2,2, II TODO: Ustalamy liczbę kolumn i rzędów 6 CSize(10, 10), // TODO: Ustalamy minimalny rozmiar panelu 7 pContext) ; 8 } O Pokrywamy funkcję OnCreateClient, aby utworzyć okna dzielone. @ Tworzymy okno dzielone podając pożądaną liczbę rzędów i kolumn. Funkcji Create okna dzielonego (linia 4) można przypisać nawet do siedmiu parametrów. Pierwszy parametr this jest wskaźnikiem do okna rodzica. Drugi i trzeci definiują odpowiednio maksymalną liczbę rzędów i kolumn, na które można będzie podzielić okno (Unia 5). Klasa CSplitterWnd pozwala na tylko jeden pionowy i jeden poziomy pasek dzielący, dlatego podana tutaj liczba rzędów oraz kolumn może wynosić tylko l lub 2. Przykładowo, jeden rząd i dwie kolumny oznaczają pojedynczy, pionowy pasek dzielący. 516 Poznaj Visual C++ 6 Tworzenie okien w trakcie działania programu Najprościej jest tworzyć okno dodając kontrolki do szablonu w trakcie projektowania programu, jeśli jednak chcemy dodać kontrolkę w trakcie działania programu, należy skorzystać z funkcji CEdit: :Create. Funkcja Create pozwala dynamicznie przywołać do życia okno dowolnego typu. Tworzenie okna w trakcie działania programu zawsze przebiega w dwóch etapach. Najpierw konstruujemy odpowiedni obiekt klasy okna (przykładowo CEdit, CComboBox), a następnie przywołujemy jego funkcję Create przesyłając jej styl okna, wymiary, jego okno rodzica i numer identyfikacyjny kontrolki. Czwarty parametr c s iż e definiuje minimalną wysokość rzędu i minimalną szerokość kolumny (linia 6). Nowy widok jest tworzony tylko wtedy, gdy użytkownik zaznaczy większy panel niż zdefiniowane tu minimalne rozmiary. Usuwany jest natomiast wtedy, kiedy panel zostanie zmniejszony poniżej tych minimalnych wymiarów. Odpowiednie ustawienia mogą być zmieniane w trakcie działania programu za pośrednictwem funkcji SetRowinfo i SetColumninfo. Piąty parametr jest wskaźnikiem do obiektu CCre-ateContext. Obiekt ten zawiera bieżące informacje o klasach dokumentu, widoku i okna obramowującego. Struktura pContext została już wcześniej inicjalizowana i jest przesyłana do funkcji Create w linii 7. Ostatnie dwa parametry są opcjonalne. Szósty definiuje styl okna. Standardowe style to WS_CHILD l WS_HSCROLL | WS_VSCROLL | SPLS_DYNAMIC_SPLIT. Ostatni parametr jest identyfikatorem okna potomnego. O ile nie zamierzamy wbudowywać w okno dzielone kolejnych okien dzielonych, najlepiej pozostać przy domyślnej wartości AFX_IDW_ PANE_FIRST. Aby uruchomić w programie dynamiczne okna dzielone, należy dokonać edycji dwóch funkcji CDSplitView tak jak to pokazaliśmy na listingu 20.2. Listing 20.2. LST21_2.CPP - dynamiczne okna dzielone 1 void CDSplitView::OnDraw(CDC* pDC) 2 { 3 CDSplitDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: Tutaj dodaj kod wyświetlający własne dane 7 TEXTMETRIC tm; 8 int nLineHeight; 9 10 // ** Pobierz bieżące rozmiary czcionki i wylicz wysokość linii 11 pDC->GetTextMetrics(&tm); 12 nLineHeight = tm.tmHeight + tm.tmExternalLeading; O 13 14 // ** Wyświetl 50 linii tekstu 15 CString str; 16 for(int nLine = l; nLine < 51; nLine++) 17 { 18 str .Format ("Linę %d - I must NOT feed my homework. to my Śdog.", nLine) ; 19 pDC->TextOut(5, nLine * nLineHeight, str); @ 20 } 21 } 22 23 void CDSplitView::OnInitialUpdate() 24 { 25 CScrollView::OnInitialUpdate() ; 26 27 CSize sizeTotal; 28 // TODO: Wylicz całkowite wymiary widoku 29 30 • // ** Zdefiniuj całkowity rozmiar przewijanego obszaru jako 1000x1000 31 sizeTotal.ex = sizeTotal.cy = 1000; 32 SetScrollSizes(MM_TEXT, sizeTotal); ® 33 ) O Wyliczamy wysokość linii tekstu w oparciu o rozmiary bieżącej czcionki. @ Wyświetlamy tekst na ekranie. © Zdefiniowanie rozmiarów przewijanego obszaru jako 1000x1000 pikseli wymusza wyświetlenie pasków przewijania. Ręczna edycja jest jedynie potrzebna, gdy w linii 31 zmieniamy rozmiary przewijanego obszaru z 100 na 1000 pikseli. W ten sposób logiczny (całkowity) rozmiar widoku jest większy niż rozmiar fizyczny (wyświetlany na ekranie), co powoduje wyświetlenie pasków przewijania. Funkcja pokrywająca OnDraw wyświetla 50 linii tekstu. Aby obliczyć rozmiar bieżącej czcionki wybranej w obiekcie kontekstu urządzenia (ang. deyice context), w linii 7 deklarujemy, a w linii 11 inicjalizujemy za pomocą funkcji CDC :: GetTextMetrics strukturę TEXTMETRIC. Po zbudowaniu i uruchomieniu aplikacji możemy wybrać jedno z pól dzielących i przewinąć wyświetlony w nim fragment widoku. Warto zauważyć, że paski przewijania ob- 518 Poznaj Visual C++ 6 sługują zarówno przewijanie widoków w pionie, jak i w poziomie. Na rysunku 20.4 można zobaczyć przykładowe okno programu DSplit. Wistv* y"« » iap'B F' ^v. s ? r1111—1111—'1—1111 — -' : - —11111— LInc 1 - must NOT fecd my homework to my dog. Li n ę 1 - must NOT leed my ho^nework 10 my dog. -'J Linę 2 - must NOT feed my homework to my dog. Li n ę 2- must NOT feed my homework to my dog.j Li n 3- must NOT feed my homework to my dog. Li n ę 3- must NOT teed my homework to my dog. Li n 4- must NOT leed my homework to my dog. :: Li n ę 4- musi NOT feed my homework to my dog. Li n 5- musi NOT feed my homework to my dog. U n e 5- must NOT feed my homework to my dog. Li n 6- must NOT feed my homework lo my dog. LI n c 6- must NOT feed my homework to my dog. Li n 7- must NOT feed my homework to my dog. Li n ę 7- musi NOT feed my homework to my dog. Li n 8- musi NOT feed my homework to my dog. i: U n e 8- must NOT fecd my homework to my dog. Li n 9- must NOT feed my homework to my dog. j U n e 9- must NOT feed my homework to my dog. Li n 10-1 must NOT feed my homework to my dog. : U n e 10-1 musi NOT feed my homework to my dog. -r | Li n 40 - must NOT feed my homework to my dog. U n e 48 - must NOT feed my homework to my dog. ti Li n 41 - musi NOT leed my homework to my dog. :: U n e 41 - must NOT (eed my homework to my dog. Li n 42 - must NOT feed my homework to my dog. U n e 42 - must NOT feed my homework to my dog. Li n 43 - must NOT feed my homework lo my dog. Li n ę 43 - musi NOT feed my homework to my dog. Li n 44 - must NOT leed my homework to my dog. U n e 44 - must NOT feed my homework to my dog. Li n 45 - must NOT feed my homework to my dog. U n e 45 - must NOT feed my homework to my dog. : Li n 46 - must NOT feed my homework (o my dog. ii U n e 4 G- must NOT feed my homework to my dog. •'••:. Li n 47 - must NOT fecd my homework to my dog. : Li n ę 47 - musi NOT feed my homework to my dog. i Li n 46 - must NOT leed my homework to my dog. :: U n e c4 a- must NOT feed my homework to my dog. : i j Li n 49 - must NOT feed my homework to my dog. Une 49 - must NOT feed my homework to my dog. '"J Li n 50 - must NOT feed my homework to my dog. : Une 50 - musi NOT fecd my homework to my dog. <ł l >\ .łteN- ;afl„:l:;li:s1 . — ,, . :»r »:s*/ ! ! ' 11/;, ^"^..Wiy1^- .^^^p^M^^sssiSĘiS'ys:^ Rysunek 20.4, Program DSplit PATRZ TAKŻE * Więcej na temat klas MDI znaleźć można w rozdziale 21. * Więcej informacji na temat kontekstu urządzenia znaleźć można w rozdziale 15. Tworzenie statycznych okien dzielonych Okno obramowujące zawierające statyczne okno dzielone już na starcie podzieleń jest na dwa panele. Liczba paneli, ich wstępne ustawienie i klasy widoków definiowane s w momencie tworzenia okna obramowującego. Również w tym momencie tworzone s;i obiekty widoków. Inaczej niż w dynamicznych oknach dzielonych, użytkownik nie mo/.e usuwać wyświetlonych w oknie paneli. Obiekty widoków są wbudowane na stałe i nie są konstruowane ani niszczone na bieżąco. Pasek dzielący jest zawsze widoczny na ekranie, a przeciągany zatrzymuje się, kiedy próbujemy zmniejszyć panel poniżej minimalnych dopuszczalnych rozmiarów. Żeby utworzyć aplikację podobną z wyglądu do Eksploratora Windows, który wyko- rzystuje pasek dzielący do odseparowania lewego panelu z widokiem Tree od prawego z widokiem List, należy skorzystać z pomocy kreatora AppWizard. Więcej na ten temai powiemy za chwilę w podrozdziale „Tworzenie aplikacji podobnej do Eksploratora Win dows". Drugim sposobem na dodanie statycznego okna dzielonego jest ręczne wpisaniu odpowiedniego kodu. Aby obejrzeć odpowiedni kod statycznego okna dzielonego, należ\ za pomocą kreatora AppWizard utworzyć nowy projekt SDI o nazwie SSplit. Na szóstej stronie kreatora należy jako bazową klasę widoku wybrać klasę CEditview. Tworzenie okien z kilkoma widokami____________________________519 Inicjalizacja statycznego okna dzielonego W zależności od aplikacji, panele statycznego okna dzielonego wyświetlają różne rodzaje widoków i mogą być przypisane różnym klasom widoków. Za pomocą kreatora AppWizard utworzymy nowy projekt aplikacji SDI, SSplit. W kroku szóstym wybieramy jako klasę bazową klasę CEditview. Modyfikowanie wyglądu pasków dzielących Aby zmodyfikować wygląd pasków dzielących, należy wywieść z klasy csplit-terWnd własną podklasę i pokryć w tej podklasie wirtualną funkcję OnDrawSplit-ter (). Funkcji należy przesłać parametr porządkowy informujący, który element okna dzielonego ma zostać malowany. Do wyboru mamy splitBox, splitBar, splitln- tersection i splitBorder. W tym projekcie dodamy fragmenty kodu implementujące statyczne okno dzielone i drugą klasę widoku. Statyczny pasek będzie dzielił okno w pionie na lewy panel, w którym umieścimy widok wywiedziony z klasy CEditview i prawy, goszczący widok wywiedziony z klasy cview. Gdy już utworzymy odpowiedni projekt, należy wykonać poniższe czynności. Wywodzenie własnej klasy za pomocą kreatora CIassWizard 1. Aby uruchomić kreator CIassWizard, wciśnij Ctri+W lub w menu yiew wybierz polecenie CIassWizard. 2. Kliknij przycisk Add Ciass, a następnie w liście, która się pojawi wybierz New. Wyświetlone zostanie okno dialogowe The New Ciass. 3. W polu Name wpisz nazwę nowej klasy. W tym przykładzie CArtView. 4. W liście kombinowanej Base Ciass wybierz klasę bazową CView. Teraz kliknij OK, żeby dodać nową klasę. 5. Aby zamknąć okno dialogowe CIassWizard kliknij OK. Teraz, gdy już utworzyliśmy nową klasę widoku, możemy dołączyć do programu kod statycznego okna dzielonego. W tym celu najpierw dodajemy do klasy CMainFrame chronioną zmienną składową typu CSplitterWnd. Zmiennej składowej nadajemy nazwę m_wndSplitter. Teraz należy dołączyć do programu statyczne okno dzielone, tak jak to pokazaliśmy w instrukcji, a następnie przeedytować funkcję CMainFrame: OnCreateClient, tak jak to widać na listingu 20.3. Dołączanie do programu statycznego okna dzielonego l. Aby uruchomić kreator CIassWizard, wciśnij Ctri+W lub w menu Yiew wybierz polecenie CIassWizard. 520_____________________________________Poznaj Visual C++ 6 2. Wybierz kartę Message Maps. 3. Z listy kombinowanej Ciass Name wybierz klasę CMainFrame. 4. Z listy Object IDs wybierz CMainFrame. 5. Z listy Messages wybierz OnCreateClient i kliknij przycisk ^dd Function. 6. Wciśnij przycisk Ędit Code. Listing 20.3. LST21_3.CPP - tworzenie statycznego okna dzielonego w funkcji OnCreateClient 1 BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT Ipcs, ŚCCreateContext* pContext) 2 { 3 // TODO: Tu dodaj własny kod i/lub przywołaj funkcję klasy bazowej 4 // ** Utwórz statyczne okno dzielone V 5 if (!m_wndSplitter.CreateStatic(this, l, 2)) 6 • return FALSE; 7 8 // ** Utwórz dwa widoki i dodaj je do paneli okna 9 if (!m_wndSplitter.CreateView(0, O, RUNTIME_ CLASS(CSSplitView), CSize(150, 100), pContext) II 10 !m_wndSplitter.CreateView(0, l, RUNTIME_CLASS(CArtYiew), @ ŚCSize(100, 100), pContext)) 11 { 12 m_wndSplitter.DestroyWindow();© 13 return FALSE; 14 } 15 16 // ** Poinformuj, że operacja zakończyła się sukcesem 17 return TRUE; 18 } O Tworzymy statyczne okno dzielone o jednym rzędzie i dwóch kolumnach (dzielimy okno w pionie). © Tworzymy widok dla każdego z dwóch paneli okna. © Jeśli proces tworzenia okna nie zakończył się sukcesem, usuwamy okno. Tworzenie okien z kilkoma widokami 521 Należy pamiętać, aby na początku pliku MainFrm.cpp dodać następujące dyrektywy łinclude: łinclude "SSplitView.h" łinclude "Artview.h" Należy również do pliku SSplitView.h przed linią zawierającą definicję klasy dodać wstępną deklarację dokumentu. Ten zabieg jest niezbędny z powodu porządku, w jakim załączane będą pliki nagłówka. Dodajemy następującą linię: ciass CSSplitDoc; ponad linią: ciass CSSplitView : public CEditView Aby utworzyć statyczne okno dzielone, zamiast wzywanej przy dynamicznym oknie dzielonym funkcji Create, korzystamy z funkcji CreateStatic. Funkcja tworząca statyczne okno dzielone CreateStatic może posiadać do pięciu parametrów. Pierwszy parametr this jest wskaźnikiem do okna rodzica. Drugi i trzeci definiują odpowiednio liczbę rzędów i kolumn. W linii 5 zdefiniowaliśmy jeden rząd i dwie kolumny, co spowoduje, że okno zostanie przedzielone na pół pionowym paskiem. Ostatnie dwa parametry są opcjonalne. W czwartym parametrze można zdefiniować styl okna. Ostatni, piąty parametr, jest identyfikatorem okna potomnego. O ile nie chcemy umieszczać w oknie dzielonym kolejnych okien dzielonych, należy skorzystać z domyślnego parametru AFX_IDW_PANE_FIRST. Liczba rzędów i kolumn statycznego okna dzielonego W odróżnieniu od dynamicznych okien dzielonych, które są ograniczone do maksimum dwóch rzędów i dwóch kolumn, statyczne okna dzielone mogą mieć nawet szesnaście rzędów i kolumn. Niemniej zbyt duża liczba rzędów i kolumn uczyni interfejs aplikacji zbyt niewygodnym dla użytkownika. Obiekt widoku dla każdego z paneli jest tworzony za pomocą funkcji CreateView, którą wzywamy w linii 9 i w linii 10. Pierwsze dwa parametry funkcji to numer rzędu i kolumny, w której widok jest umieszczany. Trzeci parametr jest wskaźnikiem do klasy bieżących informacji o klasie widoku (informacji czasu wykonania, ang. runtime informa-tion). Klasa ta musi wywodzić się koniec końców z klasy cwnd, jednak najczęściej wywodzona jest z klasy cview. Czwarty parametr klasy csize definiuje minimalną wysokość rzędu i szerokość kolumny. Wartości te można zmieniać w trakcie działania programu za pomocą funkcji SetRowinfo i SetColumninfo. Piąty parametr jest wskaźnikiem do obiektu CCreateContext. Obiekt ten zawiera bieżące informacje o klasach okna obra-mowującego i dokumentu. Struktura pContext została już inicjalizowana w funkcji CFra-mewnd: :OnCreateClient, dlatego można ją bez żadnych dodatkowych zabiegów przesłać do funkcji CreateView. 522 Poznaj Visual C++ 6 Na końcu funkcji okno zostaje albo usunięte, albo jeśli tworzenie okna zakończyło się sukcesem, funkcja zwraca po prostu (w linii 17) wartość TRUE. Aby obejrzeć działanie statycznego okna dzielonego z panelami wyświetlającymi dwa różne widoki, należy przeedytować funkcję CArWiew: OnDraw tak jak to widać na listingu 20.4. Listing 20.4. LST21_4.CPP - pokryta funkcja OnDraw 1 void CArtView::OnDraw(CDC* pDC) 2 ( 3 CDocument* pDoc = GetDocument(); O 4 5 // TODO: Tutaj dodaj własny kod malujący okno 6 l / ** Zachowaj bieżący pędzel 7 CBrush* pOldBrush = pDC->GetCurrentBrush(); @ 8 9 // ** Utwórz niebieski pędzel 10 CBrush br; 11 br.CreateSolidBrush(RGB(0,0,255)); ® 12 13 // ** Wybierz niebieski pędzel w kontekście urządzenia 14 pDC->SelectObject(&br); O 15 pDC->Ellipse(l, l, 300, 300); © 16 br.DetachO; © 17 18 br.CreateHatchBrush(HS_FDIAGONAL, RGB(255,255,0)); Q 19 pDC->SelectObject(&br); 20 pDC->Ellipse(50, 50, 200, 200); © 21 22 // ** Przywróć dawny pędzel 23 pDC->SelectObject(p01dBrush); © 24 } O Wywołujemy funkcję GetDocument (), aby wydobyć wskaźnik do klasy dokumentu aplikacji @ Pobieramy wskaźnik do pędzla aktualnie przechowywanego w kontekście urządzenia p DC © Tworzymy pędzel malujący na niebiesko O Przypisujemy nowy pędzel kontekstowi urządzenia © Rysujemy dużą elipsę Tworzenie okien z kilkoma widokami 523 © Separujemy pędzel od kontekstu urządzenia Q Tworzymy żółty pędzel, który będzie malował ukośne linie pod kątem 45 stopni © Malujemy małą elipsę © Przywracamy oryginalny pędzel kontekstu urządzenia Funkcja CArtView: :OnDraw wzywana jest, gdy trzeba odmalować prawy panel. Funkcja rysuje dwa okręgi (w oparciu o funkcję rysującą elipsę), jeden wypełniony kolorem niebieskim, a drugi ukośnymi żółtymi liniami. Parametry pozycyjne przesyłane w liniach 15 i 20 do funkcji rysujących, takich jak CDC: :Ellipse są definiowane w odniesieniu do panelu, w którym rysujemy. Po zbudowaniu i uruchomieniu aplikacji będziemy mogli obejrzeć okno programu SSplit takie jak na rysunku 20.5. Rysunek 20.5. Program SSplit Tworzenie aplikacji podobnej do Eksploratora Windows Bardzo dobrym przykładem aplikacji z wieloma widokami jest Eksplorator Windows. Pionowy statyczny pasek dzielący oddziela obydwa panele: lewy zawierający widok Tree i prawy goszczący widok List. W naszym przykładzie utworzyliśmy podobnie wyglądającą aplikację opisującą schemat produkcji w fabryce. Widok drzewa przedstawia hierarchię pracowników i maszyn, a lista po prawej zadania do wykonania. Kreator AppWizard pozwala w prosty sposób utworzyć aplikację tego typu. Poniżej zamieszczamy opcje, które trzeba włączyć, aby uzyskać pożądany efekt. 524 Poznaj Visual C++ 6 Tworzenie projektu podobnego do Eksploratora Windows 1. Utwórz nowy projekt kreatora AppWizard (exe). 2. Na pierwszej stronie kreatora wybierz jeden z dwu przełączników Single Document albo Multiple Documents. 3. Upewnij się, że została zaznaczona opcja Document/yiew Architecture Support?. 4. Na stronie czwartej kreatora wciśnij przełącznik Windows Ęxplorer. Wybranie w kreatorze AppWizard opcji Windows Ęxplorer Style spowoduje utworzenie szkieletowej aplikacji z dwoma klasami widoku: wywiedzioną z klasy CTeeView klasą CLeftView i klasą widoku wywiedzioną z klasy CListYiew. Statyczny pasek rozdzielający jest automatycznie wbudowywany w okno obramowujące, a odpowiedni kod tworzący widok jest dodawany do pokrywającej odpowiednią funkcję wirtualną funkcji OnCreateClient. Obie klasy widoku powiązane są z tym samym dokumentem i posiadają odpowiednie funkcje GetDocument. Kreator AppWizard dodaje również do paska narzędziowego cztery przyciski pozwalające zmieniać styl wyświetlania w widoku List wybierając pomiędzy listą klasyczną, małymi ikonami, dużymi ikonami i listą szczegółową. Pod paskiem narzędziowym umieszczany jest dodatkowo pasek dialogowy. Eksplorator Windows wykorzystuje opcjonalnie dodawany pasek dialogowy do wyświetlania ponad oboma widokami ich tytułów, my jednak możemy wykorzystać go w dowolnym celu. Aby usunąć pasek dialogowy, należy z klasy CMainFrame usunąć zmienną składową m_wndDlgBar i tę część funkcji CMain-Frame:: OnCreate, która odnosi się do tejże zmiennej. Sercem widoku Tree jest kontrolka Tree Klasa CTreeView tak naprawdę jest obudowaniem dla wykorzystywanej w oknach dialogowych klasy CTreeCtrI. Wersja drzewa wykorzystywana w widoku jest automatycznie dopasowywana do rozmiarów obszaru roboczego okna i jest automatycznie rozciągana, gdy zmienia się wymiary okna obramowującego. Można również dodać do niej odpowiednie funkcje obsługi komunikatów, pozwalające jej obsługiwać polecenia menu i paska narzędziowego. Do ukrytej wewnątrz widoku kontrolki można sięgać za pomocą funkcji GetTreeCtrl (). Klasa lewego panelu CLeftView jest zawsze wywodzona z klasy CTreeView i jej klasy bazowej nie można zmienić za pomocą kreatora AppWizard. Można natomiast zmienić klasę bazową prawego panelu. Na stronie szóstej kreatora AppWizard można w liście kombinowanej Base Ciass wybrać klasę dla prawego panelu. Kreator tworzy szkielet aplikacji, my natomiast jesteśmy odpowiedzialni za wypełnienie odpowiednimi danymi widoku Tree i za kod widoku z prawego panelu. Tworzenie okien z kilkoma widokami 525 PATRZ TAKŻE • Więcej na temat klasy CTreeView znaleźć można w rozdziale 19. • Więcej na temat klasy CListView znaleźć można w rozdziale 19. Inne sposoby tworzenia okien z wieloma widokami Architektura dokument/widok daje wielkie możliwości różnorodnego przedstawiania danych. Pokazaliśmy właśnie jak korzystać z okien dzielonych, ale należy zaznaczyć, że nie zawsze są one najlepszym rozwiązaniem, szczególnie gdy dane należy zaprezentować na wiele różnych sposobów. Biblioteka MFC oferuje możliwość łączenia dynamicznie utworzonego widoku z już istniejącym dokumentem. Po takim połączeniu widok będzie współdziałał z dokumentem nie gorzej niż widok zbudowany w oparciu o szablon dokumentu. Dodawanie i usuwanie widoków Nowe widoki mogą być konstruowane praktycznie w dowolnym momencie. Dokument z nowym widokiem można połączyć przesyłając wskaźnik widoku funkcji CDocu-ment: :Addview. Widok od dokumentu odłączamy za pomocą funkcji CDocu-ment: :RemoveView, tak jak poprzednio przesyłając jako parametr wskaźnik widoku. Funkcje te jednak nie tworzą, ani nie niszczą obiektu widoku. Widok musi już istnieć zanim wezwiemy funkcję Addview i musi zostać zniszczony po wezwaniu funkcji Re-moveView. Dokument posiada listę odwołujących się do niego widoków, a te dwie funkcje po prostu zarządzają wspomnianą listą. Widok może być powiązany tylko z jednym dokumentem Widok może być jednorazowo powiązany z tylko jednym dokumentem. Próba wywołania po raz drugi funkcji Addview z tym samym wskaźnikiem widoku zostanie przez program oprotestowana. Funkcje Addview i RemoveView są również przyzywane odpowiednio w funkcjach CView: :OnCreate i cview: :~cview, gdy widoki są budowane w oparciu o szablony dokumentów. Obie funkcje po wykonaniu swoich zadań przyzywają wirtualną funkcję OnChangeViewList. Funkcja CDocument: :OnchangeViewList sprawdza, czy przechowywana w dokumencie lista widoków jest pusta i jeśli tak, zamyka dokument przyzywając funkcję OndoseDocument. Jeśli zależy nam, żeby dokument pozostał otwarty, mimo likwidacji ostatniego związanego z nim widoku, możemy odpowiednio pokryć funkcję OnChangeViewList. 526_____________________________________Poznaj Visual C++ 6 Zarządzanie tworzeniem i aktywacją widoku Modyfikowanie tego kiedy i które widoki zostaną wyświetlone użytkownikowi, jest dość proste, wystarczy sięgnąć do odpowiednich funkcji obsługujących działanie architektury dokument/widok. Funkcje te pozwalają modyfikować interfejs użytkownika dodając lub usuwając odpowiednie widoki w zależności od akcji podjętych przez użytkownika. Zbudujemy teraz aplikację, która pozwoli użytkownikowi przełączać między różnymi widokami zajmującymi cały obszar roboczy okna obramowującego. Najpierw za pomocą kreatora AppWizard tworzymy projekt VPick wybierając na stronie szóstej jako klasę bazową klasę CEditYiew. Program VPick bazuje na wcześniejszym programie SSplit, demonstruje jednak podejście, które nie wymaga korzystania z okien dzielonych. Po utworzeniu projektu należy wykonać czynności opisane w przedstawionym wcześniej w tym rozdziale algorytmie „Wywodzenie własnej klasy za pomocą kreatora CIassWizard". Użytkownik może w menu wybrać widok, który go interesuje. Dwa nowe elementy dodajemy do menu definiując odpowiednie identyfikatory ID_SHOW_ART_VIEW i SHOW EDIT_VIEW oraz przypisując elementom menu odpowiednie tytuły (ang. caption). Następnie dodajemy odpowiednie funkcje obsługi nowych poleceń menu. Funkcje obsługi w tym przykładzie są implementowane w klasie CMainFrame, natomiast w aplikacji MDI dołą- czalibyśmy je do klasy CChildFrame. Dodawanie funkcji obsługi poleceń menu 1. Po uruchomieniu kreatora CIassWizard za pomocą klawiszy Ctri+W lub polecenia CIassWizard menu yiew, wybierz kartę Message Maps. 2. Z listy kombinowanej CIass Name wybierz klasę CMainFrame. 3. Z listy Object IDs wybierz identyfikator ID_SHOW_EDIT. 4. Z listy Messages wybierz COMMAND, a następnie kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 5. Z listy Messages wybierz UPDATE_COMNAND_UI i kliknij przycisk A.dd Function. W oknie dialogowym Add Member Function kliknij OK. 6. Z listy Object IDs wybierz identyfikator ID_SHOW_ART. 7. Z listy Messages wybierz COMMMAND i kliknij przycisk Add Function. W oknie dia- logowym Add Member Function kliknij OK. 8. Z listy Messages wybierz UPDATE_COMNAMD_UI i kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 9. Aby zamknąć okno dialogowe CIassWizard, kliknij OK. Znaczna część kodu wykorzystywanego w funkcjach OnShowEdit i OnShowArt jest identyczna. Zamiast więc powielać go niepotrzebnie, lepiej utworzyć pomocniczą funkcję, która będzie przywoływana w obu wspomnianych funkcjach. Odpowiedni algorytm znajduje się poniżej, a parametry funkcji opisane zostały dalej. Tworzenie okien z kilkoma widokami 527 Tworzenie funkcji pomocniczej 1. W panelu ClassView wybierz klasę CMainFrame, a następnie w menu skrótów polecenie Add Member Function. 2. W pole Function Type wpisz void. 3. W polu Function Declaration wpisz CreateActivateView (CRuntimeClass *pNew-View, UINT nID). 4. Wciśnij przełącznik Priyate Acces i kliknij OK. Pozostało nam teraz napisać odpowiedni kod funkcji. Aplikacja powinna działać w ten sposób, aby użytkownik mógł wybierać pomiędzy widokiem Edit a widokiem Art za pomocą odpowiedniej opcji menu. Wybrany widok zajmuje cały obszar roboczy okna podczas, gdy drugi pozostaje ukryty. W zależności od tego, który z widoków jest w danym momencie aktywny, uaktywniane są lub wyłączane odpowiednie opcje menu. Funkcję pokrywającą CArtView:: OnDraw edytujemy tak jak na listingu 20.4. Ta część kodu została zaczerpnięta z programu SSplit i służy tylko do wyświetlania dwóch różnych klas widoku. Funkcje składowe klasy CMainFrame należy przeedytować, tak jak to pokazaliśmy na listingu 20.5. Należy pamiętać, aby na początku pliku MainFrm.cpp dodać następujące dyrektywy łinclude: łinciude "VPickView.h" finclude "ArtView.h" Przed linią zawierająca definicję klasy należy dodać do pliku VPickView.h wstępną deklaracje dokumentu. Ten zabieg jest niezbędny z powodu porządku, w jakim załączane będą pliki nagłówka. Dodajemy następującą linię: ciass CVPickDoc; ponad linią: ciass CVPickView : public CEditView Listing 20.5. LST21_5.CPP - implementacja opcji menu pozwalających tworzyć i aktywować widoki 1 void CMainFrame::OnShowEdit() O 2 ( 3 // ** Wezwij funkcję pomocniczą przesyłając jej wskaźnik 4 // ** do bieżącej klasy widoku i unikalny identyfikator 5 CreateActivateView(RUNTIME_CLASS(CEditView), l); 6 } 7 8 void CMainFrame::OnUpdateShowEdit(CCmdUl* pCmdUl) 9 ( 10 // ** Włącz lub wyłącz opcję menu w zależności 11 // ** od tego, która klasa widoku jest aktywna 528 Poznaj Visual C++ 6 12 pCmdUI->Enable ( 13 !GetActiveView()->IsKindOf(RUNTIME_CLASS(CEditView))); @ 14 } 15 16 void CMainFrame::OnShowArt() © 17 { 18 // ** Wezwij funkcje pomocniczą przesyłając jej wskaźnik 19 // ** do bieżącej klasy widoku i odpowiedni identyfikator 20 CreateActivateView(RUNTIME_CLASS(CArtView), 2) ; 21 } 22 23 void CMainFrame::OnUpdateShowArt(CCmdUl* pCmdUl) 24 ( 25 // ** Włącz lub wyłącz opcję menu w zależności od 26 // ** tego, która klasa widoku jest aktywna 27 pCmdUI->Enable( 28 !GetActiveView()->IsKindOf(RUNTIME_CLASS(CArtView))); O 29 } 30 31 void 'CMainFrame: : CreateActivateView ( 32 CRuntimeClass *pNewViewClass, 33 UINT nID) 34 { 35 // ** Zachowaj wskaźnik do aktywnego widoku 36 CView* p01dView = GetActiveView(); 37 CView* pNewView = NULL; 38 39 // ** Zachowaj wskaźnik do aktywnego dokumentu, następnie 40 // ** przeszukaj widoki dokumentu poszukując obiektu 41 // ** widoku o tej samej klasie bieżącej co przesłana 42 // ** do funkcji 43 CDocument* pDoc = GetActiveDocument(); 44 POSITION pos = pDoc->GetFirstViewPosition(); 45 while(pos && !pNewView) 46 ( 47 CView* pView = pDoc->GetNextView(pos); @ 48 if(pView->IsKindOf(pNewViewClass)) 49 pNewView = pView; 50 } 51 // ** Sprawdź, czy widok został znaleziony. 52 // ** Jeśli nie, skonstruuj, utwórz i inicjalizuj widok. 53 if(pNewView == NULL) 54 ( 55 // ** Inicjalizuj zmienną klasy CCrea.teCon.text zawierającą 56 // ** wskaźnik do dokumentu Tworzenie okien z kilkoma widokami 529 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 } CCreateContext context; context.m_pCurrentDoc = pDoc; // ** Skonstruuj obiekt widoku korzystając z bieżącej II ** klasy i utwórz okno widoku, w pNewView = (CView*) pNewViewdass->CreateObject () ; pNewView->Create(NULL, NULL, O, CFrameWnd::rectDefault, O this, nID, &context) ; pNewView->On!nitialUpdate() ; } // ** Zdefiniuj nowy widok jako widok aktywny i wyświetl II ** jego okno. Ukryj okno starego widoku. SetActiveView(pNewView) ; pNewView->ShowWindow(SW_SHOW) ; p01dView->ShowWindow(SW_HIDE); © // ** Zamień identyfikatory, gdyż identyfikator aktywnego .// ** okna widoku powinien być AFX_IDV_PANE_FIRST. p01dView->SetDlgCtrlID(pNewView->GetDlgCtrlID() ) ; © pNewView->SetDlgCtrlID(AFX_IDW_PANE_FIRST) ; // ** Rozmieść paski kontrolne i rozciągnij okno. RecalcLayout() ; O Funkcja obsługi polecenia menu. @ Wyłączamy polecenie menu, gdy aktywna jest klasa CEditView, jeśli nie, uaktywniamy polecenie. © Funkcja obsługi polecenia menu. O Wyłączamy polecenie menu, gdy aktywna jest klas CArtView, jeśli nie, uaktywniamy polecenie. © Przeszukujemy listę widoków dokumentu w poszukiwaniu odpowiedniej klasy widoku. © Dynamicznie konstruujemy obiekt widoku. Q Tworzymy widok i przyzywamy funkcję OninitialUpdate. © Wyświetlamy nowy widok i ukrywamy stary. @ Wymieniamy identyfikatory okien. 530_____________________________________Poznaj Visual C++ 6 Jak łatwo zauważyć, obydwie funkcje obsługi poleceń menu, OnShowEdit (linia l) i On-ShowArt (linia 16), natychmiast odwołują się do pomocniczej funkcji CreateActiva-teView (zaczynającej się w linii 31). Przesyłają jej dwa parametry: wskaźnik do bieżących informacji o klasie widoku i identyfikator. Pierwszy parametr, m_pNewViewdass definiuje, jakiego rodzaju widok został wybrany. Drugi parametr służy do identyfikowania okna widoku. Funkcje OnUpdateShowEdit (linia 8) i OnUpdateShowArt, (linia 23) są przyzywane, każdorazowo, gdy element menu jest odmalowywany. Sprawdzają one za pomocą funkcji cobject: : isKindOf, który z widoków jest aktywny. W momencie, gdy klasa aktywnego widoku jest taka sama jak przesłana funkcji klasa bieżąca, funkcja IsKindOf zwraca wartość FALSE polecając funkcji CCmdUl: :Enable wyłączyć opcję. W ten sposób opcja związana z widokiem aktywnym będzie zawsze wyłączona, a ta związana z widokiem nieaktywnym zawsze włączona. Funkcja CreateActivateView (linia 31) najpierw przyzywa funkcję GetActiveView i przechowuje wskaźnik do widoku w zmiennej p0ldview, ponieważ poprzedni widok jest tutaj usuwany. Kolejnym zadaniem funkcji jest sprawdzenie, czy istnieje powiązany z aktywnym dokumentem widok zapisany w zmiennej (wskaźniku) pNewViewdass. Za pomocą funkcji GetFirstViewPosition i GetNextView (w liniach 44-50) przeszukujemy listę widoków dokumentu. Jeśli zostanie odnaleziony widok typu pNewViewdass, w linii 49 przypisujemy wartość wskaźnika do niego zmiennej pNewView. Warunek i f w linii 53 sprawdza, czy widok został znaleziony, jeśli tak, musi zostać ponownie uaktywniony, jeśli nie, musi zostać skonstruowany i aktywowany. Makroinstrukcja ASSERTJKINDOF () Zamiast korzystać z funkcji IsKindOf (), dowolny obiekt dowolnej klasy wywodzącej się z klasy cobject można przesłać do makroinstrukcji ASSERT_KINDOF() . Parametrami makroinstrukcji są nazwa klasy i wskaźnik do obiektu klasy. Makroinstrukcja sprawdza, czy klasa obiektu jest właściwa. Jeśli nie, wyświetlone zostaje okno dialogowe MFC Assert. Makroinstrukcja wykonywana jest tylko w wersji projektu testowanej w debugerze, a w normalnie wykonywanym programie jest ignorowana. W liniach 57-67 nowy obiekt jest konstruowany i tworzony. Zmienna context klasy CCreateContext jest strukturą, której przypisywany jest wskaźnik do dokumentu. Struktura ta jest następnie w linii 62 przesyłana do funkcji Create, aby utworzyć powiązanie pomiędzy dokumentem a widokiem. Obiekt widoku jest tworzony w linii 62. Zmienna pNewViewdass może w tym miejscu przyjmować wartość CEditView lub CArtView. Ponieważ obie klasy pozwalają tworzyć obiekty dynamicznie, funkcja CreateObject po prostu przyzywa odpowiedni konstruktor i zwraca wskaźnik do nowego obiektu. W linii 63 funkcja Create tworzy okno widoku i przypisuje je do obiektu wywiedzionego z klasy cview. Kiedy już proces tworzenia widoku zostanie zakończony, przyzywana jest wirtualna funkcja OninitialUpdate () dokonująca inicjalizacji widoku. Tworzenie okien z kilkoma widokami 531 Niezależnie, czy zachodzi potrzeba skonstruowania widoku, czy nie, wybrany obiekt przesyłany jest w linii 70 do funkcji SetActiveView, która aktywuje widok i definiuje go jako aktualnie pobierający dane (ang. input focus). Za każdym razem, gdy widok jest aktywowany lub wyłączany, otrzymuje komunikat OnActivateView z parametrem logicznym (BOOL) BActivate, który informuje o jego statusie. W linii 71 wyświetlamy wybrany widok, a w linii 72 ukrywamy widok zastępowany. Aktywny widok musi być zawsze opatrzony identyfikatorem AFX_IDVPANE_FIRST, ponieważ tak zostało to zaprogramowane wewnątrz funkcji RecalcLayout, niemniej z racji tego, że tylko jeden widok może być aktywny, tylko jedno okno opatrzone jest tym identyfikatorem. Dlatego w linii 76 zastępujemy identyfikator zapisany w zmiennej poi-dView identyfikatorem przechowywanym w zmiennej pNewView, a następnie w linii 77 przypisujemy zmiennej pNewView identyfikator AFX_IDW_PANE_FIRST. Przyzywana w linii 80 funkcja RecalcLayout odpowiada za odpowiednie dopasowanie okna widoku i zmiany w paskach narzędziowych oraz paskach kontroli okna obramowującego. Po zbudowaniu i uruchomieniu aplikacji VPick będziemy mogli wybierać jeden z dwu widoków przedstawionych na rysunku 20.6. Rysunek 20.6. Program VPick ZOBACZ TAKŻE f Menu opisane zostało dokładniej w rozdziale 13. • Więcej informacji na temat klas MDI znaleźć można w rozdziale 21. * Więcej na temat informacji czasu wykonania o klasie znaleźć można w rozdziale 23. Rozdział 21 Aplikacje wielodokumentowe Tworzenie aplikacji wielodokumentowej Poruszanie się między klasami aplikacji dokumentu, widoku i okna obramowuj ącego Tworzenie aplikacji w oparciu o wielodokumentową architekturę dokument/widok Wiele z najbardziej popularnych i najczęściej używanych obecnie aplikacji opartych jest na architekturze dokument/widok lub którymś z jej wariantów. Architektura ta dostarcza modułowej struktury programowania separującej kod zarządzający danymi od kodu obsługującego interfejs użytkownika. Danymi zarządza się wewnątrz dokumentu, a interfejsem użytkownika wewnątrz widoku. Dokument zazwyczaj bezpośrednio odnosi się do pliku dyskowego. Biblioteka MFC dostarcza dwóch typów aplikacji opartych na architekturze doku- ment/widok: SDI (interfejsu jednodokumentowego, ang. single document interface) i MDI (interfejsu wielodokumentowego, ang. multiple document interface). Aplikacje SDI opisane zostały w rozdziale 12. Również podstawowe założenia archi- tektury dokument/widok są takie same w aplikacjach SDI i aplikacjach MDI. Dlatego czytając poniższy rozdział, powinniśmy posiłkować się rozdziałem 12. W obu rozdziałach odwoływać się będziemy do podobnych przykładów, aby lepiej uwidocznić różnice między tymi dwoma typami aplikacji. Istotę różnicy między aplikacjami jednodokumentowymi a wielodokumentowymi w znacznej mierze wyjaśniają ich nazwy, natomiast aplikacje SDI pozwalają na jednorazowe otwarcie tylko jednego dokumentu. Aplikacje MDI pozwalają jednorazowo otwierać kilka dokumentów, z których każdy jest przypisany osobnemu oknu widoku. PATRZ TAKŻE • Więcej informacji na temat aplikacji SDI znaleźć można w rozdziale 12. 534 Poznaj Visual C++ 6 Tworzenie aplikacji wielodokumentowej Swego czasu w środowisku programistów debatowano, czy lepiej tworzyć aplikacje w oparciu o interfejs SDI, czy interfejs MDI. Część dyskutantów twierdziła, że programy powinny być oparte na jednym dokumencie, a aplikacje SDI powinny być powszechnym standardem. Jednak oprogramowanie jest tworzone dla użytkowników i to użytkownicy dyktują, jak powinny wyglądać aplikacje, z których chcą korzystać. Aplikacje MDI, takie jak MS Word czy Excell, cieszą się sporym powodzeniem wśród użytkowników. Dla niektórych programów możliwość obsługi kilku dokumentów (lub plików) wewnątrz tej samej aplikacji jest znacznie wygodniejsza niż otwieranie tego samego programu kilka razy. Tworzenie aplikacji MDI polega w znacznej mierze na wyborze odpowiednich opcji kre- atora AppWizard. Należy mieć jednak świadomość, że po utworzeniu projektu zamiana aplika- cji MDI w aplikację SDI i na odwrót nie jest sprawą łatwą. W pewnych sytuacjach zamiast przekształcać gotową aplikację, lepiej zacząć wszystko od początku. Dlatego rozpoczynając pisanie programu warto dobrze się zastanowić, czy potrzebna nam aplikacja SDI, czy MDI. Teraz utworzymy aplikację MDI, którą wykorzystamy następnie jako ilustrację pokazującą działanie architektury dokument/widok i klas aplikacji MDI. Projekt, który utworzymy postępując według przedstawionego niżej algorytmu, nosić będzie nazwę MDICoin. Tworzenie aplikacji MDI za pomocą kreatora AppWizard 1. W menu New wybierz polecenie File. Następnie w oknie dialogowym New Project na karcie Projects wybierz MFC AppWizard (exe). 2. Wpisz nazwę projektu (tutaj MDICoin) i kliknij OK. 3. Na pierwszej stronie kreatora AppWizard wciśnij przełącznik Multiple Documents i upewnij się, że została zaznaczona opcja Document/yiew Architecture Support? (rysunek 21.1). Kliknij Next. MFC AppWizdłd - Step 1 What type of appiication wouidyou hke to create? ^ Single document ^ NuKpIe document®; <~ j^iajog based ^ DocufflentĄ/iew archtecture suppoft? i iiiSB8^'"'"'88^!! ISS^I^I •:' ^B—' ••lS!!-S*S i • •• n^DR MAJŃFTW-iE1 : • Q IDR^MDieoiTWE a a Hm SIOFLMAINFBA ME ; IOBMDICOITYPE a Q sinna T«ti« • S Slling Table :: Er 'Si T<»Ł« , \ ', sM IDHMAINFRAME l i Si5"aVcft]on :; IH §i|5 c.-——.—.-,- ....-..............-.' ff^^^^^^O^^Elii^i r'^:T^t''^^ti:11^^T^1^tf^t|^i g|xf Erie Edil yw; '^ridow Mewi ClftłN flpen,.. Clii*0 ^tose 5ave OihS .... 3avs..^,.- „„...„„ 1 Erirt,.. C!it»P Print Piewem PJirtSetLp Hełp ^• ^ DovG e: St ndald |32«3;) ^J ^ij a, ^'iftt^r i.. .^".'.^Jwat. .."jtt^.^srf.^^^-i Tl'?1^1;1;?"^^^;.... , , -:--• '.^"-^"J-.Td "jClaKyKw ^]R&soufceVI^ =] FieVew Rysunek 21.5. Zasoby aplikacji MDI Poza zasobami IDR_MAINFRAME kreator AppWizard tworzy jeszcze ikonę, menu i listę łańcuchów odpowiadające specyficznemu typowi dokumentu i przypisuje tym wszystkim zasobom identyfikator IDR MDICOITYPE. Dzięki temu będziemy mogli dopasowywać do własnych potrzeb zasoby związane z każdym x typów dokumentów. 540_____________________________________Poznaj Visual C++ 6 Ikona IDR_MDICOITYPE wyświetlana jest po lewej stronie paska narzędzi, kiedy okno widoku jest zmaksymalizowane, w innych sytuacjach natomiast wyświetlana jest na pasku tytułowym okna widoku. Zasoby menu związane z danym typem dokumentu są aktywowane natychmiast, gdy aktywowany zostaje odpowiedni dokument. Jeśli w danym momencie nie ma żadnego aktywnego dokumentu, aktywowane jest menu IDR_MAINFRAME. Na rysunku 21.5 można zobaczyć różnicę między głównym menu File (wyżej) a menu File dokumentu (niżej): aplikacja MDI dodaje dodatkowe menu rozwijane Window, które pozwala użytkownikowi układać okna widoków sąsiadujące i w kaskadę za pomocą poleceń Tile i Cascade. Menu to zawiera również polecenie New Window, które pozwala użytkownikowi wyświetlać kolejne okna widoków, każde wewnątrz osobnego okna obramowującego MDI. Menu specyficzne dla danego dokumentu Każdy z dokumentów obsługiwanych przez aplikację MDI posiada swój własny zasób - menu IDR_. Jest ono wyświetlane automatycznie, gdy zostanie aktywowany odpowiedni dokument. Jeżeli żaden z dokumentów nie jest aktywny, wyświetlane jest menu IDR_MAINFRAME. Kolejnym z zasobów opatrzonych identyfikatorem IDR_MDICOTYPE jest lista łańcuchów zawierająca informacje o niektórych cechach dokumentu i wykorzystywana między innymi do automatycznego wyświetlania tytułów. Przegląd wizualnych elementów aplikacji MDI przedstawiliśmy na rysunku 21.6. Poniżej krótki opis poszczególnych elementów: • Klasa CMainFrame wywodzi się z klasy CMDlFrameWnd i odpowiedzialna jest za główne okno obramowujące aplikacji. • Klasa CToolbar odpowiada za dokowanie pasków narzędziowych w głównym oknie obramowującym. " Wywiedziona z klasy CMDlChildWnd klasa CChildFrame zawiera kod obsługujący okna obramowujące widoków. " Klasa CMDiCoinYiew implementuje widok jako okno potomne okna obramowującego MDI. • Klasa cstatusBar zawiera kod paska stanu związanego z głównym oknem obramowującym. • Okna widoków MDI pokrywają obszar roboczy głównego okna obramowującego. 6 54 Rysunek 21.6. Wizualne elementy aplikacji MDI 1. CMainFrame 2. CToolbar 3. CChildFrame 4. CMDICoinView 5. CStatusBar 6. Okna widoków MDI PATRZ TAKŻE * Więcej informacji na temat menu znaleźć można w rozdziale 13. • Więcej informacji na temat pasków narzędziowych znaleźć można w rozdziale 14. 542 Poznaj Visual C++ 6 Szablon dokumentu MDI Sercem struktury dokument/widok są szablony dokumentów. Chociaż wykorzystujemy je również przy tworzeniu aplikacji SDI, o wiele ważniejszą rolę odgrywają w aplikacjach MDI. Szablon dokumentu łączy ze sobą dokument i widok oraz okno obramowują-ce. W aplikacji jednodokumentowej nie ma zazwyczaj potrzeby zmieniać stworzonego przez kreator AppWizard kodu obsługującego szablony dokumentu. W aplikacji wielodo-kumentowej może być nam potrzebny zupełnie nowy typ dokumentu powiązany z własną klasą widoku i okna obramowującego, co oznacza, że będziemy musieli własnoręcznie napisać kod szablonu dokumentu. Szczęśliwie programowanie dodatkowych typów dokumentów nie jest specjalnie trudne, szczególnie, gdy uświadomimy sobie, że kreator CIassYizard stworzył już automatycznie kod dla pierwszego kompletu składającego się z dokumentu widoku i okna obramowującego, na którym będziemy mogli się wzorować tworząc własny szablon. Jeżeli spojrzymy jeszcze raz na rysunek 21.4 przedstawiający hierarchię klas aplikacji MDI, zobaczymy, że zawiera ona klasę CMultiDocTemplate - klasę szablonu dokumentu wykorzystywaną przez aplikacje wielodokumentowe. Mniej więcej w połowie funkcji CMDlCoinApp: :lnitlnstance (przyzywanej przez szkielet MFC'w celu inicjalizacji aplikacji) można znaleźć następujący fragment kodu: 1 // Zarejestruj szablon dokumentu aplikacji. 2 II Szablony dokumentów służą jako połączenie między dokumentami, oknami obramowujacymi i widokami. 3 CMultiDocTemplate* pDocTemplate; 4 pDocTemplate = new CMultiDocTemplate( O 5 IDR_MDICOITYPE, 6 RUNTIME_CLASS(CMDICoinDoc), 7 RUNTIME_CLASS(CChildFrame), 8 RUNTIME_CLASS(CMDICoinView)) ; 9 AddDocTemplate(pDocTemplate); @ 10 11 // Utwórz główne okno obramowujące MDI 12 CMainFrame* pMainFrame = new CMainFrame; ® 13 if (!pMainFrame->LoadFrame(IDR__MAINFRAME)) 14 return FALSE; O 15 m_pMainWnd = pMainFrame; O Tworzymy egzemplarz szablonu wielodokumentowego i przesyłamy identyfikator zasobów i bieżące klasy (czasu wykonania) jego dokumentu, widoku i okna obramowującego. @ Rejestrujemy nowo utworzony szablon dokumentu wraz z klasą aplikacji. Aplikacje wielodokumentowe 543 © Tworzymy egzemplarz okna obramowującego aplikacji. O Informujemy główne okno obramowujące, który z kompletów zasobów (takich jak menu czy pasek narzędziowy) załadować. Podczas tworzenia obiektu CMultiDocTemplate (linia 4) przesyłane są mu cztery parametry. Pierwszy parametr to identyfikator zasobów IDR_MDICOITYPE (linia 5). Identyfikator ten opisuje trzy różne zasoby związane z dokumentem SMDicoinDoc: ikonę, menu i tablicę łańcuchów. Trzy kolejne parametry są wskaźnikami do informacji czasu wykonania na temat klas dokumentu, okna obramowującego i widoku (pojawiają się odpowiednio w liniach 6, 7 i 8). Do tworzenia tych wskaźników wykorzystywana jest makroinstrukcja RUNTIME_CLASS. Dynamiczne tworzenie tych klas jest możliwe dzięki temu, że kreator AppWizard dołączył do programu obsługujące dynamiczną kreację makroinstrukcje: DECLARE_DYNCREATE i IMPLEMENT_DYNCREATE. Pomiędzy szablonami dokumentów w aplikacjach SDI i MDI jest kilka istotnych różnic. Obie klasy CSingleDocTemplate i CMultiDocTemplate wywodzą się z tej samej klasy bazowej CDocTemplate. Jednak podczas gdy klasa CSingleDocTemplate obsługuje tylko jeden obiekt dokumentu, klasa CMultidocTemplate obsługuje listę wskaźników do dokumentów (tutaj obiektów CMDlCoinDoc). Obiekty dokumentu, widoku i okna obramowującego nie są jeszcze w tym momencie tworzone. Przedstawiony fragment kodu inicjalizuje obiekt CMultiDocTemplate, zapełniając go informacjami potrzebnymi do załadowania zasobów oraz alokowania na żądanie w pamięci dokumentów, widoków i okien obramowujących. Klasa aplikacji przechowuje listę szablonów dokumentów. Przyzywana w linii 9 funkcja AddDocTemplate dodaje do tej listy nowy obiekt. Funkcję tę wzywamy za każdym razem, gdy chcemy zarejestrować nowe szablony dokumentów. W ten sposób pojedyncza aplikacja może obsługiwać różne rodzaje powiązań między dokumentem, widokiem a oknem obramowującym. Okno dialogowe New Document Jeśli w klasie aplikacji zarejestrowany jest więcej niż jeden typ dokumentu, wybranie w menu File polecenia New wywoła okno dialogowe pytające jakiego rodzaju dokument chcemy otworzyć. Opis wyświetlony w oknie zaczerpnięty jest z odpowiedniego dla tego dokumentu (z właściwym mu identyfikatorem IDR_) pola tablicy łańcuchów. Klasa CWinApp podtrzymuje obiekt CMultiDocTemplate, dopóki sama nie zostanie zniszczona, czyli dopóki aplikacja nie zostanie wyłączona. Podczas niszczenia klasy cwi-nApp pamięć oczyszczana jest z szablonów dokumentu, o ile oczywiście zostały one doda- 544_____________________________________Poznaj Visual C++ 6 ne za pomocą funkcji AddDocTemplate. Szablony dokumentów zajmują bardzo niewiele pamięci, dlatego przechowywanie ich przez cały czas życia aplikacji nie jest dużym obciążeniem dla systemu. W linii 12 za pomocą konstruktora CMainFrame tworzone jest główne okno obramo-wujące. Funkcja LoadFrame przyzywana w linii 13 wykorzystuje jako parametr identyfikator zasobów i podłącza do okna obramowującego zasoby IDR_MAINFRAME. Funkcji LoadFrame można również opcjonalnie przesłać parametr definiujący znaczniki stylów okna. Domyślne style to FWS_ADDTOTITLE i WS_OVERLAPPEDWINDOW. Styl OVERLAPPED-WINDOW łączy w sobie style podane w tabeli 21.1. Znacznik FWS_ADDTOTITLE jest specyficzny dla biblioteki MFC i sprawia, że nazwa aktywnego dokumentu jest wyświetlana na pasku tytułowym. Normalnie na pasku tytułowym wyświetlana jest najpierw nazwa apli- kacji, a po niej nazwa aktywnego dokumentu. Porządek ten można odwrócić za pomocą znacznika FWS_PREFIXTITLE. Tabela 21.1. Style głównego okna obramowującego zawarte w znaczniku WS_OVERLAPPEDWINDOW Styl Opis WS_OVERLAPPED Tworzy okno pokrywające WS_CAPTION Dodaje do okna pasek tytułowy WS_SYSMENU Dodaje do paska tytułowego standardowe menu systemowe WS_MINIMI ZEBOX Dodaje do paska tytułowego przycisk minimalizujący okno WS_MAXIMI ZEBOX Dodaje przycisk maksymalizujący okno WS_THICKFRAME Uruchamia rozciąganie okna PATRZ TAKŻE • Więcej na temat informacjach czasu wykonania o klasach można przeczytać w rozdziale 23. Porządek tworzenia dokumentu, widoku i okna obramowującego MDI Tak jak wspomnieliśmy przed chwilą, inicjalizacja szablonu dokumentu nie pociąga za sobą automatycznie konstrukcji obiektów dokumentu, widoku ani okna potomnego MDI. Szablon dokumentu przechowuje po prostu informacje czasu wykonania o klasach zbioru dokument/widok/okno obramowujące. Informacje te wykorzystywane są do tworzenia odpowiednich obiektów i zachowywania odpowiednich wskaźników w obiekcie aplikacji wtedy, gdy użytkownik postanowi utworzyć nowy dokument lub otworzyć dokument już istniejący. Za kreację i inicjalizację obiektu CMultiDocTemplate odpowiedzialny jest fragment kodu przedstawiony poniżej, uruchamiany po szczęśliwym utworzeniu okna obramowującego W funkcji CWinApp: ; Initinstance: Aplikacje wielodokumentowe 545 1 // Sięgnij do wiersza poleceń po standardowe polecenia shella DDE, by otworzyć plik 2 CCommandLineInfo cmdinfo; 3 ParseCommandLine(cmdinfo) ; 4 // Rozpatrz polecenia nadchodzące z wiersza poleceń 5 if (!ProcessShellCommand(cmdinfo)) 6 return FALSE; 7 // Tylko jedno okno zostało inicjowane, uaktualnij je l wyświetl. 8 m_pMainWnd->ShowWindow(SW_SHOW) ; 9 m pMainWnd->UpdateWindow(); Funkcja cConunandLineinfo pomaga obsługiwać parametry przesyłane aplikacji z wiersza poleceń. Standardowo opcje wiersza poleceń są inicjowane, żeby utworzyć nowy dokument, kiedy aplikacja jest uruchamiana. Sam proces konstruowania dokumentu, widoku i okna obramowującego MDI zachodzi za kulisami w przyzywanej w linii 5 funkcji cwinnApp:: ProcessShellCommand. Na listingu 21.1 przedstawiony został porządek kreacji dokumentu. Kod przedstawiony na listingu 21.1 został specjalnie przekształcony dla potrzeb ilustracji porządku tworzenia. Listing 21.2. LST22_5.CPP - porządek tworzenia dokumentu, widoku i okna obramowującego MDI 1 BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo) O 2 ( 3 if(rCmdInfo.NewFile) 4 ( 5 OnFileNewO; 6 » 7 if(rCmdInfo.OpenFile) 8 ( 9 OpenDocumentFile(rCmdInfo.strFileName) ; 10 } 11 12 } 13 void CWinApp::OnFiieNew()® 14 ( 15 m_pDocManager->OnFileNew(); 16 } 17 void CDocManager::OnFileNewO 18 { 19 pTemplate = m_templateList.GetHead();® 20 if(m_templateList.GetCount() > l) 21 ( 22 // ff przypadku więcej niż jednego szablonu okna Q 546___________________________________Poznaj Visual C++ 6 23 // dialogowego, przywołaj odpowiednie okno dialogowe 24 pTeroplate = dlg.m__pSeiectedTemplate; 25 } 26 pTemplate->OpenDocumentFile(NULL) ; 27 } 28 CMultiDocTemplate::OpenDocumentFile(LPCTSTR IpszPathName) 29 { 30 pDocument = CreateNewDocument() ; 31 pFrame = CreateNewFrame(pDocument); ® 32 33 if(IpszPathName == NULL) 34 { 35 SetDefauitTitle(pDocument); 36 pDocument->OnNewDocument(); ® 37 } 38 else 39 pDocument"->OnOpenDocument();® 40 InitialUpdateFrame(pFrame, pDocument); W 41 } O Przyzywana przez funkcję Irdtlnstanca. ,• !-; :i : , @ Funkcja obsługi komunikatów dla poiecema Filc, New. © Zmienna pTemplate jest wskaźnikiem do obiektu CMultiDocTeirip.late. . O Jeśli zarejestrowany jest więcej niż jeden dokument, w oknie dialogowym pytamy użytkownika, który chce wybrać. © Obiekty dokumentu i okna obramowujacego konstruowane sq przy wykorzystaniu bieżących (czasu wykonania) informacji b klasach zawartych w .szabionie. Funkcja CreateNewTextFrame dodatkowo konstruuje widok. ® Wirtualne funkcje OnNewDocurnent i OnOpen.poc-uinent są pokrywane, aby inicja-lizować dokument. ® Aktywujemy widok i wysyłamy komunikat WM_INITIALU?DATE, który uruchamia funkcję CView::OnInitiaiUpdate. Różnica między porządkiem kreacji w aplikacji MDI a w SDI polega na tym, że aplikacja MDI tworzy nowy dokument za każdym razem, gdy przyzywana jest funkcja OpenDocumentFile (linia 26), podczas gdy aplikacja SDI tworzy jeden dokument i używa go potem wielokrotnie. Przyzywana w linii 30 funkcja CreateNewDocument wykorzystuje informacje czasu wykonania o klasie przechowywane w szablonie do utworzenia odpowiedniego obiektu dokumentu. Funkcja ta odpowiedzialna jest również za Aplikacje wielodokumentowe 547 dodanie dokumentu do listy dokumentów. Po utworzeniu dokumentu program tworzy widok i okno obramowujące wzywając kolejną funkcje szablonu: createNewFrame. Przyzywana w linii 31 funkcja CreateNewFrama wykorzystuje przechowywane w szablonie informacje o klasie do skonstruowania obiektów okna obramowującego i widoku. Po utworzeniu okna obramowującego przyzywana jest funkcja CFrameWnd: : LoadFrame, której jako parametr przypisywany jest definiowany w konstruktorze szablonu identyfikator zasobów. W aplikacjach MDI identyfikator zasobów jest specyficzny dla danego rodzaju dokumentu. Przykładowo, w programie MDICoin funkcji przesyłany jest identyfikator IDR„MDICOITYPE. Funkcja LoadFrame ładuje każdy z zasobów (menu, pasek narzędziowy, ikonę, tablicę akceleratorów i łańcuch) i przyłącza je do okna obramowującego. Jeśli załadowanie któregoś z zasobów się nie powiedzie, funkcja LoadFrame wyświetli następujący komunikat: Warning: CDocTemplate couldn't create a frame. (Ostrzeżenie: Szablon CDocTempiate nie był w stanie utworzyć okna obramowującego.) Na końcu przyzywana jest funkcja initialUpdateFrame (linia 40). Definiuje ona utworzony widok jako widok aktywny i przesyła do zawierających okna widoku okien potomnych okna obramowującego komunikat WM„INITIALUPDATE. W efekcie tego komunikatu przyzywana jest funkcja cyiew: :OnlnitialUpdate. Funkcja ta jest bardzo często pokrywana, aby w zmienionym kodzie przeprowadzić odpowiednią inicjalizację widoku. Należy również pamiętać, aby przywołać w tym momencie funkcję omnitialupdate klasy bazowej. PATRZ TAKŻE • Więcej informacji na temat parametrów wiersza poleceń znaleźć można w rozdziale 12. Poruszanie się między elementami architektury dokument/widok Podczas tworzenia aplikacji opartej na architekturze dokument/widok zachodzi czasem potrzeba powiązania ze sobą obiektów różnych klas. Dwa najczęściej współpracujące ze sobą komponenty to oczywiście dokument i związany z nim widok, często jednak zachodzi potrzeba zdefiniowania wzajemnej interakcji również między innymi klasami. Przykładowo, może się zdarzyć, że będziemy musieli sięgnąć z klasy widoku do zmiennej składowej klasy aplikacji. W tabeli 21.2 przedstawione zostały różne funkcje biblioteki MFC umożliwiające poruszanie się między różnymi obiektami aplikacji MDI. 548 Poznaj Visual C++ 6 Tabela 21.2. Funkcje umożliwiające kontakt między różnymi klasami MDI Klasa Funkcja Funkcja zwraca AfxGetApp AfxGetMainWnd Global Global CMDIFrameWnd MDIGetActive CWnd GetParentFrame CFrameWnd GetActiveView CFrameWnd GetActiveDocument CYiew GetDocument GetFirstYiew Position CDocument CDocument GetNextView Wskaźnik do obiektu CWinnApp Wskaźnik do obiektu głównego okna obramo- wującego (CWnd) Wskaźnik do obiektu aktywnego okna potomnego MDI (CMDICildWnd) Wskaźnik do obiektu okna obramowującego rodzica (CFrameWnd) Wskaźnik do obiektu bieżącego aktywnego widoku (CYiew) Wskaźnik do obiektu bieżącego aktywnego dokumentu (CDocument) Wskaźnik do obiektu związanego z widokiem dokumentu (CDocument) Pozycję pierwszego widoku na liście widoków związanych z dokumentem wykorzystujemy do przeszukiwania listy widoków Wskaźnik do obiektu następnego widoku (CView) z listy widoków związanych z danym dokumentem Funkcje AfxGetApp i AfxGetMainWnd są funkcjami globalnymi, które można przywo- ływać w dowolnym miejscu programu. Wskaźniki zwracane przez te funkcje można spokojnie przekształcać w specyficzne dla naszej aplikacji klasy, na przykład tak jak poniżej: CMDICoinApp* pApp = CMainFrame* pFrame (CMDICoinapp*)AfxGetApp ( ); : (CMainFrame*)AfxGetMainWnd ( ); Widok jest związany z jednym konkretnym dokumentem, dlatego potrzebuje tylko jednej funkcji GetDocument. Wskaźnik zwracany przez funkcję jest automatycznie konwertowany na odpowiednią klasę dokumentu naszej aplikacji (odpowiada za to kod wygenerowany przez kreator AppWizard). Dokumentowi może być w danym momencie przypisany jeden lub więcej widoków. W aplikacji może również istnieć więcej niż jeden dokument. Aby odnaleźć aktywny dokument lub aktywny widok, należy najpierw odnaleźć aktywne okno potomne MDI, a następnie przywołać w zależności od potrzeb funkcję GetActiveDocument lub GetActiveView, tak jak pokazaliśmy poniżej: Aplikacje wielodokumentowe_______________________________549 // Znajdź aktywne okno potomne MDI CMDIChildWnd* pChild = ((CMDIFrameWnd*)AfxGetMainWnd())->MDIGetActive() ; // Znajdź aktywny dokument CMDICoinDoc* pDoc = (CMDICoinDoc*)pChild->GetActiveDocument() ; // Znajdź aktywny widok CMDICoinView* pView = (CMDICoinView*)pChild->GetActiveView(); Widok może widzieć tylko jeden dokument ri Obiekt dokumentu może posiadać kilka widoków, ale pojedynczy widok może być powiązany tylko z jednym dokumentem, i ; i L; ; Jeśli zachodzi potrzeba wykonania operacji na jednym lub kilku widokach związanych z danym dokumentem, możemy w razie potrzeby przeglądać listę widoków dokumentu za pomocą funkcji GetFi.rstViewPositi.on i GetNextView: void CMyDoc::DoTaskForAllViews() ( POSITION pos = GetFirstViewPosition(); while (pos != NULL) ( CMyView* pMyView = GetNextView(pos) ; pMyView->DoTask() ; } } Przykładowa aplikacja MDI Na początku tego rozdziału utworzyliśmy przykładowy projekt MDICoin. Do tej pory służył nam wyłącznie do ilustrowania działania aplikacji MDI dostarczając przykładowych fragmentów kodu utworzonych przez kreator AppWizard. Teraz rozwiniemy ten przykład. Początkowo będzie wyświetlał stos monet oferując jednocześnie polecenia menu umożli- wiające dodawanie i usuwanie kolejnych monet. Później utworzymy drugi typ dokumentu, służący do wyświetlania banknotów. W tym celu potrzebna nam będzie druga klasa do- kumentu i druga klasa widoku oraz dodatkowe, specyficzne dla niego zasoby. Będziemy musieli również utworzyć, inicjować i zarejestrować w aplikacji kolejny szablon dokumentu. 550_____________________________________Poznaj Visual C++ 6 Dodawanie do dokumentu zmiennych składowych Przykładowa klasa dokumentu CMDicoinDoc korzysta z jednej tylko zmiennej składowej, definiującej bieżącą liczbę monet w stosie. Aby można było sięgać do tej zmiennej spod klasy widoku CMDlCoinView, należy do klasy dokumentu dodać funkcję zwracająca jej wartość. Funkcję tę dodajemy do programu w następujący sposób. Implementowanie metod przechowujących i udostępniających dane dokumentu 1. W panelu CIassYiew wybierz klasę CMDICoin, następnie w menu skrótów wybierz polecenie Add MemberYariable. 2. Wybierz kartę MemberYariables. W polu Variable Type wpisz int. W polu Yariable Name wpisz m_nCoins, po czym wciśnij przełącznik Protected Acces. Kliknij OK. 3. Nadal w obrębie klasy CMDICoinDoc wybierz w menu skrótów polecenie Add IMember Function. 4. W polu Function J^ype wpisz int. W polu Function Declaration wpisz GetCoinCount, a następnie wciśnij przełącznik Public Acces. 5. Wewnątrz funkcji GetCoinCount wpisz następującą linię: return m nCoins; 6. W panelu ClassView kliknij dwukrotnie konstruktor CMDICoinDoc. 7. W funkcji konstruktora wpisz po komentarzu TODO następującą linię kodu; m nCoins = l ; Dokument CMDICoinDoc posiada teraz chronioną zmienną składowa m_ncoins, ini- cjalizowaną wewnątrz funkcji konstruktora. Należy pamiętać, że o ile inicjalizowanie zmiennych w konstruktorze dokumentu jest bezpieczne w aplikacjach MDI, o tyle w apli- kacjach SDI może generować błędy. W aplikacjach MDI nowy egzemplarz obiektu doku- mentu jest tworzony i alokowany w pamięci, gdy dokument jest tworzony lub otwierany, a niszczony, gdy dokument jest zamykany. W aplikacji SDI obiekt dokumentu jest alokowany w pamięci tylko raz, a następnie wykorzystywany jest we wszystkich następnych dokumentach tak, że zostaje zniszczony dopiero w momencie zamykania aplikacji. Do przykładu dodaliśmy również składową funkcję dostępu GetCoinCount, która będzie wykorzystywana przez widok do pobierania bieżącej wartości zmiennej rri_nCoins. Ochrona zmiennych dokumentu przed niepożądanymi modyfikacjami Dokument powinien zawierać wyłąc/nie chronione zmienne składowe dostępne tylko za pośrednictwem publicznych funkcji składowych. fAIKA, IAIV.C • S/Węcey na temat inicjalizacji aplikacji SOI dowiedzieć się można w rozdziale 12. Sięganie do danych dokumentu z klasy widoku Aby widok mógł pobierać dane z dokumentu, trzeba mu najpierw umożliwić dostęp do obiektu dokumentu. Biblioteka MFC robi to automatycznie, dodając do odpowiedniej klasy widoku funkcję GetDocument. Funkcja ta zwraca wskaźnik do obiektu dokumentu, do którego widok został przypisany w szablonie dokumentu. Widok CMDlCoinYiew wyświetla po prostu stos monet korzystając w tym celu z funkcji onDraw. Funkcja OnDraw przyzywana jest za każdym razem, gdy widok powinien zostać odmalowany na ekranie. W programie MDICoin funkcja CMDiCoinView:: OnDraw wygląda tak jak na listingu 21.2. Listing 21.2. LST22_1,CPP - sięganie do danych dokumentu, aby odmalować widok na ekranie 1 void CMDiCoinView::OnDraw(CDC* pDC) 2 ( 3 •CMDICoinDoc* pDoc = GetDocument(); 4 ASSERT_VALID(pDoc) ; 5 6 // TODO: Tu dodaj własny kod rysujący 7 // ** Zachowaj bieżący pędzel 8 CBrush* pOldBrush = pDC->GetCurrentBrush () ; 9 10 // ** Utwórz żółty pędzel 11 CBrush br; 12 br.CreateSolidB.':ush(RGB (255, 255,0)); 13 14 // ** Wybierz żółty pędzel w kontekście urządzenia 15 pDC->SelectObject(&br); 16 17 // ** Pobierz z dokumentu liczbę monet 18 // ** i narysuj dwie elipsy reprezentujące każdą z monet 19 for(int nCount = 0; nCount < pDoc->GetCoinCount() ; nCount++) 20 ( 21 int y = 100 - 10 * nCount; 22 pDC->Ellipse(40, y, 100, y-30); 23 pDC->Ellipse(40, y-10, 100, y-35); 24 } 25 26 II** Przywróć poprzedni pędzel 27 pDC->SelectObject(p01dBrush) ; 28 } 552___________________________________Poznaj Visual C++ 6 Pierwsze dwie linie funkcji (3 i 4) są automatycznie generowane przez kreatora AppWi- zard. Funkcja GetDocument zwraca wskaźnik do obiektu dokumentu związanego z tym widokiem. Wskaźnik ten jest wykorzystywany w linii 19 w pętli for razem z funkcją GetCoinCount, żeby pobrać z dokumentu bieżącą liczbę monet. PATRZ TAKŻE • O kontekście urządzenia pisaliśmy w rozdziale 15. ^ Informacje na temat tworzenia grafiki można znaleźć w rozdziale 16. Modyfikowanie danych dokumentu i uaktualnianie widoku Aby zmienić liczbę monet zapisaną w dokumencie, należy do programu MDICoin dodać dwa nowe polecenia menu - polecenie dodające i polecenie odejmujące jedną monetę. Wybranie któregoś z tych poleceń wywoła funkcję klasy dokumentu, która odpowiednio zwiększy lub zmniejszy liczbę monet, a następnie zadba o to, aby wszystkie związane z dokumentem widoki zostały odpowiednio uaktualnione. Jako że polecenia są specyficzne dla dokumentu związanego z monetami, trzeba je dodać do zasobu menu IDR_MDI-COITYPE. Polecenia te dodajemy w następujący sposób. Dodawanie poleceń menu 1. W panelu ResoureeView rozwiń gałąź folderu Menu i kliknij dwukrotnie IFR_MDI-COITYPE. W edytorze zasobów wyświetlony zostanie zasób menu. 2. W edytorze zasobów kliknij polecenie Ędit. W ten sposób rozwiniesz menu edycji. 3. Kliknij dwukrotnie czysty pasek u dołu rozwiniętego menu. Pojawi się widoczne na rysunku 21.7 okno dialogowe Menu Item Properties. 4. W polu ID wpisz identyfikator polecenia menu. Tutaj ID_EDIT_ADD_COIM. 5. W polu Caption wpisz tytuł polecenia menu. Tutaj: Add a Coin (Dodaj monetę). 6. W polu Proinpt wpisz tekst komentarza. W naszym przykładzie: increase the num-bar of coins (Zwiększ liczbę monet). 7. Kliknij dwukrotnie ostatni, pusty element menu. 8. W polu ID wpisz identyfikator polecenia menu. Tutaj ID_EDIT_BEMOVE_COIN. 9. W polu Caption wpisz tytuł polecenia menu. Tutaj: Remove a Coin (Usuń monetę). 10. W polu Proinpt wpisz tekst komentarza. W naszym przykładzie: Decrease the num-ber of coins (Zmniejsz liczbę monet). 11. Aby uruchomić kreatora CIassWizard wciśnij Ctri+W lub w menu V.iew wybierz polecenie CIassWizard. 12. Wybierz kartę MessageMaps. Aplikacje wielodokumentowe 553 13. Z listy kombinowanej Ciass Name wybierz CMDICoinDoc. 14. Z listy Object IDs wybierz identyfikator ID_EDIT_ADD_COIN. 15. Z listy Messages wybierz COMMANO, a następnie kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 16. Z listy Object IDs wybierz identyfikator ID_EDIT_REMOVE_COIN. 17. Z listy Messages wybierz COMMAND, a następnie kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 18. Kliknij przycisk Edit Code. Każda zmiana w przechowywanych w dokumencie danych powinna znajdować odbicie wewnątrz powiązanych z nim widoków. Tym właśnie zajmują się funkcje odmalowujące widok: OnEditAddCoin i OnEditRemoveCoin. Kod tych funkcji dla programu MDICoin można znaleźć na listingu 21.3. • Ł2J E'fe J^ y»w lisert Proffct fimU loofc ^róow Hetp liel :e? B® X Efa B ;:2- .1- H[Q ]S ę? ; q» ICMDIChiMWn, -aF^" - an |Eh Eill YIB" W<>»' H* ; Q Q MDICom leuuicN ffl-QAccdeia>oi !«• •Q Daloi) iB-t-j Icon Cul ETO Parte Addacwi Rwno^aa ci CM+X 0.1*C CHH V e-.aM.nu E; - IDR_MAINFHAME - ; l IDRMDICOiTyPE l ' IDR MDINOTTYPE i6 BaSI«oT«hle ffl L.) l"*" 9& ^.3V 0) 18 m_nCoins--; 19 20 // ** Uaktualnij widoki, aby odmalować stos monet 21 UpdateAllViews(NULL) ; 22 } Obie funkcje najpierw odpowiednio zwiększają lub zmniejszają o jeden zmienną m_nCoins (pierwsza w linii 6, druga w linii 18), a następnie (w linii 9 i w linii 21) wzywają funkcję UpdateAllViews przesyłając jej parametr NULL. Parametr NULL informuje funkcję, że wszystkie widoki związane z dokumentem mają zostać uaktualnione. Każdy z widoków uaktualniany jest osobno za pomocą funkcji OnUpdate. Funkcja ta unieważnia (ang. invalidate) cały obszar roboczy okna widoku. Unieważniony obszar jest następnie odmalowywany przez wzywaną przez funkcję OnUpdate funkcję OnDraw. Co zrobić, aby widoki uaktualniały się szybciej? y , ' '':•• ;:- ^N^;^:^^ .,.. , W: funkcji updateAl i. v.i.&w'3 i-nożna przestać specyficzne dla danej aplikacji wskazówki odnośnie odmalowanego obszaru, przesyłane .następnie; do funkcji. OnUpdate, gdzie shiżą do definiowania co dokładnie powinno zostać odmalowane. Budujemy i uruchamiamy teraz projekt MDICoin. Tworzymy za pomocą polecenia New z menu File nowe okno dokumentu. Możemy skorzystać z poleceń Add a Coin i Remove a Coin. Powinniśmy uzyskać rezultat podobny do widocznego na rysunku 21.8. Za pomocą polecenia New Window z menu Window można utworzyć nowy widok powiązany z tym samym dokumentem. Aplikacje wielodokumentowe 555 tite Erfit View Window Hełp l D 12; a; 7 -•',$', ss i' RMDICon R&ady Rysunek 21.8. Aplikacja MDICoin Dodawanie nowych szablonów dokumentów Jedną z przewag modelu MDI nad aplikacjami SDI, poza pracą z więcej niż jednym otwartym dokumentem, jest możliwość wykorzystywania wewnątrz jednej aplikacji więcej niż jednego typu dokumentu. W celu uruchomienia tej opcji należy utworzyć własne klasy dokumentu i widoku, dołączyć odpowiednie zasoby oraz utworzyć i inicjować obiekt CMultiDocTemplate. Dodamy teraz do przykładu MDICoin nowy typ dokumentu, który w miejsce monet będzie wyświetlał banknoty (ang. bilis). Aby utworzyć nowe klasy widoku i dokumentu, należy wykonać następujące czynności. Tworzenie nowych klas widoku i dokumentu 1. Aby uruchomić kreai'^1 CUssWirard. wegnij Qrt--W lub w menu yiew wybierz polecenie CIassWizard. 2. Kliknij przycisk Add Oass, a z listy, która się wtedy po|;nvi, wybierz New. Wyświetlone zostanie okno dialogowe New Cla;>s 3. W polu Name wpisz nazwę klasy. Tutaj CMDZBillDoc 4. Z listy kombinowanej Base Ciass wybierz CDocument. Aby dodać klasę, kliknii OK, 5. Kliknij przycisk Add Ciass, a w liście, która się pojawi, wybierz New. 6. W polu Name wpisz nazwę klasy. Tutaj CMDlBillview. 7. Z listy kombinowanej Base Ciass wybierz CView. Aby dodać klasę, kliknij OK. 8. Aby zamknąć okno dialogowe CIassWizard, kliknij OK. 556___________________________________Poznaj Visual C++ 6 9. W panelu ClassView wybierz klasę CMDłBillDoc, następnie w menu skrótów kliknij polecenie Add Member .Variable. 10. Wybierz kartę Member Variables. W polu Variable Type wpisz int. W polu Variable Name wpisz m_nBills, następnie wciśnij przełącznik Protected Acces. Kliknij OK. 11. Przy nadal podświetlonej klasie CMDłBillDoc wybierz w menu skrótów polecenie Add Member Function. 12. W polu Function Type wpisz int. W polu Function Declaration wpisz GetBillCo-unt i wciśnij przełącznik Public Acces. Kliknij OK. 13. Do funkcji GetBillCount wpisz następującą linię kodu: return m_Bills; 14. W panelu ClassView kliknij dwukrotnie funkcję konstruktora CMDłBillDoc. 15. W funkcji konstruktora po komentarzu TODO wpisz: m_nBills = l; 16. W panelu ClassView wybierz klasę CMDlBillview, następnie w menu skrótów wybierz polecenie Add Member Function. 17. W polu Function Type wpisz CMDłBillDoc*. W polu Function Declaration wpisz GetDocument i wciśnij przełącznik Public Acces. Kliknij OK. 18. W funkcji konstruktora po komentarzu TODO wpisz: return (CMDłBillDoc*)m_pDocument; Mamy już nowe klasy dokumentu i widoku. Dokument posiada zmienną składową przechowującą dane i odpowiednią funkcję dostępu. Klasa widoku posiada metodę (funkcję) przeznaczoną do pobierania dokumentu. Trzeba jeszcze w pliku MDłBil-lView.cpp przed dyrektywą łinclude załączającą MDIBillView.h dodać następującą dyrektywę #include: łinclude "MDłBillDoc.h" Następnym krokiem jest dołączenie zasobu menu. Aby załączyć nowy zasób menu, należy wykonać czynności podane w poniższej instrukcji. Tworzenie menu dokumentu 1. W panelu ResoureeView rozwiń gałąź folderu Menu i wybierz element IDR_MDICOITYPE. Wciśnij kolejno Ctri+C, a następnie Ctrl+V. Załączona zostanie kopia wybranego menu opatrzona identyfikatorem IDR_MDICOITYPEI. 2. W panelu ResoureeView wybierz IDRJMDICOITYPEI, następnie w menu skrótów wybierz polecenie Properties. Wyświetlone zostanie okno dialogowe Menu Properties. 3. W polu ID wpisz identyfikator menu. Tutaj IDR_MDIBILTYPE. Zamknij okno Menu Properties. Aplikacje wielodokumentowe_________________________ 557 4. W panelu ResourceView kliknij dwukrotnie IDR_MDIBILTYPE. Menu wyświedane jest w edytorze zasobów. 5. W edytorze zasobów kliknij Edit. Pojawi się rozwijane menu. 6. Kliknij dwukrotnie polecenie Add a Coin. Pojawi się okno dialogowe Menu Item Properties. 7. W polu ID wpisz identyfikator polecenia menu. Tutaj ID_EDIT_ADD_BILL. 8. W polu Caption wpisz tytuł elementu menu. Tutaj Add a Bili. 9. W polu Pronipt wpisz tekst komentarza dla elementu menu. Tutaj Increase the number of bills (Zwiększ liczbę banknotów). Następnie kliknij dwukrotnie Remove a Coin. 10. W polu ID wpisz identyfikator polecenia menu. Tutaj ID_EDIT_REMOVE_BILL. 11. W polu Caption wpisz tytuł elementu menu. Tutaj Remove a Bili. 12. W polu Pronipt wpisz tekst komentarza dla elementu menu. Tutaj Decrease the number of bills (Zmniejsz liczbę banknotów). 13. W menu File wybierz polecenie Save. Następnie w menu File kliknij Close. 14. Aby uruchomić kreator CIassWizard, kliknij w menu View polecenie CIassWizard lub wciśnij Ctri+W, następnie wybierz kartę Message Maps. 15. Z listy kombinowanej Ciass Name wybierz CMDłBillDoc. 16. Z listy IDs wybierz ID_EDIT_ADD_BILL. 17. Z listy Messages wybierz COMMAND; teraz kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 18. Z listy Objects IDs wybierz ID_EDIT_BEMOVE_BILL. 19. Z listy Messages wybierz COMMAND; teraz kliknij przycisk Add Function. W oknie dialogowym Add Member Function kliknij OK. 20. Aby zamknąć kreator CIassWizard, kliknij OK. Mamy teraz dwie nowe funkcje obsługi poleceń w klasie CMDlBillDoc::OnEditAdd-Bill i OnEditRemoveBill, które odpowiednio zwiększają i zmniejszają o jeden wartość zmiennej m_nBills. Funkcje te należy teraz przeedytować, w taki sam sposób jak na listingu 21.3 (zastępując oczywiście zmienną m_nCoins zmienną m_nBills). Po przeedy-towaniu funkcji kolejnym etapem będzie utworzenie dla dokumentu łańcucha zasobów. Tworzenie łańcucha dokumentu l. W panelu ResourceView rozwiń folder StringTable, następnie kliknij dwukrotnie String Table. W edytorze zasobów pojawi się tablica łańcuchów. 558 Poznaj Visual C++ 6 2. Aby dodać nowy łańcuch pod łańcuchem IDR_MDICOITYPE na liście łańcuchów wybierz IDR_MDICOITYPE, a następnie w menu skrótów kliknij polecenie New String. Pojawi się okno dialogowe String Properties. 3. W polu ID wpisz identyfikator łańcucha. Tutaj IDR_MDIBILTYPE;. 4. W polu Caption wpisz tytuł łańcucha. W tym przykładzie: \nMDiBil\nCoins\n\n \nMDICoi.Document\nMDICoi Document. 5. Edytuj łańcuch IDR_MAINFRAME, aby wczytać MDiCoins&Bills. 6. Edytuj łańcuch IDR_MDICOITYPE, aby wczytać \nMDXBil\r.Colns\n\n\nMDlCoi. Document\nMDICoi Document 7. Zamknij okno dialogowe String Properties. Łańcuch dokumentu ma ściśle określony format, taki sam w aplikacjach SDI i MDI. Łańcuch jest podzielony na kilka podłańcuchów ograniczonych znakiem \n (od ang. newli ne, nowa linia). Przeznaczenie każdego z łańcuchów zostało opisane w tabeli 21.3. Tabela 21.3. Łańcuch zasobów dokumentu Przykładowy łańcuch Opis 2 MDICoi 3 Coins 4 Coin Viewer (*.coi) 5. .coi 6 MDICoi.Document 7 MDICoi Document W MDI zostawiamy pusty. W aplikacji SDI łańcuch ten jest tytułem aplikacji. W aplikacji MDI tytuł aplikacji przechowywany jest osobno w IDR_MAINFRAME Domyślna nazwa dokumentu. Każdemu nowemu dokumentowi jest automatycznie przypisywany numer porządkowy. Przykładowo w programie MS Word łańcuch zawiera tekst Document, pierwszy dokument otrzymuje nazwę Documenti, drugi Document2 itd Tytuł dokumentu. Wykorzystywany przy przedstawianiu użytkownikowi listy dostępnych dokumentów. Jeśli łańcuch ten pozostawimy pusty, ukryjemy dokument przed użytkownikiem Typ dokumentu i opis dla filtra odszukującego pliki z danym roz- szerzeniem. Wykorzystywany w oknie dialogowym Filc Open na liście Files of Type (Pliki typu) Rozszerzenie pliku. Tutaj nie załączamy znaków globalnych (np. *) Nazwa rejestrująca dla Eksploratora Windows. Wykorzystywana do łączenia typu dokumentu z Eksploratorem, tak aby po kuknięciu pliku uruchamiana była odpowiednia aplikacja Nazwa identyfikacyjna dla OLE i Rejestru. Łańcuch ten przechowywany jest w Rejestrze (ang. Registry), co umożliwia obsługę OLE. Użytkownik widzi ten łańcuch, gdy uruchamia polecenie OLE, Insert Object Aplikacje wielodokumentowe 559 Teraz, gdy wszystko już jest gotowe, możemy wykorzystać nowe klasy dokumentu i widoku wewnątrz przykładu MDICoin. Resztę pracy przy tworzeniu obiektów i kodu obsługującego widoki i okna obramowujące możemy pozostawić bibliotekom MFC. W tym celu musimy zarejestrować w aplikacji nowy komplet dokument/widok tworząc drugi szablon dokumentu. Szablony dokumentu rejestrowane są w funkcji cwinApp: :lnitlns-tance pokrywającej wirtualną funkcję Initinstance. Aby utworzyć i zarejestrować szablon dokumentu, trzeba dodać odpowiedni kod przedstawiony w liniach 16- 25 listingu 21.4. Należy również pamiętać, aby dodać na początku pliku MDICoinApp.cpp następujące dyrektywy #include: łinclude "MDłBillDoc.h" finclude "MDIBillView.h" Listing 21.4. LST_3.CPP - tworzenie i inicjowanie szablonu dokumentu 1 BOOL CMDICoinApp::Initinstance() 2 { 3 // ** Uwaga: dla większej czytelności kodu usunęliśmy niektóre linie 4 5 // Zarejestruj szablon dokumentu aplikacji. Szablony 6 // dokumentów służą jako połączenie między dokumentami, oknami obramowujacymi l widokami. 7 8 CMultiDocTemplate* pDocTemplate; 9 pDocTemplate = new CMultiDocTemplate( 10 IDR_MDICOITYPE, 11 RUNTIME_CLASS(CMDICoinDoc), 12 RUNTIME_CLASS(CChildFrame), // własne potomne okno obramowujące 13 RUNTIME_CLASS(CMDICoinView)) ; 14 AddDocTemplate(pDocTemplate) ; 15 16 // ** Utwórz drugi szablon dokument 17 // ** -Ł inicjuj przesyłając identyfikator zasobów oraz 18 // ** informacje czasu wykonania o klasach dokumentu, okna obramowującego i widoku. 19 // ** Wezwij funkcję AddDpcTemplate, aby zarejestrować szablon w aplikacji. 20 pDocTemplate = new CMultiDocTemplate( 21 IDR_MDIBILTYPE, 22 RUNTIME_CLASS(CMDIBillDoc) , 23 RUNTIME_CLASS(CChildFrame), // własne potomne okno obramowujące 24 RUNTIME CLASS(CMDIBillView)); 560___________________________________Poznaj Visual C++ 6 25 AddDocTemplate(pDocTemplate) ; 26 27 // Utwórz okno obramowujące MDI 28 CMainFrame* pMainFrame = naw CMainFrame; 29 if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) 30 return FALSE; 31 m_pMainWnd = pMainFrame; 32 33 // ** Uwaga: dla większej czytelności kodu usunęliśmy niektóre linie 34 }______________________________________________________ Na koniec trzeba jeszcze dostarczyć kod, który będzie wyświetlał banknoty dolarowe w liczbie odpowiadającej zapisanej w dokumencie zmiennej. Zajmuje się tym przedstawiona na listingu 21.5 funkcja CMDlBillview: :OnDraw. Listing 21.5. LST22_4.CPP - kod funkcji malującej widok 1 void CMDlBillview::OnDraw(CDC* pDC) 2 ( 3 // ** Pobierz wskaźnik do dokumentu 4 CMDłBillDoc* pDoc = GetDocument(); O 5 ASSERT_VALID(pDoc); 6 7 // ** Zachowaj aktualny pędzel 3 CBrush* pOldBrush = pDC->GetCurrentBrush() ; 9 10 // ** Utwórz zielony pędzel 11 CBrush br; 12 br.CreateSolidBrush(RGB(0,128,32)); 13 14 // ** Wybierz zielony pędzel w kontekście urządzenia 15 pDC->SelectObject(&br); 16 17 // ** Pobierz z dokumentu liczbę banknotów i dla każdego 18 // ** banknotu namaluj prostokąt i $ jako symbol banknotu 19 for(int nCount = 0; nCount < pDoc->GetBillCount() ; nCount++) @ 20 { 21 int x = 40 + 20 * nCount; 22 pDC->Rectangle(x, 40, x+100, 90); 23 pDC->TextOut(x + 5, 45, "$") ; 24 } 25 26 // ** Przywróć poprzedni pędzel Aplikacje wielodokumentowe 561 27 pDC->SelectObject(pOldBrush) ; 28 } O Pobieramy wskaźnik do obiektu dokumentu. @ Przyzywamy funkcję dostępu dokumentu, aby pobrać aktualną liczbę banknotów. Funkcja GetDocument zwraca wskaźnik do obiektu dokumentu przechowywany następnie w zmiennej pDoc. Wskaźnik wykorzystywany jest w linii 19 w pętli for do pobrania za pomocą funkcji dostępu GetBillCount bieżącej liczby banknotów. Procedura malująca tworzy tylko nędzną imitację prawdziwego banknotu dolarowego zapełniając zielonym pędzlem naśladujący banknot prostokąt i wpisując w rogu symbol $. Jeśli po zbudowaniu i uruchomieniu projektu wybierzemy w menu File polecenie New, pojawi się zmienione okno dialogowe New. Otwórzmy teraz po jednym dokumencie każdego rodzaju. Jak łatwo zauważyć, menu Edit zmienia się w zależności od tego, który dokument jest w danym momencie aktywny. Również pasek tytułowy aplikacji zmieniać się będzie odpowiednio - jest to efekt działania związanych z danym typem dokumentu zasobów. Gotową aplikację MDICoin widać na rysunku 21.9. Rysunek 21.9. Aplikacja MDICoin PATRZ TAKŻE • O menu pisaliśmy w rozdziale 13. • Więcej informacji na temat kontekstu urządzenia znaleźć można w rozdziale 15. • Więcej na temat różnych efektów graficznych mówiliśmy w rozdziale 16. Rozdział 22 Drukowanie i podgląd wydruku Wzbogacanie standardowej aplikacji o opcje drukowania i podglądu wydruku Dołączanie dokumentów wielostronicowych Zabezpieczanie przed zniekształceniami wydruku Bezpośrednie drukowanie dokumentu z pominięciem szkieletu aplikacji Drukowanie za pomocą funkcji szkieletu aplikacji Tworzone przez kreator AppWizard szkielety aplikacji SDI i MDI oferują standardowe narzędzia i funkcje obsługujące drukowanie i oglądanie podglądu wydruku. Opcje te można wyłączyć na stronie 4 kreatora AppWizard, wyłączając Printing and Print Preview. Pozostawienie ich nie skomplikuje jednak zbytnio projektu, dlatego nie warto ich usuwać, zwłaszcza że mogą się nam później przydać. Większość procesów związanych z drukowaniem obsługiwanych jest przez kontekst urządzenia i obiekty GDI (mówiliśmy o nich w rozdziale 15). Szkielet aplikacji dostarcza nam kontekstu urządzenia obsługującego drukowanie strony dokumentu; działa on mniej więcej tak samo jak zwykły kontekst urządzenia. Niezależność obsługi drukowania od zainstalowanej drukarki Przed pojawieniem się systemu Windows większość systemów operacyjnych nie posiadało sterowników drukarki przystosowanych do obsługi grafiki. Powodowało to, że twórcy aplikacji zmuszeni byli tworzyć dla własnych programów komplety sterowników obsługujących najpopularniejsze drukarki, W ten sposób każda działająca w komputerze aplikacja miała własne sterowniki dla tych samych drukarek, niepotrzebnie du-plikując zajmujące się w gruncie rzeczy tym samym fragmenty kodu programu. 564___________________________________ Poznaj Visual C++6 Szczęśliwie wprowadzenie przez system Windows kontekstu urządzenia pozwala obecnie traktować wewnątrz programu różnego rodzaju urządzenia, takie jak drukarka, ekran komputera czy ploter, tak jakby były one po prostu dwuwymiarową powierzchnią, na której ma zostać wykonany rysunek. W ten sposób sterownik urządzenia pisany jest tylko raz, przez producenta danego urządzenia. PATRZ TAKŻE • Więcej informacji na temat szkieletu aplikacji SDI znaleźć można w rozdziale 12. * Kompletny opis działania kontekstu urządzenia znaleźć można w rozdziale 13. Korzystanie ze standardowych funkcji obsługujących drukowanie Szkielet aplikacji SDI (aplikacji jednodokumentowej) obsługuje drukowanie zawartości widoku w oparciu o informacje przechowywane w dokumencie. Ponieważ informacje te są właśnie wyświetlane w widoku, po dodaniu do aplikacji odpowiednich opcji drukowania wydrukowanie ich nie powinno stanowić większego problemu. Aby wyświetlić rysunek w widoku, aplikacja przywołuje funkcję onDraw (). Podobna do niej funkcja OnPrint () przywoływana jest, gdy widok obsługuje informacje związane z drukowaniem. Bardzo często drukowanie wykonywane jest tutaj za pomocą dokładnie tego samego kodu co w funkcji OnDraw(). W takiej sytuacji nie ma potrzeby implementowania funkcji OnPrint(); szkielet aplikacji implementuje ją automatycznie w klasie bazowej CView i wewnątrz funkcji OnPrint() przyzywa funkcję OnDraw(). Drukarka jest tutaj traktowana tak samo jak ekran komputera, ponieważ oferuje ona własny kontekst urządzenia zastępujący kontekst urządzenia ekranu. Funkcja OnDraw() ustala, czy przesłany jej kontekst urządzenia jest kontekstem urządzenia ekranu, czy też drukarki. Funkcje rysujące, działające w obu przypadkach, są jednak na tyle podobne, że nawet i bez sprawdzenia program zadziała prawidłowo. Standardową obsługę drukowania możemy obejrzeć tworząc za pomocą kreatora AppWizard aplikację SDI. W tym celu pozostawimy w kroku czwartym kreatora zaznaczoną opcję Printing and Print Preyiew (oznacza to, że możemy po prostu kliknąć Finish na pierwszej stronie kreatora), a projekt nazwiemy Printit. Standardowa obsługa drukowania Standardowe funkcje umożliwiające obsługę drukowania i podglądu wydruku dostępne są tylko w aplikacjach SDI i MDI. W aplikacjach opartych na oknach dialogowych obsługę drukowania trzeba zaprogramować samodzielnie. Drukowanie i podgląd wydruku 565 Aby zobaczyć jak program działa, będziemy musieli najpierw dostarczyć mu rysunek, który będzie można wydrukować. Rysunek taki utworzymy za pomocą przedstawionej na listingu 22.1 funkcji OnDraw klasy CPrintltView (klasy identycznej z jej bazową klasą cview). Na rysunku 22.1 zobaczyć można jak wyglądać będzie wydruk. Sam składający się z grafiki i tekstu rysunek testowy nie jest taki ważny, pomoże nam jednak porównać efekt wyświetlony na ekranie z wydrukiem. Listing 22.1. LST23_1.CPP - tworzenie w funkcji OnDraw przykładowego rysunku do wydrukowania 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void CPrintltView::OnDraw(CDC* pDC) ( CPrintItDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc) ; // TODO: Tutaj dodaj resztę kodu malującego widok II ** Zdefiniuj tryb odwzorowywania w jednostkach metrycznych pDC- >SetMapMode O (MM_LOMETRIC) ; // ** Zadeklaruj i utwórz czcionkę o wysokości 2,2 cm CFont fnBig; fnBig.CreateFont(220,O,O,O,FW_HEAVY,FALSE,FALSE,O, ANSI_CHARSET,OUT_DEFAULT_PRECI S, CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY, FF_SWISS+VARIABLE_PITCH,"Arial") ; //** Wybierz nową czcionkę i zachowaj poprzednią CFont* pOldFont = pDC->SelectObject(SfnBig); //** Zadeklaruj prostokąt obszaru roboczego CRect rcCIient; GetCIientRect(SrcCIient) ; // ** Przekształć na logiczne jednostki odwzorowywania pDC- >DPtoLP(SrcCIient) ; // ** Zdefiniuj kilka zmiennych używanych podczas rysowania const int nPoints = 50; int xm = rcCIient.Width(); int ym = rcCIient.Height(); double dAspW = xm/(double)nPoints; double dAspH = ym/(double)nPoints; 566__________ Poznaj Visual C++6 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 ) // ** Wybierz czarne pióro CPen* pOldPen = (CPen*)pDC->SelectStockObject(BLACK_PEN), // ** Narysuj linie for(int i=0;iMoveTo(xo,0); @ pDC- >LineTo(xm,yo); © pDC->LineTo(xm- xo,ym); © pDC->LineTo(0,ym-yo); © pDC->LineTo(xo,0); © // ** Przywróć poprzednie pióro pDC- >SelectObject(pOldPen) ; // ** Wpisuj tekst od góry pDC- >SetTextAlign(TA_CENTER+TA_BASELINE) ; pDC->SetBkMode(TRANSPARENT) ; // ** Wpisz szary tekst pDC->SetTextColor(RGB(64,64,64)) ; pDC->TextOut(xm/2,ym/2,"Sample Print"); © // ** Przywróć pierwotną czcionkę pDC- >SelectObject(pOldFont) ; O Ustawiony został tryb odwzorowywania MM_LOMETRIC, definiujący rozmiar logicznej jednostki współrzędnych na 1/10 milimetra. @ Malujemy widoczną na rysunku obok rozetę. © W linii 61 wyświetlamy pośrodku okna tekst „Sample Print" (Przykładowy wydruk). Drukowanie i podgląd wydruku 567 Rysunek 22.1. Rysunek testujący wydruk w oknie programu Printit Mimo 'iż funkcja OnDraw () jest dość długa, w jej kodzie nie ma nic zaskakującego. Funkcja rysuje po prostu wewnątrz obszaru roboczego serię linii, a pośrodku wpisuje ustalony tekst. Warto zauważyć, że w linii 9 tryb odwzorowywania został zdefiniowany jako MM_LOMETRIC. W ten sposób współrzędne będą definiowane w jednostkach logicznych równych 1/10 milimetra (patrz rozdział 15). W linii 13 definiowana jest czcionka o wysokości 2,2 cm, używana później w linii 61 do namalowania przykładowego tekstu. W liniach 40-50 w oparciu o współrzędne obszaru roboczego rysujemy seńę linii tworzących rozetę. Szczegóły funkcji rysującej nie są w tym momencie ważne, nasz przykład jest ilustracją zagadnień związanych z drukowaniem. Tryby odwzorowywania a ekran komputera Można zauważyć, że po wyświetleniu rysunku na ekranie czcionka nie będzie miała dokładnie wysokości 2,2 cm. Dzieje się tak dlatego, że system Windows nie potrafi dopasować się dobrze do rozdzielczości niektórych typów monitorów (nawet gdy rozdzielczość jest ustawiona prawidłowo w Panelu sterowania). Sterowniki drukarek są jednak pod tym względem dokładniejsze i na wydruku czcionka będzie miała wysokość dokładnie 2,2 cm. Po zbudowaniu i uruchomieniu aplikacji zawierającej przedstawioną tutaj funkcję On-Draw (), na ekranie wyświetlony zostanie obrazek widoczny na rysunku 22. l. Pojawia się pytanie: co należy zrobić, aby ten rysunek wydrukować? Praktycznie już nic, ponieważ aplikacja obsługując drukowanie go sięgnie do funkcji OnDraw () przesyłając jej zamiast kontekstu urządzenia okna kontekst urządzenia drukarki. 568_____________________________________Poznaj Visual C++ 6 Jeśli teraz w menu File aplikacji Printit wybierzemy polecenie Print Pre^iew, w lewym górnym rogu zobaczymy pomniejszoną wersję rysunku. Jednak widoczny na ekranie tekst będzie zdecydowanie za duży i będzie nachodził na rozetę. Nie jest to wina szkieletu aplikacji, funkcja otrzymała po prostu złe współrzędne, które nie pasują do kontekstu urządzenia drukarki. Problem generuje przyzywana w linii 23 funkcja GetdientRect (). Funkcja GetdientRect () jest funkcją składową widoku, a nie kontekstu urządzenia. Nie sprawia jednak problemów przy rysowaniu na ekranie, ponieważ kontekst urządzenia jest tu taki sam jak rozmiary prostokąta obszaru roboczego okna. Jednak kontekst urządzenia dla drukarki jest mniejszy, więc rozeta zostanie odpowiednio pomniejszona, podczas gdy czcionka tekstu będzie z uwagi na tryb odwzorowywania zawsze tych samych rozmiarów (2,2 cm). PATRZ TAKŻE * O pokrywaniu funkcji OnDraw () pisaliśmy w rozdziale 15. • Więcej na temat rysowania linii znaleźć można w rozdziale 16. 4 Więcej informacji o rysowaniu tekstu znaleźć w rozdziale 17. Pokrywanie funkcji OnPrint() Aby naprawić ten problem należy przesłać funkcji OnDrawO w miejsce prostokąta okna prostokąt drukarki. Szczęśliwie szkielet aplikacji przyzywa wirtualną funkcję, którą możemy pokryć w widoku wpisując tam odpowiednie informacje. Jak wspomnieliśmy wcześniej, funkcja ta nosi nazwę OnPrint () i jest bardzo podobna do funkcji OnDraw (). Kiedy program odmalowuje zawartość okna przywoływana jest funkcja OnDraw (), a gdy zawartość okna jest drukowana, przyzywana jest funkcja onprint (). Zastanawiające jest w jaki sposób funkcja OnDraw () wykorzystywana jest do wyświetlania podglądu wydruku. Standardowa implementacja funkcji OnPrint () w klasie CView przywołuje po prostu funkcję OnDraw (), przesyłając jej kontekst urządzenia drukarki. Funkcje wirtualne i polimorfizm Funkcja wirtualna to funkcja zdefiniowana w klasie bazowej zawierająca często kod wykonujący pewne standardowe operacje. Wewnątrz klasy wywodzącej się z klasy bazowej możemy zdefiniować własną wersję tej funkcji, wykonującą oprócz standardowych operacji również zlecone przez nas zadania. Zawsze gdy przywoływana jest funkcja o danej nazwie (nawet w obiekcie klasy bazowej), przyzywana też jest funkcja z klasy pochodnej (ang. inherited). Technika ta nosi nazwę pokrywania funkcji wirtualnych (ang. virtual function ovemding). Funkcja pokrywająca (ang. overri-de) może również odwołać się do swojej wersji z klasy bazowej bezpośrednio przywołując pokrywaną (ang. ovemden) funkcję, podając jako jej zakres kompetencji (ang. scope) klasę bazową. W ten sposób funkcje pokrywające mogą korzystać z możliwości funkcji klasy bazowej. Wirtualne funkcje są w języku C++ podstawowym sposobem implementacji polimorfizmu (ang. polymorphism). 568_____________________________________Poznaj Visual C++ 6 Jeśli teraz w menu File aplikacji Printit wybierzemy polecenie Print Pre^iew, w lewym górnym rogu zobaczymy pomniejszoną wersję rysunku. Jednak widoczny na ekranie tekst będzie zdecydowanie za duży i będzie nachodził na rozetę. Nie jest to wina szkieletu aplikacji, funkcja otrzymała po prostu złe współrzędne, które nie pasują do kontekstu urządzenia drukarki. Problem generuje przyzywana w linii 23 funkcja GetCiientRect (). Funkcja GetCiientRect () jest funkcją składową widoku, a nie kontekstu urządzenia. Nie sprawia jednak problemów przy rysowaniu na ekranie, ponieważ kontekst urządzenia jest tu taki sam jak rozmiary prostokąta obszaru roboczego okna. Jednak kontekst urządzenia dla drukarki jest mniejszy, więc rozeta zostanie odpowiednio pomniejszona, podczas gdy czcionka tekstu będzie z uwagi na tryb odwzorowywania zawsze tych samych rozmiarów (2,2 cm). PATRZ TAKŻE » O pokrywaniu funkcji OnDraw () pisaliśmy w rozdziale 15. • Więcej na temat rysowania linii znaleźć można w rozdziale 16. 4 Więcej informacji o rysowaniu tekstu znaleźć w rozdziale 17. Pokrywanie funkcji OnPrint() Aby naprawić ten problem należy przesłać funkcji OnDraw () w miejsce prostokąta okna prostokąt drukarki. Szczęśliwie szkielet aplikacji przyzywa wirtualną funkcję, którą możemy pokryć w widoku wpisując tam odpowiednie informacje. Jak wspomnieliśmy wcześniej, funkcja ta nosi nazwę OnPrint () i jest bardzo podobna do funkcji OnDraw (). Kiedy program odmalowuje zawartość okna przywoływana jest funkcja OnDraw (), a gdy zawartość okna jest drukowana, przyzywana jest funkcja Onprint (). Zastanawiające jest w jaki sposób funkcja OnDraw () wykorzystywana jest do wyświetlania podglądu wydruku. Standardowa implementacja funkcji Onprint () w klasie CView przywołuje po prostu funkcję OnDraw (), przesyłając jej kontekst urządzenia drukarki. Funkcje wirtualne i polimorfizm Funkcja wirtualna to funkcja zdefiniowana w klasie bazowej zawierająca często kod wykonujący pewne standardowe operacje. Wewnątrz klasy wywodzącej się z klasy bazowej możemy zdefiniować własną wersję tej funkcji, wykonującą oprócz standardowych operacji również zlecone przez nas zadania. Zawsze gdy przywoływana jest funkcja o danej nazwie (nawet w obiekcie klasy bazowej), przyzywana też jest funkcja z klasy pochodnej (ang. inherited). Technika ta nosi nazwę pokrywania funkcji wirtualnych (ang. virtual function ovemding). Funkcja pokrywająca (ang. overri-de) może również odwołać się do swojej wersji z klasy bazowej bezpośrednio przywołując pokrywaną (ang. ovemden) funkcję, podając jako jej zakres kompetencji (ang. scope) klasę bazową. W ten sposób funkcje pokrywające mogą korzystać z możliwości funkcji klasy bazowej. Wirtualne funkcje są w języku C++ podstawowym sposobem implementacji polimorfizmu (ang. polymorphism). Drukowanie i podgląd wydruku 569 Funkcja OnPrint () nie musi przyzywać funkcji OnDrawO . Możemy pokryć funkcję OnPrint (), aby wydrukować zupełnie inny rysunek - większość aplikacji stara się jednak drukować dokładnie to co widzi użytkownik. W tym celu wykorzystują po prostu funkcję OnDraw () przesyłając jej kontekst urządzenia drukarki. Pokrywanie wirtualnej funkcji OnPrint ( ) 1. W oknie ProjectWorkspace kliknij zakładkę GlassYicw. 2. Kliknij znak plus u góry, aby otworzyć zestawienie klas projektu. 3. Aby wyświetlić odpowiednie menu skrótów, kliknij prawym klawiszem myszy klasę. do której chcesz dodać pokrywającą funkcję OnPrint () (tutaj CPrintltView). 4. Wybierz polecenie Add Virtual Function, aby wyświetlić okno dialogowe New Vii tuał Ovemde. 5. Na liście New Virtual Functions znajdź wirtualną funkcję OnPrint. 6. Aby rozpocząć edycję funkcji OnPrintO, wciśnij przycisk Add and I (Sis Standardowa funkcja pokrywająca OnPrint () wygląda tak: void CPrintltView: :OnPrint (CDC* pO:.', CPn nt int ri* pl"r-', { // TODO: Tutaj dodaj własny kcd CView::OnPrint(pDC, pinfo); } Najbardziej rzucającą się w oczy różnicą między tą funkcją a funkcją OnDraw o JCS! wskaźnik pinfo do obiektu cprintinfo. W obiekcie tym przechowywane są niezbędne informacje na temat bieżącego wydruku, przede wszystkim potrzebne nam wymiary pro stokąta kontekstu urządzenia drukarki. Klasa cprintinfo posiada wiele bardzo nck.i-wych zmiennych składowych. Niektóre z nich przedstawione zostały w tabeli 22.1 Standardowa funkcja OnPrint () klasy CView Standardowa funkcja pokrywająca tworzona przez kreatora przyzywa funkcję On-Print () klasy bazowej CView. Jeśli zajrzymy do kodu źródłowego funkcji znajdującego się w module źródłowym . . \MFC\SRC\VIEWCORE.CPP w katalogu języka Vi-suał C++, zobaczymy, w jaki sposób działa automatyczna obsługa drukowania. Funkcja CView:: OnPrint () przywołuje po prostu funkcję OnDraw () przesyłając jej wskaźnik kontekstu urządzenia pDC. 570_____________________________________Poznaj Visual C++ 6 Tabela 22.1. Niektóre zmienne składowe klasy CPrintinfo Nazwa zmiennej Opis zawartości m_nCurPage Bieżąca strona w wydruku wielostronicowym m_nNumPreviewPages l lub 2 - w zależności od liczby wyświetlonych w podglądzie wydruku stron m_rectDraw Rozmiary prostokąta drukowanej strony m_pPD Wskaźnik do klasy CPrintDialog, jeśli korzystamy z okna dialogowego Print m_bDirect Wartość TRUE, jeśli okno dialogowe Print ma zostać pominięte in_bPreview Wartość TRUE, jeśli znajdujemy się w podglądzie wydruku m_strPageDesc Sformatowany łańcuch pomagający generować numer strony m_lpUserData Wskaźnik do obiektu przechowującego dane użytkownika Niektóre z pozostałych zmiennych składowych klasy CPrintinfo opisane zostaną jeszcze w dalszej części rozdziału, na początek potrzebne są nam jednak wymiary prostokąta wydruku. Zmienna m_rectDraw przechowuje rozmiary prostokąta wydruku bieżącej drukowanej strony. Możemy wykorzystać je w kontekście urządzenia wewnątrz funkcji OnDraw (). Struktury tej nie możemy przesłać do funkcji OnDraw (), możemy jednak przekopiować odpowiednie wymiary prostokąta (ściślej, współrzędne prawego dolnego rogu, które można traktować jak wymiary, ponieważ lewy górny róg ma współrzędne O*, 0) do zmiennej składowej przechowywanej w klasie cprintview. W tym celu po komentarzu // TODO, ale przed odwołaniem do funkcji cview: :0n-Print () należy wpisać następujący fragment kodu: // ** Kopiuj rozmiary prostokąta wydruku z struktury pinfo if (pinfo) m rcPrintRect = plnfo->m rectDraw; W ten sposób zachowamy wymiary prostokąta wydruku w zmiennej składowej m_rcPrintRect klasy CPrintltView. Musimy teraz zadeklarować tę zmienna składową klikając prawym klawiszem myszy w panelu ClassView okna ProjectWorkspace klasę CPrintltView i wybierając w menu skrótów polecenie Add Member yariable. Typ zmiennej wpisywany w polu Variable Type to oczywiście CRect a jej deklaracja to m_rcPrintRect. Zmienna powinna zostać zdefiniowana jako prywatna, żeby ukryć ją przed innymi klasami. ' przyp. tłum. Drukowanie i podgląd wydruku 57 l Publiczne, prywatne i chronione Każda funkcja i zmienna składowa zadeklarowana w definicji klasy może zostać oznaczona jako publiczna, prywatna lub chroniona. Definiujemy w ten sposób, jak dana zmienna lub funkcja jest dostępna dla funkcji innych klas. Zmienna lub funkcja publiczna dostępna jest dla wszystkich funkcji, niezależnie do jakiej klasy należą. Zmienna lub funkcja prywatna dostępna jest tylko dla zmiennych składowych tej samej klasy. Zmienna lub funkcja chroniona dostępna jest również tylko dla funkcji składowych tej samej klasy oraz dodatkowo dla tych funkcji składowych klas z niej wywiedzionych, które zostały zadeklarowane jako publiczne. W języku C++ możemy w ten sposób zwiększyć bezpieczeństwo kodu programu, pozwalając na sięganie do obiektów tylko za pomocą specjalnych (deklarowanych jako publiczne) funkcji dostępu. Dzięki temu znacznie zmniejszamy ryzyko niepożądanych modyfikacji zmiennych i usuwamy z programu jedno z potencjalnych źródeł błędów. PATRZ TAKŻE • O klasach widoków i klasie cview pisaliśmy w rozdziale 12. Kontekst urządzenia drukarki Kontekst urządzenia, z którego korzysta funkcja onprint () różni się trochę od kontekstu urządzenia ekranu. Prawdopodobnie będzie mieć mniej kolorów i będzie większy niż kontekst oferowany przez ekran. Pozostałe atrybuty można wykorzystywać do rysowania dokładnie w taki sam sposób jak w kontekście urządzenia ekranu. Właśnie dzięki tym podobieństwom możemy wykorzystywać funkcję OnDraw () zarówno do drukowania, jak i do rysowania w widoku, w taki sam sposób jak w przypadku funkcji cview:: OnPrint () klasy bazowej. Kontekst urządzenia przechowuje znacznik, którego wartość można sprawdzić za pomocą funkcji isPrinting (). Znacznik ten pozwala ustalić, czy korzystamy z kontekstu urządzenia ekranu, czy kontekstu urządzenia drukarki. Wiedząc, z którego kontekstu urządzenia korzystamy, będziemy mogli przekształcić obraz wyświetlany na ekranie w obraz drukowany lub po prostu zmienić odpowiednio rozmiary wyświetlanego obrazu, tak aby nadawał się do wydruku. W tworzonym programie pozostaje nam tylko wykorzystać współrzędne zapisane w zmiennej m_PrintRect w funkcji OnDraw (), wtedy gdy funkcja ta jest wykorzystywana do drukowania. Konieczne jest w tym przypadku skorzystanie z funkcji isprinting (), aby ustalić, czy w danym momencie funkcja OnDraw () powinna korzystać z prostokąta obszaru roboczego okna, czy z prostokąta drukarki. Funkcja OnDraw () przedstawiona została na listingu 22.2, a jej efekt na rysunku 22.2. 572___________________________________Poznaj Visual C++ 6 Listing 22.2. LST23_2.CPP - funkcja OnDrawf) sprawdzająca, czy wykorzystujemy ją do rysowania na ekranie, czy do drukowania 1 // Zadeklaruj obszar roboczy 2 CRect rcCIient; 3 4 // ** Sprawdź, czy kontekst urządzenia jest kontekstem drukarki 5 if (pDC->IsPrinting()) 6 ( 7 // ** Jeśli tak, użyj prostokąta obszaru wydruku 8 rcCIient = m_rcPrintRect; 9 } 10 else 11 ( 12 // ** Jeśli nie, skorzystaj z prostokąta obszaru roboczego 13 GetCIientRect(SrcCIient) ; 14 } 15 16 // P.rzeksztaić w jednostki logiczne 17 pPC->DPtoLP(&rcClient); O__________________________________ O Tutaj zmieniamy współrzędne wyrażone w pikselach na współrzędne w jednostkach logicznych równych 1/10 milimetra. W linii 5 w instrukcji if przyzywana jest funkcja isPrinting (). Funkcja ta zwraca wartość TRUE, jeśli kontekst urządzenia jest kontekstem drukarki (lub podglądu wydruku), a FALSE, gdy jest to jakikolwiek inny kontekst urządzenia. Jeśli okaże się, że funkcja wykorzystywana jest do drukowania, przypisujemy (w linii 8) prostokąt drukowanej strony zmiennej rcCIient. Jeśli nie, pobieramy prostokąt obszaru roboczego okna za pomocą funkcji GetCIientRect () (linia 13). Ponieważ korzystamy z trybu odwzorowywania, musimy przekształcić zarówno wymiary prostokąta wydruku, jak i prostokąta rysunku wyświetlanego na ekranie w jednostki logiczne. Zajmuje się tym w linii 17 funkcja DPtoLP (). Po dodaniu kodu z linii 4-14 do poprzedniej wersji funkcji OnDraw (), a następnie zbudowaniu i uruchomieniu aplikacji, podgląd wydruku przedstawiany przez program będzie znacznie lepszy (rysunek 22.2). Dmkowanie i podgląd wydruku 573 Pagel ^Kil?!,'1.^: ! i^.i.a^J:.':1:'^;,,^:^;''!!'':';^?-1 :a;BSSf Rysunek 22.2. Podgląd wydruku utworzony w oparciu o wymiary prostokąta strony wydruku. PATRZ RÓWNIEŻ • Szczegółowy opis trybów odwzorowywania można znaleźć w rozdziale 15. Odpowiedni stosunek długości do szerokości strony Ponieważ kartka papieru w drukarce jest zazwyczaj długa i wąska, przedstawiany w oknie podgląd wydruku zostaje odpowiednio ściśnięty. Stosunek długości do szerokości (ang. aspect ratio) jest bardzo ważny przy drukowaniu obrazu z ekranu. Aby zapobiec niepożądanemu rozciąganiu się rysunku w jedną bądź w drugą stronę przy drukowaniu, musimy zadbać, aby stosunek ten nie zmieniał się po drodze od monitora do drukarki. Funkcja przedstawiona na listingu 22.2 nie zajmowała się tym problemem. Jednak dobrze działający program powinien zadbać o poprawność wydruku także i w tym zakresie. Najlepszą strategią jest w tym przypadku sprawdzenie, które rozwiązanie pozwoli na wyświetlenie większego rysunku: czy dopasowanie jego szerokości do szerokości kartki, czy wysokości do wysokości kartki, a następnie takie skrócenie drugiego z wymiarów, aby stosunek długości do szerokości pozostał niezmieniony. Problemy z niektórymi drukarkami Większość drukarek drukuje zachowując proporcje długości do szerokości piksela takie same jak na ekranie (1:1). Jeśli jednak przyjrzymy się drukarkom termicznym, na przykład w faksach, zauważymy, że potrafią one niekiedy znacznie zmieniać stosunek długości do szerokości drukowanych pikseli. 574_____________________________________Poznaj Visual C++ 6 W tym celu potrzebne nam będą informacje o rozmiarach papieru i stosunku długości do szerokości strony używanej w drukarce. Do zdobywania tych (jak również innych) informacji służy funkcja GetDeviceCaps () kontekstu urządzenia. Przesyłając funkcji GetDeviceCaps () znacznik ASPECTX lub ASPECTY możemy zdobyć informacje o stosunku długości do szerokości drukowanych pikseli. Jeśli stosunek ten wynosi 1:1, piksel jest kwadratowy; w przeciwnym wypadku jest wydłużony i po wydrukowaniu rysunek może mieć inne proporcje niż na ekranie. W takiej sytuacji należy zdecydować, wzdłuż której osi dopasowanie da na wydruku większy rysunek, oczywiście przy zachowaniu tych sa- mych proporcji długości do szerokości co na ekranie. W ten sposób unikniemy ewentualnych generowanych przez drukarkę zniekształceń rysunku. Kod odpowiednio zmodyfikowanej w tym celu funkcji OnDraw () przedstawiony został na listingu 22.3. Listing 22.3. LST23_3.CPP - zachowywanie tych samych proporcji drukowanego rysunku co na ekranie 1 //** Zadeklaruj i pobierz prostokąt obszaru roboczego 2 CRect rcCIient; 3 GetClientRect(&rcClient); 4 5 // ** Sprawdź, czy używamy kontekstu urządzenia drukarki B if (pDC->IsPrinting()) 7 { 8 // ** Ustal proporcję szerokość wydruku .'szerokość okna 9 double dWidthRatio=(double)m_rcPrintRect.Width()/ 10 (double)rcCIient.WidthO; 11 12 // ** Ustal proporcję wysokość wydruku:wysokość okna 13 double dHeightRatio=(double)m_rcPrintRect.Height()/ O 14 (double)rcCIient.Height(); 15 16 // ** Wylicz proporcje długości do szerokości dla drukarki 17 double dAspect=(double)pDC->GetDeviceCaps(ASPECTX)/ @ 18 (double)pDC->GetDeviceCaps(ASPECTY) ; 19 20 // ** Wylicz nową, dostosowaną proporcjonalnie wysokość 21 int nHeight=(int)(rcCIient.HeightO * 22 dWidthRatio * dAspect ) ; 23 24 // ** Wylicz nową, dostosowaną proporcjonalnie szerokość 25 int nWidth=(int) (rcCIient.WidthO * 26 dHeightRatio * (1.0 / dAspect) ); 27 Dmkowanie i podgląd wydruku 575 28 // ** Zdefiniuj rozmiary prostokąta wydruku 29 rcClient=m_rcPrintRect; 30 31 // ** Sprawdź jak lepiej dopasować rysunek do strony 32 if (nHeight > nWidth) ® 33 ( 34 // ** Jeśli najlepiej wzdłuż, dostosuj szerokość 35 rcCIient.BottomRight().x= 36 m_rcPrintRect.TopLeft().x + nWidth; 37 } 38 else 39 ( 40 // ** Jeśli najlepiej wszerz, dostosuj wysokość 41 rcCIient.BottomRight() .y= 42 m_rcPrintRect.TopLeft().y + nHeight; 43 } 44 } 45 46 // Przekształć na jednostki logiczne 47 pDC->DPtoLP(SrcCIient); O Ustalamy różnicę w stosunku długości do szerokości dla strony wyświetlanej na ekranie i strony drukowanej przez drukarkę. @ W oparciu o informacje uzyskane za pomocą kontekstu urządzenia wyliczamy różnice wynikające ze sposobu drukowania pikseli przez drukarkę. © W zależności od tego, w którym kierunku dopasowanie da większy rysunek, dostosowujemy jego wymiary. Warto zauważyć, że zarówno gdy rysunek jest drukowany, jak i gdy jest wyświetlany na ekranie, wykorzystujemy wymiary okna uzyskane w linii 3 za pomocą funkcji GetC-lientRect (). Jeśli rysunek ma zostać wyświetlony na ekranie, nie musimy go dalej dopasowywać i jest on rysowany tak jak w pierwszym przykładzie. Jeśli jednak funkcja isPrinting () przyzywana w linii 6 zwróci wartość TRUE, oznacza to, że rysunek jest drukowany i należy wykonać seńę dostosowań. Najpierw musimy ustalić stosunek długości okna do długości kartki i szerokości okna do szerokości kartki. Znajdujemy je odpowiednio w liniach 9 i 13 dzieląc wymiary kartki przez wymiary okna. Następnie trzeba wyliczyć różnice wynikające ze sposobu drukowania pikseli. W linii 17 przyzywamy funkcję GetDeviceCaps (), a ustalony stosunek długości do szerokości drukowanego piksela przechowujemy w zmiennej dAspect. 576 Poznaj Visual C++ 6 W oparciu o te wartości możemy teraz wyliczyć relatywną szerokość i wysokość po wprowadzeniu poprawek wynikających z różnic w rozmiarach prostokąta obszaru roboczego i sposobu przedstawiania pikseli (odpowiednio w liniach 21 i 25). W oparciu o te wartości będziemy mogli bez obawy zniekształceń dostosować wymiary rysunku do wymiarów strony. Najpierw jednak musimy zdecydować, czy będziemy dopasowywać rysunek wzdłuż, czy wszerz. Wyrażenie warunkowe w linii 32 sprawdza, w którym przypadku rysunek będzie większy. Oznacza to, że jeśli rysunek w oknie był wysoki i wąski, najlepiej dopasować do rozmiarów kartki wysokość i odpowiednio do zmian wysokości dostosować szerokość. W zależności od tego, czy korzystniejsze jest dopasowanie w poziomie, czy w pionie, w linii 35 lub w linii 42 odpowiednio dostosowywana jest współrzędna x lub y prawego dolnego rogu rysunku. Niektóre powszechnie spotykane opcje drukowania Aplikacje graficzne udostępniają zazwyczaj kilka dodatkowych opcji pozwalających na zmienianie sposobu dostosowywania pionowego i poziomego wymiaru drukowanego rysunku. Opcja, którą tutaj dołączyliśmy, powszechnie znana jest pod nazwą Best Fit. Inną powszechnie stosowaną jest opcja Size to Page, dopasowująca wymiary rysunku do wymiarów strony (bez zachowania proporcji między wysokością a szerokością). Czasami można również natknąć się na opcję Original Image Size, drukującą (zazwyczaj zorientowany centralnie na stronie) rysunek z zachowaniem proporcji, rozmiarów rysunku widocznego na ekranie (z uwagi na dość duże rozdzielczości drukarek, rysunek wychodzi wtedy raczej mały). W linii 29 prostokątowi obszaru roboczego przypisywane są wymiary prostokąta wydruku, a następnie rysunek jest dopasowywany do rozmiarów kartki, tak aby był możliwie jak największy. Po tym ostatnim dopasowaniu program przystępuje do rysowania. Tak zmodyfikowana aplikacja będzie drukować lub wyświetlać podgląd wydruku rysunku, zachowując te same proporcje długości do szerokości co na ekranie. Jeśli rozciągniemy okno w pionie, wydruk będzie dostosowywać się do wysokości kartki, modyfikując szerokość tak, aby ich wzajemne proporcje pozostały zachowane. PATRZ TAKŻE • Więcej informacji na temat możliwości różnych urządzeń można znaleźć w rozdziale 15. Stronicowanie i orientowanie wydruku Bardzo często aplikacja zmuszona jest obsługiwać nie tylko wydruk pojedynczej strony, ale również druk długich i często skomplikowanej konstrukcji dokumentów. Szkielet aplikacji także i w tym przypadku przychodzi nam z pomocą, dostarczając okna dialogowego Print Setup i systemu pozwalającego drukować i oglądać określoną liczbę stron. Drukowanie i podgląd wydmku _____ 577 Drukowanie pojedynczego rysunku na więcej niż jednej stronie s;;5 ^ Może się zdarzyć, że będziemy musieli wydrukować pojedynczy duży rysunek podzielony na c?ęsd na więcej niż jednej stronie. W Tym celu trzeba będzie w kontekście urządzenia, podzielić rysunek. Tutaj przydaje się klasa CScroliYiew, która dostarcza ułatwiającej dzielenie funkcji ScrollTo?osit.'on{).i pozwala na stosunkowo swobodne skalowanie rysunku. Niemniej nadaj będziemy musieli ręcznie obsługiwać przenoszenie kontekstu urządzenia między poszczególnymi stronami. Definiowanie pierwszej i ostatniej strony Podstawową sprawą przy drukowaniu wielostronicowych dokumentów jest zdefiniowanie pierwszej i ostatniej strony wydruku, co ustali również, ile stron ma zostać wydrukowanych. Kiedy rozpoczyna się drukowanie, wzywana jest wirtualna funkcja szkieletu aplikacji. Funkcja ta nosi nazwę OnPreparePrintingO i posiada tylko jeden parametr, obiekt pinfo klasy cprintinfo. Tutaj po raz pierwszy pojawia się w programie obiekt cprintinfp i tu po raz pierwszy mamy okazję dostosować go do naszych potrzeb. Funkcja OnPreparePrintingO jest przez kreator AppWizard automatycznie dołączana do aplikacji SDI, dzięki czemu nie musimy robić tego sami. Kod funkcji możemy obejrzeć klikając dwukrotnie w panelu ClassView funkcję składową OnPreparePrinting () klasy CPrintView. Kod funkcji powinien wyglądać mniej więcej tak: BOOL CPrintItView::OnPreparePrinting(CPrintInfo* pinfo) ( // standardowe przygotowania do drukowania return DoPreparePrinting (pinfo) ; } Domyślnie przyzywana jest tu funkcja DoPreparePrinting (), której jako parametr przesyłany jest wskaźnik pinfo do obiektu CPrintInfo. Funkcja DoPreparePrinting () definiuje odpowiedni kontekst urządzenia i jeśli drukujemy (a nie tylko oglądamy podgląd wydruku), przyzywa standardowe okno dialogowe Print (Drukuj). Okno to opiszemy dokładniej dalej. Zanim jednak zawezwiemy funkcję DoPreparePrinting (), musimy zdefiniować zakres drukowanych stron modyfikując odpowiednio obiekt cprintinfo. W tym celu przed komentarzem// standardowe przygotowania do drukowania należy wpisać: p!nfo->SetMinPage(2) ; pInfo->SetMaxPage(8) ; Przedstawione tutaj funkcje składowe klasy cprintinfo modyfikują obiekt CPrintln-fo przesyłany za pomocą wskaźnika pinfo, tak aby drukowanie rozpoczynało się od strony drugiej (SerMinPage ()), a kończyło się na stronie 8 (SetMaxPage ()). 578___________________________________Poznaj Visual C++ 6 W ten sposób w trakcie drukowania dokumentu funkcja OnPrint () jest przyzywana sześć razy. Za każdym razem przesyłana jest jej zmienna składowa pinfo->m_nCurPage podająca bieżącą drukowaną stronę. W zależności od aplikacji, sposób definiowania liczby stron przeznaczonych do druku będzie się zmieniać. Jeśli przykładowo sprzedajemy płyty kompaktowe i chcemy wydrukować katalog oferowanych przez nas płyt, powinniśmy każdemu z krążków poświęcić jedną stronę. Jeśli więc mamy w ofercie 120 płyt, będziemy musieli wydrukować 120-stronicową broszurę. Jeśli natomiast tworzymy rozbudowane sprawozdanie lub ofertę biznesową zawierającą liczne dane liczbowe i wykresy, liczbę stron i wysokość poszczególnych elementów graficznych będziemy w programie ustalać dopiero po dokonaniu odpowiedniego ręcznego składu tekstu. Niezależnie od tego, kiedy ustalimy liczbę stron, gdy zostanie już ona zdefiniowana, musimy ją w funkcji OnPreparePrintO przypisać obiektowi CPrintInfo. Omijanie okna dialogowego Print Czasami nie ma potrzeby komplikowania użytkownikowi życia i wyświetlania okna dialogowego Print; możemy je pominąć przypisując w funkcji. OnPrepareprin— tino() zmiennej pInfo-s.in__bDirect wartość TRUE. ; ,','„::' Aby pokazać różnicę między drukowaniem zawartości okna a drukowaniem wielostronicowego dokumentu, możemy (co zostało pokazane na listingu 22.4) zamieścić w funkcji OnPrint () osobny kod odpowiedzialny za drukowanie w tym drugim przypadku. W tej wersji funkcji OnPrint () funkcja klasy bazowej cview:: OnPrint nie jest w ogóle wzywana, co oznacza również, że nie jest wykonywana funkcja OnDraw (). W tym przypadku obraz wydrukowany i obraz widoczny w oknie są zupełnie inne. Listing 22.4. LST23_4.CPP - zmienianie wydruku w zależności od drukowanej strony w funkcji OnPrint () 1 void CPrintItView::OnPrint(CDC* pDC, CPrintInfo* pinfo) 2 { 3 // TODO: Tutaj dodaj własny kod 4 5 // ** Utwórz i wybierz czcionkę 6 CFont fnTimes; 7 fnTimes.CreatePointFont(720,"Times New Roman",pDC); O 8 CFont* p01dFont=(CFont*)pDC->SelectObject(SfnTimes); 9 10 // ** Utwórz i wybierz pędzel 11 CBrush brHatch(HS_CROSS,RGB(64,64,64) ) ; 12 CBrush* pOldBrush = Drukowanie i podgląd wydruku 579 (CBrush*)pDC->SelectObj ect(SbrHatch) ; // ** Utwórz tekst strony CString strDocText; strDocText.Format("Page Number %d", plnfo-> ® m__nCurPage) ; pDC->SetTextAlign(TA_CENTER+TA_BASELINE) ; // ** Zdefiniuj kilka pomocniczych punktów CPoint ptCenter=pInfo- >m_rectDraw.CenterPoint() ; CPoint ptTopLeft-pInfo^n^rectDraw.TopLeft (); CPoint ptBotRight«pInfo->m_rectDraw. BottomRight () ; // ** Zdefiniuj rogi rombu CPoint ptPolyArray[4)= ( CPoint(ptTopLeft.x,ptCenter.y), ® . CPoint(ptCenter.x,ptTopLeft.y), ® CPoint(ptBotRight.x,ptCenter.y), ® CPoint(ptCenter.x,ptBotRight.y) ® }; // ** Narysuj romb pDC->Polygon(ptPołyArray,4) ; // ** Namaluj tekst pDC->TextOut(ptCenter.x,ptCenter.y,strDocText); O // ** Przywróć poprzedni pędzel l czcionkę pDC- >SelectObject(pOldFont) ; pDC->SelectObject(pOldBrush) ; 45 } O Za pomocą funkcji CreatePointFont () definiujemy czcionkę o rozmiarach 72 punktów. @ Z obiektu cprintinfo pobieramy numer bieżącej drukowanej strony. • © Definiujemy rogi rombu. O Drukujemy tekst zawierający numer bieżącej drukowanej strony. 580_____________________________________Poznaj Visual C++ 6 W liniach 6-12 definiowane są odpowiednie zasoby wykorzystywane do tworzenia drukowanego rysunku (czcionka i pędzel). Lepszym jednak miejscem na definiowanie tych zasobów jest opisana dalej w tym rozdziale funkcja OnBeginPrinting (). W linii 17 pokazaliśmy jak można modyfikować drukowany tekst w zależności od te- go, którą stronę właśnie drukujemy. W prawdziwej aplikacji zmienną m_nCurPage można wykorzystać do sięgania i operowania na kolejnych stronach dokumentu. W przykładzie z katalogiem płyt kompaktowych numer strony może być wykorzystywany do odwoływania się do odpowiedniego krążka, na przykład aby wydrukować razem z poświęconym mu tekstem odpowiednią okładkę. Nie mamy tutaj niestety miejsca, by przedstawić tak skomplikowany przykład, dlatego ograniczyliśmy się do wykorzystania zmiennej pinfo->m_curpage do drukowania numeru strony. W liniach 22-37 definiujemy kratkowany romb, który stanowić będzie tło tekstu. W linii 40 drukujemy pośrodku strony tekst podający numer strony, a w liniach 43 i 44 przywracamy dawną czcionkę i pędzel. W jaki sposób biblioteka MFC obsługuje podgląd wydruku? Podgląd wydruku obsługiwany jesi za pomocą specjalnej, nic opisanej w dokumentacji klasy CPreviewDC. Ten specjalny kontekst urządzenia działa tak samo jak kontekst urządzenia drukarki, ale wyświetla obraz na ekranie tak jak kontekst urządzenia ekranu. To nietypowe zachowanie kontekstu urządzenia konieczne jest, by można było na ekranie przedstawiać pewne typowe dla drukarki funkcje, takie jak na przykład stronicowanie. Mimo iż klasa CPreviewDC nie została opisana w dokumentacji, możemy zapoznać się z nią przeglądając jej kod źródłowy, który znaleźć można w katalogu języka Visual C++ w module źródłowym MFC\SRC\DCPREV.C.PP. Po uruchomieniu zbudowanej aplikacji będziemy mogli w podglądzie wydruku (menu File, polecenie Print Pre^iew) przeglądać kolejne strony drukowanego dokumentu za pomocą przycisków Next Page i PIW Page (rysunek 22.3). Jeśli mamy zainstalowaną drukarkę, możemy również dokument wydrukować. PATRZ TAKŻE • Jak wybierać obiekty w kontekście urządzenia pisaliśmy w rozdziale 15. • Malowanie za pomocą różnych rodzajów pędzli opisane zostało w rozdziale 16. Drukowanie i podgląd wydruku 581 Rysunek 22.3. Podgląd wydruku wielostronicowego dokumentu Okno dialogowe Print Kiedy drukujemy wielostronicowy dokument, program przedstawia nam (pokazane na rysunku 22.4) okno dialogowe pozwalające ustalić co dokładnie chcemy wydrukować. Jest to standardowe okno dialogowe Print, wywoływane na ekran przez funkcję cview:: Do- PreparePrinting (), która Z kolei przyzywana jest przez funkcję pokrywającą OnPrepare- Printing (). W oknie tym możemy zdefiniować, które strony mają zostać wydrukowane, liczbę kopii, znaczniki sortowania (opcja Cfillate), drukarkę, na której chcemy drukować i szczegółowe właściwości drukarki. Poić wyboru CgHate ' , .. , , : ' ,-'•,"' SsSIl drukując .kilka kopii dokuroentu użytkownik zaznaczy w oknie Print pole wyboru CoKate, sterowmk drukarki będzie drukował strony kompletami - strony o tym samym numerze ze wszystkich kopii dokumentu razem. Opcja ta dołączana jest automatycznie i nie musimy jej ręcznie programować, musi być jednak obsługiwana przez sterownik drukarki. Jeśli drukarka nie pozwala na Taki sposób drukowania, opcja będzie w oknie dialogowym wyświetlana jako nieaktywna. Użytkownik może w tym oknie dialogowym zmienić opcje drukowania, co spowoduje zmianę odpowiednich ustawień w obiekcie CPrintinfo zanim zostanie on przesłany do aplikacji. Okno to możemy zmodyfikować w tworzonym programie w zależności od licz- by opcji, które chcemy użytkownikowi udostępnić. 582 Poznaj Visual C++ 6 Rysunek 22.4. Standardowe okno dialogowe Print W tabeli 22.1 prezentującej zmienne składowe klasy CPrintinfo znajdował się wskaźnik m_pPD. Wskaźnik ten jest odwołaniem do klasy cprintDialog obudowującej w bibliotece MFC okno dialogowe Print. Klasa ta posiada zmienną składową m_pd, czyli strukturę PRINTDLG przechowującą domyślne ustawienia wyświetlane w oknie dialogowym Print. Jak widać z listingu 22.5, struktura ta posiada wiele zmiennych składowych. Zmienne te pozwalają, daleko idące dostosowanie domyślnych ustawień okna dialogowego, nawet tak daleko (jeśli lubimy wyzwania), żeby zastąpić domyślny szablon okna dialogowego zupełnie innym szablonem. Nie mamy tutaj możliwości szczegółowego opisania wszystkich zmiennych składowych tej struktury, niemniej jedną z bardziej oczywistych jest zmienna nCopies. Jeśli przed wezwaniem funkcji CView: :DoPreparePrinting() zmienimy wartość przypisaną tej zmiennej, zmieni się domyślnie wyświetlana w oknie liczba kopii wydruku. Możemy to zrobić wpisując w funkcji OnPreparePrinting (): pInfo->m_pPD->m_pd.nCopies = 15; ,v ' •/• . ••- • • ',• .:: •"•'•••.••.•• ' . • '. • •• 'Korzystanie ze struktury DeyMode Struktura DevMode przechowuje wiele przydatnych parametrów definiujących techniczne możliwości i konfigurację urządzenia. Wskaźnik tej struktury można pobrać za-pomocą funkcji GetDevMode() klasy CPrintDialog. Jeśli po dodaniu tej linii otworzymy okno dialogowe Print, domyślna liczba kopii wy- świetlana w oknie zmieni się na 15 (oczywiście jeśli zainstalowana drukarka pozwala na drukowanie kilku kopii dokumentu). Podobnie zmieniać można inne wartości zapisane w strukturze PRINTDLG. 583 Drukowanie i podgląd wydruku_________ Listing 22.5. LST23_5.CPP- struktura PRINTDLG 1 typedef struct tagPD ( 2 DWORD IStructSize; 3 HWND hwndOwner; 4 HANDLE hDevMode; 5 HANDLE hDevNames; 6 HDC hDC; 7 DWORD Flags; 8 WORD nFromPage ; 9 WORD nToPage; 10 WORD nMinPage; 11 WORD nMaxPage; 12 WORD nCcpies; 13 HINSTANCE hinstance; 14 DWORD ICustData; 15 LPPRINTHOOKPROC IpfnPrintHook; O 16 LPSETUPHOOKPROC IpfnSetupHook; 17 LPGTSTR IpPrintTemplateName; 18 LPCTSTR IpSetupTemplateName; 19 HANDLE hPrintTemplate; 20 HANDLE hSetupTemplate; 21 ) PRINTDLG; ® .-Ostatnie, sześć 'zmiennych'pozwala dostosować' c'kho'dialogowej do' naszych potrzeb),' •umożliwiając zdefiniowanie własnego :szabSo!.iu ok-na dialogowego.. Jest to : dość skomplikowane zagądmettie, zmienrse. te pozw!Uają'jedrtak',dowplQie modyfikować wygląd interfejsu, któły^zedaawiamyui^tkowhikowfceS^a:^:^!^^^-;';!?:^ Po tym jak użytkownik wciśnie w oknie dialogowym przycisk OK, wprowadzone przez niego zmiany można pobrać za pomocą przedstawionych w tabeli 22.2 funkcji dostępu klasy CPrintDialog. Jeśli na przykład chcemy przed rozpoczęciem drukowania ustalić, ile kopii wydruku użytkownik sobie zażyczył, możemy za pomocą odpowiedniej funkcji przechwycić liczbę kopii zwróconą z okna dialogowego przez funkcję cview:: DoPreparePrinting () (tak jak na listingu 22.6). Oczywiście w podobny sposób można sprawdzić każdą inną wartość zapisaną w struk- turze PRINTDLG. 584___________________________________Poznaj Visual C++ 6 Tabela 22.2. Funkcje dostępu klasy CPrintDialog Funkcja Opis działania GetCopies () Zwraca ustaloną przez użytkownika liczbę kopii GetFromPage () Podaje początkową stronę wydruku GetToPage () Podaje końcową stronę wydruku GetPortName () Zwraca port drukarki, na przykład LPT1: GetDriverName () Podaje sterownik wybranej drukarki (drukarki docelowej) GetPrinterDC () Zwraca kontekst urządzenia drukarki PrintAll () Zwraca wartość TRUE, jeśli drukowany ma być cały dokument PrintCollate () Zwraca wartość TRUE, jeśli strony mają być sortowane PrintRange () Zwraca wartość TRUE, jeśli zdefiniowany został określony zakres stron PrintSelection () Zwraca wartość TRUE, jeśli wybrane zostały strony określonego typu Listing 22.6: LST23_6.CPP — zatwierdzanie wybranej przez użytkownika w oknie dialogowym liczby kopii 1 BOOL CPrintItView::OnPreparePrinting(CPrintInfo* pinfo) 2 { 3 pInfo->SetMinPage(l) ; 4 pInfo->SetMaxPage(10) ; 5 6 pInfo->m_pPD->m_pd.nCopies = 3; 7 8 do 9 { 10 // ** Sprawdź, czy użytkownik nie anulował drukowania 11 if (DoPreparePrinting(pInfo) == FALSE) O 12 return FALSE; 13 14 // ** Ostrzeż użytkownika, jeśli zażyczył sobie zbyt wielu kopii 15 if (pInfo->m_pPD->GetCopies()>5) 16 AfxMessageBox("Please choose less than 5 copies") ; 17 18 // ** Powtarzaj pętlę, dopóki użytkownik nie zadeklaruje rozsądnej liczby kopii 19 } while(pInfo->m_pPD->GetCopies()>5); @ 585 Drukowanie i podgląd wydruku 20 return TRUE; 21 } O Wewnątrz pętli musimy pozwolić użytkownikowi przerwać operację drukowania za pomocą przycisku Cancel, gdyby w którymś momencie zrezygnował z drukowania © Warunek tutaj zdefiniowany będzie powtarzał pętlę tak długo, jak długo użytkownik nie zdefiniuje liczby kopii mniejszej lub równej pięciu. Przedstawiamy tutaj sposób w jaki można w programie sprawdzać i zatwierdzać parametry wprowadzone przez użytkownika Przyzywana w liniach 11 i 12 funkcja CYiew: :DoPreparePrinting() zwraca wartość FALSE, jeśli użytkownik wciśnie przycisk Cancel. Jeśli nie, zdefiniowana przez użytkownika liczba kopii wydruku jest sprawdzana w linii 15 i jeśli jest ich więcej niż pięć, użytkownik jest informowany, że powinien ograniczyć liczbę kopii. Pętla jest powtarzana tak długo, dopóki użytkownik nie wprowadzi odpowiedniej liczby kopii albo nie wciśnie przycisku Cancel. Opcje orientacji strony Jeśli w menu File wybierzemy polecenie Pnnt Setup (Ustawienia strony), będziemy mogli zmienić orientację strony w drukarce. W oknie dialogowym, które się pojawi, dostępne są opcje Portrait i Landscape (orientacje pionowa i pozioma). Aby móc drukować dokument w orientacji poziomej, nie trzeba dołączać do programu żadnego dodatkowego kodu. Jeśli wybierzemy opcję Landscape i uruchomimy podgląd wydruku, zobaczymy, że kontekst urządzenia został przekształcony tak, aby wyświetlać stronę ułożoną poziomo. Tak długo jak długo aplikacja pobiera odpowiednie informacje na temat rozmiaru strony ze zmiennej składowej rectDraw, obsługa drukowania w poziomie jest automatyczna. Różne rozmiary papieru Użytkownik może również zdefiniować różne rozmiary papieru, poczynając od rozmiaru kopertowego, a kończąc na stronach rozmiaru A3. Aby załączyć te opcje, nie musimy niczego programować albowiem kontekst urządzenia zostanie automatycznie dopasowany do zdefiniowanych rozmiarów strony. Zmiennej składowej obiektu cprintinfo, rectDraw zostaną przypisane odpowiednie współrzędne (rozmiary) odpowiadające wymiarom kontekstu urządzenia. 586 Poznaj Visual C++ 6 Dodawanie obiektów GDI za pomocą funkcji OnBeginPrinting () Jak już mówiliśmy, funkcja przedstawiona na listingu 22.4 działa poprawnie, ale istnieje lepszy sposób alokowania w pamięci zasobów potrzebnych do rysowania. Za każdym razem, gdy strona jest drukowana, program wzywa funkcję Onprint (), która tworzy wszystkie niezbędne zasoby (takie jak pędzel) od nowa. W przypadku dość prostego wydruku nie spowolni to zbytnio programu, ale gdy drukowany rysunek lub tekst jest bardziej skomplikowany, wygodniej jest zdefiniować zasoby i dokonać odpowiednich obliczeń tylko raz, na początku procesu drukowania. W ten sposób korzystając z raz zdefiniowanego kompletu zasobów będziemy mogli wydrukować dowolną liczbę kopii danej strony, a zasoby zostaną usunięte z pamięci dopiero po zakończeniu drukowania. Idealnym miejscem na zdefiniowanie tych zasobów jest wirtualna funkcja OnBegin- Printing (), a jej siostrzana funkcja OnEndPrintingO jest najlepszym miejscem na ich usunięcie. Funkcja OnBeginPrinting () jest przyzywana zaraz po funkcji OnPreparePrin-ting (). W niej po raz pierwszy jest definiowany kontekst urządzenia. Tenże kontekst urządzenia wykorzystywany będzie w trakcie drukowania, dlatego funkcja OnBeginPrinting () jest właściwym miejscem, by zdefiniować wykorzystywane podczas drukowania obiekty GDI i rozmiar strony. Kreator CIassWizard standardowo tworzy pustą funkcję: void CPrintItView::OnBeginPrinting(CDC* /*pDC*/, Ś CPrintInfo* /*plnfo*/) ( // TODO: tutaj można dodać dodatkowy kod inicjujący drukowanie } Przyjrzyjmy się bliżej definicji funkcji. Parametry są opatrzone komentarzami dla kompilatora i jeśli któregoś z nich nie wykorzystamy, podczas kompilacji pojawi się ko- munikat ostrzegawczy. Należy więc pamiętać o usunięciu tych komentarzy. Możemy teraz dodać do funkcji odwołanie tworzące obiekt GDI, dzięki czemu nie bę- dziemy musieli powtarzać tej procedury przy drukowaniu każdej kolejnej strony: m_fnTimes.CreatePointFont(720,"Times New Roman",pDC); m_brHatch.CreateHatchBrush(HS_CROSS,RGB(64,64,64)); Warto zauważyć, że obiekty fnTimes i brHatch poprzedzone są literą m_- w ten sposób oznaczamy obiekty, których zakres kompetencji (ang. scope) rozciąga się na całą klasę, a nie tylko na daną funkcję. Ponieważ będziemy sięgać do obiektów GDI w funkcji Onprint (), należy dodać je do deklaracji klasy w następujący sposób: protected: CFont m fnTimes; CBrush m brHatch; Drukowanie i podgląd wydruku 587 Drukowanie w kolorach a drukarki jednobarwne Wartość COLORREF zdefiniowana przez makroinstrukcję RGB(64, 64, 64) przypisuje pędzlowi kolor ciemnoszary. Większość drukarek jednobarwnych stosunkowo dobrze oddaje różne odcienie szarego odwołując się do techniki ditheringu (odwzorowywania kolorów za pomocą mozaiki punktów). Korzystając z czarno-biatej drukarki można również zdefmiować kolor nie będący odcieniem szarego. Drukarka postara się wtedy zastąpić go najbliższym mu oferowanym przez siebie kolorem. Podczas, gdy drukarki kolorowe będą zapewne w stanie całkiem dobrze oddać definiowany kolor, na drukarce czarno-białej otrzymamy po prostu jakiś odcień szarości. Obiekty te można dodać przez dwukrotne kuknięcie w oknie CIassYiew klasy CPrint- View i bezpośrednie ich wpisanie, albo też za pomocą okna dialogowego Add Member Yariable. Warto również zauważyć, że do tworzenia kreskowanego pędzla wykorzystywany jest nie kreator, ale funkcja CreateHatchBrushO. Dzieje się tak dlatego, że pędzel istnieć będzie tak długo jak długo istnieć będzie widok. Należy więc pamiętać, aby w funkcji OnBeginPrintingO zawezwać funkcję DeleteObject (), która usunie pędzel. Obiekty GDI czcionki i pędzla można usunąć w funkcji OnEndPrinting () w następujący sposób: m fnTimes.DeleteObject(); m_brHatch.DeleteObject() ; Pozostało nam tylko do zrobienia usunięcie lokalnych obiektów GDI z funkcji on-Print () i zastąpienie odwołań do nich ich wersjami ze zmiennych składowych. Robimy to zastępując CFont i CBrush odpowiednio lokalnymi zmiennymi fnTimes i brHatch wraz z ich odpowiednimi funkcjami kreacji, i wybierając uprzednio utworzony pędzel oraz czcionkę w kontekście urządzenia: CFont* pOldFont = (CFont*)pDC->SelectObject(&m_fnTimes); CBrush* pOldBrush = (CBrush*)pDC->SelectObject(&m_brHatch); Uruchomiwszy aplikację po wprowadzeniu tych zmian nie dostrzeżemy zapewne żadnej różnicy. Program robi dokładnie to samo, proces drukowania i wyświetlania podglądu wydruku będzie jednak przebiegać szybciej. W przypadku dużego, na przykład 100- stronicowego dokumentu używającego różnych zasobów GDI, ten sposób definiowania zasobów jest znacznie praktyczniejszy. Korzystanie ze współrzędnych z funkcji OnBeginPrinting ( ) Mogłoby nas kusić, by przechować w funkcji OnBeginPrintingO również współrzędne prostokąta wydruku. To jednak nie przyniesie efektu, gdyż zmienna składowa in_rectDraw klasy CPrintinfo nie została jeszcze inicjowana i program, zamiast wybranych, wpisze po prostu losowe wymiary. fAiru. i/v\z.c • O tworzeniu obiektu pędzla G D l pisaliśmy w rozdziale 16. • O tworzeniu obiektu czcionki G D l pisaliśmy w rozdziale 18. • Jak załączać zmienną składową za pomocą Classview pisaliśmy w rozdziale 19. Modyfikowanie kontekstu urządzenia Jeszcze przed wezwaniem funkcji OnDraw () i Onprint (), przyzywana jest wirtualna funkcja przygotowująca kontekst urządzenia OnPrepareDC (). Funkcja ta może zostać pokryta w klasie widoku, aby wprowadzić modyfikacje w kontekście urządzenia, które będą wspólne dla funkcji OnDraw () i Onprint (). Można tu na przykład zdefiniować określony typ odwzorowywania lub tryb malowania wspólny dla procesu drukowania i wyświetlania na ekranie. Funkcja pokrywająca nie jest tworzona przez kreator AppWi-zard, ale można ją bez problemu dodać do programu za pomocą okna dialogowego Add Virtual Function. W tworzonym tutaj przykładzie wspólnym elementem funkcji OnDraw () i OnPrintO jest funkcja SetTextAlign ( ) kontekstu urządzenia. Możemy dodać ją do funkcji OnPrepareDC () w następujący sposób: void CPrintItView::OnPrepareDC(CDC* pDC, CPrintInfo* pinfo) { pDC->SetTextAlign(TA_CENTER+TA_BASELINE) ; } Czasami, szczególnie wówczas, gdy programujemy procedurę drukowania, w której wydruk jest w stu procentach zgodny z tym co użytkownik widzi na ekranie (w oryginale angielskim stosowany jest akronim WYSIWYG, patrz słownik*), wygodnie jest definiować tryby odwzorowywania i rozmiary okna we wspólnej funkcji przyzywanej przed odwołaniem do odpowiedniej funkcji drukującej lub rysującej na ekranie. Właściwym miejscem do konfiguracji kontekstu urządzenia jest funkcja OnPrepareDC (). PATRZ TAKŻE • Na temat kontekstu urządzenia pisaliśmy w rozdziale 15. • Justowanie czcionki opisane zostało w rozdziale 17. Przerywanie wydruku Funkcję OnPrepareDC () można również wykorzystać do przerywania wydruku i innych związanych z dokumentem funkcji. Jeśli drukowany jest szczególnie długi dokument, możemy udostępnić użytkownikowi opcję pozwalającą na przerwanie drukowania. Funkcja AbortDoc () umożliwia przerwanie wydruku dokumentu. Jeśli do funkcji OnPre- • przyp. tłum. Drukowanie i podgląd wydruku______ 589 pareDC () dodamy następujące linie, przerwiemy proces drukowania po wydrukowaniu trzech stron: if (pDC->IsPrinting()) if (pInfo->m_nCurPage==3) pDC->AbortDoc(); Drukowanie bez pomocy funkcji szkieletu aplikacji Jak dotąd opisywaliśmy obsługę drukowania w oparciu o funkcje dostarczane automatycznie przez szkielety aplikacji SDI i MDI. Dostarczana przez nie obsługa drukowania działa bardzo sprawnie, ale zdarza się, że potrzebny nam jest szybki i prosty dostęp do drukarki lub sytuacje gdy nie możemy korzystać z pomocy szkieletu aplikacji, tak jak w aplikacjach opartych na oknach dialogowych. Szkielet aplikacji ukrywa przed naszymi oczami odpowiedzialne za wykonywanie zadań związanych z drukowaniem funkcje niskiego poziomu. Tutaj opiszemy pokrótce jak funkcje te działają i pokażemy drukowanie na przykładzie aplikacji opartej na oknach dialogowych. Bezpośrednie przyzywanie okna dialogowego Print W podrozdziale poświęconym korzystaniu z okna dialogowego Print, pokazaliśmy, w jaki sposób klasa cprintDialog obudowuje okno dialogowe PRINTDLG i w jaki sposób przywoływać to okno w funkcji CView: :DoPreparePrinting (). Okno to można również wykorzystać bezpośrednio do zdefiniowania drukarki docelowej i odpowiednich ustawień, w taki sam sposób, w jaki przyzywamy normalne modalne (ang. modal) okno dialogowe. Za pomocą tej samej funkcji dostępu można również zdefiniować drukowane strony i domyślną liczbę kopii dokumentu, dokładnie tak jak w funkcji DoPreparePrintingO szkieletu aplikacji. Listing 22.7 pokazuje, w jaki sposób można to okno dialogowe wykorzystać do skonfigurowania drukarki do potrzeb aplikacji bazującej na oknach dialogowych, a następnie w oparciu o ustawienia zdefiniowane w oknie dialogowym do wydrukowania niewielkich rozmiarów dokumentu. Tryb CreateDC Zamiast wyświetlać okno dialogowe Print można za pomocą wbudowanych w nie funkcji utworzyć odpowiednio skonfigurowany kontekst urządzenia. Tak utworzony kontekst urządzenia można następnie spokojnie wykorzystać do drukowania. W tym celu należy skorzystać z funkcji składowej CreatePrinterDC () klasy cprintDia-log. Domyślne ustawienia kontekstu urządzenia można również zmieniać, zmieniając wartości wchodzące w skład struktur DEVMODE i DEVNAMES. Wskaźniki do tych struktur można zdobyć za pomocą funkcji GetDevMode (), a strukturę DEVNAMES można znaleźć za pomocą zmiennej składowej m_pd struktury PRIBNTDLG. 590___________________________________Poznaj Visual C++ 6 Pokazany na tym listingu mechanizm drukowania opiera się na funkcjach Start-Doc () i EndDoc (), które opiszemy za chwilę. Za pomocą kreatora AppWizard tworzymy aplikację DIgPrint opartą na oknach dialogowych, a za pomocą kreatora CIassWizard funkcję obsługi, do której wpiszemy kod przedstawiony na listingu 22.7. Listing 22.7. LST23_7.CPP - obsługa bezpośredniego drukowania w funkcji OnOK() aplikacji bazującej na oknie dialogowym 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void CDIgPrintDlg::OnOK() ( // TODO: Tutaj dodaj własny kod kontrolujący II** Skonstruuj obiekt CPrintDialog CPrintDialog dIgPrint(FALSE,PD_ALLPAGES,this) ; if (dlgPrint.DoModal()==IDOK) { // ** Przypisz kontekst urządzenia okna dialogowego II ** obiektowi COC CDC dcPrint; dcPrint.AttachfdIgPrint.GetPrinterDC()) ; // ** Utwórz i zapełnij strukturę DOCINFO DOCIMFO myPrintJob; myPrintJob.cbSize = sizeof(myPrintJob); O myPrintJob.IpszDocName = "MyPrintJob"; O myPrintJob.IpszOutput = NULL; O myPrintJob.IpszDatatype = NULL; O myPrintJob.fwType = NULL; O // ** Rozpocznij drukowanie dokumentu if (dcPrint.StartDoc(SmyPrintJob)>=0) { // ** Rozpocznij drukowanie strony dcPrint.StartPage() ; // ** Rozpocznij drukowanie strony dcPrint.TextOut(0,0,"My Smali Print Job"); © // ** Zakończ stronę dcPrint.EndPage (); Drukowanie i podgląd wydmku 591 35 // ** Zamknij dokument 36 dcPrint.EndDoc(); 37 } 38 39 // ** Usuń kontekst urządzenia drukarki 40 dcPrint.DeleteDCO; ® 41 } 42 43 II** Wezwij bazową funkcję OnOK 44 CDialog::OnOK() ; 45 } O Zapewniamy strukturę DOCINFO podstawowymi informacjami niezbędnymi do realizacji zadania wydmku © Linia tekstu, którą drukujemy między wezwaniami do funkcji Startpage () i End-Page() © Tutaj musimy usunąć kontekst urządzenia utworzony przez okno dialogowe Print W linii 6 listingu 22.7 deklarujemy obiekt digPrint należący do klasy CPrintDia-log. Konstruktor tego okna dialogowego ma trzy parametry. Pierwszy parametr to znacznik przyjmujący wartość TRUE, jeśli wyświetlamy okno dialogowe PrintSetup, a FALSE, jeśli wyświetlamy okno dialogowe Print. Drugi parametr jest zbiorem dających się zestawiać w różnych kombinacjach znaczników (zbyt licznych, by je tutaj opisywać), które pozwalają przystosować do naszych potrzeb ustawienia okna. Trzeci parametr jest wskaźnikiem do okna rodzica; wykorzystane tutaj stówo kluczowe języka C++ this wskazuje, że rodzicem jest okno dialogowe. To okno dialogowe wyświetlane jest w linii 8 za pomocą funkcji digPrint. DoModal (). Jeśli użytkownik kuknie OK, rozpoczynamy drukowanie, jeśli nie, pozostała część bloku funkcji jest pomijana. Jeśli użytkownik wciśnie w oknie dialogowym Print przycisk OK, tworzony jest kontekst urządzenia dla drukarki, który w celu łatwiejszego operowania na nim przyłączany jest w linii 13 do klasy kontekstu urządzenia (CDC). Należy pamiętać, aby na koniec (w linii 40) utworzony kontekst urządzenia usunąć. Aby po zbudowaniu i uruchomieniu aplikacji, wydrukować zaprogramowany w przedstawionej przed chwilą funkcji tekst, wystarczy kliknąć w oknie dialogowym OK. Funkcje StartDoc () i EndDoc () Klasa CDC kontekstu urządzenia posiada wiele związanych z drukarką funkcji. Aby rozpocząć nowy wydruk, system Windows musi utworzyć specjalny dokument bufora 592_____________________________________Poznaj Visual C++ 6 (ang. spool document), który będzie przechowywał zadanie wydruku (ang. print job) i przesyłał je do drukarki, gdy zostanie zakończone. Funkcja StartDoc () informuje system Windows, że powinien rozpocząć przygotowywanie dokumentu bufora, a funkcja EndDoc (), że dokument jest już gotowy i może zostać wysłany do drukarki. Przedstawiona wcześniej funkcja AbortDoc () przerywa zadanie wydruku, tak że dokument nie zostaje wysłany do drukarki. Na listingu 22.7 przyzywamy funkcję składową kontekstu urządzenia dcPrint, StartDoc () w linii 24, przesyłając jej jako parametr wskaźnik do struktury DOCINFO. Struktura ta przechowuje szczegółowe parametry zadania wydruku. Jedyny parametr, który musimy zdefiniować, to nazwa dokumentu bufora (linia 18). Warto zwrócić uwagę, że struktura DOCINFO posiada nietypową zmienna składową cbsize informującą o rozmiarach struktury. Rozmiar ten jest w linii 17 przypisywany zmiennej sizeof (myprintJob). Tego rodzaju dziwne zachowania można czasem spotkać na poziomie funkcji API Win32; dzieje się tak dlatego, że struktura DOCINFO wywodzi się ze starszych wersji języka C. Zmienna cbsize jest niezbędna, ponieważ istnieje kilka różnych wersji struktury DOCINFO i jedynym sposobem na ich rozróżnienie jest podanie rozmiaru potrzebnej nam struktury. Funkcja StartDoc () po wezwaniu spróbuje uruchomić zadanie wydruku, a jeśli jej się powiedzie poinformuje o sukcesie. Funkcja może zawieść z wielu powodów, wliczając w to zbyt mało miejsca na dysku lub w pamięci, czy też uszkodzony sterownik drukarki. Dlatego zanim przystąpimy do drukowania, warto sprawdzić zwracane przez nią informacje. Obserwowanie bufora drukarki Możemy obserwować tworzenie, dokumentu bufora wydruku, umieszczając odpowiedni punkt kontrolny (ang. breakpoint) w funkcji OnPrintO lub po funkcji StartDoc () i otwierając ikonę statusu drukarki w grupie Printers w opcji §ettings głównego menu Start systemu Windows. Po utworzeniu dokumentu bufora powinniśmy (tak jak to zostało pokazane w linii 36) wezwać funkcję EndDoc (), która rozpocznie drukowanie dokumentu. PATRZ TAKŻE ^ Więcej na temat funkcji API i funkcji API Win 32 znaleźć można w rozdziale 28. Funkcje StartPage () i EndPage () Kolejną ciekawą związaną z kontekstem urządzenia drukarki parą funkcji są funkcje StartPage () i EndPage (). Funkcja StartPage () służy do inicjowania kontekstu urządzenia, tak aby przygotować go do drukowania kolejnej strony. W ten sposób przywrócone zostaną pewne początkowe ustawienia kontekstu urządzenia, takie jak początkowa Drukowanie i podgląd wydruku 593 pozycja kursora rysującego elementy graficzne, a w dokumencie bufora ustawione zostaną odpowiednie informacje definiujące początek strony. Zazwyczaj funkcję StartPageO przyzywamy, by w kontekście urządzenia narysować pewne szczegóły graficzne, które powinny być narysowane na danej stronie. Następnie przyzywamy funkcję OnEndPage (), która zapisuje stronę w pliku bufora i dodaje ją do dokumentu bufora. Na listingu 22.7 funkcja StartPage () jest przyzywana w linii 27. Po niej przywołujemy pojedynczą funkcję Text0ut () wpisującą na drukowanej stronie pożądany tekst, by na koniec odwołać się w linii 33 do funkcji EndPage (). Łączenie się bezpośrednio z urządzeniem Teoretycznie pisząc program nie powinniśmy zaprzątać sobie głowy tym, jaka drukarka jest podłączona do komputera, na którym działać będzie aplikacja. W razie potrzeby możemy jednak obejść dostarczany przez Windows mechanizm kontekstu urządzenia i przesłać ciąg znaków bezpośrednio do drukarki. Pozwala, na to dostępna w klasie coc funkcja Escape (), która umożliwia przesłanie fragmentu kodu bezpośrednio do podłączonego urządzenia. Funkcje Escape () odnoszące się do różnych urządzeń, są rozróżniane w oparciu o kod, który zawierają i mogą odnosić się albo do konkretnego urządzenia (przesyłane są wtedy bezpośrednio do urządzenia), albo do sterownika dostarczanego przez system Windows (są na potrzeby urządzenia tłumaczone przez sterownik). Do funkcji tych sięgamy zazwyczaj, gdy któraś z funkcji urządzenia nie jest obsługiwana przez standardowy mechanizm systemu Windows. Przyzwanie funkcji EndPage () powoduje przesłanie specjalnego kodu drukarki informującego o tym, że strona jest kompletna (Form Feed) i dokument bufora rejestruje kolejną stronę. Aby wydrukować wielostronicowy dokument, trzeba obie funkcje przyzwać odpowiednią liczbę razy, a na zakończenie przyzwać uruchamiającą wydruk funkcję EndDocO. Pomiędzy funkcjami Start Page() i EndPage () można zapełniać poszczególne strony korzystając z funkcji OnPrint () dokładnie w taki sam sposób, w jaki robiliśmy to wcześniej w aplikacji SDI. Funkcje te są również przyzywane w aplikacji SDI, ale szkielet aplikacji ukrywa je przed nami, wzywając tylko w odpowiednich momentach funkcję OnPrint (). CzęŚĆ VI Eksportowanie danych aplikacji Rozdział 23 Zachowywanie, ładowanie i transferowanie danych Zachowywanie i ładowanie danych dokumentu Tworzenie, odczytywanie i zapisywanie do plików Korzystanie ze schowka Serializacja Serializacja (ang. serialization) jest to technika wykorzystywana do przekształcania danych aplikacji w uporządkowaną listę elementów danych, a następnie zachowywania ich na dysku lub transferowania do innych programów. Ładując lub przyjmując transfer seria- lizowanych danych program wczytuje je po kolei i odtwarza zapisując w współpracujących z programem obiektach. Szkielety aplikacji SDI i MDI dostarczają odpowiednich narzędzi do obsługi serializa-cji i operacji na plikach. Jedyną rzeczą, o którą musimy zadbać jest to, aby przechowywane w programie dane serializowały się na żądanie aplikacji. Obsługa plików w aplikacjach bazujących na oknach dialogowych Aplikacje bazujące na oknach dialogowych nie muszą zazwyczaj korzystać z seriali-zacji - w normalnych sytuacjach powinna tu wystarczyć bezpośrednia obsługa plików za pomocą klasy CFile (opisanej w dalszej części tego rozdziału). Jednakże aplikacje oparte na oknach dialogowych również mogą korzystać z serializacji za pośrednictwem klasy CArchive. W tym celu należy do okna dialogowego załączyć kod odpowiedniego interfejsu użytkownika, który obsługiwać będzie serializację. 598 Poznaj Visual C++ 6 Tworzenie aplikacji SDI obsługującej operacje na plikach Aplikacja SDI utworzona za pomocą kreatora AppWizard automatycznie umożliwia tworzenie, przechowywanie i serializację obiektów danych przechowywanych w klasach wywiedzionych z klasy CDocument. Jedyną rzeczą, o której należy pamiętać jest odpowiednie zdefiniowanie łańcuchów szablonu dokumentu. Łańcuchy te możemy zdefiniować za pomocą kreatora AppWizard klikając na stronie 4 przycisk Advanced. Kreator wyświetli widoczne na rysunku 23.1 okno dialogowe Ad-vanced0ptions, umożliwiające zdefiniowanie łańcuchów związanych z szablonem dokumentu, w którym znajdziemy pola: " File Extension - w polu o tej nazwie można zdefiniować rozszerzenie pliku. Rozszerzenie to jest następnie dodawane wszystkim plikom zachowywanym lub ładowanym przez aplikację. • File Type ID - w polu tym możemy zdefiniować typ dokumentu, który zostanie zarejestrowany w rejestrze systemu, aby aplikacja powiązała z plikami danego typu swój plik wykonywalny. Zazwyczaj można w tym polu pozostawić wartość wpisaną domyślnie, bazującą na nazwie aplikacji. Kiedy użytkownik kliknie dwukrotnie plik tego typu w Eksploratorze Windows lub na pulpicie systemu, automatycznie zostanie uruchomiona odpowiednia aplikacja. " Filter Name - tutaj możemy zdefiniować standardowy filtr odnajdujący pliki z określonym rozszerzeniem. • File Type Name - tutaj wpisujemy powiązaną z identyfikatorem typu pliku nazwę typu pliku wyświetlaną przez inne aplikacje, aby opisać plik pochodzący z naszej aplikacji. Listę zarejestrowanych w systemie nazw typów plików można znaleźć wybierając w menu skrótów, które pojawi się po kuknięciu na pulpicie prawym klawiszem myszy, polecenie New. Listę tę opiszemy w dalszej części rozdziału. • File New Name - tutaj definiujemy standardową nazwę wykorzystywaną przy zachowywaniu plików, którym użytkownik nie nadał własnej nazwy. Opcje szkieletu aplikacji związane z plikami Opcje widoczne w oknie dialogowym Advanced Options przechowywane są w łańcuchu będącym jednym z zasobów IDR_MAINFRAME. Wpisane tutaj ustawienia można później zmienić edytując na stronie ResourceView okna Project Workspace łańcuch IDR MAINFRAME. Aby zademonstrować jak działa serializacja, utworzymy przykładowy projekt SDI, który nazwiemy Persist. Tworząc ten projekt należy w kreatorze AppWizard pominąć pierwsze trzy strony klikając Next. Teraz na stronie czwartej wciskamy przycisk Advanced, aby wyświetlić okno dialogowe Advanced Options i w polu File Extensions wpisujemy bib, aby aplikacja tworzyła pliki z rozszerzeniem .bib (rysunek 23.1). Zachowywanie, ładowanie i transferowanie danych 599 DocumentTBmplatBStrings WindowStyles • Non-tocs.lized strings • — •••"••— • ••••••- ••- ""•• • •••••-• File estension; FiletypeID: jbib Locslized strings - ••••-— Language: English [United States] Doc^penarne; Persist.Document Mainframecaption: |persist Filter nama: Persis Filenewname (short name): jPersis Files (*blb) Filetypenaiae(long name): PBISIS Peisis Document Ciosa Rysunek 23.1. W oknie dialogowym Advanced Options definiujemy filtr plików aplikacji PATRZ TAKŻE • Aplikacje SDI zostały dokładnie opisane w rozdziale 12. • Jak edytować łańcuchy zasobów mówimy w rozdziale 2. Tworzenie obiektów przystosowanych do serializacji Każda klasa wywiedziona z bazowej klasy biblioteki MFC cobject posiada wirtualną funkcję Serialize (), którą można w tych klasach pokrywać, aby ładować i przechowywać zmienne składowe obiektów w pliku dyskowym. Funkcja Serialize() jest automatycznie przyzywana przez szkielet aplikacji SDI lub MDI, gdy użytkownik wybierze polecenia File Save lub File Open, odpowiednio zachowując lub ładując obiekty przechowujące dane dokumentu. Klasa CArchive jest odpowiedzialna za serializację obiektów wywodzących się z klasy cobject (przekształcanie w plikową zmienną składową osadzoną w klasie CArchive lub przekształcanie na powrót ze zmiennej w obiekt). Obiekt klasy CArchive jest tworzony przez szkielet aplikacji i dołączany do zachowywanego lub otwieranego przez użytkownika pliku. Możemy pokryć funkcję Serialize () w klasie dokumentu (która wywodzi się z klasy cobject). Podczas ładowania lub zachowywania dokumentów pokrywającej funkcji Serialize () przesyłane jest odwołanie ar do tworzonego przez szkielet aplikacji obiektu CArchiwe. Obiekt CArchive zawiera osadzoną (wbudowaną) zmienną plikową połączoną z fizycznym plikiem dyskowym. 600_____________________________________Poznaj Visual C++ 6 W funkcji Seriali ze () obsługującej dokumenty aplikacji należy wpisać fragment kodu przywołujący wersję funkcji Serialize () dla każdego z obiektów aplikacji i przesyłający każdemu z nich obiekt CArchive (ar), przesłany do funkcji Serialize () dokumentu przez szkielet aplikacji. Wykorzystywanie klasy CArchive do przesyłania danych w sieci Możemy użyć klasy CArchive w połączeniu z klasami CSocket i CSocketFile. Pozwoli to nam transferować dane między dwoma komputerami w sieci lokalnej lub nawet w większej sieci takiej jak Internet. W trakcie zachowywania danych dokumentu, dane każdego serializowanego obiektu są odczytywane i przechowywane w pliku powiązanym z obiektem archiwum (obiektem klasy CArchive). Kiedy dane dokumentu są ładowane do aplikacji, odczytywany jest plik dyskowy powiązany z obiektem archiwum, a następnie dane przepisywane są do każdego z zachowanych uprzednio obiektów za pomocą funkcji Serialize () odtwarzanego obiektu. Tworzony podczas serializacji plik dyskowy nazywany serializowanym plikiem ar- chiwalnym (ang. serialized archive file) przechowuje kopię danych z każdego z obiektów aplikacji, jak również typ i numer wersji obiektu. Numer ten umożliwia zmienianie danych zapisanych w obiekcie i rozróżnianie obiektów zachowywanych w kolejnych wersjach aplikacji. Informacje o wersji i typie nazywane są często schematem (ang. schemd). Schematy serializacji i schematy baz danych Mimo iż nazwa jest w obu przypadkach taka sama, schematy serializacji różnią się znacznie od schematów baz danych. Schematy baz danych przechowują informacje o każdym polu tablicy bazy danych, tymczasem schematy serializacji przechowują tylko typ klasy i numer wersji aplikacji. Możemy wywieść z klasy cobject własną klasę, a następnie udostępnić jej możliwość serializacji danych dodając do definicji klasy makroinstrukcję DECLARE_SERIAL, a do kodu klasy makroinstrukcję IMPLEMENT_SERIAL. Te dwie makroinstrukcje zastępujące standardowe (dla klas wywiedzionych z klasy cobject) makroinstrukcje DECLARE_ DYN-CREATE i IMPLEMENT_DYNCREATE, dostarczają składni sprawiającej, że serializacja działa w sposób bardziej płynny. Deklarowanie klasy pozwalającej na serializację Jeśli tworzona przez nas aplikacja wymaga specyficznych dla niej obiektów przecho- wujących dane, będziemy również potrzebować specjalnych klas opisujących i obsługują- Zachowywanie, ładowanie i transferowanie danych 601 cych te dane. Do definicji tworzonej klasy dodamy makroinstrukcje umożliwiające za- chowywanie i ładowanie danych obiektów. Utworzymy teraz nowy plik nagłówka (.h) i plik implementacji (.cpp). Pliki te prze- chowują odpowiednio definicję i kod implementujący klasę. Tworzenie nowego pliku nagłówka dla definicji klasy 1. W kompilatorze Developer Studio kliknij menu File i wybierz polecenie New, tak samo jak w przypadku tworzenia nowego projektu. 2. W oknie dialogowym New na karcie Files wybierz opcję C/C^ Header File, aby utworzyć nowy plik. 3. W polu File ^ame wpisz nazwę pliku nagłówka (tutaj blob.h). 4. Jeśli pole wyboru Ądd to Project jest zaznaczone (domyślnie powinno być zaznaczone), do projektu automatycznie dodawany jest pusty plik nagłówka, który teraz wyświetlony zostanie w polu edytora. Upewnij się, że pole zostało zaznaczone. 5. Aby dodać nowy plik nagłówka do projektu, kliknij OK. Teraz można rozpocząć edycję definicji klasy. Na listingu 23.1 można obejrzeć definicję prostego obiektu dającego się serializować. Klasa jest zwykłą klasą wywiedzioną z klasy cobj ect, ale dodana do niej makroinstrukcja DECLARE_SERIAL umożliwia seńalizację. Listing 23. l. LST24_1.CPP - plik blob.h, definicja klasy CBlob 1 // ** Upewnij się, że klasa nie została zadeklarowana dwukrotnie 2 łifndef _BLOB_H 3 łdefine _BLOB_H 4 5 // ** Wywiedź klasę CBlob z klasy CObject 6 ciass CBlob : public CObject 7 ( 8 // ** Załącz funkcję obsługującą serializację 9 DECLARE_SERIAL(CBlob); O 10 11 public: 12 13 // ** Zadeklaruj dwa konstruktory 14 CBlob (); 15 CBlob(CPoint ptPosition) ; 16 17 // ** Zadeklaruj funkcję malującą 18 void Draw(CDC* pDC); 19 20 // ** Zadeklaruj zmienne składowe 21 CPoint m_ptPosition; © 22 COLORREF m_crColor; 23 int m_nSize; 24 unsigned m_nShape; 25 }; 26 27 #endif // _BLOB_H O Makroinstrukcja DECLARE_SERIAL zapewnia obsługę serializacji. @ Zmienne niezbędne do narysowania pojedynczego obiektu bąbelka (ang. blob). Jak widać w linii 6, nowa klasa CBlob wywiedziona została z klasy CObject. Jeśli piszemy aplikację kompatybilną z biblioteką MFC, warto wywodzić wszystkie klasy aplikacji z klasy cobject, ponieważ klasa CObject jest w tym przypadku klasą bazową wszystkich klas i oferuje pewne standardowe możliwości biblioteki MFC, takie jak informacje czasu wykonania o klasie. Informacje czasu wykonania o klasie pomagają bibliotece MFC ustalić, do jakiej klasy należy dany obiekt. Informacje te zachowywane są razem z danymi obiektu, kiedy więc zachowane dane są odtwarzane, automatycznie tworzony jest obiekt odpowiedniej klasy. Makroinstrukcja DECLARE_SERIAL w linii 9 dodaje kod niezbędny do obsługi serializacji. W liniach 14 i 15 zadeklarowane zostały dwa konstruktory. Pierwszy zadeklarowany linii 14 nie posiada żadnych parametrów i jest wykorzystywany przez szkielet aplikacji do tworzenia obiektów, kiedy odpowiednie dane ładowane są z pliku dyskowego. Konstruktor z linii 15 wykorzystywany jest do tworzenia obiektu w programie i pobiera zmienną składową klasy CPoint definiującą pozycję bąbelka. Dzięki temu (co pokażemy za chwilę) będziemy mogli definiować pozycję bąbelka za pomocą kuknięcia myszą. W linii 18 deklarowana jest funkcja Draw (), która rysować będzie na ekranie odpowiednie obiekty. Zmienne składowe z linii 21-23 muszą być serializowane, kiedy obiekt jest odtwarzany lub zachowywany. Kod klasy umożliwiającej serializację Po zdefiniowaniu nowej klasy należy napisać i dołączyć do programu odpowiedni kod umożliwiający konstruowanie (inicjowanie) i manipulowanie danymi zadeklarowanymi w definicji klasy. Zachowywanie, ładowanie i transferowanie danych 603 Tworzenie nowego pliku .cpp zawierającego implementację klasy 1. W Developer Studio kliknij menu File i wybierz polecenie New, tak jak w przypadku tworzenia nowego projektu. 2. W oknie dialogowym New na karcie Files wybierz opcję C/C"' Implementation File, aby utworzyć nowy plik. 3. W polu File Name wpisz nazwę pliku implementacji (tutaj blob.cpp). 4. Jeśli pole wyboru .Add to Project jest zaznaczone (domyślnie powinno być zaznaczone), do projektu automatycznie dodawany jest pusty plik implementacji, który teraz wyświetlony zostanie w polu edytora. Upewnij się, że pole zostało zaznaczone. 5. Aby dodać nowy plik implementacji do projektu, kliknij OK. Teraz można rozpocząć edycję pliku implementacji klasy. Jeśli korzystamy z Yisuał SourceSafe Jeśli korzystamy ze współdziałającej ze środowiskiem programowania aplikacji umożliwiającej kontrolę zasobów, takiej jak Visual SourceSafe, w tym momencie zostaniemy zapytani, czy chcemy dodać nowy zasób do projektu SourceSafe (należy odpowiedzieć tak). Oprogramowanie umożliwiające kontrolę zasobów, pozwala śledzić różne wersje programu i zmieniać moduły zasobów wracając w razie potrzeby do starszych wersji. Narzędzia takie są szczególnie przydatne, gdy nad jednym pro- jektem pracuje jednocześnie kilku programistów, ponieważ zabezpieczają kod napisany przez jednego z nich przed przypadkowymi modyfikacjami wprowadzonymi przez innego. : ^1::1^ ^/y: :- < .^:, - : ..^ ^ 1::11:::::,::,^ ', ! .-y:,^:,,': Dająca się serializować klasa musi posiadać ogólny konstruktor - to znaczy, konstruktor bez argumentów. Konstruktor ten tworzy obiekty odtwarzane w czasie serializacji z pliku dyskowego. Szkielet aplikacji może dzięki temu odtwarzać obiekty ładowane z pliku za pomocą jednego wspólnego dla wszystkich klas konstruktora. Odwołując się w nim do ogólnego konstruktora jest w stanie utworzyć egzemplarz obiektu dowolnej serializowanej klasy, a następnie w oparciu o kod funkcji Seriali ze () zapełnić utworzony obiekt zmiennymi składowymi. Oprócz tego, do klasy można również dodać własny konstruktor, który pozwoli nam na tworzenie w programie obiektów danej klasy. Plik zawierający implementację klasy musi również zawierać makroinstrukcję IMPLEMENT_SERIAL. Makroinstrukcja ta wymaga trzech parametrów. Pierwszy parametr jest nazwą klasy, drugi nazwą klasy bazowej, a trzeci (wSchema) jest zmienną typu UINT, definiującą numer wersji programu. Jeśli dodamy do klasy nowe zmienne składowe (tworząc w ten sposób nową wersję programu), które również będziemy chcieli serializować, będziemy mogli zapisać za pomocą tej funkcji nowy numer wersji programu w pliku 604_____________________________________Poznaj Visual C++ 6 i sprawdzać go, aby zachować kompatybilność z istniejącymi już starszymi wersjami serializowanego pliku archiwum. Dlaczego należy dbać o numer wersji? Jeśli tworzymy program komercyjny, to korzystając z serializacji warto zachowywać numer wersji dla każdego pliku/obiektu kolejnej wersji programu i obsługiwać w programie dane zapisane we wcześniejszych wersjach albo konwertować je dla potrzeb nowszej wersji. W przeciwnym razie dostarczając na rynek nowszą, bardziej zaawansowaną wersję programu możemy niechcący sprawić, że wszystkie pliki zapisane we wcześniejszych wersjach nie dadzą się odczytać w nowszej wersji naszego programu. Implementacja klasy CBlob przedstawiona została na listingu 23.2. Implementacja ta zawiera makroinstrukcję IMPLEMENT_SERIAL, oba wspomniane przez nas przy okazji deklaracji konstruktory i kod pozwalający narysować bąbelek. Listing 23.2. "LST24_2.CPP - plik blob.cpp, implementacja klasy CBlob 1 // ** Załącz standardowy nagłówek 2 łinclude "stdafx.h" 3 łinclude "blob.h" 4 5 // ** Dodaj makroinstrukcję obsługującą serlallzację 6 IMPLEMENT_SERIAL(CBlob,CObject,l) 7 8 // ** Załącz standardowy konstruktor 9 CBlob::CBlob() 10 ( 11 ) 12 13 // ** Konstruktor obiektów tworzonych przez użytkownika 14 CBlob::CBlob(CPoint ptPosition) O 15 { 16 // ** Zdefiniuj generator liczb losowych 17 srandtGetTickCount() ) ; 18 19 // ** Przypisz zmiennej pozycję myszy 20 m_ptPosition = ptPosition; 21 22 // ** Przypisz zmiennym wartości losowe 23 m_crColor= RGB(rand()%255,rand()%255,rand()%255); 24 m nSize = 10 + rand()%30; Zachowywanie, ładowanie i transferowanie danych 605 25 m nShape = rand(); 26 } 27 28 void CBlob:-.Draw (COC* pDC) @ 29 ( 30 // ** Utwórz i wybierz odpowiedni pędzel 31 CBrush brDraw(m_crColor); 32 CBrush* pOldBrush = pDC->SelectObject(SbrDraw); 33 CPen* pOldPen = 34 (CPen*)pDC->SelectStockObject(NULL_PEN) ; 35 // ** Zastosuj generator liczb losowych do kształtu 36 srand(m_nShape); 37 for(int n=0;n<3;n++) 38 ( 39 // ** Zdefiniuj pozycję bąbelka i losowe zmiany kształtu 40 CPoint ptBIob(m_ptPosition); 41 ptBlob+=CPoint(rand()%m_nSize,rand()%m_nSize) ; 42 43 . // ** Utwórz i odmaluj prostokątny obszar 44 CRect rcBlob(ptBIob,ptBIob); 45 rcBlob.InflateRect(m nSize,m nSize) ; 46 pDC->Ellipse(rcBlob) ; 47 } 48 49 // ** Przywróć poprzednie obiekty GDI 50 pDC->SelectObject(pOldBrush) ; 51 pDC->SelectObject(p01dPen); 52 ) O Za pomocą tego konstruktora tworzymy obiekt bąbelka w miejscu, w którym użytkownik kliknie myszą. Inne parametry bąbelka generowane są losowo. © Funkcja Draw () umożliwia obiektowi bąbelka odmalowanie się, jeśli otrzyma wskaźnik kontekstu urządzenia od funkcji OnDraw () widoku. Pliki nagłówka załączone w linii 2 i 3 dyrektywą łinclude informują kompilator o utworzonej przez nas definicji klasy i definicji klasy MFC. Makroinstrukcja IMPLE-MENT_SERIAL w linii 6 odpowiada makroinstrukcji DECLARE_SERIAL z definicji klasy. Standardowy konstruktor jest definiowany w linii 9. Kiedy plik jest ładowany do aplikacji, mechanizm serializacji tworzy za jego pomocą niezbędne obiekty, a następnie zapełnia je danymi przechowywanymi w pliku. 606 Poznaj Visual C++ 6 Drugi, specyficzny dla aplikacji konstruktor, załączony w linii 14, tworzy obiekty bą- belków, kiedy użytkownik kliknie myszą w obrębie obszaru roboczego widoku, przypisując jednocześnie bąbelkom kilka losowych parametrów definiujących kształt i kolor. Zaczynająca się w linii 28 funkcja Draw (), maluje obiekty na ekranie, kiedy tylko otrzyma wskaźnik do kontekstu urządzenia widoku. Przechowywanie danych dokumentu Po utworzeniu odpowiednich klas aplikacji należy utworzyć obiekty tych klas, które przechowywać będą dane wprowadzone przez użytkownika. Jeśli utworzymy w aplikacji obiekty, musimy je śledzić, aby móc manipulować przechowywanymi w nich danymi oraz zachowywać, ładować do programu i ewentualnie niszczyć obiekty. Gdy zachowujemy lub odtwarzamy obiekty, musimy wewnątrz pętli serializować każdy obiekt z osobna. Niektóre z klas MFC pomagają śledzić i przechowywać obiekty i automatycznie obsługują serializację wykonując potrzebne operacje na wszystkich egzemplarzach przechowywanych obiektów. Należy do nich klasa cobArray będąca jedną z dostępnych w bibliotece MFC klas kolekcji (ang. collection). Klasa ta jest tablicą (ang. array) nie posiadająca z góry zdefiniowanych rozmiarów (mogącą się w razie potrzeby rozrastać do pożądanych rozmiarów, ang. gmwable), przeznaczoną do przechowywania obiektów klas wywodzących się z klasy cobject, czyli dowolnego obiektu przeznaczonego do przechowania danych. Co więcej klasa ta obsługuje serializację, co sprawia, że bardzo dobrze nadaje się do przechowywania obiektów, które powinny być zachowywane w pliku. Tablice dopuszczające tylko dane określonego typu Tablica definiowana klasą cobArray nie sprawdza zgodności typów przechowywanych w niej obiektów, co oznacza, że może przechowywać dowolny obiekt wywodzący się z klasy cobject. Czasami wygodnie jest przechowywać obiekty różnych typów, ale zdarzają się sytuacje, kiedy wszystkie zachowywane obiekty muszą być jednego, określonego typu. Niektóre tablice, na przykład CTypedPtrArray, dbają o to, aby typy przechowywanych w nich obiektów były ze sobą zgodne. Jeśli spróbujemy do takiej tablicy dodać obiekt niewłaściwego typu, podczas kompilacji programu pojawi się odpowiedni komunikat ostrzegawczy. Aby dodać obiekt tablicy klasy cobArray do definicji klasy dokumentu, należy pod komentarzem //Atrybuty wpisać: // Atrybuty public: CObArray m_BlobArray; Dodany w ten sposób publiczny obiekt m_BlobArray będzie teraz mógł przechowywać dowolne obiekty wywodzące się z klasy cobject (takie jak CBlob w naszym przykładzie). Zachowywanie, ładowanie i transferowanie danych 607 Oprócz przechowywania obiektów, dokument musi je również niszczyć, kiedy aplikacja jest zamykana. Zajmuje się tym funkcja pokazana na listingu 23.3, której deklaracja w definicji klasy wygląda tak: void DeleteBlobs () ; Listing 23.3. LST24_3.CPP - kod funkcji DeleteBlobs () usuwającej obiekty bąbelków z pamięci 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void CPersistDoc::DeleteBlobs() O ( // ** Usuń z pamięci obiekty bąbelków for (int i=0;im_BlobArray.Add(new CBlob(point)) ; Invalidate(); Zachowywanie, ładowanie i transferowanie danych 609 Funkcja lnvalidate() poleci następnie przywołując funkcję OnDrawO odmalowanie widoku razem z nowymi dołączonymi obiektami (listing 23.4). Listing 23.4. LST24_4.CPP - rysowanie bąbelków za pomocą funkcji OnDraw () 1 void CPersistView::OnDraw(CDC* pDC) 2 { 3 CPersistDoc* pDoc = GetDocument() ; 4 ASSERT_VALID(pDoc); 5 6 // TOD// TODO: tutaj dodaj kod rysujący dla własnych danych 7 8 for(int i=0;im_BlobArray.GetSize();i++) O 9 { 10 CBlob* pBlob=(CBlob*)pDoc->m_BlobArray.GetAt(i); 11 pBlob->Draw(pDC); 12 } 13 } O W tej funkcji przeglądamy (wewnątrz pętli) tablicę zawierającą obiekty bąbelków i polecamy każdemu z nich odmalować się na ekranie. W pętli z linii 8-12 na listingu 23.4 funkcja poleca po kolei każdemu z obiektów odmalować się na ekranie wzywając w tym celu jego funkcję Draw () (linia 11). Kompilator potrzebować będzie również definicji klasy obiektów: łinclude "blob.h" Linię tę dodajemy po innych dyrektywach łinclude w pliku Persistview. cpp. Jeśli zbudujemy i uruchomimy aplikację Persist, będziemy, mogli tak jak widać na rysunku 23.2, tworzyć różnokolorowe bąbelki klikając w oknie widoku. 610 Poznaj Visual C++ 6 Rysunek 23.2. Aplikacja Persist wyświetlająca obiekty bąbelków PATRZ TAKŻE • O dokumentach i danych dokumentu pisaliśmy w rozdziale 12. • O tym jak rysować różnokolorowe figury i o kontekście urządzenia pisaliśmy w rozdziale 16. Serializowanie obiektów Aby móc serializować obiekty przechowujące dane, należy do klasy obiektów, które chcemy serializować, dołączyć funkcję Serialize (). Funkcja ta zamienia zmienne składowe przechowywane w obiektach w uporządkowaną listę, którą można zapisywać lub odczytywać z pliku. Poniżej definicja wirtualnej funkcji Serialize (). // ** Dodaj funkcję pokrywającą Serialize() virtual void Serialize(CArchive& ar); Przedstawioną powyżej deklarację funkcji SerializeO należy dodać do definicji klasy przechowywanej w odpowiednim pliku nagłówka (pliku z rozszerzeniem .h takim jak na przykład Blob.h). Następnie odpowiedni kod funkcji musi zostać załączony do pliku implementacji (pliku z rozszerzeniem .cpp, w tym przykładzie Blob.cpp). Obiekt CArchive pomaga przekształcić dane w wygodną do zachowywania w pliku listę. Dodatkowo przechowuje informacje o kierunku transferu danych, pozwalając ustalić, czy są one zachowywane, czy odtwarzane. Kiedy użytkownik wybierze opcję zachowania albo opcję załadowania nowego dokumentu, szkielet aplikacji automatycznie tworzy obiekt archiwum i otwiera plik, z którego lub do którego ma zostać dokonany transfer. Następnie przyzywana jest odpowiednia funkcja Serialize O, której jako parametr podajemy obiekt archiwum. Zachowywanie, ładowanie i transferowanie danych 611 Dodawanie funkcji wirtualnych do utworzonych przez nas klas Dotąd dodawaliśmy funkcje wirtualne korzystając z okna dialogowego New Virtual Ovemde wywoływanego poprzez menu skrótów ClassView. Jednak do klas utworzonych przez nas nie możemy dodawać funkcji wirtualnych w ten sposób. Zamiast tego należy ręcznie dodać definicję klasy do pliku nagłówka klasy (.h) i za pomocą edytora tekstu odpowiedni kod funkcji do pliku implementacji (.cpp). Klasa obiektu archiwum CArchive posiada również parę funkcji zadeklarowanych jako operatory: « oraz ». Funkcje te wykorzystywane są do transferu danych do i z archiwum. Aby zachować zmienną w archiwum, wpisujemy: ar « m_MyVar Żeby załadować zmienną znajdującą się w pliku, piszemy: ar » m MyVar Pisząc funkcję SerializeO możemy ustalić, czy obiekt będzie zachowywany, czy ładowany'do aplikacji za pomocą funkcji składowych isLoadingO i isStoringO obiektu CArchive. Najczęściej wpisujemy w nich kilka linii, które korzystając z operatorów » i « zachowują lub odtwarzają każdą ze zmiennych składowych serializowanego obiektu. Pokazaliśmy to na listingu 23.5 na przykładzie obiektu CBlob. Listing 23.5. LST24_5.CPP- funkcje serializujące klasy CBlob ____ 1 void CBlob::Serialize(CArchive& ar) 2 ( 3 CObject::Serialize (ar); 4 if (ar.IsStoringO) 5 ( 6 ar « m ptPosition; O 7 ar « m crColor; 8 ar « m nSize; 9 ar « m_nShape; 10 } 11 else 12 { 13 ar » m_ptPosition; @ 14 ar » m crColor; 15 ar » m nSize; 16 ar » m_nShape; 17 } 18 } 612 ___ __ Poznaj Visual C++6 O Podczas zachowywania zmienne składowe są dodawane do archiwum. @ Podczas ładowania zawartość archiwum jest dzielona na części i przypisywana kolejnym zmiennym składowym. W linii 4 listingu 23.5 przyzywana jest funkcja składowa isStoring () obiektu CAr-chive, pozwalająca ustalić, czy archiwum w danym momencie zachowuje, czy też ładuje dane. Jeśli archiwum zachowuje dane, musimy te dane do niego przesłać. Jeśli ładuje, musimy pobrać dane z archiwum. W liniach 6-9 archiwum zachowuje dane. Operator « przesyła każdą ze zmiennych składowych klasy CBlob do archiwum, gdzie jest ona następnie dodawana do pliku dyskowego. W liniach 13-16 wykonywana jest operacja odwrotna, operator » pobiera zmienną składową i przypisuje jej wartość zapisaną w archiwum. Aby zachować lub załadować obiekty dokumentu do programu trzeba wezwać funkcję Serialize () dla każdego z nich. W tym celu należy wezwać funkcję serialize () dokumentu i wewnątrz niej przyzywać funkcje Serialize () na każdym z obiektów. Przechowywanie obiektów w tablicy CObArray takiej jak m_BlobArray w tym przykładzie, czyni sprawę całą znacznie prostszą. Tablica cobArray obsługuje serializa-cję i w razie potrzeby zachowa wszystkie swoje elementy. Kiedy dokument jest ładowany z dysku, tablica sprawdzi liczbę obiektów, które będzie przechowywać, ich typ, utworzy obiekty (za pomocą standardowego konstruktora) i na koniec dokona serializacji (funkcja Serialize ()). Oznacza to, że wszystko co musimy zrobić w tym przypadku, to wezwać w tablicy funkcję Serialize (): void CPersistDoc::Serialize(CArchive& ar) ( m_BlobArray.Serialize(ar) ; ) Serializowanie danych dokumentu Większość aplikacji przechowuje znacznie bardziej skomplikowane dane niż te, które rozważamy w tym przykładzie. Niezależnie od tego, serializowanie danych aplikacji zawsze odbywa się za pomocą funkcji Serialize (). W razie potrzeby należy do funkcji Serialize () dokumentu dodać dodatkowe tablice lub inne bardziej skomplikowane klasy kolekcji. Można już w tym momencie zbudować i uruchomić aplikację, aby obejrzeć jak obiekty bąbelków są zachowywane i ładowane do programu przez kod odpowiedzialny za serializację. Poniżej znajduje się opis jak zachowywanie i ładowanie bąbelków przebiega z punktu widzenia użytkownika. Zachowywanie, ładowanie i transferowanie danych 613 Najpierw należy utworzyć kilka bąbelków klikając w obrębie okna. Klikamy teraz w menu File polecenie Save A^s. Aplikacja wyświetli automatycznie okno dialogowe Save As (Zachowaj jako) pozwalające zastąpić domyślną nazwę pliku Untitled.bib nazwą wybraną przez nas. Po wybraniu nazwy i wciśnięciu przycisku Save obiekty są seńalizowane i zachowywane na dysku. O tym, że dane zostały faktycznie zachowane, możemy przekonać się zamykając aplikację. Po ponownym otwarciu aplikacji można będzie załadować zachowany plik wybierając w menu File polecenie Open. Dzięki dołączonemu przez nas w kreatorze AppWizard filtrowi w oknie dialogowym pojawią się tylko pliki z rozszerzeniem .bib. W tym przypadku, jeden plik zachowany przed chwilą. Aby go otworzyć, trzeba kliknąć przycisk Open. I już. Zachowane bąbelki pojawią się dokładnie w tych samych miejscach co przedtem. W tym momencie możemy dodać do dokumentu nowe bąbelki i zachować go ponownie lub też odtworzyć zupełnie nową aplikację albo wykonać jakąkolwiek inną operację, na która pozwala mechanizm serializacji. Nawet jeśli odtworzymy plik po dwóch tygodniach, zachowane bąbelki pojawią się w tych samych miejscach i będą mieć te same kształty i kolory co wtedy, gdy je zachowywaliśmy. Lista najczęściej używanych plików Kolejną usługą, której szkielet aplikacji dostarcza automatycznie, jest lista najczęściej używanych plików. Jeśli zachowaliśmy większą liczbę plików z bąbelkami, na dole menu File pojawi się lista kilku najczęściej używanych plików. Klikając ich nazwy w menu File możemy szybko otworzyć potrzebny plik. Korzystanie z klasy CRecentFileList Szkielety aplikacji SDI i MDI do implementowania listy najczęściej używanych plików wykorzystują klasę CRecentFiieList. Aby zdefiniować własną listę najczęściej obsługiwanych plików i dołączyć ją do menu, należy sięgnąć do tej klasy bezpośrednio (raczej niż poprzez szkielet aplikacji). Klasa CRecentFileList pozwala zdefiniować nazwę i pełną ścieżkę do każdego z plików. Klasa ta potrafi również automatycznie poprawiać listę, w razie potrzeby dodając lub usuwając z niej odpowiednie pliki. Rejestrowanie typów dokumentów Korzystając z systemu Windows zauważyliście zapewne, że dwukrotne kliknięcie pliku dokumentu określonego typu uruchamia powiązaną z nim aplikację. W ten sposób zachowują się pliki, których typ został zarejestrowany w systemie. Wystarczy dołączyć do aplikacji zaledwie kilka dodatkowych linii kodu, aby w ten sam sposób powiązać tworzone przez nią pliki z plikiem wykonywalnym. Przedstawione poniżej linie kodu należy wpisać do funkcji składowej initinstance () klasy aplikacji zaraz po funkcji AddDoc-Template(pDocTemplate);: 614 Poznaj Visual C++ 6 RegisterShellFileTypes() ; EnableShellOpen () ; PATRZ TAKŻE • O dokumentach i danych dokumentu pisaliśmy w rozdziale 12. Obsługa plików Serializacja dokumentów jest bardzo wygodną techniką zapisywania danych dokumentów aplikacji SDI lub MDI, czasami jednak może zajść potrzeba wykonywania bezpośrednich operacji na plikach. Biblioteka MFC dostarcza obudowującej pliki dyskowe klasy CFile. Klasa ta obsługuje wszystkie operacje na plikach, a także zmienne związane z zapisywaniem pliku na dysku. Za pomocą obiektów CFile można tworzyć, otwierać, odczytywać i zapisywać pliki dyskowe korzystając z dostarczanych przez klasę CFile funkcji. Jeśli bezpośrednio seńalizujemy dane dokumentu lub zapisujemy plik, tak czy siak korzystać będziemy z klasy CFile lub z którejś z klas z niej się wywodzących. Wykorzy- stywana w serializacji klasa CArchive zapisując lub odczytując dane z pliku dyskowego w ostatnim etapie łączy się z obiektem klasy CFile. W tym miejscu opiszemy działanie klasy CFile oraz niektóre wyspecjalizowane klasy z niej wywiedzione. Klasa CFile Obiekt klasy CFile można utworzyć na jeden z trzech sposobów. Najprostszy z nich polega na wezwaniu konstruktora bez parametrów, który utworzy nieotwarty obiekt pliku. Przesyłając ten plik funkcji Open () można utworzyć prawdziwy plik dyskowy lub otworzyć już istniejący plik dyskowy. Drugim sposobem jest przyzwanie konstruktora posiadającego jeden parametr, identyfikator obsługi innego już otwartego pliku hFile. Korzystając z tej wersji konstruktora możemy dołączać obiekty CFile do innych już otwartych plików. Trzeci sposób polega na wezwaniu konstruktora, któremu przesyłamy dwa parametry: łańcuch definiujący nazwę pliku oraz ścieżkę do niego i drugi parametr będący kombinacją możliwych znaczników związanych z otwieraniem pliku. Operowanie na plikach systemu Windows Podobnie jak wiele innych klas MFC, klasa CFile wykorzystuje używany przez system operacyjny identyfikator obsługi pliku (ang. file handle). Identyfikator ten jest przechowywany w zmiennej składowej mJnFile obiektu CFile. Jeśli w danym momencie żaden plik nie jest otwarty lub tworzony, zmienna ta będzie przyjmować wartość CFile: .-hFileNull, w innych przypadkach będzie przechowywać systemowy identyfikator bieżącego pliku. Zachowywanie, ładowanie i transferowanie danych 615 Otwieranie plików Jedną z najważniejszych operacji na plikach jest pierwsze otwarcie pliku. Definiując odpowiednie znaczniki deklarujemy, jaką dokładnie operację będziemy wykonywać na pliku o deklarowanej nazwie. Możemy utworzyć nowy plik, otworzyć plik tylko do odczytu lub otworzyć go z możliwością zarówno zapisu, jak i odczytu. Funkcja Open () pozwala za pomocą znaczników przedstawionych w tabeli 23.1 zdefiniować w drugim parametrze nOpenFlags różne rodzaje trybów dostępu do pliku. Pierwszym parametrem jest zawsze nazwa otwieranego lub tworzonego pliku. Znaczniki przesyłane w drugim parametrze można również kombinować ze sobą na różne sposoby: CFile fileMyFile; fileMyFile.Open("MyFile.txt", CFile::modeCreate + CFile::modeNoTruncate); Przedstawiona powyżej kombinacja znaczników otworzy plik MyFile.txt, jeśli takowy istnieje, a jeśli nie utworzy nowy pusty plik o tej samej nazwie. Gdybyśmy przesłali funkcji tylko znacznik CFile: :modeCreate, pusty plik byłby tworzony nawet wówczas, gdyby istniał już plik o tej samej nazwie. Dlatego drugi znacznik CFile: :modeNoTruncate modyfikuje znacznik CFile: :modeCreate tak, aby istniejące pliki o tej samej nazwie nie były usuwane podczas zachowywania pliku. Tutaj do połączenia obu znaczników użyliśmy operatora +, ale równie dobrze możemy skorzystać z operatora logicznego OR wyrażonego symbolem |, tak jak poniżej: fileMyFile.Open("MyFile.txt", CFile::modeCreate l CFile::modeNoTruncate); Oba zapisy są sobie równoważne, ponieważ operator logiczny OR łączy ze sobą dwa znaczniki w taki sam sposób jak dodanie ich do siebie. Jeśli operacja otwarcia pliku zakończy się powodzeniem, funkcja Open () zwraca wartość TRUE, jeśli nie wartość FALSE. Opcjonalnie można funkcji przesłać również trzeci parametr, wskaźnik do obiektu CFileException. Jeśli pliku nie uda się otworzyć, informacje dlaczego operacja się nie powiodła, pojawią się w odpowiednim obiekcie wyjątku (ang. exception). Obiekt CFileException Jeśli w tracie otwierania pliku wystąpi jakiś błąd, program zwraca wskaźnik do obiektu wyjątku CFileException. Przyczyna błędu jest zapisana w jego zmiennej składowej m_cause (dla błędów z wewnętrznej listy klasy CFileException) lub w zmiennej m_iOsError (podającej kody błędów systemu operacyjnego). Niektóre przykładowe kody błędów zmiennej m cause to: CFileException: : fileNotFound (nie można znaleźć pliku), CFileException: :accesDenied (nieuprawniona próba otwarcia pliku) czy CFileException: :diskFull (brak miejsca na dysku). 616 Poznaj Visual C++ 6 Tabela 23.1. Znaczniki trybu funkcji CFile:: Open () Opis efektu Znacznik CFile::modeCreate CFile::modeNoTruncate Zawsze tworzy nowy plik (nawet gdy istnieje już plik o takiej nazwie) Znacznik ten można łączyć ze znacznikiem CFile: :modeCreate, aby otwierać nowy plik tylko wtedy, gdy nie istnieje już plik o podanej nazwie. W przeciwnym razie otwierany jest plik już istniejący Plik otwierany jest jako tylko do odczytu, nie można w nim będzie zapisywać żadnych danych CFile::modeRead CFile::modeWrite CFile::modeReadWrite CFile::shareDenyNone CFile: :sha'reExclusive CFile::shareDenyRead CFile::shareDenyWrite CFile::typeText CFile::typeBinary Plik jest otwarty jako tylko do zapisu, nie można odczytywać zapisanych w nim danych Plik pozwalać będzie zarówno na operacje odczytu, jak i na operacje zapisu Inne procesy systemu mogą odczytywać i zapisywać plik Żaden inny proces systemu nie może odczytywać ani zapisywać pliku, kiedy jest otwarty w bieżącym procesie Żaden inny proces systemu nie może odczytywać pliku, gdy ten jest otwarty Żaden inny proces systemu nie może zapisywać do pliku, gdy ten jest otwarty Znacznik wykorzystywany w niektórych klasach pochodnych do sygnalizowania specjalnych operacji na tekście pliku Nie wykonuje żadnych specjalnych operacji na znakach pliku w trakcie zapisywania lub odczytywania pliku. Znacznik ten jest wymagany tylko w niektórych klasach pochodnych Odczytywanie i zapisywanie pliku Po otwarciu pliku można na nim wykonywać operacje odczytu i zapisu dozwolone przez zdefiniowany wcześniej znacznik trybu. Klasa CFile posiada przeznaczone do obsługi tych operacji funkcje Read() i Writef). Z operacjami odczytu i zapisu pliku powiązana jest bieżąca pozycja pliku, na której się znajdujemy. Zaraz po otwarciu pliku pozycja ta zostaje ustawiona na początku pliku. Jeśli odczytamy 200 bajtów pliku, pozycja pliku jest modyfikowana w ten sposób, aby następna funkcja Read () zaczęła odczytywać plik począwszy od 201 bajtu. Jak zmieniać bieżącą pozycję pliku opowiemy za chwilę. Typowa funkcja Read () będzie wyglądać tak: CFile myFile("MyFile.txt",CFile::modeRead) ; char arMyReadBuffer[200]; Zachowywanie, ładowanie i transferowanie danych 617 UINT uBytesRead = myFile.Read(arMyReadBuffer,sizeof(arMyReadBuffer)) ; Funkcja Read () posiada dwa parametry. Pierwszy jest adresem docelowego bufora (ang. destination buffer). Wszystkie odczytywane z pliku dane będą zapisywane w tym buforze. Drugi parametr informuje funkcję Read () o tym, ile bajtów pliku chcemy odczytać. Może to być określona liczba bajtów lub tak jak w poprzednim przykładzie rozmiar bufora. Odczytywanie obszaru większego niż rozmiar bufora Należy uważać, aby nigdy nie polecać funkcji Read () odczytywać obszaru pliku większego niż rozmiary bufora. W przeciwnym razie odczytywany plik zostanie zapisany w pamięci na obszarze zawierającym inne dane programu, co niechybnie uszkodzi program uniemożliwiając jego dalsze wykonanie. Na koniec funkcja Read () zwraca liczbę odczytanych bajtów. Liczba ta może być równa liczbie zdefiniowanej w funkcji lub jeśli osiągnięty został koniec pliku, może być mniejsza informując wtedy, że wszystkie dostępne w pliku bajty informacji zostały już odczytane. Jeśli funkcja zwróci wartość O, oznacza to, że w pliku nie ma już żadnych bajtów do odczytu. Wiedzę tę możemy wykorzystać w praktyce w prostej bazującej na oknie dialogowym aplikacji. Po dodaniu odpowiedniego kodu do funkcji OninitDialog () okna dialogowego będziemy mogli odczytywać tekst zapisany w pliku dyskowym, edytować go, a następnie zachować kliknąwszy przycisk OK. Tworzymy prostą, bazującą na oknie dialogowym aplikację FileEdit, wykonując czynności opisane w rozdziale 5 i dodając do głównego okna dialogowego styl MultiLine. Następnie za pomocą kreatora CIassWizard przypisujemy zmienną m_EditBox klasy CString do kontrolki Edit (co również zostało opisane w rozdziale piątym). Po dodaniu do funkcji OninitDialog () klasy CFileEditDlg fragmentu kodu przedstawionego na listingu 23.6, będziemy mogli odczytać tekst z pliku (o na stałe zdefiniowanej nazwie) C:\MyFile.txt. Znak \ (odwrotny ukośnik) W linii 5 definiującej nazwę i ścieżkę pliku zamiast jednego pojawiają się dwa odwrotne ukośniki \. Spowodowane jest to tym, że ukośnik \ jest znakiem specjalnym języka C, wykorzystywanym do definiowania znaków, przykładowo zapis \n oznacza nową linię tekstu, zapis \r odpowiada znakowi return (Enter), a zapis \b znakowi (klawiszowi) backspace. Aby zdefiniować znak \, trzeba więc dla każdego odwrotnego ukośnika, który definiujemy, wpisać; \\. 618_____________________________________Poznaj Visual C++ 6 Listing 23.6. LST24_6.CPP - odczytywanie zawartości pliku w kontrolce Edit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // TODO: Tutaj dodaj resztę kodu inicjującego II Zadeklaruj do odczytu otwarty obiekt pliku CFile fileEditText; if(fileEditText.Open("C:\\MyFile.txt",CFile::modeRead)) { // Zadeklaruj bufor przechowujący odczytywany tekst char cBuf[512]; UINT uBytesRead; // Odczytuj dopóki w pliku jest tekst while(uBytesRead = O fileEditText.Read(cBuf,sizeof(cBuf)-l)) ( // Zakończ przez NULL po odczytaniu ostatniego znaku cBuf[uBytesRead] = NULL; // Dodaj bufor do zmiennej symbolizującej kontrolkę m_EditBox += CString(cBuf) ; } // Zamknij plik fileEditText.Close () ; // Prześlij łańcuch zapisany w zmiennej do kontrolki UpdateData(FALSE) ; O Pętla while powtarzana jest dopóki funkcja ReadO nie zwróci wartości informującej, że nie ma już więcej bajtów do odczytu i dotarliśmy do końca pliku W linii 4 listingu 23.6 deklarowany jest obiekt fileEditText klasy CFile otwierany potem w linii 5, gdzie przypisywana mu jest na stałe nazwa i ścieżka C:\MyFile. txt. Zadeklarowany w linii 8 bufor jest wykorzystywany w linii 13 do przechowywania bajtów, które zostaną odczytane z pliku. Drugi parametr funkcji definiujący liczbę odczytywanych bajtów jest o jeden bajt mniejszy od rozmiarów bufora. Dzięki temu można na końcu odczytywanych z pliku danych dodać znak NULL, pozwalający utworzyć łańcuch wyłączany pnez zero (ang. null terminated string). Po zakończeniu działania znakiem NULL Zachowywanie, ładowanie i transferowanie danych_______________ ___ 619 bufor jest konwertowany w obiekt cstring (łańcuch) i dodawany na końcu łańcucha m editBox w linii 19. Łańcuchy wytaczane przez zero Wyłączany przez zero łańcuch jest tablicą znaków zawierającą na końcu znak NULL (lub zero) informujący, że dotarliśmy do końca łańcucha. Łańcuchy wyłączane przez zero są dość powszechne w języku C i istnieje kilka funkcji, które można wykorzystywać do operacji na nich. Przykładowo funkcja strlen() zwraca długość łańcucha. Funkcja stropy () kopiuje zdefiniowany w drugim parametrze łańcuch do obiektu zdefiniowanego przez pierwszy parametr. Funkcja strcmpO porównuje dwa łańcuchy zwracając wartość zero, jeśli łańcuchy są takie same. Inne funkcje znaleźć można w sieciowej dokumentacji Microsoftu pod hasłem String Manipulation Routines. Aby korzystać z tych funkcji, należy dołączyć do programu plik nagłówka string.h. Jeśli podczas pierwszego wykonania funkcji plik jest dłuższy niż rozmiary bufora, pętla jest powtarzana tak długo, aż nie dotrzemy do końca pliku. Gdy w pliku nie ma już więcej bajtów zmiennej uBytesRead przypisywana jest wartość zero, pętla while jest przerywana, a plik w linii 23 zamykany za pomocą funkcji ciose (). Przypisana kontrolce Edit zmienna m_editBox przesyłana jest w linii 26 do kontrolki za pomocą funkcji Upda-teData(FALSE). Część edytora plików odpowiedzialna za odczytywanie jest już gotowa. Po zbudowaniu i uruchomieniu programu będziemy mogli wyświetlić w nim zawartość pliku C:\MyFile.txt. Plik ten możemy utworzyć w windowsowym programie NotePad, zachowując wpisany tekst jako C:\MyFile.txt. Gdy użytkownik wciśnie OK, będziemy musieli zapisać wprowadzone przez niego zmiany w pliku. Możemy to zrobić tworząc nowy plik, a następnie za pomocą funkcji Write () zachowując w pliku zawartość przypisanego kontrolce łańcucha. Aby załączyć do programu kod przedstawiony na listingu 23.7, należy za pomocą kreatora CIassWizard dodać funkcję obsługi OnOk () powiązanego z przyciskiem OK komunikatu BN_CLIKED. Odpowiedni algorytm znajduje się w rozdziale 4. Listing 23.7 LST24_7.CPP - zapisywanie danych z kontrolki Edit w pliku dyskowym 1 void CFileEditDlg::OnOK() 2 { 3 // TODO: Tutaj dodaj własny kod sprawdzający legalność 4 5 // Przypisz zmiennej m EditBox zawartość kontrolki 6 UpdateData(TRUE) ; 7 620 Poznaj Visual C++ 6 8 // Zadeklaruj otwarty obiekt pliku by zapisać dane 9 CFile fileEditText; 10 if (fileEditText.Open("C:\\MyFile.txt", 11 CFile: :modeCreate + CFile: .-modeWrite) ) 12 { 13 // Zapisz łańcuch 14 fileEditText.Write( 15 (LPCSTR)m_EditBox,m_EditBox.GetLength()); O 16 17 // Zamknij plik 18 fileEditText.Close () ; 19 } 20 21 CDialog::OnOK(); 22 } O Cały tekst kontrolki Edit zostanie zapisany w pliku przez funkcję Write () od razu, ponieważ długość łańcucha jest znana Na listingu 23.7 w linii 6 przyzywana jest funkcja UpdateData (), która przypisuje zmiennej składowej m_EditBox bieżącą zawartość kontrolki Edit. Deklarowany w linii 9 obiekt pliku jest otwierany w liniach 10 i 11. Znaczniki CFile; :modeCreate i CFile: : modeWrite informują, że należy utworzyć pożądany plik albo jeśli istnieje inny plik o tej samej nazwie, zapisać dane w nim. Przyzywana w linii 14 funkcja Write () przepisuje po odpowiednim przekonwertowaniu zmienną m_EditBox do bufora (LPCSTR). Procedura tutaj przedstawiona jest znacznie prostsza od tej, którą musieliśmy wykonać w przypadku funkcji Read (), ponieważ znamy długość zapisywanego łańcucha i możemy go zapisać w całości za jednym razem. Wreszcie aby zakończyć całą operację, plik jest zamykany w linii 18 za pomocą funkcji Close (). PATRZ TAKŻE • Więcej informacji na temat kontrolki Edit i wczytywania łańcuchów znaleźć można w roz- dziale 5. • Jak tworzyć okna dialogowe, pisaliśmy w rozdziale 5. f Jak przenosić dane między kontrolkami okna dialogowego a zmiennymi składowymi pisali- śmy w rozdziale 10. Zachowywanie, ładowanie i transferowanie danych 621 Bieżąca pozycja pliku Jak już wspomnieliśmy, bieżąca pozycja pliku jest zmienną definiującą, od którego miejsca w pliku funkcje Read () i Write () powinny kontynuować odczytywanie lub zapisywanie pliku. Pozycję tę można zmieniać - technika ta jest często wykorzystywana do zapisywania w różnych miejscach plików informacji opartych na rekordach o ustalonych rozmiarach. Bieżącą pozycję pliku można ustalić stosując na obiekcie pliku funkcję Get-Position (), która zwróci zmienną typu DWORD, zawierającą bieżącą wartość wskaźnika pozycji pliku. Pozycję pliku można również zmieniać korzystając z odpowiednich funkcji przeszukujących: Seek (), SeekToBegin () i SeekToEnd (), które przenoszą znacznik pozycji do określonego miejsca lub odpowiednio do początku czy końca pliku. Tylko funkcja Seek () wymaga przesłania jej odpowiednich parametrów. Pierwszy z nich, zmienna loff typu LONG, definiuje liczbę bajtów, o którą należy przesunąć pozycję pliku. Jako drugi parametr należy przesłać funkcji jeden ze znaczników przedstawionych w tabeli 23.2, definiujących, czy przesunięcie ma być wykonane względem początku, końca pliku, czy też bieżącej pozycji znacznika pliku. Tabela 23.2. Znaczniki wykorzystywane w funkcji CFile:: Seek () Znacznik Wykonywana operacja CFile: : begin Pozycja jest przesuwana o lOff bajtów od początku pliku CFile: : end Pozycja jest przesuwana o lOff bajtów od końca pliku CFile:: current Pozycja jest przesuwana o lOff bajtów od bieżącej pozycji znacznika Funkcji można przesłać również liczby ujemne, o ile punktem wyjściowym będzie bieżąca pozycja znacznika lub koniec pliku. Przykładowo, aby cofnąć się o 50 bajtów względem bieżącej pozycji, należy wpisać: LONG lOffSet = fileMyFile.Seek(-50, CFile::current) ; Funkcja Seek() zwraca następnie nową bieżącą pozycję pliku. Jeśli zdefiniowanie nowej pozycji pliku się nie powiedzie (zazwyczaj wtedy, gdy funkcja otrzyma polecenie zdefiniowania jej poza końcem lub początkiem pliku), uruchamiany jest wyjątek. Wyjątki klasy CFileException uruchamiane przez funkcję Seek () Wyjątek CFileException jest uruchamiany zawsze, gdy funkcji Seek () nie uda się wykonać zadania. Typowe przyczyny niepowodzenia (definiowane przez zmienną m_cause) to przykładowo CFileException: :badSeek kod informujący, że takiej pozycji nie ma w obrębie pliku. Może również wystąpić błąd CFileException: : irwalidSeek informujący, że próbowaliśmy przesunąć pozycję pliku w pliku, który nie został otwarty. 622 Poznaj Visual C++ 6 Odnajdywanie informacji o wykorzystywanym pliku Dostępny jest bogaty zestaw funkcji zwracających informacje o różnych parametrach bieżącego otwartego pliku. Funkcja GetLenght () zwraca zmienną typu DWORD informującą o bieżącej długości pliku (związana z nią funkcja SetLenght () rozciąga plik do odpowiedniej długości). Funkcja GetStatusO zwraca informacje o tym, kiedy plik został utworzony i kiedy był modyfikowany, jak również informuje, czy nie jest na przykład plikiem tylko do odczytu. Funkcja ta ma dwie formy. W pierwszej rozpatruje już otwarty plik zapełniając odpowiednimi danymi o statusie przesłany jej jako parametr obiekt CFi-leStatus. W drugiej wersji jako pierwszy parametr należy zdefiniować nazwę i ścieżkę do badanego pliku, a jako drugi, tak jak poprzednio, odwołanie do obiektu CFileStatus. Przykładowo, aby dowiedzieć się, kiedy ostatnio modyfikowany był plik C:\MyFile.txt, należy dodać do funkcji obsługi OnOK () (po operacji związanej z zapisem pliku) następujący fragment kodu: CFileStatus statusEditText; CFile::GetStatus("C:\\MyFile.txt",statusEditText) ; AfxMessageBox(_T("Last Modified on")+ statusEditText.m_mtime.Format("%A, %B %d, %Y")) ; Funkcja GetStatus () przyzywana jest razem z definiującym jej zakres kompetencji operatorem CFile::, gdyż jest to funkcja statyczna, która wzywana z parametrem definiującym nazwę pliku nie wymaga podania obiektu. Jeśli mamy otwarty plik, powinniśmy zamiast tego wpisać: fileMyOpenFile.GetStatus (statusEditText). Jedna ze zmiennych składowych obiektu, m_mtime, przechowująca datę ostatniej modyfikacji pliku zostanie teraz przez funkcję CTime:: Format sformatowana tak, aby wyświetlić łańcuch tekstu taki jak: Last Modified on Saturday, February 28, 19 9 8 (Ostatnio modyfikowany w sobotę, 28 lutego 1998). Jeśli teraz zbudujemy i uruchomimy aplikację, to po wciśnięciu przycisku OK zobaczymy okno komunikatu, takie jak na rysunku 23.3. 131 Last Modified onSaturday, February28,1998 OK Rysunek 23.3. Okno komunikatu wyświetlające datę ostatniej modyfikacji pliku Inne zmienne składowe klasy CFileStatus przedstawione zostały w tabeli 23.3. Zachowywanie, ładowanie i transferowanie danych 623 Tabela 23.3. Zmienne składowe klasy CFileStatus definiujące status pliku Zmienna razem z typem Przechowywane informacje CTime m_mtinie Data i czas ostatniej modyfikacji CTime m_atime Data i czas ostatniego odczytu pliku CTime m_ctime Data i czas utworzenia pliku LONG m_size Rozmiar pliku w bajtach BYTE m_attribute Różne atrybuty pliku (patrz tabela 23.4) char m_s zFullName Pełna nazwa pliku i ścieżka dostępu Większość z wymienionych tu zmiennych składowych jest dość oczywista, poza zmienną m_attribute, która przechowuje kilka znaczników, które można testować za pomocą operatora logicznego & (AND). Znaczniki te przedstawione zostały w tabeli 23.4, a testuje się je w następujący sposób: if (statusEditText.m_attribute & CFile::readOnIy) AfxMessageBox("Plik tylko do odczytu"); Plik jest testowany pod kątem tego, czy nie został mu przypisany atrybut tylko do odczytu. Pozostałe atrybuty pliku są ignorowane, ponieważ wyrażony znakiem & operator logiczny AND pominie wszystkie bity poza bitem File:: readOniy. Operacja zwróci wartość TRUE, jeśli bit ten będzie miał wartość l, w przeciwnym wypadku zwróci wartość FALSE. Tabela 23.4. Znaczniki zmiennej składowej m_attribute klasy CFileStatus Znacznik Wartość szesnastkowa Opis normal 0x01 Zwykły plik, żadnych specjalnych atrybutów readOniy 0x01 Plik tylko do odczytu hidden 0x02 Plik ukryty system 0x04 Plik systemowy volume 0x08 Plik jest w rzeczywistości osobnym dyskiem directory 0x10 Plik jest katalogiem archive 0x20 Plik archiwum Wśród innych funkcje służących do pobierania informacji o pliku znaleźć można funkcję GetFileName () zwracającą nazwę pliku (ale bez ścieżki do niego). 624_____________________________________Poznaj Visual C++ 6 Z kolei funkcja GetFilePath () zwraca pełną nazwę pliku i ścieżkę do niego. Funkcja GetFileTitle () zwraca tylko podstawową część nazwy pliku bez ścieżki i rozszerzenia, tak więc zastosowana na pliku C:\MyFile.txt zwróci tekst MyFile. Zmienianie nazwy pliku i usuwanie pliku Do zmieniania nazwy pliku i jego usuwania służą dwie statyczne funkcje Rename () i Remove (). Funkcja Rename () wymaga dwóch parametrów: starej nazwy pliku i nowej nazwy pliku. Ponieważ jest to funkcja statyczna, aby z niej skorzystać, nie trzeba otwierać żadnego pliku, wystarczy skorzystać z operatora zakresu (ang. scope operator, operator definiujący zakres kompetencji funkcji), CFile::, tak jak to pokazujemy poniżej: CFile::Rename("C:\\MyFile.txt","C:\\NewName.txt") ; Funkcja Remove () również jest funkcją statyczną, wymagającą jednak już tylko jednego parametru - nazwy pliku, który ma usunąć: CFile::Remove("C:\\MyFile.txt") ; Obie funkcje w razie niepowodzenia uruchamiają odpowiednie wyjątki. Funkcje statyczne w C++ Funkcja statyczna jest funkcją składową klasy języka C++, do przyzwania której nie potrzebny jest kontekst obiektu. Podczas gdy większość funkcji składowych potrzebuje wskaźnika this informującego, że będą korzystać z lokalnych zmiennych składowych bieżącego obiektu, funkcja statyczna nie korzysta z żadnych zmiennych składowych i może być przyzywana nawet wówczas, gdy nie istnieje żaden egzemplarz obiektu. Klasy pochodne klasy CFile Klasa CFile dostarcza podstawowych narzędzi do obsługi plików. Do bardziej specjalistycznych zadań można wykorzystać kilka innych klas wywodzących się z klasy CFile. Szczegółowe opisywanie wszystkich z nich zajęłoby zbyt wiele miejsca, dlatego skoncentrujemy się tylko na kilku wybranych. • Klasa CMemFile dostarcza pliku przechowywanego w pamięci. Przydaje się ona, gdy potrzebujemy pewnych narzędzi związanych z plikami, ale zależy nam na szybkości, którą oferuje korzystanie z pamięci operacyjnej. " Wywodząca się z klasy CMemFile klasa CSharedFile alokuje i wiąże pliki ze wspólną pamięcią (ang. shared memory). Klasa ta jest zazwyczaj wykorzystywana w we-wnątrzprocesowej komunikacji takiej, jak transfer do i ze schowka. O obsłudze transferu danych za pomocą schowka będziemy mówić za chwilę. Zachowywanie, ładowanie i transferowanie danych 625 • Klasa COleStreamFile jest klasą bazową dla wykorzystywanych w OLE operacji zapisywania w pliku. Z klasy tej wywiedzionych zostało kilka klas obsługujących konsolidowanie i osadzanie obiektów oraz automatyzację OLE, między innymi: CMoniker-File, CAsyncMonikerFile, CDataPathProperty i CCachedDataPathProperty. " Klasa csocketFile pozwala traktować połączenia sieciowe tak jakby były plikami umożliwiając na transfer danych dokumentów przez sieć w oparciu o mechanizm se-rializacji (za pomocą obiektu CArchive). • Klasa CStudioFile obudowuje pewne występujące w starszych wersjach koncepty języka C takie jak stdin i stdout pozwalające na przekierunkowania z wiersza poleceń. Klasa ta wykorzystuje powyższe znaczniki otwierania trybu tekstowego do konwertowania między trybami definiowanymi przez znaki i (ang. carriage retum, powrót do początku i linę feed, koniec linii). Z klasy tej wywodzi się klasa cinternetFile dodająca bazowe funkcje klasy do klasy CHttpFile, która pomaga obsługiwać dokumenty HTML World. Wide Web, jak również klasa CGopherFile wspomagająca obsługę teraz już wymierającego formatu serwera Gopher. • Nie wywodząca się z klasy CFile, ale związana z problemem jest klasa CRecentFi-leList. Oferuje ona opisaną wcześniej usługę listy najczęściej używanych plików. Za pomocą tej klasy można utworzyć własną listę najczęściej używanych plików. PATRZ TAKŻE • Więcej informacji na temat klas obsługujących Internat można znaleźć w rozdziale 19. Transferowanie danych za pomocą schowka Możliwość wycinania danych z jednej aplikacji i wklejania ich do innej była od początku jednym z podstawowych założeń systemu Windows. Rejestrując typy danych obsługiwane przez aplikację, umożliwiamy przesyłanie i odbieranie danych pobranych z innych egzemplarzy tej samej aplikacji lub nawet z innej aplikacji obsługującej ten sam format danych. Nowe i stare metody transferowania za pośrednictwem schowka Wraz z pojawieniem się OLE, dawny mechanizm DDE (od ang. Dynamie Data Ex-change) obsługi schowka wyparty został przez wykorzystywane w OLE obiekty i źródła danych (ang. data source). Programując aplikację powinniśmy korzystać raczej z metod OLE niż z DDE, ponieważ są lepiej obsługiwane przez bibliotekę MFC, oferują większe możliwości oraz łatwiejszą obsługę operacji typu przeciągnij i upuść (ang. drag-and-drop). 626____________________________________ Poznaj Visual C++ 6 Definiowanie formatów danych schowka Pierwszą rzeczą, którą należy zrobić, aby móc transferować dane z i do schowka, jest zarejestrowanie formatów danych obsługiwanych przez aplikację. Formaty te mogą należeć do zbioru standardowych formatów prezentowanych w tabeli 23.5. Możemy również za pomocą funkcji RegisterCIipboardFormat () zarejestrować własny unikalny format danych. Niezależnie czy jest to nasz, czy standardowy format, będzie on zmienną typu UINT. Tabela 23.5. Standardowe formaty danych schowka Znacznik formatu Opis formatu CF_TEXT Prawdopodobnie najczęściej wykorzystywany format wykorzystujący do transferu tekstu znaki i definjujące koniec linii CF_BITMAP Dane są identyfikatorem obsługi mapy bitowej CF_DIB Dane są przechowywane jako niezależna od urządzenia (ang. device independent) mapa bitowa C F_T l FF Dane są rysunkiem przechowywanym formacie graficznym TIFF CF_WAVE Dane są zapisane w dźwiękowym formacie PCM CF_PALETTE Dane są identyfikatorem obsługi do palety kolorów CF_METAFILEPICT Dane mają format metapliku zdefiniowanego przez strukturę METAFILEPICT Do opisanego wcześniej przykładu dodamy obsługę techniki wytnij i wklej (ang. cut-and-paste). W ten sposób będziemy mogli do obsługi schowka wykorzystać użyty wcześniej kod obsługujący serializację. Ponieważ plik bąbelków nie jest formatem obsługiwanym przez schowek, musimy zarejestrować go za pomocą funkcji RegisterCIipboardFormat (). Formaty schowka Format danych schowka jest powszechnie akceptowanym w systemie formatem pozwalającym różnym programom rozpoznawać definiowany przez niego typ danych i sprawdzać, czy obsługują dany typ. Z formatami schowka związana jest również funkcja countdipboardFormat () zwracająca bieżącą liczbę zarejestrowanych w schowku formatów. Kolejna funkcja, EnumCIipboardFormat () zwraca kolejne zarejestrowane w schowku formaty. Używając jej po raz pierwszy, należy jej przesłać zero, funkcja zwróci wtedy pierwszy format. Przesyłając jej pierwszy zarejestrowany format, otrzymamy drugi, przesyłając drugi, trzeci itd. Zachowywanie, ładowanie i transferowanie danych 627 Funkcja RegisterdipboardFormat () wymaga jednego parametru — łańcucha identyfikującego w sposób jednoznaczny rejestrowany format danych. Zwraca zmienną typu UINT reprezentującą zarejestrowany format. Kolejne programy rejestrujące ten sam format za pomocą funkcji RegisterdipboardFormat () będą otrzymywać z powrotem wartość zdefiniowaną przez pierwszy program. Dzięki temu jeśli w systemie działa jednocześnie kilka egzemplarzy tej samej aplikacji, rejestrowany przez nie format będzie identyfikowany przez system jako ten sam format. Jeśli dołączymy funkcję RegisterdipboardFormat () do funkcji konstruktora CPer-sistDoc, format będzie rejestrowany w momencie tworzenia dokumentu. CPersistDoc::CPersistDoc() ( // TODO: dodaj tu jednorazowy kod konstrukcji m uCIipFormat = RegisterdipboardFormat ("PersistdipFormat") ; } Zwracany przez funkcję format jest przechowywany w zmiennej składowej, aby obiekt dokumentu mógł z niego korzystać również później. Zmienną tę można załączyć do definicji klasy dokumentu zaraz po zmiennej tablicy m_BlobArray: CObArray m BlobArray; UINT m_udipFormat; // ** New Format Member To już cały kod niezbędny do zarejestrowania nowego formatu danych schowka. Jeśli aplikacja korzysta z któregoś ze standardowych typów, nie będziemy się musieli w ogóle; przejmować rejestracją typu. Jeśli przykładowo korzystamy z typu CF_TEXT, będziemy funkcjom schowka obsługującym transfer przesyłać po prostu identyfikator CF_TEXT. Kopiowanie danych do schowka Kolejną rzeczą, którą należy zaprogramować w aplikacji jest obsługa operacji wytnij i kopiuj. Operacja wycinania (ang. cut) usuwa dane z miejsca, z którego zostały wycięte. Kod odpowiedzialny za kopiowanie można załączyć do funkcji obsługi polecenia Q)py menu Edit. Funkcję obsługi OnEditCopyO można załączyć za pomocą kreatora Ciass-Wizard. Analogiczna funkcja dla polecenia wycinania nosi nazwę OnEditCut (). Odpowiednia instrukcja opisująca dodawanie do programu funkcji obsługi poleceń menu znajduje się w rozdziale 13. DDE Przed pojawieniem się OLE do transferowania danych do i z schowka wykorzystywany był mechanizm zwany DDE (korzystający z segmentów wspólnej pamięci). Funkcje DDE nadal istnieją, aby gwarantować kompatybilność z programami pisanymi dla wcześniejszych wersji systemu, ale przy programowaniu nowych aplikacji lepiej korzystać z funkcji OLE, ponieważ oferują większe możliwości i łatwiejszą obsługę operacji typu przeciągnij i upuść. 628_____________________________________Poznaj Visual C++ 6 Gdy mamy już odpowiednie funkcje obsługi, możemy dodać do programu przedstawiony na listingu 23.8 kod obsługi schowka. Cała operacja przebiega w trzech etapach: najpierw tworzony jest obiekt źródła danych OLE, następnie dane dokumentu serializo-wane są w plik wspólnej pamięci, a na koniec przesyłane do schowka. Listing 23.8. LST24_8.CPP - serializowanie danych do schowka w celu obsługi polecenia Copy 1 void CPersistDoc::OnEditCopy() 2 ( 3 // Utwórz obiekt źródła danych 4 COleDataSource* pDataSource = new COleDataSource; 5 6 // Utwórz plik wspólnej pamięci i połącz go z obiektem CArchive 1 CSharedFile fileClipCopy; 8 CArchive arCIipCopy(SfileClipCopy,CArchive::storę); 9 10 // Serializuj dokument do tego archiwum 11 Serialize(arCIipCopy); 12 arCIipCopy.Close(); 13 14 // Pobierz globalny identyfikator pamięci z pliku mem 15 HANDLE hGlobalMem = fileClipCopy.Detach(); 16 if (hGlobalMem) 17 { 18 // Przypisz danym odpowiedni format 19 pDataSource->CacheGlobalData( O 20 m uCIipFormat, hGlobalMem) ; 21 // Prześlij je do schowka 22 pDataSource->SetClipboard() ; 23 } 24 else AfxMessageBox("Can't alloc memory for copy"); 25 } @ O Funkcja CacheGlobalData () jest jedną z wielu metod definiowania odwołań do obiektu CDataSource. © Nie musimy usuwać z pamięci obiektu źródła danych, ponieważ schowek robi to automatycznie Pierwszą rzeczą, którą należy zrobić, jest utworzenie w linii 4 obiektu COleDataSource. Ten obiekt OLE umożliwia w zależności od potrzeb aplikacji kilka różnych rodzajów trans- Zachowywanie, ładowanie i transferowanie danych 629 feru danych; nie tylko transfer za pośrednictwem schowka, ale również na przykład przenoszenie danych techniką przeciągnij i upuść. Deklarowany w linii 7 obiekt CSharedFile jest specjalnym rezydującym w pamięci typem pliku. Prosty konstruktor automatycznie otwiera plik rezydujący w pamięci i alokuje dla potrzeb przechowywanych w nim danych pewien zasób wspólnej pamięci. W liniach 8-11 wykonywana jest serializacja danych dokumentu, dokładnie w taki sam sposób jak w pierwszym przedstawionym w tym rozdziale przykładzie. Serializowane dane są następnie przechowywane w pliku wspólnej pamięci (ang. shared memory file). Dokładniej mówiąc, do tworzonego w linii 8 obiektu archiwum CArchive przypisywany jest za pomocą znacznika CArchive:: storę plik wspólnej pamięci. Znacznik uruchamia funkcję archiwum isStoring (), która zwraca wartość TRUE i zachowuje w pliku dane z archiwum. Archiwum jest zamykane w linii 12, żeby przesłać jego zawartość do pliku. Jeśli potrzebny będzie większy obszar wspólnej pamięci, plik CSharedFile automatycznie alokuje tyle pamięci, ile potrzeba. W liniach 15 i 16 za pomocą funkcji DetachO pobierany jest z pliku identyfikator wspólnej (globalnej) pamięci, który następnie będziemy mogli wykorzystać w źródle danych. W linii 19 funkcji CacheGlobalData () obiektu CDataSource przesyłany jest identyfikator pamięci wraz z identyfikatorem formatu danych. Źródło danych jest już gotowe, aby przesłać je do schowka, co też robimy w linii 22 za pomocą jego funkcji składowej SetCIipboardO. Aby zdefiniować obiekty OLE i funkcje schowka, trzeba załączyć za pomocą dyrektywy #include kilka dodatkowych plików. Umieszczamy je tuż ponad definicją klasy w pliku nagłówka PersistDoc.h: łinclude // ** Dodatkowy plik nagłówka #include // ** Dodatkowy plik nagłówka ciass CPersistDoc : public CDocument { ... Ponieważ korzystamy z OLE, musimy inicjować biblioteki OLE i COM. Wykonujemy to wpisując do funkcji składowej Initinstance () klasy cpersistApp po funkcji AfxEnableControlContainer () funkcję Afx01elnt(): BOOL CPersistApp::Initinstance() ( AfxEnableControlContainer(); Afx01elnit(); // ** Inicjacja biblioteki OLE 630__________________ ____ Poznaj Visual C++6 Kody zwracane przez funkcję Afx01elnt ( ) Dla uproszczenia przykładu kod zwracany (ang. retum code) przez funkcję Afx01elnt () nie jest tutaj sprawdzany. Niemniej warto wiedzieć, że jeśli inicjacja OLE nie powiedzie się, funkcja zwraca wartość zero. Zazwyczaj oznacza to, że mamy w systemie zainstalowaną niewłaściwą wersję biblioteki DLL. Jednak ponieważ w takim przypadku pada większość programów działających w systemie, nie ma potrzeby martwić się na zapas. W ten sposób zaprogramowaliśmy już wszystko co trzeba do obsługi operacji kopiowania do schowka. Możemy teraz wykorzystać kod z funkcji obsługi polecenia Cppy i wpisać go do funkcji obsługi polecenia Cut, dodając jeszcze na koniec funkcję usuwającą dane dokumentu po ich wycięciu. Funkcja obsługi OnEditCut () powinna wyglądać mniej więcej tak: void CPersistDoc::OnEditCut() { OnEditCopyO ; DeleteBlobs () ; UpdateAllViews(NULL) ; ) Wklejanie danych ze schowka Drugą stroną transferu danych za pomocą schowka jest funkcja wklejająca. Funkcja ta używa specjalnego obiektu OLE zwanego coleDataObject. Obiekt sprawdza, czy dane odpowiedniego typu są dostępne w schowku i pobiera je stamtąd. Reszta kodu związanego z wklejaniem odwraca po prostu proces serializacji, przesyłając przechowywane w schowku bąbelki do innego dokumentu za pośrednictwem kolejnych obiektów CArchive i csha-redFile, tak jak to pokazaliśmy na listingu 23.9. Listing 23.9. LST24_9.CPP - wklejanie danych ze schowka do dokumentu poprzez serializację danych obiektu CDataObject 1 void CPersistDoc::OnEditPaste() 2 { 3 // Utwórz obiekt danych i powiąż go ze schowkiem 4 COleDataObject oleTarget; 5 oleTarget.AttachCIipboard(); 6 if (oleTarget.IsDataAvailable(m_uClipFormat)) 7 ( 8 // Pobierz identyfikator globalnej pamięci 9 HANDLE hGiobalMem = Zachowywanie, ładowanie i transferowanie danych 631 10 oleTarget.GetGlobalData(m_uClipFormat) ; 11 if (hGlobalMem) 12 { 13 // Przenieś dane do dokumentu 14 CSharedFile fileTarget; 15 fileTarget.SetHandle(hGlobalMem) ; 16 CArchive arTarget(SfileTarget,CArchive::load); O 17 DeleteBlobs (); 18 19 // Uruchom serializację i odśwież widok 20 Serialize(arTarget); 21 UpdateAllViews(NULL) ; 22 ) 23 } 24 } O Operacja wklejania jest w gruncie rzeczy odwróceniem operacji kopiowania — dane są ładowane ze wspólnej pamięci do dokumentu. Obiekt oleTarget klasy COleDataObject jest deklarowany w linii 4 i przypisywany do schowka w linii 5. Jeśli jego funkcja !sDataAvailable () zwróci wartość TRUE, oznacza to, że zapisany w zmiennej m_udipFormat format danych schowka zgadza się z formatem aplikacji i dane można transferować. W linii 9 z obiektu COleDataObject pobierany jest identyfikator wspólnej pamięci, a w linii 14 jest deklarowany i przypisywany identyfikatorowi plik wspólnej pamięci fileTarget. Następnie w linii 16 tworzony jest i definiowany jako „do załadowania" obiekt arTarget klasy CArchive. Istniejące w dokumencie bąbelki są w Unii 17 usuwane, a następnie w linii 20 z pliku pamięci ładowane są nowe. Na koniec w linii 21 zawartość okna aplikacji jest uaktualniana, aby wyświetlić wklejone ze schowka dane. Tak jak mówiliśmy w rozdziale, można załączyć dla menu Pastę (Wklej) funkcję obsługi uaktywniającą opcję tylko wtedy, gdy w schowku znajdują się dane odpowiedniego typu: void CPersistDoc::OnUpdateEditPaste(CCmdUl* pCmdUl) { COleDataObject oleTarget; oleTarget.AttachCIipboard() ; pCmdUI->Enable(oleTarget.IsDataAvailable(m_uClipFormat)) ; } 632 Poznaj Visual C++ 6 Funkcja AttachCIipboard () Funkcja AttachCIipboard () łączy obiekt COleDataObject z danymi przechowywanymi w schowku. Dane pozostaną zablokowane w schowku, dopóki nie wezwiemy funkcji RelaseO obiektu COleDataObject. Funkcja ta jest automatycznie przyzywana przez destruktor klasy COleDataObject, gdy pod koniec funkcji obiekt COleDataObject wychodzi poza jej zakres kompetencji. Powyższy fragment kodu sprawdza, czy dane odpowiedniego typu są dostępne w schowku i uaktywnia polecenie menu, jeśli testująca to funkcja lsDataAvailable () zwróci wartość TRUE. Jeśli nie, funkcji EnableO obiektu pCmdUl przypisywana jest wartość FALSE i polecenie menu pozostaje nieaktywne. W ten sposób zakończyliśmy pisanie kodu odpowiedzialnego za obsługę operacji wycinania i wklejania danych za pośrednictwem schowka. Można teraz zbudować i dwukrotnie uruchomić aplikację. Korzystając z odpowiednich poleceń będziemy mogli przenosić zawartość okna z jednej aplikacji do drugiej (przykładowe okno z rozwiniętym menu Ędit przedstawione zostało na rysunku 23.4). Jako że do programu dołączony został także kod umożliwiający serializację i zachowywanie zawartości okna, można zachować zawartość okna przed wklejeniem nowych danych. Rysunek 23.4. Wycinanie bąbelków z jednej aplikacji i wklejanie ich do drugiej PATRZ RÓWNIEŻ » Więcej informacji na temat uaktywniania i wyłączania poleceń menu można znaleźć w rozdziale 13. ^ Więcej informacji na temat OLE i COM można znaleźć w rozdziale 25. Rozdział 24 Bazy danych i widok Record Konfigurowanie źródła danych ODBC Łączenie z bazą danych w aplikacjach MFC Otwieranie zbiorów rekordów i odczytywanie rekordów Dodawanie, usuwanie i edytowanie rekordów bazy danych Tworzenie projektu opartego na widoku Record Bazy danych Duże firmy gromadzą każdego dnia swojej działalności olbrzymie ilości danych. Prze- chowują dane o klientach i dostawcach, wielkości sprzedaży, zapasach magazynowych i wiele innych informacji. Większość firm przechowuje te informacje w bazach danych, a ściślej w tzw. DMS-ach (ang. database managment system, system zarządzania bazami danych). DMS jest to taki program, który przechowuje dane w wysoko shierarchizowanej strukturze i dostarcza narzędzi do sprawnego odczytywania i uaktualniania danych. Istnieją dwa podstawowe typy baz danych: relacyjne bazy danych (ang. relational databases) i obiektowe bazy danych (ang. object databases). Różnica między nimi leży w sposobie, w jaki obsługują dane. Relacyjne bazy danych ograniczają się do rozpoznawania prostych typów danych (znaków, łańcuchów, liczb całkowitych itp.) i nie pozwalają na tworzenie nowych typów danych. Obiektowe bazy danych pracują na wyższym poziomie: pozwalają na tworzenie definiowanych przez użytkownika typów danych. Obiekty danych są w tym przypadku odbiciem obiektów wykorzystywanych w programowaniu obiektowym. Przykładowo, obiekt przechowywany w bazie danych może być człowiekiem lub pojazdem (zamiast np. łańcuchem). Niniejszy rozdział koncentruje się na relacyjnych bazach danych, ponieważ istnieją dłużej i są szerzej używane niż obiektowe bazy danych. 634 Poznaj Yisuał C++ 6 Relacyjne bazy danych Przed pojawieniem się komercyjnych baz danych aplikacje obsługujące przechowywanie danych tworzone były w oparciu o struktury plikowe. Tylko twórca programu tak naprawdę wiedział, w jaki sposób dane są zorganizowane. Oznaczało to, że jeśli użytkownik programu potrzebował niestandardowych, przystosowanych do jego potrzeb operacji na przechowywanych w bazie danych, konieczne było (zazwyczaj kosztowne i czasochłonne) przebudowanie programu. Relacyjne bazy danych usunęły większość tych problemów standaryzując projektowanie systemów przechowujących dane. Dane w relacyjnych bazach danych organizowane są tablicach, kolumnach i rzędach (ang. tables, columns, rows). Typowa baza danych może na przykład posiadać tablicę Klienci z kolumnami zatytułowanymi Nazwisko i Numer telefonu. Każdy z rzędów tablicy odpowiadać będzie jednemu klientowi. Przykładowo, jeden rząd może być zatytułowany MIKOM, a kolejny Microsoft. Układ tablic i kolumn przechowywany jest w schemacie bazy danych (ang. database schemd). Schemat ten definiuje typ danych każdej kolumny i wzajemne zależności między tablicami. ODBC Relacyjną bazę danych możemy zakupić od wielu różnych dostawców. Każda baza danych ma swoją specyficzną strukturę i własny zestaw odwołań do funkcji API. Jeśli chcielibyśmy pisać uniwersalną współdziałającą z różnymi bazami danych aplikację, musielibyśmy ogromną ilość czasu i energii poświęcić na zaznajomienie się nawet z kilkoma najbardziej podstawowymi bazami danych. Poza tym aplikacje z zasady nie powinny ograniczać się tylko do obsługi wybranych baz danych, ale współdziałać ze wszystkimi. Aby możliwe było pisanie aplikacji współpracujących z każdą bazą danych dowolnego producenta oprogramowania, potrzebna jest wspólna dla wszystkich baz danych metoda programowania. Taka metoda istnieje i nazywa się ODBC (od ang. Open Database Con-nectivity). Rozprowadzanie aplikacji ODBC Do aplikacji opartej na ODBC (jeśli zamierzamy ją sprzedawać) należy dołączyć kilka dodatkowych plików. Pliki te można znaleźć w katalogu OS\System na CD-ROM-ie Visual C++. Więcej informacji na ten temat zawiera znajdujący się w tym katalogu plik REDISTRB.WRI. ODBC zapewnia wspólny interfejs programowania dla różnych odmian relacyjnych baz danych. Aby to było możliwe, musi istnieć narzędzie pośredniczące, które interpretować będzie standardowe odwołania do funkcji ODBC na odwołania specyficzne dla określonej bazy danych. Narzędziem takim jest sterownik ODBC dostarczany przez twórcę bazy danych lub inną firmę specjalizującą się w dostarczaniu oprogramowania zabezpieczającego kompatybilność programów. ODBC jest obecnie powszechnie uznawanym standardem Bazy danych i widok Record 635 i sterowniki ODBC są dostępne dla wszystkich najważniejszych istniejących na rynku baz danych. Programując aplikację należy zainstalować sterownik ODBC dla bazy danych, z której będziemy korzystać podczas tworzenia programu. Najbardziej rozpowszechnione sterowniki ODBC Microsoftu można zainstalować w oparciu o aplikacje takie jak Office czy Visual Studio. Aby korzystać z innych baz danych, należy poprosić sprzedawcę konkretnej bazy o dostarczenie odpowiedniego sterownika ODBC. Rysunek 24.1 pokazuje, w jaki sposób sterownik ODBC łączy bazę danych z programowaną przez nas aplikacją. Baza danych ABC Sterownik ABC ODBC Administrator ODBC Aplikacja DB.exe Baza danych XYZ Sterownik XYZ ODBC Rysunek 24.1. Łączenie aplikacji z bazą danych w standardzie ODBC Polecenia są kierowane do sterownika ODBC, a następnie przesyłane do bazy danych za pomocą języka SOŁ (od ang. Structured Query Language). Język ten został stworzony specjalnie do obsługi komunikacji z bazami danych i jest obecnie powszechnie akceptowanym standardem. Odmian języka SOŁ jest praktycznie tyle, ile jest firm sprzedających bazy danych, przy czym każda firma dodaje tu własne modyfikacje. Istnieje pewien minimalny zestaw wymagań gwarantujący, że sterownik ODBC obsługiwać będzie pewien zestaw podstawowych poleceń. Nadal jednak sterownik będzie mógł obsługiwć polecenia niestandardowe, ponieważ ODBC pozwala na bezpośrednie wykonywanie instrukcji języka SOŁ. Niemniej dołączenie do aplikacji nietypowych instrukcji może spowodować, że aplikacja utraci zdolność współpracy z innymi bazami danych, a to przecież jest największą zaletą ODBC. Język SOŁ definiuje trzy typy poleceń: DDL, DML i DCL. Polecenia DDL (od ang. Data Definition Language) służą do tworzenia i modyfikowania schematu bazy danych. Przykładowe instrukcje DDL to CREATE DATABASE i CREATE TABLE. Polecenia DML (od ang. Data Manipulation Language) są wykorzystywane w zapytaniach (ang. ąuery) i do manipulowania zapisanymi w bazie wartościami. Cztery najważniejsze instrukcje DML to SELECT, INSERT, UPDATE i DELETE. Każdej instrukcji przesyłane są parametry definiujące, której kolumny (lub kolumn) i tablicy (lub tablic) dotyczy określona instrukcja. Polecenia DCL (od ang. Data Control Language) są wykorzystywane do przydzielania określonym użytkownikom szerszych lub węższych praw dostępu do zawartości bazy. 636 Poznaj Visual C++ 6 Konfigurowanie źródła danych Pierwszym zadaniem przy korzystaniu z ODBC jest skonfigurowanie źródła danych (ang. [alion Dałaba^e; c:\Dstabases\stdte932,mdb j | "letecC 1| Cteate... l Be'pa".. S^slem Database - ............. ff Nong r Daiab«e; Rysunek 24.4. Okno dialogowe ODBC Microsoft Access Setup 6. W polu Data Source Narne wpisz nazwę źródła danych. Tutaj wpisaliśmy Student Registration (Rejestracja studentów). Możemy wpisać dowolną nazwę, pamiętając jednak o tym, żeby nie utworzyć dwóch źródeł o tej samej nazwie. 7. Dodatkowo w polu Description można wpisać opis źródła danych. Tutaj wpisaliśmy Course enrollment and administration (Zaliczenia i sprawy administracyjne). 8. Kuknij przycisk Select. Pojawi się okno dialogowe Select Database. 638_____________________________________Poznaj Visual C++ 6 9. Wpisz nazwę pliku bazy danych w polu Database Name lub wybierz go z listy katalogów Directories. Lokalizacja i nazwa wybranej bazy danych wyświetlana jest w oknie dialogowym. 10. Aby zamknąć okno dialogowe ODBC Microsoft Access Setup kliknij OK. Źródto Sudent Registration powinno pojawić się teraz na liście User Data Sources. 11. Aby zamknąć okno dialogowe ODBC Data Souree Administrator, kliknij OK. Zamknij Panel sterowania. Okno przedstawione na rysunku 24.4 jest specyficzne dla bazy Access Microsoftu. Inne bazy danych będą w tym momencie wyświetlać własne konfiguracyjne (ang. setup) okna dialogowe i wymagać definiowania innych opcji. Problemy z podłączeniem źródła danych Jeśli pojawią się problemy z podłączeniem do bazy danych, można spróbować zmienić typ źródła danych. Przykładowo, jeśli wybraliśmy User DSN i pojawią się problemy, możemy spróbować wybrać typ System DSN. Warto tu zauważyć, że możemy skonfigurować dwa typy źródła danych: domyślny typ DSN użytkownika (ang. userDSN) i DSN systemu (ang. system DSN). DSN użytkownika jest dostępny tylko dla użytkownika (administratora), który dodał dane źródło, podczas gdy DSN systemu jest dostępne dla każdego, kto pracować będzie na danym komputerze. Tworzenie aplikacji korzystającej z bazy danych Sposób tworzenia aplikacji sięgającej do bazy danych zależy w znacznej mierze od kilku podstawowych założeń, które postawimy sobie na początku pisania programu. Przykładowo, musimy zdecydować, czy aplikacja ma sięgać do jednej, czy do kilku baz danych albo czy ma również obsługiwać inne typy plików. Możliwe są różne konfiguracje, które wymagają rozważenia na samym początku, ponieważ najprostszym sposobem dołączenia do aplikacji możliwości korzystania z bazy danych jest wybranie odpowiednich opcji podczas tworzenia projektu za pomocą kreatora AppWizard. Opcje te po wprowadzeniu za pomocą kreatora nie będą mogły już być zmienione i jedynym sposobem ich modyfikacji będzie ręczna edycja kodu programu. Załączanie do programu bazy danych za pomocą kreatora AppWizard Na drugiej stronie kreatora AppWizard znajdują się cztery przełączniki, które pozwalają wybrać różny stopień wykorzystania baz danych w programie (rysunek 24.5). Bazy danych i widok Record 639 MFC AppWKard - Step 2 o( 6 ^o'rdvi«» viłdo'~^~^ | What dalabase support woutó you like to include? tcord Vi«» yiłdo fH ii"'"'"" l (* None | f~ Header files orły l ^ Database View without file support ^ Database wwwithfitewppoft Ifyou include a daiabase view.>'ou must select a daEa source. DataSouree... The łabie '[Sludent]' in dala louree 'StudenI fegisttation' is seiecled. < Back Neul > Finish Cancel Rysunek 24.5. Opcje stopnia wykorzystania baz danych w aplikacji w kreatorze AppWizard Pierwsza opcja (None) jest oczywista. Aplikacja nie korzysta z baz danych. Druga opcja (Header Files Oniy) dodaje do projektu dyrektywę # include załączającą plik nagłówka zawierający definicje klas CDatabase i CRecordset. Klasy te służą do łączenia się z bazami danych i sięgania do danych w nich zawartych. Dodawanie baz danych do gotowego projektu Możemy dodać możliwość korzystania z baz danych do projektu pierwotnie za pomocą kreatora AppWizard pozbawionego tej opcji, załączając do pliku nagłówka stdafx.h dwa pliki: afxdb.h i afxdao.h. Trzecia (Database View Without File Support) i czwarta (Database Viev ^Vith File Support) opcja wymagają uprzedniego zdefiniowania źródła danych dla danego projektu i w zależności od kolejnych wybranych opcji utworzą odpowiednie klasy. Różnica pomiędzy nimi polega na tym, że druga opcja dodaje do aplikacji automatycznie menu File. Korzystając z kreatora AppWizard i opcji Database View Without File Support utworzymy aplikację SDI (o nazwie UsingDB). Po wybraniu tej opcji należy wykonać poniższe czynności. Łączenie aplikacji ze źródtem danych za pomocą kreatora AppWizard l. Na stronie drugiej kreatora wciśnij przełącznik Database View Without File Support, jeśli aplikacja nie potrzebuje menu File, w przeciwnym wypadku wciśnij Database Viev With File Support. Wybierz Database View Without File Support. Warto zauważyć, że wybranie tej opcji automatycznie definiuje aplikację jako SDI. 640 Poznaj Visual C++ 6 2. Kliknij przycisk Data Source. Pojawi się widoczne na rysunku 24.6 okno dialogowe Database Options. Database Options ———|?lx( r Datasource •• •• —————————————— —— OK ) i cr ODBC; : r fiAO; : C OLE OB: ;• Recordsetłype ^ Snapshot , Advanced | R ,^;::i-, c.b^.A»i»'^iŁLylBl»t»ai»«i«ai»BtJ | Cancel J C Dynaset <" ,?::i.- Rysunek 24.6. Okno dialogowe Database Options 3. Wybierz odpowiednią opcję sposobu połączenia z bazą danych. Tutaj będzie to ODBC. 4. Z listy kombinowanej Datasource wybierz nazwę źródła danych. Tutaj Student Regi-stration. Jeśli odpowiednia nazwa się nie pojawi, oznacza to, że należy utworzyć i skonfigurować źródło danych. Porównaj z przedstawionym wcześniej algorytmem „Tworzenie i konfigurowanie źródła danych ODBC". 5. Wciśnij jeden z przełączników w polu Recordset Type. Tutaj Snapshot. 6. Kliknij OK. Pojawi się przedstawione na rysunku 24.7 okno dialogowe Select Database Tables. 7. Wybierz tablicę lub tablice, dla których chcesz utworzyć zbiór rekordów. Tutaj Student. 8. Kliknij OK. Dodałeś do projektu możliwość korzystania z bazy danych. W następ nych krokach kreatora zdefiniujesz pozostałe opcje projektu. Zbiory rekordów typu snapshot i dynaset Snapshot to taki zbiór rekordów, który tworzy bufory dla wszystkich pól danych i nie bierze pod uwagę zmian wprowadzanych przez innych użytkowników. Natomiast dynaset to zbiór rekordów, który buforuje tylko kluczowe pola i akceptuje zmiany wprowadzone w danych przez innych użytkowników. Bazy danych i widok Record 641 Select Dalabase Tables Lourse Dynabind_Section EnrollmenT Instructor Section OK Cancel Rysunek 24.7. Okno dialogowe Select Database Tables Łączenie się z bazą danych Łączenie się z bazą danych i sięganie do zawartych w niej informacji jest obsługiwane przez dwie współpracujące ze sobą klasy: CDatabase i CRecordset. Klasa CDatabase jest odpowiedzialna za otwieranie połączenia z bazą danych. Obiekty CDatabase można wykorzystywać w programie zarówno do łączenia się z kilkoma różnymi bazami danych, jak i do kilkakrotnego łączenia się z tą samą bazą. Ponieważ otwieranie połączenia z bazą danych jest dość długim procesem i bardzo obciąża zasoby systemu, dlatego lepiej wykorzystywać ponownie połączenie już otwarte niż tworzyć nowe połączenie dla każdego zbioru rekordów. Jeden ze sposobów na powtórne wykorzystanie połączenia polega na osadzeniu obiektu CDatabase w klasie dokumentu, która ustanawia połączenie, a następnie utworzeniu funkcji GetDatabase () zwracającej wskaźnik do obiektu CDatabase. Będziemy mogli teraz przesyłać wskaźnik do obiektu CDatabase konstruktorowi obiektu CRecordset, umożliwiając w ten sposób zbiorowi rekordów korzystanie z już istniejącego połączenia. Z bazą danych łączymy się za pomocą funkcji CDatabase: :OpenEx. Prototyp tej funkcji przedstawiony jest na listingu 24.1. DAO (Data Acces objects) Alternatywnym rozwiązaniem do korzystania z klas CDatabase i CRecordset są klasy CDaoDatabase i CDaoRecordset. Różnica między nimi polega na tym, że klasy DAO współpracują tylko z bazami danych opartymi na mechanizmie (ang. en-gine) Microsoft Jet, podczas gdy dwie pierwsze klasy współpracują z bazami danych sterowanymi za pośrednictwem ODBC. Listing 24.1. LST25_1.CPP - parametry funkcji OpenEx 1 virtual BOOL OpenEx( LPCTSTR IpszConnectString, 2 DWORD dwOptions =0); 642_____________________________________Poznaj Visual C++ 6 Najczęściej funkcji przesyłany jest tylko pierwszy parametr. Zmienna ipszConnect-String jest wskaźnikiem do łańcucha zawierającego nazwę źródła danych i dodatkowe informacje niezbędne do połączenia się z konkretną bazą danych, takie jak nazwa użytkownika czy hasło. Źródła danych ODBC są konfigurowane za pomocą Administratora ODBC uruchamianego za pośrednictwem odpowiedniego apletu w Panelu sterowania. Szczegółowy opis postępowania znajduje się w przedstawionym kilka stron wcześniej algorytmie „Tworzenie i konfigurowanie źródła danych ODBC". Parametr dwOptions jest maską bitową (ang. bitmask) wykorzystywaną do definiowania kombinacji opcji połączeń, które przedstawiamy w tabeli 24.1. Domyślna wartość O otworzy bazę danych z prawem do zapisu, biblioteka kursorów ODBC nie zostanie załadowana, a okno dialogowe połączenia będzie wyświetlane tylko wtedy, gdy łańcuch (pierwszy parametr) nie będzie zawierał wystarczającej ilości danych do zrealizowania połączenia. Tabela 24.1. Możliwe wartości parametru dwOptions funkcji CDatabase: : OpenEx () Wartość Opis OpenReadOn l y Otwiera źródło danych jako tylko do odczytu useCursorZib Ładuje bibliotekę DLL kursorów ODBC. Biblioteka ta pokrywa część funkcji sterownika. Przykładowo zbiory rekordów typu dynaset są niedostępne, kiedy biblioteka kursorów jest załadowana noOdbcDialog Blokuje wyświetlanie okna dialogowego połączeń ODBC forceOdbcDialog Wyświetla okno dialogowe połączeń ODBC Funkcja CDatabase: : OpenEx () może wyglądać tak jak poniżej: CDatabase db; db.OpenEx("ODBC;DSN=Student Registration;UID=TinyTim;PWD= CASTLE", CDatabase::openReadOnly | CDatabase::noOdbcDialog)); Wspomnieliśmy wcześniej, że możliwe jest bezpośrednie odwoływanie się do instrukcji języka SOŁ. Możliwość taka bywa przydatna, gdy baza danych, z którą program współpracuje, oferuje możliwości wykraczające poza standard ODBC SQL. Do instrukcji SOŁ odwołać się można za pomocą funkcji ExecuteSQL klasy CDatabase. Funkcja ta wymaga jako parametru wskaźnika do łańcucha przechowującego instrukcję. Zapytania do bazy danych Klasa CRecordset obsługuje sięganie do danych przechowywanych w tablicach i kolumnach bazy danych. Klasa CRecordset jest klasą abstrakcyjną (ang. abstract ciass), dlatego należy wywieść z niej własną klasę. Za pomocą klasy CRecordset wykonujemy zapytania SQL (ang. ąuery). Wynik zapytania jest przechowywany w buforze rekordu. Klasa CRecordset obsługuje bieżący wskaźnik rekordu (zwany też kursorem) i umożli- Bazy danych i widok Record_______________________________ 643 wia przeszukiwanie rekordów za pomocą jej funkcji składowych: Move, MoveNext, Move-Prev, MoveFirst i MoveLast. Klasa wywiedziona z klasy CRecordset przechowuje zmienne składowe dla każdej kolumny (pola) wewnątrz zapytania. Zmiennym tym przypisywana jest bieżąca wartość reprezentowanych przez nie kolumn za każdym razem, gdy wykonywane jest odwołanie zmieniające bieżący rekord. W przykładzie UsingDB za pomocą kreatora AppWizard wywiedliśmy z klasy CRecordset klasę CUsingDBSet. Definicja i kod klasy CUsingDB-Set przedstawione zostały na listingu 24.2. Listing 24.2. LST25J2.CPP - klasa CUsingDBSet____ 1 ciass CUsingDBSet : public CRecordset 2 ( 3 public: 4 CUsingDBSet(CDatabase* pDatabase = NULL); O 5 DECLARE_DYNAMIC(CUsingDBSet) 6 7 // Pola/Parametry danych S //{(AFX_FIELD(CUsingDBSet, CRecordset) 9 long m_StudentID; @ 10 CString m Name; @ 11 int m_GradYear; @ 12 //}}AFX_FIELD @ 13 14 // Funkcje przykrywające 15 // Funkcje przykrywające utworzone przez kreator AppWizard. 16 //{(AFX_VIRTUAL(CUsingDBSet) 17 public: 18 virtual CString GetDefaułtConnect() ; 19 virtual CString GetDefaultSOL(); 20 virtual void DoFieldExchange(CFieldExchange* pFX) ; 21 //)}AFX_VIRTUAL 22 23 // Kod klasy 24 # i f de f _DEBUG 25 virtual void AssertValid() const; 26 virtual void Dump(CDumpContext& dc) const; 27 enndif 28 }; 29 CUsingDBSet::CUsingDBSet(CDatabase* pdb) 30 : CRecordset(pdb) 31 ( 32 //((AFX_FIELD_INIT(CUsingDBSet) 644_____________________________________Poznaj Visual C++ 6 33 m_StudentID =0; © 34 m_Name = _T(""); © 35 m_GradYear =0; ® 36 m_nFields =3; @ 37 //}}AFX_FIELD_INIT 38 m nDefaultType = snapshot; 39 } 40 41 CString CUsingDBSet::GetDefaultConnect() 42 ( 43 return _T("ODBC;DSN=Student registration"); O 44 } 45 46 CString CUsingDBSet::GetDefaultSQL() 47 { 48 return _T("[Student]"); © 49 ) O Możemy tutaj również przesłać wskaźnik do już istniejącego obiektu CDatabase. @ Zmienne dodane przez kreator CIassWizard do każdego pola w zbiorze rekordów. © Początkowe wartości każdego z pól w zbiorze rekordów i zmiennych m_nFields klasy CRecordset dodane przez kreator CIassWizard. O Funkcja Get przyzywana przez szkielet aplikacji, by dostarczyć łańcuch połączenia, gdy zbiór rekordów jest otwierany. © Funkcja Get przyzywana w momencie otwierania zbioru rekordów, by pobrać nazwę tablicy. Jak łatwo zauważyć, klasa dokumentu cusingDBDoc posiada zmienną składową m usingDBSet typu CUsingDBSet, a klasa widoku CUsingDBView zmienną składową m_pset, która jest wskaźnikiem do obiektu CRecordset dokumentu. Wskaźnik ten jest inicjowany w funkcji OninitialUpdate. Definicja klasy CUsingDBSet budowana jest w oparciu o wybory dokonane w kreatorze AppWizard. Wybraliśmy tablicę Student, posiadającą trzy kolumny: StudentlD, Name i GradYear. Jak widać, klasa ta ma trzy związane z nimi zmienne (linie 9-11). Typy danych i nazwy zmiennych uzyskujemy korzystając z informacji dostarczanych przez samą bazę danych. Zmiennym tym są w konstruktorze przypisywane domyślne wartości (linie 33-35). Konstruktor przypisuje również wartość początkową zmiennej m_nFields (linia 36), informująca zbiór rekordów, ile jest tu kolumn. Bazy danych i widok Record 645 Podczas tworzenia egzemplarza obiektu konstruktorowi cusingDBSet można przesłać wskaźnik do obiektu CDatabase (linia 29). Jeśli wskaźnik ten ma wartość NULL (tak jak w tym przykładzie), bazowa klasa CRecordset utworzy obiekt CDatabase i aby połączyć się z bazą danych, wezwie funkcję CDatabase: :0pen. Przedtem jednak wzywana jest wirtualna funkcja GetDefaułtConnect (linia 41), która zwraca łańcuch połączenia (linia 43). Łańcuch ten informuje obiekt CDatabase, z którym źródłem danych ma się połączyć. Wewnątrz funkcji CRecordset: :0pen przyzywana jest w linii 46 funkcja GetDe-faultSOL. Funkcja CRecordset: :0pen konstruuje standardową instrukcję SQL SELECT i wykonuje zapytanie. Konstruowana tutaj instrukcja SQL będzie mieć postać "SELECT * FROM Student". Instrukcja ta pobiera z tablicy Student zawartość wszystkich rzędów i przechowuje je w buforze rekordu. Po otwarciu zbioru rekordów będziemy mogli przejrzeć pobrane rekordy i sięgać do danych zapisanych w kolumnach. Klasa CRecordset posiada również funkcję Requery, która wykonuje zapytanie ponownie, aby odświeżyć rekordy w buforze, bez konieczności ponownego zamykania i otwierania zbioru rekordów. Odpowiednie znaczniki sygnalizują, czy bieżący rekord (kursor) znajduje się na początku, czy na końcu zbioru. Obecność tych znaczników możemy sprawdzić za pomocą funkcji ISBOF (początek pliku) i ISEOF (koniec pliku). W kodzie przedstawionym na listingu 24.3 pokazana została funkcja przeszukująca zbiór rekordów. Listing 24.3. LST25_3.CPP - przeszukiwanie zbioru rekordów 1 void CMyView::0nlterate3et() 2 ( 3 // ** Otwórz zbiór rekordów 4 CMyRecordSet rs(NULL); O 5 rs.Open(); @ 6 7 // ** Sprawdź, czy nie jest pusty 8 if(rs.IsBOF()) ® 9 return; 10 11 // ** Za pomocą funkcji Process 12 // ** przejrzyj dane rekord po rekordzie 13 while(!rs.IsEOF() ) O 14 { 15 DoSomeFunction(&rs); 16 rs.MoveNext() ; 17 } 18 } O Tworzymy obiekt klasy wywiedzionej z klasy CRecordset. 646 Poznaj Visual C++ 6 © Otwieramy połączenie ze zbiorem rekordów. ® Upewniamy się, że zbiór nie jest pusty. O Wykonujemy pętlę sięgając do każdego z rekordów. Zmienianie danych zapisanych w bazie Możliwość zapytywania baz danych i pobieranie zawartych w nich informacji jest bardzo przydatną sprawą, ale większości aplikacji to nie wystarcza. Powinniśmy mieć jeszcze możliwość dodawania, usuwania i modyfikowania rekordów zapisanych w bazie danych. Szczęśliwie klasa CRecordset automatycznie tworzy większość kodu związanego z tymi operacjami. Wyjątki związane z bazami danych Należy mieć świadomość, że wiele funkcji klasy Ckecordset, takich jak open (), AddNew.o, Edit() czy Updatet) w przypadku pojawienia się problemów uruchamia wyjątek klasy CDBException. Dlatego odwołania do tych funkcji powinny być obramowane za pomocą instrukcji try/catch. Pomoc przy usuwaniu błędów związanych z wyjątkami w bazach danych można uzyskać zaznaczając w menu Tools programu narzędziowego Tracer opcję Database Tracing. Dodawanie do bazy danych nowego rekordu odbywa się w trzech etapach. Najpierw przywoływana jest funkcja AddNew, która ustawia zbiór rekordów w tryb dodawania (ang. add) rekordów i przypisuje polom wartość NULL. Następnie uaktualniana jest zawartość pól, w których chcemy zapisać nowe rekordy. Oznacza to po prostu przypisanie odpowiednich wartości zmiennym składowym klasy CRecordset. Na koniec przyzywana jest funkcja Update, która tworzy w bazie danych nowy rekord i wyłącza tryb dodawania rekordów. W zbiorach rekordów typu snapshot, aby sięgnąć do nowo dodanych rekordów, należy skorzystać z funkcji Requery. Modyfikowanie już istniejącego rekordu odbywa się z kolei w czterech fazach. Najpierw ustawiamy bieżący rekord na rekordzie, który chcemy zmodyfikować. Następnie wzywamy funkcję Edit, która ustawia zbiór rekordów na tryb edycji (ang. edit). Następnie zmieniamy wartości pól. Na koniec przyzywamy funkcję Update, która zapisuje wartości w bazie danych i kończy tryb edycji. Z kolei usuwanie danych wykonywane jest w ramach jednego posunięcia. Wzywamy po prostu funkcję Delete, która usuwa bieżący rekord. Rekord jest usuwany zarówno ze zbioru rekordów, jak i z bazy danych. Następnie musimy zmienić bieżący rekord, ponieważ próba sięgnięcia do usuniętego rekordu wywoła komunikat o błędzie. Bazy danych i widok Record 647 Wiązanie pól z tablicami bazy danych Za każdym razem, gdy wykonywana jest operacja uaktualniania lub przewijania, dokonuje się wymiana informacji między bazą danych a buforem. Jak można się było przekonać, zbiór rekordów ma osobną zmienną składową dla każdej kolumny zapytania. To, która zmienna jest przypisana do której kolumny, definiowane jest wewnątrz funkcji DoFieldExchange. Na listingu 24.4 została przedstawiona funkcja DoFieldExchange klasy cusingDBSet pochodzącej z przykładu UsingDB. Funkcja DoFieldExchange jest przyzywana automatycznie, gdy zachodzi potrzeba jednocześnie pobrania i zachowania wartości pól. Dostarczane przez bibliotekę MFC makroinstrukcje zaczynające się od liter RFX (od ang. Record Fieid Exchange, wymiana zawartości pól rekordu) służą do wymiany danych określonego typu między zmienną składową zbioru rekordów a tablicą bazy danych. Drugi parametr przesyłany do makroinstrukcji RFX jest nazwą kolumny w tablicy bazy danych. Trzeci parametr jest zmienną składową, która ma przechować jego wartość. Warto zwrócić uwagę na komentarze z linii 3 i 8. Nie należy ich usuwać ani edytować, ponieważ są potrzebne kreatorowi CIassWizard. Listing 24.4. LST25_4.CPP - funkcja DoFieldExchange 1 void CUsingDBSet::DoFieldExchange(CFieldExchange* pFX) O 2 { 3 //((AFX_FIELD_MAP(CUsingDBSet) 4 pFX->SetFieldType(CFieldExchange::outputColumn); @ 5 RFX_Long(pFX, _T("[StudentID]"), m_StudentID); © 6 RFX_Text(pFX, _T("[Name]"), m_Name); © 7 RFX_Int(pFX, _T("[GradYear]"), m_GradYear); © 8 //)}AFX_FIELD_MAP 9 } O Funkcja DoFieldExchange jest przyzywana przez szkielet aplikacji do pobierania lub uaktualniania wartości kolumn zbioru rekordów © Informujemy, że poniższe zmienne składowe są polami wyjściowymi (ang. output fields), a nie wprowadzanymi parametrami (ang. input parameters) © Wymieniamy dane między zmiennymi składowymi zbioru rekordów a tablicą bazy danych 648 Poznaj Visual C++ 6 Widok Record Jeśli zdecydujemy w kreatorze AppWizard, że aplikacja powinna korzystać z bazy danych, klasa widoku aplikacji zostanie domyślnie wywiedziona z klasy CRecordview. Klasa CRecordview jest podklasą klasy CFormView tworzącej widok przypominający formularz. Formularz reprezentowany jest przez pozbawiony tytułu i obramowania szablon okna dialogowego zajmujący cały obszar roboczy okna widoku. Klasa CRecordview dodaje klasie CFormView możliwości wynikające z połączenia widoku z rekordami bazy danych za pomocą podklasy CRecordset. Kontrolki dodawane do szablonu okna dialo- gowego mogą być łączone bezpośrednio ze zmiennymi składowymi, które są uaktualniane poprzez pobranie wartości zapisanych w polach bazy danych. Kreator AppWizard dodaje również do widoku menu Record i ikony paska narzędziowego umożliwiające użytkownikowi przewijanie rekordów znajdujących się w zbiorze rekordów. Dzięki temu widok jest automatycznie uaktualniany w miarę pojawiania się nowych rekordów. Edytowanie szablonu widoku Record f Szablon okna dialogowego widoku Record wygląda trochę inaczej niż inne szablony tego rodzaju, ponieważ nie posiada tytułu ani obramowania, niemniej kontrolki dodaje się i edytuje tak'samo jak w przypadku innych okien dialogowych. Zazwyczaj dla każdego z pól formularza, które będzie wyświetlać dane, do formularza dodawana jest osobna kontrolka. Następnie kontrolki są przypisywane zmiennym składowym podklasy klasy CRecordset. Przykładowy program UsingDB korzysta z dwóch takich kontrolek. Jedna wyświetlać będzie nazwisko studenta, a druga rok ukończenia studiów. Bazujący na widoku Record program UsingDB tworzymy w poniższy sposób. Projektowanie szablonu okna dialogowego widoku Record 1. W panelu ResourceView rozwiń gałąź folderu Dialog i kliknij dwukrotnie IDD_USIN-GDB_FOBM. W edytorze zasobów pojawi się szablon okna dialogowego. 2. Kliknij komentarz TODO: Place Form Controls Herę (Tutaj umieść kontrolki formularza) i aby go usunąć, wciśnij przycisk Delete. 3. W pasku narzędziowym Controls wybierz przycisk symbolizujący statyczny tekst (ang. static text}. Dodaj kontrolkę Text w górnym lewym krańcu pola dialogowego, tak jak to widać na rysunku 24.8. 4. Wprowadź tytuł kontrolki Text. Tutaj Student &Name, 5. W pasku narzędziowym Controls wybierz przycisk odpowiadający kontrolce pola edycji. Dodaj ją po prawej stronie tekstu Student Name. 6. Na liście kombinowanej ID wpisz IDC NAME. 7. W pasku narzędziowym Controls wybierz przycisk symbolizujący statyczny tekst. Dodaj kontrolkę Text pod poprzednią kontrolka Text (Student Name). Bazy danych i widok Record 649 8. Wpisz tytuł kontrolki. Tutaj Graduation SYear. 13 Eto idi yiert iftimi filwwt 6utó j.9^ loofe ;e) ,ia 03 • . e :a- ;- :;n ^rów. a«b „ -li^? ^|f ^lai ^i [s?f W'""" ^J: «| CU;tlgDBView 'ji^lc^M m&r^,m:J ' i ? |l;» CU..ngD8V..» ^J 1 " ::'' fS •' ! •i: : „.,.-.„-.,-„,,.— ,.,,...^.^^;„,;,,,,,.^.2l > ,1, , , l . . . . l , , . . t . , , . l . , , i l , , , i i , , , , l , , , , l . , , , . , , , , l , , , , i , , , , i , . , , ..,,,.,„,...... ,,. --„a||iK„,"^ - ., BB1»«1 : , ,E!:li6, 11» s • • ^ ; SI«l.n.B«» l" -*• •" Qo G.«t«ii>»i:.« P f " asa : B3 § ] ^ EED m_Name. De?CT^tion: CSIring with tength yaMation Rysunek 24.9. Przypisywanie zmiennej zbioru rekordów kontrolce Edit 6. Z listy kombinowanej Category wybierz kategorię zmiennej. Tutaj Value. 7. Z listy kombinowanej Variable type wybierz typ zmienne. Tutaj CString. 8. Kliknij OK. 9. Z listy Control IDs wybierz identyfikator IDC_YEAR, po czym kliknij przycisk Add Variable. 10. Rozwiń listę kombinowaną Member Variable Name. Z listy wybierz m_pSet->m_GradYear. 11. Z listy kombinowanej Category wybierz kategorię zmiennej. Tutaj Value. 12. Z listy kombinowanej Variable type wybierz typ zmiennej. Tutaj int. 13. Aby zamknąć okno dialogowe Add Member Variable, kliknij OK. 14. Aby zamknąć kreator CIassWizard, kliknij przycisk OK. Po zbudowaniu i uruchomieniu aplikacji program będzie się automatycznie łączył ze źródłem danych Student Registration, a widok Record wyświetli w odpowiednich polach zawartość pierwszego rekordu tablicy Student. Za pomocą strzałek u góry możemy zmieniać wyświetlane rekordy. Okno aplikacji pokazane zostało na rysunku 24.10. Bazy danych i widok Record 651 Rysunek 24.10. Program UsingDB w działaniu PATRZ TAKŻE • Więcej informacji na temat projektowania okien dialogowych można znaleźć w rozdziale 3. • K/asę CFormYlew opisywaliśmy w rozdziale 18. Rozdział 25 Zasady programowania OLE i COM Podstawy modelu COM Tworzenie serwera automatyzacji OLE Serwery i kontenery OLE Programowanie w oparciu o komponenty Wraz ze wzrostem komplikacji zagadnień związanych z projektowaniem programów w tak złożonym środowisku jak Windows, korzystającym z wielu różnych interfejsów API (od ang. application programing interface, interfejs programowania aplikacji) pojawiła się potrzeba standaryzacji i uproszczenia zagadnień związanych z programowaniem w tym środowisku, która zaowocowała powstaniem technologii zwanej programowaniem w oparciu o komponenty (ang. component-based programming). Przyszłość COM - COM+ Następnym etapem rozwoju COM będzie COM+. Microsoft obiecuje, że COM+ uprości znacznie programowanie COM, tak aby obiekty COM działały w podobny sposób jak obiekty C++ i co za tym idzie, mogły być tworzone i usuwane za pomocą słów kluczowych języka C++ new i delete. Mimo że obecnie Visual C++ nie obsługuje jeszcze COM+, odpowiedni pakiet oprogramowania może pojawić się na rynku w każdej chwili. : \ : ; i'' ^ , y:.^ : , Komponenty (ang. components) są małymi fragmentami oprogramowania (podobnie do egzemplarzy obiektów klas), które za pośrednictwem odpowiednio zdefiniowanych interfejsów wykonują ściśle określone zadania. Inaczej niż egzemplarze obiektów klas, komponenty nie są ściśle powiązane z określonym programem lub komputerem. Można je pisać w różnych językach, ponieważ mogą porozumiewać się z programami i komponentami napisanymi w innych językach za pośrednictwem interfejsów. 654 Poznaj Visual C++ 6 Microsoft rozwinął tę technologię do obecnego poziomu i dziś nosi ona nazwę COM (od ang. Component Objęci Model, model programowania w oparciu o obiekty komponentów). Być może spotkaliście się z innymi terminami związanymi z tym modelem, takimi jak OLE (ang. Objęci Linking and Embeding, osadzanie i łączenanie obiektów) czy kontrolki Active X. Należy jednak pamiętać, że w obu przypadkach są to tylko implementacje techniki COM. Sama metoda COM jest niezależnym od języka i platformy sprzętowej standardem, definiującym, w jaki sposób różne obiekty mogą kontaktować się ze sobą za pomocą wspólnie akceptowanego protokołu. Najważniejszą rzeczą w obiektach COM są ich interfejsy. Komponenty są swego rodzaju zamkniętymi skrzynkami zawierającymi implementacje specyficznych, zależnych od języka programowania funkcji wykonujących zadania, do których realizacji obiekt został zaprogramowany. Aby móc korzystać z zawartych w komponentach możliwości, potrzebne narzędzie, które pozwoli na przesyłanie komponentowi odpowiednich parametrów i odbieranie wyników wykonywanych wewnątrz operacji. Tym narzędziem pośredniczącym między programem a obiektem komponentu jest interfejs. W ten sposób można zdefiniować nawet cały interfejs API. Pisząc aplikację możemy polecić programowi kontaktować się z komponentem będącym serwerem (ang. server) programu za pośrednictwem obopólnie akceptowanych interfejsów. Nie ma przy tym żadnego znaczenia w jakim języku programowania serwer został napisany. Możemy również napisać własne komponenty serwery (o ile zachowamy zgodność definicji interfejsu) i sprzedawać je jako alternatywę dla innych komponentów istniejących na rynku. Interfejs Messaging API (MAPI) jest dobrym przykładem zastosowania techniki COM. MAPI jest po prostu zbiorem standardowych interfejsów obiektów COM wykorzystywanych przy tworzeniu programów pocztowych. Każdy może pisać własne obiekty COM wykonujące zadania takie jak przechowywanie wiadomości, transport wiadomości i dostarczających adresów odbiorców wiadomości. Program Microsoft Exchange jest w znacznej mierze właśnie zbiorem takich komponentów serwerów, jest jednak również wiele innych implementacji tych komponentów. Sam kod komponentów może różnić się znacznie w zależności od programu, ale wszystkie obiekty COM korzystać będą z tych samych interfejsów. Oznacza to, że programy klienty (ang. client programs) korzystające z usług tych komponentów, takie jak Microsoft Outlook, mogą wysyłać, odbierać i przechowywać wiadomości za pomocą kompatybilnych z MAPI komponentów dowolnej firmy. Podobnie dowolny producent opro- gramowania może dostarczyć własne programy klienty (i wielu tak robi), które korzystać będą z Microsoft Exchange lub innych komponentów serwerów, nie wiedząc nawet, z którego z nich korzystają. Jedynym koniecznym warunkiem jest to, aby zarówno programy jak i komponenty korzystały z identycznych definicji interfejsów zebranych w MAPI. PATRZ TAKŻE • Jak korzystać w aplikacjach z kontrolek ActiveX mówimy w rozdziale 9. • Jak tworzyć kontrolki ActiveX mówimy w rozdziale 26. • Więcej informacji na temat MAPI znaleźć można w rozdziale 28. Zasady programowania OLE i COM 655 Interfejsy COM Interfejs jest definicją zbioru funkcji i ich parametrów. Każdy obiekt COM ma przy- najmniej jeden interfejs, niektóre jednak oferują kilka, każdy z innym zestawem funkcji. Konwencja nazewnictwa interfejsów Interfejsy COM, podobnie jak inne elementy programu, nazywane są w oparciu o funkcję, którą wykonują. Jednak zgodnie z powszechnie przyjętą konwencją, nazwy interfejsów są zazwyczaj poprzedzane literą I. Przykładowo: lUnknown, IDis-patch, IMoniker czy IMessageFilter. Obiekty COM można pisać w każdym języku, który umożliwia pisanie odpowiednich interfejsów. Niektóre języki są jednak do tego lepiej przystosowane niż inne. Przykładowo język Java działa tu bardzo dobrze, ponieważ każdy z obiektów języka Java może mieć kilka interfejsów, co w naturalny sposób pasuje do obiektów COM. Obiekty COM ukrywają za tym interfejsem właściwy kod, więc program przyzywający funkcję zdefiniowaną w interfejsie uruchomi jej implementację przechowywaną w obiekcie, która wykona zlecone zadanie. Standardowa struktura interfejsu COM jest taka sama jak struktura tablicy funkcji wirtualnych języka C++. Oznacza to, że możemy definiować i implementować interfejsy COM korzystając z mechanizmu tablicy funkcji wirtualnych. Zazwyczaj funkcje wirtualne wykorzystujemy, żeby dostarczyć możliwości pokrywania w klasie pochodnej funkcji klasy bazowej (wpisując w deklaracji funkcji w klasie bazowej słowo kluczowe virtual). Gdy pokrywamy funkcję wirtualną klasie przypisywana jest tablica funkcji wirtualnych z tą klasą - vtable. Tablice funkcji wirtualnych W języku C++ każdy egzemplarz obiektu przechowywanego w pamięci posiada związaną z nim tablicę funkcji wirtualnych (aczkolwiek niektóre z tych tablic mogą nie zawierać żadnych elementów i mieć zerową długość). Każdy element tablicy przechowuje wskaźnik do kodu implementującego funkcję wirtualną. Zawsze gdy wzywana jest w tym obiekcie funkcja wirtualna, tablica dostarcza w zależności od potrzeb albo adres funkcji klasy bazowej, albo adres funkcji klasy pochodnej. W języku C++ możemy zadeklarować klasę zawierającą tylko i wyłącznie funkcje wirtualne. Klasa taka nazywana jest klasą abstrakcyjną (ang. abstract ciass). Możemy oczywiście utworzyć obiekt klasy abstrakcyjnej, częściej jednak klasa abstrakcyjna wyko- rzystywana jest w języku C++ do tworzenia interfejsów COM. Wygląda wtedy mniej więcej tak: 656 Poznaj Visual C++ 6 ciass lUnknown { public: virtual HRESULT Querylnterface(REFIID riid, LPVOID FAR* ppv0bj)=0; virtual ULONG AddRef()=0; virtual ULONG Release()=0; } Microsoft stworzył takie definicje interfejsów C++ dla wszystkich obiektów COM, które obsługują interfejsy, możemy jednak spotkać się również z funkcjami wbudowanymi w makroinstrukcje, które wykonywać będą dokładnie to samo: STDMETHOD(Querylnterface)(THIS_ REFIID riid, LPVOID FAR* ppv0bj) PURE; STDMETHOD_(ULONG, AddRef)(THIS) PURE; STDMETHOD_(ULONG, Release)(THIS) PURE; Każdy obiekt COM musi implementować interfejs lUnknown zawierający przedstawione tutaj trzy metody (funkcje), a każdy interfejs musi implementować kod tych trzech funkcji zanim przejdzie do definiowania własnych funkcji specyficznych dla niego. Funkcje te pełnią kilka zadań: • AddRef () - kiedy klient interfejsu otrzymuje wskaźnik do interfejsu, wewnątrz interfejsu uruchamiany jest mechanizm zwany zliczaniem odwołań (ang. reference coun-ting) notujący, ilu klientów odwołuje się w danej chwili do interfejsu. • Relase () - kiedy klient pozbywa się wskaźnika do interfejsu, rachunek odwołań jest zmniejszany o jeden. Kiedy liczba zliczanych odwołań osiągnie zero, obiekt interfejsu jest niszczony (zazwyczaj niszczy się sam). • Querylnterface () - klient używający wskaźnika do danego interfejsu może spytać o wskaźnik do innego interfejsu obiektu COM, przesyłając identyfikator (tutaj riid) poszukiwanego interfejsu. Jeśli taki interfejs istnieje, funkcja AddRef () zwraca wskaźnik ppv0bj. O identyfikatorach interfejsu (riid) powiemy za chwilę. Dzięki temu, że te trzy podstawowe funkcje są implementowane przez każdy interfejs, możemy napisać program klienta pytający o egzemplarz obiektu COM (automatycznie uruchamiający funkcję AddRef ()). Pomiędzy różnymi interfejsami obiektu poruszamy się za pomocą funkcji Querylnterface (). Kiedy skończymy pracę z obiektem, dla każdego wskaźnika do obiektu, który zdobyliśmy, wzywamy funkcję Relase, która poleca obiektowi zniszczyć się, oczyszczając pamięć (chyba, że jakiś inny program jeszcze z niego korzysta). Funkcje te są uniwersalne dla wszystkich obiektów COM. Równocześnie implementując obiekt COM musimy napisać kod, który zliczać będzie odwołania i zwracać wskaźnik do odpowiedniego interfejsu lub wartość NULL, jeśli interfejs nie istnieje. Zasady programowania OLE i COM 657 Oczyszczanie pamięci po obiektach COM Ponieważ to na obiekcie COM spoczywa odpowiedzialność za zliczanie odwołań do niego, to właśnie on musi dbać o usuwanie się z pamięci wtedy, gdy wszystkie korzystające z niego programy klienty przestaną się do niego odwoływać i licznik odwołań osiągnie zero. W C++ możemy taki efekt uzyskać usuwając wskaźnik this, co spowoduje oczyszczenie pamięci zajmowanej przez obiekt. Po samozniszczeniu obiektu nie wolno w żadnym wypadku korzystać ze zmiennych składowych obiektu, ani wykonywać na nim żadnych operacji, ponieważ odpowiednia lokacja w pamięci została już wyczyszczona. Interfejs, który zawierałby tylko te trzy wspomniane tutaj funkcje, nie byłby szczególnie przydatny, dlatego zwykle interfejsy traktują tę definicje jako bazę, dołączając do niej własne funkcje umożliwiające interfejsowi realizowanie określonych zadań. Istnieje wiele sposobów implementacji kodu funkcji z tablicy vtable. Jednym z prostszych jest wyprowadzenie normalnej (nie abstrakcyjnej) klasy C++ z klasy definiującej interfejs. Następnie w wywiedzionej klasie implementujemy odpowiednie metody. Identyfikatory interfejsów, identyfikatory klas i identyfikatory GUID Jak mogliśmy się przekonać, funkcja Oueryinterface () zwraca wskaźnik do innego interfejsu, który również współpracuje z danym obiektem COM, kiedy tylko prześlemy jej potrzebny nam identyfikator interfejsu. Każdy interfejs ma swój unikalny identyfikator IID, a każda klasa COM identyfikator CLSID. Identyfikatory te to 128-bitowe liczby gwarantujące, że żaden z identyfikatorów w żadnym obiekcie COM na świecie się nie powtórzy. Identyfikatory te nazywane są identyfikatorami GUID (od ang. Globally unique ID, globalnie unikalny identyfikator). Pisząc obiekt COM lub definiując nowy interfejs możemy wykorzystać do tworzenia tych globalnych identyfikatorów program guidgen.exe. Program ten można znaleźć w katalogu ...Visual Studio\VC98\Tools\Bin\. Po uruchomieniu programu generuje nowy identyfikator GUID i daje możliwość wyboru pomiędzy czterema różnymi formatami identyfikatora (rysunek 25.1). Wybieramy format najbardziej przystający do naszych potrzeb i klikamy przycisk C^opy, aby skopiować numer do schowka. Jeśli wciśniemy przycisk New GUID program guidgen.exe wybierze kolejny unikalny identyfikator. Nie musimy się obawiać, że przez przypadek wylosuje taką samą liczbę, musielibyśmy żyć naprawdę bardzo długo. Korzystanie z programu guidgen.exe Może się zdarzyć, że w innej wersji Visual C++ program guidgen.exe znajdować się będzie w zupełnie innym katalogu. W takiej sytuacji możemy użyć polecenia Find menu T\)ols Eksploratora Windows, które go dla nas odnajdzie. 658 Poznaj Visual C++ 6 Kiedy już znajdziemy program, możemy dołączyć go jako dodatkowe narzędzie do menu Tools kompilatora Visual Studio za pomocą znajdującego się w tym menu polecenia Customize. W oknie dialogowym Customize wybierając kartę Tools, aby dodać program guidgen.exe do listy narzędzi. Create GUID Choose the desirad format below. then select "Copy" to copythe results to the dipboard (the results can then ba pasted into your source code), Choose "Exit" when done. :- GUID Format—-—•—--——..-- .-- - ,,-....-.--„._- ..- j r l. IMPLEMENT_OLECREATE(...) l r 2. DEFINE_GUID(„.) | <~ i. staticconststruct GUID •{...} l (T 0. Registry Fem-nat (ie. {»ewc«-»oQueryInterface(IID_IDirectDraw2, (LPVOID *)&pIDD2) ; (Obiekt DirectDrawjest po prostu przykładowym obiektem COM. Więcej informacji na temat jego zastosowań można znaleźć w rozdziale 28). Klasy COM poprzedzane są z kolei przedrostkiem CLSID (od globalnego identyfikatora klasy). Tak więc obiekt DirectoryDraw implementujący kod interfejsu IID_IDirectDraw2 nosić będzie przykładowo nazwę CLSID_DirectDraw. Oba identyfikatory, interfejsu i klasy, wyrażone są za pomocą liczb GUID - ich prawdziwe wartości znaleźć można w pliku DDRAW.h: DEFINE_GUID( CLSID_DirectDraw, OxD7B70EEO,0x4340,OxllCF,OxBO,0x63,0x00, 0x20,OxAF,OxC2,OxCD,0x35 ); DEFINE_GUID( IID_IDirectDraw2, OxB3A6F3EO,Ox2B43,OxllCF,OxA2,OxDE,0x00, OxAA,0x00,0x89,0x33,0x56 ); PATRZ TAKŻE • Przykład wykorzystania obiektu DirectDraw w funkcji CoCreateinstance () lub też samodzielnego wykorzystania tego obiektu znaleźć można w rozdziale 28. Tworzenie egzemplarzy obiektów COM Kiedy tworzymy egzemplarz obiektu COM, działający proces serwera (ang. server process) uruchomi obiekt i będzie go podtrzymywał. Proces serwera może być w tym przypadku naszym programem klientem, biblioteką DLL, plikiem .exe w lokalnym kom- puterze lub programem innego połączonego z naszym komputera. Każda z tych kategorii nosi nazwę kontekstu (ang. context); ich krótki opis można znaleźć w tabeli 25.1. 660 Poznaj Visual C++ 6 Tabela 25.1. Różne konteksty wykorzystywane przez działający obiekt COM Kontekst Znacznik Opis Serwer z bieżącego procesu (ang. Inproc server) Lokalny serwer (ang. Localserver) Serwer z innego komputera (ang. Remote server) CLSCTX INPROC SERVER CLSCTX LOCAL SERVER CLSCTX REMOTE SERVER Kod obiektu działa w ramach tego samego procesu co przyzywający go program klient Kod obiektu działa wewnątrz innego programu .exe, w innym procesie, ale na tym samym komputerze co program klient Kod obiektu działa na zupełnie innym komputerze niż przywołujący go program Rejestr systemu Windows służy do wiązania identyfikatora globalnego klasy (CLSID GUID) z plikami .dli lub .exe, które w razie potrzeby tworzyć i zarządzać będą obiektami COM. Wszystkie klasy COM zarejestrowane w danym momencie w systemie Windows figurują w postaci identyfikatorów w gałęzi Rejestru HKEY_CLASES_ROOT\CLSID. Pod każdym, z nich znajduje się folder (ang. key) dla każdego z różnych typów zarejestrowanych serwerów -InprocServer32, LocalServer32 lub RemoteServer32 (i inne). W każdym z folderów można znaleźć nazwę pliku .dli lub .exe, który przechowuje kod odpowiedzialny za tworzenie i zarządzanie obiektem COM. Przykładowo, na rysunku 25.2 widać okno programu Registry Editor, w którym widać identyfikator klasy obiektu DirectDraw i jego wewnątrzprocesowy plik obsługujący biblioteki DLL, ddraw.dll. Registty Editor Data "ddraw.dll" "Bolh" gi-Q {D70532'10-CE69-1100^777-000001143C57} _t] \ Name B Q {D7B7gEEO-j310-11 CF-BOS3-0020AFC2CD35} ^(Default) , ^-OiBSSSBsS .,^ThreadingModel Bs Q {D824F8<0-ABCB-101B-3B38-OOAAOOOC4F5D}- i..(l 'S O {DBCE2480-C732-101 B-BE72-BA78E9AD5B27 :» _J {DCBF8E30- 9A.IF-HCF-92SE-OOAA0057AD67)») J -tJ Rysunek 25.2. Program Registry Editor wyświetlający zarejestrowany identyfikator CLSID Serwery implementują specjalny obiekt COM zwany obiektem klasy (ang. Ciass Objęci). Obiekty klasy podobne są do innych obiektów COM, jednakże implementują standardowo interfejs iclassFactory. Przyzwanie funkcji Createinstance () na tym interfejsie tworzy klasę (obiekt) odpowiedniego typu. Zazwyczaj jednak tworzymy obiekty COM za pomocą funkcji CoCreateinstance () biblioteki COM, aby utworzyć egzemplarz obiektu lub za pomocą funkcji coGetdassObject (), pobierając wskaźnik do obiektu fabryki klas. Fabryki klas powinny być w gruncie rzeczy nazywane fabrykami obiektów, ponieważ tworzą obiekty nie klasy (to programiści tworzą klasy). Zasady programowania OLE i COM 661 CoCreateInstance() is defined as: STDAPI CoCreateInstance(REFCLSID rcisid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID* ppv); Tworzenie obiektów komponentów na komputerach połączonych przez sieć Aby utworzyć obiekt komponentu w takiej sytuacji, należy użyć funkcji CoCreate-lnstanceEx (). Funkcja ta korzysta z techniki DCOM (Distributed COM) umożliwiającej komunikację między komputerami w sieci za pośrednictwem RPC (Remote Procedurę Calis, zdalne wywoływanie procedury). Komputery klienty muszą jednak zostać skonfigurowane za pomocą programu DCOMCNFG.EXE, żeby móc rejestrować serwery COM z innego komputera. Pierwszy parametr, rcisid, jest wskaźnikiem do globalnego identyfikatora klasy, której egzemplarz jest nam potrzebny. Drugiemu parametrowi, pUnkOuter, powinniśmy normalnie przypisać wartość NULL. Parametr ten wykorzystywany jest w zaawansowanej technice COM zwanej agregacją. Trzeciemu parametrowi powinniśmy przypisać znacznik CLSCTX_SERVER. Parametr ten, dwClsContext definiuje potrzebny nam typ serwera. Znacznik CLSCTX_SERVER jest kombinacją znaczników przedstawionych w tabeli 25.1. Czwarty parametr, riid, jest odwołaniem do globalnego identyfikatora interfejsu potrzebnego nowemu obiektowi COM. Ostatni parametr, ppv, jest wskaźnikiem do wskaźnika interfejsu w aplikacji kliencie, któremu przypisany zostanie wskaźnik do interfejsu nowego obiektu COM. Przykładowo, aby utworzyć egzemplarz obiektu DirectDraw i zdobyć wskaźnik do interfejsu lDirectDraw2, należy wpisać poniższy fragment kodu: IDirectDraw2* pIDirectDraw2 = NULL; HRESULT hr = CoCreateInstance(&CLSID_DirectDraw, NULL, CLSCTX_ALL, &IID_IDirectDraw2, (void**)&pIDirectDraw2); Jeśli funkcji uda się wykonać zadanie, utworzony zostanie nowy obiekt DirectDraw, a wskaźnik pDirectDraw2 wskazywać będzie interfejs IDirectDraw2 nowego obiektu COM. Pośredniczące biblioteki DLL i szeregowanie Kiedy program klient uzyskuje wskaźnik do interfejsu obiektu COM, nie ma dla niego znaczenia, gdzie w pamięci jest zlokalizowany rzeczywisty obiekt. Jednak gdy obiekt COM znajduje się poza procesem programu klienta, wskaźnik nie może wskazywać bezpośrednio tablicy vtable funkcji interfejsu; zamiast tego wewnątrz naszego procesu tworzona jest atrapa (ang. stub), która wygląda dla programu klienta tak jak wskaźnik do lokalnego interfejsu. Kiedy przyzywamy funkcje za pośrednictwem tego wskaźnika interfejsu w tle uru- 662_____________________________________Poznaj Visual C++ 6 chomiona zostaje technika zwana szeregowaniem (ang. marshaling), łącząca aplikację klienta i obiekt COM za pośrednictwem pośredniczącej biblioteki DLL (ang. proxy DLL). Wersje pośredniczących bibliotek DLL Jeśli dokonujemy szeregowania między komputerami za pośrednictwem DCOM, musimy upewnić się, że w obu komputerach znajduje się ta sama wersja pośredniczącej biblioteki DLL. Każdy interfejs jest również zarejestrowany w Rejestrze Windows w gałęzi HKEY_CLASSES_ROOTMnterface, identyfikowany przez swój globalny identyfikator. Wewnątrz tych identyfikatorów przechowywane są foldery takie jak ProxyStubClsid32, przechowujące identyfikatory klas podające lokalizację pośredniczących bibliotek DLL, które mogą szeregować funkcje interfejsu. Pośrednicząca biblioteka DLL jest odpowiedzialna za przekształcenie parametrów do uniwersalnej postaci, w której nadawać się będą do transmisji między komputerami. Pośrednicząca biblioteka DLL może następnie skorzystać ze zdalnego odwołania do procedury (RPC), aby przyzwać funkcję obiektu COM z innego komputera. Pośredniczące biblioteki DLL mogą być tworzone automatycznie poprzez napisanie definicji w języku IDL (ang. Interface Definition Language, język definiowania interfejsów) i przepuszczenie ich przez kompilator Microsoftu IDL (MIDL). Kompilator przygotuje następnie odpowiedni kod odwołań RPC i kod odpowiedzialny za szeregowanie, nadający się do umieszczenia w pośredniczącej bibliotece DLL. Wersje interfejsów Dawny problem związany z koniecznością zapewnienia zgodności różnych wersji oprogramowania jest teraz rozwiązany w prosty sposób. Po wypuszczeniu programu w świat wystarczy tylko w następnych jego wersjach pozostawiać niezmieniony interfejs, z którego korzystały poprzednie wersje. Oznacza to, że nowsze wersje obiektów COM muszą implementować nowsze wersje interfejsu, pozostawiając stare wersje nienaruszone. Starsze programy klienci, które potrafią korzystać tylko ze starego interfejsu, będą przywoływać po prostu stare interfejsy, podczas gdy nowsze programy będą przyzywać nowe wersje interfejsu. Standardowa konwencja nazywania interfejsów polega na dodawaniu po nazwie interfejsu numeru wersji. Przykładowo, jeśli oryginalny interfejs IClassFactory zostanie udoskonalony, aby obsługiwać licencjonowanie obiektów, fabryka klas może zostać zaopatrzona w nowy interfejs ldassFactory2 z kilkoma dodatkowymi metodami wykonującymi nowe funkcje fabryki klas. Programy klienty będą teraz przyzywać interfejs IClassFactory, a następnie za pomocą funkcji Querylnterface () sięgać do nowszego interfejsu ldassFactory2. Jeśli obiekt COM nie oferuje takiego interfejsu, funkcja Queryln-terface () zwraca kod S_FALSE, a zmiennej wskaźnika poszukiwanego interfejsu przypisywana jest wartość NULL. Zasady programowania OLE i COM 663 Niezależnie od wersji interfejsu, obiekty COM mogą korzystać z tej samej wewnętrznej implementacji funkcji, które w starej i nowej wersji pozostaną niezmienione. Automatyzacja OLE Termin OLE (Object Linking i Embeding, łączenie i osadzanie obiektów) pierwotnie oznaczał możliwość dołączania obiektów różnych typów do dokumentów innego typu, na przykład arkuszy kalkulacyjnych Excela do dokumentów Worda. Dokumenty, które obsługują operacje osadzania i łączenia nazywane są dokumentami złożonymi (ang. compound). W licznych aplikacjach natknąć się możemy w menu Insert to samo polecenie Object. Aplikacje, które obsługują tę opcję, zawierają wspomniane wcześniej dokumenty złożone, dzięki czemu można dołączać do dokumentu obiekty z serwerów zarejestrowanych na liście programów serwerów OLE. Dołączane do dokumentu obiekty mogą być albo osadzane (ang. embeded), który to termin oznacza, że są one zachowywane razem z dokumentem, albo dołączane (ang. lin-ked) co oznacza, że do bieżącego pliku dołączane jest odwołanie do innego pliku (zwane monikerem, ang. moniker), który załączany będzie do dokumentu. Obecnie termin OLE ma znacznie szersze znaczenie, ponieważ obejmuje również technikę przeciągnij i upuść (ang. drag-and-drop) oraz automatyzację OLE (ang. OLE automation) - termin ten odnosi się do sytuacji, gdy program przywołuje funkcje innych programów działających w tle, tak że użytkownik nie ma pojęcia, że korzysta z innego programu. Microsoft Word jako serwer automatyzacji Jeśli mamy w komputerze programy Microsoft Outlook i Microsoft Word, łatwo zauważymy, że tworząc e-mail za pomocą programu Outlook możemy korzystać z tych samych opcji sprawdzania pisowni, z których korzystaliśmy w Wordzie. Jest to przykład sytuacji, w której Word działa jako serwer automatyzacji dla programu pocztowego Outlook. Edytor tekstu sprawdza pisownię przygotowywanej wiadomości, działa jednak w tle, niewidoczny dla użytkownika. Jak działa interfejs przesyłający Wszystkie możliwości automatyzacji wywodzą się z jednego z głównych interfejsów COM — iDispatch. Obiekt COM oferujący interfejs przesyłający przechowuje tablicę metod (ang. methods, zwykłych funkcji), zdarzeń (ang. events, funkcji, które rejestrują związane z serwerem operacje wewnątrz programu klienta) i właściwości (ang. properties, funkcji które odczytują lub zmieniają wartość określonej zmiennej obiektu COM). Programy klienci mogą sięgać do zapisanych w tej tablicy narzędzi podczas działania pro- 664_____________________________________Poznaj Visual C++ 6 gramu (dynamicznie - technika ta zwana jest późnym wiązaniem, ang. łatę binding), aby uzyskać informacje lub skorzystać z możliwości obiektu automatyzacji. Dualne interfejsy Przy okazji technik OLE i COM możemy trafić na jeszcze jeden termin: dualne interfejsy (ang. dual intelfaces). Obiekt COM może obsługiwać dualne interfejsy udostępniając dostęp do swoich funkcji interfejsu bezpośrednio przez interfejs COM oraz przez interfejs przesyłający. W ten sposób oferujemy programowi klientowi możliwości dwóch języków programowania. Szybkie programy C++ mogą sięgać do obiektów bezpośrednio przez interfejsy COM, a Visual Basie i języki skryptowe mogą sięgać robić to w oparciu o wolniejszy interfejs przesyłający. Interfejs przesyłający dodaje do klasy lUnknown cztery dodatkowe funkcje. " GetTypeinfoCount () - pozwala programowi klientowi sprawdzić, czy informacje o typie są dostępne. • GetTypeinfo () — zwraca informacje o typie. • GetDsOfNames () - zwraca pozycje tablicy (zwane identyfikatorami przesyłania, ang. dispatch IDs) odpowiadające nazwom metod, zdarzeń i właściwości. • lnvoke () - przyzywa jedną z funkcji tablicy, wymaga identyfikatora funkcji i struktury z jej parametrami VARIANT (struktura VARIANT zostanie opisana za chwilę). Interfejs przesyłający jest często wykorzystywany w OLE, przykładowo w kontrol-kach Active X, dokumentach OLE i przez Visual Basie Scripting. Ten system polegający na odnajdywaniu funkcji w wewnętrznej tablicy jest znacznie wolniejszy od bezpośredniego przyzywania funkcji w interfejsie, pozwala jednak na większą swobodę. Można utworzyć bibliotekę informacji o typach zapisaną w pliku .tlb. Biblioteki typów pozwalają Visual C++ szybko skonstruować szkieletowe klasy (zwane sterownikami przesyłania, ang. dispatch driver). Te szkieletowe klasy akceptują przesyłane parametry i przekształcają je w tablicę VARIANT po czym przyzywają funkcję lnvoke (), która przywołuje właściwy obiekt automatyzacji. Warianty Ponieważ ta sama funkcja lnvoke () przyzywana jest dla wielu różnych funkcji automatyzacji OLE, musi mieć jakiś sposób na przesyłanie parametrów (zmiennych) różnych typów w zależności od przyzywanych funkcji. Robi to przesyłając przyzywanym funkcjom strukturę DISPARAMS wskazującą tablicę struktur VARIANT. Struktura VARIANT przechowuje po prostu dane różnego typu, które mogą być szeregowane (ang. marshaling) przez OLE. Struktura ta przechowuje również znacznik typu wariantu (VARTYPE), zwany vt, informujący, który typ jest wykorzystywany. W tabeli 25.2. pokazane zostały niektóre z licznych typów, które mogą być przechowywane w strukturze VARIANT. Struktura Zasady programowania OLE i COM 665 VARIANT może również przechowywać wskaźnik do obiektu, co oznaczane jest poprzez poprzedzenie nazwy zmiennej literą p (na przykład pival) i dodanie do znacznika typu znacznika VT_BYREF (na przykład VT_I 4 | VT_BYREF). Przesyłanie tablic wariantów Korzystając z klasy COleSafeYariant możemy przesyłać funkcjom OLE całe tablice zawierające dane typu wariantowego. Klasa ta pozwala zdefiniować typ elementu, liczbę i wymiary tablicy, pozwalając w jednym wezwaniu przesyłać wielkie bloki danych. Więcej informacji na temat korzystania z tablic COleSafeArray można znaleźć w dokumentacji Microsoftu. Tabela 25.2. Niektóre typy danych akceptowanych przez warianty Typ Nazwa Znacznik typu Opis unsigned char bVal VTUI1 Jednobajtowa wartość bez znaku short iVal VTI2 Dwu bajtowa liczba ze znakiem long lVal VT 14 Czterobajtowa liczba ze znakiem float fltVal VTR4 Czterobajtowa liczba zmiennoprzecinkowa double dbVal VTR8 Ośmiobajtowa liczba zmiennoprzecinkowa BOOL boolVal VT BOOL Czterobajtowa wartość TRUE lub FALSE SCODE scode VTERROR Kod błędu obiektów COM DATĘ datę VTDATE Liczba zmiennoprzecinkowa kompatybilna z klasą COieDateTime BSTR bstrVal lUnknown punkVal IDispatch pdispval VT_BSTR Łańcuch kompatybilny z łańcuchami Visual Basica, pozwalający się konwertować na łańcuch CString VT_UNKNOWN Wskaźnik do interfej sulUnknown VT_DISPATCH Wskaźnik do interfejsu IDispatch Typy przechowywane w wariantach (ang. vańants) są całkowicie niezależne od języka i zrozumiałe dla każdego języka pozwalającego programować technikę OLE. Konwersji łańcucha CString biblioteki MFC w łańcuch BSTR (łańcuch Visual Basica) można dokonać za pomocą funkcji AllocSysString () i SetSysString () klasy CString, które odpowiednio alokują nowy łańcuch BSTR i wydobywają istniejący łańcuch BSTR z obiektu CString. Odwrotną konwersję łańcucha BSTR w łańcuch języka C++ można wykonać za pomocą przekształceń (char*) i (const char*). 666___________________________________Poznaj Visual C++ 6 Klasa coleDateTime akceptuje również bezpośrednie przesłanie struktury VARIANT jako parametru konstruktora lub też typu DATĘ wydobytego za pomocą przekształcenia (DATĘ) . Typy wariantowe wykorzystywane są nie tylko przez interfejsy przesyłające. Pojawiają się również w innych miejscach OLE jako bardzo wygodny środek przechowywania i transferowania danych różnych typów. Tworzenie serwera automatyzacji Liczne aplikacje, takie jak Word, Excel i nawet sam kompilator Developer Studio, mogą również służyć jako serwery automatyzacji. Aplikacje takie oferują aplikacjom klientom interfejs przesyłający, który może być wykorzystywany do przywoływania specjalistycznych, oferowanych przez te aplikacje funkcji. VB Scripting (Visual Basie Scripting), narzędzie wykorzystywane do pisania makro- instrukcji, korzysta z tych metod przy tworzeniu i manipulowaniu dokumentami serwerów. Kiedy piszemy makroinstrukcję dla jednego z tych programów, tak naprawdę piszemy skrócony formularz Visual Basica, który może przyzywać funkcje interfejsu przesyłającego dowolnego serwera automatyzacji. Serwery automatyzacji posiadają oprócz globalnego identyfikatora także skróconą nazwę, która może być wykorzystana do odnajdywania ich identyfikatorów. Nazwy te prze- chowywane są w rejestrze w folderze HKEY_CLASES_ROOT zawierającym foldery odpowiadające globalnym identyfikatorom klas serwerów automatyzacji. Przykładowo kompilator Microsoft Visual Studio widnieje pod następującą (wiążącą go z odpowiednim identyfikatorem) nazwą: HKEY_CLASSES_ROOT\MSDEV.APPLICATION\CLSID = {FB7FDAE2-89B8-11CF-9BE8-OOAOC90A632C} Klient może teraz za pomocą funkcji CLSiDFromString (), przesyłając jej skróconą nazwę (MSDEV.APPLICATION) i wskaźnik do struktury CLSID, pobrać globalny identyfikator klasy CLSID (FB7FDAE2-89B8-11CF-9BE8-OOAOC90A632C), a następnie zapisać go w strukturze. Przeglądanie tablicy działających obiektów Działające serwery automatyzacji często rejestrują się w tablicy działających obiektów (ang. Running Object Table), aby programy klienci automatyzacji mogły łączyć się z działającym egzemplarzem obiektu. Rejestrują się używając tzw. monikera, który jest złożoną nazwą pozwalającą identyfikować zarówno programy, dokumenty złożone, jak i zbiory danych. Rejestr działających obiektów można obejrzeć za pomocą narzędzia ...\Microsoft\Visual Studio\Common\Rotview.exe. Po uruchomieniu program Rotview.exe wyświetli działające w danym momencie w systemie (i zarejestrowane) obiekty. Kompilator Visual Studio rejestruje swój identyfikator klasy jako moniker postaci: ! (FB7FDAE2- 89B8-HCF-9BE8-OOAOC90A632C). Jeśli załadujemy dokument Worda zobaczymy, że procesor tekstu zostanie zarejestrowany w tej tablicy. Zasady programowania OLE i COM 667 Program klient może teraz za pomocą funkcji CoCreateinstance (), wymagającej jedynie wskaźnika do interfejsu przesyłającego, utworzyć działający w tle egzemplarz programu Developer Studio. Programy .exe serwerów automatyzacji mogą być uruchamiane ze znacznikiem wiersza poleceń /Automation, który zapobiega wyświetleniu ich okna na ekranie. W tym trybie z serwerem można się kontaktować tylko za pośrednictwem interfejsu przesyłającego. W ten sposób zachowuje się Word, Excel i inne serwery automatyzacji, oferując użytkownikowi swoje funkcje bez informowania go o tym, że działają. Szkielet aplikacji dla serwera automatyzacji można utworzyć za pomocą kreatora AppWizard przez zaznaczenie na stronie trzeciej opcji Automation (rysunek 25.3). MFC AppWiMid - Step 3 of 6 li T L u La — Whal compound docurrient supportwould you iike to l indude? ff None r Sontainer r' M|nl-seiver i" Full-iewr <~ Bolhcontainet^d serwer r : . • r ,- . •• ;. - : :,i111 ; What o'her supponwou!d yoii tike to ineSude? 17 ^ytomalion: F Aiai'^eXCorttrols Rysunek 25.3. Dodawanie obsługi automatyzacji za pomocą kreatora AppWizard Jeśli utworzymy szkielet aplikacji oferujący obsługę automatyzacji, zauważymy, że w programie pojawi się kilka dodatkowych plików. Jednym z nich jest plik .reg zawierający odpowiednie informacje wpisywane podczas rejestracji nowego serwera automatyzacji. Plik ten potrzebny nam będzie tylko wtedy, gdy zamierzamy instalować serwer na innych komputerach za pośrednictwem programu instalacyjnego. Serwer utworzy również automatycznie odpowiednie hasło w Rejestrze, jeśli zostanie uruchomiony normalnie, bez opcji wiersza poleceń /Automation. Kreator AppWizard zdefiniuje również automatycznie nowy globalny identyfikator klasy dla dokumentu, który znaleźć będzie można w nowym pliku .reg. Przykładowo, jeśli utworzymy szkielet serwera automatyzacji o nazwie Autoserver, w pliku .reg pojawią się następujące informacje dla Rejestru: HKEY_CLASSES_ROOT\Autoserver.Document = Autose Document HKEY_CLASSES_ROOT\Autoserver.Document\CLSID = {D11ED783-CFD8-11D1-931D-444553540000} HKEY_CLASSES_ROOT\CLSID\{Dl1ED783-CFD8- 11D1-93 ID-44 45535400 00} = Autose Document 668_____________________________________Poznaj Visual C++ 6 HKEY_CLASSES_ROOT\CLSID\(D11ED783-CFD8-11D1-931D-4445535400 00)\ProgId = Autoserver.Document HKEY_CLASSES_ROOT\CLSID\{D11ED783-CFD8-11D1-931D-4445535400 00}\LocalServer32 = AUTOSERVER.EXE Kolejnym dodanym przez kreator plikiem jest plik .odl. Plik ten zawiera język ODL (Object definition language), który wykorzystywany będzie do tworzenia biblioteki typów dla serwera automatyzacji. Kiedy będziemy dodawać do serwera automatyzacji nowe metody i właściwości, w pliku tym pojawiać się będą nowe hasła opisujące parametry wykorzystywane przez nowe funkcje. Nie musimy się martwić o obsługę ani o kompilację tego pliku, bowiem jest on obsługiwany przez kreator CIassWizard i kompilowany automatycznie w momencie budowania projektu. Biblioteki typów, programy MkTypLib i MIDL W przeszłości biblioteki typów były kompilowane z plików .odl (języka ODL) za pomocą narzędzia MkTypLib. Podobny do niego język IDL (.idi) był wykorzystywany do definiowania interfejsów, nie potrafił jednak tworzyć bibliotek typów. Microsoft w końcu zracjonalizował tę sytuację rozszerzając możliwości pliku .idi tak, aby mógł on również definiować informacje zawarte w bibliotece typów. Nowy kompilator Microsoft IDL (MIDL) potrafi kompilować zarówno pliki .idi, jak i .odl (czyniąc zbytecznym kompilator MkTypLib). Mimo iż pliki .odl są nadal wykorzystywane, nie są już niezbędne, ponieważ zawarte w nich deklaracje mogą być umieszczane w plikach .idi. Listing 25.1 przedstawia przykładowy plik .odl dla serwera automatyzacji Autoserver. Serwer ten posiada tylko jedną metodę SquareRoot () zadeklarowaną w linii 26 w interfejsie przesyłającym lAutoserver, który z kolei deklarowany jest w liniach 13-28. Przedstawiony tutaj program Autoserver posiada metodę SquareRoot (), która zwraca po prostu pierwiastek kwadratowy z przesłanej mu liczby. Listing 25. l. LST26_1 .ODL - plik ODL dla serwera automatyzacji Autoserver 1 // autoserver.odl : z niego powstanie biblioteka typów 2 3 // Plik ten zostanie przekształcony przez kompilator MIDL, 4 // aby utworzyć bibliotekę typów (autoserver.tlb). 5 6 [ uuid(DHED784-CFD8-llDl-931D-444553540000) , version(1.0) ] 7 library Autoserver 8 { 9 importlib("stdole32.tlb"); Zasady programowania OLE i COM ________________________669 10 11 // Podstawowy interfejs przesyłający dla CAutoseryerDoc 12 13 [ uuid(DHED785-CFD8-HDl-931D-444553540000) ] 14 dispinterface IAutoserver 15 { 16 properties: 17 // UWAGA - CIassWizard zachowa informację o własności 18 // Zachowaj ostrożność podczas edycji tej sekcji. 19 //((AFX_ODL_PROP(CAutoserverDoc) O 20 //}}AFX_ODL_PROP 21 22 methods: 23 // UWAGA - CIassWizard zachowa informację o metodzie 24 // Zachowaj ostrożność podczas edycji tej sekcji. 25 //((AFX_ODL_METHOD(CAutoserverDoc) @ 26 [id(l)] double SquareRoot(doubie dInputVal); 27 //)}AFX_ODL_METHOD 28 .} ; 29 30 // Informacje o klasach dla obiektu CAutoserverDoc 31 © 32 [ uuid(DllED783-CFD8-llDl-931D-444553540000) ] 33 coclass Document 34 { 35 [default] dispinterface IAutoserver; 36 }; 37 //((AFX_APPEND_ODL}} 38 //}}AFX_APPEND_ODL}} 39 }; O Kreator CIassWizard dodaje właściwości OLE tutaj występujące jako metody get i set umożliwiające sięganie do zapisanych w nich wartości © Tutaj kreator CIassWizard dodaje metody OLE © W tej sekcji dokument deklarowany jest jako klasa z jednym interfejsem przesyłającym Kreator AppWizard, aby uruchomić automatyzację OLE, dodaje do standardowego kodu źródłowego szkieletu aplikacji wiele dodatkowych elementów. Przykładowo, funkcja 670_____________________________________Poznaj Visual C++ 6 Initinstance () klasy aplikacji (CMyServerApp) musi inicjować biblioteki OLE i COM, co też robi przyzywając funkcję Afx01elnit (). W dołączonym przez kreatora kodzie można również znaleźć linię łączącą szablon dokumentu z nową zmienną składową klasy C01eTemplateServer, m_server: m_server.ConnectTeinplate(cisid, pDocTemplate, TRUE); Ten szablon serwera wywiedziony został z klasy cobjectFactory, która jest implementacją fabryki klas dla klas OLE. Aplikacje klienty korzystać będą z tej fabryki przy tworzeniu nowego obiektu dokumentu serwera za pośrednictwem funkcji CoCreateln-stance() lub funkcjiCoGetCIassObject() iCreateinstance(). Argumenty Automation i Embedded wiersza poleceń Kiedy aplikacja klient przyzywa funkcję aplikacji będącej serwerem automatyzacji, serwer automatyzacji (taki jak na przykład Word) musi zostać uruchomiony w tle. OLE załatwia to w ten sposób, że uruchamia program serwera ze znacznikiem /Automations jako parametrem wiersza poleceń. Przyzywana z funkcji initinstan-ce () serwera funkcja parseComandLine () przypisuje obiektowi ccommandLineinfo znacznik m_bRunAutomated. Aplikacja może następnie sprawdzić ten znacznik, aby stwierdzić, czy program jest uruchamiany jako serwer aplikacji, czy jako samodzielna aplikacja. Jeśli program zostanie uruchomiony z parametrem wiersza poleceń /Embedded, aplikacja będzie wiedziała, że jest wykorzystywana do obsługi obiektu osadzonego w dokumencie złożonym. Jeśli aplikacja jest wzywana przez aplikację klienta jako serwer automatyzacji, może zarejestrować dowolne obiekty automatyzacji OLE jako gotowe do działania, za pomocą następujących linii w funkcji Initinstance (): if (cmdinfo.m bRunEmbedded l| cmdinfo.m bRunAutomated) ( C01eTemplateServer::RegisterAll() ; return TRUE; } Jeśli nie, możemy uruchomić serwer automatyzacji jako normalną aplikację. Wówczas następujące linie kodu utworzą odpowiednie hasła w Rejestrze systemu Windows: m_server.UpdateRegistry(OAT_DISPATCH_OBJECT) ; COleObjectFactory::UpdateRegistryAll() ; Jeśli przyjrzymy się utworzonej przez kreator CIassWizard klasie dokumentu, również tam znajdziemy dodatkowy kod związany z automatyzacją. Identyfikator interfejsu przesyłającego dla dokumentu jest deklarowany jako stała (static const): Zasady programowania OLE i COM 671 static const IID IID_IAutoserver = ( Oxdlled785, Oxcfd8, Oxlldl, ( 0x93, Oxld, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0 } }; Sam interfejs przesyłający jest definiowany za pomocą kilku utworzonych przez kreator makroinstrukcji: BEGIN_INTERFACE_MAP(CAutoserverDoc, CDocument) INTERFACE_PART(CAutoserverDoc, IID_IAutoserver, Dispatch) END_INTERFACE_MAP() Metody w mapie przesyłania (ang. dispatch map) są automatycznie dodawane przez kolejny zestaw makroinstrukcji, który obsługuje mapę. Przykładowo, metoda SquareRoot () dodana do dokumentu programu Autoserver będzie wyglądać mniej więcej tak: BEGIN_DISPATCH_MAP(CAutoserverDoc, CDocument) //{(AFX_DISPATCH_MAP(CAutoserverDoc) DISP_FUNCTION(CAutoserverDoc, "SquareRoot", SquareRoot, VT_R8, VTS_R8) //}}AFX_DISPATCH_MAP END_DISPATCH_MAP() Łańcuch "SquareRoot" definiuje interfejs przesyłający razem z nazwą metody, a znaczniki VT R8 i VTS R8 definiują typy parametrów i wartości zwracanych przez funkcję (jako ośmiobajtowe liczby zmiennoprzecinkowe). Parametry makroinstrukcji DISP_FUNCTION Pierwszy parametr makroinstrukcji DISP_FUNCTION definiuje nazwę klasy, która implementuje funkcję. Drugi parametr definiuje zewnętrzną nazwę funkcji -tę nazwę widzieć będą klienci automatyzacji poprzez interfejs przesyłający. Trzeci parametr podaje nazwę implementującej funkcji wewnątrz serwera. Czwarty parametr definiuje typ danych VARIANT zwracany przez funkcję. Ostatni parametr jest listą oddzielonych spacjami typów danych parametru VARIANT przesyłanych funkcji. Parametry te definiowane są (w pliku AFXDISP.H) jako łańcuch binarnych znaków przekształcany później w indeksy definiujące typ danych VARIANT. Nie należy zmieniać tych makroinstrukcji ręcznie, kreator CIassWizard zrobi to za nas, kiedy będziemy dodawać nowe metody automatyzacji. Dodawanie metod serwera automatyzacji za pomocą kreatora CIassWizard 1. Aby przywołać kreator CIassWizard, wciśnij CtrI+W lub w menu yiew wybierz polecenie CIassWizard. 2. Wybierz kartę Automation i upewnij się, że w liście kombinowanej Ciass Name wybrana jest klasa dokumentu. 672 Poznaj Visual C++ 6 3. Kliknij przycisk Add Method, aby dodać nową metodę. 4. Wprowadź zewnętrzną nazwę metody (ang. extemal name), dla aplikacji klientów, taką jak SqareRoot (pierwiastek kwadratowy) dla serwera CAutoseryerDoc (rysunek 25.4). 5. Teraz powinnieneś zobaczyć wewnętrzną nazwę (ang. intemal name) metody, służącą do wzywania przedstawionego niżej zdarzenia. Nazwę tę możemy zmieniać, zazwyczaj jednak pozostawia się tutaj nazwę wpisaną domyślnie przez kreator. 6. Wybierz typ danych zwracanych przez nową metodę w liście kombinowanej Retum Type (tutaj double). Add Method Extemal name: SquareRoot Interna! narne: |SquareRoot double •Implementation r :11,,:, i Parameterlist Rysunek 25.4. Dodawanie nowej metody do serwera automatyzacji za pomocą kreatora Ciass- Wizard 7. W liście Parameter List wpisz nazwę (taką jak d!nputVal) pierwszego parametru przesyłanego przez uruchamiane zdarzenie do aplikacji właściciela. 8. W kolumnie Type listy parametrów wprowadź typ zmiennej (w przykładzie Autose-rver double). 9. Powtarzaj kroki 7 i 8 dla każdego kolejnego parametru, który powinien być przesłany z aplikacji klienta do nowej funkcji serwera automatyzacji. 10. Aby dodać nową metodę, kliknij OK. Metoda powinna pojawić się na liście zewnętrznych nazw funkcji wyświetlanych na stronie Automation kreatora CIassWizard. 11. Aby rozpocząć edycję nowej metody automatyzacji, kliknij przycisk ĘditCode. Po dodaniu metody możemy wpisać w niej dowolny kod wykonujący zadanie, do którego serwer automatyzacji jest przeznaczony. Zasady programowania OLE i COM 673 Metoda SquareRoot () programu Autoserver zwraca po prostu pierwiastek kwadratowy z liczby jej przesłanej, przywołując funkcję matematyczną sqrt (): tinclude "math.h" double CAutoserverDoc::SquareRoot(double d!nputVal) { return sqrt(d!nputVal); ) Do skonstruowania metody potrzebna jest zarówno przedstawiona wyżej implementacja, jak i opisane wcześniej czynności wykonywane w kreatorze CIassWizard. Kreator automatycznie dodaje do mapy rozsyłania i pliku .odl linie niezbędne do utworzenia informacji o typie dla nowej metody serwera automatyzacji OLE. Jeśli zbudujemy teraz serwer automatyzacji, zobaczymy, że plik .odl został skompilowany razem z naszym kodem źródłowym. W katalogu Debug (lub Relase) pojawi się dodatkowo nowy plik .llb przechowujący definicje biblioteki typów dla nowych metod interfejsu przesyłającego. Biblioteki typów Należy pamiętać, że o ile statyczne biblioteki (.lib) i biblioteki dynamiczne (.dli) przechowują kod programu, biblioteki typów (.tlb) przechowują tylko parametry i definicje zwracanych typów dla serwera automatyzacji. Właściwy kod programu znajduje się wewnątrz samego serwera. Przykładowo, jeśli załadujemy bibliotekę typów dla Worda, znajdziemy tam tylko informacje wymagane do przesyłania parametrów do funkcji Worda i informacje o tym, jakie wyniki dostaniemy z powrotem. Do przywoływania tych funkcji potrzebować będziemy zainstalowanej wersji Worda. Możemy wykorzystać bibliotekę typów do utworzenia klasy sterownika przesyłania OLE w programie klienta (co pokażemy dalej). Biblioteki typów możemy również udostępniać programistom, którzy chcą w swoich programach przywoływać nasz serwer automatyzacji. Przykładowo, biblioteki typów dla Worda można ściągnąć ze stron interne-towych Microsoftu (uww.microsoft. com) i za ich pomocą utworzyć program klienta korzystający z metod Worda. Tworzenie klienta automatyzacji Możemy uruchamiać serwery automatyzacji i przywoływać ich metody z dowolnej aplikacji klienta. Pierwsza rzecz, którą należy w tym celu zrobić, jest inicjowanie bibliotek OLE. Robimy to dodając po prostu do funkcji initinstance () odwołanie do funkcji Afx01elnit(): BOOL CAutoclintApp::Initinstance() ( Afx01elnit(); 674_____________________________________Poznaj Visual C++ 6 Aby uprościć przyzywanie metod serwera automatyzacji, możemy polecić kreatorowi CIassWizard utworzyć z biblioteki typów (pliku .tlb) klasę sterownika przesyłania (ang. dispatch dii v e r). Tworzenie klasy sterownika przesyłania na bazie biblioteki typów za pomocą kreatora CIassWizard 1. Aby przywołać kreator CIassWizard, wciśnij klawisze Cirl+W lub w menu yiew wybierz polecenie CIassWizard. 2. Wybierz kartę Automation. 3. Kliknij przycisk Add Ciass i w rozwijanym menu wybierz polecenie From a Type Library. 4. Odszukaj odpowiedni plik biblioteki typów, który będzie najpewniej przechowywany między plikami .tlb, .olb lub .dli. 5. Gdy już odnajdziesz odpowiedni plik, kliknij go dwukrotnie i wybierz opcję Open, aby wyświetlić okno dialogowe Confinn Ciasses. 6. Możesz zmienić standardowe nazwy klas i plików implementacji tworzonych z importowanych klas wybierając klasę i wpisując odpowiednie nazwy w polach edycji Ciass Name, Header File i Implementation File. 7. Kliknij OK, aby zaimportować wybrane klasy. Zostaną one dodane do tworzonego projektu. Bibliotekę typów z poprzedniego przykładu (autoserwer.tłb), możemy wykorzystać do utworzenia plików autoserver.cpp i autoserver.h, zawierających klasę sterownika przesyłania. Klasa sterownika rozsyłania posiada funkcje typu Invoke () wywołujące (za pośrednictwem pomocniczej funkcji) odpowiednią funkcję interfejsu przesyłającego serwera automatyzacji, definiowaną za pomocą wspomnianego wcześniej identyfikatora tablicy. Przykładowo, funkcja programu Autoserver SqareRoot () będzie w klasie sterownika przesyłającego wyglądać tak: double IAutoserver::SquareRoot(double d!nputVal) ( double result; static BYTE parms[] = VTS_R8; InvokeHelper(Oxl, DISPATCH_METHOD, VT_R8, (void*)sresult, parms, d!nputVal) ; return result; ) Pierwszy parametr (0x1) funkcji lnvokeHelper () powyżej jest identyfikatorem funkcji SquareRoot () z tablicy funkcji interfejsu przesyłającego serwera automatyzacji. Drugi parametr jest znacznikiem informującym, że przyzywana funkcja jest metodą, a nie Zasady programowania OLE i COM 675 zdarzeniem, czy właściwością. Trzeci parametr definiuje typ zwracanych przez metodę wyników przechowywany w zmiennej result. Czwarty parametr, parms, definiuje format parametrów. Następujące po nim parametry są parametrami przyzywanej funkcji, które można za pomocą funkcji pomocniczej lnvokeHelper() spakować w tablicę DISPARMS, przygotowując je do użycia w funkcji przyzywającej lnvoke (). Zanim przyzwiemy odpowiednie funkcje serwera automatyzacji, musimy zdobyć wskaźnik do obiektu interfejsu przesyłającego. Jeśli przyjrzymy się definiującemu klasę sterownika przesyłania plikowi nagłówka, zobaczymy, że klasa ta została wywiedziona z klasy coieDispatchDriver. Klasa ta posiada funkcje składowe pomagające zdobyć wskaźnik do interfejsu przesyłającego. Przyzywamy metodę CreateDispatch () klasy sterownika przesyłania, podając jej zrozumiałe dla techniki OLE powiązanie z odpowiednią klasą (przykładowo "Auto-server. document"). Funkcja CreateDispatch () znajdzie w Rejestrze odpowiedni identyfikator klasy i utworzy egzemplarz obiektu poszukiwanego serwera automatyzacji. Jeśli to się powiedzie, funkcja zwróci wartość TRUE i będzie można przywoływać metody sterownika. Odnajdywanie obiektów automatyzacji w Rejestrze Obiekty automatyzacji można znaleźć w Rejestrze w folderze HKEY_CLASSES_ ROOT. Wewnątrz każdego folderu obiektu znajduje się folder CLSID zawierający standardowy łańcuch ze 128-bitowym globalnym identyfikatorem CLSID identyfikującym klasę automatyzacji. , ^ : Listing 25.2 przedstawia fragment kodu tworzący egzemplarz nowej importowanej klasy sterownika przesyłania. Następnie tworzy interfejs przesyłania i przyzywa jedną z metod serwera automatyzacji. Możemy napisać opartą na oknie dialogowym aplikację Autoclient, która przetestuje kod serwera automatyzacji. Kod przedstawiony w listingu 25.2 dodamy na końcu funkcji OninitDialogO (w pliku autoclientDIg.cpp). Dyrektywa #include z linii 2 listingu powinna zostać dołączona na początku pliku. Jeśli teraz zbudujemy i uruchomimy aplikację, zobaczymy okno komunikatu widoczne na rysunku 25.2. W tle automatycznie uruchamiany jest serwer automatyzacji, co umożliwia korzystanie z funkcji wyliczającej pierwiastek kwadratowy. Listing 25.2. LST26_2.CPP - inicjowanie i przyzywanie funkcji serwera automatyzacji za pośrednictwem klasy sterownika przesyłania 1 // Załącz nagłówek klasy sterownika przesyłania 2 #include "autoserver.h" 3 676 ___ _____________ __________________Poznaj Visual C++ 6 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //.. Deklaracja funkcji l f ** Inicjuj klasę sterownika przesyłania IAutoserver IAutoserver; // ** Utwórz egzemplarz serwera i połącz go // ** z jego interfejsem przesyłającym if (IAutoserver.CreateDispatch("Autoserver.document")) O { // ** Przyzwij metodę SquareRot serwera double dNumber = 36.0; double dRoot = IAutoserver.SquareRoot(dNumber); // ** Wyświetl wynik CString strMsg; strMsg.Format("Root of %f = %f\n",dNumber,dRoot) ; AfxMessageBox(strMsg) ; O Funkcja CreateDispatch () odnajduje globalny identyfikator klasy odpowiadający zdefiniowanej nazwie i przyzywa funkcję CoCreateinstance () przywołującą interfejs przesyłający W linii 2 listingu 25.2 załączamy plik nagłówka zawierający definicję klasy sterownika przesyłania (utworzonej z biblioteki typów). W liniach 6-21 przyzywamy serwer automatyzacji. Egzemplarz obiektu klasy sterownika przesyłania deklarowany jest w linii 7. W Unii 11, aby utworzyć obiekt serwera automatyzacji i przechować wskaźnik do jego interfejsu przesyłającego (w zmiennej składowej m_lDispatch), przyzywana jest funkcja składowa CreateDispatch (). Jeśli połączenie z interfejsem się powiedzie, w linii 15 przyzywana jest metoda SqareRoot (), a następnie otrzymana od niej wartość pierwiastka zapisana w zmiennej dRoot jest w linii 20 wyświetlana w oknie komunikatu. Efekt widać na rysunku 25.5. Kiedy klasa interfejsu lAutoserwer wyjdzie poza zakres kompetencji klasy sterownika, interfejs rozsyłania zostanie automatycznie usunięty przez funkcję destruktora klasy. Możemy także usunąć sterownik przesyłania za pomocą jego funkcji składowej Rela-seDispatch (). Sterownik posiada również metody AttachDispatch () i Detach-Dispatch (), automatycznie przyłączające i odłączające wskaźnik do obiektu serwera automatyzacji. Zasady programowania OLE i COM 677 !:B 51e Edit YIBW Insert Project Debug Tools Wind i| iii iś H i0 ' %i ©: Q ' ; i r- Aiitn'- ,pt'i • , •vyv-xf;.:: .•;.:„ '•liJ'Ailn^^prr,bers)»J| ^OnInifDialog .^J^''' ; |1^ ^ .^ ! : ,. t "l J l NamB.^^.illllf.^ V6!U& ^^II^SMWS^II^iw^^^y^^fcf; '1 Nome Vnlue * .( • » - 'is' B a i i®>.*«"» .^.^^.^ .^^^^^^^^^^^^^^».... .^.^^ [1 "^rn\wa»chi ^^waziiiwaisagsswga^iafc BBiSE^fiE^fŁ ;:[i:jii:LB81il^Sll^ttlRriS]?SS' Rysunek 25.5. Przykład ilustrujący działanie automatyzacji OLE Kontenery OLE, serwery i miniserwery Serwer OLE jest aplikacją, którą można uruchamiać wewnątrz okna aplikacji kontenera. Przykładowo, arkusze kalkulacyjne Excela mogą być dodawane i edytowane wewnątrz Worda. Program Word jest w tym przypadku kontenerem OLE a Excel serwerem OLE. Dokumenty aktywne Razem z terminami pojemnik OLE i kontener OLE pojawia się też często termin dokument aktywny. Ta stosunkowo nowa technika związana z kontrolkami ActiveX rozszerza możliwości architektury kontener/serwer OLE, pozwalając osadzonemu obiektowi przejąć kontrolę nad całym obszarem roboczym kontenera (a nie tylko nad małym wycinkiem) i bezpośrednio manipulować obramowaniem, menu i paskami narzędziowymi kontenera. Więcej informacji na ten temat można znaleźć w dokumentacji Microsoftu w materiałach związanych z interfejsem IGleDocument i pokrewnymi. Aplikacje mogą równocześnie pełnić obie role, być zarówno pojemnikami, jak i ser- werami OLE. Dobrym przykładem są tu programy Word i Excel. Możemy zarówno uru- chamiać Excela wewnątrz Worda, jak i na odwrót. 678_____________________________________Poznaj Visual C++ 6 Normalny serwer może działać jako obiekt wspomagający inny program oraz jako samodzielna aplikacja. Miniserwery natomiast można uruchamiać tylko wewnątrz programu kontenera. Kontenery OLE obsługują polecenie Object (Obiekt) menu Insert (Wstaw). Serwerem OLE jest na przykład, aplikacja WordPad systemu Windows. Możemy użyć programu WordPad, żeby obejrzeć listę innych serwerów OLE. Klikamy polecenie Object w menu Insert. Pojawi się lista serwerów OLE, które można załączyć do dokumentu WordPad. Dołączany obiekt serwera OLE może być edytowany wewnątrz prostokątnego obszaru ograniczonego utworzoną z ukośnych linii grubą ramką, nazywaną również wewnętrznym obramowaniem (ang. in-place frame). Proces ten nosi nazwę wewnętrznej (ang. in-place actiyation) aktywacji i jest inicjowany przez przesłanie serwerowi przez kontener wartości znaczników zwanych słowami (ang. verbs). Kiedy obiekt serwera jest nieaktywny, ramka znika, a odpowiedni obraz wewnątrz ramki wyświetlany jest w takiej postaci w jakiej był, gdy ramka ostatni raz była aktywny (jest teraz przechowywany i wyświetlany przez odpowiedni metaplik). Gdy obiekt jest aktywny, ramka jest ponownie wyświetlana, a menu serwera dodawane jest do menu kontenera. Dane osadzonego dokumentu mogą być serializowane przez dokument kontenera za pomocą standardowych funkcji serializujących dokumentu. Dokument kontenera przesyła obiektowi CArchive dokumenty serwerów, a obiekt dokona serializacji danych serwera. Dokumenty złożone: osadzanie i dołączananie Chociaż osadzane dokumenty są przechowywane całkowicie wewnątrz dokumentu złożonego aplikacji, dołączane obiekty zachowane są jako monikery. Moniker to mały obiekt, który jednoznacznie definiuje miejsce, w którym naprawdę znajdują się dane i sposób ich wyświetlania, kiedy oglądamy je wewnątrz aplikacji klienta. Bazowy szkielet serwera OLE i kontenera OLE można utworzyć za pomocą kreatora AppWizard, niemniej aby działały prawidłowo, pewne rzeczy trzeba zaprogramować własnoręcznie. Bazowy szkielet serwera OLE uzupełniany jest o dwie dodatkowe klasy umożliwiające wewnętrzną edycję. Jedna z tych klas to klasa elementu (ang. item) serwera wywodzona z klasy CO!eServerltem. Służy ona do reprezentowania osadzonego w kontenerze elementu i dostarcza funkcji OnDraw () odmalowujących na żądanie kontenera osadzony obiekt. Klasa ta posiada również funkcję OnDoVerb() obsługującą polecenia wyświetlenia, ukrycia lub zamknięcia osadzonego obiektu wysyłane przez kontener. Kontener może również kontaktować się z serwerem za pośrednictwem oferowanego przez element serwera interfejsu lOleObject. Drugą dodatkową klasą jest okno obramowujące wewnętrznego obiektu wywodzone z klasy colelPFrameWnd. Klasa ta jest wykorzystywana do zamieszczania kodu imple- Zasady programowania OLE i COM____ __ __ __ 679 mentującego prywatne paski narzędziowe serwera i polecenia menu w miejsce poleceń kontenera w momencie, gdy uaktywniany jest serwer. Do szkieletu kontenera dodawana jest tylko jedna dodatkowa klasa wywodzona z klasy COleClientItem. Klasa ta posiada liczne funkcje pomagające utworzyć i obsługiwać elementy osadzone w dokumencie złożonym. Dostarcza również dodatkowe połączenie między kontenerem a serwerem, umożliwiając komunikację za pomocą słów przez funkcję DoYerb () oraz kilku funkcji przeznaczonych do obsługi właściwej pozycji elementu serwera i jego statusu (aktywacji, dezaktywacji). Dopisując własny kod do tych dwóch klas serwera i klasy kontenera możemy zaprogramować bardziej zaawansowane możliwości edycji elementu serwera osadzonego w kontenerze. Warto przy tym wiedzieć, że ani serwer nie musi wiedzieć, w jakim obiekcie jest osadzany, ani kontener, jaki obiekt gości. Tak długo jak serwer i jego klienty stosują się do tych samych standardów, kontener i serwer mogą być łączone bez problemów, co sprawi, że dla użytkownik będzie miał wrażenie, iż korzysta z jednej aplikacji. CzęŚĆ VII Zaawansowane zagadnienia programowania Rozdział 26 Tworzenie kontrolek ActiveX Tworzenie kontrolek ActiveX________ Implementowanie kart właściwości kontrolki Tworzenie plików rejestracji i dystrybucji dla kontrolki_______ Testowanie kontrolki za pomocą testowego kontenera Tworzenie szkieletu kontrolki ActiveX za pomocą kreatora ActiveX Control Wizard Do tworzenia szkieletów kontrolek ActiveX służy specjalny wariant kreatora AppWi-zard, ActiveX Control Wizard. Kreator ten pomaga przy tworzeniu kodu, licencjonowaniu i tworzeniu pomocy dla nowej kontrolki. Przyzywamy go wywołując okno dialogowe New za pomocą polecenia New menu File. Jeśli otworzymy kartę Projects zobaczymy opcję MFC ActiveX ControlWizard. Za jego pomocą utworzymy nową kontrolkę ActiveX o nazwie Rotary (wpisywanej w polu Project Name). Kontrolki ActiveX biblioteki Active Template Library W tym rozdziale opisujemy kontrolki ActiveX bazujące na bibliotece MFC (Microsoft Foundation Ciasses). Możemy jednak również tworzyć kontrolki w oparciu o bibliotekę ATL (Active Template Library). Kontrolki ActiveX budowane w oparciu o bibliotekę ATL są generalnie rzecz biorąc znacznie szybsze i mniej pamięciożerne niż kontrolki ActiveX zbudowane w oparciu o bibliotekę MFC. Jednak nie możemy swobodnie mieszać i łączyć ze sobą klas MFC i ATL. Biblioteka ATL jest bardzo skomplikowanym i rozległym tematem, znacznie przekraczającym zakres tej książki. Jeśli potrzebna nam będzie bardzo szybka, wymagająca niewielkich zasobów pamięci, ale skomplikowana kontrolka ActiveX, powinniśmy sięgnąć do bibliotek ATL i kreatora ATL COM AppWizard. 684 Poznaj Visual C++ 6 Definiowanie liczby kontrolek, obsługi licencjonowania i pomocy Na pierwszej karcie kreatora MFC ActiveX ControlWizard (przedstawionej na rysunku 26.1) można zdefiniować kilka podstawowych opcji związanych z projektem nowej kontrolki ActiveX. MFC ActiveX ConIrolWiiard - Stąp l o(Z Hoa/manycontrols would you tikeyour project to have? Would you iike the controls in this projeci to have a runtime license? <~ Yas. please ^ N3 nrntime license Would you Iike sourcefite eomwentstobe geneisted? ^ Yes. please ("No ^orriments Would you Iike he!p ft!as to begeneratsd? '" Yes, please f7 No hełp flles Emish Rysunek 26.1. Pierwsza z dwóch kart kreatora MFC ActiveX ControlWizard • Pierwsze widoczne u góry karty pytanie brzmi: Ile kontrolek ma zawierać projekt? (How many controls would you Iike your project to have?). Jeśli wpiszemy tu liczbę większą niż domyślne l, kreator utworzy automatycznie klasę kontrolki i klasy kart właściwości dla każdej dodatkowej kontrolki. • Następne pytanie brzmi: Czy chcesz, aby kontrolki tego projektu obsługiwały licen- cjonowanie? (Would you Iike the controls in this project to have a runtime license?). Jeśli odpowiemy, że tak (Yes, please), do klas dodane zostaną funkcje sprawdzające licencję i utworzony zostanie domyślny plik licencji .lic. Utworzona kontrolka będzie mogła być rozprowadzana tylko z ważnym plikiem licencji. • Następne pytanie: Czy tworzyć komentarze pliku źródłowego? (Would you Iike so-urce file comments to be generated?), dotyczy komentarzy do kodu źródłowego dodawanych do szkieletu aplikacji podobnie jak w kreatorze AppWizard. • Ostatnie pytanie na karcie pierwszej brzmi: Czy tworzyć pliki pomocy? (Would you Iike hełp files to be generated?). Jeśli i na to pytanie odpowiemy tak, razem z projektem utworzone zostaną odpowiednie pliki pomocy kontekstowej. Definiowanie nazw klas i dodatkowych opcji Karta druga pozwala zdefiniować wszystkie ustawienia odpowiedzialne za to, jak kontrolka ma wyglądać i jak z niej korzystać (patrz rysunek 26.2). Tworzenie kontrolek ActiveX 685 Selectthegonfroiwhose optionsyouwishtobrowse o r edit. You moyedit Its dass and fits narnes flyou wis.h Rotsiy •'] EdUNames... Which featurss woutd you Sike ihis confrot to ^a^e? 17 A.ctivates whenvisib!e r" tnvistbl@ atluniime r" Avaiiable m "lns9r!Qb(ect" dialog F Has an "Aboul" box r' AcTs as a simpfe tr:arne c'ontrol Whichwindowdass,- ifany, should this : controlsubciass'? Wauldyou like edvanced ActiveX enhancements? FillRect(rcBounds, SbrBackGnd) ; CBrush* pOldBrush = pdc->SelectObject(SbrForeGnd); // ** Calculate the relative positions and midpoint CPoint ptRelative = m_ptClicked - rcBounds.TopLeft (); CPoint ptMid(rcBounds.Width()/2,rcBounds.Height()/2); // ** Find offset from the middłe double dRelX = ptRelative.x - ptMid.x; double dRelY = ptRelative.y - ptMid.y; // ** Ustal kąt nachylenia wektora double dAngle = atan2(dRelY,dRelX); @ double dRadX = (double)ptMid.x * 0.9; double dRadY = (double)ptMid.y * 0.9; // ** Znajdź punkt końca wektora na obwodzie pokrętła int nXPos = ptMid.x + (int)(cos(dAngle) * dRadX); int nYPos = ptMid.y + (int)(sin(dAngle) * dRadY); // ** Zdefiniuj środek kółka będącego uchwytem pokrętła CPoint ptKnob=CPoint(nXPos,nYPos)+rcBounds.TopLeft() ; // ** Zdefiniuj odmalowywany prostokąt i namaluj uchwyt CRect rcPoint(ptKnob- CSize(4,4),CSize(8,8)) ; pdc->Ellipse(rcPoint); ® // ** Namaluj wielkie koło pokrętła pdc->Ellipse(ptMid.x-(int)dRadX,ptMid.y- (int)dRadY, ptMid.x+(int)dRadX,ptMid.y+(int)dRadY) ; // ** Namaluj linię od środka pokrętła do uchwytu pdc->MoveTo(ptMid) ; pdc->LineTo(ptKnob) ; pdc->3elect0bject(pOldBrush) ; O Wykorzystujemy kolor otaczającego kontrolkę kontenera, tak aby tło kontrolki nie odcinało się od tła okna kontenera. 690 Poznaj Visual C++ 6 @ Do wyliczania odpowiedniego kąta nachylenia wektora wykorzystujemy funkcje trygonometryczne. © Funkcja rysująca elipsę jest wykorzystywana do namalowania dużego koła symbolizującego pokrętło i małego kółka symbolizującego uchwyt na końcu wektora. Definiowanie właściwości otoczenia Istnieje pewien zestaw właściwości definiujących parametry otoczenia kontrolki (ang. ambient properties) wspólnych dla wszystkich kontrolek ActiveX. Właściwości te pozwalają kontrolce lepiej wtopić się w swoją aplikację rodzica. Ponieważ nie wiemy w jakich aplikacjach zaprojektowana kontrolka będzie używana, najlepiej zakodować w niej tyle właściwości definiujących parametry otoczenia, ile to tylko możliwe. Jak widać, kod z listingu 26. l wykonuje właśnie taką operację odmalowania kontrolki. W liniach 5 i 6 definiowane są pędzle malujące kontrolkę - RGB (0,255,0), kolor zielony - oraz jej tło (funkcja AmbientBackColorf), nadająca mu taki sam kolor jak kolor'"kontenera). Istnieje również bliźniacza funkcja AmbientForeColor(). Funkcje te zwracają dwie właściwości definiujące parametry otoczenia wspólne dla wszystkich kontrolek. Użyte wewnątrz kontenera pobiorą swoje wartości od kontenera, możemy je jednak ręcznie zaprogramować wewnątrz aplikacji kontenera lub umieścić je w towarzyszącym kontrolce arkuszu właściwości. Kolor samej kontrolki zaprogramujemy ręcznie jako zielony, ponieważ funkcja AmbientForeColor () nie zawsze jest obsługiwana przez aplikację. Kolor tła jest używany w linii 9 do odmalowania tła kontrolki. Możemy poprawić kontrolkę tak, aby obszar pod głównym kołem kontrolki nie był odmalowywany dwukrotnie, co pozwoli ograniczyć zbędne mignięcia obrazu. Ponieważ używany do odmalowywania prostokąt przesyła współrzędne rogów kontrolki wewnątrz kontenera, należy przeliczyć współrzędne kliknięcia myszą na relatywne współrzędne kontrolki, odejmując od współrzędnych kliknięcia współrzędne lewego górnego rogu kontrolki w linii 14. W linii 15 w podobny sposób wyliczamy współrzędne środka kontrolki. W liniach 17-28 wyliczamy za pomocą funkcji trygonometrycznych kąt nachylenia wektora definiującego ustawienie pokrętła, a następnie w oparciu o uzyskaną wartość kąta wyliczamy współrzędne uchwytu pokrętła (rysowanego w linii 35). Aby móc korzystać z funkcji trygonometrycznych, należy załączyć plik z deklaracjami funkcji matematycznych po innych dyrektywach łinclude na początku pliku RotaryCtri.cpp: łinclude "math.h" Tworzenie kontrolek ActiveX_________________________ ____ 691 W linii 38 malowane jest główne koło reprezentujące pokrętło kontrolki. Zachodzi ono częściowo na koło uchwytu tak, że to wygląda teraz jak wybrzuszenie na powierzchni dużego koła. Korzystanie z pliku nagłówka math.h Mimo iż wszystkie funkcje matematyczne są dołączone do kodu aplikacji za pośrednictwem standardowej biblioteki czasu wykonania C^, musimy załączyć plik nagłówka math.h, aby dostarczyć kompilatorowi prototypów funkcji. Funkcje tu zadeklarowane obsługują szeroki zakres funkcji matematycznych, począwszy od funkcji trygonometrycznych, przez logarytmy i funkcje zaokrąglające, po funkcje obsługujące konwersję między różnymi typami definiującymi sposób przedstawiania liczb. W liniach 42 i 43 rysujemy linię od środka koła do uchwytu, reprezentującą bieżącą pozycję pokrętła. Musimy tutaj również dodać zmienną definiującą pozycję kliknięcia m_ptClicked jako zmienną składową typu cpoint klasy CRotaryCtri. Możemy to zrobić korzystając z okna dialogowego AddMemberVariable, które można wywołać za pomocą polecenia menu skrótów panelu ClassView (uzupełniamy klasę CRotaryClass). W ten sposób dodamy do definicji klasy CRotaryCtri zmienną składową: CPoint m_ptClicked; PATRZ TAKŻE • Więcej na temat rysowania linii i okręgów pisaliśmy w rozdziale 16. • Jak dodać zmienną składową m_ptClicked typu CPoint pisaliśmy w rozdziale 8. Zarządzanie zdarzeniami wywołanymi przez użytkownika i obsługa wykonywanych przez niego operacji Zmienna m_ptdicked wykorzystywana jest na listingu 26.1 do przekształcania współrzędnych wprowadzanych przez użytkownika podczas kliknięcia myszą w kąt reprezentujący nachylenie wektora definiującego pozycję pokrętła. Musimy załączyć kod przypisujący tej zmiennej nową wartość za każdym razem, gdy użytkownik kuknie kontrolkę lub poruszy mysz wciskając jednocześnie jej lewy klawisz. Załączamy funkcję obsługi komunikatów Windows, aby przechwycić komunikat WM_LBUTTONDOWN, przechować współrzędne punktu, w którym nastąpiło kliknięcie i rozpocząć tryb przechwycenia myszy (ang. mouse capture), tak jak to pokazaliśmy na listingu 26.2. 692 Poznaj Visual C++ 6 Listing 26.2. LST27_2.CPP - funkcja obsługi OnLButtonDown () notująca pozycję myszy i uruchamiająca tryb przechwycenia myszy 1 void CRotaryCtrl::OnLButtonDown(UINT nFlags, CPoint point) 2 { 3 // ** Przywołaj funkcję klasy bazowej 4 COleControl::OnLButtonDown(nFlags, point); 5 6 // ** Uruchom tryb przechwycenia myszy 1 SetCapture() ; 8 9 // ** Zachowa-} punkt kliknięcia 10 m_ptClicked = point; O 11 12 // ** Odmaluj kontrolkę 13 InvalidateControl(); 14 ) O Punkt, w którym nastąpiło kliknięcie, zachowywany jest w zmiennej wykorzystywanej później w funkcji OnDraw (). Na listingu 26.2 w linii 7 za pomocą funkcji SetCapture () uruchamiany jest tryb przechwycenia myszy. Nowa zmienna składowa m_ptClicked wykorzystywana jest w linii 10 do przechowywania współrzędnych punktu, w którym nastąpiło kliknięcie myszą. Przyzywana na końcu funkcja InvalidateControl () działa tak samo jak używana w normalnym oknie funkcja lnvalidate () informująca, że cały obszar okna, a w tym wypadku kontrolki, musi zostać ponownie odmalowany. Możemy również przesłać funkcji InvalidateControl () prostokąt informujący, że tylko część kontrolki leżąca wewnątrz niego powinna zostać odmalowana. Po wciśnięciu przez użytkownika przycisku myszy kontrolka pokrętła powinna obracać się razem z poruszeniami myszy. W tym celu musimy dodać funkcję obsługi komunikatu WM_MOUSEMOVE. Za pomocą kreatora dodajemy funkcję obsługi OnMouseMove () przedstawioną na listingu 26.3. Listing 26.3. LST27_3.CPP - funkcja obsługi komunikatu OnMouseMove () przechowująca i zmieniająca ustawienie kontrolki 1 void CRotaryCtrl::OnMouseMove(UINT nFlags, CPoint point) 2 ( 3 // ** Sprawdź, czy lewy przycisk myszy jest wciśnięty 4 if (nFlags & MK_LBUTTON.) Tworzenie kontrolek ActiveX 693 5 ( 6 // ** Zachowaj nowa pozycję 1 m ptCIicked = point; 8 9 // ** Odmaluj kontrolkę 10 InvalidateControl (); O 11 ) 12 13 // ** Przywołaj funkcję klasy bazowej 14 COleControl::OnMouseMove(nFlags, point); 15 } O Jeśli lewy przycisk myszy jest wciśnięty, pozycja kontrolki jest uaktualniana i odmalowywana w miarę poruszeń myszy. W linii 4 sprawdzamy, czy lewy przycisk myszy jest wciśnięty, testując zmienną nFlags pod kątem obecności znacznika MK LBUTTON. Jeśli znacznik ten zostanie wykryty, pozycja myszy jest uaktualniana i kontrolka jest odmalowana, aby odwzorować poruszenia pokrętła. Na koniec musimy przechwycić komunikat WM_LBUTTONUP za pomocą utworzonej przez kreator funkcji obsługi OnLButtonUp (), aby wyłączyć tryb przechwycenia myszy. W funkcji obsługi musimy przed odwołaniem do funkcji z klasy bazowej wpisać tylko odwołanie do funkcji zwalniającej mysz: RelaseCapture() ; PATRZ TAKŻE • Szczegółowy opis zdarzeń myszy i operacji przechwycenia myszy znaleźć można w rozdziale 8. Szybki sposób testowania kontrolki W tym momencie mamy już kod wystarczający do zbudowania działającej kontrolki. Jeśli teraz zbudujemy kontrolkę, zobaczymy nowy dodatkowy krok budowy: Registering ActiveX Control... W tym kroku kontrolka jest rejestrowana za pomocą programu regsrvr32.exe, który przyzywa tylko w kontrolce globalną funkcję DlIRegisterSerwer (void), a dopiero ta funkcja dokonuje właściwej rejestracji kontrolki. Funkcję DlIRegisterSerwer (void), jak i przeciwną do niej funkcję DllUnegisterSerwer (void), znaleźć można w pliku Rotary.cpp. 694 Poznaj Visual C++ 6 Nie możemy jednak już teraz uruchomić zbudowanej kontrolki, ponieważ przeznaczona jest ona do działania wewnątrz aplikacji kontenera. Jak jednak pamiętamy z rozdziału 9, możemy bez większych trudności dodać kontrolkę ActiveX do okna dialogowego. Teraz, gdy nowa kontrolka jest już zarejestrowana, możemy za pomocą edytora zasobów dodać kontrolkę do jej własnego okna About (IDD_ABOUT_ROTARY). W tym celu zaznaczamy najpierw w oknie dialogowym edytora zasobów identyfikator IDD_ABOUT_ KOTARY, aby rozpocząć edycję zasobu (klikamy dwukrotnie IDD_ABOUT_ROTARY w oknie dialogowym zasobów kompilatora Project Workspace). Następnie dodajemy kontrolkę do okna dialogowego About, klikając je prawym klawiszem myszy i wybierając w menu skrótów polecenie Insert ActiveX Control. Za pomocą kombinacji klawiszy Ctri+T lub polecenia Test z menu Layout możemy przetestować zaprojektowaną kontrolkę (patrz rysunek 26.4). W oknie testującym okna dialogowe (ang. dialog tester) edytora zasobów będziemy mogli kliknięciem myszy dowolnie zmieniać ustawienia kontrolki pokrętła. About Hotary Control Rotaty Control, Yersion 1,0 OK T Rysunek 26.4. Najprostszy sposób testowania kontrolki za pomocą jej własnego okna dialogowego About Po zakończeniu testowania należy pamiętać, żeby usunąć kontrolkę z okna dialogowego About, ponieważ nie jest to jej właściwe miejsce - pozostawienie jej tutaj może być w przyszłości źródłem pewnych nieprzyjemnych komplikacji. Zachowywanie projektu przed testowaniem kontrolki ActiveX Testowanie kontrolek ActiveX za pośrednictwem edytora zasobów jest bardzo wygodne, ale należy pamiętać, aby przedtem zachować wszystkie pliki projektu, ponieważ ewentualne błędy w kodzie kontrolki mogą spowodować nieprzyjemne komplikacje lub problemy z korzystaniem z zasobów. Szczęśliwie kompilator Visual C++ zachowuje pliki zanim przystąpi do kompilacji, dlatego zaraz po kompilacji można z nich względnie bezpiecznie korzystać. ff\\KŁ IAlU.t • Jak dodawać kontrolkę ActiveX do okna dialogowego pisaliśmy w rozdziale 9. Uruchamianie zdarzeń Kontrolka musi w jakiś sposób informować swoją aplikację, że użytkownik zmienił jej status. Kontrolki ActiveX nie są tutaj wyjątkiem, dlatego powinniśmy do kontrolki Rotary dodać funkcję obsługi informującą aplikację o tym, kiedy pokrętło zostanie poruszone. Proces ten nazywany jest uruchamianiem lub odpalaniem zdarzeń (ang. eventfiring). Odpowiednie zdarzenie możemy dodać do kontrolki za pomocą kreatora CIassWizard. Dodawanie zdarzeń kontrolki ActiveX za pomocą kreatora CIassWizard 1. Aby uruchomić kreator CIassWizard, wciśnij klawisze CtrI+W lub kliknij polecenie CIassWizard w menu yiew. 2. Wybierz kartę ActiveX Events. 3. Aby dodać nowe zdarzenie, kliknij przycisk Add Event. 4. Wpisz zewnętrzną nazwę zdarzenia przeznaczoną dla aplikacji właściciela na liście kombinowanej Ęxtemal Name, tak jak widać na rysunku 26.5 (tutaj Repositioned). Bdernal name; OK positioned Internal name: Cancel FireRepositioned , •ImplementatiDn" • - • l ("• : : . <~ Paramster list: dNewPosition Rysunek 26.5. Dodawanie zdarzenia kontrolki ActiveX za pomocą okna dialogowego Add Event 5. W polu Intemal Name poniżej powinna pojawić się nazwa (uruchamiającej zdarzenie) wewnętrznej funkcji kontrolki. Nazwę tę można zmienić, zazwyczaj jednak pozostawia się nazwę wpisaną domyślnie przez kreator. 696 Poznaj Visual C++ 6 6. Na liście Parameter List wpisz nazwę parametru (tutaj dNewPosition), który będzie przesyłany przez zdarzenie aplikacji kontrolki. 7. Obok w kolumnie Type wprowadź typ zmiennej. 8. Powtarzaj kroki 6 i 7 dla każdego parametru przesyłanego przez zdarzenie do aplikacji będącej właścicielem kontrolki. 9. Aby dodać nowe zdarzenie, kliknij OK. Zdarzenie powinno się teraz pojawić na liście Extemal name karty ActiveX Eyents (patrz rysunek 26.6). Atld ClBEli, &dd Evenl.. Selete Sli.* ;;łK.'Mv.?.i'-A-A„i MessageMaps | MemhBrV%riob!es l Aulomalion Actr\'eXEvents | aassirlo | Eroject; Ctass QSrne: Rotaiy E:',.„\Rotaty\FtoiaryCll h. E'\...\Rofsry\PioiatyOl,cpp ^"tsrnal nowe: Selsct e dąsa that implemenis' an AdiveX ControianddickAdd Eventto sddsupportfor control avenls. YDU can useAdd Eventto addfao)ti slockand custom eyenis- Did RreReposróoned(doubfe dNff^Position); Rysunek 26.6. Karta Active Eyents kreatora CIassWizard wyświetla listę zdarzeń kontrolki Po wykonaniu opisanych wyżej czynności w panelu ClasView w klasie CRotaryCtrl pojawi się nowa funkcja FireRepositionedO . Funkcja ta będzie uruchamiać zewnętrzne (ang. extemal) zdarzenie Repositioned, czyli takie zdarzenie, które będzie docierać do goszczącej kontrolkę aplikacji. Teraz należy wpisać kod, który będzie uruchamiał wspomniane zdarzenie, gdy użytkownik zwolni lewy klawisz myszy (co jest równoważne z obróceniem pokrętła). Wcześniej trzeba jednak zachować aktualną pozycję pokrętła w klasie kontrolki. W tym celu należy do klasy kontrolki dodać nową zmienną składową albo za pośrednictwem okna dialogowego New Member Yariable, albo po prostu wpisując w definicji klasy: double m_dCurrentPosition; Wpisana tutaj zmienna typu double przechowywać będzie wartości z przedziału od 0° do 360°, definiujące pozycję uchwytu kontrolki. Przy czym wartość kąta rośnie odwrotnie do kierunku ruchu wskazówek zegara i tak (odwołując się do analogii geograficznych) 0° przypisane jest kierunkowi wschodniemu, 90° północy, 180° zachodowi, a 270° kierunkowi południowemu. Wartość zmiennej ustalamy w oparciu o kąt wyliczony w funkcji OnDraw () w następujący sposób: Tworzenie kontrolek ActiveX 697 m_dCurrentPosition = dAngle * 57.2978 + 180.0; W ten sposób przeliczymy kąt z radianów na stopnie, zachowując jego wartość w zmiennej składowej m_dCurrentPosition. Teraz należy wewnątrz funkcji onLButtonUpt) uruchomić zdarzenie, które poinformuje aplikację, że użytkownik zmienił pozycję kontrolki. W tym celu należy przywołać uruchamiającą zdarzenie funkcję Fire RepositionedO. Należy ją wpisać zaraz po funkcji RelaseCapture (), przesyłając jej zmienną definiującą bieżącą pozycję kontrolki: FireRepositioned(m dCurrentPosition) ; Po tych poprawkach aplikacja będzie otrzymywać od kontrolki zdarzenie Repositio-ned za każdym razem, gdy użytkownik zmieni położenie pokrętła i zwolni przycisk myszy. Samo zdarzenie można załączyć do kontrolki za pomocą kreatora CIassWizard, tak jak to opisywaliśmy w rozdziale 9. PATRZ TAKŻE • Więcej informacji o zdarzeniach i komunikatach Windows znaleźć można w rozdziale 4. * Jak dodawać funkcje obsługi zdarzeń do kontrolek ActiveX pisaliśmy w rozdziale 9. Tworzenie interfejsu właściwości Tworzona przez nas kontrolka ActiveX powinna posiadać zestaw właściwości (ang. properties), które my albo inni programiści wykorzystujący naszą kontrolkę będą mogli zmieniać w zależności od bieżących potrzeb tworzonego programu. Właściwości te powinny dawać się modyfikować zarówno w momencie tworzenia programu, jak i w trakcie działania aplikacji z poziomu kodu programu. Trzy kategorie właściwości Właściwości kontrolek ActiveX można podzielić na trzy podstawowe kategorie. Właściwości otoczenia (ang. ambient properties) to takie właściwości, które wartości swoje wywodzą z kontenera kontrolki, dzięki czemu pozwalają jej upodobnić się do otaczających ją elementów okna. Właściwości wyposażenia (ang. stock properties) to te, które występują we wszystkich kontrolkach i definiują kolor, czcionkę czy bieżący status kontrolki. Właściwości przez nas definiowane (ang. custom properties) będą natomiast specyficzne dla danej, konkretnej kontrolki. Właściwości te powiązane są z funkcjami automatyzacji OLE, dzięki czemu kontrolka może być wykorzystywana i modyfikowana w aplikacjach Yisuał C^, Visual Basica, przeglądarkach sieciowych i dowolnym innym programie potrafiącym komunikować się z interfejsem przesyłającym. ffMKŁ IAtV.t: ^ O automatyzacji OLE pisaliśmy w rozdziale 25. Właściwości wyposażenia Dodając do kontrolki nową właściwość tak naprawdę dodajemy nowe hasło do interfejsu przesyłającego. Każde z tych haseł powiązane jest z jednym ściśle określonym słowem, co pozwala innym językom programowania odnaleźć powiązaną z tym słowem właściwość w interfejsie przesyłającym kontrolki. Właściwości wyposażenia przedstawione w tabeli 26. l są wspólne dla wszystkich kontrolek ActiveX. Należą do nich przykładowo właściwości BackColor i ForeColor definiujące odpowiednio kolor tła i kolor powierzchni kontrolki. Właściwości wyposażenia można dodać do kontrolki posługując się kreatorem CIassWizard. Poniższa instrukcja pokazuje jak dołączyć do kontrolki właściwość ForeColor, pozwalającą zmieniać kolor pokrętła kontrolki Rotary. Dodawanie właściwości wyposażenia do kontrolki ActiveX za pomocą kreatora CIass- Wizard 1. Aby przywołać kreator CIassWizard, wciśnij CtrI+W lub w menu View wybierz polecenie CIassWizard. 2. Wybierz kartę Automation. 3. Kliknij przycisk Add Property, aby wyświetlić okno dialogowe Add Property. 4. Z listy kombinowanej Extemal Name wybierz odpowiednią nazwę właściwości. W naszym przykładzie nazwę ForeColor. Po wybraniu nazwy właściwości pozostałe pola okna zostaną wyłączone (patrz rysunek 26.7). 5. Aby dodać właściwość, kliknij OK. Dodana właściwość powinna się teraz pojawić na liście Extemal Names na karcie Automation kreatora CIassWizard. E^temal name: Of. Cancet Impiemenlałion • • • R Stocjs, <" Memberswiatilt <~ Gel/Setmelhtdt Rysunek 26.7. Dodawanie do kontrolki właściwości wyposażenia za pomocą okna dialogowego Add Property kreatora CIassWizard Tworzenie kontrolek ActiveX 699 Tabela 26.1. Właściwości wyposażenia kontrolek ActiveX Właściwość BackColorGet Fore Color Font Caption Text BorderStyle Apperance Zmieniana poprzez GetBackColor() SetBackColorO GetForeColor() SetForeColor() GetFont() SetFontO InternalGetFont() InternalGetText () InternalGetText() m sBorderStyle m sApperance Opis Definiuje kolor tła kontrolki Definiuje kolor kontrolki Definiuje czcionkę tekstu kontrolki Definiuje tekst tytułu kontrolki Podobna do właściwości Caption Dostępne są dwie wartości właściwości ccNone (bez obramowania) lub ccFixed3ingle (z obramowaniem) Przypisujemy jej wartość l dla kontrolki trój- wymiarowej i O dla kontrolki płaskiej Po dodaniu właściwości ForeColor powinna się ona pojawić w panelu CIassYiew pod hasłem JDRotary interfejsu kontrolki. Teraz w oparciu o wartość tej właściwości będzie można za pomocą funkcji GetForeColor () definiować kolor kontrolki (w funkcji On- Draw()): CBrush brForeGnd(TranslateColor(getForeColor( ))) ; Typ OLE_COLOR i funkcja TranslateColor () Funkcja TransiateColor () pozwala konwertować typ OLE_COLOR definiujący kolor w funkcjach związanych z OLE na typ COLORREF wykorzystywany przez standardowe funkcje Windows. Typ OLE_COLOR rozszerza typ COLORREF o kilka znaczników informujących o alternatywnych sposobach przechowywania informacji o kolorze. Jednym z takich alternatywnych sposobów jest przechowywanie w zmiennej indeksów do standardowych kolorów systemowych, które mogą być wykorzystywane przez funkcję GetSysColor () do pobierania ustawień kolorów typowych dla danego komputera. Inny sposób polega na definiowaniu wartości kolorów za pomocą liczb dziesiętnych, a nie tak jak w typie COLORREF za pomocą 24-bitowej zmiennej True-Color. 700_____________________________________Poznaj Visual C++ 6 l __I Dodawanie karty właściwości definiujących kolory Aby pomóc programistom, którzy będą korzystać z zaprojektowanej przez nas kontrolki, możemy dodać do niej kartę właściwości wyposażenia definiujących kolory. Karta ta jest opatrzona identyfikatorem CLSID_CColorPropPage. Osobna karta CLSID_CCFont-PropPage definiuje właściwości czcionki kontrolki. W pliku RotaryCtri.cpp zawierającym kod klasy kontrolki znajduje się sekcja // Property Pages (karty właściwości). Przechowywana jest tam tablica kart właściwości powiązanych z kontrolka. Dodając kontrolkę do okna dialogowego za pomocą edytora zasobów programista może po kliknięciu kontrolki prawym klawiszem myszy zmieniać jej właściwości. Kliknięcie prawym klawiszem myszy wyświetli karty przechowywane we wspomnianej tablicy oraz opisane w rozdziale dziewiątym karty Generał i Ali. W omawianym przykładzie kontrolka Rotary posiada jak dotąd tylko jedną, utworzoną przez kreator CIassWizard kartę właściwości: Ryi Do BEGINJPROPPAGEIDS(CRotaryCtrl, l) PROPPAGEID(CRotaryPropPage::guid) END_PROPPAGEIDS(CRotaryCtrl) czo łaja WO! Liczba kart właściwości Należy pamiętać, aby po dodaniu nowej karty właściwości odpowiednio zmodyfikować liczbę kart właściwości, inaczej przy próbie wyświetlenia kart właściwości w edytorze zasobów kompilator Developer Studio zawiesi się. wp rac jem nao] m_t Zań prze w pi Kartę właściwości wyposażenia definiujących kolory dodajemy zaraz po pierwszej karcie (CRotaryPropPage:: guid), zwiększając odpowiednio liczbę kart właściwości w makroinstrukcji BEGIN_PROPPAGEIDS: BEGIN_PROPPAGEIDS(CRotaryCtrl, 2) PROPPAGEID(CRotaryPropPage::guid) PROPPAGEID(CLSID_CColorPropPage) END_PROPPAGEIDS(CRotaryCtrl) Jeśli w tym momencie zbudujemy kontrolkę i uruchomimy ją umieszczając za pomocą edytora zasobów w oknie dialogowym IDD_ABOUTBOX_ROTARY, kontrolka pojawi się ale całkowicie czarna, ponieważ właściwości reprezentującej kolorowi kontrolki została domyślnie przypisana wartość zero. Jeśli teraz wciśniemy Alt+Enter, aby zmienić właściwości kontrolki, na karcie Ali znajdziemy właściwość ForeColor z wyświetloną obok wartością typu OLECOLOR definiującą kolor kontrolki (rysunek 26.8). Po kliknięciu przycisku ... będziemy mogli za pomocą karty Colors wybrać odpowiadający nam kolor (rysunek 26.9). Po wybraniu nowego koloru i zamknięciu okna dialogowego Kotary Con-trol Properties kontrolka będzie wyświetlana w nowym, wybranym przez nas kolorze. Tworzenie kontrolek ActiveX 701 Kotary Control Properties d Rysunek 26.8. Karta Ali z widoczną właściwością ForeColor Dodawanie własnych właściwości Możemy dodać do kontrolki własne właściwości i osobną kartę właściwości przeznaczoną dla nich. Przykładowo, możemy naokoło kontrolki dodać kreski podziałki pozwalające użytkownikowi lepiej zorientować się w pozycji kontrolki. Odpowiednia właściwość pozwoli korzystającemu z kontrolki programiście zdefiniować liczbę kresek. Rysunek 26.9. Karta Colors okna Kotary Control Properties Za pomocą karty Automation kreatora CIassWizard dodamy dwie nowe właściwości w podobny sposób, w jaki dodawaliśmy właściwości wyposażenia. Zamiast jednak wybierać jedną ze standardowych właściwości w liście kombinowanej External Names, wpisujemy własną nazwę (TicksEnable) właściwości pozwalającej wyświetlać kreski skali naokoło pokrętła. W polu VariableName wpisana zostanie automatycznie nazwa zmiennej m_ticksEnable, a w polu Notification Function nazwa funkcji OnTicksEnableChanged. Zamiast korzystać ze zmiennej składowej, lepiej zdefiniować metody Get i Set klikając przełącznik Get/Set Methods. W ten sposób wygenerujemy nazwy odpowiednich funkcji w polach Get Function i Set Function (odpowiednio GetTicksEnable i SetTicksEnable). 702 Poznaj Visual C++ 6 W polu Type należy wybrać BOOL, aby zdefiniować zmienną właściwości jako zmienną typu logicznego. Okno Add Property przedstawione zostało na rysunku 26.10. Add Property OK. E.xt@mo! name- TIcksEnable łype (^etfunction: Setlufiction: SetTicksEnable Implementetion C Membarwiable P Gel/Setmelhods Parameler iist Rysunek 26.10. Dodawanie własnej właściwości za pomocą okna dialogowego Add Property Wciśnięcie przycisku OK doda do kontrolki właściwość i związane z nią metody. Teraz musimy dodać nową właściwość, NumTicks, definiującą liczbę kresek podziałki. Ponownie wybieramy przełącznik Get/Set Methods, a w polu Type definiującym typ zmiennej wpisujemy short. Aby dołączyć do programu kod odpowiednich funkcji Get i Set, należy wybrać odpowiednią nazwę zewnętrzną (w polu Extemal Name) i kliknąć przycisk Ędit Code (na karcie Automation kreatora CIassWizard). Metoda Set służy do zapisywania nowej wartości właściwości, a Get do jej pobierania. Kod wszystkich czterech metod przedstawiony został na listingu 26.4. Zewnętrzne nazwy właściwości Zewnętrzne nazwy właściwości są wykorzystywane w automatyzacji OLE. Przechowywane są w tablicy przeglądowej (ang. lookup tobie} interfejsu iDispatch, aby program klient (taki jak kontener kontrolki) mógł w czasie działania ustalić, które właściwości kontrolki są dostępne. Listing 26.4. LST27_4.CPP - funkcje Get/Set dla właściwości tworzonych przez nas BOOL CRotaryCtrl::GetTicksEnable() // TODO: Tutaj dodaj własny kod obsługi właściwości return m bTicks; 5 } 6 7 void CRotaryCtrl::SetTicksEnable(BOOL bNewValue) 8 ( 9 // TODO: Tutaj dodaj własny kod obsługi właściwości 10 m_bTicks = bNewValue; 11 12 SetModifiedFIagO; 13 ) 14 15 short CRotaryCtrl::GetNumTicks() 16 { 17 // TODO: Tutaj dodaj własny kod obsługi właściwości 18 19 return m sNumTicks; 20 } 21 22 void CRotaryCtrl::SetNumTicks(short nNewYalue) 23 ( 24 // TODO: Tutaj dodaj własny kod obsługi właściwości 25 26 m_sNumTicks = nNewYalue; 27 28 SetModifiedFIagO; 29 } Dwie nowe wprowadzone na listingu 26.4 zmienne składowe (m_bTicks i m_sNum-Ticks) wykorzystywane są odpowiednio do definiowania, czy kreski podziałki mają być wyświetlone i ile ich ma być. Funkcje Get/Set z linii 1-13 obsługują pierwszą, funkcje Get/Set z linii 15-29 drugą właściwość, informując nas o przechowywanych w nich wartościach lub odpowiednio je modyfikując. Należy dodać definicje odpowiednich zmiennych składowych do definicji klasy albo za pomocą okna dialogowego Add Mem-ber Yariable, albo wpisując je tam ręcznie: short m sNumTicks; BOOL m_bTicks; Następnie w funkcji konstruktora CRotaryCtrl: :RotaryCtrl () klasy CRotaryCtrl należy zdefiniować ich początkowe wartości: // TODO: Tutaj inicjuj zmienne kontrolki. m_bTicks = TRUE; m sNumTicks = 20; Jeśli teraz otworzymy w panelu ClassView hasło _DRotary, powinniśmy zobaczyć tam nazwy dwóch nowych właściwości. 704 Poznaj Yisuał C++ 6 Dodawanie kontrolek kart właściwości dla własnych właściwości Metody Get i Set umożliwiają modyfikowanie właściwości z poziomu programu, warto jednak również dodać interfejs, który pomagać będzie zmieniać właściwości kon-trolki z poziomu edytora zasobów podczas projektowania aplikacji. & Właściwości karty właściwości. Projektując kartę właściwości dla tworzonych przez nas właściwości kontrolki dodajemy kod implementujący odpowiedni interfejs użytkownika, który wykorzystywany będzie podczas projektowania kontrolki. Gdy teraz korzystający z kontrolki programista po dodaniu jej do programu wciśnie Alt+Enter, wśród kart właściwości pomiędzy zakładkami kart Generał i Stock pojawi się zakładka nowej karty pozwalającej zmieniać specyficzne dla tej kontrolki właściwości. W tym celu należy w kompilatorze Project Workspace wybrać panel ResourceYiew i odnaleźć okno dialogowe IDD_PROPPAGE_ROTARY. Do okna dialogowego dodamy kontrolki reprezentujące nowe właściwości. Właściwość TicksEnabIe będzie można zmieniać klikając pole wyboru Show Tick Marks (identyfikator IDC_TICKS_ENABLED). Natomiast liczbę kresek podziałki będzie można zmieniać za pomocą pola edycji No. of Tick Marks (identyfikator IDC_NUM_TICKS). Dwie strzałki (kontrolka Spin) korzystające ze znaczników wiążących je z polem edycji (opcje ^uto Buddy i Set Buddy Integer na karcie Styles okna dialogowego Spin Properties) ułatwiać będą zmienianie liczby kresek skali. Ten moment tworzenia karty właściwości przedstawiony został na rysunku 26.11. "ii 1~ Show Tick Marks No, of Tick Marks Edit ^a ? Generał Stylas j Extended Stylas )7 ^uto buddy 17 Set buddy integer r" No thousands F Wrap T"' Arrowkeys Rysunek 26.11. Dodawanie kontrolek do karty właściwości kontrolki ActiveX Za pomocą kreatora CIassWizard można w bardzo prosty sposób powiązać wartości definiowane w kontrolkach z odpowiednimi właściwościami. Poniższa instrukcja pokazuje jak powiązać pole wyboru Show Tick Marks z właściwością TicksEnabIe. Tworzenie kontrolek ActiveX 705 Wiązanie właściwości z kontrolkami karty właściwości za pomocą kreatora CIassWizard 1. Wybierz kontrolkę, którą chcesz powiązać z właściwością (tutaj pole wyboru Show Tick Marks w oknie dialogowym IDD_PROPPAGE_ROTARY) i wciśnij CtrI+W, aby przywołać kreatora CIassWizard, w którym należy wybrać kartę Member Variables. 2. Z listy Control IDs wybierz identyfikator IDC_TICKS_ENABLED. 3. Aby dodać przypisaną kontrolce właściwość, kliknij przycisk Add Variable. Pojawi się widoczne na rysunku 26.12 okno dialogowe Add Member Variable. 4. W polu Member Variable Name wpisz m_bTicksEnabled. 5. Na liście kombinowanej Optional Property Name wpisz TicksEnabled. 6. Aby dodać nazwę zmiennej, kliknij OK. Add Member VariablB Member vari obie name. ni bTicksEnabled Cale g o r/ Value Variable type: BOOL Optional property name: JTicksEnabled|]•] Description; simple BOOLtransfer Rysunek 26.12. Wiązanie kontrolki karty właściwości z właściwością za pomocą okna dialo- gowego Add Member Variable Te same czynności powtarzamy łącząc kontrolkę IDC_NUM_TICKS ze zmienną składową m_sNumTicks typu short (do wpisania w polu Variable Type). W przypadku tej właściwości (NumTicks) opcjonalna nazwa zmiennej wpisywana w polu Optional Property Name powinna brzmieć NumTicks. CIassWizard automatycznie utworzy odpowiedni kod, a odpowiednie zmienne pojawią się na karcie Member Variables. Możemy zdefiniować dopuszczalny zakres zmiennej całkowitej, z którego użytkownik będzie mógł definiować liczby (na przykład od l do 100). W tym celu należy na karcie Member Variables wybrać odpowiednią zmienną (tutaj m_sNomTicks). Na dole karty Automation pojawią się dwa pola edycji pozwalające zdefiniować minimalną i maksymalną wartość zmiennej. W polu Minimum Value wpisujemy l, a w polu Maximum Value wpisujemy 100 (rysunek 26.13). 706 Poznaj Visual C++ 6 Kreator CIassWizard wiąże odpowiednie właściwości z interfejsami OLE dodając specjalne odwołania do makroinstrukcji DDP w funkcji DoDataExchange () klasy CRota-ryPropPage: DDP_Check(pDX, IDC_TICKS_ENABLED, m_bTicksEnabled, _T("TicksEnable") ); DDP_Text(pDX, IDC_NUM_TICKS, m_sNumTicks, _T("NumTicks") ); Po wprowadzonych zmianach funkcje Get i Set zmieniać będą początkowe wartości właściwości kontrolki ActiveX na życzenie korzystającego z niej programisty. MessageMaps MemberYariables AutomatiOR | ActiveX Ev8n(s j Ciass irrto l ^ojecf' Ciass Darrie- AddClass,,. -' Potarv ,| CRotełyPiopPage »j ——————————— AddYotiabte... Contfol !Ds: Type Member SsiBteYariabte IDC NUMTICKS : - :: f-hntt ••'••• ••'• m^NurnTirfr'';' : : : ^^^^^^^H^^^^^^^—f (DC^SPINI IDCTICKSENABLED BOOL mbTlck8Enable d :».il: ..... . . .. ...... . ... i^^^^^''/^ '•^Ht^i^^?::"'' 3sscriptian, shortwith rangę VB||l;lBl ^ll^^l^^^^^ ^lnirriuinValue' |1 ^eximum Va[ue: |100 '^.lll:^:';:': . . • . CiK | Cancel Rysunek 26.13. Definiowanie maksymalnej i minimalnej wartości zmiennej składowej m sNumTicks Utrwalanie ustawień właściwości Kiedy zdefiniujemy właściwości kontrolki za pomocą edytora zasobów, wartości tych właściwości pozostaną niezmienione nawet, wówczas gdy wykorzystamy w programie lub przeprojektujemy kontrolkę. Właściwości te są serializowane i zachowywane dla każdego egzemplarza kontrolki, dzięki czemu właściwości zdefiniowane podczas dodawania kontrolki do okna dialogowego pozostaną niezmienione, gdy będziemy to okno dialogowe edytować następnym razem. Umożliwia to funkcja DoPropExchange () kontrolki przyzywana zawsze, gdy kon-trolka jest zachowywana lub ładowana. Serializację dodanych przez nas kontrolek umożliwiają makroinstrukcje px_ przedstawione na listingu 26.5. Tworzenie kontrolek ActiveX 707 Listing 26.5. LST27_5.CPP - utrwalanie właściwości kontrolki w funkcji DoPropExchange () za pomocą makroinstrukcji PX i l /i 111111111111111111111111 ni 11/1111 ni 1111/11 i/i mii 2 11 CRotaryCtrl::DoPropExchange - utrwalanie właściwości 3 4 void CRotaryCtrl::DoPropExchange(CPropExchange* pPX) 5 ( 6 ExchangeVersion(pPX, MAKELONG(_wVerMinor,_wVerMajor)); 7 COleControl::DoPropExchange(pPX) ; 8 9 // TODO: Dla każdej właściwości przyzwij makroinstrukcję PX 10 11 // ** Serializuj właściwość uaktywniającą kreski 12 PX_Bool(pPX,_T(„TicksEnable"),m_bTicks,TRUE); O 13 14 // ** Seriaiizuj właściwość definiująca, liczbę kresek 15 PX_Short(pPX,_T(„NumTicks"),m_sNumTicks,20) ; 16 } O Makroinstrukcje PX_ są wykorzystywane do wiązania właściwości interfejsu ze zmiennymi składowymi klasy kontrolki. W linii 12 za pomocą makroinstrukcji PX_Bool utrwalana jest właściwość m_bTicks. W linii 15 za pomocą makroinstrukcji px_short utrwalana jest właściwość m_sNum-Ticks. Pierwszy parametr przyzywanych makroinstrukcji ppx jest wskaźnikiem do obiektu CPropExchange. Drugi parametr jest nazwą właściwości w konwencji OLE. Trzeci parametr jest nazwą wewnętrznej zmiennej składowej reprezentującej właściwość. Ostatni parametr definiuje zachowywaną wartość właściwości. Odpowiednie makroinstrukcje PX_ zdefiniowane są praktycznie dla każdego typu danych, przykładowo: PX_Double, PX_Color czy PX_String. W linii 6 zachowywany jest za pomocą funkcji ExchangeVersion () numer wersji. Wypuszczając na rynek kolejną wersję kontrolki z nowymi właściwościami mamy możliwość sprawdzenia wersji kontrolki. W ten sposób będziemy musieli wymienić w kodzie kontrolki tylko te właściwości, które zostały zmienione w nowej wersji, zachowując zgodność z właściwościami stosowanymi w poprzedniej wersji kontrolki. Kod malujący kreski skali dodamy na końcu przedstawionej na listingu 26.6 funkcji OnDrawO . 708_____________________________________Poznaj Visual C++ 6 Listing 26.6. LST27_6.CPP - koniec funkcji OnDraw () klasy CRotary malujący na okoto kontrolki kreski skali 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Sprawdź, czy skala została uaktywniona if (m_bTicks) { // Powtarzaj zmieniając kąt od -2*PI do +2*PI const double dPi = 3.14185; double r = -2.0 * dPi; for(int i=0;iMoveTo(CPoint(nXPos,nYPos)) ; // Narysuj promień od tego punktu na zewnątrz nXPos = ptMid.x + (int) (cos(r) * dRadX * 1.15); nYPos = ptMid.y + (int) (sin (r) * dRadY * 1.15); pdc->LineTo(CPoint(nXPos,nYPos)); O // Zmień kąt r += dPi / (m_sNumTicks / 2.0); O Kreski skali rysowane są na zewnątrz głównego okręgu pokrętła i rozchodzą się promieniście na zewnątrz. Na listingu 26.6 najpierw w linii 2 testowana jest zmienna składowa m_bTicks, aby sprawdzić, czy kreski skali zostały aktywowane. Następnie definiowana w linii 7 pętla po- wtarzana jest tyle razy, ile ma być kresek (zmienna m_SNumTicks). W liniach 10-12 defi- niowany jest początek kreski w zależności od kata przechowywanego w zmiennej r (kąt zmieniany jest w każdym kolejnym nawrocie pętli (linia 20), począwszy od wartości -2PI, a skończywszy na 2PI (kąt definiowany jest w radianach i taki zapis odpowiada matema- tycznemu zapisowi 2n). W uniach 14-17 z tego punktu rysowana jest na zewnątrz kreska skali, co w efekcie po wykonaniu pętli do końca daje otaczające kontrolkę kreski podziałki. PATRZ TAKŻE ^ Więcej na temat rysowania linii i okręgów można znaleźć w rozdziale 16. * O automatyzacji OLE pisaliśmy w rozdziale 25. Tworzenie kontrolek ActiveX 709 Kompilowanie i rejestrowanie kontrolki Pliki tworzone i wykorzystywane w procesie kompilacji kontrolki ActiveX różnią się nieco od plików aplikacji bazującej na oknie dialogowym lub aplikacji SDI czy MDI, głównie z powodu interfejsu COM/OLE. Kod kontrolki generowany jest w pliku .ocx, który różni się od pliku .dli tylko i wyłącznie nazwą. Tak więc kod źródłowy zawiera typowe dla pliku .dli funkcje rejestracji i pliki definicji. Możemy umieścić kilka sprzedawanych kontrolek w jednym pliku .dli, co znacznie ułatwi ich dystrybucję. Mimo iż budowanie kontrolki automatycznie obsługuje proces kompilacji, dobrze wiedzieć, jakie pliki są w tym momencie wykorzystywane, a które tworzone, abyśmy zdawali sobie sprawę z tego, które z nich powinniśmy rozprowadzać sprzedając kontrolkę. Różne pliki źródłowe W panelu FileView kompilatora Project Workspace w folderze Source Files projektu kontrolki Rotary znaleźć można następujące pliki: • Rotary.cpp. Plik ten przechowuje numer wersji i globalny identyfikator kontrolki. W pliku tym znajduje się również implementacja funkcji DllRegisterServer (), Dll-UnregisterServer(), Initinstance() i Exitlnstance() niezbędnych do rejestracji i uruchomienia kontrolki. • Rotary.def. Ten plik przechowuje definicje funkcji Export biblioteki DLL. • Rotary.odl. Ten plik przechowuje hasła języka ODL definiujące interfejs COM/OLE wykorzystywany przez kontrolkę, służące do tworzenia pliku biblioteki typów umożliwiającego korzystanie z kontrolki aplikacjom napisanym w innych językach. • Rotary .rc. Plik zasobów przechowujący karty zdefiniowanych przez nas właściwości i inne zasoby projektu. • RotaryCtl.cpp. Plik źródłowy języka C" zawierający kod kontrolki. • RotaryPpg.cpp. Plik źródłowy języka C^ przechowujący kod kart właściwości kontrolki. Rejestrowanie kontrolki w czasie kompilacji Odbywająca się przy okazji kompilacji rejestracja kontrolki za pomocą programu regsrvr32.exe zapisuje w Rejestrze komputera identyfikatory CLSID niezbędne dla tworzonej kontrolki. Krok ten jest standardowo dołączany do kompilacji, ponieważ najczęściej po zbudowaniu kontrolki będziemy chcieli uruchomić ją na naszym komputerze, aby wykonać niezbędne testy. Należy również pamiętać, że podobna rejestracja powinna się odbyć na komputerze końcowego użytkownika kontrolki i dlatego powinna być załączona do skryptu instalacji/konfiguracji (ang. instalationisetup script). 710_____________________________________Poznaj Visual C++ 6 Pierwszy krok kompilacji wykorzystuje kompilator MIDL (Microsoft Interface Defini-tion Language) do kompilacji pliku .odl. Jak wcześniej pisaliśmy, w efekcie tej operacji powstają definicje interfejsu i odpowiednia biblioteka typów. Następnie wykonywana jest normalna kompilacja pliku źródłowego. Na koniec uruchamiany jest program regsrvr32.exe tworzący odpowiednie hasła dla kontrolki i jej interfejsu w Rejestrze systemu. Tworzenie biblioteki typów i plików licencji Po kompilacji w katalogu Rotary/Debug pojawi się plik Rotary.tłb. Ten wygenerowany przez kompilator plik wykorzystywany będzie do tworzenia klas sterownika przesyłania i interfejsu w momencie, gdy kontrolka zostanie włączona do projektu aplikacji. Sprzedając kontrolkę powinniśmy rozprowadzać plik .tlb razem z przechowującym kod kontrolki plikiem .ocx. Możemy tutaj również załączyć pliki definicji biblioteki DLL .lib i .exp oraz plik .lic, jeśli zdecydujemy się na licencjonowanie kontrolki. Licencjonowanie kontrolek ActiveX Kiedy posiadający naszą licencję producent oprogramowania dodaje zaprojektowaną przez nas kontrolkę do swojej aplikacji, generowany jest specjalny numer licencyjny, wbudowywany następnie w kod kontrolki i przechowywany razem z programem aplikacji. Aby wygenerować ten numer licencyjny (ang. license key) i związane z nim komunikaty o autorach programu, producent oprogramowania musi posiadać odpowiedni plik licencji .lic. Po wygenerowaniu numeru licencji producent oprogramowania może rozprowadzać aplikację wśród klientów. Kiedy końcowy użytkownik uruchamia aplikację, kontener kontrolki musi przywołać funkcję Ceateinstance-Lic () tworzącą egzemplarz licencji. Aplikacja wysyła swój własny numer licencji, który porównywany jest z numerem licencji kontrolki. Jeśli oba numery się zgadzają, aplikacja będzie działać normalnie. PATRZ TAKŻE • Więcej informacji na temat bibliotek typów można znaleźć w rozdziale 25. Rejestrowanie kontrolki Sprzedając kontrolkę musimy wysłać nabywcom jedynie plik .ocx i program regsvr32.exe, który można swobodnie rozprowadzać za zgodą Microsoftu. Następnie należy zarejestrować kontrolkę na komputerze użytkownika uruchamiając program regsvr32.exe (w tym przypadku rejestrujemy kontrolkę Rotary): regsvr32.exe /s Rotary.ocx Tworzenie kontrolek ActiveX________________________________711 Opcja / s specyfikuje, że operacja ma być wykonana w tle. Bez tej opcji na ekranie pojawiłoby się okno komunikatu informujące, że operacja została zakończona sukcesem. Program testujący ActiveX Control Test Container Mimo iż możemy w dość prosty sposób przetestować zaprojektowaną kontrolkę za pomocą okna About, znacznie lepszym rozwiązaniem jest skorzystanie z załączonego do kompilatora programu ActiveX Control Test Container. Program oferuje dość spore możliwości testowania, pozwala między innymi zdefiniować wartości wszystkich właściwości związanych z kontrolką, umożliwia też śledzenie zdarzeń kontrolki i przetestowanie interfejsu użytkownika kart właściwości. Program ActiveX Control Test Container przywołujemy klikając menu Tools i wybierając w nim opcję ActiveX Control Test Container. Wybieranie testowanej kontrolki Kontrolkę ActiveX dodajemy do testowego kontenera klikając w menu Ędit polecenie Insert OLE Control albo po prostu kilkając pierwszy przycisk paska narzędziowego programu. W tym momencie wyświetlone zostanie okno dialogowe OLE Control, w którym będziemy mogli wybrać kontrolkę przeznaczoną do testowania. Ze znajdującej się tam listy wybieramy kontrolkę Kotary i klikamy OK. Problemy z odmalowywaniem kontrolki Program ActiveX Control Test Container nie wykonuje żadnych operacji przycinania obrazu (ang. clipping operations) wykonywanych przez niektóre kontenery kontrolek, które naprawiałyby ewentualne błędy w wyświetlaniu kontrolki. Dzięki temu możemy łatwo stwierdzić, kiedy kontrolką zachowuje się niewłaściwie i przykładowo maluje się częściowo na zewnątrz kontenera. Powinniśmy usunąć wszelkie błędy, które zobaczymy w tym momencie, szczególnie jeśli zamierzamy korzystać z opcji Unclipped Device Context, dostępnej w kreatorze pod przyciskiem Advanced. W tym momencie kontrolką zostanie wyświetlona w kolorze czarnym (z powodu domyślnego przypisania właściwości ForeColor wartości zero). Możemy teraz dowolnie zmieniać rozmiary i położenie kontrolki wewnątrz widoku kontenera. Testowanie właściwości kontrolki Jeśli po dodaniu kontrolki do kontenera klikniemy menu Ędit, zobaczymy, że pojawiła się tam nowa opcja: Properties-.Rotary Control Object. Polecenie to pozwala wyświetlać karty właściwości kontrolki. Możemy w tym momencie obejrzeć i zmienić do- 712 Poznaj Visual C++ 6 wolną ze zdefiniowanych wcześniej właściwości. W ten sposób będziemy mogli przetestować różne ustawienia właściwości, jak to widać na rysunku 26.14. Możemy również za pomocą lewego klawisza myszy obracać pokrętło, sprawdzając jak kontrolka będzie działać wewnątrz rzeczywistej aplikacji. Rysunek 26.14. Testowanie właściwości kontrolki Rotary w oknie programu ActiveX Control Test Container Testowanie właściwości otoczenia Możemy również definiować właściwości otoczenia kontrolki wybierając w menu Edit polecenie Set Ambient Properties. Wywołamy w ten sposób przedstawione na rysunku 26.15 okno dialogowe Ambient Properties. Z listy kombinowanej wybieramy właściwość otoczenia, którą chcemy zmienić. Następnie za pomocą kontrolek z prawej karty okna możemy zmienić ustawienia właściwości. Jeśli przykładowo, wybierzemy występującą w kontrolce Rotary właściwość BackColor, a następnie wciśniemy przycisk doose, wyświetlona zostanie lista kolorów, z której będziemy mogli wybrać kolor nam odpowiadający. Po zdefiniowaniu właściwości zamykamy okno przyciskiem CIose. Kontrolka powinna mieć teraz nowy kolor tła (aby to zobaczyć, będziemy musieli prawdopodobnie umieścić nad testowym kontenerem inne okno, a potem usunąć je, by w ten sposób wymusić odmalowanie kontrolki). Tworzenie kontrolek ActiveX 713 iisi—r "^P^ Close ^ . •Ifln-Standnrd Property; Color - YaluB . . . . • ^•"e6! ! ł~ No Property : l C "' •i:.-:1 C h:l-i- ' . i ' \ r ,::'•:;:l!••-„i,•, ' \ J ^...„^r--^,-1,1.1--^ , J; !<~——''^ j LMSGMAP const CErrorProneVi8w:;messag8Map ( M- CErroiProneVi8w::OnBeginPrinting(dBss COC"stfuct CPrintInto "5 .;] Definitions: ^ E'\LlsinaVC6\ChBD30\Listings30\ErrorPronB\E]TorPronBViBw.h(12) [ ReferencBe: —I Ę:\UsinqVC6\ChapM\Usfngs30VĘffor!^pne\ĘrrorPfoneyiew,c^^^ ^ Rysunek 27.5. Klasy pochodne klasy bazowej oraz dostępne zmienne i funkcje składowe • Derived Ciasses and Members. Prawie tak samo użyteczna opcja pozwalająca wyświetlić wszystkie klasy wywodzące się z danej klasy oraz ich własne zmienne i funkcje składowe. Możemy również wykorzystać tę opcję do przeglądania klas MFC, tak jak to widać na rysunku 27.6, gdzie pokazane zostały klasy pochodne klasy cwnd. 722 Poznaj Visual C++ 6 -ia( f) Functions: A Ali Ą Data: A Ali »j •"—"l """""' . . . -——l . . —^J .-. U CWnd .t •|J CAnimoteOrI f S 01 CButton !? '• U CBitmapButton l CJ CComboBox Da CConfrolBeir l l- il|CDJBloqBBr| Public: f CDialociBar:CDialooBarfvoidl K. f 1^: CDialogBar:~CDialogBar(void) : f V CDialogBar:CBlcFixedLayout(intint) d S struct CRuntimeClass const COialogBar::dassCDialagBar f CDialogBar::Create(dassO»Vnd*unsignedinlunsign8dinlun' f COialogBBr::CłeBte(dBssCWnd''.charconst".unsignedinluns f 1^ CDialogBar::GetRunlimeClass(void) d CDialogBaf.:msizeDefault ^i l i| CStatusBer i '••••L.J CToolBar S 0 COialog " i (] CEdit SB :] CFrameWnd ^J Oelinilions: il«ij C;\Proaram Files\DevStudio\VC\MFC\indude\atGetAt(i) ; pdwNumbers->SetAt(i, pdwNumbers->GetAt(i+1)) ; pdwNumbers->SetAt(i+l,uVal) ; } void DoSort() { CUIntArray arNumbers; for(int i=0;i<10;i++) arNumbers.Add(1+rand()%100) TRACĘ("Before Sort\n"); @ for(i=0;i arNumbers[i+1]) ® 24 ( 25 Swap(&arNumbers,i); 26 • bSorted = FALSE; 27 } 28 } 29 } while(!bSorted); 30 31 TRACĘ("After Sort\n"; O 32 for(i=0;i=0 && i<10); W ten sposób upewnimy się, że wskaźnik do tablicy nie ma wartości zero i że pozycja elementu mieści się w przedziale od O do 9. Jeśli któryś z parametrów nie spełnia tego warunku, wyświetlane jest okno dialogowe Debug Assertion Failed. W ten sposób możemy wyławiać błędy spowodowane przesłaniem do funkcji złych parametrów. Sprawdzanie parametrów funkcji za pomocą makroinstrukcji ASSERT jest bardzo dobrym nawykiem. Inna makroinstrukcja, ASSERT_VALID, wykorzystywana jest do testowania klas wywodzących się z klasy cobject (czyli większości klas MFC). Makroinstrukcja ta testuje obiekt i jego zawartość, sprawdzając jego legalność. Makroinstrukcji przesyłamy wskaźnik do obiektu, który ma zostać sprawdzony. ASSERT_VALID(pdwNumbers) ; Kolejna makroinstrukcja, ASSERT_KINDOF, służy do sprawdzania klasy obiektów wywodzących się z klasy cobject. Przykładowo, możemy sprawdzić, czy wskaźnik do obiektu widoku należy do właściwej klasy widoku wpisując: ASSERT_KINDOF(CYourSpecialView,pYView) ; Okno dialogowe Assertion Failed jest wyświetlane, kiedy testowany obiekt nie należy do podanego typu lub któregoś z jego typów pochodnych. Korzystanie ze zintegrowanego debugera 729 Inne makroinstrukcje ASSERT Niektóre inne rzadziej wykorzystywane asercje, to ASSERT_POINTER wymagająca przesłania jej jako parametrów wskaźnika i odpowiedniego typu obiektu przechowującego dane. Makroinstrukcja sprawdza następnie, czy wskaźnik nie ma wartości NULL, czy obiekt zajmuje legalny adres pamięci i czy obszar pamięci zajmowany przez obiekt jest legalny dla całego rozmiaru obiektu. Podobna do niej makroinstrukcja ASSERT_NULL_OR_POINTER akceptuje wskaźnik równy NULL, ale nie obiekty, które zajmują adres pamięci nielegalny dla danego procesu. Należy uważać, aby w żadnym wypadku nie umieszczać w makroinstrukcjach ASSERT fragmentów kodu niezbędnych do działania programu, są one bowiem w trybie Relase •usuwane z wersji przeznaczonej dla klienta. Typowy błąd generowany w ten sposób wygląda tak: int a = 0; ASSERT(++a > 0) ; if (a>9) MyFunc (); Błędy takie są dość trudne do wyśledzenia. Powyższy fragment kodu zwiększa wartość liczby całkowitej a w linii z makroinstrukcją ASSERT. Następnie jeśli parametr a jest większy od zera, przyzwana zostaje funkcja MyFunc (). W trakcie debugowania nie pojawią się żadne błędy. Jeśli jednak skompilujemy program w trybie Relase i uruchomimy go ponownie, okaże się, że program w tym momencie padnie, ponieważ operacja ++a jest wykonywana wewnątrz usuwanej w trybie Relase makroinstrukcji ASSERT. Problem pozwala rozwiązać makroinstrukcja VERIFY. Makroinstrukcja VERIFY działa tak samo jak makroinstrukcja ASSERT wyświetlając okno Debug Assertion Failed, jeśli zdefiniowany w nim warunek okaże się nieprawdziwy. W trybie Relase warunek jest nadal testowany, ale okno z komunikatem o błędzie nie jest już wyświetlane. Dlatego makroinstrukcje VERIFY należy stosować w odniesieniu do wyrażeń warunkowych, które muszą być wykonane w programie, a makroinstrukcje ASSERT, kiedy wyrażenie warunkowe jest nam potrzebne tylko w czasie debugowania programu. Jeśli więc zastąpimy makroinstrukcję ASSERT makroinstrukcją VERIFY, program będzie działał prawidłowo również po skompilowaniu w trybie Relase: VERIFY(++a > 0) ; Znacznie częściej jednak makroinstrukcja VERIFY wykorzystywana jest do testowania kodów zwracanych przez funkcje: VERIFY(MyFunc () !=FALSE); 730 Poznaj Visual C++ 6 Punkty kontrolne i przeglądanie kodu programu krok po kroku Jedną z najbardziej użytecznych technik odszukiwania błędów w programie jest ko- rzystanie z punktów kontrolnych (ang. breakpoints) i za ich pomocą przeglądanie kodu krok po kroku (ang. single stepping) tak jak będzie on wykonywany. Liczba różnych rodzajów punktów kontrolnych i dostępnych związanych z nimi informacji, które możemy wydobyć z kodu programu przeglądając go krok po kroku, jest w C++ bardzo duża. Tutaj pokażemy tylko niektóre możliwości tej techniki. Kluczowym narzędziem służącym do śledzenia wykonywanego kodu są punkty kon- trolne. Punkty kontrolne umieszczamy w dowolnych punktach programu, a następnie uruchamiamy program w debugerze. Kiedy program dotrze do miejsca oznaczonego punktem kontrolnym, w oknie edytora wyświetlany jest fragment kodu oznaczony punktem kontrolnym. Możemy teraz przeglądać dalej kod krok po kroku lub wznowić wykonywanie programu. Punkt kontrolny dodajemy do programu klikając odpowiednią linię kodu (klikając kursor edytora w odpowiedniej linii kodu w oknie edytora), a następnie wciskając ikonę punktu kontrolnego (...) na pasku Build MiniBar (rysunek 27.8) lub wciskając klawisz F9. Bardziej skomplikowane punkty kontrolne można dodawać lub usuwać za pomocą polecenia Breakpoints menu Ędit, które wywołuje widoczne na rysunku 27.9 okno dialogowe Breakpoints. Dodany do kodu punkt kontrolny obrazowany jest małym czerwonym kółkiem w linii, w której został umieszczony. Punkty kontrolne muszą być umieszczane w liniach kodu tak jak są one postrzegane przez debuger, dlatego kompilator przesuwa czasem ustawiane przez nas punkty kontrolne. 2 4 Rysunek 27.8. Dodawanie do programu punktów kontrolnych za pomocą paska narzędziowego Build MiniBar 1. Compile (Kompiluj); Ctri+F [! F6?] 2. Build (Buduj); F7 3. Stop Build (Przerwij budowanie); Ctri+Break 4. Go (Uruchom); F5 5. Insert/Remove Breakpoint (Dodaj/Usuń punkt kontrolny); F9 Korzystanie ze zintegrowanego debugera 731 Sreakpoints •1:11::1111;? :l^:::-,,.:l:,,jl:lil:::::^l••, ^^'^s1;;/'?;!,!,!:::,,:'1"';!!"- "'^.^^ • Location Data Messages OK 'Breakat Cancel |ap30\Usfings30\ErrorFrone\EfiorProneDoc.cpp,}.69 > Edit Code Condition • Clickthe Condition button i(you paramelefsforyourbreakpoint. Sreakpoints: Bemoue p;emove Ali Rysunek 27.9. Dodawanie punktów kontrolnych za pomocą okna dialogowego Breakpoints Możemy włączać i wyłączać punkty kontrolne wciskając przycisk z ikoną dłoni lub usuwać je wybierając w oknie dialogowym Breakpoints przycisk Remove lub Remove Ali. Możemy również pozostawić punkty kontrolne, ale uczynić je nieaktywnymi klikając znak zaznaczenia po lewej stronie punktu kontrolnego wymienionego w liście u dołu okna Breakpoints. Drugie kliknięcie ponownie uaktywni punkt kontrolny. Po rozmieszczeniu w programie punktów kontrolnych możemy uruchomić go w de-bugerze, klikając po kolei Build, Start Debug i Go. Możemy również skorzystać ze skrótu klikając ikonę Go (...) na pasku narzędziowym Build Minibar (przedstawionym na rysunku 27.8) lub wciskając przycisk F5. Program będzie wykonywany normalnie, dopóki nie dotrze do punktu kontrolnego. W tym momencie program zatrzyma się wyświetlając strzałkę w linii z punktem kontrolnym. Możemy teraz przyzwać pasek narzędziowy Debug (rysunek 27.10) i rozpocząć przeglądanie kodu programu krok po kroku. a CUIntArray arHiutbers: !:or arHumbers[i+l ]) {T J- Swap (ŁarlIuDłbers. i ) ; 01 MM bSorted • FAISE; Rysunek 27.10. Debuger zatrzymał program w miejscu oznaczonym punktem kontrolnym wyświetlając pasek narzędziowy Debug 732 Poznaj Visual C++ 6 Gdy program jest już zatrzymany w debugerze, możemy oglądać zawartość większości zmiennych po prostu kierując na nie kursor w oknie edytora. Ich zawartość wyświetlana jest na etykietce ToolTip w miejscu, w którym znajduje się kursor. Bardziej szczegółowe informacje można uzyskać przeciągając zmienną do okna Watch, które zostanie opisane dokładniej za chwilę. Opóźnienia przy wykonywaniu niektórych poleceń Niektóre z operacji przeglądania programu krok po kroku, takie jak Step Out czy Run to Cursor, wykonywane są z pewnym opóźnieniem widocznym szczególnie, gdy kompilator musi po drodze wykonać wielokrotną pętlę. Jest to efektem tego, że debuger musi zatrzymywać się po każdej wykonanej instrukcji, aby sprawdzić, czy został już osiągnięty punkt zdefiniowany przez programistę. Przeglądając program krok po kroku poszczególne posunięcia wykonujemy klikając jeden z czterech przycisków z ikonami nawiasów paska narzędziowego Debug: (...) lub klikając menu Debug i wybierając polecenie o odpowiedniej nazwie. Dostępne polecenia przedstawione zostały w tabeli 27.4. Tabela 27.4. Polecenia umożliwiające przeglądanie programu krok po kroku Ikona/Polecenie Step Into (Wejdź do) 1^" Step0ver (Przejdź ponad) 1^ Step Out (Wyjdź z) I11 Run to Cursor (Idź do kursora) 1^- Go (Wznów) Klawisz skrótu Efekt polecenia Fll Debuger wykona bieżącą linię i jeśli kursor znajduje się nad odwołaniem do funkcji, wejdzie do kodu tej funkcji F10 Działa podobnie jak poprzednie polecenie z tym że wykonuje zaznaczoną kursorem funkcję i zatrzymuje program dopiero po jej wykonaniu Shift+Fll Debuger wykona do końca funkcję, w której się znajdujemy, wychodząc do funkcji ją przywołującej Ctri+FlO Debuger będzie wykonywał program, dopóki nie trafi do miejsca zdefiniowanego pozycją kursora. Pozycje ustalamy po prostu klikając linię, do której chcemy dotrzeć F5 Program jest wykonywany aż do osiągnięcia następnego punktu kontrolnego Korzystanie ze zintegrowanego debugera 733 Shift+FS Przerywamy debugowanie i wracamy do trybu ': Stop Debugging umożliwiającego edycję kodu (Przerwij debugowanie) i'^l CtrI+Shift+FS To polecenie uruchamia program od początku l ' : Restart (Od początku) To polecenie przerywa wykonywanie programu l" Break Execution (Przerwij) l"/ Alt+FlO To polecenie pozwala skompilować kod pro-1 Appły Code gramu po wprowadzeniu niezbędnych popra-Changes ^,g^; kontynuować debugowanie od miejsca, (Wprowadź zmiany) w którym przerwaliśmy Za pomocą tych poleceń możemy oglądać, w jaki sposób program jest wykonywany i jak zmienia się wartość wykorzystywanych w nim zmiennych. Żółte strzałki w oknie Editor pokazują następną wykonywaną instrukcję. W dalszej części rozdziału opiszemy niektóre z okien wykorzystywanych w debuge-rze do obserwowania kodu programu. Edit and Continue Nowym bardzo wygodnym narzędziem wprowadzonym w Visual C++ 6.0 jest Edit and Continue. Narzędzie to ta pozwala zmieniać lub edytować kod programu w miejscu, w którym zatrzymaliśmy go w debugerze. Po dokonaniu niezbędnej edycji uaktywni się polecenie Appl^ Code Changes z menu Debug (i odpowiadająca mu ikona paska narzę- l"^ dziowego l ). Możemy teraz wybrać to polecenie (lub kliknąć przycisk paska narzędzi) i skompilować wprowadzone zmiany, a następnie kontynuować proces debugowania. Dzięki temu możemy naprawiać błędy w kodzie programu na bieżąco w trakcie debugowania i kontynuować proces debugowania z tymi samymi wartościami zmiennych co przed rozpoczęciem edycji. Jest to szczególnie wygodne przy usuwaniu błędów ze skom- plikowanych, rozbudowanych programów. Śledzenie zmiennych programu Okna Watch i Yariables przedstawione zostały na rysunku 27.11. Okna te służą do wy- świetlania wartości zmiennych w momencie, gdy wykonywanie programu zostanie zatrzymane w debugerze. Okna te możemy wywołać kukając w menu yiew polecenie Debug r"'"" Windows lub na pasku narzędziowym wciskając odpowiadające im ikony l 734 Poznaj Visual C++ 6 • Mtoinoll Vlii«il C<ł Itaeat] • lEilolFmneDoc.cpp] l Fł. yi V»in Insart P.opcI fiebug Imli WirAm Hett ^ESBa ; EfeK „ .; • 'ciaafi^P -3r ~3 « (g S-"' ! "si :, !:)— ^-^----^-^-^ai .. ,^1————,,„^1«^-1 „..„„„ „. , d^fca ; TRACE("Before Sorf-n"); ^Oi^i^O; i yuprpi) ^o 1 J *-1100 El D 55) Cl • « :. ^ „„rf [-; * e CE rtCTProfleApp ^j : »CEnoFloneAH: : • » InHIn.-tónctl) ; «Ot>ł|l[All»Jt(] : B- ^CEnotPloneDoc .•:::' ; •• • ^AKtrtVa6dO, ; : Ą ~CEritifProfieD. • «DuniriCDitBpC ! • •• ^ Or^ewOociiffit : ft SetiakelCArch - * f1 CE[TnP[weView , «i r--"-'^ li (arllu«bers[il > arMutbers [ l+l ] ) 0 Svap(&arHunbers. i). bSorted • FAŁSE; i } •Silll!.(lhSorted): TRACECtfter Sort-o")' hU ':..';- ,".'• " ' " " ":;:':., ^,:,,„,/„..1,..,. ^,,:.„,, " •i OK.. | ^ Rtt.. J]F»tV... ^ Name lYalue :^??^M^.t:|?: j- -^l Csriexl:j[:ioSo[10 d- BarKuitbers '' ' : {OTłntirray} ' ' i, "1-B1 CObiect """ '": {CObject} '"" ••"-'--• • •jj ^ HB «pD«ta" ' '" "7°»lil>77i "'f J • H<52:23> B 4 4 B . > N S! n 000002 0 000007 a 000009 AB'" Bun B4 •••• Butt 64 "" Buttc on ——————————— ————————' on Tolai Pl»y 52 23 m:s Track 03:50 m;s in a 00000544 "|-]<52:23>" SJELEDCIass i n 00000104 "ŁArtIst:" Static ; 13000002 A4""ComboBox n OOOOOBBO "TItle:" Static \ \ [30000091 B "New T tle" SJETextClass ; -n 00000400 "Traci, k:" Static ^ l n ooooo E 24""ComboBox i : nOOOOOBE4""ToolbarWlnclow32 n OOOOO F <| W^ BO "" msctlsstatusbar32 ^ Ł iŃUM | „ PorHelp. press F1 ^11^•^:::::.•!::::::::^:. .:^::1::::1it^i:ffi!ll^:^;^•.:l^•l';::^..::• ,1::1•::• Rysunek 27.12. Program Spy++ wyświetlający listę okien programu CD Player W menu Spy programu można znaleźć następujące polecenia: • Messages. Jest to jedna z najbardziej przydatnych opcji, umożliwiająca obserwację komunikatów wysyłanych do określonego okna (w tym do okien naszej aplikacji). Dzięki niej możemy również filtrować komunikaty tak, aby nie dochodziły do nas komunikaty informujące na przykład o poruszeniach myszy. Aby móc obserwować komunikaty, należy za pomocą polecenia Messages przywołać pokazane na rysunku 27.13 okno dialogowe Message Options. Przeciągając narzędzie Finder Tool nad dowolnym działającym w systemie oknem możemy teraz wyświetlać informacje na jego temat. Program Spy++ podświetla również wybrane okna, dzięki czemu możemy rozróżnić okno obramowujące od jego okien klientów. Inne strony okna dialogowego pozwalają definiować opcje filtrujące i opcje regulujące sposób wyświetlania informacji. Aby zamknąć okno dialogowe Message Options klikamy OK. Na rysunku 27.14, możemy zobaczyć komunikaty generowane przez pasek narzędziowy prostej aplikacji SDI. Jak widać, bez włączonych opcji filtrowania otrzymywać będziemy masę komunikatów informujących o poruszeniach myszy i sprawdzających status kursora. Możemy jednak również zauważyć znajomy komunikat WM_LBUTTONUP razem z odpowiednimi parametrami definiującymi pozycję, na której zaszło zdarzenie. 738 Poznaj Visual C++ 6 Message Oplions ToolbaiWindow32 5W004E (286.87)-(502.119)2)6x32 FFFC90DB FFFAODOB r Ali Windows in System Windows Messages j Outpu) WindowFindef Tool 1 Saiacted Objęci Window; 0000097C Takt Drąg the FinderTool overa windowto select (hen releass the mous@ but!on. CIass; Style; Rect-Thread ID: Process ID: FinderTool' |__| p !HideTŚpy»'ti | ;•• AdditionalWindows • r" Parent f~ Windowa Ol Same JN'ead r Oliidren 1" Windows of Same Process r Save Settings as Defaull OK Cancsl Hełp Rysunek 27.13. Okno Message Options programu Spy++ wykorzystywane do definiowania opcji śledzenia komunikatów okna "• Microsolt Sny" - [Messages (Window 0000097C)] 3l Spy I'6® Sfiarch yiew Massages 'ffindow Uelp n|%|e| ^ ^ij 8Mx| ł4_ 0000097C P WM_MOUSEMOVEfwKeys:MK_LBUTTONxPos:l95yPos:31 0000097CPWM_MOUSEMOVEfwKeys:MK_LBUTTONxPos:196yPos:32 0000097C P WM_MOUSEMOVE fwKeys:MK_LBUTTON xPos:196 yPos:33 0000097C P WM_TIMERwTimerlD:57345tmprc:00000000 0000097C P WM_TIMERwTimerlD:57345tmprc:00000000 0000097C P WM_TIMER wTimerlD:57345 tmprc:00000000 0000097C P WM_LBUTTONUPfwKeys:OOOOxPos:196 yPos:33 0000097C S WM_CAPTURECHANGED hwndNewCapture:00000000 0000097C R WM_CAPTURECHANGED For Hełp, press F1 Rysunek 27.14. Lista komunikatów dla paska narzędziowego w oknie programu Spy++ Windows. Okno przedstawiające przegląd okien dla pulpitu Windows przedstawione zostało na rysunku 27.12. Dwukrotne kliknięcie któregokolwiek z okien wyświetli arkusz właściwości zawierających informacje o położeniu okna i ustawienia wszystkich jego znaczników. Jeśli chcemy uaktualnić te informacje, musimy w menu Windows wcisnąć polecenie Refresh. Processes. Ta opcja pozwala nam obejrzeć listę aktualnie działających programów (czyli wykonywanych przez system procesów). Procesy te można otwierać, aby oglądać wątek (ang. thread) każdego z nich i wszystkie okna powiązane z danym wątkiem. T^hreads. Ta opcja oferuje te same informacje co poprzednia, nie podaje jednak poziomu procesu w hierarchii. Dzięki czemu na jednej liście możemy obejrzeć wątki wszystkich procesów działające w danym momencie na komputerze i wszystkie przynależne im okna. Korzystanie ze zintegrowanego debugera 739 Program Spy++ jest zbyt skomplikowany, aby opisać go tutaj dokładnie, jednak jako narzędzie ułatwiające zrozumienie struktury różnych hierarchii systemu Windows i wysy- łanych przez system komunikatów jest nieoceniony. Za jego pomocą możemy dowiedzieć się bardzo wiele po prostu podglądając działanie komercyjnej aplikacji. Program ten jest również bardzo praktycznym narzędziem pomagającym rozwiązywać pojawiające się podczas debugowania problemy z komunikatami. Pomaga również dbać o to, aby okna aplikacji otrzymywały właściwe komunikaty w odpowiedniej kolejności. PATRZ TAKŻE • Więcej informacji na temat komunikatów Windows znaleźć można w rozdziale 4. Process Viewer Więcej informacji na temat wykonywanych w systemie procesów możemy uzyskać uruchamiając program Process Viewer (PView95.exe). Aplikację tę można uruchomić za pośrednictwem przycisku Start klikając po kolei Programs (Programy) i Microsoft Visu-al Studio 6.0 Tools. Process Viewer podaje listę procesów działających w tym momencie w komputerze i pozwala sortować je przez klikanie nagłówków odpowiednich kolumn listy. Możemy również wyświetlić wszystkie wątki procesu klikając po prostu odpowiedni proces. Rysunek 27.15 przedstawia program Process Viewer razem z aplikacją Developer Studio (MSDEV.EXE) wyświetlający listę wszystkich jej procesów. Fite Frocess aixl Proces s ewerA pplii IBfl fflH •sSimi-.-^.wi—iSiSs-f- ^s^t |Typ9 ^::!J:::::^:'-^-^^^^^ l PVIEW35, E>E WNWORD E„. ERRORP RO.. Ifteiłła^ga CDPLAYE R.E,. FFFBB 553 FFFD2 617 FFFAO D... FFFA4 717 FFF81 94B Q (Normal) 1 8 (Normal) '.i 8 (Norniol) 1 8 (Normol) 7 B (Norma)) 1 32-Bll 3;-Bil 32-Bil 32-fiil 32-Bit C\PROGRAUFILES\DEVSTU DIO\V D•\MICROSOF\OFFICE',WIN WOf:^D E... E;\USINGVC6\CHAP30\USTI NGS30\... C;\PROGRAM FILES\DEVSTUDIO\S... C;\WINDCWS\COPIAYER.EX E ^ TID | OWBingRID :.": l Thfsńid F^iori^lj:^®!!!^® 1'^h,:" " g,,!^:';:!^^!^!^^!::!!!!;^,!^ iil^^ FFFC974] FFFAF47F FFFBF09 B FFFB10D 3 :FFFAAS) F FFFW3S7 FFFD7AE 3 FFFA4 717 FFFA4 747 FFFA1 747 FFFA4 747 FFFA4 747 FFFA4 7-17 FFFA4 747 8 (Normal) 7 (Below Norma!) 9 (Above Normal) 8 (Normal) 7 (Balów Normol) 6 (NormaO 8 (Norma!) Rysunek 27.15. Program Process Viewer pokazujący wszystkie wątki procesów aplikacji MSDEV.EXE OLE/COM Object Viewer Program OLE/COM Object Viewer wyświetla listę wszystkich zarejestrowanych w systemie obiektów OLE/COM, włączając w to kontrolki ActiveX, biblioteki typów, osadzone obiekty, obiekty automatyzacji i wiele innych. Możemy również nawet tworzyć egzemplarze zarejestrowanych obiektów i oglądać ich interfejsy. Program OLE/COM Object Viewer jest szczególnie użyteczny, gdy two- 740 Poznaj Visual C++ 6 rżymy aplikację OLE/COM lub szukamy trudnej do wyśledzenia wśród innych elementów kontrolki ActiveX. PATRZ RÓWNIEŻ • Jak wykorzystywać w aplikacjach kontrolki ActiveX pisaliśmy w rozdziale 9. • Jak tworzyć kontrolki ActiveX pisaliśmy w rozdziale 26. • Więcej informacji na temat OLE i COM znaleźć można w rozdziale 25. MFC Tracer Za pomocą przedstawionego na rysunku 27.16 programu MFC Tracer możemy zatrzymać normalne śledzenie wykonywanego programu lub dodać do wtedy wyświetlanych informacji informacje uzyskane z systemu Windows. Główne okno programu zawiera listę opcji, które możemy zaznaczyć, aby uzyskać dodatkowe informacje. Możemy między innymi załączyć informacje na temat komunikatów Windows, ko- munikatów baz danych, komunikatów OLE i innych informacji przydatnych do odnajdywania trudnych do wyśledzenia błędów. Komunikaty te generowane są przez kod MFC w zależności od wybranych znaczników. Możemy też wyłączyć standardowe informacje generowane przez naszą aplikację, wyłączając opcję Enable Tracing. MFC Tracę Options OK Cancel Tracę output will ba ayailabla oniywhiteusingthe debugger i7 ^nabietracing; f" Main messoge pump F OLEtracing 1~ t^atabase tracing f" MuHip!e applicationdebugging r" Maicrriessage dispatch r ^M_COMMAND dispatch r InternetCIientTracitig Rysunek 27.16. Opcje programu narzędziowego MFC Tracer Rozdział 28 Interfejsy API i zestawy SDK Korzystanie z licznych dostępnych podczas programowania w Visual C++ funkcji interfejsów API i zestawów SDK Tworzenie szybkich aplikacji dźwiękowych i graficznych za pomocą interfejsów DirectX ___________________ Programowanie wysyłania i odbierania poczty w oparciu o archi-tekturę MAPI______________________________ Wykorzystywanie interfejsu MCI do obsługi aplikacji multimedial-nych Krótkie wprowadzenie API i SDK API (od ang. application programming interface, interfejs programowania aplikacji) są to zbiory funkcji przechowywane zazwyczaj w bibliotekach DLL (ang. dynamie link li-braries, biblioteka konsolidowana dynamicznie, .dli) lub statycznych bibliotekach (ang. static libraries, .libs). Funkcje te pomagają implementować w programie inne funkcje użytkowe i dostarczają połączenia między aplikacją a sprzętem przez nią obsługiwanym. Istnieje wiele różnych interfejsów API upraszczających różne aspekty programowania poprzez możliwość odwoływania się do standardowych funkcji API. Poszczególnym interfejsom poświęcone są osobne książki, dlatego w tym rozdziale ograniczymy się do przedstawienia kilku podstawowych przykładów zastosowań funkcji API. Ostatnie postępy w rozwoju technologii OLE i COM sprawiają, że wiele z interfejsów API jest obecnie implementowanych jako obiekty COM, co znacznie ogranicza problemy z zachowaniem zgodności wersji między różnymi bibliotekami .dli. Rozdział 28 Interfejsy API i zestawy SDK Korzystanie z licznych dostępnych podczas programowania w Visual C++ funkcji interfejsów API i zestawów SDK Tworzenie szybkich aplikacji dźwiękowych i graficznych za pomocą interfejsów DirectX ______ _______ Programowanie wysyłania i odbierania poczty w oparciu o archi-tekturę MAPI______________________________ Wykorzystywanie interfejsu MCI do obsługi aplikacji multimedial-nych Krótkie wprowadzenie API i SDK API (od ang. application programming interface, interfejs programowania aplikacji) są to zbiory funkcji przechowywane zazwyczaj w bibliotekach DLL (ang. dynamie link li-braries, biblioteka konsolidowana dynamicznie, .dli) lub statycznych bibliotekach (ang. static libraries, .libs). Funkcje te pomagają implementować w programie inne funkcje użytkowe i dostarczają połączenia między aplikacją a sprzętem przez nią obsługiwanym. Istnieje wiele różnych interfejsów API upraszczających różne aspekty programowania poprzez możliwość odwoływania się do standardowych funkcji API. Poszczególnym interfejsom poświęcone są osobne książki, dlatego w tym rozdziale ograniczymy się do przedstawienia kilku podstawowych przykładów zastosowań funkcji API. Ostatnie postępy w rozwoju technologii OLE i COM sprawiają, że wiele z interfejsów API jest obecnie implementowanych jako obiekty COM, co znacznie ogranicza problemy z zachowaniem zgodności wersji między różnymi bibliotekami .dli. 742 Poznaj Visual C++ 6 SDK (od ang. software development kits, zestawy do budowania oprogramowania) są interfejsami API, wzbogaconymi o dokumentację i przykładowe programy. Zestawy SDK pomagają tworzyć określone rodzaje aplikacji, tak jak Gamę SDK pomaga tworzyć gry, a OLE DB SDK aplikacje baz danych oparte na technologii OLE. Generalnie rzecz biorąc, funkcje API nie są standardowo załączane do bibliotek projektu i wymagają łączenia się z określonymi bibliotekami. Zazwyczaj dołączamy wszystkie prototypy funkcji i definicje makroinstrukcji API potrzebne w programie przez załączenie plik nagłówka .h, specyficznego dla interfejsu API, z którego korzystamy. Zdobywanie interfejsów API i zestawów SDK Nie wszystkie dostępne interfejsy API i zestawy SDK rozprowadzane są z kompilatorem Visual C++, większość jednak można ściągnąć spod adresu internetowego Microsoftu www.microsoft.com, zazwyczaj za darmo. Nawet jeśli potrzebny nam interfejs API lub zestaw SDK jest dostępny w posiadanej przez nas wersji Visual C++, warto sprawdzić, czy nie są już dostępne ich nowsze wersje, i PATRZ TAKŻE • Na temat OLE i COM pisaliśmy w rozdziale 25. Tworzenie szybkich aplikacji dźwiękowych i graficznych za pomocą interfejsów DirectX Interfejsy API typu DirectX stworzone zostały, by rozwiązać problem szybkiego dostępu do sprzętu obsługującego obraz i dźwięk. Chociaż system Windows oferuje skomplikowane standardowe funkcje obsługi dźwięku i grafiki, nie są one wystarczająco szybkie i wygodne dla programistów tworzących gry czy programy do prezentacji graficznych. Bardzo długo jedynym sposobem ominięcia tego problemu było napisanie programu w systemie DOS rezygnując tym samym z szeregu usług (takich jak niezależność programu od sprzętu czy informacje na temat konfiguracji), które oferuje system Windows. Microsoft wypełnił tę lukę za pomocą serii interfejsów API zapewniających charakte- rystyczną dla Windows niezależność od wykorzystywanego sprzętu, ale oferujących znacznie szybszy i bardziej bezpośredni dostęp do urządzeń obsługujących dźwięk, obraz i kontakt z użytkownikiem. Te interfejsy API to DirectSound, DirectDraw, Direct3D, DirectPlay, Directlnput i DirectSetup. Łącznie tworzą one zestaw Gamę SDK, który staje się coraz popularniejszy wśród programistów piszących gry i oprogramowanie obsługujące prezentacje graficzne. Interfejsy API i zestawy SDK 743 Problemy z bardzo szybką grafiką Głównym ograniczeniem dla aplikacji obsługujących animacje komputerowe są możliwości procesora. Aby oszukać ludzkie oko dając mu złudzenie ruchomego obrazu, należy wyświetlać klatki filmu z prędkością około 24 klatek na sekundę. Jedna klatka 256-kolorowego ekranu o rozdzielczości 800x600 to 480 000 pikseli. Aby zmienić kolor każdego piksela 24 razy na sekundę, musimy w ciągu tej sekundy wykonać 11 520 000 operacji. Zdefiniowanie każdego piksela zajmuje około 12 cykli procesora, co daje 138 240000 cykli zegara tylko po to, by odświeżyć w ciągu sekundy zawartość ekranu. We współczesnych procesorach o częstotliwości 266 MHz nawet 50% czasu procesora może być poświęcane operacjom odświeżania ekranu. W pozostałych 50% czasu procesora musimy wykonać wiele skomplikowanych i czasochłonnych operacji, takich jak obliczenia 3D, odwzorowywanie tekstur, skalowanie mapy bitowej i komunikację z użytkownikiem. Interfejsy API korzystają z modelu COM i dlatego odwołania do funkcji grupowane są przez specyficzne interfejsy COM w obiektach COM implementujących te funkcje. Biblioteki DLL niezbędne dla interfejsów API nie są instalowane razem z systemem Windows, są jednak rozprowadzane za darmo i dostępne w kodzie większości współczesnych gier. Można je również ściągnąć z internetowego adresu Microsoftu: www.micro-soft.com/directK. PATRZ TAKŻE • Na temat OLE i COM pisaliśmy w rozdziale 25. DirectSound Interfejs API DirectSound służy do bezpośredniego sięgania do karty dźwiękowej w celu wygenerowania dźwięku w oparciu o bufor dźwiękowy (ang. waveform buffer). Bufor ten jest po prostu fragmentem pamięci, który może być odczytywany bezpośrednio przez kartę muzyczną zmieniającą wartości tam zapisanych na dźwięki. Najprostsza technika kodowania dźwięku polega na przypisaniu każdemu z bajtów w buforze cyfry reprezentującej jeden z 256 poziomów amplitudy fali dźwiękowej, który odpowiadać będ/ic następnie odpowiedniemu wychyleniu membrany w podłączonym do komputera głośniku Przypisując kolejnym bajtom wartości, które ułożą się w kształt fali sinusoidalnej otrzymamy czysty dźwięk określonej wysokości. Zmieniając amplitudę fali poprzez wpisanie odpowiednich liczb do bufora możemy modulować kształt fali uzyskując najróżniejsze efekty dźwiękowe. Bufor przechowujący falę, którą słyszymy nazywany jest podstawowym buforem interfejsu DirectSound (ang. primary buffer). Zazwyczaj nie tworzymy bezpośrednio pod- 744 Poznaj Visual C++ 6 stawowego bufora, tylko definiujemy dźwięki zapisane w podrzędnych buforach (ang. secondary buffers), które odegrane razem zostają automatycznie zmiksowane w dźwięk bufora podstawowego. Korzystając z możliwości miksowania dźwięku i kilku podrzędnych buforów możemy uzyskać bardzo skomplikowane efekty dźwiękowe. Na rysunku 28.1 pokazany został efekt zmiksowania dźwięku zapisanego w dwu różnych podrzędnych buforach. Dźwięk bufora przedstawiony na górze jest opadającą sinusoidą (czysty dźwięk), a fala poniżej jest utworzona z losowych wartości dających szum tła (ang. wbite noise) o rosnącej amplitudzie. Fala na samym dole pokazuje zawartość podstawowego bufora będącego efektem jednoczesnego odegrania i zmiksowania dwóch podrzędnych buforów. Efektem jest czysty dźwięk przechodzący stopniowo w szum. Możemy mieszać czyste dźwięki z dźwiękami o falach kwadratowych lub trójkątnych albo z innymi buforami zawierającymi na przykład nagrane fragmenty melodii, aby uzyskać najbardziej niesamowite efekty dźwiękowe. Pierwszą rzeczą, którą należy zrobić, aby móc korzystać z DirectSound APljest utworzenie za pomocą bezpośredniego odwołania do funkcji CoCreateinstance () interfejsu iDirectSound (patrz rozdział 25) lub korzystając ze skrótu, jaki oferuje funkcja Direct-SoundCreate (). Funkcja DirectSoundCreate () Jeśli korzystamy z funkcji DirectSoundCreate (), musimy załączyć do projektu bibliotekę dsound.lib wpisując odpowiednią linię na liście Object/L;ibrary Modliłeś dostępnej na karcie Link okna dialogowego Project Settings. Jeśli natomiast korzystamy z funkcji CoCreateinstance (), biblioteka ta wprawdzie nie będzie nam potrzebna, ale będziemy musieli zadeklarować globalny identyfikator klasy (CLSID) i identyfikator interfejsu (IID). Funkcja DirectSoundCreate () wymaga trzech parametrów: pierwszy z nich jest wskaźnikiem do globalnego identyfikatora GUID definiującego urządzenie (kartę muzyczną), z którego korzystamy. Identyfikator ten można zdobyć za pomocą funkcji zwrotnej (ang. caliback function) DirectSoundEnumerate (). Możemy również przesłać funkcji wartość NULL, aby skorzystać z urządzenia domyślnego. Drugi parametr jest wskaźnikiem do wskaźnika obiektu DirectSoundinterface i służy do przypisywania wskaźnikowi aplikacji nowego obiektu. Trzeciemu parametrowi przypisujemy zazwyczaj wartość NULL, chyba że musimy skorzystać z techniki agregacji COM (ang. COM aggregation). Jeśli funkcji uda się utworzyć interfejs, zwraca ona wskaźnik do interfejsu iDirect-Sound, który jak każdy interfejs COM może być dealokowany (usuwany) z pamięci za pomocą funkcji Relase (). Gdy już mamy interfejs IDirectSound, musimy natychmiast zdefiniować jego poziom współdziałania (ang. cooperative level). Wiele z interfejsów API typu DirectX posiada poziomy współdziałania definiujące jak sprzęt, z którego korzystają, będzie wykorzy- Interfejsy API i zestawy SDK________________________________745 stywany oraz jak jego czas będzie rozdzielany między różne aplikacje. Poziom współdziałania możemy zdefiniować przyzywając w interfejsie iDirectSoundO funkcję Set-CooperativeLevel (), przesyłając jej definiujący naszą aplikację identyfikator obsługi okna. Funkcji tej musimy również przesłać znacznik definiujący poziom współdziałania. Najczęściej stosuje się znacznik DSSCL_NORMAL umożliwiający urządzeniu podczas obsługi naszego programu pełne współdziałanie z innymi aplikacjami. Teraz możemy utworzyć podrzędne bufory dźwiękowe za pomocą funkcji CreateSo-undBufferO interfejsu iDirectSound. Funkcja CreateSoundBuffer() wymaga trzech parametrów. Pierwszy parametr to struktura DSBUFFERDESC opisująca typ bufora, który tworzymy. Drugi parametr jest wskaźnikiem do wskaźnika interfejsu bufora iDirectSo-undBuffer aplikacji. Trzeciemu parametrowi przesyłamy wartość NULL, chyba że korzystamy z agregacji COM. Struktura DSBUFFERDESC jest definiowana w następujący sposób: typedef struct _DSBUFFERDESC{ DWORD dwSize; DWORD dwFlags; DWORD dwBufferBytes; DWORD dwReserved; LPWAVEFORMATEX lpwfxFormat; } DSBUFFERDESC, *LPDSBUFFERDESC; Zmienne składowe struktury reprezentują: • Zmienna dwSize reprezentuje rozmiar struktury (który można znaleźć za pomocą operatora sizeof ()). " Zmienna dwFlags pozwala definiować kilka opcji umożliwiających kontrolę nad różnymi parametrami odgrywania dźwięku, takimi jak dźwięk stereofoniczny czy głośność. Całkiem dobry zestaw standardowych ustawień oferuje znacznik DSBCAPS_CTRLDEFAULT. • Zmienna dwBufferBytes informuje, ile bajtów danych może zawierać bufor. Dla bardziej skomplikowanych dźwięków lub efektów potrzebny będzie oczywiście dłuższy bufor, dla krótkich lub powtarzających się dźwięków krótszy. Bufory można odgrywać w pętli powtarzając ich zawartość wielokrotnie, by uzyskać powtarzające się efekty dźwiękowe. • Zmienna lpwfxFormat wskazuje strukturę WAVEFORMATEX przechowującą informacje instruujące kartę dźwiękową jak należy dany dźwięk odegrać. Struktura WAVEFORMATEX przechowuje informacje takie jak prędkość, z jaką bufor ma zostać odegrany czy liczba bajtów reprezentujących każdy pojedynczy dźwięk. Zawiera również informacje na temat techniki odgrywania dźwięku, które zależne są już od konkretnej karty muzycznej, chociaż większość kart dźwiękowych korzysta z opisanej wcześniej techniki WAVE_FORMAT_PCM. 746 Poznaj Visual C++ 6 Po zdefiniowaniu podrzędnego bufora możemy zdefiniować jego zawartość przez wpisanie odpowiednich wartości w programie albo załadowanie z pliku .wav. W obu przypadkach dostęp do bufora umożliwia funkcja Lock() interfejsu iDirectSound-Buffer zwracająca adres i rozmiary bufora. Możemy w buforze zapisać odpowiednie liczby reprezentujące dźwięki, a następnie zawezwać funkcję Uniock(), która uwolni bufor. Blokowanie i odblokowywanie bufora Należy starać się nie blokować bufora zbyt długo, bowiem jeśli zdarzy się, że aktualnie odgrywany dźwięk będzie potrzebować zablokowanego bufora i odegra w jego miejsce dźwięki generowane losowo. Niektóre karty dźwiękowe posiadają własną pamięć bufora niedostępną bezpośrednio z przestrzeni adresowej procesora. W tym przypadku funkcje Lock() i unlock() zajmują się również transferowaniem zawartości pamięci pomiędzy buforem karty a pamięcią komputera. Po przypisaniu buforowi odpowiednich reprezentujących dźwięki liczb możemy odegrać go przyzywając funkcję Play () interfejsu iDirectSoundBuffer. Funkcja ta wymaga zdefiniowania trzech parametrów. Pierwszym dwóm parametrom należy przypisać wartość zero. Trzeciemu natomiast znacznik DSBPLAY_LOOPING, jeśli chcemy odgrywać bufor w pętli (wielokrotnie) lub zero jeśli chcemy odegrać go tylko raz. Odgrywanie bufora można przerwać przyzywając funkcję Stop (). Na listingu 28.1 pokazujemy, w jaki sposób za pomocą interfejsu DirectSound można w oparciu o aplikację SDI utworzyć prosty, sterowany klawiaturą syntezator dźwięku. Aplikacja będzie tworzyć i wyświetlać w oknie zawartość dwóch podrzędnych buforów (dwie górne linie, czerwona i zielona widoczne na rysunku 28.1). Rysunek 28.1. Program SoundYiew wyświetlający zawartość dwóch podrzędnych buforów i dźwięk powstały po ich zmiksowaniu Interfejsy API i zestawy SDK 747 Kiedy użytkownik wciśnie odpowiedni klawisz, oba bufory są odgrywane dając w efekcie dźwięk, którego fala przedstawiona została na dole (niebieska linia, czego niestety nie widać na rysunku). Wysokość dźwięku (sinusoidalnej fali) definiowana jest w zależności od kodu ASCII klawisza, który został wciśnięty, dzięki czemu dalsze litery alfabetu dają wyższe dźwięki. Oczywiście program ten wymaga karty dźwiękowej. Listing 28.1. LST32_1.CPP - prosty program syntetyzatora miksujący zawartość dwóch buforów po wciśnięciu klawisza klawiatury 1 CSoundView::CSoundView() 2 { 3 m_pDSObject = NULL; 4 m_pDSBuffer = NULL; 5 m_pDSMix = NULL; 6 } 7 8 CSoundView::~CSoundView() 9 < 10 if (m_pDSMix) m_pDSMix->Release(); O 11 if (m_pDSBuffer) m_pDSBuffer->Release(); O 12 if (m_pDSObject) m_pDSObject->Release(); O 13 } 14 15 void CSoundView::PlayFreq(double dFreq) 16 ( 17 if (!m_pDSObject) 18 { 19 // Utwórz obiekt DirectSound 20 if(DS_OK==Direct3oundCreate( 21 NULL,&m_pDSObject,NULL)) 22 { 23 // Ustal poziom współdziałania 24 m_pDSObject->SetCooperativeLevel(m_hWnd, 25 DSSCL_NORMAL) ; 26 27 // Oczyść bufor obiektu DirectSound 28 memset(&m_DSBufferDesc,O,sizeof(DSBUFFERDESC)) ; 29 30 // Zdefiniuj podrzędny bufor 31 m_DSBufferDesc.dwSize = sizeof(DSBUFFERDESC); 32 m_DSBufferDesc.dwFlags = DSBCAPS_CTRLDEFAULT; 33 m_DSBufferDesc.dwBufferBytes = 4096; 34 748___________________ ____ Poznaj Visual C++ 6 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 // Zdefiniuj format fali jako 22.0Khz, l bajt DtoA static WAVEFORMATEX sWave = ( WAVE_FORMAT_PCM,1,22000,22000,1,8,0 }; m DSBufferDesc.lpwfxFormat = &sWave; // Utwórz pierwszy podrzędny bufor m_pDSObject- >CreateSoundBuffer(&m_DSBufferDesc, &m_pDSBuffer,NULL) ; // Utwórz drugi podrzędny bufor m_pDSObject- >CreateSoundBuffer(&m_DSBufferDesc, &m_pDSMix,NULL) ; } } // Jeśli obiekt DirectSound istnieje ... i f (m_pDSObject) { // Zadeklaruj wskaźniki buforów LPBYTE pBufferl,pBuffer2; LPBYTE pEnvl,pEnv2; DWORD dwSizel,dwSize2; // Usuń tło CCIientDC dcCIient(this); CRect rcCIient; GetCIientRect(&rcClient) ; int yOff = (rcCIient.Height()-24)/3; dcCIient.FilISolidRect( rcCIient,RGB(255,255,255)) ; // Zablokuj bufor m pDSBuffer->Lock(0,m_DSBufferDesc.dwBufferBytes, @ (LPVOID*)SpBufferl,SdwSizel, (LPVOID*)&pBuffer2,&dwSize2,0) ; m_pDSMix->Lock(0,m_DSBufferDesc.dwBufferBytes, (LPVOID*)&pEnvl,SdwSizel, (LPVOID*)&pEnv2,&dwSize2,0) ; // Zapełnij bufory double dRange = 128.0 / (double)dwSizel; double dXRange = (double)rcCIient.Width() / (double)dwSizel; for(int i=0;i<(int)dwSizel;i++) 80 { 81 double dAmplitude = 1.0 + dRange * (double)i; 82 BYTE bl = (BYTE)(127 + (128.0 - dAmplitude) 83 * sin((double)i / (0.147 * dFreq))); 84 BYTE b2 = (BYTE) (rand ()% (int) dAmplitude) »1; 85 *(pBufferl+i) = bl; 86 *(pEnvl+i) = b2; 87 88 BYTE b3 = bl + b2; 89 90 // Namaluj fale @ 91 int x = (int) (dXRange * (double)i); 92 dcClient.SetPixelV(x, 93 (bl»l) ,RGB(255,0,0) ) ; 94 dcClient.SetPixelV(x,y0ff+64+ 95 (b2»l) ,RGB(0,255,0) ); 96 dcClient.SetPixelV(x,yOff*2+' 97 (b3»l) ,RGB(0,0,255) ); 98 } 99 100 // Odblokuj bufory i odegraj dźwięk Q 101 m_pDSBuffer->Unlock(pBufferl,dwSizel, 102 pBuffer2,dwSize2); 103 m_pDSMix->Unlock(pEnvl,dwSizel, 104 pEnv2,dwSize2) ; 105 m_pDSBuffer->Play(0,0,0) ; 106 m_pDSMix->Play(0,0,0) ; 107 } 108 } 109 110 void CSoundView::OnKeyDown(UINT nChar.UINT nRepCnt, 111 UINT nFlags) 112 { 113 CYiew::OnKeyDown(nChar, nRepCnt, nFlags); 114 115 // Zagraj nutę o częstotliwości zależnej od wciśniętego klawisza 116 if LoadIcon(IDR_MAINFRAME); 9 10 m_pIDraw = NULL; 11 m_pIMainSurface = NULL; 12 13 CoInitialize(NULL); O 14 } 15 Interfejsy API i zestawy SDK 757 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 CDrawDlg::-CDrawDlg() ( if (m_pIMainSurface) ( m pIDraw- >SetCooperativeLevel(m_hWnd,DDSCL_NORMAL) ; m_pIDraw->ReśtoreDisplayMode(); @ m_pIMainSurface->Release() ; ) if (m pIDraw) m pIDraw->Release(); CoUninitialize() ; > BOOL CDrawDlg::OnInitDialog() { CDialog::OnInitDialog() ; // ** Inicjuj bibliotekę OLE / COM if (FAILED(CoInitialize(NULL))) return FALSE; // ** Utwórz interfejs obiektu DirectDraw HRESULT hr = CoCreateInstance(CLSID_DirectDraw,NULL, © CLSCTX_ALL, IID_IDirectDraw2, (void**)&m_pIDraw) ; if( !FAILED(hr)) { hr = m_pIDraw->Initialize((struct _GUID*)NULL); if (hr !=DD_OK) return FALSE; } // ** Zdefiniuj wyłączność na korzystanie z całego ekranu m_pIDraw->SetCooperativeLevel(m_hWnd, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN |DDSCL_ALLOWREBOOT) ; // ** Zdefiniuj strukturę opisującą powierzchnię DDSURFACEDESC DrawSurfaceDesc; memset(SDrawSurfaceDesc,O,sizeof(DrawSurfaceDesc)) ; DrawSurfaceDesc.dwSize = sizeof(DrawSurfaceDesc); DrawSurfaceDesc.dwFlags = DDSD_CAPS O | DDSD_BACKBUFFERCOUNT; DrawSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_COMPLEX | DDSCAPS_FLIP | DDSCAPS_PRIMARYSURFACE; DrawSurfaceDesc.dwBackBufferCount = l; // ** Utwórz podstawową, powierzchnię 758_____________________________________Poznaj Visual C++ 6 hr = m_pIDraw->Create3urface(SDrawSurfaceDesc, (IDirectDrawSurface**)&m_pIMainSurface,NULL) ; if (FAILED(hr)) return FALSE; // ** Znajdź podrzędny bufor DrawSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; hr = m_pIMainSurface->GetAttachedSurface( © &DrawSurfaceDesc, ddsCaps,&m_pIFlipSurface) ; 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 SetTimer(l,50,0); // ** Uruchom licznik czasu z interwałem 50ms return TRUE; ) ' void CDrawDlg::OnTimer(UINT nIDEvent) ( CDialog::OnTimer(nIDEvent) ; static RECT rc=(0,0,0,0); static double dPetals=2.0; // ** Szybko oczyść powierzchnię if (rc.right>0) { DDBLTFX dbltfx; dbltfx.dwSize=sizeof(DDBLTFX) ; dbltfx.dwFillColor=RGB(O,0,0) ; HRESULT hr = m_pIFlip3urface->Blt(&rc,NULL, © &rc,DDBLT_COLORFILL,&dbltfx) ; } HDC hdc = NULL; // ** Pobierz kontekst urządzenia dla powierzchni if (m_pIFlip3urface->GetDC(Shdc) == DD_OK) { if (hdc) ( C DC dc; dc.Attach(hdc) ; rc.right = dc.GetDeviceCaps(HORZRES) ; rc.bottom = dc.GetDeviceCaps(VERTRES); Interfejsy API i zestawy SDK 759 102 double mx = (double) (rc. right»l) ; 103 double my = (double) (rc.bottom»l) ; 104 105 CPen psCol(PS_SOLID,1,RGB(O,255,0)); 106 CPen* ppsOld = dc.SelectObject(SpsCol); 107 108 double sx = mx/2; 109 double sy = my/2; 110 double sAngle = 0.0; 111 112 // ** Narysuj klatkę animacji ,113 for(int i=0;i<(int)dPetals;i++) 114 { Q 115 double ślą = sin(sAngle); 116 double clą = cos(sAngle) ; 117 double s2a = sin(sAngle * dPetals); 118 double c2a = cos(sAngle * dPetals); 119 int x = (int) (mx+sx*cla+sx*c2a) ; 120 - int y = (int)(my+sy*sla-sy*s2a) ; 121 if (i==0) dc.MoyeTo(x,y) ; 122 else dc.LineTo(x,y); 123 sAngle+=0.01745; 124 } 125 126 dPetals+=0.1; 127 dc.SelectObject(ppsOld) ; 128 dc.Detach(); 129 } 130 m_pIFlipSurface->ReleaseDC(hdc) ; 131 } 128 133 // ** Wyświetl nowa. powierzchnię 134 m_pIMainSurface->Flip(NULL,DDFLIP_WAIT); © 135 } O Funkcja Colnitialize inicjuje biblioteki COM. @ Tryb wyświetlania zostaje ustawiony jako NORMAL, a obiekty COM są uwalniane. © Wykorzystujemy funkcję CoCreatelnstance(), aby pobrać wskaźnik do interfejsu !DirectDraw2 nowego obiektu DirectDraw. O Podstawowa powierzchnia z jednym podrzędnym buforem jest opisana w strukturze DrawSurfaceDesc. ™:C!*|A„ SKW - Wwswrssyar "-c-T. cas'lr--..^i! 3 ifsssScK -, yit, : •V:VV -f 11-1-i K ^TŁM^StL ;is c-ł.-LiL •:;:: e -^ - •- ^ -,; TfcTCid -MCO^-I-w-gTl t- a"-!-»,-M:toin ^T". Z Sr. r^utcg z a.qM, ^ ^łl LEŁK •• L1E.J tA =1 pnEllOC.l ilatł-gi L-.-I.-B" ' " ii- La: 7J. FJr.fci: e~J.Aa:ily?. i ŁLi->- ^i-i.ir.Łi3i;r..-i-i. FJ .•.ł-EJI-^/Ł-llJ kIO, *3TT;!:fc •/. Jrc P: FT.LT.a- r.=IŁ*^.E : aiłKirJ .-F E- pw-.aŁŁł-s: fe.LrlK 760 Poznaj Visual C++ 6 © Wskaźnik do podrzędnego bufora możemy pobrać za pomocą funkcji GetAtta- chedSurface (). © Funkcję Blt() wykorzystujemy, aby szybko zapełnić powierzchnię określonym kolorem zamiast kopiować w tym celu powierzchnie. Q Za pomocą funkcji matematycznych tworzymy dwa szybko wirujące skomplikowane rysunki. © Funkcja Flip() odczekuje z zamianą powierzchni do momentu, gdy ekran zakończy wyświetlanie kolejnej klatki obrazu, by uniknąć migotania wywołanego przez wyświetlanie na ekranie na wpół narysowanych stron. Dodatkowo w kodzie z listingu 28.2 musimy upewnić się, że na początku pliku DrawDIg.cpp załączone zostaną za pomocą dyrektywy ttinclude pliki przedstawione niżej, niezbędne do dostarczenia programowi identyfikatorów GUID i definicji funkcji trygonometrycznych. łinclude łinclude "DrawDIg.h" łinclude "math.h" Definicje interfejsów DirectDraw możemy dostarczyć programowi załączając na początku pliku DrawDIg.h plik nagłówka interfejsu DirectDraw: #include Następujące wskaźniki powinny zostać dodane do pliku DrawDIg.h jako zmienne składowe: IDirectDraw2* m pIDraw; IDirectDrawSurface2* m_pIMain3urface; IDirectDrawSurface2* m_pIFlipSurface; W kodzie z listingu 28.2 wskaźnikom interfejsu przypisywana jest w konstruktorze CDrawDlg (linie 10 i 11) wartość NULL, a w linii 13 za pomocą funkcji Colnitialize () inicjowana jest biblioteka COM. Funkcja oninitDialog () wykorzystywana jest do tworzenia obiektu DirectDraw za pomocą przyzywanej w linii 36 funkcji coCreateinstance (). Proces tworzenia kończymy przyzywając w linii 41 funkcję initialize (), która informuje, że będziemy korzystać w programie z aktywnego sterownika. Następnie w linii 47 ustalamy poziom współdziałania rezerwując sobie wyłączność w korzystaniu z urządzenia i prawa do całego ekranu. Struktura DrawSurfaceDesc typu DDSURFACEDESC deklarowana'jest w linii 50. Definiujemy w niej podstawowy bufor z pojedynczym buforem podrzędnym dla potrzeb podwójnego buforowania. Następnie zadeklarowana struktura DrawSurfaceDesc wykorzy- Interfejsy API i zestawy SDK 761 stywana jest w linii 59 w tworzącej powierzchnię funkcji CreateSurface (). Wskaźnik do dodatkowej powierzchni zdobywamy w linii 65 za pomocą funkcji GetAttachedSurface (). Na koniec w linii 68 za pomocą funkcji SetTimer () definiujemy licznik czasu (ang. timer) uaktywniający co 50 milisekund funkcję rysująca, której kod znaleźć można w funkcji obsługi OnTimer () w linii 73. Funkcję obsługi OnTime r () komunikatu WM_TIMER dodajemy za pomocą kreatora CIassWizard. Dzięki temu będziemy mieć pewność, że do nagłówka klasy i mapy komunikatów dodane zostaną odpowiednie linie kodu. Po zdefiniowaniu kilku domyślnych wartości przyzywana w linii 86 funkcja Bit () szybko oczyszcza bufor tła, tworząc czarne tło i usuwając poprzednie wykonane na powierzchni rysunki. W linii 92 przyzywamy funkcję GetDC (), aby zdobyć identyfikator obsługi kontekstu urządzenia dla pomocniczej powierzchni. Identyfikator ten jest następnie w linii 97 przypisywany klasie CDC. W liniach 99-126 za pomocą zwykłych odwołań do funkcji GDI i obiektu CPen rysowana jest bardzo szybko jedna klatka animacji. Kontekst urządzenia jest uwalniany w linii 130, a następnie bufory są zamieniane miejscami za pomocą funkcji Flip (), aby wyświetlić nowo narysowaną klatkę animacji. Funkcji przesyłamy znacznik DDFLIP_WAIT polecający jej, aby czekała z wykonaniem operacji do przerwy pomiędzy kolejnymi wyświetlanymi przez monitor klatkami obrazu. Przerwa między wyświetlaniem kolejnych klatek obrazu Kiedy klasyczny (oparty na lampie katodowej) monitor wyświetla obraz przesyłany mu przez kartę graficzną, strumień elektronów wysyłany przez katodę przelatuje ekran linia po Unii, zaczynając od górnego lewego rogu. Elektron uderza w powierzchnię kineskopu powodując świecenie punktu na powierzchni kineskopu. Kiedy strumień elektronów osiągnie prawy dolny róg ekranu promień wodzący kineskopu musi powrócić do prawego górnego rogu, nie podświetlając w trakcie tej operacji punktów na powierzchni kineskopu. W tym momencie strumień elektronów jest na krótką chwilę, pomiędzy kolejnymi klatkami obrazu wyświetlanymi przez monitor wyłączany. Karta graficzna w tym momencie przesyła znacznik informujący, że strumień elektronów został wyłączony. Jest to najlepszy moment, aby zamienić informacje zapisane w pamięci, ponieważ monitor nie wyświetla w tym momencie niczego. Funkcja destruktora z linii 16-25 odtwarza normalny pulpit Windows zmieniając poziom współdziałania i przyzywając funkcję RestoreDisplayMode (), która przywraca normalny tryb wyświetlania. Interfejsy są uwalniane za pomocą funkcji Re l ase (), a biblioteka COM jest automatycznie odłączana. Jeśli wprowadzimy przedstawione tu zmiany do szkieletu aplikacji opartego na oknie dialogowym, będziemy mogli zbudować aplikację bez konieczności dodawania żadnych dodatkowych bibliotek API, korzystając tylko z możliwości, które oferują nam obiekty 762_____________________________________Poznaj Visual C++ 6 COM. Jeśli teraz uruchomimy aplikację, będziemy mogli podziwiać szybką i płynną animację oferowaną przez technikę DirectX. Aplikację możemy wyłączyć w dowolnym momencie wciskając klawisz Esc (będący skrótem do ciągle aktywnego, mimo iż okno aplikacji jest zamknięte, przycisku Cancel). Wciśnięcie klawisza Esc w łagodny sposób przywróci pulpit Windows. PATRZ TAKŻE ^ O technikach COM i OLE pisaliśmy w rozdziale 25. • Aby dowiedzieć się więcej na temat kontekstu urządzenia i rysowania za pomocą obiektów i funkcji GDI należy zajrzeć do rozdziału 15. Direct3D Za pomocą interfejsu API Direct3D możemy umożliwić karcie graficznej obsługę grafiki trójwymiarowej. Wspólnie z obiektem DirectDraw interfejs ten oferuje płynną i szybką grafikę trójwymiarową, wykorzystywaną w grach komputerowych i aplikacjach wirtualnej rzeczywistości. Nowe karty graficzne wspomagające grafikę trójwymiarową Ostatnimi czasy pojawiło się na rynku wiele kart graficznych przejmujących część operacji grafiki trójwymiarowej. Specjalistyczne układy scalone karty graficznej pozwalają wykonywać część skomplikowanych obliczeń związanych z grafiką trójwymiarową znacznie szybciej niż programy graficzne. Dodatkowo uwalniana jest w ten sposób także część mocy obliczeniowej procesora, co daje programistom tworzącym gry i programy CAD możliwość zwiększenia ich efektywności. Interfejs Direct3D korzysta z zestawu rysującego złożonego z trzech modułów, które obsługują macierzowe transformacje współrzędnych, efekty oświetlenia dla światła punktowego i oświetlenia biernego. Ostatni moduł odpowiedzialny jest za rastrowanie. Za pomocą tych modułów tworzona jest wynikowa scena. Niektóre z interfejsów Direct3D oferują pełny zestaw funkcji umożliwiających konstruowanie i renderowanie trójwymiarowych widoków, które przypisać można do dwóch kategorii: trybu natychmiastowego (ang. immediate-mode) i trybu opóźnionego (ang. retained-mode). Obiekty trybu natychmiastowego są niskopoziomowymi i szybko działającymi obiektami rysowania w trzech wymiarach (ich interfejsy zostały przedstawione w tabeli 28.2). Na bazie obiektów natychmiastowych zbudowane są liczne obiekty trybu opóźnionego, umożliwiające tworzenie skomplikowanych wysokiej jakości animacji trójwymiarowych. Interfejsy API i zestawy SDK 763 Interfejs Direct3D Jego funkcje IDirect3D IDirect3DDevice IDirect3DExecuteBuffer IDirect3DLight IDirect3DMaterial IDirect3DTexture IDirect3DViewport Inicjuje środowisko graficzne i tworzy obiekty Light, Materiał i Viewport. Zarządza konfiguracją umożliwiającą sprzętowi i środowisku uruchomienie możliwości grafiki trójwymiarowej oferowanych przez sprzęt. Interfejs ten możemy utworzyć z powierzchni DirectDraw za pomocą funkcji Querylnterface () Wykonuje bufory i przechowuje informacje o wierzchołkach i instrukcje renderowania dla elementów grafiki trójwymiarowej gotowych do narysowania Konfiguruje dane oświetlenia dla każdego ze świateł określonej sceny Obsługuje właściwości materiału - stopień przezroczystości, odbijanie światła Obsługuje nakładanie na powierzchnie trójwymiarowe mapy bitowej tekstury. Grupuje efekty oświetlenia tekstury i tła w określonym elemencie, który będzie wyświetlony na ekranie. Kilka takich powierzchni ukośnych jest następnie łączonych razem i z informacjami o wierzchołkach, tak aby można je było wyświetlić na ekranie DirectPlay Interfejs API DirectPlay umożliwia łatwe konstruowanie gier komputerowych umożliwiających kilku graczom wspólną grę przez sieć bez konieczności programowania osobnej obsługi połączeń dla różnego rodzaju połączeń sieciowych. Dwa przynależne tutaj interfejsy lDirectplay2 i iDirectPlayLobby zarządzają wszystkimi aspektami odpowiednio obiektów gry trybu multiplayer i połączeniami między komputerami. Jakie połączenia sieciowe obsługuje DirectPlay? Interfejs API DirectPlay obsługuje grę w komputerach połączonych kablem szeregowym, połączenia TCP/IP sieci lokalnych, połączenia przez modem, Netware i wiele wiele innych połączeń sieciowych. Directlnput Interfejs API Directlnput pozwala na szybszy dostęp do danych wprowadzanych za pomocą myszy i klawiatury niż za pośrednictwem standardowych interfejsów API Win- 764___________________________________Poznaj Visual C++ 6 dows. Obsługuje również pobieranie danych od różnego rodzaju joysticków dostarczając funkcji pozwalających na ich kalibrację. Ten interfejs API jest stosunkowo prosty, oferuje przy tym znaczne możliwości. Składa się z dwóch interfejsów COM. Pierwszy z nich, interfejs IDirectInput, zarządza zainstalowanymi w systemie urządzeniami umożliwiającymi wprowadzanie danych i ich statusem. Umożliwia również tworzenie egzemplarzy interfejsów IDirectInputDevices obsługujących konfigurację i komunikację z konkretnymi urządzeniami. DirectSetup Interfejs API DirectSetup jest prawdopodobnie jednym z najmniejszych interfejsów API. Posiada tylko dwie funkcje: DirectXSetup (), która automatyzuje instalację i konfi-gurowanie wszystkich komponentów DirectX i funkcję DirectXRegisterApplica-tion (), która rejestruje grę jako obiekt DirectPlayLobby, umożliwiający granie w trybie multiplayer. Tworzenie wiadomości i programowanie obsługi poczty za po- mocą MAPI Interfejs Microsoft Messaging API (MAPI) tak naprawdę nie jest interfejsem API, tylko zestawem standardów tworzących architekturę. Posługując się tymi sztywnymi i roz- budowanymi zasadami twórcy oprogramowania nie muszą tworzyć od razu całego opro- gramowania związanego z obsługą poczty, ale mogą ograniczyć się do programowania wybranych fragmentów systemu pocztowego. Część twórców oprogramowania będzie więc tworzyć własne aplikacje klientów (odczytujące i zapisujące pocztę), podczas gdy inni będą tworzyć obiekty oferujące usługi w zakresie transportowania i przechowywania wiadomości lub przechowujące listę adresów w książce adresowej. Każdy z tych komponentów można zaprogramować osobno, a użytkownik może łączyć je ze sobą na różne sposoby wybierając narzędzia do przesyłania poczty, które mu najbardziej odpowiadają. Ta plastyczność systemów pocztowych jest często niezbędna w dużych firmach, które muszą zintegrować ze sobą nowsze i starsze, często bardzo różne systemy pocztowe. Pisząc aplikację MAPI możemy wybierać między dwoma wersjami MAPI, w zależności od tego jak bardzo skomplikowanych narzędzi do wysyłania poczty potrzebujemy. Prostsza wersja MAPI (Simple MAPI) zawiera minimalny zestaw funkcji niezbędny do rejestrowania i utworzenia sesji MAPI, odczytywania i pisania wiadomości oraz rozpatrywania (odnajdywania najbliższego podobnego adresu) adresów w książce adresowej. Jeśli aplikacja, którą tworzymy potrzebuje tylko tych podstawowych funkcji programu pocztowego, lepiej korzystać z prostszej wersji MAPI. Jakiekolwiek dodatkowe funkcje będą wymagały wykorzystania Extended MAPI (rozszerzonego MAPI) implementowanego w pliku mapi28.dll, który dostarczy nam potrzebnych komponentów. Niestety, wersja Extended MAPI jest z konieczności bardzo duża i skomplikowana. Aby z nią pracować, Interfejsy API i zestawy SDK________________________________765 trzeba dobrze znać zasady programowania COM, ponieważ wersja ta jest implementowana w większej części przez obiektową hierarchię interfejsów i obiektów COM. Extended MAPI SDK i przykładowy program MDBView Jeśli naprawdę potrzebne nam są możliwości Extended MAPI, musimy być przygotowani na ciężką pracę i długą naukę. Będziemy musieli również ściągnąć z sieci MAPI SDK. Ten zestaw SDK zawiera bardzo pouczający przykładowy program MDBVIEW.exe, wielce pomocny przy próbie zrozumienia skomplikowanych zagadnień programowania związanych z rozszerzonym MAPI. PATRZ TAKŻE • Na temat OLE i COM pisaliśmy w rozdziale 25. Korzystanie z prostszej wersji MAPI Prostsza wersja MAPI implementowana jest w pliku mapi28.dll, zaś prototypy funkcji i wartości odpowiednich znaczników definiowane są w pliku nagłówka mapi.h. Nasza prosta aplikacja MAPI może zarejestrować się u dostawcy wiadomości (ang. message provider) definiując profil (ang. profile) MAPI lub akceptując profil domyślny, co pozwoli jej rozpocząć sesję pocztową. Profile MAPI możemy skonfigurować za pomocą apletu Maił and Fax w Panelu sterowania (rysunek 28.2). Każdy z profili definiuje szczegółowe informacje na temat przesyłania, wiadomości i programów dostarczających książkę adresową, z których program klient będzie korzystał w momencie zarejestrowania, takich jak Microsoft Exchange czy Lotus Notes. Kiedy już się zarejestrujemy, będziemy mogli wysyłać i odbierać wiadomości i wyrejestrować się po zakończeniu. Aplet Maił and Fax Aplet Maił and Fax w Panelu sterowania może wyglądać inaczej lub mieć inny tytuł niż ten, który przedstawiamy na rysunku, w zależności od aplikacji pocztowej MAPI, którą zainstalujemy na komputerze. 766 Poznaj Visual C++ 6 s ervices Oelivery] Addresshg 'heioltowinginformation sernice s arę setup in thi$ pro file; PersonalA ddi Fersonal Fold Add... ess Boak ers Remoye Pro partie s 1 ^ Copy... About.. JahowProftle s, l OK Caneel Hełp Rysunek 28.2. Aplet Maił and Fax w Panelu sterowania w czasie konfigurowania profilu MAPI Podstawowe funkcje MAPI przedstawione zostały w tabeli 28.3. Funkcje te można przywoływać w aplikacji, albo bezpośrednio łącząc się z biblioteką mapi28.1ib, która pomoże połączyć się z biblioteką mapi28.dll, albo korzystając z funkcji LoadLibrary () i GetProc-Address () pozwalających dynamicznie ładować i odłączać bibliotekę DLL. Tabela 28.3. Podstawowe funkcje MAPI Funkcja Opis MAPILogonO MAPILogoffO MAPISendMail() MAPISendDocuments() MAPIFindNext() MAPIReadMail() MAPISaveMail() MAPIDeleteMaiK) MAPIFreeBufferf) MAPIAddress() MAPIDetails() MAPIResolveName() Rejestruje sesję MAPI przez konfigurację profilu Wyrejestrowuje z sesji MAPI Wysyła wiadomość ze struktury MapiMessage Wysyła zestaw plików dokumentów po sprecyzowaniu ich nazw Znajduje pierwszą lub następną wiadomość zwracając jej identyfikator Wczytuje różne aspekty wiadomości do struktury MapiMessage Zachowuje nową wiadomość w wewnętrznej skrzynce dostarczanej przez dostawcę Usuwa wiadomość zdefiniowaną przez ostatnią funkcję MAPIFindNext () Zwalnia bufory wykorzystywane przez MAPI Wyświetla okno dialogowe z listą adresów umożliwiając użytkownikowi modyfikację zawartości Pokazuje informacje związane z określonym adresem Zmienia niezrozumiałą nazwę odbiorcy na adres pocztowy f/MKŁ IAIV.t • Więcej informacji na temat OLE i COM znaleźć można w rozdziale 25. Dodawanie programu pocztowego MAPI za pomocą kreatora AppWizard Na stronie czwartej kreatora AppWizard (rysunek 28.3) w sekcji What features would like to include? (Jakie dodatkowe możliwości chcesz dołączyć do aplikacji?) znajduje się opcja MAPI (Messaging API) pozwalająca dodać do aplikacji SDI lub MDI możliwości interfejsu MAPI. Opcja ta dodaje do menu File polecenie Send, a do klasy dokumentu następujące hasła mapy komunikatów: ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail) ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL, OnUpdateFileSendMail) MFC AppWizald - Step 4 ol G IK Ch«ck Baz (» M». ; r n. What fealur&s would you like lo include? | 17 iDockingjcoibal! l F Initia! status bar s ^ Pnff^a aftd ptint pfeview ; F' Conteiit- tereitiye Hglp '. P 30 controls ^ F MAP! (Messaging API) l F Windows Sockets How do you waht your toolbars lo look? y Hennal ^ Inteinet E^plofet ReBafS How many f ileś would yw like on your lecent f ile list? R:ri Advanced... | |~ Cancel Rysunek 28.3. Dodawanie funkcji interfejsu MAPI do aplikacji SDI za pomocą kreatora AppWizard Jeśli zbudujemy i uruchomimy aplikację już w tym momencie, będzie ona zaopatrzona we wszystkie funkcje niezbędne do wysyłania poczty. Możemy kliknąć polecenie Send w menu File, aby przyzwać okno rejestracji MAPI. Po wybraniu profilu (lub skonfigurowaniu systemu pocztowego) pakiet pocztowy zostanie uruchomiony i będziemy mogli utworzyć i wysłać wiadomość e-mail. Szkielet aplikacji automatycznie serializuje bieżący dokument za pomocą funkcji OnSaveDocument () i dołącza go do nowego e-maila. Jeśli potrzebujemy bardziej rozbudowanych funkcji aplikacji pocztowej, musimy skorzystać z podstawowych funkcji MAPI albo sięgnąć do Extended MAPI. W kodzie z listingu 28.3. pokazujemy, jak za pomocą podstawowych funkcji MAPI implementowanych w aplikacji SDI bazującej na widoku Edit utworzyć klienta pocztowego, który będzie umożliwiał wysyłanie wiadomości za pośrednictwem polecenia Send 768_____________________________________Poznaj Visual C++ 6 menu File i odbieranie ich za pomocą polecenia Receive menu File. Odebrane wiadomości są następnie wyświetlane w widoku Edit. Przykładowy program ładuje plik biblioteki mapi28.dll dynamicznie w konstruktorze dokumentu, rejestruje nową sesję MAPI, a po wszystkim kończy sesję i zwalnia bibliotekę DLL w destruktorze. Najpierw tworzymy zwykły szkielet aplikacji SDI za pomocą kreatora AppWizard, włączając opcję, która umożliwia korzystanie z funkcji MAPI, w celu dodania do aplikacji polecenia Send. Na ostatniej stronie kreatora AppWizard wybieramy widok Edit jako widok bazowy. Za pomocą edytora zasobów dodajemy do menu File polecenie Receive. Przedstawiony niżej kod jest w większości dodawany do funkcji obsługi nowych poleceń w klasie dokumentu. Listing 28.3. LST32_3.CPP - wykorzystanie prostych funkcji MAPI do odczytywania wiadomości w aplikacji SDI korzystającej z MAPI 1 łinclude 2 3 HMODULE g_hMAPI; 4 LHAŃDLE g_hSession; 5 6 LPMAPILOGON g_lpfnLogon; 7 LPMAPILOGOFF g_lpfnLogoff; 8 LPMAPIFINDNEXT g_ipfnFindNext; 9 LPMAPIREADMAIL g_lpfnReadMail; 10 LPMAPIFREEBUFFER g_lpfnFreeBuffer; 11 12 CMailClientDoc::CMailClientDoc() 13 { 14 // ** Dynamicznie załaduj bibliotekę DLL i odpowiednie funkcje 15 g_hMAPI = LoadLibraryf"MAPI28.DLL"); 16 g_lpfnLogon = (LPMAPILOGON) 17 GetProcAddress(g_hMAPI,"MAPILogon"); O 18 g_lpfnLogoff = (LPMAPILOGOFF) 19 GetProcAddress(g_hMAPI,"MAPILogoff") ; 20 g_lpfnFindNext = (LPMAPIFINDNEXT) 21 GetProcAddress(g_hMAPI,"MAPIFindNext") ; 22 g_lpfnReadMail = (LPMAPIREADMAIL) 23 GetProcAddress(g_hMAPI,"MAPIReadMail") ; 24 g_lpfnFreeBuffer= (LPMAPIFREEBUFFER) 25 GetProcAddress(g_hMAPI,"MAPIFreeBuff er"); 26 27 (*g_lpfnLogon)(O,NULL,NULL, @ 28 MAPI_NEW_SESSION | MAPI_LOGON_UI,O,&g_hSession) ; 29 } CMailClientDoc::~CMailClientDoc() ( // ** Wyrejestruj się i zakończ sesję (*g_lpfnLogoff)(g_hSession,0,0,0) ; FreeLibrary(g_hMAPI) ; } void CMailClientDoc::OnFileReceive() { // ** Znajdź widok POSITION pos = GetFirstViewPosition(); CView* pView = GetNextView(pos) ; // ** Zadeklaruj Identyfikatory wiadomości static char szSeedMessage[512]; . static char szMessage[512]; static LPSTR pSeed = NULL; static LPSTR pMsg = szMessage; IpMapiMessage IpMessage = NULL; // ** Znajdź następna, wiadomość ULONG uIResult = (*g_lpfnFindNext)(g_hSession, ® (ULONG)pView->m_hWnd,NULL,pSeed, MAPI_LONG_MSGID, OL, pMsg) ; // ** Odczytaj następna, wiadomość uIResult = (*g_lpfnReadMail)(g_h3ession, (ULONG)pView- >m_hWnd,pMsg,MAPI_PEEK, OL,SipMessage) CString strMessage; CString strFmt; if (uIResult == OL) ( // ** Zachowaj elementy wiadomości strFmt.Format("From: %s\r\n", !pMessage->lpOriginator->lpszName) ; strMessage += strFmt; strFmt.Format("To: %s\r\n", !pMessage->lpRecips[0].IpszName); , strMessage += strFmt; strFmt.Format("Subject: %s\r\n", lpMessage->lpsz3ubject); 770 Poznaj Visual C++ 6 74 strMessage += strFmt; 75 strMessage += !pMessage->lpszNoteText; 76 pSeed = szSeedMessage; 77 stropy(p3eed,pMsg); 78 79 // ** Przypisz wiadomość widokowi Edit 80 pView->SetWindowText(strMessage) ; 81 82 // ** Zwolnij bufor 83 (*g_lpfnFreeBuffer)( (LPVOID)IpMessage) ; 84 } 85 } O Funkcje biblioteki DLL odnajdujemy ręcznie za pomocą funkcji GetProcAdress () @ Program rejestruje MAPI korzystając z domyślnego profilu © Funkcja FindNext() odnajduje następną wiadomość w oparciu o wartość przesłaną przez wiadomość poprzednią O Funkcja ReadMail () wczytuje zawartość wiadomości do bufora MapiMessage W pierwszej linii listingu 28.3 załączamy nagłówek mapi.h, który przechowuje prototypy funkcji i używane znaczniki. Konstruktor dokumentu (linie 12-29) ładuje w linii 15 za pomocą funkcji LoadLibraryO plik rnapi28.dll. Następnie za pomocą funkcji Get-ProcAddress () odnajduje adresy funkcji biblioteki DLL zachowując je w globalnych wskaźnikach deklarowanych w liniach 6-10. Na koniec w linii 27 rejestruje nową sesję MAPI zachowując identyfikator sesji w zmiennej g_h3ession. Sprawdzanie wartości zwracanych przez funkcję GetProcAddress () Po udanym załadowaniu biblioteki DLL możemy odnajdywać jej funkcje za pomocą funkcji GetProcAddress (). Funkcja ta zwraca adres funkcji definiowanej jako łańcuch, pod którym występuje w bibliotece DLL. Identyfikator biblioteki DLL również należy przesłać funkcji GetProcAddress (). Jeśli funkcja GetProcAddress () odnajdzie właściwy adres, zostanie on zapisany we wskaźniku funkcji ipfnLogon. Jeśli nie, wskaźnikowi funkcji przypisana zostanie wartość NULL. Należy zawsze sprawdzać, czy funkcji GetProcAddress () udało się zdobyć odpowiedni wskaźnik funkcji. W przeciwnym wypadku program zawiesi się, gdy wyślemy go do zerowego adresu pamięci! Interfejsy API i zestawy SDK 771 Funkcję obsługi polecenia menu OnFileReceive () dostarczającą aplikacji kod umożliwiający odczytywanie wiadomości należy dodać do dokumentu za pomocą kreatora CIassWizard. W liniach 41 i 42 odnajdujemy za jej pomocą widok Edit, którego identyfikator obsługi okna jest w linii 53 wykorzystywany w funkcji FindNext () do odnajdywania kolejnej oczekującej wiadomości. Bufor szSeedMessage dostarcza funkcji FindNext () informacji, dzięki której może ona odnaleźć następną wiadomość lub pierwszą, o ile wskaźnik pSeed ma wartość NULL. W linii 58 odczytywana jest zawartość wiadomości identyfikowanej przez wskaźnik IpMessage za pomocą odwołania do funkcji ReadMail (). Jeśli wiadomość uda się odczytać, zmiennej uIResult przypisywana jest wartość zero, a wskaźnikowi IpMessage struktura MapiMessage. Szczegółowe informacje na temat wiadomości będzie można następnie wydobywać ze struktury w oparciu o wskaźnik IpMessage (linie 65-75). Kiedy już sformatowany łańcuch strMessage, zostanie zbudowany z zapisanych w strukturze części możemy przypisać wiadomość widokowi korzystając z funkcji set-windowText () (linia 80). Następnie w linii 83 oczyszczamy strukturę za pomocą funkcji MAPiFreeBuffer () przyzywanej za pośrednictwem wskaźnika (zdefiniowanego w linii 24) g_lpfnFreeBuffer. W liniach 76-77 kopiujemy bieżącą wiadomość do bufora szSeedMessage, przygotowując się do kolejnego wezwania funkcji FindNext (), która pobierze następną wiadomość, gdy tylko użytkownik kliknie w menu polecenie Receive. Po zamknięciu dokumentu sesja pocztowa zostaje zakończona w linii 34, a biblioteka .dli jest w linii 35 odłączana za pomocą funkcji FreeLibrary (). Po zbudowaniu i uruchomieniu aplikacji będziemy mogli, tak jak to zostało pokazane na rysunku 28.4, wysyłać i odbierać pocztę za pomocą poleceń Send i Receive z menu File. Jeśli wiadomości do odebrania jest więcej, należy po prostu kolejny raz kuknąć Receive. Gdy będziemy uruchamiać aplikację po raz pierwszy, wyświetlone zostanie okno dialogowe rejestracji profilu pokazujące nasz domyślny profil pocztowy. Kliknięcie OK zatwierdzi profil przedstawiony w oknie. File Edit "D j Cg| B j Yiew hełp ^om: Microsoft To: New Windows 95 Customer Subject Welcome! Welcome to Microsoft Exchange! Welcome to Microsoft Exchange and the worid ot e-mail. E-mail provides afast and efficientway of communicating with others. There arę many different kinds of e-mail systems. You can use Microsoft Exchange with these systems as a universal lnboxto: Rysunek 28.4. Odczytywanie wiadomości e-mail w widoku Edit aplikacji SDI za pomocą funkcji Simple MAPI 772_____________________________________Poznaj Visual C++6 Biblioteki multimedialne i interfejs MCI Pojawienie się oprogramowania multimedialnego zmieniło pokutujący do niedawna wizerunek komputera PC jako topornej maszyny biurowej. Obecnie na komputerze PC można odgrywać płyty kompaktowe, oglądać telewizję, uruchamiać efekty dźwiękowe i animacje. Dzięki bibliotekom multimedialnym i interfejsowi MCI (od ang. Media Con-trol Interface) możemy bez większych problemów dołączyć wszystkie te możliwości do naszej aplikacji. Interfejs MCI i związana z nim klasa obsługująca interfejs użytkownika MCIWnd pozwalają w prosty i jednolity sposób kontrolować nawet bardzo skomplikowany sprzęt i oprogramowanie multimedialne. Interfejs multimedialny MCI Interfejs MCI dostarcza dwóch systemów przesyłania poleceń (tekstowego i bazującego na strukturach) pozwalających kontrolować z poziomu aplikacji najróżniejsze urządzenia multimedialne. Oparty na tekście system łańcuchów poleceń (ang. Command Strings) pozwala łączyć ze sobą w łańcuchu polecenia sterujące, które są następnie przesyłane do interfejsu MCI za pomocą funkcji mciSendStringO. Tekstowy system definiowania poleceń jest prosty, łatwy w użyciu i co ważne przyjazny, gdy usuwamy błędy programu w procesie debugo- wania. Polecenia MCI Polecenia MCI są zaprojektowane w taki sposób, żeby były maksymalnie ogólne. Interfejs oferuje zestaw bazowych poleceń, które można stosować na każdym urządzeniu. Te bazowe polecenia umożliwiają otwieranie, zamykanie, odczytywanie statusu i możliwości urządzenia, ładowanie danych, zachowywanie danych, odtwarzanie zapisu, zatrzymywanie odtwarzania i nagrywania, a także odszukiwanie określonego momentu odtwarzanego zapisu dla dowolnego urządzenia medialnego. Możemy również definiować polecenia za pomocą zwykłych znaczników i struktur języka C++ w systemie komunikatów poleceń (ang. Command Messages), który wykorzystuje do przesyłania komunikatów do interfejsu MCI funkcję mciSendCommandO. Ten system jest odrobinę szybszy, ponieważ nie wymaga budowania łańcuchów. Jednak oba systemy są podobne i projektując aplikację możemy wybrać ten, który będzie wydawał nam się wygodniejszy. Pierwsze polecenie, które należy wysłać do interfejsu MCI, to polecenie otwarcia konkretnego urządzenia. Możemy to zrobić albo za pomocą funkcji mciSendStringO i łańcucha open, albo za pomocą funkcji mciSendCommandO i znacznika MCI_OPEN. Możemy otworzyć dowolne urządzenie należące do jednego z typów przedstawionych w tabeli 28.4, o ile oczywiście jest zainstalowane w naszym systemie. Niektóre z urządzeń Interfejsy API i zestawy SDK 773 będą wymagały podłączenia specjalistycznego sprzętu, podczas gdy do innych, takich jak waveaudio czy digitalvideo, w systemie Windows zostało już standardowo zainstalowane odpowiednie oprogramowanie. Tabela 28.4. Typy urządzeń w interfejsie MCI Łańcuch Znacznik Opis urządzenia waveaudio MCI_DEVTYPE_WAVEFORM_AUDIO sequencer MCI_DEVTYPE_SEQUENCER cdaudio MCI_DEVTYPE_CD_AUDIO dat MCI_DEVTYPE_DAT digitalvideo MCI_DEVTYPE_DIGITAL_VIDEO vcr MCI_DEVTYPE_VCR videodisc MCI_DEVTYPE_VIDEODISC mmmovie MCI_DEVTYPE_ANIMATION overlay MCI_DEVTYPE_OVERLAY scanner MCI_DEVTYPE_SCANNER other MCI DEVTYPE OTHER Nagrywa/odtwarza pliki dźwiękowe .wav Sekwenser MIDI Odtwarza płyty kompaktowe Odtwarza/zapisuje cyfrowe taśmy dźwiękowe Odgrywa cyfrowe pliki filmowe .avi Odtwarza/nagrywa kasety VCR Odtwarza wideodyski Odtwarza animacje Odtwarza w oknie analogowy zapis wideo Skanuje rysunki Urządzenia niezdefiniowane Tak jak można się spodziewać, polecenie open wymaga paru dodatkowych parametrów, dlatego funkcji mciSendStringO należy po poleceniu open przesłać trzy (oddzielone spacjami) parametry przedstawione poniżej: open Jako parametr nazwa urządzenia można przesłać dowolną nazwę podaną w tabeli 28.4. Parametr znaczniki otwarcia może definiować zastępczą nazwę (alias identyfikujący później określony egzemplarz otwartego urządzenia) i różne specyficzne dla urządzenia opcje. Parametrowi znaczniki powiadamiające można przypisać albo wartość wait informującą, że polecenie powinno czekać na otwarcie urządzenia, albo notify polecające kontynuować wykonanie programu i powiadamiające go, kiedy urządzenie zostanie otwarte. Możemy również polecić funkcji mciSendStringO zwracać komunikat o statusie przesyłając jej bufor zwrotny (ang. retum buffer) wraz z jego rozmiarami. Jeśli chcemy otrzymywać komunikaty powiadamiające, możemy również przesłać identyfikator obsługi okna, które ma je otrzymywać. 774 Poznaj Visual C++ 6 Nazwy urządzeń MCI Interfejs MCI daje się swobodnie rozbudowywać. Nowe urządzenia MCI można do niego dodawać rejestrując odpowiednie sterowniki urządzeń. Odpowiednie hasła mówiące na temat rejestracji urządzeń multimedialnych w Windows 95 można znaleźć w pliku C:\Windows\System.ini w sekcji [mci]. W Windows NT z kolei odpowiednie informacje znajdziemy w folderze HKEY_LOCAL_MACHINE\SOFT- WARE\Microsoft\WindowsNT\CurrentVersion\#SYS...\MCI. Przykładowo, za pomocą przedstawionej niżej funkcji mciSendStringO możemy przesłać interfejsowi MCI łańcuch poleceń polecający mu otworzyć odtwarzacz płyt kompaktowych i w dalszej części programu opisywać go jako mycd: char szRetMsg[80] ; mciSendString("open cdaudio alias mycd wait", szRetMsg,sizeof(szRetMsg),m hWnd) ; Możliwości urządzeń MCI Możemy sprawdzić, czy dane urządzenie MCI oferuje konkretne możliwości (funkcje) korzystając z łańcucha capabilities. Przykładowo, aby sprawdzić, czy czytnik CD oferuje możliwości nagrywania, należy wpisać łańcuch Capabilities cdaudio can record i sprawdzić kod zwrotny informujący, czy tak jest w istocie. Odpowiedni znacznik polecenia to MCI_GETDEVCAPS. Przedstawione tu polecenie zaczeka aż odtwarzacz CD zostanie prawidłowo otwarty, jeśli wszystko pójdzie dobrze zwracając w buforze szRetMsg wartość "l". Jeśli odtwarzacz nie zostanie otwarty, w buforze zostanie zwrócona wartość "Q". Jeśli urządzenia nie uda się otworzyć, zwracana jest struktura MCIERROR, którą można przekształcić w łańcuch komunikatu za pomocą funkcji mciGetErrorString (). Ta sama operacja otwarcia urządzenia za pomocą funkcji mciSendCommand () wymaga przesłania czterech parametrów: • identyfikatora urządzenia (nie wykorzystywanego przy otwieraniu) • znacznika polecenia (MCI_OPEN) • znaczników powiadamiających: albo MCI_WAIT, albo MCI_NOTIFY • wskaźnika do struktury MCI_OPEN_PARAMS Funkcja zwróci następnie, podobnie jak to robiła funkcja mciSendString (), strukturę MCIERROR. Aby otworzyć odtwarzacz CD za pomocą funkcji mcisendCominandO, należy przesłać następujący komunikat polecenia: Interfejsy API i zestawy SDK 775 MCI_OPEN_PARMS myCDOpen = {NULL,O,"cdaudio",NULL,NULL}; errOpen = mciSendCommand(NULL,MCI_OPEN, MCI_OPEN_TYPE|MCI_WAIT,(DWORD)SmyCDOpen) ; Funkcja mciSendCommand () wykorzystuje strukturę MCI_OPEN_PARAMS przesyłaną jako wskaźnik myCDOpen. Struktura ta definiowana jest w następujący sposób: typedef struct { DWORD dwCallback; MCIDEVICEID wDeviceID; LPCSTR !pstrDeviceType; LPCSTR IpstrElementName; LPCSTR IpstrAlias; } MCI_OPEN_PARMS; Struktura MCI_OPEN_PARAMS pozwala przesłać w zmiennej dwCaliBack identyfikator obsługi okna, któremu będą przesyłane komunikaty powiadamiające. Identyfikator otwartego urządzenia jest odsyłany w zmiennej wDeviceID, którą będziemy mogli następnie wykorzystać w kolejnych odwołaniach do tegoż urządzenia. Zmienna IpstrElementName służy do definiowania nazwy pliku, jeżeli otwieramy urządzenie razem z powiązanym z nim plikiem dźwiękowym .wav lub filmowym .avi. Możemy zapisać odpowiedni alias w zmiennej IpstrAlias, aczkolwiek ten krok nie jest niezbędny, jako że urządzenie możemy zawsze zidentyfikować za pomocą zmiennej zawierającej identyfikator urządzenia wDeviceID. Różnych struktur dla różnych komunikatów poleceń MC l_ jest dokładnie tyle, ile jest łańcuchów dla łańcuchów poleceń. Po otwarciu urządzenia możemy za pomocą następnych poleceń odgrywać, zapisywać, zamykać i wykonywać wiele innych poleceń multimedialnych. Część z licznej rzeszy możliwych poleceń została przedstawiona w tabeli 28.5. Każde z poleceń posiada wiele różnych parametrów, zdecydowanie zbyt wiele, abyśmy mogli je tutaj opisywać. Tabela 28.5. Niektóre polecenia MCI Komunikat Łańcuch Opis polecenia MCI_OPEN MCI_CLOSE MCI_PLAY MCI_RECORD MCI_STOP MCIJ3EEK MCI PAUSE open close play record stop seek pause Otwiera urządzenie Zamyka urządzenie Odtwarza w urządzeniu Nagrywa w urządzeniu Zatrzymuje odgrywanie lub nagrywanie Odszukuje pozycję w materiale (filmie, utworze muzycznym) Pauzuje odgrywanie lub nagrywanie 776 Poznaj Visual C++ 6 MCI_RESUME resume Uruchamia ponownie po pauzie MCI_LOAD load Ładuje plik MCI_SAVE save Zachowuje w pliku MCI_STATUS status Pobiera informacje o statusie MCI_WINDOW window Wyświetla okno wideo dla zapisu filmowego MCI_WHERE where Definiuje pozycję okna Komunikaty powiadamiające MCI Jeśli w poleceniu MCI wykorzystamy znacznik wait lub MCI_WAIT, polecenie nie zwróci aplikacji kontroli, dopóki nie zostanie wykonane do końca. Taka procedura wykonywania poleceń jest bardzo praktyczna w niektórych sytuacjach, jednak jeśli przykładowo wydamy aplikacji odtwarzającej płyty kompaktowe polecenie odtworzenia zawartości kompaktu, aplikacja będzie czekać, dopóki polecenie odgrywania nie zostanie wykonane do końca. To może oznaczać, że na kolejną możliwość kontaktu z aplikacją użytkownik będzie musiał czekać prawie godzinę! Jeśli natomiast nie prześlemy żadnych znaczników powiadamiających ani polecających poleceniu odczekanie aż zadanie zostanie wykonane, polecenie natychmiast przekaże sterowanie z powrotem do aplikacji. Kolejnym rozwiązaniem jest przesłanie do polecenia znacznika MCI_NOTIFY nakazującego mu poinformować aplikację, gdy wykona już swoją funkcję. Aplikacja będzie mogła wtedy wyświetlić odpowiedni komunikat, odłączyć płytę kompaktową lub wykonać inną niezbędną w tym momencie akcję. Większość poleceń MCI może wysyłać komunikaty powiadamiające. Funkcje mciSetYeldProc() imciGetYeldProc() Jeśli polecimy poleceniu odczekiwać z przekazaniem kontroli aplikacji za pomocą znacznika wait, możemy chcieć, aby interfejs MCI przyzywał co pewien czas funkcje zwrotne wykonujące określone operacje lub testy. Możemy robić to za pomocą funkcji mciSetYieldProc (). Funkcja ta wymaga jako pierwszego parametru identyfikatora urządzenia, jako drugiego parametru adresu funkcji zwrotnej, a jako trzeciego parametru zmiennej typu DWORD przechowującej pewne definiowane przez użytkownika dane, które będą przesyłane funkcji zwrotnej. Jeśli funkcja wykona zadanie, zwraca wartość TRUE. Pokrewna jej funkcja mciGetyieldProc () służy do pobierania adresu innej działającej procedury tego typu w zdefiniowanym urządzeniu. Jeśli prześlemy do polecenia znacznik MCI_NOTIFY lub notify, polecenie zwróci aplikacji kontrolę natychmiast i prześle komunikat powiadamiający do pętli komunikatów (ang. message loop) okna, którego identyfikator obsługi przesłaliśmy definiując adresata komunikatu. Tym oknem może być widok, okno dialogowe lub dowolny inny obiekt klasy wywodzącej się z cwnd, który jest powiązany z działającym w programie oknem (możemy Interfejsy API i zestawy SDK________________________________777 przesłać zmienną składową zawierającą identyfikator obsługi dowolnego legalnego obiektu CWnd). Funkcję obsługi komunikatu MM_MCINOTIFY możemy dodać do aplikacji MFC wpisując do mapy komunikatów Windows makroinstrukcję ON_MESSAGE: BEGIN_MESSAGE_MAP(CMciDlg, CDialog) ON_MESSAGE(MM_MCINOTIFY,OnMCINotify) END_MESSAGE_MAP() Teraz możemy dodać funkcję obsługi, której należy przesłać dwa parametry: pierwszy identyfikujący urządzenie wysyłające komunikat powiadamiający i drugi definiujący kod statusu zwracany przez polecenie. Funkcja ta powinna wyglądać mniej więcej tak: void CMciDlg::OnMCINotify(WPARAM wFlags,LPARAM !DevID) { AfxMessageBox ("Komunikat MCI: Wykonywanie polecenia zostało zakończone!") ; } Zestaw kodów wFlags informujących o statusie polecenia przedstawiliśmy w tabe 28.6. Zmienna l De vi D identyfikuje urządzenie, które wysyła komunikat powiadamiając; Jeśli korzystamy z łańcuchów poleceń i identyfikujemy urządzenia za pomocą aliasóv możemy zdobyć prawdziwy identyfikator urządzenia przyzywając funkcję mciDetDevice (), której przesyłamy alias jako parametr służący nam za nazwę urządzenia. Tabela 28.6. Kody statusu urządzenia w komunikatach powiadamiających Kod statusu polecenia Opis MC I_NOT l FYJSUCCE s S FUL Wykonywane polecenie zakończyło się sukcesem MC I_NOT l FY_FA l LURE w tracie wykonywania polecenia wystąpił błąd MCI_NOTIFY_ABORTED Komunikat powiadamiający poprzedniego polecenia został usunięty przez następne polecenie skierowane do tego samego urządzenia, niekompatybilne z poprzednim MCI_NOTIFY_SUPERESEDED Nowe polecenie zażądało komunikatu powiadamiającego od tego samego urządzenia. Polecenia są kompatybilne i stare polecenie zostało zastąpione przez nowe 778 _______________ Poznaj Visual C++ 6 Sprawdzanie kodów zwrotnych funkcji poleceń Należy zawsze sprawdzać kod zwracany przez funkcję, która domaga się komunikatu powiadamiającego. Jeśli funkcja polecenia zwróci kod informujący o tym, że nie udało jej się wykonać zadania, komunikat powiadamiający nie zostanie wysłany. Może to doprowadzić do tego, że program zawiesi się oczekując komunikatu powiadamiającego, który nigdy nie nadejdzie. Na listingu 28.4 prezentujemy obie formy wysyłania poleceń Command String i Command Message, które wykorzystywane są tam do odtworzenia pięciosekundowego fragmentu zapisu dźwiękowego z kompaktu (pomiędzy 12 a 17 sekundą trzeciej ścieżki utworu). Przedstawiony tam fragment kodu możemy dodać w dowolnym miejscu aplikacji MFC (w przypadku aplikacji opartej na oknie dialogowym fragment ten najlepiej umieścić W funkcji InitDialog () ). Aby załączyć do programu znaczniki i prototypy funkcji interfejsu MCI, należy wpisać w pliku nagłówka następująca dyrektywę i n cłu de: łinclude "mmsystem.h" Aplikacje korzystające z bibliotek MCI powinny łączyć się z plikiem biblioteki winmm.lib. Plik ten należy wpisać w liście Object/L;ibrary Modules na karcie Link okna dialogowego Project Settings. Listing 28.4. LST32_4.CPP - fragment kodu opartego na poleceniach MCI odgrywający pięć sekund muzyki z płyty kompaktowej 1 // Odegraj 5 sekund muzyki z płyty za pomocą łańcucha polecenia 2 char szRetMsg[80]; 3 char szErrorMessage[512]; 4 MCIERROR errOpen; 5 errOpen = mciSendString("open cdaudio alias mycd wait", O 6 szRetMsg,sizeof(szRetMsg),m hWnd); 7 if (szRetMsg[0]!='l') 8 { 9 // Wyświetl komunikat o błędzie podczas otwierania 10 mciGetErrorString(errOpen,szErrorMessage,512) ; 11 AfxMessageBox(szErrorMessage) ; 12 ) 13 else 14 { 15 // Zdefiniuj format wyświetlania informacji o czasie 16 mciSendString("set mycd time format tmsf", 17 szRetMsg,sizeof(szRetMsg),m_hWnd); Interfejsy API i zestawy SDK 779 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 // Odegraj 5 sekund ze ścieżki 3 poczynając od sekundy 12 mciSendString( "play mycd from 3:0:12:0 to 3:0:17:0 wait", szRetMsg,sizeof(szRetMsg),m_hWnd) ; // ** Zamknij urządzenie mciSendString("close mycd", szRetMsg,sizeof(szRetMsg),m hWnd) ; // Odegraj 5 sekund muzyki z płyty za pomocą, komunikatu polecenia MCI_OPEN_PARMS myCDOpen=(NULL,O,"cdaudio",NULL,NULL}; © errOpen = mciSendCommand(NULL,MCI_OPEN, MCI_OPEN_TYPE|MCI_WAIT,(DWORD)SmyCDOpen) ; if (errOpen) { // Wyświetl komunikat o błędzie podczas otwierania mciGetErrorString(errOpen,szErrorMessage,512) ; AfxMessageBox(szErrorMessage) ; } • else { // Zdefiniuj format wyświetlania informacji o czasie MCI_SET_PARMS setParams = (NULL, MCI_FORMAT_TMSF,O} ; errOpen=inciSendCommand (myCDOpen.wDeviceID,MCI SET, MCI_SET_TIME_FORMAT,(DWORD)&setParams) ; // Odegraj 5 sekund ze ścieżki 3 poczynając od sekundy 12 MCI_PLAY_PARMS playParams = {NULL, MCI_MAKE_TMSF(3, O, 12, 0), © MCI_MAKE_TMSF(3, O, 17, 0)}; © mciSendCommand(myCDOpen.wDeviceID,MCI_PLAY, MCI_FROM | MCI_TO l MCI_WAIT, (DWORD)SplayParams) ; // ** Zamknij urządzenie MCI_GENERIC_PARMS genParams = (NULL); mciSendCommand(myCDOpen.wDeviceI D,MCI_CLOSE, NULL,(DWORD)SgenParams) ; O Łańcuch polecenia open definiuje alias urządzenia wykorzystywany przez następne polecenia. 780 Poznaj Visual C++ 6 @ Znacznik MCI_OPEN komunikatu polecenia odpowiada łańcuchowi open. @ Wartości czasu są dla komunikatów poleceń definiowane za pomocą makroinstrukcji MCI_MAKE_TMSF. Na początku listingu 28.4 w linii 5 przyzywana jest funkcja mciSendString () otwierająca odtwarzacz płyt kompaktowych i nadająca mu alias mycd. Jeśli urządzenia nie uda się otworzyć w liniach 10 i 11, wyświetlany jest odpowiedni komunikat o błędzie. Korzystamy w tym celu z pomocy funkcji mciGetErrorString (), która przekształca kod błędu w dający się zrozumieć łańcuch tekstu. W linii 16 definiujemy format wyświetlania czasu time format jako tmsf informując MCI, jak ma traktować wysyłane później polecenia ustalające nową pozycję po s iti on w odtwarzanym utworze (definiowaną w minutach i sekundach). W linii 19 rozpoczynamy odgrywanie płyty kompaktowej za pomocą polecenia play i definiujemy opcjonalne polecenia from i to pozwalające odegrać fragment utworu między 12 a 17 sekundą trzeciej ścieżki utworu. Znacznik wait poleca funkcji zwrócić aplikacji kontrolę dopiero po odegraniu całego fragmentu. W linii 23 zamykamy urządzenie. Ten sam fragment ścieżki odgrywany jest za pomocą komunikatów poleceń. Urządzenie jest otwierane za pomocą znacznika MCI_OPEN w linii 29. Format czasu definiowany jest za pomocą komunikatu MCI_SET poprzez przesłanie znacznika MCI_SET_TIME_ FORMAT i przypisanie formatu tmsf zmiennej składowej MCI_FORMAT_TMSF struktury setParams. W linii 49 odgrywany jest ten sam co poprzednio fragment ścieżki za pomocą makroinstrukcji MCI_MAKE_TMSF wykorzystywanej do definiowania momentu początkowego i momentu końcowego odgrywanego fragmentu, które w tym celu zapisujemy w strukturze MCI_PLAY_PARAMS. Na koniec w linii 54 urządzenie jest zamykane za pomocą komunikatu MCI_CLOSE. Dodawanie do aplikacji okna MCI Jak dotąd opisywaliśmy interfejs MCI od strony kodu programu, nie wspominając ani słowem o interfejsie użytkownika. Istnieje jednak standardowy interfejs MCI implementowany w klasie MCiWnd. Klasa MCiWnd oferuje wysoko wyspecjalizowaną kontrolkę pozwalającą użytkownikowi na pełną kontrolę nad funkcjami wykorzystywanego urządzenia multimedialnego. Interfejs kontrolki różnić się będzie znacznie w zależności od urządzenia, które obsługuje. Dla urządzenia digi tal video otrzymamy okno i kontrolki umożliwiające odtwarzanie animacji, natomiast dla urządzenia waveaudio kontrolki umożliwiające odgrywanie, przewijanie i zatrzymywanie muzyki zapisanej w pliku .wav. Interfejsy API i zestawy SDK 781 Okno MCiwnd dodajemy do aplikacji w sposób bardzo prosty. Przykładowo, wpisując na końcu funkcji initDialogO jedno tylko odwołanie do funkcji MCiWndCreate () dodajemy do okna dialogowego kontrolkę obsługującą urządzenie digitalvideo pozwalającą odtwarzać pliki .avi (rysunek 28.5). OK Cancel Rysunek 28.5. Okno MCI dołączone do standardowego okna dialogowego za pomocą funkcji MCiWndCreate() Przyzwanie funkcji MCiWndCreate () tworzy okno MCiWnd i inicjuje gotowy do odtworzenia plik .avi zdefiniowany na końcu odwołania: HWND hMCI = MCiWndCreate(m_hWnd, AfxGetApp()->m_hlnstance, MCIWNDF_SHOWALL, "C:\\Vidclip.avi:") ; Pierwszy parametr m_hWnd jest identyfikatorem obsługi okna rodzica (okna dialogo- wego). Drugi jest identyfikatorem egzemplarza obiektu aplikacji. Trzeci parametr pozwala zdefiniować jeden z licznych stylów okna przedstawionych w tabeli 28.7. Ostatni parametr pozwala zdefiniować plik o rozpoznawanym przez MCI rozszerzeniu lub urządzenie MCI, które chcemy otworzyć. Typ wyświetlonej kontro Iki będzie odpowiadać zdefiniowanemu urządzeniu lub rozszerzeniu. Jeśli urządzenie zostanie otwarte, zwracany jest odpowiedni identyfikator obsługi okna MCI (zapisywany w zmiennej hMCi). Jeśli nie, zmiennej hMCI przypisywana jest wartość NULL. Tabela 28.7. Znaczniki funkcji MCiWndCreate Znacznik Opis efektu MCIWNDF_SHOWALL MCIWNDF_SHOWNAME MCIWNDF SHOWMODE Okno korzysta z wszystkich znaczników stylów typu SHOW Wyświetla nazwę pliku lub urządzenia na pasku tytułowym oknaMCIWnd Wyświetla na pasku tytułowym bieżący tryb urządzenia: odtwarzanie, nagrywanie itp. 782 Poznaj Visual C++ 6 MCIWNDF_SHOWPOS MCIWNDF_RECORD MCIWNDF_NOAUTOSIZEWINDOW MCIWNDF_NOAUTOSIZEMOVIE MCIWNDF_NOERRORDLG MCIWNDF_NOMENU MCIWNDF_NOOPEN MCIWNDF_NOPLAYBAR MCIWNDF_MOTIFYALL MCIWNDF_NO'TIFYMODE MCIWNDF_NOTIFYPLAY [!] MCIWNDF_NOTIFYMEDIA MCIWNDF_NOTIFYSIZE MCIWNDF NOTIFYERROR Wyświetla na pasku tytułowym bieżącą pozycję w odtwarzanym pliku Jeśli urządzenie pozwala na nagrywanie, dodaje odpowiedni przycisk Nie zmienia automatycznie wymiarów okna, gdy zmienią się wymiary obrazu Nie rozszerza rozmiarów obrazu do rozmiarów okna jeśli wymiary okna zostaną zmienione Blokuje wyświetlanie komunikatów o błędach interfejsu MCI Usuwa przycisk rozwijanego menu Uniemożliwia użytkownikowi otwieranie innych plików Ukrywa wszystkie, kontro Iki użytkownika - wszystkim zarządza program Przesyła oknu rodzica wszystkie komunikaty Przesyła oknu rodzica wszystkie komunikaty o zmianie trybu urządzenia Przesyła oknu rodzica komunikaty o pozycji w odtwarzanym mateńale Przesyła oknu rodzica komunikaty o zmianie nazwy pliku lub urządzenia Przesyła oknu rodzica komunikaty o zmianie wymiarów oknaMCIWnd Przesyła oknu rodzica komunikaty o błędach MCI Zmienianie stylów okna MCI Style już istniejącego okna MCI możemy zmieniać za pomocą funkcji MCiWndChan-geStylesf). Funkcja ta wymaga jako parametrów: identyfikatora obsługi okna MCI, maski bitowej wskazującej zmieniane style i zmiennej z nowym zestawem ustawień dla okna. Powiązana z nią makroinstrukcja MCiwndGetStyles () zwraca bieżące ustawienia znaczników dla zdefiniowanego okna. Gdy okno MCiwnd jest otwarte, możemy przesyłać mu odpowiednie komunikaty kon- trolek za pomocą specjalnie zdefiniowanych makroinstrukcji. Makroinstrukcje te przesyłają komunikaty takie jak MCI_PLAY i inne odpowiadające poleceniom wymienionym w Interfejsy API i zestawy SDK 783 tabeli 28.5. Makroinstrukcji związanych z komunikatami okna MCiWnd jest bardzo wiele, te które odpowiadają poleceniom z tabeli 28.5, przedstawiliśmy w tabeli 28.8. Tabela 28.8. Makroinstrukcje okna MCiWnd Makroinstrukcja Komunikat Opis MciWndOpenDialog MCIWNDM OPEN Otwiera nowy plik lub urządzenie MCI MciWndOpen - l Otwiera nowy plik lub urządzenie MCI MciWndClose MCI CLOSE Zamyka plik lub urządzenie MCI MciWndPlay MCI PLAY Odgrywa plik lub urządzenie MciWndRecord MCIRECORD Nagrywa w urządzeniu MciWndStop MCI STOP Zatrzymuje odtwarzanie lub nagrywanie MciWndSeek MCISEEK Odnajduje pozycję MciWndPause MCI PAUSE Pauzuje odgrywanie MciWndResume MCIRESUME Uruchamia po pauzie MciWndPutDest MCI PUT DEST Ustala pozycję okna wyświetlającego film lub animację MciWndPlayFromTo MCI_SEEK, MCIWNDM PLAYTO Odtwarza od miejsca do miejsca najpierw wysyłając komunikat odszukujący pozycję, a potem odgrywając materiał do ustalonej pozycji Ustalanie bieżącego trybu okna Może się zdarzyć, że po przesłaniu do okna MCI polecenia będziemy chcieli sprawdzić jego bieżący tryb działania (czy odgrywa materiał, czy nagrywa itp.). Służy do tego instrukcja MCiWndGetMode (). Musimy jej przesłać identyfikator obsługi okna oraz wskaźnik do bufora i rozmiary bufora, który będzie przechowywał łańcuch lub strukturę zawierającą informacje o bieżącym trybie. Możliwe tryby okna to: MCI_MODE_NOT_READY, MCI_MODE_OPEN, MCI_MODE_PLAY, MCI_MODE_RECORD, MCI MODĘ PAUSE,MCI MODĘ SEEK,MCI MODĘ STOP. Makroinstrukcje te wymagają również przesłania identyfikatora obsługi okna MCI i w zależności od potrzeb jeszcze kilku innych parametrów. Przykładowo, aby odegrać plik .avi od pozycji pliku l do pozycji 5, należy wpisać: MCIWndPlayFromTo(hMCI,l,5) ; 784 Poznaj Visual C++ 6 Okno MCI możemy zniszczyć korzystając z makroinstrukcji MdWndDestroy i iden- tyfikatora obsługi hMCI: MCIWndDestroy (hMCI) ; Przesyłamy w ten sposób po prostu zamykający okno komunikat WM_CLOSE. Przedstawione tutaj makroinstrukcje możemy wykorzystywać w dowolnej aplikacji oferującej nam legalny identyfikator obsługi okna rodzica. Aby dodać do programu definicje makroinstrukcji i prototypy funkcji, należy załączyć następujący plik nagłówka: łinclude "vfw.h" Aby dodać do programu funkcje API okna MCIWnd, należy do listy Object/Library Modliłeś na karcie Link okna dialogowego Project Settings dodać plik biblioteki vfw28.1ib. Okno to wywołujemy wybierając w menu Project polecenie Settings. PATRZ TAKŻE • Okno MCIWnd jako kontrolka ActiveX opisane zostało w rozdziale 9. Słownik ActiveX patrz OLE adres bazowy (ang. base address) - adres definiujący początek obszaru zajmo- wanego przez obiekt w pamięci. adres uzupełniający (ang. offset address) również adres przesunięcia; adres wykorzystywany w połączeniu z ad- resem bazowym identyfikujący element składowy (zmienną, funkcję) określonego przez adres bazowy eg- zemplarza obiektu. aktywny (ang. active) - jeden z dwu moż- liwych stanów okna, drugim jest stan nieaktywny. anizotropiczny (ang. anisotrophic) obiekt, element, rzecz mająca w jednym kie- runku inne właściwości niż w pozo- stałych kierunkach. ANSI od American National Standards Institute (amerykańska instytucja normalizacyjna) - w żargonie kom- puterowym standard znaków teksto- wych, w którym każdemu znakowi przypisana jest inna liczba o rozmiarach jednego bajtu. API patrz interfejs API aplikacja MDI - aplikacja oparta na ar- chitekturze dokument/widok pozwa- lająca na jednoczesne otwieranie więcej niż jednego dokumentu, w odróżnieniu od aplikacji SDI, w której jednorazowo otwarty może być tylko jeden dokument. aplikacja SDI - aplikacja oparta na archi- tekturze dokument/widok pozwalająca na jednoczesne otwieranie, w od- różnieniu od aplikacji MDI, tylko jednego dokumentu, który jednak może być przedstawiany jednocześnie w kilku widokach. AppWizard - kreator, narzędzie progra- mowania umożliwiające automatyczne tworzenie szkieletu projektu Visual C++. architektura dokument/widok - podsta- wowa zasada konstrukcji aplikacji. Patrz również aplikacja MDI i aplikacja SDI. archiwum, plik archiwum (ang. archiye) - plik zawierający dane potrzebne apli- kacji w postaci serializowanej lub w postaci pliku dyskowego. asercja (ang. asertions) - test wykorzysty- wany podczas debugowania programu ostrzegający programistę, że warunek zapisany w makroinstrukcji ASSERT nie został spełniony. Instrukcji ASSERT używa się zazwyczaj do testowania, czy wstępne warunki funkcji są poprawne. aspect ratio - angielski termin oznaczający stosunek długości do szerokości. Również parametr opisujący stosunek długości do szerokości pikseli wy- świetlanych na ekranie monitora. Ma istotne znaczenie, gdy zależy nam na unikaniu zniekształceń obrazu. Słownik ActiveX patrz OLE adres bazowy (ang. bose address) - adres definiujący początek obszaru zajmo- wanego przez obiekt w pamięci. adres uzupełniający (ang. offset address) również adres przesunięcia; adres wykorzystywany w połączeniu z ad- resem bazowym identyfikujący element składowy (zmienną, funkcję) określonego przez adres bazowy eg- zemplarza obiektu. aktywny (ang. active) - jeden z dwu moż- liwych stanów okna, drugim jest stan nieaktywny. anizotropiczny (ang. anisotrophic) obiekt, element, rzecz mająca w jednym kie- runku inne właściwości niż w pozo- stałych kierunkach. ANSI od American National Standards Institute (amerykańska instytucja normalizacyjna) - w żargonie kom- puterowym standard znaków teksto- wych, w którym każdemu znakowi przypisana jest inna liczba o rozmiarach jednego bajtu. API patrz interfejs API aplikacja MDI - aplikacja oparta na ar- chitekturze dokument/widok pozwa- lająca na jednoczesne otwieranie więcej niż jednego dokumentu, w odróżnieniu od aplikacji SDI, w której jednorazowo otwarty może być tylko jeden dokument. aplikacja SDI - aplikacja oparta na archi- tekturze dokument/widok pozwalająca na jednoczesne otwieranie, w od- różnieniu od aplikacji MDI, tylko jednego dokumentu, który jednak może być przedstawiany jednocześnie w kilku widokach. AppWizard - kreator, narzędzie progra- mowania umożliwiające automatyczne tworzenie szkieletu projektu Yisuał C++. architektura dokument/widok - podsta- wowa zasada konstrukcji aplikacji. Patrz również aplikacja MDI i aplikacja SDI. archiwum, plik archiwum (ang. archive) - plik zawierający dane potrzebne apli- kacji w postaci serializowanej lub w postaci pliku dyskowego. asercja (ang. asertions) - test wykorzysty- wany podczas debugowania programu ostrzegający programistę, że warunek zapisany w makroinstrukcji ASSERT nie został spełniony. Instrukcji ASSERT używa się zazwyczaj do testowania, czy wstępne warunki funkcji są poprawne. aspect ratio - angielski termin oznaczający stosunek długości do szerokości. Również parametr opisujący stosunek długości do szerokości pikseli wy- świetlanych na ekranie monitora. Ma istotne znaczenie, gdy zależy nam na unikaniu zniekształceń obrazu. 786 Poznaj Visual C++ 6 ASSERT - instrukcja sprawdzająca, czy podany w niej warunek jest prawdziwy. Wykorzystywana przy usuwaniu błędów z programu. Patrz również asercja. asynchronicznie (ang. asynchronousły) ~ dzięki technice asynchronicznego komunikowaniu się z urządzeniami, takimi jak twardy dysk czy czytnik CD, program może kontynuować wy- konywanie zadań bez konieczności czekania na odpowiedź od urządzenia, które jest w danej chwili zajęte wykonywaniem innego zadania. atrapy funkcji (ang. function stubs) -patrz klasa przesyłająca. bezpieczeństwo typologiczne (ang. type safety) - zasada polegająca na wyko- rzystywaniu w prototypach funkcji nazw tworzonych przez nas klas zamiast nazw klas bazowych, z których są wywodzone, dzięki czemu kompilator będzie nas informował, kiedy funkcji zostanie przesłany lub też zostanie przez nią zwrócony niewłaściwy typ klasy. biblioteka DLL - biblioteka zawierająca skompilowany kod obiektu lub zasobu, z którym program może się łączyć dynamicznie, zazwyczaj przechowy- wany w pliku .dli. biblioteka importująca (ang. import libra- ry) - statyczna biblioteka przechowująca kod pozwalający identyfikować i przyzywać funkcje biblioteki DLL, zazwyczaj tworzona automatycznie przez kompilator. biblioteka MFC (od ang. Microsoft foun- dation ciasses) - zestaw klas dołączony do kompilatora Visual C++ przyspieszający i upraszczający two- rzenie aplikacji. biblioteka statyczna (ang. static link li- brary) - biblioteka zawierająca skompilowany kod obiektowy; można ją dołączać do aplikacji w czasie kompilacji, zazwyczaj przechowywana w pliku .lib. bufor (ang. buffer) - tablica bajtów wyko- rzystywana do tymczasowego przecho- wywania danych podczas transferowania ich pomiędzy programem a plikiem dyskowym lub w trakcie operacji wej- ścia/wyjścia. bufor dźwiękowy (ang. waveform buffer) - obszar pamięci przechowujący wartości liczbowe wykorzystywane do od- grywania fali dźwiękowej w karcie muzycznej. chroniona (ang. protected, protected acces) - klasa zadeklarowana jako chroniona pozwala na sięganie do jej funkcji i zmiennych składowych tylko klasom z niej wywiedzionym. Zmienne chronione dostępne są tylko dla klas wy- wiedzionych z danej klasy. chroniona zmienna składowa patrz chro- niona. CLSID - identyfikator klasy COM, 128- bitowa niepowtarzalna liczba identy- fikująca każdą klasę obiektów COM. COM (od ang. component object model) - technika programowania rozszerzająca zasady programowania obiektowego poza zakres pojedynczego programu. Dzięki tej technice różne programy mogą kontaktować się ze sobą za pośrednictwem odpowiednio zdefiniowanych interfejsów i korzystać nawzajem ze swoich obiektów (nawet jeśli są one napisane w innych Słownik 787 językach), bez konieczności posiadania wiedzy o dokładnej lokalizacji wykorzystywanych obiektów. Obiekty te (komponenty) mogą być uruchamiane wewnątrz korzystającego z nich programu, wewnątrz innego programu lub nawet na innym komputerze. COM/OLE patrz COM i OLE. Component Gallery - (galeria komponen- tów) lista zarejestrowanych kontrolek i obiektów COM obsługiwanych przez kompilator Developer Studio, które możemy dołączać do projektu. CScrollYiew - klasa MFC oferująca pod- stawowe funkcje umożliwiające two- rzenie widoków z paskami przewijania pozwalających na oglądanie rysunków lub arkuszy informacji większych niż rozmiary okna. DAO (od ang. database acces objects) - specyficzny stworzony przez Micro-soft system sięgania do baz danych współdziałający z bazami danych ko- rzystającymi z Microsoft Jet Engine. DDE (od ang. dynamie data exchange) - dawna windowsowa technika transferu danych między aplikacjami. Mimo że można jeszcze korzystać z tego mechanizmu, znacznie lepiej używać techniki OLE. debugowanie (ang. debugging) - usuwanie błędów; proces odszukiwania i napra- wiania błędów w kodzie programu. definicja klasy (ang. ciass definition) -część kodu programu przechowywana zazwyczaj w pliku nagłówka (.h) de- finiująca funkcje i zmienne składowe tworzące klasę. Definicje powinny być poprzedzone słowem kluczowym języka C++, ciass. destruktor (ang. destructor) - specjalna funkcja klas C++ o nazwie identycznej z nazwą klasy (za wyjątkiem po- przedzającego ją znaku ~). Destruktor oczyszcza pamięć zajmowaną przez obiekty klasy i wykonuje inne zdefi- niowane przez nas operacje sprzątające. DLL patrz biblioteka DLL. .dli - plik dynamicznie dołączanej do pro- gramu biblioteki zawierającej kod kontrolki ActiveX, funkcje wykorzy- stywane przez plik wykonywalny, ikony lub inne skompilowane zasoby. dokowanie (ang. docking) - łączenie okna typu paska kontrolnego, takiego jak pasek narzędziowy, okno dialogowe czy pasek stanu, z oknem obramo-wującym w taki sposób, że pasek kontrolny wydaje się być częścią tego okna. dokument (ang. documenf) - zbiór danych. dwukrotne kliknięcie - operacja polegająca na kliknięciu przez użytkownika klawiszem myszy dwa razy w krótkim odstępie czasu. dynamiczna kontrolka Edit (ang. dynamie edit central) również dynamiczny obiekt sterujący pola edycji; kontrolka Edit tworzona na moment na czas realizacji określonego zadania i usuwana zaraz potem. W tym celu alokowany jest odpowiedni obszar pamięci oczyszczany zaraz po wykonaniu zadania. dynamiczne okno dzielone (ang. dynamie splitter window) - okno dzielone dy- namicznie tworzone i niszczone w za- leżności od działań użytkownika. dynamicznie (ang. dynamie) - w trakcie działania programu, przez program. 788 Poznaj Visual C++ 6 dziedziczenie (ang. inheritance) - tworzenie nowej klasy w oparciu o możliwości innej klasy. Patrz podklasa. Eksplorator Windows (Windows Explo-rer) - standardowa aplikacja systemu Windows umożliwiająca użytkownikowi oglądanie oraz manipulowanie katalogami i plikami na dysku. element główny (ang. root node) - pod- stawowy, najwyższy element drzewa (kontrolki Tree) •exe - plik wykonywalny służący do uru- chamiania programu. fabryka klas (ang. ciass factory) - klasa służąca do tworzenia obiektów innych klas będących jej podklasami. format (ang.-format) - powszechnie akcep- towany standard prezentacji danych. fraktal Mandelbrota - fraktal odkryty przez informatyka Benoit Mandelbrota. funkcja globalna (ang. global function) - funkcja, która ma globalny zakres (kompetencji) i może być przywoływana w dowolnym miejscu programu. funkcja obsługi (ang. handler) patrz funkcja obsługi komunikatów Windows. funkcja obsługi komunikatów Windows (ang. Windows message handler) - funkcja przyzywana, gdy aplikacja otrzymuje związany z nią komunikat. Funkcja interpretuje komunikat i od- powiednio na niego reaguje. funkcja obsługi komunikatu (ang. message handler) patrz przechwyty- wanie komunikatów. funkcja odwzorowująca czcionki (ang. font mapper) - system Windows posiada zestaw funkcji, które dobierają i tworzą w oparciu o zainstalowane w komputerze czcionki czcionkę najlepiej odpowiadającą zdefiniowanym kryteriom. funkcja przeładowywana (ang. overloa-ded function) - funkcja, która może posiadać więcej niż jedną definicję. Patrz również przeładowywanie operatorów. funkcja wirtualna (ang. virtual function) - funkcja definiowana słowem kluczowym virtual. Funkcja wirtualna jest implementowana w klasie bazowej, a następnie w klasach z niej wywie- dzionych pokrywana (przedefimowa-na) poprzez dodanie do standardowego kodu dodatkowych operacji. W języku C++ funkcje wirtualne są podstawowym narzędziem pozwalającym na uzyskanie polimorfizmu (kilku różnych wersji klasy lub funkcji). funkcja wyliczeniowa (ang. enumerator) - funkcja przeszukująca po kolei listę elementów, na przykład czcionek, przyzywająca dla każdego elementu funkcję zwrotną. funkcja zwrotna (ang. caliback function) - funkcja przywoływana z innej funkcji, kiedy zajdzie odpowiednie zdarzenie lub w trakcie operacji wyliczeniowych. funkcje odcinające (ang. clipping func- tions) - funkcje pozwalające definiować obszar ekranu lub aplikacji, który będzie poza zasięgiem wszelkich funkcji rysujących. funkcje wysyłające (ang. dispatch func- tions) patrz klasa wysyłająca. GDI patrz interfejs GDI. Słownik 789 generator liczb pseudolosowych (ang. pseudo rondom number generator) generator pseudolosowy; algorytm tworzący listę liczb, które wydają się być losowe, w rzeczywistości jednak losowe nie są, ponieważ lista może być odtworzona, jeśli rozpoczniemy ją od tej samej liczby bazowej. Gopher server - popularny niegdyś protokół serwera służący do przesyłania plików i dokumentów zanikający od czasu pojawienia się sieci WWW. graficzna pozycja kursora (ang. graphics cursor position) - współrzędne kursora przechowywane przez kontekst urządzenia definiujące ostatnią pozycję rysowania, która może być wyko- rzystywana w następnych operacjach graficznych. GUID - niepowtarzalny 128-bitowy iden- tyfikator wykorzystywany w klasach i interfejsach COM. HMENU - identyfikator obsługi obiektu menu systemu operacyjnego Windows. HRESULT patrz SCODE. HTML (od ang. hypertext markup langu- age) - tekstowy język tworzenia stron intemetowych WWW, zawierający etykiety pozwalające załączać ilustracje i hiperłącza, które umożliwiają użytkownikowi przenoszenie się po- między różnymi stronami intemeto- wymi za pomocą kliknięcia myszą. identyfikator obsługi (ang. handle) - we- wnętrzny numer identyfikujący obiekty w systemie Windows (np. okna), przy- znawany na bieżąco w czasie działania systemu. IID - identyfikator interfejsu COM. 128- bitowa liczba gwarantująca niepowta- rzalność identyfikatora. ikona (ang. icon) - zasób zawierający dwie mapy bitowe wykorzystywane do tworzenia ikony. IMPLEMENT_DYNAMIC patrz infor- macje czasu wykonania o klasie. implementacja klasy (ang. ciass imp' mentation) patrz ko d implementacji. informacja o atrybucie (ang. attribute information) - informacje przechowujące domyślne kolory, tryby rysowania i akcje, które będą wpływać na działanie funkcji rysujących. informacje czasu wykonania o klasie (ang. runtime ciass information) - klasy MFC wywodzące się z klasy CObject, mogą przechowywać informacje definiujące daną klasę za pomocą makroinstrukcji DECLARE_J)YNAMIC lub innych pokrewnych. Aplikacja będzie mogła teraz ustalić klasę wykorzystywanego obiektu za pomocą makroinstrukcji RUNTIME_CLASS lub funkcji IsKindOf. informacje o typie (ang. type information) - informacje opisujące różne parametry, które należy przesłać funkcjom interfejsu przesyłającego. Informacje te można grupować w biblioteki zwane bibliotekami typów (ang. type li- braries). interfejs API (ang. application program- ming interface) - interfejs programo- wania aplikacji, specjalistyczne biblio- teki zawierające funkcje pomagające obsługiwać operacje określonego typu zazwyczaj związane z określonym elementem sprzętu, np. kartą graficzną. 790 Poznaj Visual C++ 6 interfejs GDI (ang. gmphics device interfa- ce) - zestaw standardowych obiektów i metod, które mogą być wykorzystywane przez aplikację do rysowania. interfejs MCI - biblioteka API zawierająca zbiór łatwych w użyciu funkcji obsłu- gujących różne operacje multimedial- ne, takie jak odtwarzanie kompaktów, filmów, plików .wav czy skanowanie. interfejs przesyłający OLE (ang. dispatch interface) patrz klasa przesyłająca. izotropiczny (ang. isotropic) - mający takie same właściwości we wszystkich kierunkach. język MIDL (od ang. Microsoft interface definition language) - język wyko- rzystywany do tworzenia definicji in- terfejsów i klas COM, do pisania od- wołań RPC i ogólniej do przesyłania parametrów między różnymi aplika- cjami. język SOŁ (od ang. structured query langu- age) - standardowy, tekstowy protokół służący do odczytywania i zmieniania informacji zapisanych w bazach danych. justowanie (ang. alignment) - definiowanie sposobu wyrównywania wiersza tekstu: do lewej strony, do prawej strony czy centralnie. kalibrowanie (ang. calibrate) - seria testów wykonywanych na urządzeniu w celu sprawdzenia, czy będzie działać prawidłowo. klasa interfejsu (ang. interface ciass) patrz klasa przesyłająca. klasa wysyłająca (ang. dispatch ciass) -klasa dostarczająca atrap funkcji po- zwalających poprzez odpowiedni in terfejs przyzywać funkcje obiektów COM. klasy kolekcji (ang. collection ciasses) - klasy takie jak tablice czy listy połą- czone, służące do przechowywania pewnej liczby obiektów, umożliwiające iteracyjne przeszukiwanie prze- chowywanych obiektów. klincz (ang. deadlock) - zakleszczenie; sytuacja, w której dwa zadania aplikacji oczekują wzajemnie na zasób drugiego, w efekcie czego żadne z nich nie może być wykonane. kod implementacji (ang. implementation code) - część kodu zawierająca właściwy wykonywany kod funkcji składowych klasy. kody drukarki (ang. printer codes) - po- wszechnie akceptowane kody wysyłane do drukarki polecające jej wykonać określoną funkcję, na przykład rozpoczęcie drukowania nowej strony. kolor ekranu (ang. screen color) - kolor wykorzystywany podczas edytowania ikony do zdefiniowania obszarów, które mają być przezroczyste. kolor odwracany (ang. inverse color) - podczas edytowania ikony kolor wy- stępujący w tych miejscach mapy bi- towej, które powinny zmieniać barwę w czasie przeciągania ikony. kolor pierwszego planu (ang. foreground color) - w aplikacji z elementami gra- ficznymi kolor uruchamiany gdy użytkownik kliknie lewym klawiszem myszy. kolor tła (ang. bacground color) - kolor wywoływany przez użytkownika pod- czas rysowania prawym klawiszem myszy. Słownik 791 komórka (ang. celi) - prostokątny obszar pamięci przechowujący czcionkę znaku tekstowego (włącznie z białym tłem znaku). komunikat (ang. message) patrz zdarzenie. komunikat powiadamiający (ang. notifi- cation message) - specjalny typ ko- munikatu Windows wysyłany przez kontrolkę, aby poinformować program, że użytkownik wykonał na niej jakąś operację. konstruktor (ang. constmctor) - specjalna funkcja klas C++ nosząca tę samą na- zwę co jej klasa, służąca do tworzenia i inicjowania obiektu klasy. kontekst urządzenia (ang. device comext) - obiekt systemu Windows oferujący wspólny interfejs dla różnych urządzeń sprzętowych realizujących operacje graficzne, takich jak monitor czy drukarka. kontener (ang. container) - w terminologii OLE aplikacja, która może wewnątrz swojego obszaru uruchamiać i wy- świetlać inne aplikacje. Zazwyczaj integrowana z osadzanym w ten sposób obiektem za pomocą zestawu standardowych funkcji. kontrolka trójstanowa (ang. tri-state con- trot) również trójstanowy obiekt ste- rujący; pole wyboru lub przełącznik, który posiada trzy różne ustawienia, co umożliwia wybór między trzema możliwościami. kontrolki ActiveX (ang. ActiveX contols) - gotowe do wykorzystania w programie kontrolki przyśpieszające tworzenie aplikacji, posiadające właściwości, które można definiować lub odczytywać za pośrednictwem interfejsów COM/OLE. Kontrolki te moż na wykorzystywać również na stronach WWW w razie potrzeby ładując i uruchamiając potrzebną kontrolkę dynamicznie. końcowy element drzewa (ang. leaf node) - element kontrolki Tree bez elementów potomnych. kursor (ang. cursor) - przechowywana w zasobach mapa bitowa reprezentująca punkt wskazywany na ekranie przez mysz. Kursor posiada tzw. gorący punkt (ang. hot-spot) definiujący dokładne wspófrzędne wskazywane przez mysz. Jib patrz biblioteka statyczna liczba bazowa (ang. seeding number) patrz generator liczb pseudolosowych. lista połączona (ang. linked list) - lista, w której każdy element przechowuje wskaźniki do poprzedniego i następnego elementu listy. łańcuch wytaczany przez zero (ang. null- terminated string) - standardowy sto- sowany w języku C++ sposób ozna- czania końca łańcucha znaków, który polega na wpisaniu jako ostatniego znaku zera. Jest to dość dobra metoda, ponieważ znaki ASCII wprowadzane z klawiatury zazwyczaj nie przyjmują wartości mniejszych od 32 (spacja). mapa bitowa (ang. bitmap) - tablica danych zawierająca informacje o po- szczególnych pikselach rysunku. mapa bitowa tła (ang. bacground bitmap) ~ rysunek wyświetlany jako tło w in- terfejsie użytkownika. mapa komunikatów (ang. message map) -w terminologii MFC obsługiwany przez makroinstrukcje mechanizm łączenia 792 Poznaj Visual C++ 6 komunikatów z odpowiednimi funkcjami języka C++. MCI patrz interfejs MCI. MDI patrz aplikacja wielodokumentowa. megabajt (ang. megabyte) - jednostka pamięci równa 1048576 bajtom. metaplik rozszerzony (ang. enhanced metafile) - format plikowy przecho- wujący rysunki w postaci niezależnych od sprzętu funkcji rysujących. Rozszerzony format metapliku wyko- rzystywany jest gtównie w aplikacjach Win32. metody (ang. methods) - zestaw funkcji przynależnych do określonej klasy. MFC patrz biblioteka MFC. Microsoft clip gallery - zbiór rysunków clip- art., dołączony do pakietu Microsoft Office. Rysunki te są zaprojektowane specjalnie w celu zataczania do dokumentów użytkownika. Microsoft Intemet Explorer 4 ~ przeglą- darka internetowa stworzona przez Microsoft. Microsoft QuickView - rozprowadzany z systemem Windows 95 program po- zwalający na przeglądanie różnych typów plików. MIDL patrz język MIDL. miejsce dokowania (ang. docking site) - miejsce, w którym pasek kontrolny jest dokowany w oknie obramowującym. modalne okno komunikatów (ang. modal message box) - okno komunikatu wstrzymujące inne operacje użytkow- nika, dopóki nie rozpatrzy on komu- nikatu związanego z oknem. moment wyłączenia strumienia elektronów (ang. yertical blanking period) -moment następujący, gdy monitor zakończy wyświetlanie kolejnej klatki ekranu i strumień elektronów jest wytaczany, dopóki punkt wodzący nie powróci do lewego górnego rogu ekranu. mora (ang. moire fringe) technika rysowania polegająca na nałożeniu dwóch zestawów przesuniętych względem siebie linii. możliwości urządzenia (ang. device capa- bilities) - parametry urządzenia; tech- niczne informacje na temat urządzenia dołączone do kontekstu urządzenia mogące mieć wpływ na proces ryso- wania. multimedia - w środowisku PC możliwość korzystania z różnych środków me- dialnych: filmów, płyt kompaktowych, plików muzycznych, skanowania rysunków itp. nachylenie (ang. escapement) - nachylenie czcionki tekstu względem pionowej osi x w dziesiątych częściach stopnia. numer porządkowy (ang. ordinal number) - numer porządkowy wykorzystywany do identyfikowania funkcji prze- chowywanych w bibliotece DLL. obudowywanie (ang. encapsulate) również hermetyzowanie; przechowywanie danych jednego dokumentu wewnątrz innego zazwyczaj całkowicie od- miennego dokumentu. •ocx - plik zawierający kod implementujący kontrolkę ActiveX. ODBC (od ang. open database connectmty) - zestaw standardowych sterowników zawierających funkcje pozwalające Słownik 793 aplikacji łączyć się z każdą baza danych zgodną z tym standardem. odbity komunikat powiadamiający (ang. reflected notification message) - ko- munikat powiadamiający, który został przesłany kontrolce przez okno rodzica, a następnie jest przez nią przesyłany do odpowiedniej funkcji obsługi. odcinanie, przycinanie (ang. clipping, clip) — ograniczanie obszaru okna, ekranu, który ma być w danym momencie odmalowywany przez system. Patrz również funkcje odcinające. odwołanie do koloru (ang. color reference) - 24-bitowa zmienna przechowująca informacje o nasileniu barw czerwonej, zielonej i niebieskiej w wykorzystanym do rysowania kolorze. odwzorowywanie współrzędnych (ang. coordinate mapping) patrz tryby od- wzorowywania. OLE (od ang. objęci linking and embe-ding) - technika umożliwiająca programowi wykorzystywanie wewnątrz własnego okna możliwości innego programu. Przykładowo, możemy uruchomić wewnątrz edytora tekstu arkusz kalkulacyjny. Arkusz ten jest w tym momencie obsługiwany przez program serwer taki jak Excell, a nie przez goszczący go procesor tekstu. OLE-aktywny (ang. OLE enabled) -obiekt lub widok mogący korzystać z techniki OLE, w celu wyświetlania i wykorzystywania danych z innych programów. orientacja (ang. orientation) - nachylenie osi tekstu względem osi x, mierzone w dziesiątych częściach stopnia. orientacja pionowa (ang. portrait) - ułożenie strony w drukarce, gdy jej wysokość jest większa niż szerokość. orientacja pozioma (ang. landscape) - ułożenie kartki papieru podczas dru- kowania w taki sposób, że jej szerokość jest większa od długości. otaczanie (ang. rubber banding) - technika zaznaczania grupy elementów przez rozciąganie myszą kreskowanego prostokąta w taki sposób, aby objął wszystkie elementy, które chcemy za- znaczyć. paleta systemowa (ang. system palette) - zestaw podstawowych kolorów prze- chowywany przez system Windows służący do obsługi aplikacji, które nie potrzebują specjalnie wyrafinowanej gamy kolorów. pamięć globalna (ang. global memory) - termin Windows opisujący pamięć alokowaną przez system operacyjny, która może być widoczna dla innych aplikacji, przechowywaną w przestrzeni adresowej systemu operacyjnego zamiast w lokalnej przestrzeni adresowej programu. paski przewijania (ang. scrollbars) - pio- nowe lub poziome kontrolki pozwa- lające na przewijania (przesuwanie) obrazu w oknie, na którego brzegach są umieszczone. piksel (ang. pixel) - najmniejsza część obrazu, która może być modyfikowana przez program. podsystem Win32 (ang. Win32 subsystem) - część systemu (interfejs API) im- plementująca podstawowe funkcje systemu operacyjnego w Windows. 794 Poznaj Visual C++ 6 późne wiązanie (ang. late binding) - infor- macja o typie precyzowana nie w trakcie kompilowania programu, a dopiero w trakcie jego działania. programowanie obiektowe patrz zorien- towany obiektowo. proporcjonalne dostosowywanie wymia- rów (ang. proportionaly spaced) - ob- szar zajmowany przez czcionkę jest tym węższy im węższy jest znak. Litera I będzie zajmować mniej miejsca niż litery W czy M. prywatna (ang. private, private acces) — zmienna lub funkcja zadeklarowana w ten sposób jest niedostępna dla funkcji spoza jej klasy bez pomocy zdefiniowanych w klasie funkcji do- stępu. 'Zadeklarowanie dostępu do klasy jako prywatnego czyni wszystkie zmienne i funkcje tej klasy prywatnymi. prywatny element (ang. private member) - zmienna lub obiekt dostępny tylko dla metod jego własnej klasy. Inne klasy muszą korzystać z funkcji dostępu (ang. accessfunction). przechwytywanie komunikatów (ang. catching) tworzenie funkcji obsługi, która będzie przywoływana za każdym razem, gdy zajdzie określone zdarzenie. przeglądarka (ang. browser) - aplikacja lub widok wyświetlający dane w określony sposób. Przykładowo, przeglądarka sieciowa Microsoft Internet Explorer 4 wyświetla internetowe strony WWW oparte na stronach hipertekstu korzy- stając z języka HTML. przeładowywanie operatorów (ang. ope- rator overloads) — technika pozwala- jąca implementować w klasie funkcje, które reprezentowane będą przez jeden ze standardowych operatorów składniowych języka C++ (=,>,+=,% itp.). Funkcja musi zostać opatrzona słowem kluczowym operator. Parametry funkcji wprowadzane są po prawej stronie znaku operatora. przełączniki (ang. radio controls) również przyciski opcji, przyciski radiowe; grupa opcji, z których jednocześnie może być wybrana tylko jedna. Wybór jednej z nich oznacza, że pozostałe zo- staną automatycznie wyłączone. przenoszenie bitów (ang. bit bliting) - szybkie kopiowanie prostokątnego obszaru (tablicy) bitów z jednego miejsca w drugie, zazwyczaj związane z możliwościami sprzętu. przetwarzanie wstępne (ang. prepro- cessing) - pierwszy etap procesu kompilacji, w którym otwierane są definicje makroinstrukcji, pozwalający za pomocą dyrektyw łinclude i łifdef zdecydować, które fragmenty kodu nie powinny być kompilowane. przyjazny dla użytkownika (ang. user friendly) - termin informatyczny za- rezerwowany dla zrozumiałych i łatwych w obsłudze interfejsów użytkownika. publiczna (ang. public, public acces) - deklaracja klasy, która powoduje, że jej zmienne i funkcje składowe są swobodnie dostępne dla innych klas. W ten sposób można deklarować również poszczególne zmienne i funkcje. publiczna zmienna składowa patrz pu- bliczna. punkty kontrolne (ang. breakpoints) - znaczniki umieszczane przez progra- mistę w określonych liniach kodu, Słownik 795 polecające kompilatorowi przerwać w tym miejscu wykonywanie programu. relacje przestrzenne (ang. geometrical relationship) - relacje definiujące położenie jednego elementu, obiektu względem drugiego, na przykład na prawo, powyżej, poniżej. ręczne edytowanie (ang. iniine editing) - bezpośrednie edytowanie tekstu ele- mentu w kontrolce List lub Tree. W ten sposób zmieniamy na przykład nazwę pliku w Eksploratorze Windows. rozdzielczość (ang. resolution) - wartość definiująca najmniejszy rozpoznawalny element pewnej całości. rozmiary (ang. dimensions) wymiary; liczby-definiujące długość lub szerokość obiektu, niezależnie od jego położenia w przestrzeni. schemat bazy danych (ang. database schema) - zestaw informacji o strukturze oraz wyglądzie pól i tablic baz danych. schowek (ang. Clipboard) - działający w systemie Windows wspólny dla wszystkich programów bufor pozwa- lający użytkownikom kopiować dane wewnątrz aplikacji, a także pomiędzy różnymi aplikacjami. SCODE - 32-bitowa zmienna wykorzy- stywana w licznych funkcjach OLE do przesyłania informacji o błędach. SDI patrz aplikacja SDI. serializacja (ang. serialization) - proces przekształcania zbioru zmiennych w jednolity strumień danych lub na odwrót: przekształcanie strumienia danych w zbiór zmiennych. serializować (ang. serialize) patrz seriali- zacja. SQL patrz język SQL. standardowy szkielet aplikacji (ang. stan- dard framework) - podstawowy szkielet aplikacji wykorzystywany w aplikacjach SDI i MDI. status modułu (ang. module state} -wskaźnik do informacji definiujących atrybuty i status pamięci dla określonego modułu programu, takiego jak plik .exe czy biblioteka DLL. statyczne okno dzielone (ang. static split-ter window) - okno dzielone konstruowane podczas pisania aplikacji. sygnalizator (ang. indicator) - mały panel na pasku stanu wyświetlający tekst (lub ikonę), przekazujący określone informacje o statusie lub konfiguracji. synchronizacja (ang. synchronize) - takie zarządzanie sięganiem przez wątki (ang. thread) do zasobów, które ustawia oczekujące wątki w kolejce. szablon (ang. template) - dodatkowy element języka C++ pozwalający tworzyć automatycznie funkcje i klasy określonego typu za pomocą standar- dowego kodu. szablon dokumentu (ang. document tem- plate) - zbiór klas C++ dokonujący dynamicznego tworzenia klas doku- mentu, widoku i okna obramowujące-go w aplikacjach SDI i MDI. szablon okna dialogowego (ang. dialog template) - zasób aplikacji opisujący układ kilku kontrolek dialogowych połączonych razem, by skonstruować współdziałający z użytkownikiem in- terfejs okna dialogowego. 796 Poznaj Visual C++ 6 szeregowanie (ang. marshaling) - proces pakowania parametrów funkcji do po- staci nadającej się do transmisji między procesami lub między różnymi komputerami. szkielet (ang. framework) również osnowa; standardowy kod oferujący kilka pod- stawowych usług wspólnych dla wszystkich aplikacji. szkielet aplikacji SDI (ang. SDI framework) patrz aplikacja SDI. środowisko Windows (ang. Windows enviroment) - wszystkie programy działające w systemie Windows. tablica akceleratorów (ang. accelerator tobie) - jeden z zasobów projektu za- wierający tablicę struktur definiujących kombinacje klawiszy wykorzystywane do przesyłania aplikacji poleceń. tablica łańcuchów (ang. string tobie) -zasób projektu zawierający tablicę łańcuchów. TCHAR - typ zmiennej znakowej. Jeśli korzystamy ze standardu UNICODE, zmienna TCHAR jest zmienną dwu- bajtową (wchar_t), jeśli nie jedno- bajtową (cha r). TCP/IP (od ang. Transmission Control Protocollintemet Protocot) — zbiór standardowych definicji tworzących protokół komunikacji między kom- puterami dla rozproszonych sieci komputerowych i internetu. trwały (ang. persistent) - ustawienia kon- trolki, które zostają zapisane na dysku dzięki czemu po jej ponownym uru- chomieniu pozostają takie same. tryb odwzorowywania (ang. mapping modę) - tryb kontekstu urządzenia pozwalający przekształcać współrzędne wyrażone w pikselach na współrzędne wyrażone w jednostkach używanych w świecie zewnętrznym. Patrz również współrzędne logiczne. tryby rysowania (ang. drawing modes) - różne tryby definiujące sposób wyko- nania operacji renderowania w stan- dardowych funkcjach rysujących. tworzenie egzemplarza (ang. instantiate) - tworzenie egzemplarza obiektu klasy w postaci obiektu w pamięci. tworzenie podklasy (ang. subciassing) - tworzenie nowej klasy o rozszerzonych możliwościach w oparciu o klasę już istniejącą. Unicode - zestaw znaków, w którym każ- demu znakowi przypisywana jest liczba dwubajtowa, co pozwala odwzorować wszystkie stosowane na świecie symbole literowe. URL (od ang. Universal Resource Loca-tor), adres, standardowy kodowy typ zapisu definiujący lokalizację zasobu w Internecie lub na lokalnych dyskach komputera. Przykładowo kod http: //www.chaosl.demon.co.uk informuje, iż zasób (http) jest dostępny pod intemetowym adresem www.chaosi.demon.co.uk. ustalony rozmiar (ang. fixed pitch) - sposób wyświetlania znaków tekstu polegający na tym, że każdy ze znaków zajmuje tyle samo miejsca (litera I rezerwuje sobie prostokąt tej samej szerokości co litera W). Najczęściej wykorzystywany do wyświetlania sformatowanych liczb. ytable - tablica funkcji wirtualnych tworzona przez kompilator dla każdej Słownik 797 klasy. Wykorzystywana w COM do definiowania interfejsów. wektor (ang. vector) - koncept matema- tyczny definiujący kierunek i odległość. wersja debugowana (ang. debug yersion) — wersja projektu zawierająca asercje i punkty kontrolne umożliwiające prze- glądanie kodu źródłowego w trakcie działania programu. wersja dla klienta (ang. relase yersion) - wersja programu z której zostały usu- nięte wszystkie informacje i narzędzia wykorzystywane w procesie debugo- wania, przygotowana do sprzedaży klientowi. węzeł gałęzi drzewa (ang. branch node) - element kontrolki Tree (drzewa), który posiada własne podelementy. widok (ang. view) - wizualna reprezentacja danych dokumentu przedstawiana wewnątrz okna. widok List - widok wyświetlający nazwy, a czasami również ikony elementów w postaci listy. widok Rich Edit - widok pozwalający użytkownikowi edytować tekst i załą- czać obiekty OLE. Obsługuje kilka najbardziej podstawowych operacji formatowania tekstu dostępnych w pro- cesorach tekstu. widok Tree również widok drzewa; widok wyświetlający hierarchiczną strukturę elementów tekstowych z folderami, które dają się rozwijać pokazując swoje elementy potomne. Przykładem może być lista katalogów w lewym panelu Eksploratora Windows. Win32 patrz podsystem Win32. Windowsowa architektura otwartych usług - zestaw standardów, takich jak TCP/IP, RCP i MAPI, wykorzysty- wanych do integrowania systemów Windows i pokrewnych z innymi systemami pracującymi w sieci. wirtualne kody klawiszy (ang. virtual key codes) - niezależne od urządzenia zmienne definiujące klawisze klawiatury (przykładowo VK_ESCAPE). wskaźnik (ang. pointer) - typ zmiennej służący do przechowywania adresu innej zmiennej lub obiektu. Wskaźnik pozwala różnym funkcjom przekazywać sobie adres elementu, na którym operują. wskaźnik funkcji (ang. function pointer) - specjalny typ wskaźnika języka C++ przechowujący adres początku funkcji. wspólna pamięć (ang. shared memory) - pamięć wykorzystywana wspólnie przez kilka działających aplikacji, wykorzystywana do transferu informacji między nimi. Aby uniknąć przypadkowego zapisania zawartości pamięci, zarezerwowana pamięć jest niedostępna dla innych aplikacji. współrzędne logiczne (ang. logical coor- dinates) - współrzędne wyrażone nie w pikselach, ale w jednostkach sto- sowanych w normalnym świecie (na przykład w centymetrach). Stosowana jednostka zależy od konkretnego trybu odwzorowywania. współrzędne (ang. coordinates) - liczby definiujące relatywne położenie punktu w przestrzeni w stosunku do zadeklarowanego arbitralnie punktu odniesienia, z którego wyprowadzamy prostopadłe osie współrzędnych. Na płaszczyźnie oś x i oś y. 798 WYSIWYG - akronim od angielskiego zwrotu „What you see is what you get" oznaczającego, że użytkownik uzyskuje na wydruku dokładnie to, co widzi na ekranie. wysokość znaku (ang. chdracter heighf) - wysokość obszaru zajmowanego przez czcionkę znaku. z poziomu programu (ang. programmati- cally) - wywoływanie zdarzenia sztu- cznie z kodu programu. zasoby aplikacji (ang. application resour-ce) patrz zasoby. zasób (ang. resource) dodatkowy element projektu nie będący częścią wykony- wanego kodu programu przechowujący jednak pewne niezbędne dane, takie jak: mapy bitowe, ikony, łańcuchy informacji itp. zbiór Mandelbrota patrz fraktal Mandel- brota. zdarzenie (ang. event) - komunikat prze- syłany do aplikacji przez system Windows informujący o czynności Poznaj Visual C++ 6 wykonanej przez użytkownika lub o zmianie pewnych warunków ze- wnętrznych. zmienna lokalna (ang. local vańable) - zmienna której zakres kompetencji ogranicza się do lokalnej funkcji. znak (ang. character) - litera alfabetu lub symbol numeryczny definiowany jako czcionka. zorientowany obiektowo - technika pro- gramowania polegająca na dopasowy- waniu konstrukcji programu opartej na klasach i obiektach do problemu, który program rozwiązuje. W ten sposób powstaje bardziej wygodne w użyciu i łatwiejsze w konstruowaniu opro- gramowanie składające się z modułów zawartych w osobnych obiektach, które komunikują się z innymi obiektami za pośrednictwem obopólnie akcepto- wanych interfejsów. Skorowidz "gumowa taśma", 72 AbortDoc(), 592 ActiveX Control Test Container, 711 Add(), 608 AddBar(), 355; 356 AddNewO, 646 AddRefO, 656 AddStńngO, 135; 241 AddTail(),481 adres docelowego bufora, 617 AfxGet-, 335 AfxGetApp(), 548 AfxGetMainWnd(), 548 Afx01elnt(). 630 agregacja COM, 744; 754 AmbientBackColor(), 690 AmbientForeColorO, 690 API,653;741' aplikacja uruchamianie, 25 jednodokumentowa, 277 SDI - elementy wizualne, 280 SDI - tworzenie, 274 SDICoin, 277 wielodokumentowa, 534 AppendMenu(),317;319 ApplyColor(), 342 AppWizard, 22; 58 ASSERT makro, 482; 727 ASSERTJCINDOF makro, 530 AttachCIipboardO, 632 BCHAR, 441 BEGIN_MESSAGE_MAP makro, 83 BeginPaint(), 372 biblioteka MFC, 683 typów, 673 biblioteki multimedialne, 772 bieżący przycisk sterujący, 69 bit blitting, 374 BitBItO, 373; 374 bitmapa, 47 Blt(), 755 BN_CLICKED, 79; 82 bufor dźwiękowy, 743 podrzędny bufor, 744 podstawowy bufor, 743 zwrotny, 773 CacheGlobalData(), 628 catching, 186 CButtonsDIg.cpp, 83 CCommandLineInfoO, 545 CDocument DeleteContents, 289 OnCloseDocument, 289 OnNewDocument, 288 OnOpenDocument, 288 OnSaveDocument, 289 CeateInstanceLic(), 710 CDialog::OnOK(),50 CEdit::OnChar(), 121 CenterPointO, 398; 399; 469 ChangeDialogTitle(), 87; 89 Chord(),415 chronione zmienne składowe, 550 ciass factory, 284 CIassYiew, 38; 88 CIassWizard, 93; 111 ClearSelO, 167 ClearTicsO, 167 ClickCountMessage(), 51 Cmci, 215 CMouseMsgDIgO, 200 CoCreateInstance(), 667; 676; 744; 754 CoCreateInstanceEx(), 661 ColorChange(), 342 COM, 654 COM/OLE. 205 COM+, 653 CommandLineInfo, 285 CommandToIndex(), 349 Component Gallery, 206 coolbar, 354 CountClipboardFormat(), 626 Create(),236;237;516 CreateBrushIndirectO, 413 CreateColorBrush(), 410 CreateCompatibleBitmapO, 373 CreateCompatibleDCO, 373 CreateDispatch(), 676 CreateEx(), 325; 344 CreateFont(), 435 CreateFontIndirectO, 440 CreateHatchBrushO, 413; 587 Createlnstance(), 660 CreateNewDocumcntO, 547 CreateNewFrameO, 288; 547 CreateNewTextPrame(), 546 800 Poznaj Visual C++ 6 CreatePaletteO, 755 CreatePatternBrushO, 413 CreatePenO, 392; 393 CreatePointFontO, 430; 579 CreatePopupMenuO, 312; 319 CreatePrinterDCO, 589 CreateSolidBrush(), 413 CreateSoundBuffei-0, 745 CreayeSysColorBrushO, 413 CSingleDocTemplate: :OpenDocumentPile, 287 CStringFonnat(), 46; 107 cykliczne zmienianie kolorów, 755 czcionka jakość i precyzja, 433 parametry, 432 pochylenia i orientacja, 432 tworzenie, 429 Tnie Type, 434 ustawienia odstępów, 434 usuwanie, 447 wysokości i szerokość, 431 DAO, 641 Data Yalidation, Patrz DDV DCOM, 661 DDE, 625; 627 DDV, 234 debug version, 24 debugowanie, 715 przez sieć, 722 DECLARE_SERIAL makro, 602 DefWindowProc(), 121 Delete(), 646 delete, 236 DeleteAllItems(), 138; 147 DeleteBlobs(), 607 DeleteColumnO, 489 Deleteltem(), 495 DeleteObjectO, 392; 393 DestroyWindow(), 214; 241 Developer Studio, 35 typy plików, 52 DevMode, 582 Dialog Generał Properties, 63 Direct3D, 762; 763 DirectDraw, 752 DirectDrawCreate(), 754 Directlnput, 763 DirectPlay, 763 DirectSetup, 764 DirectSound, 743 DirectSoundCreateO, 744 DirectSoundEnumerate(), 744 DirectSoundInterfaceO, 744 DirectX, 742 DirectXRegisterApplication(), 764 DirectXSetup(), 764 DISP_FUNCTION makro, 671 dithering, 587 DLU,64 DMS, 633 DockControlBar(), 328 DoDataExchange(), 229; 230; 239; 706 DoFieldExchange(), 647 dokument aktywny, 538; 677 dokument/widok, 274 dokumenty złożone, 663 DoModal(), 227 DoPropExchange(), 706 DoVerb(), 679 DPtoLP(), 377 DrawTextEx(), 445 drzewa, 123 DSBUFFERDESC, 745 DSN użytkownika, 638 DTN_DATETIMECHANGE, 176 dualne interfejsy, 664 duży skok przewijania, 468 dynamiczne okno dzielone, 512 dynaset, 640 Edit(), 646 edytor graficzny, 248 egzemplarz, 538 Ellipse(), 195;400 Enable(), 308 EnableScrollBarO, 158 EnableWindow(), 86; 87 EndDialogO, 226 EndDocO, 590; 592 EndPage(), 592 EnumCIipboardFormatO, 626 EnumFontFamilies(), 437; 438 ENUMLOGFONT, 441 Escape(), 593 ExchangeVersion(), 707 ExecWB(), 508; 509 Exitlnstance(), 688 Extended MAPI, 764 extem, 238 ExtTextOut(), 422; 428 Fail(), 235 FileView,51 FillRect(), 408 FilISolidRectO, 342 FindFirstItem(), 139 FindNext(), 770 Fire aplikacja, 154 FireRepositionedO, 696 FlipO, 755 FloatControlBar(), 334 FontCallBackO, 440 formatowanie akapitów, 504 formaty danych schowka, 626 Poznaj Visual C++ 6 funkcja dostępu, 480 pokrywająca, 568 statyczna, 624 wyliczająca, 438 zwrotna, 437 funkcje do kontaktu między klasami, 548 dostępu klasy CPrintDialog, 5S4 wirtualne, 568 GetAt(), 608 GetBackColor(), 208; 212 GetBkColorO, 425 GetCheckO, 97 GetCIientRectO, 195; 377; 399;469;568; 572 GetComputerName(), 106 GetCurrentDirectoryO, 135 GetCurrentPositionO, 395; 398; 442 GetCursorPosO, 190 GetDatabaseO, 641 GetDayO, 175 GetDayOfWeekO, 175 GetDayOfYearO, 175 GetDCO, 36ł; 755; 756 GetDesktopWindow(), 364; 365 GetDeviceCaps(), 381; 382; 385; 574 GetDeviceScrollPosition(), 470 GetDcvMode(), 582; 589 GetDIgCtrK), 168 GetDlgCtrlID(), 161; 163; 168 GetDIgItemO, 80; 84- 87; 168 GetDocument(), 551; 292 GetDsOiNames(), 664 GetEditControl(), 500 GetElements().481;482 GetFileName(), 623 GetFilePath(), 624 GetFileTitleO, 624 GetHour(), 175 GetItemDataO, 495 GetLenghtO, 622 GetLineSize(), 167 GetMinute(), 175 GetMonitorPrequencyO, 756 GetMonthO, 175 GetMonthDeltaO, 180 GetNext(), 482 GetNextItem(), 490; 491; 498 GetNumTicks(), 167 GetPageSizeO, 167 GetProcAddressO, 766; 770 GetRangeO, 154 GetRangeMax(), 166 GetRangeMin(), 166 GetReBarCtriO, 353 GetSafeHwnd(), 461 GetScanLine(), 756 GetScrollInfoO, 159 GetScrollLimitO, 475 GetScrollPosO, 158; 163 GetScrollPosition(), 470 GetScrolIRangeO, 158 GetSecond(), 175 GetSelectedItemO, 498 GetSelection(), 167 GetSelItemsO, 144 GetSeIRangeO, 182 GetStatusO, 622 GetSysColor(), 699 GetSystemDirectoryO, 135 GetTextColor(), 425 GetTextExtent(), 351:352 GetTime(), 175 GetTotalSizeO, 468 GetTreeCtrIO, 495; 524 GetTypeInfo(), 664 GetTypeInfoCount(), 664 GetUserNameO, 107 GetVersionExO, 107 GetWindowDCO. 364 GetWindowLongO, 484; 485 GetWindowsDirectoryO, 135 GetYear(), 175 GoBack(). 508 GoFowardO, 508 GoHomeO, 508 GoSearchO, 508 graficzne biblioteki Windows, 360 GUID, 657 guidgen.exe, 657 HAL, 752 HeightO, 398 HEL, 752 hierarchia klas, 537 HitTestO, 202 HTML, 508 IDC_ENABLE_DISABLE, 81 IDC_SHOW_HIDE, 81 IDC_STATIC, 104 IDD_ABOUTBOX szablon, 60 IDD_BUTTONS_DIALOG, 80 IDD_PERSON_DIALOG szablon, 60 IDĘ, 19 identyfikator, 61; 495 obstugi okna, 485 obsługi pliku, 614 oknu dialogowego, 62 IDL, 662 IDR.MAINFRAME, 540 ikona, 48;247 tworzenie, 250 indykator, 345 InflateRect(),401;415 802 Poznaj Visual C++ 6 informacji czasu wykonania, 521 InitDialogO, 778 InitialUpdateFrame, 288 Initinstance, 282, 613; 629; 670; 688 InsertAt(), 608 InsertColumn(), 145; 489 Insertltem(),136; 270; 481; 489; 495 InsertMenu(), 317 InsertStringO, 135 interfejs jednodokumentowy SDI, 533 przesyłający, 663 wielodokumentowy MDI, 533 interfejsy COM, 655 Invalidate(), 342; 443; 471; 473 InvalidateControl(), 692 InvaIidateRect(), 472 InvalidateRgn(), 472 Invoke(), 664 InvokeHelper(), 208 IsBOF(), 645 IsDataAvailable(), 632 IsEmptyO, 112 IsEOFO, 645 Islconic(), 369 . IsLoadingO, 611 IsPrinting(),571 IsStoringO, 611 IsWindowEnabled(), 87 IsWindowVisible(), 85 jednostka logiczna, 376 urządzenia, 376 kalendarz, 169 klasa abstrakcyjna, 642; 655 CActiveXDlg,213 CArchive, 599 CBitmapButton, 261; 264 CBrush , 405 CButtonsDlg, 82 CCalendar, 215 CChildFrame, 540 CCIientDC, 366 CCmdTarget, 83 CComboBox, 132 CCommandLineInfo, 285 CDaoDatabase, 641 CDaoRecordset, 641 CDatabase, 641 CDataExchange, 234 CDC, 360 CDialog, 79; 223; 225 CDocument, 536; 598 CEdit, 116; 122 CFile, 614; 624 CPileException, 615; CFont, 430 CHtmIYiew, 508 CImageList, 265 CIntemetFile, 625 CListCtrl -, 144 CListVDoc,478;481 CMainFrame -, 280-2; 325; 538; 540 CMDIChildFrame, 538 CMDICoinDoc, 538 CMDICoinYiew, 538; 540 CMemFile, 624 CMenu,312;316 CModeless, 236 CObArray, 606; 608 CObject -, 279 COleClientItem, 679 COleDataObject, 632 C01eDateTime,171;218 COleDateTimeSpan, 182; 218 .COleStreamFile, 625 CPaintDC, 368; 370 CPen,387;392 CPoint-,188 CPreviewDC, 580 CPrintInfo, 570; 577 CProgressCtrl, 152 CReBarCtrl, 353 CRecentFileList,613 CRecordset, 641; 642; 646 CRect, 195 CRect, 398 CRectTracker, 199 CRichWiew, 505 CRotaryCtrl, 685 CRotaryPropPage, 685 CScrollBar, 157 CScrollYiew, 464 CSDICoin -, 279 CSDICoinDoc-,279;291 CSDICoinYiew klasa, 282; 292 CSharedFile, 624 CSingleDocTemplate, 282 CSizeFormYiew, 460 CSocketFile, 625 CSplitterWnd,515;519 CStatic, 257 CStatusBar, 282; 343; 349 CStatusBar, 540 CStringList, 479 CStudioFile, 625 CTime klasa, 171;218 CTimeSpan,218 CToolBar, 282; 540 CTreeCtrl, 127; 136; 524 CTreeCtrl&, 497 CUsingDB, 649 CUsingDBDoc, 643 Poznaj Visual C++ 6 CWinApp, 287; 543 kolekcji, 479 MCIWnd, 780 klasy aplikacji SDI, 278 widoku dla AppWizard, 277 klawisze mnemonik. Patrz klawisze skrótów kod formatowania, 724 kody statusu urządzenia, 777 kolor czcionki, 425 pióra, 388 kompilacja, 24 kompilator MIDL, 710 komponent, 653 komunikaty debugowania, 716 poleceń, 772 powiadamiające, 500 kontekst urządzenia, 359; 517 drukarki, 571 pamięci, 373 rysującego, 394 kontrolka Edit, 460; 619 MCI, 210 Tree, 524 kontrolki ActiveX, 206 OCX, 209 paska dialogowego, 340 VBX, 209 Visual Basie, 209 kursor, 47; 247 LineToO, 396; 471 lista drzewiasta, 126 kombinowana, 124 odcinania, 755 szczegółowa, 129 listy, 123 kombinowane, 123 szczegółowe, 123 LoadBarStateO, 335 LoadBitmap(),261;410 LoadLibraryO, 766; 770 LoadMenu(),312 LoadStandardIcon(), 188 LoadToolbarO, 327 Location pole, 21 Lock(), 746 LockWindowUpdateO, 455 LPtoDP(), 377 łańcuch poleceń, 772 wyłączany przez zero, 618 Maił and Fax aplet, 765 makefile, 53 makroinstrukcje okna MCIWnd, 783 mały skok przewijania, 468 mapa komunikatów, 82 przesyłania, 671 MAPI - funkcje, 654; 764- 766 MapMode, 377 MCI, 772 komunikaty powiadamiające, 776 polecenia, 775 typy urządzeń, 773 mciGetErrorStringO, 774; 780 mciGetYieldProcO, 776 mciSendCommandO, 772; 774 mciSendStringO, 772; 774; 780 mciSetYieldProcO, 776 MCIWndChangeStyles(), 782 MCIWndCreate(), 780 MCIWndGetModeO, 783 MCIWndGetStylesO, 782 MDI, 274 MEMORYSTATUS, 106; 107 menu skrótów, 311 MessageBox(), 45; 57 MFC,21 Tracer, 740 Microsoft Foundation Ciass, Patrz MFC MINMAXINFO, 463 mnemonika, 78 ModeX, 753 ModiryMenuO, 320 moniker, 678 monitor debugowania, 722 mora, 364 MoveTo(), 394; 395;471 Navigate2(), 508 new, 236 NEWTEXTMETRICS, 442 nFlags, 188 niemodalne okna dialogowe, 236 numer licencyjny, 710 obcinanie tekstu, 428 obiekt wyjątku, 615 obiektowa baza danych, 633 obiekty DirectDraw, 753 zorientowane na zakres wartości, 149 obszar roboczy,37 ODBC, 634 Administrator, 636 odbity komunikat powiadamiający, 499 odcinanie, 471 ODL, 668 Poznaj Visual C++ 6 805 polecenia debugera, 732 PolyBezier(), 402-404 Polygon(),416 Połyline, 404 PolyTextOut(), 422 PopulateCombo(), 134 PopulateListBox(), 140 PopulateListControl(), 141 PopulateTreeO, 137; 140 powierzchnia podstawowa, 755 poziom współdziałania, 744 dlaDirectSound,751 PreCreateWindowO, 279 PreTranslateMessageO, 185 PRINTDLG, 583 printf(), 724 proces serwera, 659 Process Viewer, 739 programu pocztowego MAPI, 767 Project Settings okno dialogowe, 54 projekt, 37 nadawanie nazwy, 21 tworzenie, 20 wybór rodzaju, 21 zamknięcie, 34 otwieranie, 35 prostokąt obszaru roboczego, 470 prowadnica, 73 próbnik daty, 169 prywatny element składowy, 483 przechwytywanie myszy, 196 przenoszenie bitów, 752 przycinania obrazu, 711 przyciski, 79 PtInRectO, 198 publiczne funkcje składowe, 538 punkt kontrolny, 592; 723; 730 qsort(), 727 Querylnterface(), 656; 657; 662 Querylnterface(), 754 rand(),751 Read(), 616 ReadMail(), 770 ReadMe.txt, 362 Rectangle(), 413 RedrawWindow(), 365; 366; 368 Refresh(), 508 RegisterClipboardFomiat(), 626; 627 rejestrowanie kontrolki, 710 relacyjna baza danych, 633; 634 Relase(), 632; 656 RelaseDCO, 755; 756 release version, 24 ReleaseCapture(), 197 ReleaseDCO, 361; 364 Remove(), 624 RemoveAt(), 608 Removeltem(), 321 Rename(), 624 ResetContent(), 141 resource.h, 61 ResourceYiew, 46 RestoreDisplayModeO, 761 ręczne edytowanie, 500 RFX, 647 RoundRectO, 413 rozciąganie okna, 451 rozmiary papieru, 585 RPC, 661 rysowanie cięciw, 415 elips i okręgów, 415 krzywych, 402 linii, 396 okręgów i elips, 400 prostokątów i prostokątów z zaokrąglonymi narożnikami, 413 tekstu formatowanego i wieloliniowego, 445 tekstu przezroczystego i nieprzezroczystego, 427 tekstu., 422 w oparciu o współrzędne, 398 wielokątów, 404; 416 za pomocą pióra, 393 rysunki bitmapowe, 247 SaveBarState(), 335 ScrollToPositionO, 577 SDI, 274 SDK, 742 Seek(), 621 SeekToBegin(), 621 SeekToEndO, 621 Select0bject(),374; 390; 412 SelectStockObjectO, 410 separator, 304; 348 serializacja, 597 Serialize(),536; 599; 612 serializowany plik archiwalny, 600 SerMinPageO, 577 serwer automatyzacji, 666 SetAt(), 608 SetBackColor(), 212 SetBkColorO, 425; 485; SetBkMode(), 427 SetCapture(),196;692 SetCheck(),309;310 SetColorO, 180 SetFormatO - kody, 173 SetItemData(), 495 SetItemText(), 489; 501 SetLenghtO, 622 SetLineSizeO, 167 SetMapMode(), 377 SetMaxPage(), 577 806 Poznaj Visual C++ 6 SetMaxSelCount(), 182 SetMonthCalColorO, 175 SetMonthCalFont(), 175 SetMonthDelta(), 180 SetMousePosText(), 353 SetPageSize(), 167 SetPaneInfo(), 349; 352 SetPaneStyle(), 349 SetPaneText(), 349 SetPixel(), 364 SetPos(), 154; 167 SetRange(), 153; 166 SetRangeMax(), 166 SetRangeMin(), 166 SetScrollInfoO, 158; 159 SetScrollPosO, 158 SetScrolIRangeO, 157 SetScrollSizesO, 469 SetSelection(), 167 SetSelRange(), 182 SetStepO, 155 SetText(),331 SetTextAlign(), 423 SetTextColor(), 425; 485 SetTickFreq(),-167 SetTime(), 174 SetTimer(), 761 SetTitle(), 457 SetTodayO, 180 SetTxtBkColor(), 485 SetViewPortExt(), 377; 379; 380 SetViewPortOrg(), 377 SetWindowExt(), 377; 380 SetWindowLongO, 484; 485 SetWindowOrgO, 377 SetWindowPos(),461 SetWindowText(), 188; 455 SetWindowText(), 85; 87; 89 ShowControlBarO, 334; 335 ShowWindow(), 237 ShowWindow(), 84; 85 Sizing to content, 70 skrót klawiaturowy, 78; 305 debugowania, 725 skrypt instalacji/konfiguracji, 709 Slider Properties, 164 snapshot, 640 Source Browser, 719 specjalne komentarze, 84 Spy++,736 SqareRoot(), 674 SQL, 635 polecenia DCL, 635 polecenia DDL, 635 polecenia DML, 635 standardowy pasek narzędziowy, 323; 327 stanu, 343 StartDocO, 590; 592 StartPageO, 592 statyczne okno dzielone, 518 statyczne pole tekstowe, 102 Steplt(), 153 sterownik przesyłania, 664; 674 StopO, 746 stos pędzli, 410 stos piór, 390 strcmpO, 141; 619 strcpyO, 619 strlen(), 619 styl listy, 483 style głównego okna obramowującego, 544 piór, 390 widoku List, 484 suwaki kontrolne, 164 SwapO, 726 szablon widoku Record, 648 wielodokumentowy, 542 szablony dokumentów SDI, 282 szeregowanie, 662 szkielet aplikacji, 563 jednodokumentowej, 564 szum tła, 744 śledzenie zmiennych, 733 TabbedTextOut(), 422 tablica akceleratorów, 47 działających obiektów, 666 funkcji wirtualnych, 655 łańcuchów znakowych, 48 przeglądowa, 702 TEXTMETRICS, 442 Text0ut(), 422 mis, 226; 314 tło okna, 406 TodayO,213 TRACĘ makro, 724 TrackPopupMenu(), 312; 313; 320 TrackRubberBand(), 200; 201 TranslateColor(), 699 tree, 497 tryb Debug,715;717 natychmiastowy, 762 raportu, 483 Relase, 715 tryby odwzorowania, 375; 376 typ POINT, 463 typy funkcji DDX, 231 listy obrazów dla listy drzewiastej, 270 listy obrazów dla pola listy, 270 piór, 388 Poznaj Visual C++ 6 807 układ współrzędnych, 376 Unlock(),746;751 UnlockWindowUpdate(), 455 UpdateO, 646 UpdateAllViews(), 554 UpdateDataO, 95; 107; 112; 229; 230; 239 URL, 508 uruchamianie zdarzeń, 695 YARIANT, 664 VB Scripting, 666 YERIFY makro, 729 View window klasa, 282 Visual SourceSafe, 603 Visual Studio, 19 WaitForVerticalBlank(), 756 warunkowe punkty kontrolne, 724 wewnętrzna aktywacja, 678 wewnętrzne obramowanie obiektu, 678 widok Edit, 503 HTML, 507 List, 477 Record, 648 Rich Edit, 502 Tree, 493 Width(), 398 wieloliniowe pola edycji, 122 wirtualne kody klawiszy, 121 właściwości otoczenia, 697 przez nas definiowane, 697 wyposażenia kontrolek ActiveX, 698 Write(), 616 wskaźnik postępu, 149 wyrównanie tekstu, 423 WYSIWYG, 299 X Pos, 64 Y Pos, 64 zasoby, 26; 46 zasób dialogowy, 48 menu,48 pasków narzędziowych, 48 wersji, 48 zdarzenie Size, 456 Sizing, 453 zmienna chroniona, 479 prywatna, 479 publiczna, 479 zmienne składowe klasy CFileStatus, 623 CPńntInfo, 570 znacznik kontekstowy, 485 znaczniki, 305; 309 formatowania dla DrawText(), 446 funkcji GetNextItem() widoku Tree, 499 funkcji MCIWndCreate, 781 kolorów systemowych dla CreateColorBrush,411 pozycji dokowania paska dialogowego, 339 rodziny typograficznej, 434 stylów widoku Tree, 494 stylu kalendarza, 179 stylu pasków kontenerowych, 354 stylu wzoru pędzla, 406 trybu funkcji CFile::Open(), 616 w funkcji CFile::Seek(), 621 w funkcji GetNextItem, 490 wagi czcionki, 432 wyrównania tekstu, 423 zdarzenia Sizing, 457 zmian kolorystyki kalendarza, 181 zmiennej m_attribute, 623 źródło danych, 636 Notacja węgierska Programiści tworzący aplikacje dla Windows używają tzw. notacji węgierskiej (ang. Hungarian Notation — HN) do identyfikacji typów zastosowanych zmiennych. Większość przykładów oraz przykładowych programów w niniejszej książce wykorzystuje właśnie tę notację. Używanie jej polega na dodawaniu do nazw zmiennych prefiksów, zależnych od typu zmiennej. Poniższa tabela zawiera wykaz owych prefiksów. Typ zmiennej Prefiks Przykład int n nAge char ch chinitial float fl flAngle double- d dSalary unsigned u ulD long l iHours BOOL b bDone WORD w wSize DWORD dw dwError Wskaźnik P pButton Zmienna składowa m mnAge Składowa globalna g g„hWnd Łańcuch znakowy sz szName Znacznik f fChecked Szerokość ex cxCaption Wysokość cy cyClient Prostokąt rc rcdient Okno wnd wndMain Uchwyt h hinstance Punkt Pt ptMouse Okno listy Ib IbNames Skróty klawiaturowe w Visual C++ Kompilacja Skrót Opis F7 Generuj plik wykonywalny aplikacji Shift+F7 Kompiluj bieżący plik F4 Idź do następnego błędu F5 Uruchom debuger Shift+F5 Uruchom aplikację Edytor tekstowy Skrót Opis Ctri+F Znajdź F3 Znajdź następny Ctrl+F3 Znajdź poprzedni CtrI+A Zaznacz wszystko Ctri+C Kopiuj do schowka Ctri+N Utwórz nowy plik lub projekt Ctrl+0 Otwórz CtrI+S Zapisz bieżący plik Ctrl+V Wklej ze schowka Ctri+W Uruchom CIassWizard Ctrl+X Wytnij i przenieś do schowka CtrI+Y Ponów operację Ctri+Z Cofnij operację Edytor zasobów Skrót______Opis Ctri+D Pokaż porządek tabulacji Ctri+T Testuj okno dialogowe Debuger Skrót Opis F9 Ustaw/usuń punkt zatrzymania F10 Debugowanie krokowe Fll Debugowanie krokowe w bieżącej linii CtrI+B Edytuj w punktach zatrzymania Skróty klawiaturowe w Visual C++ Kompilacja Skrót Opis F7 Generuj plik wykonywalny aplikacji Shift+F7 Kompiluj bieżący plik F4 Idź do następnego błędu F5 Uruchom debuger Shift+FS Uruchom aplikację Edytor tekstowy Skrót______Opis CtH+F Znajdź F3 Znajdź następny Ctrl+F3 Znajdź poprzedni Ctri+A Zaznacz wszystko CtrI+C Kopiuj do schowka Ctri+N Utwórz nowy plik lub projekt Ctrl+0 Otwórz CtrI+S Zapisz bieżący plik CtrI+V Wklej ze schowka CtrI+W Uruchom CIassWizard CtrI+X Wytnij i przenieś do schowka Ctri+Y Ponów operację Ctri+Z Cofnij operację Edytor zasobów Skrót Opis Ctri+D Pokaż porządek tabulacji CtrI+T Testuj okno dialogowe Debuger Skrót Opis F9 Ustaw/usuń punkt zatrzymania F10 Debugowanie krokowe Fll Debugowanie krokowe w bieżącej linii CtrI+B Edytuj w punktach zatrzymania ŚHierarchia klas MFC - wersja 6.0 | CObject Rysowanie |——[COC Obsługa kontrolek —ICDockState" —l ClmafleLisT Obiekty GDI ——|CGdiObject~ Menu —— ICMenu -ICCIientDC :MetaFileDC_ :PaintDC •jCBitma •ICBrush •ICFont Wicraz poleceń —ICCommandLIneInto l Bazy danych ODBC ^ ćDatabase ~~ •ICRecordset zbiory rekordów użytkownika —| CLongBinary ~ Bazy danych DAO •j CPaoDatabase 1 CPaoOueryPef •fCDaoRecordset •(CPaoTableDef ^ CDaoWorkspace~ Synchronizacja {CS^róbjert^ Tablice -|CArray(Template) -ICByteArray 4CDWordArray~ -ICStringAn -ICUlntArraj ICWordArray" - tablice użytkownika Listy -ICList (Temp -ICPtrList -ICObList -ICStringLisT -listy użytkownika Mapy ĄCUap (Templał^ -ICMapWordToPtr -) CMapPtrToWoit -jCMapPtrToPtr -ICMapWorJfoi -ICMapStringToPtr -ICMapStringToOb -ICMapStringToStringt -maps of user types Ushigi intemetowe Gniazda Windows CAsyncSocket | CArchive~ l CDumpConteirt ICRuntimeClass Typy proste [CPoint ICRect ' [CSize ' | CString^ ICTime | CTimeSpan Struktury | CCreateContext l CPrintlnf6~ Klasy pomocnicze Klasy nie wywodzące się z klasy CObject API serwerów internetowych | CHtmIStream | | CHItpBIler ~1 | CHttpFilterContext| ICHttpSeryer l l CHttpServerConlext l Obsługa operacji wykonania Synchronizacja ICMuHILock"| iCSingląLock__^j -fCCilticalSection -ICB/ent -(CMutex -fCSemaphore Hierarchia klas MFC - wersja 6.0 (klasy cieniowane na szaro zostaty wprowadzone w tej wersji)