Spis treści O autorze ..................................................................................................................................................19 Podziękowania ........................................................................................................................................ 20 Wstęp ........................................................................................................................................................21
Część I IDE ...........................................................................................................................37 Rozdział 1. Wprowadzenie do IDE ............................................................................................................ 39 Wygląd IDE .................................................................................................................. 40 Konfiguracje IDE ........................................................................................................... 41 Projekty i rozwiązania .................................................................................................... 42 Uruchamianie IDE ......................................................................................................... 43 Tworzenie projektu ....................................................................................................... 44 Zapisywanie projektu .................................................................................................... 47 Podsumowanie ............................................................................................................ 49
Rozdział 2. Menu, paski narzędzi i okna .................................................................................................51 Menu .......................................................................................................................... 51 Menu File ............................................................................................................... 52 Menu Edit .............................................................................................................. 55 Menu View ............................................................................................................. 56 Menu Project .......................................................................................................... 57 Menu Build ............................................................................................................. 62 Menu Debug ........................................................................................................... 63 Menu Data ............................................................................................................. 63 Menu Format .......................................................................................................... 64 Menu Tools ............................................................................................................ 65 Menu Test .............................................................................................................. 69 Menu Window ......................................................................................................... 70 Menu Community .................................................................................................... 71 Menu Help ............................................................................................................. 71
Rozdział 4. Projektant formularzy Windows ........................................................................................ 83 Ustawianie opcji projektanta ......................................................................................... 83 Dodawanie kontrolek .................................................................................................... 85 Zaznaczanie kontrolek .................................................................................................. 85 Kopiowanie kontrolek ................................................................................................... 87 Przenoszenie i zmiana rozmiaru kontrolek ...................................................................... 88 Aranżacja kontrolek ...................................................................................................... 88 Ustawianie własności kontrolek ..................................................................................... 88 Grupowe ustawianie własności ................................................................................. 89 Ustawianie własności kilku kontrolek ........................................................................ 89 Używanie inteligentnych znaczników .......................................................................... 90 Polecenia w oknie Properties ................................................................................... 91 Dodawanie kodu do kontrolek ....................................................................................... 91 Podsumowanie ............................................................................................................ 93
Rozdział 5. Projektant WPF .................................................................................................................... 95 Ostrzeżenie o wczesnej wersji ....................................................................................... 95 Wprowadzenie do okien projektanta ............................................................................... 97 Dodawanie kontrolek .................................................................................................... 98 Zaznaczanie kontrolek .................................................................................................. 99 Kopiowanie kontrolek ................................................................................................. 100 Przenoszenie i zmiana rozmiaru kontrolek .................................................................... 100 Ustawianie własności ................................................................................................. 102 Grupowe ustawianie własności kontrolek ..................................................................... 102 Dodawanie kodu do kontrolek ..................................................................................... 103 Podsumowanie .......................................................................................................... 103
Rozdział 6. Edytor kodu Visual Basica ..................................................................................................105 Ikony na marginesie ................................................................................................... 106 Funkcja Outlining ........................................................................................................ 107 Chmurki .................................................................................................................... 109 Funkcja IntelliSense ................................................................................................... 109 Kolorowanie kodu i wyróżnianie ................................................................................... 111 Fragmenty kodu, które nadają się do użycia w innych aplikacjach ................................... 113 Używanie fragmentów kodu .................................................................................... 114 Tworzenie fragmentów kodu do użytku w innych programach ..................................... 115 Edytor kodu w czasie działania programu ..................................................................... 117 Podsumowanie .......................................................................................................... 118
Spis treści
5
Rozdział 7. Usuwanie błędów .................................................................................................................119 Menu Debug .............................................................................................................. 119 Menu Debug — podmenu Windows ............................................................................. 122 Okno Breakpoints ....................................................................................................... 126 Okna Command i Immediate ....................................................................................... 127 Podsumowanie .......................................................................................................... 129
Część II Wstęp do języka Visual Basic ............................................................................. 131 Rozdział 8. Wybieranie kontrolek Windows Forms .............................................................................133 Przeglądanie kontrolek ................................................................................................ 133 Wybieranie kontrolek .................................................................................................. 138 Kontrolki kontenery ............................................................................................... 139 Wybór opcji ........................................................................................................... 141 Wprowadzanie danych ........................................................................................... 142 Wyświetlanie danych ............................................................................................. 142 Informowanie użytkownika ..................................................................................... 143 Inicjowanie akcji .................................................................................................... 144 Wyświetlanie grafiki ............................................................................................... 146 Wyświetlanie okien dialogowych ............................................................................. 146 Wspieranie innych kontrolek .................................................................................. 147 Kontrolki niestandardowe ........................................................................................... 147 Podsumowanie .......................................................................................................... 148
Rozdział 9. Używanie kontrolek Windows Forms .................................................................................151 Kontrolki i komponenty ............................................................................................... 151 Tworzenie kontrolek .................................................................................................... 153 Tworzenie kontrolek w czasie projektowania ............................................................ 153 Wstawianie kontrolek do kontenerów ...................................................................... 154 Tworzenie kontrolek w czasie działania programu .................................................... 155 Własności .................................................................................................................. 157 Własności w czasie projektowania .......................................................................... 157 Własności w czasie działania programu .................................................................. 162 Przydatne własności kontrolek ............................................................................... 164 Własności położenia i rozmiaru .............................................................................. 168 Metody ...................................................................................................................... 169 Zdarzenia .................................................................................................................. 169 Tworzenie procedur obsługi zdarzeń w czasie projektowania ..................................... 170 Słowo kluczowe WithEvents ................................................................................... 171 Tworzenie procedur obsługi zdarzeń w czasie działania programu .............................. 172 Zdarzenia tablic kontrolek ...................................................................................... 173 Zdarzenia walidacji ................................................................................................ 173 Podsumowanie .......................................................................................................... 178
Rozdział 14. Struktura programu i modułu .........................................................................................269 Ukryte pliki ................................................................................................................ 269 Struktura plików z kodem ............................................................................................ 274 Regiony kodu ........................................................................................................ 275 Kompilacja warunkowa .......................................................................................... 276 Przestrzenie nazw ................................................................................................. 284 Typograficzne elementy kodu ....................................................................................... 286 Komentarze .......................................................................................................... 286 Komentarze XML ................................................................................................... 287 Znak kontynuacji wiersza ....................................................................................... 290 Łączenie wierszy ................................................................................................... 291 Etykiety wierszy ..................................................................................................... 291 Podsumowanie .......................................................................................................... 292
Rozdział 15. Typy danych, zmienne i stałe ...........................................................................................293 Typy danych ............................................................................................................... 294 Znaki oznaczające typy ............................................................................................... 296 Konwersja typów danych ............................................................................................. 299 Konwersja zawężająca ........................................................................................... 299 Metody analizy typów danych ................................................................................. 301 Konwersja rozszerzająca ........................................................................................ 302 Deklarowanie zmiennych ............................................................................................. 302 Lista atrybutów ..................................................................................................... 303 Dostępność .......................................................................................................... 303 Słowo kluczowe Shared ......................................................................................... 305 Słowo kluczowe Shadows ...................................................................................... 305 Słowo kluczowe ReadOnly ...................................................................................... 308 Słowo kluczowe Dim .............................................................................................. 308 Słowo kluczowe WithEvents ................................................................................... 309 Nazywanie zmiennych ............................................................................................ 311 Lista wartości brzegowych ..................................................................................... 311 Słowo kluczowe New ............................................................................................. 313 Określanie typu zmiennej ....................................................................................... 313 Wyrażenie inicjujące .............................................................................................. 314 Deklarowanie kilku zmiennych za jednym razem ....................................................... 318 Opcje Option Explicit i Option Strict .............................................................................. 319 Zasięg zmiennych ....................................................................................................... 322 Zasięg blokowy ..................................................................................................... 322 Zasięg procedurowy .............................................................................................. 323 Zasięg modułowy .................................................................................................. 324 Zasięg przestrzeni nazw ......................................................................................... 324 Ograniczanie zasięgu ............................................................................................. 325 Deklaracje parametrów ............................................................................................... 326 Procedury własności ................................................................................................... 328 Typy wyliczeniowe ....................................................................................................... 330 Anonimowe typy danych .............................................................................................. 333 Typy dopuszczające wartość pustą ............................................................................... 334
Rozdział 16. Operatory ...........................................................................................................................341 Operatory arytmetyczne .............................................................................................. 341 Operatory konkatenacji ............................................................................................... 342 Operatory porównywania ............................................................................................. 343 Operatory logiczne ...................................................................................................... 345 Operatory bitowe ........................................................................................................ 346 Priorytety operatorów .................................................................................................. 347 Operatory przypisania ................................................................................................. 348 Klasa StringBuilder ..................................................................................................... 349 Typ Date i klasa TimeSpan .......................................................................................... 351 Przeciążanie operatorów ............................................................................................. 354 Operatory z typami dopuszczającymi wartość pustą ....................................................... 357 Podsumowanie .......................................................................................................... 358
Rozdział 17. Podprocedury i funkcje ....................................................................................................359 Podprocedury ............................................................................................................. 359 Lista atrybutów ..................................................................................................... 360 Tryb dziedziczenia ................................................................................................. 364 Dostępność .......................................................................................................... 365 Nazwa podprocedury ............................................................................................. 366 Parametry ............................................................................................................. 367 Klauzula Implements interfejs.podprocedura ........................................................... 374 Instrukcje ............................................................................................................. 375 Funkcje ..................................................................................................................... 376 Procedury własności ................................................................................................... 377 Metody rozszerzające ................................................................................................. 378 Funkcje lambda .......................................................................................................... 379 Rozluźnione delegaty .................................................................................................. 381 Metody częściowe ...................................................................................................... 384 Podsumowanie .......................................................................................................... 386
Rozdział 18. Instrukcje sterujące ........................................................................................................387 Instrukcje decyzyjne .................................................................................................... 387 Jednowierszowa instrukcja If Then .......................................................................... 387 Wielowierszowa instrukcja If Then .......................................................................... 389 Instrukcja Select Case .......................................................................................... 390 Wartości wyliczeniowe ........................................................................................... 392 Instrukcja IIF ......................................................................................................... 393 Instrukcja If .......................................................................................................... 395 Instrukcja Choose ................................................................................................. 395 Instrukcje pętlowe ...................................................................................................... 397 Pętla For Next ....................................................................................................... 397 Różne typy zmiennej pętlowej pętli For Next ............................................................. 400 Pętla For Each ...................................................................................................... 401
Spis treści
9
Enumeratory ......................................................................................................... 403 Iteratory ............................................................................................................... 405 Instrukcje Do Loop ................................................................................................ 405 Pętla While End .................................................................................................... 406 Instrukcje Exit i Continue ....................................................................................... 407 Instrukcja GoTo .......................................................................................................... 407 Podsumowanie .......................................................................................................... 410
Rozdział 19. Obsługa błędów ..................................................................................................................413 Błędy a nieplanowane sytuacje .................................................................................... 413 Przechwytywanie błędów ........................................................................................ 414 Przechwytywanie niespodziewanych zdarzeń ............................................................ 416 Globalna obsługa błędów ....................................................................................... 418 Strukturalna obsługa błędów ....................................................................................... 419 Obiekty wyjątków ................................................................................................... 422 Obiekty klasy StackTrace ....................................................................................... 423 Zgłaszanie wyjątków .............................................................................................. 423 Niestandardowe klasy wyjątków ............................................................................. 426 Klasyczna obsługa błędów w Visual Basicu ................................................................... 427 Instrukcja On Error GoTo ........................................................................................ 428 Instrukcja On Error Resume Next ............................................................................ 429 Instrukcja On Error GoTo 0 ..................................................................................... 430 Instrukcja On Error GoTo -1 .................................................................................... 430 Tryb obsługi błędów ............................................................................................... 432 Strukturalna a klasyczna obsługa błędów ..................................................................... 432 Obiekt Err .................................................................................................................. 434 Debugowanie ............................................................................................................. 435 Podsumowanie .......................................................................................................... 436
Rozdział 20. Kontrolki i obiekty baz danych ........................................................................................437 Automatyczne łączenie z danymi .................................................................................. 437 Łączenie ze źródłami danych .................................................................................. 438 Wstawianie kontrolek danych na formularz .............................................................. 441 Obiekty tworzone automatycznie .................................................................................. 445 Inne obiekty danych .................................................................................................... 447 Przeglądanie danych ................................................................................................... 448 Obiekty połączenia ..................................................................................................... 449 Obiekty transakcji ....................................................................................................... 452 Adaptery danych ......................................................................................................... 454 Obiekty poleceń ......................................................................................................... 459 Obiekt klasy DataSet ............................................................................................. 460 Klasa DataTable ................................................................................................... 464 Klasa DataRow ..................................................................................................... 467 Klasa DataColumn ................................................................................................ 468 Klasa DataRelation ............................................................................................... 470 Ograniczenia ......................................................................................................... 472 Klasa DataView .......................................................................................................... 475 Klasa DataRowView .................................................................................................... 478 Proste wiązanie danych .............................................................................................. 479 Klasa CurrencyManager .............................................................................................. 480 Złożone wiązanie danych ............................................................................................. 483 Podsumowanie .......................................................................................................... 487
10
Visual Basic 2008. Warsztat programisty Rozdział 21. LINQ ....................................................................................................................................489 Wprowadzenie do LINQ ............................................................................................... 491 Podstawy składni zapytań LINQ ................................................................................... 493 Klauzula From ....................................................................................................... 493 Klauzula Where ..................................................................................................... 494 Klauzula Order By ................................................................................................. 495 Klauzula Select ..................................................................................................... 496 Wykorzystanie wyników zapytań LINQ ...................................................................... 498 Zaawansowana składnia zapytań LINQ ......................................................................... 499 Słowo kluczowe Join .............................................................................................. 499 Klauzula Group By ................................................................................................. 501 Funkcje agregujące ............................................................................................... 504 Operacje na zbiorach ............................................................................................. 504 Ograniczanie ilości zwracanych wyników .................................................................. 505 Funkcje LINQ ............................................................................................................. 506 Metody rozszerzające LINQ ......................................................................................... 508 Zapytania oparte na metodach ............................................................................... 508 Zapytania oparte na metodach i funkcje lambda ...................................................... 510 Rozszerzanie LINQ ................................................................................................ 512 LINQ to Objects .......................................................................................................... 514 LINQ to XML .............................................................................................................. 515 Literały XML .......................................................................................................... 515 LINQ w XML .......................................................................................................... 516 LINQ z XML ........................................................................................................... 518 LINQ to ADO.NET ........................................................................................................ 521 LINQ to SQL i LINQ to Entities ................................................................................ 521 LINQ to DataSet .................................................................................................... 522 Podsumowanie .......................................................................................................... 525
Rozdział 22. Tworzenie niestandardowych kontrolek .......................................................................527 Kontrolki niestandardowe — informacje ogólne ............................................................ 528 Tworzenie projektu budowy kontrolki ....................................................................... 529 Ustawianie ikony dla okna Toolbox ......................................................................... 529 Testowanie w oknie UserControl Test Container ...................................................... 530 Tworzenie projektu testowego ................................................................................ 531 Testowanie kontrolki ............................................................................................. 532 Implementowanie własności, metod oraz zdarzeń .................................................... 532 Przypisywanie atrybutów ........................................................................................ 534 Zachowanie w czasie projektowania i działania programu ......................................... 536 Kontrolki pochodne .................................................................................................... 536 Przesłanianie funkcjonalności klasy nadrzędnej ....................................................... 539 Ukrywanie funkcjonalności klasy nadrzędnej ............................................................ 540 Kontrolki złożone ........................................................................................................ 541 Budowanie kontrolek od początku ................................................................................ 543 Komponenty .............................................................................................................. 544 Niewidoczne kontrolki ................................................................................................. 545 Wybieranie klasy kontrolki ........................................................................................... 546 Kontrolki i komponenty w projektach wykonywalnych ..................................................... 547 Klasa UserControl w projektach wykonywalnych ....................................................... 547 Dziedziczące kontrolki UserControl w projektach wykonywalnych ............................... 548 Klasa Control w projektach wykonywalnych .............................................................. 548
Spis treści
11
Kontrolki dziedziczone w projektach wykonywalnych ................................................. 549 Komponenty w projektach wykonywalnych ............................................................... 549 Zabezpieczenia niestandardowych komponentów .......................................................... 549 Asemblacje z silną nazwą ...................................................................................... 550 Organ podpisujący ................................................................................................. 552 Podsumowanie .......................................................................................................... 552
Rozdział 23. Operacje typu „przeciągnij i upuść” oraz schowek ......................................................555 Zdarzenia typu „przeciągnij i upuść” ............................................................................ 556 Prosty przykład ..................................................................................................... 557 Sprawdzanie, jakie typy danych są dostępne ........................................................... 559 Przeciąganie w obrębie aplikacji ............................................................................. 560 Przyjmowanie upuszczonych plików ......................................................................... 561 Przeciąganie obiektów ........................................................................................... 562 Zmienianie nazw formatów ..................................................................................... 565 Przeciąganie kilku typów danych ............................................................................. 566 Używanie schowka ..................................................................................................... 568 Podsumowanie .......................................................................................................... 571
Rozdział 24. Funkcja Kontrola konta użytkownika ..............................................................................573 Funkcja Kontrola konta użytkownika — informacje ogólne .............................................. 573 Projektowanie programów pod kątem funkcji Kontrola konta użytkownika ........................ 575 Zwiększanie uprawnień programów .............................................................................. 578 Użytkownik ........................................................................................................... 578 Program wywołujący .............................................................................................. 578 Program wywoływany ............................................................................................. 579 Podsumowanie .......................................................................................................... 580
Część III Programowanie obiektowe ............................................................................. 583 Rozdział 25. Podstawy programowania obiektowego ........................................................................585 Klasy ......................................................................................................................... 585 Hermetyzacja ............................................................................................................. 588 Dziedziczenie ............................................................................................................. 589 Hierarchie dziedziczenia ........................................................................................ 590 Uszczegółowienie i uogólnienie .............................................................................. 591 Związki „ma” i „jest” ............................................................................................. 593 Dodawanie i modyfikowanie własności klas ............................................................. 594 Dziedziczenie interfejsów ....................................................................................... 596 Polimorfizm ................................................................................................................ 597 Przeciążanie .............................................................................................................. 598 Metody rozszerzające ................................................................................................. 599 Podsumowanie .......................................................................................................... 600
Rozdział 26. Klasy i struktury ..............................................................................................................603 Klasy ......................................................................................................................... 603 Lista atrybutów ..................................................................................................... 604 Słowo kluczowe Partial .......................................................................................... 604 Dostępność .......................................................................................................... 605 Słowo kluczowe Shadows ...................................................................................... 606 Dziedziczenie ........................................................................................................ 607
12
Visual Basic 2008. Warsztat programisty Klauzula Of lista_typów .......................................................................................... 609 Klauzula Inherits klasa_nadrzędna ......................................................................... 609 Klauzula Implements interfejs ................................................................................ 610 Struktury ................................................................................................................... 613 Struktury nie mogą dziedziczyć ............................................................................... 614 Struktury są typami wartościowymi ......................................................................... 614 Zajmowanie pamięci .............................................................................................. 615 Sterta i stos ......................................................................................................... 615 Przypisywanie obiektów ......................................................................................... 617 Przekazywanie parametrów .................................................................................... 618 Opakowywanie i odpakowywanie ............................................................................ 619 Tworzenie egzemplarzy klas — szczegóły ..................................................................... 620 Tworzenie egzemplarzy struktur — szczegóły ................................................................ 622 Zbieranie nieużytków .................................................................................................. 624 Metoda Finalize .................................................................................................... 625 Metoda Dispose ................................................................................................... 628 Stałe, własności i metody ........................................................................................... 629 Zdarzenia .................................................................................................................. 631 Deklarowanie zdarzeń ............................................................................................ 631 Zgłaszanie zdarzeń ................................................................................................ 633 Przechwytywanie zdarzeń ....................................................................................... 634 Deklarowanie niestandardowych zdarzeń ................................................................. 636 Zmienne współdzielone ......................................................................................... 640 Metody współdzielone ........................................................................................... 641 Podsumowanie .......................................................................................................... 644
Rozdział 27. Przestrzenie nazw ..........................................................................................................645 Instrukcja Imports ...................................................................................................... 646 Importowanie automatyczne ................................................................................... 648 Aliasy przestrzeni nazw .......................................................................................... 649 Elementy przestrzeni nazw ..................................................................................... 650 Główna przestrzeń nazw .............................................................................................. 650 Tworzenie przestrzeni nazw ......................................................................................... 651 Klasy, struktury i moduły ............................................................................................. 652 Rozwiązywanie przestrzeni nazw .................................................................................. 653 Podsumowanie .......................................................................................................... 656
Rozdział 28. Klasy kolekcyjne ..............................................................................................................657 Czym jest kolekcja ...................................................................................................... 657 Tablice ...................................................................................................................... 658 Wymiary tablicy ..................................................................................................... 659 Dolne granice ....................................................................................................... 660 Zmienianie rozmiaru .............................................................................................. 661 Prędkość .............................................................................................................. 661 Inne cechy klasy Array ........................................................................................... 662 Kolekcje .................................................................................................................... 665 Klasa ArrayList ..................................................................................................... 665 Klasa StringCollection ........................................................................................... 667 Kolekcje ze ścisłą kontrolą typów ........................................................................... 667 Kolekcje tylko do odczytu ze ścisłą kontrolą typów ................................................... 669 Klasa NameValueCollection ................................................................................... 670
Spis treści
13
Słowniki .................................................................................................................... 671 Klasa ListDictionary .............................................................................................. 672 Klasa Hashtable ................................................................................................... 673 Klasa HybridDictionary ........................................................................................... 674 Słowniki ze ścisłą kontrolą typów ........................................................................... 675 Inne klasy ze ścisłą kontrolą typów ......................................................................... 676 Klasa StringDictionary ........................................................................................... 677 Klasa SortedList ................................................................................................... 677 Klasa CollectionsUtil .................................................................................................. 677 Stosy i kolejki ............................................................................................................ 678 Stos .................................................................................................................... 678 Kolejka ................................................................................................................ 679 Klasy ogólne .............................................................................................................. 681 Podsumowanie .......................................................................................................... 683
Rozdział 29. Typy ogólne ......................................................................................................................685 Zalety klas ogólnych ................................................................................................... 685 Definiowanie klas ogólnych ......................................................................................... 686 Konstruktory klas ogólnych .................................................................................... 688 Więcej niż jeden typ .............................................................................................. 688 Typy ograniczone ................................................................................................... 690 Używanie typów ogólnych ............................................................................................ 692 Aliasy Imports ....................................................................................................... 693 Klasy pochodne .................................................................................................... 693 Predefiniowane klasy ogólne ....................................................................................... 694 Metody ogólne ........................................................................................................... 694 Typy ogólne a metody rozszerzające ............................................................................. 695 Podsumowanie .......................................................................................................... 696
Część IV Grafika .............................................................................................................. 699 Rozdział 30. Podstawy rysowania ........................................................................................................701 Przegląd technik rysowania ......................................................................................... 701 Przestrzenie nazw obiektów rysujących ......................................................................... 704 Przestrzeń nazw System.Drawing ............................................................................ 704 Przestrzeń nazw System.Drawing.Drawing2D ........................................................... 705 Przestrzeń nazw System.Drawing.Imaging ............................................................... 708 Przestrzeń nazw System.Drawing.Text ..................................................................... 709 Przestrzeń nazw System.Drawing.Printing ................................................................ 710 Klasa Graphics .......................................................................................................... 712 Metody rysujące .................................................................................................... 712 Metody wypełniające ............................................................................................. 717 Inne własności i metody obiektów klasy Graphics .................................................... 717 Wygładzanie krawędzi ............................................................................................ 720 Podstawy transformacji ......................................................................................... 722 Transformacje — techniki zaawansowane ............................................................... 726 Zapisywanie i przywracanie stanu obiektów klasy Graphics ....................................... 729 Zdarzenia rysowania ................................................................................................... 731 Podsumowanie .......................................................................................................... 733
14
Visual Basic 2008. Warsztat programisty Rozdział 31. Pędzle, pióra i ścieżki .......................................................................................................735 Klasa Pen .................................................................................................................. 735 Własność Alignment .............................................................................................. 738 Własność CompoundArray ..................................................................................... 739 Niestandardowe końcówki linii ............................................................................... 740 Transformacje pióra .............................................................................................. 741 Klasa Brush ............................................................................................................... 743 Klasa SolidBrush .................................................................................................. 744 Klasa TextureBrush ............................................................................................... 744 Klasa HatchBrush ................................................................................................. 746 Klasa LinearGradientBrush .................................................................................... 747 Klasa PathGradientBrush ....................................................................................... 751 Obiekty klasy GraphicsPath ......................................................................................... 756 Kwestia zbierania nieużytków ...................................................................................... 759 Podsumowanie .......................................................................................................... 762
Rozdział 32. Tekst .................................................................................................................................763 Rysowanie tekstu ....................................................................................................... 764 Formatowanie tekstu .................................................................................................. 764 Własność FormatFlags .......................................................................................... 766 Tabulatory ............................................................................................................ 769 Przycinanie ........................................................................................................... 770 Metoda MeasureString ............................................................................................... 771 Metryka fontu ............................................................................................................ 774 Podsumowanie .......................................................................................................... 777
Rozdział 33. Przetwarzanie obrazów .................................................................................................779 Klasa Image .............................................................................................................. 779 Klasa Bitmap ............................................................................................................. 781 Ładowanie obiektów klasy Bitmap .......................................................................... 782 Zapisywanie obiektów klasy Bitmap ........................................................................ 783 Implementowanie własności AutoRedraw ................................................................ 785 Operacje na pojedynczych pikselach ....................................................................... 787 Obiekty klasy Metafile ................................................................................................ 791 Podsumowanie .......................................................................................................... 794
Rozdział 34. Drukowanie ......................................................................................................................795 Jak nie drukować ....................................................................................................... 795 Podstawy drukowania ................................................................................................. 796 Drukowanie tekstu ..................................................................................................... 800 Ustawianie wydruku na środku .................................................................................... 806 Dopasowywanie obrazów na stronie ............................................................................. 809 Upraszczanie rysowania i drukowania ........................................................................... 810 Podsumowanie .......................................................................................................... 813
Część V Interakcja ze środowiskiem ............................................................................. 815 Rozdział 35. Konfiguracja i zasoby .......................................................................................................817 Przestrzeń nazw My .................................................................................................... 818 Obiekt Me a przestrzeń nazw My ............................................................................ 818 Najważniejsze sekcje przestrzeni nazw My ............................................................... 819
Spis treści
15
Środowisko ................................................................................................................ 819 Ustawianie zmiennych środowiskowych ................................................................... 820 Funkcja Environ ..................................................................................................... 821 Obiekt System.Environment ................................................................................... 821 Rejestr ...................................................................................................................... 823 Rodzime funkcje Visual Basica ............................................................................... 824 Przestrzeń nazw My.Computer.Registry ................................................................... 826 Pliki konfiguracji ......................................................................................................... 828 Pliki zasobów ............................................................................................................. 832 Zasoby aplikacji .................................................................................................... 833 Używanie zasobów aplikacji ................................................................................... 833 Zasoby wbudowane ............................................................................................... 835 Zasoby zewnętrzne ................................................................................................ 836 Zasoby lokalizacyjne .............................................................................................. 837 Klasa ComponentResourceManager ....................................................................... 838 Klasa Application ....................................................................................................... 841 Własności klasy Application ................................................................................... 841 Metody klasy Application ....................................................................................... 843 Zdarzenia klasy Application .................................................................................... 844 Podsumowanie .......................................................................................................... 846
Rozdział 36. Strumienie ........................................................................................................................847 Klasa Stream ............................................................................................................. 848 Klasa FileStream ........................................................................................................ 849 Klasa MemoryStream ................................................................................................. 850 Klasa BufferedStream ................................................................................................ 851 Klasy BinaryReader i BinaryWriter ................................................................................ 851 Klasy TextReader i TextWriter ...................................................................................... 853 Klasy StringReader i StringWriter ................................................................................. 854 Klasy StreamReader i StreamWriter ............................................................................. 855 Metody OpenText, CreateText oraz AppendText ............................................................. 856 Niestandardowe klasy strumieniowe ............................................................................ 857 Podsumowanie .......................................................................................................... 858
Rozdział 37. Obiekty systemu plików ..................................................................................................859 Uprawnienia ............................................................................................................... 859 Metody Visual Basica ................................................................................................. 860 Metody plikowe ..................................................................................................... 860 Metody systemu plików ......................................................................................... 862 Pliki z dostępem sekwencyjnym .............................................................................. 862 Pliki z dostępem swobodnym ................................................................................. 863 Pliki z dostępem binarnym ..................................................................................... 865 Klasy platformy .NET Framework .................................................................................. 866 Klasa Directory ..................................................................................................... 866 Klasa File ............................................................................................................. 867 Klasa DriveInfo ..................................................................................................... 869 Klasa DirectoryInfo ................................................................................................ 870 Klasa FileInfo ....................................................................................................... 871 Klasa SystemInfo .................................................................................................. 873 Klasa FileSystemWatcher ...................................................................................... 873 Klasa Path ........................................................................................................... 875
16
Visual Basic 2008. Warsztat programisty Obiekt My.Computer.FileSystem .................................................................................. 877 Własność My.Computer.FileSystem.SpecialDirectories .................................................. 879 Podsumowanie .......................................................................................................... 879
Rozdział 38. Windows Communication Foundation ...............................................................................881 Ostrzeżenie ................................................................................................................ 882 Koncepcje WCF .......................................................................................................... 882 Przykład użycia biblioteki WCF ..................................................................................... 883 Budowa wstępnej wersji usługi .................................................................................... 883 Tworzenie projektu QuoteService ................................................................................. 886 Testowanie usługi QuoteService .................................................................................. 888 Tworzenie klienta QuoteClient ..................................................................................... 889 Hostowanie usługi ...................................................................................................... 890 Podsumowanie .......................................................................................................... 891
Dodatki .............................................................................................................................. 917 Dodatek A Własności, metody i zdarzenia kontrolek ...........................................................................919 Dodatek B Deklaracje zmiennych i typy danych .................................................................................. 929 Dodatek C Operatory ............................................................................................................................937 Dodatek D Deklarowanie podprocedur i funkcji .................................................................................. 943 Dodatek E Instrukcje sterujące ............................................................................................................947 Dodatek F Obsługa błędów ....................................................................................................................953 Dodatek G Komponenty i kontrolki Windows Forms ............................................................................955 Dodatek H Kontrolki WPF .....................................................................................................................1035 Dodatek I Dodatki Power Pack ............................................................................................................ 1041 Dodatek J Obiekty formularzy ............................................................................................................1045 Dodatek K Klasy i struktury ................................................................................................................1059
Spis treści
17
Dodatek L LINQ ......................................................................................................................................1063 Dodatek M Typy ogólne ........................................................................................................................1073 Dodatek N Grafika .................................................................................................................................1077 Dodatek O Przydatne klasy wyjątków ................................................................................................1089 Dodatek P Specyfikatory formatu daty i godziny ..............................................................................1093 Dodatek Q Inne specyfikatory formatu ...............................................................................................1097 Dodatek R Klasa Application ................................................................................................................ 1103 Dodatek S Przestrzeń nazw My .......................................................................................................... 1107 Dodatek T Strumienie ............................................................................................................................ 1121 Dodatek U Klasy systemu plików ........................................................................................................ 1127 Dodatek V Indeks przykładów ............................................................................................................. 1143 Skorowidz ............................................................................................................................................ 1165
18
Visual Basic 2008. Warsztat programisty
O autorze Rod Stephens zaczynał jako matematyk, ale w czasie studiów w MIT odkrył przyjemność z programowania — i od tamtego czasu robi to zawodowo. W trakcie swojej kariery miał okazję pracować nad aplikacjami z tak różnych dziedzin, jak przełączanie telefonów, billingi, zarządzanie naprawami, podatki, oczyszczanie ścieków, sprzedaż biletów na koncerty, kartografia oraz treningi dla zawodowych piłkarzy. Stephens jest posiadaczem tytułu Microsoft Visual Basic Most Valuable Professional (MVP) i instruktorem kontraktowym ITT. Jest autorem osiemnastu książek, które przetłumaczono na kilka języków, a także ponad dwustu artykułów prasowych na tematy związane z językiem Visual Basic, Visual Basic for Applications, Delphi i Javą. Aktualnie regularnie pisze dla DevX (www.DevX.com). Popularną witrynę internetową Stephensa o nazwie VB Helper (www.vb-helper.com) odwiedza kilka milionów użytkowników miesięcznie. Można tam znaleźć tysiące stron z opisami sztuczek, wskazówek i przykładowym kodem w języku Visual Basic, jak również przykłady kodu z tej książki (dostępne również na FTP wydawnictwa Helion, ftp://ftp.helion.pl/przyklady/vb28wp).
Nad wydaniem oryginalnym pracowali Executive Editor Robert Elliott Development Editor Christopher J. Rivera Technical Editor John Mueller Production Editor Angela Smith Copy Editor Kim Cofer Editorial Manager Mary Beth Wakefield
Production Manager Tim Tate Vice President and Executive Group Publisher Richard Swadley Vice President and Executive Publisher Joseph B. Wikert Project Coordinator, Cover Lynsey Stanford Proofreader Nancy Hanger, Windhaven Indexer J & J Indexing
20
Visual Basic 2008. Warsztat programisty
Podziękowania Dziękuję Bobowi Elliottowi, Christopherowi Riverze, Angeli Smith i wszystkim innym osobom, które ciężko pracują, przyczyniając się do powstawania książek. Dziękuję także redaktorowi technicznemu Johnowi Muellerowi za nadanie tej publikacji dodatkowego, głębokiego wymiaru. Na stronie www.mwt.net/~jmueller znajduje się spis książek napisanych przez Johna — można tam także zasubskrybować jego darmowy newsletter .NET Tips, Trends & Technology eXTRA.
Wstęp Mówi się, że Sir Isaac Newton był ostatnim wszystko wiedzącym człowiekiem. Był znakomitym fizykiem (jego trzy zasady dynamiki położyły podwaliny pod mechanikę klasyczną, która przez trzy wieki definiowała astrofizykę), matematykiem (był jednym z wynalazców rachunku matematycznego i opracował metodę znajdowania pierwiastków równań matematycznych), astronomem, filozofem przyrody i alchemikiem. Wynalazł teleskop zwierciadlany, opracował teorię kolorów, prawo chłodzenia oraz badał prędkość rozchodzenia się dźwięku. Nie mniej ważne jest to, że Newton urodził się przed erą teorii względności, mechaniki kwantowej, sekwencjonowania genów, termodynamiki, obliczeń równoległych i wielu innych niezwykle trudnych działów nauki. Każdy, kto używał języka Visual Basic 3, także mógł wiedzieć wszystko. Był to niewielki, ale dający duże możliwości język programowania. W Visual Basicu 4 dodano klasy, co znacznie go skomplikowało. W wersjach 4, 5 i 6 pojawiło się więcej funkcji obsługi baz danych i innych technik, takich jak niestandardowe kontrolki, ale nadal język był w miarę łatwy do opanowania. Jeśli poświęciło się trochę czasu, można było stać się ekspertem we wszystkich jego dziedzinach. Visual Basic .NET znacznie przyspieszył rozszerzanie się języka Visual Basic. Platforma .NET umieściła w nim nowe narzędzia, które przyczyniły się do wzrostu poziomu jego złożoności. Zaczęto dodawać coraz więcej różnych powiązanych ze sobą technologii. Dzisiaj już nikt nie może być ekspertem we wszystkich dziedzinach związanych z językiem Visual Basic. Aby dogłębnie opisać Visual Basic, trzeba by znać się na bazach danych, kontrolkach niestandardowych, edytorach własności niestandardowych, XML-u, kryptografii, serializacji, grafice dwu- i trójwymiarowej, wielowątkowości, refleksji, modelu obiektu dokumentu (DOM), diagnostyce, globalizacji, usługach sieciowych, komunikacji wewnątrzprocesowej, przepływie pracy, pakiecie Office, technologii ASP i wielu więcej rzeczach. Nawet nie będę próbował opisać tych wszystkich tematów w tej książce. W zamian znajdziesz tu szeroką i solidną charakterystykę najważniejszych zagadnień dotyczących Visual Basica. Opiszę potężne wizualne środowisko programistyczne, dzięki któremu język ten jest bardzo produktywny. Scharakteryzuję sam Visual Basic i wyjaśnię, jak go używać do wykonywania mnóstwa ważnych zadań programistycznych.
22
Visual Basic 2008. Warsztat programisty Ponadto znajdzie się tu opis formularzy, kontrolek i innych obiektów dostarczanych przez Visual Basic do budowy aplikacji w nowoczesnym środowisku okienkowym. Ta książka może nie zawierać opisów wszystkich tematów związanych z technologią Visual Basic, ale większość technologii potrzebnych do budowy zaawansowanych aplikacji zostanie tu omówiona.
Czy używać Visual Basica 2008 Inżynierowie wyróżniają na razie pięć generacji języków programowania. Pierwsza z nich (1GL) to język maszynowy — zera i jedynki. Język drugiej generacji (2GL) to asembler udostępniający zwięzłe mnemoniki dla instrukcji maszynowych. Poza ułatwieniem pisania kodu maszynowego oferuje także kilka innych dodatkowych narzędzi. Języki trzeciej generacji (3GL) to języki wyższego poziomu, jak Pascal i FORTRAN. Udostępniają one znacznie bardziej zaawansowane konstrukcje, jak podprocedury, pętle i struktury danych. Języki czwartej generacji (4GL) to „języki naturalne” takie jak SQL. Przy ich użyciu programista może wykonywać zadania programistyczne za pomocą języka zbliżonego do ludzkiego. Na przykład instrukcja SQL "SELECT * FROM Customers WHERE Balance > 50" nakazuje bazie danych znalezienie informacji o klientach, którzy mają zaległe płatności powyżej 50 złotych. Języki piątej generacji (5GL) udostępniają potężne graficzne środowiska programistyczne, pozwalające programistom na bardziej zaawansowane wykorzystywanie ich. Środowisko Visual Studio jest niezwykle potężne. Dzięki dostępnym w nim edytorom graficznym budowa formularzy i edycja własności są łatwe i intuicyjne. Technologia IntelliSense przypomina programiście, co należy wpisać, funkcja automatycznego uzupełniania pozwala używać znaczących nazw zmiennych bez potrzeby wpisywania ich potem w całości. Punkty wstrzymania i inne zaawansowane narzędzia debugera sprawiają, że budowanie aplikacji jest łatwiejsze. Visual Basic wykorzystuje jedno z najpotężniejszych środowisk programistycznych, które kiedykolwiek powstały — Visual Studio. Nie jest to jednak jedyny język, który się nim posługuje. Między innymi robi to też język C#. Powstaje zatem pytanie — używać języka Visual Basic, a może C#? Programiści języka Visual Basic opowiadają taki dowcip „Jaka jest różnica pomiędzy Visual Basiciem .NET a C#? Jakieś trzy miesiące!”. Myśl w nim zawarta jest taka, że składnię Visual Basica .NET łatwiej zrozumieć i budowa aplikacji przy użyciu tej technologii jest łatwiejsza. Z drugiej strony programiści C# mają swoje dowcipy o Visual Basicu .NET, z których wynika, że C# daje większe możliwości.
Wstęp
23
W rzeczywistości Visual Basic .NET nie jest łatwiejszy do nauki w stosunku do C#, a C# nie jest znacznie potężniejszy. Podstawy obu tych języków są bardzo podobne. Poza kilkoma różnicami stylistycznymi (Visual Basic bazuje na wierszach, a C# wykorzystuje nawiasy i średniki) są one porównywalne. Oba wykorzystują środowisko programistyczne Visual Studio, obsługują klasy oraz narzędzia platformy .NET i wykorzystują podobną składnię do wykonywania podstawowych zadań programistycznych. Główna różnica pomiędzy tymi dwoma językami dotyczy stylu. Osoby, które miały styczność z wcześniejszymi wersjami języka Visual Basic, łatwiej przyzwyczają się do edycji 2008. Każdy, kto zna języki C++ lub Java, prawdopodobnie nie będzie miał problemów z nauką C# (lub Visual C++ albo Visual J#). Visual Basic ma pewne powiązania z innymi produktami Microsoftu. Na przykład technologia Active Server Pages (ASP) wykorzystuje ten język do tworzenia interaktywnych stron internetowych. Programy wchodzące w skład pakietu Office (Word, Excel, PowerPoint itd.) i wiele narzędzi innych producentów wykorzystują język Visual Basic for Applications (VBA) do pisania makr. Jeśli zna się Visual Basic, ma się solidne podstawy do używania tych pozostałych języków. ASP i VBA bazują na wcześniejszych wersjach języka Visual Basic, a więc nie od razu nauczysz się ich używać. Będziesz jednak w lepszej sytuacji, kiedy zdecydujesz się uczyć ASP lub VBA. Jeśli jesteś początkującym programistą, powinieneś wybrać język Visual Basic lub C#. Wydaje mi się, że Visual Basic 2008 jest nieco łatwiejszy do nauki, ale mogę nie być w pełni obiektywny, ponieważ używałem go na długo przed wynalezieniem C#. Bez względu na wybór, nie zrobisz dużego błędu, a poza tym później w razie potrzeby możesz z łatwością się przerzucić.
Kto powinien przeczytać tę książkę Książka ta jest przeznaczona dla programistów na każdym poziomie. Znajduje się tu opis języka Visual Basic 2008 od podstaw, a więc nie trzeba znać wcześniejszych jego wersji. Zamieszczę tu także omówienie wielu średnio zaawansowanych i zaawansowanych tematów. Dziedziny są opisywane tak dogłębnie, że nawet doświadczeni programiści znajdą nowe porady, sztuczki i szczegóły na temat języka. Po opanowaniu go nadal w różnych miejscach tej książki możesz napotkać przydatne ciekawostki. Dodatki pomagają znaleźć szczegóły, które łatwo się zapomina. Przez materiał wstępny przechodzimy szybko. Jeśli nigdy wcześniej nie programowałeś i przerażają Cię komputery, możesz najpierw przeczytać jakąś inną książkę, w której więcej miejsca poświęcono wstępowi. Osoby początkujące, które nie boją się komputerów, nie powinny mieć większych problemów z nauką Visual Basica 2008 za pomocą tej książki. Ci, którzy programowali w innych językach programowania, powinni znać takie podstawowe pojęcia, jak deklaracja zmiennej, typ danych czy tablica. Nie będą mieli żadnych problemów z tą książką. Indeks i dodatki powinny być szczególnie pomocne w przekładzie ze składni języków, które znasz, na składnię języka Visual Basic.
24
Visual Basic 2008. Warsztat programisty
Struktura książki Książka ta została podzielona na pięć części i uzupełniona dodatkami. Rozdziały każdej z części zostały scharakteryzowane poniżej. Doświadczeni programiści mogą na podstawie tych opisów zdecydować, które rozdziały pominąć, a które przeczytać.
Część I — IDE W rozdziałach tej części książki opiszę zintegrowane środowisko programistyczne Visual Studio (IDE) z punktu widzenia programisty języka Visual Basic. IDE to jest w zasadzie takie samo dla języka C# i innych języków, ale są pewne drobne różnice, dotyczące na przykład działania niektórych skrótów klawiszowych. Rozdział 1. — „Wprowadzenie do IDE” — znajduje się tam objaśnienie, jak zacząć pracę w zintegrowanym środowisku programistycznym Visual Studio. Dowiesz się z niego, jak skonfigurować IDE do różnego rodzaju zastosowań. Znajdziesz tam opisy projektów i rozwiązań Visual Basica. Pokażę, jak tworzyć, uruchamiać i zapisywać nowe projekty. Rozdział 2. — „Menu, paski narzędzi i okna” — znajdziesz w nim opis najbardziej użytecznych i najważniejszych poleceń dostępnych w menu i paskach narzędzi IDE. Jest tam mnóstwo opcji, dlatego scharakteryzuję tylko te najważniejsze. Rozdział 3. — „Konfiguracja IDE” — zawiera opis sposobów dostosowywania IDE do własnych potrzeb. Znajdziesz tam wyjaśnienie, jak tworzyć, ukrywać i zmieniać położenie menu i pasków narzędzi, aby mieć łatwiejszy dostęp do najczęściej używanych narzędzi. Rozdział 4. — „Projektant formularzy Windows” — znajduje się w nim opis kreatora, za pomocą którego można budować aplikacje Windows Forms. Objaśnię sposoby tworzenia, skalowania, przesuwania i kopiowania kontrolek. Pokażę, jak ustawiać własności kontrolek i dodawać kod reagujący na ich zdarzenia. Dodatkowo opiszę używanie poręcznych narzędzi projektanckich, jak inteligentne znaczniki (ang. smart tags) i czasowniki poleceń. Rozdział 5. — „Projektant WPF” — zawiera opis używania projektanta formularzy WPF (Windows Presentation Foundation). Rozdział ten jest podobny do rozdziału 4., z tym że omawia formularze WPF zamiast Windows Forms. Rozdział 6. — „Edytor kodu Visual Basica” — znajduje się w nim opis jednego z najważniejszych okien używanych przez programistów — edytora kodu. Wyjaśnię, jak pisać kod, ustawiać punkty wstrzymania, używać fragmentów kodu i maksymalnie wykorzystywać technologię IntelliSense. Rozdział 7. — „Usuwanie błędów” — zawiera opis dostępnych w Visual Studio narzędzi do usuwania błędów. Przedstawię okna debugera i objaśnię takie skomplikowane techniki, jak ustawianie kilku punktów wstrzymania w celu zlokalizowania błędów.
Wstęp
25
Część II — Wstęp do języka Visual Basic W rozdziałach tej części książki opiszę język Visual Basic i obsługujące go obiekty. Omówię formularze, kontrolki i inne obiekty wchodzące w skład interfejsu użytkownika programu oraz sposoby implementacji funkcjonalności aplikacji poprzez związanie z nimi odpowiedniego kodu. Rozdział 8. — „Wybieranie kontrolek Windows Forms” — zawiera przegląd kontrolek Windows Forms, które można umieszczać na formularzu. Kontrolki zostały opisane w grupach tematycznych, co ułatwia ich znalezienie według zastosowania. Rozdział 9. — „Używanie kontrolek Windows Forms” — znajdują się tam bardziej szczegółowe opisy sposobów używania kontrolek Windows Forms. Rozdział zawiera objaśnienie tworzenia kontrolek w czasie projektowania, a także działania programu, ustawiania wartości skomplikowanych własności oraz używania przydatnych własności, które są wspólne dla wielu różnych kontrolek. Znajdują się w nim opisy dodawania procedur obsługi zdarzeń kontrolek i sposobów sprawdzania poprawności danych wprowadzanych przez użytkownika. Rozdział 10. — „Formularze Windows” — zawiera opis formularzy używanych w aplikacjach Windows Forms. Są one jednym z rodzajów kontrolek, ale ich unikatowa pozycja w architekturze programu sprawia, że posiadają specjalne własności, omówione w tym rozdziale. Rozdział 11. — „Wybieranie kontrolek WPF” — zawiera przegląd kontrolek WPF. Zostały one przedstawione w grupach tematycznych, dzięki czemu łatwiej jest znaleźć te, które są przeznaczone do określonych zadań. Rozdział ten jest podobny do rozdziału 8., ale zamiast kontrolek Windows Forms opisano w nim kontrolki WPF. Rozdział 12. — „Używanie kontrolek WPF” — znajduje się w nim bardziej szczegółowy opis używania kontrolek WPF. Jest podobny do rozdziału 9., ale przedstawiono w nim kontrolki WPF, a nie kontrolki Windows Froms. Rozdział 13. — „Okna WPF” — zawiera opis okien używanych przez WPF zamiast Windows Forms. Rozdział jest podobny do rozdziału 10., ale przedstawiono w nim kontrolki WPF zamiast kontrolek Windows Forms. Rozdział 14. —„Struktura programu i modułu” — zawiera opis najważniejszych plików składających się na projekt Visual Basica. Przedstawiono tam niektóre ukryte pliki znajdujące się w projektach, a także częściowe objaśnienie, jaką strukturę można nadać kodowi w module, jak regiony kodu i kod kompilowany warunkowo. Rozdział 15. — „Typy danych, zmienne i stałe” — znajduje się w nim opis standardowych typów danych, które są dostępne w Visual Basicu. Objaśnię deklarowanie i inicjowanie zmiennych i stałych oraz zasięg zmiennych. Opiszę wartości i typy referencyjne, przekazywanie parametrów przez wartość i referencję oraz tworzenie zmiennych parametrów w locie. Ponadto omówię tworzenie tablic, typów wyliczeniowych i struktur. Rozdział 16. — „Operatory” — zawiera opis operatorów używanych w programie do wykonywania obliczeń. Są to operatory arytmetyczne (+, *, \), łańcuchowe (&) i logiczne (And, Or). W rozdziale tym została opisana kolejność wykonywania operatorów i konwersja typów, która jest potrzebna, gdy w jednym wyrażeniu znajduje się więcej niż jeden typ operatora (na przykład arytmetyczny i logiczny).
26
Visual Basic 2008. Warsztat programisty Rozdział 17. — „Podprocedury i funkcje”— znajduje się w nim opis używania podprocedur i funkcji do dzielenia programów na mniejsze części. Rozdział zawiera omówienie przeciążania i zasięgu procedur. Ponadto zostały tam opisane funkcje lambda i tak zwane rozluźnione delegaty — dwie nowości wprowadzone w Visual Basicu 2008. Rozdział 18. — „Instrukcje sterujące” — zawiera opis instrukcji służących do sterowania wykonywaniem kodu w programie. Zaliczają się do nich instrukcje decyzyjne (If Then Else, Select Case, IIF, Choose) i instrukcje pętlowe (For Next, For Each, Do While, While Do, Repeat Until). Rozdział 19. — „Obsługa błędów”— zawiera opis techniki obsługi i znajdowania błędów. W rozdziale znajduje się objaśnienie używania struktury obsługującej błędy Try Catch, a także starszej instrukcji — odziedziczonej po wcześniejszych wersjach — On Error. Zawiera opis typowych działań, które program może podjąć w odpowiedzi na błąd. Zostały w nim także omówione techniki zapobiegania błędom i sprawiania, by były bardziej oczywiste, kiedy wystąpią. Rozdział 20. — „Kontrolki i obiekty baz danych” — znajduje się w nim opis używania standardowych kontrolek baz danych w Visual Basicu. Należą do nich komponenty do połączeń z bazami danych, komponenty DataSet, przechowujące dane w aplikacji, a także kontrolki adaptacyjne danych, które przenoszą dane pomiędzy połączeniami a komponentami DataSet. Rozdział 21. — „LINQ” — zawiera opis funkcji technologii LINQ. Znajduje się w nim objaśnienie sposobów pisania podobnych do SQL zapytań do wyszukiwania lub zapisywania danych w obiektach, XML lub obiektach baz danych. LINQ jest nowością w Visual Basicu 2008. Rozdział 22. — „Tworzenie niestandardowych kontrolek” — znajduje się w nim opis tworzenia kontrolek dostosowanych do własnych potrzeb, których można później używać w innych aplikacjach. W rozdziale tym zostaną omówione trzy główne metody tworzenia kontrolek niestandardowych — derywacja, kompozycja i budowa od podstaw. Znajduje się tam także kilka przykładów, które można wykorzystać jako podstawę do budowy własnych kontrolek. Rozdział 23. — „Operacje typu »przeciągnij i upuść« oraz schowek” — zawiera opis sposobu obsługi w programach Visual Basic operacji typu „przeciągnij i upuść”. Znajduje się w nim wyjaśnienie, jak jeden program może rozpocząć przeciąganie do innej aplikacji, jak reagować na operacje przeciągania zapoczątkowane w innych aplikacjach, a także jak odbierać przeciągnięte dane z innej aplikacji. Ponadto zamieszczono tam opis kopiowania danych w programie z i do schowka. Używanie schowka jest podobne do niektórych typów operacji przeciągania i upuszczania, a więc tematy te naturalnie pasują do jednego rozdziału. Rozdział 24. — „Funkcja Kontrola konta użytkownika” — znajduje się tam opis modelu zabezpieczeń kont użytkownika, używany w systemie operacyjnym Windows Vista. Przy tych zabezpieczeniach wszyscy użytkownicy pracują ze zmniejszonymi, „normalnymi” zezwoleniami. Jeśli program wymaga zezwoleń administratora, pojawia się okno dialogowe, które pozwala zwiększyć poziom dostępu do aplikacji. W tym rozdziale opiszę zabezpieczenia kont użytkownika i wyjaśnię, jak wyznaczyć program do zwiększenia przywilejów.
Wstęp
27
Część III — Programowanie obiektowe Trzecia część zawiera opis podstaw programowania obiektowego w języku Visual Basic. Dodatkowo znajdziesz tam omówienie niektórych ważniejszych klas i obiektów, których można używać podczas budowy aplikacji. Rozdział 25. — „Podstawy programowania obiektowego”— zawiera opis podstawowych pojęć, które dotyczą programowania zorientowanego obiektowo. Omówione zostały trzy główne własności tego stylu programowania — hermetyzacja, polimorfizm i dziedziczenie. Znajdziesz tam też opis korzyści płynących ze stosowania tych technik oraz sposobów wykorzystania ich w języku Visual Basic. Rozdział 26. — „Klasy i struktury” — znajduje się tu opis deklarowania i używania klas oraz struktur. Zawiera objaśnienie, czym są klasy i struktury, a także omówienie różnic między nimi. W rozdziale tym przedstawiono podstawową składnię deklaracji oraz sposoby tworzenia egzemplarzy klas i struktur. Ponadto zostały opisane trudniejsze zagadnienia związane z klasami (takie jak prywatny zakres klas, deklarowanie zdarzeń oraz współdzielenie zmiennych i metod). Rozdział 27. — „Przestrzenie nazw” — zawiera opis przestrzeni nazw. W rozdziale tym wyjaśniam, jak Visual Studio kategoryzuje kod za pomocą przestrzeni nazw, a także jak dzięki nim unika kolizji nazw. Ponadto opisuję główną przestrzeń nazw projektu, sposoby rozwijania nazw (na przykład klas i funkcji) przy użyciu przestrzeni nazw oraz demonstruję dodawanie własnych przestrzeni nazw do aplikacji. Rozdział 28. — „Klasy kolekcyjne” — opisuję w nim klasy znajdujące się w Visual Studio, których można używać do przechowywania grup obiektów. Omawiam różne klasy kolekcji, słowników, kolejek i stosów. Z rozdziału tego dowiesz się, jak tworzyć ściśle typowane wersje tych klas oraz wybierać właściwy rodzaj klasy do określonego typu zadania. Rozdział 29. — „Typy ogólne” — zawiera opis szablonów, z których można tworzyć nowe klasy, zaprojektowanych specjalnie z myślą o konkretnym typie danych. Można na przykład napisać uogólnione drzewo binarne, a następnie używać go do tworzenia klas reprezentujących drzewa zamówień użytkowników, pracowników lub zadań do wykonania.
Część IV — Grafika Rozdziały tej części książki zawierają opis grafiki w Visual Basica 2008. Znajdziesz tam omówienie procedur Graphics Device Interface+ (GDI+), które są wykorzystywane przez programy w Visual Basicu do rysowania obrazów. Wyjaśnię, jak rysować linie i tekst, rysować i wypełniać kolorem koła i inne figury geometryczne. Omówię też ładowanie, obróbkę i zapisywanie map bitowych. Z tej części dowiesz się, jak generować drukowane dane wyjściowe. Rozdział 30. — „Podstawy rysowania” — znajduje się tu opis podstaw rysowania grafiki w Visual Basicu 2008, przestrzeni nazw grafiki oraz zawartych w nich klas. Szczegółowo została omówiona najważniejsza z tych klas — Graphics. Ponadto w rozdziale tym znajduje się opis procedury obsługi zdarzenia Paint i innych zdarzeń, których program musi używać, aby aktualizować swoje grafiki.
28
Visual Basic 2008. Warsztat programisty Rozdział 31. — „Pędzle, pióra i ścieżki” — zawiera opis najważniejszych poza Graphics klas graficznych — Pen i Brush. Zaprezentuję sposoby rysowania ciągłych i przerywanych linii za pomocą klasy Pen, metody wypełniania kolorem, kreskowania, wypełniania gradientem i wypełniania obrazkami. W rozdziale tym omówię ponadto klasę GraphicsPath, reprezentującą szereg linii, figur geometrycznych, krzywych i tekstu. Rozdział 32. — „Tekst”— opiszę w nim sposoby rysowania łańcuchów tekstu, tworzenie różnych rodzajów czcionek, określanie dokładnej wielkości tekstu po narysowaniu przy użyciu określonego kroju czcionki oraz użycie funkcji GDI+ do ułatwiania pozycjonowania tekstu. Z rozdziału tego dowiesz się, jak używać obiektu klasy StringFormat do określania sposobu wyrównania tekstu, czy jest on zawijany i przycięty, a także jak czytać i definiować punkty tabulacji. Rozdział 33. — „Przetwarzanie obrazów” — zawiera opis sposobów wczytywania, modyfikowania i zapisywania plików graficznych. Zaprezentuję w nim sposoby odczytu i zapisu pikseli w obrazie, a także wyjaśnię, jak zapisywać wyniki w różnych formatach graficznych, jak BMP, GIF czy JPEG. Objaśnię, jak używać obrazów, aby uzyskać funkcję automatycznego ponownego rysowania, oraz jak manipulować obrazem piksel po pikselu przy użyciu metod klasy Bitmap GetPixel i SetPixel. Opiszę też „niebezpieczne” techniki dostępowe, które znacznie przyspieszają operacje na pikselach, czego nie można dokonać, gdy używa się zwykłych metod GDI+. Rozdział 34. — „Drukowanie” — znajduje się w nim opis różnych sposobów wysyłania przez program danych do drukarki. Przedstawię sposób użycia obiektu PrintDocument do generowania danych do wydruku. Obiekt ten można wykorzystać do natychmiastowego wydrukowania danych, użyć kontrolki PrintDialog, aby pozwolić użytkownikowi wybrać drukarkę i skonfigurować jej ustawienia, lub kontrolki PrintPreviewDialog — w celu umożliwienia danej osobie zobaczenia podglądu przed wydrukowaniem.
Część V — Interakcja ze środowiskiem Z rozdziałów tej części książki dowiesz się, jak aplikacje współpracują ze swoim środowiskiem. Wyjaśnię, w jaki sposób program zapisuje i pobiera dane ze źródeł zewnętrznych (takich jak rejestr systemowy, pliki zasobów czy pliki tekstowe), współpracuje z ekranem monitora, klawiaturą i myszą, a także jak komunikuje się z użytkownikiem poprzez standardowe okna dialogowe. Rozdział 35. — „Konfiguracja i zasoby” — zawiera opis niektórych ze sposobów zapisywania przez programy w Visual Basicu danych konfiguracyjnych i zasobów do użycia w czasie działania. Do najbardziej przydatnych z nich należą zmienne środowiskowe, rejestr, pliki konfiguracyjne i pliki zasobów. Rozdział 36. — „Strumienie” — opiszę w nim klasy, za pomocą których programy w Visual Basicu pracują z danymi strumieniowymi. Niektóre z nich to FileStream, MemoryStream, BufferedStream, TextReader i TextWriter.
Wstęp
29
Rozdział 37. — „Obiekty systemu plików”— opiszę w nim klasy pozwalające aplikacjom Visual Basica komunikować się z systemem plików. Należą do nich Directory, DirectoryInfo, File i FileInfo. Klasy te umożliwiają tworzenie, badanie, przenoszenie, zmienianie nazw i usuwanie katalogów i plików. Rozdział 38. — „Windows Communication Foundation” — zawiera opis biblioteki Windows Communication Foundation (WCF), zbioru narzędzi ułatwiających budowanie aplikacji zorientowanych na usługi. W rozdziale tym wyjaśnię, jak za pomocą atrybutów WCF definiować usługi, w jaki sposób konfigurować te usługi za pomocą plików konfiguracyjnych, a także jak przy użyciu narzędzi WCF konsumować usługi. Rozdział 39. — „Przydatne przestrzenie nazw” — opiszę w nim niektóre bardziej przydatne przestrzenie nazw zdefiniowane na platformie .NET. Rozdział ten zawiera zwięzły przegląd najbardziej przydatnych systemowych przestrzeni nazw oraz bardziej szczegółowe przykłady, które demonstrują wyrażenia regularne, XML, kryptografię, refleksję, wątki i bibliotekę Direct3D.
Dodatki Dodatki zawierają informacje na temat języka Visual Basic 2008, podzielone na kategorie. Można w nich szybko sprawdzić składnię wybranych poleceń, znaleźć interesującą wersję przeciążonej procedury lub odświeżyć sobie wiedzę o wybranej klasie. Wcześniejsze rozdziały zawierają więcej informacji kontekstowych, znajdują się w nich opisy, jak wykonać konkretne zadania, a także wyjaśnienia, dlaczego jedna metoda jest lepsza od innej. Dodatek A — „Własności, metody i zdarzenia kontrolek” — zawiera opis własności, metod i zdarzeń, które są przydatne w pracy z wieloma rodzajami kontrolek. Dodatek B — „Deklaracje zmiennych i typy danych” — zawiera zestawienie zasad składniowych deklaracji zmiennych. Ponadto podano w nim rozmiary i przedziały dopuszczalnych wartości dla podstawowych typów danych. Dodatek C — „Operatory” — zawiera zestawienie standardowych operatorów, jak +, <<, OrElse i Like. Objaśnia też składnię przeciążania operatorów. Dodatek D — „Deklarowanie podprocedur i funkcji” — zawiera zestawienie reguł składni deklaracji podprocedur, funkcji i procedur własności. Dodatek E — „Instrukcje sterujące” — zawiera zestawienie instrukcji kontrolujących przepływ sterowania w programie, na przykład If Then, Select Case i pętle. Dodatek F — „Obsługa błędów” — podsumowuje zarówno strukturalne, jak i klasyczne metody obsługi błędów. Znajdują się w nim opisy niektórych przydatnych klas wyjątków. Rozdział zawiera przykład, który demonstruje budowanie niestandardowej klasy wyjątków. Dodatek G — „Komponenty i kontrolki Windows Forms” — znajdziesz w nim opisy standardowych kontrolek i komponentów Windows Forms, które są dostępne w Visual Basicu 2008. Objaśnię własności, metody i zdarzenia, które moim zdaniem są najbardziej przydatne podczas pracy z tymi komponentami.
30
Visual Basic 2008. Warsztat programisty Dodatek H — „Kontrolki WPF” — zawiera opisy kontrolek, które są automatycznie umieszczane w skrzynce z narzędziami podczas budowy aplikacji WPF. Jest to względnie krótka lista, jej zadaniem jest zaprezentowanie, jakiego rodzaju kontrolki są dostępne. Nie jest to szczegółowy opis, jak w przypadku standardowych kontrolek Windows Forms, opisanych w dodatku G. Dodatek I — „Dodatki Power Pack” — zawiera listę niektórych dodatkowych narzędzi, które można pobrać z internetu, aby ułatwić sobie programowanie w języku Visual Basic. Znajdziesz tam opis kilku narzędzi zgodności z Visual Basikiem 6, udostępnianych przez firmę Microsoft, a także paru dodatków Power Pack GotDotNet, zawierających przydatne kontrolki utworzone w Visual Basicu 2003. Dodatek J — „Obiekty formularzy” — znajdziesz w nim opis formularzy. W rzeczywistości formularze są jednym z rodzajów komponentów. Jednak ich bardzo ważna rola w aplikacjach Visual Basica sprawia, że zasługują na osobny dodatek. Dodatek K — „Klasy i struktury” — zawiera zestawienie reguł składniowych deklaracji klas i struktur oraz definiowania ich konstruktorów i zdarzeń. Dodatek L — „LINQ” — znajdziesz w nim podsumowanie składni LINQ. Dodatek M — „Typy ogólne” — zawiera podsumowanie składni deklaracji klas uogólnionych. Dodatek N — „Grafika” — zawiera zestawienie obiektów służących do generowania grafiki w Visual Basicu 2008. Znajdziesz w nim opisy najbardziej przydatnych graficzne przestrzeni nazw. Dodatek O — „Przydatne klasy wyjątków” — zawiera listę niektórych najbardziej przydatnych klas wyjątków, zdefiniowanych w języku Visual Basic. Może być konieczne zgłoszenie tych wyjątków w swoich programach. Dodatek P — „Specyfikatory formatu daty i godziny” — znajduje się w nim podsumowanie znaków, które określają format daty i godziny. Pozwalają na przykład ustawić wyświetlanie godziny w 12- lub 24-godzinnym trybie. Dodatek Q — „Inne specyfikatory formatu” — zawiera zestawienie specyfikatorów formatu liczb i typów wyliczeniowych. Dodatek R — „Klasa Application” — znajdziesz w nim opis klasy Application, która udostępnia własności i metody do kontrolowania bieżącej aplikacji. Dodatek S — „Przestrzeń nazw My” — zawiera opis przestrzeń nazw My, która daje skrócony dostęp do przydatnych własności porozrzucanych w różnych miejscach platformy .NET. Umożliwia ona pracę z programem, sprzętem komputerowym, formularzami aplikacji, zasobami i bieżącym użytkownikiem. Dodatek T — „Strumienie” — zawiera zestawienie klas strumieniowych języka Visual Basic, takich jak Stream, FileStream, MemoryStream, TextReader, CryptoStream itd.
Wstęp
31
Dodatek U — „Klasy systemu plików” — zawiera zestawienie metod umożliwiających aplikacji dostęp i manipulowanie systemem plików. Znajdziesz tam opisy takich klasycznych metod języka Visual Basic, jak FreeFile, WriteLine i ChDir, a także nowszych klas platformy .NET — FileSystem, Directory i File. Dodatek V — „Indeks przykładów” — zawiera krótkie opisy 431 przykładowych programów, które można pobrać z serwera FTP wydawnictwa Helion jako dodatek do tej książki. Na tej liście można sprawdzić, które aplikacje demonstrują wybraną technikę.
Korzystanie z tej książki Doświadczeni programiści Visual Basica dla platformy .NET mogą pominąć początkowe rozdziały, które są poświęcone podstawom. Można w nich jednak znaleźć kilka nowości, które pojawiły się w Visual Basicu 2008, a więc nie zalecam całkowitego zrezygnowania z zapoznania się z nimi. Większość podstawowych własności tego języka pozostało jednak bez zmian. Średnio zaawansowani programiści, a także ci, którzy mają mniejsze doświadczenie z językiem Visual Basic .NET, powinni dokładniej przeczytać te rozdziały. Szczególnie ważne tematy znajdują się w części trzeciej, poświęconej programowaniu obiektowemu. Nauka wszystkich rodzajów dziedziczenia i interfejsów może być nieco nużąca. Początkujący powinni tym pierwszym rozdziałom poświęcić więcej czasu, ponieważ są one wstępem do dalszego materiału. Znacznie łatwiej jest zrozumieć opis zarządzania plikami lub wyrażeń regularnych, jeśli nie trzeba zastanawiać się nad kodem obsługującym błędy, który jest uznawany za oczywisty. Programowania najlepiej uczyć się poprzez praktykę. Można wziąć tę książkę i szybko całą ją przeczytać, ale zawarte w niej informacje zostaną znacznie łatwiej zapamiętane, jeśli uruchomisz środowisko programistyczne i poeksperymentujesz nieco z własnymi aplikacjami. Gdy biorę do ręki nową książkę o programowaniu, zwykle samodzielnie analizuję przedstawione w niej przykłady kodu. Modyfikuję je, aby sprawdzić, co się stanie, jeśli zastosuję inne rozwiązania, o których autor nic nie pisze. Eksperymentuję na różnych nowych wersjach, a szczególną uwagę poświęcam błędom, które trudno jest w pełni opisać. Omówić kolekcje ściśle typowane to jedno. Utworzyć własną tego typu kolekcję z własnych danych — to drugie. Nauka poprzez praktykę może zachęcać do pomijania rozdziałów. Na przykład w rozdziale 1. szczegółowo opisuję IDE. Po przeczytaniu jego części można pominąć kilka podrozdziałów i zacząć samodzielnie badać to środowisko. Później — po zdobyciu pewnego doświadczenia w pracy w środowisku programistycznym — można wrócić do rozdziału 1. i przeczytać go dokładnie, aby sprawdzić, czy nie pominęło się czegoś istotnego.
32
Visual Basic 2008. Warsztat programisty Ostatnia część tej książki to podręcznik Visual Basica. Dodatki zawierają bardziej zwięzłe informacje o języku, podzielone na kategorie. Dzięki nim przypomnisz sobie szczegóły konkretnych operacji. Można na przykład przeczytać rozdział 8., aby dowiedzieć się, jakie kontrolki są najlepsze do różnych zastosowań, a następnie w dodatku G sprawdzić, jakie są własności, metody i zdarzenia tych kontrolek. W czasie pracy można także odwoływać się do dodatków, aby sprawdzić informacje na temat różnych klas, kontrolek i składni. Na przykład w dodatku M szybko znajdziesz to, czego chcesz się dowiedzieć o składni definicji klas uogólnionych. Jeśli szukasz więcej informacji na temat typów uogólnionych, możesz zajrzeć do rozdziału 29. lub pomocy internetowej. Jeżeli jednak musisz tylko odświeżyć sobie pamięć, szybciej będzie zajrzeć do dodatku M.
Czego będziesz potrzebować Do przeczytania tej książki i zrozumienia zawartych w niej przykładowych programów nie jest potrzebny żaden specjalistyczny sprzęt. Aby używać Visual Basica 2008 i uruchomić przykłady kodu znajdujące się na serwerze FTP wydawnictwa Helion, wystarczy zwykły komputer, który poradzi sobie z włączeniem środowiska Visual Basic 2008. Oznacza to, że potrzebna jest względnie nowa i szybka maszyna z dużą ilością pamięci. Dokładne informacje na temat wymagań i zaleceń sprzętowych formy Microsoft można znaleźć w dokumentacji Visual Basica. Do kompilacji programów Visual Basica 2008 potrzebna jest kopia Visual Basica 2008. Nie próbuj uruchamiać prezentowanych tu przykładów w edycji Visual Basica, która powstała przed platformą .NET, takiej jak na przykład Visual Basic 6. Zmiany pomiędzy Visual Basikiem 6 a Visual Basikiem .NET są tak duże, że wiele z cech tej nowszej wersji nie przekłada się na starszą edycję. Gdyby posiadało się odrobinę doświadczenia z językiem C#, znacznie łatwiej przełożyłoby się na ten język programy w Visual Basicu .NET. Możesz także użyć bezpłatnej wersji Visual Basic Express Edition. Więcej informacji na ten temat znajdziesz na stronie http://www.microsoft.com/express/. Wiele funkcji Visual Basica 2008 jest zgodnych z Visual Basikiem 2005 i wcześniejszymi edycjami Visual Basica .NET, a więc sporo przykładów będzie działać także w starszych wersjach środowiska. Nie będziesz jednak w stanie uruchomić dużej części przykładowych programów, które towarzyszą tej książce. Wymagałoby to skopiowania i wklejenia obszernych fragmentów kodu. Aby skorzystać z zabezpieczeń UAC, trzeba je mieć zainstalowane w swoim komputerze. Są domyślnie instalowane i aktywowane w systemie operacyjnym Windows Vista.
Wstęp
33
Konwencje Aby ułatwić czytelnikowi osiągnięcie jak największych korzyści z czytania tej publikacji i zorientowanie się w tekście, w książce zastosowano kilka konwencji typograficznych. Wskazówki, podpowiedzi, sztuczki i komentarze do bieżącego tematu są nieco wcięte i napisane kursywą. Konwencje zastosowane w tekście głównym:
Ważne słowa są pogrubione, gdy używa się ich pierwszy raz.
Skróty klawiszowe są prezentowane w następujący sposób: Ctrl+A.
Nazwy plików, adresy URL są prezentowane następująco: www.helion.pl.
Kod znajdujący się w tekście jest wyróżniony w ten sposób: persistence.properties.
Fragmenty kodu zostały zaprezentowane na dwa sposoby: W listingach fragmenty nowego i ważnego kodu są wyróżniane w ten sposób. Kod, który jest mniej ważny w aktualnym kontekście — lub został pokazany już wcześniej — jest wyróżniony w ten sposób.
Witryna internetowa Środowiska programistyczne, takie jak Visual Basic i systemy operacyjne Windows, nie są statyczne, a więc książki je opisujące również nie powinny takie być. Nowoczesne publikacje o tematyce programistycznej mają swoje witryny internetowe, w których można znaleźć przykładowe programy, fora do zadawania pytań i prowadzenia dyskusji, listy poprawek i zmian, a także inne materiały uzupełniające. Ta książka nie jest wyjątkiem od tej reguły. Jeśli nie odwiedzisz witryny internetowej publikacji, nie wykorzystasz w pełni wydanych pieniędzy. Witryna internetowa wydawnictwa Helion ma adres www.helion.pl. Aby znaleźć tę książkę, należy w wyszukiwarce wpisać numer ISBN, nazwisko autora lub tytuł. Witryna ta zawiera przykładowy rozdział, przykłady kodu do pobrania, komentarze innych czytelników i erratę, jeśli jest potrzebna. Przykłady kodu możesz również pobrać bezpośrednio z serwera FTP wydawnictwa Helion. Jego adres to ftp://ftp.helion.pl/przyklady/vb28wp.zip. Odwiedź witryny internetowe tej książki. Pobierz przykładowe programy, aby móc wypróbować kod prezentowany w publikacji.
34
Visual Basic 2008. Warsztat programisty Witryna internetowa tej książki, utworzona przez jej autora, znajduje się pod adresem www.vb-helper.com/vb_prog_ref.htm. Zawiera ona podobne materiały do tej, którą wymieniono wcześniej, oraz kilka dodatkowych przykładów. Na stronie głównej witryny VB Helper również znajdziesz tysiące wskazówek, sztuczek i przykładów napisanych w różnych wersjach języka Visual Basic. Aby otrzymywać informacje o zmianach wprowadzonych w tej książce lub innych moich publikacjach, możesz zasubskrybować jeden z moich newsletterów na stronie www.vbhelper.com/newsletter.html. Mniej więcej co tydzień będziesz dostawać porcję wskazówek, sztuczek i przykładów kodu Visual Basica, a także informacje o aktualizacjach moich książek i o programowaniu w języku Visual Basic. Jeśli chciałabyś coś skomentować lub znalazłeś błąd, napisz do mnie na adres RodStephens@ vb-helper.com. Zrobię, co w mojej mocy, aby jak najczęściej aktualizować swoją witrynę internetową.
Errata Dokładamy wszelkich starań, aby w tekście naszych publikacji i kodzie nie było żadnych usterek. Niestety nikt nie jest idealny — błędy zdarzają się każdemu. Jeśli znajdziesz jakąś usterkę w jednej z naszych książek (literówka lub błędny fragment kodu), będziemy bardzo wdzięczni za informacje. Jeżeli wyślesz erratę, być może zaoszczędzisz innym czytelnikom kilku godzin nerwów, a przy okazji pomożesz nam podnieść jakość naszych publikacji. Erratę do tej książki można znaleźć na stronie www.helion.pl. Należy szukać tam odpowiedniej pozycji za pomocą wyszukiwarki lub na liście publikacji. Następnie na stronie tej książki znajduje się odnośnik Zgłoś erratę oraz, jeśli już została jakaś zgłoszona, Erraty. Jeśli nie znajdziesz znalezionego przez siebie błędu na stronie Erraty, przejdź do strony Zgłoś erratę i wyślij nam informacje o znalezionej usterce. Sprawdzimy te informacje, po czym w razie potwierdzenia umieścimy je na stronie Erraty oraz uwzględnimy w kolejnych wydaniach książki.
Ostrzeżenie dotyczące Visual Basica 2008 Version 1 Z powodu błędu konfiguracji podczas przesyłania pierwszych wersji Visual Basica 2008 nowo utworzony projekt WPF zawiera usterkę. Projekt szablonu próbuje zwrócić obiekt System.Windows.Application z funkcji, która powinna zwracać obiekt WpfApplication1. Application. Błąd ten jest następujący: Option Strict On disallows implicit conversions from 'System.Windows.Application' to 'WpfApplication1.Application'.
Wstęp
35
Występuje on w instrukcji Return w poniższym kodzie: _ Friend ReadOnly Property Application() As Application Get Return Global.System.Windows.Application.Current End Get End Property
Aby naprawić ten błąd, najedź kursorem na czerwoną ramkę wyświetloną przez Visual Basic, aby rozwinąć listę Error Correction Options. Zaakceptuj sugestię poprawki: Return CType(Global.System.Windows.Application.Current, _ WpfApplication1.Application)
Błąd ten powinien zostać naprawiony w pierwszych pakietach serwisowych i późniejszych wydaniach Visual Basica 2008. Pamiętaj o tym problemie podczas lektury rozdziałów 5., 11., 12. i 13. Więcej informacji na ten temat znajduje się pod adresem support.microsoft.com/default.aspx/kb/945756.
36
Visual Basic 2008. Warsztat programisty
I
IDE Rozdział 1. Wprowadzenie do IDE Rozdział 2. Menu, paski narzędzi i okna Rozdział 3. Konfiguracja IDE Rozdział 4. Projektant formularzy Windows Rozdział 5. Projektant WPF Rozdział 6. Edytor kodu Visual Basica Rozdział 7. Usuwanie błędów
38
Część I
IDE
Rozdział 1.
Wprowadzenie do IDE
39
1
Wprowadzenie do IDE W rozdziałach pierwszej części tej książki opisane zostanie zintegrowane środowisko programistyczne (IDE) Visual Studio. Znajdą się tu informacje na temat dostępnych w nim najważniejszych okien, menu i pasków narzędzi oraz objaśnienie, jak dostosowywać je do własnych potrzeb. Zawarty tu zostanie również opis niektórych udogodnień wspomagających programistę Visual Basica, jak również narzędzia IDE, które służy do znajdowania błędów. Nawet doświadczeni programiści Visual Basica powinni pobieżnie przejrzeć zawarty tu materiał. Środowisko to jest niezwykle skomplikowane. Zawiera setki (jeśli nie tysiące) poleceń, menu, pasków narzędzi, okien, menu kontekstowych i innych narzędzi do edycji, uruchamiania i debugowania projektów Visual Basica. Nawet osoby, które już od dłuższego czasu używają tego IDE, mogą dowiedzieć się czegoś nowego. Kiedy na przykład uczę Visual Basica, na początku zajęć wprowadzam skróty klawiszowe (takie jak Alt+spacja do otwierania IntelliSense, a także Ctrl+C, Ctrl+X i Ctrl+V do kopiowania, wycinania i wklejania). Studenci jednak w ciągu kilku tygodni nie piszą wystarczająco dużo kodu, aby w pełni wykorzystać te narzędzia, dlatego później wracam do tego tematu. W tych rozdziałach znajdą się opisy najważniejszych z tych funkcji. Możesz dowiedzieć się czegoś nowego. Nawet po przeczytaniu tych rozdziałów powinno się co jakiś czas pobłądzić po opcjach IDE w celu sprawdzenia, o czym się zapomniało. Raz w miesiącu poświęć kilka minut na zbadanie rzadko używanych menu i klikanie prawym przyciskiem myszy w różnych miejscach, aby zobaczyć, co zawierają ich menu kontekstowe. W miarę zdobywania doświadczenia w programowaniu zaczniesz odkrywać nowe sposoby używania narzędzi, które wcześniej wydawały Ci się niepotrzebne lub niezrozumiałe. Przydaje się też zapisywanie odnośników do wskazówek znalezionych w internecie. Możesz utworzyć w Ulubionych folder o nazwie wskazówki Visual Basica, aby łatwiej nad nimi zapanować.
40
Część I
IDE
Z tego rozdziału dowiesz się, jak zacząć pracę w IDE Visual Studio. Znajdziesz tu wskazówki, w jaki sposób skonfigurować to środowisko do różnych rodzajów prac programistycznych; informacje, czym są projekty i rozwiązania Visual Basica; opis tworzenia, uruchamiania i zapisywania nowych projektów. Ten rozdział stanowi w głównej mierze wprowadzenie do kolejnych części książki, które będą zawierały znacznie więcej szczegółów dotyczących konkretnych zadań, takich jak używanie menu IDE, dostosowywanie menu i pasków narzędzi do własnych potrzeb i używanie projektanta formularzy Windows (ang. Windows Forms Designer) do tworzenia formularzy.
Wygląd IDE Zanim zaczniesz czytać o IDE i oglądać zrzuty ekranu, musisz wiedzieć, że Visual Studio można bardzo łatwo dostosować do własnych potrzeb. Istnieje możliwość przesuwania, ukrywania i modyfikowania menu, pasków narzędzi i okien, tworzenia własnych pasków narzędzi, dokowania, odczepiania i przestawiania pasków oraz okien. Można zmieniać zachowanie wbudowanych edytorów tekstu (zmieniać wcięcia, kolory różnych rodzajów tekstu itd.). W tych rozdziałach opisane zostanie podstawowe środowisko Visual Studio — w stanie po instalacji. Gdy przesuniesz jego zawartość i dostosujesz do własnych potrzeb, może ono w niczym nie przypominać zrzutów ekranu w tej książce. Aby uniknąć zamieszania, najlepiej nie zmieniać wyglądu podstawowych menu i pasków narzędzi. Usunięcie poleceń pomocy z menu Help i wstawienie ich do menu Edit nie przyniesie nic dobrego. Przestawienie lub usunięcie poleceń także utrudni przyswojenie przykładów prezentowanych w tej i innych książkach. Ponadto ciężko będzie Ci zrozumieć wskazówki dawane przez inne osoby, które mogą pomóc, kiedy będziesz miał z czymś problem. Zamiast dramatycznie zmieniać zawartość domyślnych menu i pasków narzędzi, lepiej ukryj te, których nie potrzebujesz, a utwórz nowe, bardziej przydatne. Dzięki temu będziesz miał pewność, że jeśli standardowe menu i paski narzędzi okażą się pomocne, łatwo je odnajdziesz. Więcej na temat dostosowywania IDE do indywidualnych potrzeb przeczytasz w rozdziale 3. Istnieje jeszcze kilka innych powodów, dla których to środowisko może wyglądać inaczej na Twoim komputerze. Widok Visual Studio różni się w zależności od systemu operacyjnego. Rysunki prezentowane w tej książce zostały wykonane w systemie Windows Vista, a więc odzwierciedlają jego styl. Włączona była opcja przezroczystego szkła, dzięki czemu okna mają duże, przezroczyste obramowania. Visual Studio może wyglądać u Ciebie inaczej — nawet jeśli używasz tego samego systemu operacyjnego, ale masz ustawiony inny styl, lub Vista dojdzie do wniosku, że Twój komputer nie jest w stanie dobrze poradzić sobie z tego rodzaju dekoracjami. Ponadto niektóre polecenia mogą nie działać identycznie we wszystkich systemach operacyjnych. Poza nowym wyglądem oknie w Windows Vista wprowadzono model zabezpieczeń o nazwie Kontrola konta użytkownika. Przy pierwszym logowaniu wszystkie konta użytkowników mają normalny poziom zezwoleń. Później, podczas instalowania różnych programów, które wymagają większych przywilejów, pojawia się okno dialogowe pozwalające zwiększyć uprawnienia — można wpisać w nim hasło administratora.
Rozdział 1.
Wprowadzenie do IDE
41
Przykładowe programy w tej książce zostały przetestowane na normalnym koncie użytkownika, a więc okno to nie powinno pojawiać się przy ich uruchamianiu. Może to się jednak zdarzyć podczas używania innych narzędzi programistycznych. Więcej informacji na ten temat znajduje się w rozdziale 24. — „Funkcja Kontrola konta użytkownika”. Ponadto wygląd Visual Studio może się różnić w zależności od używanej wersji. Dostępna bezpłatnie edycja Visual Basic 2008 Express Edition posiada mniej opcji od innych wersji, takich jak Visual Studio 2008 Team Suite. Rysunki przedstawione w tej książce zostały wykonane za pomocą tej drugiej edycji, więc jeśli używasz jakiejś odmiany tego oprogramowania, możesz nie mieć dostępu do wszystkich widocznych na zrzutach ekranu narzędzi. Informacje o bezpłatnych edycjach Express Visual Studio znajdziesz na stronie msdn2.microsoft.com/en-us/express/default.aspx, a ogólną charakterystykę programu Visual Basic — na witrynie msdn2.microsoft.com/en-us/vbasic/default.aspx. W końcu możesz mieć inne ustawienia konfiguracyjne niż te, które posiadał mój komputer podczas pisania przeze mnie tej książki. Visual Studio można skonfigurować do pracy nad projektami Visual Basica, C#, Web to ols i innymi. Ta książka została napisana przy ustawieniach dla Visual Basica. Jeśli masz inną konfigurację, zaprezentowane tutaj zrzuty ekranu mogą wyglądać inaczej na Twoim komputerze. Poniższy podrozdział zawiera więcej informacji o różnych konfiguracjach IDE. Objaśnia też, jak wybrać jedną z nich.
Konfiguracje IDE Po instalacji Visual Studio zapyta, jaki rodzaj parametrów chcesz zastosować. Dla programisty Visual Basica oczywistym wyborem są ustawienia Visual Basic Development Settings. Spowoduje to uaktywnienie parametrów ułatwiających programowanie w tej aplikacji; jest to też najlepsza opcja dla tych, którzy koncentrują się na tym języku. Innym rozsądnym wyborem jest General Development Settings. Jeśli zdecydujesz się na tę opcję, Visual Studio będzie działać podobnie do Visual Studio 2003. Jest to dobre rozwiązanie dla osób przyzwyczajonych do tej wersji, a także dla tych, którzy planują używać innych języków programowania dostępnych w tym IDE, na przykład C#. W tej książce wybrane zostały ustawienia dla Visual Basica. Jeśli zdecydowałeś się inną konfigurację, niektóre z rysunków mogą różnić się od tego, co zobaczysz na swoim ekranie. Część elementów menu może mieć odmienny wygląd lub być ustawiona w innej kolejności. Zazwyczaj gdzieś się one znajdują, trzeba je tylko znaleźć. Jeśli zechcesz po jakimś czasie zmienić konfigurację, otwórz menu Tools i kliknij opcję Import and Export Settings, aby otworzyć okno kreatora Import and Export Settings Wizard. Kliknij opcję Reset all settings i przycisk Next. Na kolejnej stronie poinformuj kreatora, czy chcesz zapisać swoje aktualne ustawienia, po czym kliknij Next. Na ostatniej stronie kreatora (pokazanej na rysunku 1.1) wybierz typ konfiguracji i kliknij przycisk Finish. Po zakończeniu zapisywania ustawień przez kreator kliknij przycisk Close.
42
Część I
IDE
Rysunek 1.1. Do zmiany konfiguracji Visual Studio służą opcje Import and Export Settings z menu Tools
Projekty i rozwiązania Zanim nauczysz się efektywnie używać IDE do zarządzania projektami i rozwiązaniami Visual Basica, musisz dowiedzieć się, czym one w ogóle są. Projekt (ang. project) to zbiór plików generujących określony rezultat. Wynikiem może być skompilowany plik wykonywalny programu, konsolidowana dynamicznie biblioteka (DLL) klas do użytku w innych projektach lub biblioteka kontrolek do użytku w innych formularzach systemu Windows. Rozwiązanie (ang. solution) to jeden lub więcej projektów, które powinny być traktowane jako grupa. Wyobraź sobie, że piszesz aplikację serwerową, która umożliwi dostęp do bazy danych zamówień. Tworzysz także program kliencki, za pomocą którego przedstawiciele handlowi będą przeszukiwać zasoby tej aplikacji serwerowej. Ponieważ te dwa projekty są ze sobą spokrewnione, rozsądnie byłoby zarządzać nimi w jednym rozwiązaniu. Po otwarciu go można by zyskać natychmiastowy dostęp do wszystkich plików w obu projektach. Zarówno projekty, jak i rozwiązania mogą zawierać pliki, które są potrzebne do budowy aplikacji, ale nie wchodzą w skład finalnej skompilowanej wersji produktu. Na przykład projekt może zawierać dokumenty dotyczące propozycji funkcji i architektury programu. Pliki te nie są dołączane do skompilowanej wersji aplikacji, ale dzięki umieszczeniu ich w projekcie łatwiej jest je znaleźć, otworzyć i edytować w trakcie pracy nad nim.
Rozdział 1.
Wprowadzenie do IDE
43
Po otwarciu projektu Visual Studio wyświetla listę tych plików razem z plikami programu. Dwukrotne kliknięcie jednego z nich powoduje, że Visual Studio otwiera go za pomocą odpowiedniej aplikacji. Jeśli na przykład klikniesz plik z rozszerzeniem .doc, zostanie on otwarty w programie Microsoft Word. Aby powiązać jeden z tych plików z projektem lub rozwiązaniem, należy w oknie Solution Explorer kliknąć prawym przyciskiem myszy projekt (więcej na ten temat piszę nieco dalej). Z menu Add trzeba wybrać opcję Add New Item, po czym w wyświetlonym oknie znaleźć plik, który chce się dodać. Rozwiązania Visual Basica często zawierają tylko jeden projekt. Przy tworzeniu prostego programu zazwyczaj nie ma potrzeby dodawania do rozwiązania żadnych dodatkowych projektów. Innym często stosowanym podejściem jest umieszczenie kodu Visual Basica w jednym projekcie, a dokumentacji (specyfikacja projektu i raporty o postępie) w innym w obrębie tego samego rozwiązania. Dzięki temu dokumentacja znajduje się cały czas pod ręką, ale nie zaśmieca niepotrzebnie okna Visual Studio podczas pracy nad kodem źródłowym. Mimo że do projektów i rozwiązań można dodawać dowolne pliki, ładowanie do nich dziesiątek niepowiązanych ze sobą dokumentów nie jest dobrym pomysłem. Czasami zdarza się, że trzeba sięgnąć do jakiegoś niepowiązanego z projektem pliku, ale dodanie go powoduje tylko niepotrzebne zamieszanie. Lepiej jest zminimalizować Visual Studio i otworzyć taki plik w osobnym programie, takim jak Word lub WordPad. Jeśli nie planujesz często używać jakiegoś pliku, nie dodawaj go do projektu.
Uruchamianie IDE Po uruchomieniu Visual Studio wyświetla stronę startową widoczną na rysunku 1.2. W sekcji Recent Projects znajduje się lista projektów i odnośniki pozwalające na otwarcie istniejących projektów lub witryn internetowych albo utworzenie nowych projektów lub witryn internetowych. Sekcja Getting Started zawiera odnośniki do tematów pomocy, które mogą okazać się przydatne dla początkujących. W zależności od połączenia internetowego sekcje Visual Studio Headlines i Visual Studio Developer News zawierają wiadomości związane z programowaniem. Na rysunku 1.2 komputer był połączony z internetem, a więc na stronie startowej wyświetliły się różne wiadomości. Visual Studio pobiera wiadomości z kanału RSS. Aby zmienić używany przez Visual Studio adres URL, należy otworzyć menu Tools, wybrać opcję Options, otworzyć kartę Environment i wybrać opcję Startup. W polu Start Page news channel trzeba wpisać nowy adres URL. Zamiast wyświetlać stronę startową Visual Studio, możesz przy uruchamianiu wykonać kilka innych czynności. Aby zmienić zachowanie Visual Studio podczas włączania, wybierz z menu Tools pozycję Options. Następnie zaznacz pole wyboru Show all settings w celu wyświetlenia wszystkich opcji, po czym otwórz element Startup folderu Environment. W menu rozwijanym At startup możesz wybrać jedną z następujących opcji:
44
Część I
IDE
Rysunek 1.2. Domyślnie Visual Studio wyświetla stronę startową
Open Home Page — otwórz stronę domową.
Load last loaded solution — otwórz ostatnie załadowane rozwiązanie.
Show Open Project dialog box — wyświetl okno dialogowe Open Project.
Show New Project dialog box — wyświetl okno dialogowe New Project.
Show empty environment — wyświetl puste środowisko.
Show Start Page — wyświetl stronę startową.
Tworzenie projektu Po uruchomieniu Visual Studio można wyświetlić widoczne na rysunku 1.3 okno New Project. W tym celu należy kliknąć odnośnik Create Project na stronie startowej lub wybrać z menu File polecenie New Project. Z drzewa Project types po lewej stronie wybierz interesującą Cię kategorię projektów, a następnie po prawej stronie — konkretny typ projektu. W oknie na rysunku 1.3 został wybrany typ Windows Forms Application. W znajdującym się niżej polu tekstowym wpisz nazwę dla tego projektu, po czym kliknij przycisk OK.
Rozdział 1.
Wprowadzenie do IDE
45
Rysunek 1.3. Okno dialogowe New Project pozwala na utworzenie nowego projektu
Na rysunku 1.4 przedstawiono IDE bezpośrednio po utworzeniu nowego projektu Windows Forms Application. Pamiętaj, że środowisko to posiada bardzo duże możliwości konfiguracyjne, więc na Twoim komputerze może wyglądać inaczej niż na rysunku 1.4, o ile dostosowano je do czyichś potrzeb.
Rysunek 1.4. Początkowo IDE wygląda mniej więcej tak
46
Część I
IDE
Najważniejsze części IDE zostały na rysunku 1.4 oznaczone numerami. Poniżej znajduje się lista, na której krotko opisano każdy z tych elementów: 1.
Menu — zawierają standardowe polecenia Visual Studio. Służą przede wszystkim do modyfikacji aktualnego rozwiązania i zawartych w nim modułów, chociaż można je dostosować do własnych potrzeb. Visual Studio zmienia menu i ich zawartość — w zależności od aktualnie wybranego obiektu. Na rysunku 1.4 widać otwarty projektant formularzy (Form Designer) — oznaczony numerem 4 — a więc IDE wyświetla menu do edycji formularzy.
2. Paski narzędzi — zawierają często używane narzędzia. Te same polecenia mogą
być dostępne także w menu, ale wybieranie ich z pasków narzędzi jest łatwiejsze i szybsze. IDE definiuje kilka standardowych pasków narzędzi, takich jak Formatting (formatowanie), Debug (usuwanie błędów) czy Image Editor (edycja obrazów). Można także tworzyć własne paski narzędzi, które będą zawierały ulubione polecenia. Visual Studio zmienia wyświetlane paski narzędzi w zależności od aktualnie wybranego obiektu. 3. Toolbox — sekcja ta zawiera narzędzia dla aktualnie wybranego elementu i dla
typu projektu, nad którym właśnie się pracuje. Na rysunku 1.4 widać wybrany projektant formularzy w aplikacji Windows Forms, a więc sekcja Toolbox zawiera narzędzia dla projektanta formularzy. Wśród nich znajdują się kontrolki i komponenty Windows Forms oraz polecenia dostępne na innych kartach — Common Controls, Data, Components itd. Do sekcji Toolbox można dodać inne niestandardowe karty, zawierające ulubione kontrolki i komponenty programisty. W różnych typach projektów mogą być dostępne odmienne narzędzia. Na przykład projekt Web wyświetlałby kontrolki i komponenty sieciowe zamiast komponentów Windows Forms. 4. Projektant formularzy — pozwala na modyfikowanie graficznego projektu
formularza. Aby umieścić kontrolkę na formularzu, należy kliknąć ją w sekcji Toolbox i przeciągnąć w wybrane miejsce na nim. W celu zmiany własności tej nowej kontrolki trzeba skorzystać z okna Properties (oznaczone numerem 6). Na rysunku 1.4 widać, że nie została wybrana żadna kontrolka, a więc okno Properties wyświetla własności formularza. 5. Solution Explorer — umożliwia zarządzanie plikami związanymi z aktualnym
rozwiązaniem. Na przykład na rysunku 1.4 widać, że można by zaznaczyć w oknie Project Explorer plik Form1.vb i kliknąć przycisk View Code (trzecia ikona od prawej, w górnej części okna Solution Explorer), aby otworzyć edytor kodu formularza. Istnieje też możliwość kliknięcia prawym przyciskiem myszy obiektu znajdującego się w oknie Solution Explorer w celu uzyskania listy dostępnych dla niego poleceń. 6. Properties — okno to pozwala modyfikować własności projektu w czasie
projektowania. Kiedy w projektancie formularzy lub w oknie Solution Explorer zostanie wybrany jakiś obiekt, w Properties wyświetlą się jego własności. Aby zmienić własności którejś z nich, należy ją kliknąć i wprowadzić nową wartość. 7. Error List — okno Error List wyświetla błędy i ostrzeżenia dla aktualnego
projektu. Jeśli na przykład zostanie użyta niezadeklarowana zmienna, zostanie to zgłoszone właśnie tutaj.
Rozdział 1.
Wprowadzenie do IDE
47
Jak widać w dolnej części rysunku 1.4, w oknie Error List znajduje się kilka kart. Jedna z nich, Task List, zawiera części oznaczone do dalszej obróbki, na przykład elementy do zrobienia. Okno Command Window pozwala na wykonywanie takich poleceń Visual Studio, jakie znajdziesz w menu, zaś dzięki Immediate wykonasz polecenia Visual Basica, najczęściej w czasie działania programu, kiedy zostanie on chwilowo wstrzymany. Karta Output zawiera dane wydrukowane przez aplikację. Z reguły program komunikuje się z użytkownikiem poprzez formularze i okna dialogowe, ale w tym miejscu może wyświetlać informacje pomocne w znajdowaniu błędów. Okno Output wyświetla też komunikaty wygenerowane przez środowisko. Jeśli na przykład zostanie skompilowana aplikacja, IDE wyśle do tego okna informacje, co robi, a także czy operacja ta zakończyła się powodzeniem. Projekt jest gotowy do użytku od razu po utworzeniu. Można uruchomić ten program za pomocą polecenia Start Debugging z menu Debug. Na tym etapie widać tylko pusty formularz bez żadnych kontrolek. Obsługuje on jednak automatycznie mnóstwo przyziemnych operacji okienkowych. Na przykład zanim napisany zostanie choćby jeden wiersz kodu, będzie można zmieniać rozmiary formularza, minimalizować go, przywracać i zamykać. Formularz rysuje swój pasek tytułu, obramowanie, menu systemowe, po czym tworzy się ponownie, gdy zostanie przykryty przez inne okno i przywrócony. System operacyjny również automatycznie obsługuje wiele zadań, na przykład wyświetlanie formularza w pasku zadań i menedżerze zadań. Windows Vista automatycznie generuje ikony podglądu dla swoich narzędzi Flip i Flip 3D, które można wyświetlić za pomocą kombinacji klawiszy Alt+Tab i Windows+Tab. Formularz nie zawiera jeszcze żadnych kontrolek, nie potrafi otwierać plików ani przetwarzać danych. W rzeczywistości nie może robić nic wyjątkowego, ale znaczna część pracy została już wykonana za Ciebie. Obsługiwane są wszystkie czynności okien, dzięki czemu możesz zająć się właściwymi funkcjami aplikacji.
Zapisywanie projektu Dodawanie kontrolek do formularzy i pisanie kodu oddziałującego z formularzem zostało opisane w dalszych rozdziałach. Teraz wyobraź sobie, że zbudowałeś kompletny projekt z kontrolkami i kodem. Jeśli będzie się próbowało zamknąć Visual Studio lub rozpocząć nowy projekt, pojawi się okno z rysunku 1.5. Trzeba będzie kliknąć przycisk Save, aby pojawiło się okno dialogowe Save Project z rysunku 1.6, a następnie przycisk Discard w celu usunięcia istniejącej kompozycji i rozpoczęcia nowej. Jeżeli kliknięty zostanie przycisk Cancel, będzie można kontynuować edycję aktualnego projektu. Podczas pracy nad nowym projektem Visual Studio zapisuje definicje jego formularzy i kod w tymczasowym katalogu. Przy każdym uruchomieniu programu te pliki są aktualizowane, dzięki czemu nie stracisz wszystkiego w razie awarii. Są jednak nadal tylko tymczasowe. Kiedy projekt będzie już gotowy do zapisania na stałe, otwórz menu File i kliknij opcję Save All, aby wyświetlić okno dialogowe widoczne na rysunku 1.6.
48
Część I
IDE
Rysunek 1.5. Przed zamknięciem Visual Studio lub rozpoczęciem nowego projektu trzeba zdecydować, co zrobić z poprzednim
Rysunek 1.6. Okno dialogowe, służące do zapisywania nowych projektów
W polu Name znajduje się nazwa, która została nadana projektowi podczas jego tworzenia. Sprawdź, czy jest ona poprawna; w razie potrzeby zmień ją. Następnie przejdź do folderu, w którym chcesz zapisać projekt. Domyślna lokalizacja jest podobna do tej niezbyt przyjaznej dla użytkownika, którą widać na ilustracji 1.6. Rysunek ten został wykonany, kiedy byłem zalogowany jako użytkownik o nazwie Developer. Gdy będziesz zachowywać swój własny projekt, słowo „Developer” zostanie zastąpione Twoją nazwą użytkownika. Upewnij się przed kliknięciem przycisku Save, że wybierasz odpowiednie miejsce do zapisania. Gdy będziesz tworzyć kolejny projekt, domyślna lokalizacja będzie taka sama jak ta, więc nie trzeba będzie zachowywać aż tak daleko posuniętej ostrożności, przy założeniu, że chcesz zapisać wiele projektów w tym samym folderze. Jeśli zaznaczysz pole wyboru Create directory for solution, Visual Studio uaktywni pole tekstowe Solution Name i doda katalog nad katalogiem projektu przechowującym rozwiązanie. Opcja ta jest najbardziej przydatna, kiedy konieczne jest umieszczenie więcej niż jednego projektu w pojedynczym rozwiązaniu. Załóżmy na przykład, że pracujesz nad graficznym narzędziem do rozmieszczania mebli biurowych — systemem CAD do aranżacji biurek i krzeseł. Chcesz, aby rozwiązanie zawierało wykonywalny projekt główny i narzędzie rysujące w postaci projektu DLL. W takiej sytuacji rozwiązanie można nazwać OfficeArranger, a wykonywalny projekt — OfficeArrangerMain. Visual Studio utworzyłoby katalog o nazwie OfficeArranger, a projekt wykonywalny umieściłoby w podkatalogu OfficeArrangerMain. Później można by dodać projekt DLL o nazwie OfficeArrangerTools i umieścić go także w katalogu rozwiązania OfficeArranger. Po wprowadzeniu nazwy i lokalizacji projektu oraz opcjonalnym określeniu osobnego katalogu rozwiązania kliknij przycisk Save.
Rozdział 1.
Wprowadzenie do IDE
49
Polecenie Save As z menu File pozwala na zapisywanie poszczególnych części rozwiązania w nowych plikach. Jeśli na przykład w oknie Project Explorer został wybrany projekt o nazwie OfficeArrangerMain, menu File będzie zawierać polecenie Save OfficeArrangerMain As. Pozwala ono na zapisanie pliku projektu pod nową nazwą. Niestety nie wykonuje nowej kopii całego projektu, a tylko kopię pliku projektu. Ten ostatni zawiera jedynie informacje o projekcie, takie jak odwołania używane w projekcie, pliki importowane przez niego czy nazwy dołączonych do niego formularzy. Nie znajdziesz tam jednak samych formularzy. Wielu początkujących użytkowników próbuje za pomocą polecenia Save As wykonywać kopie projektów lub rozwiązań — nie jest to jednak możliwe. Trzeba znaleźć folder, w którym znajduje się cały projekt lub całe rozwiązanie, po czym zrobić jego kopię.
Podsumowanie W tym rozdziale przedstawiono rozpoczynanie pracy w zintegrowanym środowisku programistycznym Visual Studio. Opisano konfigurację tego IDE dla różnych rodzajów zadań programistycznych, a także wyjaśniono, że różne konfiguracje mogą sprawić, iż Visual Studio czytelnika będzie wyglądać inaczej niż na prezentowanych w tej książce zrzutach ekranu. Ponadto omówiono projekty i rozwiązania Visual Studio oraz sposoby tworzenia, uruchamiania i zapisywania nowych projektów. W kolejnych kilku rozdziałach znajdą się bardziej szczegółowe opisy poszczególnych części IDE. Rozdział 2. — „Menu, paski narzędzi i okna” — zawiera charakterystykę poleceń dostępnych w IDE oraz menu, pasków narzędzi i drugorzędnych okien, w których się one znajdują.
50
Część I
IDE
2
Menu, paski narzędzi i okna IDE Visual Studio, udostępniające setki narzędzi do budowy i modyfikowania projektów, jest niezwykle potężne. Ceną, jaką trzeba za to wszystko zapłacić, jest złożoność. Ponieważ dostępnych jest tak wiele narzędzi, znalezienie potrzebnego może zabrać nieco czasu, nawet jeśli wiadomo, czego się szuka. W tym rozdziale opisane zostaną menu, paski narzędzi i okna zawierające narzędzia, z których można korzystać w tym IDE. Znajdzie się tu również objaśnienie zastosowania niektórych najbardziej przydatnych opcji, które są w nim dostępne, a także wskazówka, gdzie można je znaleźć, pod warunkiem że nie zostały gdzieś przeniesione podczas dostosowywania do indywidualnych potrzeb. Po lekturze tego rozdziału będziesz umieć dostosowywać menu i paski narzędzi, co pozwoli Ci uzyskać łatwy dostęp do najczęściej używanych poleceń oraz jak ukrywać te, z których korzysta się rzadko.
Menu Menu IDE zawierają standardowe polecenia Visual Studio. Służą one z reguły do manipulowania projektem i zawartymi w nim modułami. Niektóre z tych pojęć są znane ze wszystkich aplikacji systemu Windows (File/New, File/Save, Help/Contents), jednak wiele elementów dotyczy tylko programowania w Visual Studio. Dlatego w poniższych sekcjach zostaną one opisane bardziej szczegółowo. Menu można dostosowywać do własnych potrzeb — dodawać je, usuwać i przestawiać oraz robić to samo ze znajdującymi się w nich elementami. Może to być jednak źródłem zamieszania, jeśli będzie konieczne znalezienie polecenia, które zostało usunięte ze swojego normalnego miejsca. Niektórzy programiści dodają opcje do standardowych menu, zwłaszcza do menu Tools, ale usuwanie z nich czegokolwiek jest ogólnie ryzykowne. Z reguły najlepiej jest zostawić standardowe menu w spokoju, a dla własnych narzędzi utworzyć osobne paski. Więcej informacji na ten temat znajdziesz w rozdziale 3.
52
Część I
IDE
Do wielu z najbardziej przydatnych poleceń menu można uzyskać dostęp także na inne sposoby, na przykład za pomocą skrótów klawiszowych, które przyspieszają i ułatwiają korzystanie z nich. Kombinacja klawiszy Ctrl+N otwiera okno dialogowe New Project, tak jakby została wybrana opcja New Project z menu File. Jeśli bardzo często używasz jakiegoś polecenia, zajrzyj do menu i zapamiętaj jego skrót klawiszowy. W ten sposób zaoszczędzisz sporo czasu. Wiele poleceń menu jest też dostępnych w standardowych paskach narzędzi. Na przykład pasek Debug zawiera sporo opcji, które można znaleźć w menu Debug. Jeśli często używasz jakiegoś zbioru poleceń, możesz wyświetlić odpowiadający im pasek narzędzi, aby mieć do nich łatwiejszy dostęp. Sporo poleceń w Visual Studio jest też dostępnych poprzez menu kontekstowe. Na przykład kliknięcie prawym przyciskiem myszy projektu w oknie Solution Explorer sprawi, że menu kontekstowe będzie zawierać polecenie Add Reference, które wyświetla okno dialogowe Add Reference, tak jakby zostało ono otwarte za pomocą opcji Add Reference z menu Project. Często łatwiej jest znaleźć polecenie poprzez kliknięcie prawym przyciskiem myszy odpowiedniego obiektu niż szukanie w różnych menu. W poniższych podrozdziałach znajduje się opis ogólnego układu standardowych menu. Uruchom Visual Studio, aby sprawdzać na bieżąco, o czym czytasz. Visual Studio wyświetla różne menu i polecenia — w zależności od tego, jaki edytor jest aktywny. Jeśli na przykład otwarty jest edytor formularzy, a w nim formularz, Visual Studio wyświetla menu Format, za pomocą którego można ustawiać kontrolki na tym właśnie formularzu. Kiedy masz otwarty edytor kodu, menu Format jest ukryte, ponieważ nie zawiera ono opcji dla kodu.
Menu File Menu File zawiera polecenia służące do tworzenia, otwierania, zapisywania i zamykania projektów oraz plików projektów. Na poniższej liście znajdują się najważniejsze polecenia z w menu File i jego podmenu:
New Project — zamyka bieżący projekt i wyświetla okno dialogowe widoczne na rysunku 2.1. W oknie tym można utworzyć nową aplikację Windows, bibliotekę klas, aplikację konsolową, bibliotekę kontrolek i wiele więcej. Należy wybrać swój typ projektu i kliknąć przycisk OK.
New Web Site — zamyka bieżący projekt i rozpoczyna nowy projekt witryny internetowej. Wyświetla okno dialogowe, w którym można wybrać typ witryny internetowej spośród takich opcji, jak ASP.NET Web Site, ASP.NET Web Service i Personal Web Site.
Open Project — zamyka bieżący projekt i pozwala otworzyć inny istniejący.
Open Web Site — zamyka bieżący projekt i pozwala otworzyć istniejący projekt witryny internetowej.
Rozdział 2.
Menu, paski narzędzi i okna
Rysunek 2.1. Okno dialogowe New Project pozwala na rozpoczęcie wielu różnych typów projektów
Open File — wyświetla okno dialogowe widoczne na rysunku 2.2 i pozwala wybrać plik do otwarcia. Do edycji wybranego pliku IDE używa swoich zintegrowanych edytorów. Na przykład prosty edytor map bitowych pozwala ustawić rozmiar takiej mapy, zmienić jej liczbę kolorów i rysować na niej. Podczas zamykania pliku Visual Studio zapyta, czy chcesz zapisać zmiany — pozwoli wybrać miejsce, w którym plik ma zostać zachowany. Pamiętaj, że nie oznacza to automatycznego dodania go do bieżącego projektu. Aby dodać plik, należy go zapisać, po czym skorzystać z opcji Project/Add Existing Item.
Rysunek 2.2. Okno dialogowe Open File pozwala wybierać pliki do podglądu i edycji
53
54
Część I
IDE
Add — to podmenu pozwala dodawać nowe elementy do bieżącego rozwiązania. Może być bardzo przydatne podczas pracy nad dwoma ściśle powiązanymi projektami. Na przykład w aplikacji Windows Forms, wywołującej procedury zapisane w bibliotece klas, można oba projekty załadować do jednego rozwiązania i pracować nad nimi jak nad jednym. Do najważniejszych dla programistów Visual Basica poleceń tego podmenu należą New Project i Existing Project — dodają one nowy lub już istniejący projekt Visual Basica do rozwiązania.
Close — Zamyka aktualny edytor. Na przykład podczas modyfikacji kontrolek formularza w projektancie formularzy polecenie to zamknęłoby ten edytor.
Close Project — zamyka cały projekt i wszystkie zawarte w nim pliki. Jeśli jest otwarte rozwiązanie, to polecenie nazywa się Close Solution; zamyka ono całą aplikację.
Save Form1.vb — zamyka aktualnie otwarty plik, w tym przypadku Form1.vb.
Save Form1.vb As — zapisuje aktualnie otwarty plik w nowym pliku.
Save All — zapisuje wszystkie zmodyfikowane pliki. Gdy rozpoczynasz nowy projekt, pliki te są początkowo przechowywane w katalogu tymczasowym. Polecenie to pozwala na wybranie folderu, w którym projekt zostanie zapisany na stałe.
Export Template — wyświetla okno dialogowe widoczne na rysunku 2.3. Kreator Export Template Wizard pozwala tworzyć szablony projektów lub elementów, których można używać później przy rozpoczynaniu innych projektów.
Rysunek 2.3. Polecenie File/Export Template wyświetla powyższe okno dialogowe, w którym można utworzyć szablon projektu lub elementu, mający być później używanym w innych projektach
Rozdział 2.
Menu, paski narzędzi i okna
Page Setup i Print — pozwalają na konfigurację ustawień drukarki i wydruk bieżącego dokumentu. Polecenia te są aktywne tylko wtedy, gdy da się wydrukować bieżący plik — na przykład podczas przeglądania dokumentu z kodem źródłowym lub pliku konfiguracyjnego (który zawiera tekst). Gdy ogląda się mapę bitową lub formularz w trybie projektowania, polecenia te są wyłączone.
Recent Files i Recent Projects — podmenu Recent Files i Recent Projects pozwalają szybko otworzyć pliki, projekty i rozwiązania, na których niedawno się pracowało.
55
Menu Edit Menu Edit zawiera polecenia związane z obróbką tekstu i innych obiektów. Należą do nich takie standardowe polecenia, jak Undo (cofnij), Redo (powtórz), Cut (wytnij), Copy (kopiuj), Paste (wklej) i Delete (usuń). Poniżej znajduje się lista pozostałych poleceń menu Edit:
Find Symbol — pozwala przeszukać aplikację w celu znalezienia symbolu programowego, a nie zwykłego łańcucha. Można szukać elementów następujących typów — przestrzeni nazw, typów, interfejsów, własności, metod, stałych i zmiennych.
Quick Find — wyświetla okno dialogowe wyszukiwarki, w którym można przeszukać projekt, aby znaleźć określony tekst. W menu rozwijanym wybierzesz, czy przeszukany ma zostać tylko bieżący dokument, wszystkie otwarte dokumenty, bieżący projekt, czy bieżący blok. Dostępne opcje pozwalają zdecydować, czy ma być rozróżniana wielkość liter, a także czy mają być szukane tylko całe słowa.
Quick Replace — wyświetla to samo okno dialogowe co Quick Find, tyle że posiadające kilka dodatkowych kontrolek. Zawiera ono pola tekstowe do wpisywania tekstu oryginalnego i zastępczego oraz przyciski pozwalające zastąpić aktualnie znaleziony tekst lub wszystkie wystąpienia określonego tekstu innym tekstem.
Uważaj na funkcję Quick Replace. Często zamienia ona podłańcuchy dłuższych łańcuchów, przez co stają się one nieczytelne. Wyobraźmy sobie na przykład, że chcesz zamienić nazwę zmiennej hand na handed. Jeśli użyjesz polecenia Quick Replace wszystkie wystąpienia łańcucha Handles zostaną zamienione na handedles, co spowoduje błędy w Visual Basicu. Aby zminimalizować ryzyko wystąpienia tego typu błędów, używaj tej funkcji jak najrzadziej i sprawdzaj, czy nie wywołała żadnych niepożądanych efektów.
Go to — przenosi do wiersza o wybranym numerze w bieżącym pliku.
Insert File As Text — wstawia tekst zawarty w wybranym pliku w bieżącym miejscu. Może być przydatne, jeśli taki tekst zawiera fragment kodu.
Advanced — zawiera polecenia dotyczące bardziej skomplikowanych operacji formatowania dokumentu, takich jak konwersja tekstu na wielkie lub małe litery, ustawianie zawijania wierszy czy umieszczanie kodu w komentarzach i usuwanie komentarzy z kodu.
Bookmarks — pozwala dodawać, usuwać i czyścić zakładki oraz przechodzić do kolejnej lub poprzedniej z nich. Za pomocą zakładek można szybko przejść do wybranych wcześniej oznaczonych fragmentów kodu.
56
Część I
IDE
Outlining — pozwala rozwijać i zwijać sekcje kodu oraz włączać i wyłączać kontury. Dzięki zwinięciu kodu, nad którym aktualnie się nie pracuje, łatwiej jest czytać właściwy.
IntelliSense — daje dostęp do opcji funkcji IntelliSense. Na przykład polecenie List Members powoduje wyświetlenie własności, metod i zdarzeń bieżącego obiektu.
Next Method/Previous Method — przenoszą do kolejnej lub poprzedniej metody lub klasy w bieżącym dokumencie.
Menu View Menu View zawiera polecenia pozwalające włączyć lub ukryć różne okna i paski narzędzi w IDE Visual Studio. Poniżej znajduje się krótki opis poleceń w nim dostępnych:
Code — otwiera wybrany plik w oknie edytora kodu. Aby na przykład modyfikować kod formularza, można go kliknąć w oknie Solution Explorer, a następnie kliknąć polecenie View/Code.
Designer — otwiera wybrany plik w edytorze graficznym, jeśli został zdefiniowany dla wybranego typu pliku. Jeżeli na przykład stanowi on formularz, zostanie otwarty w graficznym edytorze formularzy. Jeśli plik jest klasą albo modułem kodu, menu View ukryje to polecenie, ponieważ Visual Studio nie posiada edytora graficznego dla tego typu plików.
Standardowe okna — kilka kolejnych poleceń tego menu otwiera standardowe okna IDE — Server Explorer, Solution Explorer, Object Browser, Error List, Properties Window i Toolbox. Polecenia te okazują się pomocne, gdy jedno z tych okien zostało zamknięte i trzeba je przywrócić. Najbardziej przydatne z nich zostały opisane w dalszej części tego rozdziału.
Other Windows — zawiera listę innych standardowych menu, których nie ma w samym menu View. Należą do nich Bookmark Window, Class View, Command Window, Document Outline, Output, Task List, Macro Explorer, Start Page i wiele innych. Służą one — podobnie jak polecenia otwierające standardowe okna — do przywracania innych zgubionych lub ukrytych okien.
Tab Order — jeśli aktualnie widoczny dokument jest formularzem Windows zawierającym kontrolki, polecenie to wyświetli kolejność aktywacji na każdej kontrolce. Kolejność aktywacji kontrolek można łatwo ustawić poprzez kliknięcie ich w wybranym porządku (aby ustawić kolejność aktywacji kontrolek w formularzach WPF, trzeba aktualnie używać ich własności TabIndex).
Toolbars — pozwala ukrywać i wyświetlać aktualnie zdefiniowane paski narzędzi. Zawiera listę standardowych pasków i wszystkie paski niestandardowe, które zostały utworzone przez użytkownika.
Zamiast modyfikować standardowe menu i paski narzędzi, ukryj je i stwórz nowe. Dzięki temu w razie potrzeby będziesz mieć możliwość odzyskania oryginałów.
Rozdział 2.
Menu, paski narzędzi i okna
Full Screen — polecenie to ukrywa wszystkie paski narzędzi i okna poza aktualnie używanym edytorem. Chowa także pasek zadań Windows, dzięki czemu IDE zajmuje całą dostępną przestrzeń. W ten sposób programista otrzymuje najwięcej miejsca do pracy nad otwartymi plikami. Polecenie to dodaje mały przycisk do paska tytułu, za pomocą którego można wyłączyć tryb pełnoekranowy.
Property Pages — wyświetla strony własności aktualnego elementu. Jeśli na przykład w oknie Solution Explorer zostanie zaznaczona jakaś aplikacja, polecenie to wyświetli strony jej własności, podobne do widocznej na rysunku 2.4.
57
Rysunek 2.4. Polecenie Property Pages z menu View wyświetla strony własności aplikacji
Menu Project Menu Project zawiera polecenia pozwalające dodawać i usuwać elementy projektu. To, które opcje są dostępne w danym momencie, zależy od aktualnie zaznaczonego elementu. Poniżej znajduje się opis poleceń menu Project:
New Items — kilka pierwszych poleceń pozwala dodawać nowe elementy do projektu. Ich użycie nie powinno nastręczać problemów. Na przykład polecenie Add Class dodaje nowy moduł klasy do projektu. Z kolejnych rozdziałów dowiesz się, jak używać każdego z tych typów plików.
Add new Item — wyświetla okno dialogowe widoczne na rysunku 2.5, które oferuje szeroki wachlarz elementów do wyboru, na przykład About, pliki tekstowe, pliki map bitowych i moduły klas.
58
Część I
IDE
Rysunek 2.5. Polecenie Add New Item z menu Project umożliwia dodawanie wielu różnych elementów do projektu
Add Existing Item — polecenie to pozwala na wyszukanie pliku na dysku i dodanie go do projektu. Może to być plik z kodem Visual Basica (moduł, formularz lub klasa) lub jakiś inny (na przykład związany z projektem dokumentu lub obrazu).
Exclude From Project — usuwa wybrane elementy z projektu, pozostawia jednak pliki elementów.
Show All Files — polecenie to pokazuje w oknie Solution Explorer wszystkie te pliki, które zwykle są ukryte. Zaliczają się do nich pliki zasobów związane z formularzami, ukryte klasy częściowe, takie jak wygenerowany przez projektant kod formularza, pliki zasobów i pliki z katalogów obj i bin, które są automatycznie tworzone przez Visual Studio podczas kompilacji programu. Zwykle nie są one potrzebne do pracy, dlatego mogą być ukryte. Kliknięcie tego polecenia jeden raz powoduje wyświetlenie tych plików. Drugie kliknięcie z powrotem je ukrywa.
Add Reference — polecenie to wyświetla okno widoczne na rysunku 2.6. Wybierz kategorię zewnętrznego obiektu, klasy lub biblioteki, którą chcesz znaleźć. Dla komponentu .NET kliknij kartę .NET. Dla komponentu COM (ang. Component Object Model), jak biblioteka ActiveX lub kontrolka utworzona za pomocą Visual Basica 6, wybierz kartę COM. Na karcie Projects można dodawać odwołania do innych projektów Visual Studio. Aby ręcznie wybrać plik, należy kliknąć kartę Browse.
Rozdział 2.
Menu, paski narzędzi i okna
59
Rysunek 2.6. Okno dialogowe Add Reference, które służy do dodawania odwołań do bibliotek
Wybierz z listy przewijanej odpowiedni element. Aby zaznaczyć więcej niż jedną bibliotekę, należy użyć przycisków Shift i Ctrl z kliknięciem. Po dokonaniu wyboru kliknij przycisk OK w celu dodania odwołania do projektu. Będzie można odwoływać się w kodzie do obiektów publicznych tego odwołania. Jeśli na przykład plik MyMathLibrary.dll zawiera definicję klasy o nazwie MathTools, udostępniającej publiczną funkcję o nazwie Fibonacci, w projekcie posiadającym odwołanie do tej biblioteki DLL można użyć następującego kodu: Dim math_tools As New MyMathLibrary.MathTools MsgBox.Show("Fib(5) = " & math_tools.Fibonacci(5))
Add Service Reference — wyświetla okno dialogowe widoczne na rysunku 2.7. Za jego pomocą znajdziesz usługi sieciowe i dodasz do nich odwołania, aby projekt mógł je wywoływać poprzez internet. Na rysunku 2.7 przedstawiono odwołanie do usługi TerraServer. Więcej informacji na jej temat można znaleźć pod adresem terraserver.microsoft.com.
Windows Application1 Properties — wyświetla strony własności aplikacji widoczne na rysunku 2.4.
Aby obejrzeć i zmienić różnego rodzaju ustawienia aplikacji, należy użyć kart znajdujących się po lewej stronie strony własności programu. Wiele z tych parametrów można pozostawić nieustalone, niektóre zaś mają wartości domyślne. Na przykład widoczne na rysunku 2.4 wartości Assembly name i Root namespace domyślnie przyjmują nazwę projektu nadaną podczas jego tworzenia. Na rysunku 2.8 przedstawiono stronę własności Compile. Zawiera on cztery opcje, które zasługują na omówienie.
60
Część I
IDE
Rysunek 2.7. Okno dialogowe Add Service Reference służy do dodawania odwołań do usług sieciowych
Rysunek 2.8. Karta Compile zawiera własności ważne z punktu widzenia kontroli generowania kodu
Rozdział 2.
Menu, paski narzędzi i okna
61
Pierwsza z nich, Option Explicit, określa, czy wymagane jest zadeklarowanie wszystkich zmiennych przed ich użyciem. Wyłączenie tej opcji może prowadzić do powstawania błędów. Na przykład poniższa procedura ma z założenia drukować liczby parzyste ze zbioru od 1 do 10. Niestety wkradł się błąd typograficzny, w wyniku którego instrukcja Debug.WriteLine zamiast drukować wartości zmiennej i, drukuje wartości zmiennej j. Ponieważ zmienna j nie została zainicjowana, instrukcja ta drukuje kilka pustych wartości. Jeśli jest włączona opcja Option Explicit kompilator zgłosi problem niezadeklarowanej zmiennej j; będzie można łatwo go naprawić. For i = 1 To 10 If i Mod 2 = 0 Then Debug.WriteLine(j) Next i
Druga opcja kompilatora to Option Strict. Jej wyłączenie oznacza, że kompilator może niejawnie konwertować typy danych, nawet jeśli nie są ze sobą zgodne. Na przykład Visual Basic pozwoli na próbę skopiowania w poniższym kodzie łańcucha s do zmiennej całkowitoliczbowej i. Jeśli wartością łańcucha jest przypadkowa liczba, jak w pierwszym przypadku, wszystko jest w porządku. Jeżeli łańcuch nie jest liczbą, jak w drugim przypadku, w czasie działania nastąpi awaria. Dim Dim s = i = s = i =
i As Integer s As String "10" s ' To działa. "Witaj" s ' To nie zadziała.
Jeśli opcja Option Strict zostanie włączona, IDE ostrzeże o niezgodnych typach danych, dzięki czemu łatwo rozwiążesz ten problem. Nadal będzie można używać funkcji konwersji, takich jak CInt, Int i Integer.Parse do konwersji łańcucha na liczbę całkowitą, ale trzeba będzie robić to jawnie. Należy zatem zastanowić się nad kodem i zminimalizować ryzyko, że konwersja będzie przypadkowa. Pomaga to także używać właściwych typów danych i uniknąć niepotrzebnych konwersji, które mogą spowolnić program. Trzecia dyrektywa kompilatora, Option Compare, może mieć wartość Binary lub Text. Przy ustawieniu na Binary Visual Basic porównuje łańcuchy za pomocą ich binarnych reprezentacji. Ustawienie na Text powoduje porównywanie łańcuchów metodą nierozróżniającą małych i wielkich znaków, która jest zależna od ustawień lokalnych. Opcja Binary jest szybsza, ale nie zawsze daje spodziewane wyniki. Ostatnia dyrektywa kompilatora to Option Infer. Określa ona, czy przy deklaracji zmiennej można pominąć typ danych i pozwolić Visual Basicowi odgadnąć jej typ z kontekstu. Na przykład pierwsza instrukcja w poniższym kodzie jawnie deklaruje zmienną x jako typu Single. Druga deklaruje zmienną y bez określenia typu danych. Ponieważ wartość inicjująca zmiennej y wygląda jak typ Double, Visual Basic dedukuje, że zmienna ta powinna być typu Double. Dim x As Single Dim y = 3.14159265
62
Część I
IDE
Wadą takiego zgadywania typów danych jest to, że z kodu nie wynika jasno, jaki typ danych zostanie użyty. Aby móc w powyższym przykładzie odgadnąć, czy zmienna y jest typu Single, Double, czy Decimal, trzeba znać zasady typowania Visual Basica. W celu ustawienia wartości tych wszystkich opcji można na początku kodu użyć instrukcji Option. Na przykład poniższy kod włącza dla modułu opcję Option Explicit, a wyłącza Option Infer: Option Explicit On Option Infer Off
Aby ustawić te opcje dla wszystkich plików aplikacji, należy użyć pokazanej na rysunku 2.8 strony własności. Jeśli chcesz uniknąć nieporozumień i długiego poszukiwania błędów, ustaw restrykcyjne parametry na stronie własności Compile opcji Option Explicit i Option Strict na On, a Option Infer na Off. Aby poluzować te ograniczenia w jednym pliku, wystarczy użyć w nim instrukcji Option. Na przykład może być konieczne ustawienie opcji Option Infer na On w module wykorzystującym LINQ. Więcej informacji o LINQ znajdziesz w rozdziale 21.
Menu Build Menu Build zawiera polecenia pozwalające kompilować projekty w obrębie rozwiązania. Poniżej znajduje się lista poleceń dostępnych w tym menu:
Build WindowsApplication1 — kompiluje aktualnie wybrany projekt, w tym przypadku WindowsApplication1. Visual Studio sprawdza każdy z plików projektu, aby dowiedzieć się, czy od ostatniej kompilacji zostały w nich wprowadzone jakieś zmiany. Jeśli którykolwiek z tych plików został zmodyfikowany, program ponownie go skompiluje w celu aktualizacji.
Rebuild WindowsApplication1 — kompiluje aktualnie wybrany projekt od początku. Poprzednie polecenie kompilowało tylko te pliki, które zostały zmodyfikowane, to zaś pakuje wszystkie od nowa.
Clean WindowsApplication1 — usuwa tymczasowe i pośrednie pliki, które zostały utworzone podczas kompilacji aplikacji, a pozostawia tylko źródłowe i końcowe .exe i .dll.
Publish WindowsApplication1 — wyświetla okno kreatora Publish Wizard — widoczne na rysunku 2.9. Kreator ten przeprowadza programistę przez proces udostępniania aplikacji jako pliku lokalnego, współdzielonego zasobu, na serwerze FTP lub na stronie internetowej.
Run Code Analysis on WndowsApplication1 — wykonuje analizę projektu i opisuje potencjalne problemy. Wiele ostrzeżeń dotyczy globalizacji, której Visual Studio nie obsługuje automatycznie.
Generate Code Metrics for WindowsApplication1 — generuje wskaźniki szacujące złożoność kodu. Tworzy liczby reprezentujące jego możliwości konserwacyjne (ang. maintability), złożoność (ang. complexity), głębokość dziedziczenia (inheritance depth), sprzężenie klas (class coupling) i wiersze kodu.
Rozdział 2.
Menu, paski narzędzi i okna
63
Rysunek 2.9. Kreator Publish Wizard pomaga w przygotowywaniu aplikacji do użytku
Menu Debug Menu Debug zawiera polecenia wspomagające znajdowanie błędów w programie. Pozwalają one na uruchamianie aplikacji w debugerze, przechodzenie przez kod, ustawianie i usuwanie punktów wstrzymania i ogólnie śledzenie wykonywania kodu, dzięki czemu można zobaczyć, co jest wykonywane nieprawidłowo. Więcej informacji na temat menu Debug i znajdowania błędów w kodzie Visual Basica znajduje się w rozdziale 7. — „Usuwanie błędów”.
Menu Data Menu Data zawiera polecenia związane z danymi i zasobami danych. Niektóre z nich są widoczne i aktywne tylko podczas projektowania formularza, jeśli zawiera on odpowiednie obiekty danych. Poniżej zostały opisane najbardziej przydatne polecenia menu Data:
Show Data Sources — wyświetla okno Data Sources, w którym można pracować nad źródłami danych programu. Utworzysz na przykład kontrolki związane z danymi poprzez przeciąganie i upuszczanie tabel i pól z tego okna na formularz.
Add New Data Source — wyświetla okno kreatora data Source Configuration Wizard, który prowadzi programistę przez proces dodawania źródła danych do projektu.
64
Część I
IDE
Preview Data — wyświetla okno dialogowe, które pozwala załadować dane do zbioru DataSet i przeglądać je w czasie projektowania.
Add Query — polecenie to jest dostępne podczas projektowania formularza i po wybraniu związanej ze źródłem danych kontrolki, takiej jak DataGridView lub TextBox. Otwiera ono okno dialogowe, w którym można podać zapytanie do dodania do formularza. Powoduje to umieszczenie na formularzu kontrolki ToolStrip z przyciskami ToolStripButtons, które zapełniają związaną kontrolkę poprzez wykonywanie tego zapytania.
Menu Format Menu Format zawiera polecenia ustawiające kontrolki na formularzu. Są one pogrupowane w tematyczne podmenu. Na poniższej liście opisane są podmenu menu Format:
Align — zawiera polecenia wyrównujące wybrane kontrolki na różne sposoby. Dostępne polecenia to Lefts, Centers, Rights, Tops, Middles, Bottoms i to Grid. Na przykład polecenie Lefts wyrównuje kontrolki w taki sposób, że ich lewe krawędzie ustawiają się w jednej linii. Kolejne, to Grid, wyrównuje kontrolki względem siatki. Przydaje się ono, gdy niektóre kontrolki zostaną nieco przesunięte względem siatki — na przykład z powodu użycia jednej z pozostałych opcji wyrównania albo poprzez zmianę własności Location kontrolki w oknie Properties.
Make Same Size — zawiera polecenia zmieniające rozmiar zaznaczonych kontrolek. Dostępne opcje to Width, Height, Both i Size to Grid. Ta ostatnia dostosowuje szerokości zaznaczonych kontrolek w taki sposób, że są one wielokrotnością rozmiaru siatki wyrównującej. Pozostałe polecenia nadają zaznaczonym kontrolkom taką samą szerokość, wysokość lub jedno i drugie.
Horizontal Spacing — zawiera polecenia zmieniające przestrzenie pomiędzy zaznaczonymi kontrolkami. Dostępne opcje to Make Equal, Increase, Decrease i Remove. Jeśli na przykład zostaną zaznaczone trzy kontrolki, polecenie Make Equal sprawi, że odstęp między dwiema pierwszymi będzie taki sam jak pomiędzy kolejnymi dwiema. Opcja ta może być przydatna w ustawianiu kolumn w jednej linii.
Vertical Spacing — zawiera takie same polecenia jak Horizontal Spacing, ale dostosowuje pionowe odstępy pomiędzy kontrolkami.
Center in Form — zawiera polecenia, które ustawiają zaznaczone kontrolki na środku formularza. Dostępne opcje to Horizontally i Vertically. Zauważ, że zaznaczone kontrolki są traktowane jako grupa — nie są ustawiane na środku jedna na drugiej.
Order — zawiera polecenia Bring to Front i Send to Back, które przenoszą zaznaczone kontrolki na górę lub dół stosu.
Lock Controls — blokuje wszystkie kontrolki na formularzu, przez co nie można ich przesuwać i zmieniać ich rozmiaru za pomocą myszy. Operacje da się jednak nadal wykonywać poprzez zmianę ich własności Location i Size w oknie Properties. Ponowne użycie tego polecenia odblokowuje kontrolki. Blokowanie może być przydatne, jeśli spędziło się dużo czasu na precyzyjnym ustawianiu kontrolek. Dzięki temu, że są zablokowane, nie trzeba uważać, by nie zburzyć starannie dopracowanego projektu.
Rozdział 2.
Menu, paski narzędzi i okna
65
Menu Tools Menu Tools zawiera różne narzędzia, które nie pasują do żadnego z pozostałych menu. Ponadto znajduje się tu kilka opcji, które są dostępne w innych menu, a także polecenia modyfikujące same IDE. Poniższa lista opisuje najbardziej przydatne polecenia menu Tools. Należy pamiętać, że niektóre z nich są dostępne tylko po otwarciu określonego rodzaju edytora.
Attach to Process — wyświetla okno dialogowe, które pozwala dołączyć debugera do uruchomionego procesu. Należy wybrać proces, do którego ma on zostać dołączony, i kliknąć przycisk Attach.
Connect to Device — pozwala na połączenie z fizycznym urządzeniem lub emulatorem, takim jak Pocket PC lub Smartphone, których można używać do testowania oprogramowania pisanego dla urządzeń innych niż platforma Windows, na której aplikacje są kompilowane.
Connect to Database — wyświetla okno dialogowe Connection Properties, w którym można zdefiniować połączenie z bazą danych. Zostanie ono dodane do okna Server Explorer. Będzie można go później użyć do zdefiniowania adapterów danych i innych obiektów wykorzystujących połączenia z bazami danych.
Connect to Server — wyświetla okno dialogowe, które pozwala połączyć się z serwerem baz danych.
Code Snippets Manager — uruchamia menedżera fragmentów kodu, za pomocą którego można dodawać i usuwać fragmenty kodu.
Choose Toolbox Items — wyświetla okno dialogowe, pozwalające wybrać, które narzędzia mają być widoczne w oknie Toolbox. Na przykład domyślnie komponenty OleDbDataAdapter i OleDbConnection nie są dostępne w oknie Toolbox. Możesz je do niego dodać, jeśli planujesz często ich używać.
Add-in Manager — uruchamia menedżer dodatków, który zawiera listę projektów dodatków zarejestrowanych na komputerze. Za jego pomocą można je włączać i wyłączać.
Macros — to podmenu zawiera polecenia wspomagające tworzenie, edytowanie i wykonywanie makr. Szczegóły na ten temat znajdują się w dalszej części rozdziału — w podrozdziale „Makra”.
External Tools — wyświetla okno dialogowe, które pozwala na dodawanie i usuwanie poleceń do i z menu Tools. Można na przykład umieścić polecenie uruchamiające program WordPad, MS Paint czy WinZip.
Performance Tools — zawiera polecenia, które dotyczą profilowania wydajności, takie jak Performance Wizard, New Performance Session i New Diff Report.
Device Emulation Manager — uruchamia menedżer emulacji urządzeń, który pozwala na łączenie, resetowanie, zamykanie i wykonywanie innych czynności związanych z emulatorami urządzeń.
66
Część I
IDE
Import and Export Settings — wyświetla okno dialogowe, w którym można zapisać, przywrócić lub zresetować ustawienia Visual Studio. Służy ono do konfiguracji środowisk programistycznych ogólnego przeznaczenia, testowania zespołowego, Visual Basica, C#, C++ lub Web development.
Customize — pozwala na dostosowanie do własnych potrzeb IDE Visual Studio. Szczegóły na ten temat znajdują się w rozdziale 3.
Options — pozwala określić opcje dla IDE Visual Studio. Więcej szczegółów znajdziesz w podrozdziale „Opcje”.
Makra Podmenu Macros udostępnia polecenia ułatwiające tworzenie, edytowanie i wykonywanie makr automatyzujących powtarzalne czynności programistyczne w Visual Basicu. Jeśli musisz wielokrotnie wykonać jakiś szereg kroków, możesz utworzyć makro, które będzie to robić za Ciebie. Później zamiast ręcznie wykonywać te działania, wystarczy tylko uruchomić makro. Do makr, których kiedyś używałem, należy kod: rozmieszczający kontrolki w nietypowe sposoby, jak na przykład ustawienie obrazów na obwodzie koła; generujący długie serie instrukcji, które robią to samo z jakąś kolekcją wartości tekstowych (na przykład robi instrukcje Select Case dla serii wartości tekstowych); tworzący nowe okno dialogowe poprzez utworzenie przycisków OK i Cancel, odpowiednie rozmieszczenie ich, ustawienie ich własności DialogResult i własności formularza AcceptButton oraz CancelButton; budujący formularz nazwy oraz adresu z etykiet i pól tekstowych, które posiadają odpowiednie własności Anchor. John Muller (www.mwt.net/~jmueller) używa podobnych makr do tworzenia okien dialogowych i standardowych menu, a także do budowania typowych procedur obsługi zdarzeń. Rzeczy te można zrobić również na inne sposoby, na przykład zapisać okno dialogowe jako szablon lub użyć fragmentów kodu, które zostały opisane dalej. Makra są jednak szybkie i łatwe w użyciu. Po zarejestrowaniu makra można edytować jego kod i wprowadzać w nim zmiany. Aby na przykład kod był wykonywany wiele razy, można go umieścić w pętli For. Często wystarczy rzucić okiem na kod, aby znaleźć sposób na zmodyfikowanie go w celu wykonania podobnych, ale nie identycznych czynności. Działanie większości opcji w podmenu Macros jest oczywiste. Polecenie Record TemporaryMacro służy do szybkiego rejestrowania makr do tymczasowego użytku. Po kliknięciu go pojawia się małe okienko, które zawiera przyciski pozwalające zawiesić, zakończyć lub anulować rejestrowanie. Visual Studio zapisuje wykonane polecenia w makro o nazwie TemporaryMacro. Aby je uruchomić, trzeba kliknąć polecenie Run TemporaryMacro. Jeśli zostanie utworzone nowe makro tymczasowe, stare będzie usunięte bez ostrzeżenia. Aby nie stracić starego makra, a utworzyć nowe makro tymczasowe, trzeba zapisać poprzednie pod inną nazwą za pomocą polecenia Save TemporaryMacro.
Rozdział 2.
Menu, paski narzędzi i okna
67
Polecenie Macro Explorer wyświetla okno dialogowe widoczne na rysunku 2.10. Gdy klikniesz prawym przyciskiem myszy jedno z makr, pojawi się menu podręczne, które umożliwia uruchomienie, zmianę nazwy lub usunięcie go. Zwróć uwagę na sekcję Samples, zawierającą przykładowe makra, których można używać w obecnym stanie lub po zmodyfikowaniu. Rysunek 2.10. W oknie Macro Explorer można edytować, uruchamiać i usuwać makra
Jeśli szereg czynności jest wykonywany wielokrotnie, można zastosować lepsze rozwiązanie od tworzenia makra. Być może na przykład da się powtórzyć te czynności w pętli albo przenieść wspólny kod do podprocedury, a następnie wywołać ją wielokrotnie, zamiast ciągle powtarzać kod. W takich przypadkach aplikacja nie musi zawierać długich sekwencji powtarzalnego kodu, w których ciężko jest znaleźć błędy, a do tego są trudne w konserwacji. Makra są z reguły najlepsze, kiedy trzeba pisać podobne do siebie fragmenty kodu, niedające się łatwo umieścić w procedurze, która byłaby współdzielona przez różne części aplikacji. Wyobraźmy sobie na przykład, że musisz napisać procedury obsługi zdarzeń dla kilkudziesięciu kontrolek TextBox. Przy pisaniu jednej z nich możesz zarejestrować makro. Następnie będzie można je edytować, aby generowało pozostałe kontrolki za pomocą pętli przy użyciu różnych nazw. Kod obsługujący zdarzenia można by umieścić w osobnej podprocedurze, którą wywoływałaby każda procedura obsługi zdarzeń. W ten sposób być może dałoby się uniknąć powtarzania kodu (w rzeczywistości można nawet użyć instrukcji AddHandler, aby zmusić wszystkie kontrolki do używania tej samej procedury obsługi zdarzeń — wtedy nie trzeba by nawet pisać wszystkich tych osobnych procedur obsługi zdarzeń). Makra są także przydatne w manipulowaniu IDE i wykonywaniu zadań z nim związanych. Można na przykład napisać makra pokazujące i ukrywające paski narzędzi albo zmieniające prawa dostępu do plików.
68
Część I
IDE
Okno dialogowe Options Polecenie Options z menu Tools wyświetla okno dialogowe widoczne na rysunku 2.11, które zawiera bardzo dużo stron z opcjami konfiguracyjnymi IDE Visual Studio.
Rysunek 2.11. Okno dialogowe Options pozwala na ustawianie opcji IDE
Na poniższej liście znajdują się najważniejsze kategorie opcji z okna dialogowego Options:
Environment — zawiera ogólne opcje IDE, określające na przykład, czy IDE ma używać interfejsu Multiple Document Interface (MDI), czy Single Document Interface (SDI), liczbę elementów widocznych na listach MRU, a także jak często IDE ma zapisywać informacje automatycznego odzyskiwania. Podsekcje Fonts i Colors pozwalają wskazać barwy używane przez poszczególne edytory dla różnego rodzaju tekstów. Na przykład domyślnie komentarze są zielone, ale można ten kolor zmienić.
Performance Tools — zawiera opcje dotyczące testowania wydajności, takie jak znacznik, który wskazuje, czy czas ma być prezentowany w tyknięciach zegara, czy milisekundach.
Projects and Solutions — zawiera domyślne ustawienia opcji Option Explicit, Option Strict i Option Compare.
Source Control — zawiera wpisy, które dotyczą systemu kontroli kodu źródłowego (na przykład Visual SourceSafe). Systemy te udostępniają narzędzia blokujące pliki i różnicowe, które pozwalają wielu programistom pracować nad tym samym projektem bez przeszkadzania sobie nawzajem.
Rozdział 2.
Menu, paski narzędzi i okna
Text Editor — zawiera wpisy określające własności edytora tekstu. Na tych stronach można na przykład określić, czy znaki oddzielające mają być wyróżniane, czy edytor ma pozwalać na przeciąganie i upuszczanie, czy paski przewijania mają być widoczne, a długie wiersze zawijane, czy mają być wyświetlane numery wierszy, a także czy edytor ma tworzyć inteligentne wcięcia. Dzięki podsekcji Basic/VB Specific zdecydujesz, czy edytor ma stosować funkcję outlining (zwijanie klas, funkcji itp., które można w razie potrzeby rozwinąć), czy wyświetlać separatory procedur i sugestie poprawek dla błędów.
Database Tools — zawiera parametry baz danych, jak domyślne długości pól różnego typu.
Debugging — zawiera opcje związane z usuwaniem błędów, takie jak wyświetlanie przez debugera komunikatów podczas ładowania i usuwania modułów, wyświetlanie potwierdzenia chęci usunięcia wszystkich punktów wstrzymania i zezwolenie na używanie funkcji Edit and Continue.
Device Tools — zawiera opcje związane z programowaniem dla urządzeń, takich jak Smartphone, Pocket PC czy Windows CE.
HTML Designer — zawiera opcje projektanta HTML. Opcje te pozwalają ustalić, czy projektant ma uruchamiać się w trybie kodu, czy projektowania, a także czy ma wyświetlać inteligentne znaczniki dla kontrolek w widoku projektowania.
Microsoft Office Keyboard Settings — zawiera opcje dotyczące działania klawiatury podczas używania plików programów Excel i Word w Visual Studio.
Test Tools — zawiera opcje ustawiające działanie narzędzi testujących.
Windows Forms Designer — zawiera ustawienia projektanta formularzy. Pozwalają one na przykład określić, czy projektant ma wyrównywać komponenty do siatki, czy do linii.
69
Menu Test Menu Test zawiera polecenia narzędzi testujących Visual Studio, które pozwalają na przykład przeprowadzić testy pokrycia (czy każdy wiersz kodu jest wykonywany), regresyjne (czy zmiany w kodzie nic nie zepsuły) i obciążeniowe (jak aplikacja radzi sobie przy dużym obciążeniu). Poniżej znajdują się krótkie opisy poleceń menu Test:
New Test — wyświetla okno dialogowe, które pozwala na utworzenie różnego rodzaju testów dla aplikacji.
Load Metadata File — pozwala załadować plik metadanych testu. Pliki te zawierają dane w formacie XML, opisujące listy testu, z których każda zawiera testy. Polecenie to pozwala załadować listy testów do różnych projektów.
Create New Test List — pozwala utworzyć nową listę testów. Listy testów umożliwiają grupowanie powiązanych ze sobą testów, dzięki czemu można przeprowadzać je razem — na przykład utworzyć listy testów interfejsu użytkownika, drukowania, baz danych itd.
70
Część I
IDE
Debug Tests — zaczyna wykonywanie aktualnie aktywnego w oknie Solution Explorer projektu testowego za pomocą debugera.
Run Tests — zaczyna wykonywanie aktualnie aktywnego w oknie Solution Explorer projektu testowego bez debugera.
Administer Test Controllers — umożliwia zarządzanie kontrolerami testów. Te ostatnie zarządzają agentami testów i zbierają wyniki testów. Agenci testów przeprowadzają testy.
Select Active Test Run Configuration — umożliwia zaznaczenie aktywnej konfiguracji testu. Konfiguracje przeprowadzania testów określają parametry testowe (na przykład jakie pliki są związane z testami; czy testy jednostkowe mają generować dane o pokryciu).
Edit Test Run Configurations — umożliwia edytowanie konfiguracji przeprowadzania testów.
Windows — wyświetla okna związane z testowaniem, takie jak: Test View, Test List Editor, Test Results, Code Coverage Results i Test Runs.
Menu Window Menu Window zawiera polecenia kontrolujące okna Visual Studio. W zależności od tego, jakie okna są aktywne, dostępne będą różne polecenia. Jeśli na przykład aktywne jest okno edytora kodu, będzie można skorzystać z polecenia New Window, nie da się zaś użyć opcji Floating, Dockable i Tabbed Document. Jeżeli natomiast aktywne jest okno Solution Explorer, sytuacja będzie dokładnie odwrotna. Poniżej znajdują się krótkie opisy tych poleceń:
New Window — otwiera nowe okno, które zawiera treść aktualnego okna z kodem.
Split — rozdziela okno z kodem na dwa panele, które mogą pokazywać różne części kodu. Po użyciu opcja ta zamienia się w polecenie Remove Split.
Floating, Dockable, Tabbed Document — okna podrzędne, takie jak Toolbox, Solution Explorer i Properties, mogą być dokowalne, pływające lub wyświetlane jako karty. Okno dokowalne można przykleić do krawędzi IDE lub połączyć z innymi tego typu oknami; pływające pozostaje w swoim własnym niezależnym oknie, nawet jeśli zostanie przeciągnięte w takie miejsce, gdzie powinno zostać zadokowane. Tabbed document to okno, które jest wyświetlane w głównym obszarze edycyjnym na środku IDE, razem z formularzami, klasami i innymi plikami projektu.
Auto Hide — przestawia okno podrzędne na tryb automatycznego ukrywania. Znika ono, a jego tytuł jest wyświetlany przy najbliższej krawędzi IDE. Po najechaniu kursorem lub kliknięciu tego tytułu okno to pojawi się. Jeśli zostanie kliknięte inne okno, to zostanie automatycznie ukryte.
Hide — ukrywa okno.
Rozdział 2.
Menu, paski narzędzi i okna
Auto Hide All — przełącza wszystkie okna drugorzędne na tryb automatycznego ukrywania.
New Horizontal Tab Group — dzieli główne okno dokumentu w pionie, dzięki czemu można jednocześnie przeglądać dwa dokumenty.
Form1.vb — dolna część menu Window zawiera listę otwartych plików, takich jak edytory formularzy, kodu czy map bitowych. Obok aktualnie aktywnego dokumentu znajduje się sygnalizujący to znaczek.
Windows — jeśli otwartych dokumentów jest zbyt dużo, aby wyświetlić je w menu Window, dzięki temu poleceniu będzie można obejrzeć listę tych okien w oknie dialogowym. To ostatnie pozwala przełączyć się do innego dokumentu, zamknąć jeden lub więcej albo zapisać dokumenty. Jeśli użyje się lewego przycisku myszy i klawisza Ctrl albo Shift, będzie można zaznaczyć więcej niż jeden dokument do szybkiego zamknięcia.
71
Menu Community Menu Community zawiera odnośniki do różnych stron witryny go.Microsoft.com. Polecenia te działają tylko wtedy, gdy komputer jest podłączony do internetu. Jeśli wejdziesz na stronę go.Microsoft.com, znajdziesz się w witrynie z wyszukiwarką, za pomocą której możesz poszukać pomocy wśród społeczności.
Menu Help Menu Help wyświetla typowy zestaw poleceń menu pomocy. Każdy zna dostępne tu opcje z wcześniejszych wersji. Jedynym nowym elementem w menu Help jest polecenie How Do I. Otwiera ono system pomocy i wyświetla stronę pełną odnośników do często czytanych tematów. Tematy te prowadzą do różnych kursów programistycznych. Na przykład w wątku Visual Basic/Language/ Basics/Data Types/Data Type Summary znajdziesz opisy typów danych języka Visual Basic, wymagania dotyczące ich przechowywania i zakresy dozwolonych dla nich wartości.
Paski narzędzi Paski narzędzi w Visual Studio łatwo się przestawia. Wystarczy chwycić pasek czterech kropek po lewej stronie lub w górnej części paska, aby przeciągnąć go w inne miejsce. Jeśli pasek zostanie przesunięty w kierunku którejś z krawędzi IDE, zostanie tam zadokowany albo poziomo (przy górnej lub dolnej krawędzi IDE), albo pionowo (przy lewej lub prawej krawędzi IDE). Jeżeli natomiast odsuniesz go od wszystkich krawędzi IDE, stanie się pływającym oknem.
72
Część I
IDE
Za pomocą poleceń dostępnych w menu IDE można decydować, które paski narzędzi mają być widoczne i co mają zawierać, a także tworzyć własne paski narzędzi, dostosowane do indywidualnych potrzeb. Więcej szczegółów na ten temat znajdziesz w rozdziale 3. Wiele poleceń menu jest także dostępnych na standardowych paskach narzędzi. Na przykład pasek Debug zawiera wiele poleceń, które można także znaleźć w menu o tej samej nazwie. Jeśli często używasz poleceń z jakiegoś menu, możesz wyświetlić odpowiadający mu pasek narzędzi, aby mieć do nich szybszy dostęp. Istnieje też możliwość stworzenia własnego nowego paska narzędzi i zapełnienia go ulubionymi poleceniami.
Okna podrzędne Okna podrzędne, na przykład Toolbox czy Solution Explorer, można dostosowywać z prawie taką samą łatwością, z jaką dopasowuje się paski narzędzi. Aby przenieść takie okno w inne miejsce, należy kliknąć jego pasek tytułu i przeciągnąć je. Podczas przesuwania go widoczne będą niewielkie niebieskie ikony (rysunek 2.12), które pomagają zadokować okno. Na rysunku może wydawać się to skomplikowane, ale w rzeczywistości jest bardzo proste.
IDE wyświetla cztery ikony przy swoich krawędziach. Widać je w pobliżu krawędzi na rysunku 2.12. Jeśli okno zostanie opuszczone na jedną z nich, będzie zadokowane przy odpowiedniej krawędzi IDE.
Rozdział 2.
Menu, paski narzędzi i okna
73
Jeśli okno zostanie przeciągnięte nad inne okno, IDE wyświetli ikony dokowania dla drugiego z nich. Na rysunku 2.12 jest to pięć ikon znajdujących się w pobliżu kursora na środku ekranu. Cztery ikony po bokach dokują okno przy odpowiednich krawędziach IDE. Środkowa ikona umieszcza upuszczone okno w karcie wewnątrz tego drugiego okna. Jeśli dokładnie przyjrzysz się rysunkowi 2.12, w dolnej części tej ikony zauważysz mały obrazek dokumentu z dwiema kartami. Gdy ustawisz kursor nad jedną z tych ikon dokujących, IDE wyświetli blado-niebieski prostokąt, który będzie wskazywał, gdzie znajdzie się okno, jeśli zostanie upuszczone. Na rysunku 2.12 widać, że kursor znajduje się nad prawą ikoną dokującą głównego okna dokumentu, a więc przyciemniony prostokąt zajmuje prawą część tego okna. Jeśli okno zostanie upuszczone gdzieś indziej niż ikona dokująca, stanie się oknem pływającym. Jeśli okno zostanie upuszczone w obszarze głównego dokumentu, zostanie sprowadzone do karty — i nie będzie można go już wyciągnąć. Aby je uwolnić, trzeba je zaznaczyć i użyć polecenia Dockable lub Floating z menu Window. Czasami IDE jest tak zapełnione różnymi oknami, że trudno zgadnąć, gdzie dokładnie znajdzie się jakieś okno po upuszczeniu. Zazwyczaj najlepiej w takiej sytuacji poruszać nieco kursorem, aby zobaczyć, co się będzie działo z bladym prostokątem. Okna w eksploratorze Microsoft Document Explorer, wykorzystywane przez bibliotekę MSDN i inne zewnętrzne pliki pomocy, udostępniają takie same narzędzia do dokowania swoich podokien (Index, Contents, Help, Favorites, Index Results i Search Results) i zarządzania nimi jak te, które opisano powyżej. W tym rozdziale znajdują się opisy niektórych ogólnych własności okien drugorzędnych. Poniższe dwa podrozdziały koncentrują się na omówieniu dwóch najważniejszych z nich — Toolbox i Properties.
Okno Toolbox Okno Toolbox zawiera narzędzia odpowiadające aktualnie aktywnemu dokumentowi. Są one dostępne podczas edytowania formularzy Windows, formularzy WPF, kontrolek użytkownika i stron internetowych lub innych elementów, które mogą zawierać takie obiekty, jak kontrolki i komponenty. Opcje są pogrupowane według kategorii w sekcjach zwanych kartami, mimo że w większości dokumentów ich nazwa nie ma nic wspólnego z wyglądem. Okno Toolbox (rysunek 2.13) zawiera narzędzia dla projektanta formularzy Windows. Sekcja All Windows Forms wyświetla swoje opcje w postaci ikon, zaś tym w sekcji Data przyporządkowane są nazwy. Pozostałe karty są zamknięte. Na tym rysunku okno Toolbox zostało mocno powiększone, aby pokazać jak najwięcej jego zawartości. Większość programistów ustawia je na znacznie mniejsze rozmiary i dokuje po lewej stronie IDE.
74
Część I
IDE
Rysunek 2.13. Okno Toolbox może wyświetlać narzędzia jako ikony lub jako listę ich nazw
Aby dostosować okno Toolbox do własnych potrzeb, można kliknąć prawym przyciskiem myszy na wybranej karcie, po czym użyć opcji dostępnych w pojawiającym się menu podręcznym. Poniżej znajdują się krótkie opisy tych poleceń:
List View — przełącza widok narzędzi w bieżącej karcie na listę nazw (jak w sekcji Data na rysunku 2.13) lub ikony (jak w sekcji All Windows Forms na rysunku 2.13).
Show All — pokazuje i ukrywa rzadziej używane karty, na przykład XML Schema, Dialog Editor, DataSet, Login, WPF Interoperability, Windows Workflow, Device Controls i wiele innych.
Choose Items — wyświetla okno dialogowe widoczne na rysunku 2.14. Narzędzia .NET znajdują się na karcie .NET Framework Components, a COM — na karcie COM Components. Aby znaleźć opcje niedostępne na żadnej z tych list, kliknij przycisk Browse.
Sort Items Alphabetically — sortuje alfabetycznie elementy karty w oknie Toolbox.
Reset Toolbox — przywraca domyślną konfigurację okna Toolbox. Polecenie to usuwa za pomocą polecenia Choose Items wszystkie elementy, które mogły zostać dodane.
Add Tab — tworzy nową kartę, na której można umieścić swoje ulubione narzędzia. Można je przeciągnąć z innej karty. Jeśli podczas wykonywania tej czynności przytrzymasz wciśnięty klawisz Ctrl, pozycja nie zostanie usunięta z tej pierwszej karty.
Delete Tab — usuwa kartę.
Rename Tab — zmienia nazwę karty.
Move Up, Move Down — przesuwa klikniętą kartę w górę lub w dół okna. Karty można także klikać i przeciągać w różne miejsca.
Menu kontekstowe, pojawiające się w oknie Toolbox po kliknięciu narzędzia prawym przyciskiem myszy, zawiera opisane polecenia oraz opcje Cut, Copy, Paste, Delete i Rename Item.
Okno Properties Podczas projektowania formularza okno Properties umożliwia oglądanie i modyfikowanie własności tego formularza i znajdujących się w nim kontrolek. Na rysunku 2.15 przedstawiono okno Properties, które wyświetla własności kontrolki Button o nazwie btnCalculate. Jak widać, własność Text tej kontrolki została ustawiona na Calculate, a więc taki właśnie tekst użytkownik zobaczy na przycisku. Na rysunku 2.15 przedstawiono kilka ważnych własności okna Properties, które zasługują na omówienie. W jego górnej części znajduje się rozwijana lista, która zawiera nazwy wszystkich kontrolek obecnych na formularzu. Aby zaznaczyć którąś z nich, trzeba kliknąć ją w oknie projektanta formularzy albo wybrać z tej listy. Przyciski znajdujące się w szeregu pod tą rozwijaną listą określają, jakie elementy są widoczne w oknie, a także jak są ustawione. Pierwsza ikona z lewej segreguje własności według kategorii. Na przykład w kategorii Appearance znajdują się własności dotyczące wyglądu kontrolki, takie jak BackColor (kolor tła), Font (czcionka) i Image (obraz). Drugi przycisk, z literami A i Z, sortuje alfabetycznie własności kontrolek. Ten sposób prezentacji wydaje się najbardziej przyjazny dla większości programistów. Użycie trzeciej ikony sprawi, że okno wyświetli własności kontrolek, zaś skorzystanie z czwartej (z wizerunkiem błyskawicy) będzie skutkowało wyświetleniem w oknie zdarzeń kontrolek — to trochę dziwne, że okno Properties wyświetla albo własności, albo zdarzenia, ale nie ma osobnego okna o nazwie Events (zdarzenia). Więcej informacji na temat edycji własności w oknie Properties i tworzenia procedur obsługi zdarzeń w projektancie formularzy Windows znajduje się w rozdziale 4.
76
Część I
IDE
Rysunek 2.15. Okno Properties pozwala na oglądanie i modyfikowanie własności kontrolek
Na rysunku 2.15 przedstawiono okno Properties w trakcie edycji formularza Windows. Zachowuje się ono inaczej podczas edycji formularza Windows Presentation Form. Wtedy nie ma szeregu ikon w górnej części okna. Wyświetlane są tylko własności kontrolek, bez zdarzeń; poza tym są one posegregowane według kategorii, a nie alfabetycznie, przez co czasami może być trudno znaleźć potrzebną własność. Wiele kategorii zawiera specjalną pozycję, More Properties, która znajduje się na samym dole. Pozwala ona na ukrywanie i rozwijanie rzadziej używanych własności z tej kategorii. Więcej informacji na temat edytowania własności w projektancie formularzy WPF za pomocą okna Properties znajduje się w rozdziale 5.
Podsumowanie Zintegrowane środowisko programistyczne Visual Studio udostępnia mnóstwo opcji, z których można skorzystać w pracy nad projektami. Menu i paski narzędzi zawierają setki, jeśli nie tysiące poleceń tworzących, ładujących, zapisujących i edytujących wiele różnych rodzajów projektów i plików. W tym rozdziale opisano najbardziej przydatne i najważniejsze polecenia dostępne w tym IDE. Rodzaj menu, pasków narzędzi i poleceń dostępnych w danym momencie zależy od aktywnego w danej chwili okna i aktualnego stanu projektu. Na przykład menu Format zawiera polecenia rozmieszczające kontrolki na formularzu, a więc jest dostępne tylko podczas pracy w oknie projektanta formularzy. Rozdział 3. — „Konfiguracja IDE” — koncentruje się na bardziej szczegółowym opisie sposobów aranżacji pasków narzędzi i menu Visual Studio, aby odpowiadały one indywidualnym potrzebom programisty. Znajdzie się w nim objaśnienie, jak można stworzyć własne paski narzędzi i menu. Omówione zostanie także zapełnianie ich najbardziej przydatnymi w codziennej pracy narzędziami.
3
Konfiguracja IDE Visual Studio jest wypełnione tysiącami narzędzi dostępnych poprzez polecenia menu i pasków narzędzi. Jest ich tak dużo, że gdyby wszystkie zostały pokazane naraz, IDE to byłoby bezużyteczne. W trosce o właściwości użytkowe programiści Microsoftu, którzy opracowali Visual Studio, zdecydowali, że wyświetlone zostaną tylko te narzędzia, które ich zdaniem powinny być najbardziej przydatne podczas wykonywania określonych zadań. Niestety ci ludzie nie wiedzieli, co dokładnie będziemy robić. Dlatego postanowili zgadywać najlepiej, jak potrafią. W niektórych sytuacjach możesz dojść do wniosku, że bardziej przydatny byłby całkiem inny niż standardowy zestaw narzędzi. W takich sytuacjach może być konieczne skonfigurowanie IDE do własnych potrzeb. W tym rozdziale opisane zostaną sposoby personalizacji Visual Studio. Znajdzie się tu objaśnienie tworzenia nowych pasków narzędzi i menu, dodawanie do nich poleceń oraz ustalanie wyglądu tych poleceń. Ponadto z rozdziału tego dowiesz się, jak definiować skróty klawiszowe, które dają szybszy dostęp do najczęściej używanych poleceń.
Dodawanie poleceń Polecenie Customize z menu Tools wyświetla okno dialogowe przedstawione na rysunku 3.1. Na karcie Toolbars można zaznaczyć pola wyboru odpowiadające paskom narzędzi, które mają być widoczne. Aby utworzyć nowy pasek narzędzi, do którego można dodawać własne ulubione opcje, należy kliknąć przycisk New. Tak utworzony pasek możesz zostawić jako pływające okno albo zadokować go przy jednej z krawędzi IDE. Jeśli zostanie przeciągnięty do góry, dołączy do pozostałych pasków narzędzi. Aby wyświetlić listę kategorii z rysunku 3.2, należy kliknięciem przejść na kartę Commands. Kategorię można wybrać po lewej stronie. Następnie trzeba kliknąć i przeciągnąć polecenie z listy po prawej stronie. Upuszczenie go nad paskiem narzędzi spowoduje dodanie go do tego paska. W celu dodania polecenia do menu należy najechać na nie kursorem, aby się otworzyło, po czym upuścić do niego daną pozycję.
78
Część I
IDE
Rysunek 3.1. Karta Toolbars w oknie dialogowym Customize pozwala wybrać, które paski narzędzi mają być widoczne
Rysunek 3.2. Karta Commands w oknie dialogowym Customize pozwala dodać polecenia do pasków narzędzi i menu
Aby utworzyć nowe menu, kliknij pozycję New Menu na liście po lewej stronie. Następnie przeciągnij to nowe menu z listy po prawej stronie na obszar menu w oknie IDE. Aby utworzyć polecenie wykonujące wcześniej utworzone makro, należy przejść do kategorii Macros na liście po lewej stronie. Na tej umieszczonej po prawej stronie znajdź wybrane makro i przeciągnij je na pasek narzędzi lub menu.
Rozdział 3.
Konfiguracja IDE
79
Usuwanie poleceń Aby usunąć wybrane polecenie z paska narzędzi albo menu, należy kliknąć je prawym przyciskiem myszy, po czym w menu podręcznym kliknąć pozycję Delete. Druga metoda to kliknięcie i przeciągnięcie danej pozycji w takie miejsce, w którym nie może ona zostać dodana. Na przykład można ją upuścić na okno dialogowe Customize lub na większość miejsc niebędących częścią menu albo paska narzędzi (edytory kodu, okno Properties, okno Toolbox). Jeśli kursor znajdzie się nad jednym z takich obszarów, zamieni się w ramkę z krzyżykiem. Modyfikowanie standardowych menu i pasków narzędzi może w przyszłości spowodować zamieszanie. Być może okaże się, że potrzebne jest polecenie, które zostało wcześniej usunięte — jego znalezienie może zająć sporo czasu. Lepszym sposobem na modyfikowanie standardowych poleceń jest utworzenie nowego paska narzędzi lub menu. Dodaj wybrane polecenia do nowego paska narzędzi, po czym ukryj standardowy pasek, którego miejsce zajął. Później w razie potrzeby można przywrócić stary pasek.
Modyfikowanie poleceń Jeśli jedno z poleceń w menu lub na pasku narzędzi zostanie kliknięte prawym przyciskiem myszy, gdy otwarte jest okno dialogowe Customize, Visual Studio wyświetli menu kontekstowe przedstawione na rysunku 3.3. Aby zmienić tekst wyświetlany w menu lub na pasku narzędzi, kliknij pole tekstowe Name i wprowadź nową nazwę. Polecenie Copy Button Image kopiuje obraz z przycisku do schowka, zaś Paste Button Image wkleja skopiowany obraz na przycisk. Tych dwóch opcji najczęściej używa się do kopiowania grafik z istniejących przycisków do dodawanych przycisków. Polecenie Paste Button Image wklei jednak każdy obraz znajdujący się w schowku. Można na przykład otworzyć mapę bitową w programie Microsoft Paint za pomocą kombinacji klawiszy Ctrl+A, zaznaczyć cały obraz i skorzystać z kombinacji Ctrl+C w celu skopiowania go do schowka. Następnie przy użyciu opcji Paste Button Image będzie można wkleić tę grafikę na przycisk. Pamiętaj, że przyciski mają wymiary 16×16 pikseli. Jeśli obraz w schowku jest większy, Visual Studio zmniejszy go, co maże dać dziwny efekt. Aby tego uniknąć, projektuj zawsze ikony o rozmiarach 16×16 pikseli. Polecenie Reset Button Image przywraca domyślny wygląd obrazu. W przypadku opcji związanej z makrem powoduje to wymazanie obrazu. Polecenie Edit Button Image włącza prosty edytor przycisków (rysunek 3.4). Jeśli zostanie kliknięty piksel niemający zaznaczonego koloru frontu (na rysunku 3.4 jest czarny), edytor zmieni ten piksel na kolor frontu. Kliknięcie lewym przyciskiem myszy i przeciągnięcie kursora spowoduje pokolorowanie wszystkich pikseli na jego drodze. Jeśli klikniesz nad pikselem w kolorze frontu, edytor wyczyści ten piksel i wszystkie inne, nad którymi przejdzie kursor.
80
Część I
IDE
Rysunek 3.3. Kliknij prawym przyciskiem myszy menu i pasek narzędzi, aby zmienić ich wygląd Rysunek 3.4. Za pomocą prostego edytora przycisków Visual Studio można modyfikować przyciski poleceń
Polecenie Change Button Image wyświetla menu zawierające kilkadziesiąt standardowych ikon. Aby przypisać jedną z nich do przycisku, należy ją kliknąć. Dobrym sposobem jest wybranie jednej z tych ikon na początku, a potem dostosowanie jej do swojego polecenia. Dostępne w menu podręcznym polecenie Default Style ustawia styl polecenia, który odpowiada temu, czy znajduje się ono w menu, czy na pasku narzędzi. W menu opcja ta wyświetla przycisk i tekst, zaś na pasku narzędzi — tylko przycisk. Jak na ironię — domyślny
Rozdział 3.
Konfiguracja IDE
81
styl nowego przycisku nie jest taki sam jak Default Style. Przycisk nowo utworzonego polecenia w menu lub na pasku narzędzi początkowo wyświetla tylko tekst. Aby ustawić opisywany styl, trzeba użyć polecenia Default Style. Użycie opcji Text Only (Always) sprawi, że polecenie wyświetli tylko tekst, zaś skorzystanie z Text Only (in Menus) poskutkuje tym, że na pasku narzędzi będzie widoczny przycisk, a w menu — tekst. Zastosowanie polecenia Image and Text sprawi, że widoczne będą tekst i ikona — zarówno w menu, jak i na pasku narzędzi. W końcu użycie polecenia Begin a Group spowoduje wstawienie przez IDE przed przyciskiem separatora grupy. Przycisk Rearrange Commands w oknie dialogowym Customize wyświetla okno dialogowe, które pozwala zmienić ułożenie poleceń w istniejącym menu lub na pasku narzędzi, a także zmodyfikować ich wygląd. Jednak zazwyczaj łatwiej jest je po prostu kliknąć i przeciągnąć.
Tworzenie skrótów klawiszowych Przycisk Keyboard w oknie dialogowym Customize wyświetla okno dialogowe przedstawione na rysunku 3.5. Można w nim przeglądać i edytować skróty klawiszowe.
Rysunek 3.5. Sekcja Keyboard okna dialogowego Options pozwala przeglądać i modyfikować skróty klawiszowe
82
Część I
IDE
Aby wyświetlić tylko polecenia zawierające określone słowa, należy wprowadzić te ostatnie w polu tekstowym Show commands containing. Gdy klikniesz jedno z pleceń, w oknie dialogowym zostaną wyświetlone wszystkie związane z nim skróty klawiszowe. Aby uzyskać nowy skrót, kliknij w polu tekstowym Press shortcut keys, po czym naciśnij klawisze, które mają go tworzyć. Lista rozwijana Shortcut currently used by wyświetla wszystkie polecenia, które aktualnie używają wprowadzonego właśnie skrótu. Aby dokonać przypisania, kliknij przycisk Assign.
Podsumowanie IDE Visual Studio oferuje szeroki wachlarz narzędzi. Jego menu i paski narzędzi zostały tak ustawione, aby ułatwić dostęp do opcji najczęściej stosowanych przez programistów. Jednak osoby korzystające często z innych narzędzi nie są skazane na pracę w początkowym układzie IDE. W tym rozdziale objaśniono sposoby tworzenia, ukrywania i zmieniania aranżacji menu i pasków narzędzi, co może ułatwić korzystanie z najbardziej potrzebnych opcji. Poza mnóstwem menu i pasków narzędzi Visual Studio zawiera także dziesiątki okien udostępniających różne opcje lub pozwalających przeglądać i modyfikować różne właściwości aplikacji. Jednym z pierwszych okien Visual Studio używanych przez programistów przy tworzeniu nowego projektu jest Windows Forms Designer. Można w nim dodawać kontrolki do formularzy, ustawiać je, aby tworzyły interfejs użytkownika, a także wybierać ich własności, które określają wygląd i działanie. Rozdział 4. — „Projektant formularzy Windows” — koncentruje się na objaśnianiu sposobów budowania formularzy w projektancie formularzy Windows, które są składnikiem większości aplikacji działających w tym systemie.
4
Projektant formularzy Windows Projektant formularzy Windows (ang. Windows Forms Designer) pozwala na projektowanie formularzy dla typowych aplikacji tego systemu. Dzięki niemu można dodawać kontrolki, zmieniać ich rozmiar oraz przesuwać je w obrębie formularza. Razem z oknem Properties pozwala także na zmianę własności kontrolek w celu określenia ich wyglądu i zachowania. Ten rozdział stanowi wprowadzenie do omówienia projektanta formularzy Windows. Znajdziesz tu opisy sposobów umieszczania kontrolek w formularzu, przesuwania i zmieniania ich rozmiarów, ustawiania własności oraz dodawania kodu reagującego na zdarzenia. Ta część ksiązki zawiera też wskazówki i sztuczki ułatwiające pracę z kontrolkami.
Ustawianie opcji projektanta Gdy pierwszy raz uruchomisz Visual Studio, okno Windows Form Designer będzie tak skonfigurowane, aby można go było wygodnie używać. Będzie można od razu otworzyć formularz i umieścić na nim kontrolki pobrane z okna Toolbox. Za pomocą myszy zmienisz rozmiary i położenie tych kontrolek. Menu Format zawiera opcje pozwalające rozmieszczać kontrolki i ustawiać ich wielkość. Mówiąc ogólnie, okno Windows Forms Designer udostępnia najwyższej klasy intuicyjny edytor WYSIWYG (ang. What You See Is What You Get — dostajesz to, co widzisz). Jest też kilka opcji konfiguracyjnych za kulisami, odpowiadających za działanie projektanta formularzy Windows, które trzeba znać, aby w pełni z niego korzystać. W celu dotarcia do opcji projektanta należy otworzyć menu Tools, kliknąć pozycję Options, rozwinąć gałąź Windows Forms Designer i kliknąć stronę General. Pojawi się okno dialogowe z rysunku 4.1.
84
Część I
IDE
Rysunek 4.1. Okno dialogowe, które pozwala kontrolować działanie projektanta formularzy Windows
Poniżej znajdują się opisy tych ustawień:
Optimized Code Generation — określa, czy Visual Studio generuje zoptymalizowany kod. Ustawienie to znajduje się tutaj zamiast w jakiejś bardziej związanej z kodem części okna dialogowego Options, ponieważ niektóre kontrolki mogą być niezgodne z optymalizacją kodu.
Grid Size — określa poziome i pionowe wymiary siatki, której się używa, gdy tryb LayoutMode jest ustawiony na SnapToGrid.
LayoutMode — określa, czy komponenty mają być wyrównywane do siatki, czy liniowo. Opcja SnapToGrid powoduje, że przeciągane lub skalowane obiekty są automatycznie przysuwane do najbliższego punktu siatki, zaś użycie SnapToLines poskutkuje tym, że kontrolki zostaną przyciągnięte do linii biegnących równolegle do krawędzi albo środków innych kontrolek lub do marginesów formularza. Obie te opcje ułatwiają utrzymanie takich samych rozmiarów kontrolek i odpowiednie ich wyrównanie wzdłuż krawędzi. Sporo je jednak różni, więc przed podjęciem decyzji, z której z nich korzystać, najlepiej trochę poeksperymentować.
Automatically Open Smart Tags — określa, czy Visual Studio ma domyślnie wyświetlać inteligentne znaczniki.
EnableRefactoringOnRename — określa, czy Visual Studio ma wykonywać refaktoryzację po zmianie nazwy kontrolki. Jeśli opcja ta zostanie ustawiona na True i zmieni się nazwa kontrolki, Visual Studio zaktualizuje wszystkie fragmenty kodu, w których ta ostatnia jest używana. Wartość False oznacza, że po zmianie nazwy kontrolki nie zostanie zaktualizowany żaden kod, który jej używa, przez co przestanie on działać.
AutoToolboxPopulate — określa, czy Visual Studio ma dodawać komponenty utworzone w rozwiązaniu do okna Toolbox.
Rozdział 4.
Projektant formularzy Windows
85
Wybór trybu LayoutMode jest kwestią indywidualnych preferencji. Znam wielu programistów używających każdego z tych stylów. Opcja EnableRefactoringOnRename pozwala zaoszczędzić sporo czasu, a więc włączenie jej jest prawie zawsze korzystne.
Dodawanie kontrolek Projektant formularzy Windows udostępnia kilka sposobów dodawania kontrolek do formularza. Pierwszy z nich to dwukrotne kliknięcie kontrolki w oknie Toolbox. Powoduje to wstawienie egzemplarza o typowych rozmiarach tej kontrolki w domyślnym miejscu na formularzu. Następnie za pomocą myszy można ją przesunąć i zmienić jej rozmiar. Gdy użyjesz tej metody, nowa kontrolka znajdzie się w aktualnie zaznaczonym kontenerze na formularzu. Jeśli aktualnie zaznaczona jest pozycja GroupBox, nowa kontrolka będzie wstawiona do tej kontrolki, jeżeli zaś znajdująca się w panelu TextBox, nowa kontrolka zostanie umieszczona na tym panelu. Druga metoda polega na kliknięciu kontrolki w oknie Toolbox. Spowoduje to zmianę wyglądu kursora po najechaniu nad formularz. Kursor ma postać małego znaku plus z niewielkim obrazkiem ikony kontrolki obok. Kliknięcie jakiegoś miejsca formularza powoduje dodanie tej kontrolki w tym miejscu w domyślnym rozmiarze. Zamiast tylko klikać, kontrolkę można kliknąć i przeciągnąć na formularz. Jeśli podczas klikania lub klikania i przeciągania na formularzu przytrzymasz klawisz Ctrl, nowa kontrolka zostanie umieszczona w formularzu, ale będzie nadal wybrana w oknie Toolbox, dzięki czemu będzie można dodać więcej jej egzemplarzy. Wyobraźmy sobie na przykład, że musisz utworzyć kilka pól tekstowych do wpisywania nazwy użytkownika, ulicy, miasta, województwa i kodu pocztowego. W oknie Toolbox kliknij narzędzie TextBox, a następnie pięciokrotnie kliknij na formularzu, przytrzymując za każdym razem klawisz Ctrl. Aby zakończyć dodawanie pól tekstowych, naciśnij klawisz Esc.
Zaznaczanie kontrolek Utworzona kontrolka zostanie zaznaczona przez projektanta. Jest to sygnalizowane otoczeniem jej przerywaną linią z kwadratami. Na rysunku 4.2 widać, że zaznaczona jest kontrolka Button2. Aby później zaznaczyć którąkolwiek kontrolkę w projektancie, należy ją kliknąć. W celu zaznaczenia grupy kontrolek można kliknąć w pustym miejscu i przeciągnąć mysz nad wybranymi kontrolkami. Zostanie wyświetlony prostokąt, pozwalający zorientować się, które kontrolki będą zaznaczone. Gdy puścisz przycisk myszy, zaznaczone zostaną wszystkie kontrolki, które przynajmniej częściowo były przykryte przez ten prostokąt.
86
Część I
IDE
Rysunek 4.2. Zaznaczona kontrolka jest otoczona obwódką
Większość kontrolek zaznaczonych w grupie ma obramowanie z czarnymi kwadratami. Specjalna „główna” kontrolka ma białe kwadraty. Na rysunku 4.3 widać zaznaczone cztery kontrolki. Przycisk Button1 jest kontrolką główną, dlatego ma białe obramowanie. Rysunek 4.3. Główna kontrolka grupy jest otoczona białymi kwadratami
Główna kontrolka jest przez projektanta wykorzystywana do dostosowywania pozostałych kontrolek podczas używania poleceń z menu Format. Na przykład polecenie Format/Make Same Size/Height ustawi wysokość wszystkich kontrolek z czarnym obramowaniem na taką samą wartość, jaką ma kontrolka z białym obramowaniem. Podobnie polecenie Format/Align/Tops wyrównuje kontrolki z czarnymi obramowaniami, aby ich wierzchołki znajdowały się w jednej linii z kontrolką główną.
Rozdział 4.
Projektant formularzy Windows
87
Aby zmienić kontrolkę główną, należy kliknąć wybraną pozycję. W celu dodania lub usunięcia poszczególnych kontrolek z grupy należy kliknąć je lewym przyciskiem myszy, przytrzymując jednocześnie klawisz Shift lub Ctrl. Dodatkowe przeciągnięcie kursora myszy spowoduje dodanie lub usunięcie całej grupy kontrolek. W niektórych sytuacjach kontrolki nie zostaną odznaczone, nawet jeśli użytkownik kliknie w pustym miejscu formularza. Aby odznaczyć wszystkie kontrolki, kliknij tę, która nie jest zaznaczona, lub naciśnij klawisz Esc.
Kopiowanie kontrolek Niezwykle efektywną metodą tworzenia wielu podobnych do siebie kontrolek jest utworzenie jednej i skopiowanie jej odpowiednią liczbę razy. Na przykład tworzenie opisanych wcześniej pól tekstowych dla nazwy użytkownika, ulicy, miasta, województwa i kodu pocztowego można zacząć od utworzenia kontrolki Nazwa. Następnie należy ustawić wszystkie własności, które będą miały także pozostałe kontrolki. Można na przykład ustawić szerokość, własność MaxLength na 20, a własność Anchor na Top, Left, Right, dzięki czemu będzie zmieniała rozmiar razem z kontenerem. Potem trzeba zaznaczyć kontrolkę i nacisnąć klawisze Ctrl+C, aby ją skopiować, a następnie klawisze Ctrl+V w celu wklejenia dowolnej liczby jej kopii. Na koniec należy przeciągnąć nowe kontrolki na przeznaczone dla nich miejsca — i gotowe. Wklejana kontrolka zostanie umieszczona w kontenerze, który jest aktualnie zaznaczony. Może to powodować nieporozumienia przy szybkim kopiowaniu i wklejaniu kontenerów. Wyobraźmy sobie na przykład, że chcesz utworzyć trzy kontrolki GroupBox. Tworzysz jedną i ustawiasz jej odpowiednie rozmiary. Następnie klikasz klawisze Ctrl+C oraz Ctrl+V i Ctrl+V. Pierwsza kontrolka jest skopiowana i pierwsza kopia zostaje umieszczona w oryginalnej kontrolce. Druga kopia znajduje się w pierwszej kopii. Wynik może być dziwny. Prawdopodobnie w celu ustawienia utworzonych kopii w odpowiednich miejscach najpierw trzeba będzie wyciągnąć je na formularz. Można także skopiować całą grupę kontrolek. Załóżmy, że zamierzasz utworzyć pola tekstowe Nazwa, Ulica, Miasto, Województwo i KodPocztowy, ale jednocześnie chcesz, aby po ich lewej stronie znajdowały się odpowiednie etykiety, czyli kontrolki Label. Najpierw utwórz kontrolki Label i TextBox, określ ich własności i ustaw je odpowiednio w jednej linii, tak aby kontrolka Label była po lewej stronie TextBox. Za pomocą kliknięcia i przeciągania zaznacz obie te kontrolki i skopiuj je przy użyciu klawiszy Ctrl+C. Kliknięcie klawiszy Ctrl+V spowoduje wklejenie obu skopiowanych kontrolek. Będą one ustawione w jednej linii; kontrolka Label znajduje się po lewej stronie TextBox, jak w oryginale. Kopie te są nawet zaznaczone, dzięki czemu można chwycić obie naraz i przenieść w wybrane miejsce.
88
Część I
IDE
Przenoszenie i zmiana rozmiaru kontrolek Przenoszenie kontrolek w oknie projektanta formularzy Windows jest łatwe. Wystarczy kliknąć kontrolkę i przeciągnąć ją w nowe miejsce. Aby przenieść grupę kontrolek, należy je zaznaczyć, a następnie kliknąć jedną z nich i poprzez przeciągnięcie przesunąć wszystkie. Warto zauważyć, że za pomocą przeciągania kontrolki można przenosić do wnętrza i na zewnątrz kontrolek kontenerów takie z nich, jak FlowLayoutPanel, GroupBox, Panel czy PictureBox. Przy przeciąganiu kontrolki do nowego kontenera pod kursorem pojawia się niewielki lekko przyćmiony prostokąt. Pojawienie się go oznacza, że jeśli kontrolka zostanie upuszczona w tym momencie, będzie przeniesiona na nowy kontener. Wskazówka nowego kontenera pojawia się podczas przeciągania kontrolki z formularza na kontener, z kontenera na formularz i z jednego kontenera do innego. Zmienianie rozmiaru kontrolki jest tak samo łatwe jak jej przesuwanie. Kliknij kontrolkę, aby ją zaznaczyć. Następnie kliknij i przeciągnij jeden z niewielkich białych kwadratów, które ją otaczają, aby zmienić jej rozmiar z wybranej strony. Kwadraty w rogach pozwalają modyfikować wielkość kontrolki zarówno w pionie, jak i w poziomie. Kwadraty po bokach zmieniają szerokość, a te umieszczone na górze i na dole — wysokość kontrolki. Aby zmienić rozmiar grupy kontrolek, zaznacz grupę. Następnie kliknij i przesuń jeden z kwadratów otaczających jakąś kontrolkę, która należy do tej grupy. Podczas przeciągania kursora jednocześnie zmieni się rozmiar wszystkich kontrolek. Jeśli na przykład poszerzysz jedną z nich o osiem pikseli, wszystkie pozostałe również będą szersze o tę wartość.
Aranżacja kontrolek Menu Format zawiera kilka podmenu z narzędziami ułatwiającymi ustawianie kontrolek. Na przykład w podmenu Align znajdziesz polecenia pozwalające na wyrównanie kontrolek w pionie i poziomie względem ich krawędzi lub środków. Opis poleceń tego menu znajduje się w podrozdziale „Menu Format” w rozdziale 2. Można też poeksperymentować z tymi opcjami, ponieważ nie są one zbyt skomplikowane. Więcej informacji na temat głównej roli kontrolki z białymi kwadratami w dostosowywaniu innych kontrolek znajduje się we wcześniejszym podrozdziale „Zaznaczanie kontrolek”.
Ustawianie własności kontrolek Gdy zaznaczysz kontrolkę w oknie Properties, wszystkie jej własności zostaną wyświetlone do edycji. W większości przypadków w celu zmiany wartości wybranej z nich wystarczy ją kliknąć i wpisać to, co trzeba. Niektóre kontrolki są bardziej skomplikowane niż inne —
Rozdział 4.
Projektant formularzy Windows
89
do konfigurowania ich własności dostępne są listy rozwijane lub specjalne okna dialogowe. Większość edytorów do ustawiania własności jest bardzo prosta w obsłudze, więc nie będę ich tutaj opisywać. Poza oknem Properties, w którym za jednym razem można określić ustawienia pojedynczej kontrolki, istnieją metody pozwalające szybciej konfigurować wiele własności. W kolejnych podrozdziałach znajdziesz opisy niektórych najbardziej przydatnych z tych technik.
Grupowe ustawianie własności Czasami w oknie Properties można ustawić własności dla wszystkich kontrolek znajdujących się w zaznaczonej grupie. Wyobraźmy sobie na przykład, że zaznaczono grupę kontrolek TextBox. W oknie Properties można za jednym razem ustawić własności Anchor, Text, MultiLine, Font, a także wiele innych. Jednym z zastosowań tej metody jest określenie własności Text jako pustego łańcucha dla grupy kontrolek TextBox. Niestety — jeśli kontrolki mają ustawione różne wartości dla tej własności, w oknie Properties zostanie wyświetlona pusta wartość dla własności Text. Gdy będzie się próbowało określić tę własność jako pusty łańcuch, okno Properties nic nie zmieni, ponieważ uzna, że nie zostały wprowadzone żadne zmiany. Aby obejść to ograniczenie, najpierw ustaw własność Text wszystkich kontrolek na dowolną wartość (na przykład x), a następnie usuń ją, aby wyczyścić własność Text wszystkich kontrolek.
Ustawianie własności kilku kontrolek Kiedy w oknie projektanta formularzy Windows zostanie zaznaczona jakaś kontrolka, w oknie Properties zobaczysz jedną z jej własności zaznaczoną domyślnie. Która to będzie — zależy od rodzaju zaznaczonej kontrolki i tej, która była zaznaczona wcześniej. Jeśli zarówno nowo zaznaczona, jak i wcześniejsza kontrolka mają własność, która była zaznaczona wcześniej, w oknie Properties pozostanie zaznaczona właśnie ta własność. W przeciwnym przypadku zostanie zaznaczona domyślna własność nowo zaznaczonej kontrolki. Załóżmy na przykład, że zaznaczono kontrolkę Label i kliknięto w oknie Properties jej własność TabIndex. Następnie zaznaczono kontrolkę TextBox. Posiada ona także własność TabIndex — i ta ostatnia pozostanie zaznaczona w oknie Properties. W rzeczywistości nie zawsze tak jest. Jeśli jako następna zostanie zaznaczona kontrolka ListBox, w oknie Properties będzie zaznaczona jej własność Items, mimo że posiada ona także własność TabIndex. Jednak w przypadku większości kontrolek to działa;
ma także miejsce zawsze wtedy, gdy zaznaczona zostanie inna kontrolka tego samego typu. Jeśli na przykład zaznaczysz własność TabIndex kontrolki ListBox, pozostanie ona zaznaczona po zaznaczeniu innej kontrolki typu ListBox.
90
Część I
IDE
Dzięki temu, że okno Properties stara się zachować zaznaczenie tej samej własności, można z łatwością ustawić tę samą własność kilku kontrolek. Załóżmy na przykład, że za pomocą kopiowania i wklejania tworzysz kilka kontrolek TextBox z takimi samymi własnościami Size i Anchor, a chcesz nadać im dobre nazwy. Zaznacz jedną z tych kontrolek, kliknij jej własność Name w oknie Properties i wpisz nową nazwę (na przykład txtName). Następnie kliknij inną kontrolkę TextBox. Własność Name będzie nadal zaznaczona w oknie Properties. Jeśli od razu wpiszesz nazwę dla tej kontrolki (na przykład txtStreet), zostanie ona przypisana przez okno Properties. W ten sposób możesz znacznie przyspieszyć nazywanie kolejnych kontrolek tego samego typu.
Używanie inteligentnych znaczników Wiele kontrolek po zaznaczeniu w oknie projektanta formularzy wyświetla inteligentny znacznik (ang. smart tag). Ma on postać niewielkiego prostokąta ze skierowanym w prawo trójkątem w środku. Kliknięcie go powoduje otwarcie małego okna dialogowego, w którym można szybko i łatwo wykonać niektóre najczęstsze operacje związane z kontrolką. Na rysunku 4.4 przedstawiono kontrolkę PictureBox z rozwiniętym oknem inteligentnego znacznika. Ponieważ okno jest widoczne, trójkąt jest skierowany w lewą stronę. Jego kliknięcie spowoduje ukrycie tego okna dialogowego. Rysunek 4.4. Inteligentny znacznik kontrolki PictureBox pozwala wybrać obraz, ustawić własność SizeMode oraz zadokować ją w jej kontenerze
Okno dialogowe inteligentnego znacznika kontrolki PictureBox pozwala wybrać obraz dla kontrolki, ustawić jej własność SizeMode i zadokować ją w jej kontenerze. Czynności te ustawiają własności Image, SizeMode i Dock. Wiele kontrolek, zwłaszcza tych bardziej skomplikowanych, udostępnia inteligentne znaczniki, które pozwalają na wykonywanie najczęstszych czynności poza oknem Properties.
Rozdział 4.
Projektant formularzy Windows
91
Polecenia w oknie Properties Niektóre kontrolki udostępniają w dolnej części okna Properties odnośniki, za pomocą których można wykonywać niektóre najczęstsze czynności z nimi związane. Są to tak zwane command verbs. Na rysunku 4.5 przedstawiono okno Properties po zaznaczeniu w projektancie formularzy Windows kontrolki PictureBox. Pod własnościami znajduje się odnośnik Choose Image. Jego kliknięcie powoduje pojawienie się okna dialogowego wyboru plików, tak jak po kliknięciu odnośnika Choose Image w inteligentnym znaczniku na rysunku 4.4 lub wielokropka znajdującego się po prawej stronie własności Image w oknie Properties. Rysunek 4.5. Odnośnik Choose Image pod własnościami pozwala na ustawienie własności Image kontrolki PictureBox
W książce Expert One-on-One Visual Basic Design and Development (Wrox 2007) opisałem sposoby dodawania inteligentnych znaczników i odnośników poleceń do swoich kontrolek oraz budowania własnych niestandardowych edytorów własności. Są to zaawansowane techniki, dlatego też nie zajmuję się nimi w tej publikacji.
Dodawanie kodu do kontrolek Po dodaniu odpowiednich kontrolek do formularza i ustawieniu ich własności należy dodać do formularza kod przetwarzający je i reagujący na ich zdarzenia. Kod reagujący na zdarzenia kontrolek pisze się w edytorze kodu (ang. Code Editor). Został on omówiony w rozdziale 6. — „Edytor kodu Visual Basica”. Można go otworzyć w oknie projektanta formularzy Windows.
92
Część I
IDE
Jeśli dwukrotnie klikniesz kontrolkę w projektancie formularzy Windows, Visual Studio utworzy pustą procedurę obsługi domyślnego zdarzenia tej kontrolki, po czym otworzy ją w edytorze kodu. Na przykład poniżej znajduje się kod procedury obsługi zdarzenia wygenerowanej przez IDE dla kontrolki Button1. Domyślnym zdarzeniem przycisku jest zdarzenie Click, a więc ten kod jest procedurą obsługi zdarzenia Click. Zauważ, że na końcu pierwszego wiersza znajduje się znak kontynuacji. W Visual Studio cały ten wiersz mieściłby się w jednej linijce. Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click End Sub
Innym sposobem na stworzenie procedury obsługi zdarzeń i otwarcie edytora kodu jest zaznaczenie kontrolki w oknie projektanta i kliknięcie ikony Events (obrazującej piorun), znajdującej się w pobliżu górnej części okna Properties. Spowoduje to wyświetlenie listy zdarzeń dla tej kontrolki (rysunek 4.6). Jeśli dwukrotnie klikniesz wybrane zdarzenie, w edytorze kodu utworzona zostanie, a następnie otwarta jego procedura obsługi. Rysunek 4.6. Kliknij ikonę Events, aby wyświetlić w oknie Properties zdarzenia kontrolki
Jeśli zostanie zaznaczonych więcej niż jedna kontrolka w oknie projektanta formularzy Windows, a następnie użytkownik dwa razy kliknie zdarzenie, Visual Studio utworzy procedurę obsługi zdarzeń reagującą na zdarzenia wszystkich zaznaczonych kontrolek. W celu utworzenia poniższej procedury zaznaczyłem trzy przyciski i dwukrotnie kliknąłem zdarzenie Click: Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button3.Click, Button2.Click, Button1.Click End Sub
Procedura nazywa się Button2_Click zamiast Button1_Click lub coś w tym rodzaju, ponieważ przycisk Button2 był główną kontrolką w grupie. Więcej informacji o tej ostatniej znajduje się w podrozdziale „Zaznaczanie kontrolek”.
Rozdział 4.
Projektant formularzy Windows
93
Podsumowanie Projektant formularzy Windows pozwala tworzyć formularze przeznaczone do użytku w aplikacjach dla systemu Windows. Dzięki niemu można dodawać kontrolki do formularza, zmieniać ich rozmiary oraz odpowiednio je ustawiać. Razem z oknem Properties umożliwia przeglądanie i modyfikowanie własności kontrolek oraz tworzenie procedur obsługi ich zdarzeń. Ten rozdział stanowi wprowadzenie do projektanta formularzy Windows. Wyjaśnione tu zostały sposoby wykorzystywania jego narzędzi. Z dalszych rozdziałów dowiesz się znacznie więcej o tworzeniu formularzy. Rozdziały 8. — „Wybieranie kontrolek Windows Forms” — i 9. — „Używanie kontrolek Windows Forms” — zawierają wiele informacji o rodzajach kontrolek, których można używać w projektancie formularzy Windows. Z rozdziału 10. — „Formularze Windows” — dowiesz się sporo o działaniu formularzy Windows. Podpowiem także, co można z nimi zrobić. Rozdział 5. — „Projektant WPF” — zawiera opis projektanta służącego do budowania formularzy Windows Presentation Foundation. Pod niektórymi względami jest on podobny do projektanta formularzy Windows. Na przykład do umieszczania kontrolek na formularzu używa się okna Toolbox, zaś do przeglądania i modyfikowania własności kontrolek służy okno Properties. W tym rozdziale znajdziesz opisy niektórych najważniejszych różnic pomiędzy tymi dwoma typami projektantów formularzy.
94
Część I
IDE
5
Projektant WPF Projektant WPF (ang. Windows Presentation Foundation Designer) pozwala na interaktywne tworzenie okien WPF, tak jak dzięki projektantowi Windows Forms Designer można tworzyć formularze Windows. Udostępnia wizualny kreator, za pomocą którego dodasz kontrolki do okna. Jeśli zostanie zaznaczona jedna lub więcej kontrolek w kreatorze, okno Properties wyświetli własności tych obiektów i pozwoli na edycję wielu z nich. Poza wizualnym kreatorem projektant ten udostępnia także edytor kodu XAML (ang. Extensible Application Markup Language), definiującego interfejs użytkownika. Pozwala to na taką edycję własności i aranżację kontrolek, jaka jest niemożliwa, gdy używa się kreatora wizualnego. Ten rozdział stanowi wprowadzenie do projektanta WPF. Znajdziesz tu objaśnienie, jak dodawać kontrolki do okna, jak je przesuwać i zmieniać ich rozmiary, ustawiać własności oraz pisać kod reagujący na zdarzenia. Pamiętaj, że pierwsza wersja Visual Basica 2008 zawiera błąd w projektach WPF. Więcej informacji na ten temat znajduje się w podrozdziale „Ostrzeżenie dotyczące Visual Basica 2008 Version 1” — we „Wstępie” na stronie 34.
Ostrzeżenie o wczesnej wersji Wersja projektanta WPF dostępna w Visual Studio 2008 jest drugą edycją (pierwsza została wydana jako dodatek do platformy .NET 3.0. Więcej informacji na ten temat znajduje się na stronie msdn2.microsoft.com/library/aa480198.aspx). Wiele produktów zyskuje swój ostateczny kształt dopiero w wersji trzeciej — ten wydaje się nie być wyjątkiem pod tym względem. Mimo że projektant WPF jest narzędziem wizualnym, ma wiele wad. Oto niektóre z nich:
96
Część I
IDE
W trybie graficznym nie można wstawiać kontrolek do kontrolek, które nie są używane jako kontenery. Nie da się na przykład umieścić kontrolki Grid w kontrolce Button. Aby to zrobić, trzeba użyć języka XAML.
Okno Properties nie udostępnia edytorów dla wielu typów obiektów, a spora część tych, które dostarcza, jest niepełna. Na przykład brakuje narzędzi do wybierania pędzli (nie ma nawet tych służących do wybierania jednolitych kolorów), okna dialogowego do wybierania czcionek (nie ma nawet listy dostępnych nazw czcionek) i graficznych edytorów, które usprawniają budowanie stylów lub szablonów.
Okno Properties nie udostępnia żadnych opisów zaznaczonych własności, przez co pomocy trzeba szukać w dokumentacji.
Okno Properties pozwala na zmianę wielu własności poprzez wpisanie ich nowych wartości w polu tekstowym. Nie ma jednak list rozwijanych dla opcji, które da się wyliczyć, ani żadnych wskazówek pomagających zorientować się, jakie wartości są dozwolone. Trzeba wiedzieć, które z nich można wpisać.
Okno Properties nie może wyświetlać własności w kolejności alfabetycznej.
Projektant nie może pracować w trybie wyrównywania do siatki.
Funkcja IntelliSense edytora kodu XAML jest niekompletna, ponieważ nie służy pomocą w wielu sytuacjach, w których powinna.
Projektant wizualny ma tak dużo wad, że często łatwiej jest utworzyć niektóre części interfejsu użytkownika w edytorze kodu XAML. Na przykład projektant ten nie udostępnia żadnych metod wykonywania zasobów, stylów i szablonów. A są to trzy filary łatwego w konserwacji interfejsu. Nie daje też żadnych możliwości tworzenia pędzli gradientów, które w aplikacjach dla systemu Windows Vista są praktycznie obowiązkowe. Na szczęście nietrudno te rzeczy wykonać w edytorze kodu XAML. Dodatkowo omawiany projektant zawiera pewną liczbę oczywistych błędów, takich jak brak możliwości umieszczania jednego rodzaju kontrolek w niektórych innych ich typach i wyświetlanie niepoprawnych komunikatów o błędach. Zdecydowanie najgorsze jest to, że projektant ten czasami doprowadza do awarii całego środowiska Visual Studio, zazwyczaj podczas próby ponownego narysowania obszaru projektanta, przez co wszystkie niezachowane dane zostają utracone. Dla własnego bezpieczeństwa pamiętaj o częstym zapisywaniu efektów swojej pracy. Obszar projektanta często robi się nieczytelny — trzeba kliknąć przycisk, aby go ponownie narysować. Nie jest to wielki problem, ale stanowi duże rozczarowanie po wielu latach używania doskonałego projektanta Windows Forms Designer. Podsumowując, projektant WPF jest nieporównywalnie słabiej dopracowany niż niezwykle potężny Windows Forms Designer. Dla pewności powtórzę jeszcze raz — często zapisuj efekty swojej pracy. Projektant Windows Forms Designer stał się na tyle bezpieczny, że wielu programistów rozleniwiło się, więc rzadko zachowują oni to, co stworzyli. Dopóki Projektant WPF nie zostanie udoskonalony, należy często zapisywać efekty pracy, aby nic nie stracić w razie awarii.
Rozdział 5.
Projektant WPF
97
Pomijając te wszystkie wady, projektant WPF jest potężnym narzędziem. Pozwala szybko utworzyć podstawową strukturę okna WPF i ustawić kontrolki. Może być konieczne dostosowanie ustawienia kontrolek i utworzenie dodatkowych elementów, na przykład zasobów i stylów, w edytorze kodu XAML, ale na początek projektant wizualny jest dobry. Mimo że edytor XAML jest pod pewnymi względami niedoskonały, udostępnia narzędzia potrzebne do dostrojenia interfejsu użytkownika utworzonego w trybie wizualnym. Te dwie części projektanta WPF łącznie dostarczają wszystko to, co jest potrzebne do budowy estetycznych i ciekawych interfejsów użytkownika WPF.
Wprowadzenie do okien projektanta Na rysunku 5.1 przedstawiono IDE Visual Studio z otwartym projektantem WPF. Rozmieszczenie okien można zmienić, ale domyślnie Toolbox znajduje się po lewej, a Properties po prawej stronie — pod oknem Solution Explorer. Projektant WPF znajduje się na środku. Obszar projektowania wizualnego jest na górze, na dole zaś — edytor kodu XAML.
Rysunek 5.1. Projektant WPF składa się z obszaru projektowania wizualnego i edytora kodu XAML
Za pomocą przycisku z ikoną przedstawiającą dwie przeciwnie ustawione strzałki można zamienić panele edytora miejscami. Opcja ta jest przydatna, gdy jeden z nich jest mały, a drugi duży. Można szybko się przełączać poprzez przechodzenie od projektanta wizualnego do edytora kodu XAML.
98
Część I
IDE
Czasami, zwłaszcza gdy zmodyfikuje się kod w edytorze XAML, projektant wizualny nie wie, co z tym zrobić, i nie jest w stanie narysować kontrolek w oknie. W takim przypadku w górnej części okna może pojawić się komunikat informujący, że edytor nie wie, co zrobić; będzie on zachęcał do kliknięcia, które spowoduje jeszcze raz załadowanie kodu XAML. Aby zaoszczędzić czas, przed odświeżeniem projektanta dokończ edytowanie kodu XAML. Jeśli jesteś w trakcie wpisywania czegoś i wystąpią problemy z XAML, kliknięcie komunikatu na górze okna projektanta spowoduje tylko wyświetlenie komunikatów o błędach. Jeśli w kodzie XAML są błędy, edytor może na górze wyświetlić odpowiednią informację. Dwukrotne kliknięcie tego komunikatu spowoduje otwarcie listy Error z wyszczególnionymi usterkami. Następnie można będzie je naprawić w edytorze XAML i odświeżyć projektant. Czasami obszar roboczy projektanta wizualnego rysuje się tylko częściowo, nie rysuje się w ogóle lub pozostawia na ekranie prostokąty albo inne pozostałości. W takiej sytuacji zazwyczaj wystarczy odświeżyć projektant poprzez kliknięcie na nim.
Dodawanie kontrolek Projektant WPF pozwala dodawać kontrolki do formularza na kilka sposobów, podobnie jak ma to miejsce w przypadku projektanta formularzy Windows. Jeśli wiesz, jak to zrobić, możesz pominąć ten podrozdział. Po pierwsze, kontrolkę można umieścić na formularzu w domyślnym rozmiarze i lokalizacji poprzez kliknięcie jej dwukrotnie w oknie Toolbox, a następnie przeniesienie jej w inne miejsce i zmianę rozmiaru. Jeśli użyjesz tej metody, nowa kontrolka zostanie umieszczona w aktualnie zaznaczonym kontenerze w oknie. Jeżeli aktualnie zaznaczoną kontrolką jest StackPanel, nowa kontrolka znajdzie się w niej. Jeśli zaś będzie to TextBox, znajdująca się w kontrolce Grid, nowa kontrolka zostanie umieszczona w kontrolce Grid. Po drugie, jeśli kliknie się kontrolkę w oknie Toolbox i najedzie nad okno, kursor zmieni się w krzyżyk. Kliknięcie w tym oknie spowoduje dodanie kontrolki w wybranym miejscu z domyślnymi rozmiarami. Można też kliknąć i przeciągnąć myszą, aby poza umiejscowieniem kontrolki określić też jej wielkość. Jeśli podczas wybierania kontrolki w oknie Toolbox przytrzyma się wciśnięty klawisz Ctrl, narzędzie to pozostanie zaznaczone także po jego wstawieniu w oknie projektowym, dzięki czemu będzie można szybko umieścić kilka egzemplarzy tej samej kontrolki. Wyobraźmy sobie na przykład, że chcesz utworzyć kilka kontrolek TextBox dla nazwy użytkownika, ulicy, miasta, województwa i kodu pocztowego. W oknie Toolbox kliknij narzędzie TextBox, a następnie pięciokrotnie kliknij na formularzu, przytrzymując za każdym razem klawisz Ctrl. Aby zakończyć dodawanie pól tekstowych, naciśnij klawisz Esc.
Rozdział 5.
Projektant WPF
99
Zaznaczanie kontrolek Utworzona kontrolka zostaje zaznaczona przez projektant. Jest to sygnalizowane otoczeniem jej jasnoszarą linią. Na rysunku 5.1 widać, że zaznaczony jest przycisk na dole po prawej. Aby później zaznaczyć którąkolwiek kontrolkę w projektancie, trzeba będzie ją kliknąć. W celu zaznaczenia grupy kontrolek można kliknąć w pustym miejscu i przeciągnąć mysz nad wybranymi kontrolkami. Zostanie wyświetlony prostokąt — pozwoli on zorientować się, które kontrolki będą zaznaczone. Po puszczeniu przycisku myszy zaznaczone zostaną wszystkie kontrolki, które przynajmniej częściowo były przykryte przez ten prostokąt. Gdy zostanie zaznaczona grupa kontrolek, na ich rogach pojawią się niewielkie znaczki, które będą przypominały fragment ramki. Na rysunku 5.2 widać, że zaznaczone są pole tekstowe i przycisk na dole po prawej.
Rysunek 5.2. Zaznaczone kontrolki są otoczone niewielkimi znaczkami
Gdy zaznaczona zostanie grupa kontrolek, nowe kontrolki będzie można dodawać za pomocą klawisza Shift i kliknięcia myszą. Do usuwania i dodawania z powrotem należącej już do grupy kontrolki służy kombinacja klawisz Ctrl+kliknięcie. Można także nacisnąć klawisz Shift, po czym kliknąć i przeciągnąć; inna metoda to naciśnięcie klawisza Ctrl oraz kliknięcie i przeciągnięcie w celu dodania lub włączenia-wyłączenia grupy kontrolek z większej grupy. Aby szybko odznaczyć wszystkie kontrolki, należy nacisnąć klawisz Esc.
100
Część I
IDE
Kopiowanie kontrolek Aby uzyskać wiele podobnych do siebie kontrolek, dobrze jest utworzyć jedną i skopiować ją odpowiednią liczbę razy. Wyobraźmy sobie na przykład, że chcesz utworzyć formularz kontaktowy z polami tekstowymi do wprowadzania nazwy użytkownika, ulicy, miasta, województwa i kodu pocztowego. Możesz w tym celu utworzyć kontrolkę Label, która będzie zawierać tekst Nazwa, i obok niej kontrolkę TextBox. Następnie ustawisz własności pola tekstowego, takie jak MaxLength i MaxLines. Potem za pomocą kliknięcia i przeciągnięcia zaznaczysz obie te kontrolki, skopiujesz je do schowka i wkleisz ich nowe kopie do okna. Nowa kontrolka TextBox będzie miała taki sam rozmiar oraz takie same własności MaxLength i MaxLines, jakie posiada oryginał; będzie znajdować się obok kontrolki Label. Kontrolki te będzie można przeciągnąć w przeznaczone dla nich miejsce, a następnie wkleić kolejne.
Przenoszenie i zmiana rozmiaru kontrolek Przenoszenie kontrolek formularzy w projektancie WPF jest łatwe. Wystarczy kliknąć wybraną pozycję i przeciągnąć ją w nowe miejsce. Kontrolki kontenery zachowują się nieco inaczej. Kiedy zostanie zaznaczony kontener typu Grid, StackPanel czy WrapPanel, projektant wyświetli nad jego lewym górnym rogiem uchwyt do przeciągania. Ma on postać małego kwadratu, który zawiera strzałkę skierowaną w czterech kierunkach. Aby przesunąć kontener i zawarte w nim kontrolki, należy kliknąć i przeciągnąć ten uchwyt. W celu przeniesienia grupy kontrolek trzeba je najpierw zaznaczyć. Następnie należy kliknąć jedną z nich i przeciągnięciem przemieścić całą grupę. Jeśli jedną z kontrolek jest kontener, za pomocą jego uchwytu można przesunąć całą grupę. Kontrolki można wstawiać do takich kontenerów, jak Grid czy StackPanel, a także usuwać je stamtąd za pomocą przeciągania i upuszczania. Kiedy jakaś kontrolka zostanie przesunięta nad nowy kontener, wszystkie pozostałe będą przyciemnione. Biały pozostanie tylko ten nowy kontener, dzięki czemu łatwiej będzie się zorientować, w którym dokładnie miejscu znajdzie się kontrolka po jej upuszczeniu. Podczas przeciągania kontrolki wyświetlane są wspomagające linie, wskazujące miejsca, w których przeciągana kontrolka znajduje się na równi z innymi kontrolkami. W przypadku niektórych kontrolek pojawiają się linie, które pokazują wyrównanie ich podstawowej linii pisma z podstawową linią pisma innych kontrolek. Na rysunku 5.3 widać, że przeciągany jest dolny przycisk. Cztery linie pomocnicze pokazują, że kontrolka ta jest wyrównana z lewą i prawą krawędzią przycisku znajdującego się na górze, lewą krawędzią kontrolki GroupBox i górną krawędzią kontrolki Ellipse. Liczby widoczne na liniach pomocniczych określają odległość ustawianej kontrolki od kontrolek, z którymi jest wyrównana.
Rozdział 5.
Projektant WPF
101
Rysunek 5.3. Linie pomocnicze pomagają wzajemnie wyrównać kontrolki
Zmienianie rozmiaru kontrolki jest tak samo łatwe jak jej przesuwanie. Kliknij kontrolkę, aby ją zaznaczyć. Następnie kliknij i przeciągnij jedną z jej krawędzi albo jeden z niewielkich białych kwadratów, które ją otaczają — w celu zmiany jej rozmiaru z wybranej strony. Kwadraty w rogach pozwalają modyfikować wielkość kontrolki — zarówno w pionie, jak i w poziomie. Kwadraty umieszczone po bokach zmieniają szerokość, a te, które znajdują się na górze i na dole — wysokość kontrolki. Kontrolki WPF posiadają dosyć złożony zestaw własności, które pozwalają określić ich zakotwiczenie w kontenerach. Na szczęście projektant wizualny udostępnia pomoce, które ułatwiają zrozumienie zasad zakotwiczania kontrolek. Po zaznaczeniu kontrolki projektant wyświetla obok jej krawędzi symbole oznaczające sposób zakotwiczenia. Strzałka prowadząca od krawędzi kontrolki do odpowiedniej krawędzi kontenera oznacza, że kontrolka pozostanie w takiej samej odległości od tej krawędzi swojego kontenera, nawet jeśli jego rozmiar się zmieni. Na rysunku 5.1 widać, że dolna i prawa krawędź zaznaczonego przycisku są połączone z dolną i prawą krawędzią kontenera. Kiedy okno zmieni rozmiar, przycisk ten przesunie się, aby pozostać w takiej samej odległości od tych krawędzi. Koło znajdujące się w pobliżu krawędzi zaznaczonej kontrolki oznacza, że krawędź ta pozostaje w takiej samej odległości od przeciwległej krawędzi tej kontrolki. Na rysunku 5.1 koła oznaczają, że lewa i górna krawędź zaznaczonego przycisku przemieszczają się, gdy jego dolna i prawa krawędź zostają przesunięte, dzięki czemu kontrolka ma zawsze taką samą szerokość i wysokość. Gdyby lewa i górna krawędź tego przycisku były połączone strzałkami z kontenerem, zmieniałby on rozmiar razem z kontenerem. Projektant nie pozwala na usunięcie więzi ze wszystkimi krawędziami kontenera (w taki sposób, że zostają zamienione na koła). Jeśli strzałka zostanie usunięta z jednej strony, pojawi się po przeciwnej. Zakotwiczenia krawędzi kontrolek można łatwo przełączać za pomocą kliknięcia. Jeśli klikniesz koło, zostanie ono zmienione w strzałkę — i odwrotnie.
102
Część I
IDE
Ustawianie własności Własności zaznaczonej kontrolki można przeglądać i edytować w oknie Properties. Dla własności logicznych dostępne jest pole wyboru ustawiające wartość. Wiele innych własności wystarczy kliknąć, po czym wprowadzić nową wartość do pola tekstowego. Niestety okno Properties nie jest zbyt pomocne, więc trzeba dokładnie wiedzieć, co chce się wpisać. Na przykład można określić własność Margin jako pusty łańcuch, aby całkowicie usunąć marginesy. Jedna liczba ustawia marginesy z wszystkich czterech stron kontrolki. Dwie liczby oddzielone przecinkiem określają lewy i prawy oraz górny i dolny margines. Jeśli zostaną wpisane cztery liczby oddzielone przecinkami, będą one wyznaczać wielkość marginesów odpowiednio lewego, górnego, prawego i dolnego. Nie można wpisać ani trzech, ani więcej niż czterech liczb dla tej własności, jednak okno Properties w żaden sposób o tym nie informuje. Innym przykładem może być własność Background, określająca sposób wypełnienia kontrolki. Można ją ustawić na wartość liczbową typu #FF0000FF (kolor niebieski) albo użyć predefiniowanej nazwy koloru, jak Green — okno Properties w takim przypadku automatycznie skonwertuje ją na wartość liczbową #FF00FF00. Wartości te trzeba wpisywać samodzielnie w polu tekstowym. Nie znajdziesz tu listy rozwijanej, okna wyboru kolorów ani żadnej innej tego typu pomocy. Liczbowe wartości kolorów są podawane w systemie ARGB (Alpha, Red, Green, Blue). Jest to liczba szesnastkowa, w której każde dwie cyfry reprezentują komponenty określające nieprzezroczystość, czerwień, zieleń i niebieskość w skali od 0 do 255. Na przykład wartość #FF0080FF ma nieprzezroczystość FF (całkowita nieprzezroczystość), wartość czerwonego 00 (brak), zielony 80 (półzielony), a niebieski FF (maksymalnie niebieski). W wyniku powstaje odcień niebieskiego. Okno Properties nie oferuje żadnej pomocy dla bardziej skomplikowanych własności, na przykład dla Background. Tę ostatnią można także ustawić na gradient w inny sposób. Należy napisać odpowiedni kod XAML.
Grupowe ustawianie własności kontrolek Jeśli ma się zaznaczoną grupę kontrolek, można czasami za pomocą okna Properties ustawić dla niej za jednym razem tę samą własność. Załóżmy, że zaznaczyłeś grupę kontrolek TextBox. W takim przypadku możesz w oknie Properties ustawić takie same wartości dla własności Width, Height, Margin, MaxLength i wielu innych. Czasami da się tak zrobić nawet po zaznaczeniu różnego rodzaju kontrolek. Jeśli na przykład zaznaczonych zostanie kilka kontrolek Label i TextBox, będzie można ustawić ich własności Width, Height i Margin. Wtedy nie da się jednak ustawić własności MaxLength, ponieważ kontrolki Label nie posiadają ich.
Rozdział 5.
Projektant WPF
103
Dodawanie kodu do kontrolek Po dodaniu odpowiednich kontrolek do formularza i ustawieniu ich własności należy dodać do formularza kod przetwarzający je i reagujący na ich zdarzenia. Istnieje możliwość dodania części kodu deklaratywnie w edytorze XAML. Można na przykład sprawić, by procedura wyzwalająca reagowała na zmianę własności lub zdarzenie kontrolki. Możesz także pisać kod Visual Basica reagujący na zdarzenia kontrolek, tak jak w aplikacjach Windows Forms. Jeśli klikniesz dwukrotnie kontrolkę w projektancie WPF, Visual Studio utworzy pustą procedurę obsługi domyślnego zdarzenia tej kontrolki, po czym otworzy ją w edytorze kodu. Na przykład poniżej znajduje się procedura obsługi zdarzeń wygenerowana przez IDE dla kontrolki Button. Domyślnym zdarzeniem kontrolki Button jest Click, dlatego kod ten stanowi procedurę obsługi zdarzenia Click. Zauważ, że na końcu pierwszego wiersza znajduje się znak kontynuacji. W Visual Studio wiersz ten mieściłby się w całości w jednej linijce. Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click End Sub
Rozluźnione delegaty pozwalają usunąć parametry z deklaracji procedury obsługi zdarzeń. Na przykład w przypadku przycisku fakt, że jest uruchomiona procedura obsługi zdarzenia Click, często wystarcza, aby program wiedział, co się dzieje, a więc nie są potrzebne żadne parametry. W takiej sytuacji można je usunąć w celu uproszczenia kodu. Nową procedurę obsługi zdarzenia można także utworzyć w edytorze kodu. W lewym górnym rogu jego okna znajduje się rozwijana lista, która zawiera spis wszystkich kontrolek umieszczonych w tym oknie. Gdy zaznaczona zostanie jedna pozycja na tym spisie, z innej listy znajdującej się w prawym górnym rogu okna będzie można wybrać dla niej zdarzenie. Kiedy się to zrobi, edytor utworzy odpowiednią procedurę obsługi.
Podsumowanie Projektant WPF pozwala tworzyć okna używane w aplikacjach WPF. Można w nim dodawać kontrolki do okna oraz zmieniać ich rozmiary i położenie. W połączeniu z oknem Properties pozwala przeglądać i modyfikować własności kontrolek oraz tworzyć procedury obsługi zdarzeń, dzięki którym możliwa jest interakcja z tymi kontrolkami. Ten rozdział stanowi wprowadzenie do projektanta WPF. Wyjaśniono w nim, jak wykorzystać jego narzędzia. Inne części książki zawierają znacznie więcej wskazówek, które dotyczą budowania okien. W rozdziale 11. — „Wybór kontrolek WPF” — i 12. — „Używanie
104
Część I
IDE
kontrolek WPF” — znajdują się dodatkowe informacje na temat rodzajów kontrolek dostępnych w projektancie WPF. Z rozdziału 13. — „Okna WPF” — dowiesz się nieco więcej o oknach i stronach WPF. Rozdział 6. — „Edytor kodu Visual Basica” — koncentruje się na opisie edytora kodu, który służy do modyfikowania kodu działającego za plecami formularzy Windows i zdarzeń kontrolek WPF. W dalszych częściach książki znajdziesz informacje na temat języka programowania Visual Basic, którego można używać w tym edytorze.
6
Edytor kodu Visual Basica Visual Studio posiada kilka edytorów dla odmiennych typów dokumentów, które zawierają parę różnych rodzajów kodu. Są tu na przykład: edytor kodu HTML (Hypertext Markup Language), XML (Extensible Markup Language), XAML (Extensible Application Markup Language) i Visual Basic. Wszystkie one mają pewne cechy wspólne, jak wyświetlanie komentarzy i słów kluczowych innym kolorem. Jako programista Visual Basica, będziesz często używać edytora kodu Visual Basica. Dlatego musisz poświęcić trochę czasu na dokładne zapoznanie się z jego funkcjami. Najbardziej oczywistym przeznaczeniem tego edytora jest możliwość wpisywania kodu. Jednak jest on znacznie bardziej skomplikowany niż zwykły edytor tekstowy, jak na przykład Notatnik. Udostępnia wiele funkcji ułatwiających pisanie poprawnego kodu Visual Basica. W tym rozdziale opiszę niektóre najważniejsze z jego funkcji. Edytor kodu Visual Basica posiada wiele funkcji, których nie mają inne tego typu narzędzia w Visual Studio. Na przykład edytory HTML, XML czy XAML nie pozwalają na ustawianie punktów wstrzymania ani narzędzi umożliwiających stopniowe wykonywanie kodu. Nawet edytor C# nie zawiera niektórych funkcji narzędzia Visual Basic, takich jak bezpośrednia aktualizacja wskaźników błędów podczas pisania kodu. Na rysunku 6.1 znajduje się edytor kodu z kodem Visual Basica w trakcie działania. Dla ułatwienia zostało włączone numerowanie wierszy. Aby włączyć tę funkcję, kliknij opcję Options w menu Tools, a następnie przejdź do strony Text Editor/Basic/General i zaznacz pole wyboru Line Numbers.
106
Część I
IDE
Rysunek 6.1. Edytor kodu Visual Basica posiada wiele funkcji, takich jak numerowanie wierszy i ikony oznaczające punkty wstrzymania i zakładki
Ikony na marginesie Na szarym marginesie po lewej stronie numeracji wierszy znajdują się ikony reprezentujące informacje, które dotyczą odpowiadających im wierszy kodu. W poniższej tabeli znajdują się opisy ikon w wierszach od 3. do 11. Wiersz
Ikona
Opis
3.
Żółta strzałka
Wskazuje miejsce wstrzymania wykonywania kodu.
4.
Czerwone koło
Punkt wstrzymania.
5.
Puste czerwone koło
Nieaktywny punkt wstrzymania.
6.
Czerwone koło ze znakiem plus
Punkt wstrzymania z warunkiem lub testem licznika wystąpień.
10.
Czerwony diament
Punkt wstrzymania, który wykonuje jakieś działania, kiedy dotrze do niego sterowanie.
11.
Biało-niebieski prostokąt
Zakładka.
Rozdział 6.
Edytor kodu Visual Basica
107
Ikony te mogą się łączyć; wskazują wtedy na więcej niż jeden warunek. Na przykład w wierszu 12. znajdują się: biało-niebieski prostokąt, który oznacza zakładkę, pusty czerwony diament, symbolizujący wyłączony punkt wstrzymania wykonujący jakieś działania, a także znak plus, oznaczający, że punkt ten posiada jakiś warunek lub licznik wystąpień. Zauważ, że niektóre z wierszy kodu są oznaczone inaczej niż tylko za pomocą ikon. Aktualnie wykonywany wiersz ma żółte tło. Te z włączonymi punktami wstrzymania zawierają biały tekst na czerwonym tle. Aby dodać lub usunąć prosty punkt wstrzymania, kliknij na szarym marginesie. W celu wstawienia bardziej złożonego punktu wstrzymania kliknij na marginesie — utworzony zostanie prosty punkt wstrzymania. Kliknij go prawym przyciskiem myszy i wybierz jedno z poleceń dostępnych w menu kontekstowym. Poniżej znajduje się opis tych poleceń:
Delete Breakpoint — usuwa punkt wstrzymania.
Disable Breakpoint — dezaktywuje punkt wstrzymania. Kiedy ten ostatni jest nieaktywny, polecenie to zamienia się w Enable Breakpoint.
Location — zmienia numer wiersza punktu wstrzymania. Zazwyczaj łatwiej jest usunąć punkt wstrzymania za pomocą kliknięcia, po czym w ten sam sposób utworzyć go w nowym miejscu.
Condition — ustawia warunek na punkcie wstrzymania. Można na przykład uzależnić zatrzymanie wykonywania przez punkt wstrzymania od tego, czy zmienna num_employees ma wartość większą od 100.
Hit Count — ustawia licznik wystąpień punktu wstrzymania. Można na przykład zdecydować, że punkt wstrzymania będzie zatrzymywał wykonywanie, gdy zostanie osiągnięty określoną liczbę razy.
Filter — pozwala ustawić punkt wstrzymania tylko dla wybranych procesów lub wątków.
When Hit — określa działanie punktu wstrzymania w chwili jego wyzwolenia. Może on na przykład wyświetlić komunikat w oknie Output lub uruchomić makro.
Aby wstawić lub usunąć zakładkę, należy umieścić kursor w wybranym wierszu i kliknąć narzędzie Toggle Bookmark. Ma ono postać niebiesko-białego prostokąta, a znajduje się na pasku narzędzi Text Editor i w górnej części okna Bookmarks (rysunek 6.1). Pozostałe narzędzia zakładek umożliwiają przechodzenie do kolejnej i poprzedniej zakładki, kolejnej lub poprzedniej w bieżącym folderze albo kolejnej lub poprzedniej w bieżącym dokumencie. Ostatnia opcja usuwa wszystkie zakładki.
Funkcja Outlining Domyślnie edytor kodu pracuje w trybie zwijania kodu (ang. outlining). W pierwszym wierszu kodu na rysunku 6.1 widać niewielki prostokąt ze znakiem minusa w środku, umieszczonym obok numeru wiersza. Prostokąt ten wskazuje na możliwość zwinięcia klasy Form1. Kliknięcie go spowoduje zwinięcie definicji klasy i wyświetlenie prostokąta ze znakiem plusa. Ponowne kliknięcie tego prostokąta spowoduje rozwinięcie definicji tej klasy.
108
Część I
IDE
Szara linia wychodząca w dół z tego prostokąta prowadzi do innych zwijalnych części kodu, które również można zwijać i rozwijać, aby uzyskać jak najlepszy widok kodu. Niżej na rysunku 6.1 widać, że została zwinięta podprocedura RandomizeArray. Wielokropek i prostokąt otaczający jej nazwę stanowią dodatkowe wskazówki, że ten fragment kodu został ukryty. Edytor automatycznie tworzy zwijalne elementy dla przestrzeni nazw, klas i ich metod oraz modułów i ich metod. Do uczynienia sekcji kodu zwijalną można też użyć instrukcji Region. Umieść na przykład kilka powiązanych ze sobą podprocedur w jednym obszarze, aby móc je zwijać i rozwijać jako grupę. Na rysunku 6.2 przedstawiono więcej przykładów zastosowania funkcji zwijania. W wierszu 36. zaczyna się obszar o nazwie Randomization Functions, obejmujący trzy zwinięte podprocedury. Zwróć uwagę, że odpowiadająca mu instrukcja End Region zawiera komentarz o takiej samej treści jak ten, który wstawiłem przy nadawaniu regionowi nazwy. Nie jest to wymagane, ale ułatwia znalezienie końca danego regionu.
Rysunek 6.2. Edytor kodu pozwala na zwijanie przestrzeni nazw, klas i ich metod, modułów i ich metod oraz regionów
Wiersz 89. zawiera zwinięty region o nazwie Utility Functions. W wierszu 95. zaczyna się moduł o nazwie HelperRoutines, zawierający jedną zwiniętą podprocedurę.
Rozdział 6.
Edytor kodu Visual Basica
109
W końcu wiersz 114. zawiera zwiniętą przestrzeń nazw ImageResources. Zauważ, że numery ukrytych wierszy są pomijane. Na przykład podprocedura RandomizeIntegerArray została zwinięta w wierszu 38. Składa się ona z piętnastu wierszy (wliczając instrukcję Sub), a więc kolejny widoczny wiersz będzie miał numer 53. Zwróć uwagę również na to, że komentarze przed podprocedurami nie są zwijane. Jeśli umieszczony zostanie krótki komentarz przed każdą procedurą, będzie można znacznie podnieść czytelność takiego zwijanego kodu.
Chmurki Jeśli najedzie się kursorem myszy nad zmienną, edytor wyświetli chmurkę z informacjami o niej. Jeżeli na przykład znajdziesz się nad zmienną całkowitoliczbową o nazwie num_actions, pojawi się napis Dim num_actions As Integer. Jeżeli najedzie się kursorem nad podprocedurę lub wywołanie funkcji, chmurka wyświetli informacje o tej metodzie. Gdy znajdziesz się nad podprocedurą RandomizeArray (która jako parametr przyjmuje tablicę liczb całkowitych), pojawi się następujący napis — Private Sub RandomizeArray(arr() As Integer). Jeśli najedzie się nad zmienną w czasie działania programu, chmurka wyświetli jej wartość. O ile zmienna ta jest konstrukcją złożoną (na przykład tablicą lub strukturą), pojawi się jej nazwa i znak plusa. Najechanie na ten znak lub kliknięcie go spowoduje rozwinięcie listy składowych tej zmiennej. Na rysunku 6.3 widać, że najechano myszą nad zmienną arr. Edytor wyświetlił znak plusa i tekst arr{Length = 100). Kiedy kursor znalazł się nad plusem, pojawiły się wartości widoczne na rysunku. Strzałki na górze i dole listy przewijają te listy po najechaniu na nie kursorem. Jeśli zmienna posiada własności będące odwołaniami do innych obiektów, możesz je rozwinąć poprzez najechanie na ich znaki plusa. W ten sposób zagłębisz się w hierarchię obiektów zmiennej na dowolną głębokość.
Funkcja IntelliSense Kiedy programista zaczyna pisać kod, edytor próbuje zgadnąć, co ma zostać wpisane. Jeśli na przykład wprowadzisz tekst Me., edytor domyśli się, że masz zamiar użyć jednej z własności lub metod aktualnego obiektu. Funkcja IntelliSense wyświetla listę własności i metod, które mogą być potrzebne. W miarę wprowadzania większej ilości tekstu lista IntelliSense przewija się do opcji najbliższych temu, co wpisano.
110
Część I
IDE
Rysunek 6.3. Jeśli najedzie się kursorem myszy nad zmienną w czasie działania programu, będzie można podejrzeć jej wartość
Na rysunku 6.4 widać, że wpisano Me.Set, a więc funkcja IntelliSense wyświetla metody bieżącego obiektu, których nazwy zaczynają się od ciągu Set.
Rysunek 6.4. Funkcja IntelliSense wyświetla listę własności i metod, które mogą być potrzebne
Rozdział 6.
Edytor kodu Visual Basica
111
Listę w oknie IntelliSense można przewijać za pomocą klawiszy strzałek do góry i w dół. Po znalezieniu szukanego elementu na liście należy nacisnąć klawisz Tab, aby go zaakceptować. W celu zamknięcia okna IntelliSense i wpisania reszty kodu ręcznie trzeba użyć klawisza Esc. Gdy wpiszesz metodę i jej otwierający nawias, funkcja IntelliSense wyświetli informacje o jej parametrach. Na rysunku 6.5 przedstawiono informacje o parametrach metody SetBounds obiektu formularza. Przyjmuje ona cztery parametry — x, y, width i height.
Rysunek 6.5. Funkcja IntelliSense wyświetla informacje o parametrach metody
IntelliSense wyświetla krótką charakterystykę bieżącego parametru x. Gdy będziesz wpisywać kolejne parametry, pojawią się ich opisy. Funkcja IntelliSense informuje także, czy istnieją przeciążone wersje metody. Na rysunku 6.5 widać, że została opisana pierwsza z dwóch dostępnych wersji. Do przechodzenia pomiędzy przeciążonymi wersjami służą strzałki po lewej stronie.
Kolorowanie kodu i wyróżnianie Edytor kodu nadaje różnym elementom składni języka odmienne kolory. Barwy te można zmienić poprzez kliknięcie pozycji Options w menu Tools i przejście na kartę Fonts and Colors w grupie Environment. Jeśli jednak nie ma jakiegoś bardzo dobrego powodu do zmiany tych kolorów, lepiej pozostawić je takimi, jakie są, aby uniknąć późniejszych kłopotów. W poniższej tabeli znajdują się opisy niektórych domyślnych barw, które są używane przez edytor kodu do kolorowania różnych elementów składni języka.
112
Część I
IDE
Element składni
Sposób wyróżnienia
Komentarz
Zielony kolor tekstu
Błąd kompilatora
Faliste niebieskie podkreślenie
Słowo kluczowe
Niebieski kolor tekstu
Inny błąd
Faliste fioletowe podkreślenie
Słowo kluczowe preprocesora
Niebieski kolor tekstu
Region tylko do odczytu
Jasnoszare tło
Zdezaktualizowany kod
Fioletowy kolor tekstu
Typy użytkownika
Granatowy kolor tekstu
Ostrzeżenia
Faliste zielone podkreślenie
Kilka innych elementów, których barwę warto zmienić, jest domyślnie pisanych czarnym kolorem na białym tle. Należą do nich identyfikatory (nazwy zmiennych, typy, metody i własności obiektów, nazwy przestrzeni nazw itd.), liczby i łańcuchy. Kiedy edytor znajdzie w kodzie błąd, wyróżni go falistym podkreśleniem. Gdy najedziesz na to miejsce kursorem, pojawi się chmurka z opisem usterki. Jeśli Visual Studio zgadnie, co programista mógł mieć na myśli, na końcu falowanej linii wyświetli niewielki prostokąt, sugerujący, że mogą się w nim znajdować jakieś przydatne wskazówki. Instrukcja przypisania i = "12", widoczna na rysunku 6.6, zawiera błąd, ponieważ stanowi próbę przypisania wartości łańcuchowej do zmiennej całkowitoliczbowej, a to jest wbrew opcji Option Strict ustawionej na On. Edytor wyświetla falowane podkreślenie i wskaźnik sugestii, ponieważ wie, jak naprawić tę usterkę. Znajdujące się na dole okno Error List również zawiera opis tego błędu.
Rysunek 6.6. Jeśli edytor wie, na czym polega problem, wyświetla wskaźnik sugestii
Rozdział 6.
Edytor kodu Visual Basica
113
Jeśli najedzie się kursorem na wskaźnik, edytor wyświetli chmurkę z charakterystyką problemu i ikonę błędu. Kliknięcie jej spowoduje wyświetlenie okna dialogowego z opisem błędu i listą działań, które mogą być potrzebne do jego usunięcia. Na rysunku 6.7 przedstawiono okno dialogowe z sugerowaną poprawką usterki z rysunku 6.6. Kliknięcie odnośnika nad poprawką kodu w oknie sugestii lub dwukrotne kliknięcie samego proponowanego kodu spowoduje wprowadzenie odpowiednich zmian.
Rysunek 6.7. Okno dialogowe podpowiada, jak można rozwiązać problem
Fragmenty kodu, które nadają się do użycia w innych aplikacjach Fragmenty kodu, które można wykorzystać w wielu różnych programach (ang. snippets), są przechowywane w odpowiedniej bibliotece, dzięki czemu można je z łatwością wstawić do dowolnej aplikacji. Visual Studio zawiera setki takich fragmentów kodu; wykonują one różne standardowe działania. Przed rozpoczęciem pracy nad skomplikowanym fragmentem programu powinno się sprawdzić, czy coś podobnego nie jest już dostępne w bibliotece. Warto nawet poświęcić trochę czasu na przejrzenie w menedżerze Snippet Manager, znajdującym się w menu Tools, dostępnych fragmentów kodu przed rozpoczęciem jakiegokolwiek projektu. Nie ma sensu na przykład opracowywać metod statystycznych, skoro ktoś już wcześniej to zrobił i udostępnił kod.
114
Część I
IDE
Fragmenty kodu są przechowywane w plikach tekstowych ze znacznikami XML, dzięki czemu łatwo je udostępniać innym programistom. W witrynie uzupełniającej tej książki, www.vb-helper.com/vb_prog_ref.htm, można podzielić się swoimi fragmentami kodu z innymi osobami tworzącymi aplikacje, jak również pobrać to, co udostępnili inni. W kolejnych podrozdziałach znajduje się objaśnienie używania fragmentów kodu w programach, a także ich tworzenia.
Używanie fragmentów kodu Aby wstawić do programu fragment kodu, należy kliknąć prawym przyciskiem myszy w wybranym miejscu i kliknąć opcję Insert Snippet — wyświetlona zostanie odpowiednia lista kategorii. W celu znalezienia wymaganych fragmentów trzeba dwukrotnie kliknąć wybraną kategorię. Jeśli zostanie zaznaczony jakiś fragment kodu, pojawi się chmurka z jego opisem. Na rysunku 6.8 widać edytor przygotowujący się do wstawienia fragmentu o nazwie Tworzy własność publiczną z kategorii VbProgRef Code Snippets.
Rysunek 6.8. Gdy wybierzesz fragment kodu, wyświetli się chmurka z jego opisem
Aby wstawić fragment kodu do programu, kliknij go dwukrotnie. Może on wymagać dostosowania niektórych wartości. Są one wyróżnione jasnozielonym kolorem tła, a pierwsza z nich jest od razu zaznaczona. Gdy najedziesz kursorem myszy nad którąś z nich, wyświetli się chmurka z jej opisem. Pomiędzy wartościami wymagającymi zmian można przemieszczać się za pomocą klawisza Tab.
Rozdział 6.
Edytor kodu Visual Basica
115
Na rysunku 6.9 przedstawiono edytor po wstawieniu tego kodu. Tekst Własność typu Integer jest wyróżniony i zaznaczony. Dodatkowo zaznaczono też fragmenty Integer, 0 i MyProperty. Mysz znajduje się nad fragmentem Własność typu Integer, dzięki czemu chmurka wyświetla informacje o tej wartości.
Rysunek 6.9. Wartości, które należy zmienić, są wyróżnione
Tworzenie fragmentów kodu do użytku w innych programach Aby uzyskać nowy fragment kodu, należy utworzyć plik typu XML ze znacznikami własności, które definiują ten fragment, oraz wszelkie wartości, mające być zmienionymi przez użytkownika. Poniższy kod prezentuje fragment kodu Tworzy własność publiczną, który został użyty w poprzednim podrozdziale. Zewnętrzne znaczniki CodeSnippets i CodeSnippet są standardowe — nie należy ich zmieniać. Opis fragmentu powinien znajdować się w znaczniku Title w sekcji Header. W znaczniku Snippet utwórz sekcję Declarations, opisującą tekst, który powinien zostać zmieniony przez użytkownika. W tym przykładzie zostały zdefiniowane symbole DataType, Description, DefaultValue i PropertyName. Każda definicja posiada identyfikator ID; może też zawierać znaczniki ToolTip i Description.
116
Część I
IDE
Po deklaracjach znajduje się znacznik Code, zawierający kod źródłowy fragmentu. Składnia informuje procesory XML, że wszystkie znaki — łącznie ze znakiem powrotu karetki — mają umieszczać pomiędzy znakami w znaczniku. Tworzy własność publicznąDataTypeTyp danych własności.IntegerDescriptionOpis własności.Własność typu Integer.DefaultValueDomyślna wartość własności.0PropertyNameNazwa własności.MyProperty
Zapisz definicję XML fragmentu kodu w wybranym wcześniej katalogu. Aby dodać ten ostatni do listy lokalizacji fragmentów kodu, należy w menu Tools kliknąć opcję Code Snippets Manager — wyświetlone zostanie okno widoczne na rysunku 6.10. Kliknij przycisk Add, przejdź do kategorii ze swoim fragmentem i kliknij OK. Od tej pory ten katalog i znajdujące się w nim fragmenty będą dostępne w menu podręcznych Insert Snippet.
Rozdział 6.
Edytor kodu Visual Basica
117
Rysunek 6.10. Okno Code Snippets Manager pozwala dodawać i usuwać katalogi z fragmentami kodu
Edytor kodu w czasie działania programu Edytor kodu w czasie działania programu zachowuje się nieco inaczej niż w trybie projektowania. Wiele z funkcji projektanckich nadal działa, na przykład punkty wstrzymania, zakładki, funkcja IntelliSense i fragmenty kodu. W czasie działania programu edytor dodaje nowe narzędzia, które służą do kontroli tej aplikacji. Aby móc testować i monitorować jakąś wartość, należy kliknąć ją prawym przyciskiem myszy i z menu podręcznego wybrać opcję Add Watch lub Quick Watch. Polecenia Step Into, Step Over i Step Out w menu Debug lub jego pasku narzędzi pozwalają na przechodzenie programu przez kod. Najechanie kursorem nad zmienną powoduje wyświetlenie chmurki z jej wartością (więcej informacji na temat chmurek znajduje się w podrozdziale „Chmurki”). Aby przejść kursorem do kolejnej instrukcji, która zostanie wykonana przez program, należy kliknąć prawym przyciskiem na dowolnej instrukcji i kliknąć pozycję Set Next Statement. Opcja Run To Cursor powoduje wykonywanie aplikacji do wiersza, w którym znajduje się kursor. Kliknij prawym przyciskiem myszy, po czym kliknij opcję Set Next Statement, aby przeskoczyć do nowej lokalizacji. Można także przejść w inne miejsce poprzez przeciągnięcie na marginesie żółtej strzałki. Istnieje kilka ograniczeń dotyczących tego, gdzie można przenieść wykonywanie programu. Nie można na przykład wyjść z jednej procedury i wskoczyć do innej.
118
Część I
IDE
Za pomocą wszystkich tych funkcji można przejść przez kod w czasie jego wykonywania, a także dowiedzieć się, co robi w każdym kroku. Sprawdzisz wartości zmiennych, prześledzisz ścieżkę wykonawczą instrukcji If Then, wejdziesz i wyjdziesz z procedur oraz wykonasz program — aż do spełnienia określonych warunków. Więcej informacji na temat menu Debug i jego podmenu znajduje się w podrozdziale „Menu Debug” w rozdziale 2. — „Menu, paski narzędzi i okna”. O technikach usuwania błędów przeczytasz w rozdziale 7. — „Usuwanie błędów”. Inne funkcje dostępne podczas działania programu można odkryć poprzez studiowanie edytora właśnie w czasie działania aplikacji. Kliknij prawym przyciskiem myszy w różnych miejscach narzędzia, aby sprawdzić, jakie polecenia są dostępne w tym trybie.
Podsumowanie Edytor kodu Visual Studio jest jednym z najważniejszych narzędzi Visual Basica. Mimo że za pomocą projektanta formularzy można rozmieścić kontrolki na formularzu, nie jest on w stanie wiele zdziałać bez kodu działającego za ich plecami. Edytor kodu Visual Basica pozwala wpisywać kod do modułu, ale nie jest to jego jedyna funkcja. Wyświetla chmurki zawierające wartości zmiennych, pozwala zwijać i rozwijać sekcje kodu, dzięki czemu można skupić się na bieżącym zadaniu. Funkcja IntelliSense przypomina, jakie są dostępne metody, a także jakie przyjmują one parametry. Ponadto edytor ten koloruje składnię kodu źródłowego i natychmiast zaznacza błędy, a dzięki fragmentom kodu (ang. snippets) umożliwia wielokrotne wykorzystywanie w łatwy sposób wcześniej napisanego kodu. Wiele z tych narzędzi ułatwia zrozumienie działania kodu w trakcie jego pisania. Rozdział 7. — „Usuwanie błędów” — koncentruje się na opisie narzędzi, które pomagają pojąć działanie kodu w trakcie jego wykonywania. Pozwalają one przejść przez kod w czasie działania programu, aby sprawdzić, co on dokładnie robi, a także jakie popełnia błędy.
7
Usuwanie błędów Edytor kodu opisany w rozdziale 6. — „Edytor kodu Visual Basica” — udostępnia narzędzia, dzięki którym pisanie aplikacji w Visual Basicu jest względnie łatwe. Takie funkcje, jak wskazywanie błędów, chmurki i IntelliSense ułatwiają generowanie kodu zgodnego z zasadami tego języka. Żaden edytor kodu ani inne narzędzie nie może zagwarantować, że pisany przez nas kod naprawdę zrobi to, co chcemy. Usuwanie błędów (debugowanie) jest procesem, który polega na takim modyfikowaniu kodu, aby działał poprawnie i zwracał dobre wyniki. Narzędzia testujące, takie jak NUnit (www.nunit.org), potrafią na wiele sposobów zadbać, by kod działał poprawnie, ale są przydatne tylko wówczas, gdy pisze się to, co należy. Jeśli potrzebujesz systemu płatności, a napiszesz aplikację inwentaryzacyjną, żadne narzędzie Ci nie pomoże. W zależności od poziomu skomplikowania aplikacji debugowanie może być niezwykle trudne. Chociaż Visual Studio nie zrobi tego za Ciebie, udostępnia wiele narzędzi, które ułatwiają wykonywanie tego zadania. Pozwala na zatrzymywanie wykonywania programu, dzięki czemu można posprawdzać wartości zmiennych i zmodyfikować je, zbadać źródła danych, a następnie krok po kroku wykonać kod. W tym rozdziale opisane zostaną najważniejsze narzędzia debugowania Visual Basica. Przyjrzymy się opcjom dostępnym w menu Debug i innych oknach IDE, które bardzo się przydają podczas usuwania błędów.
Menu Debug Menu Debug zawiera polecenia, które pomagają w debugowania programu. Umożliwiają one uruchamianie aplikacji w debugerze, przechodzenie przez kod, ustawianie i usuwanie punktów wstrzymania i ogólnie śledzenie wykonywania kodu, dzięki czemu można sprawdzić, co on robi, a także co ewentualnie wykonuje źle.
120
Część I
IDE
Ponieważ w efekcie narzędzia te mogą znacznie ułatwić znajdowanie problemów w kodzie, należy poświęcić nieco czasu na naukę posługiwania się nimi. Dzięki nim czas potrzebny na znalezienie błędu może stać się kwestią minut zamiast godzin, a nawet dni. Zestaw narzędzi dostępnych w menu Debug zmienia się w zależności od kilku warunków, takich jak typ otwartego pliku; to, czy program został uruchomiony; wiersz kodu, w którym znajduje się kursor; to, czy wiersz ten zawiera punkt wstrzymania. Poniżej znajdują się opisy poleceń, które będą dostępne, gdy wykonywanie programu zostanie zatrzymane w wierszu zawierającym punkt wstrzymania. W odmiennych warunkach będzie można korzystać z innych zestawów narzędzi.
Windows — to podmenu zawiera polecenia wyświetlające inne okna związane z debugowaniem. Jego dokładniejszy opis znajduje się w podrozdziale „Menu Debug — podmenu Windows”.
Continue — wznawia wykonywanie programu. Aplikacja działa, aż się skończy, dotrze do punktu wstrzymania lub zostanie zatrzymana przez programistę.
Break All — zatrzymuje wykonywanie wszystkich programów działających w debugerze. Może to być więcej niż jedna aplikacja, jeśli debugowanych jest więcej niż jeden program w tym samym czasie. Opcja ta może być przydatna, jeśli na przykład dwie aplikacje ściśle ze sobą współpracują.
Stop Debugging — zatrzymuje wykonywanie programu i kończy sesję debugera. Aplikacja zostaje natychmiast zamknięta, a więc nie ma czasu na wykonanie żadnego kodu czyszczącego.
Step Into — zmusza debuger do wykonania bieżącego wiersza kodu. Jeśli jest w nim wywoływana jakaś funkcja, podprocedura lub inny rodzaj procedury, punkt wykonywania zostanie przeniesiony do tej właśnie procedury. Nie zawsze jest oczywiste, czy w danym wierszu jest wywoływana jakaś procedura. Na przykład wiersz kodu, który ustawia własność obiektu, może być zwykłym parametrem własności lub wywołaniem procedury konfigurującej ją.
Step Over — zmusza debuger do wykonania bieżącego wiersza kodu. Jeśli w kodzie tym jest wywoływana funkcja, podprocedura lub inna procedura, narzędzie wywoła ją, ale nie wejdzie do niej, dzięki czemu nie trzeba będzie przechodzić przez jej kod. Jeśli jednak w procedurze tej znajduje się punkt wstrzymania, wykonywanie zostanie w nim wstrzymane.
Step Out — sprawia, że debuger działa aż do opuszczenia aktualnie wykonywanej procedury. Wykonywanie zostaje wstrzymane, kiedy program dochodzi do wiersza kodu, w którym procedura ta została wywołana.
Quick Watch — wyświetla okno dialogowe z informacjami na temat wybranego obiektu w kodzie. Na rysunku 7.1 przedstawiono okno dialogowe z informacjami o kontrolce TextBox, która nosi nazwę txtDirectory. Widać niektóre z własności kontrolki, takie jak TabIndex, TabStop, Tag i Text.
Rozdział 7.
Usuwanie błędów
Rysunek 7.1. Okno dialogowe QuickWatch pozwala sprawdzić własności obiektu i opcjonalnie ustawić dla niego czujkę
Jeśli kliknie się prawym przyciskiem myszy wartość jednej z własności, będzie można ją zmienić w tym oknie. Kliknięcie przycisku Add Watch spowoduje dodanie przez debuger tego wyrażenia do okna Watch z rysunku 7.2. Aby szybciej utworzyć czujkę (ang. watch), można zaznaczyć nazwę zmiennej w kodzie i przeciągnąć ją, po czym upuścić w oknie Watch. W celu usunięcia czujki należy kliknąć ją prawym przyciskiem myszy i kliknąć opcję Remove Watch.
Rysunek 7.2. W oknie Watch można śledzić wartości wyrażeń
Exceptions — gdy wybierzesz to polecenie, wyświetlone zostanie okno dialogowe z rysunku 7.3. Jeśli zostanie zaznaczone pole wyboru Thrown, debuger zatrzyma się za każdym razem, gdy wystąpi wybrany typ błędu. Jeżeli zostanie zaznaczone pole wyboru User-unhandled, debuger zatrzyma się, gdy wystąpi zaznaczony rodzaj usterki, ale program nie przechwyci go za pomocą procedury obsługi błędów. Wyobraźmy sobie na przykład, że w programie znajduje się wywołanie podprocedury powodującej wyjątek dzielenia przez zero. Rozwiń w oknie kategorię Common Language Runtime Exceptions, następnie kliknij System i znajdź opcję System.DivideByZeroException (aby szybko znaleźć tę opcję, użyj przycisku Find). Jeśli zaznaczysz pole wyboru Thrown, debuger będzie zatrzymywał się w podprocedurze, gdy wystąpi wyjątek dzielenia przez zero, nawet jeśli kod jest chroniony przez procedurę obsługi błędów. Jeżeli jednak zaznaczysz pole wyboru User-unhandled, debuger zatrzyma się tylko wówczas, gdy w czasie występowania usterki nie będzie aktywna żadna procedura obsługi błędów.
121
122
Część I
IDE
Rysunek 7.3. Okno dialogowe Exceptions pozwala ustalić sposób obsługi przez Visual Basic nieprzechwyconych wyjątków
Toggle Breakpoint — włącza i wyłącza punkt wstrzymania w bieżącym wierszu. Kiedy sterowanie dojdzie do wiersza z aktywnym punktem wstrzymania, zatrzyma się, aby można było obejrzeć kod i sprawdzić wartości zmiennych. Punkty wstrzymania można też włączać i wyłączać poprzez klikanie na lewym marginesie w edytorze kodu lub umieszczanie kursora w wybranym wierszu i naciskanie klawisza F9.
New Breakpoint — to podmenu zawiera polecenie Break At Function. Wyświetla ono okno dialogowe, w którym można określić funkcję, na której powinno się zatrzymać wykonywanie programu.
Delete All Breakpoints — usuwa wszystkie punkty wstrzymania z całego rozwiązania.
Menu Debug — podmenu Windows Podmenu Windows w menu Debug zawiera polecenia, które otwierają okna związane z debugowaniem. Poniżej znajdują się krótkie opisy najbardziej przydatnych z nich. W dwóch kolejnych podrozdziałach bardziej szczegółowo omówię okna Breakpoints, Command i Immediate.
Immediate — wyświetla okno Immediate, w którym można wpisywać i wykonywać instrukcje Visual Basica. Bardziej szczegółowy opis tego okna znajduje się w podrozdziale „Okna Command i Immediate”, umieszczonym w dalszej części rozdziału.
Locals — wyświetla okno Locals z rysunku 7.4, które prezentuje wartości zmiennych zdefiniowanych w lokalnym kontekście. Aby zmienić którąś wartość, kliknij ją i wpisz wybraną liczbę. W celu rozwinięcia lub zwinięcia wartości kliknij znajdujący się po jej lewej stronie znak plusa. Na przykład na rysunku 7.4 pozycja Me jest obiektem z wieloma własnościami posiadającymi swoje własne wartości. Kliknięcie znaku plusa spowoduje rozwinięcie tego obiektu i wyświetlenie jego własności. Te ostatnie mogą być także obiektami, a więc możliwe jest dalsze ich rozwijanie.
Rozdział 7.
Usuwanie błędów
123
Rysunek 7.4. Okno Locals wyświetla wartości zmiennych zdefiniowanych w lokalnym kontekście
Breakpoints — wyświetla okno Breakpoints z rysunku 7.5, w którym znajdziesz punkty wstrzymania, ich lokalizacje oraz warunki. Dzięki zaznaczaniu i odznaczaniu pól wyboru po lewej stronie można włączać i wyłączać te punkty wstrzymania. Klikanie prawym przyciskiem myszy pozwala edytować lokalizację, warunek, licznik wystąpień i akcję punktu wstrzymania. Na pasku narzędzi tego okna można utworzyć nowy punkt wstrzymania na funkcji, usunąć jeden lub wszystkie, włączyć lub wyłączyć wszystkie, przejść do kodu źródłowego punktu wstrzymania oraz zmienić kolumny widoczne w tym oknie. Kliknięcie prawym przyciskiem pozwala na zmianę warunku punktu wstrzymania (warunek decydujący o tym, czy punkt wstrzymania jest aktywowany), licznika wystąpień (licznik określający liczbę aktywowania punktu wstrzymania) oraz akcji When Hit (podejmowana w chwili dotarcia sterowania do punktu wstrzymania). Więcej szczegółów na ten temat znajduje się w podrozdziale „Okno Breakpoints”.
Output — wyświetla okno Output z wynikami kompilacji i danymi wygenerowanymi przez instrukcje Debug i Trace.
Autos — wyświetla okno Autos z rysunku 7.6. Zawiera ono wartości zmiennych lokalnych i globalnych, które znajdują się w bieżącym wierszu kodu oraz w trzech wierszach umieszczonych przed i za nim.
124
Część I
IDE
Rysunek 7.6. Okno Autos wyświetla zmienne używane w bieżącej instrukcji oraz trzech instrukcjach przed i za nią
Call Stack — wyświetla okno Call Stack z rysunku 7.7. Prezentuje ono procedury, które wywołały inne procedury, aby dojść do obecnego miejsca w programie. W tym przypadku program zatrzymał się na wierszu 26. w funkcji FindEmployee. Ta ostatnia została wywołana przez funkcję SearchDatabase w wierszu 14., ta zaś — przez procedurę obsługi zdarzeń btnLocals_Click. Dwukrotne kliknięcie linijki spowoduje przeniesienie do odpowiedniego miejsca na stosie wywołań programu. Technika ta umożliwia przechodzenie w górę stosu w celu zbadania kodu, który wywołał aktualnie działające procedury. Metoda ta jest bardzo efektywna w sprawdzaniu, jaki kod odpowiada za daną procedurę.
Rysunek 7.7. W oknie Call Stack można zobaczyć, które procedury wywołały daną procedurę
Threads — wyświetla okno Threads z rysunku 7.8. Wątek (ang. thread) to osobna ścieżka wykonawcza działająca w programie. Aplikacja wielowątkowa może pracować w kilku wątkach, które wykonują jednocześnie parę zadań. Okno Threads pozwala kontrolować priorytety i zawieszenia wątków. W ostatnim wierszu jest wyznaczona lokalizacja WindowsApplication1.Form1.FindEmployee, co oznacza, że wątek ten wykonuje procedurę FindEmployee w module Form1 programu WindowsApplication1.
Rysunek 7.8. Okno Threads wyświetla informacje o wątkach wykonawczych programu
Rozdział 7.
Usuwanie błędów
125
Aby zawiesić wątek, należy go kliknąć prawym przyciskiem myszy i kliknąć opcję Freeze. Dwukrotne kliknięcie lewym przyciskiem myszy lub kliknięcie prawym przyciskiem myszy i kliknięcie opcji Thaw spowoduje wznowienie wykonywania wątku.
Watch — to podmenu zawiera polecenia Watch 1, Watch 2, Watch 3 i Watch 4. Każde z nich wyświetla inne okno czujki. Jeśli utworzysz czujkę za pomocą opisanego wcześniej polecenia QuickWatch z menu Debug, zostanie ona umieszczona w oknie Watch 1 (rysunek 7.2). Można wykonywać kopie czujek poprzez przeciąganie ich z jednego okna do innego, a także poprzez klikanie w kolumnie Name w pustym wierszu na dole okna czujki i wpisanie wyrażenia, które ma być obserwowane. Przydatną sztuczką jest przeciągnięcie okien Watch 2, 3 i 4 na okno Watch 1, aby były kartami w tym samym oknie. Dzięki temu można łatwo grupować i zarządzać czterema zestawami czujek.
Modules — wyświetla okno Modules z rysunku 7.9. Zawiera ono informacje o używanych przez program plikach DLL i EXE. Znajduje się tu nazwa pliku i ścieżka każdego modułu. Okno informuje też, czy każdy moduł jest zoptymalizowany, czy jest to nasz kod (w odróżnieniu od zainstalowanej biblioteki), a także czy załadowane są symbole debugowania. Wyświetla ono również kolejność ładowania (moduły o małych numerach są ładowane na początku), wersję oraz znacznik czasu każdego modułu. Kliknięcie kolumny spowoduje posortowanie modułów według niej.
Rysunek 7.9. Okno Modules wyświetla informacje o modułach używanych przez program
Processes — wyświetla listę procesów związanych z sesją Visual Studio. Zaliczane są do nich wszystkie programy uruchomione przez Visual Studio oraz procesy, do których dołączył się programista, gdy użył polecenia Attach to Process z menu Debug.
Zazwyczaj okna te są zamknięte w czasie działania programu w osobnych kartach na jednym obszarze w dolnej części okna IDE. Dzięki temu można szybko się między nimi przełączać; nie zajmują też one zbyt dużo miejsca.
126
Część I
IDE
Okno Breakpoints Punkt wstrzymania to wiersz kodu, w którym wyznaczono zatrzymanie programu. Kiedy aplikacja dojdzie do tego wiersza, jej wykonywanie zostanie wstrzymane, a Visual Studio wyświetli kod w oknie edytora kodu. Dzięki temu będzie można zbadać lub ustawić zmienne, sprawdzić, która procedura wywołała procedurę zawierającą ten kod, lub w jakiś inny sposób dowiedzieć się, co robi kod. Okno Breakpoints zawiera listę wszystkich punktów wstrzymania zdefiniowanych w programie. Jest to przydatne z kilku powodów. Przede wszystkim — jeśli zostanie zdefiniowanych dużo punktów wstrzymania, może być trudno znaleźć wszystkie z nich. Podczas gdy niektóre polecenia pozwalają wyłączyć, włączyć lub usunąć wszystkie punkty wstrzymania naraz, czasami może być konieczne odszukanie tylko jednego z nich. Często stosowaną techniką podczas debugowania jest umieszczenie źle działającego kodu w komentarzach, wstawienie nowego kodu i ustawienie w pobliżu tej modyfikacji punktu wstrzymania, co pozwala sprawdzić, jak działa nowa wersja. Po zakończeniu testowania kodu najczęściej trzeba się pozbyć starego lub nowego fragmentu, a więc nie ma sensu na ślepo usuwać wszystkich punktów wstrzymania. W oknie Breakpoints wyświetlana jest lista wszystkich punktów wstrzymania. Kliknięcie na jednym z nich pozwala szybko przejść do odpowiedniego miejsca w kodzie programu. Systemy kontroli kodu źródłowego, takie jak Visual SourceSafe, śledzą wszystkie zmiany w kodzie. Kiedy wystąpią jakieś problemy, można porównać aktualną wersję kodu z wcześniejszą, aby sprawdzić, czym się różnią. W najgorszym przypadku istnieje możliwość przywrócenia starej wersji kodu. Jest to znacznie łatwiejsze niż samodzielne zapamiętywanie wszystkich zmian i usuwanie ich. Ponadto okno Breakpoints umożliwia modyfikowanie zdefiniowanych punktów wstrzymania. Zaznaczanie i odznaczanie pól wyboru po lewej stronie pozwala włączać i wyłączać punkty wstrzymania. Za pomocą narzędzi na pasku narzędzi włączysz i wyłączysz wszystkie punkty wstrzymania, usuniesz je lub przejdziesz do ich kodu źródłowego. Jeśli klikniesz prawym przyciskiem myszy punkt wstrzymania i wybierzesz opcję Condition, wyświetlone zostanie okno dialogowe z rysunku 7.10. Domyślnie punkt wstrzymania wstrzymuje wykonywanie tam, gdzie się znajduje. W tym oknie dialogowym można dodać warunek, który będzie określać, czy dany punkt wstrzymania ma aktywować się, kiedy zostanie osiągnięty. W tym przypadku program zatrzyma się tylko wówczas, gdy wyrażenie (i=j) And (i>20) będzie miało wartość True w chwili osiągnięcia punktu wstrzymania. Pamiętaj, że warunki punktów wstrzymania mogą znacznie spowolnić działanie aplikacji. Kliknięcie prawym przyciskiem myszy punktu wstrzymania i wybranie opcji Hit Count spowoduje wyświetlenie okna dialogowego Breakpoint Hit Count z rysunku 7.11. Gdy wykonywanie dochodzi do punktu wstrzymania, za każdym razem zostaje zwiększony jego licznik wystąpień (ang. hit count). W oknie tym można zdecydować, że punkt wstrzymania będzie aktywował się w zależności od wartości licznika wystąpień.
Rozdział 7.
Usuwanie błędów
127
Rysunek 7.10. Okno dialogowe Breakpoint Condition pozwala zdefiniować warunek, który decyduje o tym, czy Visual Studio zatrzyma wykonywanie programu po dojściu do niego
Rysunek 7.11. Okno dialogowe Breakpoint Hit Count pozwala uzależnić aktywację punktu wstrzymania od liczby razy, którą został osiągnięty przez kod
Na liście rozwijanej dostępne są opcje break always, break when the hit count is equal to, break when the hit count is a multiple of oraz break when the hit count is greater than or equal to. Jeśli wybrana zostanie któraś z nich — poza pierwszą — będzie można w przeznaczonym do tego polu wpisać odpowiednią wartość, dzięki czemu program zatrzyma wykonywanie po osiągnięciu punktu wstrzymania odpowiednią liczbę razy. Jeśli na przykład zaznaczysz opcję break when the hit count is a multiple of i w polu tekstowym wprowadzisz wartość 2, wykonywanie będzie się zatrzymywać na co drugim dojściu do punktu wstrzymania. Kliknięcie prawym przyciskiem myszy punktu wstrzymania i wybranie opcji When Hit spowoduje wyświetlenie okna dialogowego When Breakpoint is Hit z rysunku 7.12, które pozwala określić akcję punktu wstrzymania, kiedy zostanie on aktywowany. Wybranie opcji Print a message sprawi, że program wyświetli komunikat w oknie Output. Run a macro wykonuje makro VBA. Zaznaczenie opcji Continue execution sprawia, że program kontynuuje działanie bez zatrzymania.
Okna Command i Immediate Okna Command i Immediate umożliwiają wykonywanie poleceń, kiedy w program jest zatrzymany w debugerze. Jednym z najbardziej przydatnych opcji zawartych w nich jest instrukcja Debug.Print. Na przykład Debug.Print x wyświetla wartość zmiennej x. Zamiast instrukcji Debug.Print można użyć jej skróconej wersji w postaci znaku zapytania. Poniżej zobaczysz, jak to polecenie może wyglądać w oknie Command. Symbol > w tym przypadku jest znakiem zachęty do wpisania polecenia (jest on wyświetlony przez okno), a 123 to wynik — wartość zmiennej x. W oknie Immediate instrukcja nie zawierałaby symbolu >.
128
Część I
IDE
Rysunek 7.12. Okno dialogowe When Breakpoint Is Hit pozwala określić akcje Visual Basica w chwili aktywacji punktu wstrzymania
>? x 123
Polecenie >immend nakazuje oknu Command otwarcie okna Immediate. Natomiast polecenie >cmd nakazuje oknu Immediate otwarcie okna Command. Mimo iż funkcje tych okien mogą się czasami nakładać, służą do dwóch bardzo różnych celów. Okno Command może wysyłać polecenia do IDE Visual Studio. Pojawiają się one zwykle w menu lub na paskach narzędzi — albo mogłyby się tam znajdować. Na przykład poniższe polecenie wykorzystuje inne — QuickWatch z menu Debug — do otwarcia okna QuickWatch dla zmiennej first_name: >Debug.QuickWatch first_name
Jednym szczególnie przydatnym poleceniem jest Tools.Alias. Wyświetla ono listę aliasów poleceń zdefiniowanych w IDE. Na przykład informuje, że znak ? jest aliasem polecenia Debug.Print, zaś znaki ?? są aliasem polecenia Debug.QuickWatch. Okno Command oferuje także wsparcie ze strony funkcji IntelliSense. Jeśli zostanie wpisana nazwa menu, na przykład Debug lub Tools, IntelliSense wyświetli polecenia, które są w nim dostępne. Podczas gdy okno Command wysyła polecenia do IDE, okno Immediate wykonuje instrukcje Visual Basica. Wyobraźmy sobie na przykład, że napisałeś podprocedurę o nazwie CheckPrinter. Zostanie ona wykonana przez poniższą instrukcję wprowadzoną w oknie Immediate: CheckPrinter
Rozdział 7.
Usuwanie błędów
129
Wykonywanie podprocedur w oknie Immediate jest szybkim i łatwym sposobem na testowanie podprocedur, ponieważ pozwala uniknąć pisania kodu interfejsu użytkownika, który ma obsługiwać wszystkie możliwe zdarzenia. Można wywołać podprocedurę lub funkcję z różnymi parametrami, aby sprawdzić, co będzie się działo. Jeśli w procedurze tej są jakieś punkty wstrzymania, debuger zatrzyma się na nich. Podobnie można ustawiać wartości zmiennych globalnych, a następnie wywoływać procedury, które z nich korzystają. Poniższe polecenia okna Immediate ustawiają wartość zmiennej m_PrinterName i wywołują podprocedurę CheckPrinter: m_PrinterName = "LP_REMOTE" CheckPrinter
W oknach Command i Immediate można wykonywać znacznie bardziej skomplikowane polecenia. Na przykład załóżmy, że w Twoim programie znajduje się poniższa instrukcja otwierająca plik do odczytu: Dim fs As FileStream = File.OpenRead("C:\Program Files\Customer Orders\Summary" & _ datetime.Now().ToString("yymmdd") & ".dat")
Załóżmy, że program uległ awarii, ponieważ inna jego część usunęła ten plik. Aby sprawdzić, czy istnieje, można wpisać w oknie Immediate poniższy kod (wszystko w jednym wierszu). W miarę przechodzenia przez kolejne fragmenty kodu będzie można za pomocą tej instrukcji sprawdzić, kiedy plik został usunięty. ?System.IO.File.Exists("C:\Program Files\Customer Orders\Summary" DateTime.Now().ToString("yymmdd") & ".dat")
&
_
Poprzez ewaluowanie tego skomplikowanego wyrażenia w postaci łańcucha okno tworzy nazwę pliku. Następnie za pomocą polecenia System.IO.File.Exists sprawdza, czy on istnieje — wyświetla odpowiednio wartość True lub False.
Podsumowanie Mimo iż Visual Basic nie może wykonać debugowania za programistę, dostarcza narzędzi, które znacznie to ułatwiają. Dzięki opcjom dostępnym w menu Debug i oknom związanym z debugowaniem można dociec, co program robi, a także co wykonuje źle. W rozdziałach pierwszej części tej książki znajdują się opisy podstawowych obszarów środowiska programistycznego. Omówione w nich zostały okna, menu i paski narzędzi, które służą do budowy i debugowania aplikacji Visual Basic. W następnej części bardziej szczegółowo przyjrzymy się etapom budowy aplikacji. Rozdział 8. — „Wybieranie kontrolek Windows Forms” — zawiera opisy kontrolek Windows Forms, które są najczęściej używane w aplikacjach Windows Forms.
130
Część I
IDE
II
Wstęp do języka Visual Basic Rozdział 8. Wybieranie kontrolek Windows Forms Rozdział 9. Używanie kontrolek Windows Forms Rozdział 10. Formularze Windows Rozdział 11. Wybieranie kontrolek WPF Rozdział 12. Używanie kontrolek WPF Rozdział 13. Okna WPF Rozdział 14. Struktura programu i modułu Rozdział 15. Typy danych, zmienne i stałe Rozdział 16. Operatory Rozdział 17. Podprocedury i funkcje Rozdział 18. Instrukcje sterujące Rozdział 19. Obsługa błędów Rozdział 20. Kontrolki i obiekty baz danych Rozdział 21. LINQ Rozdział 22. Tworzenie niestandardowych kontrolek Rozdział 23. Operacje typu „przeciągnij i upuść” oraz schowek Rozdział 24. Funkcja Kontrola konta użytkownika
132
Część II
Wstęp do języka Visual Basic
Rozdział 8.
Wybieranie kontrolek Windows Forms
133
8
Wybieranie kontrolek Windows Forms Kontrolki są niezwykle ważną częścią każdej interaktywnej aplikacji. Dostarczają użytkownikom informacje (Label, ToolTip, TreeView, PictureBox) oraz organizują je, dzięki czemu łatwiej jest je zrozumieć (GroupBox, Panel, TabControl). Umożliwiają wprowadzanie danych ( TextBox , RichTextBox , ComboBox , MonthCalendar ), wybieranie opcji (RadioButton, CheckBox, ListBox), kontrolowanie aplikacji (Button, MenuStrip, ContextMenuStrip) oraz uzyskiwanie dostępu do obiektów znajdujących się poza programem (OpenFileDialog, SaveFileDialog, PrintDocument, PrintPreviewDialog). Niektóre kontrolki oferują wsparcie dla innych kontrolek ( ImageList , ToolTip , ContextMenuStrip , ErrorProvider). W tym rozdziale znajduje się tylko bardzo krótki opis standardowych kontrolek Windows Forms oraz kilka wskazówek pomagających w podjęciu decyzji, której z nich użyć w określonym celu. Znacznie więcej informacji na ten temat (najważniejsze własności, metody i zdarzenia) zawiera Dodatek G.
Przeglądanie kontrolek Na rysunku 8.1 przedstawiono okno Visual Basica Toolbox ze standardowymi kontrolkami Windows Forms. Ponieważ pozwala ono zarówno na dodawanie, jak i usuwanie kontrolek, na Twoim komputerze może zawierać nieco inny ich zestaw.
134
Część II
Wstęp do języka Visual Basic
Rysunek 8.1. Visual Basic udostępnia dużą liczbę standardowych kontrolek Windows Forms
W poniższej tabeli znajdują się krótkie opisy kontrolek widocznych na rysunku 8.1. Zachowano kolejność kontrolek na rysunku (zaczynając od pierwszego rzędu na górze i patrząc od lewej do prawej). Kontrolka
Przeznaczenie
Rząd 1 Pointer
Jest to narzędzie wskaźnika, a nie kontrolka. Kliknięcie go spowoduje odznaczenie wszystkich kontrolek zaznaczonych na formularzu. Później będzie można zaznaczyć nowe.
BackgroundWorker
Wykonuje zadanie asynchronicznie, a kiedy skończy, powiadamia program główny.
BindingNavigator
Tworzy interfejs użytkownika do nawigacji przez źródło danych. Udostępnia na przykład przyciski pozwalające użytkownikowi poruszanie się do przodu i wstecz przez dane, a także dodawanie i usuwanie rekordów itd.
BindingSource
Hermetyzuje źródło danych formularza i udostępnia metody nawigacji po nich.
Button
Prosty przycisk. Kiedy użytkownik go kliknie, program wykona jakąś akcję.
CheckBox
Pole do zaznaczania i czyszczenia przez użytkownika.
CheckedListBox
Lista elementów z polami wyboru, które użytkownik może zaznaczać i czyścić.
ColorDialog
Umożliwia użytkownikowi wybranie standardowego lub niestandardowego koloru.
ComboBox
Pole tekstowe z listą rozwijaną, w którym użytkownik może wprowadzić lub wybrać wartość tekstową.
ContextMenuStrip
Menu pojawiające się po kliknięciu kontrolki prawym przyciskiem myszy. Należy ustawić własność ContextMenuStrip dla dowolnej kontrolki; reszta odbędzie się automatycznie.
Rozdział 8. Kontrolka
Wybieranie kontrolek Windows Forms
135
Przeznaczenie
Rząd 2 DataGridView
Kontrolka siatki, która pozwala względnie łatwo wyświetlać duże ilości skomplikowanych danych z powiązaniami hierarchicznymi lub typu sieciowego.
DataSet
Znajdujący się w pamięci schowek danych z własnościami podobnymi do relacyjnej bazy danych. Przechowuje obiekty reprezentujące tabele, które zawierają wiersze i kolumny. Może też przedstawiać wiele koncepcji baz danych, na przykład indeksy i powiązania kluczy obcych.
DateTimePicker
Pozwala użytkownikowi wybrać datę i godzinę w jednym z kilku stylów.
DirectoryEntry
Reprezentuje węzeł w hierarchii Active Directory.
DirectorySearcher
Przeszukuje hierarchię Active Directory.
DomainUpDown
Pozwala użytkownikowi przewijać listę opcji do wyboru za pomocą klikania strzałek skierowanych w górę i w dół.
ErrorProvider
Wyświetla obok kontrolki wskaźnik błędu.
EventLog
Daje dostęp do dzienników zdarzeń systemu Windows.
FileSystemWatcher
Powiadamia aplikację o zmianach w katalogu lub pliku.
FlowLayoutPanel
Wyświetla swoje kontrolki w rzędach lub kolumnach. Na przykład ustawia je jedną obok drugiej, aż skończy się miejsce w poziomie. Wtedy przechodzi do nowego rzędu.
Rząd 3 FolderBrowserDialog
Pozwala na wybór folderu.
FontDialog
Pozwala ustawić własności pisma (nazwę czcionki, rozmiar, pogrubienie itd.).
GroupBox
Dla przejrzystości grupuje powiązane ze sobą kontrolki. Dodatkowo definiuje domyślną grupę RadioGroup dla przycisków RadioButton, które zawiera.
HelpProvider
Wyświetla pomoc dla kontrolek, które ją posiadają, jeśli użytkownik aktywuje kontrolkę i naciśnie klawisz F1.
HScrollBar
Poziomy pasek przewijania.
ImageList
Zawiera zbiór obrazów, których mogą używać inne kontrolki. Na przykład te wyświetlane przez kontrolkę TabControl na jej kartach są przechowywane w kontrolce ImageList. W swoim kodzie również można pobierać obrazy na własny użytek z listy ImageList.
Label
Wyświetla tekst, którego użytkownik nie może zmodyfikować, zaznaczyć kliknięciem ani przeciągnąć.
LinkLabel
Wyświetla etykietę, której niektóre części mogą być hiperłączami. Kiedy użytkownik kliknie hiperłącze, program wykona jakieś działania.
ListBox
Wyświetla listę elementów do wyboru dla użytkownika. W zależności od tego, jakie ustawienia własności tej kontrolki wprowadzono, będzie można zaznaczyć jeden lub kilka elementów.
ListView
Wyświetla listę elementów w jednym z czterech możliwych widoków — LargeIcon, SmallIcon, List oraz Details.
136
Część II
Wstęp do języka Visual Basic
Kontrolka
Przeznaczenie
Rząd 4 MaskedTextBox
Pole tekstowe, które wymaga, by wprowadzane w nim dane miały określony format (na przykład numer telefonu lub kod pocztowy).
MenuStrip
Reprezentuje główne menu, podmenu i elementy menu formularza.
MessageQueue
Umożliwia komunikację pomiędzy różnymi aplikacjami.
MonthCalendar
Wyświetla kalendarz, który pozwala użytkownikowi wybrać daty.
NotifyIcon
Wyświetla ikonę w zasobniku systemowym lub polu stanu.
NumericUpDown
Pozwala użytkownikowi zmienić liczbę za pomocą strzałek.
OpenFileDialog
Pozwala użytkownikowi wybrać plik do otwarcia.
PageSetupDialog
Pozwala użytkownikowi ustawić własności drukowanych stron. Na przykład umożliwia określenie podajnika papieru drukarki, rozmiaru strony, marginesów i orientacji (pionowa lub pozioma).
Panel
Kontener kontrolek. Za pomocą własności Anchor i Dock można sprawić, że kontrolka ta będzie zmieniać rozmiar — zawarte w niej kontrolki również ulegną modyfikacji. Kontener ten może automatycznie wyświetlać paski przewijania i definiować grupę RadioButton dla wszystkich przycisków RadioButton w niej się znajdujących.
PerformanceCounter
Daje dostęp do liczników wydajności systemu Windows.
Rząd 5 PictureBox
Wyświetla obraz. Dodatkowo udostępnia przydatną powierzchnię do rysowania.
PrintDialog
Wyświetla standardowe okno dialogowe drukowania. Użytkownik może wybrać w nim drukarkę, strony do wydrukowania, a także ustawić opcje drukarki.
PrintDocument
Reprezentuje dane, które mają zostać wysłane do drukarki. Program może wykorzystywać ten obiekt do drukowania i wyświetlania widoków druku.
PrintPreviewControl
Wyświetla podgląd wydruku w obrębie jednego z formularzy aplikacji.
PrintPreviewDialog
Wyświetla podgląd wydruku w standardowym oknie dialogowym.
Process
Pozwala programowi na interakcję z procesami oraz na uruchamianie i zatrzymywanie ich.
ProgressBar
Wyświetla szereg kolorowych pasków, które wskazują postęp długiej operacji.
PropertyGrid
Wyświetla informacje o obiekcie w formacie podobnym do używanego w oknie Properties w czasie projektowania.
RadioButton
Reprezentuje jedną z wzajemnie wykluczających się opcji. Kiedy użytkownik kliknie taki przycisk, Visual Basic odznaczy wszystkie pozostałe kontrolki RadioButton w tej samej grupie. Grupy są definiowane przez kontrolki GroupBox i Panel oraz klasę Form.
ReportViewer
Wyświetla raport wygenerowany lokalnie lub zdalnie na serwerze raportowym. Zdalne przetwarzanie wymaga serwera raportującego Microsoft SQL Server 2005.
Rozdział 8. Kontrolka
Wybieranie kontrolek Windows Forms
137
Przeznaczenie
Rząd 6 RichTextBox
Pole tekstowe, które obsługuje rozszerzenia tekstu wzbogaconego.
SaveFileDialog
Pozwala użytkownikowi nazwać plik, w którym program ma zapisać dane.
SerialPort
Reprezentuje port szeregowy oraz udostępnia metody do jego kontroli, odczytu z niego i zapisu do niego.
ServiceController
Reprezentuje usługę Windows i pozwala zarządzać usługami.
SplitContainer
Pozwala użytkownikowi przeciągać pionową lub poziomą linię, dzięki czemu można podzielić dostępną przestrzeń na dwie części.
Splitter
Udostępnia linię, którą użytkownik może przeciągać, aby podzielić dostępną przestrzeń pomiędzy dwie kontrolki. O aranżacji i rozmiarze tych ostatnich decydują własności Dock, kolejność na stosie i kontrolka Splitter. Kontrolka SplitterContainer automatycznie oddziela dwa kontenery, dzięki czemu zazwyczaj jest łatwiejsza w użyciu.
StatusStrip
Tworzy obszar (zazwyczaj w dolnej części formularza), w którym aplikacja może wyświetlać komunikaty o stanie, małe obrazki i inne wskaźniki stanu aplikacji.
TabControl
Wyświetla szereg kart związanych ze stroną, zawierających własne kontrolki. Użytkownik klika jedną z nich, aby wyświetlić odpowiednią stronę.
TableLayoutPanel
Wyświetla kontrolki na siatce.
TextBox
Wyświetla tekst, który użytkownik może zmienić.
Rząd 7 Timer
Okresowo wyzwala zdarzenie. Program może w odpowiedzi na to zdarzenie podjąć jakieś działania.
ToolStrip
Wyświetla zbiór przycisków, menu rozwijanych i innych narzędzi, które pozwalają użytkownikowi kontrolować aplikację.
ToolStripContainer
Kontener, który pozwala kontrolce ToolStrip zadokować się przy niektórych lub wszystkich jego krawędziach. Kontrolkę ToolStripContainer można zadokować przy formularzu, aby umożliwić użytkownikowi zadokowanie kontrolki ToolStrip przy każdej krawędzi tego formularza.
ToolTip
Wyświetli chmurkę, jeśli użytkownik najedzie na odpowiednią kontrolkę.
TrackBar
Pozwala użytkownikowi przeciągać wskaźnik wzdłuż paska w celu wybrania wartości liczbowej.
TreeView
Wyświetla dane hierarchiczne w graficznej formie, która przypomina drzewo.
VScrollBar
Pionowy pasek przewijania.
WebBrowser
Przeglądarka internetowa w kontrolce. Tę ostatnią można umieścić na formularzu i za pomocą jej metod przejść do strony internetowej. Kontrolka ta wyświetla wyniki dokładnie tak samo, jak gdyby użytkownik używał samodzielnej przeglądarki. Jednym z pożytecznych jej zastosowań jest wyświetlanie pomocy internetowej.
138
Część II
Wstęp do języka Visual Basic
Przykładowy program o nazwie UseToolStripContainer, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip), zawiera kontrolkę ToolStripContainer z dwiema kontrolkami ToolStrip. Można je przeciągać i dokować przy krawędziach kontrolki ToolStripContainer. Szczegółowe opisy kontrolek znajdują się w Dodatku G — „Komponenty i kontrolki Windows Forms”.
Wybieranie kontrolek Zapamiętanie wszystkich szczegółów dotyczących wymienionych kontrolek jest bardzo trudne. Przy takiej liczbie narzędzi do wyboru czasami ciężko zdecydować, które z nich najlepiej się nada do wykonania określonego zadania. Aby uprościć kod obsługujący błędy, powinno się wybierać kontrolki o jak najwęższym zakresie funkcjonalności, które mogą wykonać dane zadanie. Bardziej restrykcyjne zmniejszają prawdopodobieństwo podania przez użytkownika nieprawidłowych danych. Restrykcyjne kontrolki podnoszą też poziom bezpieczeństwa aplikacji. Wyświetlając listę opcji do wyboru, zamiast pozwalać użytkownikom wpisać, co im się podoba, program może chronić sam siebie przed problemami. Na przykład dwoma najczęściej spotykanymi rodzajami ataków na strony internetowe jest przepełnienie bufora, które polega na wpisaniu w polu tekstowym znacznie więcej tekstu niż potrzeba, a także SQL injection — wpisanie w polu tekstowym specjalnie spreparowanego kodu, mającego na celu zmianę działania bazy danych. Podanie zestawu opcji zamiast zezwolenia użytkownikowi na wpisanie, co chce, oddala ryzyko tych dwóch problemów. Załóżmy na przykład, że użytkownik musi wybrać jeden z rozmiarów — Small, Medium lub Large. Można pozwolić mu wprowadzić wybraną opcję do pola tekstowego, ale w takim przypadku mógłby napisać na przykład Nutria. Program musiałby sprawdzić, czy użytkownik wpisał złe słowo, a jeśli tak — wyświetlić komunikat o błędzie. Możliwe też, że trzeba by zająć cenne miejsce na ekranie w celu pokazania listy opcji do wyboru. Lepszym pomysłem byłoby użycie grupy kontrolek RadioButton lub kontrolki ComboBox z własnością DropDownStyle ustawioną na DropDownList. Dzięki temu użytkownik będzie widział listę pozycji do wyboru i mógł wybrać tylko jedną z prawidłowych opcji. Jeśli program inicjuje kontrolki jakimiś wartościami, zamiast pozostawiać je niezdefiniowane, wie, że zawsze zaznaczona jest jakaś poprawna opcja. W kolejnych podrozdziałach umieszczono opisy różnych kategorii kontrolek. Znajdują się tam też wskazówki, kiedy ich używać.
Rozdział 8.
Wybieranie kontrolek Windows Forms
139
Kontrolki kontenery Te kontrolki zawierają, grupują i pomagają ustawiać inne kontrolki. Zaliczają się do nich FlowLayoutPanel, TableLayoutPanel, GroupBox, Panel, TabControl oraz SplitContainer. Kontrolka FlowLayoutPanel ustawia znajdujące się w niej kontrolki w rzędach i kolumnach. Jeśli na przykład jej własność FlowDirection jest określona jako LeftToRight, ustawia ona swoje kontrolki w rzędach od lewej do prawej. Kiedy miejsce w jednym rzędzie się skończy, dana kontrolka przejdzie do kolejnego. Kontrolka FlowLayoutPanel jest szczególnie przydatna do tworzenia zestawów narzędzi. Warto jej używać również w sytuacjach, gdy trzeba wyświetlić jak najwięcej kontrolek jednocześnie, a ich dokładne ustawienie nie jest zbyt ważne. Kontrolka TableLayoutPanel wyświetla swoją zawartość na siatce. Wszystkie komórki w jednym wierszu mają tę samą wysokość, a wszystkie komórki w jednej kolumnie — jednakową szerokość. Natomiast kontrolka FlowLayoutPanel umieszcza kontrolki jedną obok drugiej, aż zapełni wiersz — wtedy przechodzi do nowego. Na rysunku 8.2 przedstawiono przykładowy program o nazwie LayoutPanels — można go pobrać z serwera FTP wydawnictwa Helion. Rysunek 8.2. Kontrolka FlowLayoutPanel umieszcza kontrolki blisko siebie, zaś TableLayoutPanel ustawia je na siatce
Kontrolka GroupBox znajduje zastosowanie w grupowaniu powiązanych ze sobą kontrolek lub kontrolek RadioButton w grupie (kontrolka RadioButton została opisana w podrozdziale „Wybór opcji”, który znajduje się w dalszej części tego rozdziału). Tworzy widoczne obramowanie i podpis, dzięki czemu użytkownik łatwiej domyśli się, o co chodzi w skomplikowanym formularzu. Podstawową zasadą przy projektowaniu interfejsów użytkownika jest to, że można w jednym czasie ewaluować od pięciu do dziewięciu elementów. Lista pięciu czy sześciu opcji jest do zaakceptowania, ale spis, który zawiera kilkadziesiąt pozycji, może wprowadzać zamieszanie. Jeśli umieści się opcje w kategoriach oddzielonych od siebie wizualnie za pomocą kontrolki GroupBox, można sprawić, że interfejs będzie znacznie łatwiejszy do zrozumienia. Zamiast próbować zająć się kilkudziesięcioma opcjami do wyboru naraz, użytkownik może rozbić problem na mniejsze części i zająć się każdą grupą osobno.
140
Część II
Wstęp do języka Visual Basic
Kontrolka Panel również może zawierać kontrolki RadioButton w grupie. Nie wyświetla jednak obramowania, przez co trzeba w jakiś inny sposób zaznaczyć, że dane przyciski należą do jednej grupy. Można na przykład użyć kilku kontrolek Panel w jednym rzędzie, z których każdy zawierałby kolumnę kontrolek RadioButton. Wtedy użytkownik mógłby wybrać po jednej opcji z każdej kolumny. Jedną z najważniejszych własności kontrolki Panel jest jej zdolność do automatycznego wyświetlania pasków przewijania. Jeśli własność AutoScroll kontrolki Panel jest ustawiona na wartość True, a w kontrolce tej znajdzie się więcej treści, niż może ona pomieścić, automatycznie pojawią się paski przewijania, aby użytkownik mógł zobaczyć całą jej zawartość. Jednak przewijanie w tę i z powrotem może być uciążliwe, dlatego nie jest to najlepszy wybór dla danych, które trzeba często przeglądać. Jeśli użytkownik musi ciągle przechodzić pomiędzy różnymi kontrolkami w przewijanym panelu, lepszym rozwiązaniem byłoby dla niego użycie kontrolki TabControl. Kontrolka TabControl grupuje dane na kartach. Użytkownik może szybko przechodzić pomiędzy nimi. Kontrolka ta w razie potrzeby wyświetli paski przewijania, chociaż nie jest to zbyt zgrabne. TabControl dobrze sprawdza się w przypadku danych dających podzielić się w naturalny sposób na grupy, które można umieścić na poszczególnych kartach. Nie jest jednak dobrym rozwiązaniem, gdy użytkownik musi porównywać dane znajdujące się na jednej karcie z zawartymi w innej. Kontrolka SplitContainer pozwala użytkownikowi podzielić obszar ekranu na dwa przystające do siebie regiony. Zawiera dwie kontrolki Panel, w których można umieszczać swoje własne kontrolki. Kiedy użytkownik przeciąga linię dzielącą oba panele, kontrolka odpowiednio zmienia rozmiary obu. Można ustawić własność AutoScroll tych paneli na True, aby w razie potrzeby automatycznie wyświetlały paski przewijania. Kontrolka SplitContainer okazuje się przydatna, gdy formularz jest za mały, by pomieścić wszystkie dane, które program musi wyświetlić. Dzięki niej użytkownik może powiększyć jedną część ekranu kosztem drugiej. Jest to szczególnie przydatne, gdy w celu porównania wartości z tych dwóch regionów trzeba je jednocześnie widzieć. Mimo że kontenery SplitContainer można zagnieżdżać w innych kontenerach tego samego typu, najłatwiej ich używać, kiedy oddzielają tylko dwa regiony. Duże grupy kontrolek SplitContainer, które oddzielają wiele obszarów, są zazwyczaj zatłoczone i mało czytelne. Przykładowy program o nazwie UseSplitter, który można pobrać z serwera FTP wydawnictwa Helion, dzieli swój formularz na dwa regiony za pomocą kontrolki SplitContainer. Ta ostatnia zawiera dwie kontrolki: Panel i Splitter, co w sumie daje formularz podzielony na dwa regiony. Dzięki temu, że za pomocą kontrolki Splitter nie trzeba ustawiać własności Dock ani kolejności na stosie, jak w kontrolce Splitter, kontrolka SplitContainer jest łatwiejsza w użyciu. Opisane tu kontenery pomagają odpowiednio ustawiać zawarte w nich kontrolki. Własności Anchor i Dock wszystkich kontrolek znajdujących się wewnątrz tych kontenerów działają
względem nich. Wyobraźmy sobie na przykład, że umieszczono serię przycisków z własnością Anchor = Top, Left, Right wewnątrz kontrolki SplitContainer, dzięki czemu mają one taką samą szerokość, jaką posiada zawierający je panel. Kiedy użytkownik przeciągnie linię dzielącą, przyciski automatycznie zmienią rozmiary, aby pasować do otaczającego je panelu.
Rozdział 8.
Wybieranie kontrolek Windows Forms
141
Wybór opcji Kontrolki opcji do wyboru pozwalają użytkownikowi wybrać odpowiednie wartości. Właściwie stosowane, pozwalają zminimalizować ryzyko wprowadzenia niepoprawnych danych. Dzięki temu potrzebna jest mniejsza ilość kodu obsługującego błędy. Do kontrolek tych zaliczają się: CheckBox, CheckedListBox, ComboBox, ListBox, RadioButton, DateTimePicker, MonthCalendar, DomainUpDown, NumericUpDown, TrackBar, HScrollBar oraz VScrollBar. Kontrolka CheckBox pozwala na zaznaczenie wybranej opcji bez względu na inne. Aby pozwolić na wybranie tylko jednej pozycji z kilku, należy użyć kontrolki RadioButton. Jeśli formularz wymaga więcej niż pięciu do siedmiu kontrolek CheckBox, warto rozważyć możliwość użycia w zamian kontrolki CheckedListBox. Kontrolka CheckedListBox pozwala użytkownikowi wybrać spośród kilku niezależnych od siebie opcji. Zazwyczaj ma postać listy kontrolek CheckBox z paskami przewijania, które pokazują się w razie potrzeby. Kontrolka ComboBox pozwala użytkownikowi na zdecydowanie się na jedną opcję. Jest ona szczególnie przydatna, gdy jej własność DropDownStyle jest ustawiona na DropDownList, ponieważ dzięki niej można wybrać daną pozycję z listy rozwijanej w dół. Aby umożliwić użytkownikowi zdecydowanie się na jakąś wartość lub wprowadzenie takiej, której nie ma na liście, należy ustawić własność DropDownStyle na Simple lub DropDown. Kontrolka ta spełnia podobną rolę do prostej kontrolki ListBox, ale zajmuje mniej miejsca. Kontrolka ListBox wyświetla listę opcji do wyboru. Można ją tak skonfigurować, aby użytkownik mógł zdecydować się na jedną lub więcej pozycji. Kontrolka ListBox zajmuje więcej miejsca od ComboBox, ale może być łatwiejsza w użyciu, jeśli jej lista jest bardzo długa. Oczywiście gdy użytkownik ma do wyboru zbyt wiele opcji, łatwo może przez przypadek odznaczyć wszystkie zaznaczone wcześniej pozycje i zaznaczyć kolejny element. Aby ułatwić życie osobie korzystającej z komputera, warto zastanowić się nad użyciem w zamian kontrolki CheckedListBox, która nie jest obciążona tym problemem. Kontrolka RadioButton pozwala wybrać jedną opcję ze zbioru. Na przykład trzy kontrolki tego typu mogą reprezentować opcje Small, Medium i Large. Jeśli użytkownik zaznaczy jedną z nich, pozostałe zostaną automatycznie odznaczone. Ten typ kontrolki jest przydatny, gdy lista opcji nie jest duża. Przynosi też korzyść pokazania użytkownikowi wszystkich opcji do wyboru naraz. Jeśli lista opcji jest długa, należy rozważyć wybór kontrolki ListBox lub ComboBox. Kontrolki DateTimePicker i MonthCalendar pozwalają użytkownikowi wybierać daty i godziny. Zapewniają poprawność wprowadzonych danych, dzięki czemu są z reguły lepsze od innych kontrolek, które służą do wyboru daty i godziny. Jeśli na przykład pozwoli się użytkownikowi wprowadzić datę w kontrolce TextBox w postaci liczbowej, trzeba będzie dodatkowo napisać kod, który sprawdzi, czy nie wpisał on czegoś w rodzaju „29 luty 2008”. Kontrolki DomainUpDown i NumericUpDown pozwalają przewijać listę opcji. Jeśli spis ten jest krótki, lepszym rozwiązaniem będzie wybranie kontrolki ListBox lub ComboBox. Jednak DomainUpDown i NumericUpDown zajmują bardzo mało miejsca, a więc mogą być przydatne na mocno zatło-
142
Część II
Wstęp do języka Visual Basic
czonych formularzach. Gdy użytkownik kliknie jedną ze strzałek kontrolki, będzie mógł bardzo szybko przewinąć wartości. Zatem kontrolki te mogą być przydatne w przypadku bardzo długich list opcji. Kontrolka TrackBar pozwala na wybranie wartości liczbowej za pomocą przeciąganego wskaźnika. Zajmuje ona znacznie więcej miejsca od kontrolki NumericUpDown, ale jest też bardziej intuicyjna. Ponadto wymaga sporej zręczności przy dużym przedziale dozwolonych wartości. HScrollBar i VScrollBar pozwalają użytkownikowi na wybór opcji liczbowej za pomocą przeciągania „kciuka”, podobnie jak kontrolka TrackBar. Wszystkie trzy mają nawet podobne własności. Główna różnica przejawia się w ich wyglądzie. Kontrolki ScrollBar są bardziej elastyczne, jeśli chodzi o rozmiary (TrackBar ma ustaloną wysokość w stosunku do szerokości), więc niektórym użytkownikom mogą wydawać się bardziej eleganckie. Jednak osoby korzystające z komputera są przyzwyczajone do zwykłego przewijania obszarów formularza, a więc użycie ich jako pasków do wybierania liczb może wywoływać zdziwienie.
Wprowadzanie danych Czasami kontrolki opisane w poprzednim podrozdziale okazują się niepraktyczne. Na przykład nie da się wprowadzić rozsądnie długiej historii pracy lub komentarza za pomocą ComboBox lub RadioButton. Kontrolki RichTextBox, TextBox i MaskedTextBox pozwalają na wpisywanie tekstu z niewieloma ograniczeniami. Są one najbardziej przydatne, gdy użytkownik musi wprowadzić duże ilości tekstu, który nie wymaga walidacji. Kontrolka TextBox jest mniej skomplikowana i łatwiejsza w użyciu niż RichTextBox. Dlatego jeśli nie są konieczne dodatkowe funkcje tej drugiej, można używać pierwszej z wymienionych kontrolek. Jeżeli dodatkowe funkcje (takie jak możliwość wyboru kroju pisma, wcięcia, wyrównanie akapitów, indeks górny i dolny, różne kolory, cofanie i ponawianie więcej niż jednego kroku itd.) są niezbędne, należy użyć RichTextBox. MaskedTextBox jest kontrolką TextBox, która wymaga, aby wprowadzane dane były w określonym formacie. Może na przykład wymuszać podawanie numeru telefonu następująco: 234-567-8901. Jest przydatna tylko w przypadku krótkich pól ze ściśle ustalonym formatem danych. Redukuje natomiast ryzyko wprowadzenia przez użytkownika niepoprawnych informacji.
Wyświetlanie danych Kontrolki opisane w tym podrozdziale wyświetlają dane użytkownikowi. Należą do nich kontrolki Label, DataGridView, ListView, TreeView oraz PropertyGrid. Pierwsza z nich wyświetla tekst, który użytkownik może tylko przeczytać — nie da się go ani zaznaczyć, ani zmodyfikować. Ponieważ nie można zaznaczyć tekstu, nie da się go również skopiować do schowka. Jeśli tekst ten zawiera wartość, którą użytkownik mógłby chcieć skopiować i wkleić gdzieś indziej (na przykład numer seryjny, numer telefonu, adres e-mail, adres URL itd.), można użyć kontrolki TextBox z własnością ReadOnly ustawioną na True.
Rozdział 8.
Wybieranie kontrolek Windows Forms
143
Kontrolka DataGridView służy do wyświetlania danych tabelarycznych. Potrafi także wyświetlać po kilka tabel połączonych powiązaniami typu element główny-szczegół, które użytkownik może łatwo i szybko przeszukiwać. Istnieje również możliwość takiego skonfigurowania tej kontrolki, by można było aktualizować znajdujące się w niej dane. Kontrolka ListView wyświetla dane, które naturalnie wyglądają jak zbiór ikon lub lista wartości z kolumnami zawierającymi dodatkowe szczegóły. Można posortować te dane według kolumny z elementami lub szczegółami, ale wymaga to trochę pracy. Kontrolka TreeView wyświetla dane hierarchiczne w formie drzewa — podobnego do tego, które zawiera katalogi w Explorerze Windows. Można pozwolić użytkownikowi na edytowanie etykiet jego węzłów. Kontrolka PropertyGrid wyświetla informacje o obiekcie w formacie podobnym do używanego przez okno Properties w trybie projektowania. Pozwala ona użytkownikowi zorganizować własności alfabetycznie lub według kategorii. Umożliwia też edytowanie ich wartości. Na rysunku 8.3 przedstawiono przykładowy program o nazwie EmployeePropertyGrid (można go pobrać z serwera FTP wydawnictwa Helion), który wyświetla informacje o obiekcie Employee w kontrolce PropertyGrid. Rysunek 8.3. Kontrolka PropertyGrid wyświetla własności obiektu
Informowanie użytkownika W tym podrozdziale opiszę kontrolki dostarczające informacje użytkownikowi. Zaliczają się do nich ToolTip, HelpProvider, ErrorProvider, NotifyIcon, StatusStrip oraz ProgressBar. Ich głównym celem jest poinformowanie użytkownika, co się dzieje, bez zabraniania mu kontynuowania wykonywania innych czynności. Na przykład kontrolka ErrorProvider oznacza pole jako niepoprawne, ale nie blokuje wprowadzania danych w innych miejscach. ToolTip wyświetla krótką informację na temat przeznaczenia kontrolki, na którą użytkownik najechał kursorem. HelpProvider dostarcza bardziej szczegółowe informacje o przezna-
czeniu danej kontrolki, kiedy użytkownik aktywuje ją i naciśnie klawisz F1. Wysokiej jakości aplikacje dostarczają zarówno chmurki (ang. tooltip), jak i pomoc w przycisku F1 dla każdej kontrolki, której funkcja może nie być oczywista. Opcje te nie są zbyt natrętne
144
Część II
Wstęp do języka Visual Basic
— i pojawiają się tylko wtedy, gdy użytkownik ich potrzebuje. Dlatego lepiej jest przesadzić ze zbyt dużą ilością pomocy, niż dostarczyć jej za mało. ErrorProvider oznacza kontrolki zawierające niepoprawne dane. Lepiej jest używać kontrolek wyboru opcji, które nie pozwalają na wprowadzanie błędnych informacji, ta jednak jest przydatna, kiedy okazuje się to niemożliwe.
Kontrolka NotifyIcon wyświetla niewielką ikonę w obszarze powiadomień paska zadań, która informuje użytkownika o stanie aplikacji. Jest szczególnie przydatna w przypadku programów działających w tle, którym nie trzeba ciągle poświęcać uwagi. Jeśli aplikacja wymaga natychmiastowej reakcji ze strony użytkownika, powinna wyświetlać okno dialogowe lub ramkę z komunikatem, zamiast zdawać się na kontrolkę NotifyIcon. Obszar powiadomień paska zadań, zwany również zasobnikiem systemu Windows, to niewielkie miejsce na pasku zadań. Po prawej stronie wyświetla godzinę i datę oraz ikony, które oznaczają status różnych działających aplikacji. Kontrolka StatusStrip wyświetla obszar (zazwyczaj w dolnej części formularza), w którym program może podać użytkownikowi pewne informacje na temat swojego stanu. Może on mieć postać niewielkiej ikony lub krótkiego komunikatu tekstowego. Przekazuje znacznie więcej informacji niż kontrolka NotifyIcon, ale jest widoczny tylko wtedy, gdy widać formularz. Kontrolka ProgressBar pokazuje, jaka część długiego zadania została ukończona. Zazwyczaj zadanie to jest wykonywane synchronicznie, przez co użytkownik nie ma nic więcej do roboty, jak tylko wpatrywać się w pasek postępu. Kontrolka ProgressBar pozwala zorientować się, że operacja nie utknęła w miejscu.
Inicjowanie akcji Kontrolki każdego rodzaju reagują na zdarzenia, a więc każda z nich może inicjować akcję. Niemniej jednak użytkownicy oczekują wykonywania różnych czynności tylko od niektórych kontrolek. Na przykład naciśnięcie przycisku ma włączać jakąś akcję, ale kliknięcie etykiety lub pola wyboru nie powinno uruchamiać długich procesów. Aby uniknąć zamieszania, należy do uruchamiania akcji korzystać tylko z tych kontrolek, które są w tym celu używane najczęściej. Należą do nich: Button, MenuStrip, ContextMenuStrip, ToolStrip, LinkLabel, TrackBar, HScrollBar, VScrollBar oraz Timer. Wymienione kontrolki — z wyjątkiem Timer — pozwalają użytkownikowi zainicjować akcję. Wszystkie te kontrolki komunikują się z programem za pomocą procedur obsługi zdarzeń. Na przykład procedura obsługi zdarzenia Click kontrolki Button w normalnej sytuacji zmusza aplikację do wykonania jakichś czynności, kiedy użytkownik kliknie przycisk. Inne kontrolki również dysponują zdarzeniami, które mogą inicjować akcje. Na przykład CheckBox udostępnia zdarzenia CheckChanged i Click, których można użyć do wykonania jakichś działań. Jeśli przechwyci się odpowiednie zdarzenia, będzie można użyć prawie każdej kontrolki do zainicjowania określonej akcji. Ponieważ głównym przeznaczeniem tych narzędzi nie jest wykonywanie kodu, nie zostały one wymienione w tym podrozdziale.
Rozdział 8.
Wybieranie kontrolek Windows Forms
145
Kontrolka Button pozwala użytkownikowi zmusić program do wykonania wybranej funkcji. Przycisk jest zazwyczaj cały czas widoczny na formularzu, a więc najczęściej przydaje się, gdy trzeba często wykonywać jakąś akcję lub jest ona częścią tego, co stanowi główne zadanie aplikacji. Dla rzadziej wykonywanych akcji należy używać kontrolek MenuStrip i ContextMenuStrip. Elementy w kontrolce MenuStrip również pozwalają użytkownikowi zmusić program do wykonania wybranych akcji. W jej przypadku konieczne jest wykonanie większej liczby czynności, niż gdy używa się przycisku, ponieważ trzeba otworzyć menu, zaleźć interesującą opcję i kliknąć ją. Należy jednak pamiętać o tym, że menu zajmują mniej miejsca na formularzu niż przyciski. Do często używanych opcji znajdujących się w menu można także przypisać skróty klawiszowe (typu F5 lub Ctrl+S) — ich uruchamianie jest nawet łatwiejsze niż używanie przycisków. ContextMenuStrip posiada takie same wady i zalety, jakie ma kontrolka MenuStrip. Jest ona jednak dostępna tylko z danych kontrolek na formularzu, a więc przydaje się dla poleceń mających zastosowanie jedynie w określonych warunkach. Na przykład Zapisz ma zastosowanie do wszystkich danych załadowanych przez program, a więc powinno znajdować się w kontrolce MenuStrip. Z polecenia usuwającego określony obiekt z rysunku da się skorzystać tylko w stosunku do tego obiektu. Dzięki umieszczeniu tej opcji w kontrolce ContextMenuStrip, która jest związana z obiektem, pozostaje ona ukryta, gdy użytkownik pracuje nad czymś innym. Dodatkowo sprawia, że związek pomiędzy akcją (usunięcie) a obiektem jest oczywisty — zarówno dla programu, jak i użytkownika.
Kontrolka ToolStrip łączy niektóre najlepsze cechy menu i przycisków. Wyświetla szereg przycisków, dzięki czemu można ich użyć bez wchodzenia do menu. Są one małe, a znajdują się w górnej części formularza, więc nie zajmują tyle miejsca co duże przyciski. Zwykle przyciski lub przyciski kontrolki ToolStrip, które znajdują się na formularzu, reprezentują często używane polecenia z menu. Te ostatnie można uruchamiać za pomocą skrótów klawiszowych lub przycisków, jeśli nie pamięta się skrótów. LinkLabel wyświetla tekst — podobnie jak kontrolka Label. Dodatkowo pokazuje niebieski
tekst z podkreśleniem, specjalny kursor, kiedy użytkownik najeżdża na niego, a także uruchamia zdarzenie, gdy kliknie się ten tekst. Dzięki temu z kontrolki tej warto korzystać, gdy kliknięcie tekstu ma sprawiać, że wykonana zostanie jakaś akcja. Na przykład na stronie internetowej kliknięcie odnośnika spowoduje przejście do wskazywanej przez niego strony. TrackBar, HScrollBar i VScrollBar pozwalają użytkownikowi wybrać wartość liczbową za pomocą przeciągania rączki. Jak napisałem wcześniej w podrozdziale „Wybór opcji”, dzięki tym kontrolkom można wybierać wartości liczbowe. Istnieje też możliwość używania ich do interaktywnego wykonywania pewnych akcji. Na przykład z pasków przewijania często korzysta się do przewijania części formularza. Bardziej ogólnie: służą do zmuszania programu do podejmowania działań na podstawie jakiejś nowej wartości. Na przykład za pomocą paska przewijania można pozwolić użytkownikowi wybrać komponenty czerwieni, zieleni i niebieskiego dla obrazu. W chwili zmiany przez niego wartości paska przewijania program aktualizuje kolory grafiki.
Kontrolka Timer wyzwala określoną akcję w równych odstępach czasu. Kiedy uruchamia ona swoje zdarzenie, program wykonuje akcję.
146
Część II
Wstęp do języka Visual Basic
Wyświetlanie grafiki Kontrolki opisane w tym podrozdziale służą do wyświetlania obrazów graficznych na ekranie lub prezentacji ich na wydruku. Są to Form, PictureBox, PrintPreviewControl, PrintDocument oraz PrintPreviewDialog. Kontrolka Form (która także może wyświetlać obrazy graficzne) umożliwia rysowanie, ale z reguły lepiej jest rysować w PictureBox niż w samym formularzu. Dzięki temu łatwiej jest przenieść grafikę przy zmianie projektu formularza. Jeśli na przykład dojdziesz do wniosku, że obraz może być za duży, możesz z łatwością przenieść kontrolkę PictureBox do przewijanej kontrolki Panel. Znacznie trudniej byłoby przepisać kod, który miałby przesunąć rysunek z kontrolki Form do PictureBox. Kontrolka PrintPreviewControl wyświetla podgląd wydruku obiektu PrintDocument. Program reaguje na zdarzenia wyzwalane przez obiekt PrintDocument. PrintPreviewDocument wyświetla wyniki w kontrolce znajdującej się na jednym z formularzy programu. PrintPreviewDialog wyświetla obrazy z obiektu PrintDocument — podobnie jak kontrolka PrintPreviewControl — ale posiada własne okno dialogowe. Jeśli nie ma konieczności zaprojektowania w specjalny sposób podglądu wydruku, łatwiej jest użyć PrintPreviewDialog, niż budować własne okno dialogowe za pomocą kontrolki PrintPreviewControl. PrintPreviewDialog udostępnia wiele funkcji, które pozwalają użytkownikowi na powięk-
szenie, przewijanie i zmienianie stron podglądanego dokumentu. Samodzielne zaimplementowanie ich wszystkich wymagałoby bardzo dużo pracy.
Wyświetlanie okien dialogowych Visual Basic oferuje szeroki wybór okien dialogowych, które pozwalają użytkownikowi na dokonywanie standardowych wyborów. Użycie właściwego z nich jest zazwyczaj łatwe, ponieważ każde ma bardzo konkretne przeznaczenie. Poniższa tabela zawiera listę tych okien dialogowych i krótki opis ich funkcji. Okno dialogowe
Przeznaczenie
ColorDialog
Wybór koloru.
FolderBrowserDialog
Wybór folderu (katalogu).
FontDialog
Wybór czcionki.
OpenFileDialog
Wybór pliku do otwarcia.
PageSetupDialog
Ustawienia strony do druku.
PrintDialog
Wydruk dokumentu.
PrintPreviewDialog
Wyświetlenie podglądu wydruku.
SaveFileDialog
Wybór pliku do zapisania.
Okna te demonstruje program UseDialogs, który można pobrać z serwera FTP wydawnictwa Helion.
Rozdział 8.
Wybieranie kontrolek Windows Forms
147
Wspieranie innych kontrolek Wiele kontrolek Visual Basica wymaga wsparcia ze strony innych tego typu narzędzi. Dwie kontrolki najczęściej używane przez inne to ImageList i PrintDocument. Zaliczają się do nich również DataConnector i DataNavigator. ImageList przechowuje obrazy, które wyświetlają inne kontrolki. Kod programu może także pobierać z niej obrazy i używać ich w dowolny sposób. PrintDocument wspomaga drukowanie i podglądanie wydruku. Generuje grafikę wysyłaną do drukarki albo kontrolki PrintPreviewDialog lub PrintPreviewControl. DataConnector tworzy łącze pomiędzy źródłem danych a kontrolkami z nią związanymi. Za pomocą jej metod program może nawigować po danych, sortować je, filtrować oraz aktualizować. Ponadto kontrolka ta odpowiednio aktualizuje związane z nią kontrolki.
Kontrolka DataNavigator udostępnia metody do nawigowania po źródłach danych, takich jak DataConnector.
Kontrolki niestandardowe W Visual Basicu jest dostępnych mnóstwo różnych kontrolek, które są gotowe do użycia. Nie są to jednak wszystkie te, z których można korzystać. Poprzez kliknięcie prawym przyciskiem myszy w oknie Toolbox i wybranie opcji Choose Items można uzyskać dostęp do ogromnej listy dostępnych w systemie komponentów .NET i COM. Można także używać kontrolek utworzonych przez inne firmy, które oferują je w sprzedaży lub udostępniają bezpłatnie w sieci. Wiele z nich wykonuje specjalistyczne zadania, na przykład generuje kody kreskowe, tworzy kształty formularzy, zniekształca obrazy czy tworzy specjalne efekty graficzne. Są też kontrolki rozszerzające funkcjonalność standardowych tego typu narzędzi. Istnieje kilka kontrolek rysujących dwu- i trójwymiarowe wykresy i grafy. Inne oferują bardziej rozbudowane usługi raportowania od narzędzi dostępnych w Visual Studio. Gdy wpisze się frazę windows forms controls w dowolnej większej wyszukiwarce internetowej, będzie można znaleźć mnóstwo witryn internetowych, które oferują kontrolki do pobrania bezpłatnie lub za pewną kwotę. Oto kilka adresów, które warto odwiedzić:
MVPs.org (www.mvps.org) — witryna prowadząca do zasobów udostępnianych przez ludzi związanych z programem Microsoftu Most Valuable Professional (MVP). Projekt Common Controls Replacement Project (ccrp.mvps.org) oferuje kontrolki duplikujące i rozszerzające standardowe kontrolki programuVisual Basic 6. Strona ta nie jest już rozwijana, ale niektóre ze starych kontrolek szóstej wersji Visual Basica mogą stać się inspiracją do budowy własnych nowych narzędzi. Witryna MVPs.org jest ponadto dobrym ogólnym źródłem danych.
148
Część II
Wstęp do języka Visual Basic
Windows Forms .NET (windowsclient.net) — oficjalna strona Microsoftu, założona dla społeczności zainteresowanej Windows Forms .NET.
ASP.NET (www.asp.net) — oficjalna społeczność ASP.NET Microsoftu.
Wymienione wyżej witryny są dobre do rozpoczęcia poszukiwań, ale nie zamykają listy. Kontrolki do pobrania można znaleźć w setkach (o ile nie tysiącach) stron internetowych. Gdy pobiera się niestandardowe kontrolki i produkty, zawsze należy zachowywać pewną powściągliwość. Dodanie każdego tego typu narzędzia do projektu powoduje uzależnienie tego ostatniego od niego. Gdy przenosi się efekt pracy do nowszej wersji Visual Basica, należy się upewnić, że tam również dana kontrolka będzie działała. Jeśli powstanie nowa wersja tego narzędzia, trzeba będzie sprawdzić, czy będzie działała w używanej edycji programu. Jeśli nie, można utknąć ze starą — już niewspieraną wersją kontrolki. Problem staje się jeszcze poważniejszy, gdy kontrolki i narzędzia współpracują ze sobą. Jeśli zmieni się cokolwiek, konieczne będzie znalezienie zestawu wersji wszystkich narzędzi, które będą ze sobą zgodne. Osobiście staram się używać niestandardowych kontrolek jak najrzadziej, ponieważ przy pisaniu tej książki nie mogę wychodzić z założenia, że czytelnik posiada określone narzędzia innych firm. Korzystam z WinZipa (www.WinZip.com) i Internet Download Managera (www.InternetDownloadManager.com) poza projektami, ale nie wewnątrz nich. Kontrolek niestandardowych należy używać wtedy, gdy pozwalają zaoszczędzić dużo czasu. Jednak przed ich zastosowaniem warto przemyśleć, ile czasu zajęłoby poradzenie sobie bez nich, a ile zastąpienie ich innymi w razie potrzeby migracji do nowszej wersji Visual Basica.
Podsumowanie Kontrolki stanowią główne połączenie pomiędzy użytkownikiem a aplikacją. Umożliwiają programowi wysyłanie danych do użytkownika, a użytkownikowi kontrolowanie programu. Kontrolki są wszędzie, w praktycznie każdej aplikacji działającej w systemie Windows. Tylko niewielka liczba programów uruchomionych w tle może się bez nich obyć. W tym rozdziale krótko opisałem przeznaczenie standardowych kontrolek Visual Basica. Zamieściłem tu też wskazówki dotyczące odpowiednich z nich w przypadku wykonywania różnych zadań. Bardziej szczegółowy opis tych kontrolek znajduje się w Dodatku G.
Rozdział 8.
Wybieranie kontrolek Windows Forms
149
Nawet gdy wie się wszystko o kontrolkach, nie ma się gwarancji, że stworzony interfejs użytkownika będzie odpowiedni. Projektowanie intuicyjnych i łatwych w użyciu interfejsów jest sztuką. Dobry projekt umożliwia użytkownikowi wykonywanie pracy w naturalny sposób, przy czym zminimalizowana jest ilość marnowanego czasu. Źle zaprojektowany interfejs może przeszkadzać w wykonywaniu czynności, a nawet zamienić najprostsze z nich w walkę z programem. Informacje na temat budowy użytecznych aplikacji można znaleźć w książkach poświęconych projektowaniu interfejsów. Objaśniono w nich typowe problemy i rozwiązania związane z tym ostatnimi. Wiele też można nauczyć się poprzez badanie istniejących już aplikacji, które są uznane za dobre. Przyjrzyj się układowi ich formularzy i okien dialogowych. Nie należy podkradać zastosowanych tam rozwiązań, ale spróbować zrozumieć, dlaczego kontrolki zostały w nich poukładane właśnie w taki sposób. Przyjrzyj się aplikacjom, które szczególnie lubisz, a do tego są wyjątkowo łatwe w użyciu. Porównaj je z programami, które uważasz za niezgrabne i mało przejrzyste. Ten rozdział stanowi wprowadzenie do kontrolek Windows Forms. W rozdziale 9. — „Używanie kontrolek Windows Forms” — znajdziesz bardziej szczegółowe objaśnienie sposobów ich wykorzystywania. Zamieszczone tam zostaną opisy metod dodawania kontrolek do formularza w trakcie projektowania i działania programu, a także wskazówki, jak używać ich własności, metod i zdarzeń.
150
Część II
Wstęp do języka Visual Basic
9
Używanie kontrolek Windows Forms Kontrolka to obiekt programistyczny, który posiada graficzny składnik. Rezyduje ona na formularzu i reaguje na działania użytkownika — dostarcza mu informacje oraz czasami pozwala sobą manipulować. Kontrolkami są pola tekstowe, etykiety, przyciski, listy rozwijane, elementy menu, paski narzędzi i prawie wszystko, co widać i z czym można w jakiś sposób współpracować w aplikacji Windows. Komponent jest podobny do kontrolki — tyle że nie widać go w czasie działania programu. Kiedy komponent zostanie dodany do formularza w trakcie projektowania, pojawi się pod nim w zasobniku komponentów. Po zaznaczeniu komponentu w oknie Properties można przejrzeć i zmodyfikować jego własności. W czasie działania programu pozostaje on niewidoczny dla użytkownika, chociaż może wyświetlać widoczne obiekty, jak menu, okna dialogowe czy ikony stanu. W tym rozdziale opisane zostaną kontrolki i komponenty w ujęciu ogólnym. Omówię różne ich rodzaje. Nauczysz się używać ich w programie — zarówno w czasie jego działania, jak i projektowania, aby dostarczały użytkownikowi informacje i umożliwiały mu sprawowanie kontroli nad nimi. Znajdzie się tu ponadto ogólne objaśnienie, jak działają własności i metody zdarzenia kontrolek. Wymienione zostaną też niektóre najbardziej przydatne z nich, które są dostępne w klasie Control. Inne kontrolki utworzone na bazie tej klasy dziedziczą sposoby używania tych własności, metod i zdarzeń, chyba że zostaną one jawnie przesłonięte. W Dodatku G — „Komponenty i kontrolki Windows Forms” — zostaną opisane bardziej szczegółowo niektóre najczęściej używane kontrolki.
Kontrolki i komponenty Większość kontrolek ma postać graficzną. Przyciski, pola tekstowe i etykiety prezentują użytkownikowi informacje w takiej właśnie formie. Wyświetlają dane i pozwalają na wyzwalanie akcji programu. Niektóre kontrolki (takie jak kontrolki siatki, widoku drzewa i kalendarza) są bardzo potężne i dostarczają bogaty zestaw narzędzi do interakcji z użytkownikiem.
152
Część II
Wstęp do języka Visual Basic
Natomiast komponenty w czasie projektowania są reprezentowane przez ikony, nie widać ich zaś, gdy program działa. Mogą wyświetlać jakieś inne obiekty (jak okna dialogowe, menu czy wskaźniki graficzne), ale same są ukryte przed wzrokiem użytkownika. Wiele komponentów prezentuje użytkownikowi informacje. Inne dostarczają dane wymagane przez kontrolki graficzne. Na przykład program może wykorzystywać komponenty połączenia, adaptera danych i zbioru danych do określania informacji, które mają zostać pobrane z bazy danych. Następnie kontrolka siatki może zaprezentować te dane użytkownikowi. Ponieważ obiekty połączenia, adaptera danych i zbioru danych są komponentami, można zdefiniować ich własności w czasie projektowania bez pisania kodu. Na rysunku 9.1 przedstawiono formularz w trakcie projektowania, który zawiera kilka komponentów. Są one widoczne w znajdującym się pod formularzem zasobniku komponentów.
Rysunek 9.1. Niektóre komponenty dostarczają danych dla kontrolek graficznych
Ten formularz zawiera cztery komponenty. Timer1 uruchamia regularnie zdarzenie, dzięki czemu program może w określonych odstępach czasu wykonywać określone czynności. ErrorProvider1 wyświetla ikonę błędu i komunikaty dla wybranych kontrolek na formularzu, takich jak TextBox. Komponent BackgroundWorker1 wykonuje zadania asynchronicznie, podczas gdy program główny działa niezależnie od niego. ImageList1 zawiera zbiór obrazów do użytku przez inną kontrolkę. Komponent ten jest zazwyczaj związany z jakąś kontrolką typu Button, ListView czy TreeView i dostarcza dla niej grafiki. Na przykład kontrolka ListView może używać obrazów z komponentu ImageList do wyświetlania ikon dla zawartych w niej elementów.
Rozdział 9.
Używanie kontrolek Windows Forms
153
Jeśli pominąć brak graficznej reprezentacji komponentów na formularzu, używanie ich nie różni się znacząco od stosowania kontrolek. Własności komponentów ustawia się w oknie Properties, a procedury obsługi i kod wywołujący ich metody definiuje się w edytorze kodu. Reszta tego rozdziału koncentruje się na kontrolkach, ale wszystko, co zostało napisane, ma zastosowanie również do komponentów.
Tworzenie kontrolek Najczęściej kontrolki dodaje się do formularza w formie graficznej w czasie projektowania. Czasami jednak może być konieczne umieszczenie ich podczas działania programu. Dzięki temu można zmienić wygląd aplikacji w czasie jej działania — w odpowiedzi na jakieś potrzeby lub polecenia wydawane przez użytkownika. Załóżmy na przykład, że aplikacja wymaga od jednego do stu pól tekstowych. Przez większość czasu wystarcza tylko kilka, ale w zależności od aktywności użytkownika może potrzebować ich znacznie więcej. Można utworzyć sto pól tekstowych i ukryć te, które nie są aktualnie potrzebne, ale oznaczałoby to marnowanie pamięci przez większość czasu. Jeśli utworzy się tylko tyle kontrolek, ile jest aktualnie potrzebnych, będzie można w większości przypadków zmniejszyć zużycie pamięci. W kolejnych podrozdziałach wyjaśnię, jak tworzyć kontrolki — zarówno w czasie projektowania, jak i działania programu.
Tworzenie kontrolek w czasie projektowania Aby utworzyć kontrolkę w czasie projektowania, kliknij dwukrotnie formularz w oknie Solution Explorer w celu otwarcia go w edytorze formularzy. Wybierz kontrolkę w oknie Toolbox. Jeśli używana karta tego okna jest w trybie List View, widoczne są w niej nazwy kontrolek. Jeżeli karta wyświetla tylko ikony, nazwę kontrolki można sprawdzić w chmurce, która pojawia się po najechaniu na nią kursorem. Na przykład na rysunku 9.2 przedstawiono chmurkę z informacjami o komponencie HelpProvider. Wybraną kontrolkę da się wstawić do formularza na kilka sposobów. Można ją dwukrotnie kliknąć, aby została wstawiona w domyślnych: rozmiarze i lokalizacji. Po dodaniu kontrolki do formularza IDE odznacza to narzędzie i zaznacza wskaźnik (lewy górny róg na aktualnej karcie okna Toolbox). Po drugie, można zaznaczyć wybrane narzędzie w oknie Toolbox, a następnie kliknąć je i przeciągnąć w wybrane miejsce na formularzu. Jeśli klikniesz na formularzu bez przeciągania, IDE wstawi tę kontrolkę w tym miejscu w domyślnym rozmiarze. Po dodaniu kontrolki do formularza IDE odznacza to narzędzie i zaznacza wskaźnik. Po trzecie, jeśli kliknie się i przeciągnie narzędzie z okna Toolbox na formularz, Visual Basic utworzy nową kontrolkę w domyślnym rozmiarze — w miejscu, w którym została upuszczona.
154
Część II
Wstęp do języka Visual Basic
Rysunek 9.2. Niektóre komponenty dostarczają danych dla kontrolek graficznych
Po czwarte, jeśli planujesz wstawić kilka egzemplarzy tego samego typu kontrolki, naciśnij klawisz Ctrl i — nie puszczając go — kliknij wybrane narzędzie. Dzięki temu pozostanie ono zaznaczone nawet po dodaniu tej kontrolki do formularza. Jeśli kontrolka zostanie kliknięta i przeciągnięta na formularz, IDE utworzy ją w miejscu, w którym została upuszczona. Zachowa ona zaznaczenie narzędzia, dzięki czemu będzie można natychmiast utworzyć kolejną kontrolkę. Kiedy klikniesz na formularzu, ale nie przeciągniesz myszą, IDE wstawi kontrolkę w tym miejscu w domyślnym rozmiarze. Po zakończeniu dodawania egzemplarzy tego typu kontrolki kliknij wskaźnik, aby zatrzymać dodawanie nowych kontrolek.
Wstawianie kontrolek do kontenerów Niektóre kontrolki mogą zawierać inne kontrolki — są to na przykład GroupBox i Panel. Kontrolkę można umieścić w kontenerze na kilka sposobów. Zaznaczenie kontenera i dwukrotne kliknięcie kontrolki w oknie Toolbox powoduje wstawienie jej do tego kontenera. Kiedy zaznaczysz jakieś narzędzie i przeciągniesz je do kontenera, Visual Basic umieści je w tym kontenerze — bez względu na to, czy jest on zaznaczony, czy nie. Można też kliknąć narzędzie i przeciągnąć je do kontenera z okna Toolbox albo kliknąć i przeciągnąć kontrolki z jednej części formularza na ten kontener. Jeśli podczas upuszczania kontrolek zostanie przytrzymany klawisz Ctrl, Visual Basic zrobi ich kopie, zamiast je przenieść. Dwa najczęściej popełniane przez programistów błędy polegają na umieszczaniu kontrolki nad kontenerem, do którego mają zostać wstawione — i odwrotnie. Na przykład można w różnych kontrolkach Panel umieścić grupy kontrolek, a następnie ukrywać i wyświetlać te panele, aby prezentować odmienne zestawy kontrolek w różnych sytuacjach. Jeśli kontrolka znajduje się nad panelem, a nie w nim, pozostaje widoczna, nawet gdy panel jest ukryty. Aby sprawdzić, czy kontrolka znajduje się w kontenerze, należy lekko nim poruszyć. Jeśli kontrolka również się poruszy, oznacza to, że jest w tym kontenerze. Jeżeli się nie poruszy, znajduje się poza nim.
Rozdział 9.
Używanie kontrolek Windows Forms
155
Tworzenie kontrolek w czasie działania programu Normalnie kontrolki tworzy się interaktywnie w czasie projektowania. Czasami jednak wygodniej jest robić to podczas działania programu. Może na przykład nie być wiadomo, ile danych będzie trzeba wyświetlić, dopóki aplikacja nie zacznie działać. Czasami nieznaną ilość danych można zaprezentować w liście, siatce lub innej kontrolce, która przechowuje zmienną liczbę elementów. Zdarza się jednak, że informacje te muszą być prezentowane w etykietach lub polach tekstowych. W takich przypadkach konieczne jest tworzenie nowych kontrolek w czasie działania programu. Poniższy kod demonstruje przykładowy sposób tworzenia przez program nowej kontrolki Label. Najpierw została zadeklarowana zmienna typu Label i zainicjowana słowem kluczowym New. Etykieta ta jest pozycjonowana za pomocą metody SetBounds, a jej własność Text ustawiono na Witaj, świecie!. Następnie etykieta ta zostaje dodana do bieżącej kolekcji Controls formularza. Dim lbl As New Label lbl.SetBounds(10, 50, 100, 25) lbl.Text = "Witaj, świecie!" Me.Controls.Add(lbl)
Aby umieścić kontrolkę w kontenerze innym niż formularz, trzeba wstawić ją do kolekcji Controls tego kontenera. W celu na przykład wstawienia opisanej wcześniej kontrolki Label do kontenera GroupBox o nazwie grpLabels należałoby użyć instrukcji grpLabels.Controls.Add(lbl). Etykieta zazwyczaj tylko wyświetla jakiś komunikat, a więc nie trzeba przechwytywać jej zdarzeń. Inne kontrolki, jak przyciski i paski przewijania, nie są zbyt przydatne, jeśli program nie może reagować na ich zdarzenia. Są dwie metody przechwytywania zdarzeń nowej kontrolki. Po pierwsze, można zadeklarować jej zmienną za pomocą słowa kluczowego WithEvents. Następnie otworzyć formularz w edytorze kodu, wybrać nazwę tej zmiennej ze znajdującej się po lewej stronie listy rozwijanej i zdarzenie z listy rozwijanej znajdującej się po prawej stronie, aby utworzyć dla niej procedurę obsługi zdarzeń. Poniższy kod demonstruje tę metodę. Deklaruje on zmienną klasową btnHi za pomocą słowa kluczowego WithEvents. Kliknięcie przycisku btnMakeHiButton spowoduje, że jego procedura obsługi zdarzeń zainicjuje tę zmienną. Ustawi położenie oraz tekst kontrolki i doda ją do kolekcji Controls formularza. Kiedy użytkownik kliknie ten przycisk, procedura obsługi zdarzeń btnHi_Click zostanie wykonana, po czym wyświetli komunikat. ' Deklaracja przycisku btnHi przy użyciu słowa kluczowego WithEvents. Private WithEvents btnHi As Button ' Utworzenie nowego przycisku btnHi. Private Sub btnMakeHiButton_Click() Handles btnMakeHiButton.Click btnHi = New Button btnHi.SetBounds(96, 50, 75, 23) btnHi.Text = "Cześć" Me.Controls.Add(btnHi)
156
Część II
Wstęp do języka Visual Basic
End Sub ' Użytkownik kliknął przycisk btnHi. Private Sub btnHi_Click() Handles btnHi.Click MessageBox.Show("Cześć") End Sub
Ta metoda jest przydatna, gdy wiadomo, ile kontrolek jest potrzebnych. Dzięki temu dla nich wszystkich można zdefiniować zmienne za pomocą słowa kluczowego WithEvents. Jeśli nie wiadomo, ile trzeba utworzyć kontrolek, metoda ta okazuje się jednak niepraktyczna. Wyobraźmy sobie na przykład, że należy utworzyć przycisk dla każdego pliku znajdującego się w jakimś katalogu. Kliknięcie tego przycisku przez użytkownika powinno powodować otwarcie odpowiedniego pliku. Jeśli nie ma informacji, ile plików będzie znajdować się w katalogu, nie wiadomo, ile zmiennych utworzyć. Jednym z rozwiązań tego problemu jest użycie instrukcji AddHandler, która dodaje procedury obsługi zdarzeń do nowych kontrolek. Poniższy kod demonstruje tę metodę. Kliknięcie przycisku btnMakeHelloButton spowoduje, że jego procedura obsługi zdarzeń Click utworzy nowy obiekt Button, który zapisze w zmiennej zadeklarowanej lokalnie. Procedura ta ustawi położenie i tekst przycisku oraz wstawi go do kolekcji Controls formularza, jak wcześniej. Następnie program wykorzysta instrukcję AddHandler do przerobienia podprocedury Hello_Click na procedurę obsługi zdarzenia Click przycisku. Kiedy użytkownik kliknie ten nowy przycisk, podprocedura Hello_Click wyświetli komunikat. ' Utworzenie nowego przycisku Hello. Private Sub btnMakeHelloButton_Click() Handles btnMakeHelloButton.Click ' Utworzenie przycisku. Dim btnHello As New Button btnHello.SetBounds(184, 50, 75, 23) btnHello.Text = "Dzień dobry" Me.Controls.Add(btnHello) ' Dodanie procedury obsługi zdarzenia Click do przycisku. AddHandler btnHello.Click, AddressOf Hello_Click End Sub ' Użytkownik kliknął przycisk Hello. Private Sub Hello_Click() MessageBox.Show("Dzień dobry") End Sub
Tej samej procedury można użyć do obsługi zdarzeń więcej niż jednego przycisku. W takim przypadku kod może przekonwertować parametr nadawcy na obiekt Button, po czym na podstawie własności przycisku Name, Text i innych określić, który przycisk został naciśnięty. Aby wyrzucić kontrolkę z formularza, wystarczy usunąć ją z jego kolekcji Controls. W celu zwolnienia zasobów związanych z tą kontrolką należy ustawić wszystkie odwołujące się do niej zmienne na wartość Nothing. Na przykład poniższy kod usuwa kontrolkę btnHi, która została utworzona w pierwszym przykładzie: Me.Controls.Remove(btnHi) btnHi = Nothing
Rozdział 9.
Używanie kontrolek Windows Forms
157
Ten kod może usuwać kontrolki interaktywnie utworzone podczas projektowania, jak również w czasie działania programu. Przykładowy program, który można pobrać z serwera FTP wydawnictwa Helion (ftp:// ftp.helion.pl/przyklady/vb28wp.zip), demonstruje techniki dodawania i usuwania przycisków.
Własności Własność to wartość skojarzona z kontrolką. Często odpowiada ona w oczywisty sposób wyglądowi lub zachowaniu tego narzędzia. Na przykład własność Text reprezentuje wyświetlany przez kontrolkę tekst, BackColor — kolor tła, a własności Top i Left — jej położenie itd. Wiele własności, wliczając Text, BackColor, Top i Left, ma zastosowanie do kontrolek różnego rodzaju. Są też takie, które dotyczą tylko określonego jednego ich typu. Na przykład kontrolka ToolStrip posiada własność ImageList, wyznaczającą kontrolkę ImageList, która zawiera obrazy mające być wyświetlanymi przez kontrolkę ToolStrip. Niewiele kontrolek posiada własność ImageList. W kolejnych podrozdziałach zostaną opisane sposoby modyfikowania własności kontrolek w czasie projektowania i zmienianie ich za pomocą kodu podczas działania programu.
Własności w czasie projektowania Aby zmodyfikować własności kontrolki w czasie projektowania, należy otworzyć w projektancie formularzy formularz, na którym się znajduje, po czym kliknąć ją. W oknie Properties pojawią się jej własności. Na rysunku 9.3 przedstawiono okno Properties, które prezentuje własności kontrolki Button. Na przykład własność Text tej kontrolki ma wartość Tworzy przycisk, a jej własność TextAlign (decydująca o tym, gdzie na przycisku zostanie wyświetlony tekst) jest ustawiona na MiddleCenter. Rysunek 9.3. Okno Properties pozwala na modyfikację własności kontrolki w czasie projektowania
158
Część II
Wstęp do języka Visual Basic
Lista rozwijana, znajdująca się w górnej części okna Properties, bezpośrednio pod tytułem Properties, oznajmia, że jest to kontrolka o nazwie btnMakeHiButton i że należy ona do klasy System.Windows.Forms.Button. Wiele własności można ustawić poprzez kliknięcie ich wartości w oknie Properties i wpisanie nowych. Metoda ta działa w przypadku prostych wartości liczbowych i tekstowych, jak własności Name i Text, a także niektórych innych własności, kiedy wpisanie wartości ma sens. Na przykład kontrolka HScrollBar (poziomy pasek przewijania) posiada własności Minimum, Maximum i Value, określające wartość minimalną, maksymalną i bieżącą kontrolki. Można kliknąć te własności w oknie Properties i wpisać im nowe wartości. Po naciśnięciu klawisza Enter lub przejściu do innej własności kontrolka sprawdza wprowadzoną wartość. Jeśli jest ona nieprawidłowa (na przykład ABC zamiast liczby), IDE zgłasza błąd, po czym pozwala go naprawić.
Własności złożone Kilka własności ma wartości złożone. Location określa współrzędne X i Y lewego górnego rogu kontrolki, zaś Size — szerokość i wysokość kontrolki. Własność Font jest obiektem, który posiada własną nazwę czcionki, rozmiar, informacje o grubości i inne cechy pisma. W oknie Properties po lewej stronie tych własności znajduje się znak plusa. Kiedy go klikniesz, zostanie rozwinięta lista wartości zawartych w danej własności. Na rysunku 9.4 przedstawiono to samo okno Properties co wcześniej, tyle że z rozwiniętą własnością Font. Jej podwartości można modyfikować niezależnie od reszty, jak wartości wszystkich innych własności. Rysunek 9.4. Okno Properties pozwala zmieniać złożone własności w czasie projektowania
Rozdział 9.
Używanie kontrolek Windows Forms
159
Gdy rozwinięta zostanie własność złożona, po lewej stronie pojawi się znak minusa (zobacz własność Font na rysunku 9.4). Kliknięcie go spowoduje zwinięcie własności i ukrycie jej składników. Niektóre własności złożone udostępniają bardziej zaawansowane metody do ustawiania swoich wartości. Jeśli klikniesz wielokropek znajdujący się po prawej stronie własności Font na rysunku 9.4, IDE wyświetli okno dialogowe wyboru czcionki, które pozwoli ustawić wiele własności tej ostatniej.
Ograniczenia własności Niektóre własności akceptują bardziej ograniczone wartości. Na przykład Visible przyjmuje wartości logiczne, przez co może mieć tylko wartość True lub False. Kiedy klikniesz tę własność, po jej prawej stronie pojawi się strzałka listy rozwijanej. Kliknięcie tej ostatniej spowoduje rozwinięcie listy, z której będzie można wybrać własność True lub False. Wartości wielu własności można wyliczyć. Własność FlatStyle kontrolki Button przyjmuje wartości Flat, Popup, Standard i System. Kiedy klikniesz strzałkę listy rozwijanej po prawej stronie tej własności, pojawi się lista, z której będzie można wybrać jedną z tych wartości. Można także kliknąć dwukrotnie wybraną własność, aby przejrzeć jej dozwolone wartości. Po wybraniu własności można przeglądać jej wartości za pomocą strzałek do góry i w dół. Niektóre własności akceptują odmienne wartości w różnych sytuacjach. Na przykład część z nich zawiera odwołania do innych kontrolek. Własność ImageList kontrolki Button jest odwołaniem do komponentu ImageList, zawierającego obraz, który powinien być wyświetlany przez ten przycisk. Jeśli klikniesz strzałkę listy rozwijanej, znajdującą się po prawej stronie tej wartości, w oknie Properties zostanie wyświetlona lista umieszczonych na formularzu komponentów ImageList, których można użyć dla tej własności. Spis ten zawiera też pozycję (none), która usuwa wszystkie wcześniejsze odwołania kontrolki w tej własności. Wiele własności przyjmuje bardzo wyspecjalizowane wartości i udostępnia specjalne edytory ułatwiające wybieranie tych ostatnich. Na przykład własność Anchor pozwala zakotwiczyć krawędzie kontrolki przy krawędziach jej kontenera. Zwykle kontrolka jest zakotwiczona przy górnej i lewej krawędzi kontenera, dzięki czemu pozostaje w tym samym położeniu, nawet jeśli jego rozmiar zostanie zmieniony. Jeżeli zaś zostanie zakotwiczona także z prawej strony, jej prawa krawędź będzie się przesuwała w lewo i w prawo — wraz ze zwiększaniem i zmniejszaniem szerokości kontenera. Dzięki temu można tworzyć kontrolki zmieniające rozmiar w wybrany sposób razem ze swoim kontenerem. Jeśli zaznaczysz własność Anchor i klikniesz znajdującą się po prawej stronie strzałkę listy rozwijanej, w oknie Properties pojawi się mały edytor graficzny z rysunku 9.5. Poprzez klikanie prostokątów znajdujących przy czterech krawędziach można zakotwiczać lub odkotwiczać kontrolkę z wybranych stron. Aby zatwierdzić ustawienia, należy nacisnąć klawisz Enter. W celu anulowania trzeba skorzystać z klawisza Esc.
160
Część II
Wstęp do języka Visual Basic
Rysunek 9.5. Niektóre własności, na przykład Anchor, udostępniają specjalne edytory, które ułatwiają ustawianie ich wartości
Inne złożone własności mogą udostępniać odmienne edytory. Sposób ich użycia jest z reguły oczywisty. Kliknij znajdujący się po prawej stronie wartości własności wielokropek lub strzałkę listy rozwijanej, aby otworzyć edytor i poeksperymentować z metodami stosowania go. Nazwę każdej własności można kliknąć prawym przyciskiem i wybrać z menu podręcznego opcję Reset, aby przywrócić jej domyślną wartość. Wiele z własności złożonych przyjmuje wartość (none) — w ich przypadku Reset zazwyczaj ustawia właśnie tę wartość.
Własności kolekcji Niektóre własności reprezentują kolekcje obiektów. Na przykład kontrolka ListBox wyświetla listę elementów. Jej własność Items jest kolekcją zawierającą te składniki. W oknie Properties wartość tej własności jest prezentowana jako (Collection). Jeśli zaznaczysz tę własność i klikniesz znajdujący się po prawej stronie wielokropek, w oknie Properties pojawi się proste okno dialogowe, w którym będzie można edytować tekst wyświetlany przez elementy tej kontrolki. Okno to jest bardzo proste — wystarczy wprowadzić tekst elementów w osobnych linijkach i kliknąć przycisk OK. Inne własności są znacznie bardziej skomplikowane. Aby na przykład utworzyć kontrolkę TabControl, wyświetlającą obrazy na swoich kartach, konieczne jest utworzenie także komponentu ImageList. Zaznacz własność Images komponentu ImageList i kliknij znajdujący się po prawej stronie wielokropek w celu wyświetlenia okna dialogowego z rysunku 9.6. Kiedy klikniesz przycisk Add, okno to wyświetli okno dialogowe wyboru pliku, w którym będzie można dodać obraz do kontrolki. Lista po lewej stronie zawiera grafiki, które zostały już załadowane — razem z ich miniaturami. Po prawej stronie widoczne są własności obrazów.
Rozdział 9.
Używanie kontrolek Windows Forms
161
Rysunek 9.6. Okno dialogowe, które pozwala na załadowanie obrazów do kontrolki ImageList w czasie projektowania
Po dodaniu obrazów do kontrolki ImageList utwórz kontrolkę TabControl. Zaznacz jej własność ImageList, kliknij strzałkę listy rozwijanej po prawej stronie i wybierz utworzoną wcześniej kontrolkę ImageList. Następnie zaznacz własność TabPages kontrolki TabControl i kliknij wielokropek po prawej stronie, aby wyświetlić okno widoczne na rysunku 9.7. Rysunek 9.7. Okno dialogowe, które pozwala edytować strony kontrolki TabControl
Kliknij przycisk Add, aby dodać strony kart do kontrolki. Wybierz stronę, kliknij jej własność ImageIndex, a następnie znajdującą się po prawej stronie strzałkę listy rozwijanej, po czym wybierz numer obrazu na liście ImageList, który chcesz wykorzystać dla tej karty. Na rysunku 9.8 przedstawiono przykładowy program ImageTabs, który można pobrać z serwera FTP wydawnictwa Helion.
162
Część II
Wstęp do języka Visual Basic
Rysunek 9.8. Kontrolka TabControl wyświetla na swoich kartach obrazy przechowywane w komponencie ImageList
Niektóre własności zawierają nawet kolekcję obiektów, z których każdy ma inną kolekcję obiektów. Na przykład kontrolka ListView posiada własność Items, która jest kolekcją. Każdy element tej ostatniej jest obiektem posiadającym własność SubItems, będącą sama w sobie kolekcją. Kiedy kontrolka ListView zostanie wyświetlona jako lista ze szczegółami, obiekt w kolekcji Items będzie reprezentować jeden rząd w widoku, a jego wartości SubItems — drugorzędne wartości w tym rzędzie. Aby ustawić te wartości w czasie projektowania, należy zaznaczyć kontrolkę ListView i kliknąć wielokropek znajdujący się po prawej stronie jej własności Items w oknie Properties. Utwórz element w edytorze, po czym kliknij wielokropek, który znajduje się po prawej stronie jego własności SubItems. Inne skomplikowane własności udostępniają podobne edytory. Mimo że mogą one implementować skomplikowane powiązania pomiędzy różnymi kontrolkami i komponentami, zazwyczaj łatwo nauczyć się ich obsługi metodą prób i błędów.
Własności w czasie działania programu Visual Basic pozwala ustawiać większość własności kontrolek podczas projektowania, ale często trzeba uzyskiwać do nich dostęp i modyfikować je w czasie działania programu. Może na przykład zajść potrzeba zmiany tekstu etykiety w celu poinformowania użytkownika, że coś się zmieniło; wyłączenia przycisku, który w danej sytuacji jest niepotrzebny; odczytania wartości zaznaczonej przez użytkownika na liście. W kodzie źródłowym własność jest zwykłą zmienną publiczną, zdefiniowaną przez obiekt. Aby ustawić wartość własności, należy wprowadzić nazwę jej kontrolki, postawić kropkę i wpisać nazwę samej własności. Na przykład poniższy kod sprawdza tekst kontrolki TextBox o nazwie txtPath. Jeśli na końcu tego tekstu nie ma znaku /, zostanie on dodany przez program. Ten kod zarówno odczytuje, jak i ustawia własność Text: If Not txtPath.Text.EndsWith(“/”) Then txtPath.Text
& = “/”
Jeśli własność zawiera odwołanie do obiektu, można w swoim kodzie używać własności i metod tego ostatniego. Poniższy kod wyświetla okno z komunikatem, który informuje, czy krój pisma kontrolki txtPath jest pogrubiony.
Rozdział 9.
Używanie kontrolek Windows Forms
163
Sprawdza on własność Font kontrolki TextBox. Zwraca ona odwołanie do obiektu Font, który posiada własność Bold. If txtPath.Font.Bold Then MessageBox.Show(“Bold”) Else MessageBox.Show(“Nie Bold”) End If
Zauważ, że własności obiektu Font są tylko do odczytu, przez co kod nie może ustawić wartości własności txtPath.Font.Bold. Aby zmienić własność pisma kontrolki TextBox, trzeba utworzyć nową czcionkę, jak w poniższym kodzie. Kod ten przekazuje konstruktorowi obiektu Font kopię bieżącej czcionki kontrolki TextBox jako szablon oraz wartość, która informuje, że nowa czcionka powinna być pogrubiona: txtPath.Font = New Font(txtPath.Font, FontStyle.Bold). Jeśli własność reprezentuje kolekcję lub tablicę, można przejść przez nią za pomocą pętli lub iteracyjnie, jakby była ona zadeklarowana jak zwykła kolekcja lub tablica. Poniższy kod tworzy listę elementów wybranych przez użytkownika w kontrolce ListBox o nazwie lstChoices: For Each selected_item As Object In lstChoices.SelectedItems() Debug.WriteLine(selected_item.ToString) Next selected_item
Niektóre własności są tylko do odczytu w czasie działania programu, przez co aplikacja może je sprawdzać, ale nie zmieniać ich wartości. Na przykład własność Controls kontrolki Panel zwraca kolekcję odwołań do kontrolek znajdujących się w panelu. Można ją tylko odczytać w czasie działania programu, a więc nie da się jej ustawić na inną kolekcję. (Kolekcja udostępnia metody służące do dodawania i usuwania kontrolek, dzięki czemu nie trzeba zamieniać całej kolekcji, a tylko znajdujące się w niej kontrolki). Zauważ także, że w czasie projektowania kolekcja ta nie pojawia się w oknie Properties. Zamiast bezpośrednio pracować nad nią, dodaje się i usuwa interaktywnie kontrolki poprzez przenoszenie ich do i na zewnątrz kontrolki Panel. Inną własnością tylko do odczytu, której nie ma w oknie Properties, jest własność Bottom. Reprezentuje ona odległość pomiędzy górną krawędzią kontenera kontrolki a dolną krawędzią samej kontrolki. Wartość ta to w rzeczywistości własność Top z dodatkiem własności Height (control.Bottom = control.Top + control.Height), dzięki czemu można ją modyfikować za pomocą tych własności, zamiast bezpośrednio ustawiać własność Bottom. Teoretycznie własność może też być tylko do zapisu w czasie działania programu. Przypominałaby ona jednak bardziej metodę niż własność, dlatego większość kontrolek używa właśnie metod. W praktyce własności tylko do odczytu są nieczęsto spotykane, a własności tylko do zapisu są niezwykle rzadkie.
164
Część II
Wstęp do języka Visual Basic
Przydatne własności kontrolek W tym podrozdziale opisane zostaną niektóre najbardziej przydatne własności udostępniane przez klasę Control. Zestawienie tych i innych własności tej klasy znajduje się w Dodatku A — Własności kontrolek, metody i zdarzenia. Dodatek ten nie zawiera opisu wszystkich własności, a tylko tych najbardziej przydatnych. Wszystkie kontrolki (wliczając kontrolkę Form) dziedziczą bezpośrednio bądź pośrednio po klasie Control. Oznacza to, że dysponują własnościami, metodami i zdarzeniami tej klasy (chyba że jawnie je przesłonią). Mimo że własności te są dostępne dla wszystkich kontrolek dziedziczących po klasie Control, wiele z nich jest uznawanych za zaawansowane, zatem nie są pokazywane na karcie Common funkcji IntelliSense. Na przykład program powinien określać położenie kontrolki na podstawie jej własności Location, a nie własności Top i Left. Dlatego Location znajduje się na karcie Common, a Left i Top — na karcie Advanced. Na rysunku 9.9 przedstawiono kartę Common funkcji IntelliSense dla kontrolki Label. Znajduje się na niej własność Location, ale nie ma własności Left. Poprzez kliknięcie karty All można uzyskać dostęp do Left i innych zaawansowanych własności.
Rysunek 9.9. Własność Location znajduje się na karcie Common, nie ma tam natomiast własności Left
Rozdział 9.
Używanie kontrolek Windows Forms
165
Kiedy wpiszesz nazwę kontrolki i część łańcucha Left, która wystarczy, by odróżnić go od łańcucha Location (w tym przypadku lblDirectory.Le), okno automatycznie zmniejszy się w celu pomieszczenia tylko własności Left. Przeznaczenie wielu z własności klasy Control jest oczywiste, ale są i takie, którym trzeba poświęcić chwilę uwagi. W kolejnych podrozdziałach bardziej szczegółowo opiszę niektóre skomplikowane własności.
Własności Anchor i Dock Własności Anchor i Dock pozwalają kontrolce automatycznie zmieniać rozmiar wraz z jej kontenerem. Pierwsza z nich określa, które krawędzie kontrolki mają pozostać w stałej odległości od odpowiednich krawędzi kontenera. Na przykład własność Anchor zwykle jest ustawiona na Top, Left. Oznacza to, że górna i lewa krawędź kontrolki pozostają w tym samym miejscu, kiedy kontener zmienia rozmiar. Jeśli lewy górny róg kontrolki znajduje się początkowo w punkcie o współrzędnych (8, 16), to pozostanie w nim także wtedy, gdy rozmiar kontenera zostanie zmieniony. Jest to normalne zachowanie kontrolki, które sprawia, że wydaje się ona być ustawiona na stałe w kontenerze. Z drugiej strony wyobraźmy sobie, że ustawiono własność Anchor kontrolki na Top, Right i umieszczono kontrolkę w prawym górnym rogu jej kontenera. Kiedy zmienia się rozmiar kontenera, kontrolka przesuwa się cały czas, aby pozostać w jego rogu. Jeśli zostaną ustawione dwie przeciwne wartości Anchor, kontrolka zmieni swój rozmiar, aby dopasować się do obu. Załóżmy na przykład, że utworzono przycisk znajdujący się w odległości ośmiu pikseli od lewej, prawej i górnej krawędzi swojego kontenera. Następnie ustawiono własność Anchor tej kontrolki na Top, Left, Right. Kiedy zmodyfikuje się rozmiar tego kontenera, wielkość kontrolki również się zmieni — zawsze będzie się ona znajdowała w odległości ośmiu pikseli od jego lewej, prawej i górnej krawędzi. Bardziej prawdopodobną sytuacją może być umieszczenie kontrolek Label po lewej stronie za pomocą ustawienia własności Anchor na Top, Left, dzięki czemu będą miały stałe miejsce na formularzu. Po prawej stronie można umieścić kontrolki TextBox i inne kontrolki z własnością Anchor ustawioną na Top, Left, Right — wtedy będą one zmieniały rozmiar, kiedy zmodyfikuje się szerokość formularza. W podobny sposób można utworzyć kontrolki zmieniające rozmiar w pionie. Jeśli na przykład ustawi się własność Anchor kontrolki ListBox na Top, Left, Bottom, będzie się ona rozciągała w pionie, wykorzystując dostępną przestrzeń; będzie wyświetlała tyle swoich elementów, ile będzie można. Jeśli dla własności Anchor nie zostaną podane żadne wartości, kontrolka zostanie zakotwiczona na środku swojego kontenera. Załóżmy na przykład, że ustawiono przycisk w środkowo-dolnej części formularza i określono jego własność Anchor na Bottom. Ponieważ kontrolkę umieszczono na środku formularza, jej środek pokrywa się ze środkiem tego ostatniego. Kiedy zmieni się rozmiar formularza, kontrolka przesunie się, aby pozostać wyśrodkowaną w poziomie.
166
Część II
Wstęp do języka Visual Basic
Jeśli po obu stronach tej wyśrodkowanej kontrolki zostaną umieszczone inne kontrolki, wszystkie będą się przesuwać w celu pozostania wyśrodkowanymi jako grupa. Dobrze jest poeksperymentować z tą własnością, aby zobaczyć, jak ona działa. W czasie działania programu własność Anchor można ustawić na AnchorStyles.None lub logiczną kombinację wartości AnchorStyles.Top, AnchorStyles.Bottom, AnchorStyles.Left i AnchorStyles.Right. W przykładowym programie AnchorButton, który można pobrać z serwera FTP wydawnictwa Helion, został użyty poniższy kod. Ustawia on kontrolkę btnAnchored w prawym dolnym rogu formularza i określa jej własność Anchor na Bottom, Right, aby tam pozostała: Private Sub Form1_Load() Handles MyBase.Load btnAnchored.Location = New Point( _ Me.ClientRectangle.Width - Button1.Width, _ Me.ClientRectangle.Height - Button1.Height) btnAnchored.Anchor = AnchorStyles.Bottom Or AnchorStyles.Right End Sub
Własność Dock Określa ona, czy kontrolka przyczepi się do jednej, czy do większej liczby krawędzi swojego kontenera. Jeśli na przykład ustawimy tę własność na Top, kontrolka zostanie zadokowana przy górnej krawędzi swojego kontenera. Będzie wypełniać go od lewej do prawej, a jej górna krawędź pokryje się z górną krawędzią tego kontenera. Jeśli rozmiar kontenera zostanie zmieniony, kontrolka pozostanie na górze, zachowa swoją wysokość, zaś jej szerokość zmieni się zgodnie z szerokością kontenera. Tak zachowuje się typowy pasek narzędzi. Efekt jest podobny do umieszczenia kontrolki w górnej części kontenera — tak, że zapełnia go w poziomie — i ustawienia jej własności Anchor na Top, Left, Right. Własność Dock może przyjmować wartości Top, Bottom, Left, Right, Fill oraz None. Wartość Fill sprawia, że kontrolka zajmuje całą dostępną przestrzeń kontenera. Jeśli jest jedyną jego kontrolką, zapełnia go w całości. Jeśli kontener zawiera więcej niż jedną kontrolkę z własnością Dock ustawioną na inną wartość niż None, wszystkie one są ustawiane zgodnie z ich kolejnością na stosie (czasami nazywaną kolejnością na osi Z — Z-order). Kontrolka, która jest pierwsza w tej kolejce (normalnie zostałaby narysowana jako pierwsza z tyłu), będzie pozycjonowana jako pierwsza za pomocą własności Dock. Jako druga ustawiona zostanie kolejna kontrolka na stosie itd. Na rysunku 9.10 przedstawiono przykładowy program Docking, który można pobrać z serwera FTP wydawnictwa Helion. Zawiera on cztery kontrolki TextBox z własnościami Dock ustawionymi na różne wartości. Pierwsza kontrolka na stosie ma wartość Dock ustawioną na Left, dzięki czemu zajmuje lewą krawędź formularza. Wartość Dock kolejnej kontrolki jest ustawiona na Top, a więc zajmuje ona pozostałą górną część formularza. Trzecia ma wartość Right, więc znajduje się po prawej stronie. Ostatnia z kontrolek jest ustawiona na wartość Fill — wykorzystuje całą pozostałą przestrzeń.
Rozdział 9.
Używanie kontrolek Windows Forms
167
Rysunek 9.10. Zadokowane kontrolki są ustawiane zgodnie z ich kolejnością na stosie
Kontrolki zadokowane przy krawędzi zmieniają swoje rozmiary, aby wypełnić kontener w jednym kierunku. Na przykład kontrolka z własnością Dock ustawioną na Top zajmuje całą dostępną przestrzeń w poziomie, natomiast ta z własnością Dock określoną jako Fill zapełnia całą dostępną przestrzeń w kontenerze we wszystkich kierunkach. Poza tym własność Dock nie ustawia zbyt inteligentnie kontrolek w kontenerze, kiedy zmieniany jest jego rozmiar. Załóżmy na przykład, że mamy dwie kontrolki ustawione jedna nad drugą. Własność Dock pierwszej z nich jest ustawiona na Top, a drugiej na Fill. Można te kontrolki tak ustawić, aby dzieliły formularz w pionie na dwie równe części. Kiedy jednak zmieni się wysokość tego formularza, druga kontrolka, której własność Dock ma wartość Fill, zajmie całą nową przestrzeń, a pierwsza pozostanie bez zmian. Za pomocą własności Dock nie da się sprawić, aby kontrolki dzieliły formularz na dwie części po zmianie jego rozmiaru. Własność Anchor również nie nadaje się do tego celu. W zamian konieczne jest użycie kodu podobnego do tego poniżej. Kiedy formularz zmienia rozmiar, kod ten przesuwa i skaluje kontrolki TextBox1 i TextBox2, aby zapełniały formularz poprzez podzielenie go w pionie na dwie równe części. Private Sub Form1_Load() Handles Me.Load ArrangeTextBoxes() End Sub Private Sub Form1_Resize() Handles Me.Resize ArrangeTextBoxes() End Sub Private Sub ArrangeTextBoxes() Dim wid As Integer = Me.ClientRectangle.Width Dim hgt1 As Integer = Me.ClientRectangle.Height \ 2 Dim hgt2 As Integer = Me.ClientRectangle.Height - hgt1 txtTop.SetBounds(0, 0, wid, hgt1) txtBottom.SetBounds(0, hgt1, wid, hgt2) End Sub
Podobny kod został wykorzystany do podzielenia formularza pomiędzy dwa pola tekstowe w programie DivideForm, który można pobrać z serwera FTP wydawnictwa Helion. Kiedy chce się podzielić formularz, pomocna może też być kontrolka SplitterContainer. Zawiera ona dwa panele, które mogą przetrzymywać inne kontrolki. Użytkownik może przeciągać linię dzielącą te dwa panele, aby dostosować rozmiar każdego z nich.
168
Część II
Wstęp do języka Visual Basic
Własności położenia i rozmiaru Kontrolki posiadają wiele własności związanych z ich położeniem i rozmiarem, a różnice pomiędzy nimi nie zawsze są jasne. Do najdziwniejszych aspektów kontrolek należą pojęcia obszaru klienckiego, obszaru pozaklienckiego i obszaru eksploatacyjnego. Obszar kliencki kontrolki to powierzchnia w jej wnętrzu, na której można rysować lub umieszczać inne kontrolki. Obszar pozakliencki to cała reszta. Na typowym formularzu obszarem pozaklienckim są krawędzie i pasek tytułu. Obszar kliencki stanowi powierzchnia znajdująca się po wewnętrznej stronie krawędzi i pod paskiem tytułu. Można na nim umieszczać kontrolki i rysować grafikę. Menu formularza mogą wprowadzić zamieszanie pomiędzy obszar kliencki i pozakliencki. Kierując się logiką, można pomyśleć, że menu stanowią część obszaru pozaklienckiego, ponieważ kontrolki są umieszczane pod nimi. Są one jednak kontrolkami, a inne kontrolki można umieszczać zarówno pod, jak i nad nimi (chociaż wyglądałoby to dziwnie dla użytkownika), więc znajdują się w obszarze klienckim. Obszar eksploatacyjny kontrolki to obszar kliencki, z którego usunięto wszystkie wewnętrzne ozdoby. Na przykład kontrolka GroupBox wyświetla wewnętrzną krawędź i tytuł. Mimo iż można nad nimi umieścić kontrolki, zwykle się tego nie robi. Obszar eksploatacyjny obejmuje przestrzeń w obrębie krawędzi kontrolki GroupBox i pod obszarem, w którym znajduje się tytuł. W poniższej tabeli znajduje się zestawienie własności związanych z rozmiarem i położeniem kontrolki. Własność
Typ danych
Odczyt/Zapis
Przeznaczenie
Bounds
Rectangle
Odczyt i zapis
Rozmiar i położenie kontrolki w kontenerze, wliczając w to obszary pozaklienckie.
ClientRectangle
Rectangle
Odczyt
Rozmiar i położenie obszaru klienckiego w kontrolce.
ClientSize
Size
Odczyt i zapis
Rozmiar obszaru klienckiego. Jeśli własność ta zostanie ustawiona, kontrolka dostosuje swój rozmiar, aby zrobić miejsce dla obszaru pozaklienckiego, a także udostępni ten określony obszar kliencki.
DisplayRectangle
Rectangle
Odczyt
Rozmiar i położenie obszaru w obrębie kontrolki, w którym normalnie można by rysować lub umieszczać inne kontrolki.
Location
Point
Odczyt i zapis
Położenie lewego górnego rogu kontrolki w kontenerze.
Size
Point
Odczyt i zapis
Rozmiar kontrolki z obszarami pozaklienckimi.
Left, Top, Width, Height
Integer
Odczyt i zapis
Rozmiar i położenie kontrolki w kontenerze, wliczając w to obszary pozaklienckie.
Bottom, Right
Integer
Odczyt
Położenie prawego dolnego rogu kontrolki w kontenerze.
Rozdział 9.
Używanie kontrolek Windows Forms
169
Metody Metoda wykonuje kod związany z kontrolką. Może ona być funkcją zwracającą jakąś wartość lub podprocedurą niezwracającą żadnej wartości. Metody mogą przyjmować parametry, podobnie jak wszystkie inne funkcje i podprocedury. Ponieważ metody wykonują kod, nie można ich wywoływać w czasie projektowania. Uruchamianie ich jest możliwe tylko za pomocą kodu w czasie działania programu. Dodatek A zawiera zestawienie najbardziej przydatnych metod klasy Control. Kontrolki dziedziczące po niej również udostępniają te metody, chyba że je przesłoniły.
Zdarzenia Kontrolki i inne obiekty zgłaszają zdarzenia, które informują program o tym, że nastąpiły jakieś zmiany sytuacji. Czasami zgłaszanie zdarzenia nazywa się uruchamianiem lub wyzwalaniem zdarzenia. Konkretne klasy kontrolek udostępniają zdarzenia odpowiadające ich przeznaczeniu. Na przykład kontrolka Button udostępnia zdarzenie Click, które informuje program o tym, że użytkownik kliknął przycisk. Program reaguje na zdarzenie poprzez utworzenie procedury obsługi zdarzeń, która je przechwytuje i podejmuje odpowiednie działania. Każde zdarzenie definiuje własny format procedury obsługi zdarzeń i określa parametry, które będzie ona odbierać. Często parametry te dostarczają dodatkowe informacje o samym zdarzeniu. Na przykład kiedy jakiś fragment formularza zostanie przykryty i odkryty, formularz ten uruchomi swoje zdarzenie Paint. Procedura obsługi zdarzeń Paint przyjmuje jako parametr obiekt typu PaintEventArgs. Własność gr tego obiektu jest odwołaniem do obiektu Graphics, za pomocą którego program może ponownie narysować treść formularza. Niektóre procedury obsługi zdarzeń pobierają parametry, które są wykorzystywane do wysyłania informacji o zdarzeniu z powrotem do obiektu, który je uruchomił. Na przykład procedura obsługi zdarzeń FormClosing klasy Form posiada parametr typu FormClosingEventArgs. Jest on obiektem posiadającym własność Cancel. Jeśli program ustawi tę własność na True, formularz anuluje zdarzenie FormClosing i pozostanie otwarty. Na przykład procedura ta może zweryfikować poprawność formatu wprowadzonych przez użytkownika danych. Jeśli byłyby one nieprawidłowe, program mógłby wyświetlić komunikat o błędzie i pozostawić formularz otwarty. Mimo iż wiele z najbardziej przydatnych zdarzeń kontrolek jest ściśle związanych z konkretnym typem kontrolki, dziedziczą one niektóre zdarzenia po klasie Control. Dodatek A zawiera zestawienie najważniejszych zdarzeń klasy Control. Kontrolki dziedziczące po niej udostępniają także te zdarzenia, chyba że je przesłoniły.
170
Część II
Wstęp do języka Visual Basic
Tworzenie procedur obsługi zdarzeń w czasie projektowania Procedurę obsługi zdarzeń można w czasie projektowania utworzyć na kilka sposobów. Jeśli otworzysz formularz w projektancie formularzy i klikniesz dwukrotnie jakąś kontrolkę, zostanie otwarty edytor kodu z automatycznie wstawioną domyślną procedurą obsługi zdarzeń tej kontrolki. Na przykład kontrolka TextBox otwiera swoją procedurę obsługi zdarzenia TextChanged, kontrolka Button otwiera procedurę obsługi zdarzenia Click, a sam formularz — procedurę obsługi zdarzenia Load. Aby utworzyć inną procedurę obsługi zdarzeń kontrolki, należy najpierw kliknąć tę ostatnią, a następnie w oknie Properties — przycisk Events (który przedstawia ikonę pioruna). Dzięki temu w oknie tym pojawi się spis najczęściej używanych zdarzeń tej kontrolki. Jeśli są już zdefiniowane jakieś procedury obsługi zdarzeń, na przykład dla innych kontrolek, można je wybrać z list rozwijanych tych zdarzeń. Aby utworzyć nową procedurę obsługi zdarzeń, należy dwukrotnie kliknąć wybrane zdarzenie. Aby utworzyć inne, niebędące domyślnymi, procedury obsługi zdarzeń lub procedury wewnątrz edytora kodu, należy otworzyć okno kodu, po czym wybrać najpierw kontrolkę z listy rozwijanej po lewej stronie, a następnie procedurę obsługi zdarzeń z listy rozwijanej po prawej stronie (rysunek 9.11). W celu utworzenia procedury obsługi zdarzeń dla samego formularza trzeba wybrać (Form1 Events) z listy po lewej stronie, a później zdarzenie z listy po prawej stronie.
Rysunek 9.11. Aby utworzyć procedurę obsługi zdarzeń w oknie kodu, należy wybrać kontrolkę z listy po lewej stronie, a następnie zdarzenie z listy po prawej
Rozdział 9.
Używanie kontrolek Windows Forms
171
Okno kodu tworzy procedurę obsługi zdarzeń z poprawnymi parametrami i wartością zwrotną. Na przykład poniższy kod przedstawia pustą procedurę obsługi zdarzenia Click kontrolki TextBox (zauważ, że dwa pierwsze wiersze są zawinięte w książce, ale w edytorze kodu zajmowałyby jeden wiersz). Teraz wystarczy tylko wpisać kod, który ma zostać wykonany w odpowiedzi na zdarzenie. Dzięki temu, że Visual Basic obsługuje rozluźnione delegaty, w deklaracji procedury obsługi zdarzeń można pominąć parametry, jeśli nie są potrzebne. Aby kod był łatwiejszy do czytania w tej książce, parametry te są zawsze opuszczane, gdy nie są niezbędne. Private Sub TextBox1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles TextBox1.Click End Sub
Słowo kluczowe WithEvents Jeśli obiekt zostanie zadeklarowany za pomocą słowa kluczowego WithEvents, będzie można przechwytywać jego zdarzenia. Po zadeklarowaniu zmienną taką znajdziesz na liście rozwijanej po lewej stronie okna edytora kodu, jak każdą inną kontrolkę. Następnie będzie można wybrać jedno ze zdarzeń tego obiektu z listy znajdującej się po prawej stronie. Kiedy zostanie przypisany egzemplarz jakiegoś obiektu do tej zmiennej, wszystkie zdefiniowane dla niej procedury obsługi będą odbierać zdarzenia tego właśnie obiektu. Jeśli później zmienna ta zostanie ustawiona na wartość Nothing, procedury te nie będą odbierać więcej zdarzeń. Zazwyczaj nie trzeba tworzyć zmiennych WithEvents dla kontrolek, ponieważ Visual Basic robi to automatycznie. Jednak dzięki użyciu tego typu zmiennych można szybko i łatwo włączać i wyłączać zdarzenia. Wyobraźmy sobie, że chcesz, by program przez jakiś czas śledził zdarzenia myszy tylko w kontrolce PictureBox. Deklarujesz zmienną PictureBox w następujący sposób: Private WithEvents m_Canvas As PictureBox
Kiedy program chce odbierać zdarzenia, ustawia tę zmienną na swoją kontrolkę PictureBox, jak w poniższym kodzie. Od tej pory procedury obsługi zdarzeń tej zmiennej, takie jak m_Canvas_MouseDown, m_Canvas_MouseMove i m_Canvas_MouseUp, są włączone. m_Canvas = PictureBox1
Kiedy program nie chce już odbierać więcej tych zdarzeń, ustawia zmienną m_Canvas na Nothing, jak w poniższej instrukcji. Skoro zmienna m_Canvas posiada wartość Nothing, nie ma skojarzonej z nią kontrolki, która generowałaby dla niej zdarzenia. m_Canvas = Nothing
172
Część II
Wstęp do języka Visual Basic
Tworzenie procedur obsługi zdarzeń w czasie działania programu W czasie działania programu można nie tylko tworzyć procedury obsługi zdarzeń, ale także przypisywać je do kontrolek. Najpierw utwórz procedurę obsługi zdarzeń. Jej parametry muszą być odpowiednie dla typu tworzonej procedury obsługi zdarzeń. Na przykład procedura obsługi zdarzeń Click kontrolki TextBox musi przyjmować dwa parametry typów System.Object i System.EventArgs. Aby mieć pewność, że wszystkie szczegóły się zgadzają, można zacząć od utworzenia procedury obsługi zdarzeń dla normalnej kontrolki w czasie projektowania. Wybierz kontrolkę z lewej listy rozwijanej w oknie edytora kodu, a następnie zdarzenie z listy po prawej stronie. Zmień nazwę utworzonej w ten sposób procedury na coś bardziej odpowiedniego (na przykład nazwę Button1_Clicked można zamienić na ToolClicked) i usuń instrukcję Handles, która wiąże procedurę z kontrolką. Możesz także usunąć samą kontrolkę, jeśli nie potrzebujesz jej do niczego. Utworzoną procedurę obsługi zdarzeń można dodawać i usuwać z kontrolki za pomocą instrukcji AddHandler i RemoveHandler. Poniższy kod pokazuje, jak przykładowy program — do pobrania z serwera FTP wydawnictwa Helion — przełącza procedurę obsługi zdarzeń, którą przycisk wykonuje, kiedy zostanie kliknięty: ‘ Dodaje i usuwa procedurę obsługi zdarzeń 1. Private Sub radEventHandler1_CheckedChanged() _ Handles radEventHandler1.CheckedChanged If radEventHandler1.Checked Then AddHandler btnClickMe.Click, AddressOf EventHandler1 Else RemoveHandler btnClickMe.Click, AddressOf EventHandler1 End If End Sub ‘ Dodaje i usuwa procedurę obsługi zdarzeń 2. Private Sub radEventHandler2_CheckedChanged() _ Handles radEventHandler2.CheckedChanged If radEventHandler2.Checked Then AddHandler btnClickMe.Click, AddressOf EventHandler2 Else RemoveHandler btnClickMe.Click, AddressOf EventHandler2 End If End Sub ‘ Wyświetla informację o procedurze obsługi zdarzeń. Private Sub EventHandler1(ByVal sender As System.Object, _ ByVal e As System.EventArgs) MessageBox.Show(“EventHandler1”) End Sub Private Sub EventHandler2(ByVal sender As System.Object, _ ByVal e As System.EventArgs) MessageBox.Show(“EventHandler2”) End Sub
Rozdział 9.
Używanie kontrolek Windows Forms
173
Kiedy użytkownik zaznaczy lub wyczyści przycisk radiowy (ang. radio button) radEventHandler1, procedura obsługi zdarzeń tej kontrolki o nazwie CheckedChanged doda lub usunie procedurę EventHandler1 do lub ze zdarzenia Click kontrolki btnClickMe. Analogicznie — kiedy zaznaczysz lub wyczyścisz przycisk radiowy radEventHandler2, procedura obsługi zdarzeń tego przycisku o nazwie CheckedChanged doda lub usunie procedurę EventHandler2 do lub ze zdarzenia Click kontrolki btnClickMe. Procedury obsługi zdarzeń EventHandler1 i EventHandler2 wyświetlają komunikat. Informuje on, która z nich jest wykonywana. Instrukcje AddHandler i RemoveHandler pozwalają w miarę łatwo przełączać jedno lub dwa zdarzenia. Jeśli konieczne jest przełączenie wielu procedur obsługi zdarzeń dla jednej kontrolki za jednym razem, prostsze może się okazać użycie zmiennej, która została zadeklarowana przy użyciu słowa kluczowego WithEvents.
Zdarzenia tablic kontrolek W Visual Basicu 6 i wcześniejszych wersjach tego języka można było używać tablic kontrolek (ang. control array). Tablica kontrolek to zestaw kontrolek o takiej samej nazwie i ze wspólnymi procedurami obsługi zdarzeń. To, która kontrolka uruchamiała zdarzenie, było ustalane za pomocą parametru procedury obsługi zdarzeń, określającego jej indeks w tablicy. Jeśli grupa kontrolek wykonuje podobne zadania, wspólna procedura obsługi zdarzeń może pozwolić zaoszczędzić sporą ilość kodu. Visual Basic .NET nie pozwala na używanie tablic kontrolek, ale podobny efekt można osiągnąć na kilka innych sposobów. Najpierw załóżmy, że dodano kontrolkę do formularza i zdefiniowano dla niej procedury obsługi zdarzeń. Następnie skopiowano i wklejono tę kontrolkę, aby utworzyć pozostałe kontrolki dla tego formularza. Domyślnie wszystkie one współdzielą procedury obsługi zdarzeń, które utworzono dla pierwszej z nich. Jeśli przyjrzysz się kodowi procedur obsługi zdarzeń, zauważysz, że instrukcje Handles posiadają listy wszystkich skopiowanych kontrolek. W razie potrzeby można także ręcznie dopisać dodatkowe kontrolki do instrukcji Handles. Inną metodą na zmuszenie kontrolek do współdzielenia procedur obsługi zdarzeń jest użycie instrukcji AddHandler. Pierwszy parametr procedury obsługi zdarzeń jest zmienną typu System.Object, zawierającą odwołanie do obiektu, który zgłosił to zdarzenie. Program może na podstawie tego obiektu i jego własności (na przykład własności Name lub Text) określić, która kontrolka zgłosiła zdarzenie, a następnie wykonać odpowiednie akcje.
Zdarzenia walidacji Walidacja danych jest ważnym składnikiem wielu aplikacji. Visual Basic udostępnia dwa zdarzenia ułatwiające ten proces — Validating i Validated. W kolejnych podrozdziałach opiszę trzy metody użycia tych zdarzeń do sprawdzania poprawności danych.
174
Część II
Wstęp do języka Visual Basic
Walidacja zintegrowana Zdarzenie Validating uruchamia się, gdy kod powinien sprawdzić poprawność danych od kontrolki. Ma to miejsce, gdy kontrolka jest aktywowana, a formularz zamykany, albo gdy aktywacja przechodzi z jednej kontrolki na inną, której własność CausesValidation jest ustawiona na True. Walidacja zintegrowana wykorzystuje zdarzenie Validating do wykonywania wszystkich operacji sprawdzania poprawności danych. Procedura obsługi zdarzenia Validating może sprawdzić, czy dane w kontrolce zawierają dozwolone wartości, po czym podjąć odpowiednie działania, jeśli tak nie jest. Weźmy na przykład widoczny na rysunku 9.12 program FiveDigits, który można pobrać z serwera FTP wydawnictwa Helion. Procedura obsługi zdarzenia Validating pierwszej kontrolki TextBox sprawdza, czy wartość w tej kontrolce to dokładnie pięć cyfr. Jeśli pole to nie zawiera pięciu cyfr, jak na rysunku, program oznacza wartość tej kontrolki jako nieprawidłową za pomocą kontrolki ErrorProvider, po czym z powrotem aktywuje tę samą kontrolkę. Kontrolka ErrorProvider wyświetla niewielki wykrzyknik po prawej stronie pola tekstowego i kilkakrotnie mruga tym znakiem, aby zwrócić uwagę użytkownika. Kiedy najedzie się kursorem nad tę ikonę, kontrolka ErrorProvider wyświetli w chmurce informacje o błędzie. Rysunek 9.12. Zdarzenie Validating zostaje uruchomione, kiedy aktywowana jest inna kontrolka z własnością CausesValidation ustawioną na True
Druga kontrolka TextBox w tym przykładzie ma wartość CausesValidation ustawioną na False. Kiedy użytkownik przechodzi z pierwszej kontrolki na drugą, zdarzenie Validating nie zostaje zgłoszone, więc kontrolka TextBox nie będzie oznaczona wykrzyknikiem. Trzecia kontrolka TextBox ma własność CausesValidation ustawioną na True, dzięki czemu kiedy użytkownik przejdzie do niej, zostanie uruchomione zdarzenie Validating pierwszej kontrolki TextBox, zatem jej wartość będzie oznaczona, jeśli jest niepoprawna. Zdarzenie Validating jest uruchamiane także wtedy, gdy użytkownik próbuje zamknąć formularz. Poniższy kod przedstawia procedurę obsługi zdarzenia Validating, której użyto w tym przykładzie. Zauważ, że klauzula Handles zawiera listę wszystkich trzech zdarzeń Validating kontrolek TextBox, a więc procedura ta przechwytuje zdarzenie Validating wszystkich trzech kontrolek. ' Waliduje zawartość pola tekstowego. Private Sub txtNumber_Validating(ByVal sender As Object, ByVal e As System. ComponentModel.CancelEventArgs) Handles txtNumber1.Validating, txtNumber2. Validating, txtNumber3.Validating ' TextBox. Dim text_box As TextBox = DirectCast(sender, TextBox) ' Walidacja wartości kontrolki. ValidateFiveDigits(text_box, e.Cancel) End Sub
Rozdział 9.
Używanie kontrolek Windows Forms
175
' Weryfikacja, czy kontrolka TextBox zawiera pięć cyfr. Private Sub ValidateFiveDigits(ByVal text_box As TextBox, ByRef cancel_event As Boolean) If text_box.Text.Length = 0 Then ' Zezwolenie na łańcuch o zerowej długości. cancel_event = False Else ' Zezwolenie na pięć cyfr. cancel_event = Not (text_box.Text Like "#####") End If ' Sprawdzenie, czy zdarzenie będzie anulowane. If cancel_event Then ' Niepoprawne. Błąd. errBadDigits.SetError(text_box, _ text_box.Name & " musi zawierać dokładnie pięć cyfr") Else ' Poprawne. Czyści wszystkie błędy. errBadDigits.SetError(text_box, "") End If End Sub
Procedura obsługi zdarzeń odbiera kontrolkę, która uruchomiła zdarzenie w swoim parametrze nadawcy. Za pomocą instrukcji DirectCast konwertuje rodzajowy obiekt Object na TextBox i przekazuje go do podprocedury ValidateFiveDigits, która wykonuje wszystkie interesujące nas czynności. Ponadto przekazuje parametr e.Cancel, dzięki czemu podprocedura ta może anulować w razie potrzeby akcję, która spowodowała to zdarzenie. Podprocedura ValidateFiveDigits sprawdza zawartość kontrolki CheckBox i ustawia parametr cancel_event na True, jeśli tekst ten składa się z więcej niż zera znaków, ale nie z dokładnie pięciu cyfr. Parametr ten jest przekazywany przez referencję, co powoduje, że pierwotna wartość e.Cancel zostaje zmieniona w wywołującej procedurze obsługi zdarzeń. To powoduje ponowne aktywowanie kontrolki TextBox, która zgłosiła to zdarzenie — i która zawiera niepoprawne dane. Jeśli parametr cancel_event ma wartość True, wartość w kontrolce jest niepoprawna, więc program wykorzystuje komponent ErrorProvider o nazwie errBadDigits do przypisania komunikatu o błędzie do kontrolki TextBox. Jeśli parametr cancel_event ma wartość False, wartość jest poprawna, dzięki czemu program czyści komunikat o błędzie komponentu ErrorProvider dla kontrolki TextBox.
Walidacja rozdzielna Zdarzenie Validated kontrolki zostaje uruchomione, kiedy przestaje ona być aktywna na rzecz innej kontrolki, której własność CausesValidation jest ustawiona na True, lub kiedy zostaje zamknięty formularz. Kontrolka powinna już była sprawdzić swoje dane w zdarzeniu Validating, stąd nazwa Validated. To zdarzenie jednak nie ma wiele wspólnego z prawdziwą walidacją; jest ono uruchamiane bez względu na to, czy w kodzie znajduje się procedura obsługi zdarzenia Validating, a nawet czy wartość kontrolki jest poprawna. Jedyna sytuacja, w której zdarzenie to nie zostanie
176
Część II
Wstęp do języka Visual Basic
wykonane, ma miejsce wtedy, gdy walidacja nie będzie ukończona. Może się to zdarzyć, gdy procedura obsługi zdarzenia Validating anuluje zdarzenie powodujące walidację. W poprzednim podrozdziale opisałem ustawianie i czyszczenie błędu kontrolki w jej procedurze obsługi zdarzenia Validating. Alternatywną metodą jest ustawianie błędów w procedurze obsługi zdarzenia Validating i czyszczenie ich w procedurze obsługi zdarzenia Validated, jak w poniższym kodzie. Jeśli kontrolka zawiera niepoprawną wartość, Validating anuluje zdarzenie, które spowodowało tę walidację, przez co zdarzenie Validated nie zostanie wykonane. Jeżeli wartość w kontrolce jest poprawna, procedura obsługi zdarzenia Validating nie anuluje tego zdarzenia i zostanie wykonana procedura obsługi zdarzenia Validated, czyszcząca wcześniejszy błąd. ‘ Walidacja zawartości kontrolki TextBox. Private Sub txtNumber_Validating(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles txtNumber1.Validating, txtNumber2.Validating, txtNumber3.Validating ‘ Walidacja wartości tej kontrolki. ValidateFiveDigits(DirectCast(sender, TextBox), e.Cancel) End Sub ‘ Sprawdzenie, czy kontrolka TextBox zawiera pięć cyfr. Private Sub ValidateFiveDigits(ByVal text_box As TextBox, _ ByRef cancel_event As Boolean) ‘ Anulowanie, jeśli łańcuch ma więcej niż 0 znaków i nie jest to pięć cyfr. cancel_event = (text_box.Text.Length < > 0) And _ Not (text_box.Text Like “#####”) ‘ Sprawdzenie, czy zdarzenie będzie anulowane. If cancel_event Then ‘ Niepoprawne. Błąd. ErrorProvider1.SetError(text_box, _ text_box.Name & “ must contain exactly five digits”) End If End Sub ‘ Walidacja zakończona powodzeniem. Czyszczenie błędów. Private Sub txtNumber_Validated(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtNumber1.Validated, txtNumber2.Validated, txtNumber3.Validated ‘ Poprawne. Czyszczenie błędu. ErrorProvider1.SetError(DirectCast(sender, TextBox), “”) End Sub
Metodę tę demonstruje przykładowy program o nazwie FiveDigitsSeparate, który można pobrać z serwera FTP wydawnictwa Helion.
Walidacja odroczona Poprzednie metody zmuszały do naprawienia błędu poprzez natychmiastowe utrzymywanie aktywnej kontrolki, w której został znaleziony błąd. W przypadku niektórych aplikacji lepszym rozwiązaniem może się okazać pozwolenie użytkownikowi na wypełnienie pozostałych pól i powrócenie do problemu później. Na przykład ktoś, kto pisze bezwzrokowo na klawiaturze, może wpisywać dane do kilku pól, nie patrząc przez chwilę w górę. W wyniku tego osoba taka wprowadzałaby tylko kilka razy niepoprawne wartości w jednym polu, a zatem marnowałaby czas.
Rozdział 9.
Używanie kontrolek Windows Forms
177
Poniższy kod przedstawia technikę, która umożliwia użytkownikowi kontynuowanie wpisywania danych w pozostałych polach: ‘ Walidacja zawartości kontrolki TextBox. Private Sub txtNumber_Validating(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles txtNumber1.Validating, txtNumber2.Validating, txtNumber3.Validating ‘ Walidacja wartości tej kontrolki. ValidateFiveDigits(DirectCast(sender, TextBox)) End Sub ‘ Sprawdzenie, czy kontrolka ta zawiera pięć cyfr. Private Sub ValidateFiveDigits(ByVal text_box As TextBox) ‘ Sprawdzenie, czy dane są poprawne. If (text_box.Text.Length < > 0) And _ Not (text_box.Text Like “#####”) _ Then ‘ Niepoprawne. Błąd. errBadDigits.SetError(text_box, _ text_box.Name & “ must contain exactly five digits”) Else ‘ Poprawne. Czyszczenie błędu. errBadDigits.SetError(text_box, “”) End If End Sub ‘ Sprawdzenie, czy jakieś pola mają komunikaty o błędach. Private Sub Form1_FormClosing(ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing ‘ Założenie, że anulujemy zamykanie. e.Cancel = True ‘ Sprawdzenie, czy są błędy. If IsInvalidField(txtNumber1) Then Exit Sub If IsInvalidField(txtNumber3) Then Exit Sub ‘ Jeśli dotarliśmy tutaj, dane są w porządku. e.Cancel = False End Sub ' Jeśli do tej kontrolki jest przypisany komunikat o błędzie, ' zostanie on wyświetlony, kontrolka będzie aktywowana, ' a na koniec zostanie zwrócona wartość True. Private Function IsInvalidField(ByVal ctl As Control) As Boolean ‘ Sprawdzenie, czy z kontrolką jest skojarzony komunikat o błędzie. If errBadDigits.GetError(ctl).Length = 0 Then ‘ Brak komunikatu o błędzie. Return False Else ‘ Jest komunikat o błędzie. ‘ Wyświetlenie tego komunikatu. MessageBox.Show(errBadDigits.GetError(ctl)) ‘ Aktywowanie kontrolki. ctl.Focus() Return True End If End Function
178
Część II
Wstęp do języka Visual Basic
Procedura obsługi zdarzeń Validating wywołuje podprocedurę ValidateFiveDigits — podobnie jak wcześniej. Jednak tym razem podprocedura ta nie pobiera parametru cancel_event. Jeśli wartość obiektu kontrolki TextBox zawiera błąd, procedura ta przypisuje do niej komunikat o błędzie za pomocą komponentu ErrorProvider, po czym kończy działanie. Kiedy użytkownik próbuje zamknąć formularz, zostaje wykonana procedura obsługi zdarzeń FormClosing. Zakłada ona, że jakieś pole zawiera niepoprawne dane, zatem ustawia parametr e.Cancel na wartość True. Następnie wywołuje funkcję IsInvalidField dla każdej kontrolki, którą chce sprawdzić pod kątem poprawności danych. Jeśli którekolwiek wywołanie tej funkcji zwróci wartość True, procedura ta zakończy działanie, parametr e.Cancel nadal będzie miał wartość True, a formularz nie będzie chciał się zamknąć. Jeżeli wszystkie pola pomyślnie przejdą walidację, ta procedura obsługi zdarzeń ustawi parametr e.Cancel na False, a formularz zostanie zamknięty. Funkcja IsInvalidField pobiera komunikat o błędzie, który został przypisany do kontrolki za pomocą metody GetError komponentu ErrorProvider. Jeśli jest on pusty, funkcja ta zwraca wartość False, która oznacza, że dane są poprawne. Jeśli komunikat nie jest pusty, funkcja wyświetla go, aktywuje kontrolkę i zwraca wartość True, oznaczającą, że dane formularza są niepoprawne. Kiedy podczas zamykania formularza aktywna jest kontrolka TextBox, jej zdarzenie Validating zostaje uruchomione przed zdarzeniem FormClosing, dzięki czemu ma ona czas na sprawdzenie swoich danych przed zamknięciem formularza. Technikę tę demonstruje przykładowy program o nazwie FiveDigitsDeferred, który można pobrać z serwera FTP wydawnictwa Helion. Jeśli formularz jest oknem dialogowym, poprawność jego danych można sprawdzić w procedurze obsługi zdarzeń Click przycisku Ok zamiast w zdarzeniu formularza FormClosing.
Podsumowanie W tym rozdziale ogólnie opisano kontrolki, komponenty i obiekty. Objaśniono, jak tworzyć kontrolki, a także jak używać ich własności, metod i zdarzeń. Dodatkowo przedstawiono dodawanie i usuwanie procedur obsługi zdarzeń oraz strategie i zdarzenia walidacji danych. W dodatku A — „Własności, metody i zdarzenia kontrolek” — opisano najbardziej przydatne własności, metody i zdarzenia udostępniane przez klasę Controls. Wszystkie kontrolki dziedziczące po tej klasie dziedziczą te własności, metody i zdarzenia, chyba że je przesłaniają. W dodatku G — „Komponenty i kontrolki Windows Forms” — znajdziesz bardziej szczegółowe omówienia standardowych kontrolek i komponentów Windows. Dodatek ten pomaga dobrze zrozumieć działanie kontrolek, dzięki czemu można maksymalnie wykorzystać ich możliwości. Klasa Form dziedziczy po klasie Control wszystkie jej własności, metody i zdarzenia. W pewnym sensie Form jest rodzajem kontrolki mającej specjalne wymagania i udostępniającej funkcje, których nie mają inne tego typu narzędzia. Aby ułatwić Ci efektywne wykorzystywanie tych obiektów, w rozdziale 10. — „Formularze Windows” — bardziej szczegółowo opiszę klasę Form.
10
Formularze Windows Klasa Visual Basic Form jest potomkiem klasy Controls. Jej hierarchia dziedziczenia wygląda następująco — Control-ControllableControl-ContainerControl-Form. Oznacza to, że formularz jest rodzajem kontrolki. Poza przypadkami przesłonięcia dziedziczy własności, metody i zdarzenia zdefiniowane w klasie Control. Pod wieloma względami formularz ten to tylko jeszcze jeden rodzaj kontrolki (jak TextBox czy ComboBox). Jednak formularze mają też kilka specjalnych cech, które odróżniają je od innych rodzajów kontrolek. To na nich zazwyczaj umieszcza się inne kontrolki. Ponadto formularze pełnią centralną rolę w większości aplikacji Visual Basica. Są największym elementem graficznym, z którym użytkownik wchodzi w bezpośrednie interakcje. Można je minimalizować, przywracać, maksymalizować i zamykać. Grupują one treść dostarczaną przez inne kontrolki, dzięki czemu użytkownik może nią w odpowiedni sposób zarządzać. W tym rozdziale opiszę niektóre specjalne cechy formularzy Windows, których nie mają inne obiekty. Omówię przede wszystkim sposoby wykorzystywania formularzy przez różne typowe programy. Objaśnię na przykład, jak tworzyć aplikacje MDI (ang. Multiple Document Interface), niestandardowe okna dialogowe oraz ekrany powitalne (ang. splash screen). Aplikacja MDI wyświetla więcej niż jeden dokument naraz w osobnych oknach w większym nadrzędnym formularzu MDI. Programy tego typu zazwyczaj dostarczają narzędzia do zarządzania zawartymi w nich formularzami potomnymi. Pozwalają one na minimalizowanie tych formularzy, aranżację ikon zminimalizowanych formularzy, wyświetlenie widoku kafelków formularzy potomnych w formularzu nadrzędnym itd. Visual Studio potrafi wyświetlać wiele okien (projektantów formularzy, edytorów kodu, edytorów map bitowych itd.) w formularzu nadrzędnym, a więc jest to aplikacja MDI. Aplikacja SDI (ang. Single Document Interface) wyświetla tylko jeden dokument w formularzu. Na przykład program Microsoft Paint może mieć otwarty tylko jeden obraz naraz, a więc jest aplikacją typu SDI. Niektóre aplikacje SDI mogą wyświetlać więcej niż jeden dokument naraz, ale każdy z nich znajduje się wtedy na osobnym formularzu. Własności, metody i zdarzenia obiektów klasy Form zostaną omówione w tym rozdziale tylko pobieżnie. Szczegółowy ich opis znajduje się w Dodatku J.
180
Część II
Wstęp do języka Visual Basic
Przezroczystość Obiekty klasy Form udostępniają kilka własności pozwalających na sprawienie, by formularz był częściowo przezroczysty. Opacity określa nieprzezroczystość formularza. W czasie projektowania w oknie Properties własność ta jest prezentowana w jednostkach procentowych, gdzie 100% oznacza, że formularz jest całkowicie nieprzezroczysty, a 0%, że jest on całkowicie przezroczysty. W czasie działania programu Opacity musi być traktowana jako wartość zmiennoprzecinkowa, należąca do przedziału od 0.0 (całkowita przezroczystość) do 1.0 (całkowita nieprzezroczystość). Wartość własności Opacity niższa niż 100% pozwala użytkownikowi zobaczyć, co znajduje się pod formularzem. Da się na przykład utworzyć częściowo przezroczyste okno dialogowe wyszukiwania, aby w czasie trwania operacji szukania można było widzieć umieszczony pod nim dokument. Na rysunku 10.1 przedstawiono przykładowy program o nazwie SemiTransparent, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Aplikacja ta zawiera formularz z własnością Opacity ustawioną na 66%. Nadal widoczne jest obramowanie formularza, pasek tytułu, menu systemowe, pasek menu i przycisk, ale widać też okno IDE Visual Basica, prześwitujące spod spodu.
Rysunek 10.1. Formularz z własnością Opacity ustawioną na 66% pozwala zobaczyć znajdujące się pod nim okno IDE Visual Studio
Rozdział 10.
Formularze Windows
181
Jeśli wartość własności Opacity jest wyższa od 0%, formularz zachowuje się całkiem normalnie — pomijając jego wygląd. Użytkownik może go klikać, używać znajdujących się na nim kontrolek, minimalizować i maksymalizować oraz zmieniać jego rozmiar poprzez przeciąganie jego krawędzi. Jeśli własność Opacity ma wartość 0%, formularz jest całkowicie przezroczysty, a użytkownik komunikuje się z nim wyłącznie poprzez klawiaturę. Może na przykład przechodzić między kontrolkami, korzystając w tym celu z klawisza Tab, wpisywać tekst, wywoływać aktywny przycisk za pomocą spacji, a także naciskać klawisze Esc lub Cancel, aby nacisnąć przyciski Accept i Cancel formularza. Natomiast formularz nie wykrywa kliknięć myszy. Oczywiście użytkownik nie widzi formularza, przez co znalezienie aktywnej kontrolki graniczy z cudem. Wielu programistów nie czuje potrzeby używania przezroczystych formularzy. Dobrze zaprojektowana aplikacja pozwala na takie przesuwanie okien, by nie zasłaniały się one wzajemnie. Przezroczyste formularze mogą być nieczytelne, utrudniać życie użytkownikom ze specjalnymi wymaganiami i spowalniać szybkość działania programu. Są ciekawym efektem specjalnym, ale wiele osób uważa je za zbędny gadżet. Przykładowy program o nazwie TransparentForm, który można pobrać z serwera FTP wydawnictwa Helion, zawiera formularz z własnością Opacity ustawioną na 0, przez co nie widać go po uruchomieniu. Pomiędzy jego kontrolkami można nadal przemieszczać się za pomocą klawisza Tab i naciskać jego przyciski poprzez korzystanie ze spacji. Jeśli własność Opacity ma wartość 2%, formularz nadal jest niewidoczny, ale reaguje na mysz, a więc może pokrywać znajdujące się pod nim okna. Przykładowy program o nazwie CoverAll, również dostępny w internecie, wyświetla zmaksymalizowany formularz z własnością Opacity ustawioną na 2%. Zazwyczaj własność Opacity należy ustawiać na taką wartość, aby użytkownik mógł bez trudu dostrzec formularz. Czasami dobrze jest ustawić przezroczyste paski narzędzi, siatki własności i inne okna drugorzędne, dzięki czemu nie zasłonią one całkowicie formularza głównego. W takim przypadku ustawienie wartości Opacity poniżej 50% sprawia, że trudno odczytać zawartość formularza drugorzędnego, a powyżej 75% — że trudno dojrzeć formularz główny. Rozsądnym kompromisem wydaje się wartość około 66%. Druga własność, która umożliwia określenie przezroczystości formularza, to TransparencyKey. Jest to kolor, który informuje Visual Basic, jakie obszary na formularzu mają być całkowicie przezroczyste. Kiedy formularz jest wizualizowany, wszystkie obszary mające określony kolor tła nie są rysowane. Na rysunku 10.2 przedstawiono przykładowy program o nazwie Hole, który można pobrać z serwera FTP wydawnictwa Helion. Własność TransparencyKey jego formularza jest ustawiona na kolor czerwony. Ponieważ zarówno formularz, jak i etykieta Hole mają czerwone tło, są przezroczyste. Własność ForeColor etykiety jest ustawiona na kolor czarny, dzięki czemu tekst pozostał widoczny. Procedura obsługi zdarzeń Paint rysuje wewnątrz formularza czarną elipsę.
182
Część II
Wstęp do języka Visual Basic
Rysunek 10.2. Własność TransparencyKey formularza umożliwia tworzenie formularzy o różnych kształtach, jak na tej ilustracji
Przykładowy program GhostForm, który również można pobrać z serwera FTP wydawnictwa Helion, także posiada przezroczyste tło, dzięki czemu widoczne są tylko jego obramowanie i kontrolki. Własność TransparencyKey jest najczęściej używana do tworzenia formularzy o różnych kształtach lub skórach. Aby pozbyć się obramowania, paska tytułu i przycisków systemowych, należy ustawić własność FormBorderStyle na None. BackColor i TransparencyKey ustaw na kolory, których nie chcesz pokazać na formularzu. Następnie innym kolorem narysuj figurę, której kształt ma przybrać Twój formularz. Na rysunku 10.3 przedstawiono program Smiley, którego formularz ma kształt uśmiechniętej buźki. Procedura obsługi zdarzeń Paint tego formularza rysuje obraz pobrany z mapy bitowej. Za pomocą tego typu formularzy można tworzyć ciekawie wyglądające ekrany powitalne i okna dialogowe O programie. Są one jednak zbyt rozpraszające, aby mogły znaleźć szersze zastosowanie w głównym interfejsie programu. Zauważ, że formularz ten nie posiada paska tytułu, obramowania ani przycisków systemowych, przez co użytkownik nie może go przenieść, zmienić jego rozmiaru, zmaksymalizować go ani zamknąć. Gdy używa się tego formularza jako ekranu powitalnego, należy ustawić jego automatyczne zamknięcie po kilku sekundach za pomocą kontrolki Timer. Aby mógł służyć jako okno dialogowe typu O programie, trzeba dodać do niego przycisk zamykający okno.
Rozdział 10.
Formularze Windows
183
Rysunek 10.3. Własność TransparencyKey umożliwia tworzenie formularzy o różnych kształtach, na przykład takich jak ten
Jeśli własności Opacity i TransparencyKey zostaną użyte jednocześnie, piksele odpowiadające tej drugiej wartości zostaną całkowicie usunięte, a wszystkie pozostałe piksele będą pokazywane zgodnie z ustawieniem własności Opacity.
Okna O programie, ekrany powitalne i formularze logowania Własności TransparencyKey i Opacity umożliwiają tworzenie nietypowych i ciekawych kształtów formularzy. Gdyby zostały wykorzystane we wszystkich oknach aplikacji biznesowej, działałyby na użytkownika rozpraszająco. Jednak pozwalają one nieco urozmaicić ekrany powitalne, okna dialogowe O programie i formularze logowania. Wszystkie te trzy rodzaje formularzy mają ze sobą wiele wspólnego. Każdy z nich z reguły wyświetla nazwę aplikacji, numer jej wersji, informacje o prawach autorskich, znaku towarowym itd. Mogą także prezentować numer seryjny, nazwę zarejestrowanego użytkownika oraz adres witryny internetowej i numer telefonu, pod którym uzyska się pomoc.
184
Część II
Wstęp do języka Visual Basic
Różnica między nimi polega na sposobie ich zamykania. Ekran powitalny wyłącza się automatycznie po kilku sekundach. Okno dialogowe O programie zamyka użytkownik poprzez kliknięcie przycisku Ok. Formularz logowania zostaje wyłączony po wprowadzeniu prawidłowej nazwy użytkownika i hasła oraz kliknięciu przycisku Ok. Można go też zamknąć poprzez kliknięcie przycisku Anuluj, ale wtedy nie zostanie uruchomiona aplikacja główna. Czasami ekran powitalny jest wyświetlany, gdy aplikacja uruchamia się, ładuje potrzebne dane i wykonuje inne czynności przygotowawcze. W takim przypadku program zamyka ten ekran po zakończeniu swoich czynności lub po upływie określonej liczby sekund, w zależności od tego, która z tych dwóch sytuacji nastąpi jako druga. Wymienione formularze różnią się też rodzajem zawartych w nich kontrolek. Ekran powitalny musi posiadać kontrolkę Timer, która określa, kiedy ma zostać zamknięty. Okno dialogowe O programie musi mieć jeden przycisk Ok. Na formularzu logowania powinny znajdować się kontrolki TextBox, służące do wprowadzania nazwy użytkownika i hasła, dwie etykiety identyfikacyjne oraz przyciski Ok i Anuluj. Ekran powitalny i formularz logowania wyświetlają powitanie użytkownika, a więc nie ma sensu stosować obu ich naraz. Nadal jednak konieczne jest utworzenie dwóch bardzo podobnych formularzy — ekranu powitalnego i okna O programie lub formularza logowania i okna O programie. Przy dobrym rozplanowaniu można do utworzenia ekranu powitalnego, okna dialogowego O programie i formularza logowania wykorzystać jeden formularz. Odpowiednie kontrolki są dodawane w czasie działania programu. Można też utworzyć formularz z wszystkimi trzema zestawami kontrolek, po czym ukrywać te, które nie są w danej chwili potrzebne. Poniższy kod demonstruje sposób wyświetlania formularza jako ekranu powitalnego lub okna dialogowego O programie przez program SplashScreen, który można pobrać z serwera FTP wydawnictwa Helion. ' Wyświetla ekran powitalny. Public Sub ShowSplash() Me.tmrUnload.Enabled = True ' Kontrolka Timer zamyka okno. Me.TopMost = True ' Utrzymuje okno na wierzchu. Me.Show() ' Pokazuje okno w sposób niemodalny. End Sub ' Usuwa ekran powitalny. Private Sub tmrUnload_Tick() Handles tmrUnload.Tick Me.Close() End Sub ' Wyświetla formularz jako okno dialogowe O programie. Public Sub ShowAbout() btnOK.Visible = True ' Przycisk Ok zamyka to okno dialogowe. Me.ShowDialog() ' Pokazuje okno modalnie. End Sub ' Zamyka okno dialogowe O programie. Private Sub btnOK_Click() Handles btnOK.Click Me.Close() End Sub
Rozdział 10.
Formularze Windows
185
Ten formularz zawiera zarówno kontrolkę Timer o nazwie tmrUnload, jak i przycisk Ok o nazwie btnAboutOk. Metoda ShowSplash włącza kontrolkę Timer i wywołuje metodę Show, która wyświetla formularz. Własność Interval kontrolki Timer została ustawiona na 3000 milisekund, dzięki czemu po upływie trzech sekund zostanie uruchomione jej zdarzenie Timer, które zamyka formularz. Metoda ShowAbout uwidacznia przycisk btnOk i wywołuje metodę ShowDialog, która wyświetla modalnie formularz. Formularz modalny jest aktywny, a użytkownik nie może używać innych części aplikacji, dopóki to okno nie zostanie zamknięte. Kiedy użytkownik kliknie przycisk Ok, jego procedura obsługi zdarzeń Click zamknie ten formularz.
Kursory Własność Cursor formularza określa rodzaj widocznego na nim kursora. Klasa Form dziedziczy własność Cursor po klasie Control, w związku z czym inne kontrolki również ją posiadają. Aby ustawić wybrany kursor dla jakiejś kontrolki, należy odpowiednio określić jej własność Cursor. Jeśli na przykład użyje się kontrolki Label jako łącza hipertekstowego, będzie można sprawić, by kursor po najechaniu na nią zmieniał się w ikonę ręki, jak w przeglądarkach internetowych, co jest znakiem, że ma się do czynienia z odnośnikiem. Klasa Cursor udostępnia kilka standardowych kursorów. Na przykład poniższa instrukcja ustawia dla formularza domyślny kursor systemowy, czyli strzałkę skierowaną do góry i nieco w lewą stronę. Me.Cursor = Cursors.Default
Na rysunku 10.4 przedstawiono program o nazwie ShowCursors, który można pobrać z serwera FTP wydawnictwa Helion. Wyświetla on nazwy i ikony standardowych kursorów zdefiniowanych w klasie Cursor w systemie Windows Vista. We wcześniejszych wersjach tego systemu wartości AppStarting i WaitCursor wyświetlały ikonę klepsydry zamiast animowanych kół.
Rysunek 10.4. Klasa Cursor definiuje standardowe kursory
186
Część II
Wstęp do języka Visual Basic
Jeśli kontrolka nie ustawia jawnie swojego kursora, dziedziczy go po swoim kontenerze. Jeżeli zaś znajduje się bezpośrednio na formularzu, wyświetla kursor, który jest aktualnie pokazywany przez ten formularz. Oznacza to, że można za jednym razem ustawić kursor dla wszystkich kontrolek znajdujących się w kontenerze poprzez ustawienie kursora dla tego kontenera. Analogicznie — jeśli kontrolka znajduje się w kontrolce GroupBox, Panel lub innym kontenerze, dziedziczy jego kursor. Można ustawić kursor dla wszystkich kontrolek poprzez ustawienie kursora dla ich kontenera. Jednym z najczęstszych zastosowań kursorów jest wskazywanie, że aplikacja wykonuje jakieś działania. Kiedy program zaczyna zajmować się jakimś długotrwającym zadaniem, ustawia kursor na Cursors.WaitCursor, a gdy je zakończy, przywraca Cursors.Default. Poniżej znajduje się przykładowy kod: Me.Cursor = Cursors.WaitCursor ' Długie zadanie. ... Me.Cursor = Cursors.Default
Program UseWaitCursor, także dostępny na serwerze FTP, wyświetla kursor oczekiwania, kiedy zostanie kliknięty jego przycisk. Jeśli program składa się z więcej niż jednego formularza, musi ustawić kursor dla każdego z nich osobno. Można to zrobić ręcznie lub za pomocą pętli w kolekcji My.Application.OpenForms. Przedstawiona poniżej podprocedura SetAllCursors nieco ułatwia ustawianie kursora dla wszystkich formularzy: Private Sub SetAllCursors(ByVal the_cursor As Cursor) For Each frm As Form In My.Application.OpenForms frm.Cursor = the_cursor Next frm End Sub
Poniższy fragment programu wykorzystuje podprocedurę SetAllCursors podczas wykonywania długich zadań: SetAllCursors(Cursors.WaitCursor) ' Długie zadanie. ... SetAllCursors(Cursors.Default)
Program UseMultipleWaitCursors, również do pobrania z serwera FTP, wyświetla za pomocą podprocedury SetAllCursors kursor oczekiwania dla każdego formularza, kiedy zostanie kliknięty jego przycisk. Aby użyć niestandardowego kursora, należy utworzyć obiekt klasy Cursor za pomocą pliku lub zasobu zawierającego kursor lub ikonę. Następnie trzeba ustawić ten nowy obiekt jako wartość własności Cursor formularza. Poniższy kod ustawia kursor formularza z zasobu o nazwie SmileIcon.ico: Me.Cursor = New Cursor(My.Resources.SmileIcon.Handle)
Powyższy kod został użyty do wyświetlenia niestandardowego kursora w programie SmileCursor, który można pobrać z serwera FTP wydawnictwa Helion.
Rozdział 10.
Formularze Windows
187
Ikony Każdy formularz w aplikacji Visual Basica posiada własną ikonę. Jest ona widoczna po lewej stronie paska tytułu, w systemowym pasku zadań i w aplikacjach typu Menedżer zadań czy Eksplorator Windows. Niektóre z tych aplikacji wyświetlają ikony różnej wielkości. Na przykład Eksplorator Windows używa ikon o rozmiarach 32×32 piksele w widoku Duże ikony i 16×16 pikseli w pozostałych widokach. Ikony na paskach narzędzi mają wielkość 16×16, 24×24 lub 32×32 piksele. Dla wielu innych celów mogą one mieć w systemie Windows jeszcze inne rozmiary. Więcej informacji na temat różnych wielkości ikon używanych przez system Windows Vista znajduje się na stronie msdn2.microsoft.com/en-us/library/aa511280.aspx. Jeśli dany plik ikony nie udostępnia rozmiarów wymaganych przez system, Windows zmniejsza lub powiększa istniejący obraz. W ten sposób może powstać mało atrakcyjna wizualnie ikona. Aby uzyskać dobre wyniki, należy zawsze dostarczać grafikę w rozmiarach przynajmniej 16×16 i 32×32 piksele. W zależności od tego, jaki posiadasz system, być może trzeba będzie dostarczyć także inne rozmiary. Zintegrowany edytor ikon Visual Studio umożliwia definiowanie ikon dla różnych modeli kolorów, od monochromatycznego po 24-bitową głębię kolorów, a także w rozmiarach z przedziału 16×16 do 256×256 pikseli. Umożliwia nawet tworzenie ikon o niestandardowych rozmiarach, jak 32×48 pikseli, chociaż mało prawdopodobne, że system Windows będzie takich potrzebować. Aby włączyć ten edytor, otwórz okno Solution Explorer i kliknij w nim dwukrotnie pozycję My Project w celu otwarcia okna Project Properties. Kliknij kartę Resources, rozwiń listę Add Resource i kliknij pozycję Add New Icon. Za pomocą dostępnych narzędzi rysowania utwórz nową ikonę, a następnie kliknij ją prawym przyciskiem myszy i użyj opcji z podmenu Current Icon Image Types, aby ustawić jej różne rozmiary. Aby przypisać ikonę do formularza w czasie projektowania, otwórz okno projektanta formularzy i w oknie Properties zaznacz własność Icon. Kliknij znajdujący się po prawej stronie przycisk z obrazem wielokropka, a następnie wybraną ikonę. Aby przypisać ikonę do formularza w czasie działania programu, należy własność Icon formularza ustawić na obiekt klasy Icon. W poniższym kodzie własność ta została ustawiona na zasób ikony o nazwie MainFormIcon: Me.Icon = My.Resources.MainFormIcon
Niektóre aplikacje oznaczają swój stan poprzez modyfikację ikony. Na przykład program monitorujący jakiś proces może zmieniać jej kolor na czerwony, kiedy wykryje błąd, a nawet przełączać się pomiędzy dwiema ikonami, aby uzyskać wrażenie mrugania.
188
Część II
Wstęp do języka Visual Basic
Ikony aplikacji W systemie Windows ikona aplikacji jest wyświetlana w pasku tytułu formularza, pasku zadań i Menedżerze zadań. Programy (takie jak Eksplorator Windows), które traktują inne aplikacje jako całość, a nie poszczególne formularze, wyświetlają ikony przypisane właśnie do całych programów, a nie ich formularzy. Aby ustawić ikonę aplikacji, otwórz okno Solution Explorer i dwukrotnie kliknij pozycję My Project w celu otwarcia okna Project Properties. Przejdź do karty Application, rozwiń listę Icon i wybierz z niej odpowiedni plik ikony lub poszukaj go na dysku za pomocą opcji . Aby ustawić ikonę dla formularza, otwórz go w oknie projektanta formularzy. W oknie Properties kliknij własność Icon, a następnie znajdujący się po prawej stronie wielokropek i wybierz odpowiedni plik ikony. Pamiętaj, że ikony w różnych wymienionych zastosowaniach mają odmienne rozmiary. Na przykład ta z paska tytułu jest bardzo mała, natomiast znajdująca się w Menedżerze zadań — względnie duża. Jak pisałem w poprzednim podrozdziale, zintegrowany w Visual Studio edytor ikon pozwala na definiowanie obrazów dla różnych modeli kolorystycznych i rozmiarów w jednym pliku ikony.
Ikony powiadomienia Aplikacje Visual Basica mogą pokazywać jeszcze jeden rodzaj ikony za pomocą kontrolki NotifyIcon. Wyświetla ona ikonę w zasobniku systemowym. Zasobnik systemowy (czasami nazywany obszarem powiadomień) to niewielki obszar z małymi ikonami, z reguły umiejscowiony w dolnej prawej części paska zadań. Na rysunku 10.5 przedstawiono program UseNotifyIcon, który można pobrać z serwera FTP wydawnictwa Helion, i jego ikonę powiadomienia. Ikona sygnalizacji świetlnej, widoczna w pobliżu kursora, jest wyświetlana przez kontrolkę NotifyIcon. Po najechaniu na nią kursorem pojawia się chmurka z tekstem — w tym przypadku Zatrzymany. Program ten dodatkowo ustawia dla swojego formularza ikonę odpowiadającą ikonie NotifyIcon. Rysunek 10.5. Kontrolka NotifyIcon pozwala na wyświetlenie ikony stanu w zasobniku systemowym
Własność Icon określa ikonę wyświetlaną przez kontrolkę. Typowa aplikacja zmienia ją, odzwierciedlając w ten sposób stan programu. Na przykład program monitorujący obciążenie systemu może za pomocą ikony w zasobniku informować użytkownika o aktualnym obciążeniu. Ikony powiadomień są najbardziej przydatne w aplikacjach pozbawionych interfejsu użytkownika lub działających w tle, w których osoba korzystająca z komputera zazwyczaj nie ma kontaktu z ich formularzami.
Rozdział 10.
Formularze Windows
189
Ponadto ikony powiadomień często zawierają menu kontekstowe, które pojawia się w wyniku kliknięcia ich prawym przyciskiem myszy. Elementy takiego menu pozwalają użytkownikowi ustawiać niektóre opcje aplikacji. Bardziej szczegółowy opis kontrolki NotifyIcon znajduje się w Dodatku G.
Własności adoptowane przez kontrolki potomne Niektóre własności formularza są adoptowane przez kontrolki znajdujące się na nim. Na przykład domyślnie kontrolka Label przyjmuje kolor tła swojego formularza. Jeśli zmodyfikowana zostanie własność BackColor formularza, zmieni się też kolor tła znajdujących się na nim kontrolek Label. Niektóre własności przejmowane przez kontrolki potomne to BackColor, ContextMenu, Cursor, Enabled, Font i ForeColor. Jednak nie każda kontrolka używa wszystkich tych własności. Na przykład TextBox przejmuje po elemencie nadrzędnym tylko Enabled i Font. Jeśli któraś z tych własności zostanie ustawiona bezpośrednio dla jakiejś kontrolki, wartość ta będzie miała pierwszeństwo przed wartością elementu nadrzędnego. Jeśli na przykład określisz własność BackColor kontrolki Label jako kolor czerwony, tło tej kontrolki pozostanie czerwone, nawet jeśli barwa tła jej formularza będzie inna. Niektóre z tych własności nie są nawet zbyt przydatne dla samego obiektu klasy Form, ale wykorzystują je jego kontrolki. Na przykład formularz nie wyświetla automatycznie tekstu na swojej powierzchni, w związku z czym nigdy nie używa swojej własności Font. Ta ostatnia jest jednak dziedziczona przez znajdujące się na nim kontrolki Label, TextBox, ComboBox, List, RadioButton, CheckBox i wiele innych. Dzięki temu własność Font formularza pełni funkcję centralnego punktu ustawiania własności pisma dla wszystkich kontrolek. Jeśli zmieni się ona, nawet w czasie działania programu, zmodyfikowany zostanie wygląd wszystkich znajdujących się na formularzu kontrolek. Zmiana jest widoczna we wszystkich kontrolkach, także w umieszczonych w kontenerach GroupBox, Panel i innych, a więc tych, które nie rezydują bezpośrednio na samym formularzu. Własności te pozwalają także utrzymać spójny wygląd aplikacji — zarówno jeśli chodzi o kontrolki, jak i inne jej części. Poniższy kod za każdym razem drukuje na przykład łańcuch Witaj, świecie, gdy formularz musi zostać ponownie narysowany. Kod ten tworzy krój pisma o nazwie Comic Sans MS. Private Sub Form1_Paint(ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint Dim new_font As New Font("Comic Sans MS", 20) e.Graphics.DrawString("Witaj, świecie!", _ new_font, Brushes.Black, 10, 10) new_font.Dispose() End Sub
190
Część II
Wstęp do języka Visual Basic
Zamiast zmuszać różne części programu do tworzenia własnych czcionek, można użyć własności Font formularza, jak w poniższym kodzie. Dzięki temu kod jest prostszy, a różne jego części używają tej samej czcionki. Private Sub Form1_Paint(ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint e.Graphics.DrawString("Witaj, świecie!", Me.Font, Brushes.Black, 10, 100) End Sub
Miłym dodatkiem do zmiany wartości własności Font jest wyzwolenie zdarzenia Paint, dzięki czemu po zmianie własności pisma formularza kod ten zostaje automatycznie wykonany przez ponowne narysowanie tekstu za pomocą nowych własności. Program ChangeFormFont, który można pobrać z serwera FTP wydawnictwa Helion, posiada trzy przyciski radiowe i etykietę. Kiedy zostanie kliknięty jeden z tych przycisków, wygląd pisma na formularzu zmieni się; automatycznie zostanie zmodyfikowana również czcionka etykiety.
Metody przywracające ustawienia domyślne własności Klasa Form udostępnia kilka metod przywracających niektóre własności do ich domyślnych ustawień. Do najbardziej przydatnych z nich należą ResetBackColor, ResetCursor, ResetFont, ResetForeColor oraz ResetText. Jeśli zostanie zmieniona któraś z odpowiadających powyższym metodom własności formularza, czy to w czasie projektowania, czy działania programu, metody te przywrócą ją do wartości domyślnej. Wartości domyślne mogą być różne w różnych systemach. W moim komputerze w tej chwili własność BackColor jest przywracana do wartości Control, Cursor do Default, Font do ośmiopunktowej zwykłej (nie pogrubionej, ani pochylonej) czcionki Microsoft Sans Serif, ForeColor do ControlText, a Text — do pustego łańcucha. Ponieważ wiele z tych własności (wszystkie z wyjątkiem własności Text) jest przejmowanych przez znajdujące się na formularzu kontrolki, metody te przywracają ustawienia domyślne także tych kontrolek.
Przesłanianie procedury WndProc System operacyjny Windows wysyła do aplikacji różne komunikaty, które informują o zmianach w środowisku Windows. Nakazują one rysowanie, przesuwanie, skalowanie, ukrywanie, minimalizowanie i zamykanie formularzy, a także reagowanie na zmiany zachodzące w środowisku Windows. Wykonują też wszystkie inne czynności związane z tym środowiskiem.
Rozdział 10.
Formularze Windows
191
Każda aplikacja Windows posiada podprocedurę reagującą na te komunikaty. Zazwyczaj nosi ona nazwę WindowProc. W formularzach Visual Basica .NET procedura ta nazywa się WndProc. Można ją przesłonić, aby wykonywała odpowiednie specjalne akcje w odpowiedzi na docierające do formularza określone komunikaty. Program FixedAspectRatio, który można pobrać z serwera FTP wydawnictwa Helion, reaguje na komunikaty WM_SIZING. Kiedy znajdzie te komunikaty, ustawia nową wysokość i szerokość formularza, by stosunek między tymi parametrami był zawsze taki sam. Gdy przesłania się metodę WndProc, nie można zapomnieć, że nowo utworzona metoda musi wywoływać wersję tej metody, dostępną w klasie bazowej, jak w poniższej instrukcji: MyBase.WndProc(m)
Jeśli tego nie zrobisz, program nie będzie odpowiednio reagował na zdarzenia. Na przykład formularz nie będzie mógł poprawnie się narysować, zmienić swojego rozmiaru, a nawet się utworzyć. Gdy przesłania się metodę WndProc, trzeba także zdecydować, jakie komunikaty ma ona przechwytywać, jakie parametry komunikaty te mają przyjąć, a także co można zrobić, by bezpiecznie na nie działać. Jednym ze sposobów na zebranie informacji o komunikatach jest użycie poniższej metody WndProc i wykonanie czynności, którą chce się zbadać (na przykład zmienić wielkość formularza): Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) Debug.WriteLine(m.ToString) MyBase.WndProc(m) End Sub
Powyższy kod został wykorzystany do wyświetlenia informacji o odebranych komunikatach w programie ViewWindowsMessages, który można pobrać z serwera FTP wydawnictwa Helion. Poniższa instrukcja wyświetla wyniki dla komunikatu WM_SIZING, który został wysłany do formularza podczas zmieniania jego rozmiaru. Pokazuje ona co najmniej nazwę komunikatu (WM_SIZING) i jego wartość liczbową (szesnastkowa liczba 0x214). msg=0x214 (WM_SIZING) hwnd=0x30b8c wparam=0x2 lparam=0x590e29c result=0x0
Na stronach Microsoftu i innych witrynach poświęconych programowaniu można znaleźć dodatkowe cenne informacje na ten temat (na przykład: co znaczy m.WParam i m.LParam). Warto także zauważyć, że klasa Form dziedziczy podprocedurę WndProc po klasie Control, w związku z czym dziedziczą ją także wszystkie pozostałe kontrolki. Oznacza to, że można przesłonić także procedurę WndProc innych kontrolek, aby zmienić ich działanie. Poniższy przykładowy kod demonstruje działanie klasy NoCtxMnuTextBox. Kontrolka ta powstała na bazie kontrolki TextBox. Jej podprocedura WndProc wychwytuje komunikaty WM_CONTEXTMENU, a dla wszystkich pozostałych wywołuje metodę WndProc swojej klasy bazowej. Przez to, że kontrolka ta nie przetwarza komunikatu WM_CONTEXTMENU, nie wyświetla
192
Część II
Wstęp do języka Visual Basic
menu kontekstowego z opcjami Kopiuj, Wytnij i Wklej, które pokazuje się po kliknięciu jej prawym przyciskiem myszy. Public Class NoCtxMnuTextBox Inherits System.Windows.Forms.TextBox Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) Const WM_CONTEXTMENU As Integer = & H7B If m.Msg < > WM_CONTEXTMENU Then MyBase.WndProc(m) End If End Sub End Class
Podobny kod został wykorzystany w programie NoContextMenu, który można pobrać z serwera FTP wydawnictwa Helion. Nie wyświetla on menu kontekstowego po kliknięciu prawym przyciskiem myszy pola tekstowego.
SDI i MDI Aplikacje z interfejsem jednodokumentowym (ang. Single-Document Interface — SDI) w każdym formularzu wyświetlają jeden dokument. W tym przypadku dokumentem może być plik znajdujący się na dysku lub grupa powiązanych elementów, jak na zamówieniu, liście pracowników czy rysunku architektonicznym. Przykładami aplikacji SDI są Microsoft Paint i Notatnik. Na rysunku 10.6 przedstawiono aplikację SDI SDIEdit, którą można pobrać z serwera FTP wydawnictwa Helion. Wyświetla ona trzy pliki na osobnych formularzach. Rysunek 10.6. Aplikacja SDI wyświetla dokumenty na osobnych formularzach
Rozdział 10.
Formularze Windows
193
Natomiast aplikacja z interfejsem wielodokumentowym (ang. Multiple-Document Interface — MDI) również wyświetla dokumenty na osobnych formularzach, ale wszystkie je umieszcza w formularzu kontenerze. Na przykład Visual Studio może działać jak aplikacja MDI, czyli wyświetlać swoje formularze podrzędne (projektanci formularzy, edytory kodu itd.) na kartach. Poszczególne okna dokumentów nazywają się formularzami potomnymi MDI, a formularz kontener nosi nazwę kontenera MDI lub formularza nadrzędnego MDI. Na rysunku 10.7 przedstawiono aplikację MDI MDIEdit, którą można pobrać z serwera FTP wydawnictwa Helion. Posiada ona trzy formularze potomne MDI. Rysunek 10.7. Aplikacja MDI wyświetla dokumenty w formularzach znajdujących się w obrębie formularza kontenera
W kolejnych podrozdziałach opiszę niektóre cechy formularzy MDI. Podpowiem też, kiedy wybrać każdy z tych rodzajów aplikacji.
Aplikacje MDI Formularz kontener MDI świadczy kilka usług na rzecz swoich formularzy potomnych. Utrzymuje je w jednej grupie, dzięki czemu łatwo jest je znaleźć. Jeśli któryś z tych formularzy zostanie przesunięty w taki sposób, że nie zmieści się w formularzu nadrzędnym, kontener automatycznie wyświetli paski przewijania. Program tego typu wyświetla w pasku zadań i Menedżerze zadań ikonę kontenera MDI, ale nie formularzy potomnych. Jeśli kontener MDI zostanie zminimalizowany, znajdujące się w nim formularze zostaną ukryte razem z nim. Jeżeli jeden z formularzy potomnych MDI zostanie zmaksymalizowany, wypełni on cały formularz nadrzędny, a jego tytuł stanie się częścią tytułu tego formularza. Jeśli na przykład tytułem formularza nadrzędnego jest Rodzic,
194
Część II
Wstęp do języka Visual Basic
a tytuł formularza podrzędnego to Dziecko, po zmaksymalizowaniu formularza podrzędnego tytuł formularza nadrzędnego będzie brzmiał Rodzic – [Dziecko]. Kontener MDI udostępnia także kilka metod służących do aranżacji jego formularzy potomnych. Poniższy kod demonstruje takie kaskadowe ustawienie formularzy podrzędnych, że nakładają się one na siebie i ustawiają w pionie lub poziomie. Pokazuje też aranżację ikon wszystkich zminimalizowanych formularzy: Private Sub mnuWinCascade_Click() Handles mnuWinCascade.Click Me.LayoutMdi(MdiLayout.Cascade) End Sub Private Sub mnuWinTileVertical_Click() Handles mnuWinTileVertical.Click Me.LayoutMdi(MdiLayout.TileVertical) End Sub Private Sub mnuWinTileHorizontal_Click() Handles mnuWinTileHorizontal.Click Me.LayoutMdi(MdiLayout.TileHorizontal) End Sub Private Sub mnuWinArrangeIcons_Click() Handles mnuWinArrangeIcons.Click Me.LayoutMdi(MdiLayout.ArrangeIcons) End Sub
Inne przydatne polecenia, które można dodać do aplikacji MDI, to Minimalizuj wszystkie (ang. Minimize All), Przywróć wszystkie (ang. Restore All), Maksymalizuj wszystkie (ang. Maximize All) i Zamknij wszystkie (ang. Close All). Można je zaimplementować za pomocą pętli przechodzącej przez kolekcję MdiChildren kontenera MDI, jak w poniższym kodzie: Private Sub mnuWinMinimizeAll_Click() Handles mnuWinMinimizeAll.Click For Each frm As Form In Me.MdiChildren frm.WindowState = FormWindowState.Minimized Next frm End Sub Private Sub mnuWinRestoreAll_Click() Handles mnuWinRestoreAll.Click For Each frm As Form In Me.MdiChildren frm.WindowState = FormWindowState.Normal Next frm End Sub Private Sub mnuWinMaximizeAll_Click() Handles mnuWinMaximizeAll.Click For Each frm As Form In Me.MdiChildren frm.WindowState = FormWindowState.Maximized Next frm End Sub Private Sub mnuWinCloseAll_Click() Handles mnuWinCloseAll.Click For Each frm As Form In Me.MdiChildren frm.Close() Next End Sub
W niektórych aplikacjach można by również dostarczyć polecenia dla podzbiorów formularzy potomnych. Załóżmy, że program wyświetla główny rekord zamówienia i wiele powiązanych z nim elementów w formularzach potomnych MDI. Może być konieczne zezwolenie
Rozdział 10.
Formularze Windows
195
użytkownikowi na zamknięcie wszystkich elementów zamówienia przy pozostawieniu otwartego głównego formularza zamówienia. Wiele aplikacji MDI posiada menu Okno (ang. Window), które zawiera listę otwartych formularzy potomnych MDI. Poprzez kliknięcie jednego z elementów tej listy można przenieść odpowiadający mu formularz na wierzch przed pozostałe. Tworzenie listy potomków w Visual Basicu jest bardzo łatwe. Zaznacz kontrolkę MenuStrip. W oknie Properties ustaw własność MdiWindowListItem tej kontrolki na menu, które ma zawierać tę listę. Visual Basic automatycznie będzie ją aktualizować podczas otwierania i zamykania okien potomnych. Na rysunku 10.8 przedstawiono menu Okno z listą potomków MDI. W tej chwili aktywny jest formularz MDIEdit.sln (pod menu), dlatego obok niego na liście znajduje się haczyk. Rysunek 10.8. Własność MdiWindowListItem kontrolki MenuStrip określa, który element menu będzie wyświetlać listę potomków MDI
Większość typowych aplikacji Visual Basica wykorzystuje technologię SDI, dlatego podczas tworzenia nowego programu domyślnie powstaje aplikacja SDI. Aby otrzymać aplikację MDI, utwórz w normalny sposób nowy program, a następnie ustaw własność IsMdiContainer formularza początkowego na wartość True. Jego wygląd w projektancie formularzy zmieni się, dzięki czemu będzie wiadomo, że jest to formularz nadrzędny MDI. Można też kliknąć w menu Project polecenie Add Windows Form. W oknie dialogowym nowego formularza zaznacz element MDI Parent Form, nadaj temu formularzowi własną nazwę i kliknij przycisk Add. Visual Basic utworzy nowy formularz nadrzędny MDI wraz z zestawem standardowych kontrolek, które mogą być przydatne, takich jak pasek menu ze standardowymi menu (File, Edit, View itd.) oraz pasek narzędzi ze standardowymi narzędziami (new, open, save itd.).
196
Część II
Wstęp do języka Visual Basic
W czasie projektowania formularz MDI wygląda jak każdy inny formularz. Aby zmusić formularz podrzędny do rezydowania wewnątrz kontenera MDI, należy w czasie działania programu ustawić jego własność MdiParent na ten właśnie kontener. Poniżej znajduje się kod, który demonstruje, jak formularz nadrzędny MDI z rysunku 10.7 tworzy nowe formularze podrzędne MDI. Gdy użytkownik kliknie polecenie Otwórz w menu Plik lub narzędzie Otwórz na pasku narzędzi, ta procedura obsługi zdarzeń wyświetli okno dialogowe otwierania pliku. Jeśli zaznaczy się jakiś plik i kliknie przycisk Otwórz, zostanie utworzony nowy obiekt Form1. Ładuje on wybrany przez użytkownika plik do kontrolki TextBox o nazwie txtContents, ustawia tytuł formularza na nazwę wybranego pliku (bez ścieżki), własność MdiParent tego formularza na Me (formularz nadrzędny MDI), po czym wyświetla go. Formularz zostaje natychmiast pokazany w kontenerze MDI oraz dodany do listy potomków. Private Sub OpenFile() Handles mnuFileOpen.Click, toolOpen.Click If dlgOpen.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then Dim frm As New Form1 frm.FileName = dlgOpen.FileName frm.txtContents.Text = _ My.Computer.FileSystem.ReadAllText(dlgOpen.FileName) frm.txtContents.Select(0, 0) frm.Text = New FileInfo(dlgOpen.FileName).Name frm.MdiParent = Me frm.Show() End If End Sub
Zazwyczaj menu systemowe dostępne z lewej strony paska tytułu formularza zawiera polecenie Zamknij ze skrótem klawiszowym Alt+F4. Zamyka ono formularz. Menu systemowe formularza podrzędnego MDI również zawiera polecenie Zamknij, ale jego skrótem klawiszowym jest Ctrl+F4. Kliknięcie go lub naciśnięcie odpowiadającej mu kombinacji klawiszy spowoduje zamknięcie tylko odpowiedniego formularza podrzędnego, kontener MDI pozostanie otwarty. Menu systemowe formularza podrzędnego MDI zawiera też polecenie Następny, które aktywuje kolejny formularz podrzędny w kontenerze MDI. Jego skrót klawiszowy to Ctrl+F6, chociaż działa też kombinacja Ctrl+Tab. Ten drugi skrót może być łatwiejszy do zapamiętania, ponieważ jest podobny do kombinacji Alt+Tab, która przenosi do kolejnej otwartej aplikacji. Ponadto lepiej zgadza się ze skrótami zamykającymi formularze — Alt+F4 zamyka formularz najwyższego poziomu, natomiast skrót Ctrl+F4 — potomka MDI. Skrót Alt+Tab przenosi do kolejnej aplikacji, a Ctrl+Tab — do kolejnego formularza potomnego MDI.
Zdarzenia MDI Zdarzenia formularza potomnego MDI zazwyczaj mają miejsce przed odpowiednimi zdarzeniami formularza nadrzędnego MDI. Jeśli na przykład użytkownik spróbuje zamknąć formularz MDI, zdarzenie FormClosing zostanie odebrane przez wszystkie formularze podrzędne, zanim dotrze do formularza nadrzędnego. Następnie formularze podrzędne odbierają zdarzenie FormClosed, a na końcu swoje zdarzenie FormClosed odbiera formularz nadrzędny.
Rozdział 10.
Formularze Windows
197
Warto zauważyć, że formularze podrzędne MDI odbierają te zdarzenia, gdy zamykany jest formularz podrzędny. Jeśli użytkownik zamyka formularz podrzędny MDI, odbiera on zdarzenie FormClosing, po którym następuje FormClosed. Jeśli procedura obsługi zdarzeń FormClosing formularza ustawi swój parametr e.Cancel na wartość True, zamykanie będzie anulowane i formularz pozostanie otwarty. Technika ta pozwala na zapewnienie, że dane formularza są spójne i zostały zapisane. Na przykład poniższy kod sprawdza własność Modified kontrolki txtContents, aby dowiedzieć się, czy dane zawarte w formularzu zmieniły się od chwili jego załadowania. Program ustawia tę własność na wartość False, gdy plik został otwarty albo utworzony od nowa, lub na True, jeśli użytkownik zmodyfikował tekst formularza. W przypadku wartości True aplikacja wyświetla okno dialogowe z zapytaniem, czy zapisać zmiany. ' Sprawdzanie, czy można bezpiecznie zamknąć formularz. Private Sub mdiChild_FormClosing(ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing If txtContents.Modified Then ' Są niezapisane zmiany. ' Trzeba zapytać użytkownika, czy mają one zostać zapisane. Select Case MessageBox.Show( _ "Dane zostały zmienione. Zapisać zmiany?", _ "Zapisać zmiany?", _ MessageBoxButtons.YesNoCancel, _ MessageBoxIcon.Question) Case Windows.Forms.DialogResult.Yes ' Zapisanie zmian. SaveFile() ' Sprawdzenie, czy operacja zakończyła się powodzeniem. e.Cancel = txtContents.Modified Case Windows.Forms.DialogResult.No ' Odrzucenie zmian. ' Pozostawienie parametru e.Cancel = False. Case Windows.Forms.DialogResult.Cancel ' Anulowanie zamknięcia. e.Cancel = True End Select End If End Sub
Kiedy użytkownik kliknie przycisk Tak, program wywoła zapisującą zmiany podprocedurę o nazwie SaveFile. Zachowuje ona dane i ustawia własność Modified kontrolki txtContents na False, jeśli operacja zakończy się powodzeniem. Jeżeli zaś operacja zakończy się niepowodzeniem (jeśli na przykład plik z danymi jest zablokowany), pozostawi własność Modified ustawioną na True. Jeśli użytkownik kliknie przycisk No, zamierzając odrzucić zmiany, procedura obsługi zdarzeń FormClosing pozostawi ustawienie parametru e.Cancel na False, dzięki czemu formularz
zostanie zamknięty w normalny sposób. Jeśli użytkownik kliknie przycisk Cancel, informując, że jednak nie chce zamknąć formularza, procedura obsługi zdarzeń ustawi parametr e.Cancel na True, aby pozostawić formularz otwarty.
198
Część II
Wstęp do języka Visual Basic
Jeśli użytkownik spróbuje zamknąć kontener MDI i procedura obsługi zdarzeń FormClosing któregokolwiek formularza podrzędnego ustawi parametr e.Cancel na True, zamykanie zostanie anulowane dla wszystkich formularzy potomnych. Formularze potomne, które nie odebrały jeszcze zdarzenia FormClosing, nie odbiorą go. Wszystkie formularze potomne pozostaną otwarte, nawet te, które ustawiły parametr e.Cancel na False. Po przetworzeniu przez formularze podrzędne zdarzeń ClosingForm formularz nadrzędny nadal będzie miał ostatnie słowo. Odbierze zdarzenie FormClosing z parametrem e.Cancel ustawionym na True, jeśli którykolwiek z formularzy podrzędnych ustawi go na True. Wartość e.Cancel tylko wtedy może być False, gdy wszystkie formularze podrzędne pozostawią jego wartość False. Formularz nadrzędny MDI może pozostawić parametr e.Cancel w spokoju, przyjmując wartość wybraną przez formularze podrzędne. Może też przesłonić tę wartość i zmusić program do zakończenia. Formularze podrzędne nadal mają szansę na zapisanie swoich danych w zdarzeniach ClosingEvent. W tym jednak przypadku zostaną zamknięte, a więc lepiej by było, gdyby
podjęły jakieś działania, jeśli chcą zachować dane.
MDI a SDI Zarówno aplikacje MDI, jak i SDI mają swoje zalety. W tych drugich łatwiej jest opanować tworzenie menu. Menu odnosi się do dokładnie jednego formularza i nie ma potrzeby scalania ani zamieniania go, tak jak w przypadku zmiany przez użytkownika formularzy podrzędnych MDI. Aplikacje SDI są szczególnie przydatne, kiedy program musi mieć otwarty tylko jeden dokument naraz. Przykładowymi programami tego typu są Notatnik, Microsoft Paint i wiele innych, które pozwalają na pracę z tylko jednym dokumentem naraz. Aplikacje te są na tyle lekkie, że użytkownik może w razie potrzeby uruchomić kilka ich egzemplarzy. Aplikacje MDI pozwalają na wyświetlenie wielu podobnych plików naraz, przy czym jednocześnie pulpit nie zostaje zaśmiecony. Na przykład w Visual Studio dzięki interfejsowi MDI można otworzyć wszystkie pliki projektu jeden obok drugiego. Otwarcie projektantów formularzy, edytorów kodu, edytorów zasobów i innych plików w osobnych oknach spowodowałoby całkowite przykrycie pulpitu i zapełnienie ikonami paska zadań. Umieszczenie tych wszystkich formularzy w kontenerze MDI ułatwia pracę w programie. Całe oprogramowanie Visual Studio jest reprezentowane w systemie przez jeden formularz kontener i jedną ikonę. Menu Window zawiera listę formularzy podrzędnych MDI, na której można łatwo znaleźć potrzebną pozycję. Można także tworzyć aplikacje hybrydowe, wyświetlające kilka kontenerów MDI, z których każdy przechowuje dowolną liczbę formularzy podrzędnych. Na przykład każdy z tych kontenerów mógłby zawierać formularze związane z konkretnym zamówieniem — dane klienta, zamówione towary itd. Dzięki temu te powiązane ze sobą elementy pozostałyby zgrupowane. Dodatkowo użytkownik miałby możliwość wyświetlenia informacji o więcej niż jednym zamówieniu w osobnych kontenerach MDI.
Rozdział 10.
Formularze Windows
199
W praktyce takie hybrydy okazują się zazwyczaj problematyczne i słabo zaprojektowane. Łatwiej byłoby utworzyć standardową aplikację MDI i pozwolić użytkownikowi na uruchomienie kilku egzemplarzy tego programu, aby mógł wyświetlić dane paru zamówień. Czasami jednak łatwiejsze może być utworzenie jednej aplikacji z wieloma formularzami MDI. Jeśli na przykład program ma współpracować z chronioną hasłem bazą danych, musiałby on poprosić użytkownika o podanie nazwy użytkownika i hasła tylko jeden raz, a wszystkie kontenery MDI dzieliłyby to jedno połączenie między sobą. Często można uniknąć konieczności tworzenia wielu formularzy (i w związku z tym formatu MDI) poprzez użycie innych kontrolek, które pozwolą zmieścić więcej informacji tylko na jednym. Na przykład takie kontrolki, jak ComboBox, ListBox, TreeView, SplitterContainer i wiele innych mogą wyświetlać duże ilości danych na małej powierzchni, w razie potrzeby włączając paski przewijania. Kontrolka TabControl pozwala na wyświetlenie kilku stron danych w jednym formularzu. Można na przykład na poszczególnych kartach umieścić różne strony zawierające informacje, które dotyczą zamówienia — dane klienta, samo zamówienie, zamówione towary, adres wysyłki towaru i dane dotyczące płatności itd. Za pomocą tego rodzaju formularzy z kartami umieszczonych w kontenerach MDI można tworzyć bardzo potężne aplikacje, które umożliwiają użytkownikowi łatwe zarządzanie bardzo dużymi ilościami danych. Jedną z wad tych kontrolek jest to, że utrudniają one porównywanie danych wyświetlonych obok siebie. Wyobraźmy sobie na przykład, że jeden formularz pokazuje poszczególne adresy (do wysyłki, kontaktowy itp.) na różnych kartach. W takiej sytuacji porównanie ich w celu sprawdzenia, czy są takie same, jest utrudnione. Jeśli podejrzewasz, że użytkownik mógłby chcieć porównać jakieś dwa zestawy danych, postaraj się tak rozplanować układ aplikacji, aby informacje te można było wyświetlić obok siebie.
Listy ostatnio używanych plików Interfejsy MDI i SDI umożliwiają zarządzanie dokumentami na różne sposoby. Innym narzędziem pomagającym użytkownikowi w zarządzaniu plikami jest lista ostatnio używanych plików (ang. Most Recently Used list — MRU list). Przedstawia ona zbiór elementów menu (zazwyczaj w dolnej części menu Plik), które reprezentują ostatnio otwierane przez użytkownika pliki. Kliknięcie jednego z nich powoduje ponowne otwarcie odpowiedniego pliku. Na rysunku 10.9 przedstawiono taką listę w prostej aplikacji do edycji plików. Rysunek 10.9. Lista ostatnio używanych plików ułatwia ponowne otwieranie plików, które były niedawno używane
200
Część II
Wstęp do języka Visual Basic
Zgodnie z konwencją przed tymi elementami znajdują się znaki akceleracyjne 1, 2, 3 itd. Gdybyśmy otworzyli menu Plik z rysunku 10.9 i nacisnęli na przykład klawisz 2, program otworzyłby plik MruList.vb. Kiedy użytkownik otwiera nowy plik lub zapisuje istniejący pod nową nazwą, plik ten zostaje umieszczony na górze listy. Większość aplikacji wyświetla w tym spisie do czterech elementów. Kiedy użytkownik otworzy więcej plików, najstarsze zostaną usunięte. Jeśli otwarcie pliku z listy ostatnio używanych nie powiedzie się, większość aplikacji wyrzuci go z tej listy. Jeżeli na przykład użytkownik wybierze z listy element, którego plik został usunięty z dysku, program wyrzuci ten element z listy ostatnio używanych. Budowanie listy ostatnio używanych plików w Visual Basicu nie jest trudne. Widoczny na rysunku 10.9 program MruList, który można pobrać z serwera FTP wydawnictwa Helion, tworzy ten spis za pomocą klasy MruList. Zarządza ona menu, w którym ma być wyświetlana lista ostatnio używanych plików, i aktualizuje je, gdy użytkownik otwiera i zamyka poszczególne z nich. Jeśli na przykład konfiguracja tej klasy zezwala na przechowywanie czterech elementów na liście, a użytkownik otworzy piąty plik, najstarsza pozycja zostanie usunięta, a zastąpi ją nowa. Klasa ta zapisuje i pobiera listę ostatnio używanych plików z rejestru systemu. Kiedy użytkownik klika pozycję na tej liście, klasa ta uruchamia zdarzenie, dzięki któremu program główny może otworzyć odpowiedni plik. Dodatkowo w klasie tej dostępna jest metoda Add, za pomocą której aplikacja w chwili otwierania pliku przez użytkownika dodaje go do listy ostatnio używanych. Więcej szczegółów znajdziesz w samym kodzie programu, który można pobrać z internetu. Poniższy kod demonstruje sposób użycia klasy MruList przez program główny MruList. Jest to prosta przeglądarka plików tekstowych, pozwalająca na otwieranie i przeglądanie plików. W programie tym została zadeklarowana zmienna typu MruList o nazwie m_MruList. Dzięki użyciu słowa kluczowego WithEvents łatwo można przechwycić zdarzenie OpenFile tego obiektu. Obiekt klasy MruList jest inicjowany przez procedurę obsługi zdarzeń New, przekazującą do niego nazwę aplikacji, menu Plik oraz liczbę elementów, które mają być dostępne na liście. Kiedy użytkownik kliknie znajdujące się w menu Plik polecenie Otwórz, program wyświetli okno dialogowe otwierania pliku. Jeżeli zaznaczy się jakiś plik i kliknie przycisk Otwórz, aplikacja wywoła podprocedurę OpenFile poprzez przekazanie do niej nazwy zaznaczonego pliku. Jeśli użytkownik kliknie jeden z plików wymienionych na liście ostatnio używanych plików, wykonana zostanie procedura obsługi zdarzeń m_MruList_OpenFile, która wywołuje podprocedurę OpenFile poprzez przekazanie jej nazwy klikniętego pliku. Podprocedura OpenFile ładuje treść tego pliku do kontrolki TextBox o nazwie txtContents. Następnie wywołuje metodę Add obiektu klasy MruList poprzez przekazanie do niej nazwy pliku. Na zakończenie ustawia tytuł formularza na nazwę pliku bez ścieżki.
Rozdział 10.
Formularze Windows
201
Imports System.IO Public Class Form1 Private WithEvents m_MruList As MruList ' Inicjacja listy ostatnio używanych plików. Private Sub Form1_Load() Handles Me.Load m_MruList = New MruList("SdiMruList", mnuFile, 4) End Sub ' Pozwala użytkownikowi otworzyć plik. Private Sub mnuFileOpen_Click() Handles mnuFileOpen.Click If dlgOpen.ShowDialog() = Windows.Forms.DialogResult.OK Then OpenFile(dlgOpen.FileName) End If End Sub ' Otwiera plik wybrany z listy ostatnio używanych plików. Private Sub m_MruList_OpenFile(ByVal file_name As String) _ Handles m_MruList.OpenFile OpenFile(file_name) End Sub ' Otwiera plik i dodaje go do listy ostatnio używanych plików. Private Sub OpenFile(ByVal file_name As String) txtContents.Text = File.ReadAll(file_name) txtContents.Select(0, 0) m_MruList.Add(file_name) Me.Text = "[" & New FileInfo(file_name).Name End Sub End Class
&
"]"
Klasę MruList można łatwo przekonwertować na komponent. Jeśli temu ostatniemu zostaną nadane własności ApplicationName, FileMenu i MaxEntries, będzie można ustawiać ich wartości w czasie działania programu. Więcej informacji na temat tworzenia komponentów znajduje się w rozdziale 22. — „Tworzenie niestandardowych kontrolek”.
Okna dialogowe Formularz można łatwo wykorzystać jako okno dialogowe. W tym celu utwórz go i umieścić na nim wszystkie potrzebne kontrolki. Dodaj jeden lub więcej przycisków pozwalających na anulowanie okna. Wiele okien dialogowych posiada przyciski Ok i Anuluj, ale można też użyć tych z etykietami Tak, Nie, Ponów itp. Istnieje też możliwość ustawienia własności FormBorderStyle na FixedDialog, chociaż nie jest to obowiązkowe. Własność AcceptButton formularza ustaw na przycisk mający być aktywowanym przez naciśnięcie klawisza Enter, natomiast CancelButton — na ten, który ma być aktywowany przez naciśnięcie klawisza Esc. Własność DialogResult formularza określa wartość zwrotną okna dialogowego. Jeśli program główny wyświetla okno dialogowe za pomocą jego metody ShowDialog, metoda ta zwraca właśnie wartość DialogResult.
202
Część II
Wstęp do języka Visual Basic
Poniższy kod demonstruje wyświetlenie przez główny program okna dialogowego i reakcję na jego wynik. Aplikacja ta tworzy nowy egzemplarz formularza dlgEmployee i wyświetla go za pomocą wywołania metody ShowDialog. Jeśli użytkownik kliknie przycisk Ok, metoda ShowDialog zwróci wartość DialogResult.OK, a program wyświetli wpisane w oknie imię i nazwisko pracownika. Jeżeli zaś kliknięty zostanie przycisk Anuluj, metoda ShowDialog zwróci wartość DialogResult.Cancel, a program pokaże komunikat Operacja anulowana. Private Sub btnShowDialog_Click() Handles btnShowDialog.Click Dim dlg As New dlgEmployee If dlg.ShowDialog() = Windows.Forms.DialogResult.OK Then MessageBox.Show( _ dlg.txtFirstName.Text & " " & _ dlg.txtLastName.Text) Else MessageBox.Show("Operacja anulowana") End If End Sub
Jeśli użytkownik kliknie przycisk Anuluj lub zamknie formularz za pośrednictwem menu systemowego, formularz ten automatycznie ustawi swoją własność DialogResult na wartość Cancel, po czym zostanie zamknięty. Jeżeli użytkownik kliknie jakiś inny przycisk, procedura obsługi zdarzeń powinna ustawić własność DialogResult na odpowiednią wartość. Wtedy formularz zostanie automatycznie zamknięty. Własność DialogResult można ustawić także dla przycisku, aby określała wartość zwracaną przez okno dialogowe, kiedy użytkownik kliknie go. Gdy klika się ten przycisk, Visual Basic automatycznie ustawia własność DialogResult formularza. Poniższy kod pokazuje, jak formularz przyjmujący dane pracownika reaguje na kliknięcie przez użytkownika przycisku Ok. Najpierw sprawdza, czy kontrolki TextBox, przyjmujące imię i nazwisko, nie są puste. Jeśli któraś z nich jest pusta, procedura obsługi zdarzeń wyświetli komunikat o błędzie i zwróci wartość, nie ustawiając własności DialogResult formularza. Jeżeli obie zawierają dane, program ustawi własność DialogResult na wartość OK, co spowoduje zamknięcie formularza. Warto zauważyć, że okno to nie potrzebuje procedury obsługi zdarzeń dla przycisku Anuluj. Jeśli użytkownik kliknie ten przycisk, Visual Basic automatycznie ustawi własność DialogResult formularza na wartość Cancel, po czym zamknie go. Private Sub btnOk_Click() Handles btnOk.Click ' Sprawdzenie, czy imię zostało wpisane. If txtFirstName.Text.Length = 0 Then MessageBox.Show( _ "Podaj imię", _ "Musisz podać imię", _ MessageBoxButtons.OK, _ MessageBoxIcon.Exclamation) txtFirstName.Select() Exit Sub End If ' Sprawdzenie, czy zostało podane nazwisko. If txtLastName.Text.Length = 0 Then MessageBox.Show( _ "Podaj nazwisko", _ "Musisz podać nazwisko", _
Rozdział 10.
Formularze Windows
203
MessageBoxButtons.OK, _ MessageBoxIcon.Exclamation) txtLastName.Select() Exit Sub End If ' Akceptacja okna dialogowego. Me.DialogResult = Windows.Forms.DialogResult.OK End Sub
Opisane okno dialogowe zostało wykorzystane w programie CustomDialog, który można pobrać z serwera FTP wydawnictwa Helion. Wiele okien dialogowych posiada przyciski Ok i Anuluj, w związku z czym zazwyczaj ustawiają one własność DialogResult na wartość OK lub Cancel. Może ona jednak przyjmować także inne wartości, jak Abort, Ignore, No, None, Retry i Yes. Aby sprawdzić, która z nich została ustawiona, można użyć w programie instrukcji If Then lub Select Case.
Kreatory Jednym z często spotykanych rodzajów okien dialogowych jest kreator. Jest to formularz przeprowadzający użytkownika przez szereg czynności związanych z jakimś złożonym zadaniem. Na przykład skomplikowane jest tworzenie połączenia z bazą danych, dlatego Visual Basic udostępnia kreator konfiguracji połączenia z bazą danych, który pomaga użytkownikowi wprowadzić poprawne dane dla różnych rodzajów baz danych. Po zakończeniu pracy narzędzie dodaje obiekt połączenia z bazą danych do bieżącego formularza. Na rysunku 10.10 przedstawiono typowy kreator. Użytkownik wprowadza dane na każdej z kart po kolei. Narzędzie prosi o podanie imienia i nazwiska pracownika, danych identyfikacyjnych (numer ubezpieczenia i identyfikator pracowniczy), adresu i numeru telefonu, lokalizacji biura i numeru wewnętrznego, a także o określenie stanowiska. Wiele składających się z kart kreatorów zawiera także przyciski Dalej i Wstecz, które ułatwiają nawigację pomiędzy kartami. Kiedy użytkownik wypełni wszystkie pola, kreator uaktywni przycisk Ok. Gdy kliknie się ten przycisk lub przycisk Anuluj, kontrolka zwróci wartość do programu głównego, który ten zwrócony wynik potraktuje tak samo jak wynik każdego innego okna dialogowego. Na rysunku 10.11 przedstawiono inny rodzaj okna kreatora. Zamiast kart zawiera on przyciski, za pomocą których użytkownik przechodzi do kolejnych stron. Kreator ten dopiero wówczas uaktywnia kolejny przycisk, gdy wprowadzi się wszystkie wymagane dane na poprzedniej stronie. Na rysunku 10.11 przycisk Biuro jest nieaktywny, ponieważ użytkownik nie wypełnił jeszcze wszystkich pól na stronie Adres. Czasami kreator z przyciskami lepiej sprawdza się od kreatora z kartami, ponieważ aby aktywował się kolejny przycisk, użytkownik musi podać wszystkie wymagane wcześniej dane. W kreatorze z kartami można na samym początku pozostawić puste pole lub wprowadzić dane w nieprawidłowym formacie (na przykład niepoprawny numer telefonu), a dowiedzieć się o tym dopiero po naciśnięciu przycisku Ok.
204
Część II
Wstęp do języka Visual Basic
Rysunek 10.10. Kreator przeprowadza użytkownika przez złożony proces
Rysunek 10.11. Kreator z przyciskami zamiast kart do nawigacji po stronach
Podsumowanie Mimo że formularze są tylko jednym z rodzajów kontrolek, mają kilka cech charakterystycznych. Stanowią podstawowy składnik aplikacji oraz posiadają wiele własności, metod i zdarzeń, których nie mają inne kontrolki. Więcej informacji na temat metod, własności i zdarzeń formularzy znajduje się w Dodatku J. W tym rozdziale opisano niektóre najbardziej typowe zastosowania formularzy. Objaśniono sposoby tworzenia ekranów powitalnych, okien O programie i formularzy logowania, kontrolowanie kursora myszy i ikon, przesłanianie metody WndProc w celu przechwytywania komunikatów Windows, tworzenie aplikacji MDI i narzędzi pomagających użytkownikowi zarządzać formularzami podrzędnymi MDI oraz tworzyć okna dialogowe i kreatorów. Po opanowaniu tych umiejętności można budować formularze implementujące większe części programu. W rozdziałach 8., 9. i 10. znajdują się opisy kontrolek Windows Forms i klasy Form. Kolejne trzy zawierają analogiczne informacje o kontrolkach i formularzach Windows Presentation Foundation (WPF). W rozdziale 11. — „Wybieranie kontrolek WPF” — znajdziesz ogólną charakterystykę kontrolek WPF i wskazówki, których z nich używać do określonych celów, podobnie jak rozdział 8. zawierał podobne informacje na temat kontrolek Windows Forms.
11
Wybieranie kontrolek WPF Jednym z najważniejszych dodatków w Visual Basicu 2008 jest biblioteka Windows Presentation Foundation (WPF). Pozwala ona na całkowitą zmianę filozofii tworzenia interfejsów użytkownika. Dostarcza narzędzia umożliwiające oddzielenie interfejsu od działającego za nim kodu. Dzięki temu nad tymi elementami aplikacji mogą pracować dwa oddzielne zespoły specjalistów — jeden z nich stanowią eksperci od interfejsów użytkownika, drugim są programiści Visual Basica. Nowy język o nazwie Extensible Application Markup Language (XAML, wym. zamml) pozwala budować interfejsy użytkownika za pomocą instrukcji deklaratywnych zamiast kodu wykonywalnego. Umożliwia określanie rozmiaru, położenia i innych własności leżących na formularzu kontrolek WPF. Dzięki niemu można definiować style, z których będą korzystać różne kontrolki. Pozwala też na transformacje i animacje, zmieniające działanie tych kontrolek. Biblioteka WPF została wprowadzona już w .NET 3.0. W związku z tym można jej wraz z kilkoma innymi nowymi technologiami używać już w wersji Visual Basica z 2005 roku. Jest to jednak pierwsza edycja Visual Basica z tą biblioteką, daleko jej do doskonałości. Podobnie jak w aplikacjach Windows Forms, w programach WPF najważniejszą rolę odgrywają kontrolki. Dostarczają one użytkownikowi informacje (Label, StatusBar, TreeView, ListView, Image) oraz organizują je, aby łatwo było je zrozumieć (Border, StackPanel, DockPanel, TabControl). Pozwalają na wprowadzanie danych (TextBox, TextBlock, ComboBox, PasswordBox), wybieranie opcji (RadioButton, CheckBox, ListBox) oraz sterowanie działaniem aplikacji (Button, Menu, Slider). Aby użytkownik mógł maksymalnie efektywnie wykorzystać aplikację, zastosowane w niej kontrolki muszą zostać odpowiednio dobrane. Mimo iż często do jednego zadania nadaje się kilka tego typu narzędzi, niektóre z nich sprawdzają się w pewnych sytuacjach lepiej niż inne. Na przykład informację o stanie programu można zaprezentować za pomocą zmiany napisu na przycisku, chociaż nie jest to coś, do czego przyciski nadają się najlepiej. Zazwyczaj lepszym sposobem na poinformowanie o stanie jest użycie etykiety w pasku stanu, ponieważ użytkownik spodziewa się tego i może to zrozumieć. Nikt nie oczekuje, że informacja o stanie programu będzie widoczna na przycisku ze zmieniającym się tekstem.
206
Część II
Wstęp do języka Visual Basic
W tym rozdziale krótko opiszę najczęściej używane kontrolki WPF, dzięki czemu dowiesz się, które z nich najlepiej sprawdzają się w określonych sytuacjach. Aby ułatwić Ci znajdowanie potrzebnych pozycji, w kolejnych podrozdziałach omówię grupy kontrolek wyłonione na podstawie ich przeznaczenia. Jeśli na przykład konieczne jest poinformowanie użytkownika o stanie programu, należy zajrzeć do podrozdziału „Informowanie użytkownika”. W tym rozdziale zamieszczam tylko bardzo krótki i zwięzły opis kontrolek WPF oraz kilka wskazówek pomagających w podjęciu decyzji, której z nich użyć w danym celu. Kolejny — „Używanie kontrolek WPF” — koncentruje się na bardziej szczegółowym omówieniu tych narzędzi. Zawiera wyszczególnienie ich najważniejszych własności, metod i zdarzeń. Warto pamiętać, że pierwsza wersja Visual Basica 2008 zawiera błąd w projektach WPF. Więcej informacji na ten temat znajduje się we „Wstępie” w sekcji „Ostrzeżenie dotyczące Visual Basica 2008 Version 1” na stronie 34.
Przegląd kontrolek Kontrolki WPF można podzielić na kilka grup. Niektóre z nich odpowiadają tym z Windows Forms. Jednak część kontrolek w WPF odgrywa znacznie ważniejszą rolę niż w aplikacjach Windows Forms. W szczególności w WPF bardzo ważne są kontrolki aranżacji i organizacji układu innych kontrolek. Programiści Windows Forms zwykle rozmieszczają kontrolki na formularzu i ustawiają im odpowiednie rozmiary. Aplikacje WPF częściej rozmieszczają kontrolki w hierarchiach kontrolek StackPanel oraz Grid i pozwalają im na rozmieszczenie ich zawartości. W dalszych podrozdziałach znajdziesz opisy najważniejszych kategorii kontrolek WPF. Wiele z podstawowych sposobów użycia przedstawianych narzędzi demonstrują przykładowe programy, które można pobrać serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/ przyklady/vb28wp.zip). Nie wszystkie z opisywanych tutaj kontrolek są dostępne domyślnie po utworzeniu aplikacji WPF. Niektóre z nich trzeba własnoręcznie dodać do okna Toolbox. W tym celu kliknij prawym przyciskiem myszy jedną z sekcji w oknie Toolbox, a następnie opcję Choose Items. W oknie dialogowym Choose Toolbox Items przejdź do karty WPF Components, zaznacz pola wyboru obok kontrolek, których potrzebujesz, a na koniec kliknij przycisk OK. Pamiętaj też, że dodatkowe kontrolki, które mogą być dostępne w oknie Choose Toolbox Items, nie zostały tutaj opisane. W kolejnych podrozdziałach omówię tylko najczęściej używane tego typu narzędzia.
Rozdział 11.
Wybieranie kontrolek WPF
207
Kontrolki kontenery Kontrolki układu określają rozkład kontrolek, które się w nich znajdują. Mogą na przykład ustawiać je pionowo, poziomo lub w wierszach i kolumnach. Zalecaną metodą aranżacji kontrolek WPF jest zmuszenie kontenerów do określenia położenia znajdujących się w nich kontrolek potomnych i zezwolenie tym potomkom na wykorzystanie dostępnej im przestrzeni. Jest to szczególnie ważne w aplikacjach międzynarodowych, ponieważ nigdy nie wiadomo, ile miejsca dana kontrolka może potrzebować w różnych językach. Wyobraźmy sobie na przykład, że w formularzu znajduje się kontrolka StackPanel. Zawiera ona kilka przycisków wyświetlających okna dialogowe aplikacji. Jeśli usuniesz własności Width tych przycisków, będą się one automatycznie rozciągały w poziomie, aby zajmować całą dostępną im przestrzeń. Dzięki temu w celu poszerzenia nieco przycisków, żeby zmieścił się tekst w innym języku, wystarczy poszerzyć sam formularz. Kontrolka StackPanel poszerzy się, aby wypełnić formularz, a przyciski — w celu wypełnienia tej kontrolki. Przykładem programu z przyciskami o stałej wysokości, ale zmiennej szerokości jest ResizingButton, który można pobrać z serwera FTP wydawnictwa Helion. W aplikacjach Windows Forms podobny efekt można uzyskać za pomocą własności Anchor i Dock. Kontrolki układu są ważne również dlatego, że mogą zawierać inne kontrolki. Niektóre z kontrolek WPF mogą przechowywać tylko jeden element — jedną z nich jest Expander. Jeśli w kontrolce tej zostanie umieszczona inna kontrolka układu, jak choćby StackPanel, będzie można wstawiać do niej inne kontrolki. W poniższej tabeli znajduje się zestawienie kontrolek WPF, których główną funkcją jest przechowywanie i aranżacja innych kontrolek. Kontrolka Border1
Przeznaczenie kontrolki
1
BulletDecorator
Tworzy widoczne obramowanie lub tło dla treści. 2
Zawiera dwóch potomków. Pierwszy z nich pełni funkcję punktora, a drugi jest z nim wyrównany. Kontrolki tej można na przykład użyć do wyrównania obrazów punktorów z etykietami. (Zobacz przykładowy program o nazwie BulletDecorator, który można pobrać z serwera FTP wydawnictwa Helion).
Canvas
Tworzy obszar, w którym można wypozycjonować elementy potomne przez określenie ich własności Width, Height, Canvas.Left i Canvas.Top. (Zobacz przykładowy program o nazwie UseCanvas, który można pobrać z serwera FTP wydawnictwa Helion).
DockPanel
Dokuje kontrolki przy swojej lewej, prawej, dolnej lub górnej krawędzi, podobnie jak własność Dock w aplikacjach Windows Forms. Jeśli własność LastChildFill tej kontrolki ma wartość True, jej ostatni potomek będzie zajmował całą pozostałą wolną przestrzeń. (Zobacz przykładowy program o nazwie UseDockPanel, który można pobrać z serwera FTP wydawnictwa Helion).
208
Część II
Wstęp do języka Visual Basic
Kontrolka
Przeznaczenie kontrolki
Expander
Wyświetla nagłówek z ikoną, która oznacza, że dalsza treść jest zwinięta lub rozwinięta. Użytkownik może kliknięciem tego nagłówka rozwinąć lub zwinąć treść tej kontrolki. (Zobacz przykładowy program o nazwie UseExpander, który można pobrać z serwera FTP wydawnictwa Helion).
Grid
Wyświetla swoich potomków w wierszach i kolumnach. Kontrolka ta jest podobna do kontrolki Windows Forms TableLayoutPanel.
GridSplitter
Pozwala użytkownikowi na zmianę rozmiaru dwóch wierszy lub kolumn w kontrolce Grid.
GridView
Wyświetla dane w kolumnach w kontrolce ListView.
GroupBox
1
Wyświetla obramowanie i napis, podobnie jak kontrolka Windows Forms GroupBox.
Panel
Kontrolka nadrzędna dla kontrolek Canvas, DockPanel, Grid, TabPanel, ToolbarOverflowPanel, UniformGrid, StackPanel, VirtualizingPanel oraz WrapPanel. Najczęściej lepiej jest użyć jednej z wymienionych tutaj klas zamiast kontrolki Panel, ale ta ostatnia może zostać wykorzystana do implementacji niestandardowych kontrolek paneli.
ScrollViewer
1 2
1
Wyświetla pionowe i poziome paski przewijania dla pojedynczego elementu treściowego. (Zobacz przykładowy program o nazwie UseScrollViewer, który można pobrać z serwera FTP wydawnictwa Helion).
Separator
Oddziela dwie kontrolki znajdujące się w kontrolce układu. (Zobacz przykładowy program o nazwie UseSeparator, który można pobrać z serwera FTP wydawnictwa Helion).
StackPanel
Rozmieszcza potomków w jednym wierszu lub kolumnie. Jeśli jest za dużo kontrolek, te, które się nie mieszczą, zostaną obcięte.
TabControl
Rozmieszcza potomków na kartach. Elementy, które powinny znajdować się na tych kartach, umieszczone są w kontrolkach TabItem. (Zobacz przykładowy program o nazwie UseTabControl, który można pobrać z serwera FTP wydawnictwa Helion).
TabItem
1
Przechowuje treść jednej karty kontrolki TabControl.
Viewbox
1
Rozciąga swojego potomka, aby ją wypełniał. Własność Stretch określa, czy kontrolka ta rozciąga swojego potomka bez zmiany stosunku wysokości do szerokości. (Zobacz przykładowy program o nazwie UseViewbox, który można pobrać z serwera FTP wydawnictwa Helion).
VirtualizingStackPanel
Generuje elementy potomne, przechowujące elementy, które mieszczą się na dostępnym obszarze. Na przykład w przypadku pracy z kontrolką ListBox, związaną ze źródłem danych, kontrolka VirtualizingStackPanel generuje tylko te elementy, które zmieszczą się w ListBox. Jeśli kontrolka ta nie jest związana z żadnym źródłem danych, zachowuje się jak StackPanel.
WrapPanel
Rozmieszcza swoich potomków w wierszach lub kolumnach, zależnie od własności Orientation. Kiedy wiersz lub kolumna jest pełna, kolejny potomek przechodzi do następnego wiersza lub kolumny. Kontrolka ta jest podobna do kontrolki Windows Forms FlowLayoutPanel. (Zobacz przykładowy program o nazwie UseWrapPanel, który można pobrać z serwera FTP wydawnictwa Helion).
Ta kontrolka może mieć tylko jeden element podrzędny. Ta kontrolka powinna mieć dwóch potomków.
Rozdział 11.
Wybieranie kontrolek WPF
209
Znaczna część kontrolek układu potrafi zmieniać rozmiar swoich potomków, jeśli się im na to zezwoli. Jeżeli na przykład w pierwszym wierszu i kolumnie kontrolki Grid zostanie umieszczona kontrolka Button, będzie ona domyślnie zmieniać wielkość w odpowiedzi na modyfikację rozmiaru jej wiersza i kolumny. Własność Margin tej kontrolki określa odległość, w jakiej znajduje się ona od krawędzi komórki. Jeśli kontrolka potomna ma bezpośrednio ustawione własności Width i Height, będą one miały pierwszeństwo przed zasadami rozmieszczania kontrolek rodzica. Jeżeli na przykład zostaną one ustawione dla kontrolki Button, która znajduje się w siatce, przycisk ten nie będzie modyfikował wielkości wraz ze zmianą rozmiarów jego komórki w tej siatce. Aby uzyskać pożądany efekt, należy sprawdzić, jak własności Margin, Width i Height współpracują z nadrzędną kontrolką układu.
Wybieranie opcji Kontrolki wyboru opcji pozwalają użytkownikowi na wybór różnych wartości. Przy odpowiednim ich używaniu można zminimalizować ryzyko wprowadzenia niepoprawnych danych, co z kolei pozwala zmniejszyć ilość kodu obsługującego błędy. Poniższa tabela zawiera krótki opis kontrolek WPF, które pozwalają na wybór opcji. Kontrolka
Przeznaczenie kontrolki
CheckBox
Pozwala na wybór elementu niezależnie od innych elementów.
ComboBox
Wyświetla elementy listy rozwijanej. Są one reprezentowane przez kontrolki ComboBoxItem. (Zobacz przykładowy program o nazwie UseComboBox, który można pobrać z serwera FTP wydawnictwa Helion).
ComboBoxItem ListBox
ListBoxItem
1
1
Przechowuje treść jednego elementu listy ComboBox. Wyświetla elementy w postaci listy. Są one reprezentowane przez kontrolki ListBoxItem. Kontrolka ta automatycznie wyświetla paski przewijania, gdy są potrzebne. (Zobacz przykładowy program o nazwie UseListBox, który można pobrać z serwera FTP wydawnictwa Helion).
1
Przechowuje treść jednego elementu listy ListBox.
RadioButton
Pozwala na wybranie jednego elementu z grupy. Jeśli użytkownik zaznaczy któryś z nich, wszystkie pozostałe, mające tego samego rodzica, zostają odznaczone. (Zobacz przykładowy program o nazwie UseRadioButtons, który można pobrać z serwera FTP wydawnictwa Helion).
ScrollBar
Pozwala na wybór wartości liczbowej za pomocą przeciągania „rączki”. Paski przewijania są zazwyczaj wykorzystywane przez inne kontrolki, takie jak ScrollViewer, a w aplikacjach powinno się raczej używać suwaków. (Zobacz przykładowy program o nazwie UseScrollBar, który można pobrać z serwera FTP wydawnictwa Helion).
Slider
Pozwala na wybranie wartości liczbowej za pomocą przeciągania „rączki”. Jest podobna do kontrolki Windows Forms TrackBar. (Zobacz przykładowy program o nazwie UseSlider, który można pobrać z serwera FTP wydawnictwa Helion).
Ta kontrolka może zawierać tylko jednego potomka.
210
Część II
Wstęp do języka Visual Basic
Wprowadzanie danych Czasami kontrolki opisane w poprzednim podrozdziale okazują się mało praktyczne. Na przykład użytkownik nie może wpisać wystarczająco długiego opisu biograficznego ani komentarzy w kontrolkach ComboBox czy RadioButton. W takich przypadkach można użyć kontrolki tekstowej, w której da się wpisać tekst. W poniższej tabeli znajdują się krótkie opisy kontrolek WPF, które pozwalają na wpisywanie tekstu. Kontrolka
Przeznaczenie kontrolki
PasswordBox
Kontrolka podobna do TextBox, ale wyświetlająca znak maskujący zamiast tych wpisywanych przez użytkownika. (Zobacz przykładowy program o nazwie UsePasswordBox, który można pobrać z serwera FTP wydawnictwa Helion).
RichTextBox
Kontrolka podobna do TextBox, ale reprezentująca tekst w formie obiektu dokumentu. Więcej informacji na temat dokumentów znajduje się w dalszej części rozdziału — w podrozdziale „Zarządzanie dokumentami”.
TextBox
Dzięki niej można wprowadzać zwykły tekst. Opcjonalnie może ona pozwalać na stosowanie znaków powrotu karetki i tabulatorów oraz zawijać tekst.
Wyświetlanie danych Poniższe kontrolki wyświetlają informacje dla użytkownika. Ich opis znajduje się w poniższej tabeli. Kontrolka
Opis
Label
Wyświetla tekst, którego nie można edytować.
TextBlock
Wyświetla bardziej skomplikowany, niedający się edytować tekst. Treść tej kontrolki może zawierać znaczniki wstawiane bezpośrednio w tekście, określające specjalne formatowanie. Są to AnchoredBlock, Bold, Hyperlink, InlineUIContainer, Italic, LineBreak, Run, Span oraz Underline.
TreeView
Wyświetla dane hierarchiczne w formacie drzewa, podobnego do drzewa katalogów, które jest wyświetlane przez Eksplorator Windows.
Informacje dla użytkownika Przeznaczeniem tych kontrolek — podobnie jak opisanych w poprzednim podrozdziale kontrolek wyświetlających dane — jest informowanie użytkownika bez wchodzenia z nim w interakcje. Poniższa tabela zawiera krótkie opisy tych kontrolek WPF.
Rozdział 11.
Wybieranie kontrolek WPF
211
Kontrolka
Przeznaczenie kontrolki
Popup
Wyświetla swoją treść w oknie nad inną kontrolką. Zamiast niej z reguły można używać kontrolek Tooltip i ContextMenu. (Zobacz przykładowy program o nazwie UseUsePopup, który można pobrać z serwera FTP wydawnictwa Helion).
ProgressBar
Pokazuje, jaka część długiego zadania została już wykonana. Zadanie to zazwyczaj jest wykonywane synchronicznie, przez co użytkownik nie ma nic innego do roboty, jak patrzeć na ten pasek postępu. Kontrolka ta informuje, że operacja nie utknęła w martwym punkcie. (Zobacz przykładowy program o nazwie UseProgressBar, który można pobrać z serwera FTP wydawnictwa Helion).
StatusBar
Wyświetla w dolnej części formularza kontener, w którym można umieszczać kontrolki informujące o stanie aplikacji. Mimo iż w kontrolce tej może znaleźć się wszystko, jej przeznaczeniem jest prezentowanie informacji o stanie programu, nie narzędzi. Z reguły nie umieszcza się w niej menu, list rozwijanych, przycisków, pasków narzędzi ani żadnych innych kontrolek, które pozwalają na sterowanie aplikacją. (Zobacz przykładowy program o nazwie UseStatusBar, który można pobrać z serwera FTP wydawnictwa Helion).
StatusBarItem ToolTip
1
1
Przechowuje treść jednego elementu kontrolki StatusBar. Wyświetla chmurkę. Aby utworzyć dla kontrolki prostą chmurkę, należy ustawić jej własność Tooltip. Kontrolka ta służy do budowania bardziej złożonych chmurek. Na przykład może zawierać kontrolkę StackPanel, w której znajdują się inne kontrolki. (Zobacz przykładowy program o nazwie UseToolTip, który można pobrać z serwera FTP wydawnictwa Helion).
Ta kontrolka może zawierać tylko jednego potomka.
Inicjowanie akcji Każda kontrolka reaguje na zdarzenia, a co za tym idzie — może inicjować akcje. W praktyce jednak użytkownicy oczekują, że tylko niektóre kontrolki wykonają jakieś czynności. W poniższej tabeli znajdują się opisy kontrolek, które w normalnych warunkach inicjują jakieś działania. Kontrolka Button
1
ContextMenu
Przeznaczenie kontrolki Zgłasza zdarzenie Click, które może zostać przechwycone przez program w celu wykonania jakiegoś działania. (Zobacz przykładowy program o nazwie UseButtonRepeatButtonl, który można pobrać z serwera FTP wydawnictwa Helion). Wyświetla menu kontekstowe dla innych kontrolek. Normalnie kontrolka ContextMenu zawiera kontrolki MenuItem. (Zobacz przykładowy program o nazwie UseMenuContextMenu, który można pobrać z serwera FTP wydawnictwa Helion).
212
Część II
Wstęp do języka Visual Basic
Kontrolka
Przeznaczenie kontrolki
Menu
Wyświetla menu dla formularza. Normalnie menu to zawiera kontrolki MenuItem, reprezentujące menu najwyższego rzędu. Elementy te zawierają kolejne kontrolki MenuItem, które reprezentują poszczególne polecenia. (Zobacz przykładowy program o nazwie UseMenuContextMenu, który można pobrać z serwera FTP wydawnictwa Helion).
MenuItem
Reprezentuje element kontrolki ContextMenu lub Menu.
PrintDialog
Wyświetla standardowe okno dialogowe drukowania systemu Windows. Nie należy umieszczać tej kontrolki w oknie, lecz zbudować je i wyświetlić za pomocą kodu. (Zobacz przykładowy program o nazwie UsePrintDialog, który można pobrać z serwera FTP wydawnictwa Helion).
RepeatButton
1
1
Działa jak kontrolka Button, która zgłasza wielokrotnie zdarzenie Click, kiedy zostanie naciśnięta i przytrzymana. (Zobacz przykładowy program o nazwie UseButtonRepeatButton, który można pobrać z serwera FTP wydawnictwa Helion).
ToolBar
Zawiera elementy. Zwykle kontrolka ta zajmuje górną część formularza i zawiera takie elementy poleceń, jak przyciski i listy rozwijane. (Zobacz przykładowy program o nazwie UseToolBar, który można pobrać z serwera FTP wydawnictwa Helion).
ToolBarTray
Grupuje paski narzędzi i pozwala użytkownikowi na przeciąganie ich w różne miejsca. (Zobacz przykładowy program o nazwie UseToolBar, który można pobrać z serwera FTP wydawnictwa Helion).
Ta kontrolka może zawierać tylko jednego potomka.
Prezentowanie grafiki i mediów Każda kontrolka może wyświetlić obraz graficzny. Poniższy kod w języku XAML tworzy pędzel ImageBrush, po czym za jego pomocą wypełnia tło kontrolki Grid:
Program wyświetlający obraz w tle kontrolki Grid nosi nazwę FormImage; można go pobrać z serwera FTP wydawnictwa Helion.
Rozdział 11.
Wybieranie kontrolek WPF
213
Mimo że kontrolka Grid może wyświetlić obraz lub jakąś inną grafikę, jej prawdziwym przeznaczeniem jest aranżacja innych kontrolek. Poniższa tabela zawiera zestawienie kontrolek, których główną funkcją jest prezentowanie grafiki i mediów. Kontrolka
Przeznaczenie kontrolki
Ellipse
Rysuje elipsę.
Image
Wyświetla obraz graficzny. Może opcjonalnie rozciągać tę grafikę ze zniekształceniem lub bez.
Line
Rysuje odcinek.
MediaElement
Prezentuje klipy audio i wideo. Do sterowania odtwarzaniem udostępnia metody Play, Pause i Stop oraz własności Volume i SpeedRatio (zobacz przykładowy program UseMediaElement, który można pobrać z serwera FTP wydawnictwa Helion).
Path
Rysuje figurę na podstawie zestawu opcji.
Polygon
Rysuje zamknięty wielokąt.
Polyline
Rysuje zestaw połączonych ze sobą odcinków.
Rectangle
Rysuje prostokąt z opcjonalnie zaokrąglonymi rogami.
Wszystkie obiekty rysujące figury (Ellipse, Line, Path, Polygon, Polyline i Rectangle) mają własności Stroke, StrokeThickness i Fill, pozwalające na definiowanie ich wyglądu. Mimo iż głównym przeznaczeniem tych kontrolek jest tworzenie prostych (lub nie do końca prostych) figur geometrycznych, udostępniają one — podobnie jak wszystkie inne tego typu narzędzia — pełen zestaw zdarzeń. Kilka z opisanych tu kontrolek figur geometrycznych demonstruje program o nazwie DrawingShapes, który można pobrać z serwera FTP wydawnictwa Helion. Również dostępna w internecie aplikacja EllipseClick za pomocą tzw. triggerów zmienia kolor elipsy, kiedy znajdzie się nad nią kursor, a także wyświetla komunikat, gdy użytkownik kliknie ją.
Elementy nawigacji Kontrolka Form umożliwia nawigację po zewnętrznych stronach internetowych i stronach aplikacji. Aby wyświetlić stronę internetową lub XAML, należy użyć metody Navigate tego narzędzia. Kontrolka ta dostarcza przyciski, które pozwalają przechodzić dalej i wstecz podczas odwiedzania stron. Kontrolkę Frame z nawigacją po dwóch obiektach typu Page demonstruje program o nazwie UseFrame, który można pobrać z serwera FTP wydawnictwa Helion.
214
Część II
Wstęp do języka Visual Basic
Zarządzanie dokumentami Biblioteka WPF udostępnia trzy różne rodzaje dokumentów — dokumenty płynne (ang. flow document), dokumenty o ustalonym formatowaniu (ang. fixed document) oraz dokumenty XPS. Umożliwiają one wyświetlanie i drukowanie tekstu w wysokiej jakości. Poniższa tabela zawiera zestawienie kontrolek WPF do wyświetlania tego typu dokumentów. Kontrolka
Przeznaczenie kontrolki
DocumentViewer
Wyświetla dokumenty o ustalonym formatowaniu strona po stronie.
FlowDocumentPageViewer
Wyświetla dokumenty płynne po jednej stronie. Jeśli kontrolka ta jest wystarczająco szeroka, może wyświetlać kilka kolumn, ale nadal tylko jeden dokument naraz.
FlowDocumentReader
Wyświetla płynne dokumenty w jednym z trzech trybów. W trybie single page działa jak kontrolka FlowDocumentReader, zaś trybie scrolling — jak FlowDocumentScrollViewer. W trybie reading mode wyświetla dwie strony obok siebie, tak jak w prawdziwych książkach.
FlowDocumentScrollViewer
Wyświetla cały płynny dokument na jednej długiej przewijanej stronie. Tę ostatnią można przewijać za pomocą pasków przewijania.
Cyfrowy atrament Kontrolki atramentu cyfrowego obsługują dane wprowadzane z tabletu. Zwykle atramentu cyfrowego w aplikacjach tabletowych używa się tylko wtedy, gdy użytkownik może wprowadzać dane poprzez rysowanie na ekranie za pomocą pióra tabletu. Programy te zazwyczaj są wyposażone w funkcję rozpoznawania tekstu, która pozwala im zrozumieć, co jest wpisywane. Dodatkowo do wykonywania operacji typowych dla myszy zwykle wykorzystują one pióro. Aby na przykład kliknąć przycisk, można lekko stuknąć piórem albo stuknąć i przeciągnąć w celu przeciągnięcia jakiegoś elementu. Więcej informacji na temat tabletów znajduje się na stronie msdn.microsoft.com/mobility/tabletpc/default.aspx. Mimo że kontrolki atramentu cyfrowego są najbardziej przydatne w tabletach, WPF udostępnia dwa tego typu narzędzia, których można używać we wszystkich aplikacjach Visual Basica. Kontrolka
Przeznaczenie kontrolki
InkCanvas
Wyświetla lub przechwytuje linie atramentu.
InkPresenter
Wyświetla linie atramentu.
Rozdział 11.
Wybieranie kontrolek WPF
215
Podsumowanie Kontrolki stanowią łącze pomiędzy użytkownikiem a aplikacją. Umożliwiają programowi dostarczanie danych do użytkownika, a temu — sterowanie działaniem programu. Ten rozdział zawiera krótki opis kontrolek WPF z podziałem na kategorie. Dzięki temu łatwiej jest podjąć decyzję, którego narzędzia użyć do danego zadania. Jeśli użytkownik musi wybrać jedną z opcji, powinien poszukać odpowiedniej kontrolki w podrozdziale „Wybieranie opcji”. Jeżeli natomiast potrzebne jest wyświetlenie przez program informacji o jego stanie, należy przeczytać podrozdział „Wyświetlanie danych”. Ten rozdział stanowi krótki wstęp do kontrolek WPF. Zawiera kilka wskazówek, które pomagają dobrać odpowiednie narzędzie do zadania. W rozdziale 12. — „Używanie kontrolek WPF” — znajdziesz bardziej szczegółowy opis tych kontrolek. Omówione w nim zostaną najważniejsze własności, metody i zdarzenia najbardziej przydatnych kontrolek WPF.
216
Część II
Wstęp do języka Visual Basic
12
Używanie kontrolek WPF Kod stojący za kontrolkami WPF jest taki sam jak kod kontrolek Windows Forms. Oznacza to, że wszystko, co zostało napisane wcześniej o aplikacjach, formularzach, kontrolkach, kodzie Visual Basica, obsłudze błędów, rysowaniu, drukowaniu, raportach itd., pozostaje prawie bez zmian. Rozdział 11. — „Wybieranie kontrolek WPF” — zawiera krótkie opisy najczęściej używanych kontrolek WPF, pogrupowanych według kategorii, co pomaga w znalezieniu takiej, która najlepiej nadaje się do określonego zadania. W tym rozdziale znajdziesz bardziej szczegółowe informacje na temat biblioteki WPF. Opisane zostaną niektóre najważniejsze koncepcje leżące u jej podłoża. Zamieszczone będzie też bardziej szczegółowe objaśnienie działania części kontrolek i sposobów ich użycia. WPF to temat rzeka. W zasadzie biblioteka ta odtwarza całą funkcjonalność biblioteki Windows Forms. Rozdział ten nie jest nawet próbą opisania wszystkich narzędzi i technik wykorzystywanych przez tę technologię. W zamian omówię tu niektóre najważniejsze pojęcia i objaśnię, jak tworzyć podstawowe formularze WPF. Wiele z tematów opisywanych w tym rozdziale demonstrują przykładowe programy, które można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Pamiętaj, że w pierwszej wersji Visual Basica 2008 projekty WPF zawierają błąd. Więcej informacji na ten temat można znaleźć w podrozdziale „Wstępu” — „Ostrzeżenie dotyczące Visual Basica 2008 Version 1” na stronie 34.
Koncepcje WPF Z koncepcyjnego punktu widzenia aplikacje WPF są pod wieloma względami podobne do programów Windows Forms. Zarówno jedne, jak i drugie wyświetlają formularz lub okno z kontrolkami. Te ostatnie w obu tych systemach posiadają własności, metody i zdarzenia, które decydują o ich wyglądzie i zachowaniu.
218
Część II
Wstęp do języka Visual Basic
Kontrolki wykorzystywane w aplikacjach Windows Forms należą do przestrzeni nazw System.Windows.Forms. Natomiast aplikacje WPF korzystają z zestawu kontrolek dostępnego w przestrzeni nazw System.Windows.Controls. Wiele z nich pełni takie same funkcje jak ich odpowiedniki wśród kontrolek Windows Forms, chociaż mają różne możliwości. Na przykład w obu omawianych przestrzeniach nazw dostępne są przyciski, etykiety, listy rozwijane i pola wyboru, ale wygląd i działanie tych elementów w obu przypadkach są odmienne. Na użycie podobnych, ale jednak innych kontrolek w bibliotece WPF zdecydowano się z dwóch powodów. Po pierwsze, te nowe narzędzia pozwalają lepiej wykorzystać możliwości graficzne nowoczesnych komputerów i oprogramowania. Łatwiej za ich pomocą uzyskać różne efekty, takie jak przezroczyste tło, gradienty, rotacje, dwu- i trójwymiarowe figury, multimedia i inne. Drugim głównym celem biblioteki WPF jest lepsze oddzielenie interfejsu użytkownika od kodu źródłowego programu. W kolejnych podrozdziałach bardziej szczegółowo opiszę tę koncepcję, jak również inne kluczowe dla WPF pojęcia.
Oddzielenie interfejsu użytkownika od kodu źródłowego Pomysł oddzielenia interfejsu użytkownika od kodu źródłowego nie jest nowy. Programiści Visual Basica już od wielu lat tworzą aplikacje z chudym interfejsem użytkownika. W tym przypadku zawiera on minimalną ilość kodu, a większość pracy wykonuje za pomocą procedur zapisanych w bibliotekach. Niestety kod wywołujący te biblioteki znajduje się w tych samych plikach, które definiują interfejs użytkownika, przynajmniej w aplikacjach Windows Forms tworzonych za pomocą Visual Studio 2008. Oznacza to, że całkowite oddzielenie interfejsu użytkownika od kodu jest niemożliwe. Jeśli na przykład jeden programista chce zmodyfikować interfejs, drugi nie może w tym samym czasie pracować nad jego kodem źródłowym. Biblioteka WPF lepiej oddziela kod źródłowy programu od interfejsu użytkownika. Aplikacja przechowuje definicję interfejsu w plikach tekstowych z kodem w języku XAML. Jest to specjalny rodzaj języka Extensible Markup Language (XML), służący do definiowania elementów interfejsu użytkownika, takich jak przyciski, etykiety, kontenery, tła, kolory, czcionki, style i inne atrybuty kontrolek. Z plikiem XAML skojarzony jest plik zawierający kod Visual Basica. Zawiera on cały kod napisany przez programistę do obsługi zdarzeń i manipulowania kontrolkami, w znacznym stopniu podobnie jak kod Windows Forms. Dzięki dokładnemu oddzieleniu interfejsu użytkownika od kodu źródłowego biblioteka WPF umożliwia pracowanie nad tym samym formularzem więcej niż jednemu programiście jednocześnie. Na przykład grafik może zbudować interfejs poprzez zdefiniowanie etykiet, menu, przycisków itd. za pomocą narzędzia o nazwie Expression Design, a programista Visual Basica — dołączyć do tego odpowiedni kod. Więcej informacji na temat narzędzia Expression Design znajduje się na stronie www.microsoft.com/expression/products/overview.aspx.
Rozdział 12.
Używanie kontrolek WPF
219
Dzięki temu, że definicja interfejsu jest niezależna od działającego w jej tle kodu, grafik będzie później mógł poprzestawiać swoje kontrolki w pliku XAML, zmienić ich wygląd itd. — i nie powinno to mieć wpływu na ich działanie. Nie wiadomo jeszcze, czy wizja Microsoftu się spełni, ale na spotkaniu użytkowników Visual Studio przedstawiciel tej firmy powiedział, że program Expression Design nie będzie dołączany do subskrypcji MSDN. Oświadczenie to spotkało się z ostrą krytyką ze strony obecnych na spotkaniu osób. Spośród około setki ludzi obecnych w pomieszczeniu nikt nie zadeklarował, że jego firma byłaby skłonna lub miałaby środki finansowe na zatrudnianie osobnych projektantów grafiki i programistów. Ich zdaniem ci drudzy powinni w przyszłości zajmować się budowaniem interfejsów użytkownika, tak jak robią to obecnie. Podejrzewam zatem, że plany Microsoftu mogą jeszcze ulec pewnym zmianom. Możliwe, że zintegrowane środowisko programistyczne (IDE) i okno Properties nie będą udostępniały wszystkich narzędzi, które posiada program Expression Design. Jeśli jednak klienci nie będą chcieli mieć osobnego produktu do projektowania graficznego, możliwe, że Microsoft odpowie na potrzeby rynkowe i niektóre z tych funkcji udostępni także w IDE. Funkcje te można też zaimplementować za pomocą kodu XAML lub Visual Basica, chociaż byłoby to nieco trudniejsze niż używanie wbudowanych narzędzi do projektowania. Jeśli stosowanie nowych funkcji będzie zbyt skomplikowane, programiści nie będą się po prostu nimi zajmować.
Hierarchie kontrolek WPF W aplikacjach WPF ważna jest klasa Window, która odgrywa podobną rolę jak Form w aplikacjach Windows Forms. Podczas gdy obiekt Form może zawierać dowolną liczbę kontrolek, obiekt Window może mieć tylko jedną. Aby formularz WPF wyświetlał więcej niż jedną kontrolkę, trzeba najpierw umieścić na nim jakiś kontener, a dopiero do niego wstawiać inne kontrolki. Na przykład po utworzeniu nowej aplikacji WPF obiekt Window początkowo zawiera tylko kontrolkę Grid, która może przechowywać dowolną liczbę innych kontrolek w wierszach i kolumnach. Inne kontrolki kontenery to między innymi Canvas, DockPanel, DocumentViewer, Frame, StackPanel i TabControl. W wyniku tego powstaje struktura przypominająca drzewo, którego korzeniem jest pojedynczy obiekt Window. Zgadza się to z hierarchiczną naturą języka XAML. Język ten jest rodzajem języka XML, a jego pliki muszą zawierać element nadrzędny dla wszystkich pozostałych elementów. W związku z tym powinny one również posiadać element korzenia. Kiedy przyjrzysz się plikom XAML prezentowanym w dalszej części tego rozdziału, zauważysz, że wszystkie one zaczynają się od elementu Window, który zawiera pozostałe. Wiele z kontrolek, które nie są kontenerami, może zawierać tylko jeden element, określający własność Content. Można na przykład ustawić własność Content kontrolki Button na tekst, który ma zostać wyświetlony.
220
Część II
Wstęp do języka Visual Basic
Własność Content może mieć tylko jedną wartość, ale nie musi ona być tak prosta jak zwykły tekst. Na przykład na rysunku 12.1 przedstawiono kontrolkę Button, zawierającą kontrolkę Grid z trzema etykietami. Rysunek 12.1. Ten przycisk zawiera kontrolkę Grid z trzema etykietami
WPF w IDE IDE Visual Studio udostępnia edytory do pracy z klasami WPF Window i kontrolkami. Podstawowa zasada działania tego środowiska jest taka sama dla WPF i dla Windows Forms. Różnice objawiają się dopiero w szczegółach. Można na przykład za pomocą projektanta okien WPF edytować okna WPF. W oknach WPF można umieszczać różne narzędzia z okna Toolbox — w podobny sposób jak kontrolki Windows Forms. Jednak — mimo licznych podobieństw — projektant okien WPF i projektant formularzy Windows różnią się wieloma szczegółami. Chociaż okno Properties wyświetla własności kontrolek WPF, tak jak kontrolek Windows Forms, sporo wartości jest w tym oknie prezentowanych inaczej. Okno to reprezentuje niektóre własności z wartościami logicznymi za pomocą pól wyboru. Inne własności, które przyjmują wartości dające się wyliczyć, są reprezentowane przez pola tekstowe, w których można wpisywać wartości (jeśli się je zna). Okno to reprezentuje niektóre własności obiektów za pomocą nazw typów tych obiektów, nie pozwala zaś na zaznaczanie obiektów — w przeciwieństwie do okna projektanta formularzy Windows. Niektóre te cechy być może zostaną usunięte w przyszłych wersjach Visual Studio. Inne mogą być miejscami zarezerwowanymi przez Microsoft dla bardziej zaawansowanych funkcji, a część może po prostu oznaczać zmianę filozofii projektowania tej firmy. Mimo że niektóre z tych edytorów mogą być niewygodne w użyciu, a części może brakować, należy pamiętać, że edytory te tworzą tylko kod składający interfejs użytkownika. Zawsze można ręcznie edytować plik XAML, aby dodać efekty, których brakuje w oknie Properties. W kolejnych podrozdziałach wyjaśnię, jak pisać kod XAML i działający w jego tle kod Visual Basica.
Rozdział 12.
Używanie kontrolek WPF
221
Edytowanie kodu XAML Na rysunku 12.2 przedstawiono IDE po utworzeniu nowego projektu WPF. Większa część tego okna powinna wyglądać znajomo. Znajdujące się po lewej stronie okno Toolbox zawiera narzędzia, które można umieszczać na oknie pośrodku ekranu. Okno Solution Explorer, które znajduje się po prawej stronie, wyświetla listę plików wykorzystywanych przez aplikację. Okno Properties pokazuje własności i ich wartości dla kontrolki zaznaczonej w oknie projektanta. Na rysunku 12.2 zaznaczony został obiekt Window, dlatego w górnej części okna Properties widać jego typ System.Windows.Window.
Rysunek 12.2. IDE Visual Studio wygląda prawie tak samo w projektach WPF i Windows Forms
Jedną z największych różnić, jeśli chodzi o wygląd IDE podczas tworzenia aplikacji WPF i Windows Forms, jest centralny edytor. W programie Windows Forms formularz edytujemy w oknie edytora formularzy Windows. W aplikacjach WPF używamy graficznego edytora XAML (rysunek 12.2), za pomocą którego edytujemy kod XAML obiektu Window. W górnej części tego obszaru znajduje się graficzny edytor, do którego można przeciągać kontrolki z okna Toolbox, podobnie jak w edytorze Windows Forms. Dolna część wyświetla automatycznie generowany kod XAML. Jeśli dokładnie przyjrzysz się rysunkowi 12.2, zauważysz, że w edytorze kodu XAML znajduje się pojedynczy element Window, który zawiera wszystkie pozostałe. Bezpośrednio po utworzeniu nowego projektu element obiektu Window zawiera tylko jedną kontrolkę Grid.
222
Część II
Wstęp do języka Visual Basic
Najczęściej obiekty WPF tworzy się za pomocą graficznego edytora i okna Toolbox, ponieważ tak jest najłatwiej. Po zaznaczeniu kontrolki w edytorze graficznym wiele z jej własności można przejrzeć i zmodyfikować w oknie Properties. Niestety nie daje ono dostępu do wszystkich własności kontrolek. Niektóre z nich można w tym oknie tylko odczytać. Inne z kolei da się edytować, ale nie ma dla nich dedykowanych edytorów. Na przykład własność Background określa sposób rysowania tła kontrolki. Można ją ustawić w oknie Properties na nazwę koloru typu Red lub podać wartość liczbową, taką jak #FF0000FF (niebieski). Własność tę możesz też jednak ustawić na pędzel, który kontrolka ma wykorzystać do rysowania swojego tła. Pędzlem takim może być na przykład obiekt LinearGradientBrush, RadialGradientBrush czy PathGradientBrush. Tego typu wartości definiuje się w kodzie XAML albo za pomocą kodu Visual Basica, ale okno Properties nie udostępnia żadnego edytora, który ułatwiałby to zadanie, przynajmniej nie w tej wersji oprogramowania. Na rysunku 12.3 przedstawiono obiekt Window z kontrolką Grid, która ma w tle gradient liniowy. Zwróć uwagę, że w oknie Properties własność Background ma wartość odpowiadającą nazwie klasy użytego pędzla System.Windows.Media.LinearGradientBrush. Znajdujący się na dole po lewej stronie kod XAML jest odpowiedzialny właśnie za tę definicję tła kontrolki Grid.
Rysunek 12.3. Do ustawienia gradientu w tle kontrolki można wykorzystać kod XAML, ale nie okno Properties
Rozdział 12.
Używanie kontrolek WPF
223
Poniżej znajduje się kod XAML, który ustawia gradient w tle omawianej kontrolki:
Powyższy kod jest wykorzystywany przez program GradientBackground, który można pobrać z serwera FTP wydawnictwa Helion. Element Window zawiera jeden element Grid. Ten z kolei zawiera element Grid.Background, zawierający element LinearGradientBrush. Atrybuty StartPoint i EndPoint wyznaczają punkty, pomiędzy którymi gradient ma rozciągać swoje kolory. Mogą one przyjmować wartości od (0,0) — górny lewy róg kontrolki, do (1,1) — prawy dolny róg kontrolki. Zagnieżdżone w elemencie LinearGradientBrush elementy GradientStop określają, z jakich kolorów ma składać się gradient. Wartości użyte w tym przypadku oznaczają, że zaczynając od punktu początkowego StartPoint, tło będzie czerwone, w połowie drogi przejdzie w białe, a następnie stopniowo będzie zamieniać się w niebieskie, zmierzając w kierunku punktu końcowego EndPoint. Zmiany wprowadzone w kodzie XAML nie zawsze są od razu widoczne w projektancie graficznym. Aby odświeżyć okno projektanta, należy kliknąć kartę Design. Program Expression Design udostępnia dodatkowe narzędzia do edycji kodu XAML. Posiada na przykład edytory, które umożliwiają interaktywne definiowanie teł gradientowych i krzywych Beziera. Mimo że w Visual Studio nie ma podobnych narzędzi, efekty te można uzyskać za pomocą kodu XAML. Analogicznie — okno Properties nie ułatwia ustawiania własności Content kontrolki niebędącej kontenerem na inną kontrolkę, ale można to w prosty sposób zrobić w kodzie XAML. Aby na przykład umieścić kontrolkę Grid w kontrolce Button, wystarczy pomiędzy znacznikiem otwierającym a zamykającym tej kontrolki wpisać definicję kontrolki Grid. Poniższy kod programu GridButton, który można pobrać z serwera FTP wydawnictwa Helion, tworzy przycisk z kontrolką Grid, podobny do tego z rysunku 12.1:
224
Część II
Wstęp do języka Visual Basic
Element najwyższego rzędu Window zawiera kontrolkę Grid z jednym elementem Button, zaś ten ostatni —inną kontrolkę Grid. Wymiary wierszy i kolumn tej siatki definiują elementy Grid.Row i Grid.Column. Wewnętrzna kontrolka Grid zawiera trzy kontrolki Label. Pierwsza z nich wyświetla tekst GL; została umieszczona w górnym lewym rogu (domyślnie) i znajduje się w lewej górnej komórce siatki (wiersz 0, kolumna 0). Druga kontrolka Label wyświetla tekst Na środku. Została umieszczona na środku i znajduje się w drugim wierszu siatki (wiersz 1) oraz pierwszej kolumnie (kolumna 0). Jej własność ColumnSpan ma wartość 3, dzięki czemu rozciąga się ona na wszystkie trzy kolumny w drugim wierszu. Ostatnia kontrolka Label wyświetla tekst PD, znajduje się w prawym dolnym rogu i została umieszczona w prawej dolnej komórce siatki (wiersz 2, kolumna 2). Edytor graficzny i okno Properties nie dają dostępu do wszystkich funkcji XAML, ale umożliwiają zbudowanie podstawowego interfejsu użytkownika dla aplikacji WPF. Po zdefiniowaniu podstawowej struktury okna można wykorzystać kod XAML do udoskonalenia swojego produktu (na przykład poprzez dodanie gradientów w tłach).
Edytowanie kodu Visual Basica Każdy plik XAML jest związany z plikiem zawierającym kod Visual Basica. Po utworzeniu nowego projektu WPF plik ten jest domyślnie od razu otwarty. Na rysunku 12.3. widać, że otwarty jest plik XAML o nazwie Window1.xaml. Druga karta reprezentuje odpowiedni plik Visual Basica, który nazywa się Window1.xaml.vb. Kliknij tę kartę, aby przejrzeć kod źródłowy Visual Basica.
Rozdział 12.
Używanie kontrolek WPF
225
Poniżej znajduje się kod źródłowy Visual Basica, który został wygenerowany dla pliku XAML po utworzeniu nowego projektu: Class Window1 End Class
W pliku tym można wpisywać procedury obsługi zdarzeń, podobnie jak w kodzie formularzy Windows Forms. Z listy znajdującej się po lewej stronie można wybrać jakąś kontrolkę lub pozycję Window1 Events, a następnie z listy po prawej stronie — zdarzenie dla wybranego wcześniej obiektu. Procedurę obsługi domyślnych zdarzeń kontrolki można też utworzyć poprzez dwukrotne kliknięcie jej w projektancie okien WPF. Metoda ta nie jest jednak skuteczna dla wszystkich kontrolek (na przykład Grid, Label czy StackPanel), lecz działa dla tych, które najczęściej wymagają procedur obsługi zdarzeń (jak Button, CheckBox, ComboBox, RadioButton i TextBox). Można też wpisywać podprocedury i funkcje niezwiązane z obsługą zdarzeń, podobnie jak w innych plikach Visual Basica. W pliku z kodem Visual Basica można sprawdzać i ustawiać własności kontrolek oraz wywoływać metody kontrolek, tak samo jak w projektach Windows Forms. Jedyna różnica objawia się w funkcjonalności udostępnianej przez kontrolki WPF. Odpowiada ona poleceniom języka XAML, które służą do definiowania kontrolek. Na przykład poniższy kod Visual Basica tworzy ten sam przycisk z kontrolką Grid, który widać na rysunku 12.1. W poprzednim podrozdziale — „Edytowanie kodu XAML” — znajduje się kod XAML, który odpowiada za utworzenie tego przycisku. Class Window1 Private Sub Window1_Loaded() Handles Me.Loaded ‘ Utworzenie siatki. Dim grd As New Grid() grd.Width = btnGrid.Width - 10 grd.Height = btnGrid.Height - 10 ‘ Dodanie wierszy i kolumn. AddRow(grd, New GridLength(33, AddRow(grd, New GridLength(33, AddRow(grd, New GridLength(33, AddCol(grd, New GridLength(33, AddCol(grd, New GridLength(33, AddCol(grd, New GridLength(33,
‘ Wstawienie elementów do siatki. Dim lbl1 As New Label() lbl1.Content = “GL" lbl1.HorizontalAlignment = Windows.HorizontalAlignment.Left lbl1.VerticalAlignment = Windows.VerticalAlignment.Top lbl1.SetValue(Grid.RowProperty, 0) lbl1.SetValue(Grid.ColumnProperty, 0) grd.Children.Add(lbl1)
226
Część II
Wstęp do języka Visual Basic Dim lbl2 As New Label() lbl2.Content = “Na środku" lbl2.HorizontalAlignment = Windows.HorizontalAlignment.Center lbl2.VerticalAlignment = Windows.VerticalAlignment.Center lbl2.SetValue(Grid.RowProperty, 1) lbl2.SetValue(Grid.ColumnProperty, 0) lbl2.SetValue(Grid.ColumnSpanProperty, 3) grd.Children.Add(lbl2) Dim lbl3 As New Label() lbl3.Content = “PD" lbl3.HorizontalAlignment = Windows.HorizontalAlignment.Right lbl3.VerticalAlignment = Windows.VerticalAlignment.Bottom lbl3.SetValue(Grid.RowProperty, 2) lbl3.SetValue(Grid.ColumnProperty, 2) grd.Children.Add(lbl3)
‘ Wstawienie siatki na przycisk. btnGrid.Content = grd End Sub ‘ Dodanie do siatki wiersza o określonej wysokości. Private Sub AddRow(ByVal my_grid As System.Windows.Controls.Grid, _ ByVal height As GridLength) Dim row_def As New RowDefinition() row_def.Height = height my_grid.RowDefinitions.Add(row_def) End Sub ‘ Dodanie do siatki kolumny o określonej wysokości. Private Sub AddCol(ByVal my_grid As System.Windows.Controls.Grid, _ ByVal width As GridLength) Dim col_def As New ColumnDefinition() col_def.Width = width my_grid.ColumnDefinitions.Add(col_def) End Sub Private Sub btnGrid_Click() Handles btnGrid.Click MessageBox.Show(“Clicked!", “Clicked", _ MessageBoxButton.OK, _ MessageBoxImage.Information) End Sub End Class
Procedura obsługi zdarzeń Loaded głównej klasy Window zostaje uruchomiona po załadowaniu formularza. Najpierw tworzy ona kontrolkę Grid i ustawia jej szerokość oraz wysokość. Następnie wywołuje podprocedury AddRow i AddCol, za pomocą których tworzy trzy wiersze i trzy kolumny. Procedury te ułatwiają tworzenie wierszy i kolumn. Opiszę je nieco później. Następnie zostają utworzone trzy kontrolki Label, a procedura ustawia ich własności. Niektóre z nich, jak HorizontalAlignment czy Content, są bardzo proste. Inne natomiast, na przykład Grid.RowProperty, GridColumnProperty i Grid.ColumnSpan — nieco bardziej skomplikowane. Używanie tych własności ma sens tylko wtedy, gdy kontrolki Label znajdują się w kontrolce Grid, a więc nie są one typowymi własnościami etykiet. Są one dodawane
Rozdział 12.
Używanie kontrolek WPF
227
przez metodę SetValue kontrolki Grid, która robi to w podobny sposób jak ExtenderProvider. Jeśli w kontrolce StackPanel zostanie umieszczona kontrolka Button, własności tych nie będzie w oknie Properties. Wszystkie zainicjowane etykiety zostają wstawione do siatki (Grid) za pomocą metody Children.Add tej kontrolki. Po utworzeniu wszystkich kontrolek własność Content kontrolki Button zostaje ustawiona na tę nową siatkę. Podprocedura AddRow tworzy nowy obiekt RowDefinition, reprezentujący rząd siatki. Ustawia wysokość tego obiektu i dodaje go do kolekcji RowDefinitions kontrolki Grid. Podprocedura AddCol używa podobnych metod do utworzenia nowej kolumny. Ostatni fragment kodu w tym przykładzie to procedura obsługi zdarzenia Click przycisku o nazwie btnGrid. Kliknięcie tego przycisku powoduje wyświetlenie okna z komunikatem. Wszystko, co można zadeklarować w języku XAML, da się ująć w postaci procedury w Visual Basicu. W kolejnym podrozdziale — noszącym tytuł „Język XAML” — opiszę niektóre możliwości tego języka oraz pokażę je na przykładach. Dalszy podrozdział — „Procedury w WPF” — zawiera objaśnienie sposobów implementacji niektórych z tych funkcji za pomocą kodu Visual Basica zamiast XAML.
Język XAML Język XAML jest rodzajem języka XML, który definiuje pewne dozwolone kombinacje elementów XML. Na przykład dokument XAML powinien zawierać jeden element nadrzędny dla wszystkich pozostałych, który ma reprezentować okno. Obiekt ten może mieć jednego potomka, którym zazwyczaj jest jakiś kontener. Ten z kolei może posiadać wielu potomków o określonych własnościach, jak Width czy Height. XAML jest bardzo skomplikowanym językiem. Wiele jego elementów może znajdować się tylko w ściśle określonych miejscach w pliku. Na przykład w elemencie Button mogą znajdować się takie atrybuty, jak Background, BorderThickness, Margin, Width, Height i Content. Edytor XAML posiada funkcję IntelliSense, która ułatwia przypomnienie sobie, co można wstawić w danym miejscu, jednak mimo to tworzenie tego typu plików stanowi nie lada wyzwanie. Jednym z najlepszych sposobów na nauczenie się języka XAML jest poszukanie przykładów w internecie. Wiele witryn, ze stroną Microsoftu na czele, udostępnia takie przykłady. Podczas gdy dokumentacja okazuje się trudna w użyciu, przykłady mogą znacznie ułatwić naukę wybranych technik. Na dobry początek można zajrzeć na stronę msdn2.microsoft.com/library/ms752059.aspx, na której znajduje się przegląd cech języka XAML, oraz na stronę biblioteki Windows Presentation Foundation — msdn2.microsoft.com/library/ms754130.aspx. Jeśli odkryjesz jakieś inne źródła dobrych przykładów, napisz mi o nich na adres [email protected], a ja umieszczę odnośniki do nich na stronie stanowiącej uzupełnienie tej książki.
228
Część II
Wstęp do języka Visual Basic
W poniższych podrozdziałach opisane zostaną niektóre podstawowe elementy aplikacji XAML. Znajdą się w nich objaśnienia, jak tworzyć obiekty, jak za pomocą zasobów, stylów i szablonów sprawić, że obiekty będą miały spójny wygląd i dadzą się łatwo modyfikować, a także jak uzyskać ich interaktywność poprzez transformacje i animacje. Ze znajdującego się w dalszej części rozdziału w podrozdziale „Procedury w WPF” dowiesz się, jak uzyskać takie same efekty przy użyciu Visual Basica.
Obiekty W plikach XAML obiekty są reprezentowane przez elementy XML. Ich własności odpowiadają atrybutom lub osobnym elementom zagnieżdżonym w elementach głównych. Na przykład poniższy kod XAML wyświetla okno z obiektem Grid. Element Grid posiada atrybut Background, który zmienia kolor tła tego elementu na czerwony.
Bardziej skomplikowane własności muszą być ustawiane za pomocą osobnych podelementów. Poniższy kod przedstawia podobny obiekt Grid z gradientem liniowym w tle:
Zamiast atrybutu Background element Grid zawiera element Grid.Background. Ten z kolei ma element LinearGradientBrush, który definiuje tło. Atrybuty StartPoint i EndPoint informują, że gradient ma rozpocząć się w lewym górnym rogu siatki (0,0), a zakończyć w dolnym prawym rogu (1,1). Znajdujące się wewnątrz definicji pędzla elementy GradientStop definiują kolory, które gradient ma wyświetlać, przechodząc z jednego rogu do przeciwnego. W tym przypadku rozpoczyna się on od czerwieni, w połowie drogi zamienia się w biel, a na koniec przechodzi w niebieski.
Rozdział 12.
Używanie kontrolek WPF
229
Własność Background można ustawić tylko jeden raz. Jeśli w jednym elemencie zostanie ustawiony atrybut Background i użyty element Grid.Background, edytor XAML zgłosi błąd. Elementy obiektów często zawierają inne elementy, które dodatkowo definiują różne ich cechy. Poniższy kod definiuje siatkę z dwoma rzędami i trzema kolumnami (od tej pory opuszczam element Window, aby zaoszczędzić trochę miejsca). Każdy z tych rzędów zajmuje 50 procent wysokości siatki. Pierwsza kolumna ma szerokość 50 pikseli, a pozostałe dwie stanowią po 50 procent tego, co zostało.
Znak * w miarach oznacza, że kontrolka podzieli swoją szerokość lub wysokość proporcjonalnie pomiędzy elementy, które zawierają tę gwiazdkę. Jeśli na przykład siatka zawiera dwa rzędy o wysokości 50*, każdy z nich będzie miał połowę dostępnej przestrzeni w pionie. Jeśli dwa rzędy byłyby wysokości 10* i 20*, pierwszy miałby wysokość o połowę mniejszą od drugiego. Jeśli kontrolka zawiera także elementy bez gwiazdki, najpierw zajmowana jest przestrzeń dla nich. Wyobraźmy sobie na przykład siatkę z rzędami o wysokości 10, 20* i 30*. Wtedy pierwszy z nich będzie miał wysokość 10, drugi zabierze ⅔ pozostałej przestrzeni, a trzeci zajmie resztę. W większości przykładów zaprezentowanych w tej książce wartości są przynajmniej zbliżone do procentów, ponieważ takie łatwiej jest zrozumieć. W treści elementu reprezentującego obiekt może także znajdować się treść tego obiektu. W niektórych przypadkach jest to zwykły tekst. Poniższy kod definiuje obiekt Button z napisem Kliknij mnie:
W treści obiektu mogą też znajdować się inne obiekty. Poniższy kod definiuje siatkę z trzema rzędami i trzema kolumnami, która zawiera dziewięć przycisków:
230
Część II
Wstęp do języka Visual Basic
Z reguły budowę obiektu Window najłatwiej jest rozpocząć w edytorze graficznym XAML, ale z czasem może być konieczne zajrzenie do kodu i sprawdzenie, co edytor wygenerował. Zazwyczaj utworzony kod jest bliski, ale nie dokładnie taki jak wymagany. Na przykład w wyniku ustawiania rozmiaru i pozycjonowania elementu za pomocą klikania i przeciągania edytor może określić jego własność Margin jako 10,10,11,9, podczas gdy w założeniu było 10,10,10,10 (albo po prostu 10). Czasami problemy sprawia też ustawienie kontrolek dokładnie tam, gdzie się chce. Niektóre z tych wartości można poprawić w oknie Properties, ale niekiedy łatwiej zrobić to bezpośrednio w kodzie XAML.
Zasoby Na rysunku 12.4 przedstawiono program o nazwie Calculator, który można pobrać z serwera FTP wydawnictwa Helion. Aplikacja zawiera trzy grupy przycisków z gradientem radialnym w tle w podobnych kolorach. Przyciski z cyframi, +/– oraz przecinek mają żółte tła, narysowane przez obiekty RadialGradientBrush. CE, C i = mają niebieskie tła, a przyciski działań matematycznych — zielone. Rysunek 12.4. Modyfikowanie tego programu jest ułatwione dzięki użyciu w nim zasobów
Rozdział 12.
Używanie kontrolek WPF
231
Każdy z tych przycisków można by utworzyć osobno poprzez dodanie do niego odpowiedniego obiektu RadialGradientBrush w celu pokolorowania tła. Wyobraźmy sobie jednak, że w pewnym momencie decydujesz się na zmianę koloru wszystkich przycisków z cyframi na czerwony. Trzeba by zmodyfikować po kolei każdy z dwunastu obiektów RadialGradientBrush. Oznacza to dużo pracy, a w czasie jej wykonywania można wiele razy popełnić jakiś błąd. Zmiany byłyby jeszcze trudniejsze, gdyby zdecydowano się na zmodyfikowanie liczby kolorów używanych przez przyciski (aby na przykład utworzyć gradient, który przechodziłby od żółtego przez czerwony do pomarańczowego) lub całkowitą zmianę pędzla, na przykład na LinearGradientBrush. Jednym z ułatwień konserwacji projektów, które oferuje język XAML, są zasoby (ang. resources). Zdefiniowanego zasobu użyjesz przy definiowaniu obiektów. W tym przypadku zasoby można wykorzystać do reprezentowania teł przycisków, a następnie posłużyć się nimi do ustawiania własności Background każdego przycisku. Jeśli później będzie konieczna zmiana tego tła, trzeba będzie tylko zmodyfikować zasoby. Poniższy kod demonstruje, w jaki sposób kalkulator z rysunku 12.4 tworzy zasób LinearGradientBrush o nazwie brResult, który jest wykorzystywany przez program do rysowania ramki z wynikiem. Wielokropki oznaczają usunięte — dla klarowności — fragmenty kodu. ... ... ...
Element Window zawiera znacznik Window.Resources z definicjami zasobów. Pędzel jest zdefiniowany przez element LinearGradientBrush. Jedną z najważniejszych części tego ostatniego jest atrybut x:Key, który identyfikuje go i pozwala użyć później. Poniższy kod demonstruje definicję obiektu Label, który wyświetla wyniki obliczeń. Atrybut Background odwołuje się do zasobu brNumber.
232
Część II
Wstęp do języka Visual Basic
Jeśli później będzie trzeba zmienić kolor tła etykiety prezentującej wynik, wystarczy zmodyfikować definicję zasobu brResult. W tym przykładzie zasób jest wykorzystywany przez tylko jedną etykietę, a więc oszczędność czasu jest znikoma. Natomiast przyciski tego samego programu używają jednego zasobu wielokrotnie. Jednak nie korzystają one z zasobów bezpośrednio, a za pośrednictwem styli, które zostaną opisane w kolejnym podrozdziale.
Style Zasoby ułatwiają tworzenie wielu kontrolek z takim samym atrybutem, jak na przykład tło. Style zwiększają te możliwości i pozwalają na zebranie w jednym pakiecie kilku własności. Można na przykład zdefiniować styl definiujący tło, szerokość, wysokość i własności pisma, a następnie wykorzystać go do definiowania kontrolek. Za pomocą stylów zdefiniujesz też inne style. Można na przykład utworzyć styl podstawowy, mający zastosowanie do wszystkich przycisków w aplikacji, a następnie za jego pomocą zdefiniować inne — dla różnych rodzajów przycisków. Poniższy listing przedstawia kod stylu o nazwie styAllButtons. Zawiera on elementy Setter, ustawiające własności kontrolek. Styl ten określa własność Focusable kontrolki jako false i margines jako 2,2,2,2.
Poniżej znajduje się definicja stylu o nazwie styClear dla przycisków C, CE i = kalkulatora:
Atrybut BasedOn sprawia, że nowy styl zostaje utworzony przy użyciu własności zdefiniowanych w styAllButtons. Następnie dodaje on za pomocą elementów Setter nowe wartości dla własności Background (ustawia na zasób pędzla o nazwie brClear) i Grid.Row (wszystkie te przyciski znajdują się w pierwszym rzędzie przycisków kalkulatora). Następnie przesłania wartość stylu styAllButtons dla własności Margin tych przycisków poprzez ustawienie nad nimi większego marginesu. Poniżej znajduje się kod definiujący przycisk C. Dzięki ustawieniu stylu tego przycisku na styClear większość jego własności zostaje ustawiona za pomocą jednej instrukcji. Następnie określane są własność Grid.Column przycisku (wartości te są różne dla przycisków C, CE i =) i jego treść.
Rozdział 12.
Używanie kontrolek WPF
233
Dzięki stylom można utrzymać wszystkie wspólne własności zestawu kontrolek w jednej kolekcji. Jeśli teraz trzeba by było zmienić kolor przycisków C, CE i =, wystarczyłoby zmodyfikować definicję pędzla brClear. Aby dokonać zmian w marginesach tych pędzli, należy zmodyfikować styl styClear. Jak widać na podstawie przykładów, dzięki tej technice kod kontrolek pozostaje prosty. Dodatkowo style ułatwiają wprowadzanie później zmian własności kontrolek. Jeśli na przykład zdecydujesz się określić rodzinę czcionek i rozmiar pisma dla przycisków C, CE i =, będziesz musiał tylko dodać odpowiednie elementy Setter do stylu styClear, zamiast dodawać własność do każdego z tych przycisków. Aby ustawić czcionkę dla wszystkich przycisków w programie, wystarczy wstawić odpowiednie elementy Setter do stylu styAllButtons, a pozostałe style automatycznie uwzględnią te zmiany.
Szablony Szablony określają sposób rysowania kontrolek i ich domyślne działanie. Na przykład domyślny szablon przycisków sprawia, że zmieniają one kolor na biały, kiedy najedzie na nie kursor myszy. Gdy przycisk zostanie kliknięty, stanie się nieco ciemniejszy, a przy jego górnej i lewej krawędzi pojawią się cienie. Za pomocą elementów Template można zmienić to domyślne zachowanie. Poniższy kod, którego miejsce jest w sekcji Window.Resources, definiuje szablon przycisku:
234
Część II
Wstęp do języka Visual Basic
Kod zaczyna się od elementu Style, który zawiera dwa elementy Setter. Pierwszy z nich ustawia własność Margin przycisku na 2,2,2,2, a drugi określa własność Template. Jego wartością jest element ControlTemplate, który wskazuje na przyciski. Element ControlTemplate zawiera element Grid, w którym przechowuje inne elementy. W tym przypadku element Grid zawiera element Polygon (wielokąt) o nazwie pgnBorder. Atrybut Points określa punkty wyznaczające ten wielokąt. Dzięki ustawieniu atrybutu Fill wielokąta na Stretch wielokąt wypełnia całą dostępną mu przestrzeń, a współrzędne Points mieszczą się w skali 0.0 do 1.0 w obrębie tego obszaru. Atrybut Fill wielokąta został ustawiony na pędzel brOctagonUp. Definicja tego ostatniego znajduje się w innym miejscu sekcji Window.Resources, której nie ma na tym listingu. Jest to pędzel RadialGradientBrush, który przechodzi od koloru białego w środku do czerwonego na brzegach. Element ControlTemplate zawiera też sekcję Triggers. Znajdujący się w niej element Trigger zostaje wykonany, gdy warunek IsMouseOver tego przycisku ma wartość true. Jeśli zdarzenie to ma miejsce, element Setter zmienia własność Stroke wielokąta na Black. Drugi element Setter ustawia własność Fill tego samego wielokąta na inny pędzel o nazwie brOctagonOver (którego również tutaj nie pokazano). Przechodzi on od koloru czerwonego w środku do białego na krawędziach. Jako że styl ten nie posiada atrybutu x:Key, można go stosować do wszystkich przycisków w elemencie Window, które nie mają bezpośrednio ustawionego stylu. Poniższy kod jest wykorzystywany do tworzenia kontrolek przez program ButtonTemplate, który można pobrać z serwera FTP wydawnictwa Helion:
Element Window zawiera element Grid, w którym znajduje się sześć przycisków. Pierwsze cztery z nich nie mają bezpośrednio ustawionego stylu, dzięki czemu używają zdefiniowanego wcześniej stylu ośmiokąta.
Rozdział 12.
Używanie kontrolek WPF
235
Pozostałe przyciski mają atrybuty Style ustawione na styl styYellowButton (również zdefiniowany w sekcji Windows.resources, ale nie znajdziesz go tu), dzięki czemu posiadają żółte tło. Styl ten dodatkowo ustawia tekst przycisków na górze i pośrodku. Kiedy kursor znajdzie się nad takim przyciskiem, tło tego ostatniego stanie się pomarańczowe. Kliknięcie lewym przyciskiem myszy spowoduje zmianę koloru tła na czerwony, a tekst zmieni się w Wciśnięty!. Kod ten demonstruje program o nazwie ButtonTemplate, który można znaleźć na serwerze FTP wydawnictwa Helion. Pobierz go, aby sprawdzić, jak działają elementy Trigger. Na rysunku 12.5 przedstawiono rezultat opisanego kodu. Kursor znajduje się nad drugim przyciskiem, dzięki czemu ten ostatni ma czarne obramowanie, a jego tło przechodzi od czerwonego w środku do białego na krawędziach. Rysunek 12.5. Szablony pozwalają zmienić wygląd i zachowanie takich obiektów, jak przyciski
W wersji Visual Studio, której aktualnie używam, ośmiokątne przyciski nie są rysowane w graficznym edytorze XAML. Po uruchomieniu programu działają one jak należy, ale wydaje się, że edytor XAML nie rozpoznaje kodu ustawiającego kolor żółtego przycisku na biały w momencie jego kliknięcia. Wcześniejsza wersja tego projektanta wyświetlała tylko tekst. Za pomocą szablonów można zmienić wygląd i zachowanie obiektów XAML, co pozwala nadać aplikacjom niepowtarzalny styl. Pamiętaj jednak, żeby nie przesadzać. Drastyczna zmiana koloru, kształtu, napisu i innych cech przycisków w chwili ich kliknięcia może być rozpraszająca dla użytkownika. Szablony służą do sprawiania, że aplikacja będzie wyróżniała się wyglądem, ale nie odstraszała.
Transformacje Własności decydują o podstawowych cechach wyglądu kontrolki, ale można dokonać bardziej radykalnych zmian w tej kwestii. W tym celu należy użyć elementu RenderTransform. Poniższy kod tworzy przycisk obrócony o 270 stopni. Element Button.RenderTransform zawiera element RotateTransform, który reprezentuje rotację.
236
Część II
Wstęp do języka Visual Basic
W języku XAML dostępne są także elementy TranslateTransform i ScaleTransform, które umożliwiają translację i skalowanie obiektów. Program RotatedButton wykorzystuje transformację do narysowania kilku obróconych oraz przeskalowanych pionowo i poziomo przycisków. Jest on przedstawiony na rysunku 12.6. Można go pobrać z serwera FTP wydawnictwa Helion. Rysunek 12.6. Za pomocą elementów RotateTransform i ScaleTransform można obracać i skalować przyciski — zarówno w pionie, jak i poziomie
Istnieje też element TransformGroup, za pomocą którego można wykonać serię transformacji obiektu. Na przykład pozwala on na translację, skalowanie, obrót i ponowną translację obiektu.
Animacje W podrozdziale „Szablony” wyjaśniłem, jak używać elementów Trigger, aby zmienić wygląd obiektu w odpowiedzi na wybrane zdarzenia. Można się z niego na przykład dowiedzieć, jak sprawić, by przycisk zmieniał kolor tła i obramowania, kiedy najedzie na niego kursor. Język XAML umożliwia także tworzenie bardziej skomplikowanych działań, które będą trwały przez określony czas. Można na przykład sprawić, że kliknięty przycisk będzie się powoli obracał. Za rozpoczęcie animacji odpowiada tzw. trigger (wyzwalacz), a do jej kontroli służy element Storyboard. Poniższy kod programu SpinButton, który można pobrać z serwera FTP wydawnictwa Helion, sprawia, że jego przycisk — gdy zostanie kliknięty — obraca się wokół własnej osi:
Większość tego kodu powinna wydawać się znajoma. Atrybuty elementu Button ustawiają jego nazwę, treść i rozmiar. Element Background wypełnia przycisk gradientem radialnym RadialGradientBrush. Element Button zawiera element RenderTransform, podobny do tych, które zostały opisane w poprzednim podrozdziale. W tym przypadku transformacja polega na obróceniu przycisku (RotateTransform) przy początkowym ustawieniu kąta obrotu na 0, dzięki czemu przycisk znajduje się w swoim normalnym położeniu. Środek tej rotacji jest ustawiony na środek przycisku. Nosi ona nazwę rotButton, za pomocą której do tej rotacji mogą odwoływać się także inne części kodu. Za elementem transformacji znajduje się sekcja Triggers. Umieszczony w niej element EventTrigger reaguje na zdarzenie typu routed event ButtonClick. Zdarzenie routed event jest po prostu przetwarzane w taki sposób, aby można je było przechwycić za pomocą zwykłej procedury obsługi zdarzeń. Kiedy użytkownik klika przycisk, zostaje uruchomione zdarzenie Button.Click, a do akcji wkracza trigger. Element Actions triggera zawiera zadania, które mają zostać przez niego wykonane. W tym przypadku jest to akcja BeginStoryboard. W elemencie BeginStoryboard znajduje się element Storyboard, który reprezentuje zadania tego elementu. Atrybut TargetName elementu Storyboard określa obiekt docelowy, na rzecz którego element Storyboard powinien działać, w tym przypadku jest to obiekt RotateTransform o nazwie rotButton. Atrybut TargetProperty wskazuje, którą własność docelowego przycisku ma zmieniać Storyboard — w tym przypadku jest to własność Angle obiektu. Element Storyboard zawiera także element DoubleAnimationUsingKeyFrames. Klatka kluczowa (ang. key frame) jest punktem o znanych wartościach w sekwencji animacji. Program oblicza wartości znajdujące się pomiędzy tymi klatkami. W ten sposób zapewnia płynność animacji.
238
Część II
Wstęp do języka Visual Basic
Wspomniany element DoubleAnimationUsingKeyFrames zawiera zestaw elementów SplineDoubleKeyFrame, które definiują kluczowe wartości animacji. Każda z klatek kluczowych podaje swój czas w animacji w godzinach, minutach i sekundach, a także wartość modyfikowanej własności, którą ta powinna przybrać w tym momencie. W tym przypadku kąt obrotu powinien wynosić 0 stopni na początku, 30 po ukończeniu 20 procent animacji, 330, kiedy zostanie zakończone 80 procent oraz 360 na zakończenie. W rezultacie przez pierwsze 0,2 sekundy przycisk obraca się powoli, przez kolejne 0,6 sekundy przyspiesza, a na zakończenie znowu obraca się wolniej. Program SpinButton tworzy animację przy użyciu tylko jednej własności — kąta obrotu przycisku. Jednak możliwe jest też wykorzystanie większej liczby własności. Również dostępny w internecie program SpinAndGrowButton tworzy animacje poprzez zmodyfikowanie jednocześnie kąta obrotu i rozmiaru przycisku. Kod tej aplikacji różni się zasadniczo w dwóch miejscach od poprzedniego. Po pierwsze, element RenderTransform tego nowego przycisku zawiera grupę TransformGroup z dwiema transformacjami — jedna odpowiada za kąt obrotu, a druga za rozmiar przycisku:
Druga różnica dotyczy elementu Storyboard. W poniższym kodzie zostały opuszczone jego atrybuty TargetName i TargetProperty. Zawiera on trzy elementy DoubleAnimationUsingKeyFrame — i to właśnie w nich zostały zastosowane wspomniane atrybuty. Te trzy animacje modyfikują kąt obrotu przycisku oraz jego szerokość i wysokość.
Rozdział 12.
Używanie kontrolek WPF
239
Za pomocą elementów Storyboard można tworzyć skomplikowane animacje, uruchamiane w odpowiedzi na określone zdarzenia. Podobnie jednak jak ma to miejsce w przypadku szablonów, należy podchodzić do nich wstrzemięźliwie. Kilka niewielkich animacji może podnieść walory wizualne programu, natomiast zbyt duża liczba obszernych będzie tylko rozpraszać i denerwować użytkowników.
Rysowanie figur geometrycznych W bibliotece WPF dostępnych jest kilka obiektów, które służą do rysowania dwuwymiarowych figur. W kolejnych podrozdziałach zostaną opisane najbardziej przydatne z nich — Line, Ellipse, Rectangle, Polygon, Polyline oraz Path.
Odcinek Obiekt Line rysuje prosty odcinek, który łączy dwa wyznaczone punkty. Te ostatnie wyznaczają atrybuty X1, Y1, X2 i Y2. Poniższy kod rysuje odcinek, który zaczyna się w punkcie o współrzędnych (10, 10), a kończy w punktach (90, 90) oraz (90, 10) i (10, 90):
Atrybuty Stroke i StrokeThickness określają kolor i grubość odcinka. Atrybuty StrokeStartLineCap i StrokeEndLineCap określają wygląd punktu początkowego i końcowego odcinka. W tym przykładzie są one zaokrąglone.
Elipsa Elipsę rysuje obiekt Ellipse. Poniższy kod rysuje elipsę z gradientem liniowym w tle:
240
Część II
Wstęp do języka Visual Basic
Prostokąt Do rysowania prostokątów służy obiekt Rectangle. Składnia jest podobna do składni elipsy.
Wielokąt Do rysowania zamkniętych wielokątów służy obiekt Polygon. Listę punktów, które mają zostać połączone, wyznacza atrybut Points. Obiekt ten automatycznie zamyka figurę poprzez połączenie ostatniego punktu z pierwszym. Poniższy kod rysuje czteroramienną gwiazdę:
Linia łamana Do rysowania łamanych służy obiekt Polyline. Jest on podobny do obiektu Polygon, tyle że nie zamyka automatycznie figury poprzez połączenie ostatniego i pierwszego punktu. Poniższy kod rysuje cztery przerywane linie:
Rozdział 12.
Używanie kontrolek WPF
241
Ten kod zwraca uwagę na kilka ogólnych zagadnień związanych z rysowaniem linii. Atrybut StrokeLineJoin określa sposób łączenia linii. W tym przypadku zostały one połączone zaokrąglonymi rogami. Atrybut StrokeDashArray określa styl linii przerywanej. Wartości wyznaczają liczbę rysowanych i pomijanych jednostek. W tym przypadku 2,1,2,3 oznacza, że rysowane są dwie jednostki, pomijana jest jedna, znowu rysowane są dwie jednostki i pomijane trzy. Każda jednostka reprezentuje szerokość linii.
Ścieżka Obiekt Path rysuje różne figury, takie jak odcinki, łuki i krzywe. Obiekt ten może być niesłychanie skomplikowany. W jego skład mogą wchodzić inne obiekty rysujące oraz kilka innych obiektów rysujących gładkie krzywe. Obiekt Path można zdefiniować na dwa sposoby. Po pierwsze, umieścić w nim inne elementy (Line, Ellipse itd.), które mają zostać przez niego narysowane. Druga (i bardziej zwięzła) metoda polega na użyciu atrybutu Data elementu Path. Jest to atrybut tekstowy, zawierający szereg zakodowanych poleceń rysowania figur geometrycznych. Na przykład poniższy kod przenosi obiekt Path do punktu o współrzędnych (20,20), a następnie łączy następujące punkty — (80,20), (50, 60), (90, 100) i (50, 120):
Współrzędne punktów można oddzielać za pomocą spacji lub przecinków. Aby kod był łatwiejszy do odczytania, można pomiędzy punktami X i Y stosować przecinki, a spacjami oddzielać całe punkty, jak w powyższym kodzie. W niektórych poleceniach można używać zarówno małych, jak i wielkich liter. Te pierwsze oznaczają, że współrzędne jednego punktu są względne do współrzędnych poprzedniego punktu. Na przykład poniższe dane powodują przeniesienie obiektu do punktu o współrzędnych (10,20) i rysowanie do punktu o współrzędnych bezwzględnych (30,40): Data="M 10,20 L 30,40"
Natomiast poniższe dane przenoszą obiekt tak jak poprzednio do punktu o współrzędnych (10,20), ale tym razem rysowanie biegnie na odległość (30,40) względem aktualnego położenia. W wyniku tego odcinek kończy się w punkcie (10+30, 20+40) = (40,60). Data="M 10,20 l 30,40"
242
Część II
Wstęp do języka Visual Basic
Nie ma miejsca na pełny opis obiektu Path, ale w poniższej tabeli znajduje się zestawienie poleceń, których można używać w jego atrybucie Data. Polecenie
Rezultat
Przykład
F0
Ustawia regułę wypełniania na regułę nieparzystości lub parzystości.
F0
F1
Ustawia regułę wypełniania na regułę bez zera.
F1
M lub m
Przenosi do wyznaczonego punktu bez rysowania.
M 10,10
L lub l
Rysuje linię do wyznaczonego punktu (lub wyznaczonych punktów).
L 10,10 20,20 30,10
H lub h
Rysuje poziomą linię od bieżącego punktu do podanej współrzędnej X.
h 50
V lub v
Rysuje pionową linię od bieżącego punktu do podanej współrzędnej Y.
v 30
C lub c
Rysuje krzywą Beziera trzeciego stopnia. Przyjmuje trzy parametry — dwa punkty kontrolne i jeden punkt końcowy. Krzywa zaczyna się w bieżącym punkcie i przechodzi w kierunku pierwszego punktu kontrolnego. Kończy swój bieg w punkcie końcowym, nadchodząc z kierunku drugiego punktu kontrolnego.
C 20,20 60,0 50,50
S lub s
Rysuje gładką krzywą Beziera trzeciego stopnia. Jako parametry przyjmuje dwa punkty — kontrolny i końcowy. Krzywa ta swój pierwszy punkt kontrolny definiuje jako odzwierciedlenie drugiego punktu kontrolnego, używanego przez poprzednie polecenie S, a następnie rysuje krzywą Beziera trzeciego stopnia przy użyciu tego punktu i swoich dwóch punktów. W ten sposób powstaje zestaw gładko połączonych krzywych Beziera.
S 60,0 50,50 S 80,60 50,70
Q lub q
Rysuje krzywą Beziera drugiego stopnia. Przyjmuje dwa parametry — punkt kontrolny i punkt końcowy. Krzywa zaczyna się w bieżącym punkcie, a zmierza w kierunku punktu kontrolnego. Kończy się w punkcie końcowym, nadchodząc od strony punktu kontrolnego.
Q 80,20 50,60
T lub t
Rysuje gładką krzywą Beziera drugiego stopnia. Jako parametr przyjmuje tylko jeden punkt — punkt końcowy. Krzywa ta swój punkt kontrolny definiuje jako odzwierciedlenie punktu kontrolnego, który jest używany przez poprzednie polecenie T, a następnie rysuje krzywą Beziera drugiego stopnia przy użyciu tego punktu. W ten sposób powstaje gładka krzywa Beziera, przechodząca przez wszystkie punkty podane jako parametry do kolejnych poleceń T.
T 80,20 T 50,60 T 90,100
Rozdział 12.
Używanie kontrolek WPF
Polecenie
Rezultat
Przykład
A lub a
Rysuje łuk eliptyczny. Przyjmuje pięć parametrów: size — promienie X i Y łuku; rotation_angle — kąt obrotu elipsy; large_angle — 0, jeśli łuk ma rozciągać się na mniej niż 180 stopni; 1, jeśli łuk ma mieć 180 lub więcej stopni; sweep_direction — 0, jeśli łuk ma być rysowany w kierunku odwrotnym do ruchu wskazówek zegara; 1 w sytuacji przeciwnej; end_point — punkt, w którym ma kończyć się łuk.
A 50,20 0 1 0 60,80
Z lub z
Zamyka figurę poprzez narysowanie linii od bieżącego punktu do punktu początkowego obiektu Path.
z
243
Kilka różnych obiektów Path demonstruje program Shapes, który można pobrać z serwera FTP wydawnictwa Helion. Widoczny na rysunku 12.7 program BezierCurves, również dostępny w internecie, przedstawia przykłady czterech różnych rodzajów krzywych Beziera. Aplikacja rysuje dodatkowo szarą łamaną, aby uwidocznić parametry tych krzywych. Rysunek 12.7. Obiekt Path może rysować krzywe Beziera
Widoczna po lewej stronie krzywa Beziera trzeciego stopnia łączy dwa punkty końcowe przy użyciu dwóch punktów kontrolnych, które określają jej kierunek przy końcach. Druga z kolei gładka krzywa Beziera trzeciego stopnia przechodzi przez pierwszy, trzeci i piąty punkt. Drugi punkt określa jej kierunek po wyjściu z pierwszego punktu i przed wejściem do trzeciego. Krzywa ta automatycznie definiuje punkt kontrolny, który określa jej kierunek po wyjściu z trzeciego punktu, dzięki czemu gładko przechodzi ona przez ten punkt. W końcu czwarty punkt określa kierunek krzywej, kiedy dochodzi ona do ostatniego, piątego punktu. Kolejna krzywa przedstawia dwie krzywe drugiego stopnia. Pierwsza z nich łączy punkty pierwszy i trzeci z drugim, który determinuje jej kierunek w obu wcześniejszych punktach. Druga krzywa łączy punkty trzeci i piąty, a kierunek określa za pomocą punktu czwartego.
244
Część II
Wstęp do języka Visual Basic
Ostatnia przedstawiona na rysunku 12.7 krzywa Beziera przechodzi za pomocą polecenia M do punktu o współrzędnych (20,20). Następnie używa trzech gładkich krzywych Beziera drugiego stopnia do połączenia trzech kolejnych punktów. Krzywa ta automatycznie definiuje punkty kontrolne potrzebne do gładkiego połączenia punktów. Gdy dysponuje się tymi wszystkimi obiektami, zwłaszcza obiektem Path, można narysować praktycznie wszystko. Edytor graficzny XAML nie udostępnia interaktywnych narzędzi do tworzenia figur, ale w tym celu można wykorzystać edytor tekstowy. Pomocne może być narysowanie wymaganej figury najpierw na papierze.
Procedury w WPF W poprzednich podrozdziałach objaśniono sposoby wykorzystywania języka XAML do budowy okien WPF. Przy jego użyciu można definiować kontrolki, zasoby, style, szablony, transformacje, a nawet animacje. Aplikacja odczytuje kod XAML i tworzy odpowiednie kontrolki i inne obiekty składające się na interfejs użytkownika. Często najprostszym sposobem na zbudowanie formularza jest użycie edytora XAML, ale w razie potrzeby można tworzyć dokładnie takie same obiekty w języku Visual Basic. Interfejs powinno się tworzyć w języku XAML, aby utrzymać rozdział interfejsu użytkownika od kodu źródłowego. Czasami jednak zbudowanie elementów dynamicznych (na przykład w odpowiedzi na załadowanie danych, wprowadzenie danych przez użytkownika czy na błędy) może być łatwiejsze w kodzie Visual Basica. Na przykład poniższy kod Visual Basica dodaje przycisk do formularza WPF: ‘ Przycisk, który chcemy utworzyć. Private WithEvents btnClickMe As Button ‘ Budowanie interfejsu użytkownika. Private Sub Window1_Loaded() Handles Me.Loaded ‘ Domyślna siatka okna. Dim grd As Grid = DirectCast(Me.Content, Grid) ‘ Dodanie przycisku. btnClickMe = New Button() btnClickMe.Content = “Kliknij mnie" btnClickMe.Margin = New Thickness(5) grd.Children.Add(btnClickMe) End Sub ‘ Użytkownik kliknął przycisk. Private Sub btnClickMe_Click() Handles btnClickMe.Click MessageBox.Show(“Kliknięty!") End Sub
Rozdział 12.
Używanie kontrolek WPF
245
Pierwszą czynnością powyższego fragmentu programu jest konwersja własności Content okna na obiekt Grid. Następnie należy utworzyć przycisk i ustawić niektóre jego własności oraz dodać go do kolekcji Children kontrolki Grid. Zmienna kontrolki Button została zadeklarowana na poziomie modułu; zawiera słowo kluczowe WithEvents, dzięki czemu przechwycenie zdarzenia Click tego przycisku jest łatwe. Program ProceduralAnimatedButton, który można pobrać z serwera FTP wydawnictwa Helion, implementuje w Visual Basicu niektóre z opisanych wcześniej technik zaimplementowanych w języku XAML. Tworzy obiekt pędzla i przy jego użyciu definiuje styl dla przycisków. Następnie za pomocą tego ostatniego tworzy trzy przyciski. Kiedy kursor najeżdża nad jeden z tych przycisków, program tworzy i odtwarza animację, która zwiększa wybraną pozycję. Gdy kursor schodzi z przycisku, rozmiar tego ostatniego zostaje przywrócony do stanu początkowego. Poniższy kod tworzy obiekty interfejsu użytkownika w czasie ładowania okna programu: Private WithEvents btnCenter As Button Private Const BIG_SCALE As Double = 1.5 Private Sub Window1_Loaded() Handles Me.Loaded ‘ Utworzenie stylu dla przycisków. Dim br_button As New RadialGradientBrush( _ Colors.HotPink, Colors.Red) br_button.Center = New Point(0.5, 0.5) br_button.RadiusX = 1 br_button.RadiusY = 1 Dim style_button As New Style(GetType(Button)) style_button.Setters.Add(New Setter(Control.BackgroundProperty, _ br_button)) style_button.Setters.Add(New Setter(Control.WidthProperty, CDbl(70))) style_button.Setters.Add(New Setter(Control.HeightProperty, CDbl(40))) style_button.Setters.Add(New Setter(Control.MarginProperty, _ New Thickness(5))) ‘ Ustawienie środka transformacji na punkt o współrzędnych (0.5, 0.5). style_button.Setters.Add(New Setter( _ Control.RenderTransformOriginProperty, New Point(0.5, 0.5))) ‘ Utworzenie kontrolki StackPanel do przechowywania przycisków. Dim stack_panel As New StackPanel() stack_panel.Margin = New Thickness(20) ‘ Utworzenie lewego przycisku. Dim btn_left As Button btn_left = New Button() btn_left.Style = style_button btn_left.Content = "Lewy" btn_left.RenderTransform = New ScaleTransform(1, 1) btn_left.SetValue( _ StackPanel.HorizontalAlignmentProperty, _ Windows.HorizontalAlignment.Left) AddHandler btn_left.MouseEnter, AddressOf btn_MouseEnter
246
Część II
Wstęp do języka Visual Basic
AddHandler btn_left.MouseLeave, AddressOf btn_MouseLeave stack_panel.Children.Add(btn_left) ‘ Utworzenie środkowego przycisku. btnCenter = New Button() btnCenter.Style = style_button btnCenter.Content = "Środkowy" btnCenter.RenderTransform = New ScaleTransform(1, 1) btnCenter.SetValue( _ StackPanel.HorizontalAlignmentProperty, _ Windows.HorizontalAlignment.Center) AddHandler btnCenter.MouseEnter, AddressOf btn_MouseEnter AddHandler btnCenter.MouseLeave, AddressOf btn_MouseLeave stack_panel.Children.Add(btnCenter) ‘ Utworzenie prawego przycisku. Dim btn_right As New Button btn_right.Style = style_button btn_right.Content = "Prawy" btn_right.RenderTransform = New ScaleTransform(1, 1) btn_right.SetValue( _ StackPanel.HorizontalAlignmentProperty, _ Windows.HorizontalAlignment.Right) AddHandler btn_right.MouseEnter, AddressOf btn_MouseEnter AddHandler btn_right.MouseLeave, AddressOf btn_MouseLeave stack_panel.Children.Add(btn_right) Me.Content = stack_panel End Sub
Kod ten zaczyna się od deklaracji kontrolki Button przy użyciu słowa kluczowego WithEvents. Program tworzy trzy przyciski, ale zdarzenia Click przechwytuje tylko dla tego jednego. W kodzie znajduje się też definicja stałej, która określa rozmiar powiększonego przycisku. Kiedy okno jest ładowane, program tworzy pędzel RadialGradientBrush i definiuje jego własności. Następnie powstaje obiekt Style, mający zastosowanie do obiektów Button. Zostaje też dodanych kilka obiektów Setter do obiektu Style, które mają za zadanie ustawić własności Background, Width, Height, Margin i RenderTransformOrigin kontrolki Button. Następnie program tworzy trzy obiekty Button. Ustawia różne ich własności, wliczając ustawienie własności Style na utworzony wcześniej obiekt Style. Ponadto określa własność RenderTransform każdej kontrolki Button jako obiekt ScaleTransform, który początkowo skaluje przycisk w pionie i poziomie o współczynnik 1. Później transformacja ta będzie wykorzystywana do powiększania i zmniejszania tej kontrolki Button. Za pomocą metody SetValue własność HorizontalAlignment każdej kontrolki Button została ustawiona na kontrolkę StackPanel. Metoda AddHandler tworzy dla każdego z tych przycisków procedurę obsługi zdarzeń MouseEnter i MouseLeave. Na końcu kontrolki Button zostają wstawione do kolekcji Children kontrolki StackPanel. Procedura obsługi zdarzenia Loaded okna kończy się ustawieniem jego własności Content na utworzoną kontrolkę StackPanel z kontrolkami Button.
Rozdział 12.
Używanie kontrolek WPF
247
Poniższy kod demonstruje reakcję programu na najechanie kursorem na przycisk: ‘ Kursor przesunięty nad przycisk. ‘ Powiększenie przycisku. Private Sub btn_MouseEnter(ByVal btn As Button, _ ByVal e As System.Windows.Input.MouseEventArgs) ‘ Transformacja przycisku. Dim scale_transform As ScaleTransform = _ DirectCast(btn.RenderTransform, ScaleTransform) ‘ Utworzenie animacji DoubleAnimation. Dim ani As New DoubleAnimation(1, BIG_SCALE, _ New Duration(TimeSpan.FromSeconds(0.15))) ‘ Utworzenie zegara dla animacji. Dim ani_clock As AnimationClock = ani.CreateClock() ‘ Skojarzenie zegara z własnościami ScaleX i ScaleY ‘ transformacji. scale_transform.ApplyAnimationClock( _ ScaleTransform.ScaleXProperty, ani_clock) scale_transform.ApplyAnimationClock( _ ScaleTransform.ScaleYProperty, ani_clock) End Sub
Powyższy kod tworzy obiekt ScaleTransform przycisku. Następnie powstaje obiekt DoubleAnimation, zmieniający wartość z 1 na BIG_SCALE (zdefiniowanej we wcześniejszej instrukcji Const jako 1.5) w ciągu 0,15 sekundy. Do kontroli animacji użyto zegara AnimationClock, który utworzono przez instrukcję CreateClock. Na końcu zostaje dwukrotnie wywołana metoda ApplyAnimationClock obiektu ScaleTransformation — po jednym razie dla skalowania pionowego i poziomego. W rezultacie obiekt ScaleTransform kontrolki Button powiększa zarówno jej wysokość, jak i szerokość. Procedura obsługi zdarzeń btn_MouseLeave jest bardzo podobna — tylko zmniejsza przycisk z rozmiaru BIG_SCALE do 1. Podobny kod do zwiększania i zmniejszania przycisków jest wykorzystywany przez program GrowingButtons. Jednak zamiast prostego obiektu DoubleAnimation do powiększania przycisków używa obiektu DoubleAnimationUsingKeyFrames. Pozwala on na zdefiniowanie zestawu wartości, przez które animacja musi przejść. Poniżej znajduje się procedura obsługi zdarzenia MouseEnter tego programu: Private Const BIG_SCALE As Double = 1.75 Private Const END_SCALE As Double = 1.5 Private Sub btn_MouseEnter(ByVal btn As Button, _ ByVal e As System.Windows.Input.MouseEventArgs) ‘ Transformacja przycisku. Dim scale_transform As ScaleTransform = _ DirectCast(btn.RenderTransform, ScaleTransform) ‘ Utworzenie obiektu DoubleAnimation, który najpierw ‘ dodatkowo powiększa przycisk, a później ‘ przywraca go do normalnego dużego rozmiaru. Dim ani As New DoubleAnimationUsingKeyFrames() Dim fr1 As New SplineDoubleKeyFrame(1.0, KeyTime.FromPercent(0.0))
248
Część II
Wstęp do języka Visual Basic
Dim fr2 As New SplineDoubleKeyFrame(BIG_SCALE, KeyTime.FromPercent(0.5)) Dim fr3 As New SplineDoubleKeyFrame(END_SCALE, KeyTime.FromPercent(1.0)) ani.KeyFrames.Add(fr1) ani.KeyFrames.Add(fr2) ani.KeyFrames.Add(fr3) ani.Duration = New Duration(TimeSpan.FromSeconds(0.33)) ‘ Utworzenie zegara dla animacji. Dim ani_clock As AnimationClock = ani.CreateClock() ‘Dim ani_clock As AnimationClock = ani.CreateClock() ‘ Skojarzenie zegara z własnościami ScaleX i ScaleY ‘ transformacji. scale_transform.ApplyAnimationClock( _ ScaleTransform.ScaleXProperty, ani_clock) scale_transform.ApplyAnimationClock( _ ScaleTransform.ScaleYProperty, ani_clock) ‘ Wstawienie przycisku na wierzch stosu. grdMain.Children.Remove(btn) grdMain.Children.Add(btn) End Sub
Zamiast zwiększyć współczynnik skali przycisków z 1 do 1.5, animacja ta najpierw powiększa go do 1.75, a potem zmniejsza do 1.5. Daje to bajkowy efekt, który zyskał ostatnio dużą popularność. Po zakończeniu animacji przycisk jest usuwany z kolekcji Children głównej kontrolki Grid, a następnie ponownie do niej dodany, dzięki czemu pojawia się nad sąsiadującymi z nim przyciskami i częściowo je przesłania. Na rysunku 12.8 przedstawiono program GrowingButtons, który można pobrać z serwera FTP wydawnictwa Helion, Kursor znajduje się nad przyciskiem Wtorek. Rysunek 12.8. Program GrowingButtons tworzy animację przycisków za pomocą kodu Visual Basica
Inne przykładowe programy, dostępne do pobrania w internecie, demonstrują pozostałe techniki proceduralnej biblioteki WPF. Na przykład aplikacja ProceduralCalculator tworzy kalkulator podobny do tego z rysunku 12.4, ale cały jego interfejs użytkownika został złożony przy użyciu kodu Visual Basica. Natomiast program GridButtonCode tworzy za pomocą Visual Basica przycisk z siatką, który jest podobny do przycisku przedstawionego na rysunku 12.1.
Rozdział 12.
Używanie kontrolek WPF
249
Dokumenty W bibliotece WPF wyróżniane są trzy rodzaje dokumentów — płynne, o ustalonym formatowaniu i XPS. Dzięki temu można uzyskać wysoką jakość wyświetlania i wydruku tekstu. Na przykład dokumenty o ustalonym formatowaniu pozwalają na tworzenie dokumentów zachowujących takie samo formatowanie — zarówno na monitorze, jak i w wydruku o niskiej lub wysokiej rozdzielczości. Dokument wykorzystuje funkcje każdego z tych urządzeń, aby osiągnąć jak najlepszy możliwy rezultat. Każdy z tych trzech wymienionych rodzajów dokumentów jest bardzo skomplikowany. Dokładny opis zająłby zbyt dużo miejsca. W kolejnych podrozdziałach scharakteryzuję je zatem zwięźle, a także przedstawię krótkie przykłady.
Dokumenty płynne Przeznaczeniem dokumentów płynnych (ang. flow document) jest wyświetlanie jak największej ilości danych w najlepszy z możliwych sposób. Brane są pod uwagę ograniczenia środowiskowe, takie jak rozmiar kontrolki zawierającej dokument. Kiedy kontrolka powiększa się, dokument przegrupowuje swoją zawartość, aby wykorzystać nową dostępną przestrzeń. Gdy zaś znowu się zmniejsza, dokument ponownie przegrupowuje swoją treść, aby dobrze układała się w danej przestrzeni. Przypomina to zachowanie przeglądarki internetowej, gdy położenie obiektów na stronie zmienia się wraz z modyfikacją rozmiaru jej okna. Dokumenty płynne w WPF są reprezentowane przez kontrolkę FlowDocument. Może ona zawierać cztery podstawowe elementy treści — List, Section, Paragraph oraz Table. Ich przeznaczenie jest raczej oczywiste — wyświetlanie danych w postaci listy, w sekcji, w akapicie oraz w tabeli. Mimo iż w elementach tych najczęściej umieszcza się tekst, można tam znaleźć również inne obiekty. Na przykład w akapicie (Paragraph) mogą występować takie kontrolki, jak Button, Label, TextBox czy Grid. Można też umieszczać w nim figury geometryczne, na przykład Polygon, Ellipse czy Path. Piąty element treści to BlockUIElement. Może on zawierać kontrolki interfejsu użytkownika, na przykład Button, Label czy TextBox, ale posiada tylko jednego potomka. Jeśli jednak ten ostatni jest kontenerem typu Grid czy StackPanel, może zawierać inne kontrolki. W bibliotece WPF dostępne są trzy rodzaje obiektów wyświetlających dokumenty płynne — FlowDocumentReader, FlowDocumentPageViewer oraz FlowDocumentScrollViewer. Obiekt FlowDocumentReader pozwala użytkownikowi wybrać jeden z trzech różnych trybów widoku — pojedynczą stronę, dwie strony jak w książce lub dokument przewijany. W trybie pojedynczej strony użytkownik widzi jedną stronę. Obiekt określa jej wielkość na podstawie swoich rozmiarów. Jeśli szerokość czytnika na to pozwoli, dokument zostanie wyświetlony w dwóch lub więcej kolumnach, mimo że nadal będzie uznawał, że pokazuje tylko jedną stronę, nawet jeśli będzie się ona składała z kilku kolumn.
250
Część II
Wstęp do języka Visual Basic
W trybie odczytu książkowego czytnik wyświetla dwie strony za jednym razem. Obiekt dzieli pionowo swoją powierzchnię na dwie połowy i każdą z nich wypełnia jedną stroną danych. Wyświetlane są zawsze dwie strony, bez względu na to, jakiej są wielkości. W trybie przewijania czytnik pokazuje całą zawartość dokumentu na jednej długiej przewijanej stronie. Przypomina to sposób wyświetlania długich dokumentów przez przeglądarki internetowe. Przedstawiony na rysunku 12.9 program UseFlowDocumentReader, który można pobrać z serwera FTP wydawnictwa Helion, prezentuje obiekt FlowDocumentReader, wyświetlający dokument w trybie odczytu książkowego. Za pomocą opcji w menu Widok można zmienić tryb wyświetlania.
Rysunek 12.9. Dokument wyświetlony w książkowym trybie odczytu
Ten program demonstruje kilka przydatnych cech obiektów FlowDocument. Nagłówki sekcji znajdują się w obiektach Paragraph, które definiują własności swojego pisma za pomocą obiektu Style. Aby zmodyfikować wygląd wszystkich tych nagłówków, wystarczy zmienić tylko ten styl. Tekst tego dokumentu został pokolorowany za pomocą gradientu liniowego LinearGradientBrush, który przechodzi od barwy czerwonej do niebieskiej (efekt ten jest widoczny dopiero na kolorowym monitorze).
Rozdział 12.
Używanie kontrolek WPF
251
W pierwszej sekcji tego dokumentu znajduje się tabela, kontrolki Button i TextBox, elipsa (Ellipse) oraz siatka (Grid), która zawiera wielokąt (Polygon). Dzięki użyciu elementu pływającego (Floater) inna kontrolka Grid, zawierająca wielokąt i tekst, może przemieszczać się po dokumencie i ustawiać w miejscach, w których dobrze pasuje. W dokumencie tym znajduje się także lista, której jeden element zawiera obiekt Polygon. Ten ostatni rysuje trójkąt. W dolnej części tego obiektu FlowDocumentReader znajduje się pasek narzędzi. Kliknięcie znajdującej się po lewej stronie ikony szkła powiększającego powoduje pojawienie się pola tekstowego, które służy do wpisywania szukanego tekstu. Jeśli wprowadzona tam fraza zostanie znaleziona, użytkownik będzie mógł przechodzić do kolejnych i poprzednich za pomocą dostępnych strzałek. Na środku tego paska znajduje się licznik, który wyświetla numer aktualnej strony oraz liczbę wszystkich stron. Trzy przyciski umieszczone po prawej stronie pozwalają przełączać widoki — pojedynczej strony, książkowy oraz przewijany. Ostatnia kontrolka po prawej stronie to suwak, za pomocą którego można zmniejszyć lub powiększyć dokument. Obiekty FlowDocumentPageViewer i FlowDocumentScrollViewer zachowują się tak samo jak obiekt FlowDocumentReader — w trybie odpowiednio pojedynczej strony i przewijania. Zastosowanie tych kontrolek demonstrują dostępne do pobrania z internetu programy UseFlowDocumentPageViewer i UseFlowDocumentScrollViewer. Jeśli element FlowDocument zostanie wyświetlony samodzielnie, zachowa się jak obiekt FlowDocumentReader. Zobacz program UseFlowDocument, który można pobrać z serwera FTP wydawnictwa Helion.
Dokumenty o ustalonym formatowaniu Obiekt FixedDocument reprezentuje dokument, który powinien zawsze być wyświetlany w dokładnie taki sposób, jak go zaprojektowano. Podczas gdy obiekt FlowDocument pozwala swojej treści zmieniać położenie, aby wykorzystać jego aktualny rozmiar, w obiekcie FixedDocument wszystko pozostaje cały czas na tym samym miejscu. Jeśli obiekty FlowDocument wydawały się podobne do przeglądarki internetowej, obiekty FixedDocument można porównać do przeglądarki plików PDF Adobe Acrobat. W obiekcie FixedDocument znajdują się jeden lub więcej obiektów PageContent, z których każdy zawiera obiekt FixedPage. Treść umieszcza się w obiektach FixedPage, w których mogą znajdować się wszystkie typowe kontenery i inne obiekty. Do wyświetlania obiektu FixedDocument można w programie wykorzystać kontrolkę DocumentViewer. Udostępnia ona narzędzia do drukowania, zmniejszania i powiększania, ustawiania rozmiaru dokumentu, aby wypełniał całe okno, wyświetlania dokumentów w trybie jednej strony i dwóch stron oraz wyszukiwania fraz w tekście. Przykład wyświetlania dokumentu FixedDcoument w kontrolce DocumentViewer demonstruje program UseFixedDocument, który można pobrać z serwera FTP wydawnictwa Helion.
252
Część II
Wstęp do języka Visual Basic
Dokumenty XPS Poza dokumentami płynnymi i o ustalonym formatowaniu biblioteka WPF definiuje jeszcze trzeci ich rodzaj: XML Paper Specification (XPS). Jest to otwarty standard oparty na XML-u, stosowany do reprezentacji dokumentów o ustalonym formatowaniu. Dokumenty XPS są przechowywane w plikach zwanych pakietami (ang. package). Pakiet składa się z mniejszych fragmentów — części (ang. part). Te ostatnie są plikami i folderami. Dokument XPS zapisany na dysku ma postać archiwum ZIP, które zawiera te pliki i foldery. Gdy zmieni się rozszerzenie tego dokumentu z .xps na .zip, będzie można go otworzyć w każdym programie obsługującym pliki ZIP. Istnieje na przykład możliwość użycia w tym celu Eksploratora Windows. Części dokumentów tworzą logiczną hierarchię (pamiętaj, że dokument ten wykorzystuje format XML, który ma charakter hierarchiczny). Sam dokument może zawierać obiekt FixedDocumentSequence, który z kolei zawiera jeden lub więcej obiektów FixedDocument. Dokumenty te są podobne do opisanych w poprzednim podrozdziale, a więc mogą zawierać kontrolki kontenerowe z dowolną liczbą hierarchicznie ustawionych obiektów. Poza tym, że posiadają funkcje dostępne za pośrednictwem obiektów FixedDocument, dokumenty XPS umożliwiają także cyfrowe podpisywanie pakietów. Podpis pakietu zawiera informacje, kto jest jego właścicielem, datę i godzinę podpisania, a także daje pewność, że dokument od tamtej pory nie został zmodyfikowany. Jeden dokument może posiadać więcej niż jeden podpis cyfrowy, a poszczególne jego części mogą mieć różne poziomy ochrony. Można na przykład zabronić modyfikowania treści dokumentu, ale zezwolić na wstawianie adnotacji. Dokumenty XPS — podobnie jak pozostałe obiekty dokumentów WPF — są skomplikowane. Nie ma tu wystarczająco dużo miejsca, aby je dokładnie opisać. Więcej szczegółowych informacji na ich temat można znaleźć na stronach pomocy Microsoftu (warto zacząć od strony msdn2.microsoft.com/en-us/library/system.windows.xps) i ogólnie w internecie. Pamiętaj, że wiele z dostępnych w sieci przykładów wykorzystania biblioteki WPF zostało napisanych w jej wczesnych wersjach beta, przez co mogą one nie przynosić takiego efektu, jaki można było osiągnąć na początku. Aby zmusić je do prawidłowego działania, być może trzeba będzie odpowiednio je dostosować.
Podsumowanie Jednym z głównych celów biblioteki WPF jest dokładniejsze oddzielenie interfejsu użytkownika od działającego w jego tle kodu. Język XAML służy do tworzenia interfejsów użytkownika za pomocą instrukcji deklaratywnych i pozwala na późniejsze dodanie kodu obsługi zdarzeń wszystkich potrzebnych kontrolek. Dzięki temu, że interfejs użytkownika jest oddzielony od kodu źródłowego, nad tymi dwoma aspektami mogą niezależnie pracować różne zespoły. Do budowy interfejsu można zatrudnić grafika pracującego w edytorze
Rozdział 12.
Używanie kontrolek WPF
253
graficznym XAML, a do pisania kodu źródłowego — programistę Visual Basica. Grafik może później zmodyfikować swoje dzieło, przy czym programista nie będzie zmuszony do zmiany kodu. Dodatkowo biblioteka WPF wprowadza setki nowych obiektów, które służą do definiowania interfejsów użytkownika. Umożliwiają one tworzenie okien wykorzystujących najnowsze osiągnięcia graficzne sprzętu. Pozwalają też uzyskać takie zaawansowane efekty, jak przezroczystość i obracanie kontrolek. Nowe obiekty rysujące generują skomplikowane dwui trójwymiarowe efekty graficzne. Zasoby i style pozwalają dostosowywać obiekty do własnych potrzeb, dzięki czemu łatwo kontrolować ich wygląd w jednym miejscu. Triggery, animacje i elementy Storyboard umożliwiają interakcję z użytkownikiem na bardzo wysokim poziomie. Dzięki temu główny kod nie musi zajmować się tymi kosmetycznymi zadaniami. Nowe obiekty dokumentów pozwalają prezentować dane w taki sposób, że rozpływają się po dostępnej przestrzeni, aby ją maksymalnie wykorzystać, lub pozostają nieruchomo w ustalonym formacie. Bardzo przydatne przeglądarki dokumentów umożliwiają przewijanie, powiększanie i zmniejszanie, drukowanie oraz kopiowanie danych pozycji do schowka. Biblioteka WPF wprowadza bardzo dużo nowych funkcji, a w tym rozdziale przedstawiono zaledwie niewielką ich część. W aplikacjach Windows Forms ważną rolę odgrywa kontrolka Form. Reprezentuje ona najwyższy poziom komponentów interfejsu użytkownika, w których rezydują wszystkie pozostałe kontrolki. W WPF sytuacja jest nieco mniej jasna. Obiektem najwyższego poziomu może tutaj być obiekt klasy Window, który mniej więcej odpowiada kontrolce Form. Może go jednak stanowić również Page, PageFunction lub FlowDocument. W rozdziale 13. — „Okna WPF” — opiszę klasę Windows i pozostałe klasy najwyższego poziomu, a także objaśnię ich specjalną rolę w aplikacjach WPF.
254
Część II
Wstęp do języka Visual Basic
13
Okna WPF W aplikacjach Windows Forms specjalną rolę odgrywają obiekty Form. Reprezentują one najwyższy rząd komponentów interfejsu użytkownika, w których rezydują wszystkie pozostałe kontrolki. Typowa aplikacja Windows Forms zaczyna działanie od wyświetlenia obiektu Form. Formularz ten może zawierać przyciski, menu i różne kontrolki otwierające inne obiekty Form. Jednak wszystkie one znajdują się w obiektach Form. W aplikacjach WPF kontrolki można wyświetlać w obiekcie Window, który zasadniczo jest odpowiednikiem obiektu Form w aplikacjach Windows Forms, lub w obiekcie Page. Pod wieloma względami ten ostatni przypomina obiekt Window, tyle że bez dodatków, takich jak obramowanie, pasek tytułu i menu systemowe (Maksymalizuj, Minimalizuj, Przywrócenie, Zamknij itd.). Obiekt Page musi znajdować się w innym obiekcie, który posiada te dodatki. Z reguły jest on wyświetlany w przeglądarce internetowej, chociaż zamiast niego można użyć kontrolki Frame, która działa podobnie jak przeglądarka internetowa. W tym rozdziale zostaną opisane sposoby używania obiektów najwyższego poziomu Window i Page. Nauczysz się wyświetlać i zarządzać wieloma obiektami tego typu oraz poznasz kilka przykładów demonstrujących proste systemy nawigacji. Pamiętaj, że w pierwszej wersji Visual Basica 2008 w projektach WPF znajdował się błąd. Więcej informacji na ten temat można znaleźć we „Wstępie” na stronie 34. w sekcji „Ostrzeżenie dotyczące Visual Basica 2008 Version 1”.
Aplikacje okienkowe Typowa aplikacja WPF wyświetla swoje kontrolki w obiektach Window. Aby utworzyć tego typu program, kliknij polecenie New Project w menu File w celu wyświetlenia okna dialogowego New Project. Na karcie Windows zaznacz pozycję WPF Application, wpisz nazwę aplikacji i kliknij przycisk OK.
256
Część II
Wstęp do języka Visual Basic
Nowa aplikacja na początku składa się z jednej klasy Window o nazwie Window1. Otwórz okno Solution Explorer i kliknij dwukrotnie pozycję Window1.xaml, aby móc edytować kontrolki tego okna. W celu zmodyfikowania kodu Visual Basica tego okna dwukrotnie kliknij pozycję Window1.xaml.vb. Aby dodać kolejne klasy Window, otwórz menu Project i kliknij w nim pozycję Add Window. Wprowadź nazwę dla tworzonej klasy i kliknij przycisk OK. Aby utworzyć okno w kodzie, należy utworzyć zmienną odwołującą się do nowego egzemplarza tego okna. Następnie trzeba wywołać jego metodę Show, która pokaże je w sposób niemodalny, lub metodę ShowDialog, która wyświetli je w sposób modalny. Poniższy kod tworzy nowe okno typu Window2 i wyświetla je modalnie: Dim win2 As New Window2 win2.ShowDialog()
Mimo iż istnieje wiele podobieństw pomiędzy wykorzystywaniem przez program obiektów Window i Form, są też poważne różnice. Na przykład obie te klasy posiadają własność DialogResult, która wskazuje, w jaki sposób użytkownik zamknął formularz. Ich metoda ShowDialog zwraca ten wynik, dzięki czemu można łatwo sprawdzić tę wartość za pomocą kodu. W obiekcie Form wartość DialogResult jest DialogResult — jest to typ wyliczeniowy, udostępniający takie wartości, jak OK, Cancel, Yes i No, informujące, za pomocą którego przycisku użytkownik zamknął formularz. Jeśli kod ustawia tę wartość, formularz zostaje automatycznie ukryty, w wyniku czego metoda ShowDialog kończy działanie. Natomiast wartość DialogResult obiektu Window jest wartością logiczną, wskazującą, czy użytkownik zaakceptował, czy anulował okno dialogowe. Jeśli potrzebnych jest więcej informacji (czy użytkownik kliknął przycisk Tak, Nie, czy Anuluj), należy napisać procedurę, która zapamięta, jaki przycisk został kliknięty. Jeśli kod ustawi tę wartość, okno zostanie automatycznie zamknięte, w wyniku czego metoda ShowDialog zakończy działanie. Niestety okno to będzie zamknięte, a nie ukryte, przez co nie da się go ponownie wyświetlić (nie można wyświetlić okna po jego zamknięciu). Aby zapamiętać, który przycisk został kliknięty przez użytkownika, a następnie ukryć okno, nie zamykając go, należy zaimplementować nową własność, a nie używać DialogResult. Ponadto trzeba będzie zająć się osobno ukryciem okna. Także klasa Button — zarówno w Windows Forms, jak i WPF — posiada własności pozwalające na zdefiniowanie dla okna przycisku domyślnego i anulowania, ale w obu przypadkach działają one inaczej. Własność DialogResult obiektu Button w Windows Forms można ustawić na taką wartość, którą chcemy, aby przycisk ten nadał własności DialogResult formularza. Jeśli użytkownik kliknie ten przycisk, własności zostanie nadana wartość DialogResult formularza, a ten ostatni będzie ukryty, dzięki czemu metoda ShowDialog zwróci tę wartość.
Rozdzia 13.
Okna WPF
257
Aby w aplikacji WPF wyznaczyć, który przycisk jest przyciskiem anulowania formularza, można ustawić jego własność IsCancel na wartość True. Jeśli użytkownik kliknie go lub naciśnie klawisz Esc, przycisk ten odpowiednio ustawi własność DialogResult tego formularza i zamknie go, dzięki czemu metoda ShowDialog zakończy działanie. Niestety formularz zostanie zamknięty, a nie tylko ukryty, przez co później nie będzie można go jeszcze raz wyświetlić (nie można wyświetlić okna, które zostało zamknięte). Własność IsDefault przycisku WPF można ustawić w taki sposób, aby włączała się, kiedy użytkownik naciśnie klawisz Enter. Niestety nie powoduje to automatycznego określenia własności DialogResult formularza i nie zamyka okna. Jedno z rozwiązań tego problemu demonstruje program UseDialog, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Klasa okna dialogowego Window2 zawiera trzy przyciski z etykietami Tak, Nie oraz Anuluj. Poniższy kod demonstruje, jak okno to obsługuje kliknięcia swoich przycisków. Dla każdego z nich uruchamiana jest procedura obsługi zdarzeń btn_Click. Zapisuje ona tekst przycisku w publicznej zmiennej o nazwie UserClicked i zamyka formularz. Partial Public Class Window2 Public UserClicked As String = "Anuluj" Private Sub btn_Click(ByVal btn As Button, _ ByVal e As System.Windows.RoutedEventArgs) _ Handles btnYes.Click, btnNo.Click, btnCancel.Click UserClicked = btn.Content Me.Close() End Sub End Class
Poniższy kod demonstruje, jak główne okno tego programu wyświetla okno dialogowe i sprawdza jego wynik. Kiedy użytkownik klika przycisk Wyświetl okno, program tworzy nowe okno dialogowe i wyświetla je w sposób modalny. Następnie sprawdza jego wartość własności UserClicked, aby dowiedzieć się, który przycisk został kliknięty. Private Sub btnShowDialog_Click() Handles btnShowDialog.Click Dim win2 As New Window2 win2.ShowDialog() Select Case win2.UserClicked Case "Tak" MessageBox.Show("Kliknięto Tak", "Tak", MessageBoxButton.OK) Case "Nie" MessageBox.Show("Kliknięto Nie", "Nie", MessageBoxButton.OK) Case "Anuluj" MessageBox.Show("Kliknięto Anuluj", "Anuluj", _ MessageBoxButton.OK) End Select End Sub
Z obiektem Window można zrobić prawie wszystko to, co robi się z obiektem Form: tworzyć nowe egzemplarze klasy Window, wyświetlać je modalnie lub niemodalnie, zamykać je oraz przeglądać i modyfikować własności jednego okna z poziomu innego.
258
Część II
Wstęp do języka Visual Basic
Niemniej jednak operacje tych typów obiektów czasem różnią się szczegółami. Mogą mieć nieco inne własności i wymagać trochę innego podejścia. Jednak klasa Window daje bardzo duże możliwości. Przy odrobinie wytrwałości można przy jej użyciu tworzyć interfejsy użytkownika o bardzo dobrych własnościach użytkowych. W wielu aspektach klasa Window sprawia wrażenie ubogiej wersji klasy Form — przypomina jedną z jej wcześniejszych edycji. Miejmy nadzieję, że przyszłe wersje biblioteki WPF będą wyposażone w bardziej rozbudowane opcje kontroli o funkcjonalności, do której przywykliśmy w klasie Form.
Aplikacje na stronach Strona (Page) jest podobna do okna Window bez obramowania. Nie posiada ona własnych dodatków (ramki, paska tytułu itd.), muszą one zostać dostarczone przez jej kontener. Strony często rezydują w przeglądarkach internetowych, chociaż kontrolka WPF Form również może wyświetlać tego typu obiekty. W kolejnych podrozdziałach opiszę tworzenie aplikacji WPF przy użyciu obiektów Page.
Aplikacje przeglądarkowe Aby utworzyć aplikację typu XAML Browser Application (XBAP), otwórz menu File i kliknij opcję New Project w celu wyświetlenia okna dialogowego New Project. Na karcie Windows zaznacz pozycję WPF Browser Application, wprowadź nazwę projektu i kliknij przycisk OK. Nowo utworzona aplikacja będzie składała się z tylko jednej klasy Page o nazwie Page1. Można ją przeglądać i edytować w dokładnie taki sam sposób jak klasę Window. Aby zmodyfikować kontrolki, otwórz okno Solution Explorer i dwukrotnie kliknij pozycję Page1.xaml. Kliknij dwukrotnie pozycję Page1.xaml.vb w celu wprowadzenia kodu działającego w tle tej karty. Aby uruchomić tę aplikację, przejdź do menu Debug i kliknij opcję Start Debugging. Domyślna przeglądarka internetowa systemu powinna się uruchomić i wyświetlić tę stronę. Visual Studio jest dobrze zintegrowane z przeglądarką Internet Explorer, dzięki czemu osoby jej używające mogą ustawiać punkty wstrzymania w celu zatrzymywania wykonywania kodu — podobnie jak w aplikacjach Windows Forms i WPF. Aby dodać kolejne klasy Page do aplikacji, wejdź do menu Project i kliknij opcję Add Page. Wprowadź nazwę dla swojej nowej klasy i kliknij przycisk OK. Aby utworzyć stronę za pomocą kodu, utwórz zmienną odwołującą się do nowego egzemplarza tej strony. Następnie wyświetl ją za pomocą metody Navigate obiektu NavigationService bieżącej strony.
Rozdzia 13.
Okna WPF
259
Poniższy kod tworzy nową stronę typu Page2 i wyświetla ją za pomocą obiektu NavigationService: Dim p2 As New Page2 NavigationService.Navigate(p2)
Ze względu na to, że aplikacja ta rezyduje w oknie przeglądarki, interakcja z nią różni się nieco od interakcji z innego typu programami. Zamiast wyświetlać nowe formularze i okna dialogowe, będzie ona z reguły pokazywać nowe rzeczy w tym samym oknie przeglądarki. Takie podejście ma kilka konsekwencji. Na przykład zaprezentowany powyżej kod tworzy nowy egzemplarz klasy Page2 i wyświetla go. Gdyby użytkownik miał później jeszcze raz wykonać ten sam kod, utworzony i wyświetlony zostałby drugi egzemplarz tej klasy. Ponieważ w wyniku tego powstałyby dwa egzemplarze tej klasy, nie miałyby one tych samych kontrolek, w związku z czym wszystkie zmiany dokonane przez osobę pracującą z komputerem (wprowadzony tekst, zaznaczone przyciski radiowe itd.) nie byłyby przez nie współdzielone. Po pojawieniu się tego drugiego egzemplarza użytkownik mógłby się zdziwić, gdzie zniknęły wszystkie jego wcześniejsze ustawienia. Można tego uniknąć poprzez użycie w programie jednej globalnej zmiennej, która przechowywałaby odwołania do egzemplarza Page2. Za każdym razem, gdy potrzebne jest wyświetlenie tej strony, aplikacja może pokazać ten sam egzemplarz. Karta ta będzie miała zachowane wszystkie wcześniejsze ustawienia, dzięki czemu użytkownik nie będzie musiał niczego robić od początku. Rozwiązanie to pozwala pozbyć się jednego problemu, ale prowadzi do kolejnego. Ponieważ aplikacja działa w oknie przeglądarki, można w niej używać wszystkich narzędzi historii tego kontenera. Jeśli użytkownik kliknie przycisk Wstecz, zostanie przeniesiony do poprzedniej strony. Wszystko tutaj jest w porządku, ale przeglądarka zapisuje w historii każdą stronę wyświetloną za pomocą instrukcji NavigationService.Navigate. Już wyjaśniam, w czym problem. Wyobraźmy sobie aplikację, której strona główna zawiera przycisk prowadzący do drugiej strony. Na tej z kolei znajduje się przycisk odsyłający z powrotem do tej pierwszej. Jeśli osoba pracująca z komputerem przejdzie kilka razy w tę i z powrotem pomiędzy nimi, w historii przeglądarki pojawi się plątanina odnośników do stron. Mimo że będzie ona odzwierciedlała aktywność użytkownika, w rzeczywistości na niewiele się przyda. Aby zmniejszyć liczbę niepotrzebnych stron w historii przeglądarki, można użyć metod GoForward i GoBack obiektu NavigationService. W omawianym przypadku najlepszym rozwiązaniem byłoby użycie metody GoBack w celu powrotu do strony głównej. Metoda GoBack zamiast tworzyć nową pozycję w historii, jak metoda Navigate, przechodzi o jedną pozycję wstecz w istniejącej już historii. Po kilku takich przejściach w obu kierunkach w historii będą znajdowały się tylko dwie strony. Jedna z nich będzie dostępna pod przyciskiem Dalej, a druga — pod przyciskiem Wstecz. Technikę tę demonstruje dostępny do pobrania w internecie program o nazwie BrowserApp. Aplikacja ta zawiera dwie strony z przyciskami prowadzącymi do drugiej z nich. Na każdej z nich znajduje się pole tekstowe, w którym można wpisać jakiś tekst, aby sprawdzić, czy wartości zostaną zachowane po przejściu do drugiej strony. Poniższy fragment kodu demonstruje mechanizm przejścia ze strony głównej do drugiej strony. Jeśli obiekt NavigationService może przejść do przodu, kod wywołuje jego metodę GoForward. Jeśli nie może, jego metoda Navigate przenosi go do nowego obiektu Page2.
260
Część II
Wstęp do języka Visual Basic
Private Sub btnPage2_Click() Handles btnPage2.Click If NavigationService.CanGoForward Then NavigationService.GoForward() Else NavigationService.Navigate(New Page2) End If End Sub
Poniższy kod odpowiada za powrót z drugiej strony do pierwszej. Wywołuje on po prostu metodę GoBack obiektu NavigationService. Private Sub btnBack_Click() Handles btnBack.Click Me.NavigationService.GoBack() End Sub
Po zakończeniu pracy nad aplikacją XBAP można ją uruchomić poprzez otwarcie w przeglądarce jej skompilowanego pliku xbap. Kiedy skompilowałem prezentowany wcześniej program, jego plik o nazwie BrowserApp.xbap został utworzony w katalogu projektu bin/Debug. Bez problemu otworzyły go przeglądarki Internet Explorer 7.0 i Firefox 2.0. Tworzenie klasy Page wygląda prawie dokładnie tak samo jak tworzenie klasy Window. Służy do tego ten sam edytor XAML i kodu Visual Basica. Główna różnica przejawia się w sposobie nawigacji pomiędzy poszczególnymi formularzami. W aplikacji WPF tworzy się obiekty Window i używa ich metod Show i ShowDialog. W aplikacji XBAP tworzy się obiekty Page i używa metod obiektu NavigationService.
Aplikacje ramkowe Mimo że obiekty Page zazwyczaj rezydują w oknie przeglądarki, mogą także znajdować się w kontrolce Frame. Program przenosi tę ostatnią do strony, a dalej wszystko odbywa się jak w typowej aplikacji XBAP. Na rysunku 13.1 przedstawiono program FrameApp, który można pobrać z serwera FTP wydawnictwa Helion. Ładuje on obiekt Page1 do kontrolki Frame za pomocą poniższej instrukcji: fraPages.Navigate(New Page1)
Rysunek 13.1. Kontrolka Frame umożliwia nawigację pomiędzy obiektami Page
Rozdzia 13.
Okna WPF
261
Ten program zawiera te same klasy Page1 i Page2, które zostały użyte w opisanej wcześniej aplikacji BrowserApp. Skoro aplikacje XBAP tak dobrze działają w przeglądarkach internetowych, po co hostować je w kontrolce Frame? W jednej zwykłej aplikacji WPF można umieścić jedną lub więcej ramek, aby pozwolić użytkownikowi przeglądać różne dane lub wykonywać różne zadania w tym samym czasie. Można na przykład wyświetlić ramkę pomocy w osobnym oknie. Jeśli treść każdej ramki zostanie utworzona w osobnej aplikacji XBAP, ramki te będzie można ładować w czasie działania programu. Dzięki temu łatwo zaktualizujesz program, zamieniając tylko poszczególne aplikacje XBAP. Kontrolka Frame również udostępnia proste narzędzia nawigacyjne w postaci przycisków do przechodzenia dalej i wstecz, które mogą w niektórych sytuacjach być łatwiejsze w użyciu. Siódma zasada na liście Microsoftu Top Rules for the Windows Vista User Experience na stronie http://msdn2.microsoft.com/en-us/library/Aa511327.aspx brzmi: „Używaj nawigowalnych interfejsów użytkownika, które działają, opierając się na Eksploratorze Windows”. Na stronie tej napisano, że ten rodzaj interakcji upraszcza nawigację nawet w tradycyjnych aplikacjach. Osobiście uważam, że ten postulat jest próbą obrócenia słabości w mocną stronę. Przeglądarki internetowe korzystają z tego rodzaju nawigacji, ponieważ nie mają możliwości dostarczenia innych narzędzi nawigacyjnych niż hiperłącza na stronach internetowych. Z pewnością istnieją sytuacje, w których ten rodzaj nawigacji jest dobrym rozwiązaniem (na przykład w kreatorach prowadzących użytkownika przez kolejne etapy jakiejś czynności). Jednak większość aplikacji desktopowych działa bardziej naturalnie, kiedy użytkownik może otworzyć kilka okien do wykonywania różnych zadań. Daj mi znać, co o tym myślisz — napisz do mnie na adres [email protected]. Kontrolka Frame daje większą kontrolę niż przeglądarka. Pozwala na przykład łatwiej uzyskać dostęp do historii strony. Ponadto rozmiar kontrolki Frame można kontrolować, natomiast nad położeniem i rozmiarem przeglądarki nie mamy żadnej władzy. Wyświetlanie obiektów Page w kontrolce Frame nie zawsze jest dobrym rozwiązaniem, ale w niektórych przypadkach sprawdza się znakomicie.
Aplikacje PageFunction W dokumentacji Microsoftu można przeczytać, że PageFunction to „specjalny typ strony, który pozwala na traktowanie nawigacji do innej strony w podobny sposób jak wywołania metody”. Oświadczenie to jest bardzo mylące. Nawigowanie do strony PageFunction jest w rzeczywistości bardzo podobne do nawigowania do każdego innego obiektu Page. Różnica polega na tym, że strona PageFunction z założenia pobiera parametry, kiedy jest wyświetlana, a zwraca wynik, gdy kończy działanie.
262
Część II
Wstęp do języka Visual Basic
Obiekt PageFunction nie wykonuje tych zadań w taki sam sposób, w jaki robi to wywołanie metody. Program przekazuje do niego parametry w jego konstruktorze. Odbiera wartość zwrotną poprzez przechwycenie zdarzenia Return tego obiektu i sprawdzenie wartości parametru przekazanego do tego zdarzenia. Zastosowanie omawianej klasy PageFunction demonstruje widoczny na rysunku 13.2 program UsePageFunction, który można pobrać z serwera FTP wydawnictwa Helion. Jest to aplikacja XBAP, która składa się ze strony startowej Page i strony PageFunction. Ta pierwsza zawiera dwa pola tekstowe — jedno do wprowadzania wartości początkowej i jedno dla wartości zwrotnej. Wpisz coś w polu Wartość początkowa i kliknij przycisk Do strony 2. Rysunek 13.2. Klasa PageFunction upraszcza przekazywanie do stron parametrów i wartości zwrotnych
Druga strona wyświetla tekst wpisany na pierwszej stronie, aby pokazać, że program z powodzeniem przekazał go do obiektu PageFunction. Jeśli zmodyfikujesz go i naciśniesz przycisk Powrót, na pierwszej stronie zostanie wyświetlony ten zmieniony tekst, co stanowi dowód na to, że wartość zwrotna obiektu PageFunction została przekazana z powodzeniem. Poniższy kod demonstruje sposób działania obiektu PageFunction. Zauważ, że klasa ta dziedziczy po klasie PageFunction(Of String). Oznacza to, że jest to klasa PageFunction, a także że jej parametr i wartość zwrotna są łańcuchami. Użycie innego typu danych w klasie PageFunction demonstruje zaprezentowany w kolejnym podrozdziale kreator. Partial Public Class PageFunction1 Inherits PageFunction(Of String) ' Wartość początkowa. Public Sub New(ByVal initial_value As String) Me.InitializeComponent() txtValue.Text = initial_value End Sub
Rozdzia 13.
Okna WPF
263
' Zwrócenie bieżącego tekstu. Private Sub btnReturn_Click() Handles btnReturn.Click OnReturn(New ReturnEventArgs(Of String)(txtValue.Text)) End Sub End Class
Konstruktor klasy PageFunction jako parametr przyjmuje łańcuch znaków. Wywołuje on swoją metodę InitializeComponent, przygotowującą kontrolki do użytku, i zapisuje przekazany mu łańcuch w polu tekstowym na stronie. Kiedy użytkownik klika przycisk Powrót, procedura obsługi zdarzeń btnReturn_Click wywołuje metodę obiektu PageFunction o nazwie OnReturn poprzez przekazanie do niej obiektu ReturnEventArgs(Of String). Ten ostatni staje się wartością zwrotną, która jest odbierana przez pierwszą stronę. Łańcuch do zwrócenia przez ten obiekt zostaje przekazany do jego konstruktora. W tym przypadku wynikiem jest tekst, który został zmodyfikowany w polu tekstowym. Poniżej znajduje się kod strony startowej: Class Page1 Private WithEvents page2 As PageFunction1 Private Sub btnPage2_Click() Handles btnPage2.Click page2 = New PageFunction1(txtInitialValue.Text) NavigationService.Navigate(page2) End Sub ' Przechwycenie zdarzenia powrotu i przetworzenie wyniku. Private Sub page2_Return(ByVal sender As Object, _ ByVal e As System.Windows.Navigation.ReturnEventArgs(Of String)) _ Handles page2.Return txtReturnedValue.Text = e.Result End Sub End Class
Na początku zostaje zadeklarowana zmienna typu PageFunction1 o nazwie page2. Dzięki użyciu słowa kluczowego WithEvents łatwo można przechwycić zdarzenia tego obiektu. Kiedy użytkownik klika przycisk Do strony 2, procedura obsługi zdarzeń btnPage2_Click tworzy nowy obiekt PageFunction1 i zapisuje go w zmiennej page2, dzięki czemu może przechwytywać jego zdarzenia. Następnie przechodzi do niego. Kiedy obiekt PageFunction wywołuje swoją metodę OnReturn, strona ta przechwytuje jego zdarzenie Return. Procedura obsługi zdarzeń odbiera parametr e, zawierający wartość zwrotną w swojej własności Result. Wynik zostaje wyświetlony w polu tekstowym txtReturnedValue. Nie jest to dokładne odzwierciedlenie wywołania metody, ale pozwala na przekazanie wartości do obiektu PageFunction i odebranie wyniku. W następnym podrozdziale opiszę bardziej skomplikowany przykład zastosowania klasy PageFunction.
264
Część II
Wstęp do języka Visual Basic
Kreatory Mimo że obiekty klasy PageFunction, czyli ogólnie w tym przypadku aplikacje XBAP, nie nadają się do wykorzystania w każdej sytuacji, doskonale sprawdzają się przy budowaniu kreatorów. Typowy kreator przeprowadza użytkownika przez kolejne etapy jakiegoś zadania, aż zostanie ono ukończone. Program BrowserWizard, który można pobrać z internetu, jest aplikacją XBAP, która tworzy prosty kreator wyboru dań obiadu za pomocą obiektów PageFunction. Strona startowa jest obiektem Page, który zawiera przycisk Start oraz listę końcowych wyborów dokonanych przez użytkownika (początkowo jest ona pusta). Kiedy kliknie się przycisk, zostanie wyświetlona pierwsza strona kreatora. Pierwsza strona jest obiektem PageFunction, zawierającym listę rozwijaną, z której można wybrać jedną z trzech przystawek oraz pole wyboru pozwalające dodać surówkę. Po zdecydowaniu się na konkretne pozycje użytkownik może kliknąć przycisk Dalej, aby przejść do drugiej strony. Jest też przycisk Anuluj, który powoduje natychmiastowe zamknięcie kreatora. Ostatnia strona również jest obiektem PageFunction. Zawiera ona listę rozwijaną z deserami do wyboru. Posiada — podobnie jak druga strona — przyciski Wstecz i Anuluj, a zamiast przycisku Dalej znajdziesz tam przycisk Zakończ, kończący pracę kreatora. Gdy go klikniesz, sterowanie wróci do pierwszej strony, na której zostaną wyświetlone dokonane przez Ciebie wybory. Aplikacja ta do każdej ze swoich stron przekazuje obiekt typu WizardData. Kod tej klasy przedstawia poniższy listing. Jest ona odpowiedzialna za przechowywanie informacji o dokonanych przez użytkownika wyborach na kolejnych etapach pracy kreatora: Public Enum DessertType None IceCream Cake Pie Cookie End Enum ' To są dane, które wprowadzi użytkownik. ' Tutaj ustawiamy wartości domyślne. Public Class WizardData Public Canceled As Boolean = True Public Appetizer As String = "" Public Entree As String = "" Public Salad As Boolean = False Public Drink As String = "" Public Dessert As DessertType = DessertType.None End Class
Poza polami danych do przechowywania wybranych przez użytkownika opcji klasa WizardData zawiera także definicję pola Canceled, które umożliwia śledzenie, czy na którymś z etapów kreator nie został anulowany.
Rozdzia 13.
Okna WPF
265
Poniżej znajduje się kod strony startowej: Class WizardStart Private WithEvents m_Page1 As WizardPage1 ' Wyświetlenie pierwszej strony. Private Sub btnPage1_Click() Handles btnPage1.Click m_Page1 = New WizardPage1(New WizardData) NavigationService.Navigate(m_Page1) End Sub ' Pierwsza strona zwróciła wartość. Private Sub m_Page1_Return(ByVal sender As Object, _ ByVal e As System.Windows.Navigation.ReturnEventArgs(Of WizardData)) _ Handles m_Page1.Return Dim wiz_data As WizardData = e.Result ' Sprawdzenie, czy użytkownik nie anulował kreatora. If wiz_data.Canceled Then lblAppetizer.Content = "" lblEntree.Content = "" lblSalad.Content = "" lblDrink.Content = "" lblDessert.Content = "" Else lblAppetizer.Content = wiz_data.Appetizer lblEntree.Content = wiz_data.Entree lblSalad.Content = wiz_data.Salad.ToString lblDrink.Content = wiz_data.Drink lblDessert.Content = wiz_data.Dessert.ToString End If End Sub End Class
Strona ta deklaruje zmienną typu WizardPage1. Dzięki użyciu słowa kluczowego WithEvents łatwo można przechwycić zdarzenie Return tego obiektu. Kiedy użytkownik klika przycisk Start, procedura obsługi zdarzeń btnPage1_Click tworzy nowy obiekt klasy WizardPage1 poprzez przekazanie do jego konstruktora nowego obiektu klasy WizardData, po czym przechodzi do tej nowej strony. Kiedy obiekt WizardPage1 zwraca wartość, strona startowa przechwytuje jego zdarzenie Return. Jeśli zmienna Canceled tego zwróconego obiektu WizardData ma wartość True, program czyści wszystkie kontrolki na stronie głównej, które reprezentują dokonane wybory. Jeżeli wartość tej zmiennej to False, aplikacja pokazuje dokonane wybory w kontrolkach na stronie startowej. Zauważ, że parametr e procedury obsługi zdarzeń Return ma typ zawierający rodzajowy specyfikator Of WizardData. Dzięki temu własność e.Return jest typu WizardData, co sprawia, że łatwo jest jej użyć w kodzie. Poniżej znajduje się początkowy fragment kodu źródłowego pierwszej strony. Należy zauważyć, że klasa ta dziedziczy po klasie PageFunction(Of WizardData), a więc jej konstruktor i metoda OnReturn przyjmują jako parametry obiekty klasy WizardData.
266
Część II
Wstęp do języka Visual Basic
Inherits PageFunction(Of WizardData) Private m_WizardData As WizardData Private WithEvents m_Page2 As WizardPage2 ' Zapisanie obiektu WizardData. Public Sub New(ByVal wizard_data As WizardData) InitializeComponent() m_WizardData = wizard_data End Sub
W kodzie tym znajduje się deklaracja prywatnej zmiennej typu WizardData do przechowywania informacji o aktualnych wyborach dokonanych przez użytkownika. Konstruktor tej klasy inicjuje jej kontrolki, a następnie zapisuje przekazany do niego w tej zmiennej obiekt typu WizardData. Kod ten tworzy także zmienną typu WizardPage2. Obiekt ten jest wyświetlany, kiedy użytkownik kliknie na tej stronie przycisk Dalej. Kiedy strona zwróci wartość, przedstawiony poniżej fragment programu przechwyci jej zdarzenie Return i wywoła metodę OnReturn tej strony poprzez przekazanie do niej ten parametru zdarzenia, który wcześniej odebrał. To spowoduje przekazanie wyników kolejnej strony z powrotem do strony startowej. ' Kolejna strona zwraca wynik. Private Sub m_Page2_Return(ByVal sender As Object, _ ByVal e As System.Windows.Navigation.ReturnEventArgs(Of WizardData)) _ Handles m_Page2.Return OnReturn(e) End Sub
Poniżej znajduje się kod odpowiedzialny za nawigację na pierwszej stronie: ' Otwarcie kolejnej strony. Public Sub btnNext_Click() Handles btnNext.Click If NavigationService.CanGoForward Then NavigationService.GoForward() Else m_Page2 = New WizardPage2(m_WizardData) NavigationService.Navigate(m_Page2) End If End Sub ' Zwrócenie wyniku, który oznacza anulowanie kreatora. Private Sub btnCancel_Click() Handles btnCancel.Click m_WizardData.Canceled = True OnReturn(New ReturnEventArgs(Of WizardData)(m_WizardData)) End Sub
Kiedy kliknie się przycisk Dalej, procedura obsługi zdarzeń btnNext_Click sprawdzi, czy obiekt NavigationService może przejść dalej. Jeśli użytkownik przeszedł do następnej strony, a następnie wrócił do tej, własność CanGoForward tego obiektu będzie miała wartość True. W takim przypadku program nie tworzy nowego obiektu dla drugiej strony. Zamiast tego wywołuje metodę GoForward obiektu NavigationService, aby jeszcze raz wyświetlić ten sam obiekt.
Rozdzia 13.
Okna WPF
267
Jeśli własność NavigationService.CanGoForward ma wartość False, oznacza to, że druga strona nie była jeszcze wyświetlana. Program tworzy nowy obiekt klasy WizardPage2 poprzez przekazanie do jego konstruktora obiektu klasy WizardData, zawierającego informacje o aktualnych wyborach użytkownika. Następnie przechodzi do nowej strony. Jeśli użytkownik kliknie przycisk Anuluj, procedura obsługi zdarzeń btnCancel_Click ustawi własność Canceled bieżącego obiektu WizardData na wartość True i wywoła metodę OnReturn aktualnej strony poprzez przekazanie do niej tego obiektu. Sterowanie wróci do strony startowej, która przechwyci zdarzenie Return i dowie się, że własność Canceled obiektu WizardData ma wartość True. Poniższy kod zapisuje dane dotyczące wyboru przystawki i surówki: ' Zapisanie wyboru. Private Sub cboAppetizer_SelectionChanged() _ Handles cboAppetizer.SelectionChanged ' Własność Text listy rozwijanej nie została jeszcze zaktualizowana. If cboAppetizer.SelectedIndex < 0 Then m_WizardData.Appetizer = "" Else m_WizardData.Appetizer = _ cboAppetizer.Items(cboAppetizer.SelectedIndex).Content End If End Sub ' Zapisanie wyboru. Private Sub chkSalad_Checked() _ Handles chkSalad.Checked, chkSalad.Unchecked m_WizardData.Salad = chkSalad.IsChecked End Sub
Kiedy użytkownik wybiera przystawkę, procedura obsługi zdarzeń cboAppetizer_SelectionChanged zachowuje ten wybór w polu Appetizer obiektu klasy WizardData. Analogicznie — jeśli zaznaczy się lub wyczyści pole wyboru Surówka, wybór ten zapisze procedura obsługi zdarzeń chkSalad_Checked. Druga i trzecia strona kreatora różnią się od pierwszej tylko dwoma szczegółami. Po pierwsze, użytkownik może z tych stron przejść do strony wcześniejszej. Poniżej znajduje się kod odpowiedzialny za przechodzenie do poprzedniej strony, gdy kliknie się przycisk Wstecz: ' Przejście do poprzedniej strony. Private Sub btnPrev_Click() Handles btnPrev.Click NavigationService.GoBack() End Sub
Po drugie, na ostatniej stronie nie ma przycisku Dalej do kolejnej strony. Zamiast tego znajdziesz tam przycisk Zakończ, którego kliknięcie spowoduje wykonanie poniższego kodu: ' Zakończenie. Public Sub btnFinish_Click() Handles btnFinish.Click m_WizardData.Canceled = False OnReturn(New ReturnEventArgs(Of WizardData)(m_WizardData)) End Sub
268
Część II
Wstęp do języka Visual Basic
Kod ten ustawia pole Canceled obiektu WizardData na wartość False, aby zaznaczyć, że użytkownik nie anulował kreatora. Następnie zostaje wywołana metoda OnReturn obiektu PageFunction, przyjmująca jako argument nowy obiekt ReturnEventArgs, który zawiera obiekt WizardData. W tym momencie sterowanie przechodzi z powrotem przez nawigacje poszczególnych stron. Druga strona przechwytuje zdarzenie Return tej strony i wywołuje własną metodę OnReturn. Na zakończenie strona startowa przechwytuje zdarzenie Return pierwszej strony kreatora i wyświetla wyniki znajdujące się w zwróconym obiekcie WizardData. Klasy PageFunction nie są koniecznym elementem każdej aplikacji, ale znacznie upraszczają kod kreatorów, w których informacje są przekazywane w dwie strony pomiędzy serią stron.
Podsumowanie W aplikacjach Windows Forms wszystko znajduje się w obiektach Form. Niektóre z tych klas mogą reprezentować okna dialogowe lub pochodzić od klasy Form, ale w zasadzie wszystko znajduje się na jakimś formularzu. Aplikacje WPF mogą opierać się na obiektach Window lub Page. Te pierwsze działają w systemie operacyjnym, podobnie jak formularze Windows Forms. Obiekty Page muszą znajdować się w jeszcze jakimś innym kontenerze — zazwyczaj w przeglądarce internetowej lub kontrolce Frame w obiekcie Window. Klasa PageFunction reprezentuje zmodyfikowaną wersję obiektu Page, który ułatwia przekazywanie wartości pomiędzy różnymi stronami. Rozdziały od 8. do 13. stanowią praktyczne wprowadzenie do używania kontrolek. Znajdziesz w nich objaśnienie, jak używać zarówno kontrolek Windows Forms, jak i WPF. Ponadto zawierają opisy najwyższego poziomu klas do budowy interfejsu użytkownika — Form dla programu Windows Forms oraz Window, Page i PageFunction dla aplikacji WPF. Mimo że kontrolki same w sobie są bardzo obszernym tematem, aplikacje w Visual Basicu nie składają się tylko z nich. Konieczne jest też opanowanie kodu działającego w ich tle, który pozwala na pobieranie od nich wartości, manipulowanie tymi wartościami oraz wyświetlanie wyników w innych kontrolkach. Ten temat zostanie szczegółowo omówiony w kilku kolejnych rozdziałach. Pierwszy z tej serii rozdział 14. — „Struktura programu i modułu” — zawiera objaśnienie rodzajów plików wchodzących w skład projektu Visual Basica oraz struktury plików z kodem źródłowym.
14
Struktura programu i modułu Rozwiązanie Visual Basica składa się z przynajmniej jednego projektu, ten zaś — z plików związanych z jednym zadaniem. Celem projektu jest zazwyczaj wygenerowanie jakiegoś rodzaju skompilowanych danych (na przykład pliku wykonywalnego, biblioteki klas, biblioteki kontrolnej itd.). Wszystkie pliki z nimi związane (na przykład pliki źródłowe, pliki zasobów, dokumentacja, itd.) są objęte projektem. W tym rozdziale opiszę podstawy struktury projektów Visual Basica. Objaśnię przeznaczenie niektórych najczęściej używanych plików oraz sposoby ich wykorzystywania w zarządzaniu aplikacjami. Ponadto rozdział ten zawiera podstawowy opis struktury plików z kodem źródłowym. Znajduje się tu objaśnienie, czym są regiony, przestrzenie nazw oraz moduły. Dodatkowo w rozdziale tym zostały opisane niektóre proste narzędzia typograficzne języka Visual Basic, takie jak komentarze, znaki kontynuacji wiersza i etykiety wierszy. Nie wykonują one żadnych instrukcji programistycznych, ale stanowią bardzo ważną część struktury kodu każdej aplikacji.
Ukryte pliki Na rysunku 14.1 przedstawiono okno Solution Explorer z otwartym rozwiązaniem, które składa się z dwóch projektów. Rozwiązanie to nosi nazwę MySolution, a zawarte w nim projekty to WindowsApplication1 i WindowsApplication2. Każdy z nich posiada element My Project, który reprezentuje jego własności, a także różne pliki z ustawieniami konfiguracyjnymi projektu i formularz o nazwie Form1. W projekcie WindowsApplication2 został kliknięty przycisk Show All Files (drugi przycisk od lewej), dzięki czemu widać wszystkie jego pliki. WindowsApplication1 posiada takie same pliki, ale są one domyślnie ukryte.
270
Część II
Wstęp do języka Visual Basic
Rysunek 14.1. Rozwiązanie składa się z jednego lub więcej projektów zawierających różne pliki
Pliki te Visual Basic generuje do różnych celów. Na przykład Resources.resx zawiera zasoby wykorzystywane w projekcie, a Settings.settings przechowuje ustawienia projektu. Zasoby to dane rozprowadzane razem z programem, ale niebędące przez niego modyfikowanymi. Zalicza się do nich polecenia wiersza poleceń, łańcuchy komunikatów o błędach oraz ikony i pliki dźwięków. Są one na przykład często wykorzystywane do lokalizacji programu w różnych językach. Polega to na utworzeniu różnych zestawów plików zasobów i załadowaniu odpowiedniego z nich w danym czasie. Więcej na temat zasobów znajduje się w rozdziale 35. — „Konfiguracja i zasoby” (w praktyce zasoby można modyfikować, ale wtedy bardziej przypominają ustawienia niż zasoby, dlatego nie rozpisuję się tutaj na ten temat — w rzeczywistości modyfikacja zasobów w zasobach z silnymi nazwami jest powodem do podniesienia alarmu, że ktoś niepowołany mógł majstrować przy pliku). Ustawienia to wartości, które sterują wykonywaniem programu. Mogą to być znaczniki informujące aplikację, które opcje ma wyświetlić, a także jak wykonać określone zadania. Można na przykład stworzyć różne profile, które będą wymuszać działanie programu w ograniczonym trybie demonstracyjnym lub w pełnym trybie licencjonowanym. Zazwyczaj ustawienia aplikacji .NET są przechowywane w plikach z rozszerzeniem .config, chociaż mogą się one znajdować także w rejestrze lub plikach .ini.
Rozdział 14.
Struktura programu i modułu
271
Na poniższej liście znajdują się opisy plików, które składają się na projekt WindowsApplication2 (widoczny na rysunku 14.1). Na Twoim komputerze ich spis może być nieco inny, ale ta charakterystyka powinna dać Ci ogólny pogląd na temat tego, jakie pliki biorą udział w budowie projektu.
WindowsApplication2 — folder reprezentujący cały projekt. Można go zwijać i rozwijać, aby ukryć lub odkryć szczegóły projektu.
My Project — folder reprezentujący informacje zbiorcze projektu, zdarzenia poziomu aplikacji, zasoby oraz ustawienia konfiguracyjne. Aby edytować te wartości, należy dwukrotnie kliknąć tę pozycję.
Application.myapp — plik XML, który definiuje własności aplikacji (na przykład czy jest to program typu single instance, a także czy zamyka się on w trybie AfterMainFormCloses, czy AfterAllFormsClose).
Application.Designer.app — plik, który zawiera kod wykorzystujący wartości zdefiniowane w pliku Application.myapp.
AssemblyInfo.vb — plik zawierający informacje o asemblacji, jak prawa autorskie, nazwa firmy, znak towarowy i wersja programu.
Resources.Designer.vb — plik zawierający kod Visual Basica, który działa na zasobach zdefiniowanych w pliku Resources.resx. Jeśli na przykład w pliku Resources.resx zostanie zdefiniowany zasób łańcuchowy o nazwie Greeting, Visual Basic doda do tego modułu własność tylko do odczytu, dzięki czemu wartość tę będzie można odczytywać za pomocą poniższego kodu: MessageBox.Show(My.Resources.Greeting)
Settings.Designer.vb — plik z kodem Visual Basica, który działa na ustawieniach zdefiniowanych w pliku Settings.settings. Jest podobny do pliku Resources.Designer, działającego na zawartości pliku Resources.resx. Na przykład w poniższym kodzie zostało wykorzystane ustawienie UserMode: If My.Settings.UserMode = "Urzędnik" Then ...
References — folder zawierający odwołania do zewnętrznych komponentów typu DLL czy COM.
bin — folder wykorzystywany do kompilacji programu przed jego uruchomieniem. Zawiera skompilowany plik .exe.
obj — folder wykorzystywany do kompilacji programu przed jego uruchomieniem.
ApplicationEvents.vb — plik z kodem źródłowym, który zawiera procedury obsługi zdarzeń na poziomie aplikacji dla obiektu MyApplication. Znajdują się tam przykład procedury obsługi zdarzeń Startup, Shutdown oraz NetworkAvailabilityChanged.
Form1.vb — plik formularza. Zawiera kod formularza, kontrolki, procedury obsługi zdarzeń tych kontrolek itd.
Form1.Designer.vb — plik z wygenerowanym przez projektanta kodem Visual Basica, który tworzy formularz. Następnie go inicjuje, dodaje umieszczone na nim kontrolki oraz definiuje zmienne kontrolek ze słowem kluczowym WithEvents, dzięki czemu łatwo można przechwytywać ich zdarzenia.
272
Część II
Wstęp do języka Visual Basic
Niektóre projekty mogą posiadać jeszcze inne ukryte pliki. Kiedy na przykład do formularza dodawane są kontrolki, projektant umieszcza w nim plik zasobów, w którym będą przechowywane wszystkie wymagane przez te kontrolki zasoby. W typowych sytuacjach nie ma potrzeby bezpośredniego edytowania tych ukrytych plików. Można je modyfikować pośrednio za pomocą różnych narzędzi. Na przykład pliki Resources.Designer.vb, Settings.Designer.vb oraz Form1.Designer.vb są generowane automatycznie w czasie modyfikowania odpowiadających im plików zasobów Resources.resx, Settings.settings oraz Form1.vb. Nie trzeba nawet bezpośrednio otwierać wszystkich plików źródłowych. Na przykład kliknięcie w oknie Solution Explorer pozycji My Project powoduje wyświetlenie strony własności widocznej na rysunku 14.2. Na karcie Application, również zamieszczonej na tej ilustracji, można ustawiać opcje wysokiego poziomu. Znajdujący się na dole przycisk View Application Events pozwala edytować zdarzenia na poziomie aplikacji, które są przechowywane w pliku ApplicationEvents.vb.
Rysunek 14.2. Strony własności pozwalają ustawiać zasoby, ustawienia i ogólne opcje konfiguracyjne projektów
Rozdział 14.
Struktura programu i modułu
273
Na karcie References można przeglądać, dodawać i usuwać referencje projektu. Nietrudno się domyślić, że karty Resources i Settings służą do edytowania zasobów i ustawień projektu. Szczególnie ważną sekcją, niewidoczną na tej ilustracji, jest Assembly Information. Gdy klikniesz przycisk Assembly Information, wyświetlone zostanie okno dialogowe z rysunku 14.3. Rysunek 14.3. Okno dialogowe Assembly Information pozwala zdefiniować podstawowe informacje o projekcie, takie jak tytuł, prawa autorskie i numer wersji
Wielu z elementów tego okna, na przykład tytułu (Title) czy opisu (Description) aplikacji, nie trzeba omawiać. Są to zwykłe łańcuchy, które identyfikują program. Pola Assembly version i File Version są wykorzystywane przez środowisko Visual Studio do weryfikacji zgodności pomiędzy komponentami aplikacji. Pole GUID (ang. Globally Unique Identifier) zawiera unikatowy identyfikator asemblacji i jest generowany przez Visual Studio. Pole wyboru Make assembly COM-Visible pozwala zdecydować, czy typy zdefiniowane w pliku wykonywalnym mają być widoczne dla aplikacji COM. Więcej informacji na temat tego okna dialogowego znajduje się na stronie Microsoftu msdn2.microsoft.com/en-gb/library/ 1h52t681(VS.80).aspx. Asemblacja (ang. assembly) to podstawowa jednostka wdrożeniowa, poddawana kontroli wersji w Visual Studio .NET. Może ona zawierać plik wykonywalny, plik DLL lub bibliotekę kontrolną. W jednym pliku asemblacji zazwyczaj znajduje się cały jeden projekt. Okno dialogowe Assembly Information pozwala zdefiniować informacje, które powinny być związane z plikiem asemblacji — nazwę firmy, opis, prawa autorskie, znak towarowy, nazwę, nazwę produktu, tytuł i wersję (wartości major, minor, revision i build). Łatwy dostęp do tych wartości w czasie działania aplikacji zapewnia przestrzeń nazw My.Application.AssemblyInfo. Poniższy kod jest fragmentem programu ShowAssemblyInfo, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/ vb28wp.zip). Wyświetla on podczas uruchamiania zestaw informacji w etykietach:
Struktura plików z kodem Formularze, klasy i moduły kodu powinny zawierać poniższe sekcje, ustawione w takiej właśnie kolejności (jeśli zostaną użyte):
Instrukcje Option — Option Explicit, Option Strict, Option Compare lub Option Infer. Domyślnie opcja Option Explicit jest ustawiona na On, Option Strict na Off, Option Compare na Binary, a Option Infer na On.
Instrukcje Imports — deklarują przestrzenie nazw, których będzie używał moduł.
Podprocedurę Main — procedura rozpoczynająca wykonywanie kodu po uruchomieniu programu.
Instrukcje Class, Module oraz Namespace — zgodnie z zapotrzebowaniem.
Aby uniknąć potencjalnie denerwujących i trudnych do wytropienia błędów, ustaw opcję Option Explicit na On, Option Strict na On i Option Infer na Off. Zobacz także podrozdział „Menu Project” w rozdziale 2. Niektóre z tych elementów można opuścić. Na przykład opcjonalne jest używanie instrukcji Option i Imports. Zauważ, że program Windows może zacząć działać od podprocedury Main
lub od wyświetlenia formularza, kiedy to podprocedura ta nie jest potrzebna. Klasy i moduły kodu nie wymagają podprocedury Main. Poniższy listing przedstawia prosty moduł kodu. Opcja Option Explicit została w nim ustawiona na On (zmienne muszą zostać zadeklarowane przed użyciem), Option Strict na On (niejawne konwersje typów powodują błąd), a opcja Option Infer — na Off (zmienne muszą mieć bezpośrednio określone typy). Kod ten importuje przestrzeń nazw System.IO, dzięki czemu można bez problemu używać zdefiniowanych w niej klas. Dalej znajduje się definicja klasy Employee: Option Explicit On Option Strict On Option Infer Off Imports System.IO Public Class Employee ... End Class
Rozdział 14.
Struktura programu i modułu
275
Z reguły w jednym pliku umieszcza się tylko jeden moduł lub klasę, ale użycie większej liczby instrukcji Class i Module nie jest zabronione. Instrukcje Class i Module tworzą najwyższy poziom węzłów w hierarchii kodu. Kliknięcie znaku minusa, który znajduje się po lewej stronie takiej instrukcji, powoduje zwinięcie jej kodu. Kiedy instrukcja jest zwinięta, można ją rozwinąć poprzez kliknięcie znajdującego się po jej lewej stronie znaku plusa. W każdym projekcie można odwoływać się do dowolnych klas publicznych lub zmiennych albo procedur publicznych zdefiniowanych w modułach. Jeśli dwa moduły zawierają zmienną lub procedurę o takiej samej nazwie, program może wybrać tę, której potrzebuje, poprzez użycie nazwy modułu jako prefiksu. Jeśli na przykład moduły AccountingTools i BillingTools zawierają podprocedurę o nazwie ConnectToDatabase, poniższa instrukcja wykona ją z modułu BillingTools: BillingTools.ConnectToDatabase
Regiony kodu Klasy i moduły definiują regiony kodu, które można zwijać i rozwijać, aby ułatwić sobie czytanie go. Podprocedury i funkcje również definiują sekcje tego typu. Dodatkowo za pomocą instrukcji Region można we własnym zakresie tworzyć takie zwijalne sekcje kodu. Istnieje na przykład możliwość objęcia takim regionem kilku podprocedur, które wspólnie rozwiązują jakiś jeden problem. Poniżej znajduje się prosty region: #Region "Procedury rysujące" ... #End Region
Pamiętaj, że funkcje wyszukiwania i zastępowania tekstu działają w normalny sposób tylko w regionach rozwiniętych. Jeśli w czasie wyszukiwania lub zamieniania tekstu w bieżącym dokumencie lub bieżącej sekcji jakiś region będzie zwinięty, jego kod nie będzie brany pod uwagę w tym wyszukiwaniu lub zamienianiu. Natomiast podczas wyszukiwania i zastępowania tekstu w całym projekcie brany jest pod uwagę także kod z regionów zwiniętych. Sama instrukcja End Region nie informuje, którego regionu koniec oznacza. Aby kod był przejrzysty, zwłaszcza gdy znajduje się w nim wiele regionów, można po tych instrukcjach wstawiać komentarze, które będą określać, jaki moduł one kończą, jak poniżej: #Region "Procedury rysujące" ... #End Region ' Procedury rysujące
W swoim kodzie bardzo często używam regionów. Pozwalają one na zwinięcie kodu, nad którym aktualnie nie pracuję, a ponadto grupują go w logiczne sekcje. Tworzenie regionów pomaga utrzymać powiązane fragmenty kodu w jednym miejscu, co ułatwia czytanie go.
276
Część II
Wstęp do języka Visual Basic
Czasami dobrym pomysłem może być przeniesienie powiązanych ze sobą fragmentów kodu do osobnego pliku. Słowo kluczowe Partial pozwala umieszczać wybrane fragmenty klas w różnych plikach. Można na przykład przenieść do osobnego pliku kod formularza, który ładuje i zapisuje dane, a następnie za pomocą słowa kluczowego Partial zaznaczyć, że jest on częścią tego formularza. Szczegółowy opis słowa kluczowego Partial znajduje się w rozdziale 26. — „Klasy i struktury”. Słowa tego nie da się natomiast używać dla modułów, które w całości muszą znajdować się w jednym pliku. W tej sytuacji można posłużyć się regionami, aby wydzielić grupy powiązanych ze sobą procedur i ułatwić czytanie kodu.
Kompilacja warunkowa Instrukcje kompilacji warunkowej pozwalają na dołączanie lub wykluczanie kodu z kompilacji programu. Podstawowa instrukcja kompilacji warunkowej przypomina wielowierszową instrukcję If Then Else. Poniższy listing przedstawia kod typowej takiej instrukcji. Jeśli wartość conditional1 jest True, do kompilowanego programu zostanie dołączony kod bloku code_block_1. Jeżeli wartość ta jest False, ale wartość condition2 jest True, w skład kompilowanego programu wejdzie kod bloku code_block_2. Jeśli żaden z warunków nie zostanie spełniony, do programu zostanie dołączony kod bloku code_block_3. #If
condition1 Then code_block_1... #ElseIf condition2 Then code_block_2... #Else code_block_3... #End If
Należy pamiętać, że kod, który nie został dołączony przez instrukcję kompilacji warunkowej, jest całkowicie pomijany w wykonywalnej postaci programu. Visual Studio decyzję o dołączeniu lub niedołączeniu bloku kodu podejmuje w czasie kompilacji. Oznacza to, że kod, który nie został dołączony, nie zajmuje miejsca w skompilowanym pliku, a także że nie można ustawić w debugerze instrukcji wykonywania na te opuszczone wiersze, ponieważ po prostu ich nie ma. Natomiast zwykła instrukcja If Then Else dołącza cały kod z wszystkich swoich bloków do wykonywalnej wersji programu, a dopiero w czasie działania aplikacji podejmuje decyzję, który z nich wykonać. Ze względu na to, że instrukcje kompilacji warunkowej są ewaluowane w czasie kompilacji, ich warunki właśnie wtedy muszą dać się sprawdzić. Mogą to być na przykład wyrażenia zawierające wartości zdefiniowane przy użyciu dyrektyw kompilatora (opisane nieco dalej). Nie powinny zawierać wartości generowanych w czasie działania programu (na przykład wartości zmiennych). W rzeczywistości instrukcje kompilacji warunkowej ewaluują swoje warunki w czasie projektowania, dzięki czemu informacje o nich można uzyskać już w czasie pisania kodu. Jeśli na przykład opcja Option Explicit jest ustawiona na On, poniższa instrukcja przypisania zostanie oznaczona przez Visual Basic jako błędna. Ponieważ pierwszy warunek ma wartość
Rozdział 14.
Struktura programu i modułu
277
True, zmienna X jest zadeklarowana jako łańcuch. Włączona opcja Option Explicit zabra-
nia niejawnych konwersji pomiędzy typami całkowitoliczbowymi i łańcuchowymi, przez co instrukcja ta zostaje przez IDE oznaczona jako błąd. #If True Then Dim X As String #Else Dim X As Integer #End If X = 10
Należy jednak pamiętać, że niedołączany do kompilacji kod nie jest ewaluowany przez IDE. Gdyby pierwszy warunek miał wartość False, kod ten działałby bez zarzutów, ponieważ zmienna X zostałaby zadeklarowana jako typu Integer. IDE nie ewaluuje pozostałego kodu, a więc nie zauważa błędu, który by wystąpił, gdyby warunek ten miał wartość False. Usterka ujawniłaby się prawdopodobnie dopiero wtedy, gdybyśmy naprawdę spróbowali użyć tej drugiej części kodu. Stałe kompilacji warunkowej można ustawiać na dwa sposoby — w kodzie i w ustawieniach kompilacji projektu.
Ustawianie stałych w kodzie Do ustawiania stałych kompilacji warunkowej bezpośrednio w kodzie programu służy instrukcja #Const. Na przykład: #Const UserType = "Urzędnik" #If UserType = "Urzędnik" Then ' Kod związany z urzędnikami... ... #ElseIf UserType = "Nadzorca" Then ' Kod związany z nadzorcami... ... #Else ' Kod związany z innymi pracownikami... ... #End If
Należy pamiętać, że definicje tych stałych są dostępne dopiero od miejsca, w którym wpisano je w kodzie. Jeśli stała zostanie użyta przed jej zdefiniowaniem, ma wartość False (niestety opcja Option Explicit nie ma zastosowania do tych stałych). Oznacza to, że poniższy kod wyświetli wartość Slow przed wartością Fast: #If UseFastAlgorithm Then MessageBox.Show("Fast") #Else MessageBox.Show("Slow") #End If #Const UseFastAlgorithm = True #If UseFastAlgorithm Then
278
Część II
Wstęp do języka Visual Basic
MessageBox.Show("Fast") #Else MessageBox.Show("Slow") #End If
Wielu programistów — w celu uniknięcia problemów — definiuje te stałe na początku pliku. Pamiętaj również, że zdefiniowaną już stałą można przedefiniować za pomocą drugiej instrukcji #Const. Oznacza to, że nie są one stałymi w sensie wartości, których nie można modyfikować.
Ustawianie stałych za pomocą ustawień kompilacji projektu Aby ustawić stałe za pomocą ustawień kompilacji projektu, otwórz okno Solution Explorer i dwukrotnie kliknij pozycję My Project. Kliknij kartę Compile, a następnie przycisk Advanced Compile Options w celu otwarcia okna dialogowego Advanced Compiler Settings z rysunku 14.4. Nazwy i wartości stałych wprowadź w polu tekstowym Custom constants. Wartości te powinny być wpisywane w formacie NazwaStałej="Wartość", a poszczególne definicje należy pooddzielać przecinkami.
Rysunek 14.4. Okno Advanced Compiler Settings pozwala na zdefiniowanie stałych kompilacji
Stałe zdefiniowane w oknie dialogowym Advanced Compiler Settings są dostępne w każdym miejscu projektu. Można je przedefiniować za pomocą dyrektywy #Const. Przedefiniowana stała zachowuje nową wartość do końca pliku — lub aż zostanie przedefiniowana. Program CompilerConstantsSettings, który można pobrać z serwera FTP wydawnictwa Helion, zawiera stałe ustawione w tym oknie dialogowym i kod sprawdzający ich wartości.
Rozdział 14.
Struktura programu i modułu
279
Stałe predefiniowane Visual Basic automatycznie definiuje kilka stałych kompilacji warunkowej, za pomocą których można wyznaczyć kod do skompilowania przez aplikację. Poniższa tabela zawiera opis tych stałych. W wartościach stałych kompilacji rozróżniane są małe i wielkie litery. Na przykład wartość stałej CONFIG należy porównywać z łańcuchem Debug, a nie debug. Stała
Opis
CONFIG
Łańcuch określający nazwę aktualnej wersji. Zazwyczaj jest to Debug lub Release.
DEBUG
Wartość logiczna, która wskazuje, czy jest to wersja Debug programu. Domyślnie stała ta ma wartość True podczas kompilacji wersji Debug programu.
PLATFORM
Łańcuch określający docelową platformę dla bieżącej konfiguracji programu. Jeśli nie zostanie zmieniona, wartość tej stałej to AnyCPU.
TARGET
Łańcuch informujący o rodzaju aplikacji budowanej w projekcie. Może to być winexe (aplikacja Windows Forms lub WPF), exe (aplikacja konsolowa), library (biblioteka klas) lub module (moduł kodu).
TRACE
Wartość logiczna, która określa, czy obiekt Trace powinien wysyłać dane do okna Output.
VBC_VER
Liczba określająca wersje minor i major Visual Basica. Wartość ta dla Visual Basica 2008 wynosi 9.0.
_MyType
Łańcuch określający rodzaj aplikacji. Do typowych wartości należą: Console dla programu konsolowego, Windows dla klasy lub biblioteki kontrolnej Windows oraz WindowsForms dla aplikacji Windows Forms.
Więcej informacji na temat stałej _MyType i jej powiązań z innymi specjalnymi stałymi kompilacji znajduje się na stronie msdn2.microsoft.com/enus/library/ms233781(VS.80).aspx. Sposoby sprawdzania wartości tych stałych w programie demonstruje aplikacja CompilerConstantsInCode, którą można pobrać z serwera FTP wydawnictwa Helion. Program WpfCompilerConstantsInCode, także do pobrania z internetu, jest wersją WPF tej aplikacji. W kolejnych podrozdziałach bardziej szczegółowo opiszę stałe DEBUG, TRACE i CONFIG oraz typowe sposoby ich użycia.
Stała kompilacji DEBUG Zwykle gdy tworzona jest kompilacja typu Debug projektu, Visual Basic ustawia stałą DEBUG na wartość True. Natomiast przy kompilacji wersji ostatecznej stała ta ma wartość False. Menedżer konfiguracji (ang. Configuration Manager) pozwala na wybór opcji Debug, Release lub innej, zdefiniowanej przez programistę.
280
Część II
Wstęp do języka Visual Basic
Po aktywowaniu menedżera konfiguracji można go otworzyć poprzez kliknięcie projektu w oknie Solution Explorer i kliknięcie polecenia Configuration Manager w menu Build. Na rysunku 14.5 przedstawiono okno Configuration Manager. Wybierz z listy rozwijanej opcję Debug lub Release i kliknij przycisk Close.
Rysunek 14.5. W oknie Configuration Manager można wybrać opcję Debug lub Release
Jeśli w menu Build nie ma polecenia Configuration Manager, otwórz menu Tools i kliknij opcję Options. Rozwiń węzeł Projects and Solutions i kliknij pozycję General. Następnie zaznacz pole wyboru Show advanced build configurations. Kiedy stała DEBUG ma wartość True, metody obiektu Debug wysyłają dane do okna Output. W przeciwnym przypadku metody obiektu Debug nie generują żadnego kodu, przez co nie tworzy on żadnych danych. Dzięki temu obiekt ten jest przydatny do wyświetlania danych diagnostycznych podczas pracy nad projektem oraz ukrywania tych komunikatów w wersjach finalnych, które są przeznaczone dla użytkownika końcowego. W kolejnych podrozdziałach opiszę niektóre najbardziej przydatne własności i metody obiektu Debug.
Metoda Assert Metoda Debug.Assert ewaluuje wyrażenie logiczne. Jeśli jego wartością jest False, wyświetla komunikat o błędzie. Jako opcjonalne parametry może ona przyjmować komunikat o błędzie i szczegółowy komunikat do wyświetlenia. Poniższy listing demonstruje użycie metody Debug.Assert, która sprawdza, czy zmienna NumEmployees ma wartość większą niż zero: Debug.Assert(NumEmployees > 0, _ "Liczba pracowników musi być większa od zera.", _ "Program nie może wygenerować zestawień czasu pracy, jeśli nie ma zdefiniowanych żadnych pracowników.")
Rozdział 14.
Struktura programu i modułu
281
Powyższa instrukcja została użyta w programie EmployeeAssert, który można pobrać z serwera FTP wydawnictwa Helion. Jeśli zmienna NumEmployees ma wartość 0, instrukcja ta wyświetla okno dialogowe z komunikatem o błędzie i szczegółowymi informacjami o nim. Dodatkowo pokazuje stos wywołań, na którym można sprawdzić, jaki kod wywołał inny, co doprowadziło do tej sytuacji. Praktyczne znaczenie ma tylko kilka pierwszych pozycji tego stosu, ponieważ reszta dotyczy pomocniczych bibliotek języka Visual Basic, które wykonują program. Dodatkowo okno to zawiera trzy przyciski — Abort, Retry oraz Ignore. Pierwszy z nich natychmiast zamyka program. Retry włącza debuger, aby można było dokładnie zbadać kod. Przycisk Ignore pozwala kontynuować, tak jakby warunek instrukcji Assert zwrócił wartość True. Dobrym zastosowaniem metody Assert jest weryfikacja przed rozpoczęciem obliczeń, czy parametry lub inne zmienne wartości procedur wyglądają na sensowne. Wyobraźmy sobie na przykład, że podprocedura AssignJob przypisuje do jakiejś pracy mechanika. Procedura ta mogłaby zaczynać się od kilku instrukcji Assert, które sprawdzałyby, czy taka osoba istnieje, czy jest taka praca, czy osoba ta ma odpowiednie kwalifikacje do wykonywania tej pracy itd. Tego rodzaju błędy zazwyczaj łatwiej jest wyłapywać przed rozpoczęciem jakichś długich obliczeń czy większych modyfikacji bazy danych, które mogą w pewnym momencie zakończyć się niepowodzeniem, ponieważ mechanik nie ma na przykład samochodu, aby dojechać na miejsce. Jeśli stała DEBUG nie ma wartości True, metoda Assert nic nie robi. W ten sposób można się pozbyć mało przyjaznych komunikatów w wersji programu przeznaczonej dla klienta. Okno dialogowe z różnymi technicznymi szczegółami jest tak straszne dla użytkowników, że nie ma sensu im go pokazywać. Decyzję, które testy umieścić w instrukcjach Assert, aby móc je później łatwo usunąć, trzeba dokładnie rozważyć. Wyobraźmy sobie na przykład, że za pomocą instrukcji Assert sprawdzamy, czy użytkownik wprowadził poprawny łańcuch. Skompilowana wersja wykonawcza programu jest pozbawiona tego testu, przez co program staje się bezbronny wobec nieprawidłowych danych. Gdy używa się metody Assert do weryfikacji warunku, należy upewnić się, że program będzie działał bezpiecznie także wtedy, gdy warunek ten nie będzie spełniony, a instrukcja zostanie usunięta.
Metoda Fail Metoda Debug.Fail wyświetla komunikat o błędzie, jak metoda Debug.Assert, kiedy jej warunek logiczny ma wartość False.
IndentSize, Indent, Unindent oraz IndentLevel Te własności i metody określają głębokość wcięcia stosowaną przez obiekty Debug, które wysyłają dane do okna Output. Za ich pomocą można powcinać dane podprocedur, aby lepiej zaznaczyć strukturę programu.
282
Część II
Wstęp do języka Visual Basic
Własność IndentSize określa liczbę spacji dla każdego poziomu wcięcia, zaś IndentLevel — poziom wcięcia. Jeśli na przykład własność IndentSize ma wartość 4, a IndentLevel — 2, dane są wcinane na głębokość ośmiu spacji. Metody Indent i Unindent zwiększają i zmniejszają poziom wcięcia o jeden.
Write, WriteLine, WriteIf oraz WriteLineIf Te procedury wysyłają dane do okna Output. Metoda Print drukuje tekst i kończy działanie bez rozpoczęcia nowego wiersza, a WriteLine drukuje tekst i przechodzi do kolejnego wiersza. Metody WriteIf i WriteLineIf pobierają logiczny parametr i działają tak samo jak metody Write i WriteLine, jeśli parametr ten ma wartość True.
Obiekt Trace Obiekt Trace jest bardzo podobny do obiektu Debug — ma taki sam jak on zestaw własności i metod. Różnica polega na tym, że obiekt Trace generuje dane, gdy zdefiniowana jest stała TRACE, a nie DEBUG. Stała TRACE jest z reguły definiowana zarówno w wersji rozwojowej, jak i końcowej programu, dzięki czemu metoda Trace.Assert i inne metody tego obiektu działają w każdej wersji aplikacji. Stała DEBUG jest domyślnie definiowana tylko w wersjach rozwojowych, dzięki czemu komunikaty obiektu Debug wyświetlane są tylko w tych wersjach. Do obiektu Trace (lub Debug) można wstawić obiekty nasłuchujące, które wykonują różne działania, zależnie od danych wygenerowanych przez ten obiekt. Na przykład taki obiekt nasłuchujący może zapisywać dane obiektu Trace w pliku dziennika.
Stała CONFIG Wartość stałej CONFIG określa nazwę rodzaju wersji programu. Z reguły jest to Debug (wersja testowa) lub Release (wersja ostateczna), ale można także tworzyć własne konfiguracje, takie jak interim build, point release, alfa, beta i inne. Aby wybrać wersję testową lub ostateczną, kliknij projekt w oknie Solution Explorer, a następnie w menu Build opcję Configuration Manager. Zostanie wyświetlone okno dialogowe z rysunku 14.5. Z listy rozwijanej wybierz pozycję , aby otworzyć okno dialogowe New Project Configuration. Wprowadź nazwę dla nowej konfiguracji, wybierz istniejącą konfigurację, z której ma ona skopiować swoje początkowe ustawienia, po czym kliknij przycisk OK. Poniższy kod demonstruje użycie zmiennej kompilatora CONFIG do określenia rodzaju kompilacji oraz podjęcia różnych działań w zależności od tego typu: #If CONFIG = "Debug" Then ' Kompilacja Debug... #ElseIf CONFIG = "Release" Then ' Kompilacja Release... #ElseIf CONFIG = "InterimBuild" Then
Rozdział 14.
Struktura programu i modułu
283
' Niestandardowa kompilacja InterimBuild... #Else MsgBox("Nieznany typ kompilacji") #End If
Jednym z zastosowań różnych konfiguracji jest obsługa poszczególnych systemów operacyjnych. Kod może zdecydować, która konfiguracja jest aktywna, i na tej podstawie wykonać odpowiedni kod dla docelowego systemu operacyjnego. Może na przykład być konieczne obejście ograniczonych uprawnień, które są domyślnie nadawane w systemie Windows Vista.
Stałe poziomu debugowania Czasami przydatna jest możliwość łatwego dostosowywania poziomu danych diagnostycznych, które są generowane przez program. Można w tym celu zdefiniować stałą DEBUG_LEVEL, aby wysyłać dane do okna Output w zależności od jej wartości. Na przykład instrukcje Debug pierwszego poziomu można umieścić w głównych podprocedurach, instrukcje drugiego poziomu w drugorzędnych podprocedurach, a instrukcje trzeciego poziomu we wszystkich ważniejszych procedurach. Następnie wystarczy odpowiednio zdefiniować stałą DEBUG_LEVEL, aby uzyskać wymagane dane. Poniższy listing przedstawia krótki przykład. Funkcja IsPhoneNumberValid sprawdza, czy jej parametr jest poprawnym siedmio- lub dziesięciocyfrowym numerem telefonicznym. Jeśli stała DEBUG_LEVEL ma przynajmniej wartość 1, funkcja ta wyświetla komunikaty po rozpoczęciu działania i po zakończeniu. Ponadto wcina wygenerowane dane po rozpoczęciu i usuwa wcięcie przed zakończeniem działania. Jeśli stała DEBUG_LEVEL ma wartość przynajmniej 2, funkcja ta wyświetla instrukcje, które informują, kiedy ma zamiar sprawdzać siedmio- i dziesięciocyfrowe numery telefoniczne. #Const DEBUG_LEVEL = 2 Private Function IsPhoneNumberValid(ByVal phone_number As String) As Boolean #If DEBUG_LEVEL > = 1 Then Debug.WriteLine("Entering IsPhoneNumberValid(" & phone_number & ")") Debug.Indent() #End If ' Sprawdzenie, czy numer jest siedmiocyfrowy. #If DEBUG_LEVEL > = 2 Then Debug.WriteLine("Sprawdzanie czy numer jest siedmiocyfrowy.") #End If Dim is_valid As Boolean = _ phone_number Like "###-####" If Not is_valid Then #If DEBUG_LEVEL > = 2 Then Debug.WriteLine("Sprawdzanie czy numer jest dziesięciocyfrowy.") #End If is_valid = phone_number Like "###-###-####" End If
284
Część II
Wstęp do języka Visual Basic
#If DEBUG_LEVEL > = 1 Then Debug.Unindent() Debug.WriteLine("Wychodzenie z funkcji IsPhoneNumberValid, zwracanie " is_valid) #End If Return is_valid End Function
&
Poniżej znajdują się przykładowe dane z okna Output, wygenerowane przy ustawieniu stałej DEBUG_LEVEL na 2: Entering IsPhoneNumberValid(123-4567) Checking for 7-digit phone number Leaving IsPhoneNumberValid, returning True
Z danych tych wynika, że funkcja zweryfikowała łańcuch 123-4567, nie musiała sprawdzać, czy jest to numer dziesięciocyfrowy, po czym zwróciła wartość True. Technikę tę wykorzystuje się do ustawiania różnych poziomów danych debugowania w programie DebugLevel, który można pobrać z serwera FTP wydawnictwa Helion. Więcej informacji na temat debugowania aplikacji w języku Visual Basic znajduje się w rozdziale 19. — „Obsługa błędów”.
Przestrzenie nazw Przestrzenie nazw są wykorzystywane przez Visual Studio do kategoryzowania kodu. Jedna przestrzeń nazw może zawierać inne przestrzenie nazw, które z kolei mogą zawierać jeszcze inne itd., w wyniku czego tworzy się hierarchia przestrzeni nazw. Przestrzenie nazw mogą być pomocne w dzieleniu kodu na kategorie. Dzięki umieszczaniu odmiennych procedur w różnych przestrzeniach nazw można zmusić wybrane części programu do dołączania tylko tych przestrzeni nazw, których rzeczywiście używają. W ten sposób łatwiej jest pominąć te procedury, których aplikacja nie potrzebuje. Ponadto dzięki tej technice można zdefiniować na przykład kilka metod o takiej samej nazwie, przynależnych do różnych przestrzeni nazw. Można na przykład zdefiniować przestrzeń nazw Accounting, która zawiera przestrzenie nazw AccountsReceivable i AccountsPayable. Każda z nich może zawierać podprocedurę o nazwie ListOutstandingInvoices. Aby wywołać którąś z nich należałoby napisać Accounting.AccountsReceivable.ListOutstandingInvoices lub Accounting.AccountsPayable. ListOutstandingInvoices. Instrukcji Namespace można używać tylko na poziomie pliku lub wewnątrz innej przestrzeni nazw. Nie da się jej stosować w klasach i modułach. W przestrzeniach nazw można zagnieżdżać inne przestrzenie nazw, klasy oraz moduły. Kod przedstawiony na poniższym listingu definiuje przestrzeń nazw AccountingModules. Zawiera ona dwie klasy PayableItem i ReceivableItem, moduł AccountingRoutines oraz zagnieżdżoną przestrzeń nazw OrderEntryModules. W module AccountingRoutines znajduje się definicja podprocedury PayInvoice. Wszystkie klasy, moduły i przestrzenie nazw mogą definiować inne elementy.
Rozdział 14.
Struktura programu i modułu
285
Namespace AccountingModules Public Class PayableItem ... End Class Public Class ReceivableItem ... End Class Module AccountingRoutines Public Sub PayInvoice(ByVal invoice_number As Long) ... End Sub ... End Module Namespace OrderEntryModules Public Class OrderEntryClerk ... End Class ... End Namespace End Namespace
Kod, w którym używana jest przestrzeń nazw jakiegoś modułu, nie musi jawnie go identyfikować. Jeśli moduł ten zawiera definicję jakiejś zmiennej lub procedury, której nazwa jest unikatowa, nie trzeba w celu jej użycia podawać jego nazwy. W tym przypadku jest tylko jedna podprocedura o nazwie PayInvoice, a więc można ją wywoływać jako AccountingModules.PayInvoice. Gdyby przestrzeń nazw AccountingModules zawierała jeszcze jeden moduł, w którym znajdowałaby się podprocedura PayInvoice, konieczne byłoby dokładne określenie, której podprocedury użyć, na przykład: AccountingModules.AccountingRoutines.PayInvoice. Moduły są przejrzyste w swoich przestrzeniach nazw, ale o zagnieżdżonych przestrzeniach nazw nie da się tego powiedzieć. Ponieważ zagnieżdżona przestrzeń nazw OrderEntryModules definiuje klasę OrderEntryClerk, konieczne jest podanie pełnej ścieżki przestrzeni nazw do tej klasy, jak poniżej: Dim oe_clerk As New AccountingModules.OrderEntryModules.OrderEntryClerk
Należy pamiętać, że każdy projekt Visual Basica definiuje swoją własną przestrzeń nazw, która obejmuje cały projekt. Przestrzeń ta nosi taką samą nazwę jak jej projekt. Aby obejrzeć lub zmodyfikować tę przestrzeń nazw najwyższego poziomu, kliknij dwukrotnie pozycję My Project w oknie Solution Explorer, aby otworzyć stronę własności projektu oraz przejdź na kartę Application. Nową nazwę dla głównej przestrzeni nazw wpisz w polu tekstowym Root namespace, które znajduje się na górze po prawej stronie. Aby uprościć dostęp do przestrzeni nazw wewnątrz pliku, można użyć instrukcji Imports. Wyobraźmy sobie na przykład, że pracujemy nad projektem o nazwie GeneralAccounting, którego główna przestrzeń nazw ma nazwę GeneralAccounting. Pierwsza instrukcja znajdująca się w poniższym kodzie umożliwia używanie w programie elementów przestrzeni AccountingModules bez przedrostka AccountingModules. Druga pozwala na używanie elementów zdefiniowanych w przestrzeni nazw OrderEntryModules, zagnieżdżonej w przestrzeni AccountingModules. Pozostałe dwa wiersze tego kodu deklarują zmienne przy użyciu klas z tych zaimportowanych przestrzeni nazw.
286
Część II
Imports Imports ... Private Private
Wstęp do języka Visual Basic GeneralAccounting.AccountingModules GeneralAccounting.AccountingModules.OrderEntryModules m_OverdueItem As PayableItem m_ThisClerk As OrderEntryClerk
' Z przestrzeni nazw AccountingModules. ' Z przestrzeni nazw ' AccountingModules.OrderEntryModules.
Typograficzne elementy kodu Istnieje kilka typograficznych elementów kodu, które mogą sprawić, że struktura programu będzie nieco bardziej przejrzysta i łatwiejsza do zrozumienia. Nie wykonują one żadnych instrukcji programistycznych, ale stanowią ważną część struktury kodu. Do elementów tych należą komentarze, znaki kontynuacji wiersza i znaki łączące oraz etykiety wierszy.
Komentarze Komentarze pomagają innym programistom (a także nam samym po upływie dłuższego czasu) zrozumieć przeznaczenie i strukturę kodu. Początek komentarza wyznacza pojedynczy cudzysłów ('), nieznajdujący się w cudzysłowie podwójnym. Wszystkie znaki, które stoją za nim do końca wiersza, stanowią komentarz; są ignorowane przez Visual Basic. Jeśli na końcu wiersza zawierającego komentarz znajduje się znak kontynuacji wiersza (opisany dalej), Visual Basic ignoruje ten ostatni. Oznacza to, że wiersz nie jest kontynuowany w kolejnym wierszu, przez co komentarz kończy się w bieżącym wierszu. Innymi słowy: nie da się za pomocą znaku kontynuacji wiersza utworzyć wielowierszowego komentarza. Na poniższym listingu po pierwszej deklaracji znajduje się komentarz. Kończy się on znakiem kontynuacji wiersza, dzięki czemu drugą deklarację moglibyśmy potraktować jako jego część. Tak oczywiście nie jest. Ponieważ taki zapis może wprowadzać w błąd, najlepiej nie stosować znaku kontynuacji wiersza na końcu komentarza. Drugi wiersz deklaruje i inicjuje łańcuch wartością zawierającą dwa pojedyncze cudzysłowy. Ponieważ znajdują się one w łańcuchu otoczonym cudzysłowami podwójnymi, żaden z nich nie rozpoczyna nowego komentarza. Trzeci pojedynczy cudzysłów w tym samym wierszu znajduje się poza tym łańcuchem, a więc wyznacza początek komentarza. Dim num_customers As Integer ' Liczba klientów _ Dim product_name As String = "Rock'n'Roll" ' Nazwa programu.
Aby kontynuować komentarz w kolejnym wierszu, trzeba użyć jeszcze jednego znaku komentarza, jak poniżej: ' Zwraca wartość True, jeśli adres jest poprawny. Funkcja ta sprawdza format adresu, ' aby dowiedzieć się, czy jest on poprawny. Znajduje także kod pocztowy i ' sprawdza, czy pasuje do niego podane miasto. Nie sprawdza, czy istnieje ' podana ulica i podany numer na tej ulicy. Private Function IsAddressValid(ByVal address_text As String) As Boolean ...
Rozdział 14.
Struktura programu i modułu
287
Aby szybko umieścić w komentarzu fragment kodu lub usunąć komentarz z fragmentu kodu, zaznacz ten fragment za pomocą myszy i w menu Edit otwórz podmenu Advanced. W celu wstawienia znaków komentarza kliknij opcję Comment Selection, aby usunąć znaki komentarza, kliknij opcję Uncomment Selection. Polecenia te są także dostępne w formie przycisków na pasku narzędzi Standard. Innym sposobem na szybkie usunięcie fragmentu kodu z programu jest otoczenie go dyrektywami kompilatora, jak poniżej: #If False Dim A Dim B Dim C #End If
Then As Integer As Integer As Integer
Zawsze używaj komentarzy, aby ułatwić czytanie swojego kodu. Nie spowalniają one finalnej wersji programu, a więc nie ma żadnego powodu, żeby ich unikać.
Komentarze XML Zwykły komentarz jest fragmentem tekstu, który dostarcza informacje programiście czytającemu kod. Komentarze XML pozwalają na dodanie pewnych dodatkowych informacji do komentarzy. Można na przykład oznaczyć komentarz jako podsumowanie opisujące podprocedurę. Visual Studio automatycznie pobiera dane z komentarzy XML i tworzy z nich plik XML, który opisuje projekt. Prezentuje on hierarchiczny kształt projektu, ukazując komentarze do jego modułów, przestrzeni nazw, klas i innych elementów. Wynik nie jest zbyt łatwy do odczytu, ale za jego pomocą można automatycznie wygenerować bardziej przydatną dokumentację. Bloki komentarzy XML można umieszczać przed elementami kodu, które nie znajdują się wewnątrz żadnej metody. Z reguły za ich pomocą opisuje się moduły, klasy, zmienne, własności, metody lub zdarzenia. Aby rozpocząć blok komentarza, umieść kursor w wierszu przed elementem, który chcesz opisać, po czym wpisz trzy pojedyncze cudzysłowy ('''). Visual Studio automatycznie wstawi szablon bloku komentarza XML. Jeśli opisywany element pobiera jakieś parametry, wstawiony szablon będzie zawierał sekcje do ich opisu. Dlatego najkorzystniej jest wszystkie je zdefiniować przed utworzeniem bloku komentarza XML. Poniżej znajduje się blok komentarza XML dla prostej podprocedury. Zawiera on sekcję summary, przeznaczoną na opis podprocedury, dwie sekcje param do opisu parametrów tej podprocedury oraz sekcję remarks na dodatkowe informacje. ''' ''' ''' ''' ''' ''' Public Sub AssignJobs(ByVal jobs() As Job, ByVal employees() As Employee) End Sub
288
Część II
Wstęp do języka Visual Basic
Zauważ, że elementy XML mogą obejmować kilka wierszy, jak w tym przypadku element summary. Kolejne sekcje komentarza XML można dodawać poprzez prostu wpisanie ich. Należy trzymać się konwencji, która nakazuje wstawienie na początku każdego wiersza trzech pojedynczych cudzysłowów. Na przykład poniższy kod jest uzupełnioną wersją powyższego komentarza, w której poza treścią dodano element WrittenBy z atrybutem date: ''' ''' Przypisuje zadania do pracowników, maksymalizując wartość całkowitą przydzielonych zadań. ''' ''' Tablica zadań do przydzielenia. ''' Tablica pracowników do przydzielenia. ''' Pełne przypisanie nie daje gwarancji unikatowości. ''' Rod Stephens Public Sub AssignJobs(ByVal jobs() As Job, ByVal employees() As Employee) End Sub
Komentarze te są nieco rozwlekłe i trudne do czytania. W powyższym przykładzie wcale nie tak łatwo jest wychwycić najważniejsze informacje o podprocedurze po szybkim rzucie okiem na kod. Aby ułatwić czytanie komentarzy XML, Visual Basic dla każdego takiego bloku definiuje sekcję outlining. Kliknięcie znaku minusa znajdującego się po lewej stronie pierwszego wiersza bloku powoduje zwinięcie go i pozostawienie widocznego tylko podsumowania. Kliknięcie następnie znaku plusa powoduje ponowne rozwinięcie komentarzy. Kod na poniższym listingu pochodzi z początku aplikacji przypisującej zadania do pracowników. Projekt ten składa się z dwóch plików — formularza o nazwie Form1.vb i modułu kodu — Module1.vb. Formularz ten zawiera bardzo mało kodu. W module natomiast znajdują się definicje klas Job i Employee oraz podprocedura AssignJobs. Każdy z tych elementów posiada własny blok komentarza XML. Public Class Form1 Private m_Jobs() As Job Private m_Employees() As Employee End Class Module Module1 Public Class Job Public JobNumber As Integer ''' ''' Lista umiejętności potrzebnych do wykonania zadania. ''' ''' Wymagany sprzęt jest wyszczególniony jako umiejętności. Public SkillsRequired As New Collection ''' ''' Wartość tego zadania. ''' ''' Wyższe liczby oznaczają większy priorytet. Public Priority As Integer End Class Public Class Employee
Rozdział 14.
Struktura programu i modułu
289
Public FirstName As String Public LastName As String ''' ''' Lista umiejętności posiadanych przez pracownika. ''' ''' Wymagany sprzęt jest wyszczególniony jako umiejętności. Public Skills As New Collection End Class ''' ''' Przydziela zadania pracownikom. ''' ''' Tablica zadań do przydzielenia. ''' Tablica pracowników do zadań. ''' Przydzielenie maksymalizuje całkowitą wartość przydzielonych zadań. ''' Rod Stephens Public Sub AssignJobs(ByVal jobs() As Job, ByVal employees() As Employee) End Sub End Module
Na rysunku 14.6 przedstawiono okno dialogowe ObjectBrowser z opisem własności SkillsRequired klasy Job. Obszar znajdujący się po prawej stronie na dole przedstawia sekcje XML summary i remarks tej własności. Projekt ten nazywa się AssignJobs, a jego główna przestrzeń nazw to AssignJobsRoot, przez co pełna ścieżka do klasy Job, pokazana jako drzewo po lewej stronie, to AssignJobst (projekt), AssignJobsRoot (główna przestrzeń nazw), Module 1 (moduł), Job (klasa).
Rysunek 14.6. Okno Object Browser wyświetla sekcje XML summary i remarks elementu
W czasie kompilacji programu Visual Studio umieszcza wszystkie komentarze XML w pliku XML o takiej samej nazwie, jaką nosi plik wykonywalny w katalogu bin/Debug projektu. Poniżej przedstawiony został wynik tego działania. Jeśli dobrze przyjrzysz się temu dokumentowi, znajdziesz w nim komentarze XML.
290
Część II
Wstęp do języka Visual Basic
AssignJobs Lista umiejętności potrzebnych do wykonania zadania. Wymagany sprzęt jest wyszczególniony jako umiejętności. Wartość tego zadania. Wyższe liczby oznaczają większy priorytet. Lista umiejętności posiadanych przez pracownika. Wymagany sprzęt jest wyszczególniony jako umiejętności. Przydziela zadania pracownikom. Tablica zadań do przydzielenia. Tablica pracowników do zadań. Przydzielenie maksymalizuje całkowitą wartość przydzielonych zadań. Rod Stephens
Klasy przydzielania zadań, które można obejrzeć w oknie Object Browser, są zdefiniowane w programie AssignJobs, który można pobrać z serwera FTP wydawnictwa Helion. Po skompilowaniu tej aplikacji, która w rzeczywistości nie przydziela żadnych zadań, można zbadać jej dokumentację XML.
Znak kontynuacji wiersza Znaki kontynuacji wiersza pozwalają na dzielenie długich wierszy kodu na krótsze, dzięki czemu łatwiej się je czyta. Aby kontynuować wiersz kodu w następnej linijce, należy na jego końcu postawić znak podkreślenia (_). Poniższy kod jest tak traktowany przez Visual Basic, jakby w całości znajdował się w jednej linijce: Dim background_color As Color = _ Color.FromName( _ My.Resources.ResourceManager.GetString( _ "MainFormBackgroundColor"))
Jak pamiętamy z poprzedniego podrozdziału, nie można kontynuować komentarzy. Wszystko, co znajduje się w komentarzu, jest ignorowane przez Visual Basic.
Rozdział 14.
Struktura programu i modułu
291
Wiersz da się podzielić na dwie części w dowolnym miejscu, w którym może znajdować się spacja i pomiędzy elementami. Możesz na przykład zrobić to za otwierającym nawiasem listy parametrów, jak w poniższym przykładzie: AReallyReallyLongSubroutineNameThatTakesFiveParameters( _ parameter1, parameter2, parameter3, parameter4, parameter5)
Wiersza nie można podzielić wewnątrz łańcucha. Aby podzielić łańcuch, należy go zakończyć w jednym wierszu i ponownie rozpocząć w kolejnym, jak poniżej: Dim txt As String = "Aby podzielić długi łańcuch na kilka wierszy, " "zakończ go, dodaj znak kontynuacji " & _ "(space + underscore) " & _ "i ponownie otwórz w następnym wierszu."
&
_
Visual Basic zazwyczaj nie wymusza swoich typowych wcięć, jeśli chodzi o kontynuowane wiersze, dzięki czemu można je tak ustawić, aby jak najlepiej oddawały strukturę aplikacji. Na przykład wielu programistów w długich wywołaniach podprocedur wyrównuje parametry: DoSomething( _ parametr1, _ parametr2, _ parametr3)
Łączenie wierszy Ne tylko można dzielić długie wiersze na kilka krótszych, ale także łączyć krótkie instrukcje w jednym wierszu. Dwie instrukcje znajdujące się w jednym wierszu muszą być oddzielone dwukropkiem. Poniższy wiersz kodu zawiera trzy instrukcje, które zapisują komponenty koloru tła formularza w zmiennych r, g i b: r = BackColor.R : g = BackColor.G : b = BackColor.B
Technika ta jest najbardziej przydatna, gdy w programie znajduje się kilka wierszy o bardzo podobnej strukturze. Gdy zbadasz te wiersze, być może znajdziesz różnice, które mogą oznaczać błąd. Technikę łączenia wierszy należy stosować ostrożnie. Jeśli instrukcje są długie lub kolejne połączone wiersze nie mają podobnej struktury, kod może być trudny do odczytu. Jeśli kod czyta Ci się lepiej, kiedy każda instrukcja znajduje się w osobnym wierszu, zatrzymaj taką strukturę. Większa liczba wierszy kodu nie spowalnia działania programu.
Etykiety wierszy Po lewej stronie każdego wiersza można wstawić jego etykietę. Może to być jakaś nazwa lub liczba zakończona dwukropkiem. Poniższy kod definiuje trzy etykiety wierszy. Pierwsza z nich nosi nazwę DeclareX i oznacza deklarację zmiennej X. Druga ma wartość 10 i znajduje się w wierszu z komentarzem. Trzecia nosi nazwę Done i oznacza pusty wiersz. DeclareX:
Dim X As Single
292
Część II
10: Done:
Wstęp do języka Visual Basic ' Jakieś działania.
Etykiety po to są nadawane wierszom, aby móc później przeskoczyć do danego wiersza. Na przykład do wiersza o określonej etykiecie mogą przechodzić instrukcje GoTo, On Error GoTo oraz Resume. Instrukcje te w Visual Basicu .NET są już mniej przydatne, niż były w Visual Basicu 6, ponieważ zostały wyparte przez strukturalną obsługę błędów (blok Try Catch). Nadal jednak można ich używać.
Podsumowanie Rozwiązanie Visual Studio składa się z elementów uporządkowanych hierarchicznie. Na najwyższym poziomie znajduje się jeden lub więcej projektów. Każdy projekt zawiera kilka standardowych elementów, takich jak My Project (reprezentujący projekt jako całość), References (rejestrujący informacje o odwołaniach do zewnętrznych obiektów), elementów bin i obj (wykorzystywanych przez Visual Studio podczas kompilacji aplikacji) oraz app.config (który przechowuje informacje konfiguracji). Ponadto znajdują się w nim formularze, klasy i inne moduły kodu. Zwykle wiele z tych plików jest ukrytych i nie ma potrzeby edytowania ich bezpośrednio. Zamiast tego do przeglądania i modyfikowania wartości aplikacji można użyć stron Properties, które są dostępne po kliknięciu pozycji My Project w oknie Solution Explorer. Pozostałe ukryte pliki przechowują kod i zasoby, które określają wygląd formularza. Można je modyfikować poprzez edytowanie formularza w projektancie formularzy. W module kodu można grupować powiązane ze sobą fragmenty kodu w blokach będących modułami, klasami, regionami i za pomocą przestrzeni nazw. Przy użyciu instrukcji kompilacji warunkowej i stałych kompilacji warunkowej można łatwo dodawać i usuwać z kompilowanego programu fragmenty kodu. Obiekty Debug i Trace pozwalają generować komunikaty i ostrzeżenia, w zależności od tego, czy zostały zdefiniowane określone predefiniowane stałe. W końcu elementy typograficzne, takie jak komentarze, znaki kontynuacji wiersza i łączenie wierszy pozwalają w taki sposób formatować kod, że jest łatwiejszy do odczytania i zrozumienia. Komentarze XML dostarczają dodatkowe informacje, przydatne w oknie Object Browser, za pomocą których można automatycznie generować dokumentację. Mimo iż wszystkie te elementy nie są wymagane w Visual Basicu, ich zastosowanie może oznaczać różnicę rzędu dobrego zrozumienia kodu i niezrozumienia go w ogóle. Biorąc pod uwagę cykl życia aplikacji, jej rozwój, debugowanie, udoskonalanie i konserwowanie, elementy te mogą zadecydować o powodzeniu całego projektu. W tym rozdziale opisałem elementy strukturalne, które wchodzą w skład plików z kodem źródłowym. Można w nich umieszczać kod zbierający, przechowujący i przetwarzający dane. Rozdział 15. — „Typy danych, zmienne i stałe” — zawiera charakterystykę zmiennych, w których program przechowuje wartości. Nauczysz się deklarować zmienne, dowiesz się, jakiego typu dane mogą one przechowywać, a także jak Visual Basic konwertuje jeden typ danych na inny.
15
Typy danych, zmienne i stałe Zmienne należą do najbardziej podstawowych elementów, z których składa się aplikacja. Zmienna to obiekt w programie, który przechowuje jakąś wartość. Tą ostatnią może być liczba, litera, łańcuch znaków, data, struktura zawierająca inne wartości lub obiekt reprezentujący zarówno dane, jak i związane z nimi akcje. Jeśli zmienna zawiera jakąś wartość, program może nią operować. Na liczbach będzie wykonywać działania arytmetyczne, na łańcuchach — łańcuchowe (konkatenacja, obliczanie podłańcuchów, znajdowanie wyznaczonych podłańcuchów w łańcuchach) na datach — związane z datami (znajdowanie różnicy pomiędzy dwiema datami, dodanie okresu czasu do daty) itd. Na zachowanie zmiennej mają wpływ cztery czynniki:
Typ danych określa rodzaj danych przechowywanych w zmiennej (liczba całkowita, znak, łańcuch itd.).
Zasięg zmiennej wyznacza kod, z którego można uzyskać do niej dostęp. Jeśli na przykład zmienna zostanie zadeklarowana w pętli For, będzie dostępna tylko dla kodu w tej pętli. Jeżeli zaś zmienna zostanie zadeklarowana na początku podprocedury, będzie można jej używać w każdym miejscu tej ostatniej.
Dostępność określa, jaki kod z innych modułów może uzyskać dostęp do zmiennej. Jeśli zmienna zostanie zadeklarowana na poziomie modułowym (poza wszelkimi podprocedurami w module) przy użyciu słowa kluczowego Private, będzie dostępna tylko w tym module. Jeżeli w deklaracji zmiennej zostanie użyte słowo kluczowe Public, będzie można jej używać także w innych modułach.
Czas życia określa, jak długo ważna jest wartość zmiennej. Zmienna znajdująca się w podprocedurze, która została utworzona za pomocą zwykłej instrukcji Dim, jest tworzona, kiedy podprocedura ta zaczyna działać, a zostaje zniszczona, gdy podprocedura kończy działanie. Jeśli znowu uruchomi się tę podprocedurę, zmienna będzie ponownie utworzona, a jej wartość — jeszcze raz ustawiona. Jeżeli jednak zmienna zostanie zadeklarowana przy użyciu słowa kluczowego Static, podprocedura przy każdym uruchomieniu będzie używać tego samego jej egzemplarza. Oznacza to, że wartość takiej zmiennej jest zachowywana pomiędzy wywołaniami tej podprocedury.
294
Część II
Wstęp do języka Visual Basic
Na przykład zmienna zadeklarowana w podprocedurze ma taki sam jak ona zasięg. Kod znajdujący się poza tą podprocedurą nie ma dostępu do tej zmiennej. Jeśli zmienna zostanie zadeklarowana na poziomie modułowym, poza wszelkimi procedurami, będzie miała zasięg modułowy. Jeżeli w jej deklaracji zostało użyte słowo kluczowe Private, zmienna będzie dostępna tylko dla kodu w tym module. Jeśli zmienna została zadeklarowana przy użyciu słowa kluczowego Public, będzie dostępna także poza swoim modułem. Widoczność to termin, który łączy pojęcia zakresu, dostępności i czasu życia. Określa, czy dany fragment kodu może użyć jakiejś zmiennej. Jeśli zmienna jest dostępna dla jakiegoś kodu, ten znajduje się w jej zasięgu, mieści się ona w czasie jego życia (została utworzona i jeszcze jej nie zniszczono), jest dla niego widoczna. W tym rozdziale objaśnię składnię deklaracji zmiennych w języku Visual Basic. Nauczysz się deklarować zmienne rozmaitych typów, o różnym zasięgu, dostępności i czasie życia. Ponadto dowiesz się o pewnych sprawach, które należy wziąć pod uwagę, gdy dobiera się typ deklaracji. Dodatkowo w rozdziale tym znajdą się opisy kilku nowych pojęć związanych ze zmiennymi, jak typy anonimowe i dopuszczające wartość pustą, które mogą skomplikować deklaracje zmiennych. Pojęcia zasięgu i typu danych dotyczą także stałych, parametrów i procedur własności. Z tego powodu zostały one także opisane w tym rozdziale. Na końcu znajduje się krótki opis konwencji nazewniczych. To, które konwencje przyjmiemy, nie jest tak ważne jak samo to, aby przyjąć jakieś w ogóle. Z tego rozdziału dowiesz się, gdzie można znaleźć konwencje stosowane przez Microsoft Consulting Services. Z zasad tych można utworzyć własne konwencje nadawania nazw.
Typy danych W poniższej tabeli znajduje się zestawienie podstawowych typów danych języka Visual Basic. Typ
0 do +/− 79 228 162 514 264 337 593 543 950 335 bez punktu dziesiętnego. 0 do +/− 7,9228162514264337593543950335 z 28 miejscami po przecinku.
Single
4 bajty
−3.4028235E + 38 do −1.401298E-45 (dla wartości ujemnych) 1,401298E-45 do 3,4028235E + 38 (dla wartości dodatnich).
Double
8 bajtów
−1,79769313486231570E+308 do −4,94065645841246544E-324 (dla wartości ujemnych) 4,94065645841246544E-324 do 1,79769313486231570E+308 (dla wartości dodatnich).
String
zmienny
Zależy od platformy. Łańcuch może w przybliżeniu zawierać od 0 do 2 miliardów znaków Unicode.
Date
8 bajtów
1 stycznia, 0001 0:0:00 do 31 grudnia 9999 11:59:59 w nocy.
Object
4 bajty
Wskazuje jakiś typ danych.
Structure
zmienny
Składowe struktur mają swoje własne zakresy.
295
W przestrzeni nazw System dostępne są też całkowitoliczbowe typy danych, jawnie określające swoją liczbę bitów. Na przykład typ Int32 reprezentuje 32-bitową liczbę całkowitą. Użycie go zamiast Integer pozwala podkreślić, że zmienna używa 32 bitów. Czasami dzięki temu kod staje się bardziej przejrzysty. Wyobraźmy sobie na przykład, że musimy wywołać funkcję interfejsu programistycznego aplikacji, przyjmującą jako parametr 32-bitową liczbę całkowitą. W szóstej wersji Visual Basica 32 bity zajmuje typ Long, natomiast w Visual Basicu .NET — Integer. Aby zaznaczyć, że używana jest 32-bitowa liczba całkowita, parametrowi temu można nadać typ Int32. Typy danych, które bezpośrednio określają swoje rozmiary, to Int16, Int32, Int64, UInt16, UInt32 oraz UInt64.
Typ Integer jest zazwyczaj najszybszy ze wszystkich typów całkowitoliczbowych. Program z reguły działa szybciej, jeśli zostaną w nim użyte typy Integer zamiast Char, Byte, Short, Long czy Decimal. Typu Integer należy używać zawsze wówczas, gdy nie jest wymagany większy rozmiar, jak Long czy Decimal, lub gdy nie trzeba oszczędzać pamięci za pomocą mniejszych typów Char czy Byte. W wielu przypadkach oszczędności, które przynosi zastosowanie typów Char lub Byte, nie są warte dodatkowego czasu działania i wysiłku, chyba że mamy do czynienia z bardzo dużymi tablicami danych. Należy pamiętać, że nie można bezpiecznie założyć, iż wymagania zmiennej, które dotyczą miejsca w pamięci, są takie same jak jej rozmiar. W niektórych przypadkach program może przenieść zmienną, aby zaczynała się w miejscu bardziej naturalnym dla danej platformy sprzętowej. Jeśli na przykład utworzymy strukturę składającą się z kilku zmiennych typu Short (2 bajty), program może pomiędzy nie powstawiać po dwa dodatkowe bajty, aby wszystkie zajmowały pola o szerokości czterech bajtów, ponieważ dzięki temu można lepiej wykorzystać możliwości sprzętu. Więcej informacji na temat struktur znajduje się w rozdziale 26. — „Klasy i struktury”.
296
Część II
Wstęp do języka Visual Basic
Sposób przydzielania pamięci dla struktur przez Visual Basic da się zmienić za pomocą atrybutów. W takim przypadku można dokładnie określić sposób ułożenia struktury. Jest to jednak bardzo zaawansowane zagadnienie, więc nie opisuję go w tej książce. Niektóre typy danych obarczone są jeszcze dodatkowym narzutem. Na przykład tablica przechowuje dodatkowe informacje o każdym ze swoich wymiarów.
Znaki oznaczające typy Niektóre typy danych w języku Visual Basic muszą być oznaczane za pomocą specjalnych znaków. Zebrano je w poniższej tabeli. Znak
Typ danych
%
Integer
&
Long
@
Decimal
!
Single
#
Double
$
String
Typ danych zmiennej można określić poprzez postawienie za jej nazwą w deklaracji odpowiedniego znaku. Używając później tej zmiennej, można ten znak opuszczać. Na przykład poniższy kod deklaruje zmienną num_desserts jako typ Long, a zmienną satisfaction_quotient — jako Double. Następnie zmiennym tym zostają przypisane wartości. Dim num_desserts & Dim satisfaction_quotient# num_desserts = 100 satisfaction_quotient# = 1.23
Jeśli opcja Option Explicit jest wyłączona, znak określający typ danych można wstawić przy pierwszym użyciu zmiennej. Jeżeli znak ten zostanie opuszczony, Visual Basic wybierze domyślny typ danych, biorąc pod uwagę wartość przypisaną do zmiennej. Jeśli przypisywana wartość jest liczbą całkowitą, mieszczącą się w zakresie typu Integer, Visual Basic zadeklaruje tę zmienną jako typu Integer. Jeżeli wartość ta będzie zbyt duża dla typu Integer, Visual Basic ustawi ją jako Long. Jeśli natomiast będzie zawierać punkt dziesiętny, zostanie zamieniona na typ Double. Poniższy kod demonstruje pierwsze użycie trzech zmiennych (opcja Option Explicit jest wyłączona). Pierwsza instrukcja ustawia zmienną an_integer na wartość 100. Wartość ta mieści się w zakresie typu Integer, a więc zmienna ta będzie typu Integer. Druga instrukcja ustawia zmienną a_long na wartość 10000000000, która nie mieści się w zakresie typu Integer, a więc zmienna ta będzie typu Long. Trzecia instrukcja ustawia zmienną a_double na wartość 1.0. Zawiera ona punkt dziesiętny, a więc Visual Basic zamienia ją na typ Double.
Jeśli wartość zmiennej zostanie ustawiona na True lub False, Visual Basic ustawi ją na typ logiczny. Daty w języku Visual Basic są oddzielane znakami #. Jeśli zmiennej zostanie przypisana wartość będąca datą, program ustawi typ danych tej zmiennej na Date. Poniższy kod przypisuje wartość logiczną i typu Date: a_boolean = True a_date = #12/31/2007#
Poza znakami typów danych w języku Visual Basic dostępne są znaki typów literałów, określające typ danych wartości literałowych. Są to wartości wpisywane bezpośrednio do kodu — w takich instrukcjach, jak przypisanie czy inicjacja. W poniższej tabeli zebrano znaki typów literałów języka Visual Basic. Znak
Typ danych
S
Short
US
UShort
I
Integer
UI
UInteger
L
Long
UL
ULong
D
Decimal
F
Single (F jak floating point — zmiennoprzecinkowy)
R
Double (R jak Real — liczba rzeczywista)
c
Char (zwróć uwagę, że litera c jest mała)
Znak typu literału określa typ danych wartości literalnej; może pośrednio wskazywać na typ danych przypisanej do niej zmiennej. Wyobraźmy sobie na przykład, że poniższy kod reprezentuje pierwsze użycie zmiennych i oraz ch (przy wyłączonej opcji Option Explicit). W normalnej sytuacji zmienna i zostałaby zamieniona na typ Integer, ponieważ wartość 123 mieści się w zakresie tego typu. Jednak ze względu na literę L po tej wartości wartość ta jest typu Long, a więc zmienna i również jest typu Long. Analogicznie — w normalnej sytuacji zmienna ch byłaby zamieniona na typ String, ponieważ wartość "X" wygląda jak łańcuch. Znak c, który znajduje się za tą wartością, informuje jednak Visual Basic, że jest to typ Char: i = 123L ch = "X"c
298
Część II
Wstęp do języka Visual Basic
Ponadto literalną wartość całkowitoliczbową można poprzedzić znakami &H, które wskazują, że jest to liczba szesnastkowa (o podstawie 16), lub znakami &O, oznaczającymi liczbę ósemkową (o podstawie 8). Na przykład wszystkie trzy poniższe instrukcje ustawiają zmienną flags na tę samą wartość. Pierwsza z nich używa wartości dziesiętnej 100, druga szesnastkowej &H64, a trzecia — ósemkowej &O144. flags = 100 ‘ Dziesiętny — 100. flags = & H64 ‘ Szesnastkowy — & H64 = 6 * 16 + 4 = 96 + 4 = 100. flags = & O144 ‘ Ósemkowy — & O144 = 1 * 8 * 8 + 4 * 8 + 4 = 64 + 32 + 4 = 100.
Na marginesie: warto wspomnieć, że funkcje Hex i Oct konwertują wartości liczbowe na łańcuchy reprezentujące je w zapisie szesnastkowym i ósemkowym. Jest to w pewnym sensie odwrotność działania kodów &H i &O — wymuszanie interpretacji literałów łańcuchowych jako wartości szesnastkowych lub ósemkowych. Kod na poniższym listingu wyświetla wartość zmiennej flags w zapisie dziesiętnym, szesnastkowym i ósemkowym: Debug.WriteLine(flags) ‘ Dziesiętny. Debug.WriteLine(Hex(flags)) ‘ Szesnastkowy. Debug.WriteLine(Oct(flags)) ‘ Ósemkowy.
Czasami konieczne jest użycie znaków typu literału, aby wartość pasowała do typu danych zmiennej. Na przykład pierwsze przypisanie w poniższym kodzie próbuje przypisać wartość "X" do zmiennej typu Char. Powoduje to błąd, ponieważ "X" jest wartością typu String. Mimo iż dla programisty jest oczywiste, że kod ten próbuje przypisać znak X do tej zmiennej, Visual Basic zauważa, iż te typy danych nie pasują do siebie. Druga instrukcja przypisania działa, ponieważ przypisuje do zmiennej wartość typu Char "X"c. Kolejna instrukcja jest błędna, gdyż próbuje przypisać wartość typu Double 12.34 do zmiennej typu Decimal. Ostatnie przypisanie działa, ponieważ wartość 12.34 D jest literałem typu Decimal. Dim ch As Char ch = "X" ch = "X"c
‘ Błąd, ponieważ „X” jest typu String. ‘ Dobrze, ponieważ „X”c jest typu Char.
Dim amount As Decimal amount = 12.34 ‘ Błąd, ponieważ 12.34 jest typu Double. amount = 12.34D ‘ Dobrze, ponieważ 12.34D jest typu Decimal.
Poniżej został zaprezentowany inny sposób wykonania tych przypisań. W tym przypadku użyto funkcji konwersji typów danych CChar i CDec do przekonwertowania wartości na odpowiednie typy. Więcej na temat funkcji konwersji typów danych można znaleźć w kolejnym podrozdziale, którego tytuł brzmi: „Konwersja typów danych”. ch = CChar("X") amount = CDec(12.34)
Używanie znaków typu danych, znaków typu literałów oraz przypisań domyślnych typów danych Visual Basica może prowadzić do powstania bardzo niejasnego kodu. Nie można wymagać od każdego, aby zauważył, że jakaś zmienna jest typu Single, ponieważ przy jej pierwszym użyciu postawiono za nią znak !. Aby kod był bardziej przejrzysty, należy stosować deklaracje zmiennych z jawnym określeniem typu danych.
Rozdział 15.
Typy danych, zmienne i stałe
299
Konwersja typów danych W typowych sytuacjach wartości określonego typu przypisuje się do zmiennych o takim samym typie. Na przykład wartość łańcuchową przypisuje się do zmiennej typu String, wartość całkowitoliczbową do Integer itd. To, czy wartość jednego typu można przypisać do zmiennej innego typu, zależy od tego, czy operacja ta wymaga konwersji zawężającej, czy rozszerzającej.
Konwersja zawężająca Konwersja zawężająca to operacja polegająca na zamianie jednego typu danych na inny — o węższym zakresie wartości niż ten pierwszy. Na przykład poniższy kod kopiuje wartość zmiennej typu Long do zmiennej typu Integer. Wartość typu Long może być zbyt duża dla typu Integer, a więc jest to konwersja zawężająca. Wartość zmiennej typu Long może, ale nie musi zmieścić się w zmiennej typu Integer. Dim an_integer As Integer Dim a_long As Long ... an_integer = a_long
Poniższy listing demonstruje mniej oczywisty przypadek. Wartość zmiennej typu String jest przypisywana do zmiennej typu Integer. Jeśli łańcuch ten jest liczbą (na przykład "10" lub "1.23"), przypisanie powiedzie się. Jeżeli jednak zawiera on wartość niebędącą liczbą (jak "Witamy"), przypisanie zakończy się niepowodzeniem i spowoduje błąd. Dim an_integer As Integer Dim a_string As String ... an_integer = a_string
Innym nieoczywistym rodzajem konwersji zawężającej jest konwersja klasy na klasę pochodną. Załóżmy, że klasa Employee dziedziczy po klasie Person. W takim przypadku ustawienie zmiennej klasy Employee na obiekt klasy Person jest konwersją zawężającą, ponieważ bez dodatkowych informacji nie da się zgadnąć, czy ten obiekt klasy Person jest prawidłowym obiektem klasy Employee. Wszystkie obiekty klasy Employee są klasy Person, ale nie wszystkie obiekty klasy Person są klasy Employee. Dim an_employee As Employee Dim a_person As Person ... an_employee = a_person
Jeśli opcja Option Strict jest włączona, Visual Basic nie zezwala na wykonywanie niejawnych konwersji zawężających, a gdy jest wyłączona, Visual Basic próbuje wykonać konwersję zawężającą i zgłasza błąd, jeżeli mu się to nie uda (na przykład przy próbie skopiowania wartości typu Integer 900 do zmiennej typu Byte).
300
Część II
Wstęp do języka Visual Basic
Aby wykonać konwersję zawężającą przy włączonej opcji Option Strict, trzeba posłużyć się jedną z funkcji konwersji typów danych. Visual Basic spróbuje wtedy wykonać konwersję i zgłosi błąd, jeśli operacja nie powiedzie się. Funkcja CByte konwertuje wartości liczbowe na wartości typu Byte. W związku z tym można użyć poniższego kodu do skopiowania wartości typu Integer do zmiennej typu Byte: Dim an_integer As Integer Dim a_byte As Byte ... a_byte = CByte(an_integer)
Jeśli zmienna typu Integer zawiera wartość mniejszą od zera lub większą od 255, wartość ta nie będzie pasować do zmiennej typu Byte, więc funkcja CByte zgłosi błąd. Poniższa tabela zawiera zestawienie funkcji konwersji typów danych języka Visual Basic. Funkcja
Konwertuje na
CBool
Boolean
CByte
Byte
CChar
Char
CDate
Date
CDbl
Double
CDec
Decimal
CInt
Integer
CLng
Long
CObj
Object
CSByte
SByte
CShort
Short
CSng
Single
CStr
String
CUInt
UInteger
CULng
ULong
CUShort
UShort
Funkcje CInt i CLng zaokrąglają wartości zawierające część ułamkową do najbliższej liczby całkowitej. Jeśli ułamek wynosi dokładnie .5, funkcje te zaokrąglają do najbliższej całkowitej liczby parzystej. Na przykład wartość 0.5 zostanie zaokrąglona do 0, wartość 0.6 do 1, a 1.5 do 2. Natomiast funkcje Fix i Int obcinają część ułamkową liczby. Fix odcina część ułamkową, zmierzając w stronę zera, a więc Fix(-0.9) wynosi 0 i Fix(0.9) też wynosi tyle samo. Funkcja Int odcina część ułamkową i zmierza w stronę ujemnej nieskończoności, a więc Int(-0.9) wynosi -1, a Int(0.9) — 0.
Rozdział 15.
Typy danych, zmienne i stałe
301
Funkcje Fix i Int różnią się od CInt i CLng także tym, że zwracają taki sam typ danych, jaki został im przekazany. CInt zawsze zwraca wartość typu Integer, bez względu na to, jakiego typu wartość została do niej przekazana. Jeśli do funkcji Fix zostanie przekazana wartość typu Long, funkcja ta zwróci wartość typu Long. Funkcja CType przyjmuje jako parametry wartość i typ danych, po czym konwertuje tę wartość na podany typ, jeśli jest to możliwe. Na przykład poniższy kod wykonuje za pomocą funkcji CType konwersję zawężającą typu Long na typ Integer. Dzięki temu, że wartość zmiennej a_long mieści się w typie Integer, konwersja ta kończy się powodzeniem. Dim an_integer As Integer Dim a_long As Long = 100 an_integer = Ctype(a_long, Integer)
Instrukcja DirectCast działa podobnie do funkcji CType — z tym wyjątkiem, że można jej używać tylko wówczas, gdy konwertowana zmienna implementuje ten nowy typ lub po nim dziedziczy. Wyobraźmy sobie na przykład, że zmienna dessert_obj jest ogólnego typu Object i wiadomo, iż wskazuje na obiekt typu Dessert. Poniższy kod konwertuje ten ogólny typ Object na bardziej konkretny typ Dessert: Dim dessert_obj As Object = New Dessert("Lody") Dim my_dessert As Dessert my_dessert = DirectCast(dessert_obj, Dessert)
Instrukcja DirectCast zgłasza błąd, jeśli zostaje użyta do zmiany typu danych obiektu. Na przykład poniższy kod nie działa, mimo że konwersja typu Integer na typ Long jest konwersją zawężającą: Dim an_integer As Integer = 100 Dim a_long As Long a_long = DirectCast(an_integer, Long)
Instrukcja TryCast działa tak samo jak instrukcja DirectCast — tyle że zwraca wartość Nothing w przypadku wystąpienia błędu, zamiast go zgłaszać.
Metody analizy typów danych Wszystkie podstawowe typy danych, z wyjątkiem typu String, udostępniają metodę Parse, która próbuje przekonwertować podany jej łańcuch na zmienną tego typu. Na przykład poniższe dwie instrukcje próbują przekonwertować wartość łańcuchową zmiennej txt_entered na typ Integer: Dim txt_entered As String = "112358" Dim num_entered As Integer ... num_entered = CInt(txt_entered) ‘ Użycie funkcji CInt. num_entered = Integer.Parse(txt_entered) ‘ Użycie metody Integer.Parse.
302
Część II
Wstęp do języka Visual Basic
Niektóre z tych metod pobierają dodatkowe parametry, które pozwalają kontrolować operację konwersji. Na przykład metody liczbowe mogą przyjmować parametr określający format zapisu liczb, w którym ma zostać wyrażona zwrócona przez nie liczba. Klasowe metody analizujące są bliższe obiektowemu paradygmatowi programowania niż funkcje konwertujące. Są one także nieco szybsze. Działają jednak tylko na łańcuchach, przez co do konwersji na przykład typu Long na Integer trzeba użyć funkcji CInt zamiast metody Integer.Parse.
Konwersja rozszerzająca Odwrotnie, niż ma to miejsce w przypadku konwersji zawężającej, w konwersji rozszerzającej nowy typ danych zawsze jest w stanie pomieścić wartość starego typu. Na przykład Long może pomieścić każdą wartość Integer, dlatego kopiowanie wartości typu Integer do zmiennej typu Long jest konwersją rozszerzającą. Visual Basic zezwala na wykonywanie konwersji rozszerzających. Należy jednak pamiętać, że tego typu konwersja wiąże się czasem z pewną utratą danych. Na przykład zmienna typu Decimal może przechowywać więcej znaczących cyfr niż zmienna typu Single. W tej ostatniej można zapisać każdą wartość typu Decimal, ale nie przy zachowaniu tej samej precyzji. Jeśli wartość typu Decimal zostanie przypisana do zmiennej typu Single, może nastąpić utrata precyzji.
Deklarowanie zmiennych Pełna składnia deklaracji zmiennej jest następująca: [ lista_atrybutów ] [ dostępność ] [Shared] [Shadows] [ReadOnly] _ Dim [WithEvents] nazwa [( lista_wartości_brzegowych )] [As [New] [= wyrażenie_inicjujące ]
typ ] _
Wszystkie deklaracje mają wspólną tylko jedną cechę — zawierają nazwę zmiennej. Poza tym różne deklaracje mogą być do siebie zupełnie niepodobne. Wszystkie pozostałe elementy deklaracji zmiennej można pominąć. Na przykład dwie poniższe deklaracje nie zawierają ani jednego wspólnego słowa kluczowego: Dim i = 1 ‘ Deklaracja prywatnej zmiennej typu Integer o nazwie i. (Option Explicit Off). Public j As Integer ‘ Deklaracja publicznej zmiennej typu Integer o nazwie j.
Te wszystkie możliwości deklarowania zmiennych na różne sposoby sprawiają, że składnia ta wydaje się dosyć przytłaczająca. Jednak większość deklaracji jest niezwykle prosta. Dwie zaprezentowane powyżej są bardzo łatwe do zrozumienia. W kolejnych podrozdziałach szczegółowo opiszę poszczególne elementy składni deklaracji zmiennej.
Rozdział 15.
Typy danych, zmienne i stałe
303
Lista atrybutów Opcjonalna lista atrybutów to zbiór pooddzielanych przecinkami atrybutów zmiennej. Udoskonalają one definicję zmiennej poprzez dostarczenie dodatkowych informacji kompilatorowi i systemowi wykonawczemu. Atrybuty są wyspecjalizowanym narzędziem. Pozwalają rozwiązywać problemy, które występują przy wykonywaniu bardzo specyficznych zadań programistycznych. Gdy na przykład pisze się kod serializujący i deserializujący dane, można wykorzystać atrybuty serializacji, aby uzyskać większą kontrolę nad tym procesem. Poniższy kod definiuje klasę OrderItem. Deklaruje ona trzy zmienne publiczne — ItemName, Quantity oraz Price. Za pomocą atrybutów zostało zaznaczone, że zmienna ItemName ma być zapisywana jako tekst, zmienna Price ma być przechowywana jako atrybut o nazwie Const, a zmienna Quantity — jako atrybut o domyślnej nazwie Quantity. Public Class OrderItem _ Public ItemName As String Public Price As Decimal _ Public Quantity As Integer End Class
_
Poniższy kod demonstruje serializację XML obiektu OrderItem: Cookie
Ponieważ atrybuty są bardzo wyspecjalizowanym narzędziem, nie będę ich tutaj opisywać. Więcej na ten temat można znaleźć w odpowiednich sekcjach pomocy internetowej. Aby uzyskać informacje o atrybutach serializacji XML, poszukaj frazy System.Xml.Serialization Namespace lub wejdź na stronę http://msdn.microsoft.com/en-us/library/system.xml.serialization.aspx. Dodatkowe informacje na temat atrybutów zajdziesz w sekcji Attributes podręcznika Visual Basic Language Reference lub na stronie http://msdn.microsoft.com/library/en-us/vbls7/html/ vblrfVBSpec4_10.asp. Aby znaleźć listę atrybutów modyfikujących deklaracje zmiennych, szukaj w pomocy internetowej frazy Attribute Hierarchy lub na stronie http://msdn.microsoft.com/ library/en-us/cpref/html/frlrfsystemattributeclasshierarchy.asp.
Dostępność Klauzula dostępność w deklaracji zmiennej może mieć jedną z poniższych wartości:
Public — tego słowa kluczowego można używać tylko w deklaracjach zmiennych
na poziomie modułu, klasy, struktury, przestrzeni nazw lub pliku. Nie da się go stosować w podprocedurach. Słowo kluczowe Public oznacza, że zmienna powinna być dostępna wszędzie wewnątrz i na zewnątrz jej modułu. Daje ono najbardziej swobodny dostęp do zmiennej.
304
Część II
Wstęp do języka Visual Basic
Protected — tego słowa kluczowego można używać tylko na poziomie klasy, nie wewnątrz modułu ani procedury znajdującej się w klasie. Oznacza ono, że zmienna ma być dostępna tylko w jednej klasie lub klasach potomnych tej klasy. Zmienna taka jest dostępna w tej samej lub potomnej klasie, nawet jeśli egzemplarz tej klasy jest inny niż egzemplarz zawierający tę zmienną. Na przykład jeden obiekt klasy Employee ma dostęp do zmiennej Protected, zadeklarowanej w innym obiekcie klasy Employee.
Friend — tego słowa kluczowego można używać tylko dla zmiennych deklarowanych
na poziomie modułu, klasy, przestrzeni nazw lub pliku, nie zaś na poziomie podprocedury. Słowo kluczowe Friend oznacza, że zmienna powinna być dostępna wszędzie wewnątrz i na zewnątrz jej modułu w obrębie jednego projektu. Różnica pomiędzy tym słowem kluczowym a słowem Public polega na tym, że to drugie zezwala na dostęp do zmiennej także spoza projektu. Problem ten jest związany głównie z bibliotekami kodu i kontrolek. Wyobraźmy sobie na przykład, że piszemy bibliotekę kodu, która zawiera dużą liczbę procedur, a następnie tworzymy program korzystający z niej. Jeśli w bibliotece tej znajdzie się zmienna zadeklarowana przy użyciu słowa kluczowego Public, zawarty w niej kod oraz program główny będą mogły używać tej zmiennej. Jeśli natomiast zmienna zostanie zadeklarowana za pomocą słowa kluczowego Friend, dostęp do niej będzie miał tylko kod znajdujący się w tej bibliotece. W kodzie programu będzie ona niedostępna.
Protected Friend — tego słowa kluczowego można używać tylko na poziomie
klasy, nie wewnątrz modułu ani procedury znajdującej się w klasie. Jest to połączenie słów kluczowych Protected i Friend. Zmienna zadeklarowana za jego pomocą jest dostępna tylko w swojej klasie lub klasach potomnych tej klasy w obrębie jednego projektu.
Private — tego słowa kluczowego można używać wyłącznie dla zmiennych
deklarowanych na poziomie modułu, klasy lub struktury, nie wewnątrz podprocedur. Zadeklarowana w taki sposób zmienna jest dostępna tylko dla kodu w tym samym module, klasie lub strukturze. Jeśli zmienna znajduje się w klasie lub strukturze, jest dostępna także w innych egzemplarzach tej klasy lub struktury. Na przykład jeden obiekt klasy Customer ma dostęp do zmiennych Private zadeklarowanych w innym obiekcie klasy Customer.
Static — to słowo kluczowe można stosować tylko w deklaracjach zmiennych wewnątrz procedur lub bloków znajdujących się wewnątrz procedur (na przykład pętli For lub bloku Try Catch). Nie można go używać w połączeniu ze słowami kluczowymi Shared i Shadows. Zmienna zadeklarowana jako Static zachowuje swoją wartość pomiędzy różnymi okresami swojego życia.
Jeśli na przykład jakaś podprocedura ustawi zmienną Static na 27, przy kolejnym jej uruchamianiu zmienna ta będzie miała wartość początkową 27. Wartość ta jest przechowywana w pamięci, przez co nie zostaje zapisana po zamknięciu programu. Aby zachować wartości zmiennych pomiędzy różnymi uruchomieniami aplikacji, należy użyć bazy danych, rejestru systemowego lub jakiegoś innego sposobu na trwałe przechowywanie danych.
Rozdział 15.
Typy danych, zmienne i stałe
305
Słowo kluczowe Shared Słowa kluczowego Shared można używać na poziomie modułu, klasy, struktury, przestrzeni nazw oraz pliku. Nie da się go stosować w podprocedurach. Oznacza ono, że wszystkie egzemplarze klasy lub struktury, które zawierają tę zmienną, współdzielą ją. Wyobraźmy sobie na przykład, że w klasie Order zostaje zadeklarowana zmienna Shared o nazwie NumOrders, która reprezentuje liczbę wszystkich zamówień w aplikacji. W takim przypadku wszystkie egzemplarze klasy Order współdzielą tę samą zmienną NumOrders. Jeśli jeden egzemplarz tej klasy ustawi zmienną NumOrders na 10, będzie ona miała taką wartość we wszystkich pozostałych egzemplarzach owej klasy. Dostęp do współdzielonej (Shared) zmiennej można uzyskać poprzez konkretny egzemplarz klasy lub przy użyciu samej tej klasy. Na przykład poniższy kod ustawia wartość zmiennej NumOrders na 100 za pomocą zmiennej NumOrders obiektu order1. Następnie ustawia wartość tej zmiennej przy użyciu samej klasy i za jej pomocą również wartość tę wyświetla. order1.NumOrders = 100 MessageBox.Show(order1.NumOrders) MessageBox.Show(order2.NumOrders) Order.NumOrders = 101 MessageBox.Show(Order.NumOrders)
‘ Użycie obiektu order1 do ustawienia NumOrders = 100. ‘ Użycie obiektu order1 do wyświetlenia 100. ‘ Użycie innego obiektu Order do wyświetlenia 100. ‘ Użycie klasy do ustawienia NumOrders = 101. ‘ Użycie klasy do wyświetlenia 101.
Słowa kluczowego Shared nie można używać w parze ze słowem kluczowym Static. Ma to sens, ponieważ zmienna współdzielona jest w pewnym sensie statyczna (Static) w zawierającej ją klasie lub strukturze. Jeśli jeden egzemplarz klasy zmodyfikuje wartość tej zmiennej, ta nowa wartość będzie dostępna także we wszystkich pozostałych egzemplarzach tej klasy. Nawet jeżeli wszystkie one ulegną zniszczeniu lub nigdy nie zostaną utworzone, klasa i tak bezpiecznie przechowa tę wartość. Jest to trwałość podobna do tej, którą wymusza słowo kluczowe Static.
Słowo kluczowe Shadows Shadows można używać dla zmiennych deklarowanych na poziomie modułu, klasy, struktury, przestrzeni nazw oraz pliku, ale nie w podprocedurach. Słowo to oznacza, że zmienna ukrywa zmienną o takiej samej nazwie z klasy bazowej. Typowym przykładem jest sytuacja, w której podklasa udostępnia zmienną o takiej samej nazwie, jaką nosi jedna ze zmiennych zadeklarowanych w jednej z jej klas przodków.
Kod przedstawiony na poniższym listingu pochodzi z programu ShadowTest, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Demonstruje on użycie słowa kluczowego Shadows: Public Class Person Public LastName As String Public EmployeeId As String End Class
306
Część II
Wstęp do języka Visual Basic
Public Class Employee Inherits Person Public Shadows EmployeeId As Long End Class Public Class Manager Inherits Employee Public Shadows LastName As String End Class Private Sub TestShadows() Dim txt As String = "" Dim mgr As New Manager mgr.LastName = "Nazwisko kierownika" mgr.EmployeeId = 1 Dim emp As Employee = CType(mgr, Employee) emp.LastName = "Nazwisko pracownika" emp.EmployeeId = 2 Dim per As Person = CType(mgr, Person) per.LastName = "Nazwisko osoby" per.EmployeeId = "A" txt txt txt
& = "Manager: " & = "Employee: " & = "Person: "
& & &
mgr.EmployeeId emp.EmployeeId per.EmployeeId
& ": " & ": " & ": "
& & &
mgr.LastName emp.LastName per.LastName
& & &
vbCrLf vbCrLf vbCrLf
txtResults.Text = txt txtResults.Select(0, 0) End Sub
Ten kod definiuje klasę Person, która zawiera dwie zmienne typu String o nazwach LastName i EmployeeId. Klasa Employee dziedziczy po klasie Person i deklaruje własną wersję zmiennej EmployeeId. Dzięki użyciu słowa kluczowego Shadows zmienna ta przesłania zmienną o tej samej nazwie zadeklarowaną w klasie nadrzędnej. Warto zauważyć, że słowo kluczowe Shadows działa nawet wówczas, gdy oryginalna zmienna i jej nowa wersja mają różne typy. W tym przypadku zmienna EmployeeId ma typ Long w klasie Employee i String w klasie Person. Klasa Manager dziedziczy po klasie Employee i definiuje własną wersję zmiennej LastName. Obiekty tej klasy używają tej wersji, natomiast obiekty klas Employee i Person korzystają ze zdefiniowanej w klasie Person. Po definicjach klas znajduje się kod demonstrujący ich działanie. Najpierw zostaje utworzony obiekt klasy Manager. Zmienna LastName tego obiektu zostaje ustawiona na łańcuch Nazwisko kierownika, a EmployeeId — na wartość 1. Wartość zmiennej LastName jest przechowywana w wersji tej zmiennej zadeklarowanej w klasie Manager przy użyciu słowa kluczowego Shadows, a wartość zmiennej EmployeeId — w zmiennej EmployeeId zadeklarowanej za pomocą słowa kluczowego Shadows w klasie Employee. Następnie program tworzy zmienną typu Employee, która wskazuje na obiekt klasy Manager. Ma to sens, ponieważ klasa Manager dziedziczy po klasie Employee. Manager jest typem klasy Employee, a więc zmienna typu Employee może wskazywać na obiekty klasy Manager.
Rozdział 15.
Typy danych, zmienne i stałe
307
Program ustawia zmienną LastName tego obiektu klasy Employee na łańcuch Nazwisko pracownika, a jego zmienną EmployeeId na 2. Wartość LastName jest przechowywana w jej wersji zadeklarowanej w klasie Person, a wartość EmployeeId — w zmiennej EmployeeId, która została zadeklarowana przy użyciu słowa kluczowego Shadows w klasie Employee. Ponieważ klasa Manager nie przesłania tej deklaracji swoją własną deklaracją zmiennej EmployeeId ze słowem kluczowym Shadows, wartość ta przesłania wartość przechowywaną w obiekcie klasy Manager. Następnie program tworzy zmienną klasy Person i ustawia ją na ten sam obiekt klasy Manager. To również jest rozsądne posunięcie, ponieważ Manager jest typem klasy Person, dzięki czemu zmienna klasy Person może wskazywać na obiekty klasy Manager. Następnie program ustawia zmienną LastName tego obiektu klasy Person na Nazwisko osoby, a jego zmienną EmployeeId na łańcuch A. Klasa Person nie dziedziczy po żadnej klasie, a więc wymienione wartości zostają zapisane w zdefiniowanych w niej wersjach zmiennych. Ponieważ klasa Employee nie przesłania deklaracji zmiennej LastName w klasie Person, wartość ta przesłania tę przechowywaną przez obiekt klasy Employee. Na zakończenie program drukuje wartości zmiennych EmployeeId i LastName każdego z obiektów. Poniżej został przedstawiony wynik działania tego programu. Zauważ, że wartość zmiennej EmployeeId w obiekcie Employee (2) przesłoniła wartość zapisaną przez obiekt klasy Manager (1), a także że wartość zmiennej LastName klasy Person (Nazwisko osoby) przesłoniła wartość zapisaną przez obiekt klasy Employee (Nazwisko pracownika). Manager: 2: Nazwisko kierownika Employee: 2: Nazwisko osoby Person: A: Nazwisko osoby
Dostęp do przesłoniętych wersji zmiennych zazwyczaj nie jest potrzebny. Jeśli deklarujemy wersję zmiennej LastName w klasie Employee, przesłaniając jej deklarację w klasie Person, najprawdopodobniej mamy ku temu jakiś dobry powód (w przeciwieństwie do zaprezentowanego wyżej przykładu, który miał tylko zademonstrować zagadnienie) — i nie musimy mieć bezpośredniego dostępu do tej oryginalnej wartości. Jeśli jednak zajdzie potrzeba uzyskania dostępu do przesłoniętej wersji zmiennej, można użyć zmiennych z klasy nadrzędnej. Na przykład kod na poprzednim listingu tworzy obiekty klas Employee i Person, które wskazują na obiekt klasy Manager. W ten sposób uzyskuje dostęp do przesłoniętych zmiennych tego obiektu. W klasie można w podobny sposób rzutować obiekt Me na klasę nadrzędną. Na przykład poniższy kod z klasy Manager ustawia zmienną klasy Person na ten sam obiekt i ustawia jego zmienną EmployeeId: Public Sub SetPersonEmployeeId(ByVal employee_id As String) Dim per As Person = CType(Me, Person) per.EmployeeId = employee_id End Sub
Aby uzyskać dostęp do zmiennych zadeklarowanych w klasie nadrzędnej, można też użyć słowa kluczowego MyBase. Poniższy kod z klasy Manager ustawia wartość zmiennej LastName, która została zadeklarowana w klasie nadrzędnej Employee:
308
Część II
Wstęp do języka Visual Basic
Public Sub SetManagerLastName(ByVal last_name As String) MyBase.LastName = last_name End Sub
Słowo kluczowe ReadOnly Słowa kluczowego ReadOnly można używać dla zmiennych zadeklarowanych na poziomie modułu, klasy, struktury, przestrzeni nazw oraz pliku, nie zaś wewnątrz podprocedur. Oznacza ono, że program może odczytać, ale nie zmodyfikować wartość zmiennej. Zmienną taką można zainicjować na dwa sposoby. Pierwszy z nich polega na wstawieniu instrukcji inicjującej do deklaracji zmiennej, jak w poniższym przykładzie: Public Class EmployeeCollection Public ReadOnly MaxEmployees As Integer = 100 ... End Class
Drugi sposób to inicjacja zmiennej w konstruktorach obiektu. Poniższy fragment programu deklaruje zmienną tylko do odczytu (ReadOnly) o nazwie MaxEmployees. Pusty konstruktor ustawia ją na wartość 100. Drugi konstruktor pobiera parametr w postaci liczby całkowitej i ustawia zmienną MaxEmployees na jego wartość. Public Class EmployeeCollection Public ReadOnly MaxEmployees As Integer Public Sub New() MaxEmployees = 100 End Sub Public Sub New(ByVal max_employees As Integer) MaxEmployees = max_employees End Sub ... End Class
Po zainicjowaniu obiektu nie można już zmienić wartości zmiennej do odczytu. Ograniczenie to dotyczy zarówno kodu wewnątrz modułu, w którym została zadeklarowana zmienna, jak i tego w innych modułach. Aby umożliwić modyfikowanie zmiennej przez kod znajdujący się w tym samym co ona module i jednocześnie uniemożliwić modyfikowanie jej z innych miejsc, należy użyć procedury własności. Więcej informacji na ten temat znajduje się w dalszej części rozdziału, w podrozdziale „Procedury własności”.
Słowo kluczowe Dim Słowo kluczowe Dim oficjalnie informuje Visual Basic, że chce się utworzyć zmienną. Można je opuścić, jeśli zostanie użyte jedno z następujących słów kluczowych — Public, Protected, Friend, Protected Friend, Private, Static lub ReadOnly. Jeśli w deklaracji zmiennej pojawi się któreś z nich, edytor kodu Visual Basica automatycznie usunie słowo kluczowe Dim, o ile zostało wcześniej wpisane.
Rozdział 15.
Typy danych, zmienne i stałe
309
Domyślnie wszystkie zmienne zadeklarowane za pomocą słowa kluczowego Dim są prywatne (Private). Poniższe dwie instrukcje są równoważne: Dim num_people As Integer Private num_people As Integer
Wielu programistów używa słowa kluczowego Private w swoich deklaracjach, aby rozwiać wszelkie wątpliwości. Dzięki użyciu go nie trzeba pamiętać, że słowo kluczowe Dim oznacza to samo. Jednym z miejsc, w którym często spotyka się słowo kluczowe Dim, są deklaracje zmiennych w podprocedurach. Nie można tam używać słowa kluczowego Private (ani Public, Protected, Friend, Protected Friend oraz ReadOnly), a więc pozostają tylko Static i Dim.
Słowo kluczowe WithEvents Słowo kluczowe WithEvents informuje Visual Basic, iż zmienna jest specjalnego typu obiektowego, a także że może zgłaszać zdarzenia, które będzie trzeba przechwycić. Na przykład poniżej znajduje się deklaracja zmiennej Face jako obiektu klasy PictureBox, która może zgłaszać zdarzenia: Private WithEvents Face As PictureBox
Dla każdej zmiennej zadeklarowanej przy użyciu słowa kluczowego WithEvents Visual Basic tworzy wpis na liście rozwijanej po lewej stronie okna edytora kodu (rysunek 15.1).
Rysunek 15.1. Visual Basic tworzy wpisy na liście rozwijanej dla zmiennych zadeklarowanych przy użyciu słowa kluczowego WithEvents
310
Część II
Wstęp do języka Visual Basic
Jeśli wybrany zostanie jakiś obiekt z listy po lewej stronie, lista po prawej zapełni się zdarzeniami tego obiektu, które można by chcieć przechwycić (rysunek 15.2).
Rysunek 15.2. Jeśli z listy po lewej stronie zostanie wybrany jakiś obiekt, lista po prawej zapełni się zdarzeniami tego obiektu, które można przechwycić
Gdy programista wybierze zdarzenie, Visual Basic utworzy odpowiednią pustą procedurę jego obsługi. Generowanie procedur obsługi zdarzeń w ten sposób jest znacznie bezpieczniejsze i łatwiejsze niż wpisywanie ich i tworzenie wszystkich parametrów własnoręcznie. Deklarowanie zmiennych przy użyciu słowa kluczowego WithEvenets jest niezwykle przydatną techniką. Można zmusić zmienną, by wskazywała na jakiś obiekt w celu przechwycenia jego zdarzenia. Aby móc później przetwarzać zdarzenia jakiegoś innego obiektu za pomocą tych samych procedur obsługi zdarzeń, wystarczy tylko zmienić obiekt, na który wskazuje ta zmienna. Jeśli nie chce się odbierać już więcej zdarzeń, można tę zmienną ustawić na Nothing. Niestety przy użyciu słowa kluczowego WithEvents nie można zadeklarować tablicy. Oznacza to, że nie da się za pomocą prostej deklaracji sprawić, że określony zestaw procedur obsługi zdarzeń będzie przetwarzał zdarzenia pochodzące od więcej niż jednego obiektu. Efekt ten można jednak uzyskać za pomocą metody AddHandler, która jawnie ustawia procedury obsługi zdarzeń dla wielu obiektów. Więcej informacji na ten temat znajduje się w podrozdziale „Przechwytywanie zdarzeń” rozdziału 26. — „Klasy i struktury”.
Rozdział 15.
Typy danych, zmienne i stałe
311
Nazywanie zmiennych Nazwę zmiennej określa klauzula nazwa w deklaracji. Musi być ona prawidłowym identyfikatorem Visual Basica. Zasady rządzące identyfikatorami są nieco mało przejrzyste, ale ogólnie należy pamiętać, że powinny się one zaczynać od litery lub znaku podkreślenia. Dalej może się znajdować dowolna liczba liter, cyfr i znaków podkreślenia. Jeśli identyfikator zaczyna się od znaku podkreślenia (zdarza się to rzadko), musi zawierać przynajmniej jeszcze jeden inny dozwolony znak (literę, cyfrę lub znak podkreślenia), aby nie mylił się ze znakiem kontynuacji wiersza. Oto kilka przykładów: num_employees
Poprawny.
NumEmployees
Poprawny.
_manager
Poprawny (ale rzadko spotykany).
_
Niepoprawny (składa się tylko z jednego znaku podkreślenia).
__
Poprawny (ale może być bardzo mylący).
1st_employee
Niepoprawny (nie zaczyna się od litery ani znaku podkreślenia).
Zwykły identyfikator nie może być identyczny ze słowami kluczowymi Visual Basica. Można jednak zastosować sztuczkę i umieścić go w kwadratowych nawiasach. Gdy stosuje się takie nawiasy, można nadać identyfikatorowi taką samą nazwę, jaką posiadają słowa kluczowe języka Visual Basic. Na przykład w poniższym fragmencie programu podprocedura ParseString pobiera jeden parametr o nazwie String, który jest typu String: Public Sub ParseString(ByVal [String] As String) Dim values() As String = Split([String]) ... End Sub
Gdy wpisze się początek wywołania tej podprocedury w edytorze kodu, funkcja IntelliSense wyświetli następujący opis tej procedury: ParseString(String as String). Zasady te pozwalają na tworzenie dziwnych i potencjalnie wprowadzających w błąd identyfikatorów. Można na przykład uzyskać zmienne o nazwach String, Boolean, ElseIf czy Case. W zależności od ustawień systemowych znaki podkreślenia mogą być trudne do odczytania na ekranie lub po wydrukowaniu. Przez to może się wydawać, że zmienne o nazwie __ (dwa znaki podkreślenia) znikają, a nazwy _Nazwa i Nazwa bywają trudne do odróżnienia. Mimo iż nazwy te są dozwolone, mogą być niezwykle mylące i przyczyniać się do powstawania błędów, których znalezienie zajmuje wiele godzin. Aby uniknąć problemów, staraj się oszczędnie gospodarować zmiennymi, których nazwy zaczynają się od znaków podkreślenia lub są takie same jak słowa kluczowe.
Lista wartości brzegowych Klauzula lista_wartości_brzegowych w deklaracji zmiennej wyznacza końce tablicy. Powinna to być lista nieujemnych liczb całkowitych oddzielanych przecinkami, określających górne granice wymiarów tablicy. Dolna granica zawsze wynosi zero. Można ją opcjonalnie określić, ale musi to być wartość zerowa.
312
Część II
Wstęp do języka Visual Basic
Henry Ford kiedyś powiedział „Każdy klient może zażądać samochodu w dowolnym kolorze, jeśli wybierze kolor czarny”. Podobna reguła ma zastosowanie tutaj — można określić dowolną dolną granicę dla tablicy, jeżeli wynosi ona zero. Kod na poniższym listingu definiuje dwie tablice na dwa różne sposoby. Pierwsza instrukcja deklaruje tablicę jednowymiarową 101 obiektów klasy Customer z indeksami od 0 do 100. Druga definiuje dwuwymiarową tablicę obiektów klasy Order. Pierwszy wymiar ma wartości brzegowe od 0 do 100, a drugi od 0 do 10. Elementy tej tablicy zawierają się w przedziale od orders(0, 0) do orders (100, 10), co w sumie daje 101×11=1111 elementów. Dwie ostatnie instrukcje definiują podobne tablice, ale jawnie określają dolne wartości brzegowe. Private Private Private Private
customers(100) As Customer orders(100, 10) As Order customers2(0 To 100) As Customer orders2(0 To 100, 0 To 10) As Order
Dla niektórych programistów określenie dolnej wartości brzegowej może być korzystne, ponieważ nie będą musieli pamiętać, że wynosi ona zawsze 0. Dotyczy to w szczególności programistów Visual Basica 6 lub wcześniejszych wersji tej technologii, w których tablice mogły mieć inne niż 0 dolne wartości brzegowe. Należy zauważyć, że tego typu deklaracje z użyciem obiektowych typów danych nie tworzą obiektów. Na przykład pierwsza deklaracja w powyższym kodzie definiuje 101 elementów tablicy, które wskazują na Nothing. Początkowo nie wskazują one na egzemplarze klasy Customer. Po takiej deklaracji program musi utworzyć każdy obiekt osobno, jak poniżej: Private customers(100) As Customer For i As Integer = 0 To 100 customers(i) = New Customer Next i
Można też użyć instrukcji inicjującej do zadeklarowania i zainicjowania obiektów za jednym razem. Więcej informacji na temat inicjowania tablic w deklaracjach znajduje się w dalszej części rozdziału, w podrozdziale „Wyrażenia inicjujące”. Jeśli zostaną wpisane nawiasy bez żadnych wartości brzegowych, Visual Basic zdefiniuje tablicę, ale bez jakichkolwiek ograniczeń. Wartości brzegowe będzie można ustawić później za pomocą instrukcji ReDim. Instrukcja ta pozwala również zmienić już ustawione wartości brzegowe. Kod na poniższym listingu deklaruje dwie tablice o nazwach a1 i a2. Początkowo dla a1 zostaje przydzielonych 11 elementów, a dla tablicy a2 — nic. Następnie instrukcja ReDim przydziela 21 elementów dla obu tych tablic. Dim a1(10) As Integer Dim a2() As Integer ReDim a1(20) ReDim a2(0 To 20)
Rozdział 15.
Typy danych, zmienne i stałe
313
Instrukcja ReDim nie może zmienić liczby wartości brzegowych w tablicy. Aby zadeklarować, ale nie zainicjować tablicę wielowymiarową, należy użyć przecinków, tak jakby definiowało się wartości brzegowe. Poniżej znajduje się deklaracja trójwymiarowej tablicy i jej inicjacja w osobnych etapach: Dim a1(,,) As Integer ReDim a1(10, 20, 30)
Słowo kluczowe New Słowo kluczowe New w deklaracji zmiennej obiektowej informuje Visual Basic, że tworzony jest nowy egzemplarz tego obiektu. Bez tego słowa tworzona jest zmienna obiektowa bez żadnej referencji do jakiegokolwiek obiektu. Początkowo ma wartość Nothing. Na przykład pierwsza instrukcja poniższego kodu deklaruje zmienną obiektową klasy Employee o nazwie emp1. Od tego miejsca zmienna ta jest zdefiniowana, ale nie wskazuje na żaden obiekt. Jeśli sprawdzimy jej wartość, stwierdzimy, że jest to Nothing. W drugim wierszu zmienna emp1 zostaje ustawiona na nowy obiekt klasy Employee. Ostatni wiersz tworzy zmienną obiektową klasy Employee o nazwie emp2 i przypisuje ją do nowego obiektu klasy Employee. Efekt jest taki sam jak w pierwszych dwóch wierszach, ale został uzyskany za pomocą jednej instrukcji. Dim emp1 As Employee emp1 = New Employee Dim emp2 As New Manager
Jeśli klasa obiektu posiada konstruktory przyjmujące parametry, można je wstawić za nazwą tej klasy. Wyobraźmy sobie na przykład, że klasa Employee posiada dwa konstruktory — pusty (nieprzyjmujący żadnych parametrów) i przyjmujący jako parametry łańcuchy, które wyznaczają imię i nazwisko. Poniższy kod tworzy dwa obiekty klasy Employee przy użyciu tych dwóch różnych konstruktorów: Dim emp1 As New Employee Dim emp2 As New Employee("Rod", "Stephens")
Pamiętaj, że zawsze trzeba podać takie parametry, które pasują do jednego z konstruktorów. Jeśli klasa nie posiada konstruktora niepobierającego żadnych argumentów, nie można użyć słowa kluczowego New bez parametrów. Gdyby klasa Employee nie miała pustego konstruktora, pierwszy wiersz na powyższym listingu byłby błędny.
Określanie typu zmiennej Klauzula As informuje Visual Basic, jakiego typu zmienna jest deklarowana. Na przykład poniższa instrukcja As oznacza, że zmienna cx jest typu Single: Dim cx As Single
314
Część II
Wstęp do języka Visual Basic
Jeśli opcja Option Infer jest włączona, nie ma obowiązku podawania typu dla zmiennych lokalnych. Jeżeli klauzula As zostanie pominięta, Visual Basic odgadnie typ danych zmiennej na podstawie jej wartości. Na przykład poniższy kod deklaruje zmienną o nazwie message. Ponieważ zmiennej tej została przypisana wartość łańcuchowa, Visual Basic dedukuje, że jest to zmienna typu String. Dim message = "Witajcie!"
Niestety kod zawierający takie domyślne typy danych jest trudniejszy do zrozumienia. Można zgadnąć, że poprzednia deklaracja tworzy zmienną typu String, ale gdyby została użyta klauzula As String, byłoby znacznie łatwiej. W tym przypadku wnioskowanie typu pozwala zaoszczędzić tylko kilka znaków, a utrudnia czytanie kodu. Przeanalizujmy teraz poniższą instrukcję: Dim x = 1.234
Czy zmienna x jest typu Single, Double, Decimal, czy jeszcze jakiegoś innego? W tym przypadku znacznie trudniej zgadnąć, jakiego typu danych użyje Visual Basic (x jest typu Double). Aby uniknąć nieporozumień i maksymalnie uprościć kod, by był on łatwy do zrozumienia, dobrze jest wyłączyć opcję Option Infer. Wtedy można na początku każdego modułu, w którym jest ona potrzebna, umieścić instrukcję Option Infer. Jednak nawet w tych modułach zalecam bezpośrednio określać typy danych wszystkich zmiennych, dla których jest to możliwe. Jedyna sytuacja, w której przydaje się wnioskowanie typów, występuje wówczas, gdy nie da się łatwo określić tego wymaganego przez zmienną. Na przykład technologia LINQ pozwala generować wyniki z mylącymi typami danych, przez co wnioskowanie typów może być bardzo pomocne podczas jej używania. Więcej informacji na temat LINQ znajduje się w rozdziale 21. Po utworzeniu nowego projektu opcja Option Infer jest domyślnie włączona. Wyłącz ją dla całego projektu, a następnie włącz tylko w tych plikach, w których jest niezbędna.
Wyrażenie inicjujące Klauzula wyrażenie_inicjujące przedstawia dane, które mają zostać użyte do inicjacji zmiennej. W podstawowej formie jest to prosta wartość. Poniższa instrukcja deklaruje zmienną o nazwie num_employees i przypisuje jej wartość początkową zero: Dim num_employees As Integer = 0
Bardziej skomplikowane typy danych mogą wymagać złożonych klauzul inicjujących. Jeśli deklaracja dotyczy zmiennej obiektowej, można w niej użyć słowa kluczowego New. Na przykład pierwszy wiersz poniższego kodu deklaruje zmienną obiektową klasy Employee o nazwie emp1 i ustawia ją na nowy obiekt klasy Employee. Druga instrukcja robi to samo, ale za pomocą instrukcji As New, która pozwala opuścić osobną klauzulę inicjującą. Ta wersja jest nieco bardziej zwięzła, ale można używać każdej z nich bez różnicy w działaniu. Dim emp1 As Employee = New Employee("Rod", "Stephens") Dim emp2 As New Employee("Rod", "Stephens")
Rozdział 15.
Typy danych, zmienne i stałe
315
Słowo kluczowe With pozwala na zainicjowanie obiektu bez używania do tego specjalnego konstruktora. Dzięki tej instrukcji przypiszesz wartości do publicznych własności obiektu i zmiennych zaraz po jego utworzeniu. Poniższy fragment kodu tworzy nowy obiekt klasy Employee oraz ustawia wartości jego zmiennych FirstName i LastName, podobnie jak wcześniejsze: Dim emp3 As New Employee With {.FirstName = "Rod", .LastName = "Stephens"}
Inicjowanie tablic Tablice posiadają własną specjalną składnię inicjującą. Aby zadeklarować i zainicjować tablicę za pomocą jednej instrukcji, trzeba opuścić wartości brzegowe tej tablicy. Są one poznawane przez Visual Basic na podstawie danych inicjujących. Wartości tablicy należy umieścić w nawiasach klamrowych i pooddzielać przecinkami. Poniższa instrukcja inicjuje jednowymiarową tablicę liczb całkowitych: Dim fibonacci() As Integer = {1, 1, 2, 3, 5, 8, 13, 21, 33, 54, 87}
W przypadku tablic wielowymiarowych liczbę wymiarów wyznaczają przecinki w nawiasach zmiennej. Dane tablicy otoczone są klamrami. Każdy wymiar danych jest zagnieżdżony w poprzednim, przy czym jest on otoczony nawiasami klamrowymi, a jego elementy są pooddzielane przecinkami. Najłatwiej jest to sobie wyobrazić jako tablicę tablic. Na przykład tablica trzywymiarowa jest tablicą tablic dwuwymiarowych. Każda z tych dwuwymiarowych tablic jest tablicą tablic jednowymiarowych. Aby lepiej uwidocznić strukturę tablicy, można użyć znaków kontynuacji wiersza i wcięć. Poniższy kod deklaruje i inicjuje dwuwymiarową tablicę liczb całkowitych i drukuje jej wartości: Dim int_values(,) As Integer = _ { _ {1, 2, 3}, _ {4, 5, 6} _ } For i As Integer = 0 To 1 For j As Integer = 0 To 2 txt & = int_values(i, j) Next j txt & = vbCrLf Next i
Zostaje wydrukowany następujący wynik: 123 456
Poniższy kod deklaruje i inicjuje trzywymiarową tablicę łańcuchów. Treść każdej z tych wartości określa jej położenie w tablicy. Na przykład wartość str_values(0, 1, 1) to "011". Zwróć uwagę na wcięcia, które ułatwiają zrozumienie danych. Elementy pierwszego wymiaru
316
Część II
Wstęp do języka Visual Basic
są wcięte na jeden poziom, a drugiego — na dwa. Ostatni wymiar jest tablicą jednowymiarową, którą łatwo zrozumieć dzięki samym przecinkom oddzielającym wartości. Po zainicjowaniu tablicy przez wszystkie jej wartości przechodzi pętla i drukuje je. Dim str_values(,,) As String = _ { _ { _ {"000", "001", "002"}, _ {"010", "011", "012"} _ }, _ { _ {"100", "101", "102"}, _ {"110", "111", "112"} _ } _ } For i As Integer = 0 To 1 For j As Integer = 0 To 1 txt & = "[ " For k As Integer = 0 To 2 txt & = str_values(i, j, k) Next k txt & = "] " Next j txt & = vbCrLf Next i
&
" "
Poniżej znajduje się wynik działania tej pętli: [ 000 001 002 ] [ 010 011 012 ] [ 100 101 102 ] [ 110 111 112 ]
Podobny kod — demonstrujący technikę inicjacji tablic — został użyty w programie InitializeArrays, który można pobrać z serwera FTP wydawnictwa Helion. Pamiętaj, że dla każdego wymiaru musisz podać odpowiednią liczbę elementów. Na przykład poniższa deklaracja jest niepoprawna, ponieważ drugi wiersz zawiera mniej elementów niż pierwszy: Dim int_values(,) As Integer = _ { _ {1, 2, 3}, _ {4, 5} _ }
Inicjowanie tablic obiektów Podstawowa składnia inicjacji tablicy obiektów jest podobna do inicjującej wszystkie inne tablice. W tym przypadku również opuszcza się wartości brzegowe oraz umieszcza wartości w nawiasach klamrowych. Różnica dotyczy wartości inicjujących, ponieważ zmienne obiektowe nie przyjmują zwykłych wartości typu 12 czy "Test", których używa się do inicjowania tablic liczb całkowitych i łańcuchów.
Rozdział 15.
Typy danych, zmienne i stałe
317
Jeśli w deklaracji tablicy obiektów zabraknie klauzuli inicjującej, Visual Basic utworzy tylko zmienne obiektowe, bez obiektów. Początkowo wszystkie elementy tablicy będą miały wartość Nothing. Poniższy kod tworzy tablicę zawierającą 11 referencji do obiektów klasy Employee. Początkowo wszystkie te referencje są ustawione na Nothing. Dim employees(0 To 10) As Employee
Aby zainicjować te obiekty, należy robić to pojedynczo przy użyciu Nothing lub konstruktorów klas. Opcjonalnie można za pomocą instrukcji With ustawić publiczne własności i zmienne po utworzeniu obiektu. Poniższy kod deklaruje tablicę obiektów klasy Employee. Dwa elementy zostały zainicjowane za pomocą konstruktora klasy Employee, który przyjmuje imię i nazwisko pracownika, zaś dwa inne — przy użyciu pustego konstruktora i instrukcji With. Kolejne dwa inicjuje sam pusty konstruktor, a ostatnie dwa zostały zainicjowane wartością Nothing. Dim employees() As Employee = _ { _ New Employee("Alicja", "Arnold"), _ New Employee("Bartosz", "Bartnik"), _ New Employee With {.FirstName = "Cecylia", .LastName="Celińska"}, _ New Employee With {.FirstName = "Daniel", .LastName="Drwal"}, _ New Employee, _ New Employee, _ Nothing, _ Nothing _ }
Do inicjacji tablic z większą liczbą wymiarów należy używać składni, która została opisana wcześniej, zaś w celu indywidualnej inicjacji każdego elementu trzeba korzystać z wartości Nothing lub słowa kluczowego New oraz konstruktorów obiektów.
Inicjowanie zmiennych XML Jedną z nowości w Visual Basicu 2008 jest możliwość zapisywania danych XML w obiektach klasy XElement. W tym celu należy zadeklarować zmienną obiektową klasy XElement i ustawić ją na poprawnie sformatowany kod XML. Ten ostatni musi zaczynać się w tym samym wierszu logicznym, w którym znajduje się przypisanie wartości do zmiennej, chociaż jak zwykle można przenieść kod XML do nowego wiersza za pomocą znaku kontynuacji wiersza. Visual Basic wczytuje znacznik otwierający, następnie doczytuje dane XML, aż dojdzie do odpowiadającego mu znacznika zamykającego. W związku z tym dane XML czasem zawierają białe znaki, tak jak dokumenty XML. W szczególności mogą one obejmować kilka wierszy, przy czym nie ma konieczności stosowania znaków kontynuacji wiersza. Jeśli w danych XML użyjesz znaków kontynuacji wiersza, zostaną one potraktowane jako część tych danych. Na przykład poniższy kod deklaruje zmienną o nazwie book_node, która zawiera dane reprezentujące książkę:
318
Część II
Wstęp do języka Visual Basic
Dim book_node As XElement = _ < Book > < Title > The Bug That Was < /Title > < Year > 2008 < /Year > < Pages > 376 < /Year > < /Book >
Tego typu deklaracje i inicjacje ułatwiają wprowadzanie danych XML bezpośrednio do kodu Visual Basica. Wartości literalne XML można inicjować przy użyciu znacznie bardziej skomplikowanych wyrażeń. Na przykład za pomocą LINQ pobierzesz wartości z relacyjnej bazy danych i utworzysz z nich dokument XML. Więcej informacji na temat LINQ znajduje się w rozdziale 21.
Deklarowanie kilku zmiennych za jednym razem W Visual Basicu .NET w jednej instrukcji można zadeklarować kilka zmiennych. Na przykład poniższa instrukcja deklaruje dwie zmienne typu Integer o nazwach num_employees i num_customers: Private num_employees, num_customers As Integer
Słowa kluczowe określające dostępność (Private, Public itd.) oraz pozostałe słowa kluczowe — Shared, Shadows i ReadOnly — można umieszczać tylko na początku deklaracji; mają one zastosowanie do wszystkich deklarowanych zmiennych. W powyższej instrukcji zmienne num_employees i num_customers są prywatne (Private). Aby zadeklarować zmienne różnych typów, należy użyć kilku pooddzielanych przecinkami klauzul As. Poniższa instrukcja deklaruje dwie zmienne typu Integer i jedną zmienną typu String: Private emps, custs As Integer, cust As String
Jeśli przynajmniej dwie zmienne współdzielą jedną klauzulę As, nie można dla żadnej z nich użyć instrukcji inicjującej. Natomiast każda zmienna, która posiada swoją własną klauzulę As, może zostać zainicjowana w tej samej deklaracji. W powyższym przykładzie nie było możliwości zainicjowania zmiennych typu Integer, ale można było zainicjować zmienną typu String, jak poniżej: Private emps, custs As Integer, cust As String = "Cozmo"
Aby zainicjować wszystkie trzy zmienne, każdej z nich należałoby utworzyć jej własną klauzulę As: Private emps As Integer = 5, custs As Integer = 10, cust As String = "Cozmo"
W jednej instrukcji można także deklarować i inicjować wiele obiektów, tablic oraz tablic obiektów.
Rozdział 15.
Typy danych, zmienne i stałe
319
Kombinacje te, mimo iż dozwolone, szybko stają się zbyt mało jasne, aby mogły mieć jakieś praktyczne zastosowanie. Nawet znajdująca się poniżej względnie prosta instrukcja może prowadzić do nieporozumień w późniejszym czasie. Programista — tylko rzuciwszy okiem — może stwierdzić, że wszystkie trzy zmienne są zadeklarowane jako typu Long: Private num_employees, num_customers As Integer, num_orders As Long
Zagrożenie pomyłką można zredukować poprzez użycie tylko jednej instrukcji As na deklarację. Wtedy programista z łatwością określi, jak zostały zdefiniowane zmienne — wystarczy, że spojrzy na początek i koniec deklaracji. Na początku dowie się, jaka jest dostępność zmiennych, czy są one współdzielone, czy przesłaniają jakieś inne zmienne, a także czy są tylko do odczytu. Na końcu sprawdza, jaki jest ich typ. Można także utrzymać prostotę kodu, jeśli zadeklaruje się wszystkie inicjowane zmienne w osobnych deklaracjach. Wtedy programista czytający kod nie musi się zastanawiać, czy instrukcja inicjująca ma zastosowanie do jednej, czy do wszystkich zmiennych. Nie ma nic złego w deklarowaniu wielu krótkich zmiennych w jednej instrukcji, jeśli tylko potrzebny do tego kod nie zwiększa ryzyka popełnienia błędu. Poniższe instrukcje deklarują pięć zmiennych typu Integer i trzy zmienne typu String. Rozbicie tego kodu na osiem osobnych instrukcji nie wpłynęłoby znacząco na zwiększenie przejrzystości kodu. Dim i, j, k, R, C As Integer Dim X, Y, Z As Single
Opcje Option Explicit i Option Strict Opcje kompilatora Option Explicit i Option Strict odgrywają bardzo ważną rolę w deklaracjach zmiennych. Kiedy opcja Option Explicit jest ustawiona na wartość On, każda zmienna musi przed użyciem zostać zadeklarowana. Jeśli opcja ta jest wyłączona (Off), Visual Basic — napotykając zmienną — po raz pierwszy automatycznie ją tworzy. Na przykład w poniższym kodzie nie ma ani jednej bezpośredniej deklaracji zmiennej. W czasie wykonywania kodu Visual Basic dociera do pierwszej instrukcji num_managers = 0. Nie rozpoznaje takiej zmiennej, a więc ją tworzy. W podobny sposób powstaje zmienna i, kiedy Visual Basic znajduje ją w pętli For. Option Explicit Off Option Strict Off Public Class Form1 ... Public Sub CountManagers() num_managers = 0 For i = 0 To m_Employees.GetUpperBound(0) If m_Employees(i).IsManager Then num_managrs += 1 Next i MsgBox(num_managers)
320
Część II
Wstęp do języka Visual Basic
End Sub ... End Class
Wyłączenie opcji Option Explicit może prowadzić do dwóch bardzo poważnych problemów. Po pierwsze, ukrywa ona po cichu błędy typograficzne. Jeśli uważnie przyjrzysz się kodowi na powyższym listingu, zauważysz, że pętla For zwiększa źle napisaną zmienną num_managrs zamiast zmiennej num_managers. Jako że opcja Option Explicit jest wyłączona, Visual Basic zakłada, iż chcemy użyć nowej zmiennej, a więc tworzy zmienną num_managrs. Po zakończeniu działania pętli program wyświetla wartość zmiennej new_managers, która wynosi zero, ponieważ nigdy nie została zwiększona. Drugi problem z wyłączeniem opcji Option Explicit polega na tym, że Visual Basic nie wie, do czego tak naprawdę mają służyć tworzone przez niego zmienne. Brakuje informacji, czy będą one typu Double, String, czy może PictureBox. Nawet gdy przypisana zostanie wartość takiej zmiennej (na przykład typu Integer), Visual Basic nie będzie wiedzieć, czy zmienna ta będzie zawsze używana do przechowywania tego typu wartości, czy może kiedyś zapiszemy w niej łańcuch. Aby zapewnić sobie swobodę działania, Visual Basic tworzy niezadeklarowane zmienne jako obiekty ogólne. Dzięki temu może przypisać takiej zmiennej każdą wartość. Niestety może to się niekorzystnie odbić na wydajności programu. Na przykład aplikacje znacznie lepiej radzą sobie z operacjami na liczbach całkowitych niż na obiektach. Jeśli w programie ma być użyta liczba całkowita, a zostanie ona utworzona jako obiekt, program będzie działać znacznie wolniej. Jeśli opcja Option Infer jest włączona, Visual Basic może drogą dedukcji odgadnąć bezpośredni typ danych zmiennej zadeklarowanej z typem. W takim przypadku aplikacja może nie zostać spowolniona. Z kodu jednak nie da się poznać, czy jest to właśnie taka sytuacja, a więc mogą powstawać niejasności. Jeśli posłużyć się zaawansowaną terminologią, liczby całkowite są typami wartościowymi, a obiekty typami referencyjnymi. Typ referencyjny to wskaźnik reprezentujący lokalizację obiektu w pamięci. Kiedy typ wartościowy zostanie potraktowany jako typ referencyjny, Visual Basic wykona operację zwaną pakowaniem (ang. boxing), która polega na opakowaniu wartości w obiekt, dzięki czemu będzie można używać do niej referencji. Jeśli zostanie wykonana operacja z wykorzystaniem dwóch opakowanych wartości, Visual Basic najpierw je odpakuje, wykona odpowiednie działania, a następnie opakuje wynik, aby zapisać go w innej zmiennej referencyjnej. Wszystkie te operacje pakowania i odpakowywania wiążą się z dużym narzutem. Kod przedstawiony na poniższym listingu demonstruje różnicę w szybkości działania programu ze zmiennymi z bezpośrednio określonymi typami i ze zmiennymi ogólnego typu Object (kod ten pochodzi z aplikacji TimeGenericObjects, którą można pobrać z serwera FTP wydawnictwa Helion): Dim num_trials As Integer = Integer.Parse(txtNumTrials.Text) Dim start_time As DateTime
Rozdział 15.
Typy danych, zmienne i stałe
321
Dim stop_time As DateTime Dim elapsed_time As TimeSpan start_time = Now For i As Integer = 1 To num_trials Next i stop_time = Now elapsed_time = stop_time.Subtract(start_time) lblIntegers.Text = elapsed_time.TotalSeconds.ToString("0.000000") Refresh() start_time = Now For j = 1 To num_trials Next j stop_time = Now elapsed_time = stop_time.Subtract(start_time) lblObjects.Text = elapsed_time.TotalSeconds.ToString("0.000000")
Kod ten wykonuje dwie pętle For. W pierwszej z nich zmienna pętlowa ma jawnie określony typ Integer. W drugiej natomiast brak deklaracji zmiennej pętlowej, a więc Visual Basic automatycznie tworzy ją jako typ Object, kiedy jest potrzebna. W jednym teście druga pętla potrzebowała ponad 60 razy więcej czasu niż pierwsza. Druga dyrektywa kompilatora, która wpływa na deklaracje zmiennych, to Option Strict. Kiedy opcja ta jest wyłączona, Visual Basic niejawnie konwertuje wartości jednego typu na inny, nawet jeśli typy te nie są ze sobą w pełni zgodne. Na przykład zezwoli instrukcjom w poniższym kodzie na próbę skopiowania łańcucha s do zmiennej typu Integer i. Jeśli łańcuch ten reprezentuje akurat liczbę (jak w pierwszym przypadku), operacja ta powiedzie się. Jeżeli nie jest on liczbą (drugi przypadek), instrukcja spowoduje błąd w czasie działania programu. Dim Dim s = i = s = i =
i As Integer s As String "10" s ‘ To działa. "Witajcie" s ‘ To nie działa.
Jeśli opcja Option Strict jest włączona, Visual Basic ostrzega o możliwych niedozwolonych konwersjach w czasie kompilacji. Nadal jednak można do konwersji łańcuchów na liczby całkowite używać funkcji konwertujących, takich jak CInt, Int czy Integer.Parse, ale wymaga to podjęcia jawnych działań. Aby uniknąć zamętu i zapewnić sobie pełną kontrolę nad deklaracjami zmiennych, należy zawsze włączać opcje Option Explicit i Option Strict. Dziwne jest to, że nie są one uruchomione domyślnie. Mogłyby nawet zostać całkiem usunięte, a program działałby tak, jakby były włączone. Możliwe, że dzieje się tak z jakichś historycznych powodów, a może dlatego, że związany z Visual Basikiem język VBScript nie pozwala na deklarowanie zmiennych.
322
Część II
Wstęp do języka Visual Basic
Więcej informacji na temat opcji Option Explicit i Option Strict (włącznie z instrukcjami do ich włączania) znajduje się w podrozdziale „Menu Project” rozdziału 2. — „Menu, paski narzędzi i okna”.
Zasięg zmiennych Zasięg zmiennej określa, w których miejscach kodu można uzyskać do niej dostęp. Jeśli na przykład zmienna zostanie zadeklarowana w podprocedurze, dostęp do niej będzie miał tylko kod tej podprocedury. Wyróżnia się cztery poziomy zasięgu zmiennej (w kolejności rosnącej): blokowy, procedurowy, modułowy oraz przestrzeni nazw.
Zasięg blokowy Blok to zbiór instrukcji zawartych w konstrukcji zakończonej jakiegoś rodzaju instrukcją End, Else, Loop lub Next. Jeśli zmienna zostanie zadeklarowana w bloku, będzie miała zasięg blokowy, więc dostęp do niej da się uzyskać tylko w tym bloku. Co więcej, będzie ona widoczna tylko od miejsca, w którym została zadeklarowana. Zmienne zadeklarowane w instrukcji otwierającej blok również stanowią jego część. Należy pamiętać, że zmienna blokowa jest widoczna we wszystkich podblokach swojego bloku. W kodzie na poniższym listingu została użyta pętla For ze zmienną pętlową i zadeklarowaną w tej instrukcji For. Zasięg tej zmiennej ogranicza się do bloku tej pętli For. Kod wewnątrz tej pętli widzi tę zmienną i, a kod poza pętlą — już nie. Wewnątrz pętli znajduje się deklaracja zmiennej j. Zmienna ta również ma zasięg obejmujący blok tej pętli For. Jeśli zmienna i jest równa j, program deklaruje zmienną M i używa jej. Zasięg tej zmiennej ogranicza się tylko do dwóch wierszy pomiędzy instrukcjami If i Else. Jeśli zmienna i nie jest równa zmiennej j, kod deklaruje zmienną N. Ta zmienna obejmuje swoim zasięgiem tylko dwa wiersze pomiędzy instrukcjami Else i End If. Dalej znajduje się deklaracja zmiennej k. Ona także ma zasięg blokowy, ale jest dostępna dopiero po jej zadeklarowaniu, przez co nie było do niej dostępu wcześniej w pętli For. For i As Integer = 1 To 5 Dim j As Integer = 3 If i = j Then Dim M As Integer = i + Debug.WriteLine("M: " Else Dim N As Integer = i * Debug.WriteLine("N: " End If Dim k As Integer = 123 Debug.WriteLine("k: " & Next i
j &
M)
j &
N)
k)
Rozdział 15.
Typy danych, zmienne i stałe
323
Do innych konstrukcji definiujących bloki zaliczają się:
Instrukcje Select Case — każda klauzula Case definiuje swój własny blok.
Instrukcje Try Catch — sekcja Try i każda instrukcja Exception definiują bloki. Należy pamiętać, że zmienne wyjątków zdefiniowane w instrukcjach Exception mają swoje własne bloki, a więc mogą na przykład wszystkie posiadać taką samą nazwę. Try Dim i As Integer = CInt("zła wartość") Catch ex As InvalidCastException Dim txt As String = "InvalidCastException" MsgBox(txt) Catch ex As Exception Dim txt As String = "Wyjątek" MsgBox(txt) End Try
Instrukcje If Then mieszczące się w jednym wierszu — są one na tyle mętne, że powinno się ich unikać, aczkolwiek poniższy kod jest poprawny: If manager Then Dim txt As String = "M" : MsgBox(txt) Else _ Dim txt As String = "E" : MsgBox(txt)
Pętle While — zmienne zadeklarowane w pętlach mają zasięg ograniczony do tych pętli.
Instrukcje Using — zasoby pobierane przez blok i zmienne zadeklarowane w tym bloku są lokalne. Instrukcja Using w poniższym kodzie definiuje w swoim bloku dwa obiekty klasy Employee i zmienną i. Zmienne te są widoczne tylko w tym bloku. Using _ emp1 As New Employee("Anna", "Ambroziak"), _ emp2 As New Employee("Bogdan", "Biegacz"), _ Dim i As Integer ... End Using
Ponieważ zasięg blokowy jest najbardziej restrykcyjnym ze wszystkich rodzajów zasięgów, należy używać go, kiedy to tylko możliwe, aby uniknąć zamętu. Więcej na temat ograniczania zasięgu zmiennych znajduje się w dalszej części tego rozdziału, w podrozdziale „Ograniczanie zasięgu”.
Zasięg procedurowy Jeśli zmienna zostanie zadeklarowana w podprocedurze, funkcji lub innej procedurze, ale nie w bloku, będzie widoczna w każdym miejscu tej procedury za swoją deklaracją. Zmienna ta jest niewidoczna poza swoją procedurą. W pewnym sensie jej zasięg jest blokowy, gdzie blokiem jest procedura.
324
Część II
Wstęp do języka Visual Basic
Parametry procedur również mają zasięg procedurowy. Na przykład w poniższym kodzie parametry order_object i order_item obejmują zasięgiem podprocedurę AddOrderItem: Public Sub AddOrderItem(ByVal order_object As Order, ByVal order_item As OrderItem) order_object.OrderItems.Add(order_item) End Sub
Zasięg modułowy Zmienna z zasięgiem modułowym jest dostępna we wszystkich miejscach swojego modułu, klasy lub struktury, nawet w tych znajdujących się przed jej deklaracją. Na przykład poniższy kod działa poprawnie, mimo że podprocedura DisplayLoanAmount jest zadeklarowana przed zmienną m_LoanAmount, której wartość wyświetla: Private Class Lender Public Sub DisplayLoanAmount() MsgBox(m_LoadAmount) End Sub Private m_LoanAmount As Decimal ... End Class
Aby nadać zmiennej zasięg modułowy, należy zadeklarować ją przy użyciu słowa kluczowego Private, Protected lub Protected Friend. Jeśli zmienna zostanie zadeklarowana jako prywatna, będzie widoczna tylko dla kodu z tego samego co ona modułu. Jeśli zmienna zostanie zadeklarowana jako chroniona (Protected), będzie dostępna tylko w kodzie swojej klasy i klas po niej dziedziczących. Pamiętajmy, że słowa kluczowego Protected można używać tylko w klasach. Zmienna Protected Friend jest zarówno chroniona, jak i zaprzyjaźniona (Friend). Oznacza to, że jest dostępna tylko w swojej klasie i klasach po niej dziedziczących (Protected), a także znajdujących się w tym samym projekcie (Friend). Te słowa kluczowe mają zastosowanie zarówno do deklaracji zmiennych, jak i procedur. Można na przykład zadeklarować podprocedurę, funkcję lub procedurę własności jako Private, Protected lub Protected Friend. Więcej informacji na temat słów kluczowych, które określają dostępność, znajduje się we wcześniejszym podrozdziale, którego tytuł brzmi „Dostępność”. Zasięg modułowy i procedurowy demonstruje program ScopeTest, który można pobrać z serwera FTP wydawnictwa Helion.
Zasięg przestrzeni nazw Domyślnie każdy projekt definiuje przestrzeń nazw, która obejmuje cały jego kod. Jednak za pomocą instrukcji Namespace utworzysz inne przestrzenie nazw. Technikę tę można wykorzystać do podzielenia swojego kodu na kategorie.
Rozdział 15.
Typy danych, zmienne i stałe
325
Jeśli zmienna zostanie zadeklarowana przy użyciu słowa kluczowego Public, będzie mieć zasięg przestrzeni nazw; dostępna będzie dla całego kodu znajdującego się w tej samej przestrzeni, bez względu na to, czy został on umieszczony w tym samym projekcie, czy w jakimś innym, a także we wszystkich przestrzeniach nazw zagnieżdżonych w jej przestrzeni nazw. Jeśli programista nie utworzy żadnej własnej przestrzeni nazw, cały projekt będzie objęty jedną przestrzenią nazw, dzięki czemu zmienne Public staną się zmiennymi o zasięgu globalnym. Jeśli zmienna zostanie zadeklarowana przy użyciu słowa kluczowego Private, będzie mieć zasięg przestrzeni nazw; dostępna będzie w całej tej przestrzeni w obrębie swojego projektu, a także we wszystkich przestrzeniach nazw zagnieżdżonych w jej przestrzeni nazw. Jeśli programista nie utworzy żadnych własnych przestrzeni nazw, cały projekt będzie należał do jednej przestrzeni nazw, dzięki czemu zmienne zadeklarowane jako Friend obejmą swoim zasięgiem cały projekt. Więcej informacji na temat słów kluczowych Public i Friend znajduje się we wcześniejszej części rozdziału — w podrozdziale „Dostępność”.
Ograniczanie zasięgu Istnieje kilka powodów, dla których powinno się nadawać zmiennym jak najmniejszy możliwy zasięg, pozwalający im spełnić ich rolę. Ograniczony zasięg utrzymuje lokalny charakter zmiennych, dzięki czemu programiści nie mogą nieprawidłowo ich użyć w jakiejś odległej części programu, która nie jest bezpośrednio związana z ich głównym przeznaczeniem. Im mniej zmiennych globalnych, tym mniej programiści muszą zapamiętać podczas pracy nad kodem. Mogą skupić się na swoich bieżących zadaniach, zamiast zastanawiać się, czy zmienne r i c są zadeklarowane globalnie, a także czy aktualnie pisany kod nie będzie im przeszkadzać. Ograniczanie zasięgu zmiennych pozwala zatrzymać je bliżej ich deklaracji, dzięki czemu programiści łatwiej je znajdują. Jednym z najlepszych przykładów takiej sytuacji jest pętla For z deklaracją jej zmiennej pętlowej bezpośrednio w instrukcji For. Programista od razu widzi, że zmienna ta jest typu Integer, nie musi przeglądać całej podprocedury w poszukiwaniu jej deklaracji. Od razu też wiadomo, że zmienna ta ma zasięg blokowy, a więc poza tą pętlą można używać innych zmiennych o takiej samej nazwie. Ograniczony zasięg oznacza, że programista nie musi się zastanawiać, czy stara wartość zmiennej będzie zakłócać działanie bieżącego kodu, a także czy ostateczna jej wartość nie będzie później zakłócać działania jakiegoś innego kodu. Jest to szczególnie ważne dla zmiennych pętlowych. Jeśli na początku podprocedury zostanie zadeklarowana zmienna i, która następnie będzie wykorzystywana wielokrotnie w różnych pętlach, upewnienie się, że stare wartości nie będą zakłócać działania nowych pętli, może zająć sporo czasu. Jeżeli zmienna i zostanie zadeklarowana osobno we wszystkich instrukcjach For, każda pętla będzie posiadać własną wersję tej zmiennej, dzięki czemu nie ma szans, aby wzajemnie się one zakłócały.
326
Część II
Wstęp do języka Visual Basic
W końcu — zmienne z większym zasięgiem bywają częściej alokowane, przez co zwykle zajmują pamięć. Na przykład zmienne blokowe i niestatyczne deklarowane z zasięgiem procedurowym są alokowane, gdy okazują się potrzebne, a niszczone, kiedy ich zasięg się kończy. Dzięki temu zwolnionej zostaje trochę pamięci. Zmienna zadeklarowana jako statyczna (Static) lub z zasięgiem modułowym albo przestrzeni nazw jest zwalniana dopiero po zakończeniu działania aplikacji. Jeśli taka zmienna jest dużą tablicą, może przez cały czas pracy programu zajmować duże ilości pamięci.
Deklaracje parametrów Deklaracje parametrów podprocedur, funkcji i procedur własności mają zawsze zasięg niestatyczny procedurowy. Zmienne parametryczne są przez Visual Basic tworzone w chwili rozpoczęcia działania procedury, a niszczone, gdy zakończy ona działanie. Kod znajdujący się wewnątrz podprocedury posiada dostęp do tych parametrów, nie ma go zaś ten znajdujący się na zewnątrz niej. Poniższa przykładowa podprocedura przyjmuje jako parametr liczbę całkowitą. Wartość ta w tej procedurze nosi nazwę employee_id. Można uzyskać dostęp do wartości employee_id wewnątrz tej podprocedury, zaś na zewnątrz — już nie. Public Sub DisplayEmployee(ByVal employee_id As Integer) ... End Sub
Podczas gdy podstawowe zasady dotyczące zasięgu parametrów są proste (jest to zasięg niestatyczny procedurowy), ich niektóre specjalne własności sprawiają, że sytuacja może się komplikować. Mimo iż problem ten nie dotyczy ściśle zagadnienia zasięgu, jest mu na tyle bliski, że warto opisać go w tym miejscu. Parametr można zadeklarować przy użyciu słowa kluczowego ByRef lub ByVal (ByVal jest domyślne, jeśli nie zostanie użyte żadne). Jeśli zadeklarujemy zmienną za pomocą słowa kluczowego ByVal, procedura utworzy własną lokalną zmienną parametryczną o zasięgu procedurowym, zgodnie z oczekiwaniami. Jeśli natomiast do deklaracji parametru zostanie użyte słowo kluczowe ByRef, procedura nie utworzy osobnej kopii tej zmiennej parametrycznej. W zamian będzie używać referencji do przekazanego parametru, a wszystkie zmiany dokonane przez procedurę na tej wartości będą odzwierciedlane w podprocedurze wywołującej. Na przykład kod na poniższym listingu przedstawia dwie procedury podwajające swoje parametry. Podprocedura DoubleItByVal deklaruje swój parametr przy użyciu słowa kluczowego ByVal. Tworzy ona nową zmienną o nazwie X, po czym kopiuje do niej wartość swojego parametru. Parametr X jest dostępny w tej podprocedurze, która mnoży go przez 2 i przerywa działanie. W tym momencie kończy się zasięg parametru; zostaje on zniszczony. Podprocedura DoubleItByRef deklaruje swój parametr przy użyciu słowa kluczowego ByRef. Zmienna X tej procedury jest referencją do zmiennej, która została do niej przekazana. Podprocedura ta podwaja wartość X, co powoduje podwojenie jej w kodzie wywołującym.
Rozdział 15.
Typy danych, zmienne i stałe
327
Podprocedura TestParameters wywołuje obie te procedury. Zawiera ona deklarację zmiennej o nazwie value, którą przekazuje do podprocedury DoubleItByVal, a następnie wyświetla jej wartość, gdy podprocedura ta zakończy działanie. Ponieważ podprocedura DoubleItByVal deklaruje swój parametr przy użyciu słowa kluczowego ByVal, wartość tej zmiennej pozostaje niezmieniona, a więc wynik wynosi 10. Następnie podprocedura TestParameters wywołuje podprocedurę DoubleItByRef i wyświetla wynik po zakończeniu jej działania. Podprocedura DoubleItByRef deklaruje swój parametr przy użyciu słowa kluczowego ByRef, a więc wartość zmiennej value zostaje podwojona — wynosi 20. Sub DoubleItByVal(ByVal X As Single) X*= 2 End Sub Sub DoubleItByRef(ByRef X As Single) X*= 2 End Sub Sub TestParameters() Dim value As Single value = 10 DoubleItByVal(value) Debug.WriteLine(value) value = 10 DoubleItByRef(value) Debug.WriteLine(value) End Sub
Nawet od tego skomplikowanego sposobu traktowania parametrów przez procedury są wyjątki. Jeśli do procedury zostanie przekazana wartość literalna lub wynik wyrażenia, nie będzie zmiennej do przekazania przez referencję, przez co Visual Basic będzie musiał utworzyć swoją własną tymczasową zmienną. W takim przypadku żadne zmiany dokonane w parametrze ByRef nie są zwracane do procedury wywołującej, ponieważ nie przekazała ona żadnej zmiennej do procedury. Poniższe instrukcje przekazują do podprocedury DoubleItByref wyrażenie literalne i wynik wyrażenia: DoubleItByRef(12) ‘ Wyrażenie literalne. DoubleItByRef(X + Y) ‘ Wynik wyrażenia.
Inna sytuacja, w której parametr ByRef nie modyfikuje zmiennej w kodzie wywołującym, ma miejsce, gdy zostanie opuszczona opcjonalna zmienna. Na przykład poniższa podprocedura przyjmuje opcjonalny parametr ByRef. Jeśli zostanie ona wywołana bez tego parametru, Visual Basic utworzy parametr o nazwie employee_id od początku, aby móc go użyć w tej podprocedurze. Ponieważ została ona wywołana bez przekazania do niej zmiennej, nie modyfikuje żadnej wartości. Sub UpdateEmployee(Optional ByRef employee_id As Integer = 0) ... End Sub
Prawdopodobnie najbardziej podstępnym sposobem na uniknięcie modyfikacji przez zmienną ByRef zmiennej w kodzie wywołującym jest otoczenie tej zmiennej nawiasami. Zmuszają one Visual Basic do obliczenia tego, co się w nich znajduje jako wyrażenie,
328
Część II
Wstęp do języka Visual Basic
przez co tworzona jest tymczasowa zmienna, która przechowuje wynik tego wyrażenia. Następnie zostaje ona przekazana do procedury. Jeśli jej parametr jest zadeklarowany przy użyciu słowa kluczowego ByRef, zostanie zaktualizowana zmienna tymczasowa, ale nie oryginalna, przez co procedura wywołująca nie dostrzeże żadnych zmian. Poniższy kod wywołuje podprocedurę DoubleItByRef i przekazuje do niej zmienną value w nawiasach. Podprocedura ta podwaja utworzoną przez Visual Basic zmienną tymczasową, a zmienną value pozostawia bez zmian. DoubleItByRef((value))
Podczas używania parametrów pamiętaj o tych wszystkich zasadach. Parametry mają zasięg niestatyczny procedurowy, ale słowo kluczowe ByRef może czasami pomóc w wyprowadzeniu ich wartości poza procedurę. Więcej informacji na temat procedur i ich parametrów znajduje się w rozdziale 17. — „Podprocedury i funkcje”.
Procedury własności Procedury własności mogą reprezentować wartości podobne do zmiennych. Dla reszty programu wyglądają one jak zmienne, dlatego warto omówić je w tym rozdziale. Poniższy listing przedstawia procedury własności implementujące własność Name. Procedura Property Get zwraca wartość prywatnej zmiennej m_Name, zaś Property Set zapisuje nową wartość w zmiennej m_Name. Private m_Name As String Property Name() As String Get Return m_Name End Get Set(ByVal Value As String) m_Name = Value End Set End Property
Program może używać tych procedur w dokładnie taki sposób, jakby istniała jedna publiczna zmienna o nazwie Name. Gdyby na przykład ten kod znajdował się w klasie Employee, poniższy przedstawiałby sposób ustawiania i sprawdzania przez program wartości Name dla obiektu klasy Employee o nazwie emp: emp.Name = "Rod Stephens" MessageBox.Show(emp.Name)
Istnieje kilka powodów, dla których można zdecydować się na użycie procedur własności zamiast zmiennej publicznej. Po pierwsze, procedury te dają dodatkową kontrolę nad sprawdzaniem i ustawianiem wartości. Można na przykład przed zapisaniem wartości
Rozdział 15.
Typy danych, zmienne i stałe
329
w zmiennej sprawdzić, czy jest prawidłowa. Jeśli numer telefoniczny lub kod pocztowy byłyby w niepoprawnym formacie, kod ten mógłby zgłosić błąd. W procedurach własności można ustawiać punkty wstrzymania. Załóżmy, że program ulega awariom, ponieważ jakiś fragment kodu ustawia niepoprawną wartość jednej ze zmiennych. Jeśli zmienna ta jest zaimplementowana przy użyciu procedur własności, można ustawić punkt wstrzymania w procedurze Property Set i zatrzymać aplikację, kiedy ustawia tę wartość. W ten sposób względnie szybko znajdzie się źródło problemu. Procedury własności pozwalają ustawiać i sprawdzać wartości w innych formatach niż te, w których rzeczywiście chcemy je zapisać. Na przykład poniżej znajduje się definicja procedur własności Name, które zapisują imię i nazwisko w zmiennych m_FirstName i m_LastName. Gdyby często trzeba było używać oddzielnie imienia i nazwiska, można by utworzyć procedury własności, które dawałyby dostęp do każdej z tych wartości osobno. Private m_LastName As String Private m_FirstName As String Property MyName() As String Get Return m_FirstName & " " & m_LastName End Get Set(ByVal Value As String) m_FirstName = Value.Split(" "c)(0) m_LastName = Value.Split(" "c)(1) End Set End Property
Procedur własności można w końcu używać do tworzenia zmiennych tylko do odczytu i tylko do zapisu. Poniższy kod demonstruje utworzenie procedury własności tylko do odczytu NumEmployees i procedury własności tylko do zapisu NumCustomers (procedury własności tylko do zapisu są rzadko spotykane, ale dozwolone). Public ReadOnly Property NumEmployees() As Integer Get ... End Get End Property Public WriteOnly Property NumCustomers() As Integer Set(ByVal Value As Integer) ... End Set End Property
Nie jest konieczne zapamiętanie wszystkich szczegółów składni procedur własności. Jeśli wpisze się pierwszy wiersz i naciśnie klawisz Enter, Visual Basic wstawi resztę do uzupełnienia. Jeżeli zostanie użyte słowo kluczowe ReadOnly lub WriteOnly, utworzona będzie tylko jedna odpowiednia procedura.
330
Część II
Wstęp do języka Visual Basic
Typy wyliczeniowe Typ wyliczeniowy jest listą osobnych wartości. W jego definicji wyznacza się dozwolone dla niego wartości. Zmienna takiego typu może mieć tylko jedną z nich. Wyobraźmy sobie na przykład, że piszemy program, którego użytkownicy mogą mieć jeden z trzech poziomów uprawnień dostępu: urzędnik, nadzorca lub administrator. Można w takim przypadku zdefiniować typ wyliczeniowy o nazwie AccessLevel z wartościami Clerk (urzędnik), Supervisor (nadzorca) i Administrator. Zmienna zadeklarowana jako typu AccessLevel będzie mogła mieć tylko jedną z tych trzech wartości. Poniżej znajduje się prosty przykład, który ilustruje omawiany problem. Kod ten definiuje typ AccessLevel i deklaruje zmienną tego typu o nazwie m_AccessLevel. Dalej podprocedura MakeSupervisor ustawia zmienną m_AccessLevel na wartość AccessLevel.Supervisor. Zauważ, że przed wartością znajduje się nazwa jej typu wyliczeniowego. Public Enum AccessLevel Clerk Supervisor Administrator End Enum Private m_AccessLevel As AccessLevel ‘ Uprawnienia użytkownika. ‘ Ustawianie uprawnień nadzorcy. Public Sub MakeSupervisor() m_AccessLevel = AccessLevel.Supervisor End Sub
Składnia deklaracji typu wyliczeniowego jest następująca: [lista_atrybutów] [dostępność] [Shadows] Enum nazwa [As typ ] [lista_atrybutów] nazwa_wartości [= wyrażenie_inicjujące] [lista_atrybutów nazwa_wartości [= wyrażenie inicjujące] ... End Enum
Większość tych elementów, łącznie z listą atrybutów i dostępnością, jest podobna do składników deklaracji zmiennej. Więcej informacji na ten temat znajdziesz we wcześniejszej części rozdziału, w podrozdziale „Deklarowanie zmiennych”. Wartość typ musi być liczbą całkowitą typu Byte, Short, Integer lub Long. Jeśli się ją pominie, wartości typu wyliczeniowego zostaną zapisane jako typy Integer. Element nazwa_wartości określa nazwy, które mogą wchodzić w skład typu wyliczeniowego. Dla każdej wartości można użyć wyrażenia inicjującego, ale nie jest to obowiązkowe. Wartości te muszą być zgodne z typem danych całego wyliczenia (Byte, Short, Integer lub Long). Jeśli wyrażenie_inicjujące jakiejś wartości zostanie opuszczone, wartość ta będzie o jeden większa od poprzedniej. Pierwsza wartość domyślnie wynosi zero.
Rozdział 15.
Typy danych, zmienne i stałe
331
Na przykład w poprzednim przykładzie poszczególne wartości przedstawiały się następująco: Clerk=0, Supervisor=1 oraz Administrator=2. W poniższym kodzie domyślne wartości zostały zmienione — Clerk=10, Supervisor=11, a Administrator=-1: Public Enum AccessLevel Clerk = 10 Supervisor Administrator = -1 End Enum
Zazwyczaj najważniejszą rzeczą w typach wyliczeniowych jest to, że ich wartości muszą być unikatowe, dzięki czemu nie trzeba inicjować ich jawnie. Należy zauważyć, że wartościom wyliczeniowym można nadać te same całkowitoliczbowe wartości w sposób jawny lub niejawny. Na przykład w poniższym kodzie zostało zdefiniowanych kilka równoważnych wartości AccessLevel. Trzy pierwsze — Clerk, Supervisor oraz Administrator — przyjmują odpowiednio wartości domyślne 0, 1 oraz 2. Wartość User zostaje jawnie ustawiona na 0, a więc jest równa wartości Clerk. Następnie wartości Manager i SysAdmin przyjmują domyślnie wartości o jeden większe od poprzedniej, czyli 1 i 2 (takie same jak Supervisor i Administrator). Na końcu wartość Superuser zostaje jawnie ustawiona na taką samą wartość, jaką posiada SysAdmin. Public Enum AccessLevel Clerk Supervisor Administrator User = 0 Manager SysAdmin Superuser = SysAdmin End Enum
Ten kod nie jest całkiem jasny. W poniższym jest oczywiste, że niektóre wartości są równoważne z innymi: Public Enum AccessLevel Clerk Supervisor Administrator User = Clerk Manager = Supervisor SysAdmin = Administrator Superuser = Administrator End Enum
Podobny efekt do typu wyliczeniowego można uzyskać przy użyciu zmiennych całkowitoliczbowych i stałych, co obrazuje poniższy kod. Jego działanie jest w przybliżeniu takie samo jak poprzednich przykładów.
332
Część II
Wstęp do języka Visual Basic
Public Const Clerk As Integer = 0 Public Const Supervisor As Integer = 1 Public Const Administrator As Integer = 2 Private m_AccessLevel As Integer
‘ Poziom uprawnień użytkownika.
‘ Ustawienie poziomu nadzorcy. Public Sub MakeSupervisor() m_AccessLevel = Supervisor End Sub
Należy jednak zaznaczyć, że typ wyliczeniowy ma kilka zalet w stosunku do zmiennych i stałych. Po pierwsze, uniemożliwia przypisywanie bezsensownych wartości do zmiennej. W poprzednim kodzie można było ustawić zmienną m_AccessLevel na 10, co nie miałoby żadnego sensu. Gdy użyjesz typu wyliczeniowego, Visual Basic będzie mógł sprawdzić, czy wartość przypisywana do zmiennej jest odpowiednia. Zmienną taką można ustawić tylko na jedną z wartości dostępnych w typie wyliczeniowym lub na wartość zapisaną w innej zmiennej tego samego typu wyliczeniowego. Jeśli z jakiegoś powodu konieczne jest ustawienie zmiennej wyliczeniowej na wynik jakichś obliczeń, można użyć funkcji CType, która konwertuje wartości całkowitoliczbowe na typy wyliczeniowe. Na przykład poniższa instrukcja ustawia wartość zmiennej m_AccessLevel na wartość zmiennej integer_value. Zmuszenie programisty do wykonania tego typu konwersji zmniejsza ryzyko przypadkowego ustawienia wartości wyliczeniowej. m_AccessLevel = CType(integer_value, AccessLevel)
Kolejną korzyścią płynącą z używania typów wyliczeniowych jest to, że umożliwiają one Visual Basicowi uaktywnienie funkcji IntelliSense. Jeśli programista wpisze m_AccessLevel=, Visual Basic wyświetli listę dozwolonych wartości typu AccessLevel. Ostatnią zaletą typów wyliczeniowych jest to, że udostępniają one metodę ToString, która zwraca tekstową reprezentację nazw wartości. Na przykład poniższy kod wyświetli łańcuch "Clerk". Dim access_level As AccessLevel = Clerk MessageBox.Show(access_level.ToString())
Program AccessLevelEnum, który można pobrać z serwera FTP wydawnictwa Helion, tworzy wyliczenie AccessLevel i wyświetla wyniki zwrócone przez metodę ToString dla każdego z jego elementów. Jeśli masz w programie wartość, która posiada ograniczoną i stałą liczbę możliwych opcji, warto zastanowić się nad zdefiniowaniem typu wyliczeniowego. Jeżeli zdefiniujesz kilka stałych, które reprezentują związane ze sobą wartości, także rozważ zamianę ich na wyliczenie. Dzięki temu możesz skorzystać z ulepszonego sprawdzania typów języka Visual Basic oraz funkcji IntelliSense.
Rozdział 15.
Typy danych, zmienne i stałe
333
Anonimowe typy danych Anonimowy typ danych to obiektowy typ danych, tworzony automatycznie przez Visual Basic, któremu nigdy nie jest nadawana nazwa. Jest on niejawnie używany przez potrzebujący go kod, a następnie usuwany. Poniższy kod pobiera za pomocą LINQ dane z tablicy obiektów BookInfo o nazwie m_BookInfo. Najpierw za pomocą zapytania LINQ zapełnia zmienną book_query wybranymi książkami. Następnie iteruje przez wyniki zapisane w tej zmiennej, dodając informacje o książkach do łańcucha. Na zakończenie wyświetla ten łańcuch w polu tekstowym. Dim book_query = _ From book In m_BookInfo _ Where book.Year > 1999 _ Select book.Title, book.Pages, book.Year _ Order By Year Dim txt As String = "" For Each book In book_query txt & = book.Title & " (" book.Pages & " pages)" Next book txtResult.Text = txt
& &
book.Year & ", " ControlChars.CrLf
&
_
Zmienna book_query jest uporządkowaną sekwencją obiektów, która zawiera dane wybrane przez zapytanie — Title, Pages oraz Year. Tego typu obiekt nie posiada żadnej jawnej definicji — jest anonimowym typem utworzonym przez Visual Basic do przechowania wybranych wartości Title, Pages i Year. Jeśli w edytorze kodu nad zmienną book_query znajdzie się kursor, pojawi się chmurka informująca, że typ tej zmiennej to: System.Linq.IOrderedSequence(Of
< anonymous type > )
Dalej znajduje się pętla For Each, która przechodzi przez obiekty znajdujące się w zmiennej book_query. Zmienna pętlowa book musi być tego samego typu co elementy sekwencji. Kod nie określa bezpośrednio typu danych tej zmiennej, a więc Visual Basic może się go domyślić. Jeśli najedziemy kursorem na zmienną book w edytorze kodu, pojawi się chmurka określająca jej typ danych jako: < anonymous type >
Typów anonimowych nie należy używać jawnie. Na przykład nie powinno być potrzeby zadeklarowania nowego obiektu anonimowego typu. Ich przeznaczeniem jest wspomaganie LINQ. Mimo że typów anonimowych nie używa się jawnie, warto wiedzieć, czym one są. W tym przykładzie Visual Basic wnioskuje typ danych zmiennych book_query i book. Jest to ważne, ponieważ zmienne te mają typy anonimowe, przez co nie można jawnie określić ich typu. Ponieważ te typy danych są wnioskowane, kod zadziała tylko po włączeniu opcji Option Infer.
Więcej informacji na temat LINQ znajduje się w rozdziale 21. — „LINQ”.
334
Część II
Wstęp do języka Visual Basic
Typy dopuszczające wartość pustą W większości relacyjnych baz danych istnieje pojęcie wartości pustej (ang. null). Oznacza ona, że konkretne pole nie zawiera żadnych danych. Dzięki niej możliwe jest odróżnienie prawidłowej wartości zero lub innych wartości bez wyrazu od nieistniejących. Na przykład wartość pusta w polu tekstowym oznacza, że nie ma w nim żadnych danych, zaś wartość bez wyrazu — że pole to zawiera jakąś wartość, która nic nie reprezentuje. Aby utworzyć zmienną dopuszczającą wartości puste (ang. nullable variable), należy przed jej nazwą lub nazwą jej typu postawić znak zapytania. Można także zadeklarować zmienną jako typu Nullable(Of typ). Na przykład poniższy kod deklaruje trzy zmienne typu Integer, które dopuszczają wartości puste: Dim i As Integer? Dim j? As Integer Dim k As Nullable()Of Integer)
Aby zmienna dopuszczająca wartości puste miała wartość pustą (null), należy ustawić ją na wartość Nothing. Poniższy kod sprawia, że zmienna num_choices jest pusta: num_choices = Nothing
Aby dowiedzieć się, czy zmienna dopuszczająca wartości puste zawiera jakąś wartość, należy porównać ją z wartością Nothing za pomocą operatora Is. Poniższy kod sprawdza, czy zmienna dopuszczająca wartość pustą o nazwie num_choices zawiera jakąś wartość. Jeśli tak, zostanie ona powiększona. W przeciwnym przypadku będzie ustawiona na 1. If num_choices IsNot Nothing Then num_choices += 1 Else num_choices = 1 End If
Obliczenia z użyciem zmiennych dopuszczających wartości puste są przeprowadzane zgodnie z zasadami propagacji wartości pustych, co gwarantuje, że wynik jest zawsze sensowny. Jeśli na przykład jakaś zmienna typu Integer nie posiada wartości, nie ma sensu dodawać do niej innej wartości (ile wynosi wartość pusta + 3?). Jeśli jeden lub więcej operandów wyrażenia zawiera wartość pustą, wynik jest wartością pustą. Gdyby na przykład zmienna num_choices z poprzedniego przykładu zawierała wartość pustą, działanie num_choices + 1 również zwróciłoby wartość pustą (dlatego właśnie poprzedni kod przed zwiększeniem wartości zmiennej num_choices sprawdza, czy nie jest ona pusta). Typy dopuszczające wartość pustą zostały zademonstrowane w programie NullableTypes, który można pobrać z serwera FTP wydawnictwa Helion.
Rozdział 15.
Typy danych, zmienne i stałe
335
Stałe Pod wieloma względami stała przypomina zmienne tylko do odczytu. Zarówno w deklaracjach zmiennych, jak i stałych można używać atrybutów, słów kluczowych określających dostępność oraz wyrażeń inicjujących. Zmienne tylko do odczytu i stałe reprezentują wartości, których nie można zmienić po przypisaniu. Składnia deklaracji stałej jest następująca: [ lista_atrybutów ] [ dostępność ] [Shadows] _ Const nazwa [As typ ] = wyrażenie_inicjujące
Ogólne informacje na temat znaczenia poszczególnych części deklaracji stałych znajdują się we wcześniejszym podrozdziale, którego tytuł brzmi „Deklarowanie zmiennych”. W kolejnych podrozdziałach opiszę tylko różnice pomiędzy deklaracjami zmiennych tylko do odczytu i stałych.
Dostępność Gdy deklaruje się zmienną, można opuścić słowo kluczowe Dim, jeśli użyje się jednego ze słów kluczowych Public, Protected, Friend, Protected Friend, Private, Static lub ReadOnly. W deklaracji stałej nie wolno opuścić słowa kluczowego Const, ponieważ informuje ono Visual Basic, że deklarowana jest stała, a nie zmienna. W deklaracjach stałych nie można używać słów kluczowych Static, ReadOnly oraz Shared. Static oznacza, że z czasem wartość zmieni się i powinna ona zostać przechowana pomiędzy zakończeniem działania procedury a jej ponownym uruchomieniem. Ponieważ wartości stałej nie da się zmienić, funkcjonalność ta traci sens. Słowo kluczowe ReadOnly byłoby zbędne, ponieważ wartości stałej i tak nie można zmienić. Słowo kluczowe Shared oznacza, że zmienna klasowa jest współdzielona przez wszystkie egzemplarze tej klasy. Jeśli jeden obiekt zmodyfikuje tę wartość, wszystkie pozostałe dostrzegą tę zmianę. Ponieważ wartości stałej nie można zmienić, nie ma potrzeby jej współdzielić. Wszystkie obiekty cały czas mają do dyspozycji tę samą wersję stałej. Można ją uważać za współdzieloną cały czas. W deklaracjach stałych można używać wszystkich pozostałych słów kluczowych, które określają dostępność: Public, Protected, Friend, Protected Friend oraz Private.
Typ stałej Jeśli włączona jest opcja Option Strict, konieczne jest określenie typu stałej. Może ona być typu wbudowanego (Boolean, Byte, Short, Integer, Long, Decimal, Single, Double, Char, String, Date lub Object) lub wyliczeniowego. Nie da się zadeklarować stałej, która jest klasą, strukturą lub tablicą.
336
Część II
Wstęp do języka Visual Basic
Jeśli stała zostanie zadeklarowana jako typ danych Object, wyrażenie_inicjujące, będzie musiała ustawić ten obiekt na wartość Nothing. Jeżeli potrzebna jest stała reprezentująca jakiś inny obiekt albo strukturę, klasę lub tablicę, należy zamiast stałej użyć zmiennej tylko do odczytu. Ponieważ ogólna klasa Object nie zgłasza żadnych zdarzeń i nie można utworzyć stałej innej klasy, w deklaracjach stałych nie ma sensu używać słowa kluczowego WithEvents. Mimo iż Visual Basic wnioskuje typy zmiennych lokalnych, nie robi tego dla stałych. Jeśli opcja Option Strict jest włączona, wszystkie stałe muszą mieć bezpośrednio określone typy.
Wyrażenie_inicjujące Wyrażenie_inicjujące przypisuje stałej jej nigdy niezmieniającą się wartość. W wyrażeniu tym nie da się stosować zmiennych, ale można używać funkcji konwertujących typu CInt, a także wartości wcześniej zdefiniowanych stałych i wartości wyliczeniowych. Wyrażenie może zawierać takie znaki, jak # lub &H. Jeśli deklaracja nie określa bezpośrednio typu (a opcja Option Explicit jest wyłączona), typ wartości określa typ stałej.
Przeanalizujmy poniższy przykładowy kod. Pierwsza instrukcja konwertuje za pomocą funkcji CInt wartość 123.45 na stałą typu Integer. Instrukcje druga i trzecia ustawiają wartości dwóch stałych typu Long na liczby szesnastkowe. Kolejna instrukcja tworzy kombinację wartości dwóch poprzednich instrukcji za pomocą operatora bitowego Or. Ostatnia z poniższych instrukcji ustawia stałą na wartość zdefiniowaną przez typ wyliczeniowy AccessLevel. Private Private Private Private Private
Const Const Const Const Const
MAX_VALUES As Integer = CInt(123.45) MASK_READ As Long = & H1000 & MASK_WRITE As Long = & H2000 & MASK_READ_WRITE As Long = MASK_READ Or MASK_WRITE MAX_ACCESS_LEVEL As AccessLevel = AccessLevel.SuperUser
Delegaty Delegat to obiekt odwołujący się do jakiejś podprocedury, funkcji lub innej metody. Metoda ta może być metodą egzemplarza udostępnianą przez obiekt, współdzieloną metodą klasową lub zdefiniowaną w module kodu. Zmienna delegatowa zachowuje się jak wskaźnik do podprocedury lub funkcji. Zmienne delegatowe czasami nazywane są bezpiecznymi dla typów wskaźnikami do funkcji (ang. type-safe function pointer). Słowo kluczowe Delegate definiuje klasę delegatu oraz wyznacza parametry i typ zwrotny metody, do której będzie się on odwoływał.
Rozdział 15.
Typy danych, zmienne i stałe
337
W poniższym kodzie instrukcja Delegate deklaruje StringDisplayerType jako delegat do podprocedury przyjmującej łańcuch jako parametr. Dalej zmienna m_DisplayStringRoutine zostaje zadeklarowana jako tego właśnie typu. Zmienna ta może przechowywać referencję do podprocedury przyjmującej parametr łańcuchowy. Następnie zostaje ona ustawiona na podprocedurę ShowStringInOutputWindow. Na końcu znajduje się wywołanie podprocedury tego delegatu z łańcuchem jako parametrem. ‘ Definicja delegatu StringDisplayerType jako wskaźnika do podprocedury ‘ pobierającej parametr łańcuchowy. Private Delegate Sub StringDisplayerType(ByVal str As String) ‘ Deklaracja zmiennej StringDisplayerType. Dim m_DisplayStringRoutine As StringDisplayerType ‘ Przypisanie tej zmiennej do podprocedury. m_DisplayStringRoutine = AddressOf ShowStringInOutputWindow ‘ Wywołanie podprocedury tego delegatu. m_DisplayStringRoutine("Witaj świecie")
Zaprezentowany w powyższym przykładzie delegat zawiera referencję do podprocedury zdefiniowanej w module kodu. Delegat może także przechowywać adres współdzielonej metody klasowej lub metody egzemplarza. Wyobraźmy sobie na przykład, że klasa Employee zawiera definicję współdzielonej funkcji GetNumEmployees, która zwraca liczbę załadowanych pracowników. Załóżmy również, że klasa ta zawiera definicję funkcji egzemplarza o nazwie ToString, zwracającej imię i nazwisko obiektów Employee. Poniżej znajduje się kod źródłowy programu UseDelegates, do pobrania z internetu, demonstrujący użycie obu tych funkcji: Dim emp As New Employee("Rod", "Stephens") ‘ Delegat wskazujący na współdzieloną metodę klasową. Private Delegate Function NumEmployeesDelegate() As Integer Private Sub btnShared_Click() Handles btnShared.Click Dim show_num As NumEmployeesDelegate show_num = AddressOf Employee.GetNumEmployees MessageBox.Show(show_num().ToString, "# Employees") End Sub ‘ Delegat wskazujący na klasową metodę egzemplarza. Private Delegate Function GetNameDelegate() As String Private Sub btnInstance_Click() Handles btnInstance.Click Dim show_name As GetNameDelegate show_name = AddressOf emp.ToString MessageBox.Show(show_name(), "Name") End Sub
Najpierw zostaje zadeklarowany i zainicjowany obiekt klasy Employee o nazwie emp. Dalej znajduje się definicja delegatu o nazwie NumEmployeesDelegate, który jest wskaźnikiem na funkcję zwracającą liczbę całkowitą. Procedura obsługi zdarzeń o nazwie btnShared_Click deklaruje zmienną tego typu, ustawia ją na adres współdzielonej funkcji klasy Employee o nazwie GetNumEmployees oraz wywołuje tę funkcję. Następnie zostaje zdefiniowany delegat
338
Część II
Wstęp do języka Visual Basic
o nazwie GetNameDelegate, który jest wskaźnikiem na funkcję zwracającą łańcuch. Zmienną tego typu deklaruje procedura obsługi zdarzeń o nazwie btnInstance_Click. Następnie ustawia ona adres obiektu emp na funkcję ToString i wywołuje tę funkcję. Przykłady te są nieco naciągane, ponieważ podprocedury te i funkcje można by z łatwością wywołać bezpośrednio, bez używania żadnych delegatów. Pokazują one jednak, jak program może zapisać delegat wskazujący na podprocedurę lub funkcję, a później ją wywołać. W prawdziwej aplikacji wartość zmiennej delegatowej zostałaby ustawiona i użyta znacznie później. Jedna zmienna delegatowa może przechowywać referencje do różnych metod, w zależności od sytuacji w programie. Na przykład mogą istnieć różne podprocedury do wysyłania danych do formularza, drukarki lub mapy bitowej. Program może ustawić zmienną delegatowi na każdą z tych procedur. Później aplikacja będzie mogła wywołać procedurę tej zmiennej, przy czym nie będzie musiała wiedzieć, która procedura zostanie rzeczywiście wykonana. Inną przydatną techniką jest przekazanie zmiennej delegatowej do podprocedury lub funkcji. Wyobraźmy sobie na przykład, że piszemy podprocedurę sortującą tablicę obiektów klasy Customer. Procedura ta mogłaby jako parametr przyjmować zmienną delegatową, wskazującą na funkcję, która ma zostać użyta do porównywania sortowanych obiektów. Gdy przekazuje się do tej procedury różne funkcje, można wymusić sortowanie według nazwy firmy, kontaktu, identyfikatora, wielkości sprzedaży itd. Delegaty sprawiają wiele problemów niejednemu programiście, ale warto włożyć nieco więcej wysiłku, aby je opanować. Nadają one nowy wymiar programowaniu, pozwalają operować podprocedurami i funkcjami w taki sposób, jakby były one zwykłymi danymi.
Konwencje nazewnicze Wiele zespołów programistycznych przyjmuje pewne konwencje nazewnicze, aby ujednolicić swój kod i ułatwić jego czytanie. W ten sposób opracowano wiele konwencji — ciężko powiedzieć, która z nich jest najlepsza. Nie ma wielkiego znaczenia, którą z nich zastosujesz. Ważne, by opracować zestaw reguł, a potem zawsze się ich trzymać. Jedną z prostych konwencji jest używanie małych_liter_ze_znakami_podkreślenia w nazwach zmiennych o zasięgu procedurowym, LiterRóżnejWielkości dla zmiennych o zasięgu modułowym i globalnym oraz samych WIELKICH_LITER dla wszystkich stałych. Do odróżniania zasięgu modułowego i globalnego można używać przedrostków m_ i g_, a typ danych obiektów dobrze jest określać za pomocą skrótów. Na przykład poniżej znajduje się definicja zmiennej klasy PictureBox z zasięgiem modułowym: Private m_picCanvas As PictureBox
Nazwy procedur są z reguły pisane literami o różnej wielkości.
Rozdział 15.
Typy danych, zmienne i stałe
339
Wielu programistów posuwa się nawet dalej — stosuje przedrostki, które określają skrótowo typ dla wszystkich zmiennych, nie tylko obiektowych. Na przykład poniższa instrukcja deklaruje zmienną typu Integer: Dim iNumEmployees As Integer
Gdy będzie się ściśle trzymać tych zasad, nigdy nie powinno się stanąć przed koniecznością przypisania wartości jednej zmiennej do wartości innej, chyba że obie mają taki sam skrót typu. Jeśli zauważysz instrukcję z pomieszanymi typami zmiennych, dokładnie zbadaj, czy w kodzie nie ma jakiegoś problemu ze złym dopasowaniem typów danych. Na przykład poniższa instrukcja powinna wzbudzić podejrzenie programisty, ponieważ przypisuje ona wartość typu Integer do zmiennej typu Long: mlngNumEmployees = intNumAbsent + intNumPresent
Niektórzy programiści rozszerzają te zasady na wszystkie obiekty programistyczne, łącznie z funkcjami i podprocedurami. Na przykład globalna funkcja zwracająca łańcuch mogłaby się nazywać gstrGetWebmasterName. Ogólnie rzecz biorąc, te wszystkie informacje o zasięgu i typie zmiennej nabierają tym większego znaczenia, im dalej od swojej deklaracji jest używana zmienna. Jeśli zostanie ona zadeklarowana w podprocedurze, programista zazwyczaj będzie w stanie zapamiętać jej typ. Jeśli ma wątpliwości, zawsze może spojrzeć wyżej i upewnić się. Jeśli natomiast zmienna zostanie zadeklarowana globalnie w jakimś rzadko odwiedzanym przez programistów module, być może będą oni mieli problem z zapamiętaniem jej zasięgu i typu. W takim przypadku przydają się przedrostki, które pomagają w zapamiętaniu najważniejszych informacji o zmiennej. Bez względu na zastosowaną konwencję nazewniczą najważniejszym elementem nazwy jest jej część opisowa. Nazwa mblnDL informuje, że jest to wartość logiczna z zasięgiem modułowym, ale nic nie mówi o znaczeniu tej wartości (zmienne z takimi okropnymi nazwami zdarzają się bardzo często). Znacznie lepsza jest nazwa mblnDataIsLoaded. Jeszcze nigdy nie spotkałem się z sytuacją, w której projekt straciłby coś z powodu braku przedrostków typu mbln. Widziałem natomiast wielu programistów, którzy tracili mnóstwo czasu, ponieważ opisowe części nazw ich zmiennych były niejasne. Opracowanie ogólnych konwencji nazewniczych, które definiują skróty dla nazw wszystkich ważniejszych typów danych, kontrolek, obiektów, komponentów baz danych, menu, stałych i procedur, zajęłoby bardzo dużo czasu i znacznie więcej miejsca, niż można na to poświęcić w takiej książce, jak ta. Na stronie http://support.microsoft.com/default.aspx?scid=kb;en-us;110264 znajduje się artykuł opisujący konwencje używane przez Microsoft Consulting Services. Wyjaśniono w nim wszystko, łącznie ze skrótami typów danych, stosowaniem w pierwszej części nazwy funkcji czasowników (GetUserName zamiast UserName) oraz konwencjami dotyczącymi komentarzy. Dzięki zastosowaniu konwencji nazewniczych i programistycznych kod jest znacznie łatwiejszy do odczytania dla innych programistów. Możesz stosować te zalecane przez Microsoft lub poszukać w sieci innych. Wybierz najlepsze Twoim zdaniem zasady, a odrzuć te, które uważasz za nieprzydatne. Bardziej ważne jest to, by pisać spójny kod, niż stosować jakiś konkretny zestaw zasad.
340
Część II
Wstęp do języka Visual Basic
Podsumowanie Dwie najważniejsze cechy określane w deklaracji zmiennej to jej typ danych oraz widoczność. Na tę ostatnią składają się zasięg (część kodu zawierająca zmienną, jak pętla For, podprocedura czy moduł), dostępność (kod, który może uzyskać dostęp do zmiennej wyznaczany przez słowa kluczowe typu Private, Public i Friend) oraz czas życia (czas od utworzenia zmiennej do jej zniszczenia). Aby uniknąć nieporozumień, określaj typ danych, gdy tylko jest to możliwe, staraj się też nadawać zmiennym jak najmniejszy zasięg. Włącz opcje Option Explicit i Option Strict w celu uzyskania pomocy od IDE na temat potencjalnych błędów związanych z zasięgiem i typem zmiennych, zanim zaczną sprawiać prawdziwe problemy. Użycie zapytań LINQ komplikuje nieco kod. Przy użyciu tej technologii nie jest możliwe bezpośrednie zadeklarowanie typu danych każdej zmiennej. Zapytania LINQ zwracają zestawy obiektów typu anonimowego. W czasie iteracji przez te obiekty zmienna pętlowa będzie miała taki sam jak one typ. Gdy nie można jawnie zadeklarować typu zmiennej, należy jeszcze bardziej uprościć kod, aby w późniejszym czasie łatwo go było naprawić i ulepszać. Parametry, procedury własności i stałe podlegają podobnym zasadom, jeśli chodzi o typ danych i zasięg. Gdy zapoznasz się technikami deklarowania zmiennych, nie powinny one sprawiać Ci większych problemów. Jedną z najważniejszych czynności, które można wykonać, by sprawić, że kod będzie łatwiejszy do debugowania i w utrzymaniu, jest zadbanie o zachowanie jego spójności. Pomocne jest zastosowanie dobrych konwencji nazewniczych. Przejrzyj wytyczne stosowane przez Microsoft Consulting Services i przyjmij te zasady, które najbardziej Ci odpowiadają. Gdy już wiesz, jak deklarować zmienne, możesz zacząć naukę wykonywania na nich działań. W rozdziale 16. — „Operatory” — opiszę symbole (typu +, - oraz ^), za pomocą których można wykonywać działania przy użyciu różnych zmiennych.
16
Operatory Operator jest podstawowym elementem kodu, który wykonuje działania na jednej (lub większej liczbie) wartości w celu wygenerowania wyniku. Wartości, którymi operuje operator, noszą nazwy operandów. Na przykład w poniższej instrukcji operatorem jest znak + (dodawanie), operandami B i C, a wynik zostaje przypisany do A: A = B + C
Operatory w języku Visual Basic dzielą się na pięć kategorii — arytmetyczne, konkatenacji, porównywania, logiczne i bitowe. W tym rozdziale najpierw opiszę wymienione kategorie operatorów, a następnie inne związane z nimi zagadnienia, jak priorytety operatorów, operatory przypisania oraz przeciążanie operatorów. Dodatkowo przedstawię pewne wyspecjalizowane problemy związane z łańcuchami i datami.
Operatory arytmetyczne W poniższej tabeli znajdują się opisy operatorów arytmetycznych, które są dostępne w języku Visual Basic. Większość programistów powinna znać prawie wszystkie z nich. Cztery operatory, które mogą wymagać dodatkowych wyjaśnień, to: \, Mod, << oraz >>. Operator
Zastosowanie
Przykład
Wynik
^
Potęgowanie
2 ^ 3
(2 do potęgi 3) = 2 * 2 * 2 = 8
-
Negacja
-2
–2
*
Mnożenie
2 * 3
6
/
Dzielenie
3 / 2
1.5
\
Dzielenie całkowitoliczbowe
17 \ 5
3
342
Część II
Wstęp do języka Visual Basic
Operator
Zastosowanie
Przykład
Wynik
Mod
Reszta z dzielenia
17 Mod 5
2
+
Dodawanie
2 + 3
5
-
Odejmowanie
3 - 2
1
<<
Bitowe przesunięcie w lewo
10110111 << 1
01101110
>>
Bitowe przesunięcie w prawo
10110111 >> 1
01011011
Operator \ wykonuje działanie dzielenia całkowitoliczbowego. Zwraca wynik dzielenia pierwszego operandu przez drugi po opuszczeniu reszty. Należy pamiętać, że wynik nie jest zaokrąglany, a odrzucana jest tylko reszta z dzielenia. Na przykład 7\4=1, a -7\4 = -1, nie 2 i -2, jak można by się spodziewać. Operator Mod zwraca resztę z dzielenia pierwszego operandu przez drugi. Na przykład 17 Mod 5 = 2, ponieważ 17 = 3*5+2. Operator << przesuwa bity wartości całkowitoliczbowej w lewo i dopełnia puste bity po prawej zerami. Na przykład wartość bitowa 10110111 po przesunięciu o jeden bit w lewo wynosi 01101110. Przesunięcie wartości 10110111 o dwa bity w lewo daje wartość 11011100. Operator >> przesuwa bity w prawo i dopełnia puste bity po lewej zerami. Na przykład wartość bitowa 10110111 po przesunięciu o jeden bit w prawo wynosi 01011011. Przesunięcie tej wartości w prawo o dwa bity daje wynik 00101101. Niestety stosowanie wartości bitowych w języku Visual Basic nie jest łatwe, ponieważ nie można korzystać w kodzie z wartości binarnych typu 10110111. W zamian trzeba użyć wartości szesnastkowej &HB7 lub dziesiętnej 183. Ostatnie dwie pozycje tabeli przedstawiają te wartości w zapisie binarnym, dzięki czemu łatwiej zrozumieć zasadę działania przesunięcia bitowego. Aplikacją, która pozwala na łatwą konwersję pomiędzy binarnym, ósemkowym, szesnastkowym i dziesiętnym zapisem, jest Kalkulator systemu Windows. Aby ją włączyć, należy przejść do menu Start/Wszystkie programy/Akcesoria i kliknąć pozycję Kalkulator. W menu Widok zaznaczyć opcję Naukowy. Kliknąć przycisk radiowy Hex, Dec, Oct lub Bin, wprowadzić jakąś wartość, a następnie kliknąć inny przycisk radiowy, aby przekonwertować tę wartość.
Operatory konkatenacji W języku Visual Basic dostępne są dwa operatory konkatenacji — + i &. Obydwa łączą łańcuchy. Ponieważ symbol + oznacza także dodawanie, kod będzie bardziej przejrzysty, jeśli do konkatenacji będzie użyje się wyłącznie operatora &. Ponadto kod z użyciem operatora & jest szybszy i powoduje mniej problemów, ponieważ informuje Visual Basic, że jego operandy są łańcuchami.
Rozdział 16.
Operatory
343
Operatory porównywania Operatory porównywania porównują jedną wartość z inną i zwracają wartość logiczną (True lub False), zależną od wyniku tego porównywania. Poniższa tabela przedstawia operatory porównywania dostępne w języku Visual Basic. Zastosowanie pierwszych sześciu z nich jest względnie proste. Należy zauważyć, że Not nie jest operatorem porównywania, przez co nie znajdziesz go na tej liście. Zostanie on opisany w kolejnym podrozdziale — „Operatory logiczne”. Operator
Zastosowanie
Przykład
Wynik
=
Równy
A = B
True, jeśli A równa się B.
<>
Nierówny
A <> B
True, jeśli A nie równa się B.
<
Mniejszy niż
A < B
True, jeśli A jest mniejszy od B.
<=
Mniejszy bądź równy
A <= B
True, jeśli A jest mniejszy bądź równy B.
>
Większy niż
A > B
True, jeśli A jest większy od B.
=>
Większy bądź równy
A >= B
True, jeśli A jest większy bądź równy B.
Is
Równość dwóch obiektów
emp Is mgr
True, jeśli zmienne emp i mgr odwołują się do tego samego obiektu.
IsNot
Nierówność dwóch obiektów
emp IsNot mgr
True, jeśli zmienne emp i mgr odwołują się do różnych obiektów.
TypeOf…Is
Obiekt ma określony typ
TypeOf(obj) Is Manager
True, jeśli obj wskazuje na obiekt klasy Manager.
Like
Pasuje do wzorca tekstowego
A like "###-####"
True, jeśli A zawiera trzy cyfry, myślnik i cztery cyfry.
Operator Is zwraca wartość True, jeśli obydwa jego operandy odwołują się do tego samego obiektu. Jeżeli na przykład zostanie utworzony obiekt klasy Order i dwie zmienne A i B, które na niego wskazują, wyrażenie A Is B zwraca wartość True. Należy pamiętać, że operator Is zwraca wartość False, jeśli jego operandy wskazują różne obiekty klasy Order, które przypadkowo mają takie same wartości własności. Operator IsNot jest krótszą formą zapisu mniej zgrabnej konstrukcji Not…Is. Na przykład instrukcja A IsNot Nothing jest równoznaczna z Not (A Is Nothing). Nothing to specjalna wartość, która oznacza nieobiekt. Gdy ma się zmienną obiektową, można — używając operatora IsNot — porównać ją do wartości Nothing, aby sprawdzić, czy reprezentuje ona cokolwiek. Pamiętajmy, że nie można za pomocą operatora Is ani IsNot porównać zmiennej obiektowej z wartością 0 lub jakąkolwiek inną wartością liczbową. Is i IsNot można używać wyłącznie z takimi obiektami, które są przechowywane w zmiennych i ze specjalną wartością Nothing.
344
Część II
Wstęp do języka Visual Basic
Operator TypeOf zwraca wartość True, jeśli jego operand ma określony typ. Jest on szczególnie przydatny w podprocedurach przyjmujących parametr, który może należeć do więcej niż jednego typu obiektowego. Za pomocą TypeOf można sprawdzić, jaki typ obiektu został przekazany w takiej podprocedurze. Operator Like zwraca wartość True, jeśli jego pierwszy operand pasuje do wzorca zdefiniowanego przez drugi. Jeżeli wzorzec zawiera zwykłe znaki, łańcuch musi dokładnie do nich pasować. Wzorzec może też zawierać sekwencje kilku znaków specjalnych, które przedstawiono w poniższej tabeli. Znak(i)
Znaczenie
?
Dopasowuje dowolny pojedynczy znak.
*
Dopasowuje zero lub więcej znaków.
#
Dopasowuje dowolną jedną cyfrę.
[znaki]
Dopasowuje dowolny ze znaków znajdujących się w nawiasach kwadratowych.
[!znaki]
Dopasowuje każdy znak, który nie znajduje się w nawiasach kwadratowych.
A-Z
Jeśli znajduje się w nawiasach kwadratowych, dopasowuje każdy znak z przedziału od A do Z.
W nawiasach kwadratowych można tworzyć kombinacje przedziałów znaków i pojedynczych znaków. Na przykład wzorzec [a-zA-Z] dopasowuje każdą literę z przedziału od a do z i od A do Z. W poniższej tabeli przedstawiono kilka przydatnych wzorców. Wzorzec
Znaczenie
[2-9] ##-####
Siedmiocyfrowy numer telefoniczny.
[2-9]##-[2-9]##-####
Dziesięciocyfrowy numer telefoniczny z numerem kierunkowym.
1-[2-9]##-[2-9]##-####
Jedenastocyfrowy numer telefoniczny, zaczynający się od cyfry 1 i numeru kierunkowego.
#####
Pięciocyfrowy kod pocztowy.
#####-####
Pięciocyfrowy kod pocztowy + cztery cyfry.
?*@?*.?*
Adres e-mail.
Wzorce te oczywiście nie zapewniają pełnej ochrony przed błędami. Na przykład wzorzec adresu e-mail sprawdza, czy adres ten składa się z przynajmniej jednego znaku, małpy, znowu jednego znaku, kropki i jeszcze jednego znaku. „Przepuszcza” na przykład adres [email protected]. Nie sprawdza jednak, czy wszystkie części adresu są poprawne, to znaczy akceptuje także adres [email protected] oraz pozwala na użycie więcej niż jednego znaku @ — [email protected]@bad_value. Znacznie szersze możliwości dopasowywania wzorców oferują wyrażenia regularne. Więcej informacji na ich temat znajduje się w podrozdziale „Wyrażenia regularne” rozdziału 39. — „Przydatne przestrzenie nazw”.
Rozdział 16.
Operatory
345
Operatory logiczne Operatory logiczne tworzą kombinację dwóch wartości logicznych. W zależności od wyniku zwracają wartość True lub False. W poniższej tabeli przedstawiono operatory logiczne języka Visual Basic. Operator
Zastosowanie
Przykład
Wynik
Not
Negacja logiczna lub bitowa.
Not A
True, jeśli A ma wartość False.
And
Logiczne lub bitowe And.
A And B
True, jeśli A i B mają wartości True.
Or
Logiczne lub bitowe Or.
A Or B
True, jeśli A lub B lub obie wartości są True.
Xor
Logiczne lub bitowe Or wykluczające.
A Xor B
True, jeśli A lub B, ale nie obie, jest True.
AndAlso
Logiczne lub bitowe And ze skróconym sposobem wyznaczania wartości wyrażenia.
A AndAlso B
True, jeśli A i B są True
Logiczne lub bitowe Or ze skróconym sposobem wyznaczania wartości wyrażenia.
A OrElse B
OrElse
(zobacz komentarze). True, jeśli A lub B lub obie są True (zobacz poniższe komentarze).
Zastosowanie operatorów Not, And i Or jest proste. Operator Xor to skrót od słów exclusive or (wykluczające Or). Operator ten zwraca wartość True, jeśli jeden — i tylko jeden — z jego operandów ma wartość True. Wyrażenie A Xor B posiada wartość True, jeśli A ma wartość True lub B ma wartość True, ale obydwa naraz nie mają wartości True. Operatory AndAlso i OrElse działają podobnie do And i Or — tyle że stosują skrócony sposób wyznaczania wartości wyrażenia. Oznacza to, że Visual Basic może przestać obliczać dalej operandy, jeśli jest w stanie odgadnąć wynik końcowy bez wykonywania tej czynności. Weźmy na przykład wyrażenie A AndAlso B. Jeśli Visual Basic wyznaczy wartość operandu A, która będzie False, wiadomo, że całe wyrażenie będzie miało wartość False, bez względu na wartość operandu B, który w takim przypadku nie jest wyznaczany. Jeśli operandy A i B są prostymi zmiennymi logicznymi, nie ma żadnego znaczenia, czy Visual Basic wyznaczy ich wartości, czy nie. Wyobraźmy sobie jednak, że są to czasochłonne funkcje. W poniższym kodzie funkcja TimeConsumingFunction mogłaby musieć wyszukiwać jakieś wartości w bazie danych lub pobierać dane z witryny internetowej. W takim przypadku pominięcie wyznaczania wartości drugiego operandu mogłoby zaoszczędzić dużo czasu. If TimeConsumingFunction("A") AndAlso TimeConsumingFunction("B") Then ...
Podobnie jak operator AndAlso może zatrzymać wyznaczanie wartości drugiego operandu, jeśli jeden ma wartość False, OrElse zatrzyma wyznaczanie wartości, jeżeli jeden z jego
346
Część II
Wstęp do języka Visual Basic
operandów ma wartość True. Wyrażenie A OrElse B ma wartość True, jeśli albo operand A, albo B ma wartość True. Jeżeli program dowie się, że operand A ma wartość True, nie będzie musiał wyznaczać wartości operandu B. Ponieważ operatory AndAlso i OrElse robią to samo co operatory And i Or, tylko czasami szybciej, można zadać pytanie: „Po co w ogóle używać tych drugich?”. Głównym powodem jest możliwość wystąpienia efektu ubocznego. Efekt uboczny to czynność wykonywana przez procedurę, która nie jest jej oczywistą funkcją. Wyobraźmy sobie na przykład, że funkcja NumEmployees otwiera bazę danych pracowników i zwraca liczbę znajdujących się w niej rekordów, po czym pozostawia ją otwartą. To jest właśnie efekt uboczny działania tej funkcji. Teraz wyobraźmy sobie, że funkcja NumCustomers w podobny sposób otwiera bazę danych klientów; przeanalizujmy poniższą instrukcję: If (NumEmployees()
>
0) AndAlso (NumCustomers()
>
0) Then ...
Po wykonaniu tego kodu nie ma pewności, które bazy danych są otwarte. Jeśli funkcja NumEmployees zwróci wartość 0, wartość pierwszego operandu operatora AndAlso będzie False, przez co nie wyznaczy on wartości funkcji NumCustomers, która z kolei z tego powodu nie otworzy bazy danych klientów. W niektórych warunkach operatory AndAlso i OrElse mogą poprawić szybkość działania aplikacji. Nie należy jednak używać ich z operandami, które mają efekty uboczne.
Operatory bitowe Operatory bitowe sposobem działania w dużym stopniu przypominają operatory logiczne, tyle że porównują wartości bit po bicie. Operator negacji bitowej Not zamienia bity swoich operandów z jedynek na zera i odwrotnie. Na przykład: 10110111 Not 01001000
Operator And wstawia w danym miejscu w wyniku jedynkę, jeśli w obu operandach w tym samym miejscu były również jedynki. Poniżej znajduje się wynik połączenia dwóch wartości binarnych za pomocą operatora bitowego And: 10101010 And 00110110 00100010
Operator bitowy Or wstawia w danym miejscu w wyniku jedynkę, jeśli którykolwiek z jego operandów ma w tym miejscu jedynkę. Na przykład: 10101010 Or 00110110 10111110
Operator bitowy Xor wstawia w danym miejscu w wyniku jedynkę, jeśli tylko jeden z jego operandów, nie obydwa, ma w tym miejscu jedynkę. Na przykład:
Rozdział 16.
Operatory
347
10101010 Xor 00110110 10011100
Nie ma bitowych odpowiedników operatorów AndAlso i OrElse.
Priorytety operatorów Gdy Visual Basic wyznacza wartość złożonego wyrażenia, musi zastosować określoną kolejność wykonywania poszczególnych operatorów. Przeanalizujmy na przykład wyrażenie 1 + 2 * 3 / 4 + 2. Poniżej znajdują się trzy możliwe kolejności obliczania jego części; dają one trzy różne wyniki: 1 + (2 * 3) / (4 + 2) = 1 + 6 / 6 = 2 1 + (2 * 3 / 4) + 2 = 1 + 1.5 + 2 = 4.5 (1 + 2) * 3 / (4 + 2) = 3 * 3 / 6 = 1.5
Priorytety operatorów określają, które operatory są wykonywane przed którymi. Na przykład zgodnie z zasadami języka Visual Basic operacje mnożenia i dzielenia są wykonywane przed dodawaniem, dlatego poprawny jest wynik drugiego z przedstawionych równań. W poniższej tabeli przedstawiono operatory w kolejności odpowiadającej ich priorytetom. Podczas wyznaczania wartości wyrażenia program najpierw oblicza działania operatorów znajdujących się najwyżej, a następnie przechodzi coraz niżej. Jeśli operatory mają taki sam priorytet lub w wyrażeniu użyto tego samego operatora więcej niż jeden raz, obliczane są one w kolejności od lewej do prawej. Operator
Opis
()
Grupowanie (nawiasy okrągłe).
^
Potęgowanie.
-
Negacja.
*, /
Mnożenie i dzielenie.
\
Dzielenie całkowitoliczbowe.
Mod
Reszta z dzielenia.
+, -, +
Dodawanie, odejmowanie oraz konkatenacja.
&
Konkatenacja.
<<, >>
Przesunięcie bitowe.
=, <>, <, <=, >, >=, Like, Is, IsNot, TypeOf...Is
Operatory porównywania.
Not
Logiczna i bitowa negacja.
And, AndAlso
Logiczne i bitowe And z i bez skróconego sposobu wyznaczania wartości wyrażeń.
Xor, Or, OrElse
Logiczne i bitowe Xor oraz Or z i bez skróconego sposobu wyznaczania wartości wyrażeń.
348
Część II
Wstęp do języka Visual Basic
Nawiasy nie są prawdziwymi operatorami, ale mają wyższy priorytet niż wszystkie prawdziwe operatory; zostały tu wymienione, aby lista była pełna. Za pomocą nawiasów można zawsze bezpośrednio określić kolejność wykonywania działań w wyrażeniu. Jeśli są w tej kwestii choćby najmniejsze wątpliwości, w celu ich rozwiania należy zawsze używać nawiasów. Nie powodują one żadnego dodatkowego opóźnienia, a pomagają uniknąć niepotrzebnych nieścisłości.
Operatory przypisania W języku Visual Basic zawsze był dostępny prosty operator przypisania: =. W Visual Basicu .NET dodano kilka nowych operatorów przypisania, obsługujących często spotykane instrukcje, w których wartość jest ustawiana na samą siebie w połączeniu z inną. Na przykład obie poniższe instrukcje dodają do zmiennej iterations wartość 10: iterations = iterations + 10 iterations += 10
‘ Pierwotna składnia. ‘ Nowa składnia.
Wszystkie pozostałe operatory przypisania tworzy się w podobny sposób, poprzez dodanie znaku równości do operatora arytmetycznego. Na przykład wyrażenie A ^= B jest równoważne z A = A ^ B. Nadal można używać pierwotnej składni. Jednak ta nowa czasami ma przewagę nad starą, jeśli chodzi o szybkość działania. Jeśli po lewej stronie przypisania znajduje się coś innego niż prosta zmienna, Visual Basic może zaoszczędzić czas poprzez wyznaczenie tej wartości tylko jeden raz. Na przykład poniższy kod dodaje 0.1 do wartości upustu przyznanego klientowi. Dzięki użyciu operatora += Visual Basic musi tylko jeden raz sprawdzić jej lokalizację. Customers(cust_num).Orders(order_num).Discount += 0.1
W większości aplikacji wydajność jest odpowiednia — bez względu na rodzaj użytej składni operatora przypisania. Zazwyczaj najlepiej sprawdza się użycie tej wersji, która najlepiej pasuje w danym miejscu i jest najłatwiejsza do zrozumienia. Wydajnością należy zacząć się zajmować dopiero wówczas, gdy jest pewne, że są z nią problemy. Pełna lista operatorów przypisania jest następująca: =, ^=, *=, /=, \=, +=, -=, &=, <<= oraz >>=. Jeśli jest włączona opcja Option Strict, zmienne muszą mieć odpowiednie typy danych. Na przykład operator /= zwraca typ Double, przez co nie można go używać z liczbami typu Integer, jak w poniższym przykładzie: Dim i As Integer = 100 i /= 2
‘ Operacja niedozwolona.
Aby wykonać takie działanie, trzeba przedtem przekonwertować wynik na liczbę typu Integer, jak w poniższej instrukcji: i = CInt(i / 2)
Rozdział 16.
Operatory
349
Ma to sens, ponieważ próbujemy przypisać wartość dzielenia zmiennoprzecinkowego do zmiennej typu całkowitoliczbowego. Nie jest już tak oczywiste, dlaczego poniższy kod jest niepoprawny. W tym przypadku program próbuje przypisać wynik typu Integer do zmiennej typu Single, a więc wydaje się, że wszystko powinno być w porządku. Przecież wartość typu Integer mieści się w typie Single. Dim x As Single x \= 10
‘ Operacja niedozwolona.
Problem nie leży w przypisaniu, a w wykonaniu działania. Poniższa instrukcja jest równoważna z poprzednią — i również jest niepoprawna: x = x \ 10
‘ Operacja niedozwolona.
Problem w obu tych instrukcjach polega na tym, że operator \ przyjmuje jako argumenty dwie liczby całkowite. Jeśli włączona jest opcja Option Strict, program nie przekonwertuje automatycznie zmiennej zmiennoprzecinkowej na całkowitą. Aby zmusić tę instrukcję do działania, należy ręcznie przekonwertować tę zmienną na typ Integer, jak poniżej: x = CLng(x) \ 10
‘ Operacja dozwolona.
Zarówno operator +=, jak i &= łączy łańcuchy, ale ten drugi jest mniej dwuznaczny, a więc powinien być używany zawsze, kiedy to możliwe. Jego zastosowanie może dodatkowo zwiększyć szybkość działania programu, ponieważ bezpośrednio informuje on Visual Basic, że jego operandy są łańcuchami.
Klasa StringBuilder Operatory & i &= pozwalają połączyć kilka łańcuchów w jeden. Jeśli jednak trzeba połączyć dużą liczbę łańcuchów, można to zrobić szybciej przy użyciu klasy StringBuilder. Została ona zoptymalizowana specjalnie pod kątem długich operacji łączenia wielu łańcuchów w jeden długi łańcuch. W przypadku niewielkich fragmentów kodu różnica pomiędzy typem String a klasą StringBuilder jest niezauważalna. Jeśli planujemy połączyć jeden raz tylko kilkanaście łańcuchów, użycie klasy StringBuilder nie sprawi wielkiej różnicy w szybkości działania programu. Jeżeli natomiast trzeba utworzyć bardzo długi łańcuch z połączenia wielu mniejszych lub prostszych, ale za to wielokrotnie za pomocą pętli, klasa StringBuilder może znacznie przyspieszyć działanie aplikacji. Poniższy kod pochodzi z programu StringBuilderTest1, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Porównuje on prędkości tworzenia długiego łańcucha z wykorzystaniem klasy StringBuilder i bez niej: Private Sub btnGo_Click() Handles btnGo.Click Const ADD_STRING As String = "1234567890" Dim num_trials As Long = Long.Parse(txtNumTrials.Text) Dim start_time As DateTime Dim stop_time As DateTime
350
Część II
Wstęp do języka Visual Basic
Dim elapsed_time As TimeSpan Dim txt As String Dim string_builder As New StringBuilder lblString.Text = "" lblStringBuilder.Text = "" Application.DoEvents() txt = "" start_time = Now For i As Long = 1 To num_trials txt = txt & ADD_STRING Next i stop_time = Now elapsed_time = stop_time.Subtract(start_time) lblString.Text = elapsed_time.TotalSeconds.ToString("0.000000") txt = "" start_time = Now For i As Long = 1 To num_trials string_builder.Append(ADD_STRING) Next i txt = string_builder.ToString() stop_time = Now elapsed_time = stop_time.Subtract(start_time) lblStringBuilder.Text = elapsed_time.TotalSeconds.ToString("0.000000") End Sub
Ten kod łączy wielokrotnie łańcuch 1234567890. Najpierw używa zmiennej typu String, a później obiektu klasy StringBuilder. Test przy użyciu typu String, w którym konkatenacja została wykonana 10 000 razy, a w jej wyniku powstał łańcuch o długości 100 000 znaków, trwał 1,6 sekundy. Gdy skorzystano z klasy StringBuilder, program ukończył operację w ciągu około 0,001 sekundy. Co prawda tworzenie tak długich łańcuchów nie jest zbyt częstym zadaniem programistycznym. Jednak nawet przy krótszych łańcuchach czasami można zauważyć znaczącą różnicę. Przykładowy program StringBuilderTest2, również dostępny w internecie, używa poniższego kodu do połączenia łańcucha 1234567890 100 razy; tworzy łańcuch o długości 1000 znaków. Buduje on go, powtarzając swoje czynności określoną liczbę razy. W jednym teście budowa 1000-znakowego łańcucha 10 000 razy zajęła przy użyciu typu String 0,95 sekundy, podczas gdy zastosowanie klasy StringBuilder skróciło ten czas do 0,06 sekundy. Private Sub btnGo_Click() Handles btnGo.Click Const ADD_STRING As String = "1234567890" Dim num_trials As Long = Long.Parse(txtNumTrials.Text) Dim start_time As DateTime Dim stop_time As DateTime Dim elapsed_time As TimeSpan Dim txt As String Dim string_builder As New StringBuilder lblString.Text = "" lblStringBuilder.Text = "" Application.DoEvents()
Rozdział 16.
Operatory
351
start_time = Now For i As Long = 1 To num_trials txt = "" For j As Long = 1 To 100 txt = txt & ADD_STRING Next j Next i stop_time = Now elapsed_time = stop_time.Subtract(start_time) lblString.Text = elapsed_time.TotalSeconds.ToString("0.000000") txt = "" start_time = Now For i As Long = 1 To num_trials string_builder = New StringBuilder For j As Long = 1 To 100 string_builder.Append(ADD_STRING) Next j txt = string_builder.ToString() Next i stop_time = Now elapsed_time = stop_time.Subtract(start_time) lblStringBuilder.Text = elapsed_time.TotalSeconds.ToString("0.000000") End Sub
Typ String i operacje na łańcuchach są nieco bardziej intuicyjne niż klasa StringBuilder. Dlatego jeśli nie ma problemu z wydajnością, zyskamy dzięki używaniu typu String, ponieważ wtedy kod będzie łatwiejszy do odczytania. Jeżeli konieczne jest tworzenie bardzo długich łańcuchów lub wielokrotne tworzenie długich łańcuchów, komplikacja kodu, spowodowana przez użycie klasy StringBuilder, może być warta oferowanego przez nią zwiększenia wydajności.
Typ Date i klasa TimeSpan Date znacznie różni się od pozostałych typów danych. Działania na większości typów danych zwracają wynik tego samego typu lub innego, ale zgodnego z nim. Na przykład wynikiem odejmowania dwóch liczb typu Integer jest liczba typu Integer. Natomiast wynik podzielenia dwóch liczb typu Integer za pomocą operatora / stanowi liczba typu Double. Nie jest to typ Integer, ale jest on zgodny z tym użytym typem, ponieważ typ Integer nie zawsze może pomieścić wynik dzielenia.
Jeśli jednak odejmiemy od siebie dwie wartości typu Date, wynik nie będzie typu Date. Ile na przykład wynosi 7 sierpnia odjąć 20 lipca? Nie ma sensu traktować wyniku takiego działania jako daty (typu Date). Dlatego w języku Visual Basic różnica pomiędzy dwiema datami jest określana jako obiekt klasy TimeSpan. Odmierza on czas, który dzieli dwie daty. W przypadku działania 7 sierpnia odjąć 20 lipca wynik wynosi 18 dni. Poniższe równania definiują arytmetykę typów Date i klasy TimeSpan:
Date - Date = TimeSpan
Date + TimeSpan = Date
352
Część II
Wstęp do języka Visual Basic
TimeSpan +TimeSpan = TimeSpan
TimeSpan - TimeSpan = TimeSpan
Klasa TimeSpan definiuje ponadto negację jednoargumentową (ts2 = -ts1), ale inne działania (takie jak mnożenie obiektu TimeSpan przez liczbę) nie są zdefiniowane. W niektórych jednak przypadkach, jeśli jest to konieczne, można przeprowadzić wszystkie obliczenia. W programie MultiplyTimeSpan, który można pobrać z serwera FTP wydawnictwa Helion, użyto poniższej instrukcji do ustawienia obiektu ts2 klasy TimeSpan na dwunastokrotność czasu przechowywanego w obiekcie ts1 klasy TimeSpan: ts2 = New TimeSpan(ts1.Ticks * 12)
W Visual Basicu 2005 dla typu Date i obiektów klasy TimeSpan są zdefiniowane następujące operatory: +, -, <, >, <=, >=, <> oraz =. We wcześniejszych wersjach tego języka operatory te były niezdefiniowane, ale klasa Date udostępniała w zamian odpowiednie metody operatorowe. Na przykład metoda op_Subtraction klasy Date odejmuje od siebie dwie daty i zwraca obiekt klasy TimeSpan. Metody te są nadal dostępne i możesz ich używać, jeśli uznasz, że zwykłe operatory są mało jasne. W poniższej tabeli przedstawiono metody operatorowe klasy Date. Pamiętaj, że w Common Language Runtime typ Date nazywa się DateTime, dlatego jeśli będziesz chciał znaleźć w pomocy internetowej informacje na temat tych metod, powinieneś szukać nazwy DateTime. Składnia
Zwraca obiekt klasy TimeSpan, który wyznacza okres pomiędzy date1 i date2. Zwraca wartość, która wskazuje, czy date1 jest większy, mniejszy, czy równy date2.
Rozdział 16.
Operatory
353
Metoda Compare wyróżnia się nieco spośród pozostałych. Zwraca ona wartość typu Integer zamiast typu Boolean lub Date. Jej wartość jest mniejsza od zera, jeśli date1date2, a także równa zero, jeśli date1=date2. Są to metody współdzielone, a więc nie trzeba używać konkretnego egzemplarza klasy Date, aby z nich korzystać. Na przykład poniższy fragment programu wyświetla liczbę dni dzielących daty 20 lipca i 7 sierpnia: Dim date1 As Date = #7/20/04# Dim date2 As Date = #8/7/04# Dim elapsed_time As TimeSpan elapsed_time = Date.op_Subtraction(date2, date1) Debug.WriteLine(elapsed_time.Days)
Operatory te są nieco problematyczne. Aby uprościć tego rodzaju obliczenia, typ danych Date udostępnia inne, łatwiejsze do czytania metody do wykonywania najczęstszych działań. Podczas gdy metody operatorowe pobierają jako parametry obydwa operandy, te metody pobierają jako jeden parametr pojedynczy operand, zaś jako drugi parametr przyjmują bieżący obiekt. Na przykład metoda Add obiektu Date dodaje obiekt DateSpan do daty, po czym zwraca powstałą w ten sposób datę. Poniższa tabela zawiera zestawienie tych metod. Składnia
Zwraca datę plus podaną liczbę tyknięć (jednostek długości 100 nanosekund).
result_timespan = date1.Subtract
Zwraca różnicę pomiędzy date1 i date2.
(date2) result_integer = date1.CompareTo (date2)
Zwraca wartość, która określa, czy date1 jest większy, mniejszy, czy równy date2.
result_boolean = date1.Equals(date2)
Zwraca wartość True, jeśli date1 jest równy date2.
354
Część II
Wstęp do języka Visual Basic
Metoda CompareTo zwraca wartość mniejszą od zera, jeśli date1date2, a także zero, jeśli date1=date2.
Przeciążanie operatorów Operatory języka Visual Basic są przeznaczone do stosowania w wyrażeniach z użyciem standardowych typów danych, jak Integer czy Boolean. Niektórych operatorów można także używać z obiektami, na przykład Is i IsNot, ale takie operatory, jak * czy Mod ogólnie nie nadają się do pracy z obiektami. Niemniej jednak operatory te można zdefiniować także w swoich klasach i strukturach za pomocą instrukcji Operator. Jest to zaawansowany temat. Osoby początkujące powinny pominąć ten podrozdział, aby wrócić do niego po przeczytaniu rozdziału 26. — „Klasy i struktury”. Ogólna składnia instrukcji przeciążania operatora wygląda następująco: [ ] Public [ Overloads ] Shared [ Shadows ] _ [ Widening | Narrowing ] Operator symbol ( operandy ) As ... End Operator
typ
Poniżej znajduje się opis poszczególnych elementów tej deklaracji:
Atrybuty — atrybuty operatora.
Public — wszystkie operatory muszą być deklarowane jako Public Shared.
Overloads — tego słowa można używać wyłącznie wówczas, gdy operator przyjmuje dwa parametry należące do klasy bazowej i klasy potomnej jako swoje dwa parametry. W takim przypadku oznacza to, że definiowany operator przeciąża operator zdefiniowany w klasie bazowej.
Shared — wszystkie operatory muszą być deklarowane jako Public Shared.
Shadows — operator zastępuje podobny operator w klasie bazowej.
Widening — oznacza, że operator definiuje konwersję rozszerzającą, która zawsze kończy się powodzeniem w czasie działania programu. Na przykład liczba typu Integer zawsze zmieści się w typie Single, dlatego zapisanie liczby typu Integer w zmiennej typu Single jest konwersją rozszerzającą. Operator ten musi przechwytywać i obsługiwać wszystkie błędy. CType powinien zawierać albo słowo kluczowe Widening, albo Narrowing.
Narrowing — oznacza, że operator definiuje konwersję zawężającą, która może nie powieść się w czasie działania programu. Na przykład liczba typu Single nie zawsze zmieści się w typie Integer, dlatego zapisanie jej w zmiennej typu Integer jest konwersją zawężającą. Operator CType musi zawierać albo słowo kluczowe Widening, albo Narrowing.
Symbol — symbol operatora. Może to być +, —, *, /, \, ^, &, <<, >>, =, <>, <, >, <=, >=, Mod, Not, And, Or, Xor, Like, IsTrue, IsFalse lub CType.
Rozdział 16.
Operatory
355
Operandy — deklaracje obiektów, którymi ma manipulować operator. Operatory jednoargumentowe +, -, Not, IsTrue oraz IsFalse przyjmują jeden operand. Operatory dwuargumentowe +, -, *, /, \, ^, &, <<, >>, =, <>, <, >, <=, >=, Mod, And, Or, Xor, Like oraz CType przyjmują dwa operandy.
Typ — wszystkie operatory muszą mieć typ zwrotny i zwracać wartość poprzez instrukcję Return.
Przeciążanie operatorów podlega kilku ograniczeniom:
Niektóre operatory występują w parach, więc jeśli zdefiniuje się jeden z nich, konieczne będzie zdefiniowanie również tego drugiego. Pary te to = i <>, < i >, <= i >= oraz IsTrue i IsFalse.
W przypadku standardowych operatorów jedno- i dwuargumentowych klasa lub struktura, w której są one definiowane, powinna pojawiać się w operandzie. Jeśli chodzi o operator konwersji CType, ta klasa lub struktura musi pojawiać się w operandzie lub typie zwrotnym.
Operatory IsTrue i IsFalse muszą zwracać wartości logiczne.
Drugi operand operatorów, << i >>, musi być typu Integer.
Jeśli programista zdefiniuje operator, Visual Basic automatycznie dostarczy odpowiadający mu operator przypisania. Jeśli na przykład zostanie zdefiniowany operator +, program dostarczy operator przypisania +=. Chociaż nie da się bezpośrednio stosować operatorów IsTrue i IsFalse, można ich używać pośrednio. Jeśli dla klasy zostanie zdefiniowany operator IsTrue, Visual Basic będzie za jego pomocą sprawdzał, czy dany obiekt powinien być traktowany jako True w wyrażeniach logicznych. Na przykład poniższa instrukcja sprawdza przy użyciu operatora IsTrue, czy obiekt c1 ma zostać uznany za wartość True: If c1 Then ...
Jeśli zostaną zdefiniowane operatory And i IsFalse, Visual Basic wykorzysta je do obsługi także operatora AndAlso. Aby to działało, operator And musi zwracać ten sam typ klasy lub struktury jak ta, w której został zdefiniowany. Wyobraźmy sobie na przykład, że zdefiniowaliśmy operatory And i IsFalse dla klasy Composite, a także że zmienne c1, c2 i c3 są egzemplarzami tej klasy. Teraz przeanalizujmy poniższą instrukcję: c3 = c1 AndAlso c2
Visual Basic wyznacza wartość obiektu c1 za pomocą operatora IsFalse. Jeśli operator ten zwróci wartość True, program nie wyznaczy już wartości obiektu c2. W zamian przyjmie, że cała instrukcja ma wartość False, po czym zwróci tę wartość. Ponieważ operator IsFalse zwrócił wartość True dla obiektu c1, Visual Basic wie, że c1 ma wartość fałszywą, a więc ustawia obiekt c3 na wartość obiektu c1. Jest to bardzo zagmatwane. Może się nieco rozjaśnić, jeśli dokładnie przemyślisz, jak Visual Basic wyznacza wartości wyrażeń logicznych przy użyciu normalnego operatora AndAlso. Analogicznie: jeśli zostaną zdefiniowane operatory Or i IsTrue, Visual Basic automatycznie dostarczy operator OrElse.
356
Część II
Wstęp do języka Visual Basic
Chociaż w języku Visual Basic nie da się utworzyć dwóch wersji jednej funkcji, które różniłyby się tylko typem zwrotnym, można to zrobić z operatorami konwersji CType. Kiedy program podejmuje próbę konwersji, Visual Basic potrafi na podstawie wyniku określić, którego operatora konwersji należy użyć. Poniższy kod, definiujący klasę o nazwie Complex, która reprezentuje liczby zespolone, pochodzi z programu ComplexNumbers — można go pobrać z serwera FTP wydawnictwa Helion. Definiuje on operatory +, - oraz *, które implementują normalne operacje dodawania, odejmowania i mnożenia liczb zespolonych. Dodatkowo zawiera definicje operatorów =, <> oraz jednoargumentowych operatorów negacji, a także operator konwersji, który konwertuje obiekty klasy Complex na typ Double poprzez zwrócenie ich wartości bezwzględnej. Public Class Complex Public Re As Double Public Im As Double ‘ Konstruktory. Public Sub New() End Sub Public Sub New(ByVal real_part As Double, ByVal imaginary_part As Double) Re = real_part Im = imaginary_part End Sub ‘ ToString. Public Overrides Function ToString() As String Dim txt As String = Re.ToString If Im < 0 Then txt & = " - " & Math.Abs(Im).ToString Else txt & = " + " & Im.ToString End If Return txt & "i" End Function ‘ Operatory. Public Shared Operator *(ByVal c1 As Complex Return New Complex( _ c1.Re * c2.Re - c1.Im c1.Re * c2.Im + c1.Im End Operator Public Shared Operator +(ByVal c1 As Complex Return New Complex( _ c1.Re + c2.Re, _ c1.Im + c2.Im) End Operator Public Shared Operator -(ByVal c1 As Complex Return New Complex( _ c1.Re - c2.Re, _ c1.Im - c2.Im) End Operator Public Shared Operator =(ByVal c1 As Boolean
As Complex, ByVal c2 As Complex) _ * c2.Im, _ * c2.Re) As Complex, ByVal c2 As Complex) _
As Complex, ByVal c2 As Complex) _
As Complex, ByVal c2 As Complex) _
Rozdział 16.
Operatory
357
Return (c1.Re = c2.Re) AndAlso (c1.Im = c2.Im) End Operator Public Shared Operator < > (ByVal c1 As Complex, ByVal c2 As Complex) _ As Boolean Return (c1.Re < > c2.Re) OrElse (c1.Im < > c2.Im) End Operator Public Shared Operator -(ByVal c1 As Complex) As Complex Return New Complex(-c1.Re, -c1.Im) End Operator Public Shared Narrowing Operator CType(ByVal c1 As Complex) As Double Return System.Math.Sqrt(c1.Re * c1.Re + c1.Im * c1.Im) End Operator End Class
Przy przeciążaniu operatorów łatwo dać się ponieść. Sam fakt, że można zdefiniować operator dla klasy, nie oznacza, iż powinno się to robić. Można na przykład wymyślić jakiś sposób dodawania obiektów klasy Employee, ale byłoby to prawdopodobnie wbrew intuicji. Lepiej zapewne napisać podprocedurę lub funkcję z wszystko mówiącą nazwą, niż używać dwuznacznych operatorów typu + czy >>.
Operatory z typami dopuszczającymi wartość pustą Typy dopuszczające wartość pustą zostały opisane w rozdziale 15. — „Typy danych, zmienne i stałe”. Zmienna zadeklarowana jako dopuszczająca wartości puste rozróżnia wartości zerowe, nic niereprezentujące, zwykłe wartości i stan nieprzechowywania w ogóle żadnej wartości. Jeśli na przykład zmienna x zostanie zadeklarowana jako zmienna typu Integer, dopuszczająca wartości puste, będzie można ustawić ją na specjalną wartość Nothing, aby zaznaczyć, że nie zawiera żadnych danych. Jeśli wszystkie operandy w wyrażeniu zawierają prawdziwe dane (nie ma wartości Nothing), operacje porównywania arytmetycznego oraz operacje logiczne i bitowe zwrócą takie wartości, jakie są spodziewane. Jeżeli przynajmniej jedna ze zmiennych dopuszczających wartość pustą w wyrażeniu ma specjalną wartość Nothing, Visual Basic wyznaczy taką jego wartość przy zastosowaniu specjalnych zasad propagacji wartości pustych. Jeśli jeden lub więcej operandów działania arytmetycznego, porównywania logicznego lub bitowego ma wartość Nothing, wynik jest równy Nothing. Jeśli na przykład zmienne x i y dopuszczają wartości puste, a x nie zawiera żadnej wartości, poniższe wyrażenia, a także wszystkie inne wyrażenia, w których użyta jest zmienna x, mają wartość Nothing. -x x + y x * y x ^ y x > >
y
Więcej informacji na temat typów dopuszczających wartości puste znajduje się w rozdziale 15. — „Typy danych, zmienne i stałe”.
358
Część II
Wstęp do języka Visual Basic
Podsumowanie Operatory są używane przez programy do manipulowania zmiennymi, stałymi i wartościami literalnymi, w wyniku czego powstają nowe wartości. Operatory języka Visual Basic dzielą się na pięć kategorii — arytmetyczne, konkatenacji, porównywania, logiczne oraz bitowe. W większości przypadków użycie operatorów jest proste i intuicyjne. Priorytety operatorów określają kolejność wykonywania działań w wyrażeniach. Jeśli nie jest ona jasna, należy zawsze używać nawiasów — w celu wyjaśnienia jej. Nawet jeżeli zastosowanie nawiasów nie wpłynie na sposób wykonania instrukcji, dzięki nim kod może się stać bardziej zrozumiały. Mogą też one pomóc uniknąć popełniania czasochłonnych błędów. Typ danych String ma pewne specjalne wymagania. Działania na łańcuchach odgrywają w wielu aplikacjach bardzo ważną rolę, w związku z czym utworzono klasę StringBuilder, która optymalizuje pracę na łańcuchach. Z jednej strony — jeśli program operuje tylko na kilku krótkich łańcuchach, raczej nie ma potrzeby używania klasy StringBuilder, a skorzystanie z typu String sprawi, że kod będzie bardziej przejrzysty. Z drugiej strony — jeśli aplikacja tworzy niezwykle długie łańcuchy lub łączy bardzo duże liczby łańcuchów, użycie klasy StringBuilder może pozwolić zaoszczędzić znaczną ilość czasu. Typ danych Date również zachowuje się inaczej niż inne typy danych. W jego przypadku zwykłe operatory typu + i - działają inaczej niż przy innych typach. Na przykład wynikiem operacji odejmowania wartości typu Date od wartości typu Date jest obiekt klasy TimeSpan, a nie kolejna wartość typu Date. Ma to głęboki sens, jeśli weźmie się pod uwagę, czym w rzeczywistości są wartości typu Date i TimeSpan. Podobnie jak operatory dodawania i odejmowania działają w specjalny sposób na wartościach typu Date i TimeSpan, za pomocą techniki przeciążania operatorów można zdefiniować operatory dla własnych klas. Definiowanie dzielenia lub potęgowania może nie być zbyt sensowne w przypadku klas Employee, Customer czy Orders, ale w niektórych przypadkach niestandardowe operatory zwiększą czytelność kodu. Można na przykład łatwo wyobrazić sobie poniższą instrukcję, która dodaje obiekt klasy OrderItem do obiektu klasy CustomerOrder: the_order += new_item
W tym rozdziale opisałem zasady używania operatorów do wykonywania działań na zmiennych w celu wygenerowania nowych wyników. Typowy program potrafi wykonywać ten sam zestaw działań wielokrotnie na różnych wartościach zmiennych. Mimo iż obliczenia te można wykonać w długich seriach, ich kod byłby zawiły i trudny do konserwacji. W rozdziale 17. — „Podprocedury i funkcje” — objaśnię sposoby rozbijania programu za pomocą podprocedur i funkcji na części, których można używać wielokrotnie. Ułatwia to i ujednolica wykonywanie obliczeń.
17
Podprocedury i funkcje Podprocedury i funkcje pozwalają podzielić mało poręczny długi kod na części, nad którymi łatwiej jest zapanować. Za ich pomocą można wydzielić fragment kodu wielokrotnego użytku, po czym umieścić go w jednym miejscu, z którego będzie w razie potrzeby wywoływany. Pozwala to nie tylko uniknąć powtarzania się w kodzie, ale umożliwia także zarządzanie nim i aktualizowanie w jednym miejscu. Podprocedura wykonuje działania na rzecz kodu, który ją wywołał. Funkcja wykonuje jakieś działania i zwraca wynik. Ten ostatni może być rezultatem jakichś obliczeń lub kodem stanu, który informuje, czy funkcja zakończyła działanie powodzeniem, czy nie. Podprocedury i funkcje są czasami nazywane procedurami. Niekiedy noszą one również miano metod, zwłaszcza jeśli należą do jakiejś klasy. W tym rozdziale opiszę podprocedury i funkcje. Objaśnię składnię deklaracji i używania każdej z nich w aplikacjach Visual Basica. Dodatkowo znajdzie się tu kilka wskazówek, jak można sprawić, że procedury będą łatwiejsze w konserwacji.
Podprocedury Nazwę podprocedury definiuje instrukcja Sub. Pozwala ona także zadeklarować przyjmowane przez podprocedurę argumenty oraz zdefiniować typy danych jej parametrów. Instrukcje wykonawcze podprocedury znajdują się pomiędzy instrukcjami Sub i End Sub. Składnia definicji podprocedury przedstawia się następująco: [lista_atrybutów] [tryb_dziedziczenia] [dostępność] _ Sub nazwa_podprocedury ([parametry]) [ Implements interfejs.podprocedura ] [ instrukcje ] End Sub
Poszczególne części tej deklaracji zostaną opisane w kolejnych podrozdziałach.
360
Część II
Wstęp do języka Visual Basic
Lista atrybutów Lista atrybutów jest opcjonalna. Składa się ona z oddzielanych przecinkami atrybutów podprocedury. Atrybut stanowi usprawnienie definicji klasy, metody, zmiennej lub innego elementu, które dostarcza dodatkowe informacje kompilatorowi i systemowi wykonawczemu. Zastosowanie atrybutu do klasy, zmiennej, metody lub innego obiektu programistycznego jest czasami nazywane dekorowaniem tego elementu. Atrybuty są wyspecjalizowanym narzędziem, które służy do rozwiązywania problemów powstających w czasie wykonywania bardzo specyficznych zadań programistycznych. Na przykład atrybut Conditional oznacza, że podprocedura jest uzależniona od definicji jakiejś stałej kompilatora. Demonstruje go program o nazwie AttributeConditional, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Poniżej znajduje się fragment jego kodu: #Const DEBUG_LIST_CUSTOMERS = True ' #Const DEBUG_LIST_EMPLOYEES = True Private Sub Form1_Load() Handles MyBase.Load ListCustomers() ListEmployees() txtResults.Select(0, 0) End Sub _ Private Sub ListCustomers() txtResults.Text & = "ListCustomers” & End Sub _ Private Sub ListEmployees() txtResults.Text & = "ListEmployees” & End Sub
vbCrLf
vbCrLf
W powyższym kodzie została zdefiniowana stała kompilatora o nazwie DEBUG_LIST_CUSTOMERS. Wartość stałej DEBUG_LIST_EMPLOYEES nie jest zdefiniowana, ponieważ jej definicja znajduje się w komentarzu. Procedura obsługi zdarzeń o nazwie Form1_Load wywołuje podprocedury ListCustomers i ListEmployees. W definicji podprocedury ListCustomers znajduje się atrybut Conditional z parametrem DEBUG_LIST_EMPLOYEES. Jest to informacja dla kompilatora, aby nie generować kodu dla tej procedury, jeśli parametr DEBUG_LIST_CUSTOMERS nie jest zdefiniowany. Stała ta jest zdefiniowana, a więc kompilator generuje kod dla tej podprocedury. Podprocedura ListEmployees została zdefiniowana przy użyciu atrybutu Conditional z parametrem DEBUG_LIST_EMPLOYEES. Stała ta nie jest zdefiniowana, a więc kompilator nie generuje kodu dla tej podprocedury i jej wywołanie w podprocedurze Form1_Load jest ignorowane. Poniżej znajduje się tekst wygenerowany przez ten program: ListCustomers
Rozdział 17.
Podprocedury i funkcje
361
Visual Basic definiuje około 200 atrybutów. Wiele z nich ma bardzo wyspecjalizowane przeznaczenie, przez co nie są one zbyt często używane. Jednak niektóre są bardzo przydatne. Na przykład atrybut Browsable określa, czy własność lub zdarzenie ma zostać pokazane w oknie Properties. To prosty atrybut, dzięki czemu jego opis jest krótki. Natomiast atrybut System.EnterpriseServices.ApplicationQueuing umożliwia kolejkowanie plików wykonywalnych oraz pozwala na odczytywanie wywołań metod z kolejek komunikatów wywołań. Jest przydatny tylko w bardzo specyficznych sytuacjach, przez co nie został tutaj opisany. Istnieje wiele takich atrybutów, które dostarczają edytorowi lub IDE jakieś metadane. Dlatego ich efekty często widać dopiero po wyświetleniu obiektu w tym edytorze lub IDE. Tworząc kontrolkę lub komponent, można umieścić jeden egzemplarz swojej pracy na formularzu, aby podejrzeć jego własności w oknie Properties. W takim przypadku przydatnych będzie wiele różnych rodzajów atrybutów. Jeśli tworzy się klasę Employee, używaną wyłącznie w kodzie, można znaleźć zastosowanie dla znacznie mniejszej liczby atrybutów. W Visual Basicu 2008 dostępna jest bardzo ważna kontrolka o nazwie PropertyGrid. Umożliwia ona wyświetlanie własności obiektu na formularzu — jak w oknie Properties. Kontrolka ta uznaje wszystkie atrybuty związane z własnościami i pozwala na wykorzystanie ich na całkiem innym poziomie. Poniżej znajduje się lista z opisami niektórych najbardziej przydatnych atrybutów. Większość z nich należy do przestrzeni nazw System.ComponentModel. Przestrzenie nazw pozostałych atrybutów oraz informacje na temat ich parametrów można znaleźć w pomocy internetowej. Nawet te najbardziej przydatne atrybuty mają wyspecjalizowane zastosowania, przez co nie każdy od razu zauważy ich przydatność. Jeśli któryś z nich wydaje Ci się bezsensowny, pomiń go i wróć do tej listy, kiedy zdobędziesz więcej doświadczenia w tworzeniu niestandardowych kontrolek.
AttributeUsage — własne atrybuty da się tworzyć poprzez dziedziczenie po klasie Attribute. Atrybut ten można nadać tworzonej przez siebie klasie atrybutu, aby
określić sposób użycia tego tworzonego atrybutu. Istnieje możliwość zdefiniowania, czy dany element może posiadać kilka egzemplarzy tego atrybutu, czy atrybut ten może być dziedziczony przez klasy potomne, a także co może mieć ten atrybut (asemblacje, klasy, metody itd.).
Browsable — określa, czy własność lub zdarzenie ma być wyświetlane w edytorze typu okno Properties. Jeśli do konstruktora tego atrybutu zostanie przekazana wartość False, okno Properties nie wyświetli tej własności.
Category — określa, w której grupie powinna znajdować się własność lub zdarzenie
w edytorze typu okno Properties. Jeśli na przykład użytkownik kliknie w oknie Properties przycisk Categorized, własności w tym oknie zostaną pogrupowane według kategorii. Atrybut ten wyznacza kategorię, do której należy zaliczyć daną własność. Warto zauważyć, że nie ma nic niezwykłego w nazwach tych kategorii. Można użyć dowolnego łańcucha. W razie potrzeby w oknie Properties zostanie utworzona nowa kategoria.
DefaultEvent — określa nazwę domyślnego zdarzenia klasy. Jeśli klasa reprezentuje
kontrolkę lub komponent i zostanie on dwukrotnie kliknięty na formularzu, w oknie edytora kodu wyświetli się kod tego zdarzenia. Na przykład domyślnym
362
Część II
Wstęp do języka Visual Basic
zdarzeniem kontrolek Button jest Click. Dlatego dwukrotne kliknięcie przycisku w czasie projektowania powoduje wyświetlenie w edytorze kodu procedury obsługi zdarzeń Click tej kontrolki.
DefaultProperty — określa nazwę domyślnej własności klasy. Wyobraźmy sobie, że domyślną własnością komponentu Employee jest LastName. Zaznaczamy formularz i w oknie Properties klikamy własność FormBorderStyle. Następnie klikamy komponent Employee. Ponieważ nie posiada on własności FormBorderStyle, okno Properties wyświetla jego własność domyślną, czyli LastName.
DefaultValue — określa domyślną wartość własności. Jeśli klikniemy tę własność prawym przyciskiem myszy i wybierzemy opcję Reset, zostanie ona ustawiona właśnie na tę wartość. Pamiętaj, że nie możesz popełnić tutaj błędu. Na przykład nie ustawiaj jako wartości łańcucha "nieznany", jeśli własność jest typu Integer.
Description — definiuje opis elementu. Jeśli własność posiada atrybut Description
i zostanie ona zaznaczona w oknie Properties, treść tego opisu zostanie wyświetlona na samym dole tego okna. W Visual Basicu 2008 możliwe jest też tworzenie opisów procedur i ich parametrów za pomocą komentarzy XML. Są one wykorzystywane przez funkcję IntelliSense. Więcej informacji na ten temat znajduje się w podrozdziale „Komentarze XML” rozdziału 14. — „Struktura programu i modułu”.
Localizable — określa, czy dana własność powinna dać się lokalizować. Jeśli ma wartość True, zlokalizowane wartości są automatycznie zapisywane w odpowiednich plikach zasobów, a jeżeli False (domyślna) — wszystkie lokalizacje używają tej samej wartości.
Aby wypróbować ten atrybut, ustaw własność Localizable formularza na True, po czym wprowadź jakąś wartość. Następnie ustaw własność Language tego formularza na inny język i nadaj własności Localizable jakąś inną wartość. Visual Basic będzie automatycznie używać odpowiednich wartości, w zależności od lokalizacji użytkownika.
MergableProperty — określa, czy dana własność może zostać scalona z tą samą własnością innego komponentu w oknie Properties. Jeśli atrybut ten ma wartość False i zostanie zaznaczona więcej niż jedna kontrolka z tą własnością, ta ostatnia nie zostanie wyświetlona w oknie Properties.
Jeśli atrybut ten ma wartość True i zostanie zaznaczony więcej niż jeden egzemplarz kontrolki z tą własnością, zostanie ona wyświetlona w oknie Properties, o ile we wszystkich zaznaczonych kontrolkach ma ona taką samą wartość. Jeżeli wprowadzimy nową wartość, zostanie ona uwzględniona we wszystkich tych kontrolkach. W ten sposób działa na przykład własność Text kontrolek TextBox, Label i wielu innych.
ParenthesizePropertyName — określa, czy w edytorach typu okno Properties nazwa własności ma znajdować się w nawiasach. Jeśli tak jest, nazwa zostaje przeniesiona na samą górę listy (w przypadku alfabetycznego wyświetlania własności) lub na górę kategorii (w przypadku wyświetlania własności według kategorii).
ReadOnly — określa, czy własność jest tylko do odczytu. Na przykład w oknie Properties własność taka miałaby szary kolor, który oznaczałby, że nie można jej modyfikować. Atrybut ten jest nieco dziwny w użyciu, ponieważ ReadOnly to
Rozdział 17.
Podprocedury i funkcje
363
słowo kluczowe języka Visual Basic. Jeśli zostanie wpisana sama nazwa atrybutu ReadOnly, Visual Basic nie będzie wiedzieć, co robić. Aby uniknąć problemów, należy zawsze używać jego pełnej nazwy System.ComponentModel.ReadOnly lub stosować składnię <[ReadOnly] (True) >….
RecommendedAsConfigurable — oznacza, że własność powinna być związana z plikiem konfiguracyjnym. Własność ta zostanie wyświetlona, gdy zaznaczymy obiekt i rozwiniemy element (Dynamic Properties). Jeśli klikniesz wielokropek po prawej stronie, pojawi się okno dialogowe, w którym będzie można rzutować własność na klucz w pliku konfiguracyjnym.
RefreshProperties — określa, w jaki sposób edytor ma odświeżyć pozostałe własności obiektu, jeśli zmieni się wartość tej własności. Dopuszczalne wartości to Default (pozostałe własności nie są odświeżane), Repaint (wszystkie pozostałe własności są odświeżane) oraz All (wszystkie własności zostają ponownie pobrane i odświeżone).
Conditional — oznacza, że metodę można wywoływać, jeśli jest zdefiniowana stała kompilacji, na przykład DEBUG czy MY_CONSTANT. Jeżeli stała ta nie jest
zdefiniowana, kod tej metody jest nadal generowany, a parametry użyte w jej wywołaniu są porównywane z typami jej parametrów. Jednak wywołania tej metody są ignorowane w czasie działania programu. Jeśli metoda ma więcej niż jeden atrybut Callable, może być wywoływana, o ile którakolwiek z określonych stałych jest zdefiniowana w czasie kompilacji. Pamiętaj, że stała musi być zdefiniowana w programie głównym, a nie w komponencie, jeśli tworzony jest akurat ten drugi. Zaznacz program główny, otwórz menu Project, kliknij opcję Properties na samym dole, otwórz folder Configuration Properties, kliknij Build i w polu tekstowym Custom constants wprowadź wartość, na przykład IS_DEFINED=True. Aby całkowicie wykluczyć kod z kompilacji, można użyć dyrektywy kompilatora #if. Jednak w takim przypadku wszelkie wywołania wyłączonej procedury będą powodować błędy kompilacji, ponieważ metoda ta nie będzie istniała. Atrybut Conditional pozwala ukryć metodę przy równoczesnym zezwoleniu na jej wywoływanie.
DebuggerHidden — informuje debugery, czy metoda powinna dać się debugować. Jeśli atrybut ten ma wartość True, IDE będzie pomijać tę metodę i nie będzie zatrzymywać się w ustawionych w niej punktach wstrzymania.
DebuggerStepThrough — określa, czy programista może wykonywać metodę krok po kroku w debugerze. Jeśli atrybut ten ma wartość True, IDE nie będzie wykonywać metody instrukcja po instrukcji, ale będzie zatrzymywać się w punktach wstrzymania.
ToolboxBitmap — informuje IDE, gdzie znajduje się mapa bitowa ikony kontrolki
lub komponentu wyświetlanej w oknie Toolbox. Może to być plik lub typ w asemblacji zawierającej tę mapę bitową i jej nazwę. Jest to niezgrabne, ale niezbędne przy tworzeniu kontrolek i komponentów.
NonSerializedAttribute — oznacza, że jakaś składowa serializowalnej klasy nie powinna być serializowana. Atrybut ten pozwala usuwać dane, które nie powinny być poddawane serializacji.
364
Część II
Wstęp do języka Visual Basic
Obsolete — oznacza, że element (klasa, metoda, własność lub cokolwiek innego) jest przestarzały. Istnieje możliwość zdefiniowania komunikatu, który powinien być wyświetlany przez edytor kodu, gdy programista użyje tego elementu (na przykład "W zamian użyj metody NewMethod"). Dodatkowo można wskazać, czy zastosowanie go ma być traktowane przez IDE jako ostrzeżenie, czy błąd.
Serializable — oznacza, że klasa da się serializować. Wszystkie pola publiczne i prywatne są serializowane domyślnie. Należy pamiętać, że niektóre procedury wymagają, by klasa była serializowalna, nawet jeśli nie używamy serializacji. Ponadto pamiętaj, że atrybuty znajdujące się w przestrzeni nazw System.Xml.Serialization mogą znacznie usprawnić kontrolę serializacji.
ThreadStaticAttribute — oznacza, że współdzielona (Shared) zmienna klasowa
nie powinna być współdzielona przez wątki. Każdy wątek tworzy własną kopię tej zmiennej i wszystkie egzemplarze tej klasy w każdym wątku używają tej samej kopii. Znalezienie atrybutów potrzebnych do konkretnego zadania może być trudne. Poszukiwania te pomagają zrozumieć, że klasy atrybutów dziedziczą pośrednio lub bezpośrednio po klasie Attribute. Informacje na temat klasy Attribute znajdują się na stronie http://msdn.microsoft.com/en-us/library/system.attribute.aspx. Listę klas dziedziczących po klasie Attribute można znaleźć pod adresem http://msdn.microsoft.com/en-us/library/system.attribute_derivedtypelist.aspx.
Tryb dziedziczenia W miejscu tryb_dziedziczenia można wstawić jedną z następujących wartości — Overloads, Overrides, Overridable, NotOverridable, MustOverride, Shadows lub Shared. Określają one sposób dziedziczenia przez podprocedurę zadeklarowaną w jakiejś klasie po jej klasie nadrzędnej lub dozwoloną metodę dziedziczenia jej przez klasy potomne. Poniżej znajduje się lista z opisami tych słów kluczowych:
Overloads — oznacza, że podprocedura posiada taką samą nazwę, jaką ma inna procedura zdefiniowana w tej klasie. Aby Visual Basic mógł rozróżnić różne wersje jednej procedury, muszą one mieć różne listy parametrów (jeśli listy parametrów są takie same, słowo to działa jak słowo kluczowe Overrides, które zostanie opisane w dalszej części podrozdziału). Jeżeli przeciążana jest podprocedura zdefiniowana w klasie nadrzędnej, konieczne jest użycie tego słowa kluczowego. Jeśli przeciążane są tylko podprocedury znajdujące się w tej samej klasie, można to słowo opuścić. Jeżeli jednak słowo to zostanie użyte w którejkolwiek z przeciążonych podprocedur, konieczne będzie użycie go we wszystkich.
Overrides — oznacza, że podprocedura zastępuje podprocedurę o identycznej nazwie i takich samych parametrach, która znajduje się w klasie nadrzędnej.
Overridable — oznacza, że podprocedurę tę można przesłonić w klasie potomnej.
Jest to domyślne zachowanie podprocedury, która przesłania inną podprocedurę.
NotOverridable — oznacza, że nie można przesłonić tej podprocedury w klasie potomnej. Tego słowa kluczowego da się używać tylko w podprocedurach, które przesłaniają inne podprocedury.
Rozdział 17.
Podprocedury i funkcje
365
MustOverride — oznacza, że wszystkie klasy potomne muszą przesłaniać tę
podprocedurę. Jeśli to słowo kluczowe zostanie użyte, trzeba będzie opuścić cały kod podprocedury oraz instrukcję End Sub, jak poniżej: MustOverride Sub Draw() MustOverride Sub MoveMap(ByVal X As Integer, ByVal Y As Integer) MustOverride Sub Delete() ...
Jeśli klasa zawiera podprocedurę posiadającą w deklaracji słowo kluczowe MustOverride, w deklaracji tej klasy musi zostać użyte słowo kluczowe MustInherit. W przeciwnym przypadku Visual Basic nie będzie wiedzieć, co zrobić z wywołaniem takiej podprocedury, która przecież nie zawiera żadnego kodu. Słowo kluczowe MustOverride jest przydatne w definiowaniu podprocedur, które muszą być implementowane przez klasy potomne, a których domyślna implementacja w klasie nadrzędnej nie ma żadnego konkretnego zastosowania. Wyobraźmy sobie na przykład, że napisaliśmy klasę Drawable, reprezentującą figury geometryczne, które można narysować, a także że utworzyliśmy jej klasy potomne o nazwach Rectangle, Ellipse, Line itd. Do rysowania ogólnej figury klasa Drawable udostępnia podprocedurę o nazwie Draw. Ponieważ jednak klasa Drawable nie reprezentuje żadnej konkretnej figury geometrycznej, nie może zawierać domyślnej implementacji tej podprocedury. Aby zmusić klasy potomne do implementacji podprocedury Draw, w deklaracji klasy Drawable użyjemy słowa kluczowego MustOverride.
Shadows — oznacza, że podprocedura zastępuje jakiś element o identycznej nazwie (najczęściej inną podprocedurę) z klasy nadrzędnej, który nie musi posiadać takich samych parametrów. Jeśli klasa nadrzędna udostępnia więcej niż jedną przeciążoną wersję podprocedury, podprocedura ta przesłania je wszystkie. Jeżeli klasa potomna zawiera więcej niż jedną przeciążoną wersję tej podprocedury, wszystkie te wersje muszą zawierać w deklaracji słowo kluczowe Shadows.
Shared — oznacza, że podprocedura jest związana z samą klasą, a nie jakimś jej konkretnym egzemplarzem. Można ją wywołać przy użyciu nazwy tej klasy (NazwaKlasy.PodproceduraShared) lub przy użyciu konkretnego obiektu (obiekt_klasy.PodproceduraShared). Przez to, że podprocedura ta nie jest związana z żadnym konkretnym egzemplarzem klasy, nie może ona używać własności ani metod udostępnianych przez nie. Korzysta ona tylko z innych współdzielonych własności i metod oraz zmiennych globalnych.
Dostępność Klauzula dostępność może mieć jedną z następujących wartości — Public, Protected, Friend, Protected Friend lub Private. Określają one, w którym miejscu w kodzie można wywoływać daną podprocedurę. Poniżej znajduje się lista z opisami tych wartości:
Public — oznacza, że nie ma żadnych ograniczeń dotyczących wywoływania
podprocedury. Można ją wywoływać wewnątrz, a także na zewnątrz jej klasy lub modułu.
366
Część II
Wstęp do języka Visual Basic
Protected — oznacza, że podprocedura jest dostępna jedynie w swojej klasie lub klasach potomnych. Słowa kluczowego Protected można używać tylko
w deklaracjach podprocedur znajdujących się w klasie.
Friend — oznacza, że podprocedurę można wywoływać w całym projekcie. Różnica pomiędzy tym słowem kluczowym a słowem Public polega na tym, że Public zezwala na dostęp do podprocedury także spoza projektu. Problem ten
dotyczy przede wszystkim bibliotek kodu (DLL) i bibliotek kontrolek. Wyobraźmy sobie, że utworzyliśmy bibliotekę kodu, która zawiera dużą liczbę podprocedur, a następnie napisaliśmy program używający jej. Jeśli jakaś podprocedura została w niej zadeklarowana przy użyciu słowa kluczowego Public, może ona być używana zarówno przez tę bibliotekę, jak i ten program. Jeśli natomiast podprocedura w bibliotece jest zadeklarowana przy użyciu słowa kluczowego Friend, dostęp do niej można uzyskać tylko wewnątrz niej, nie w programie.
Protected Friend — oznacza, że podprocedura jest zarówno Protected, jak i Friend, zatem dostępna tylko w swoim projekcie i swojej klasie lub klasach potomnych.
Private — oznacza, że podprocedura jest dostępna tylko w klasie lub module,
w którym została zdefiniowana. Aby zmniejszyć ilość danych do zapamiętania przez programistę, należy trzymać się ogólnej zasady deklarowania podprocedur z jak najbardziej ograniczoną dostępnością, która pozwala im normalnie działać. Jeśli to możliwe, deklaruj podprocedury jako prywatne. Dzięki temu programiści pracujący nad innymi częściami aplikacji nie będą musieli nawet wiedzieć o ich istnieniu. Będą mogli tworzyć własne podprocedury o takich samych nazwach, przy czym nie będzie ryzyka, że użyją nieodpowiedniej. Jeśli później okaże się, że podprocedura jest potrzebna poza swoim modułem lub klasą, będzie można zmienić jej deklarację, aby zwiększyć dostępność.
Nazwa podprocedury Nazwa podprocedury musi być według zasad języka Visual Basic poprawnym identyfikatorem. Oznacza to, że powinna zaczynać się od litery lub znaku podkreślenia. Dalej może znajdować się zero lub więcej liter, cyfr i znaków podkreślenia. Jeśli nazwa zaczyna się od znaku podkreślenia, musi zawierać przynajmniej jeden inny znak, dzięki czemu Visual Basic odróżni ją od znaku kontynuacji wiersza. Wielu programistów stosuje tzw. wielbłądzią konwencję nazywania podprocedur. Polega ona na pisaniu każdego wyrazu składającego się na nazwę z wielkiej litery. Dobrym zwyczajem jest zaczynanie nazwy krótkim czasownikiem oraz opisywanie, co dana procedura robi. Przykładowe nazwy tego typu to LoadData, SaveNetworkConfiguration oraz PrintExpenseReport. Nazwy podprocedur, które zaczynają się od znaków podkreślenia, mogą być trudne do odczytania. Dlatego należy zachować je dla specjalnych przypadków lub całkowicie ich unikać. Szczególnie mylące są nazwy typu _1 czy __ (dwa znaki podkreślenia).
Rozdział 17.
Podprocedury i funkcje
367
Parametry Sekcja parametry definiuje argumenty przyjmowane przez podprocedurę jako parametry. W deklaracji parametrów należy podać ich liczbę oraz typy. Sekcja ta również wyznacza nazwy identyfikujące te wartości. Deklarowanie parametrów jest bardzo podobne do deklarowania zmiennych. Więcej informacji na temat deklarowania zmiennych, typów danych i tym podobnych można znaleźć w rozdziale 15. — „Typy danych, zmienne i stałe”. W kolejnych podrozdziałach opiszę niektóre najważniejsze szczegóły związane z deklarowaniem parametrów.
Słowo kluczowe ByVal Jeśli przed deklaracją parametru zostanie umieszczone opcjonalne słowo kluczowe ByVal, podprocedura będzie tworzyć własną kopię tego parametru z zasięgiem procedurowym. Podprocedura ta może dowolnie zmodyfikować tę wartość, a jej odpowiednik w procedurze wywołującej nie ulegnie zmianie. Przeanalizujmy poniższy przykładowy kod. Program główny inicjuje zmienną A i drukuje jej wartość w oknie Output. Następnie wywołuje podprocedurę DisplayDouble, która deklaruje swój parametr X przy użyciu słowa kluczowego ByVal. Podprocedura ta podwaja wartość parametru X, po czym wyświetla tę nową wartość. Ponieważ parametr ten został zadeklarowany przy użyciu słowa kluczowego ByVal, podprocedura posiada własną lokalną kopię tej zmiennej, dzięki czemu podwojenie jej wartości nie powoduje zmiany wartości zmiennej A w programie głównym. Kiedy podprocedura kończy działanie, a sterowanie wraca do aplikacji głównej, zostaje wyświetlona wartość zmiennej A. Private Sub Main() Dim A As Integer = 12 Debug.WriteLine("Main: " DisplayDouble(A) Debug.WriteLine("Main: " End Sub
&
A)
&
A)
Private Sub DisplayDouble(ByVal X As Integer) X *= 2 Debug.WriteLine("DisplayDouble: " & X) End Sub
Poniżej znajduje się wynik działania tego programu: Main: 12 DisplayDouble: 24 Main: 12
368
Część II
Wstęp do języka Visual Basic
Słowo kluczowe ByRef Jeśli parametr zostanie zadeklarowany przy użyciu słowa kluczowego ByRef, podprocedura nie utworzy osobnej kopii tej zmiennej parametrycznej. W zamian użyje referencji do oryginalnej zmiennej przekazanej do niej. Wszelkie modyfikacje poczynione w tej wartości przez tę podprocedurę będą widoczne także w podprocedurze wywołującej. Przeanalizujmy poniższy program. Jego kod jest prawie taki sam jak ten z poprzedniej aplikacji. Różnica polega na tym, że podprocedura DisplayDouble deklaruje swój parametr przy użyciu słowa kluczowego ByRef. Tak jak to miało miejsce poprzednio, program główny inicjuje zmienną A i drukuje jej wartość w oknie Output. Następnie wywołuje podprocedurę DisplayDouble, która podwaja swój parametr X i wyświetla wynik tego działania. Ponieważ parametr X został zadeklarowany przy użyciu słowa kluczowego ByRef, procedura ta podwaja wartość zmiennej A, przekazanej do niej przez program główny. Kiedy podprocedura kończy działanie, a sterowanie wraca do programu głównego, wyświetla on nową podwojoną wartość zmiennej A. Private Sub Main() Dim A As Integer = 12 Debug.WriteLine("Main: " DisplayDouble(A) Debug.WriteLine("Main: " End Sub
&
A)
&
A)
Private Sub DisplayDouble(ByRef X As Integer) X *= 2 Debug.WriteLine("DisplayDouble: " & X) End Sub
Poniżej znajduje się wynik działania tego programu: Main: 12 DisplayDouble: 24 Main: 24
Deklarowanie tablic ByVal i ByRef Jeśli parametr tablicowy zostanie zadeklarowany przy użyciu słowa kluczowego ByVal lub ByRef, słowo to odniesie się do samej tej tablicy, a nie do jej wartości. W obu przypadkach podprocedura może zmodyfikować wartości tej tablicy. Przedstawiona na poniższym listingu podprocedura o nazwie DoubleArrayValues posiada parametr arr. Jest on tablicą liczb całkowitych. Został zadeklarowany przy użyciu słowa kluczowego ByVal. Podprocedura ta przechodzi za pomocą pętli przez tę tablicę i podwaja każdą jej wartość. Następnie jeszcze raz przechodzi przez tę tablicę i wyświetla jej nowe wartości. Później przypisuje zmienną arr do nowej tablicy liczb całkowitych. Na zakończenie ponownie przechodzi za pomocą pętli przez tę nową tablicę, po czym jeszcze raz wyświetla nowe wartości. Private Sub DoubleArrayValues(ByVal arr() As Integer) ' Podwajanie wartości. For i As Integer = arr.GetLowerBound(0) To arr.GetUpperBound(0)
Rozdział 17.
Podprocedury i funkcje
369
arr(i) *= 2 Next i ' Wyświetlanie wartości. For i As Integer = arr.GetLowerBound(0) To arr.GetUpperBound(0) Debug.WriteLine(arr(i)) Next i Debug.WriteLine("----------”) ' Utworzenie nowej tablicy liczb całkowitych. arr = New Integer() {-1, -2} ' Wyświetlenie tych wartości. For i As Integer = arr.GetLowerBound(0) To arr.GetUpperBound(0) Debug.WriteLine(arr(i)) Next i Debug.WriteLine("----------”) End Sub
Poniższy kod deklaruje tablicę liczb całkowitych, która zawiera liczby 1, 2 i 3. Wywołuje on podprocedurę DoubleArrayValues i przechodzi za pomocą pętli przez tę tablicę, a następnie wyświetla jej wartości po zakończeniu działania tej podprocedury. Dim the_values() As Integer = {1, 2, 3} DoubleArrayValues(the_values) For i As Integer = the_values.GetLowerBound(0) To the_values.GetUpperBound(0) Debug.WriteLine(the_values(i)) Next i
Poniżej znajduje się wynik działania tego programu. Podprocedura DoubleArrayValues wyświetla podwojone wartości tablicy 2, 4 i 6, przypisuje nową tablicę do swojej lokalnej zmiennej arr oraz wyświetla jej wartości -1 i -2. Kiedy podprocedura ta kończy działanie, program główny wyświetla swoją własną wersję tych wartości. Zauważ, że zostały one zaktualizowane przez podprocedurę DoubleArrayValues, ale przypisanie zmiennej arr do nowej tablicy nie wpłynęło w żaden sposób na tablicę the_values programu głównego. 2 4 6 ----------1 -2 ---------2 4 6
Zobaczmy teraz, co się stanie, gdy podprocedura DoubleArrayValues zostanie zadeklarowana za pomocą poniższej instrukcji: Private Sub DoubleArrayValues(ByRef arr() As Integer)
370
Część II
Wstęp do języka Visual Basic
Gdy podprocedura DoubleArrayValues przypisuje nową tablicę do swojej zmiennej arr, procedura wywołująca ją widzi tę zmianę, dzięki czemu tablica the_values otrzyma tę nową tablicę. Poniżej znajduje się wynik działania takiego programu: 2 4 6 ----------1 -2 ----------1 -2
Parametry w nawiasach Można na kilka sposobów sprawić, że podprocedura nie zmodyfikuje parametru zadeklarowanego przy użyciu słowa kluczowego ByRef. Najtrudniejsza do zrozumienia sytuacja ma miejsce wówczas, gdy zmienna przekazywana do podprocedury zostanie otoczona nawiasami. Te ostatnie zmuszają Visual Basic do wyznaczenia wartości tego, co się w nich znajduje, jako wyrażenia. Program tworzy tymczasową zmienną, która przechowuje wynik tego wyrażenia, po czym przekazuje ją do procedury. Jeśli parametr tej procedury jest zadeklarowany przy użyciu słowa kluczowego ByRef, podprocedura ta aktualizuje tę tymczasową zmienną, ale nie wartość oryginalną, przez co procedura wywołująca nie widzi żadnych zmian tej wartości. Poniższy fragment kodu wywołuje podprocedurę DisplayDouble poprzez przekazanie do niej zmiennej A w nawiasach. Podprocedura ta zmienia wartość swojego parametru, ale wynik tej modyfikacji nie zostaje przeniesiony na zmienną A. Private Sub Main() Dim A As Integer = 12 Debug.WriteLine("Main: " DisplayDouble((A)) Debug.WriteLine("Main: " End Sub
&
A)
&
A)
Private Sub DisplayDouble(ByRef X As Integer) X *= 2 Debug.WriteLine("DisplayDouble: " & X) End Sub
Oto wynik działania tego programu: Main: 12 DisplayDouble: 24 Main: 12
Więcej informacji na temat deklarowania parametrów przy użyciu słów kluczowych ByVal i ByRef znajduje się w rozdziale 15. — „Typy danych, zmienne i stałe”.
Rozdział 17.
Podprocedury i funkcje
371
Słowo kluczowe Optional Jeśli parametr zostanie zadeklarowany przy użyciu słowa kluczowego Optional, może on zostać pominięty w wywołaniu podprocedury. Deklarując parametr opcjonalny, należy podać jego wartość domyślną, która zostanie użyta w razie jego opuszczenia przez procedurę wywołującą. Przedstawiona na poniższym listingu podprocedura DisplayError przyjmuje opcjonalny parametr łańcuchowy. Jeśli procedura wywołująca dostarczy ten parametr, ta podprocedura wyświetli go. Jeżeli ten parametr nie zostanie podany przez procedurę wywołującą, procedura DisplayError wyświetli swój domyślny komunikat "Wystąpił błąd". Podprocedura PlaceOrder sprawdza swój parametr the_customer. Jeśli ma on wartość Nothing, podprocedura ta wywołuje podprocedurę DisplayError, aby wyświetlić komunikat "Nie ma klienta w podprocedurze PlaceOrder". Następnie PlaceOrder wywołuje funkcję IsValid parametru the_customer. Jeśli funkcja ta zwróci wartość False, podprocedura ta wywoła podprocedurę DisplayError. Tym razem opuszcza ona parametr, dzięki czemu podprocedura DisplayError wyświetla swój domyślny komunikat. Private Sub DisplayError(Optional ByVal error_message As String = _ "Wystąpił błąd”) MsgBox(error_message) End Sub Private Sub PlaceOrder(ByVal the_customer As Customer, _ ByVal order_items() As OrderItem) ' Sprawdzenie, czy parametr the_customer istnieje. If the_customer Is Nothing Then DisplayError("Nie ma klienta w podprocedurze PlaceOrder”) Exit Sub End If ' Sprawdzenie, czy parametr the_customer jest poprawny. If Not the_customer.IsValid() Then DisplayError() Exit Sub End If ' Wygenerowanie zamówienia. ... End Sub
Parametry opcjonalne muszą znajdować się na końcu listy parametrów. Jeśli jeden z parametrów posiada słowo kluczowe Optional, wszystkie znajdujące się za nim parametry również muszą je mieć. Najlepszym zastosowaniem dla parametrów opcjonalnych jest inicjacja wartości w konstruktorze klasy. Poniższy listing przedstawia klasę DrawableRectangle. Konstruktor tej klasy przyjmuje jako parametry położenie oraz rozmiar prostokąta. Są one opcjonalne, a więc można je opuścić. Ponieważ każdy parametr ma wartość domyślną, konstruktor zawsze posiada wszystkie cztery wartości, dzięki czemu może zainicjować zmienną obiektową Bounds.
372
Część II
Wstęp do języka Visual Basic
Public Class DrawableRectangle Public Bounds As Rectangle Public Sub New( _ Optional ByVal X As Integer = 0, _ Optional ByVal Y As Integer = 0, _ Optional ByVal Width As Integer = 100, _ Optional ByVal Height As Integer = 100) Bounds = New Rectangle(X, Y, Width, Height) End Sub ... End Class
Należy pamiętać, że przeciążone podprocedury nie mogą się różnić tylko parametrami opcjonalnymi. Gdyby w wywołaniu podprocedury zostały opuszczone wszystkie opcjonalne parametry, Visual Basic nie wiedziałby, którą jej wersję wybrać.
Parametry opcjonalne a przeciążanie Programiści są podzieleni co do tego, kiedy należy używać parametrów opcjonalnych, a kiedy stosować przeciążanie procedur. Wyobraźmy sobie na przykład, że mamy metodę o nazwie FireEmployee (zwolnienie pracownika), która przyjmuje jeden lub dwa parametry określające imię i nazwisko użytkownika lub imię i nazwisko oraz powód zwolnienia. Parametr dotyczący przyczyny zwolnienia można zadeklarować jako opcjonalny lub utworzyć dla każdej możliwej listy parametrów osobną przeciążoną wersję tej procedury. Jednym z argumentów przemawiających za parametrami opcjonalnymi jest fakt, że przeciążanie metod wiąże się z duplikowaniem dużych ilości kodu. Łatwo jednak sprawić, że każda wersja tej metody będzie wywoływała inną wersję, przyjmującą więcej parametrów, poprzez przekazanie wartości domyślnych. Na przykład w poniższym kodzie pierwsza wersja metody FireEmployee wywołuje jej drugą wersję: Public Sub FireEmployee(ByVal employee_name As String) FireEmployee(employee_name, "Powód nieznany”) End Sub Public Sub FireEmployee(ByVal employee_name As String, ByVal reason As String) ... End Sub
Przeciążanie metod stosuje się zwykle wtedy, gdy różne wersje jednej procedury robią coś innego. Być może uda się zmusić jedną procedurę do wykonywania różnych czynności — w zależności od wartości parametrów opcjonalnych — ale z reguły wydzielenie kodu do przeciążonej procedury pozwala na utworzenie bardziej przejrzystego kodu.
Tablice parametrów Czasami wygodnie jest, gdy podprocedura może przyjmować zmienną liczbę parametrów. Może to być na przykład podprocedura przyjmująca jako parametry adresy e-mail osób, do których mają zostać wysłane wiadomości. Przechodziłaby ona za pomocą pętli przez nazwiska, aby wysłać do każdego po kolei list elektroniczny.
Rozdział 17.
Podprocedury i funkcje
373
Jednym ze sposobów jest użycie długiej listy parametrów opcjonalnych. Na przykład wartość każdego z nich mogłaby zostać ustawiona na pusty łańcuch. Następnie podprocedura wysyłałaby e-mail na każdy adres w parametrze, który nie jest pusty. Niestety taka podprocedura musiałaby zawierać osobny kod dla każdego z opcjonalnych parametrów. Na dodatek zawsze istniałby jakiś górny limit parametrów, które mogą zostać przyjęte (bez względu na to, ile z nich zostanie wpisanych na listę). Lepszym rozwiązaniem jest użycie słowa kluczowego ParamArray, które zamienia ostatni argument podprocedury na listę parametrów. Tablica parametrów może zawierać dowolną liczbę wartości parametrów. W czasie działania programu podprocedura przechodzi za pomocą pętli przez taką tablicę i przetwarza w odpowiedni sposób wartości jej parametrów. Przedstawiona na poniższym listingu podprocedura o nazwie DisplayAverage przyjmuje jako parametr tablicę parametrów values. Sprawdza wartości brzegowe tej tablicy, aby upewnić się, że zawiera ona przynajmniej jedną wartość. Jeśli tablica nie jest pusta, podprocedura sumuje jej wartości i dzieli wynik przez liczbę wszystkich wartości. W ten sposób oblicza średnią. ' Oblicza średnią kilku wartości. Private Sub DisplayAverage(ByVal ParamArray values() As Double) ' Nic nie robi, jeśli nie ma żadnych parametrów. If values Is Nothing Then Exit Sub If values.Length < 1 Then Exit Sub ' Obliczanie średniej. Dim total As Double = 0 For i As Integer = LBound(values) To UBound(values) total += values(i) Next i ' Wyświetlenie wyników. MessageBox.Show((total / values.Length).ToString) End Sub
Poniżej prezentuję jeden ze sposobów wykorzystania tej podprocedury w programie. W tym przypadku podprocedura DisplayAverage wyświetliłaby średnią liczb całkowitych od 1 do 7, czyli 4. DisplayAverage(1, 2, 3, 4, 5, 6, 7)
Tablice parametrów mają następujące ograniczenia:
Podprocedura może mieć tylko jedną tablicę parametrów, która musi znajdować się na końcu listy parametrów.
Żaden z pozostałych parametrów na liście parametrów nie może być opcjonalny.
Wszystkie listy parametrów są zadeklarowane przy użyciu słowa kluczowego ByVal, dzięki czemu żadne zmiany dokonywane przez podprocedurę w tablicy parametrów nie mają wpływu na procedurę wywołującą.
374
Część II
Wstęp do języka Visual Basic
Wartości w tablicy parametrów są zawsze opcjonalne, dzięki czemu procedura wywołująca może dostarczyć dowolną ich liczbę (także zero). Przy deklarowaniu tablicy parametrów nie można używać słowa kluczowego Optional.
Wszystkie elementy tablicy parametrów muszą mieć ten sam typ danych. Można jednak użyć tablicy zawierającej ogólny typ Object, dzięki czemu będzie ona mogła zawierać praktycznie wszystko. Wadą tej metody jest to, że być może okaże się konieczne przekonwertowanie tych elementów na bardziej specyficzny typ (za pomocą funkcji DirectCast lub CInt). W przeciwnym razie wykorzystanie ich cechy będzie niemożliwe.
Procedura wywołująca może przekazać do tablicy parametrów dowolną liczbę wartości (także zero), a także przekazać wartość Nothing. Program może też zamiast bezpośrednich wartości przekazać w miejsce tablicy parametrów tablicę wartości. Poniższe dwa wywołania podprocedury DisplayAverage dają ten sam wynik: DisplayAverage(1, 2, 3, 4, 5, 6, 7) Dim values() As Double = {1, 2, 3, 4, 5, 6, 7} DisplayAverage(values)
Klauzula Implements interfejs.podprocedura Interfejs definiuje zestaw własności, metod i zdarzeń, które klasa go implementująca musi udostępniać. Przypomina on nieco klasę z wszystkimi własnościami, metodami i zdarzeniami zadeklarowanymi przy użyciu słowa kluczowego MustOverride. Każda klasa dziedzicząca po takiej klasie bazowej musi udostępniać własne implementacje tych wszystkich elementów.
Przedstawiony na poniższym listingu interfejs o nazwie IDrawable definiuje podprocedurę Draw, funkcję Bounds oraz własność IsVisible. Na początku definicji klasy DrawableRectangle znajduje się instrukcja Implements IDrawable. Informuje ona Visual Basic, że ta klasa implementuje interfejs IDrawable. Tworząc deklarację klasy, wystarczy wpisać instrukcję Implements i nacisnąć klawisz Enter, a Visual Basic automatycznie wstawi deklaracje potrzebne do zaspokojenia wymagań tego interfejsu. W tym przypadku została utworzona pusta funkcja Bounds, podprocedura Draw oraz procedury własności IsVisible. Programiście pozostaje tylko wypełnić je kodem. Często stosowaną przez programistów praktyką jest zaczynanie nazwy interfejsu od wielkiej litery I, dzięki czemu wiadomo od razu, że jest to interfejs. Public Interface IDrawable Sub Draw(ByVal gr As Graphics) Function Bounds() As Rectangle Property IsVisible() As Boolean End Interface Public Class DrawableRectangle Implements IDrawable
Rozdział 17.
Podprocedury i funkcje
375
Public Function Bounds() As System.Drawing.Rectangle _ Implements IDrawable.Bounds End Function Public Sub Draw(ByVal gr As System.Drawing.Graphics) _ Implements IDrawable.Draw End Sub Public Property IsVisible() As Boolean Implements IDrawable.IsVisible Get End Get Set(ByVal Value As Boolean) End Set End Property End Class
Na powyższym listingu widać, w którym miejscu została użyta klauzula Implements interfejs. podprocedura. W tym przypadku mamy podprocedurę Draw, która implementuje metodę Draw interfejsu IDrawable. Kiedy wpisana zostanie instrukcja Implements i naciśnięty klawisz Enter, Visual Basic wygeneruje puste procedury, niezbędne do zaspokojenia wymagań interfejsu. Wtedy nie trzeba będzie samodzielnie wpisywać całej klauzuli Implements interfejs.podprocedura. Visual Basic wstawi ją za nas. Jedyna sytuacja, w której konieczna jest ręczna modyfikacja tej instrukcji, ma miejsce, gdy zostanie zmieniona nazwa interfejsu lub podprocedury — albo gdy zechcemy użyć jakiejś innej podprocedury do zaspokojenia potrzeb interfejsu. Można by na przykład wstawić do klasy DrawableRectangle metodę DrawRectangle i dodać do jej deklaracji instrukcję Implements IDrawable.Draw. Visual Basic nie obchodzą nazwy procedur, jeśli jedna z nich implementuje metodę IDrawable.Draw.
Instrukcje Sekcja instrukcje podprocedury zawiera cały kod Visual Basica, który jest potrzebny do jej działania. Mogą się w niej znaleźć wszystkie typowe deklaracje zmiennych, pętle For, bloki Try i inne utensylia języka. W ciele podprocedury nie mogą znajdować się instrukcje deklarujące moduły, klasy, podprocedury, funkcje, struktury, typy wyliczeniowe ani żadne inne instrukcje poziomu plikowego. Nie można na przykład zdefiniować jednej podprocedury w innej podprocedurze. Nową instrukcją, której można używać w podprocedurach, jest Exit Sub. Polecenie to zmusza podprocedurę do natychmiastowego zakończenia działania i zwrócenia sterowania do procedury, która ją wywołała. Instrukcją równoważną z Exit Sub jest Return.
376
Część II
Wstęp do języka Visual Basic
Instrukcji Exit Sub i Return można używać dowolną liczbę razy, zmuszając podprocedurę do kończenia działania w różnych miejscach. Na przykład poniższa podprocedura sprawdza, czy podany numer telefonu jest w formacie siedmio-, czy dziesięciocyfrowym. Jeśli jest to format dziesięciocyfrowy, podprocedura kończy działanie. Jeżeli siedmiocyfrowy, podprocedura również kończy działanie. Jeśli numer nie jest w żadnym z tych dwóch formatów, podprocedura wyświetla komunikat o błędzie. Private Sub ValidatePhoneNumber(ByVal phone_number As String) ' Sprawdzenie, czy numer jest w formacie dziesięciocyfrowym. If phone_number Like "###-###-####” Then Exit Sub ' Sprawdzenie, czy numer jest w formacie siedmiocyfrowym. If phone_number Like "###-####” Then Return ' Numer jest niepoprawny. MsgBox("Niepoprawny numer telefoniczny " MsgBoxStyle.Exclamation, _ "Niepoprawny numer telefoniczny”) End Sub
& phone_number, _
Funkcje Funkcje są w zasadzie tym samym co podprocedury — tyle że zawsze zwracają jakąś wartość. Składnia definicji funkcji przedstawia się następująco: [lista_atrybutów] [tryb_dziedziczenia] [dostępność] _ Function nazwa_funkcji ([parametry]) [As typ_zwrotny] [ Implements interfejs.funkcja ] [ instrukcje ] End Function
Składnia ta jest prawie taka sama jak składnia definicji podprocedury. Opis większości klauzul tej deklaracji znajduje się we wcześniejszym podrozdziale — „Podprocedury”. Jedna z różnic między nimi polega na tym, że definicja funkcji kończy się instrukcją End Function zamiast End Sub. Analogicznie: funkcja może zakończyć wcześniej działanie za pomocą instrukcji Exit Function, a nie Exit Sub. Jedyną prawdziwą różnicą pomiędzy tymi dwiema składniami jest obecność klauzuli As typ_zwrotny w deklaracji funkcji za listą parametrów. Informuje ona Visual Basic o typie wartości zwracanej przez funkcję. Wartość zwrotną funkcji da się ustawić na dwa różne sposoby. Po pierwsze, można ustawić jej nazwę na wartość, która ma zostać zwrócona. Przedstawiona poniżej funkcja o nazwie Factorial oblicza silnię podanej liczby. Dla N! silnia N wynosi N*(N-1)*(N-2)…*1. Funkcja ta inicjuje swoją zmienną result wartością 1, po czym za pomocą pętli przechodzi od tej wartości do liczby określonej przez parametr number, mnożąc te wartości przez wartość zmiennej result. Na zakończenie ustawia swoją nazwę, Factorial, na wartość zmiennej result, która powinna zostać zwrócona.
Rozdział 17.
Podprocedury i funkcje
377
Private Function Factorial(ByVal number As Integer) As Double Dim result As Double = 1 For i As Integer = 2 To number result *= i Next i Factorial = result End Function
Funkcja może przed zwróceniem wartości tyle razy przypisywać i zmieniać przypisanie swojej wartości zwrotnej, ile potrzebuje. Wartość, która została przypisana jako ostatnia, jest wartością zwrotną funkcji. Po drugie, wartość zwrotną funkcji można przypisać do słowa kluczowego Return. Poniżej znajduje się funkcja Factorial, zmodyfikowana w taki sposób, by zademonstrować tę technikę: Private Function Factorial(ByVal number As Integer) As Double Dim result As Double = 1 For i As Integer = 2 To number result *= i Next i Return result End Function
Instrukcja Return jest mniej więcej równoznaczna z ustawieniem nazwy funkcji na wartość zwrotną i użyciem instrukcji Exit Function bezpośrednio za nim. Może ona jednak umożliwić kompilatorowi wykonanie pewnych czynności optymalizujących, dlatego technika ta jest uznawana za lepszą od ustawiania nazwy funkcji na wartość zwrotną.
Procedury własności Procedury własności mogą reprezentować wartości podobne do własności. Typowa procedura własności do odczytu i zapisu zawiera funkcję zwracającą wartość własności oraz podprocedurę przypisującą tę wartość. Poniższy listing przedstawia procedury własności, które implementują własność Value. Procedura Property Get jest funkcją zwracającą wartość w prywatnej zmiennej o nazwie m_value. Property Set zapisuje nową wartość w zmiennej m_Value. Private m_Value As Single Property Value() As Single Get Return m_Value End Get Set(ByVal Value As Single) m_Value = Value End Set End Property
378
Część II
Wstęp do języka Visual Basic
Mimo iż własność ta została zaimplementowana jako para procedur własności, program może traktować tę wartość jako pojedynczą własność. Wyobraźmy sobie, że powyższy kod znajduje się w klasie OrderItem. W takiej sytuacji poniższy kod ustawia własność Value obiektu klasy OrderItem o nazwie paper_item: paper_item.Value = 19.95
Procedury własności można dodawać do wszystkich typów modułów obiektowych. Na przykład za ich pomocą da się zaimplementować własności dla formularzy lub własnych klas. Mniej oczywiste jest to, że procedur własności można także używać w modułach kodu. Dla procedur wyglądają one jak zwykłe zmienne. Jeśli zaprezentowany wyżej przykład procedury własności zostałby umieszczony w module kodu, program mógłby działać w taki sposób, jakby miał do dyspozycji zmienną o nazwie Value, która została zdefiniowana w module. Więcej informacji na temat procedur własności znajduje się w podrozdziale „Procedury własności” rozdziału 15. — „Typy danych, zmienne i stałe”.
Metody rozszerzające Metody rozszerzające (ang. extension methods) pozwalają na dodawanie nowych metod do istniejących klas bez konieczności ponownego pisania tych klas lub tworzenia ich klas potomnych. Aby utworzyć metodę rozszerzającą, należy dodać do deklaracji zwykłej metody atrybut Extension. Dalsza część to w zasadzie zwykła podprocedura lub funkcja przyjmująca jeden lub więcej parametrów. Pierwszy z nich wyznacza klasę rozszerzaną przez tę metodę. Parametr ten może być wykorzystywany przez metodę do sprawdzania elementu, dla którego została wywołana. Pozostałe parametry są przekazywane do metody i odpowiednio przez nią przetwarzane. Na przykład poniższy kod dodaje podprocedurę o nazwie MatchesRegexp do klasy String. Atrybut Extension informuje Visual Basic, że jest to metoda rozszerzająca. Pierwszym parametrem tej metody jest łańcuch, a więc metoda ta rozszerza klasę String. Drugi to wyrażenie regularne. Metoda ta zwraca wartość True, jeśli parametr String pasuje do tego wyrażenia regularnego. ' Zwraca wartość True, jeśli łańcuch pasuje do wyrażenia regularnego. < Extension() > _ Public Function MatchesRegexp(ByVal the_string As String, _ ByVal regular_expression As String) As Boolean Dim reg_exp As New Regex(regular_expression) Return reg_exp.IsMatch(the_string) End Function
Poniżej znajduje się przykład użycia tej metody do sprawdzenia, czy łańcuch zapisany w zmiennej phone_number wygląda jak siedmiocyfrowy numer telefoniczny: If Not phone_number.MatchesRegexp("^[2-9]\d{2}-\d{4}$”) Then MessageBox.Show("Niepoprawny format numeru”) End If
Rozdział 17.
Podprocedury i funkcje
379
Zastosowanie metody MatchesRegexp demonstruje przykładowy program o nazwie ValidatePhone, który można pobrać z serwera FTP wydawnictwa Helion. Program ten wykorzystuje tę metodę także do zdefiniowania poniższych trzech dodatkowych metod rozszerzających, które sprawdzają, czy łańcuch wygląda jak poprawny siedmio- lub dziesięciocyfrowy numer telefoniczny. Metody te wywołują metodę MatchesRegexp i przekazują do niej odpowiednie wyrażenie regularne. ' Zwraca wartość True, jeśli łańcuch wygląda jak siedmiocyfrowy numer telefoniczny. < Extension() > _ Public Function IsValidPhoneNumber7digit(ByVal the_string As String) _ As Boolean Return the_string.MatchesRegexp("^[2-9]\d{2}-\d{4}$”) End Function ' Zwraca wartość True, jeśli łańcuch wygląda jak dziesięciocyfrowy numer telefoniczny. < Extension() > _ Public Function IsValidPhoneNumber10digit(ByVal the_string As String) _ As Boolean Return the_string.MatchesRegexp("^([2-9]\d{2}-){2}\d{4}$”) End Function ' Zwraca wartość True, jeśli łańcuch wygląda jak siedmio- lub dziesięciocyfrowy numer telefoniczny. < Extension() > _ Public Function IsValidPhoneNumberUS(ByVal the_string As String) _ As Boolean Return IsValidPhoneNumber7digit(the_string) OrElse _ IsValidPhoneNumber10digit(the_string) End Function
Jeśli po zbudowaniu klasy trzeba zmienić jakiejś jej własności, najłatwiejszym sposobem jest bezpośrednie zmodyfikowanie jej kodu. Spowoduje to mniejsze zamieszanie niż użycie metod rozszerzających, które mogą znajdować się w jakimś rzadko używanym module, sprawiającym wrażenie niepowiązanego z oryginalną klasą. Jeśli konieczne jest dodanie metod do istniejących klas, których nie można bezpośrednio zmodyfikować, na przykład String i innych standardowych klas Visual Basica i .NET Framework, metody rozszerzające mogą się okazać niezastąpione.
Funkcje lambda Są to funkcje definiowane za pomocą jednej instrukcji. Definicja funkcji lambda zaczyna się od słowa kluczowego Function. Dalej powinna znajdować się nazwa funkcji, wymagane przez nią parametry oraz pojedyncza instrukcja, której wartość jest wartością zwrotną całej funkcji. Poniższy fragment kodu pochodzi z programu LambdaFunction, który można pobrać z serwera FTP wydawnictwa Helion:
380
Część II
Wstęp do języka Visual Basic
' Definicja funkcji lambda, która dodaje dwie liczby całkowite. Dim plus = Function(i1 As Integer, i2 As Integer) i1 + i2 ' Zmienne A i B. Dim A As Integer = Integer.Parse(txtA.Text) Dim B As Integer = Integer.Parse(txtB.Text) ' Wywołanie funkcji lambda. txtResult.Text = plus(A, B).ToString
Na początku znajduje się definicja zmiennej o nazwie plus. Przechowuje ona referencję do funkcji lambda, która przyjmuje jako parametry dwie liczby całkowite i zwraca ich sumę. Następnie program pobiera wartości wejściowe z pól tekstowych i wywołuje funkcję plus, po czym przekazuje jej te wartości. Wynik konwertuje na łańcuch i wyświetla go w polu tekstowym o nazwie txtResult. W tym przykładowym programie została utworzona zmienna przechowująca referencję do funkcji lambda, a następnie wywołano tę funkcję za pomocą tej właśnie zmiennej. Równie dobrze można by wywołać tę funkcję podczas jej definiowania. Technikę tę demonstruje program InlineFunction (do pobrania z serwera FTP wydawnictwa Helion), z którego pochodzi poniższy fragment kodu. W wierszu tym jest tworzona i wywoływana funkcja bez tworzenia jakiejkolwiek referencji. txtResult.Text = _ (Function(i1 As Integer, i2 As Integer) i1 + i2)(A, B).ToString
Ze względu na to, że funkcje lambda są deklarowane w jednym wierszu kodu, czasami nazywa się je funkcjami inline. Funkcja lambda zdefiniowana wewnątrz podprocedury lub funkcji jest czasami nazywana funkcją zagnieżdżoną. Funkcje lambda różnią się od funkcji inline. Bardziej adekwatną nazwą dla tego drugiego przykładu jest funkcja inline, ponieważ funkcja ta znajduje się w tym samym wierszu, który jej używa. Poza tym nie ma ona nazwy. Bez względu na to, jakiej metody użyto do zdefiniowania funkcji lambda, program może przekazać ją do innej procedury, która wywoła ją później. Wyobraźmy sobie na przykład, że podprocedura PerformCalculations przyjmuje jako parametr funkcję, której powinna użyć do wykonania swoich obliczeń. Poniższy listing przedstawia program wywołujący podprocedurę PerformCalculations poprzez przekazanie do niej zdefiniowanych wcześniej funkcji lambda: ' Definicja funkcji plus. Dim plus = Function(i1 As Integer, i2 As Integer) i1 + i2 ' Wywołanie podporcedury PerformCalculations z przekazaniem do niej funkcji lambda. PerformCalculations(plus) ' Wywołanie podprocedury PerformCalculations z przekazaniem do niej funkcji inline lambda. PerformCalculations(Function(i1 As Integer, i2 As Integer) i1 + i2)
Funkcje inline zostały opracowane z myślą o LINQ. Są ona najczęściej używane w połączeniu z tą technologią. Więcej informacji na temat technologii LINQ znajduje się w rozdziale 21. — „LINQ”.
Rozdział 17.
Podprocedury i funkcje
381
Rozluźnione delegaty Jeśli zmienna zostanie przypisana do wartości zmiennej innego typu, w niektórych warunkach Visual Basic dokona automatycznej konwersji typów danych. Jeżeli zmienna typu Single zostanie ustawiona na wartość zmiennej typu Integer, typ Integer zostanie automatycznie przekonwertowany na Single. Jeśli wyłączona jest opcja Option Strict, można wykonać także operację odwrotną — jeżeli zmienna Integer zostanie przypisana do wartości typu Single, Visual Basic automatycznie przekonwertuje tę wartość Single na typ Integer (jeśli się da). W podobny sposób rozluźnione delegaty (ang. relaxed delegates) pozwalają w pewnych sytuacjach Visual Basicowi konwertować typy danych parametrów metod. Jeśli podprocedura jest wywoływana poprzez delegat, Visual Basic próbuje dokonać konwersji jej parametrów, jeśli tylko jest to możliwe. Najłatwiej zrozumieć to na przykładzie. Poniżej znajduje się deklaracja typu delegacyjnego o nazwie TestDelegate. Metody pasujące do tego delegatu powinny być podprocedurami przyjmującymi jako parametr obiekt klasy Control. ' Deklaracja typu delegacyjnego. Private Delegate Sub TestDelegate(ByVal ctl As Control)
Poniżej znajdują się definicje trzech podprocedur, z których każda przyjmuje inny typ parametru. Pierwsza przyjmuje obiekt klasy Object, druga obiekt klasy TextBox, trzecia zaś nie przyjmuje żadnego parametru. Należy zauważyć, że pierwsza z tych podprocedur nie zadziała, jeśli będzie włączona opcja Option Strict. Opcja ta uniemożliwia późne wiązanie, przez co program nie może użyć własności Text udostępnianej przez ogólny typ Object. ' Bardziej ogólny typ parametru. Private Sub Test1(ByVal obj As Object) obj.Text = "Test1” ' Musi być wyłączona opcja Option Strict. End Sub ' Bardziej specyficzny typ parametru. Private Sub Test2(ByVal text_box As TextBox) text_box.Text = "Test2” End Sub ' Brak parametru. Private Sub Test3() txtField3.Text = "Test3” End Sub
Poniższy kod deklaruje trzy zmienne typu TestDelegate i ustawia je na adresy powyższych trzech podprocedur: ' Zmienne typu delegatowego przechowują ' referencje do podprocedur. Private Sub1 As TestDelegate = AddressOf Test1 Private Sub2 As TestDelegate = AddressOf Test2 ' Musi być wyłączona opcja Option Strict. Private Sub3 As TestDelegate = AddressOf Test3
382
Część II
Wstęp do języka Visual Basic
Pierwsze przypisanie udało się, mimo że podprocedura Test1 nie pasuje dokładnie do typu delegatu. Podprocedura Test1 jako parametr przyjmuje obiekt klasy Object, a TestDelegate — obiekt klasy Control. Kiedy Visual Basic wywoła zmienną Sub1, przekaże ona do podprocedury parametr klasy Object, ponieważ Sub1 jest typu TestDelegate, a ten typ przyjmuje jako parametr obiekt klasy Control. Ten ostatni jest podtypem klasy Object, a więc można bezpiecznie przekazać obiekt klasy Control w miejsce obiektu klasy Object. Dzięki temu instrukcja przypisująca zmienną Sub1 do adresu podprocedury Test1 może działać. Drugi wiersz tego kodu, który przypisuje zmienną Sub2 do podprocedury Test2, działa tylko wtedy, gdy wyłączona jest opcja OptionStrict. Kiedy Visual Basic wywołuje zmienną Sub2, przekazuje tej podprocedurze obiekt klasy Control jako parametr, ponieważ zmienna Sub2 jest typu TestDelegate, który jako parametr przyjmuje obiekt klasy Control. Podprocedura Test2 przyjmuje jako parametr obiekt klasy TextBox, a nie każdy obiekt klasy Control jest polem tekstowym. Oznacza to, że w czasie projektowania Visual Basic nie ma możliwości sprawdzenia, czy wywołanie delegatu Sub2 jest bezpieczne, a więc jeśli włączona jest opcja Option Strict, uznaje to przypisanie za błąd. Jeżeli opcja Option Strict jest wyłączona, Visual Basic zezwala na to przypisanie, choć jeśli program przekaże w czasie działania do delegatu Sub2 inną kontrolkę niż TextBox, zakończy działanie awarią. Jest to sytuacja podobna do ustawienia zmiennej klasy TextBox na wartość zmiennej klasy Control. Jeśli opcja Option Strict jest włączona, Visual Basic nie zezwoli na to przypisanie. Ostatnia instrukcja przypisania ustawia zmienną Sub3 na adres podprocedury Test3. Podprocedura ta nie przyjmuje żadnych parametrów. Jest to specjalny przypadek dozwolony przez Visual Basic — jeśli metoda nie musi używać parametrów wyznaczonych przez delegat, może opuścić jego parametry. Należy pamiętać, że powinny zostać opuszczone wszystkie parametry albo żaden. Nie można opuścić tylko niektórych. Poniżej znajdują się wywołania podprocedur wskazywanych przez nasze trzy zmienne typu TestDelegate, które przekazują do nich referencje do różnych obiektów klasy TextBox. Podprocedura Sub1 traktuje pole tekstowe txtField1 jako obiekt klasy Object, Sub2 traktuje pole tekstowe txtField2 jako obiekt klasy TextBox, a Sub3 ignoruje swój parametr. Sub1(txtField1) Sub2(txtField2) Sub3(txtField3) ' Test3(txtField3) ' To nie działa.
Bezpośrednie wywołanie podprocedury Test3 w ostatnim wierszu nie działa. Opuszczenie listy parametrów metody jest dopuszczalne wyłącznie przy wywoływaniu jej poprzez delegat. Przy bezpośrednim wywołaniu lista dostarczonych parametrów musi pasować do listy parametrów zadeklarowanych w metodzie. Użycie tego kodu demonstruje program RelaxedDelegates, który można pobrać z serwera FTP wydawnictwa Helion. Wszystkie te zasady dotyczące rozluźnionych delegatów są nieco zagmatwane. Mimo iż zwiększają one nieco zakres możliwości programisty, mogą ujemnie wpłynąć na czytelność kodu. Możesz zadać pytanie: „Czym się martwić?”. Jeśli używasz delegatów, jak w tym przykładzie, możesz zechcieć uniknąć rozluźnionych delegatów, aby nie utrudniać czytania kodu.
Rozdział 17.
Podprocedury i funkcje
383
Zasady te dotyczą także procedur obsługi zdarzeń. W tym przypadku są one bardzo przydatne. Pozwalają zmieniać typy parametrów procedur obsługi na bardziej ogólne lub specyficzne, a nawet całkowicie je opuścić. Poniżej znajduje się prosta procedura obsługi zdarzenia Click przycisku. Przyjmuje ona dwa parametry typu Object i EventArgs. Poniższy kod pobiera jakiś tekst do pola tekstowego. Private Sub btnLoad_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnLoad.Click txtContents.Text = System.IO.File.ReadAllText(txtFile.Text) End Sub
Wiele procedur obsługi zdarzeń musi bezpośrednio mieć do czynienia z kontrolką, która zgłosiła zdarzenie. W takim przypadku pierwszą czynnością procedury obsługi zdarzeń jest zazwyczaj przekonwertowanie ogólnego parametru sender z typu Object na bardziej specyficzny typ kontrolki. Poniżej znajduje się podobna do powyższej procedura obsługi zdarzenia Click przycisku, ale deklarująca swój parametr sender jako typu Button. Wszystko działa, dopóki zdarzenie jest zgłaszane przez przycisk, dzięki czemu parametr sender rzeczywiście jest przyciskiem. Gdyby procedurę tę zastosowano dla zdarzenia TextChanged kontrolki TextBox, program uległby awarii podczas próby konwersji typu TextBox na Button przy zgłaszaniu zdarzenia. ' Opcja Option Strict musi być wyłączona. Private Sub btnLoad2_Click(ByVal btn As Button, _ ByVal e As Object) Handles btnLoad2.Click txtContents.Text = System.IO.File.ReadAllText(txtFile.Text) End Sub
Należy pamiętać, że w tej wersji konieczne jest wyłączenie opcji Option Strict. Jeśli jest ona włączona, Visual Basic nie zezwoli tej podprocedurze na obsługę zdarzenia Click przycisku. Jest to podobne do zablokowania przez opcję Option Strict ustawienia zmiennej klasy Button na ogólną zmienną klasy Object. W przedstawionych do tej pory przykładach zadeklarowane parametry miały bardziej ograniczone typy niż te przekazywane do kodu przez kontrolkę zgłaszającą zdarzenie. Da się je także uczynić bardziej ogólnymi. Parametr e można zadeklarować jako typu Object zamiast EventArgs. Zazwyczaj na niewiele się to zdaje. Może się przydać, gdybyśmy chcieli użyć tej samej procedury obsługi zdarzeń do przechwytywania różnego rodzaju zdarzeń dostarczających rozmaite typy argumentów, ale trudno wymyślić dobry przykład, w którym technika ta nie byłaby zbyt zagmatwana. Częściej zdarza się, że procedura obsługi zdarzeń całkowicie ignoruje swoje parametry. Zazwyczaj każdy przycisk posiada własną procedurę obsługi zdarzenia Click, dzięki czemu nie trzeba sprawdzać parametrów, aby dowiedzieć się, który przycisk został kliknięty. Poniżej znajduje się definicja procedury obsługi zdarzenia Click przycisku, nieprzyjmującej żadnych parametrów. Kiedy użytkownik klika przycisk btnLoad3, Visual Basic nie przekazuje tej procedurze żadnych parametrów. Kod ten jest jaśniejszy od poprzednich wersji, po części z tego powodu, że instrukcja Sub mieści się w całości w jednym wierszu.
384
Część II
Wstęp do języka Visual Basic
Private Sub btnLoad3_Click() Handles btnLoad3.Click txtContents.Text = System.IO.File.ReadAllText(txtFile.Text) End Sub
Użycie rozluźnionych delegatów demonstruje program RelaxedEventHandlers, który można pobrać z serwera FTP wydawnictwa Helion. Delegaty rozluźnione w przypadku zmiennych delegacyjnych czasem wprowadzają więcej zamieszania, niż są warte, ale mogą znacznie uprościć procedury obsługi zdarzeń. Zadeklarowanie parametrów z bardziej specyficznymi typami (na przykład Button zamiast Object) ułatwia pisanie i czytanie kodu. Jednak bardzo dużą wadą tej techniki jest konieczność wyłączenia opcji Option Strict. Jeszcze lepsza jest możliwość opuszczania parametrów, gdy nie są potrzebne. Pozwala to uprościć kod, przy czym wyłączenie opcji Option Strict nie jest konieczne.
Metody częściowe Metoda częściowa to prywatna podprocedura, która jest zadeklarowana w jednym miejscu, a zaimplementowana w innym. W jednym miejscu znajduje się deklaracja podprocedury ze słowem kluczowym Partial i pustym ciałem. Natomiast w innej części klasy umieszczono powtórzenie tej deklaracji bez słowa kluczowego Partial, ale z ciałem metody. Czemu to ma służyć? Metody częściowe zostały wynalezione na potrzeby generatorów kodu. Szczegóły ich dotyczące są bardzo zaawansowane technicznie; nie ma sensu ich tutaj opisywać. Metody częściowe są z założenia bardziej efektywną alternatywą dla zdarzeń. Zamiast zgłaszać zdarzenie do przechwycenia przez klasę, wygenerowany kod może wywołać metodę częściową. Jeśli ta ostatnia nie ma ciała, kompilator optymalizuje to wywołanie i nic się nie dzieje, dokładnie tak samo, jak gdyby obiekt nie przechwycił zdarzenia. Początkowo specjaliści z Microsoftu zamierzali zezwolić na dostarczanie domyślnego ciała dla metod częściowych, które miałoby być wykonywane, gdyby nie było żadnego innego. Funkcjonalność ta jednak nie pojawiła się w Visual Basicu 2008. Może będzie dostępna w przyszłości. Poniżej znajduje się zestawienie różnic pomiędzy procedurami obsługi zdarzeń a metodami częściowymi.
Zdarzenie może zostać przechwycone przez dowolną liczbę procedur obsługi zdarzeń, podczas gdy metoda częściowa ma tylko jedno ciało.
Zdarzenie może zostać zadeklarowane przy użyciu słów kluczowych Public, Private lub Friend, natomiast metoda częściowa musi być Private.
Zgłaszanie zdarzenia powoduje pewne opóźnienie, nawet gdy żadna procedura go nie przechwytuje. Jeśli metoda częściowa nie posiada ciała, kompilator ignoruje jej wywołanie, dzięki czemu nie ma żadnego opóźnienia.
Rozdział 17.
Podprocedury i funkcje
385
Procedury obsługi zdarzeń mogą być dodawane i usuwane przez program w czasie jego działania, natomiast ciało metody częściowej jest lub nie jest jej nadawane w czasie projektowania.
Praktycznie każdy fragment kodu może przechwytywać zdarzenia obiektu, ale tylko ten ostatni widzi swoją metodę częściową (ponieważ jest prywatna).
Metody częściowe zostały opracowane dla generatorów kodu, ale nietrudno znaleźć dla nich własne dobre zastosowanie. Ponadto warto o nich wiedzieć — pozwala to zorientować się, o co chodzi w kodzie, który został automatycznie wygenerowany. Poniższa definicja podprocedury TestMethod pochodzi z programu PartialMethods, który można pobrać z serwera FTP wydawnictwa Helion. Public Class Form1 ' Definicja metody TestMethod bez ciała. Partial Private Sub TestMethod(ByVal msg As String) End Sub ' Reszta kodu została opuszczona... End Class
Poniższa definicja ciała tej metody znajduje się w innym module: Partial Public Class Form1 ' Implementacja metody TestMethod. Private Sub TestMethod(ByVal msg As String) MessageBox.Show(msg) End Sub End Class
Kiedy użytkownik kliknie przycisk w tym programie, ten ostatni wywoła podprocedurę TestMethod i przekaże do niej łańcuch, który ma zostać wyświetlony. Jeśli umieścimy definicję ciała tej metody w komentarzu, aplikacja zignoruje to wywołanie. Efekty podobne do metod częściowych da się uzyskać na kilka innych sposobów. Po pierwsze, można zmusić klasę do zgłoszenia zdarzenia. Jeśli to ostatnie nie będzie przechwycone, zostanie ono zignorowane — tak jak ignorowane są wywołania metod częściowych bez zdefiniowanego ciała. Drugi sposób polega na użyciu w deklaracji metody atrybutu Conditional. W takim przypadku Visual Basic usuwa z kodu tę metodę i wszystkie jej wywołania, jeśli nie zostanie spełniony określony warunek. Technikę tę demonstruje program o nazwie AttributeConditional, który można pobrać z serwera FTP wydawnictwa Helion. Więcej informacji na temat tej aplikacji znajduje się we wcześniejszej części rozdziału — w podrozdziale „Lista atrybutów”. Metody częściowe przypominają też pod pewnymi względami interfejsy, które również definiują sygnatury metod, ale nie dostarczają ich implementacji.
386
Część II
Wstęp do języka Visual Basic
W końcu metody częściowe są podobne do klas przesłaniających metody. Każda klasa potomna może przesłonić dające się przesłaniać metody, aby dostarczyć dla nich ciała. Jeśli metoda ma ciało w klasie nadrzędnej, klasa potomna może pozostawić to ciało w spokoju i odziedziczyć je po swoim rodzicu. Metody częściowe zostały opracowane z myślą o generatorach kodu, ale można ich używać we własnych programach, jeśli zajdzie taka potrzeba.
Podsumowanie Podprocedury i funkcje pozwalają podzielić program na mniejsze, nadające się do wielokrotnego użycia części Podprocedura wykonuje zestaw poleceń. Funkcja również wykonuje zestaw poleceń, ale zwraca też jakąś wartość. Procedury własności składają się z par funkcji i podprocedur, które symulują zachowanie prostych własności. Są to podstawowe bloki składające się na proceduralną część aplikacji. W rozdziałach od 25. do 29. opiszę drugą połowę struktury aplikacji — obiekty odpowiedzialne za działanie programu. Obiekty i podprocedury oraz funkcje razem wzięte definiują aplikację. W tym rozdziale opisałem sposoby dzielenia zbyt długich fragmentów kodu na podprocedury i funkcje mniejszych rozmiarów. Ponadto objaśniłem techniki związane z podprocedurami i funkcjami, jak metody rozszerzające i rozluźnione delegaty, które pozwalają na wykorzystywanie istniejących klas i zdarzeń na nowe sposoby. Dotychczasowe rozdziały zawierały opisy tylko pisania prostych instrukcji kodu, wykonywanych jedna po drugiej bez żadnych odchyleń. Większość programów działa jednak w bardziej skomplikowany sposób. Niektóre instrukcje są wykonywane tylko po spełnieniu określonych warunków, inne zaś trzeba powtórzyć kilka razy. W rozdziale 18. — „Instrukcje sterujące” — opiszę instrukcje wykorzystywane przez Visual Basic do kontrolowania przepływu sterowania w programie. Należą do nich instrukcje decyzyjne (If Then Else, Select Case, IIF, Choose) i pętlowe (For Next, For Each, Do While, While Do, Repeat Until).
18
Instrukcje sterujące Instrukcje sterujące powodują wykonanie przez program określonych innych instrukcji, jeśli zostaną spełnione konkretne warunki. Kontrolują one ścieżkę, którą podąża działająca aplikacja. Ich zadaniem jest zmuszanie programu do wykonywania jednych instrukcji zamiast innych, a także wykonywania niektórych z nich wielokrotnie. Wyróżnia się dwie główne kategorie instrukcji sterujących — decyzyjne (inaczej warunkowe) oraz pętlowe. W poniższych podrozdziałach szczegółowo opiszę instrukcje decyzyjne i pętlowe, które są dostępne w Visual Basicu.
Instrukcje decyzyjne Instrukcja decyzyjna lub warunkowa reprezentuje gałąź programu. Oznacza ona miejsce, w którym aplikacja musi zdecydować na podstawie jakiegoś warunku, czy wykonać jedną, czy inną instrukcję — lub ewentualnie nie wykonać żadnej. Do instrukcji tych zaliczają się If, Choose oraz Select Case.
Jednowierszowa instrukcja If Then Jednowierszowa instrukcja If Then ma dwa podstawowe formaty. Pierwszy z nich pozwala na wykonanie pojedynczej instrukcji, jeśli zostanie spełniony jakiś warunek. Jej składnia jest następująca: If warunek Then instrukcja
Jeśli warunek jest spełniony, program wykonuje instrukcję. Najczęściej jednowierszowa instrukcja warunkowa If Then jest bardzo prosta (na przykład przypisuje jakąś wartość do zmiennej lub wywołuje jakąś podprocedurę).
388
Część II
Wstęp do języka Visual Basic
Poniższa instrukcja sprawdza własność IsManager obiektu emp. Jeśli własność ta ma wartość True, własność Salary tego obiektu zostaje ustawiona na 90000. If emp.IsManager Then emp.Salary = 90000
Drugi format jednowierszowej instrukcji If Then jest mniej jasny i ogólnie trudniejszy w użyciu. Jeśli prosta forma jednowierszowej instrukcji If Then nie nadaje się do jakiegoś zadania, wielu programistów — chcąc uniknąć komplikacji — wybiera wielowierszową instrukcję If Then, która zostanie opisana w kolejnym podrozdziale. W drugiej formie jednowierszowej instrukcji If Then znajduje się jest słowo kluczowe Else. Jej składnia jest następująca: If warunek Then instrukcja1 Else instrukcja2
Jeśli warunek jest spełniony, zostaje wykonana pierwsza instrukcja. Jeżeli nie jest on spełniony, program wykonuje drugą instrukcję. Decyzja, którą instrukcję wykonać, jest typu „jedna albo druga”. Program wykonuje jedną albo drugą z nich, ale nigdy obie naraz. Tego typu instrukcja If Then Else może wydawać się skomplikowana, jeśli jest zbyt długa, by zmieścić się w oknie edytora kodu. Lepszym rozwiązaniem w przypadku dłuższych instrukcji jest użycie wielowierszowej instrukcji If Then Else. Szybkość działania jednoi wielowierszowych instrukcji If Then Else jest porównywalna (w przeprowadzonym przeze mnie teście wielowierszowa wersja potrzebowała tylko 80 procent czasu), a więc można używać tej wersji, która wydaje się najłatwiejsza do zrozumienia. Instrukcje wykonywane przez jednowierszową instrukcję If Then mogą być prostymi pojedynczymi poleceniami (na przykład przypisaniem wartości do zmiennej) albo seriami prostych instrukcji, które znajdują się w jednym wierszu i są pooddzielane dwukropkami. Na przykład poniższy kod sprawdza wartość logicznej zmiennej is_new_customer. Jeśli ma ona wartość True, program wywołuje metodę Initialize obiektu reprezentującego klienta, a następnie metodę Welcome. If is_new_customer Then customer.Initialize() : customer.Welcome()
Użycie kilku takich pooddzielanych dwukropkami instrukcji niesie ze sobą duże ryzyko popełnienia błędu. Jeszcze gorzej, jeśli ma to miejsce w jednowierszowej instrukcji If Then Else: If order.Valid() Then order.Save() : order.Post() Else order.order.Delete()
Jednowierszowa instrukcja If Then może także zawierać klauzule Else If. Na przykład poniższy kod sprawdza zmienną X. Jeśli ma ona wartość 1, program ustawia zmienną txt na łańcuch "Jeden". Jeżeli zaś zmienna X ma wartość 2, aplikacja ustawia zmienną txt na wartość "Dwa". Jeśli wartość zmiennej X jest inna niż 1 i 2, zmienna txt zostaje ustawiona na znak zapytania. Dim txt As String If X = 1 Then txt = "Jeden" Else If X = 2 Then txt = "Dwa" Else txt = "?"
Rozdział 18.
Instrukcje sterujące
389
Liczba klauzul Else If nie jest w żaden sposób ograniczona. Każda instrukcja wykonawcza może składać się z wielu prostych instrukcji pooddzielanych dwukropkami. Niestety taki niejasny kod jak w tych przykładach może powodować różne niemiłe błędy, których łatwo uniknąć, jeśli użyje się w zamian wielowierszowych instrukcji If Then. Podsumowując, jeśli da się napisać prostą jednowierszową instrukcję If Then bez żadnych klauzul Else If lub Else, a wygląda ona zgrabnie, nie wahaj się — używaj wersji jednowierszowej. Jeżeli jednak powstaje zbyt długa i niejasna instrukcja, która zawiera klauzule Else If i Else lub wykonuje szereg instrukcji pooddzielanych dwukropkami, zazwyczaj bardziej się opłaca użyć wielowierszowej instrukcji If Then. Może ona zająć więcej miejsca, ale będzie prostsza, łatwiejsza do debugowania i lepiej podda się konserwacji.
Wielowierszowa instrukcja If Then Wielowierszowa instrukcja If Then może wykonać więcej niż jeden wiersz kodu, gdy zostanie spełniony jakiś warunek. Składnia takiej instrukcji w najprostszej formie jest następująca: If
warunek Then instrukcje... End If
Jeśli warunek zostanie spełniony, program wykona wszystkie instrukcje znajdujące się przed instrukcją End If. Podobnie jak wersja jednowierszowa, wielowierszowa instrukcja If Then może zawierać klauzule Else If oraz Else. Należy pamiętać, że w związku ze starą tradycją w wielowierszowych instrukcjach If Then pisze się jedno słowo ElseIf zamiast Else If. Składnia takiej instrukcji jest następująca: If
warunek1 Then instrukcje1... ElseIf warunek2 instrukcje2... Else instrukcje3... End If
Jeśli zostanie spełniony pierwszy warunek, program wykona pierwszy zestaw instrukcji. Jeżeli pierwszy warunek nie będzie spełniony, aplikacja sprawdzi drugi warunek. Jeśli ten będzie spełniony, zostaną wykonane instrukcje z drugiego zestawu. Program będzie sprawdzać po kolei wszystkie warunki, aż znajdzie taki, który będzie spełniony. Wtedy wykona odpowiadające mu instrukcje. Jeśli program dojdzie do instrukcji Else, wykona odpowiadający jej kod. Jeżeli zaś dojdzie do instrukcji End If, nie znalazłszy po drodze spełnionego warunku ani klauzuli Else, nie wykona żadnego z bloków instrukcji. Należy pamiętać, że program wychodzi z instrukcji If Then natychmiast po wykonaniu któregokolwiek z jej bloków kodu — nie sprawdza już pozostałych warunków. Pozwala to
390
Część II
Wstęp do języka Visual Basic
zaoszczędzić trochę czasu. Ma to szczególnie duże znaczenie, gdy warunki zawierają funkcje. Jeśli sprawdzenie każdego warunku wymaga wykonania jakiejś czasochłonnej funkcji, pominięcie wszystkich pozostałych testów pozwala zaoszczędzić dużą ilość czasu.
Instrukcja Select Case Instrukcja Select Case wykonuje jeden z kilku fragmentów kodu — w zależności od pojedynczej wartości. Jej podstawowa składnia jest następująca: Select Case wartość_testowa Case wyrażenie_porównawcze1 instrukcje1 Case wyrażenie_porównawcze2 instrukcje2 Case wyrażenie_porównawcze3 instrukcje3 ... Case Else instrukcje_else End Select
Jeśli wartość testowa pasuje do pierwszego wyrażenia porównawczego, program wykonuje instrukcje bloku instrukcje1. Jeżeli zaś pasuje ona do drugiego wyrażenia porównawczego, aplikacja wykonuje instrukcje bloku instrukcje2. Program kontynuuje sprawdzanie wyrażeń w instrukcjach Case, aż znajdzie pasujące lub wyczerpią się wszystkie instrukcje Case. Jeśli wartość testowa nie pasuje do żadnego wyrażenia w instrukcjach Case, program wykonuje kod bloku instrukcje_else. Pamiętaj, że sekcję Case Else można opuścić. Jeśli wtedy aplikacja nie dopasuje żadnego wyrażenia, nie zostanie wykonany żaden kod. Instrukcja Select Case ma taką samą funkcjonalność jak instrukcja If Then Else. Poniższy program robi to samo przy użyciu instrukcji Select Case co zaprezentowany wcześniej: If
wartość_testowa = wyrażenie_porównawcze1 Then instrukcje1 ElseIf wartość_testowa = wyrażenie_porównawcze2 Then instrukcje2 ElseIf wartość_testowa = wyrażenie_porównawcze3 Then instrukcje3 ... Else instrukcje_else End If
Czasami instrukcja Select Case jest prostsza od długiej instrukcji If Then Else. Często też szybciej działa, w znacznej mierze dzięki temu, że nie musi sprawdzać wyrażenia testowego dla każdej instrukcji Case. Jeśli wartość testowa jest prostą zmienną, różnica nie ma znaczenia, ale jeżeli jest ona czasochłonną funkcją, różnica może być znaczna. Wyobraźmy sobie na przykład, że wartość testowa reprezentuje funkcję otwierającą bazę danych i znajdującą jakąś wartość. Instrukcja Select Case znajdzie tę wartość tylko jeden raz, po czym będzie jej używać w każdej operacji porównywania, natomiast instrukcja If Then Else będzie otwierać bazę danych dla każdej operacji porównywania.
Rozdział 18.
Instrukcje sterujące
391
W przedstawionym powyżej przykładzie instrukcji If Then założono, że wyrażenia porównawcze są stałe. Jednak wyrażenie porównawcze może być także przedziałem wartości zdefiniowanych za pomocą słów kluczowych To i Is oraz listą wyrażeń pooddzielanych przecinkami. Wersje te zostaną opisane w poniższych trzech podrozdziałach. W ostatnim z nich omówię technikę kontrolowania instrukcji Select Case za pomocą wartości wyliczeniowych.
Słowo kluczowe To Słowo kluczowe To określa przedział wartości, do których powinna pasować wartość wyrażenia testowego. Poniższy kod sprawdza zmienną num_items. Jeśli jej wartość mieści się w przedziale od 1 do 10, program wywołuje podprocedurę ProcessSmallOrder. Jeśli wartość ta zawiera się w przedziale od 11 do 100, aplikacja wywołuje podprocedurę ProcessLargeOrder. Jeśli zmienna num_items ma wartość niższą od 1 lub wyższą od 100, program odtwarza dźwięk. Select Case num_items Case 1 To 10 ProcessSmallOrder() Case 11 To 100 ProcessLargeOrder() Case Else Beep() End Select
Słowo kluczowe Is Słowo kluczowe Is pozwala na wykonywanie porównań logicznych przy użyciu wartości testowej. W wyrażeniu porównującym zajmuje ono miejsce wartości testowej. Na przykład poniższy kod działa prawie dokładnie tak samo jak poprzednia jego wersja. Jeśli zmienna num_items ma wartość mniejszą lub równą 10, zostaje wywołana podprocedura ProcessSmallOrder. Jeżeli pierwsza klauzula Case nie została zastosowana, a zmienna num_items ma wartość mniejszą lub równą 100, program wywołuje podprocedurę ProcessLargeOrder. Jeśli żadna z tych sytuacji nie ma miejsca, aplikacja odtwarza dźwięk. Select Case num_items Case Is < = 10 ProcessSmallOrder() Case Is < = 100 ProcessLargeOrder() Case Else Beep() End Select
Ta wersja programu nieco różni się od poprzedniej. Jeśli zmienna num_items ma wartość mniejszą od 1, aplikacja ta wywołuje podprocedurę ProcessSmallOrder, podczas gdy poprzednia odtwarza dźwięk. W klauzuli Is można używać operatorów =, <>, <, <=, > oraz >= (jeśli w klauzuli Case zostanie użyta prosta wartość typu Case 7, w rzeczywistości będzie to oznaczało niejawne użycie operatora = — Case Is = 7).
392
Część II
Wstęp do języka Visual Basic
Wyrażenia oddzielane przecinkami Wyrażenie porównawcze może się składać z kilku wyrażeń pooddzielanych przecinkami. Jeśli wartość testowa pasuje do któregokolwiek z nich, program wykonuje kod odpowiadający temu wyrażeniu. Na przykład poniższy program sprawdza wartość zmiennej department_name. Jeśli wartością jest Nowe technologie, Test lub Operacje komputerowe, aplikacja dodaje do łańcucha address_text tekst Budynek 10. Jeśli zmienna ta ma wartość Finanse, Sprzedaż lub Księgowość, do zmiennej adress_text zostaje dodany tekst Budynek 7. Można jeszcze dodać kolejne instrukcje Case, które sprawdzają więcej wartości zmiennej department_name, a także instrukcję Else. Select Case department_name Case "Nowe technologie", "Test", "Operacje komputerowe" address_text & = "Budynek 10" Case "Finanse", "Sprzedaż", "księgowość" address_text & = "Budynek 7" ... End Select
Należy pamiętać, że nie można używać wyrażeń oddzielanych przecinkami w klauzulach Case Else. Na przykład poniższy kod jest niepoprawny: Case Else, "Siedziba główna" ' Nie działa.
W jednej klauzuli Case można mieszać i dopasowywać wiele stałych oraz wyrażeń To i Is, co demonstruje poniższy przykład. Program ten sprawdza wartość zmiennej item_code i wywołuje podprocedurę DoSomething, jeśli wartość ta jest mniejsza od 10, należy do przedziału od 30 do 40 włącznie, wynosi dokładnie 100 lub jest większa od 200. Select Case item_code Case Is < 10, 30 To 40, 100, Is DoSomething() End Select
>
200
Skomplikowane wyrażenia porównujące są czasami zbyt zagmatwane. Jeśli jakieś wyrażenie jest zbyt zawiłe, należy wziąć pod uwagę możliwość napisania go jeszcze raz w prostszy sposób. Pomocne może być zapisywanie wartości w tymczasowych zmiennych.
Wartości wyliczeniowe Instrukcje Select Case bardzo dobrze działają na listach pojedynczych wartości. W każdej instrukcji Case można użyć jednej lub kilku wartości pooddzielanych przecinkami. Typy wyliczeniowe, definiowane za pomocą instrukcji Enum, również obsługują wartości pojedyncze, a więc dobrze sprawdzają się w pracy z instrukcjami Select Case. Typ wyliczeniowy definiuje wartości, a instrukcja Select Case używa ich, jak na poniższym listingu:
Rozdział 18.
Instrukcje sterujące
393
Private Enum JobStates Pending Assigned InProgress ReadyToTest Tested Released End Enum Private m_JobState As JobStates ... Select Case m_JobState Case Pending ... Case Assigned ... Case InProgress ... Case ReadyToTest ... Case Tested ... Case Released ... End Select
Aby zabezpieczyć się przed błędami czasami popełnianymi przy modyfikowaniu typu wyliczeniowego, wielu programistów używa instrukcji Case Else, która zgłasza wyjątek. Jeśli do wyliczenia zostanie dodana nowa wartość, ale zapomnisz dodać dla niej odpowiedniego kodu instrukcji Select Case, instrukcja ta po znalezieniu tej nowej wartości zgłosi błąd. To znacznie ułatwia znajdowanie tego typu usterek. Więcej informacji na temat typów wyliczeniowych znajduje się w podrozdziale „Typy wyliczeniowe” rozdziału 15. — „Typy danych, zmienne i stałe”.
Instrukcja IIF Instrukcja IIF wyznacza wartość wyrażenia logicznego i zwraca jedną z dwóch wartości, zależnie od tego, czy wyrażenie to jest prawdziwe, czy fałszywe. Wyglądem może ona bardziej przypominać instrukcję przypisania lub wywołanie funkcji niż instrukcję decyzyjną, jak If Then. Składnia niniejszej instrukcji jest następująca: variable
Na przykład poniższa instrukcja sprawdza własność IsManager obiektu klasy Employee. Jeśli własność ta ma wartość True, pensja (Salary) tego pracownika zostaje ustawiona na 90000, a jeżeli False, pensja zostaje ustawiona na 10000. emp.Salary = IIf(emp.IsManager, 90000, 10000)
394
Część II
Wstęp do języka Visual Basic
Należy zauważyć, że instrukcja IIF zwraca typ danych Object. Jeśli opcja Option Strict jest włączona, wykonanie powyższej instrukcji nie będzie możliwe, ponieważ przypisuje ona wynikowy typ Object do zmiennej typu Integer. Aby zaspokoić wszystkie wymagania Visual Basica, należy dokonać jawnej konwersji na ten typ, jak poniżej: emp.Salary = CInt(IIf(emp.IsManager, 90000, 10000))
Instrukcja IIF ma kilka wad. Po pierwsze, jest mało jasna. Kiedy programista zacznie ją wpisywać, funkcja IntelliSense podpowie, że jej parametrami są: warunek, wartość, jeśli True, a także wartość, jeśli False. Jednak w czasie czytania kodu trzeba pamiętać, co oznaczają poszczególne elementy tej instrukcji. Jeszcze gorzej to wygląda, jeżeli IIF zostanie użyta w innej instrukcji. Przeanalizujmy na przykład poniższy kod: For i = 1 To CType(IIf(employees_loaded, num_employees, 0), Integer) ' Przetwarzanie employee i. ... Next i
Z reguły kod staje się jaśniejszy, gdy instrukcja IIF zostanie zastąpiona instrukcją If Then. Inną wadą tej instrukcji jest to, że wyznacza obie wartości, bez względu na to, czy warunek jest prawdziwy, czy fałszywy. Przeanalizujmy poniższy przykładowy kod. Jeśli zmienna logiczna use_groups ma wartość True, wartość zmiennej num_objects zostaje ustawiona na wartość zwróconą przez funkcję CountGroups. Jeżeli zmienna logiczna use_groups ma wartość False, zmienna num_objects zostaje ustawiona na wartość zwróconą przez funkcję CountIndividuals. Instrukcja IIF wyznaczy wartości obu tych funkcji — bez względu na to, której rzeczywiście potrzebuje. Jeśli są one czasochłonne lub wykonywane w jakiejś dużej pętli, może to zabrać dużo czasu. num_objects = CType( _ IIf(use_groups, CountGroups(), CountIndividuals()), Integer)
Poniżej przedstawiam przykład jeszcze gorszej sytuacji. Jeśli zmienna data_loaded ma wartość True, poniższa instrukcja ustawi num_loaded=num_employees. Jeżeli zaś ma wartość False, zmienna num_loaded zostanie ustawiona na wartość zwróconą przez funkcję LoadEmployees (ładującą pracowników i zwracającą liczbę załadowanych obiektów). num_loaded = CType(IIf(data_loaded, num_employees, LoadEmployees()), Integer)
Instrukcja IIF wyznaczy wartości zarówno zmiennej num_employees, jak i funkcji LoadEmployees(). Jeśli pracownicy są już załadowani, instrukcja IIF wywoła funkcję LoadEmployees(), aby załadować ich jeszcze raz, zignoruje zwrócony przez tę funkcję wynik i ustawi num_loaded=num_employees. Funkcja LoadEmployees może zmarnować mnóstwo czasu na ładowaniu danych, które są już załadowane. Co gorsza, program może nie być w stanie załadować danych, które są już załadowane. Ostatnią wadą instrukcji IIF jest to, że ustępuje ona szybkością działania porównywalnej instrukcji If Then Else. W przeprowadzonym przeze mnie teście instrukcja IIF zajęła dwa razy tyle czasu, co porównywalna z nią instrukcja If Then.
Rozdział 18.
Instrukcje sterujące
395
Jedyną sytuacją, w której można wykazać, że instrukcja IIF może być jaśniejsza, jest ta, gdy mamy dużo bardzo prostych instrukcji. W takim przypadku instrukcje IIF mogą ułatwić dostrzeżenie wspólnych cech kodu i szybkie znalezienie błędów. Na przykład kod na poniższym listingu inicjuje kilka pól tekstowych przy użyciu łańcuchów. Jeśli łańcuch nie jest jeszcze zainicjowany, instrukcja IIF ustawia wartość jego pola tekstowego na . txtLastName.Text = IIf(last_name Is Nothing, " < Missing > ", last_name) txtFirstName.Text = IIf(first_name Is Nothing, " < Missing > ", first_name) txtStreet.Text = IIf(street Is Nothing, " < Missing > ", street) txtCity.Text = IIf(city Is Nothing, " < Missing > ", city) txtState.Text = IIf(state Is Nothing, " < Missing > ", state) txtZip.Text = IIf(zip Is Nothing, " < Missing > ", zip)
Aby uniknąć niepotrzebnych komplikacji, używaj instrukcji IIF tylko wówczas, gdy znacznie uprości ona kod.
Instrukcja If Instrukcja If rozwiązuje niektóre problemy związane z instrukcją IIF. Wyznacza ona — podobnie jak IIF — wartość wyrażenia logicznego i zwraca jedną z dwóch wartości, zależnie od tego, czy wyrażenie to jest prawdziwe, czy fałszywe. Różnica między nimi polega na tym, że instrukcja If wyznacza tylko tę wartość, którą zwraca. Na przykład poniższy kod sprawdza własność IsManager obiektu klasy Employee. Jeśli własność ta ma wartość True, pensja takiego pracownika zostaje ustawiona na wartość zwróconą przez funkcję getManagerSalary, a funkcja GetEmployeeSalary nie zostaje w ogóle wywołana. Jeśli własność IsManager ma wartość False, pensja pracownika zostaje ustawiona na wartość zwróconą przez funkcję getEmployeeSalary, a nie zostaje wywołana funkcja getManagerSalary. Jest to inne działanie niż instrukcji IIF, która wyznaczyłaby wartości obu tych funkcji, bez względu na to, którą wartość by zwróciła. Jeśli funkcje te byłyby bardzo czasochłonne, za pomocą instrukcji If można by znacznie usprawnić program. emp.Salary = If(emp.IsManager, GetManagerSalary(), GetEmployeeSalary())
Poza tą jedną różnicą instrukcje If i IIF działają tak samo. Więcej informacji można znaleźć w poprzednim podrozdziale.
Instrukcja Choose Instrukcja IIF wybiera jedną z dwóch wartości na podstawie wyrażenia logicznego, a instrukcja Choose wybiera jedną z wielu opcji na podstawie liczby całkowitej. Jej składnia jest następująca: variable
Jeśli parametr index jest liczbą 1, instrukcja Choose zwraca pierwszą wartość — wartość1. Jeżeli index jest liczbą 2, Choose zwraca wartość2 itd. Jeśli parametr index jest liczbą mniejszą od 1 lub większą od liczby wartości na liście, instrukcja Choose zwraca wartość Nothing.
396
Część II
Wstęp do języka Visual Basic
Wady tej instrukcji są takie same jak instrukcji IIF. Choose wyznacza wszystkie wartości, bez względu na to, która została wybrana. Może to mieć ujemny wpływ na szybkość działania programu. Szczególną ostrożność należy zachować, jeśli wartości są funkcjami z efektami ubocznymi. Instrukcja Choose jest często mniej przejrzysta od porównywalnej Select Case. Jeśli poszczególne wartości nie są do siebie podobne (są pomieszane liczby całkowite, obiekty, wywołania funkcji itd.), zawierają skomplikowane funkcje lub zajmują kilka wierszy kodu, bardziej przejrzysta może się okazać instrukcja Select Case. Jeśli jednak wartości są krótkie i łatwo zrozumiałe oraz jest ich duża liczba, przejrzystsza może być instrukcja Choose. Na przykład obie poniższe instrukcje Choose i Select Case robią to samo. Dzięki temu, że wartości są krótkie i zrozumiałe, Choose jest przejrzysta. Instrukcja Select Case jest nieco długa. Gdyby było jeszcze więcej opcji do wyboru, instrukcja ta byłaby jeszcze dłuższa, przez co stałaby się mniej czytelna. fruit = Choose(index, "jabłko", "banan", "wiśnia", "daktyl") Select Case index Case 1 fruit = "jabłko" Case 2 fruit = "banan" Case 3 fruit = "wiśnia" Case 4 fruit = "daktyl" End Select
Mimo iż trudno czasami określić, czy bardziej przejrzysta będzie instrukcja Choose, czy Select Case, ta druga jest z pewnością szybsza. W przeprowadzonym przeze mnie teście instrukcja Choose zajęła ponad pięć razy więcej czasu niż Select Case. Jeśli jej kod znajduje się w jakiejś często wykonywanej pętli, może to mieć duże znaczenie. Instrukcje Choose i Select Options nie są jedynymi opcjami do wyboru. Można także zapisać wartości w tablicy i pobierać je z niej za pomocą indeksów. Na przykład poniższy kod przechowuje wartości z poprzedniego przykładu w tablicy. Następnie wybiera z niej odpowiednią wartość za pomocą indeksu. Dim fruit_names() As String = {"jabłko", "banan", "wiśnia", "daktyl"} fruit = fruit_names(index - 1)
Zwróć uwagę na to, że indeks używany do pobrania odpowiedniej wartości jest w powyższym kodzie zmniejszany o 1. Jest to podyktowane tym, że instrukcja Choose indeksuje swoje wartości, zaczynając od numeru 1, natomiast tablice w Visual Basicu są indeksowane od 0. Odjęcie jedynki pozwala na używanie tych samych indeksów, z których korzystano wcześniej. Ta wersja zmusza programistę do spojrzenia na kod z innej perspektywy. Należy wiedzieć, że tablica fruit_names zawiera nazwy owoców potrzebnych w programie. Jeśli zrozumiesz zastosowanie tej tablicy, zademonstrowana instrukcja przypisania stanie się dla Ciebie jasna.
Rozdział 18.
Instrukcje sterujące
397
Od instrukcji Select Case nieco szybsze byłoby przypisanie, zwłaszcza gdyby dało się wcześniej zainicjować tablicę fruit_names. Jeśli instrukcja Choose odpowiada Ci i nie zaciemnia niepotrzebnie Twojego kodu, nie wahaj się jej użyć. Jeżeli natomiast w danej sytuacji jaśniejsza wydaje się instrukcja Select Case, skorzystaj z niej. Jeśli przewidujesz, że będzie trzeba wykonać wiele instrukcji przypisania, a można wcześniej utworzyć tablicę wartości, działanie programu da się przyspieszyć poprzez zastosowanie właśnie techniki z tablicą wartości.
Instrukcje pętlowe Instrukcje pętlowe zmuszają program do wielokrotnego wykonania określonego zestawu instrukcji. Pętla może zostać wykonana określoną liczbę razy, działać, aż zostanie spełniony jakiś warunek, a także aż jakiś warunek przestanie być spełniany. Wyróżnia się dwa główne rodzaje instrukcji pętlowych. Pętle typu For są wykonywane określoną (teoretycznie) znaną liczbę razy. Na przykład For może wykonać zestaw instrukcji dziesięć razy albo po jednym razie dla każdego obiektu znajdującego się w jakiejś kolekcji. Jeśli znana jest liczba elementów kolekcji, wiadomo, ile razy zostanie wykonana pętla. Pętla While działa, dopóki jakiś warunek jest spełniany, a także aż jakiś warunek będzie spełniony. Bez odpowiednich informacji nie da się określić, ile razy kod takiej pętli zostanie wykonany. Wyobraźmy sobie na przykład program pobierający za pomocą funkcji o nazwie InputBox nazwiska od użytkownika, aż zostanie naciśnięty przycisk Anuluj. W takim przypadku nie da się zgadnąć, ile nazwisk użytkownik wprowadzi przed naciśnięciem tego przycisku. W kolejnych podrozdziałach omówię instrukcje pętlowe dostępne w Visual Basicu. Pierwsze dwa zawierają opisy pętli typu For, a dalsze — pętli typu While. Niektóre z opisywanych tu rodzajów pętli demonstruje program o nazwie Loops, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip).
Pętla For Next Pętla For Next jest najczęściej używaną instrukcją pętlową w Visual Basicu. Jej składnia jest następująca: For zmienna [As typ_danych] = wartość_początkowa To wartość_końcowa [Step inkrementacja] instrukcje [Exit For] instrukcje [Continue For] instrukcje Next [zmienna]
398
Część II
Wstęp do języka Visual Basic
Wartość zmienna to zmienna pętlowa, która kontroluje całą pętlę. Kiedy program dochodzi do instrukcji For, ustawia tę zmienną na wartość początkową. Następnie porównuje ją z wartością końcową. Jeśli wartość tej zmiennej przekroczyła wartość końcową, pętla kończy działanie. Należy pamiętać, że pętla może nie zostać wykonana ani razu, jeśli odpowiednio ułożą się wartości tych zmiennych. Na przykład poniższa pętla działa dla wartości employee_num=1, employee_num=2,..., employee_num=num_employees. Jeśli program nie załadował żadnych pracowników, czyli zmienna num_employees ma wartość 0, kod wewnątrz tej pętli nie zostanie wykonany ani razu. For employee_num = 1 To num_employees ProcessEmployee(employee_num) Next employee_num
Po porównaniu zmiennej pętlowej z wartością końcową program wykonuje instrukcje pętli. Następnie zwiększa zmienną pętlową o wartość inkrementacja, po czym cykl zaczyna się od nowa od porównania wartości zmiennej pętlowej ze zmienną końcową. Jeśli klauzula inkrementacja zostanie opuszczona, program zwiększy zmienną pętlową o 1. Należy pamiętać, że wartość inkrementacji może być ujemna lub ułamkowa, jak poniżej: For i As Integer = 3 To 1 Step -0.5 Debug.WriteLine(i) Next i
Jeśli wartość inkrementacja jest dodatnia, pętla jest wykonywana, dopóki zmienna <= wartość_końcowa. Jeżeli zmienna jest ujemna, pętla jest wykonywana, dopóki zmienna >= wartość_końcowa. Oznacza to, że pętla nie wykonywałaby się w nieskończoność, gdyby wartość inkrementacji powodowała oddalenie zmiennej pętlowej od wartości końcowej. Na przykład w poniższym kodzie wartość_początkowa = 1, a inkrementacja= -1. Zmienna i przyjmowałaby wartości i = 1, i = 0, i = -1 itd. — i nigdy nie doszłaby do wartości końcowej 2. Ponieważ jednak wartość inkrementacji jest ujemna, pętla zostaje wykonana tylko wtedy, gdy i >= 2. Ponieważ pierwszą wartością zmiennej i jest 1, pętla nie zostaje wykonana ani razu. For i As Integer = 1 To 2 Step -1 Debug.WriteLine(i) Next i
Nie ma wymogu używania nazwy zmiennej w instrukcji Next, chociaż ułatwia to późniejsze czytanie kodu. Jeśli w instrukcji tej zostanie użyta nazwa zmiennej, musi ona być taka sama jak nazwa zmiennej w instrukcji For. Jeśli w instrukcji For nie zostanie określony typ danych zmiennej pętlowej, opcja Option Explicit jest włączona, a Option Infer wyłączona, zmienna ta musi zostać zadeklarowana przed pętlą. Na przykład poniższy fragment programu deklaruje zmienną i poza pętlą: Dim i As Integer For i = 1 To 10 Debug.WriteLine(i) Next i
Rozdział 18.
Instrukcje sterujące
399
Deklarowanie zmiennej pętlowej w instrukcji For jest dobrym zwyczajem. W ten sposób ogranicza się zasięg tej zmiennej, dzięki czemu nie trzeba pamiętać, do czego ona służy w innych częściach programu. Ponadto dzięki zatrzymaniu deklaracji zmiennej w pobliżu jej użycia ułatwia zapamiętanie, do czego się jej używa. Dodatkowo jedną taką zmienną licznikową można wykorzystać w kilku pętlach, przy czym nie ma ryzyka popełnienia pomyłki. Jeśli mamy kilka pętli wymagających zmiennej pętlowej o określonej nazwie, we wszystkich można zadeklarować i użyć tej samej zmiennej i, wiedząc, że nie będą one sobie wzajemnie przeszkadzać. Wartości wartość_początkowa i wartość_końcowa są wyznaczane przed rozpoczęciem wykonywania pętli. Program nigdy nie wyznacza ich ponownie, nawet jeśli się one zmienią. Na przykład poniższy kod wykonuje pętlę dla wartości od 1 do this_customer.Orders(1).NumItems. Program wyznacza wartość this_customer.Orders(1).NumItems przed pierwszym wykonaniem pętli, po czym już więcej do tego nie wraca. W ten sposób oszczędzany jest czas działania aplikacji, zwłaszcza w przypadku wyrażeń o takiej długości, których wielokrotne wyznaczanie może zająć dużo czasu. For item_num As Integer = 1 To this_customer.Orders(1).NumItems this_customer.ProcessItem(item_num) Next item_num
Jeśli wartość końcowa musi być wyznaczana przed każdą iteracją pętli, należy użyć pętli While zamiast For. Instrukcja Exit For zmusza program do wyjścia z pętli For przed jej normalnym zakończeniem. Na przykład poniższy kod przechodzi za pomocą pętli przez tablicę employees. Kiedy znajdzie pozycję z własnością IsManager ustawioną na True, zapisuje indeks tego pracownika i natychmiast kończy działanie za pomocą instrukcji Exit For. Dim manager_index As Integer For i As Integer = employees.GetLowerBound(0) To _ employees.GetUpperBound(0) If employees(i).IsManager Then manager_index = i Exit For End If Next i
Instrukcja Exit For kończy działanie tylko tej pętli For, która bezpośrednio ją zawiera. Jeśli pętla For jest zagnieżdżona w innej pętli For, instrukcja ta powoduje wyjście tylko z tej wewnętrznej pętli. Instrukcja Continue For zmusza pętlę do przeskoczenia z powrotem do początku, zwiększenia swojej zmiennej pętlowej i uruchomienia się jeszcze raz od nowa. Jest ona szczególnie przydatna, gdy wiadomo, że nie trzeba wykonywać dalszej części ciała pętli i można zacząć nową iterację. Istnieje możliwość zmiany zmiennej pętlowej wewnątrz pętli, ale zazwyczaj nie okazuje się to dobrym pomysłem. Pętla For Next ma bardzo ściśle określone przeznaczenie, a modyfikowanie zmiennej pętlowej wewnątrz niej stanowi złamanie tych postanowień, przez co kod jest trudniejszy do zrozumienia i ciężko znaleźć w nim błędy. Jeśli konieczne jest modyfikowanie zmiennej pętlowej na bardziej skomplikowane sposoby, niż pozwala na to pętla For Next, należy użyć pętli While. Wtedy programiści czytający kod nie spodziewają się prostej pętli z inkrementacją.
400
Część II
Wstęp do języka Visual Basic
Różne typy zmiennej pętlowej pętli For Next Zmienna kontrolna pętli For Next ma z reguły jakiś typ całkowitoliczbowy, na przykład Integer lub Long, chociaż może mieć dowolny z typów podstawowych Visual Basica. Na przykład poniższa pętla wyświetla wartości 1,0, 1,5, 2,0, 2,5 i 3,0 przy użyciu zmiennej pętlowej zadeklarowanej jako typ Single: For x As Single = 1 To 3 Step 0.5 Debug.WriteLine(x.ToString("0.0")) Next x
Niestety ze względu na fakt, że za pomocą liczb zmiennoprzecinkowych nie da się precyzyjnie reprezentować każdej możliwej wartości, z typami tymi wiążą się błędy zaokrąglania, które mogą prowadzić do osiągania niespodziewanych wyników. Powyższy kod działa zgodnie z oczekiwaniami, przynajmniej na moim komputerze. Natomiast ten, który znajduje się poniżej, sprawia problemy. W idealnej sytuacji pętla ta wyświetlałaby wartości z przedziału 1 do 2 przy inkrementacji o jedną siódmą. Niestety z powodu błędów zaokrąglania po siedmiu cyklach pętli zmienna x ma wartość około 1.85714316. Po dodaniu wartości 1/7 do tej zmiennej otrzymamy wynik 2.0000003065381731. Jest to więcej niż wartość końcowa, która wynosi 2, a więc program wychodzi z pętli, przez co dla x = 2 instrukcja Debug nie zostanie wykonana. For x As Single = 1 To 2 Step 1 / 7 Debug.WriteLine(x) Next x
Jednym z rozwiązań takiego problemu może być użycie całkowitoliczbowej zmiennej kontrolnej pętli. Błąd zaokrąglania nie dotyczy liczb całkowitych, w przeciwieństwie do liczb zmiennoprzecinkowych, dzięki czemu programista zyskuje lepszą kontrolę nad wartościami używanymi w pętli. Poniższy fragment programu robi mniej więcej to samo co poprzedni. Jednak tym razem użyto zmiennej pętlowej typu Integer, dzięki czemu pętla zostaje wykonana dokładnie osiem razy, zgodnie z wymaganiami. Ostatnia wartość wydrukowana przez program w oknie Output to 2. Dim x As Single x = 1 For i As Integer = 1 To 8 Debug.WriteLine(x) x += CSng(1 / 7) Next i
Jeśli sprawdzimy wartość zmiennej x w debugerze, stwierdzimy, że jej prawdziwa wartość w czasie ostatniego cyklu pętli wynosi w przybliżeniu 2.0000001702989851. Gdyby ta zmienna kontrolowała wykonywanie pętli, program — wiedząc, że jest ona większa od 2 — nie wyświetliłby swojej ostatniej wartości.
Rozdział 18.
Instrukcje sterujące
401
Pętla For Each Pętla For Each iteruje przez elementy kolekcji, tablic i innych klas kontenerowych obsługujących ten rodzaj pętli. Jej składnia jest następująca: For Each zmienna [As instrukcje [Exit For] instrukcje [Continue For] instrukcje Next [zmienna]
typ_obiektu ] In
grupa
W tym schemacie grupa oznacza kolekcję, tablicę lub dowolny inny obiekt obsługujący pętlę For Each. Podobnie jak miało to miejsce w pętli For Next, zmienna pętlowa musi zostać zadeklarowana albo w instrukcji For, albo przed nią, jeśli opcja Option Explicit jest włączona, a Option Infer wyłączona. Aby obsługiwać pętlę For Each, obiekt grupowy musi implementować interfejs System.Collections.IEnumerable, który zawiera definicję metody GetEnumerator, zwracającej enumerator. Więcej informacji na ten temat znajduje się w kolejnym podrozdziale „Enumeratory”. Zmienna kontrolna musi mieć typ zgodny z obiektami znajdującymi się w grupie. Jeśli ta ostatnia zawiera obiekty klasy Employee, zmienna ta może być obiektem tej klasy. Może też być ogólnego typu Object lub dowolnej innej klasy, której obiekty da się przekonwertować na obiekty klasy Employee. Jeśli na przykład klasa Employee dziedziczy po klasie Person, zmienna kontrolna może być typu Person. Visual Basic nie wie, jakiego rodzaju obiekty są przechowywane w kolekcji lub tablicy, dopóki nie spróbuje ich użyć. Jeśli typ zmiennej kontrolnej nie zgadza się z typem obiektu, program generuje błąd, gdy pętla For Each próbuje przypisać tę zmienną do tego obiektu. Oznacza to, że jeśli kolekcja lub tablica zawiera obiekty więcej niż jednego typu, zmienna kontrolna musi mieć taki typ, który zgadza się z tymi wszystkimi typami. Jeśli obiekty te nie dziedziczą po jakiejś wspólnej klasie, konieczne jest użycie zmiennej kontrolnej typu Object. Pętla For Each — podobnie jak For Next — pozwala na używanie instrukcji Exit For i Continue For. Podobnie jak to ma miejsce w pętli For Next, zadeklarowanie zmiennej pętlowej w instrukcji For Each jest dobrym zwyczajem. W ten sposób zawęża się zakres tej zmiennej, dzięki czemu nie trzeba pamiętać jej zastosowania w innych miejscach programu. Ponadto deklaracja zmiennej znajduje się blisko miejsca, w którym jest ona używana, dzięki czemu łatwiej zapamiętać jej typ. Dodatkowo ułatwia to wielokrotne używanie zmiennych licznikowych, bez obawy, że wystąpią jakieś błędy. Jeśli kilka pętli potrzebuje zmiennej pętlowej o określonej nazwie, we wszystkich nich można użyć nazwy obj, person lub jakiejkolwiek innej — i nie będą się one wzajemnie zakłócać.
402
Część II
Wstęp do języka Visual Basic
Instrukcje znajdujące się w pętli mogą modyfikować wartość zmiennej pętlowej, ale nie wpływa to na postęp pętli w przechodzeniu przez kolekcję lub tablicę. Pętla ustawia tę zmienną na kolejny obiekt w grupie i kontynuuje działanie, jakby jej wartość nigdy nie została zmieniona. Aby uniknąć problemów, lepiej tego nie robić. Zmiany w kolekcji są natychmiast widoczne w pętli. Jeśli na przykład jakaś instrukcja w pętli doda na końcu kolekcji nowy obiekt, pętla ta będzie kontynuować działanie, aż przetworzy także ten nowy element. Analogicznie — jeśli któraś z instrukcji usunie jeden z elementów kolekcji (który nie został jeszcze przetworzony), pętla nie przetworzy go. Efekt operacji dodania lub usunięcia elementu z kolekcji zależy od tego, czy znajduje się on przed, czy za miejscem w kolekcji, w którym aktualnie znajduje się pętla. Jeśli na przykład zostanie usunięty element znajdujący się przed aktualnym elementem, zachowanie pętli nie zmieni się, ponieważ jest on przetworzony. Jeżeli natomiast zostanie usunięty element, który nie został jeszcze przetworzony, pętla już go nie przetworzy. Usunięcie bieżącego elementu wydaje się wprowadzać pętlę w oszołomienie, ponieważ kończy ona działanie bez zgłoszenia żadnego błędu. Dodawanie i usuwanie elementów z tablic nie jest widoczne w pętli. Jeśli instrukcja ReDim doda jakieś elementy na końcu tablicy, pętla nie przetworzy ich. Jeżeli jednak programista spróbuje uzyskać do nich dostęp, program wygeneruje błąd "Index was outside the bounds of the array". Jeśli natomiast za pomocą instrukcji ReDim elementy zostaną usunięte z końca tablicy, pętla i tak je przetworzy! Jeżeli zostaną zmodyfikowane wartości tablicy, na przykład zmienią się własności jakiegoś obiektu lub element tablicy zostanie ustawiony na całkiem nową wartość, pętla uwzględni te zmiany. Aby uniknąć tych potencjalnych źródeł problemów, nie modyfikuj kolekcji ani tablic, gdy są przetwarzane przez pętlę For Each. Często spotykaną czynnością związaną z kolekcjami jest analizowanie wszystkich ich elementów i usuwanie niektórych z nich. W przypadku pętli For Each usunięcie bieżącego elementu powoduje przedwczesne zakończenie jej działania. Inną metodę, która wydaje się prawidłowa (ale nie jest), stanowi takie użycie pętli For Each, jak na poniższym listingu. Jeśli pętla usunie jeden obiekt z kolekcji, przeskoczy kolejny, ponieważ usunięcie to spowodowało zmniejszenie indeksu o jeden, a więc następny element znajdzie się na pozycji, która została już przetworzona. Co gorsza, zmienna kontrolna i będzie zwiększana, aż osiągnie oryginalną wartość employees.Count. Jeśli pętla usunęła jakieś obiekty, kolekcja nie będzie już zawierała tylu elementów. Program spróbuje uzyskać dostęp do indeksu spoza kolekcji, co spowoduje błąd. Dim emp As Employee For i As Integer = 1 To employees.Count emp = CType(employees(i), Employee) If emp.IsManager Then employees.Remove(i) Next i
Rozdział 18.
Instrukcje sterujące
403
Jednym z rozwiązań tego problemu może być przechodzenie przez obiekty kolekcji w odwrotnej kolejności, co demonstruje poniższy listing. W tej wersji programu pętla nie musi używać indeksu, gdy został on usunięty, ponieważ odlicza wstecz. Ponadto indeks obiektu w kolekcji nie zmienia się, jeśli obiekt ten nie został jeszcze przetworzony. Pętla przetwarza każdy z obiektów dokładnie jeden raz, bez względu na to, które z nich zostały usunięte. For i As Integer = employees.Count To 1 Step -1 emp = CType(employees(i), Employee) If emp.IsManager Then employees.Remove(i) Next i
Enumeratory Enumerator to obiekt, który pozwala przechodzić pomiędzy obiektami znajdującymi się w kontenerze jakiejś klasy. Na przykład enumeratory są udostępniane przez kolekcje, tablice zwykłe oraz tablice asocjacyjne. W tym podrozdziale opiszę enumeratory kolekcji, ale te same zasady dotyczą pozostałych wymienionych rodzajów klas. Enumerator pozwala na przeglądanie obiektów kolekcji, ale nie na modyfikowanie samych kolekcji. Za jego pomocą zmodyfikujesz poszczególne obiekty, ale zazwyczaj nie uda Ci się ich dodać, usunąć ani przestawić. Początkowo enumerator zostaje ustawiony przed pierwszym elementem kolekcji. Aby przejść do kolejnego, należy użyć metody enumeratora o nazwie MoveNext. Metoda ta zwraca wartość True, jeśli z powodzeniem przejdzie do kolejnego obiektu, lub False, jeżeli w kolekcji nie ma więcej obiektów. Metoda Reset przywraca enumerator do jego pierwotnego położenia przed pierwszym obiektem, dzięki czemu można jeszcze raz przejść przez kolekcję. Metoda Current zwraca obiekt, który jest aktualnie odczytywany przez enumerator. Należy pamiętać, że metoda ta zwraca wartość typu Object, a więc najczęściej przed jego użyciem konieczne jest użycie funkcji CType do przekonwertowania tego obiektu na bardziej specyficzny typ. Wywołanie metody Current powoduje zgłoszenie błędu, jeśli enumerator w tym momencie nie odczytuje żadnego obiektu. Sytuacja ta może się zdarzyć, gdy enumerator znajduje się przed pierwszym lub za ostatnim obiektem. Przedstawiony poniżej fragment programu przechodzi za pomocą enumeratora przez kolekcję o nazwie m_Employees. Zadeklarowano w nim zmienną klasy Employee o nazwie emp oraz utworzono obiekt klasy IEnumerator o nazwie employee_enumerator. Enumerator kolekcji został utworzony za pomocą metody tej kolekcji — GetEnumerator. Dalej znajduje się pętla While. Jeśli metoda employee_enumerator.MoveNext zwraca wartość True, oznacza to, że enumerator z powodzeniem przeszedł do kolejnego obiektu w kolekcji. Po wczytaniu obiektu program konwertuje ogólny typ obiektu zwróconego przez własność Current na typ Employee za pomocą funkcji CType, a następnie wyświetla jego wartości Title, FirstName oraz LastName. Po przetworzeniu wszystkich obiektów w kolekcji metoda employee_enumerator.MoveNext zwraca wartość False, a pętla While kończy działanie. Dim emp As Employee Dim employee_enumerator As IEnumerator employee_enumerator = m_Employees.GetEnumerator()
404
Część II
Wstęp do języka Visual Basic
Do While (employee_enumerator.MoveNext) emp = CType(employee_enumerator.Current, Employee) Debug.WriteLine(emp.Title & " " & emp.FirstName Loop
&
" "
&
emp.LastName)
Niektóre kontenery pozwalają na używanie enumeratorów korzystających z bardziej specyficznych typów danych. Na przykład da się w programie zastosować ogólną listę List do przechowywania określonego rodzaju obiektów, jak na przykład Employee. Do tej listy można następnie użyć ogólnego enumeratora odpowiedniego typu, w tym przypadku IEnumerator(Of Employee). Wtedy własność Current enumeratora będzie zwracać typ Employee zamiast Object, dzięki czemu nie będzie potrzebna konwersja typów. Tego typu listę, List(Of Employee), tworzy program o nazwie EnumerateEmployees, który można pobrać z serwera FTP wydawnictwa Helion. Przechodzenie przez tę listę jest realizowane przez enumerator IEnumerator(Of Employee). Więcej informacji na temat typów ogólnych znajduje się w rozdziale 29. — „Typy ogólne”. Pętla For Each daje mniej więcej taki sam dostęp do elementów kontenera jak enumerator. Chociaż w pewnych sytuacjach enumerator okazuje się bardziej naturalnym rozwiązaniem niż ta pętla. Może on na przykład pominąć kilka elementów, nie badając ich dokładnie, a także udostępnia metodę Reset, która pozwala zacząć enumerację od nowa. Aby ponownie uruchomić pętlę For Each, trzeba ją powtórzyć poprzez umieszczenie jej najczęściej w innej pętli, która określa, kiedy zatrzymać proces. W dokumentacji Visual Basica napisano, że enumerator jest ważny do pierwszej modyfikacji kolekcji. Jeśli do kolekcji zostanie dodany jakiś element lub coś się z niego usunie, przy pierwszym użyciu enumeratora zgłosi on wyjątek niedozwolonego działania. Wydaje się jednak, że zasada ta nie zawsze ma zastosowanie, ponieważ czasami enumerator działa nawet po zmodyfikowaniu jego kolekcji. Może to oczywiście prowadzić do dużych nieporozumień. Aby tego uniknąć, nigdy nie modyfikuj kolekcji analizowanej za pomocą enumeratora. Interfejs IEnumerable definiuje funkcjonalność wymaganą dla numeratorów, dzięki czemu każda implementująca go klasa udostępnia enumeratory. Każda klasa obsługująca pętlę For Each musi także implementować interfejs IEnumerable, a co za tym idzie — wszystkie takie klasy obsługują enumeratory. Niektóre z klas implementujących interfejs IEnumerable zostały przedstawione w poniższej tabeli. Array
HybridDictionary
SqlDataReader
ArrayList
ListDictionary
Stack
Collection
MessageQueue
String
CollectionBase
OdbcDataReader
StringCollection
ControlCollection
OleDbDataReader
StringDictionary
DataView
OracleDataReader
TableCellCollection
DictionaryBase
Queue
TableRowCollection
DictionaryEntries
ReadOnlyCollectionBase
XmlNode
Hashtable
SortedList
XmlNodeList
Rozdział 18.
Instrukcje sterujące
405
Iteratory Koncepcja iteratora jest podobna do enumeratora. Jeden i drugi udostępnia metody pozwalające przechodzić przez obiekty znajdujące się w jakimś rodzaju kontenera. Iteratory są bardziej wyspecjalizowane od enumeratorów, ponieważ działają z określonym rodzajem klasy. Podczas gdy za pomocą ogólnego obiektu IEnumerator można przechodzić przez elementy obiektu każdej klasy implementującej interfejs IEnumerator (tablica, kolekcja, tablica asocjacyjna itd.), iterator jest związany z jedną ściśle określoną klasą kontenerową. Na przykład obiekt GraphicsPath reprezentuje zestaw połączonych ze sobą odcinków i krzywych. Aby przejść przez te odcinki i krzywe zawarte w takim obiekcie, można użyć iteratora GraphicsPathIterator. Iteratory są znacznie bardziej wyspecjalizowane od enumeratorów. Sposób ich użycia zależy od tego, co chce się zrobić, a także od rodzaju iteratora. Nie będę ich tutaj szczegółowo opisywać.
Instrukcje Do Loop W Visual Basicu .NET dostępne są trzy podstawowe rodzaje instrukcji Do Loop. Pierwszy stanowi pętla działająca w nieskończoność. Jej składnia jest następująca: Do
Ten rodzaj pętli Do Loop wykonuje swój kod, aż przerwie ją program. Przedstawiona na poniższym listingu pętla przetwarza zamówienia usług. Najpierw sprawdza za pomocą funkcji WorkOrderAvailable, czy zostały złożone jakieś zamówienia. Jeśli tak, wywołuje funkcję ProcessWorkOrder, aby je przetworzyć. Następnie pętla zostaje powtórzona, aby sprawdzić, czy są jeszcze jakieś zamówienia. Do ' Sprawdzenie czy zostało złożone jakieś zamówienie. If WorkOrderAvailable() Then ' Przetworzenie kolejnego zamówienia. ProcessWorkOrder() End If Loop
Pętla ta sprawdza, czy są dostępne zamówienia w nieskończoność. Większość programów umożliwia zatrzymanie takiej pętli w jakiś sposób. Może to być zrealizowane za pomocą instrukcji Exit Do (opisanej w dalszej części rozdziału), która kończy działanie pętli, gdy użytkownik kliknie przycisk Stop. Drugi i trzeci rodzaj pętli Do Loop przeprowadzają test, na podstawie którego podejmują decyzję, czy kontynuować działanie, czy je zakończyć. Różnica pomiędzy tymi dwiema wersjami polega na umiejscowieniu tego testu.
406
Część II
Wstęp do języka Visual Basic
W drugiej wersji pętli Do Loop test znajduje się na początku, dzięki czemu jest on przeprowadzany przed wykonaniem kodu. Jeśli już za pierwszym razem na podstawie tego testu okaże się, że pętla nie powinna kontynuować działania, jej instrukcje nie zostaną ani razu wykonane. Jej składnia jest następująca: Do {While | Until} instrukcje [Exit Do] instrukcje [Continue Do] instrukcje Loop
warunek
Ostatnia wersja pętli Do Loop przeprowadza swój test na końcu. Tutaj instrukcje pętli są wykonywane przed przeprowadzeniem testu. Oznacza to, że kod ten zostanie na pewno wykonany przynajmniej jeden raz. Jej składnia jest następująca: Do instrukcje [Exit Do] instrukcje [Continue Do] instrukcje Loop {While | Until}
warunek
Jeśli w warunku zostanie użyte słowo kluczowe While, pętla będzie wykonywana, dopóki warunek ten będzie spełniony. Użycie słowa kluczowego Until oznacza, że pętla będzie wykonywana, dopóki warunek będzie niespełniony. Warto zauważyć, że instrukcja Until warunek jest równoważna z While Not warunek. Należy zawsze wybierać wariant, który najlepiej pasuje do danego kontekstu i najbardziej odpowiada programiście. Instrukcja Exit Do pozwala na wyjście z najbliższej pętli przed jej normalnym zakończeniem. Continue Do zmusza pętlę do przeskoczenia z powrotem do instrukcji Do i rozpoczęcia nowej iteracji. Instrukcja ta jest szczególnie przydatna, gdy wiadomo, że nie trzeba wykonywać dalszej części pętli, a program chce jak najszybciej zacząć nową iterację. W przeciwieństwie do For pętla Do nie zwiększa automatycznie zmiennej pętlowej ani nie przechodzi do kolejnego obiektu w kolekcji. Warunek pętli Do musi zostać zmodyfikowany bezpośrednio przez jej kod przed wywołaniem instrukcji Continue Do. W przeciwnym razie pętla będzie działała w nieskończoność.
Pętla While End Pętla While End jest równoważna z Do While. Jej składnia jest następująca: While warunek instrukcje [Exit While] instrukcje [Continue While] instrukcje End While
Rozdział 18.
Instrukcje sterujące
407
Poniższa pętla Do While jest równoważna z powyższą pętlą: Do While warunek instrukcje [Exit Do] instrukcje [Continue Do] instrukcje Loop
Instrukcja Exit While powoduje wyjście z pętli While End, podobnie jak instrukcja Exit Do powoduje wyjście z pętli Do While Loop. Analogicznie instrukcja Continue While zmusza program do powrotu do początku pętli While End, podobnie jak instrukcja Continue Do w pętlach Do. Różnica pomiędzy pętlami While End i Do While Loop ma charakter wyłącznie stylistyczny — można używać tej, która nam bardziej odpowiada, działanie programu będzie takie samo. Ponieważ pętla Do Loop oferuje większe możliwości przy swoich czterech wersjach ze słowami kluczowymi While i Until na początku i na końcu, wydaje się, że dobrze jest się jej trzymać.
Instrukcje Exit i Continue Instrukcje Exit i Continue zostały opisane w poprzednich podrozdziałach, ale zasługują na krótkie podsumowanie. Instrukcja Exit pozwala na wcześniejsze zakończenie działania pętli. Dzięki Continue można przeskoczyć z powrotem na początek pętli przed zakończeniem iteracji. Obie te instrukcje działają na najgłębiej zagnieżdżoną pętlę odpowiedniego typu. Na przykład Exit For kończy działanie najgłębiej osadzonej pętli For, która ją otacza. Działanie tych instrukcji demonstruje program ExitAndContinue, który można pobrać z serwera FTP wydawnictwa Helion.
Instrukcja GoTo Instrukcja GoTo zmusza program do bezwarunkowego przejścia do wyznaczonego miejsca w kodzie. Ponieważ steruje ona działaniem aplikacji, jest to instrukcja sterująca. Jej składnia wygląda następująco: GoTo etykieta_wiersza ... etykieta_wiersza: ...
408
Część II
Wstęp do języka Visual Basic
Mimo iż instrukcja ta nie jest sama w sobie instrukcją decyzyjną, często się ją wykorzystuje do naśladowania tego typu instrukcji. Na przykład poniższy kod demonstruje naśladowanie instrukcji If Then Else za pomocą instrukcji GoTo. Najpierw sprawdzana jest wartość zmiennej purchase_total. Jeśli jest ona mniejsza od 1000, sterowanie przechodzi do wiersza z etykietą SmallOrder. Jeżeli zmienna purchase_total ma wartość większą lub równą 1000, program wykonuje kod przeznaczony do przetwarzania dużych zamówień. If purchase_total < 1000 Then GoTo SmallOrder ' Przetwarzanie dużych zamówień. ... Exit Sub SmallOrder: ' Przetwarzanie małych zamówień. ...
Poniższy kod robi mniej więcej to samo co poprzedni, tyle że nie używa instrukcji GoTo: If purchase_total < 1000 Then ' Przetwarzanie dużych zamówień. ... Else ' Przetwarzanie małych zamówień. ... End If
Instrukcja GoTo jest także wykorzystywana do naśladowania pętli. Poniższy kod za pomocą tej instrukcji przenosi sterowanie wstecz, aby dziesięć razy wywołać podprocedurę DoSomething: Dim i As Integer = 1 StartLoop: DoSomething() i += 1 If i < = 10 Then GoTo StartLoop
Poniższy kod robi to samo, tyle że nie używa instrukcji GoTo: For i As Integer = 1 To 10 DoSomething() Next i
Wadą instrukcji GoTo jest jej zbyt duża elastyczność. Możliwe, że za jej pomocą chaotyczny i niedoświadczony programista zmusi program do przeskakiwania z miejsca w miejsce bez ładu i składu. W ten sposób może powstać tak zwany program spaghetti (nazwa ta wzięła się od wykresu demonstrującego przepływ sterowania takiej aplikacji, która wygląda jak spaghetti), bardzo trudny do zrozumienia, debugowania i konserwowania. W wielu zespołach programistycznych używanie instrukcji GoTo jest całkowicie zabronione, dzięki czemu można uniknąć powstania tego rodzaju kodu. Istnieje nawet grupa programistów, którzy uważają, że instrukcja ta powinna zostać całkowicie usunięta z Visual Basica. Dzięki dostępności instrukcji If Then Else, pętli For Next i While oraz innych instrukcji sterujących GoTo nie jest niezbędna.
Rozdział 18.
Instrukcje sterujące
409
Jednak niektórzy programiści uważają, że instrukcja GoTo w pewnych sytuacjach pozwala uprościć kod. Poniższy fragment programu na początku wykonuje jakąś inicjację. Może otwierać bazy danych, tworzyć pliki tymczasowe, łączyć się z internetem lub wykonywać jakieś inne działania przygotowawcze przed uruchomieniem. Następnie wykonuje kilka zadań, z których każde może się nie powieść, co spowodowałoby, że kontynuowanie byłoby bezcelowe. Jeśli na którymś z tych etapów zmienna should_stop zostanie ustawiona na wartość True, program użyje instrukcji GoTo, aby przeskoczyć do swojego kodu czyszczącego. Ten ostatni zamyka wszystkie otwarte bazy danych, usuwa pliki tymczasowe, zamyka pliki stałe oraz wykonuje wszelkie pozostałe działania czyszczące. ' Rozpoczęcie, otwarcie baz danych, plików itd. Initialize() ' Wykonywanie długich serii zadań. DoStuff1() If should_stop Then GoTo CleanUp DoStuff2() If should_stop Then GoTo CleanUp DoStuff3() If should_stop Then GoTo CleanUp ... CleanUp: ' Zamykanie baz danych, usuwanie plików tymczasowych itd. PerformCleanUp()
Instrukcja GoTo w powyższym programie pozwala przeskoczyć do procedur czyszczących, kiedy tylko program musi zakończyć swoje działanie. Sytuacja ta może się zdarzyć, jeśli nie powiedzie się wykonywanie jakiegoś zadania, użytkownik anuluje operację lub wszystkie zadania zostaną zakończone. Należy zauważyć, że jest to bardzo specyficzny sposób użycia instrukcji GoTo. Sterowanie jest przenoszone tylko do przodu, a nigdy wstecz. Ponadto przeskok następuje do procedury czyszczącej, a nie jakiegoś dowolnego miejsca w kodzie. Te fakty ułatwiają sprawianie, że instrukcja GoTo będzie bardziej zrozumiała; pomagają też uniknąć powstania kodu spaghetti. Poniższy kod robi to samo co poprzedni, ale nie używa instrukcji GoTo. Na każdym etapie program sprawdza wartość zmiennej should_stop, aby dowiedzieć się, czy ma kontynuować działanie. ' Rozpoczęcie, otwarcie baz danych, plików itd. Initialize() ' Wykonywanie długich serii zadań. DoStuff1() If Not should_stop Then DoStuff2() If Not should_stop Then DoStuff3() ' Zamykanie baz danych, usuwanie plików tymczasowych itd. PerformCleanUp()
410
Część II
Wstęp do języka Visual Basic
Poniżej znajduje się jeszcze jedna wersja programu bez użycia instrukcji GoTo. Tym razem kod, który wcześniej zawierał instrukcję GoTo, został umieszczony w nowej podprocedurze. Procedura ta do przedwczesnego zatrzymania działania aplikacji wykorzystuje instrukcję Exit Sub zamiast GoTo. Sub DoWork() ' Rozpoczęcie, otwarcie baz danych, plików itd. Initialize() ' Wykonanie wszystkich zadań. PerformTasks() ' Zamykanie baz danych, usuwanie plików tymczasowych itd. PerformCleanUp() End Sub ' Wykonywanie długich serii zadań. Sub PerformTasks() DoStuff1() If should_stop Then Exit Sub DoStuff2() If should_stop Then Exit Sub DoStuff3() If should_stop Then Exit Sub End Sub
Koncepcja instrukcji Exit Sub jest nieco inna niż instrukcji GoTo, mimo iż obie powodują bezwarunkowe przeskoczenie w inne miejsce. Należy jednak pamiętać, że działanie instrukcji Exit Sub ma ściśle określony efekt — kończy wykonywanie przez program bieżącej podprocedury. Nie da się za jej pomocą przeskakiwać w dowolne miejsca w kodzie i ewentualnie utworzyć kodu spaghetti. Jeśli kiedykolwiek poczujesz chęć użycia instrukcji GoTo, zastanów się przez chwilę, czy nie dałoby się tego zapisać jakoś inaczej. Jeżeli nie możesz wymyślić niczego prostszego od instrukcji GoTo, użyj jej. Napisz wtedy bardzo szczegółowe komentarze, aby zapobiec ewentualnym problemom w przyszłości.
Podsumowanie Instrukcje sterujące są sercem każdego programu. Instrukcje decyzyjne wskazują, które części aplikacji mają zostać wykonane, zaś instrukcje pętlowe określają, ile razy mają one zostać wykonane. Do najczęściej używanych instrukcji decyzyjnych należą jedno- i wielowierszowe instrukcje If Then oraz Select Case. Instrukcje IIF i Choose często bywają mniej przejrzyste i czasami są powolniejsze, a więc zamiast z nich należy z reguły korzystać z instrukcji If Then oraz Select Case. W niektórych jednak sytuacjach instrukcje IIF i Choose mogą dodatnio wpłynąć na przejrzystość kodu. Dobierając instrukcję do konkretnego zadania, należy zawsze kierować się rozsądkiem.
Rozdział 18.
Instrukcje sterujące
411
Najczęściej używanymi pętlami są For Next, For Each oraz Do Loop. Niektóre klasy kontenerowe obsługują także enumeratory, które pozwalają przechodzić przez ich elementy. W niektórych sytuacjach enumerator może być bardziej naturalnym rozwiązaniem niż pętla For Each. Pętla While End jest równoważna z Do While. Można używać tej, która bardziej się podoba, chociaż wiele osób pewnie zdecyduje się na tę drugą, ponieważ jest bardziej spójna z pozostałymi pętlami Do Loop. W końcu instrukcja GoTo jest często używana do tworzenia instrukcji decyzyjnych i pętli. Niestety nierozważne posługiwanie się nią prowadzi do powstania kodu spaghetti, który trudno zrozumieć, debugować i konserwować. Aby uniknąć późniejszych problemów, należy w miarę możliwości nie stosować tej instrukcji, a jeśli już zostanie użyta, powinno się pisać szczegółowe komentarze do niej. Niektórzy programiści używają instrukcji GoTo w specjalnych sytuacjach, podczas gdy inni za wszelką cenę usiłują jej uniknąć. Kod z użyciem instrukcji GoTo można napisać w taki sposób, aby się jej pozbyć — na dłuższą metę zawsze się to opłaci. Za pomocą opisanych w tym rozdziale instrukcji sterujących można pisać bardzo skomplikowane programy. Tak naprawdę da się tworzyć tak złożone aplikacje, że trudno zagwarantować, iż będą prawidłowo działać. Czasami nawet względnie prosty program zawiera usterki. W rozdziale 19. — „Obsługa błędów” — opiszę metody ochrony aplikacji przed błędami oraz techniki podejmowania działań, które pomogą naprawić powstałe problemy lub przynajmniej uniknąć zamknięcia aplikacji.
412
Część II
Wstęp do języka Visual Basic
19
Obsługa błędów Mimo iż teoretycznie można napisać program doskonale przewidujący wszystkie możliwe sytuacje, w praktyce w przypadku większych aplikacji jest to niebywale trudne. Bardzo ciężko jest przygotować się na każdą ewentualność w dużym programie. Błędy powodujące otrzymanie niespodziewanych wyników mogą powstawać z powodu nieodpowiedniej implementacji projektu aplikacji. Także użytkownicy i uszkodzone bazy danych mogą dostarczyć do programu takie wartości, których ten nie potrafi obsłużyć. Tak samo dane, których program miał nigdy nie obsługiwać, mogą mieć swoje źródło w zmianie wymagań — ewoluujących w czasie. Dobrym tego przykładem jest błąd pluskwy milenijnej. Kiedy inżynierowie pisali programy księgowe, finansowe, do obsługi magazynów i inne systemy w latach 60. i 70., nie myśleli o tym, że będą one jeszcze w użyciu w 2000 roku. Ponieważ w tamtych czasach przestrzeń dyskowa i pamięć były dość drogie, lata zapisywano jako wartości dwubitowe (na przykład liczba 89 oznaczała rok 1989). Kiedy nadszedł rok 2000, programy te nie mogły odróżnić daty 1901 od 2001. Był nawet taki śmieszny przypadek, kiedy system automatycznej rejestracji zaczął wydawać tabliczki licencyjne dla pojazdów poruszających się bez koni dla nowych samochodów, ponieważ wydawało mu się, że auta wyprodukowane w roku 00 muszą być antykami. Problem pluskwy milenijnej nie był w rzeczywistości błędem. Był to przykład użycia w oprogramowaniu danych, dla których oprogramowanie to nie zostało stworzone. W tym rozdziale opiszę różne rodzaje sytuacji wyjątkowych, które mogą wystąpić w aplikacjach — od nieplanowanych danych (na przykład pluskwa milenijna) po zwykłe błędy w kodzie. Dzięki odpowiedniemu planowaniu można zawczasu utworzyć solidną aplikację, która będzie elegancko działać, nawet jeśli zdarzą się jakieś nieprzewidziane sytuacje.
Błędy a nieplanowane sytuacje Różnego rodzaju niezaplanowane sytuacje mogą spowodować awarię skądinąd bardzo wysokiej jakości programu. Sposób obsługi tych zdarzeń zależy od ich rodzaju.
414
Część II
Wstęp do języka Visual Basic
Na potrzeby tego rozdziału słowo błąd (ang. bug) definiujemy jako nieprawidłowość w kodzie źródłowym. Niektóre błędy są od razu widoczne i łatwe do naprawienia. Należą do nich błędy typograficzne oraz przypadki nieprawidłowego użycia obiektów (na przykład użycie złej własności kontrolki). Inne są o wiele bardziej subtelne, dadzą się wykryć dopiero jakiś czas po ich wystąpieniu. Na przykład procedura wstawiająca dane może umieścić nieprawidłowe dane w rzadko używanym polu w obiekcie Customer. Problem ten zostanie wykryty dopiero po jakimś czasie, gdy program spróbuje uzyskać dostęp do tego pola. Tego rodzaju błędy są trudne do wychwycenia i naprawienia, ale istnieją pewne środki zapobiegawcze, których podjęcie może ułatwić znajdowanie tego typu usterek. Krótka lekcja historii. Angielski termin „bug” jest używany do określania jakiegoś rodzaju defektu, przynajmniej od czasów telegrafów. W informatyce termin ten prawdopodobnie pochodzi od owada — ćmy — który został złapany pomiędzy dwoma przekaźnikami w jednym z wczesnych komputerów w 1945 roku. Więcej informacji, łącznie ze zdjęciem tego pierwszego szkodnika komputerowego, można znaleźć na stronie www.jamesshuggins.com/h/tek1/first_computer_bug.htm. Nieplanowana sytuacja to przewidywalne niepożądane zdarzenie, które wiadomo, że czasem ma miejsce mimo wszelkich wysiłków programisty. Na przykład prosta operacja drukowania może się nie udać z wielu powodów. Drukarka może być odłączona od prądu, niepołączona z komputerem, niepołączona z siecią, może brakować w niej tonera lub papieru, możliwe, że ma problemy z wadliwą pamięcią, papier może się zablokować w podajniku lub urządzenie jest po prostu zepsute. Nie są to błędy, ponieważ oprogramowanie nie jest wadliwe. Istnieje jakiś problem, który pozostaje poza kontrolą programu; musi on zostać naprawiony. Inną bardzo często spotykaną nieplanowaną sytuacją jest wprowadzenie nieprawidłowych danych przez użytkownika. Program czasem wymaga, by wpisano w polu tekstowym wartość z przedziału od 1 do 10, ale osoba pracująca z komputerem zamiast tego wpisze 0,9999 albo nutria.
Przechwytywanie błędów Błędy z definicji są nieplanowane. Żaden programista nie siada i nie myśli: „Może w tej deklaracji zmiennej warto by zrobić jakiś błąd”. Ponieważ błędy są z natury nieprzewidywalne, nie można z góry określić, gdzie wystąpią. Ich oznak można się jednak doszukać w tym, jak zachowuje się program. Wyobraźmy sobie na przykład podprocedurę sortującą elementy zamówienia według ceny. Jeśli podprocedura ta odbierze zamówienie na 100 000 produktów, najprawdopodobniej coś jest nie tak. Jeżeli jednym z tych produktów jest klawiatura komputerowa w cenie 73 bilionów złotych, wiadomo, że coś nie gra. Jeśli klient składający zamówienie nie istnieje, pewnie gdzieś jest jakiś błąd. Procedura ta mogłaby kontynuować działanie i posortować te wszystkie 100 000 produktów w cenach od kilku gorszy do 73 bilionów złotych. Później spróbowałaby wydrukować fakturę składającą się z pięciu tysięcy stron bez danych adresowych ani informacji o sposobie zapłaty. Dopiero wtedy programiści zorientowaliby się, że wystąpił jakiś problem.
Rozdział 19.
Obsługa błędów
415
Zamiast próbować obejść jakoś dane, które sprawiają problemy, lepiej by było, gdyby ta procedura sortująca od razu informowała programistę, że dzieje się coś złego, aby można było spróbować od razu podjąć odpowiednie kroki. Im wcześniej błąd zostanie wykryty, tym łatwiej będzie go znaleźć. Opisywaną usterkę będzie prościej odszukać, jeśli wykryje ją procedura sortująca, a nie wtedy, gdy poczekamy, aż program spróbuje wydrukować złą fakturę. Procedury czasem same się chronią, a aplikacja jako całość może profilaktycznie sprawdzać swoje dane wejściowe i wyjściowe oraz raportować programistom o wszystkim, co jest podejrzane. Niektórzy programiści sprzeciwiają się zmuszaniu procedur do spędzania znacznych ilości czasu na sprawdzaniu danych, o których wiadomo, że są poprawne. Przecież jedna procedura wygenerowała te dane i przekazała je do innej procedury, a więc jest jasne, że są one poprawne, ponieważ ta pierwsza procedura dobrze wykonała swoją część zadania. Ponieważ jednak błędy są z natury niespodziewane, nie można bezpiecznie założyć, że wszystkie procedury działają perfekcyjnie, a dane są poprawne. Wiele firm stosuje zautomatyzowane narzędzia testujące, których zadaniem jest wczesne wykrywanie problemów. Narzędzia testowania regresyjnego mogą wykonywać kod, aby sprawdzić, czy jego wynik nie zmienił się po zmodyfikowaniu innych części programu. Jeśli opracujesz zestaw procedur testowych, które będą sprawdzały dane i wyniki podprocedur, będziesz mógł je wprowadzić do automatycznego systemu testującego. Aby uniknąć spowolnienia działania programu przez kod sprawdzający, można użyć metody Assert obiektu klasy Debug do sprawdzania nietypowych sytuacji. W czasie debugowania aplikacji instrukcje te zgłaszają błędy, jeśli wykryją coś podejrzanego. Podczas opracowywania wersji końcowej, która jest przeznaczona do użytku przez klienta, instrukcje Debug.Assert są usuwane z kodu programu. To sprawia, że aplikacja działa szybciej i nie wyświetla użytkownikowi tajemniczych komunikatów. Te dodatkowe fragmenty kodu można dodawać i usuwać z programu za pomocą stałych kompilatora DEBUG, TRACE oraz CONFIG. Poniższy kod pochodzi z programu SortOrders, który można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Sprawdza on dane wejściowe podprocedury (program ten nic w rzeczywistości nie robi, demonstruje tylko, jak pisać kod sprawdzający dane wejściowe). Private Sub SortOrderItems(ByVal the_order As Order) ' Sprawdzanie danych wejściowych. Debug.Assert(the_order.Items IsNot Nothing, "Brak zamówionych towarów.") Debug.Assert(the_order.Customer IsNot Nothing, "Brak klienta w zamówieniu.") Debug.Assert(the_order.Items.Count < 100, "Za dużo zamówionych produktów.") ... ' Sortowanie produktów. ... ' Sprawdzanie danych wyjściowych. #If DEBUG Then ' Sprawdzenie, czy produkty zostały posortowane. Dim order_item1 As OrderItem
416
Część II
Wstęp do języka Visual Basic
Dim order_item2 As OrderItem order_item1 = DirectCast(the_order.Items(1), OrderItem) For i As Integer = 2 To the_order.Items.Count order_item2 = DirectCast(the_order.Items(i), OrderItem) Debug.Assert(order_item1.Price < = order_item2.Price, _ "Zamówione produkty nie zostały prawidłowo posortowane.") order_item1 = order_item2 Next i #End If End Sub
Na początku podprocedura ta sprawdza swoje dane wejściowe. Najpierw czy przekazany do niej obiekt klasy Order posiada kolekcję Items, a także czy jego zmienna Customer nie ma wartości Nothing. Ponadto sprawdza, czy zamówienie nie składa się z więcej niż 100 elementów. Jeśli w czasie testowania przyjdzie jakieś większe zamówienie, programiści będą mogli zwiększyć tę liczbę do 200 lub na jakąś inną wartość, ale nie powinna ona być zbyt duża. Przed zakończeniem działania podprocedura przechodzi za pomocą pętli przez posortowane elementy zamówienia, aby sprawdzić, czy zostały prawidłowo pogrupowane. Jeśli którykolwiek produkt kosztuje więcej niż ten znajdujący się przed nim, program zgłasza błąd. Ponieważ kod ten znajduje się w instrukcji #If DEBUG Then, w wersji ostatecznej aplikacji zostaje on usunięty. Po wystarczająco długim testowaniu aplikacji powinno się wykryć większość tego typu błędów. W czasie tworzenia wersji ostatecznej programu kompilator usuwa kod sprawdzający, dzięki czemu skompilowana aplikacja będzie lżejsza i szybsza.
Przechwytywanie niespodziewanych zdarzeń Mimo iż nikt nie chce, by zdarzały mu się niespodziewane sytuacje, przy odrobinie wysiłku można przewidzieć, gdzie taka sytuacja może mieć miejsce. Najczęściej zdarza się to wówczas, gdy program musi pobrać coś spoza swojego kodu. Na przykład kiedy aplikacja powinna uzyskać dostęp do pliku, strony internetowej, dyskietki albo napędu CD-ROM, który jest niedostępny. Również zawsze wtedy, gdy program pobiera dane od użytkownika, należy liczyć się z tym, że mogą one być nieprawidłowe. Zauważ, jaka jest różnica między opisywanymi tu niespodziewanymi zdarzeniami a błędami omówionymi w poprzednim podrozdziale. Jeśli przeprowadzi się odpowiednią ilość testów, można w końcu usunąć wszystkie błędy. Natomiast żadne testy nie zapobiegną wystąpieniu sytuacji niespodziewanej. Bez względu na to, jaki kod napiszemy, użytkownik zawsze może wyjąć dyskietkę z napędu. Kod chroniący program przed niespodziewanymi zdarzeniami należy pisać dla wszystkich znanych możliwości. Ogólnie lepiej jest bezpośrednio sprawdzać, czy sytuacje te nie mają miejsca, niż po prostu próbować wykonać wszystkie działania i przechwytywać wszelkie błędy, które mogą zostać zgłoszone. Sprawdzanie problematycznych zdarzeń zazwyczaj daje pełniejsze informacje na temat tego, co jest nie tak. Ponadto technika ta jest szybsza od przechwytywania błędów, ponieważ opisane nieco dalej techniki strukturalnej obsługi błędów implikują dość duży narzut.
Rozdział 19.
Obsługa błędów
417
Na przykład poniższa instrukcja ustawia zmienną typu Integer na wartość wpisaną przez użytkownika w polu tekstowym: Dim num_items As Integer = Integer.Parse(txtNumItems.Text)
Użytkownik powinien wprowadzić do tego pola prawidłową wartość, ale niestety może też wpisać coś innego niż liczbę, zbyt dużą wartość jak na typ Integer albo liczbę ujemną, podczas gdy wymagana jest dodatnia. Pole może nawet zostać puste. Poniższy kod, który sprawdza, czy wprowadzane przez użytkownika dane są typu Integer, pochodzi z programu ValidateInteger. Można go pobrać z serwera FTP wydawnictwa Helion. ' Sprawdzenie, czy użytkownik nie pozostawił pola pustego. Dim num_items_txt As String = txtNumItems.Text If num_items_txt.Length < 1 Then MessageBox.Show("Proszę wprowadzić wartość.") txtNumItems.Focus() Exit Sub End If ' Sprawdzenie, czy wprowadzono liczbę. If Not IsNumeric(num_items_txt) Then MessageBox.Show("Wymagana jest liczba.") txtNumItems.Select(0, num_items_txt.Length) txtNumItems.Focus() Exit Sub End If ' Przypisanie wartości. Dim num_items As Integer Try num_items = Integer.Parse(txtNumItems.Text) Catch ex As Exception MessageBox.Show("Błąd w zmiennej num_items." txtNumItems.Select(0, num_items_txt.Length) txtNumItems.Focus() Exit Sub End Try
&
vbCrLf
& ex.Message)
' Sprawdzenie, czy wprowadzona liczba mieści się w przedziale od 1 do 100. If num_items < 1 Or num_items > 100 Then MessageBox.Show("Num Items must be between 1 and 100") txtNumItems.Select(0, num_items_txt.Length) txtNumItems.Focus() Exit Sub End If
Powyższy kod najpierw sprawdza, czy pole nie jest puste, a następnie — za pomocą funkcji IsNumeric — czy wprowadzone do niego dane reprezentują liczbę. Niestety funkcja ta nie działa dokładnie jak funkcje typu Integer.Parse. Zwraca ona wartość False dla takich wartości, jak &H10. Ta ostatnia jest poprawną liczbą szesnastkową, a funkcja Integer.Parse potrafi poprawnie ją zinterpretować. Ponadto funkcja IsNumeric zwraca wartość True dla wartości typu 123456789012345, które nie mieszczą się w zakresie typu Integer, oraz 1.2, będące wprawdzie liczbami, ale nie całkowitymi. Ponieważ funkcja
418
Część II
Wstęp do języka Visual Basic
IsNumeric nie działa jak funkcja Integer.Parse, konieczne jest zastosowanie w programie bloku Try Catch, który chroni aplikację przed konwersją łańcucha na liczbę całkowitą.
Na zakończenie program sprawdza, czy podana mu wartość mieści się w wyznaczonym przedziale. Jeśli przejdzie ona pomyślnie wszystkie testy, zostanie użyta dalej. Typowa podprocedura często musi pobierać i sprawdzać wiele wartości, a wielokrotne przepisywanie jej kodu byłoby bardzo uciążliwe. Lepszym rozwiązaniem okazałoby się przeniesienie jej do funkcji IsValidInteger i wywoływanie tej funkcji w razie potrzeby. Podobne procedury można napisać do walidacji danych innych typów, jak numery telefonów, adresy e-mail, adresy domów itd.
Globalna obsługa błędów Błędy najlepiej jest przechwytywać jak najbliżej miejsca, w którym wystąpiły. Jeśli problem pojawi w jakiejś określonej podprocedurze, najłatwiej będzie go naprawić, gdy zostanie on przechwycony właśnie w niej. Niestety błędy często występują w niespodziewanych miejscach. Jeśli nie ochronimy odpowiednim kodem każdej podprocedury w programie (jest to często stosowana strategia), problem może się pojawić w jednym z niechronionych miejsc. We wcześniejszych wersjach Visual Basica nie można było przechwycić błędu, przez co aplikacja ulegała awarii. W najnowszych edycjach tego języka umożliwiono definiowanie globalnej procedury obsługi błędów, przechwytującej wszystkie usterki, które nie zostały przechwycone przez inny kod. Aby zdefiniować procedury obsługi błędów, które działają na poziomie aplikacji, kliknij dwukrotnie w oknie Solution Explorer pozycję My Project. Przejdź na kartę Application i kliknij przycisk View Application Events. Dzięki temu otworzy się okno dla zdarzeń na poziomie aplikacji. Z listy rozwijanej po lewej stronie wybierz pozycję (My Application Events). Wtedy z listy rozwijanej po prawej stronie będzie można wybrać jedno z kilku zdarzeń, między innymi NetworkAvailabilityChanged, Shutdown, Startup, StartupNextInstance oraz UnhandledException. Kliknij ostatnie z tych poleceń, aby utworzyć procedurę obsługi zdarzeń UnhandledException. W tej procedurze można podjąć wszystkie działania niezbędne do obsłużenia błędu. Ponieważ będzie to zazwyczaj nieprzewidziany błąd, istnieją niewielkie szanse, że uda się go właściwie naprawić. Można jednak przynajmniej przed zamknięciem aplikacji zapisać dane o nim. Parametr zdarzeniowy e posiada własność ExitApplication, którą można ustawić na wartość True albo False, dzięki czemu Visual Basic zostanie poinformowany, czy aplikacja ma zostać zamknięta, czy nie. Poniższy kod — wyświetlający komunikat o nieobsłużonym błędzie — pochodzi z programu o nazwie GlobalException, który można pobrać z serwera FTP wydawnictwa Helion. Ustawia on własność e.ExitApplication na False, dzięki czemu aplikacja nie przestaje działać.
Rozdział 19. Private Sub MyApplication_UnhandledException( _ ByVal sender As Object, ByVal e As _ System.Windows.Forms.UnhandledExceptionEventArgs) _ Handles Me.UnhandledException MessageBox.Show("Wyjątek przechwycony globalnie." e.Exception.Message) e.ExitApplication = False End Sub
&
Obsługa błędów
vbCrLf &
419
_
Kiedy aplikacja zostanie uruchomiona w IDE, po dotarciu do miejsca, w którym znajduje się instrukcja powodująca błąd, Visual Basic zatrzyma wykonywanie programu w debugerze, dzięki czemu zdarzenie UnhandledException nigdy nie zostanie wykonane. Jeśli natomiast włączysz skompilowaną wersję aplikacji, zdarzenie to zostanie uruchomione, a do akcji wkroczy globalna procedura obsługi błędów.
Strukturalna obsługa błędów W Visual Basicu .NET wprowadzono strukturalną obsługę błędów, która polega na użyciu bloków Try. Jej składnia jest następująca: Try instrukcje_try ... [Catch ex As typ_wyjątku_1 instrukcje_wyjątku_1 ...] [Catch ex As typ_wyjątku_2 instrukcje_wyjątku_2 ...] ... [Catch końcowe_instrukcje_wyjątku ...] [Finally instrukcje_finally ...] End Try
Program wykonuje kod bloku instrukcje_try. Jeśli któraś z tych instrukcji zgłasza wyjątek, przeskakuje on do pierwszej instrukcji Catch. Jeśli wyjątek ten pasuje do pierwszego typu wyjątku, zostają wykonane instrukcje instrukcje_wyjątku_1. Typ wyjątku może dokładnie pasować do klasy wyjątku instrukcji Catch lub być podklasą tej klasy. Wyobraźmy sobie na przykład, że kod w bloku instrukcje_try wykonuje działanie dzielenia przez zero. Powoduje to zgłoszenie wyjątku typu DivideByZeroException. Klasa ta dziedziczy po klasie ArithmeticException, która z kolei dziedziczy po SystemException , dziedziczącej po Exception . Oznacza to, że program zatrzymałby się na pierwszej instrukcji Catch, która przechwytuje wyjątki typu DivideByZeroException, AruthmeticException, SystemException, lub na Exception.
Jeśli zgłoszony wyjątek nie pasuje do pierwszego typu wyjątków, program sprawdza drugą instrukcję Catch. Testowane są po kolei wszystkie instrukcje Catch, aż zostanie znaleziona taka, która pasuje do zgłoszonego wyjątku, lub gdy instrukcje te zostaną wyczerpane.
420
Część II
Wstęp do języka Visual Basic
Pamiętaj, że instrukcje Try należy ustawić w takiej kolejności, aby najbardziej specyficzna znajdowała się na samym początku. W przeciwnym razie ogólniejsze instrukcje będą przechwytywać wyjątki, które mogłyby zostać obsłużone przez te bardziej szczegółowe. Na przykład ogólna klasa Exception obejmuje wszystkie wyjątki, więc jeśli pierwsza instrukcja Catch przechwytuje wyjątki jej typu, pozostałe instrukcje Catch nie zostaną nigdy wykonane. Jeśli dwie instrukcje Catch nie są ze sobą powiązane, a więc żadna nie może przechwycić wyjątku tej drugiej, na początku wstaw tę, której błąd jest bardziej prawdopodobny. Dzięki temu program będzie działał wydajniej, ponieważ najpierw sprawdzi najczęściej występujące problemy. Ponadto dzięki temu kod, który ma największe szanse zostać wykonanym, znajduje się wyżej, co ułatwia znalezienie go. Jeśli żadna instrukcja Catch nie pasuje do typu zgłoszonego wyjątku, zostaje on przesłany o jeden poziom w górę stosu wywołań Visual Basica, do procedury, która wywołała bieżącą procedurę. Jeżeli ta wyższa procedura posiada odpowiedni kod do obsługi tego wyjątku, naprawia błąd. Jeśli nie, jest on przesyłany jeszcze wyżej, aż zostanie znaleziona instrukcja Catch, która może go obsłużyć, lub aż skończy się stos wywołań. Jeżeli zostanie osiągnięty koniec stosu wywołań, Visual Basic wywoła opisaną w poprzednim podrozdziale procedurę obsługi zdarzeń UnhandledException, o ile taka istnieje. Jeśli jej nie ma, program ulegnie awarii. Jeśli w instrukcji Catch nie zostanie określony żaden typ, będzie ona pasowała do wszystkich wyjątków. Jeżeli zgłoszony wyjątek nie pasuje do żadnego z wcześniejszych typów, program wykonuje blok kodu końcowe_instrukcje_wyjątku. Należy zauważyć, że instrukcja Catch ex As Exception również pasuje do wszystkich wyjątków, a więc jest równoważna z samą instrukcją Catch. Ponadto daje łatwy dostęp do własności i metod obiektu wyjątku. Klasy, których należy użyć w instrukcjach Catch , da się wybrać na kilka sposobów. Po pierwsze, można spędzić mnóstwo czasu na przeszukiwaniu pomocy internetowej. Drugą i łatwiejszą metodą jest pozwolenie programowi na awarię, po czym przeanalizowanie wygenerowanego przezeń komunikatu. Na rysunku 19.1 przedstawiono komunikat o błędzie, który został wygenerowany przez aplikację podczas próby przekonwertowania łańcucha "Witaj" na liczbę całkowitą za pomocą funkcji Integer.Parse. Z okna dialogowego wyjątku łatwo wywnioskować, że program powinien obsługiwać wyjątek typu FormatException. Jeszcze innym sposobem na sprawdzenie typu wyjątków do przechwycenia jest użycie instrukcji Catch ex As Exception na końcu listy instrukcji Catch. W bloku tej instrukcji należy umieścić kod wyświetlający nazwę typu wyjątku (użyj funkcji TypeName) lub wynik zwrócony przez jego metodę ToString. Jeśli pojawią się jakieś nowe typy wyjątków, będzie można utworzyć dla nich osobne instrukcje Catch, które będą podejmować działania odpowiednie właśnie dla nich. Przy przechwytywaniu niektórych typów wyjątków podjęcie odpowiednich działań może nie być możliwe. Jeśli na przykład program zajmie całą dostępną pamięć, Visual Basic zgłosi wyjątek OutOfMemoryException. Skoro nie ma wolnej pamięci, trudno zrobić cokolwiek rozsądnego. Analogicznie — jeśli wystąpią jakieś problemy z systemem plików, zapisanie opisu błędów, które wystąpiły w pliku dziennika, może okazać się niemożliwe.
Rozdział 19.
Obsługa błędów
421
Rysunek 19.1. Kiedy program ulega awarii, wygenerowany przez niego komunikat zawiera informację o typie zgłoszonego wyjątku
Po zakończeniu wykonywania kodu w instrukcjach Try i wykonaniu kodu obsługującego wyjątki w bloku Catch program wykonuje instrukcje Finally. Sekcję Finally można wykorzystać do wykonania jakiegoś kodu, bez względu na to, czy wykonanie instrukcji Try powiedzie się, czy nie. Nie ma obowiązku umieszczania instrukcji Catch w blokach Try, ale opuszczenie ich przeczy przeznaczeniu tych bloków. Jeśli taka instrukcja Try zgłosi wyjątek, w programie nie będzie kodu do jego obsługi, a więc zostanie on przesłany w górę stosu wywołań. W końcu aplikacja znajdzie jakąś odpowiednią procedurę obsługi błędów albo dojdzie do końca stosu, po czym ulegnie awarii. Jeśli nie planujesz pisać instrukcji Catch, równie dobrze możesz w ogóle nie używać bloków Try. Każdy blok Try powinien posiadać przynajmniej jedną sekcję Catch lub Finally, chociaż sekcje te nie muszą zawierać żadnego kodu. Na przykład poniższy blok Try wywołuje podprocedurę DoSomething i ignoruje wszystkie zgłaszane przez nią błędy dzięki użyciu pustej sekcji Catch: Try DoSomething() Catch End Try
Użycie pustej sekcji Finally jest dozwolone, ale niezbyt pożyteczne. Poniższy kod nie chroni programu przed żadnymi wyjątkami, ponieważ nie robi nic w bloku Finally. Równie dobrze można by całkiem opuścić ten blok Try.
422
Część II
Wstęp do języka Visual Basic
Try DoSomething() Finally End Try
Przykładowe użycie bloku Try Catch do obsługi błędów demonstruje program ThrowError, który można pobrać z serwera FTP wydawnictwa Helion.
Obiekty wyjątków Kiedy instrukcja Catch przechwytuje wyjątek, jej zmienna wyjątkowa zawiera informacje o błędzie, który ten wyjątek zgłosił. Różne klasy wyjątków mogą mieć odmienną funkcjonalność, ale wszystkie one posiadają pewien trzon funkcjonalności — udostępniany przez klasę Exception, po której dziedziczą. Poniższa tabela zawiera zestawienie najczęściej używanych własności i metod klasy Exception. Własność lub metoda
Opis
InnerException
Wyjątek, który spowodował bieżący wyjątek. Wyobraźmy sobie na przykład, że piszemy bibliotekę narzędzi przechwytującą wyjątek i zgłaszającą nowy własny wyjątek, który opisuje problem w odniesieniu do tej naszej biblioteki. Własność InnerException należy ustawić na wyjątek przechwycony przed zgłoszeniem tego nowego wyjątku.
Message
Zwraca krótki komunikat opisujący wyjątek.
Source
Zwraca nazwę aplikacji lub obiektu, który zgłosił wyjątek.
StackTrace
Zwraca łańcuch zawierający stos. Ten ostatni wyznacza, w którym miejscu znajdował się program, gdy wystąpił błąd.
TargetSite
Zwraca nazwę metody, która zgłosiła wyjątek.
ToString
Zwraca łańcuch opisujący wyjątek i zawierający dane ze śledzenia stosu.
Program ShowExceptionInfo, który można pobrać z serwera FTP wydawnictwa Helion, wyświetla wartości Message, StackTrace oraz ToString jednego wyjątku. Niezbędnym minimum jest zapisanie w dzienniku lub wyświetlenie wartości Message dla każdego niespodziewanego wyjątku, aby można było dowiedzieć się, co to było. Dodanie wartości StackTrace lub wyniku zwróconego przez metodę ToString pozwala dodatkowo dowiedzieć się, gdzie ten wyjątek wystąpił. Poniżej znajduje się wynik zwrócony przez metodę ToString, wygenerowany przez obiekt wyjątku typu DivideByZeroException: System.DivideByZeroException: Attempted to divide by zero. at ShowExceptionInfo.Form1.CheckVacationPay() in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 25 at ShowExceptionInfo.Form1.CalculateEmployeeSalaries() in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary
Rozdział 19.
Obsługa błędów
423
Projects\ShowExceptionInfo\Form1.vb:line 18 at ShowExceptionInfo.Form1.btnCalculate_Click(Object sender, EventArgs e) in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 5
Wartości StackTrace i ToString mogą pomóc programiście w zlokalizowaniu błędu, ale użytkownika końcowego raczej przestraszą. Nawet skrócona forma tych danych, stosowana przez własność Message, zazwyczaj do niczego się im nie przydaje. Kiedy użytkownik kliknie przycisk Pokaż niezapłacone faktury, komunikat Próba dzielenia przez zero raczej nie powie mu, o co chodzi. Kiedy program przechwytuje błąd, dobrym zwyczajem jest zapisywanie pełnego komunikatu ToString w pliku dziennika lub wysłanie go na e-mail programisty. W aplikacji natomiast należy zaprezentować taką informację, która będzie zrozumiała dla użytkownika. Na przykład można wyświetlić następujący komunikat — Nie można zsumować niezapłaconych faktur. Raport o tym błędzie został wysłany do obsługi technicznej. Następnie program powinien próbować kontynuować działanie w jak najelegantszy sposób. Może nie zdołać już dokończyć bieżących obliczeń, ale nie powinien przestać działać. Musi w miarę możliwości pozwalać na dalszą pracę nad innymi zadaniami.
Obiekty klasy StackTrace Metody wyjątków ToString i StackTrace zwracają tekstową reprezentację stosu wywołań programu. W aplikacji można też używać obiektów klasy StackTrace do analizowania jej postępu bez generowania błędu. Poniższy kod demonstruje wyświetlenie stosu wywołań w oknie Immediate: Imports System.Diagnostics ... Dim stack_trace As New System.Diagnostics.StackTrace(True) Debug.WriteLine(stack_trave.ToString())
Klasa StackTrace udostępnia także metody służące do analizowania ramek funkcji na stosie wywołań. Dostęp do obiektów klasy StackFrame, które reprezentują ramki, dają własność FrameCount oraz metody GetFrame i GetFrames. Obiekty klasy StackFrame dostarczają dodatkowe informacje, których nie udostępnia metoda ToString obiektu StackTrace, takie jak nazwa pliku każdego kodu, numer wiersza i kolumny. Program ClimbStackTrace, który można pobrać z serwera FTP wydawnictwa Helion, przechodzi kolejno przez warstwy stosu wywołań i wyświetla informacje o każdym z poziomów.
Zgłaszanie wyjątków Poza przechwytywaniem wyjątków czasami program musi wygenerować swoje własne. Obsługiwanie wyjątków nazywa się ich przechwytywaniem (ang. catching), a ich powodowanie — zgłaszaniem lub wyrzucaniem (ang. throwing).
424
Część II
Wstęp do języka Visual Basic
Aby zgłosić błąd, program tworzy egzemplarz wyjątku takiego typu, który chce wygenerować, po czym przekazuje do konstruktora dodatkowe — opisujące problem — informacje. Aplikacja może też w razie potrzeby ustawić jeszcze inne pola wyjątku, na przykład własność Source, informującą kod, który go przechwycił, gdzie on powstał. Następnie do wygenerowania wyjątku używana jest instrukcja Throw. Jeśli gdzieś na stosie wywołań znajduje się odpowiednia procedura obsługi błędów, Visual Basic przeskakuje do niej, po czym przetwarza ona ten wyjątek. Poniższy kod — demonstrujący ochronę klasy DrawableRectangle przed nieprawidłowymi danymi wejściowymi — pochodzi z programu DrawableRect, który można pobrać z serwera FTP wydawnictwa Helion. Public Class DrawableRectangle Public Sub New(ByVal new_x As Integer, ByVal new_y As Integer, _ ByVal new_width As Integer, ByVal new_height As Integer) ' Sprawdzenie, czy new_width > 0. If new_width <= 0 Then Dim ex As New ArgumentException( _ "DrawableRectangle musi mieć szerokość większą od zera.", _ "new_width") Throw ex End If ' Sprawdzenie, czy new_height > 0. If new_height <= 0 Then Throw New ArgumentException( _ "DrawableRectangle musi mieć wysokość większą od zera.", _ "new_height") End If ' Zapisanie wartości parametrów. ... End Sub ... End Class
Konstruktor tej klasy przyjmuje cztery argumenty — współrzędne X i Y oraz szerokość i wysokość. Jeśli szerokość jest mniejsza lub równa zero, program tworzy obiekt ArgumentException. Do konstruktora tego wyjątku przekazuje łańcuch opisu i nazwę nieprawidłowego argumentu. Po utworzeniu wyjątku zostaje on zgłoszony za pomocą instrukcji Throw. Wysokość obiektu jest sprawdzana w taki sam sposób, ale utworzenie i zgłoszenie tego wyjątku zostało zrealizowane w jednej instrukcji, aby zademonstrować inną metodę zgłaszania wyjątków. Poniższy kod demonstruje sposób użycia bloku Try do ochrony programu podczas tworzenia nowego obiektu DrawableRectangle: Try Dim rect As New DrawableRectangle(10, 20, 0, 100) Catch ex As Exception MessageBox.Show(ex.Message) End Try
Rozdział 19.
Obsługa błędów
425
Kiedy potrzebne jest zgłoszenie wyjątku w programie, najłatwiej wykorzystać w tym celu jakąś istniejącą już klasę. Są dwa sposoby na uzyskanie list klas wyjątków, z których można coś wybrać — w zależności od potrzeb. Po pierwsze, spis najbardziej przydatnych klas wyjątków zawiera dodatek O — „Przydatne klasy wyjątków”. Dobra lista znajduje się też w końcowej części tematu Introduction to Exception Handling In Visual Basic .NET na stronie msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vstechart/html/vbtchexceptions errorsinvisualbasicnet.asp. Natomiast na stronie msdn2.microsoft.com/library/zbea8bay znajduje się bardzo długi spis klas wyjątków dziedziczących po klasie System.Exception. Innym sposobem na znalezienie klas wyjątków jest otwarcie okna Object Browser (za pomocą polecenia Object Browser z menu View) i wpisanie w polu słowa Exception. Na rysunku 19.2 przedstawiono okno Object Browser. Wyświetla ono około 400 trafień, z których wiele to klasy wyjątków. Zaznaczona została klasa System.FormatException, dzięki czemu widoczny jest jej opis.
Rysunek 19.2. W oknie Object Browser można znaleźć klasy wyjątków
Przy dobieraniu klasy do zgłaszanego wyjątku należy kierować się rozsądkiem. Na przykład Visual Basic używa klasy System.Reflection.AmbiguousMatchException, kiedy próbuje powiązać wywołanie podprocedury z metodą obiektu, a nie wie, z której przeciążonej wersji skorzystać. Dzieje się to na niższym poziomie niż ten, na którym działa program, a więc nie zastosujemy tej klasy w dokładnie tym samym celu. Klasa ta może jednak być przydatna, gdy nasza procedura analizuje łańcuch, po czym na jego podstawie nie może zdecydować się, jakie działania podjąć. W takim przypadku można użyć tej klasy do reprezentacji błędu, mimo iż nie jest to dokładnie jej przeznaczenie. Przed użyciem którejkolwiek z tych klas sprawdź w pomocy internetowej, czy za jej pomocą osiągniesz odpowiedni rezultat. Jeśli nie ma klasy, która by odpowiadała Twoim wymaganiom, zawsze możesz utworzyć własną klasę wyjątków, o czym napiszę w kolejnym podrozdziale „Niestandardowe klasy wyjątków”.
426
Część II
Wstęp do języka Visual Basic
Niektóre wyspecjalizowane klasy i biblioteki posiadają własne klasy wyjątków. Na przykład obiekty serializujące i kryptograficzne zawierają zestawy klas wyjątków, których użycie ma sens w ich własnym obszarze. Są to z reguły bardzo wyspecjalizowane klasy, przez co nie znajdą one zastosowania w naszych programach, chyba że powtarzamy w nich zgłoszenie błędu odebranego od jednego z takich obiektów.
Niestandardowe klasy wyjątków Kiedy konieczne jest zgłoszenie wyjątku w programie, najłatwiej w tym celu wykorzystać istniejącą już klasę. Dzięki użyciu standardowych klas inni programiści łatwo mogą się zorientować, o co chodzi w naszym kodzie. Ponadto zapobiega to powstaniu zjawiska utworzenia nadmiernej liczby wyjątków, które zmusza programistów do sprawdzania dziesiątek lub setek typów wyjątków. Czasami jednak istniejące klasy nie pozwalają osiągnąć odpowiedniego rezultatu. Wyobraźmy sobie na przykład, że tworzymy klasę zawierającą dane, które mogą egzystować przez długi czas. Jeśli program spróbuje użyć jej obiektu, który nie był przez jakiś czas odświeżany, chcemy zgłosić jakiś rodzaj wyjątku związanego z wygaśnięciem danych. Można by użyć na siłę klasy System.TimeoutException, ale ten typ wyjątku nie całkiem pozwoli osiągnąć cel. Lepsza byłaby klasa Expired, jednak należy ona do przestrzeni nazw System.Net.Cookie. Jej użycie wymagałoby dołączenia do programu całej tej przestrzeni nazw, mimo że aplikacja nie ma do czynienia z plikami cookie. W takiej sytuacji chyba najlepszym rozwiązaniem jest utworzenie własnej klasy wyjątków. Jest to bardzo łatwe. Wystarczy utworzyć nową klasę, która będzie dziedziczyć po System. ApplicationException, a następnie konstruktory do tworzenia egzemplarzy tej nowej klasy. I to wszystko. Zgodnie z konwencją nazwa klasy wyjątków powinna kończyć się słowem Exception. Ponadto — również zgodnie z konwencją — każda klasa wyjątków musi posiadać przynajmniej trzy przeciążone wersje konstruktora. Pierwsza z nich powinna nie przyjmować żadnych parametrów i inicjować wyjątek za pomocą domyślnego komunikatu, który opisuje ogólny typ błędu. Pozostałe dwie wersje powinny przyjmować komunikat o błędzie i komunikat o błędzie z wewnętrznym obiektem wyjątku. Konstruktory te przekazują swoje parametry do konstruktorów klasy bazowej w celu odpowiedniego zainicjowania obiektu. Aby dopełnić wszystkich obowiązków, można także utworzyć konstruktor przyjmujący jako parametry obiekt klasy SerializationInfo i StreamingContext. Ta wersja również może przekazywać swoje parametry do konstruktora klasy bazowej, dzięki czemu nie trzeba robić z nimi nic specjalnego. Ten konstruktor jest przydatny, gdy wyjątek musi być serializowany i deserializowany. Jeśli nie masz pewności, czy będziesz potrzebować tego konstruktora, oznacza to, że najprawdopodobniej poradzisz sobie bez niego. Jeśli jednak zdecydujesz się na jego utworzenie, musisz w pliku z tą klasą wyjątków zaimportować przestrzeń nazw System.Runtime.Serialization, aby dołączyć definicje klas SerializationInfo i StreamingContext.
Rozdział 19.
Obsługa błędów
427
Poniższy kod — definiujący klasę ObjectExpiredException — pochodzi z programu CustomException, który można pobrać z serwera FTP wydawnictwa Helion. Imports System.Runtime.Serialization Public Class ObjectExpiredException Inherits System.ApplicationException ' Bez parametrów. Domyślny komunikat. Public Sub New() MyBase.New("Ważność tego obiektu skończyła się.") End Sub ' Z ustawianiem komunikatu. Public Sub New(ByVal new_message As String) MyBase.New(new_message) End Sub ' Z ustawieniem komunikatu i wewnętrznego wyjątku. Public Sub New(ByVal new_message As String, _ ByVal inner_exception As Exception) MyBase.New(new_message, inner_exception) End Sub ' Dodanie obiektów SerializationInfo i StreamingContext. Public Sub New(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) MyBase.New(info, context) End Sub End Class
Gdy zdefiniujesz niestandardową klasę wyjątków, jej wyjątki będzie można zgłaszać i przechwytywać w taki sam sposób, jak ma to miejsce w przypadku innych klas zdefiniowanych w Visual Basicu. Na przykład poniższa instrukcja zgłasza błąd klasy ObjectExpiredException: Throw New ObjectExpiredException("Ten obiekt klasy Customer utracił już ważność.")
Nadrzędna klasa System.ApplicationException automatycznie obsługuje własności Message, StackTrace i ToString tych obiektów, dzięki czemu nie trzeba implementować ich własnoręcznie.
Klasyczna obsługa błędów w Visual Basicu Strukturalna obsługa błędów z użyciem bloków Try jest względnie nowym wynalazkiem, wprowadzonym we wczesnych wersjach Visual Basica .NET. Visual Basic 6 i poprzednie edycje posługują się składnią bardziej zorientowaną na wiersze kodu, którą czasami nazywa się klasyczną obsługą błędów w Visual Basicu. Techniki tej można używać także w Visual Basicu .NET, chociaż preferowana jest technika strukturalna. W istocie da się stosować obie te techniki nawet w jednym programie, oby nie w tej samej procedurze. Wady i zalety każdej z tych metod opiszę w podrozdziale „Strukturalna a klasyczna obsługa błędów”.
428
Część II
Wstęp do języka Visual Basic
Klasyczna procedura obsługi błędów zaczyna się od instrukcji On Error, która informuje Visual Basic, co ma robić, gdy napotka błąd. Instrukcja ta występuje w czterech formach — On Error GoTo wiersz, On Error Resume Next, On Error GoTo 0 oraz On Error GoTo -1. Wiele z opisywanych w kolejnych podrozdziałach zagadnień zostało zademonstrowanych w programie ClassicErrorHandling, który można pobrać z serwera FTP wydawnictwa Helion.
Instrukcja On Error GoTo Jeśli Visual Basic napotka błąd, po tej instrukcji wejdzie w tryb obsługi błędów, po czym przeskoczy do wyznaczonego wiersza. Zaczynająca się w nim procedura obsługi błędów może wykonać wszelkie niezbędne działania. Poniższy kod wykonuje instrukcję On Error GoTo LoadPayrollError i wywołuje podprocedurę LoadPayrollFile. Jeśli procedura ta spowoduje błąd, Visual Basic przeskoczy do wiersza z etykietą LoadPayrollError. Znajdujący się tam kod obsługi błędów wyświetli komunikat i zakończy działanie tej podprocedury. Następnie program wykona instrukcję On Error GoTo PrintPaychecksError i wywoła procedurę PrintPaychecks. Jeśli ta ostatnia spowoduje błąd, zostanie wykonany kod zaczynający się w wierszu z etykietą PrintPaychecksError. Po zakończeniu działania tego kodu procedura zakończy działanie za pomocą instrukcji Exit Sub, co pozwoli jej uniknąć znajdującego się dalej kodu obsługi błędów. Private Sub ProcessPayroll() ' Załadowanie pliku listy płac. On Error GoTo LoadPayrollError LoadPayrollFile() On Error GoTo PrintPaychecksError ' Wydruk czeków. PrintPaychecks() ' Koniec. Exit Sub LoadPayrollError: MessageBox.Show("Błąd ładowania pliku listy płac.") Exit Sub PrintPaychecksError: MessageBox.Show("Błąd drukowania czeków.") Exit Sub End Sub
Program może wyjść z trybu obsługi błędów za pomocą instrukcji Exit Sub, Exit Function, Exit Property, Resume lub Resume Next. Instrukcje Exit Sub, Exit Function i Exit Property zmuszają program do natychmiastowego wyjścia z procedury, w której wystąpił błąd. Oznacza to koniec trybu obsługi błędów dla tej usterki.
Rozdział 19.
Obsługa błędów
429
Instrukcja Resume wymusza wznowienie wykonywania instrukcji, która spowodowała błąd. Jeśli usterka nie została naprawiona, wystąpi jeszcze raz, przez co program może wpaść w nieskończoną pętlę. Instrukcji tej należy używać wyłącznie wtedy, gdy istnieje możliwość naprawienia błędu. Jeżeli na przykład program musi odczytać dane z dyskietki, której nie ma w napędzie, może poprosić użytkownika o włożenie dyskietki i spróbować jeszcze raz odczytać z niej dane. Instrukcja Resume Next wymusza wznowienie wykonywania programu od instrukcji znajdującej się za tą, która spowodowała błąd. Należy je używać, gdy nie da się naprawić błędu, ale aplikacja powinna mimo to kontynuować działanie. Wyobraźmy sobie na przykład, że nie udaje się odczytać danych z pliku. Program może mimo to kontynuować, dzięki czemu w następnej instrukcji przynajmniej zamknie ten plik.
Instrukcja On Error Resume Next Jeśli Visual Basic napotka błąd, po tej instrukcji opuści tę, która go spowodowała, a następnie wznowi wykonywanie od kolejnej. Jeśli nie ma znaczenia, czy instrukcja ta została zakończona, On Error Resume Next pozwala aplikacji kontynuować działanie bez sprawdzania błędów. Jeśli konieczne jest podjęcie odpowiednich działań, gdy wystąpią błędy, można użyć obiektu klasy Err do szukania błędów w każdej instrukcji. Na przykład poniższy kod używa instrukcji On Error Resume Next i wywołuje podprocedurę DoSomething. Kiedy ta ostatnia zakończy działanie, program sprawdza własność Number obiektu klasy Err, aby dowiedzieć się, czy wystąpił jakiś błąd. Jeśli tak, aplikacja wyświetla odpowiedni komunikat i wychodzi z tej podprocedury. Jeśli podprocedura DoSomething nie spowoduje błędu, program wywoła podprocedurę DoSomethingElse i wykona dla niej podobne testy. On Error Resume Next DoSomething() If Err.Number < > 0 Then MessageBox.Show("Błąd w podprocedurze DoSomething.") Exit Sub End If DoSomethingElse() If Err.Number < > 0 Then MessageBox.Show("Błąd w podprocedurze DoSomethingElse.") Exit Sub End If ...
Instrukcji tej można też użyć do szukania różnych rodzajów błędów i podejmowania na tej podstawie odpowiednich kroków. Poniższy program nie wykonuje żadnych ciekawych działań, jeśli nie wystąpi ani jeden błąd. Jeżeli wartość Err.Number wynosi 11, oznacza to, że aplikacja próbowała dzielić przez zero. Wtedy zmienna X zostaje ustawiona na wartość domyślną. Jeśli wystąpi jakiś inny błąd, program poinformuje o tym użytkownika i wyjdzie z tej podprocedury. ' Próba wyznaczenia wartości zmiennej X. On Error Resume Next X = CalculateValue()
430
Część II
Wstęp do języka Visual Basic
Select Case Err.Number Case 0 ' Nie ma błędów. Nic nie robimy. Case 11 ' Dzielenie przez zero. Ustawienie wartości domyślnej. X = 1000 Case Else ' Niespodziewany błąd. Poinformowanie o tym użytkownika. MessageBox.Show("Bąd przy wyznaczaniu wartości zmiennej X." Err.Description) Exit Sub End Select ...
&
vbCrLf
&
Instrukcja On Error GoTo 0 Instrukcja On Error GoTo 0 wyłącza aktywną procedurę obsługi błędów. Tę ostatnią należy wyłączyć, gdy nie ma już związku z tym, co robi program. Poniższy kod instaluje procedurę obsługi błędów podczas ładowania danych. Po zakończeniu ładowania danych aplikacja ta dezaktywuje ją za pomocą instrukcji On Error GoTo 0, po czym przechodzi do dalszych działań. On Error GoTo LoadDataError ' Ładowanie danych. ... ' Dane zostały załadowane. On Error GoTo 0 ... Exit Sub LoadDataError: MessageBox.Show("Błąd ładowania danych." Exit Sub End Sub
& vbCrLf
&
Err.Description)
Dezaktywacja procedury obsługi błędów powoduje, że program przestaje traktować nieprawidłowe działania jako błędy. W poprzednim przykładzie użytkownik mógłby się zdziwić, że wystąpił problem podczas ładowania danych, gdy aplikacja robiła coś innego. W innych przypadkach pozostawienie aktywnej procedury obsługi błędów może spowodować, że program będzie próbował niesłusznie naprawiać jakieś problemy, których nie ma. Na przykład poprosi użytkownika o włożenie dyskietki po odczytaniu z niej danych. Dezaktywacja starych procedur obsługi błędów prowadzi także do awarii programu, gdy wystąpią jakieś niespodziewane problemy. W ten sposób programiści mogą odkryć i obsłużyć nowe typy błędów, najczęściej poprzez dodanie nowych procedur obsługi błędów.
Instrukcja On Error GoTo -1 Instrukcja On Error GoTo -1 jest bardzo podobna do instrukcji On Error GoTo 0. Także dezaktywuje ona aktywne procedury obsługi błędów, ale również wyłącza tryb obsługi błędów, jeśli jest włączony. Poniższy kod, demonstrujący różnicę pomiędzy tymi instrukcjami, pochodzi z programu OnErrorGoToMinus1, który można pobrać z serwera FTP wydawnictwa Helion.
Rozdział 19.
Obsługa błędów
431
Dim i As Integer Dim j As Integer = 0 On Error GoTo DivideError1 i = 1 \ j DivideError1: On Error GoTo -1 On Error Resume Next i = 1 \ j Exit Sub On Error GoTo DivideError2 i = 1 \ j Exit Sub DivideError2: On Error GoTo 0 On Error Resume Next i = 1 \ j Exit Sub
' Ta instrukcja powoduje błąd. ' Wejście w tryb obsługi błędów. ' Zakończenie trybu obsługi błędów. ' Ignorowanie błędów w przyszłości. ' Ten błąd zostanie zignorowany.
' Ta instrukcja powoduje błąd.
' Wejście w tryb obsługi błędów. ' Ta instrukcja NIE wyłącza trybu obsługi błędów. ' Nie działa w trybie obsługi błędów. ' Ten błąd nie zostaje przechwycony i program ulega awarii.
Program ten instaluje procedurę obsługi błędów za pomocą instrukcji On Error GoTo DivideError1, a następnie wykonuje polecenie powodujące błąd dzielenia przez zero. Wyjście z trybu obsługi błędów w celu kontynuowania działania programu jest realizowane za pomocą instrukcji On Error GoTo -1. Dalej znajduje się instrukcja On Error Resume Next, pozwalająca zignorować kolejne błędy oraz jeszcze jedno dzielenie przez zero. Ponieważ już działa, program ignoruje ten błąd. Następnie program instaluje drugą procedurę obsługi błędów za pomocą instrukcji On Error GoTo DivideError2. Jeszcze raz wykonuje dzielenie przez zero, aby wejść do procedury obsługi błędów i włączyć tryb obsługi błędów. Tym razem w procedurze tej została użyta instrukcja On Error GoTo 0. Odinstalowuje ona bieżącą procedurę obsługi błędów (On Error GoTo DivideError2), ale nie wyłącza trybu obsługi błędów. Dalej znajduje się instrukcja On Error Resume Next. Niestety zostaje ona zignorowana, ponieważ program działa w trybie obsługi błędów. Gdyby aplikacja wyszła z trybu obsługi błędów za pomocą instrukcji Resume, instrukcja ta zadziałałaby. Nie działa ona jednak, jeśli program jest w trybie obsługi błędów. Tym razem nie ma żadnej aktywnej procedury obsługi błędów, przez co błąd dzielenia nie zostaje obsłużony, a aplikacja ulega awarii. Aby uniknąć komplikacji, nie należy używać tej techniki obsługi błędów z kodem obsługującym błędy, który działa w całym ciele procedury. W zamian należy umieścić ten kod obsługi błędów na końcu procedury i wrócić do jej kodu głównego za pomocą instrukcji Exit Sub, Exit Function, Exit Property, Resume lub Resume Next. Instrukcja On Error GoTo -1 zazwyczaj powoduje większe zamieszanie, niż jest tego warta.
432
Część II
Wstęp do języka Visual Basic
Tryb obsługi błędów Bez wątpienia najtrudniejszą do zrozumienia częścią klasycznej techniki obsługi błędów jest tryb obsługi błędów. Instrukcja On Error GoTo wiersz przestawia program na tak zwany tryb obsługi błędów, który zostaje wyłączony dopiero przez instrukcję Exit Sub, Exit Function, Exit Property, Resume, Resume Next lub On Error GoTo -1. W trybie obsługi błędów większość instrukcji obsługujących błędy nie działa normalnie. Z reguły dają one jakieś efekty dopiero wtedy, gdy zostanie on wyłączony. Instrukcja On Error Resume Next, która znajduje się na listingu w poprzednim podrozdziale, nie działa, ponieważ zostaje wykonana, gdy program jest w trybie obsługi błędów. Próba wykonania kodu obsługującego błędy w trybie obsługi błędów jest jednym z najczęściej popełnianych błędów przez programistów. Kod obsługujący błędy musi być bezpieczny, w przeciwnym razie program ulegnie awarii (albo przynajmniej błąd zostanie przesłany w górę stosu wywołań do procedury wywołującej). Jeśli musisz wykonać działania, które mogą spowodować błąd w procedurze obsługi błędów, przenieś odpowiadający im kod do jakiejś podprocedury. Procedura taka może mieć własny kod obsługujący błędy. Technikę tę demonstruje kod znajdujący się poniżej. Podprocedura SetDefaultValue posiada własną instrukcję On Error Resume Next, która pozwala jej uniknąć awarii, jeśli wystąpią jakieś problemy. Private i, j As Integer Private Sub PerformCalculation() On Error GoTo EquationError i = 1 \ j Exit Sub EquationError: SetDefaultValue() Resume Next End Sub Private Sub SetDefaultValue() On Error Resume Next i = 2 \ j End Sub
Strukturalna a klasyczna obsługa błędów Nowsza strukturalna technika obsługi błędów, która polega na użyciu instrukcji Try, ma kilka zalet w stosunku do metody klasycznej. Po pierwsze, w technice klasycznej nie jest od razu oczywiste, czy dany fragment kodu jest chroniony przez procedurę obsługi błędów. Aby sprawdzić, czy tak jest, trzeba cofnąć się w kodzie i znaleźć instrukcję On Error. Napotykając wiersz z etykietą i chcąc sprawdzić, jaka procedura obsługi błędów może być zainstalowana w tym czasie, należy znaleźć wszystkie miejsca, w których znajdują się instrukcje GoTo lub Resume, przeskakujące do niego.
Rozdział 19.
Obsługa błędów
433
W klasycznej obsłudze błędów nie jest też oczywiste, czy program działa w trybie obsługi błędów. Czasami nie da się tego sprawdzić bez uruchomienia aplikacji. Poniższy kod chroni się za pomocą instrukcji On Error GoTo, a następnie inicjuje zmienną typu Integer wartością wprowadzoną przez użytkownika. Jeśli wpisana zostanie poprawna wartość, kod będzie działać w normalnym (nieobsługującym błędów) trybie. Jeżeli użytkownik wprowadzi wartość, która nie będzie poprawną wartością typu Integer, program przeskoczy do wiersza z etykietą BadFormat i przejdzie w tryb obsługi błędów. Przed uruchomieniem aplikacji nie da się stwierdzić, czy będzie ona działała w trybie obsługi błędów, kiedy dojdzie do poniższego komentarza. Dim i As Integer On Error GoTo BadFormat i = CInt(txtNumber.Text) BadFormat: ' Czy został włączony tryb obsługi błędów? ...
W końcu klasycznego kodu obsługi błędów nie można zagnieżdżać. Aby wykonać ryzykowne działanie w procedurze obsługi błędów, trzeba umieścić jego kod w osobnej podprocedurze z jej własnym kodem obsługi błędów. Strukturalna obsługa błędów jest pozbawiona tych ograniczeń. Gdy spojrzy się na blok Try lub Catch, od razu będzie wiadomo, czy dany wiersz kodu jest chroniony (znajduje się w bloku Try), czy stanowi część procedury obsługi błędów (znajduje się w bloku Catch). Instrukcje Try można nawet zagnieżdżać, co demonstruje poniższy kod. Program ten inicjuje zmienną typu Integer za pomocą wartości wprowadzonej przez użytkownika. Jeśli wpisana zostanie niepoprawna wartość, program przejdzie do pierwszego bloku Catch. Tam spróbuje ustawić tę zmienną za pomocą odpowiednich obliczeń. Jeśli działania te nie powiodą się (na przykład j=0), kolejny blok Catch ustawi ją na wartość domyślną. ' Pobranie wartości od użytkownika. Try i = Integer.Parse(txtNumber.Text) Catch ex As Exception ' Wartość podana przez użytkownika jest zła. ' Obliczenie innej wartości. Try i = 1 \ j Catch ex2 As Exception ' Obliczona wartość jest zła. ' Użycie domyślnej wartości. i = 3 End Try End Try
W końcu strukturalna obsługa błędów jest wolna od tego oszałamiającego trybu obsługi błędów. Sama możliwość zaplątania się z tym trybem sprawia, że warto sięgnąć po technikę strukturalną. Jedną z zalet klasycznej metody obsługi błędów jest to, że przy jej użyciu łatwiej można ignorować błędy dzięki dostępności instrukcji On Error Resume Next. W poniższym fragmencie programu użyto klasycznej techniki obsługi błędów do wykonania trzech podprocedur i zignorowania wszelkich wygenerowanych przez nie błędów.
434
Część II
Wstęp do języka Visual Basic
On Error Resume Next DoSomething() DoSomethingElse() DoSomethingMore() ...
Poniżej znajduje się ten sam kod, tyle że zapisany w technice strukturalnej. Ta wersja jest znacznie bardziej rozwlekła i mniej czytelna. Try DoSomething() Catch End Try Try DoSomethingElse() Catch End Try Try DoSomethingMore() Catch End Try ...
Obiekt Err Kiedy występuje błąd, Visual Basic inicjuje obiekt o nazwie Err. Dzięki własnościom tego obiektu można uzyskać dodatkowe informacje o błędzie. Odpowiadają one własnościom obiektów wyjątków, które są używane w sekcjach Catch instrukcji Try. Poniższa tabela zawiera zestawienie tych własności. Własność
Opis
Description
Komunikat opisujący błąd.
Erl
Numer wiersza, w którym wystąpił błąd.
HelpContext
Identyfikator kontekstu pomocy dla błędu.
HelpFile
Pełna ścieżka do pliku pomocy, który opisuje błąd.
LastDLLError
Kod błędu systemowego, wygenerowany przez wywołanie biblioteki DLL (jeśli dotyczy).
Number
Numer błędu. Wartość 0 oznacza, że nie wystąpił żaden błąd.
Source
Nazwa obiektu lub aplikacji, która spowodowała błąd.
Obiekt Err udostępnia także trzy przydatne metody do pracy z błędami — Clear, Raise oraz GetException. Pierwsza z nich czyści informacje o obiekcie i resetuje je przed wykonaniem kolejnej instrukcji. Jeśli instrukcja znajdująca się za błędem nie zgłasza kolejnego błędu, niewyczyszczony obiekt Err może pokazywać stare informacje o błędzie, jak na kolejnej stronie:
Rozdział 19.
Obsługa błędów
435
On Error Resume Next X = Single.Parse(txtX.Text) If Err.Number < > 0 Then MessageBox.Show(Err.Description) ' Wyświetlenie błędu. Err.Clear ' Wyczyszczenie błędu. End If Y = Single.Parse(txtY.Text) If Err.Number < > 0 Then MessageBox.Show(Err.Description) ' Wyświetlenie błędu. Err.Clear ' Wyczyszczenie błędu. End If ...
Metoda Raise obiektu Err generuje błąd. Na przykład poniższa instrukcja zgłasza błąd numer pięć — "Procedure call Or argument is invalid": Err.Raise(5)
W końcu metoda GetException zwraca obiekt klasy Exception, który reprezentuje błąd obiektu Err. Obiektu tego używa się tak samo jak każdego innego obiektu wyjątku. W szczególności można za pomocą jego własności StackTrace sprawdzić, gdzie wystąpił błąd. W klasycznej metodzie obsługi błędów informacje o błędzie można uzyskać za pomocą obiektu Err. W metodzie strukturalnej z instrukcją Try da się używać obiektów klasy Exception, które są dostarczane przez instrukcje Catch, dzięki czemu można obejść się bez obiektu Err.
Debugowanie W Visual Basicu dostępny jest bogaty zestaw narzędzi do debugowania aplikacji. W środowisku tym można zatrzymywać program w różnych miejscach, aby sprawdzić lub zmienić wartości zmiennych, zbadać stos wywołań oraz wywołać jakieś procedury, które będą testować różne części aplikacji. Można wykonywać program po jednej instrukcji i patrzeć, co się po kolei dzieje, a nawet zmodyfikować kod źródłowy programu i puścić dalej jego wykonywanie. Narzędzia dostępne w środowisku programistycznym Visual Basica do debugowania aplikacji zostały opisane w rozdziale 7. — „Usuwanie błędów”. Należą do nich te pozwalające wykonać kod programu krok po kroku, punkty wstrzymania oraz okna typu Immediate, Locals czy Call Stack. Szczegółowe informacje na ten temat znajdują się w rozdziale 7. Poza punktami wstrzymania do zatrzymywania programu w określonym wierszu można jeszcze używać instrukcji Stop. Bywa ona szczególnie przydatna przy wykrywaniu nieoczekiwanych wartości w czasie testowania. Na przykład poniższa instrukcja zatrzymuje aplikację, gdy zmienna m_NumEmployees ma wartość mniejszą od 1 lub większą od 100: If (m_NumEmployees
<
1) Or (m_NumEmployees
> 100) Then Stop
436
Część II
Wstęp do języka Visual Basic
Podsumowanie W praktyce pamiętanie o możliwości wystąpienia błędów wszystkich typów w dużej aplikacji jest niezwykle trudne. Należy zawsze starać się przewidzieć jak najwięcej problemów, ale trzeba także być przygotowanym na to, że mogą wystąpić nieoczekiwane błędy. Kod sprawdzający błędy powinien jasno informować o ich wystąpieniu oraz w miarę możliwości naprawiać je. Przewidzenie wszystkich możliwych błędów może być niemożliwe, ale przy odrobinie wysiłku można sprawić, by program wykrywał i raportował wartości, których niepoprawność jest oczywista. Należy także zająć się niespodziewanymi sytuacjami (typu wpisanie przez użytkownika numeru telefonicznego w miejscu przeznaczonym na pesel) i sprawić, by program zachowywał się przy ich występowaniu elegancko. Aplikacja nie ma wpływu na całe swoje otoczenie (działania użytkownika, stan drukarki oraz łączność sieciową), ale powinna być w gotowości do działania, gdy nie wszystko idzie zgodnie z planem. Kiedy już zdarzy się błąd, można go zlokalizować i naprawić za pomocą takich narzędzi, jak punkty wstrzymania, czujki i okna Locals, Auto, Immediate oraz Call Stack. Możliwe, że w programie składającym się z setek tysięcy wierszy kodu nigdy nie uda się znaleźć wszystkich możliwych błędów, ale można zredukować ich liczbę do takiego poziomu, że będzie się dało dość bezpiecznie w nim pracować. Rozdziały od 8. do 13. koncentrują się na kontrolkach, formularzach oraz innych obiektach interfejsu użytkownika. W rozdziałach od 14. do 18. opiszę kod działający w tle interfejsu użytkownika. Rozdział 20. — „Kontrolki i obiekty baz danych” — zawiera omówienie tematyki związanej z bazami danych, która mieści się zarówno w kategorii interfejsu użytkownika, jak i w innych. Opiszę w nim kontrolki baz danych, których można używać do budowy interfejsu użytkownika, a także komponenty i inne obiekty, za pomocą których da się tymi bazami sterować.
20
Kontrolki i obiekty baz danych Opisane w rozdziale 8. — „Wybieranie kontrolek Windows Forms” — kontrolki pozwalają użytkownikowi komunikować się z programem. Umożliwiają one aplikacji wyświetlanie danych użytkownikowi, a temu — kontrolowanie programu. Kontrolki baz danych pełnią mniej więcej tę samą rolę pomiędzy aplikacją a bazą danych. Przenoszą dane z bazy danych do programu oraz umożliwiają wysyłanie danych z powrotem z aplikacji do bazy danych. Programowanie baz danych jest bardzo obszernym tematem, który został opisany w wielu różnych książkach. Rozległość tego zagadnienia sprawia, że żadna ogólna publikacja o Visual Basicu nie jest w stanie omówić go szczegółowo. Programowanie baz danych jest jednak także bardzo ważnym tematem. Każdy programista Visual Basica powinien znać przynajmniej podstawy ich używania w aplikacjach. W tym rozdziale opiszę techniki tworzenia źródeł danych oraz używania zadań typu „przeciągnij i upuść” do tworzenia prostych tabelarycznych i rekordowych prezentacji danych. Dodatkowo omówię najważniejsze w Visual Basicu kontrolki i obiekty, które służą do pracy z bazami danych. Chociaż jest to zdecydowanie za mało, informacje te pozwolą Ci zacząć budować własne podstawowe aplikacje bazodanowe. Pamiętaj, że przykładowe programy do tego rozdziału używają lokalizacji baz danych z mojego komputera. Jeśli pobierzesz je z mojej strony (www.vb-helper.com/vb_prog_ref.htm), będziesz prawdopodobnie musiał wiele z nich zmodyfikować, aby dostosować je do warunków na swoim komputerze.
Automatyczne łączenie z danymi Visual Studio udostępnia takie narzędzia, dzięki którym wprowadzanie danych do baz jest niezwykle łatwe. Chociaż proces ten jest względnie prosty, składa się z wielu etapów. Ponadto każdy z nich może mieć kilka wariantów, przez co opis wszystkich możliwych sposobów budowy połączenia z bazą danych zajmuje dużo czasu.
438
Część II
Wstęp do języka Visual Basic
Dla uproszczenia etapy te zostały podzielone na dwa moduły — łączenie ze źródłami danych oraz dodawanie kontrolek do formularza.
Łączenie ze źródłami danych Aby utworzyć program bazodanowy, utwórz nową aplikację, po czym w menu Data kliknij polecenie Add New Data Source w celu uruchomienia kreatora Data Source Configuration Wizard z rysunku 20.1.
Rysunek 20.1. Kreator Data Source Configuration Wizard pozwala na dodawanie i konfigurowanie źródeł danych
Źródłami danych w aplikacjach Visual Basica mogą być bazy danych, usługi sieciowe oraz obiekty. Najbardziej oczywistym wyborem jest baza danych. Wybierz rodzaj źródła danych i kliknij przycisk Next, aby przejść do wybierania połączenia z danymi na stronie z rysunku 20.2. Jeśli posiadasz utworzone wcześniej połączenia z danymi, możesz wybrać jedno z nich z listy rozwijanej. Jeśli nie masz jeszcze żadnych połączeń, kliknij przycisk New Connection, aby otworzyć okno dialogowe Choose Data Source z rysunku 20.3. Można w nim wybrać odpowiednie źródła danych, na przykład bazy danych Microsoft Access, źródła danych ODBC, Microsoft SQL Server oraz bazy danych Oracle. W tym przypadku użyta została baza danych Microsoft Access.
Rozdział 20.
Kontrolki i obiekty baz danych
439
Rysunek 20.2. Wybierz istniejące połączenie z bazą danych lub kliknij przycisk New Connection, aby utworzyć nowe Rysunek 20.3. Wybór typu źródła danych dla nowego połączenia
Jeśli okno dialogowe z rysunku 20.4 pojawia się jako pierwsze, można pominąć okno z rysunku 20.3, o ile został w nim wybrany odpowiedni typ źródła danych. Aby zmienić źródło danych, kliknij przycisk Change — zostanie otwarte okno widoczne na rysunku 20.3. Po wybraniu typu źródła danych na liście rozwijanej pojawią się dostępni dla niego dostawcy. Wybierz jednego z nich i kliknij przycisk OK, aby wyświetlić okno dialogowe z rysunku 20.4.
440
Część II
Wstęp do języka Visual Basic
Rysunek 20.4. Okno dialogowe Add Connection pozwala utworzyć nowe połączenie z danymi
W zależności od tego, jaki typ bazy danych został wybrany w oknie na rysunku 20.3, może ono wyglądać nieco inaczej niż na rysunku 20.4. Jeśli na przykład zdecydowałbyś się na bazę danych SQL Server, w oknie dialogowym Add Connection należałoby podać nazwę źródła danych i serwera, a nie nazwę pliku z bazą danych. Wpisz wszystkie wymagane dane w odpowiednich polach. W przypadku bazy danych SQL Server podaj nazwę serwera, sposób uwierzytelniania, nazwę bazy danych i inne informacje. Jeśli korzystasz z bazy danych Microsoft Access, podaj nazwę pliku lub kliknij przycisk Browse, widoczny na rysunku 20.4, aby znaleźć go na dysku. W razie potrzeby wprowadź nazwę użytkownika i hasło, po czym kliknij przycisk OK. Po powrocie do okna Data Source Configuration Wizard z rysunku 20.2 na liście rozwijanej powinno być zaznaczone to nowe połączenie. Kliknięciem znaku plusa znajdującego się obok etykiety Connection string można wyświetlić informacje o połączeniu, przy użyciu których kreator będzie łączył źródło danych z bazą danych. Mogą one na przykład wyglądać następująco: Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\ClassRecords.mdb
Gdy klikniesz przycisk Next, kreator poinformuje Cię, że wybrany plik bazy danych nie należy do projektu, po czym zapyta, czy chcesz go dodać. Jeśli klikniesz przycisk Yes, kreator doda tę bazę danych do projektu, dzięki czemu pojawi się ona w oknie Solution Explorer. Jeśli planujesz rozprowadzać tę bazę danych razem z aplikacją, dołączenie jej do projektu może ułatwić wspólne zarządzanie tą bazą i kodem źródłowym Visual Basica. Następnie kreator zapyta, czy użytkownik chce zapisać łańcuch połączenia w pliku konfiguracyjnym projektu. Jeśli pozostawisz to pole zaznaczone, kreator wstawi ten łańcuch do pliku o nazwie app.config.
Rozdział 20.
Kontrolki i obiekty baz danych
441
Poniżej znajduje się część pliku konfiguracyjnego, która zawiera łańcuch połączenia:
Później program pobiera tę wartość i łączy się z bazą danych za pomocą wartości Settings.Default.ClassRecordsConnectionString. Można łatwo sprawić, że aplikacja będzie się łączyła z innym źródłem danych poprzez zmodyfikowanie tego ustawienia konfiguracyjnego i ponowne uruchomienie programu. Nigdy nie zapisuj haseł baz danych w pliku konfiguracyjnym. Jest on bowiem przechowywany jako zwykły tekst, a więc każdy może go odczytać. Jeśli musisz używać hasła, utwórz łańcuch połączenia z symbolem zastępczym dla niego. Następnie w czasie działania programu, gdy ten łańcuch będzie już załadowany, użytkownik wprowadzi prawdziwe hasło, które zostanie wstawione w miejsce tego symbolu zastępczego.
Wstawianie kontrolek danych na formularz W tym momencie mamy już zdefiniowane podstawowe połączenie z bazą danych. Visual Studio wie już, gdzie ta baza się znajduje. Posiada też wszystkie informacje potrzebne do utworzenia łańcucha połączenia z nią. Teraz trzeba podjąć decyzję, jakie dane umieścić w tej bazie danych, a także jak zaprezentować je na formularzu. Kliknij przycisk Next, aby wyświetlić okno dialogowe z rysunku 20.5. Strona ta przedstawia obiekty dostępne w bazie danych. Ta baza danych zawiera dwie tabele — Students i TestScores. Kliknij znaki plusa, które znajdują się obok tych obiektów. Spowoduje to rozwinięcie ich — będzie można sprawdzić, co zawierają. Na rysunku 20.5 tabele zostały rozwinięte, dzięki czemu widać ich pola. Zaznacz obiekty bazy danych, które chcesz umieścić w źródle danych. Na rysunku 20.5 zaznaczone zostały obie tabele. Gdy klikniesz przycisk Finish, kreator doda do aplikacji kilka obiektów. W oknie Solution Explorer pojawi się nowy plik o nazwie ClassRecordDataSet.xsd. Jest to plik definicji schematu, który opisuje to źródło danych. Jeśli dwukrotnie klikniesz ten plik, jego zawartość zostanie wyświetlona w edytorze z rysunku 20.6. Widać tu tabele zdefiniowane przez ten schemat oraz ich pola. Linia łącząca pliki, która na lewym końcu ma klucz, a na prawym symbol nieskończoności, oznacza, że tabele te pozostają w relacji typu „jeden do wielu”. Tutaj pola Students.StudentId i TestScores.StudentId tworzą relację klucza obcego. Oznacza to, że każda wartość StudentId w tabeli TestScores musi odpowiadać jakiejś wartości StudentId w tabeli Students. Jeśli kliknie się dwukrotnie lewym przyciskiem myszy lub jednokrotnie prawym i wybierze pozycję Edit Relation, wyświetli się okno dialogowe z rysunku 20.7. W edytorze tym można modyfikować relacje.
442
Część II
Wstęp do języka Visual Basic
Rysunek 20.5. Okno, w którym można wybrać obiekty bazy danych do uwzględnienia w źródle danych
Rysunek 20.6. Edytor schematów przedstawia tabele zdefiniowane przez schemat oraz ich powiązania
Rozdział 20.
Kontrolki i obiekty baz danych
443
Rysunek 20.7. Okno dialogowe do edytowania relacji między tabelami źródła danych
Na samym dole tabel na rysunku 20.6 widać dwa obiekty adaptacyjne tabel z etykietami Fill, GetData(). Reprezentują one obiekty adaptacyjne danych, przy użyciu których program będzie później przenosił dane z i do źródła danych. Poza dodaniem do okna Solution Explorer pliku schematu kreator Data Source Configuration Wizard dodał także nowy obiekt DataSet do okna Data Sources z rysunku 20.8 (jeśli nie widać tego okna, kliknij w menu Data polecenie Show Data Sources). Za pomocą znaków plusa i minusa można rozwijać i zwijać obiekty znajdujące się w obiekcie DataSet. Na rysunku 20.8 rozwinięty jest obiekt DataSet i jego tabele. Zauważ, że tabela TestScores znajduje się pod tabelą Students, ponieważ pozostaje z nią w relacji rodzic-dziecko. Opis całego procesu wymaga dużej ilości tekstu i wielu rysunków, ale w rzeczywistości budowa źródła danych przebiega szybko. Po zdefiniowaniu źródła danych można utworzyć prosty interfejs użytkownika, przy czym nie wymaga to wiele pracy. Wystarczy tylko przeciągnąć obiekty z okna Data Sources na formularz. Gdy przeciągniesz tabelę z okna Data Sources na formularz, Visual Basic automatycznie utworzy kontrolki BindingNavigator i DataGridView oraz inne komponenty do wyświetlania danych z tabeli. Na rysunku 20.9 widać wynik tego wstawiania po uruchomieniu programu.
444
Część II
Wstęp do języka Visual Basic
Rysunek 20.8. W oknie Data Sources pojawiło się nowe źródło danych
Rysunek 20.9. Przeciągnij tabelę z okna Data Sources na formularz, aby utworzyć prostą kontrolkę DataGridView
Zamiast przeciągać na formularz całą tabelę, można przesunąć tylko wybrane kolumny bazy danych. Wtedy Visual Basic doda do formularza kontrolki reprezentujące te kolumny. Na rysunku 20.10 przedstawiono kolumny z tabeli Students, które zostały przeciągnięte na formularz. Rysunek 20.10. Za pomocą przeciągania i upuszczania kolumn tabeli można utworzyć widok rekordowy zamiast siatkowego
Gdy w oknie Data Sources zaznaczysz tabelę, po prawej stronie pojawi się strzałka, która oznacza listę rozwijaną. Będzie można tam wybrać inny styl wyświetlania zawartości tabeli, jak widać na rysunku 20.11. Jeśli na przykład zostanie wybrany styl Details, Visual Basic wyświetli dane przeciągniętej tabeli w widoku szczegółów rekordów, podobnym do tego z rysunku 20.10, zamiast siatki z rysunku 20.9.
Rozdział 20.
Kontrolki i obiekty baz danych
445
Rysunek 20.11. Z listy rozwijanej w oknie Data Sources można wybrać styl wyświetlania zawartości tabeli
W podobny sposób można zmieniać style wyświetlania zawartości różnych kolumn bazy danych. Należy zaznaczyć wybraną kolumnę w oknie Data Sources, po czym z jej listy rozwijanej wybrać wyświetlanie w polu tekstowym, na etykiecie, etykiecie z łączem, liście rozwijanej lub w innej kontrolce. Od tej pory kiedy na formularz zostanie przeciągnięta kolumna lub tabela z widokiem rekordowym, Visual Basic będzie wyświetlał jej wartości w tego rodzaju kontrolkach.
Obiekty tworzone automatycznie Kiedy z okna Data Sources są przeciągane na formularz tabele i kolumny, Visual Basic nie tylko umieszcza na tym formularzu kontrolki DataGridView. Dodatkowo tworzy około dwadzieścia innych kontrolek i komponentów. Pięć z tych obiektów, które mają większe znaczenie, to DataSet, TableAdapter, BindingSource, BindingNavigator oraz TableAdapterManager. Program przechowuje dane w obiekcie klasy DataSet. Pojedynczy obiekt tego typu może reprezentować całą bazę danych. Zawiera on obiekty klasy DataTable, które reprezentują tabele bazy danych. Każdy obiekt tej klasy zawiera obiekty klasy DataRow, reprezentujące wiersze tabeli. Każdy obiekt klasy DataRow zawiera natomiast elementy reprezentujące wartości kolumn w każdym wierszu. Obiekt klasy TableAdapter kopiuje dane pomiędzy bazą danych a obiektem klasy DataSet. Udostępnia on metody do wykonywania działań na bazie danych (takich jak wybieranie, wstawianie, aktualizowanie i usuwanie rekordów). W głębi tego obiektu ukryty jest obiekt połączenia zawierający informacje o bazie danych, które umożliwiają obiektowi klasy TableAdapter odnalezienie jej. Obiekt klasy TableAdapterManager zajmuje się koordynacją aktualizacji pomiędzy różnymi obiektami klasy TableAdapter. Najbardziej przydaje się to w przypadku hierarchicznych zbiorów danych, ale ten temat wykracza poza zakres niniejszej książki. Kod wygenerowany przez kreatora również wykorzystuje obiekt klasy DataAdapterManager do aktualizacji utworzonego przez siebie zbioru danych.
446
Część II
Wstęp do języka Visual Basic
Obiekt klasy BindingSource zawiera wszystkie dane obiektu klasy DataSet. Udostępnia też programistyczne funkcje kontrolne. Pozwalają one na przechodzenie przez dane, dodawanie i usuwanie elementów itd. Obiekt klasy BindingNavigator dostarcza interfejs użytkownika, który pozwala na kontrolowanie obiektu klasy BindingSource. Na rysunku 20.12 przedstawiono związki między obiektami klas DataSet, TableAdapter, BindingSource oraz BindingNavigator. Z tych czterech tylko obiekt klasy BindingNavigator jest widoczny na formularzu. Rysunek 20.12. Do wyświetlania danych Visual Basic wykorzystuje obiekty DataSet, TableAdapter, BindingSource oraz BindingNavigator
Nawet wszystkie te obiekty razem wzięte nie wystarczą, by zmusić program do wyświetlenia danych. Tworząc je, Visual Basic dodaje jeszcze do formularza poniższy kod: Public Class Form1 Private Sub StudentsBindingNavigatorSaveItem_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles StudentsBindingNavigatorSaveItem.Click Me.Validate() Me.StudentsBindingSource.EndEdit() Me.TableAdapterManager.UpdateAll(Me.ClassRecordsDataSet) End Sub Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load 'TODO: This line of code loads data into the ' 'ClassRecordsDataSet.Students’ table. ' You can move, or remove it, as needed. Me.StudentsTableAdapter.Fill(Me.ClassRecordsDataSet.Students) End Sub End Class
Procedura obsługi zdarzenia StudentsBindingNavigatorSaveItem_Click zostaje uruchomiona, gdy użytkownik kliknie narzędzie Save obiektu BindingNavigator. Procedura ta powoduje, że obiekt klasy TableAdapter zapisuje wszystkie zmiany dokonane w tabeli Students w bazie danych. Procedura obsługi zdarzenia Form1_Load zmusza obiekt klasy TableAdapter do skopiowania danych z bazy danych do obiektu DataSet w odpowiedzi na załadowanie formularza. Visual Basic robi to wszystko automatycznie. Jeśli w tym momencie program zostałby uruchomiony, wyświetlałby on dane i można by było nimi manipulować. Nie jest on jednak doskonały. Nie sprawdza w żaden sposób danych i pozwala zamknąć aplikację bez zapisania dokonanych zmian. Jest to jednak bardzo dobry początek pracy.
Rozdział 20.
Kontrolki i obiekty baz danych
447
Inne obiekty danych Do utworzenia prostego programu, który pozwalałby wyświetlać i modyfikować dane, wystarczyć może zastosowanie rozwiązania z poprzedniego podrozdziału. W technice tej większość pracy wykonuje automatycznie Visual Basic, a programista nie musi zagłębiać się w niskopoziomowe szczegóły dostępu do bazy danych. Można także napisać własną aplikację za pomocą obiektów podobnych do tych, które tworzy Visual Basic. Nic też nie stoi na przeszkodzie, aby utworzyć własne obiekty klas DataSet, TableAdapter, BindingSource oraz BindingNavigator w celu powiązania kontrolek z bazą danych. Jeśli konieczne jest manipulowanie bazą danych bezpośrednio za pomocą kodu, tworzenie tych wszystkich obiektów nie zawsze ma sens. Aby programowo zmodyfikować jeden rekord, z pewnością nie ma po co tworzyć obiektów klas DataGridView, BindingNavigator oraz BindingSource. Dla takich przypadków w Visual Basicu utworzono kilka innych rodzajów obiektów, za pomocą których można pracować nad bazami danych. Wyróżnia się cztery ich kategorie:
Kontenery danych — przechowują dane po załadowaniu do aplikacji, w podobny sposób jak obiekt klasy DataSet. Można wiązać z nimi kontrolki, aby automatycznie wyświetlać i przetwarzać dane.
Połączenia — dostarczają informacje, które pozwalają programowi połączyć się z bazą danych.
Adaptery danych — przenoszą dane między bazą danych a kontenerem.
Obiekty poleceń — dostarczają instrukcje do przetwarzania danych. Obiekt polecenia może wybierać, aktualizować, wstawiać oraz usuwać dane z bazy danych, a także wykonywać procedury zapamiętane w bazie danych.
Klasy kontenerów danych i adapterów są klasami ogólnymi, a więc można ich używać z różnymi rodzajami baz danych. Natomiast różne typy obiektów połączeń i poleceń są specyficzne dla różnych rodzajów baz danych. Na przykład obiekty połączeń OleDbConnection, SqlConnection, OdbcConnection oraz OracleConnection działają odpowiednio z bazami danych Object Linking and Embedding Database (OLE DB), SQL Server (włącznie z Microsoft Data Engine — MSDE), Open Database Connectivity (ODBC) oraz Oracle. Obiekty baz danych SQL Server i Oracle działają tylko z określonymi rodzajami baz danych, są za to zoptymalizowane dla tych baz, co zwiększa ich wydajność. Poza tym, że obsługują różne typy baz danych, sposób działania tych obiektów jest mniej więcej taki sam. W kolejnych podrozdziałach objaśnię sposoby przenoszenia danych do i z bazy danych za pomocą tych obiektów. Opiszę najbardziej przydatne własności, metody i zdarzenia udostępniane przez obiekty połączeń, transakcji, adapterów danych oraz poleceń. W dalszej części rozdziału omówię obiekty klas DataSet i DataView oraz sposoby takiego wiązania z nimi kontrolek, aby automatycznie wyświetlały dane.
448
Część II
Wstęp do języka Visual Basic
Przeglądanie danych Istnieją trzy podstawowe obiekty służące do przenoszenia danych do i z bazy danych — połączenie, adapter danych oraz kontener danych, na przykład DataSet. Obiekt połączenia definiuje połączenie z bazą danych. Zawiera nazwę i informację o lokalizacji bazy danych, nazwę użytkownika i hasło, informacje o silniku tej bazy oraz znaczniki, które określają, jakiego rodzaju dostępu będzie potrzebował program. Obiekt adaptera danych definiuje odwzorowanie bazy danych na obiekt klasy DataSet. Określa, jakie dane są pobierane z bazy danych, a także które kolumny tej bazy są rzutowane na które kolumny obiektu DataSet. Obiekt DataSet przechowuje dane wykorzystywane w aplikacji. Może on zawierać więcej niż jedną tabelę, a także definiować i wymuszać powiązania pomiędzy nimi. Na przykład baza danych używana w poprzednich podrozdziałach zawiera tabelę TestScores z polem StudentId. Wartości tego ostatniego muszą być wartościami dostępnymi w tabeli Students. Nazywa się to ograniczeniem klucza obcego (ang. foreign key constraint). Obiekt DataSet może reprezentować to ograniczenie i zgłaszać błąd, gdy program próbuje utworzyć rekord TestScores z wartością StudentId, której nie ma w tabeli Students. Kiedy obiekty połączenia, adaptera danych i DataSet zostaną zainicjowane, program może wywołać metodę Fill adaptera, która będzie kopiować dane z bazy danych do obiektu DataSet. Później wywoła metodę Update adaptera, kopiującą wszystkie zmiany dokonane w obiekcie DataSet z powrotem do bazy danych. Proces ten przedstawiono na rysunku 20.13. Rysunek 20.13. Do przenoszenia danych do i z bazy danych wykorzystywane są obiekty połączenia, adaptera danych i DataSet
Jeśli porówna się rysunki 20.12 i 20.13, zauważy się pewne podobieństwa. W obu technikach do kopiowania danych pomiędzy bazą danych a obiektem DataSet korzysta się z adaptera danych. Na pierwszy rzut oka może się wydawać, że na rysunku 20.12 nie jest używany obiekt połączenia. W rzeczywistości jednak obiekt klasy TableAdapter zawiera wewnętrzny obiekt połączenia, za pomocą którego uzyskuje dostęp do bazy danych. Największa różnica jest taka, że na rysunku 20.12 został użyty obiekt klasy BindingSource, którzy tworzy dodatkową warstwę między obiektem klasy DataSet a kontrolkami programu. Dodatkowo zawiera on obiekt klasy DataNavigator, pozwalający użytkownikowi na eksplorację danych obiektu klasy BindingSource. Podobnie jak miało to miejsce w poprzednim przykładzie, program wykorzystujący obiekty z rysunku 20.13 mógłby wywołać metodę Fill adaptera w procedurze obsługi zdarzeń Load formularza. Gdy następnie użytkownik naciśnie przycisk Save, w procedurze obsługi zdarzenia FormClosing formularza lub w każdym innym momencie, gdy trzeba zapisać dane, mógłby wywołać metodę Update.
Rozdział 20.
Kontrolki i obiekty baz danych
449
Obiekty połączenia Obiekt połączenia odpowiada za połączenia aplikacji z bazą danych. Umożliwia adapterowi danych przenoszenie danych do i z obiektu klasy DataSet. Różne typy tego obiektu (OleDbConnection, SqlConnection, OdbcConnection, OracleConnection itd.) mają mniej więcej te same funkcje, chociaż istnieją między nimi pewne różnice. Aby dowiedzieć się, czy dany obiekt połączenia udostępnia określoną własność, metodę lub zdarzenie, najlepiej zajrzeć do pomocy internetowej. Pod adresem http://msdn2.microsoft.com/ en-us/library/32c5dh3b(vs.71).aspx znajdują się łącza do stron, na których znajdziesz objaśnienia, jak łączyć się z bazami danych SQL Server, OLE DB, ODBC oraz Oracle. Znajdują się tam też odnośniki do informacji na temat klas SqlConnection, OleDbConnection oraz OdbcConnection. Jeśli masz zamiar bardzo intensywnie korzystać z jednego typu bazy danych (na przykład SQL Server), sprawdź, co oferuje obiekt połączenia tego typu, aby dowiedzieć się, czy nie posiada on jakichś specjalnych funkcji dla tego typu bazy danych. Niektóre obiekty połączenia działają z więcej niż jednym typem bazy danych. Na przykład obiekt OleDbConnection pracuje z każdą bazą danych, która posiada dostawcę OLE BD (Object Linking and Embedding Database). Analogicznie — obiekt OdbcConnection działa z bazami danych, które posiadają dostawców ODBC (Open Database Connectivity), na przykład MySQL. Z reguły połączenia dedykowane jednemu rodzajowi bazy danych (na przykład SqlConnection i OracleConnection) są wydajniejsze. Jeśli istnieje możliwość, że po jakimś czasie konieczna będzie zmiana bazy danych, można zmniejszyć ilość pracy związanej z tą modyfikacją poprzez używanie tylko takich funkcji, które są wspólne dla wszystkich typów połączeń. Narzędzia dla tych obiektów nie są automatycznie wyświetlane w oknie Toolbox. Aby je tam wstawić, kliknij prawym przyciskiem myszy wybraną kartę okna Toolbox, a następnie polecenie Choose Items. Zaznacz narzędzia, które chcesz dodać (na przykład OracleCommand lub OdbcConnection), po czym kliknij OK. W poniższej tabeli znajdziesz najbardziej przydatne własności klas OleDbConnection i SqlConnection. Własność
Opis
ConnectionString
Łańcuch definiujący połączenie z bazą danych.
ConnectionTimeout
Długość czasu, przez który obiekt oczekuje na połączenie z bazą danych. Po jego upływie obiekt zgłasza błąd.
Database
Zwraca nazwę bieżącej bazy danych.
DataSource
Zwraca nazwę bieżącego pliku bazy danych lub serwera baz danych.
Provider
(Tylko obiekty OleDbConnection). Zwraca nazwę dostawcy bazy danych OLE DB (na przykład Microsoft.Jet.OLEDB.4.0).
450
Część II
Wstęp do języka Visual Basic
Własność
Opis
ServerVersion
Zwraca numer wersji serwera baz danych. Wartość ta jest tylko wtedy dostępna, gdy połączenie jest otwarte. Może wyglądać na przykład tak: 04.00.0000.
State
Zwraca aktualny stan połączenia. Wartością może być Closed, Connecting, Open, Executing (wykonywanie polecenia), Fetching (pobieranie danych) oraz Broken (połączenie zostało nawiązane, ale coś je przerwało; można je zamknąć i otworzyć ponownie).
Własność ConnectionString składa się z wielu pól pooddzielanych średnikami. Poniżej znajduje się typowa wartość własności ConnectionString obiektu OleDbConnection, który otwiera bazę danych Access. Tutaj każde pole zostało przedstawione w osobnym wierszu, ale w rzeczywistości wszystkie one tworzą jeden długi ciąg. Jet OLEDB:Global Partial Bulk Ops=2; Jet OLEDB:Registry Path=; Jet OLEDB:Database Locking Mode=1; Data Source="C:\Personnel\Data\Personnel.mdb"; Mode=Share Deny None; Jet OLEDB:Engine Type=5; Provider="Microsoft.Jet.OLEDB.4.0"; Jet OLEDB:System database=; Jet OLEDB:SFP=False; persist security info=False; Extended Properties=; Jet OLEDB:Compact Without Replica Repair=False; Jet OLEDB:Encrypt Database=False; Jet OLEDB:Create System Database=False; Jet OLEDB:Don’t Copy Locale on Compact=False; User ID=Admin; Jet OLEDB:Global Bulk Transactions=1"
W Twoim systemie wartość Data Source będzie inna. W tym przykładzie baza danych znajduje się w lokalizacji C:|Personnel\Data\Personnel.mdb. Być może trzeba będzie ją zmienić, aby pasowała do Twojego systemu. Wiele z tych własności jest opcjonalnych. Zapamiętanie, które z nich są nieobowiązkowe (a nawet które pola są dozwolone dla danego typu obiektu połączenia), nie zawsze jest łatwe. Na szczęście zwykle nie jest to konieczne. Zamiast wpisywać wszystkie te pola do kodu lub własności ConnectionString kontrolki połączenia w oknie Properties, można pozwolić, aby łańcuch ten został automatycznie utworzony przez Visual Basic. Wystarczy tylko zastosować się do wskazówek opisanych we wcześniejszym podrozdziale „Łączenie ze źródłami danych”. Gdy utworzysz lub wybierzesz połączenie z bazą danych, będziesz mógł obejrzeć łańcuch połączenia, jeśli klikniesz znak plusa z rysunku 20.2. Można zaznaczyć ten łańcuch za pomocą myszy, a następnie skopiować go przy użyciu kombinacji klawiszy Ctrl+C. Poniższy fragment kodu demonstruje utworzenie, otwarcie, użycie i zamknięcie obiektu klasy OleDbConnection. Założono, że nazwa bazy danych została wprowadzona w polu tekstowym txtDatabase.
Rozdział 20.
Kontrolki i obiekty baz danych
451
' Utworzenie łańcucha połączenia. Dim connect_string As String = _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=""" & txtDatabase.Text & """;" & _ "Persist Security Info=False" ' Otwarcie połączenia z bazą danych. Dim conn_people As New OleDb.OleDbConnection(connect_string) conn_people.Open() ' Używanie połączenia. '... ' Zamknięcie połączenia. conn_people.Close() conn_people.Dispose()
Podobny kod otwierający połączenie przed wstawieniem danych do bazy danych można znaleźć w programie CommandInsert, który pobierzesz z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/vb28wp.zip). Poniższa tabela zawiera zestawienie najbardziej przydatnych metod udostępnianych przez klasy OleDbConnection i SqlConnection. Metoda
Opis
BeginTransaction
Rozpoczyna transakcję bazy danych i zwraca obiekt transakcji, który ją reprezentuje. Transakcja zapewnia, że zestaw poleceń zostanie w całości wykonany lub anulowany. Więcej informacji na ten temat znajduje się w dalszej części rozdziału — w podrozdziale „Obiekty transakcji”.
ChangeDatabase
Zmienia aktualnie otwartą bazę danych.
Close
Zamyka połączenie z bazą danych.
CreateCommand
Tworzy obiekt polecenia, który może wykonać jakieś działania na bazie danych. Może to być wybieranie rekordów, utworzenie tabeli, aktualizacja rekordu itd.
Open
Otwiera połączenie za pomocą wartości z własności ConnectionString.
Najbardziej przydatnymi zdarzeniami obiektu połączenia są InfoMessage i StateChange. Pierwsze z nich ma miejsce, gdy dostawca bazy danych wysyła ostrzeżenie lub komunikat informacyjny. Program może odczytać taki komunikat i podjąć jakieś działania albo wyświetlić go użytkownikowi. Zdarzenie StateChange występuje, gdy zmienia się stan połączenia z bazą danych. Warto zauważyć, że przedstawiona na rysunku 20.12 metoda używania obiektu połączenia opiera się na metodach Fill i Update adaptera danych, a nie na metodach Open i Close obiektu połączenia. Metody Fill i Update automatycznie otwierają połączenie, wykonują swoje zadania i zamykają połączenie, dzięki czemu programista nie musi sam zajmować się obiektem połączenia. Jeśli na przykład program wywoła metodę Fill, adapter danych szybko otworzy połączenie z bazą danych, skopiuje z niej dane do obiektu DataSet i zamknie bazę. Dzięki zastosowaniu takiego modelu interakcji z bazą danych wszystkie połączenia będą otwarte przez bardzo krótki czas.
452
Część II
Wstęp do języka Visual Basic
Obiekty transakcji Transakcja definiuje zestaw działań bazy danych, które powinny zostać wykonane jako jedno. Albo wszystkie z nich muszą być wykonane, albo żadne. Nie może się zdarzyć, że zostanie wykonane tylko jedno z nich, a reszta nie. Klasycznym przykładem jest przelew pieniędzy z jednego konta na inne. Wyobraźmy sobie, że program próbuje podjąć pewną kwotę z jednego konta i dodać ją do innego. Jednak po odjęciu odpowiedniej sumy z pierwszego z kont program ulega awarii. Baza danych zgubiła pieniądze — niezbyt dobra wiadomość dla posiadaczy kont. Z drugiej strony wyobraźmy sobie, że program wykonuje te działania w odwrotnej kolejności — najpierw dodaje pieniądze do drugiego konta, a dopiero potem odejmuje je od pierwszego. Tym razem jeśli aplikacja ulegnie awarii w połowie operacji, baza danych utworzy nowe pieniądze — zła wiadomość dla banku. Rozwiązaniem tego problemu jest zapakowanie tych dwóch operacji w jedną transakcję bazy danych. Jeśli program ulegnie awarii w połowie drogi, baza danych cofnie transakcję, dzięki czemu dane pozostaną nienaruszone. Nie jest to tak dobre jak bezbłędne dokończenie operacji, ale przynajmniej baza danych jest nienaruszona, a pieniądze pozostaną zachowane. W Visual Basicu do otwierania transakcji służy metoda BeginTransaction obiektu połączenia. Po otwarciu transakcji program tworzy obiekty poleceń związane z tym połączeniem i transakcją, a następnie wykonuje je. Po zakończeniu tych działań aplikacja może wywołać metodę Commit obiektu transakcji, aby zatwierdzić wszystkie działania, lub metodę Rollbackw celu anulowania ich. Poniższy kod pochodzi z programu Transactions, który można pobrać z serwera FTP wydawnictwa Helion. Wykonuje on dwa działania w jednej transakcji. Aplikacja usuwa pewną kwotę pieniędzy z jednego konta i dodaje ją do innego. ' Utworzenie przelewu. Private Sub btnUpdate_Click() Handles btnUpdate.Click ' Otwarcie połączenia. Dim connAccounts As New OleDbConnection(MakeConnectString()) connAccounts.Open() ' Utworzenie transakcji. Dim trans As OleDbTransaction = _ connAccounts.BeginTransaction(IsolationLevel.ReadCommitted) ' Utworzenie polecenia dla tego połączenia ' i tej transakcji. Dim cmd As New OleDbCommand( _ "UPDATE Accounts SET Balance=Balance + ? WHERE AccountName=?", _ connAccounts, _ trans) ' Utworzenie parametrów dla pierwszego polecenia. cmd.Parameters.Add(New OleDbParameter("Balance", _ Decimal.Parse(txtAmount.Text))) cmd.Parameters.Add(New OleDbParameter("AccountName", _ "Alice’s Software Emporium")) ' Wykonanie pierwszego polecenia. cmd.ExecuteNonQuery()
Rozdział 20.
Kontrolki i obiekty baz danych
453
' Utworzenie parametrów dla drugiego polecenia. cmd.Parameters.Clear() cmd.Parameters.Add(New OleDbParameter("Balance", _ -Decimal.Parse(txtAmount.Text))) cmd.Parameters.Add(New OleDbParameter("AccountName", _ "Bob’s Consulting")) ' Wykonanie drugiego polecenia. cmd.ExecuteNonQuery() ' Zatwierdzenie transakcji. If MessageBox.Show( _ "Commit transaction?", _ "Commit?", _ MessageBoxButtons.YesNo, _ MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes _ Then ' Zatwierdzenie transakcji. trans.Commit() Else ' Cofnięcie transakcji. trans.Rollback() End If ' Wyświetlenie sald bieżących. ShowValues(connAccounts) ' Zamknięcie połączenia. connAccounts.Close() End Sub
Najpierw zostaje utworzone połączenie. Do budowy odpowiedniego łańcucha połączenia została użyta funkcja MakeConnectString. Następnie metoda BeginTransaction tworzy obiekt transakcji o nazwie trans. Później został zdefiniowany obiekt klasy OleDbCommand o nazwie cmd, a treść jego polecenia ustawiono na "UPDATE Accounts SET Balance=Balance+? WHERE AccountName=?". Należy zauważyć, że przekazuje on obiekt transakcji do konstruktora obiektu polecenia, aby sprawić, że polecenie to będzie częścią transakcji. Znaki zapytania znajdujące się w treści polecenia reprezentują jego parametry. Program definiuje wartości tych ostatnich poprzez dodanie dwóch obiektów parametrów do obiektu polecenia. Następnie wywołuje metodę ExecuteNonQuery tego polecenia, aby wykonać zapytanie. Program czyści parametry polecenia, dodaje dwa parametry z innymi wartościami i jeszcze raz wywołuje metodę ExecuteNonQuery tego polecenia. Następnie zostaje wyświetlone okno z zapytaniem, czy użytkownik chce zatwierdzić transakcję. Jeśli kliknie on przycisk Tak, program wywoła metodę Commit transakcji — obie operacje aktualizacji zostaną wykonane. Jeżeli zaś kliknie przycisk Nie, aplikacja wywoła metodę Rollback transakcji — obie operacje aktualizacji będą anulowane.
454
Część II
Wstęp do języka Visual Basic
Na zakończenie program wywołuje procedurę ShowValues, wyświetlającą zaktualizowane dane, po czym zamyka połączenie. Zamiast klikać przycisk Tak lub Nie, kiedy program zapyta, czy ma zatwierdzić transakcję, można zatrzymać aplikację za pomocą IDE. Kiedy później znowu uruchomisz ten program, zauważysz, że żadna z operacji nie została wykonana. Poza metodami Commit i Rollback obiekty transakcji mogą udostępniać inne metody, które pozwalają na wykonywanie bardziej złożonych transakcji. Na przykład klasa OleDbTransaction udostępnia metodę Begin, dzięki której da się utworzyć transakcję zagnieżdżoną. Podobnie klasa SqlTransaction posiada metodę Save, tworzącą „punkt bezpieczeństwa”, do którego można cofnąć część transakcji. Aby znaleźć informacje o tego typu metodach, należy przejrzeć pomoc internetową dla wybranego typu obiektu transakcji. Na stronie http://msdn2.microsoft.com/ en-us/library/2k2hy99x(vs.71).aspx znajduje się przegląd zagadnień związanych z transakcjami. Znajdujące się na jej samym dole odnośniki prowadzą do informacji o klasach OleDbTransaction, SqlTransaction oraz OdbcTransaction.
Adaptery danych Adapter danych przenosi dane między połączeniem a obiektem DataSet. Najważniejszymi metodami tego obiektu są Fill i Update. Przenoszą one dane z i do bazy danych. Adaptery danych posiadają także czasami bardzo przydatne własności i inne metody. Poniższa tabela zawiera zestawienie najbardziej przydatnych własności adaptera danych. Własność
Opis
DeleteCommand
Obiekt polecenia, którego adapter danych używa do usuwania wierszy.
InsertCommand
Obiekt polecenia, którego adapter danych używa do wstawiania wierszy.
SelectCommand
Obiekt polecenia, którego adapter danych używa do wybierania wierszy.
TableMappings
Kolekcja obiektów DataTableMapping, która określa, w jaki sposób tabele w bazie danych są odwzorowywane w obiekcie DataSet. Każdy obiekt DataTableMapping posiada kolekcję ColumnMappings, która wskazuje, jak kolumny w bazie danych są odwzorowywane na kolumny w tabeli DataSet.
UpdateCommand
Obiekt polecenia, którego adapter danych używa do aktualizacji wierszy.
Obiekty poleceń można tworzyć na kilka różnych sposobów. Jeśli na przykład do budowy adaptera w czasie projektowania zostanie użyty kreator Data Adapter Configuration Wizard (opisany niebawem), obiekty te zostaną utworzone automatycznie przez ten kreator. Adapter taki można zaznaczyć i rozwinąć te obiekty w oknie Properties. W ten sposób da się sprawdzić ich własności, wliczając własność CommandText, która definiuje te polecenia. Innym sposobem na utworzenie tych poleceń jest użycie obiektu budującego polecenia. Jeśli taki obiekt budujący zostanie dołączony do adaptera danych, ten ostatni automatycznie wygeneruje przy jego użyciu potrzebne mu polecenia.
Rozdział 20.
Kontrolki i obiekty baz danych
455
Aplikacja GenerateCommands, którą można pobrać z serwera FTP wydawnictwa Helion, określa polecenia używane przez adapter danych za pomocą poniższego kodu. Tworzy on nowy obiekt OleDbCommandBuilder, po czym przekazuje jego konstruktor do adaptera danych. Następnie zbiera informacje o automatycznie wygenerowanych poleceniach za pomocą metod GetDeleteCommand, GetInsertCommand i GetUpdateCommand tego obiektu budującego. ' Dołączenie obiektu budującego polecenia do adaptera danych ' i wyświetlenie wygenerowanych poleceń. Dim command_builder As New OleDbCommandBuilder(OleDbDataAdapter1) Dim txt As String = "" txt &= command_builder.GetDeleteCommand.CommandText txt &= command_builder.GetInsertCommand.CommandText txt &= command_builder.GetUpdateCommand.CommandText txtCommands.Text = txt txtCommands.Select(0, 0)
& & &
vbCrLf & vbCrLf & vbCrLf &
vbCrLf vbCrLf vbCrLf
Poniżej znajduje się wynik powyższych instrukcji. Instrukcje DELETE i UPDATE są zawinięte w kilku wierszach. Obiekt budujący polecenia wygenerował te polecenia na podstawie instrukcji SELECT * From Books, która została użyta do załadowania obiektu DataSet. DELETE FROM ((? = 1 AND ((? = 1 AND ((? = 1 AND ((? = 1 AND
Books WHERE ((Title = ?) AND URL IS NULL) OR (URL = ?)) AND Year IS NULL) OR (Year = ?)) AND ISBN IS NULL) OR (ISBN = ?)) AND Pages IS NULL) OR (Pages = ?)))
INSERT INTO Books (Title, URL, Year, ISBN, Pages) VALUES (?, ?, ?, ?, ?) UPDATE Books SET Title = ?, URL = ?, Year = ?, ISBN = ?, Pages = ? WHERE ((Title = ?) AND ((? = 1 AND URL IS NULL) OR (URL = ?)) AND ((? = 1 AND Year IS NULL) OR (Year = ?)) AND ((? = 1 AND ISBN IS NULL) OR (ISBN = ?)) AND ((? = 1 AND Pages IS NULL) OR (Pages = ?)))
Własność adaptera danych TableMappings pozwala zmienić sposób odwzorowywania przezeń danych z bazy w obiekcie klasy DataSet. Można na przykład sprawić, że tabela Employees zostanie skopiowana z bazy danych do tabeli People w obiekcie DataSet. Zazwyczaj jednak nie ma potrzeby zmieniania nazw tabel i kolumn, a ponadto modyfikacji tych można dokonać interaktywnie w czasie projektowania w łatwiejszy sposób niż w kodzie, dlatego wartości te zazwyczaj zostawia się w spokoju podczas działania programu. Aby utworzyć kontrolkę adaptera danych w czasie projektowania, otwórz formularz w projektancie formularzy, następnie kartę Data w oknie Toolbox, po czym kliknij dwukrotnie wybraną kontrolkę adaptera danych (jeśli w oknie Toolbox nie ma narzędzia, którego potrzebujesz, kliknij to okno prawym przyciskiem myszy, a następnie opcję Choose Items i wybierz odpowiedni adapter danych). Gdy adapter danych zostanie utworzony, włączy się kreator Data Adapter Configuration Wizard. Na jego pierwszej karcie można wybrać lub utworzyć połączenie z danymi w bardzo podobny sposób, jak ma to miejsce w kreatorze Data Source Configuration Wizard z rysunku 20.2. Wybierz lub utwórz połączenie zgodnie ze wskazówkami zawartymi w podrozdziale „Łączenie ze źródłami danych”.
456
Część II
Wstęp do języka Visual Basic
Kliknij przycisk Next, aby przejść do strony widocznej na rysunku 20.14. Za pomocą przycisków opcji zdecyduj, w jaki sposób adapter ma pracować ze źródłem danych. W ten sposób określisz, jak adapter będzie pobierać, aktualizować, usuwać i wstawiać dane do bazy danych. Wybranie opcji Use SQL Statements sprawi, że adapter będzie używał prostych instrukcji SQL. Zdecydowanie się na Create New stored procedures będzie miało ten skutek, że kreator wygeneruje procedury zapamiętane w bazie danych. Wybranie Use existing stored procedures sprawi, że kreator będzie używał procedur zapamiętanych, które zostały utworzone wcześniej. Na rysunku 20.14 widać, że aktywna jest tylko pierwsza z tych opcji, ponieważ tylko ona jest dostępna dla adaptera OleDbDataAdapter, który został użyty w tym przykładzie. Rysunek 20.14. Okno, które pozwala zdecydować, w jaki sposób adapter ma przetwarzać dane w bazie danych
Gdy zaznaczysz opcję Use SQL Statements i naciśniesz przycisk Next, pojawi się formularz z rysunku 20.15. Jeśli potrafisz pisać instrukcje SQL, wpisz instrukcję SELECT — której Twój adapter będzie używać, aby pobierać swoje dane. Jeśli nie potrafisz pisać tych instrukcji lub nie wiesz, jaką strukturę ma baza danych, kliknij przycisk Query Builder, aby otworzyć okno Query Builder z rysunku 20.16. W górnej części tego okna po lewej stronie znajduje się okienko zawierające te tabele, które zostały wskazane do użycia w zapytaniu SQL. Pola wyboru wyznaczają, które pola tabeli zostały wybrane. Aby dodać nową tabelę do zapytania, kliknij prawym przyciskiem myszy w tym obszarze i wybierz opcję Add Table. Na rysunku 20.16 przedstawiono okno Query Builder. W jego górnej części widać, że w zapytaniu użyto tabeli Books, a także zaznaczono jej pola Title, Year i Pages.
Rysunek 20.16. Za pomocą okna Query Builder można interaktywnie określić, które dane mają zostać pobrane przez adapter danych
458
Część II
Wstęp do języka Visual Basic
Pod tabelą i obszarem do wybierania pól znajduje się siatka zawierająca wybrane pola. W kolumnach można wpisywać modyfikatory dla każdego z nich. W kolumnie Alias wpiszesz nazwę dla pola, pod którą będzie ono zwracane przez zapytanie. Na rysunku 20.16 widać, że pole Year zostanie zwrócone pod nazwą PubYear. Pole wyboru Output wskazuje, czy pole jest wybrane. Jego funkcja jest taka sama jak pól wyboru w górnej części okna. Kolumna Sort Type pozwala określić, czy wyniki mają być posortowane rosnąco, czy malejąco. Kolumna Sort Order wyznacza kolejność sortowania pól. Zapytanie z rysunku 20.16 najpierw sortuje dane według pola Year w kolejności malejącej. Jeśli więcej niż jedna książka została wydana w tym samym roku, wszystkie publikacje będą sortowane rosnąco według pola Title. Kolumna Filter pozwala dodawać warunki dla pól. Wybranie ustawień z rysunku 20.16 sprawia, że zostaną wybrane tylko te rekordy, w których pole Year ma wartość większą od 1998. Kolejne pola, które byłoby widać po przewinięciu w prawo, pozwalają wstawić dodatkowe filtry pooddzielane słowem OR. Można by na przykład wybrać książki, których rok wydania (Year) jest większy niż 1998 OR (lub) mniejszy niż 1995. Jeśli filtry zostaną zdefiniowane dla więcej niż jednego pola, będą łączone za pomocą słowa AND. Na przykład na rysunku 20.16 widać, że zostaną wybrane rekordy, w których pole Year ma wartość większą od 1998 oraz (AND) pole Pages ma wartość wyższą niż 400. Pod siatką znajduje się pole tekstowe, wyświetlające kod tworzonego zapytania SQL. Widać, że zapytanie to wybiera te pola, które zostały zaznaczone na samej górze, została w nim użyta odpowiednia klauzula WHERE oraz ustawia ono wyniki w odpowiedniej kolejności. Aby wykonać zapytanie i wyświetlić wyniki na samym dole, należy kliknąć przycisk Execute Query. Za jego pomocą można sprawdzić, czy utworzone zapytanie odpowiednio działa, jeszcze przed zakończeniem pracy nad adapterem danych. Kliknij przycisk OK, aby zamknąć okno Query Builder i powrócić do okna Data Adapter Configuration Wizard. Gdy klikniesz przycisk Next, kreator wyświetli stronę podsumowującą, na której wymieni, co zrobił, a czego nie zrobił podczas tworzenia adaptera. W zależności od zapytania użytego do wybierania danych kreator może nie wygenerować wszystkich poleceń do wybierania, aktualizowania, wstawiania i usuwania danych. Jeśli na przykład zapytanie łączy więcej niż jedną tabelę, kreator nie będzie w stanie zgadnąć, jak wszystkie je aktualizować, przez co nie wygeneruje poleceń wstawiania, aktualizowania i usuwania. Kliknij przycisk Finish, aby zamknąć kreator i utworzyć nowy adapter danych z obiektem połączenia. W oknie Properties znajdują się jego obiekty DeleteCommand, InsertCommand, SelectCommand oraz UpdateCommand. Wartości ich własności CommandText zawierają instrukcje SQL, których używają te obiekty. Dodatkowo kreator wygenerował domyślne odwzorowania tabel, które służą do przekształcania wartości z bazy danych na wartości obiektu klasy DataSet.
Rozdział 20.
Kontrolki i obiekty baz danych
459
Obiekty poleceń Polecenia baz danych są reprezentowane przez klasy obiektów poleceń (OleDbCommand, SqlCommand, OdbcCommand oraz OracleCommand). Mogą one być kwerendami SQL lub jakimiś innymi instrukcjami, jak INSERT, UPDATE, DELETE czy CREATE TABLE. Własność Connection obiektu dostarcza bazie danych obiekt połączenia, na rzecz którego wykonuje ona swoje polecenie. Własność CommandText dostarcza kwerendę SQL, która jest reprezentowana przez to polecenie. Własność CommandType informuje bazę danych o typie polecenia. Może to być StoredProcedure (własność CommandText jest nazwą procedury zapamiętanej), TableDirect (własność CommandText jest nazwą jednej lub większej liczby tabel, z których baza danych powinna zwrócić dane) lub Text (własność CommandText jest instrukcją SQL). Kolekcja Parameters obiektu polecenia zawiera obiekty parametrów, które definiują wszystkie wartości potrzebne do wykonania go. Poniższy kod pochodzi z programu CommandInsert, który można pobrać z serwera FTP wydawnictwa Helion. Tworzy on obiekt OleDbCommand, który wykonuje instrukcję SQL INSERT INTO PeopleNames (FirstName, LastName) VALUES (?, ?). Znaki zapytania to symbole zastępcze dla parametrów, które będą dodane później. Następnie zostają utworzone dwa nowe obiekty klasy OleDbParameter, a następnie dodane do kolekcji Parameters polecenia. Kiedy będzie wywołana metoda ExecuteNonQuery tego polecenia, znaki zapytania zostaną zamienione przez adapter na wartości parametrów — w takiej kolejności, w jakiej znajdują się w kolekcji Parameters. W tym przypadku wartość txtFirstName.Text zastępuje pierwszy znak zapytania, a wartość txtLastName.Text — drugi. Private Sub btnAdd_Click() Handles btnAdd.Click ' Utworzenie łańcucha połączenia. Dim connect_string As String = _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=""" & txtDatabase.Text & """;" & _ "Persist Security Info=False" ' Otwarcie połączenia z bazą danych. Dim conn_people As New OleDb.OleDbConnection(connect_string) conn_people.Open() ' Utworzenie polecenia wstawiającego dane. Dim cmd As New OleDbCommand( _ "INSERT INTO PeopleNames (FirstName, LastName) " & _ "VALUES (?, ?)", _ conn_people) ' Utworzenie parametrów dla tego polecenia. cmd.Parameters.Add(New OleDbParameter("FirstName", txtFirstName.Text)) cmd.Parameters.Add(New OleDbParameter("LastName", txtLastName.Text)) ' Wykonanie polecenia. Try cmd.ExecuteNonQuery() Catch ex As Exception MessageBox.Show(ex.Message) End Try ' Pokazanie aktualnych wartości. ShowValues(conn_people)
460
Część II
Wstęp do języka Visual Basic
' Zamknięcie połączenia. conn_people.Close() conn_people.Dispose() End Sub
Własność Transaction obiektu polecenia wyznacza obiekt transakcji, z którym polecenie to jest związane. Więcej informacji o transakcjach znajduje się we wcześniejszym podrozdziale „Obiekty transakcji”. Obiekty poleceń udostępniają trzy metody do wykonywania treści ich własności CommandText. Metoda ExecuteNonQuery wykonuje polecenie niebędące kwerendą i niezwracające żadnej wartości. Metoda ExecuteScalar wykonuje polecenie i zwraca pierwszą kolumnę z pierwszego zaznaczonego wiersza. Jest ona przydatna do wykonywania poleceń zwracających pojedyncze wartości, jak SELECT COUNT(*) FROM Users. Metoda ExecuteReader wykonuje instrukcję SELECT i zwraca obiekt klasy DataReader (na przykład OleDbDataReader). Za pomocą tego obiektu program może przechodzić przez zwrócone wiersze danych. Dwie inne bardzo przydatne metody obiektu polecenia to CreateParameter i Prepare. Jak nietrudno się domyślić, pierwsza z nich wstawia nowy obiekt do kolekcji Parameters obiektu polecenia. Natomiast metoda Prepare kompiluje polecenie do takiej postaci, żeby baza danych mogła wykonać je szybciej. Zazwyczaj wielokrotne wykonanie skompilowanego polecenia przy użyciu różnych parametrów jest szybsze od wykonania wielu nowych poleceń.
Obiekt klasy DataSet Obiekt klasy DataSet jest sztandarowym obiektem przechowującym dane w pamięci komputera. Posiada on podobną jak relacyjne bazy danych funkcjonalność, potrzebną do wytwarzania, ładowania, przechowywania, manipulowania i zapisywania danych. Może on przechowywać wiele tabel powiązanych skomplikowanymi relacjami typu rodzicdziecko i z ograniczeniami unikatowości (ang. uniqueness constraint). Udostępnia metody pozwalające scalać obiekty klasy DataSet, wyszukiwać rekordy odpowiadające zadanym kryteriom oraz zapisywać dane na różne sposoby (na przykład w relacyjnej bazie danych lub pliku XML). Pod wieloma względami przypomina on w pełni funkcjonalną bazę danych, która zamiast na dysku jest przechowywana w pamięci. Jednym z najczęstszych sposobów użycia obiektu klasy DataSet jest załadowanie go z relacyjnej bazy danych przy uruchamianiu programu, wyświetlenie jego danych i pozwolenie użytkownikowi na manipulowanie nimi za pomocą różnych kontrolek oraz zapisanie wszelkich zmian z powrotem w bazie danych przy zamykaniu aplikacji. Czasami dane zamiast z bazy danych mogą być pobierane z pliku XML, czyli obiekt klasy DataSet bywa tworzony z pominięciem bazy danych. Przy niewielkim nakładzie pracy programista może związać z tym obiektem odpowiednie kontrolki, które pozwalają przeglądać i manipulować złożonymi danymi. Poniższy kod pochodzi z programu MemoryDataSet, który można pobrać z serwera FTP wydawnictwa Helion. Na początku zostaje utworzony obiekt klasy DataSet o nazwie Scores. Następnie program ten tworzy obiekt klasy DataTable o nazwie Students i dodaje go do kolekcji Tables utworzonego wcześniej obiektu klasy DataSet.
Rozdział 20.
Kontrolki i obiekty baz danych
Private Sub Form1_Load() Handles MyBase.Load ' Utworzenie obiektu DataSet. Dim scores_dataset As New DataSet("Scores") ' Utworzenie tabeli Students. Dim students_table As DataTable = _ scores_dataset.Tables.Add("Students") ' Dodanie kolumn do tabeli Students. students_table.Columns.Add("FirstName", GetType(String)) students_table.Columns.Add("LastName", GetType(String)) students_table.Columns.Add("StudentId", GetType(Integer)) ' Wymuszenie, aby pole StudentId było unikatowe. students_table.Columns("StudentId").Unique = True ' Wymuszenie, by kombinacje FirstName-LastName były unikatowe. Dim first_last_columns() As DataColumn = { _ students_table.Columns("FirstName"), _ students_table.Columns("LastName") _ } students_table.Constraints.Add( _ New UniqueConstraint(first_last_columns)) ' Utworzenie tabeli TestScores. Dim test_scores_table As DataTable = _ scores_dataset.Tables.Add("TestScores") ' Dodanie kolumn do tabeli TestScores. test_scores_table.Columns.Add("StudentId", GetType(Integer)) test_scores_table.Columns.Add("TestNumber", GetType(Integer)) test_scores_table.Columns.Add("Score", GetType(Integer)) ' Wymuszenie, by kombinacje StudentId-TestNumber były unikatowe. Dim studentid_testnumber_score_columns() As DataColumn = { _ test_scores_table.Columns("StudentId"), _ test_scores_table.Columns("TestNumber") _ } test_scores_table.Constraints.Add( _ New UniqueConstraint(studentid_testnumber_score_columns)) ' Utworzenie związku łączącego ' pola StudentId obu tabel. scores_dataset.Relations.Add( _ "Student Test Scores", _ students_table.Columns("StudentId"), _ test_scores_table.Columns("StudentId")) ' Utworzenie danych studentów. students_table.Rows.Add(New Object() {"Art", "Ant", 1}) students_table.Rows.Add(New Object() {"Bev", "Bug", 2}) students_table.Rows.Add(New Object() {"Cid", "Cat", 3}) students_table.Rows.Add(New Object() {"Deb", "Dove", 4}) ' Utworzenie losowych wyników testów. Dim score As New Random For id As Integer = 1 To 4 For test_num As Integer = 1 To 10 test_scores_table.Rows.Add( _ New Object() {id, test_num, score.Next(65, 100)}) Next test_num Next id ' Dołączenie obiektu DataSet do obiektu DataGrid. grdScores.DataSource = scores_dataset End Sub
461
462
Część II
Wstęp do języka Visual Basic
Następnie program ten za pomocą metody Columns.Add obiektu DataTable dodaje do tabeli kolumny FirstName, LastName oraz StudentId. Później ustawia własność Unique kolumny StudentId na True, aby uniemożliwić powstanie takich samych identyfikatorów dla różnych studentów. Później zostanie utworzona tablica obiektów klasy DataColumn, które będą zawierały referencje do kolumn FirstName i LastName. Będzie ona wykorzystana do utworzenia obiektu UniqueConstraint, który zostanie następnie dodany do kolekcji Constraints tej tabeli. Dzięki temu pewne jest, że para FirstName-LastName każdego z rekordów będzie unikatowa. W podobny sposób jest tworzona tabela TestScores: powstają w niej kolumny StudentId, TestNumber i Score oraz ograniczenie unikatowości na pary kolumn StudentId-TestNumber. Następnie program tworzy relację łączącą kolumnę StudentId z tabeli Students z kolumną StudentId z tabeli TestScores. Potem zostaje dodanych kilka rekordów, które definiują studentów, oraz kilka losowych rekordów TestScores. Na końcu program dołącza swój obiekt klasy DataSet do kontrolki DataGrid, aby wyświetlić wynik. Użytkownik może za pomocą tej kontrolki analizować i modyfikować dane w taki sam sposób, jakby zostały one załadowane z bazy danych. Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy DataSet. Własność
Opis
CaseSensitive
Określa, czy łańcuchy w obiektach klasy DataTable mają być porównywane z rozróżnianiem małych i wielkich liter.
DataSetName
Nazwa obiektu klasy DataSet. Nie jest ona zbyt często przydatna, chociaż wyznacza nazwę elementu głównego w reprezentacji XML obiektu.
DefaultViewManager
Zwraca obiekt klasy DataViewManager, za pomocą którego można wyznaczyć ustawienia domyślne (kolejność sortowania, filtr) obiektów klasy DataView, które zostaną później utworzone.
EnforceConstraints
Decyduje, czy obiekt klasy DataSet powinien egzekwować ograniczenia podczas aktualizowania danych. Jeśli na przykład konieczne jest dodanie rekordów do tabeli potomnej przed utworzeniem rekordów głównych, można ustawić tę własność na False. Należy jednak unikać tego rodzaju problemów poprzez dodawanie rekordów w odpowiedniej kolejności.
HasErrors
Zwraca wartość True, jeśli któryś z obiektów klasy DataTable obiektu klasy DataSet zawiera błędy.
Namespace
Przestrzeń nazw obiektu klasy DataSet. Jeśli własność ta ma jakąś wartość, węzeł główny reprezentacji XML obiektu klasy DataSet zawiera atrybut xmlns, na przykład .
Prefix
Wyznacza prefiks XML używany przez obiekt DataSet jako alias jego przestrzeni nazw.
Relations
Kolekcja obiektów klasy DataRelation, które reprezentują relacje rodzic-dziecko wśród kolumn w różnych tabelach.
Tables
Kolekcja obiektów DataTable, które reprezentują tabele znajdujące się w obiekcie DataSet.
Rozdział 20.
Kontrolki i obiekty baz danych
463
Własności XML obiektu DataSet mają wpływ na to, w jaki sposób obiekt ten odczytuje i zapisuje swoje dane w formacie XML. Jeśli na przykład własność Namespace ma wartość my_namespace, a własność Prefix — wartość pfx, dane XML tego obiektu mogą wyglądać następująco: ArtAnt1BevBug2 ... 11781281 ...
Poniższa tabela zawiera zestawienie najbardziej przydatnych metod obiektu klasy DataSet. Metoda
Opis
AcceptChanges
Zatwierdza wszystkie modyfikacje danych dokonane od ich załadowania lub ostatniego wywołania tej metody. Kiedy w obiekcie klasy DataSet zmieni się jakiś wiersz, będzie on oznaczony jako zmodyfikowany. Jeśli usunie się wiersz, będzie on oznaczony jako usunięty, ale w rzeczywistości cały czas tam będzie. Jeśli wywołasz metodę AcceptChanges, nowe i zmodyfikowane wiersze będą oznaczone jako Unchanged (niezmodyfikowane) zamiast Added (dodane) i Modified (zmodyfikowane), a usunięte wiersze zostaną usunięte na stałe.
Clear
Usuwa wszystkie wiersze z tabel obiektu klasy DataSet.
Clone
Tworzy kopię obiektu klasy DataSet — z wszystkimi jego tabelami, relacjami i ograniczeniami, ale bez danych.
Copy
Tworzy kopię obiektu klasy DataSet z wszystkimi jego tabelami, relacjami, ograniczeniami i danymi.
GetChanges
Tworzy kopię obiektu klasy DataSet, zawierającą tylko te wiersze, które zostały zmodyfikowane. Za pomocą opcjonalnego parametru można zdecydować, jaki typ zmian ma zawierać nowy obiekt (dodane, zmodyfikowane, usunięte lub niezmienione).
GetXml
Zwraca łańcuch, który jest reprezentacją XML obiektu klasy DataSet.
GetXmlSchema
Zwraca definicję schematu XML (XSD) obiektu klasy DataSet.
464
Część II
Wstęp do języka Visual Basic
Metoda
Opis
HasChanges
Zwraca wartość True, jeśli którakolwiek z tabel obiektu klasy DataSet zawiera nowe, zmodyfikowane lub usunięte wiersze.
Merge
Dołącza obiekt klasy DataSet, DataTable lub tablicę obiektów klasy DataRow do tego obiektu klasy DataSet.
ReadXml
Wczytuje dane XML ze strumienia lub pliku do obiektu klasy DataSet.
ReadXmlSchema
Wczytuje schemat XML ze strumienia lub pliku do obiektu klasy DataSet.
RejectChanges
Cofa wszystkie zmiany dokonane po załadowaniu obiektu klasy DataSet lub ostatnim wywołaniu metody AcceptChanges.
WriteXml
Wysyła dane obiektu klasy DataSet w formacie XML do strumienia lub pliku. Może opcjonalnie dołączyć schemat tego obiektu.
WriteXmlSchema
Zapisuje schemat XSD obiektu klasy DataSet w pliku XML lub strumieniu.
Niektóre z tych metod stanowią lustrzane odbicie innych metod, bardziej wyspecjalizowanych obiektów przechowujących dane. Na przykład metoda HasChanges zwraca wartość True, jeśli w którejkolwiek z tabel obiektu klasy DataSet zostały dokonane zmiany. Obiekty DataTable i DataRow również udostępniają metody HasChanges zwracające wartość True, jeśli jakieś zmiany zostały dokonane w ich bardziej ograniczonym zasięgu. Do tych lustrzanych metod należą AcceptChanges, Clear, Clone, Copy, GetChanges oraz RejectChanges. Więcej informacji na ich temat znajduje się w kolejnych podrozdziałach, w których opiszę obiekty klas DataTable i DataRow.
Klasa DataTable Klasa DataTable reprezentuje dane jednej tabeli znajdującej się w obiekcie klasy DataSet. Obiekt klasy DataTable zawiera obiekty klasy DataRow, które reprezentują jego dane, obiekty DataColumn, definiujące kolumny tej tabeli, obiekty ograniczeń danych tabeli (na przykład ograniczenie unikatowości wymaga, aby tylko jeden wiersz w danej kolumnie zawierał określoną wartość) oraz obiekty reprezentujące relacje między kolumnami tej tabeli i innych tabel. Ponadto udostępnia on metody i zdarzenia do wykonywania działań na wierszach. Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy DataTable. Własność
Opis
CaseSensitive
Określa, czy łańcuchy w obiektach klasy DataTable mają być porównywane z rozróżnianiem małych i wielkich liter.
ChildRelations
Kolekcja obiektów klasy DataRelation, które definiują relacje rodzic-dziecko, gdzie ta tabela jest rodzicem. Wyobraźmy sobie na przykład, że tabela Orders definiuje rekordy zamówień oraz zawiera pole OrderId. Przyjmijmy, że tabela OrderItems zawiera elementy zamówienia i posiada pole OrderId. Jeden rekord tabeli Orders może korespondować z wieloma rekordami tabeli OrderItems, które są połączone przez tę samą wartość pola OrderId. W tym przypadku tabela Orders jest rodzicem, a OrderItems dzieckiem.
Rozdział 20.
Kontrolki i obiekty baz danych
465
Własność
Opis
Columns
Kolekcja obiektów klasy DataColumn, które definiują kolumny tabeli (nazwę tabeli, typ danych, wartość domyślną, maksymalną długość itd.).
Constraints
Kolekcja obiektów klasy Constraint, które reprezentują ograniczenia na danych tabeli. Ograniczenie ForeignKeyConstraint wymaga, aby wartości z niektórych kolumn tabeli znajdowały się w innej tabeli (na przykład wartość State rekordu Addresses musi znajdować się także w kolumnie StateName tabeli States). Ograniczenie UniqueConstraint wymaga, aby wartości znajdujące się w zbiorze kolumn były unikatowe w skali tabeli (na przykład tylko jeden rekord Student może posiadać określoną parę wartości FirstName i LastName).
DataSet
Obiekt DataSet, który zawiera ten obiekt DataTable.
DefaultView
Zwraca obiekt klasy DataView, za pomocą którego można przeglądać, sortować i filtrować wiersze tabeli.
HasErrors
Zwraca wartość True, jeśli któryś z obiektów klasy DataTable zawiera błędy.
MinimumCapacity
Początkowa pojemność tabeli. Jeśli na przykład wiadomo, że do tabeli zostanie załadowanych 1000 rekordów, można tę własność ustawić na 1000, aby odpowiednia ilość pamięci została przydzielona za jednym razem, zamiast być zwiększana po trochu. Ta metoda jest nieco bardziej wydajna.
Namespace
Przestrzeń nazw obiektu klasy DataTable. Jeśli własność ta ma jakąś wartość, węzeł główny reprezentacji XML tego obiektu zawiera atrybut xmlns, na przykład .
ParentRelations
Kolekcja obiektów klasy DataRelation, które definiują relacje typu rodzic-dziecko, gdzie ta tabela jest dzieckiem. Więcej szczegółów na ten temat znajduje się w opisie własności ChildRelations.
Prefix
Wyznacza prefiks XML, który jest używany przez obiekt klasy DataTable jako alias jego przestrzeni nazw.
PrimaryKey
Pobiera lub tworzy tablicę obiektów klasy DataColumn, które definiują klucz główny tabeli. Ten ostatni zawsze jest unikatowy i daje najszybszy dostęp do rekordów.
Rows
Kolekcja obiektów klasy DataRow, które zawierają dane tabeli.
TableName
Nazwa tabeli.
Własności XML obiektu klasy DataTable mają wpływ na to, w jaki sposób obiekt ten odczytuje i zapisuje swoje dane w formacie XML. Jeśli na przykład własność Namespace ma wartość my_namespace, a Prefix ma wartość pfx, dane w formacie XML jednego z rekordów tego obiektu mogą wyglądać następująco: ArtAnt1
Tabela na następnej stronie zawiera zestawienie najbardziej przydatnych metod obiektów klasy DataTable.
466
Część II
Wstęp do języka Visual Basic
Metoda
Opis
AcceptChanges
Zatwierdza wszystkie modyfikacje dokonane na wierszach tabeli od ich załadowania lub ostatniego wywołania tej metody.
Clear
Usuwa wszystkie wiersze z tabeli.
Clone
Tworzy kopię obiektu klasy DataTable — z wszystkimi jego relacjami i ograniczeniami, ale bez danych.
Compute
Wyznacza wartość wyrażenia przy użyciu wierszy zaspokajających warunek filtru. Na przykład instrukcja tblTestScores.Compute("SUM(Score)", "StudentId = 1") oblicza sumę wartości kolumny Score obiektu klasy DataTable o nazwie tblTestScores, dla których StudentId jest równy 1.
Copy
Tworzy kopię obiektu klasy DataTable — z wszystkimi jego relacjami, ograniczeniami i danymi.
GetChanges
Tworzy kopię obiektu klasy DataTable, zawierającą tylko te wiersze, które zostały zmodyfikowane. Za pomocą opcjonalnego parametru można zdecydować, jaki typ zmian ma zawierać nowy obiekt (dodane, zmodyfikowane, usunięte lub niezmienione).
GetErrors
Tworzy tablicę obiektów klasy DataRow, które zawierają błędy.
ImportRow
Kopiuje dane z obiektu klasy DataRow do obiektu klasy DataTable.
LoadDataRow
Metoda ta przyjmuje jako parametr tablicę wartości. Przeszukuje tabelę w celu znalezienia wiersza zawierającego wartości pasujące do wartości klucza głównego tej tablicy. Jeśli go nie znajdzie, utworzy go przy użyciu tych wartości z tablicy. Metoda ta zwraca obiekt klasy DataRow, który znajdzie lub utworzy.
NewRow
Tworzy nowy obiekt klasy DataRow, który pasuje do schematu tabeli. Aby dodać do tabeli nowy wiersz, można utworzyć nowy obiekt klasy DataRow, zapełnić jego pola i użyć metody Rows.Add tej tabeli.
RejectChanges
Cofa wszystkie zmiany dokonane po załadowaniu obiektu klasy DataTable lub ostatnim wywołaniu metody AcceptChanges.
Select
Zwraca tablicę obiektów klasy DataRow, które zostały wybrane z tabeli. Opcjonalne parametry pozwalają na wyznaczenie wyrażenia filtrującego, do którego muszą pasować te wybrane wiersze, kolumn do posortowania i kolejności sortowania oraz stanów wierszy, które mają zostać wybrane (nowy, zmodyfikowany, usunięty itd.).
Obiekty klasy DataTable dodatkowo udostępniają kilka przydatnych zdarzeń, których zestawienie zawiera poniższa tabela. Zdarzenie
Opis
ColumnChanged
Ma miejsce po zmianie jakiejś wartości w wierszu.
ColumnChanging
Ma miejsce, gdy jakaś wartość w wierszu jest właśnie modyfikowana.
RowChanged
Ma miejsce po zmodyfikowaniu wiersza. Jeśli użytkownik zmodyfikuje kilka kolumn w wierszu, zdarzenie ColumnChanged zostanie uruchomione dla każdej z tych operacji. RowChanged jest uruchamiane, gdy użytkownik przechodzi do nowego wiersza.
RowChanging
Ma miejsce, gdy wiersz jest właśnie modyfikowany.
RowDeleted
Ma miejsce po usunięciu wiersza.
RowDeleting
Ma miejsce, gdy wiersz jest właśnie usuwany.
Rozdział 20.
Kontrolki i obiekty baz danych
467
Klasa DataRow Obiekty klasy DataRow reprezentują dane znajdujące się w jednym rekordzie obiektu klasy DataTable. Ten obiekt jest względnie prosty. Jego główną funkcję stanowi przechowywanie danych obiektu klasy DataTable — i to właśnie ten obiekt wykonuje większość czynności. Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektu klasy DataRow. Własność
Opis
HasErrors
Zwraca wartość True, jeśli dane w wierszu zawierają błąd.
Item
Pobiera lub tworzy wartość jednego z elementów wiersza. Istnieją przeciążone wersje tej własności z różnymi parametrami, które identyfikują kolumny. Parametr ten jest indeksem kolumny (wartości tych indeksów zaczynają się od zera), jej nazwą lub obiektem klasy DataColumn. Drugi opcjonalny parametr może wyznaczać wersję wiersza, dzięki czemu w wierszu, który został zmodyfikowany, da się odczytać jego pierwotną wersję.
ItemArray
Pobiera lub ustawia wszystkie wartości w wierszu przy użyciu tablicy obiektów ogólnego typu Object.
RowError
Pobiera lub ustawia tekst komunikatu o błędzie w wierszu.
RowState
Zwraca aktualny stan wiersza — Added, Deleted, Modified lub Unchanged.
Table
Zwraca referencję do obiektu DataTable, który zawiera ten wiersz.
Jeśli jakiś wiersz posiada komunikat o błędzie zdefiniowany przez własność RowError, kontrolka DataGrid wyświetla po jego lewej stronie odpowiedni wskaźnik w postaci czerwonego koła z białym wykrzyknikiem. Gdy najedzie się na niego kursorem, pojawi się chmurka z tekstem zapisanym we własności RowError. Na rysunku 20.17 widać, że własność RowError trzeciego wiersza została ustawiona na tekst Nie zarejestrowany. Rysunek 20.17. Kontrolka DataGrid oznacza wiersze, których własność RowError nie jest pusta
Poniższy kod pochodzi z aplikacji MemoryDataSetWithErrors, którą można pobrać z serwera FTP wydawnictwa Helion. Ustawia on błąd na trzeciej kolumnie drugiego wiersza (pamiętaj, że numery indeksów zaczynają się od zera) i na trzecim wierszu. Wynik tego działania widać na rysunku 20.17. students_table.Rows(1).SetColumnError(2, "Zły format imienia i nazwiska") students_table.Rows(2).RowError = "Nie zarejestrowany"
468
Część II
Wstęp do języka Visual Basic
Poniższa tabela zawiera zestawienie najbardziej przydatnych metod obiektów klasy DataRow. Metoda
Opis
AcceptChanges
Zatwierdza wszystkie modyfikacje dokonane na wierszu od jego załadowania lub ostatniego wywołania tej metody.
BeginEdit
Wprowadza wiersz w tryb edycji danych. Powoduje to zawieszenie wszelkich jego zdarzeń, dzięki czemu możliwe jest zmodyfikowanie kilku pól bez wywoływania zdarzeń walidacji. Metoda BeginEdit jest niejawnie wywoływana, gdy użytkownik zmodyfikuje wartość związanej z wierszem kontrolki, a EndEdit, gdy zostanie wywołana metoda AcceptChanges. Choć jest to tryb edycji, zachowywane są zarówno wartość pierwotna, jak i zmodyfikowana, dzięki czemu zmiany można zatwierdzić za pomocą metody EndEdit lub cofnąć za pomocą metody CancelEdit.
CancelEdit
Anuluje dokonane modyfikacje w wierszu i przywraca pierwotne wartości.
ClearErrors
Czyści błędy kolumny wiersza i wiersza.
Delete
Usuwa wiersz.
GetChildRows
Zwraca tablicę obiektów klasy DataRow, które reprezentują wiersze potomne tego wiersza, zgodnie z relacją rodzic-dziecko.
GetColumnError
Zwraca treść błędu przypisanego do kolumny.
GetParentRow
Zwraca obiekt klasy DataRow, który reprezentuje rekord nadrzędny tego wiersza, zgodnie z relacją rodzic-dziecko.
GetParentRows
Zwraca tablicę obiektów klasy DataRow, które reprezentują rekordy nadrzędne tego wiersza, zgodnie z relacjami danych.
HasVersion
Zwraca wartość True, jeśli wiersz jest w określonej wersji (Current, Default, Original lub Proposed). Na przykład podczas edycji wiersz ma wersje Current i Proposed.
IsNull
Informuje, czy określona kolumna zawiera wartość NULL.
RejectChanges
Cofa wszystkie zmiany dokonane w wierszu od załadowania danych lub ostatniego wywołania metody AcceptChanges.
SetColumnError
Ustawia treść komunikatu o błędzie dla jednej z kolumn wiersza. Jeśli kolumna posiada komunikat o błędzie, kontrolka DataGrid wyświetla po jej lewej stronie czerwone koło z białym wykrzyknikiem. Na rysunku 20.17 widać ten znak przy drugiej kolumnie drugiego wiersza. Jeśli najedziesz na niego kursorem, wyświetli się chmurka z komunikatem.
SetParentRow
Ustawia rodzica wiersza zgodnie z relacjami danych.
Klasa DataColumn Obiekty DataColumn reprezentują kolumny znajdujące się w obiektach klasy DataTable. Definiują one nazwę i typ danych przechowywanych w kolumnie, przy użyciu których można definiować relacje między różnymi kolumnami. Tabela na następnej stronie zawiera zestawienie najbardziej przydatnych własności obiektów klasy DataColumn.
Rozdział 20.
Kontrolki i obiekty baz danych
469
Własność
Opis
AllowDBNull
Określa, czy kolumna może zawierać wartości NULL.
AutoIncrement
Określa, czy nowe wiersze automatycznie generują automatycznie inkrementowane wartości dla kolumny.
AutoIncrementSeed
Określa wartość początkową dla automatycznie inkrementowanej kolumny.
AutoIncrementStep
Określa, o jaką wartość ma być zwiększana wartość automatycznie inkrementowanej kolumny.
Caption
Pobiera lub ustawia podpis kolumny. Należy pamiętać, że niektóre kontrolki mogą nie używać tej wartości. Na przykład kontrolka DataGrid wyświetla wartość własności ColumnName, a nie Caption.
ColumnMapping
Określa sposób zapisu kolumny w formacie XML. Własność ta może mieć jedną z następujących wartości — Attribute (kolumna jest zapisywana jako atrybut elementu reprezentującego wiersz), Element (kolumna jest zapisywana jako podelement), Hidden (kolumna nie jest zapisywana) lub SimpleContent (kolumna jest zapisywana jako XmlText wewnątrz elementu reprezentującego wiersz). Jeśli kolumna jest ukryta, kontrolka DataGrid nie wyświetla jej wartości. Przykład użycia tej własności znajduje się pod tą tabelą.
ColumnName
Wyznacza nazwę kolumny w obiekcie klasy DataTable. Pamiętajmy, że adaptery danych wykorzystują nazwy kolumn do rzutowania ich na kolumny obiektu klasy DataSet. Zatem jeśli własność ta zostanie zmodyfikowana bez aktualizacji odwzorowań, jej kolumna może nie zostać wypełniona danymi.
DataType
Określa typ danych kolumny. Jeśli własność ta zostanie zmodyfikowana po rozpoczęciu ładowania danych przez obiekt klasy DataTable, Visual Basic zgłosi błąd. Typy obsługiwane przez język to: Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64, SByte, Single, String, TimeSpan, UInt16, UInt32 oraz UInt64.
DefaultValue
Wyznacza wartość domyślną, która jest przypisywana do kolumny w nowych wierszach.
Expression
Wyznacza wyrażenie dla kolumny. Za pomocą tej własności można tworzyć kolumny obliczane. Na przykład użycie wyrażenia Quantity * Price spowoduje, że w kolumnie będzie wyświetlany wynik mnożenia ilości towaru przez jego cenę.
MaxLength
Wyznacza maksymalną długość kolumny tekstowej.
Namespace
Przestrzeń nazw kolumny. Jeśli własność ta posiada jakąś wartość, węzły główne XML wierszy mają atrybut xmlns, na przykład 12.
Ordinal
Zwraca indeks kolumny, pod jakim występuje w kolekcji Columns obiektu klasy DataTable.
Prefix
Wyznacza prefiks używany przez obiekt klasy DataColumn jako alias jego przestrzeni nazw. Jeśli na przykład przestrzeń nazw to my_namespace, a prefiks to pfx, pole StudentId wiersza w XML może wyglądać następująco: 12.
ReadOnly
Określa, czy kolumnę można modyfikować po utworzeniu rekordu.
Table
Zwraca referencję do obiektu klasy DataTable, który zawiera tę kolumnę.
Unique
Określa, czy różne wiersze w tabeli mogą mieć w tej kolumnie takie same wartości.
470
Część II
Wstęp do języka Visual Basic
Poniższy fragment kodu — definiujący odwzorowania XML kolumn tabeli Students — pochodzi z programu MemoryDataSetXmlMappedColumns, który można pobrać z serwera FTP wydawnictwa Helion. Zgodnie z tym zapisem kolumny FirstName i LastName powinny zostać zapisane jako atrybuty elementów reprezentujących wiersze, a kolumna StudentId jako XmlText. Należy zauważyć, że nie można użyć wartości SimpleContent własności ColumnMapping, jeśli którakolwiek inna kolumna ma własność ColumnMapping ustawioną na wartość Element lub SimpleContent. students_table.Columns("FirstName").ColumnMapping = MappingType.Attribute students_table.Columns("LastName").ColumnMapping = MappingType.Attribute students_table.Columns("StudentId").ColumnMapping = MappingType.SimpleContent
Poniżej znajduje się fragment kodu XML, który reprezentuje informacje o studentach:
FirstName="Art" FirstName="Bev" FirstName="Cid" FirstName="Deb"
Poniższy fragment programu tworzy elementy z kolumn FirstName i LastName jako podelementy elementów reprezentujących wiersze tabeli Students, a kolumnę StudentId zapisuje jako atrybut: students_table.Columns("FirstName").ColumnMapping = MappingType.Element students_table.Columns("LastName").ColumnMapping = MappingType.Element students_table.Columns("StudentId").ColumnMapping = MappingType.Attribute
Poniżej znajduje się wynik działania tego kodu: ArtAntBevBugCidCatDebDove
Klasa DataRelation Obiekty klasy DataRelation reprezentują relacje typu rodzic-dziecko między zestawami kolumn znajdujących się w różnych tabelach. Wyobraźmy sobie na przykład, że mamy bazę danych zawierającą tabelę Students z polami FirstName, LastName oraz StudentId. Natomiast tabela TestScores posiada pola StudentId, TestNumber oraz Score. Pola StudentId łączą te
Rozdział 20.
Kontrolki i obiekty baz danych
471
dwie tabele w związku rodzic-dziecko. Oznacza to, że każdy rekord w tabeli Students może korespondować z dowolną liczbą rekordów w tabeli TestScores. W tym przykładzie rodzicem jest tabela Students, a dzieckiem — TestScores. Relację tę definiuje poniższy kod. Polem nadrzędnym jest Students.StudentId, a podrzędnym — TestScores.StudentId. ' Utworzenie relacji łączącej pola StudentId dwóch tabel. scores_dataset.Relations.Add( _ "Student Test Scores", _ students_table.Columns("StudentId"), _ test_scores_table.Columns("StudentId"))
Obiekt klasy DataRelation może nawet połączyć relacyjnie więcej niż jedną kolumnę w tych dwóch tabelach. Tabele te można na przykład połączyć za pomocą kombinacji pól LastName i FirstName. W większości programów nie ma potrzeby modyfikować relacji po jej utworzeniu. Przedstawiona w powyższym fragmencie kodu metoda Relations.Add obiektu klasy DataSet tworzy relację, którą w zasadzie można już dalej pozostawić bez zmian. Jeśli jednak konieczne będzie zmodyfikowanie jej, można będzie to zrobić za pomocą metod i własności udostępnianych przez ich obiekty. Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy DataRelation. Własność
Opis
ChildColumns
Zwraca tablicę obiektów klasy DataColumn, które reprezentują kolumny podrzędne.
ChildKeyConstraint
Zwraca obiekt klasy ForeignKeyConstraint, który definiuje ograniczenie na tę relację. Za jego pomocą można określić zachowanie relacji, gdy program aktualizuje, usuwa lub modyfikuje wartości używane w tym związku. Jeśli na przykład pole StudentId łączy tabele Students i TestScores, a zostanie usunięty rekord z tej pierwszej, przy użyciu tej relacji będzie można usunąć wszystkie odpowiadające mu rekordy w tabeli TestScores.
ChildTable
Zwraca obiekt klasy DataTable, który reprezentuje tabelę podrzędną tej relacji.
DataSet
Zwraca referencję do obiektu klasy DataSet, który zawiera tę relację.
Nested
Określa, czy dane podrzędne mają być zagnieżdżane w wierszach nadrzędnych w reprezentacji XML obiektu klasy DataSet. Więcej szczegółów na ten temat znajduje się pod tabelą.
ParentColumns
Zwraca tablicę obiektów klasy DataColumn, które reprezentują kolumny nadrzędne.
ParentKeyConstraint
Zwraca obiekt klasy UniqueConstraint na tę relację. Obiekt ten wymaga, by wartości w kolumnach nadrzędnych były unikatowe w skali tabeli nadrzędnej.
ParentTable
Zwraca obiekt klasy DataTable, który reprezentuje tabelę nadrzędną tej relacji.
RelationName
Określa nazwę relacji.
472
Część II
Wstęp do języka Visual Basic
W reprezentacji XML obiektu klasy DataSet tabele z reguły są zapisywane osobno. Można jednak za pomocą własności Nested sprawić, by rekordy jednej tabeli zostały zagnieżdżone w innej. Wyobraźmy sobie na przykład, że tabele Students i TestScores są połączone wspólnym polem StudentId. Jeśli własność Nested tej relacji zostanie ustawiona na True, w reprezentacji XML rekordy tabeli TestScores każdego studenta zostałyby zagnieżdżone w rekordach tabeli Students, na przykład: DebDove441814268 ...
Powyższy kod XML pochodzi z programu MemoryDataSetNestedXml, który można pobrać z serwera FTP wydawnictwa Helion. Należy zauważyć, że w tej reprezentacji wartość StudentId tabeli TestScores jest niepotrzebna, ponieważ taką samą zawiera podelement StudentId elementu Students. Gdyby własność ColumnMapping kolumny TestScores.StudentId została ustawiona na wartość Hidden, te niepotrzebne elementy zniknęłyby, a rezultat byłby następujący: DebDove4181268 ...
Ograniczenia Ograniczenia to pewne restrykcje dotyczące danych w kolumnach tabeli. Obiekt klasy DataSet obsługuje dwa rodzaje obiektów ograniczeń:
Rozdział 20.
Kontrolki i obiekty baz danych
473
Obiekty klasy ForeignKeyConstraint ograniczają wartości w jednej tabeli na podstawie wartości w innej. Można na przykład zażądać, by wartości znajdujące się w polu State tabeli Addresses znajdowały się w polu StateName tabeli States. W ten sposób zapobiegnie się utworzeniu przez program rekordu w tabeli Addresses, którego pole State ma wartość XZ.
Obiekty klasy UniqueConstraint wymagają, aby jedno pole lub kombinacja dwóch i więcej pól w jednej tabeli były unikatowe. Na przykład tabela Employee może wymagać, by kombinacje wartości FirstName i LastName były unikatowe. Dzięki temu pewne jest, że program nie utworzy rekordów w tabeli Employees z takimi samymi wartościami FirstName i LastName.
W kolejnych podrozdziałach bardziej szczegółowo opiszę te dwa typy obiektów ograniczeń.
Klasa ForeignKeyConstraint Poza wymuszaniem, by wartości z jednej tabeli istniały w innej tabeli, obiekt klasy ForeignKeyConstraint może także wyznaczać sposób propagacji zmian dokonanych w jednej tabeli do drugiej. Wyobraźmy sobie na przykład, że tabela Addresses posiada ograniczenie ForeignKeyConstraint, które wymaga, by jej pole State zawierało jedną z wartości znajdujących się w polu StateName tabeli States. Usunięcie rekordu dla stanu Kolorado z tabeli States może spowodować automatyczne usunięcie przez ograniczenie wszystkich rekordów z tabeli Addresses, w których została użyta nazwa tego stanu. Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy ForeignKeyConstraint. Własność
Opis
AcceptRejectRule
Określa działanie podejmowane po wykonaniu metody AcceptChanges. Własność ta może przyjmować wartość None (nic nie robi) lub Cascade (aktualizuje wartości pól potomka, aby dopasować je do nowych wartości rodzica).
Columns
Zwraca tablicę referencji do kolumn potomnych ograniczenia.
ConstraintName
Wyznacza nazwę ograniczenia.
DeleteRule
Określa działanie podejmowane w wyniku usunięcia wiersza. Własność ta może przyjmować następujące wartości: Cascade (usuwa wiersze potomne), None (nic nie robi), SetDefault (ustawia pola potomne na wartości domyślne) oraz SetNull (zmienia wartości pól potomnych na NULL).
RelatedColumns
Zwraca tablicę referencji do kolumn nadrzędnych ograniczenia.
RelatedTable
Zwraca referencję do nadrzędnej tabeli ograniczenia.
Table
Zwraca referencję do podrzędnej tabeli ograniczenia.
UpdateRule
Określa, jakie działanie ma zostać podjęte w odpowiedzi na aktualizację wiersza. Własność ta może przyjmować następujące wartości: Cascade (aktualizuje wartości wierszy potomnych, aby pasowały), None (nic nie robi), SetDefault (ustawia pola potomne na wartości domyślne) oraz SetNull (zmienia wartości pól potomnych na NULL).
474
Część II
Wstęp do języka Visual Basic
Poniższy kod tworzy ograniczenie klucza obcego, które wiąże relacyjnie pole nadrzędne Students.StudentId z polem podrzędnym TestScores.StudentId: scores_dataset.Relations.Add( _ "Wyniki testów studenta", _ students_table.Columns("StudentId"), _ test_scores_table.Columns("StudentId"))
Podobny kod — definiujący relację pomiędzy tabelami Students i TestScores — został użyty w programie MemoryDataSet, który można pobrać z serwera FTP wydawnictwa Helion.
Klasa UniqueConstraint Aby wymusić, by w jednej kolumnie wszystkie wartości były unikatowe, można ustawić własność Unique tej kolumny na True. Spowoduje to automatyczne utworzenie obiektu klasy UniqueConstraint i dodanie go do obiektu klasy DataTable. Poniższy fragment kodu demonstruje, w jaki sposób można wymusić, by kolumna StudentId tabeli Students miała same unikatowe wartości: students_table.Columns("StudentId").Unique = True
Za pomocą konstruktora obiektów klasy UniqueConstraint można utworzyć wymóg, by grupa pól miała unikatową wartość kombinowaną. Poniższy fragment kodu tworzy tablicę obiektów klasy DataColumn, które reprezentują pola FirstName i LastName tabeli Students. Program ten następnie przekazuje tę tablicę do konstruktora obiektów klasy UniqueConstraint, dzięki czemu zostaje utworzone ograniczenie, polegające na tym, że pary FirstName-LastName są unikatowe w skali tabeli. ' Wymusza, by kombinacje wartości FirstName-LastName były unikatowe. Dim first_last_columns() As DataColumn = { _ students_table.Columns("FirstName"), _ students_table.Columns("LastName") _ } students_table.Constraints.Add( _ New UniqueConstraint(first_last_columns))
Po wykonaniu tego kodu program może utworzyć dwa rekordy z takimi samymi wartościami FirstName lub LastName, ale nie powstaną dwa rekordy, w których obie te wartości są takie same. Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy UniqueConstraint. Własność
Opis
Columns
Zwraca tablicę obiektów klasy DataColumn, reprezentujących te kolumny, które muszą być unikatowe.
ConstraintName
Określa nazwę ograniczenia.
IsPrimaryKey
Zwraca True, jeśli kolumny te stanowią klucz główny tabeli.
Table
Zwraca referencję do obiektu klasy TableData, który zawiera to ograniczenie.
Rozdział 20.
Kontrolki i obiekty baz danych
475
Program MemoryDataSet, który można pobrać z serwera FTP wydawnictwa Helion, definiuje kilka ograniczeń unikatowości. Jedno z nich wymaga, by pole StudentId było unikatowe, inne zaś — by kombinacje FirstName-LastName były unikatowe.
Klasa DataView Obiekty klasy DataView reprezentują dające się konfigurować widoki danych znajdujących się w obiektach klasy DataTable. Za pomocą tego obiektu można wybrać niektóre lub wszystkie dane z obiektu klasy DataTable i wyświetlić je po uprzednim posortowaniu, jednocześnie nie modyfikując w żaden sposób tego obiektu DataTable. W programie wolno użyć kilku obiektów klasy DataView, z których każdy może wybierać i sortować dane w inny sposób. Następnie obiekty te można powiązać z kontrolkami typu DataGrid, aby umożliwić wyświetlanie różnych widoków. Jeśli którykolwiek z tych widoków modyfikuje swoje dane, na przykład dodaje lub usuwa wiersze, aktualizowany jest podstawowy obiekt klasy DataTable oraz wszystkie pozostałe widoki, które muszą uwzględnić te zmiany. Poniższy kod, demonstrujący użycie kontrolek DataGrid, pochodzi z programu DataGrids, który można pobrać z serwera FTP wydawnictwa Helion. Private Sub Form1_Load() Handles MyBase.Load ' Utworzenie obiektu klasy DataTable. Dim contacts_table As New DataTable("Contacts") ' Dodanie kolumn. contacts_table.Columns.Add("FirstName", GetType(String)) contacts_table.Columns.Add("LastName", GetType(String)) contacts_table.Columns.Add("Street", GetType(String)) contacts_table.Columns.Add("City", GetType(String)) contacts_table.Columns.Add("State", GetType(String)) contacts_table.Columns.Add("Zip", GetType(String)) ' Sprawienie, że kombinacje FirstName-LastName będą unikatowe. Dim first_last_columns() As DataColumn = { _ contacts_table.Columns("FirstName"), _ contacts_table.Columns("LastName") _ } contacts_table.Constraints.Add( _ New UniqueConstraint(first_last_columns)) ' Dane adresowe. contacts_table.Rows.Add(New Object() {"Art", "Ant", _ "1234 Ash Pl", "Bugville", "CO", "11111"}) contacts_table.Rows.Add(New Object() {"Bev", "Bug", _ "22 Beach St", "Bugville", "CO", "22222"}) contacts_table.Rows.Add(New Object() {"Cid", "Cat", _ "3 Road Place Lane", "Programmeria", "KS", "33333"}) contacts_table.Rows.Add(New Object() {"Deb", "Dove", _ "414 Debugger Way", "Programmeria", "KS", "44444"})
476
Część II
Wstęp do języka Visual Basic
contacts_table.Rows.Add(New Object() {"Ed", "Eager", _ "5746 Elm Blvd", "Bugville", "CO", "55555"}) contacts_table.Rows.Add(New Object() {"Fran", "Fix", _ "647 Foxglove Ct", "Bugville", "CO", "66666"}) contacts_table.Rows.Add(New Object() {"Gus", "Gantry", _ "71762-B Gooseberry Ave", "Programmeria", "KS", "77777"}) contacts_table.Rows.Add(New Object() {"Hil", "Harris", _ "828 Hurryup St", "Programmeria", "KS", "88888"}) ' Związanie siatki grdAll z obiektem klasy DataTable. grdAll.DataSource = contacts_table grdAll.CaptionText = "All Records" ' Utworzenie widoku DataView dla State = CO. Dim dv_co As New DataView(contacts_table) dv_co.RowFilter = "State = 'CO'" grdCO.DataSource = dv_co grdCO.CaptionText = "CO Records" ' Utworzenie widoku DataView dla FirstName > = E. Dim dv_name As New DataView(contacts_table) dv_name.RowFilter = "FirstName >= 'E’" grdName.DataSource = dv_name grdName.CaptionText = "FirstName >= E" End Sub
Program ten tworzy obiekt klasy DataTable o nazwie Contacts, który zawiera pola FirstName, LastName , Street , City , State oraz Zip . Nakłada ograniczenie unikatowości na pary FirstName-LastName oraz dodaje do tabeli kilka wierszy danych. Następnie wiąże z nią kontrolkę DataGrid o nazwie grdAll. Później program ten tworzy na podstawie tej tabeli obiekt klasy DataView o nazwie dv_co, w taki sposób ustawia jego własność RowFilter, aby wybierał te wiersze, których pole State zawiera wartość CO, a także wiąże ten obiekt DataView z kontrolką DataGrid o nazwie grdCO. Na końcu tworzy jeszcze jeden obiekt klasy DataView z własnością RowFilter, wybierającą rekordy, których pole FirstName jest równe lub większe od E, a także wiąże go z kontrolką DataGrid o nazwie grdName. Rezultat działania tego kodu przedstawiono na rysunku 20.18. Znajdująca się na górze kontrolka DataGrid jest związana z obiektem DataTable; wyświetla wszystkie wiersze tabeli. Druga kontrolka DataGrid jest związana z obiektem klasy DataView o nazwie dv_co; wyświetla rekordy, w których State = CO. Najniższa kontrolka DataGrid jest związana z obiektem klasy DataView o nazwie dv_name, a więc wyświetla rekordy, w których FirstName>= E. Jeśli dane w którejkolwiek z tych kontrolek zostaną zmodyfikowane, pozostałe kontrolki natychmiast odzwierciedlą te zmiany. Klasa DataView została zaprojektowana bardziej z myślą o wyświetlaniu danych niż o ich przechowywaniu. W zasadzie operuje ona na danych znajdujących się w obiektach klasy DataTable, a więc nie udostępnia funkcji do zarządzania relacjami i ograniczeniami, do których dostęp zapewniała ta klasa. Oferuje natomiast łącza do reprezentowanych przez siebie obiektów klasy DataRow. Z tych ostatnich można w razie potrzeby dojść do ich obiektów klasy DataTable.
Rozdział 20.
Kontrolki i obiekty baz danych
477
Rysunek 20.18. Różne obiekty klasy DataView mogą wyświetlać odmienne widoki danych
Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy DataView. Własność
Opis
AllowDelete
Określa, czy można usuwać wiersze z obiektu klasy DataView. Jeśli własność ta ma wartość False, kontrolki typu DataGrid, które są związane z tym obiektem, nie będą pozwalały na usuwanie z niego wierszy.
AllowEdit
Określa, czy można edytować wiersze obiektu klasy DataView. Jeśli własność ta ma wartość False, kontrolki typu DataGrid, które są związane z tym obiektem, nie będą pozwalały na edytowanie jego wierszy.
AllowNew
Określa, czy do obiektu klasy DataView można wstawiać nowe wiersze. Jeśli własność ta ma wartość False, kontrolki typu DataGrid, które są związane z tym obiektem, nie będą pozwalały na wstawianie do niego wierszy.
Count
Zwraca liczbę wierszy wybranych przez widok.
Item
Zwraca obiekt klasy DataRowView, który reprezentuje wiersz w widoku.
RowFilter
Łańcuch, który określa wybrane przez widok rekordy.
RowStateFilter
Określa stan rekordów, które powinny zostać wybrane przez widok. Własność ta może przyjmować następujące wartości: Added, CurrentRows (wiersze niezmodyfikowane, nowe i zmodyfikowane), Deleted, ModifiedCurrent (bieżąca wersja zmodyfikowanych wierszy), ModifiedOriginal (oryginalna wersja zmodyfikowanych wierszy), None, OriginalRows (wiersze oryginalne, niezmienione i usunięte) oraz Unchanged.
478
Część II
Wstęp do języka Visual Basic
Własność
Opis
Sort
Łańcuch wyznaczający kolumny, według których powinny zostać posortowane dane. Na przykład łańcuch "State, City, Zip" oznacza sortowanie najpierw według kolumny State, później City, a na końcu Zip.
Table
Wyznacza obiekt klasy DataTable, który jest podstawą tego obiektu klasy DataView.
Poniższa tabela zawiera zestawienie niektórych najbardziej przydatnych metod klasy DataView. Metoda
Opis
AddNew
Dodaje nowy wiersz do obiektu klasy DataTable, na którym opiera się ten obiekt klasy DataView.
Delete
Usuwa wiersz z określonym indeksem z obiektu klasy DataTable, na którym opiera się ten obiekt klasy DataView.
Find
Zwraca indeks wiersza pasującego do kluczowych kolumn sortowania widoku. Metoda ta zwraca wartość -1, jeśli żaden wiersz nie pasuje do przekazanych do niej wartości. Jeśli liczba przekazanych wartości nie pasuje do liczby wartości kluczowych obiektu DataView, zgłasza błąd.
FindRows
Zwraca tablicę obiektów klasy DataRowView, które reprezentują wiersze pasujące do kluczowych kolumn sortowania widoku.
Własność Sort obiektów klasy DataView określa nie tylko pola, według których mają być sortowane dane, lecz także pola kluczowe używane przez metodę Find. Poniższy kod sortuje obiekt klasy DataView o nazwie dv_name według pól FirstName i LastName. Następnie za pomocą metody Find wyświetla indeks wiersza, w którym FirstName = Hil, a LastName = Harris. dv_name.Sort = "FirstName, LastName" MessageBox.Show(dv_name.Find(New String() {"Hil", "Harris"}).ToString)
Klasa DataRowView Obiekty klasy DataRow mogą przechowywać dane w więcej niż jednym stanie. Jeśli na przykład zostanie zmodyfikowany wiersz obiektu klasy DataTable, jego obiekt klasy DataRow będzie zawierać oryginalne oraz zmienione dane tego wiersza. Obiekt klasy DataRowView reprezentuje widok obiektu klasy DataRow w jednym określonym stanie. Możliwe stany to Current, Default, Original oraz Proposed. Obiekt klasy DataView zawiera obiekty klasy DataRowView, reprezentujące widok obiektu klasy DataTable, który wybiera konkretne wiersze w określonym stanie. Przeznaczeniem obiektów klasy DataRowView jest reprezentowanie obiektów w jednym określonym stanie, dzięki czemu są one względnie proste. Głównie wskazują wybrany stan i odwołują się do obiektów klasy DataRow.
Rozdział 20.
Kontrolki i obiekty baz danych
479
Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy DataRowView. Własność
Opis
DataView
Obiekt klasy DataView, który zawiera ten obiekt klasy DataRowView.
IsEdit
Zwraca wartość True, jeśli wiersz jest w trybie edycji.
IsNew
Zwraca wartość True, jeśli wiersz jest nowy.
Item
Pobiera lub ustawia wartość jednego z pól wiersza.
Row
Obiekt klasy DataRow, który jest reprezentowany przez ten obiekt klasy DataRowView.
RowVersion
Wersja obiektu klasy DataRow, który jest reprezentowany przez ten obiekt (Current, Default, Original lub Proposed).
Proste wiązanie danych Związanie prostej własności typu Text ze źródłem danych jest względnie proste. Najpierw należy utworzyć obiekt klasy DataSet, DataTable lub DataView, który będzie służył jako źródło danych. Można to zrobić zarówno w czasie projektowania przy użyciu kontrolek, jak i podczas działania programu za pomocą zmiennych obiektowych. Jeśli źródło danych jest budowane podczas projektowania, w tym samym czasie można także dowiązać własność. W tym celu zaznacz wybraną kontrolkę i otwórz okno Properties. Następnie rozwiń pozycję (DataBindings) i odszukaj własność, którą chcesz dowiązać (na przykład Text). Kliknij znajdującą się po prawej stronie strzałkę, po czym w okienku, które się pojawi, wybierz źródło danych, jakie chcesz powiązać z tą własnością. Na rysunku 20.19 przedstawiono okno wiążące własność Text kontrolki o nazwie txtTitle z polem Title tabeli Books, która znajduje się w obiekcie klasy DataSet o nazwie booksDataSet. W czasie działania programu można przypisać prostą własność do źródła danych za pomocą kolekcji DataBindings kontrolki. Metoda Add tej kolekcji przyjmuje jako parametry nazwę własności, która ma zostać przywiązana, źródło danych oraz nazwę elementu w źródle danych, który ma zostać przywiązany. Poniższa instrukcja wiąże własność Text kontrolki o nazwie txtUrl z polem URL tabeli Books, która znajduje się w obiekcie klasy DataSet o nazwie dsBooks: txtUrl.DataBindings.Add("Text", dsBooks.Books, "URL")
Po związaniu pierwszej własności Visual Basic dodaje do formularza kontrolkę BindingSource. Można za jej pomocą wiązać także inne własności. Po otwarciu listy rozwijanej widocznej na rysunku 20.19 rozwiń istniejącą już kontrolkę BindingSource, zamiast tworzyć nową.
480
Część II
Wstęp do języka Visual Basic
Rysunek 20.19. Proste własności kontrolek można przypisywać do źródeł danych w czasie projektowania
To wszystko, jeśli chodzi o wiązanie prostych własności. Nie udostępnia ono samo w sobie żadnych funkcji nawigacyjnych. Gdyby zostały związane własności Text kilku kontrolek TextBox, po uruchomieniu programu widoczne byłyby tylko dane pierwszego rekordu źródła danych. Aby umożliwić użytkownikowi przeglądanie źródła danych, należy użyć obiektu klasy CurrencyManager.
Klasa CurrencyManager Niektóre kontrolki, na przykład DataGrid, posiadają własne interfejsy nawigacyjne. Jeśli DataGrid zostanie związana z obiektem klasy DataSet, użytkownik będzie mógł przeglądać tabele tego obiektu, przeglądać i edytować jego dane oraz przechodzić po łączach między tymi tabelami. Kontrolka DataGrid posiada swoje własne metody do nawigacji po danych. Natomiast w przypadku prostszych kontrolek, jak TextBox, które mogą wyświetlać tylko jedną wartość naraz, konieczne jest własnoręczne dostarczenie jakiegoś sposobu nawigacji. Do ustawiania źródła danych w określonym miejscu służy obiekt klasy CurrencyManager. Nadzoruje on listę obiektów klasy Binding, które wiążą źródło danych z takimi kontrolkami, jak na przykład TextBox.
Rozdział 20.
Kontrolki i obiekty baz danych
481
Poniższa tabela zawiera zestawienie najbardziej przydatnych własności obiektów klasy CurrencyManager. Własność
Opis
Bindings
Kolekcja wiązań, którymi zarządza obiekt.
Count
Zwraca liczbę wierszy skojarzonych z obiektem klasy CurrencyManager.
Current
Zwraca referencję do aktualnego obiektu danych (wiersza).
List
Zwraca obiekt implementujący interfejs IList, który dostarcza danych dla obiektu klasy CurrencyManager. Jeśli na przykład źródłem danych jest obiekt klasy DataSet lub DataTable, ten obiekt jest klasy DataView.
Position
Sprawdza lub ustawia aktualną pozycję w zbiorze danych. Na przykład w obiekcie klasy DataTable jest to numer wiersza.
Obiekty klasy CurrencyManager udostępniają także kilka metod do manipulowania danymi. Poniższa tabela zawiera zestawienie najbardziej przydatnych z nich. Metoda
Opis
AddNew
Dodaje nowy element do źródła danych.
CancelCurrentEdit
Anuluje bieżącą operację edycji.
EndCurrentEdit
Kończy bieżące edytowanie i zatwierdza wszystkie zmiany.
Refresh
Ponownie zapełnia związane z obiektem kontrolki.
RemoveAt
Usuwa element znajdujący się pod określonym indeksem.
Kiedy pozycja obiektu klasy CurrencyManager w zbiorze danych zmienia się, zgłasza on zdarzenie PositionChanged. Poniższy kod źródłowy pochodzi z programu o nazwie BindSimple, który można pobrać z serwera FTP wydawnictwa Helion. Służy on do eksploracji zbioru danych DataSet. Public Class Form1 Private WithEvents m_CurrencyManager As CurrencyManager Private Sub Form1_Load() Handles MyBase.Load 'TODO: This line of code loads data into the _ 'BooksDataSet.Books’ table. You can move, or remove it, as needed. Me.BooksTableAdapter.Fill(Me.BooksDataSet.Books) ' Utworzenie obiektu klasy CurrencyManager. m_CurrencyManager = DirectCast(Me.BindingContext( _ BooksBindingSource), CurrencyManager) ' Wyświetlenie numeru rekordu. m_CurrencyManager_PositionChanged() End Sub ' Przejście do poprzedniego rekordu. Private Sub btnPrev_Click() Handles btnPrev.Click
482
Część II
Wstęp do języka Visual Basic
If m_CurrencyManager.Position = 0 Then Beep() Else m_CurrencyManager.Position -= 1 End If End Sub ' Przejście do kolejnego rekordu. Private Sub btnNext_Click() Handles btnNext.Click If m_CurrencyManager.Position >= m_CurrencyManager.Count - 1 Then Beep() Else m_CurrencyManager.Position += 1 End If End Sub ' Przejście do pierwszego rekordu. Private Sub btnFirst_Click() Handles btnFirst.Click m_CurrencyManager.Position = 0 End Sub ' Przejście do ostatniego rekordu. Private Sub btnLast_Click() Handles btnLast.Click m_CurrencyManager.Position = m_CurrencyManager.Count - 1 End Sub Private Sub m_CurrencyManager_PositionChanged() _ Handles m_CurrencyManager.PositionChanged lblPosition.Text = _ (m_CurrencyManager.Position + 1) & " z " m_CurrencyManager.Count End Sub
&
_
' Dodanie rekordu. Private Sub btnAdd_Click() Handles btnAdd.Click m_CurrencyManager.AddNew() txtTitle.Focus() End Sub ' Usunięcie bieżącego rekordu. Private Sub btnDelete_Click() Handles btnDelete.Click If MessageBox.Show("Czy na pewno chcesz usunąć ten rekord?", _ "Dalej?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) _ = Windows.Forms.DialogResult.Yes _ Then m_CurrencyManager.RemoveAt(m_CurrencyManager.Position) End If End Sub End Class
W czasie ładowania formularza program wypełnia jego zbiór danych i zapisuje referencję do obiektu klasy CurrencyManager, który pozwala kontrolować tabelę Books tego zbioru. Następnie wywołuje podprocedurę m_CurrencyManager_PositionChanged, aby wyświetlić indeks bieżącego rekordu (o tym piszę nieco dalej).
Rozdział 20.
Kontrolki i obiekty baz danych
483
Przyciski pozwalające przejść do poprzedniego i kolejnego rekordu działają na zasadzie zmiany własności Position obiektu klasy CurrencyManager. Na przykład procedura obsługi zdarzeń przycisku przenoszącego do poprzedniego rekordu sprawdza, czy indeks pozycji jest większy od zera, a jeśli tak, zmniejsza go o jeden. Analogicznie — przycisk przenoszący do następnego rekordu zwiększa indeks bieżącej pozycji o jeden, jeśli obiekt CurrencyManager nie wyświetla właśnie ostatniego rekordu. Przyciski przenoszące do pierwszego i ostatniego rekordu ustawiają odpowiednio na indeksy pierwszego i ostatniego rekordu. Gdy zmienia się pozycja obiektu klasy CurrencyManager, za każdym razem zostaje wykonana jego procedura obsługi zdarzeń PositionChanged. Procedura ta wyświetla indeks bieżącego rekordu na etykiecie. Kiedy użytkownik kliknie przycisk dodawania rekordów, zostanie wywołana metoda AddNew obiektu klasy CurrencyManager. Poza utworzeniem nowego rekordu uaktywni ona także pierwsze pole tekstowe, aby ułatwić wprowadzanie tego nowego rekordu. Kiedy w końcu użytkownik klika przycisk usuwający rekordy, program prosi o potwierdzenie chęci wykonania tej operacji, a następnie wywołuje metodę RemoveAt obiektu klasy CurrencyManager. Na rysunku 20.20 przedstawiono działający program BindSimple. Rysunek 20.20. Przyciski w tym programie umożliwiają dodawanie, usuwanie i przeglądanie rekordów tabeli dzięki użyciu obiektu klasy CurrencyManager
Przykładowy program o nazwie BindSimpleMemoryDataSet, który można pobrać z serwera FTP wydawnictwa Helion, jest podobny do tej aplikacji, tyle że zamiast ładować dane z bazy danych, używa wbudowanego obiektu klasy DataSet.
Złożone wiązanie danych Dla niektórych kontrolek (na przykład TextBox czy Label) wystarczające jest związanie własności Text. Jednak wiele innych nie wyświetla tylko prostych łańcuchów tekstu. Wyobraźmy sobie na przykład, że mamy bazę danych, która zawiera tabelę o nazwie Users z polami FirstName, LastName oraz UserType. Tabela UserTypes posiada pola UserTypeId i UserTypeName. Wartość znajdująca się w polu UserType w tabeli Users powinna być taka sama jak wartość w polu UserTypes.UserTypeId. Pole UserTypeName tabeli UserTypes zawiera takie wartości, jak Programmer, ProjectManager, Department Manager, Program Manager oraz Lab Director.
484
Część II
Wstęp do języka Visual Basic
Tworząc formularz do prezentacji danych z tabeli Users, chcielibyśmy użyć kontrolki ComboBox, aby zezwolić na wybór tylko jednej z dozwolonych opcji — Programmer, ProjectManager itd. Niestety wartości te nie są zapisane w tabeli Users. Zawiera ona natomiast wartość UserTypeId, która odpowiada wybranej przez użytkownika wartości UserTypeName. Kiedy wybierze się jakąś wartość z pola UserTypes.UserTypeName, kontrolka ComboBox powinna odnaleźć odpowiadającą jej wartość pola UserTypes.UserTypeId i zapisać ją w polu Users.UserType. Z pewnością metoda prostych wiązań, która dobrze sprawdzała się w przypadku pól tekstowych, w tej sytuacji nie będzie odpowiednia. Dowiązanie tej kontrolki będzie składało się z dwóch dosyć skomplikowanych etapów — zdefiniowania obiektu klasy DataSet oraz związania z nim kontrolki. Każdy z nich jest łatwy do przeprowadzenia, ale trzeba bardzo uważać, by nie popełnić żadnego błędu. Jeśli zostanie pominięty jakiś szczegół, kontrolka ComboBox nie będzie działać, a komunikaty Visual Basica na niewiele się zdadzą. Technikę tę demonstruje program o nazwie BindComboBox, który można pobrać z serwera FTP wydawnictwa Helion. Aby dalej śledzić treść tego podrozdziału, możesz pobrać aplikację z internetu i skopiować dołączoną do niej bazę danych do nowego katalogu. Na samym początku należy utworzyć połączenie z danymi. W tym celu w menu Data kliknij pozycję Add New Data Source. Za pomocą kreatora Data Source Configuration Wizard utwórz źródło danych, które będzie wybierać z bazy danych tabele Users i UserTypes. Następnie utwórz na formularzu kontrolkę ComboBox o nazwie cboUserType. Zaznacz w oknie Properties jej własność DataSource i rozwiń jej listę rozwijaną. Zaznacz tabelę UserTypes, jak na rysunku 20.21. Dzięki temu kontrolka będzie wiedziała, gdzie szukać danych. Rysunek 20.21. Ustawianie własności DataSource kontrolki ComboBox na tabelę UserTypes
Rozdział 20.
Kontrolki i obiekty baz danych
485
Gdy ta własność zostanie ustawiona, Visual Basic doda do formularza komponenty DataSet, BindingSource oraz TableAdapter. Umożliwiają one uzyskanie dostępu do tabeli UserTypes. Ustaw własność DisplayMember kontrolki ComboBox na pole w tabeli, która ma być przeszukiwana (wyznaczonej przez własność DataSource), jakie wyświetli ta kontrolka. W tym przypadku jest to UserTypeName. Własność ValueMember tej kontrolki ComboBox ustaw na pole w tabeli, która ma być przeszukiwana, reprezentujące wartość, jaką kontrolka ta będzie odczytywać i zapisywać w bazie danych. W tym przypadku jest to pole UserTypeId. Następnie należy związać kontrolkę ComboBox z polem, które ma ona zapisywać i odczytywać z bazy danych. W tym przypadku jest to pole UserType z tabeli Users. Aby uprościć to wiązanie, dodaj do formularza nowy komponent BindingSource. Zmień jego nazwę na UsersBindingSource, a własność DataSource ustaw na ComputerUsersDataSet, jak na rysunku 20.22. Następnie ustaw własność DataMember obiektu BindingSource na tabelę Users. Rysunek 20.22. Ustawianie własności DataSource obiektu BindingSource na ComputerUsersDataSet
Ostatnia własność kontrolki ComboBox, którą trzeba ustawić, to SelectedValue. Kliknij kontrolkę ComboBox, przejdź do okna Properties i rozwiń znajdującą się na samym górze pozycję (DataBindings). Rozwiń listę znajdującą się po prawej stronie własności SelectedValue i zaznacz pole, które kontrolka ma odczytywać oraz zapisywać do bazy danych. W tym przypadku jest to UserType obiektu UsersBindingSource. Ustawianie tej własności przedstawiono na rysunku 20.23. Następnie utwórz kontrolki TextBox do wyświetlania zawartości pól FirstName i LastName tabeli Users. W oknie Properties otwórz ich elementy (Data Bindings) i ustaw ich własności Text na pola FirstName i LastName obiektu UserBindingSource.
486
Część II
Wstęp do języka Visual Basic
Rysunek 20.23. Ustawianie własności SelectedValue obiektu BindingSource na pole UserType obiektu UserBindingSource
Aby na zakończenie umożliwić użytkownikowi przeglądanie danych, dodaj do formularza obiekt klasy BindingNavigator. Ustaw własność BindingSource tego komponentu na UsersBindingSource — program będzie gotowy do użycia. Na rysunku 20.24 przedstawiono program BindComboBox, który można pobrać z serwera FTP wydawnictwa Helion. Rysunek 20.24. W czasie działania programu kontrolka ComboBox wyświetla pole związane ze swoją własnością DisplayMember, a aktualizuje pole związane z jej własnością SelectedValue
Opcje do wyboru, które są dostępne w tej kontrolce, są pobierane z pola UserTypeName tabeli UserTypes. Jeśli na liście tej zostanie wskazany nowy rodzaj użytkownika, kontrolka ta automatycznie dokona odpowiednich zmian w tabeli Users. Technika wiązania kontrolki ListBox jest dokładnie taka sama jak kontrolki ComboBox. Przykładowy program o nazwie BindListBox, który można pobrać z serwera FTP wydawnictwa Helion, działa w dużym stopniu podobnie do aplikacji BindComboBox, tyle że zamiast kontrolki ComboBox użyto w nim ListBox. W miarę przechodzenia przez rekordy kontrolka ta wybiera odpowiednie typy użytkownika dla każdego rekordu użytkownika.
Rozdział 20.
Kontrolki i obiekty baz danych
487
Podsumowanie Bazy danych w Visual Basicu to niezwykle obszerny temat. W tym rozdziale oczywiście nie opisano wszystkich ich szczegółów, ale stanowi on przydatne wprowadzenie do tych technik. Objaśniłem sposoby tworzenia źródeł danych oraz przeciągania tabel i pól z okna Data Sources na formularz. W rozdziale tym znalazły się opisy najważniejszych kontrolek i obiektów baz danych, takich jak połączenia, adaptery danych oraz klasy DataSet i DataTable. Ponadto wprowadziłem podstawowe wiadomości na temat prostego i złożonego wiązania danych oraz używania obiektów klasy CurrencyManager do nawigowania po danych. Więcej informacji na temat programowania baz danych w Visual Basicu .NET można znaleźć w książkach poświęconych w całości temu tematowi. Jako że techniki te ostatnio uległy sporym zmianom, powinieneś poszukać jakiejś aktualnej pozycji. W starszych publikacjach można znaleźć opisy podstawowych obiektów bazodanowych, jak DataSet, DataTable, DataRow i CurrencyManager, próżno natomiast w nich szukać obiektów takich nowych klas, jak TableAdapter, DataConnector czy DataNavigator. Najbardziej przydatne powinny być książki, w których opisano Visual Basic 2005, a także nowsze. Wyczerpujące omówienie przedstawionych tu zagadnień można na przykład znaleźć w publikacji Expert One-on-One Visual Basic 2005 Database Programming, której autorem jest Roger Jennings (Wrox, 2005). Osoby, które planują tworzyć i zrządzać bardzo dużymi bazami danych, powinny także przeczytać coś na ten temat. Dzięki książkom poświęconym temu zagadnieniu można nauczyć się projektowania, tworzenia i konserwowania bazy danych w czasie życia programu. Należy też przeczytać jakąś pozycję tylko o tej bazie danych, której ma się zamiar używać. Na przykład osoby planujące pracę z bazami danych SQL Server powinny zapoznać się z publikacjami o bazach danych SQL Server. Aby zostać ekspertem od baz danych, trzeba poświęcić bardzo dużo czasu, a wiadomości zdobyte w tym rozdziale powinny na początek wystarczyć. Opisane w tym rozdziale kontrolki i obiekty pozwalają na manipulację danymi znajdującymi się w bazie danych. Umożliwiają łączenie się z bazą danych, odczytywanie i aktualizowanie jej danych oraz wyświetlanie ich na formularzu. W rozdziale 21. — „LINQ” — przedstawię inny sposób ładowania danych i manipulowania nimi. Technologia LINQ pozwala na wykonywanie skomplikowanych kwerend — przypominają one kwerendy SQL, które pobierają dane z list, kolekcji, tablic i innych struktur danych.
488
Część II
Wstęp do języka Visual Basic
21 LINQ
LINQ (Language Integrated Query) jest nową technologią, którą zaprojektowano, by umożliwić pobieranie danych w taki sam sposób ze wszystkich źródeł danych. Najlepiej by było, gdyby dało się za pomocą jednej metody pobierać dane z tablic, list, relacyjnych baz danych, plików XML, arkuszy Excel oraz innych zbiorów danych. Aktualnie API LINQ obsługuje relacyjne bazy danych, obiekty przechowywane w programie w tablicach i listach oraz dane XML. LINQ to skomplikowana technologia. Udostępnia ona dziesiątki metod rozszerzających, które mają zastosowanie we wszystkich rodzajach obiektów przechowujących dane, takich jak tablice, słowniki i listy. Składnia LINQ w Visual Basicu konwertuje kwerendy podobne do SQL na wywołania funkcji LINQ. Narzędzia LINQ dzielą się na trzy przedstawione poniżej kategorie:
LINQ to Objects — funkcje LINQ przeznaczone do pracy z obiektami Visual Basica, takimi jak tablice, słowniki i listy. W większości przykładów przedstawionych w tym rozdziale użyto właśnie tego typu obiektów do zademonstrowania sposobów użycia LINQ.
LINQ to XML — funkcje LINQ służące do zapisu i odczytu danych w formacie XML. Za ich pomocą można łatwo przenosić dane pomiędzy hierarchiami XML a innymi obiektami Visual Basica.
LINQ to ADO.NET — funkcje umożliwiające pisanie kwerend w stylu LINQ do pobierania danych z relacyjnych baz danych.
Pierwszy podrozdział tego rozdziału — „Wprowadzenie do LINQ” — stanowi proste wprowadzenie do LINQ. Wiele zaawansowanych funkcji LINQ jest bardzo skomplikowanych, przez co trudno je zrozumieć. Podstawy natomiast są niezwykle proste. We wstępie tym zostaną zaprezentowane przykłady, które mają na celu zademonstrowanie elementarnych pojęć. Dzięki nim można zrozumieć te podstawy.
490
Część II
Wstęp do języka Visual Basic
Podrozdział „Podstawy składni zapytań LINQ” zawiera opis najbardziej przydatnych poleceń używanych w zapytaniach LINQ. Umożliwiają one wykonywanie skomplikowanych zapytań, które polegają na wybieraniu, filtrowaniu i aranżacji danych pobranych z obiektów znajdujących się w programie. W kolejnym podrozdziale — „Zaawansowana składnia zapytań LINQ” — znajduje się opis dodatkowych poleceń LINQ. Następny podrozdział — „Funkcje LINQ„ — zawiera omówienie funkcji udostępnianych przez LINQ, które nie są obsługiwane przez składnię zapytań LINQ Visual Basica. Aby ich użyć, trzeba je zastosować na rzecz tablic, słowników, list i innych obiektów, które rozszerzają. W podrozdziale „Metody rozszerzające LINQ” znajdziesz opisy technik rozszerzania przez LINQ takich obiektów, jak tablice, słowniki i listy. Zostały w nim omówione zapytania oparte na metodach oraz sposoby pisania niestandardowych rozszerzeń, które zwiększają ich możliwości. Pozostała część tego rozdziału zawiera opisy trzech głównych sposobów użycia LINQ — LINQ to Objects, LINQ to XML oraz LINQ to ADO.NET. Spośród nich najłatwiej opisać technologię LINQ to Objects, ponieważ — w przeciwieństwie do pozostałych dwóch — nie wymaga żadnej specjalistycznej wiedzy (poza znajomością samego Visual Basica). Aby zrozumieć LINQ to XML, należy znać technologię XML, która sama w sobie jest dosyć skomplikowana. Podobnie jest w przypadku LINQ to ADO.NET — trzeba znać zagadnienia związane z relacyjnymi bazami danych, takimi jak SQL Server. A to już temat rzeka, poświęcono mu mnóstwo książek. Ponieważ najłatwiej opisać LINQ to Objects, w tym rozdziale skoncentruję się właśnie na tej technologii. W związku z tym także większość prezentowanych tu przykładów będzie jej używać. Na końcu rozdziału znajduje się też jednak nieco informacji o LINQ to XML i LINQ to ADO.NET, które pozwolą się zorientować, jakie możliwości oferują te technologie. Techniki opisane w tym rozdziale zostały zademonstrowane w dwudziestu przykładowych programach, które można pobrać z serwera FTP wydawnictwa Helion (ftp://ftp.helion.pl/ przyklady/vb28wp.zip). LINQ jest jedną z nowinek w Visual Basicu, a więc niewykluczone, że jego składnia i inne szczegóły zmienią się w kolejnych wersjach. Możliwe nawet, że modyfikacje te zostaną wprowadzone, zanim książka ta trafi do rąk czytelników. Jeśli będą potrzebne jakieś modyfikacje, na stronie internetowej www.vb-helper.com zamieszczę odpowiednie uzupełnienia i aktualizacje przykładowych programów. Aby sprawdzić, czy nastąpiły jakieś zmiany, odwiedź tę stronę. Jeśli zasubskrybujesz newslettery, które znajdują się na stronie www.vb-helper.com/newsletter.html, będziesz co tydzień otrzymywać porcję porad i sztuczek oraz powiadomienia o modyfikacjach.
Rozdział 21.
LINQ
491
Wprowadzenie do LINQ API LINQ daje dosyć niskopoziomowy dostęp do danych w różnych formatach. Visual Basic tworzy wyższą warstwę nad tym API, która sprawia, że wysyłanie zapytań do źródeł danych jest łatwiejsze. Na tej wyższej warstwie do określania, które dane mają zostać pobrane ze źródła, są używane wyrażenia zapytań (ang. query expression). Mają one składnię podobną do SQL, dzięki czemu będą wyglądać znajomo dla osób, które miały do czynienia z relacyjnymi bazami danych. Wyobraźmy sobie na przykład, że w programie została zdefiniowana klasa o nazwie Customer z typowymi dla klientów własnościami, czyli Name, Phone, StreetAddress, AccountBalance itd. Przyjmijmy również, że lista all_customers zawiera wszystkie obiekty tej klasy Customer. Biorąc to pod uwagę, poniższe zapytanie wybiera wszystkich klientów, którzy mają ujemne salda na swoich kontach. Wyniki zostały posortowane rosnąco według salda, dzięki czemu najbardziej zadłużonych klientów wymieniono na samej górze (podobne zapytanie wykonuje program LinqLambda, w którym jest zdefiniowana klasa Customer; aplikację można pobrać internetu). Dim overdue_custs = _ From cust In all_customers _ Where cust.AccountBalance < 0 Order By cust.AccountBalance Ascending _ Select cust.Name, cust.AccountBalance
Visual Basic konwertuje niejawnie to wyrażenie zapytania na wywołania API LINQ — i w ten sposób pobiera odpowiednie dane. Przez te ostatnie można następnie przejść za pomocą pętli, co demonstruje poniższy fragment kodu: For Each cust In overdue_custs Debug.WriteLine(cust.Name & Next cust
": "
&
cust.AccountBalance)
Kod ten zawiera kilka interesujących elementów. Po pierwsze, w obu powyższych fragmentach brak deklaracji typu danych zmiennej wyrażeniowej oraz pętlowej cust. Typy danych obu tych zmiennych są automatycznie wnioskowane przez Visual Basic. Jeśli zatrzymalibyśmy ten program w czasie działania i sprawdzili za pomocą funkcji TypeName typ tych zmiennych, zobaczylibyśmy następującą informację: d__b(Of Customer,VB$AnonymousType_0(Of String,Decimal)) VB$AnonymousType_0(Of String,Decimal)