You are on page 1of 907

Twoja przepustka do świata C#!

Doskonały podręcznik
do nauki praktycznego
programowania
w C#, XAML i .NET

Zarządzaj swoimi
obiektami dzięki
wykorzystaniu wszystkie
abstrakcji tajniki wzorca
i dziedziczenia Model-Widok
-Widok-Modelu

Napisz w pełni
funkcjonalną
staromodną

Dowiedz się, w jaki


sposób programowanie
asynchroniczne
pozwoliło Krystynie Przekonaj się, jak Janek
spełnić wymagania zastosował kolekcje
klientów i LINQ, by ujarzmić
niesforną kolekcję
komiksów

O REILLY* Jennifer Greene, Andrew Stellman


Książka ta dedykowana jest pamięci wielorybicy Sludgie,
która przypłynęła na Brooklyn 17 kwietnia 2007 roku.

Byłaś w naszym kanale tylko jeden dzień,


ale w naszych sercach pozostaniesz na zawsze.

7
Autorzy

Andrew Stellm an, mimo że został wychowany na


nowojorczyka, dwukrotnie mieszkał w Pittsburghu.
Pierwszy raz wtedy, gdy ukończył Carnegie
M ellon’s School of Com puter Science, i ponownie,
gdy razem z Jenny rozpoczęli działalność
konsultantów i zaczęli pisać pierwszą książkę dla
O ’Reilly.
Jennifer Greene studiowała filozofię w college’u,
Po zakończeniu studiów został programistą w EMI- ale, jak większość osób zajmujących się tą dziedziną,
Capitol Records — miało to sens, ponieważ nie mogła znaleźć pracy. N a szczęście była dobrym
uczęszczał do LaG uardia High School of Music testerem oprogramowania, więc zaczęła wykonywać
and A rt and Performing Arts, studiując w klasie to zdalnie. Wtedy po raz pierwszy naprawdę poznała
wiolonczeli i gitary basowej. W trakcie swej kariery zagadnienia związane z tworzeniem oprogramowania.
zawodowej był wiceprezesem dużego banku
inwestycyjnego, architektem wielkich, działających Przeniosła się do Nowego Jorku w 1998 roku, aby
w czasie rzeczywistym systemów komputerowych, testować oprogramowanie finansowe. Od samego
zarządzał dużymi, międzynarodowymi zespołami początku zarządzała grupą programistów testerów
programistów, udzielał konsultacji firmom, szkołom i kierowników produktów, zajmujących się projektami
i organizacjom takim jak Microsoft, National oprogramowania związanego z mediami i finansami.
Bureau of Economic Research oraz MIT. Miał
Jeździła po całym świecie, by pracować z różnymi
przywilej pracy z najlepszymi specjalistami i lubi
zespołami programistów nad przeróżnymi fajnymi
myśleć, że czegoś się od nich nauczył.
projektami.
Kiedy nie pisze książek, tworzy bezużyteczne
Uwielbia podróżować, oglądać filmy z Bollywood,
(ale zabawne) programy, gra na instrumencie
czytać komiksy, grać na PS3 i bawić się ze swym
i w gry komputerowe, trenuje tai-chi i aikido
wielkim kotem syberyjskim — Saschą.
oraz opiekuje się szpicem miniaturowym.

Jenny i Andrew tw orzą wspo'/nie oprogramowanie i p isz ą o nżynierii oprogramowania od czasu poznania
W ro ^ : lCh7 ierWSZ»'' *3 ^
. Applied
A ' h ' Softw
co ftw —
are Project
n' M anagem ent, zo sta ła wydana przez 0 ' Rei//y
w 2005 roku. Inną książką napisaną przez S te //mana i Gree n d/a wy dawnictwa 0 ’Rei//y była Be au t,fu / 7eams
(wydana w 2009 roku), a p ierw szą z serii Head First. Head R r s t pMp, °p u b/ik° wa/i w roku 2007:
W 2 003 roku założy/i Ste//m an & Greene Consu/ting w ce/u t ^ r r o r n a nap rawdę starannych projektów
do badania wpływu herbicydów na weteranów z W ietnamu. Gdy m e p iszą p rogramów ani książek. to można
ich spotkać na meetingach i konferencjach inżynierów oprogramowania, architektów i kierowników projektów.
S prawdź ich b/og Building B etter Softw are: h ttp ://w w w s te llm a n -greene-c° m ,
a/bo ś/edź na Twitterze: @AndrewSte//man i' (aO en n y O ^ n .

8
Spis treści

Spis treści (skrócony)


Wstęp 31
1. Zacznij pisać programy w C #. Napisz coś fajnego, i to szybko! 43
2. To tylko kod. Pod maską 95
3. Obiekty: zorientuj się! Tworzenie kodu ma sens 143
4. Typy i referencje. Jest 10:00. Czy wiesz, gdzie są Twoje dane? 181
Laboratorium C # numer 1. Dzień na wyścigach 225
5. Hermetyzacja. Co ma być ukryte... niech będzie ukryte 235
6. Dziedziczenie. Drzewo genealogiczne Twoich obiektów 275
7. Interfejsy i klasy abstrakcyjne. Klasy, które dotrzymują swoich obietnic 329
8. Typy wyliczeniowe i kolekcje. Przechowywanie dużej ilości danych 385
9. Odczyt i zapis plików. Zachowaj te bajty dla mnie! 441
Laboratorium C # numer 2. Wyprawa 495
10. Projektowanie aplikacji dla Sklepu Windows z użyciem XAML. 517
Przenosząc swoje aplikacje na wyższy poziom
11. Async, await i serializacja kontraktu danych. Przepraszam, że przerywam 565
12. Obsługa wyjątków. Gaszenie pożarów nie jest już popularne 599
13. Kapitan Wspaniały. Śmierć obiektu 639
14. Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ. 677
Przejmij kontrolę nad danymi
15. Zdarzenia i delegaty. Co robi Twój kod, kiedy nie patrzysz 729
16. Projektowanie aplikacji według wzorca MVVM. Świetne aplikacje od zewnątrz 773
i od środka
Laboratorium C # numer 3. Invaders 835
17. Projekt dodatkowy! Napisz aplikację Windows Phone 859
A Pozostałości. 11 najważniejszych rzeczy, które chcieliśmy umieścić w tej książce 873
Skorowidz 905

Spis treści (z prawdziwego zdarzenia)


Wstęp

W Przygotuj się na C#. Właśnie sobie siedzisz i próbujesz się czegoś nauczyć, ale mózg wciąż

Dla kogo jest ta książka?


Wiemy, o czym myślisz
Metapoznanie: myślenie o myśleniu
powtarza Ci,
że cała ta nauka nie jest ważna. Twój umysł mówi: „Lepiej wyjdź z pokoju i zajmij się ważniejszymi sprawami,
takimi jak to, których dzikich zwierząt unikać, oraz to, że strzelanie z łuku na golasa nie jest dobrym pomysłem".
W jaki sposób oszukać mózg, tak aby myślał, że Twoje życie naprawdę zależy od nauki C#?
32
33
35
Zmuś swój mózg do posłuszeństwa 37
Czego potrzebujesz do tej książki? 38
Przeczytaj to 39
Grupa korektorów technicznych 40
Podziękowania 41

9
Spis treści

Zacznij pisać programy w C#

Napisz coś fajnego, i to szybko!

1 Czy chcesz tworzyć wspaniałe programy naprawdę szybko? Wraz z C# dostajesz

do ręki potężny język program ow ania i wartościowe narzędzie. Dzięki Visual Studio IDE
do historii przejdą sytuacje, w których musiałeś pisać jakiś nędzny kod, by ponownie zapewnić
prawidłowe działanie przycisku. I to nie wszystko. Dodatkowo będziesz mógł skupić się na
faktycznym w y ko n yw a n iu napraw dę fajn ych program ów , zamiast starać się zapamiętać,
który parametr metody odpowiadał za nazwę przycisku, a który za wyświetlany na nim tekst.
Brzmi zachęcająco? Przewróć zatem stronę i przystąpmy do programowania.
Dlaczego powinieneś uczyć się C # 44
Ich unikaj.
C # oraz Visual Studio ułatwiają wiele czynności 45
Co robić w Visual Studio... 46
Co Visual Studio robi w naszym im ie n iu . 46
Obcy atakują! 50
Tylko Ty możesz uratować Ziemię 51
Oto co masz zamiar napisać 52
Zacznij od pustej aplikacji 54
Określ wymiary siatki na stronie 60
Dodaj kontrolki do siatki 62
Używaj właściwości, by zmieniać wygląd kontrolek 64
To kontrolki sprawiają, że gra działa 66
Stworzyłeś scenę, na której będzie prowadzona gra 71
Och! Kosmici Czym zajmiesz się teraz? 72

I wciągają, ludzi'.
Niedobrze! Dodaj metody, które coś zrobią
Podaj kod metody
73
74
Dokończ metodę i uruchom program 76
Oto co zrobiłeś do tej pory 78
Dodaj liczniki czasu zarządzające rozgrywką 80
Popraw działanie przycisku Start 82
Uruchom program, by zobaczyć postępy w pracy 83
Dodaj kod obsługujący interakcję użytkownika z kontrolkami 84
Dotknięcie człowiekiem wroga kończy grę 86
% Teraz już można bawić się grą 87
Zadbaj, by wrogowie wyglądali jak obcy 88
Dodaj ekran startowy i tytuł 89

* Opublikuj swoją aplikację


Użyj programu Remote Debugger, by uruchomić aplikację
90

na innym komputerze 91
Rozpocznij zdalne debugowanie 92

/ O
10

H
Spis treści

To tylko kod

Pod maską
Jesteś programistą, nie jedynie użytkownikiem IDE. IDE może wykonać za Ciebie
wiele pracy, ale na razie jest to wszystko, co może dla Ciebie zrobić. Oczywiście istnieje wiele
po w tarza lnych czynności podczas pisania aplikacji i IDE okazuje się tu bardzo pomocne.
Praca z nim to jednak dopiero poczgtek. Możesz wycisnąć ze swoich programów znacznie
więcej — pisanie kodu C# to właśnie droga, która doprowadzi Cię do tego celu. Kiedy
osiągniesz mistrzowski poziom w kodowaniu, nie będzie żadnej rzeczy, której Twój program
nie umiałby zrobić.

Kiedy robisz to... 96


...ID E robi to 97
Skąd się biorą programy 98
IDE pomaga Ci kodować 100
Anatomia programu 102
W tej samej przestrzeni nazw mogą być dwie klasy 107
Twoje programy używają zmiennych do pracy z danymi 108
C # używa znanych symboli matematycznych 110
Użyj debuggera, by zobaczyć, jak zmieniają się wartości zmiennych 111
Pętle wykonują czynność wielokrotnie 113
Instrukcje if/else podejmują decyzje 114
Utwórz aplikację od samego początku 115
Niech przycisk coś zrobi 117
Ustal warunki i sprawdź, czy są prawdziwe 118
Tworzenie klasycznych aplikacji Windows jest łatwe 129
Przepisz program jako klasyczną aplikację Windows 130
Twój program wie, gdzie zacząć 134
Możesz zmienić punkt wejścia programu 136
Kiedy zmieniasz coś w IDE, zmieniasz także swój kod 138

Za każdym razem, kiedy tworzysz nowy


program denniujesz dfa niego przestrzeń nazw
W ten sp °sób jego kod jest odseparowany
od innych klas platformy .N ET
Klasy zawierają fragmenty y
kodu Twojego programu
(chociaż istnieją ta lże bar­
dzo małe aplikacje składają­
ce się z tylko jednej klasy)

Naciśnij przycisk, ab y z m ienić tekst

Klasa posiada jedną lub wię


cej metod. Twoje metody
zawsze będą umieszczane
wewnątrz klas, a każda
z nich będzie się składała
z instrukcji i wyrażeń —
jak te, które do tej pory
widziałeś. 11
Spis treści

Obiekty: zorientuj się!

Tworzenie kodu ma sens


Każdy pisany przez Ciebie program rozwiązuje jakiś problem.
Rozpoczynając pisanie programu, zawsze warto zastanowić się, jaki problem ma on
rozwiązywać. Właśnie do tego przydają się obiekty. Pozwalają one tworzyć strukturę kodu tak,
by odpowiadała ona rozwiązywanemu problemowi, dzięki czemu będziesz mógł skoncentrować
się na nim samym, a nie na mechanice tworzenia kodu. Prawidłowe użycie obiektów spowoduje,
że proces pisania kodu stanie się bardziej intuicyjny, a jego późniejsza analiza i modyfikacja
— znacznie łatwiejsze.

W jaki sposób Maciek myśli o swoich problemach 144


W jaki sposób system nawigacyjny w samochodzie Maćka
rozwiązuje jego problemy 145
Klasa Navigator napisana przez Maćka
posiada metody do ustalania i modyfikacji tras 146
Wykorzystaj to, czego się nauczyłeś, do napisania
prostego programu używającego klas 147
Maciek ma pewien pomysł 149
navfgotorl
Maciek może użyć obiektów do rozwiązania swojego problemu 150
Używasz klasy do utworzenia obiektu 151
Navigator
SetCurrentLocationO Kiedy tworzysz obiekt na podstawie klasy,
SetDestinationO to taki obiekt nazywamy instancją klasy 152
ModifyRouteToAvoidO
ModifyRouteTolnclude() Lepsze rozwiązanie... uzyskane dzięki obiektom! 153
GetRouteO
GetTimeToDestinationO Instancja używa pól do przechowywania informacji 158
Total D¡stance ()
Stwórzmy kilka instancji! 159
Dzięki za pamięć 160
Co Twój program ma na myśli 161
nav!gator3
Możesz używać nazw klas i metod
w celu uczynienia kodu bardziej intuicyjnym 162
Nadaj swojej klasie naturalną strukturę 164
Diagramy klas pozwalają w sensowny sposób zorganizować klasy 166
Kiedy d efin iu jesz k/asę,
d efiniujesz tak że j e j metody, Utwórz klasę do pracy z kilkoma facetami 170
podobnie ja k p ro jek t definiuje
układ pom ieszczeń w domu. Utwórz projekt dla facetów 171
Utwórz formularz do interakcji z facetami 172
Jest jeszcze prostszy sposób inicjalizacji obiektów 175

M ożesz użyć jednego projektu


do zbudow ania dowo/nej /iczby
domów. M ożesz również przy
użyciu jed n ej k/asy utworzyć
każdą /iczbę obiektów. ■— -

12
Spis treści

Typy i referencje

Jest 10:00. Czy wiesz, gdzie są Twoje dane?


Typ danych, baza danych, dane komandora porucznika... wszystko
to są ważne rzeczy. Bez danych Twoje programy są bezużyteczne. Potrzebujesz
informacji dostarczanych przez użytkowników. Na ich podstawie wyszukujesz lub tworzysz
nową inform ację i zwracasz im ją. W rzeczywistości prawie wszystko, co robisz podczas
programowania, sprowadza się do pracy z danym i w taki czy w inny sposób. W tym rozdziale
dowiesz się o różnych aspektach ty p ó w danych C#, nauczysz się pracować z danymi
w programie, a nawet odkryjesz kilka pilnie strzeżonych sekretów o b ie k tó w (psssst...
obiekty to także dane).
Typ zmiennej określa rodzaj danych, jakie zmienna może przechowywać 182
Zmienna jest jak kubek z danymi 184
10 kilogramów danych w pięciokilogramowej torebce 185
Nawet wtedy, gdy liczba ma prawidłowy rozmiar, nie możesz przypisać jej
do każdej zmiennej 186
Kiedy rzutujesz wartość, która jest zbyt duża, C # dopasowuje ją
automatycznie 187
C # przeprowadza niektóre rzutowania automatycznie 188
Kiedy wywołujesz metodę, zmienne muszą pasować do typów parametrów 189
Przetestuj kalkulator zwrotu kosztów 193
Połączenie = z operatorem 194
Także obiekty używają zmiennych 195
Korzystaj ze swoich obiektów za pomocą zmiennych referencyjnych 196
Referencje są jak etykiety do Twoich obiektów 197
Jeżeli nie ma już żadnej referencji, Twoje obiekty są usuwane z pamięci 198
Referencje wielokrotne i ich efekty uboczne 199
Dwie referencje oznaczają DWA sposoby na zmianę danych obiektu 204
Specjalny przypadek: tablice 205
Dog fido; Tablice mogą także zawierać grupę zmiennych referencyjnych 206
Dog lucky = new Dog () ; Witamy w barze Niechlujny Janek — najtańsze kanapki w mieście! 207
Obiekty używają referencji do komunikacji między sobą 209
Po®
Tam, gdzie obiektów jeszcze nie było 210
Napisz grę w literki 215
fido = new Dog() Kontrolki to też obiekty, podobne do innych 219

lucky = nuli; W /
^ p y k !-
Do9
/ V \

13
Spis treści

Laboratorium C# numer 1

Dzień na wyicięach
Janek, Bartek i Arek uwielbiają chodzić na tor wyścigowy, ale ciągła utrata pieniędzy powoduje u nich
frustrację. Potrzebują symulatora, aby mogli określić zwycięzcę, zanim wyłożą pieniądze na zakłady.
Jeśli dobrze wywiążesz się z zadania, będziesz miał procenty z ich wygranych.

Specyfikacja: stwórz symulator wyścigów 226


Końcowy produkt 234
Spis treści

Hermetyzacja

Co ma być ukryte... niech będzie ukryte

5 Czy kiedykolwiek marzyłeś o odrobinie prywatności? Czasami Twoje obiekty


czują się tak samo. Na pewno nie lubisz sytuacji, w których ktoś, komu nie ufasz, czyta Twój
pamiętnik lub przegląda wykazy Twoich operacji bankowych. Dobre obiekty nie pozwalają
innym obiektom na oglądanie swoich pól. W tym rozdziale nauczysz się wykorzystywać
potęgę herm etyzacji. Sprawisz, że dane o b ie k tó w będą pryw atne , i dodasz metody,
które umożliwią Ci zabezpieczenie dostępu do danych.

Krystyna planuje przyjęcia 236


Co powinien robić program szacujący? 237
Napiszesz program dla Krystyny 238
Jazda próbna Krystyny 244
Każda opcja powinna być obliczana osobno 246
Bardzo łatwo przez przypadek źle skorzystać z obiektów 248
Hermetyzacja oznacza, że niektóre dane w klasie są prywatne 249
v Użyj hermetyzacji w celu kontroli dostępu do metod i pól Twojej klasy 250
----- VJ . . y y . .
Ale czy jego prawdziwa tożsamość jest NAPRAWDĘ chroniona? 251
Dostęp do prywatnych pól i metod można uzyskać tylko z wnętrza klasy 252
Hermetyzacja utrzymuje Twoje dane w nieskazitelnym stanie 260
Właściwości sprawią, że hermetyzacja będzie łatwiejsza 261
Utwórz aplikację do przetestowania klasy Farmer 262
Użyj automatycznych właściwości do ukończenia klasy 263
'Hj Co wtedy, gdy chcemy zmienić pole mnożnika wyżywienia? 264
Użyj konstruktora do inicjalizacji pól prywatnych 265

,Tak7j
Ilo ś ć gości
Jed zen ie

b
(2 5 z ł od osoby)
O p cja zd ro w a?

_ N i£ ^

15
Spis treści

Dziedziczenie

Drzewo genealogiczne Twoich obiektów

6 Czasami CHCIAŁBYŚ być dokładnie taki sam jak Twoi rodzice.


Czy kiedykolwiek natknąłeś się na obiekt, który robiłby praw ie wszystko, czego byś
sobie od niego życzył? Czy kiedykolwiek znalazłeś się w takiej sytuacji, że gdybyś zm ienił
dosłow nie kilka rzeczy, obiekt byłby doskonały? Cóż, to tylko jeden z wielu powodów,
które sprawiają, że dziedziczenie zalicza się do najważniejszych koncepcji i technik
w języku C#. Kiedy skończysz czytać ten rozdział, będziesz wiedział, jak rozszerzać obiekty,
by móc wykorzystywać ich zachowania i jednocześnie dysponować elastycznością,
która pozwoli Ci te zachowania modyfikować. Unikniesz w ie lo kro tn e g o pisania kodu,
przedstaw isz p ra w d z iw y św ia t znacznie dokładniej, a w efekcie otrzymasz kod ła tw ie js z y
do zarządzania.

Krystyna organizuje także przyjęcia urodzinowe 276


Potrzebujemy klasy BirthdayParty 277
Stwórz program Planista przyjęć w wersji 2.0 278
Jeszcze jedna rzecz... Czy możesz dodać opłatę 100 zł za przyjęcia
dla ponad 12 osób? 285
Kiedy klasy używają dziedziczenia, kod musi być napisany tylko raz 286
Zbuduj model klasy, rozpoczynając od rzeczy ogólnych
i przechodząc do bardziej konkretnych 287
W jaki sposób zaprojektowałbyś symulator zoo? 288
Użyj dziedziczenia w celu uniknięcia powielania kodu w klasach potomnych 289
Różne zwierzęta wydają różne dźwięki 290
Pomyśl, w jaki sposób pogrupować zwierzęta 291
Stwórz hierarchię klas 292
Każda klasa pochodna rozszerza klasę bazową 293
Aby dziedziczyć po klasie bazowej, użyj dwukropka 294
Wiemy, że dziedziczenie dodaje pola, właściwości i metody klasy bazowej
do klasy p o to m n e j. 297
Klasa pochodna może przesłaniać odziedziczone metody
w celu ich modyfikacji lub zmiany 298
W każdym miejscu, gdzie możesz skorzystać z klasy bazowej,
możesz zamiast niej użyć jednej z jej klas pochodnych 299
Klasa pochodna może ukrywać metody klasy b a z o w e j 3 0 6
Używaj override i virtual, by dziedziczyć zachowania 308
Klasa potomna może uzyskać dostęp do klasy bazowej,
używając słowa kluczowego base 310
Jeśli Twoja klasa bazowa posiada konstruktor, klasa pochodna też musi go mieć 311
Teraz jesteś już gotowy do dokończenia zadania Krystyny 312
Stwórz system zarządzania ulem 317
Użyj dziedziczenia, aby rozszerzyć system zarządzania pszczołami 324

16
Spis treści

Interfejsy i klasy abstrakcyjne

Klasy, które dotrzymują swoich obietnic


Czyny potrafią powiedzieć więcej* niż słowa. Czasami potrzebujesz pogrupować
swoje obiekty na podstawie tego, co robią, zamiast tego, po jakiej klasie dziedziczą. To jest
moment, w którym należy powiedzieć o interfejsach. Pozwalają one na pracę z każdą
klasą, która jest w stanie wykonać daną czynność. Jednak wraz z w ie lk im i m ożliw ościam i
przychodzi w ie lk a odpow iedzialność i każda klasa, która implementuje interfejs, musi
w yp e łn ić w szystkie swoje obowiązki... albo kompilator połamie Ci nogi, zrozumiałeś?

Wróćmy do pszczelej korporacji 330


Możemy użyć dziedziczenia do utworzenia klas dla różnych typów pszczół 331
Dziedziczenie
Interfejs daje klasie do zrozumienia, że musi ona zaimplementować
określone metody i właściwości 332
Użyj słowa kluczowego interface do zdefiniowania interfejsu 333
Abstrakcja Teraz możesz utworzyć instancję NectarStinger, która będzie wykonywała
dwa rodzaje zadań 334
Klasy implementujące interfejsy muszą zawierać WSZYSTKIE ich metody 335
Hermetyzacja
Poćwicz trochę z interfejsami 336
Nie możesz utworzyć instancji interfejsu, ale możesz uzyskać jego referencję 338
Referencje interfejsów działają tak samo jak referencje obiektów 339
Za pomocą „is” możesz sprawdzić, czy klasa implementuje określony interfejs 340
Polimorfizm
Interfejsy mogą dziedziczyć po innych interfejsach 341
RoboBee 4000 może wykonywać zadania pszczół bez potrzeby
spożywania cennego miodu 342
Ekspres do kawy także jest urządzeniem 344
Rzutowanie w górę działa w odniesieniu do obiektów i interfejsów 345
Rzutowanie w dół pozwala zamienić urządzenie
z powrotem w ekspres do kawy 346
Rzutowanie w górę i w dół działa także w odniesieniu do interfejsów 347
Jest coś więcej niż tylko public i private 351
Modyfikatory dostępu zmieniają widoczność 352
Obiekty niektórych klas nigdy nie powinny być tworzone 355
Klasa abstrakcyjna jest jak skrzyżowanie klasy i interfejsu 356
Jak wspominaliśmy, obiekty niektórych klas nigdy nie powinny być tworzone 358
Metoda abstrakcyjna nie ma ciała 359
Piekielny diament śmierci 364
Polimorfizm oznacza, że jeden obiekt może przyjmować wiele różnych postaci 367

17
Spis treści

Typy wyliczeniowe i kolekcje

Przechowywanie dużej ilości danych

8 Z deszczu pod rynnę. W rzeczywistym świecie nie musisz się zwykle zajmować danymi
w małych ilościach i w niewielkich fragmentach. Nie, Twoje dane przychodzą do Ciebie
w grupach, stosach, pękach, kopach. Potrzebujesz jakiegoś potężnego narzędzia do ich
zorganizowania. Nadszedł czas, aby przedstawić kolekcje. Pozwalają one przechowywać
i sortow ać dane, a także zarządzać tymi z nich, które Twój program musi przeanalizować.
Dzięki temu możesz myśleć o pisaniu programów do pracy z danymi, a samo ich
przechowywanie zostawić kolekcjom.

Łańcuchy znaków nie zawsze sprawdzają się


przy kategoryzowaniu danych 386
Typy wyliczeniowe pozwalają Ci wyliczyć prawidłowe wartości 387
Typy wyliczeniowe pozwalają na reprezentowanie liczb za pomocą nazw 388
Z tablicami ciężko się pracuje 392
Listy ułatwiają przechowywanie kolekcji... czegokolwiek 393
Listy są bardziej elastyczne niż tablice 394
Listy kurczą się i rosną dynamicznie 397
Typy generyczne mogą przechowywać każdy typ 398
Inicjalizatory kolekcji działają tak samo jak inicjalizatory obiektu 402
Stwórzmy listę kaczek 403
Listy są proste, ale SORTOWANIE może być skomplikowane 404
ICom parable<T> pomoże Ci posortować listę kaczek 405
Użyj interfejsu IComparer, aby powiedzieć liście, jak ma sortować 406
Utwórz instancję obiektu porównującego 407
IComparer może wykonywać złożone porównania 408
Przesłonienie metody ToString() pozwala obiektom przedstawiać się 411
Zmień pętle foreach tak, by obiekty Duck i Card same się opisywafy 412
Pisząc pętlę foreach, używasz IEnum erable<T> 413
Używając IEnumerable, możesz rzutować całą listę w górę 414
Możesz tworzyć własne przeciążone metody 415
/P y id - Użyj słownika do przechowywania kluczy i wartości 421
Wybrane funkcjonalności słownika 422
/ V \ Napisz program korzystający ze słownika 423
I jeszcze W IĘCEJ typów kolekcji... 435
Kolejka działa według regufy: pierwszy przyszedł, pierwszy wyszedł 436
Stos działa według regufy: ostatni przyszedł, pierwszy wyszedł 437

18
Spis treści

Odczyt i zapis plików

Zachowaj te bajty dla mnie!

9 Czasem opłaca się być trwałym. Do tej pory wszystkie programy były krótkotrwałe.
Uruchamiały się, działały przez chwilę i były zamykane. Czasami nie jest to wystarczające,
zwłaszcza jeżeli zajmujesz się ważnymi danymi. Musisz mieć możliwość zapisania swojej
pracy. W tym rozdziale pokażemy sposób zapisyw ania danych do pliku, a następnie
w czytyw a n ia tych in fo rm acji z po w ro te m do programu. Dowiesz się co nieco o klasach
strum ie ni .NET i zetkniesz się z tajemnicami systemów szesnastkowego i dw ójkow ego.

C # używa strumieni do zapisu i odczytu danych 442


Różne strumienie zapisują i odczytują różne rzeczy 443
FileStream odczytuje dane z pliku i zapisuje je w nim 444
W jaki sposób zapisać tekst do pliku w trzech prostych krokach 445
Kanciarz wymyślił nowy diabelski plan 446
Zapis i odczyt wymaga dwóch obiektów 449
Dane mogą przechodzić przez więcej niż jeden strumień 450
Użyj wbudowanych obiektów do wyświetlenia
standardowych okien dialogowych 453
Okna dialogowe są kolejnymi kontrolkami .NET 454
Okna dialogowe także są obiektami 455
Używaj wbudowanych klas File oraz Directory do pracy z plikami i katalogami 456
Używaj okien dialogowych do otwierania i zapisywania plików
(wszystko za pomocą kilku linijek kodu) 459
Dzięki IDisposable obiekty usuwane są prawidłowo 461
Unikaj błędów systemu plików, korzystając z instrukcji using 462
Zapisywanie danych do plików wymaga wielu decyzji 468
Użyj instrukcji switch do wybrania właściwej opcji 469
Dodaj przeciążony konstruktor Deck(), który wczytuje karty z pliku 471

69 1 17 1 1 4 1 0 1 107 97 33
Kiedy obiekt jest serializowany, serializowane są także wszystkie obiekty
z nim powiązane... 475
E u re k a! -L 1 i '/ M ..
Serializacja pozwala Ci zapisywać lub odczytywać całe grafy obiektów naraz 476
.NET automatycznie konwertuje tekst do postaci Unicode 481
C # może użyć tablicy bajtów do przesyłania danych 482
Do zapisywania danych binarnych używaj klasy BinaryWriter 483
Pliki utworzone dzięki serializacji można także zapisywać i odczytywać ręcznie 485
Sprawdź, gdzie pliki się różnią, i użyj tej informacji do ich zmiany 486
Praca z plikami binarnymi może być skomplikowana 487
6 9 1 1 7 7 1 4 707 7079
■— «07 973, fc, Użyj strumieni plików do utworzenia widoku w postaci szesnastkowej 488

O""0 StreamReader i StreamWriter będą do tego odpowiednie 489

19
Spis treści

Laboratorium C# numer 2

Wyprawa
Twoim zadaniem jest stworzenie gry przygodowej, w której potężny wojownik wyrusza na
wyprawę i dzielnie walczy, poziom za poziomem, ze śmiertelnie niebezpiecznymi wrogami.
Stworzysz system turowy. Oznacza to, że najpierw gracz wykonuje jeden ruch, a następnie
ruch wykonuje przeciwnik. Gracz może przesunąć się lub zaatakować; potem możliwość
ruchu i ataku dostaje każdy z wrogów. Gra toczy się do czasu, aż gracz pokona wszystkich
przeciwników na wszystkich siedmiu poziomach lub zginie.

Specyfikacja: utwórz grę przygodową 496


Zabawa dopiero się zaczyna! 516
Spis treści

Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Przenosząc swoje aplikacje na wyższy poziom


Jesteś już gotów, by wkroczyć do zupełnie nowego świata tworzenia aplikacji.
Korzystanie z technologii WlnForms do tworzenia klasycznych aplikacji dla systemu Windows
jest doskonałym sposobem nauki ważnych rozwiązań języka C#, niemniej jednak możesz
pójść znacznie dalej. W tym rozdziale dowiesz się, jak używać języka XAM L do projektowania
aplikacji przeznaczonych dla Sklepu Windows, nauczysz się tworzyć aplikacje działające na
d o w o lnych urządzeniach, integrow ać dane ze stronami przy użyciu w ią za n ia danych
i używać Visual Studio do ujawniania tajemnic stron XAML poprzez badanie obiektów
tworzonych na podstawie kodu XAML.

Siatka składa się z kwadratów o wielkości Każda jednostka jest podzielona na Damian używa Windows 8 518
20 pikseli, nazywanych jednostkami. podjednostki o wielkości 5 pikseli.
Technologia Windows Forms korzysta z grafu obiektów
I \ stworzonego przez IDE
Użyj IDE do przejrzenia grafu obiektów
524
527
Aplikacje dla Sklepu Windows używają XAML do tworzenia obiektów
interfejsu użytkownika 528

© Idź na ry Przeprojektuj formularz Idź na ryby!, zmieniając go w aplikację


dla Sklepu Windows 530
Określanie postaci strony rozpoczyna się od dodania kontrolek 532
Wiersze i kolumny mogą zmieniać wielkość, dostosowując się
do rozmiarów strony 534
Skorzystaj z systemu siatki, by określić układ stron aplikacji 536
Wiązanie danych kojarzy strony XAML z klasami 542
Kontrolki XAML mogą zawierać te k s t . i nie tylko 544
Użyj wiązania danych, by usprawnić aplikację Niechlujnego Janka 546
Korzystaj z zasobów statycznych, by deklarować obiekty
w kodzie XAML 552
Wyświetlaj obiekty, używając szablonów danych 554
Interfejs INotifyPropertyChanged pozwala powiązanym obiektom
przesyłać aktualizacje 556
Zmodyfikuj klasę MenuMaker, by informowała Cię, gdy zmieni się
właściwość GeneratedDate 557

POWIĄZANIE
ItemsSource="{Binding}"

O , ek t \) ^
k

21
Spis treści

Async, await i serializacja kontraktu danych

Przepraszam, że przerywam
Nikt nie lubi być zmuszanym do oczekiwania... zwłaszcza użytkownicy.
Komputery są doskonałe w wykonywaniu wielu rzeczy jednocześnie, nie ma zatem żadnego
powodu, aby Twoje aplikacje nie mogły tego robić. W tym rozdziale dowiesz się, jak sprawić,
by dzięki zastosow aniu m etod asynchronicznych Twoje aplikacje reagowały błyskawicznie.
Nauczysz się także korzystać z w b ud ow a nych narzędzi do w yb ie ra n ia p likó w , wyświetlać
okienka z kom unikatam i oraz asynchronicznie zapisywać i odczytyw ać dane z p likó w
bez „zawieszania" aplikacji. Połączysz te wszystkie możliwości z serializacją kontraktu danych
i opanujesz tworzenie bardzo nowoczesnych aplikacji.

Damian ma problemy z plikami 566


Aplikacje dla Sklepu Windows używają await, by błyskawicznie reagować 568
Używaj klasy FileIO do odczytywania i zapisywania plików 570
Napisz nieco mniej prosty edytor tekstów 572
Kontrakt danych jest abstrakcyjną definicją danych obiektu 577
Do odnajdywania i otwierania plików używaj metod asynchronicznych 578
Klasa KnownFolders ułatwia dostęp do najczęściej używanych folderów 580
W kodzie XML jest serializowany cały graf obiektów 581
Prześlij kilka obiektów Guy do lokalnego folderu aplikacji 582
Wypróbujmy działanie aplikacji 586
Używaj klasy Task, by wywoływać jedną metodę asynchroniczną w i n n e j 5 8 7
F ile lO . Napisz dla Damiana nową aplikację do zarządzania wymówkami 588
Odrębna strona, wymówka i ExcuseManager 589
© AppendLinesAsync
Utwórz stronę główną aplikacji Menedżera wymówek 590
© AppendTextAsync
Dodaj pasek aplikacji do strony głównej 591
© Equals
Napisz klasę ExcuseManager 592
© Rea cl E '.iffer Asy nc
Dodaj kod obsługujący stronę 594
© ReadLinesAsync
© ReadTextAsync
© ReferenceEquals
© WriteEufferAsync
© WriteBytesAsync

22
Spis treści

Obsługa wyjątków

Gaszenie pożarów nie jest już popularne


Programiści nie mają być strażakami. Pracowałeś jak wół, przebrnąłeś przez
dokumentacje techniczne i kilka zajmujących książek Rusz głową!, wspiąłeś się na szczyt
swoich możliwości: jesteś m istrzem program ow ania. W dalszym ciągu musisz jednak
odrywać się od pracy, ponieważ program w yłącza się lub nie zachow uje się tak, jak
po w in ie n. Nic nie wybija Cię z rytmu tak, jak obowiązek naprawienia dziwnego błędu...
Z obsługą w y ją tk ó w możesz jednak napisać kod, który poradzi sobie z pojaw iającym i się
problem am i. Jest nawet lepiej, możesz bowiem zareagować na ich pojawienie się i sprawić,
że wszystko będzie dalej działało.

HEJ, TEN PROGRAM


Damian potrzebuje swoich wymówek, aby być mobilnym 600
JEST NAPRAWDĘ STABILNY!
Kiedy program zgłasza wyjątek, .NET tworzy obiekt Exception 604
Kod Damiana zrobił coś nieoczekiwanego 606
Wszystkie obiekty wyjątków dziedziczą po Exception 608
Debugger pozwala Ci wyśledzić wyjątki w kodzie i zapobiec im 609
Użyj debuggera wbudowanego w IDE, aby znaleźć problem
w programie do zarządzania wymówkami 610
Twoja klasa, teraz już
użytkownik z obsługą wyjątków Oj, oj! — w kodzie dalej są błędy... 613
Obsłuż wyjątki za pomocą try i catch 615
Co się stanie, jeżeli wywoływana metoda będzie niebezpieczna? 616
Użyj debuggera do prześledzenia przepływu w blokach try/catch 618
Jeśli posiadasz kod, który ZAWSZE musi zostać wykonany,
zastosuj finally 620
Użyj obiektu Exception w celu uzyskania informacji o problemie 625
Użyj więcej niż jednego bloku catch do wyłapania
różnych typów wyjątków 626
Jedna klasa zgłasza wyjątek, inna klasa go przechwytuje 627
Łatwy sposób na uniknięcie licznych problemów: using umożliwia Ci
stosowanie try i finally za darmo 631
i n t [ ] an A rra y = { 3 , 4 , 1 , 11};
i n t aV alu e = a n A r ra y [1 5 ] ; Unikanie wyjątków: zaimplementuj IDisposable, aby przeprowadzić
własne procedury sprzątania 632
Najgorszy z możliwych bloków catch: komentarze 634
Kilka prostych wskazówek dotyczących obsługi wyjątków 636

23
Spis treści

K a p ita n W sp a n ia ły
ŚmieTlobiektu

Twoją ostatnią szansą na ZROBIENIE czegoś... jest użycie finalizatora 646


Kiedy DOKŁADNIE wywoływany jest finalizator? 647
Dispose() działa z using, a finalizatory działają z mechanizmem
oczyszczania pamięci 648
Finalizatory nie mogą polegać na stabilności 650
Spraw, aby obiekt serializował się w Dispose() 651
Struktura jest podobna do o b ie k tu . 655
. a l e nie jest obiektem 655
Wartości są kopiowane, referencje są przypisywane 656
Struktury traktowane są jak typy wartościowe,
obiekty jak typy referencyjne 657
Stos i sterta: więcej na temat pamięci 659
Używaj parametrów wyjściowych, by zwracać z metody
więcej niż jedną wartość 662
Przekazuj referencje, używając modyfikatora ref 663
Używaj parametrów opcjonalnych, by określać wartości domyślne 664
Jeśli musisz używać wartości pustych, stosuj typy, które je akceptują 665
Typy akceptujące wartości puste poprawiają odporność programów 666
„Kapitan” W sp an iały . nie tak bardzo 669
Metody rozszerzające zwiększają funkcjonalność ISTNIEJĄCYCH klas 670
Rozszerzanie podstawowego typu: string 672

24
Spis treści

Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Przejmij kontrolę nad danymi


To świat przepełniony danymi... Lepiej, żebyś wiedział, jak w nim żyć.
Czasy, gdy mogłeś programować kilka dni, a nawet kilka tygodni, bez konieczności pracy
z ogrom em danych, minęły już bezpowrotnie. Nadeszła epoka, w której wszystko opiera
się na nich. W tym miejscu do akcji wkracza LINQ. To nie tylko sposób na pobieranie
danych w prosty, intuicyjny sposób. Pozwala on także grupow ać i łączyć dane pochodzące
z różnych źródeł. A kiedy już podzielisz dane na fragmenty, którymi można łatw o zarządzać,
Twoje aplikacje dla Sklepu Windows będą korzystać z kon tro le k do na w ig ow an ia,
pozwalających poruszać się po danych, przeglądać je, a nawet powiększać i wyświetlać
szczegółowe informacje na ich temat.

Janek jest superfanem Kapitana W spaniałego. 678


. a l e jego kolekcja zajmuje każde wolne miejsce 679
Dzięki LINQ możesz pobrać dane z różnych źródeł 680
Kolekcje .NET są przystosowane do działania z LINQ 681
LINQ ułatwia jwykonywanie zapytań 682
LINQ jest prosty, ale Twoje zapytania wcale takie być nie muszą 683
Janek chętnie skorzystałby z pomocy 686
Zacznij pisać aplikację dla Janka 688
Używaj słowa kluczowego new, by tworzyć typy anonimowe 691
LINQ ma wiele zastosowań 694
Dodaj nowe zapytania do aplikacji Janka 696
LINQ może połączyć Twoje wyniki w grupy 701
Połącz wartości Janka w grupy 702
Użyj join do połączenia dwóch kolekcji w jedną sekwencję 705
Janek zaoszczędził mnóstwo szmalu 706
Użyj semantycznego powiększenia, aby przejść do danych 712
Dodaj zoom semantyczny do aplikacji Janka 714
Zrobiłeś na Janku wielkie wrażenie 719
Szablon Split App ułatwia tworzenie aplikacji służących
do przeglądania danych 720

25
Spis treści

Zdarzenia i delegaty

Co robi Twój kod, kiedy nie patrzysz


Twoje obiekty zaczynają myśleć o sobie. Nie możesz zawsze kontrolować tego,
co one robią. Czasami różne rzeczy... zdarzają się. Kiedy to następuje, chciałbyś, aby Twoje
obiekty były wystarczająco sprytne i odpowiednio reagow ały. To miejsce, w którym do akcji
wkraczają zdarzenia. Jeden obiekt udostępnia zdarzenie, inny je obsługuje i wszystko pracuje
razem, aby całość działała sprawnie. Jest to wspaniałe, o ile nie chcesz, by Twój obiekt mógł
kontrolować, kto będzie mógł nasłuchiwać jego zdarzeń. W tedy bardzo pomocne okazują się
fun kcje zw ro tn e.

Czy kiedykolwiek marzyłeś o tym, aby Twoje obiekty


potrafiły samodzielnie myśleć? 730
Ale skąd obiekt WIE, że ma odpowiedzieć? 730
Kiedy wystąpi ZDARZENIE... obiekty nasłuchują 731
Poznajemy zdarzenia trasowane Jeden obiekt wywołuje zdarzenie, inne nasłuchują... 732
Potem inne obiekty obsługują zdarzenie 733

KB =:=: Łącząc punkty


ID E automatycznie tworzy za Ciebie procedury obsługi zdarzeń
734
738
Ogólny typ EventHandler pozwala definiować własne typy zdarzeń 744
Formularze używają wielu różnych zdarzeń 745
Jedno zdarzenie, wiele procedur obsługi 746
Aplikacje dla Sklepu Windows używają zdarzeń
do zarządzania cyklem życia procesu 748
Dodaj zarządzanie cyklem życia procesu do aplikacji Janka 749
Kontrolki XAML korzystają ze zdarzeń trasowanych 752
Utwórz aplikację do badania zdarzeń trasowanych 753
Połączenie nadawców zdarzenia z jego odbiorcami 758
Delegat ZASTĘPUJE właściwą metodę 759
Delegat w akcji 760
Każdy obiekt może subskrybować publiczne zdarzenie... 763
Użyj funkcji zwrotnej, by wiedzieć, kto nasłuchuje 764
Funkcje zwrotne są jedynie sposobem używania delegatów 766
Możesz używać funkcji zwrotnych w oknach dialogowych
MessageDialog 768
Użyj delegatów, by skorzystać z panelu Ustawienia 770

O OQ
26
^ p ir e ° 6 /ek* *
Spis treści

Projektowanie aplikacji według wzorca M V V M

Świetne aplikacje od zewnątrz i od środka


Twoje aplikacje muszą być nie tylko wspaniałe wizualnie. Kiedy mówimy
o projekcie, co Ci przychodzi do głowy? Przykład jakiejś wspaniałej architektury budowlanej?
Doskonale rozplanowana strona WWW? Produkt zarówno estetyczny, jak i dobrze
zaprojektowany? Dokładnie te same zasady odnoszą się do aplikacji. W tym rozdziale poznasz
w zorzec M o d e l-V ie w -V ie w M o d e l (M VVM , model-widok-widok modelu) i dowiesz się,
jak używać go do tworzenia dobrze zaprojektowanych aplikacji o luźnych powiązaniach. Przy
okazji poznasz animacje, szablony kon tro le k używane do projektowania wyglądu aplikacji,
nauczysz się stosować kon w e rte ry, by ułatwiać korzystanie z techniki wiązania danych,
a w końcu zobaczysz, jak połączyć te wszystkie elementy, by stw o rzyć solidne p odstaw y
do tworzenia dowolnych aplikacji w języku C#.

Liga „Koszykówka. Rusz głową” potrzebuje swojej aplikacji 774


Jednak czy wszyscy uzgodnią, jak napisać tę aplikację? 775
Czy projektujesz pod kątem wiązania danych,
czy łatwości pracy z danymi? 776
Wzorzec MVVM pozwala projektować, uwzględniając
zarówno wiązanie, jak i dane 777
Użyj wzorca MVVM, by rozpocząć tworzenie aplikacji
dla ligi koszykówki 778
Kontrolki użytkownika pozwalają tworzyć swoje własne kontrolki 781
Sędziowie potrzebują stopera 789
Wzorzec MVVM oznacza myślenie o stanie aplikacji 790
Zacznij tworzenie modelu aplikacji stopera 791
Zdarzenia ostrzegają resztę aplikacji o zmianie stanu 792
Utwórz widok prostej aplikacji stopera 793
Dodaj model widoku aplikacji stopera 794
Konwertery automatycznie konwertują wartości na potrzeby powiązań 798
Konwertery mogą operować na wielu różnych typach danych 800
Stan wizualny sprawia, że kontrolki odpowiadają na zmiany 806
Używaj DoubleAnimation, by animować wartości zmiennoprzecinkowe 807
Używaj animacji obiektów do animowania wartości obiektów 808
Stwórz stoper wskazówkowy, używając tego samego modelu widoku 809
Kontrolki interfejsu użytkownika można także tworzyć w kodzie C # 814
C # pozwala także na tworzenie „prawdziwych” animacji 816
Użyj kontrolki użytkownika, by wyświetlać rysunki tworzące animację 817
Niech Twoje pszczoły latają po stronie 818
Użyj ItemsPanelTemplate, by powiązać pszczoły z kontrolką Canvas 821
Gratulujemy! (Choć jeszcze nie skończyłeś...) 834

27
Spis treści

Laboratorium C# numer 3

Invaders
Dzięki temu laboratorium oddasz hołd jednej z najbardziej popularnych, czczonych
i powielanych ikon w historii gier komputerowych. Nie potrzebuje ona żadnego wprowadzenia.
Czas utworzyć grę Invaders.

Dziadek wszystkich gier 836


Można zrobić znacznie więcej... 857

* * * * * *

* * * * * *
M A . j £ *■*

i/ sz JL '
\
Spis treści

Projekt dodatkowy!

Napisz aplikację Windows Phone


Klasy, obiekty, XAML, hermetyzacja, dziedziczenie, polimorfizm, LINQ, MVVM... dysponujesz juz
wszystkimi narzędziami niezbędnymi do pisania wspaniałych aplikacji dla Sklepu Windows oraz
tradycyjnych aplikacji okienkowych. Czy jednak wiesz, że tych samych narzędzi możesz użyć
do pisania aplikacji dla W indows P h o n e? Tak, to prawda! W tym dodatkowym projekcie
poznasz proces tworzenia gry dla systemu Windows Phone. Jeśli nie posiadasz odpowiedniego
urządzenia, to i tak nie masz się czym przejmować — będziesz mógł w nią grać na em ulatorze
W ind ow s Phone. A zatem zaczynajmy!

29
Spis treści

Pozostałości

11 najważniejszych rzeczy, które chcieliśmy


umiescic w tej książce
Zabawa dopiero sę zaczyna! Pokazaliśmy Ci mnóstwo wspaniałych narzędzi do tworzenia
naprawdę potężnych program ów w C#. Nie było jednak możliwe, abyśmy w tej książce zmieścili
każde narzędzie, technologię i technikę — nie ma ona po prostu tylu stron. Musieliśmy
podjąć naprawdę przemyślaną decyzję, co umieścić, a co pominąć. Oto kilka tematów, których nie
mogliśmy przedstawić. Pomimo tego, że nie zajęliśmy się nimi, w dalszym ciągu myślimy, że są one
ważne i przydatne. Należałoby więc chociaż o nich wspomnieć. Tak też zrobiliśmy.

1. Na temat aplikacji dla Sklepu Windows można dowiedzieć się


znacznie więcej 874
2. Podstawy 876
3. Przestrzenie nazw i złożenia 882
4. Użyj BackgroundWorker, by poprawić działanie
interfejsu użytkownika 886
5. Klasa Type oraz metoda GetType() 889
6. Równość, IEquatable oraz Equals() 890
7. Stosowanie yield return do tworzenia obiektów
umożliwiających iterację 893
8. Refaktoryzacja 896
9. Anonimowe typy i metody oraz wyrażenia lambda 898
10. Zastosowanie LINQ to XML 900
11. Windows Presentation Foundation 902
Czy wiesz, że C # i .NET Framework potrafią... 903

Developer Command Prompt for VS2012

c :\ U s e r s \ P u b lic\Documents>type HelloMorld.es
us i n g System;
class Hel l o W orld {
p u b l i c s tatic void Main(string[] args) {
C o n s o l e . MriteLine("Witaj, t’wieciel “
}
c :\ U s e r s \ P u bl ic\Documents>csc H e l l oWorld.cs
K o m pilator M i crosoft (R) Visual C # w wersji 4 . 0 . 30319.33440
dla prog r a m u Microsoft (R) .NET Framework 4.5
C opyright (C) Microsoft Corporation. Wszelkie prawa zastrzeżor

c :\ U s e r s \ P u b l i c \ D o c u m e n t s > H e l l o W o r ld .t
Witaj, świeciel

c :\ U s e r s \ P u b lic\Documents>

Skorowidz 905

30
Jak korzystać z tej książki?

Wstęp

- c -

31
Jak korzystać z tej książki?

Dl a kogo jest ta książka?


Jeżeli n a którekolw iek z tych p ytań odpow iesz „ tak ” : C zy zn a sz ju ż inny ję zy k
programowania, a te raz
m usisz p rzerzu cić się
© Czy chcesz n a u cz y ć się C # ? ^
na C # ?
© Czy lubisz m ajstrow ać — uczyć się, ro b iąc coś, zam iast tylko czytać?

© Czy p referu jesz tw ó rcze rozm ow y p rz y obied zie, C zy jesteś ju ż dobrym


a nie su c h e , n u d n e w y k ład y a k a d e m ic k ie ? programistą C # , lecz
chciałbyś dowiedzieć się
czegoś w ięcej o X A M L,
to je st to książka dla Ciebie.
w zorcu model-widok-
model widoku (M V V M )
o raz pisaniu aplikacji dla
Sklepu W indows?

Kto prawdopodobnie powinien unikać tej k siążk i?


C zy chcesz nabrać
praktycznego
Jeżeli n a którekolw iek z tych p ytań odpow iesz „ tak ” : dośw iadczenia,
pisząc dużo kodu?
© Czy pisan ie dużej ilości k o d u je st dla C iebie n u d n e i nieprzyjem ne?

© Czy jesteś zapalonym p ro g ra m istą C + + lub Java, k tóry szuka książki Jeśli ta k , to w ie d z, że
w stylu encyklopedycznym ? w iele osób podobnych do
Ciebie skorzystało z tej
© Czy b o isz się sp ró b o w ać czegoś now ego? W olałbyś raczej p o d d a ć się książki dokładnie w tych
leczeniu k anałow em u, niż połączyć paski z k ratk ą? Czy w ydaje Ci się, samych celach!
że książka tech n iczn a o C # nie m oże być pow ażna, jeżeli zagadnienia
są opisan e p o ludzku?
Korzystanie z tej książki nie
wymaga żadnych doświadczeń
to nie je st to książka dla Ciebie. programistycznych... a jedynie
ciekawości i zainteresowania!
Tysiące początkujących
programistów, którzy nie
posiadali doświadczenia,
skorzystało ju ż z książki
[Komentarz dziatu m arketingu: C#. R usz głową, by nauczyć się
ta ks iążka j e s t dla każdego pisania kodu. I Ty m o żesz do
z kartą kredytową]. nich dołączyć!

C'

32 Wstęp
Wstęp

Wiemy, o czym myślisz.


„Czy taką książkę o p ro g ram o w an iu w C # m o żn a traktow ać
p ow ażn ie?”

„O co chodzi z tym i o b razk am i?”

„Czy m ożna się uczyć w te n sp o só b ?” Tw° j mózg myśli,


z e T0 j e s t ważne.

Wiemy, co myśli Twój mózg.


Twój m ózg prag n ie nowości. Z aw sze szuka, skanuje, czeka n a coś
niezwykłego. W ten sposób został skonstruow any i to pozw ala Ci żyć.

C o robi Twój m ózg, gdy n ap o ty k a n a zwykłe, rutynow e, codzienne


rzeczy? W szystko, co m oże zrobić, to zapobiec n ak ład an iu się ich na
prawdziwe w yzwania p o p rzez w ybranie jedynie rzeczy ważnych. N ie
zajm uje się zapam iętyw aniem tych nudnych — nigdy nie p rze jd ą one
przez filtr „to jest m ało w ażn e”.

Skąd Twój m ózg w ie, co je st isto tn e? Przypuśćm y, że jesteś na


wycieczce i z naprzeciw ka w yskakuje tygrys. C o się dzieje w Twojej
głowie i w ciele?

N eu ro n y strzelają. E m ocje szaleją. H orm ony buzują.

T o w ten sposób Twój m ózg w ie...

To musi być ważne! Nie zapomnij o tym!

W yobraź sobie jed n ak , że jesteś w do m u lub w bibliotece. Jest


bezpiecznie, ciepło, nie m a tygrysów. U czysz się. Przygotow ujesz się do
egzam inu lub p róbujesz przyswoić sobie pew n ą technologię. Twój szef
myśli, że zajm ie Ci to tydzień, najwyżej dziesięć dni.

Jest je d e n problem . Twój m ózg p ró b u je Ci wyświadczyć w ielką


przysługę. C hce zadbać o to , aby ewidentnie n iep o trze b n e tem aty nie
zajm ow ały cennych zasobów , k tó re m ogą być użyte do przechow yw ania
rzeczy n apraw dę wielkich. N a przykład takich ja k tygrysy, zagrożenia
pożarow e o raz to, abyś nigdy nie zam ieszczał tych „im prezow ych” zdjęć
n a Facebooku.

N ie m a prostej m eto d y n a p rzek azan ie mózgowi: „H ej, m ózgu, dziękuję


ci b ardzo, ale bez w zględu n a to , ja k n u d n a jest ta książka i ja k nisko
jestem te ra z na em ocjonalnej skali R ich te ra , naprawdę chcę, abyś
zap am iętał w szystkie te rzeczy”.

jesteś tutaj ► 33
Jak korzystać z tej książki?

T r a k t u j e m y c z y t e l n i k a „ R u s z g ł o w ą !” ja k u c z n i a ^

tylko tekst na stronie. Wiemy, co stymuluje Twój mozg.

Niektóre z zasad nauki Rusz gk>wq!


Przedstaw to obrazow o. Ł®t w. ' ^ f ^ 0° 8 9 % lepsza zdolność do
Dzięki temu nauka jest b a r d ^ d ^ m a ^ S ^ p ^ zrozunliate.
przypominania i Przekazywa™ ^ stronie, umieść )e
Zamiast wstawiać słowa na konc^ & prawdopodobieństwo,

c T y te ln ic y ^ o z ^ ą ^ z opisywanymi zagadnieniami,
będzie nawet dwukrotnie większe. n<:tatnie

Użyj stylu nich bezpośrednie


badania wykazały, że studenci wy ■ t t j rozmowy, polegający na

!b s s s s ^ ś sbbss
E S S S $ 5£S£# *» — ^ obicd" podaas

a y t f n S s f b y ć zainspirowany,
rozwiązywania P ^ 16™ ^ 5' ^ n i a prowokacyjne pytania i czynności,
Potrzebne są do tego wyzwarla móza0wych oraz wielu zmysłów.
które wymagają uruchomienia obu półkul mo g następujące uczucie: „naprawdę

P rz y c ią g n ij - i u t n * ™ 1- « « A ™ k l6 [c
chcę się tego nauczyć, ale po jednej s ro wnaciajaCe w oko niespodziewane. Uczenie się

opanujesz materiał, jeśli takie nie będzie.

Z adziałaj na emocje W i e m y , ^ ° n S n e g o . p lm ię tS ito , o co się

za serce historii o chłopcu,, jegc^ W ^ z a m y ^ ^ ^ _

S ó re p S c h o T z ip o rozwiązaniu
: działu inżynierii
.....
oprogramowania tego nie wie.

34
Wstęp

Metapoznanie: myślenie o myśleniu


Jeżeli n apraw dę chcesz się uczyć i zależy Ci n a tym , by robić to szybko i dogłębnie, zwróć
uw agę, w jaki sposób... zw racasz uw agę n a pew ne rzeczy. Pom yśl, ja k myślisz. N au cz się
sposobu nauki.

W iększość z nas nie przech o d ziła kursów m etap o z n a n ia i teo rii n auki podczas dorastania.
O czekiw ano od nas u czen ia się, ale nie nauczono nas tego.

Przypuszczam y, że skoro trzym asz tę książkę w ręk u , to n ap raw d ę chciałbyś nauczyć się
tw orzenia program ów w C # . P raw d o p o d o b n ie nie chcesz n a to tracić dużo czasu. Jeżeli
m asz zam iar używać tego, co znajdziesz w tej książce, m usisz p am iętać, co przeczytałeś.
A by stało się to łatw iejsze, pow inieneś wszystko zrozum ieć, a aby odnieść najw iększe
korzyści z lektury tej lub jakiejkolw iek innej książki czy źródła, musisz
przejąć odpow iedzialność za swój m ózg. T en p o d p u n k t dotyczy mózgu.

Sztuczka polega n a tym , że m usisz oszukać go tak, aby traktow ał


ten m ateriał jak coś N ap raw d ę W ażnego. Coś kluczow ego dla
T w ojego istnienia. T a k sam o w ażnego jak tygrys. W przeciw nym razie
będziesz ciągle walczył ze swoim m ózgiem , aby p rzestał on blokow ać
d o starczane m u inform acje.

W jaki sposób oszukać mózg, aby traktow ał C# tak, jakby


był on głodnym tygrysem?

Istnieje w olny i nużący lub szybszy i bardziej efektyw ny sposób.


W olny p o leg a n a ciągłym p o w tarzaniu. W iesz oczywiście, że jesteś
w stanie nauczyć się i zap am iętać naw et najnudniejszy tem at, jeżeli bez przerw y będziesz wbijał
sobie do głowy to sam o. P ow tarzając w ystarczająco długo, zm usisz m ózg do przyznania: „N ie wydaje się
to dla niego w ażne, ale skoro to pow tarza, powtarza i jeszcze raz pow tarza, to przypuszczam , że coś w tym
m usi być”.

Szybszy sposób poleg a n a tym , aby zrobić wszystko, co zw iększa aktyw n o ść m ózgu, korzystając p rzed e
wszystkim z różnych jej rodzajów . E lem en ty znajdujące się n a p o p rzed n iej stro n ie stanow ią dużą część
rozw iązania. Skuteczność stosow ania tych wszystkich uro zm aiceń w stym ulacji m ózgu je st udow odniona.
N a przykład badania , k tó re polegały n a u m ieszczaniu słów wewnątrz obrazków (w przeciw ieństw ie
do w pisyw ania ich w innych m iejscach, takich ja k tytuły i zwykły tek st), wykazały, że m ózg pró b u je
znaleźć pow iązania pom iędzy tymi słowam i i o brazkam i, p o b u d zają c jed n o cześn ie do pracy w iększą
liczbę neuronów . W ięcej pobudzonych k o m ó re k nerw owych to w iększa szansa, że m ózg zakw alifikuje
inform ację jako w artą zap am iętan ia i z większym praw d o p o d o b ień stw em ją zachow a.

Styl rozm ow y jest pom ocny, p o niew aż ludzie zw racają w iększą uw agę n a szczegóły podczas konw ersacji.
Są bow iem zm uszeni do podtrzym yw ania w ym iany zdań i jej zakończenia. N iesam ow ite jest to, że m ózg
nie interesuje się faktem , że „konw ersacja” odbyw a się p om iędzy T o b ą i książką! Z drugiej strony,
jeżeli styl książki jest suchy i form alny, Twój m ózg p o strzeg a to w taki sam sposób, jakbyś był n a sali
konferencyjnej i siedział razem z pasywnymi słuchaczam i. N ie d a się nie zasnąć.

O brazki i styl konw ersacji to d o p iero początek.

jesteś tutaj ► 35
Jak korzystać z tej książki?
Kiedy defin iujesz klasę,
d efin iujesz także j e j met° d y
Oto co zrobiliśmy: podobnie ja k projekt d e f ilu je
układ pom ieszczeń w dom u.

Użyliśmy ilustracji, ponieważ Twój mózg jest zaprogramowany do odbioru obrazów, nie s*
tekstu. Biorąc to pod uwagę, możemy powiedzieć, że jeden obrazek jest wart tyle, co tysiąc
słów. Kiedy ilustracje z nimi współpracują, to dopiero się dzieje! Umieściliśmy tekst wewnątrz
obrazków, ponieważ mózg działa efektywniej, gdy jest wewnątrz czegoś, nad czym pracuje.
Może s2 użyć jednego
Jest to podejście zgoła odmienne od wypisywania tekstu w tytule lub umieszczania go gdzieś projektu do zbudowania
v dowo/nej /ic2by domów.
w innym miejscu. Możesz również przy
uży c iu je d n e j k/asy
ufwcirzyć każdą /iczbę
obiektów.
Użyliśmy redundancji, powtarzając tę samą rzecz na kilka różnych sposobów,
z wykorzystaniem odmiennych form przekazu działających na wiele zmysłów, aby zwiększyć
szansę, że zawartość książki zostanie zakodowana w więcej niż jednym obszarze Twojego mózgu.

Użyliśmy zagadnień i obrazków w niespodziewany sposób, jako że mózg jest przystosowany do


poznawania rzeczy nowych. Wykorzystaliśmy obrazki, które przenoszą pewien ładunek emocjonalny,
ponieważ mózg jest też zaprogramowany do zwracania szczególnej uwagi na biochemię emocji.
To dzięki temu, że coś czujesz, pewne rzeczy stają się łatwiejsze do zapamiętania. Nie ma znaczenia,
że uczucie to wywołało tylko coś humorystycznego, zaskakującego lub interesującego.

Użyliśmy stylu spersonifikowanego, przyjmującego postać rozmowy, gdyż mózg przystosowany jest
do większej aktywności wtedy, gdy wierzy, że z kimś rozmawiasz, niż wtedy, gdy pasywnie słuchasz
prezentacji. Zachowuje się tak samo nawet podczas czytania.

Zamieściliśmy tu dziesiątki zadań, ponieważ mózg jest zaprojektowany do nauki i zapamiętywania większej ilości
danych, jeżeli nad tym pracujesz, a nie, gdy tylko o tym czytasz. Ćwiczenia są w stylu trudne-ale-do-zrobienia, takie
zadania preferuje bowiem większość osób.
CELNE SPOSTRZEŻENIA
Użyliśmy wielu stylów nauczania, ponieważ Ty mógłbyś preferować procedury z rodzaju
krok po kroku, ktoś inny chciałby najpierw zobaczyć ogólny zarys, a jeszcze inny czytelnik —
przykład. Bez względu na preferencje, każdy odniesie pewne korzyści, zapoznając się z tym
samym materiałem prezentowanym w różny sposób. Pogawędki przy kominku
Umieściliśmy w książce treści przeznaczone dla obu półkul mózgowych. Im bardziej wykorzystujemy
potencjał mózgu, tym większa jest jego zdolność do zapamiętywania oraz nauki. Poza tym możesz
się na niej dłużej skupić. W związku z tym, że praca z wykorzystaniem jednej półkuli często wiąże się
z odpoczynkiem drugiej, możesz być bardziej produktywny, ucząc się przez dłuższy czas.

Wykorzystaliśmy historie i ćwiczenia, które prezentują więcej niż jeden punkt widzenia, ponieważ Twój umysł może być
bardziej skupiony i uczy się dokładniej podczas wyrażania ocen i sądów.

Zamieściliśmy tu wyzwania i ćwiczenia, zadawaliśmy pytania, które nie zawsze mają prostą odpowiedź, ponieważ
mózg bardziej się skupia, pracując nad problemem. Pomyśl nad tym — kształt Twojego ciała nie poprawi się
od samego obserwowania ludzi na siłowni. Zrobiliśmy, co w naszej mocy, abyś podczas ciężkiej nauki pracował
nad rzeczami właściwymi. W ten sposób nie będziesz zajmował dendrytów przetwarzaniem przykładu trudnego
do zrozumienia lub tłumaczeniem trudnego, żargonowego lub nadmiernie zwięzłego tekstu.

Skorzystaliśmy z ludzi. W opowiadaniach, przykładach, obrazkach, bo... bo jesteś człowiekiem. Twój umysł
zwraca większą uwagę na ludzi, którzy wykonują różne czynności.

36 Wstęp
Wstęp

Zmuś swój mózg do posłuszeństwa


Z robiliśm y to , co n ależało do nas. R eszta zależy o d C iebie. T e w skazówki to tylko
początek; słuchaj sw ojego um ysłu i spraw dzaj, co działa, a co nie. P ró b u j nowych rzeczy.

^ Zwolnij. Im więcej zrozumiesz, tym mniej @ Rozmawiaj o tym. Na cały głos.


będziesz miał do zapamiętania.
Mówienie uaktywnia kolejną część mózgu. Jeśli próbujesz
Nie ograniczaj się tylko do czytania. Zatrzymaj się i pomyśl. coś zrozumieć lub chcesz zwiększyć szansę na zapamiętanie
Jeżeli w książce stawiane są pytania, nie przeskakuj wprost tego w przyszłości, powiedz to na głos. Jeszcze lepiej —
do odpowiedzi, lecz wyobraź sobie, że ktoś naprawdę je spróbuj powiedzieć to głośno komuś innemu. Będziesz się
zadaje. Im bardziej zmusisz umysł do pracy, tym większa uczył znacznie szybciej i odkryjesz szczegóły, których nie
jest szansa na naukę i zrozumienie. zauważyłeś wcześniej podczas czytania.

© Wykonuj ćwiczenia. Pisz własne komentarze. Q Słuchaj umysłu.


Wstawiliśmy rozwiązania, ale gdybyśmy robili wszystko Zwracaj uwagę na sytuacje, w których Twój mózg jest
za Ciebie, mogłoby to wyglądać tak, jakby ktoś wyręczał przeciążony. Gdy dojdziesz do wniosku, że prześlizgujesz się
Cię w Twojej pracy. Nie ograniczaj się do patrzenia po tekście lub zapominasz, co przed chwilą przeczytałeś, czas
na ćwiczenia. Użyj ołówka. Istnieje wiele świadectw zrobić sobie przerwę. Po dotarciu do pewnego punktu nie
potwierdzających to, że aktywność fizyczna podczas nauki będziesz się uczył szybciej, wkładając do głowy coraz więcej.
może zwiększyć jej wydajność. Cały proces może na tym tylko ucierpieć.

@ Przeczytaj „Nie istnieją głupie pytania". ® Poczuj coś.


Oznacza to, że powinieneś przeczytać wszystko. To nie jest Twój mózg musi wiedzieć, że to coś ma znaczenie. Wczuj się
część poboczna — to integralna część głównej zawartości! w opowiadania. Wstaw własne opisy do obrazków. Jęczenie
Nie omijaj jej. nad głupim żartem jest mimo wszystko lepsze niż brak
jakichkolwiek uczuć.
@ Spraw, aby była to ostatnia rzecz, którą
czytasz przed snem, a przynajmniej ostatnia Pisz dużo programów!
stanowiąca wyzwanie.
Istnieje tylko jeden sposób na naukę programowania: pisanie
Część nauki (zwłaszcza transfer do pamięci długotrwałej) dużej ilości kodu. Będziesz to robił stale podczas czytania
odbywa się już p o odstawieniu książki. Twój mózg tej książki. Programowanie to zdolność, którą się nabywa,
potrzebuje dla siebie czasu, w którym dokonuje dalszego a jedyna droga wiodąca do tego celu to ciągły trening.
przetwarzania. Jeżeli dołożysz mu wtedy coś nowego, to, Zamierzamy umożliwić Ci zdobycie doświadczenia: każdy
czego się wcześniej nauczyłeś, może zostać utracone. rozdział posiada ćwiczenia, które przedstawiają pewien
problem do rozwiązania. Nie pomijaj ich — duża część
Q Pij wodę. Dużo wody.
procesu nauki odbywa się właśnie podczas pracy nad nimi.
Twój mózg najlepiej pracuje w wodzie. Odwodnienie (które Zamieściliśmy odpowiedź do każdego ćwiczenia — nie bój
ma miejsce, zanim poczujesz się spragniony) zmniejsza się zaglądnąć do rozwiązania, jeżeli utknąłeś! (Łatwo się
zdolności poznawcze. pogubić przy czymś prostym). Spróbuj jednak najpierw sam
rozwiązać problem. Bezsprzecznie musisz jednak znaleźć
rozwiązanie, zanim przejdziesz do kolejnej części książki.

jesteś tutaj ► 37
Jak korzystać z tej książki? Zrzuty ekranów zamieszczone w tej książce zostały zrobione
w Visual Studio Express, najnowszej darmowej wersji środowiska
dostępnej w czasie pisania tej książki. Kolejne wydania będą
aktualizowane, jednak firma Microsoft zazwyczaj zapewnia
Czego potrzebujesz do tej książki? możliwość pobierania starszych wersji Visual Studio.

N apisaliśm y tę książkę, korzystając z V isual S tudio E x press 2012 fo r W indow s 8 o raz V isual Studio E x p ress 2012
for W indow s D esktop. W szystkie zrzuty ekranów , jakie zam ieściliśm y w tej książce, zostały zro b io n e przy użyciu tych
dwóch w ersji V isual S tudio, dlatego zalecam y, abyś tak że Ty ich używał. Jeśli używasz innej w ersji V isual S tudio 2010 —
Proffesional, P rem ium , U ltim ate lub T e st P ro fessio n al — zauważysz pew ne d ro b n e różnice (nie spraw ią Ci o n e je d n ak
żadnych problem ów podczas pracy n ad kodem przedstaw ionym w książce).

Instalacja Visual Studio 2012 Express Edition ----------------------------------------------------------

★ Visual Studio Express 2012 fo r W ind ow s 8 można pobrać bezpłatnie z witryny firmy Microsoft. Środowisko instaluje się
bezproblemowo, także gdy na komputerze są już zainstalowane inne wersje Visual Studio 2012, jak również starsze wersje
Visual Studio: h ttp://w w w .visualstudio.com /dow nloads/dow n load-visual-studio-vs.

Kliknij łącze „Install « VCT Irr 1Ï nr,nv.s1t w 11ap

now", aby uruchomić


'i łlirt A VA ■riflTA■.
instalator sieciow y, który
a ro m a ty c zn ie pobierze "ftj 3 1 ir. ïfa* y j * j j p a Wl i MV, r ir v r L '^ M 1*
* Visual Studio
V isual Studio. la \ M.i a * .i -j.h. Im n - lii .1 .. r. al tai
iTiTr W r l m r : T ą nd'.'.T V . . ^ r l ¥ i r W H K n T ,~ f ï
j : hi AO r.i. Wt i i?mj~ w.Vm i t i a m:■>#.. Express 2012 for Windows 8

Dodatkowo będziesz
także m usiał
wygenerować
klucz produktu,
co w przypadku
korzystania
z wersji Express
nic nie kosztuje
(choć wymaga
utworzenia konta
Microsoft.com).

★ Po zainstalowaniu tej wersji Visual Studio w podobny sposób będziesz musiał zainstalować Visual Studio Express 2012 fo r
W ind ow s Desktop.

Co zrobić, je ś li nie masz Windows 8 lub nie możesz uruchomić Visual Studio 2 0 1 2 ?
W iele ćwiczeń zam ieszczonych w tej książce w ym aga p o siad an ia system u W indow s 8. Oczywiście rozum iem y, że niektórzy
z Czytelników m ogą go nie używać — n a przykład w ielu p rofesjonalnych p rogram istów używ a w pracy k om puterów
z ta k starym system em operacyjnym jak W indow s 2003, bądź m ają zainstalow ane V isual Studio 2010 i nie m ogą go
zaktualizow ać. Jeśli należysz do tej g ru p y Czytelników, to nie m u sisz się przejm ow ać — w ciąż będziesz m ógł w ykonać
niem al wszystkie ćw iczenia zam ieszczone w książce. O to ja k to zrobić:

★ Ćw iczenia zam ieszczone w rozdziałach o d 3. do 9. i w pierw szych dwóch lab o rato riach w ogóle nie w ym agają system u
W indows 8. B ędziesz naw et w stanie w ykonać je, używając V isual Studio 2010 (a naw et 2008), choć w tym przypadku to,
co zobaczysz n a ek ran ie, będzie się nieco różnić o d zrzutów zam ieszczonych w książce.

★ W p rzypadku pozostałych rozdziałów będziesz m u siał tworzyć trad y cy jn e a p lik acje W indow s P re se n ta tio n F o u n d atio n
(W PF), a nie aplikacje dla system u W indow s 8. W ięcej inform acji n a te n te m a t m ożesz tak że znaleźć w p u n k cie 11.
d o d atk u „P ozostałości”.

38 Wstęp
Wstęp

Przeczytaj to Użyliśmy dużej ¡/ości


diagramów, aby trudne
pojęcia byty łatw iejsze
T o pew nego rodzaju dośw iadczenie z pro g ram o w an iem , a nie p o rad n ik do zrozum ienia.
encyklopedyczny. C elow o usunęliśm y wszystko, co m oże stan ąć n a d ro d ze do po zn an ia
głównych zagadnień poruszanych w tej książce. Podczas pierw szego czytania musisz
zacząć o d początku, poniew aż dalsze rozdziały zakładają, że w idziałeś ju ż wcześniej
pew ne rzeczy i się ich nauczyłeś.

Zadania NIE są opcjonalne.

Z a d a n ia i ćw iczenia nie są tylko d o d atk iem , są głów ną częścią tej książki. N iek tó re
z nich p o m agają w zapam iętyw aniu, n iek tó re u łatw iają zrozum ienie, in n e pozw alają
zastosow ać zdobytą w iedzę. N ie p o m ija j ćwiczeń. Z agadkow y b asen to jedyne
ćw iczenia, których nie m usisz robić, ale stanow ią o n e d o sk o n ałą szansę dla Tw ojego
m ózgu, aby pom yślał o słow ach w innym kontekście.

Nadmiarowość jest celowa i ważna. Powinieneś wykonywać


WSZYSTKIE ćwiczenia
K siążkę R u sz głową! w yróżnia to , że n ap isan a je st w celu rzeczywistej pom ocy .Z a o strz ofówek”.
w zrozum ieniu opisywanych w niej zagadnień. C hcem y, abyś p o zakończeniu lektury
n apraw dę p am iętał to , czego się nauczyłeś. W iększość książek z założenia nie Zaostrz ołówek
przew iduje nadm iarow ości i p o w tó rek , ale ta je st o n auce. N ie k tó re zagadnienia
zobaczysz w ięc częściej niż jed e n raz. %
Wykonuj wszystkie ćwiczenia!
Ćwiczenia oznaczone etuk
Jednym w ielkim założeniem podczas p isan ia tej książki było to , że chcesz się nauczyć »Czczenia» (buty sportow
program ow ać w C # . W iem y, że szybko chcesz przystąpić do dzieła i zacząć pisać ą n?prawdę ważne! Nie
kod. D aliśm y Ci w iele okazji do d o sk o n alen ia um iejętności, um ieszczając w każdym chcesz
n cesz sI ię
° h'nauczyć
je ś li m C*.
PraMd
rozdziale ćwiczenia. N ie k tó re z nich oznaczyliśm y ety k ietą „Z ró b to !” — gdy zobaczysz
coś takiego, m ożesz być pew ien, że w celu zn alezienia rozw iązania przejdziem y przez
kilka etapów . Jeżeli zobaczysz napis „Ć w iczenia” z b u tam i sportow ym i, rozw iązanie
w iększej części p ro b le m u będzie n ależało do C iebie. N ie obaw iaj się, jeżeli będziesz
m usiał p o d ejrzeć rozw iązanie — to n ie o sz u stw o ! N ajw ięcej nauczysz się je d n a k
w tedy, gdy sam do niego dojdziesz. Ćwiczenia

U m ieściliśm y także rozw iązania wszystkich zad ań , abyś m ógł je p o b rać. Z najdziesz je Gdy Widzisz logo Z agadk ow ego
n a ftp://ftp.helion.pl/przyklady/cshru3.zip. basenu", zadanie m e j e s t
obowiązkowe. Jeżeli m e lubjsz
pokrętnej logiki, takie ćw iczenia
Ćwiczenia „W ytęż umysł” nie mają rozwiązań. m ogą Ci s i ę nie spodobać.

D la jednych z nich nie m a właściwych odpow iedzi, dla innych ćwiczeń „W ytęż
um ysł” częścią n auki je st ok reślen ie, k tó re z odpow iedzi są pop raw n e. W niektórych
ćw iczeniach tego typu znajdziesz w skazówki, k tó re nap ro w ad zą Cię n a właściwy trop.

jesteś tutaj ► 39
Jak korzystać z tej książki?

Grupa korektorów technicznych


Lisa Kellner Chris BumOVV5

Choć nie zam ieszczam y tu


ich zdjąć, jednak równie
wspaniatą robotą wykonali
korektorzy poprzednich
wydań książki: Joe Albahari,
J ay Hilyard, Aayam Singh,
Theodore, P eter Ritchie,
Bill M eitelski, Andy Parker,
Wayne Bradney, Dave
Murdoch, B ridgette
Julie Landers, Nick Paldino,
David Sterling. Specjalne
podziękowania chcielibyśmy
przekazać Alanowi
Ouellette oraz innym
czy telnikom za informacje
o problemach, których nie
z auważyła kontrola jakości
dwóch poprzednich wydań
książki.
K orektorzy techniczni:

K iedy napisaliśm y tę książkę p o raz pierwszy, zaw ierała o n a m nóstw o pom yłek, problem ów , literów ek, nieścisłości
i okropnych błędów obliczeniow ych. D o b ra , nie było w cale ta k źle. Jesteśm y je d n a k niezm iern ie w dzięczni za pracę,
k tó rą korekto rzy techniczni w ykonali dla tej książki. M ogliśm y pójść do d ru k arn i z błędam i (w łączając w to je d e n lub dwa
napraw dę pow ażne), gdyby nie n ajlepsza g ru p a k o rek to ró w technicznych, jak a K IE D Y K O L W IE K istn iała...
P rzed e w szystkim n apraw d ę chcem y podziękow ać L isie K e lln e r — to ju ż d z iew ią ta (!) k siążk a, k tó r ą d la n a s p o p raw ia,
a jej p ra c a w o g ro m n y m s to p n iu p o p ra w iła c zy te ln o ść k o ń c o w eg o p ro d u k tu . D z ię k u je m y C i, Liso! S p ec ja ln e
p o d z ię k o w a n ia chcieliśm y ta k ż e p rz e k a z a ć C h riso w i B u rro w so w i, R eb ece D u n n - K r a h n o ra z D avid o w i S terlin g o w i
z a n ie z lic z o n ą liczbę w sk a z ó w e k te c h n ic z n y c h o ra z J o e m u A lb a h a ri i Jo n o w i S k eet z a ic h u w a ż n ą i d b a łą re c e n z ję
p ierw szeg o w y d a n ia k siążk i, a ta k ż e N ickow i P a la d in o , k tó ry w p o d o b n y sp o só b z a d b a ł o jej d ru g ie w yd an ie.
C h ris B u rro w s jest p ro g ram istą w firm ie M icrosoft, zatru d n io n y m w zespole pracującym n ad k o m p ilato rem C # ,
który zajm ow ał się głów nie p ro jek to w an iem i im p lem en tacją nowych (zwłaszcza dynam icznych) aspektów C # 4.0.
R eb eca D u n n - K r a h n je s t zało ż y c ie lk ą S e m a p h o re S tu d io s, sk le p u z o p ro g ra m o w a n ie m w V ic to rii w K a n a d z ie ,
k tó ry sp ecjalizu je się w ap lik a c ja c h .N ET. M ie sz k a w V ic to rii w ra z ze sw ym m ę ż e m T o b ia se m , dziećm i: S o p h ią
i S e b a stia n e m , k o te m o ra z tr z e m a k u rc z a k a m i.
D av id S te rlin g przez n iem al trzy la ta praco w ał w zespole zajm ującym się tw orzeniem k o m p ilato ra V isual C # .
J o h n n y H alife je s t głów nym a rc h ite k te m i w spółzało ży cielem M u ra l.ly (http://m urally.com ) — in te rn e to w e g o sta rtu p u
p ozw alającego u ży tko w n ik o m n a tw o rz e n ie m u rali: g ro m a d z e n ie n a n ic h d ow olnych tre śc i i o rg an izo w an ie ich
w elastyczny i organiczn y sp o só b w je d n e j dużej p rz e strz en i. J o h n n y je s t sp ecjalistą o d tech n o lo g ii c h m u r i ro zw iązań
zap ew niających d u żą skalow alność. J e s t ta k ż e p a s jo n a te m b ie g a n ia i w ielk im fa n e m sp o rtu .

40 Wstęp
Wstęp

Podziękowania
N a sz redaktor:

C hcem y podziękow ać naszej red a k to rce , C o u rtn e y N a sh ,


za edycję tej książki.

Courtney Nash

Z espół O ’Reilly:

Je st ta k w ielu ludzi w O ’Reilly, którym chcielibyśm y podziękow ać, że m am y


nadzieję, że nikogo nie pom iniem y. Szczególne podzięk o w an ia chcielibyśmy
złożyć red a k to rc e w ydania M e la n ie Y a rb ro u g h t, E llen T ro u tm an -Z aig , k tó ra
p rzygotow ała indeks, R a c h e l M o n a g h a n za jej d o k ład n ą k o rek tę, R onow i
B ilo d e a u za d obrow olne pośw ięcenie czasu i w ykonanie ostatn iej weryfikacji
— wszystkim , którzy pom ogli w ydać tę książkę w rekordow ym czasie. Jak
zwykle kocham y M a ry T re s e le r i nie m ożem y się doczekać p racy z n ią p o raz
kolejny! W ielkie p o dziękow ania dla naszych pozostałych przyjaciół i redaktorów ,
A n d y ’ego O ra m a , M ik e ’a H e n d ric k s o n a , L a u rie P e try k i, T im a O ’R eilly o raz
S a n d e rs a K le in fe ld a . Jeżeli te ra z czytacie tę książkę, to m ożecie też podziękow ać
najlep szem u zespołow i teg o przem ysłu: M a rs e e H e n o n , S a rz e P e y to n i reszcie
starych znajom ych w S ebastopolu.

jesteś tutaj ► 41
42 Wstęp
1. Zacznij pisać proqramv w C #

Napisz coś fajnego, i to szybko!

Czy chcesz tworzyć wspaniałe programy naprawdę szybko? Wraz z C#


dostajesz do ręki św ie tn y język program ow ania i wartościowe narzędzie. Dzięki Visual
Studio IDE już nie będziesz musiał poświęcać długich godzin na pisanie nędznego kodu,
by ponownie zapewnić prawidłowe działanie przycisku.. I to nie wszystko. Dodatkowo będziesz
mógł skupić się na pisaniu napraw dę fajn ych program ów , zamiast starać się zapamiętać,
który parametr metody odpowiadał za nazwę przycisku, a który za wyświetlany na nim tekst.
Brzmi zachęcająco? Przewróć zatem stronę i przystąpmy do programowania.

to jest nowy rozdział ► 43


C# to ułatw ia

Dlaczego powinieneś uczyć się C#


IDE — lub V isual Stu d io Integrated
Develop m ent Environment — pełni
C # o raz V isual Studio ID E u łatw iają Ci p o zn an ie tajników p isan ia k o d u i, ważną rolę w pracy z C#. To program,
co w ażne, pisan ia go szybko. K iedy pracujesz z C # , p a k ie t V isual Studio który pozwala Ci edytować kod,
zarządzać plikami, a także publikować
je st Tw oim najlepszym przyjacielem o raz stałym kom panem . Twoje aplikacje w Sklep ie Windows.

A oto lista czynności,


które ID E wykonuje za Ciebie:
p riv a te vo id I n it i- o i- , r>
j u i n î t T a î î 2eComponent(]
tb i s . button! = rnoif c. + •
Z a każdym razem , kiedy zam ierzasz rozpocząć thi s.SuspendLayout (] ¡S'™' ndo,s' For["s•Button(J;
pisanie p ro g ram u lub chociażby um ieścić przycisk / / button!

na form ularzu, Twój p ro g ram p o trzeb u je całej th is .b u tt o n !.L o c a tio n - ? *


; "but “ r f Ste"-Dr» iri3-Pointi,0S, 56] ;
masy pow tarzającego się kodu. t h i s .tujttonl .'pitif„ rie f™ o ?'s t a " - Dral' i n9.Si2e(75. 23 ] ■

using r , nprt;ons.Generic; // formt ' ' i,Ste"'EK"tH- d'--(this.tluttonl(,lickh


//
m resp ace A > » J v'C3 v™ ^^•^toScaieOimensTons = « 4.
tnis.AutoScaleMode = Svst^m %y s tem-Drawing.SizeF('8 F iac i
1 s t a t i c c la s s P r o s ™

1 III e n try p a in t fo r the a p p lic a tio n .


th,!M
tnîs.ReXt
esum=etayout(fa
"form! ?se];
// / </summary>
[STAThread]
s t a t i c v o id M am U

z
py^ isknieco
h'ego \a °fo Z kÎlar
w ie ^ y D°danie
J £ ? t-' nZyS°WdoaĆ
wizualnych może spow od y e!ementów
wynikowy kod bP e Z ed7 ać‘.
Co otrzymujesz razem £ 2 2 / d łuższy. ę e
z Visual Studio oraz C # ?
Z językiem C # , przystosow anym do prog ram o w an ia
W indow s, o raz V isual Studio ID E m ożesz natychm iast
skupić się n a tym , co pow inien ro b ić Twój p rogram .

zabiera mniej czasu

r
® No to zaczynajmy!

Czyje« dobrze7

c # o raz V isual Studio


IDE p osiadają w cześniej Wartość
przygotowane struktury ,
zarządzające nudnym kodem
który j e s t elementem mm w^u
najczęściej wykonywanych
zadań programistycznych.

p o s tę p
do danych
44 Rozdział 1.
Zacznij pisać programy w C#

C# oraz Visual Studio


ułatwiają wiele czynności
K iedy używasz C # i V isual Studio, dostajesz wiele
w spaniałych m ożliwości bez żadnego dodatkow ego
n ak ład u pracy. R easum ując, uzyskujesz możliwość:

Tworzenia aplikacji SZYBKO. Pro g ram y w C # pisze się błyskawicznie. Język jest
p otężn y i łatw y do o panow ania, a V isual Studio ID E p rzejm u je o g ro m n ą część pracy
i w ykonuje ją za C iebie autom atycznie. M ożesz p o m in ąć przyziem ne spraw y zw iązane
z kodow aniem ID E , skupiając się n a tym , co Twój k o d pow inien wykonywać.

Zaprojektowania wspaniale wyglądającego interfejsu użytkownika. V isual


D esig n er w środow isku V isual Studio ID E je st jednym z najprostszych istniejących
narzędzi do projektow ania. R o b i za C iebie ta k w iele, że kreow anie oszałam iających
interfejsów staje się je d n ą z najbardziej satysfakcjonujących czynności podczas tw orzenia
aplikacji w C # . M ożesz budow ać p ro fesjo n aln e, w p ełni fu nkcjonalne pro g ram y
bez n iep o trzeb n eg o tra c e n ia w ielu godzin n a p isanie p o raz kolejny o d podstaw
graficznego in terfejsu użytkow nika.

Tworzenia programów zachwycających wyglądem. Łącząc C # z X A M L , językiem


znaczników stworzonym w celu projektow ania interfejsów użytkow nika, będziesz m iał do
dyspozycji jed n o z najbardziej efektywnych narzędzi do tw orzenia program ów z graficznym
interfejsem użytkow nika... i skorzystasz z niego, by tworzyć program y, któ re nie tylko
św ietnie działają, ale i w spaniale wyglądają. \
Skupienia się na rozwiązywaniu PRAWDZIWYCH problemów. ID E rob i za Ciebie
w iele, ale w dalszym ciągu to Ty p an u jesz n a d tym, co tw orzysz za p o m o cą C # .
ID E pozw ala Ci skupić się n a Tw oim p ro g ram ie , p racy (lub zabaw ie!) o raz klientach,
a do tego zajm uje się całą czarn ą ro b o tą , ta k ą jak:

★ śledzenie wszystkich T w oich projektów ,


o
★ u łatw ianie edycji kodu,

★ kontro lo w an ie grafiki, dźwięków, ikon o raz innych zasobów w T w oich p ro jek tach ,

★ zarządzanie i in terak cja z bazam i danych.

O znacza to , że cały ten czas, jaki m usiałbyś spędzić, w ykonując rutynow e zadania,
A k a c je
m ożesz przeznaczyć n a tw o rz e n ie zab ó jc z y c h p ro g ram ó w .

Wkrótce dow iesz się,


co nap rawdę mamy na myśli.

jesteś tuta
Zatem zaczynajm y
Jeśli nie w idzisz tej opcji, to najprawdopodobniej
uruchom iłeś V isual Stu d io 2012 for W M ow s D esktop.
W takim przypadku będziesz m usiał j e _zam knąć
Co robić w Visual Studio... i uruchomić V isual Studio 2012 for wi’ndows 8.

A zatem , jeśli jeszcze tego nie zrobiłeś, to u ru ch o m V isual S tudio 2012 fo r W indow s 8. P o m iń stro n ę startow ą
i w ybierz z m en u F IL E opcję N ew Project. D o w yboru m am y kilka różnych rodzajów projektów . N a liście z lewej
strony rozw iń opcję V isual C # o ra z W indow s Store, a n a stęp n ie w ybierz szablon p ro je k tu aplikacji dla S klepu
W indow s — B lan k A pp (XAM L). W rezu ltacie ID E utw orzy fo ld er Visual Studio 2012 w Tw oim folderze
M oje dokum enty, n astęp n ie um ieści w nim fo ld er Projects, a w ew nątrz niego utw orzy now ą aplikację
(m iejsce, w którym zostanie u tw o rzo n a aplikacja, m o żn a zm ienić, p o d ając je w p o lu L ocation).

B r o i ........................................
G L a r Opcje dostępne w Twojej
' , . wersji IDE mogą wyglądać
Obejru/i łoi nieco inaczej.

Tak wygląda o kno dialogowe N ew Project


w Visual Studio 2012 fo r Windows 8
Express Edition. Jeśli używasz Visual
Studio w wersji Professional lub Team
Foundation, m oże ono wyglądać
nieco inaczej. N ie m a się je d n a k czym
przejm ować, wszystko i ta k będzie
działało ta k samo.

Pamięta/, by Wybierz z menu


utw orzeniu. W t^ ? r / S a v c As - spowoduje
gfównego opcją MSZuStkich plików
to zapisanie na dysku w s z y ^ ^ r,
Co Visual Studio robi w naszym im ieniu... wchodzących " s k ła d y tJ0 zostan ie zapisany

K iedy tylko zapiszem y nowy p ro jek t, ID E tw orzy g ru p ę plików, do której, t y C t T p l i k , nad którym aktualnie
m iędzy innym i, b ęd ą należały pliki: M ainPage.xaml, MainPage.xaml.cs pracujesz.
o raz A pp.xam l.cs. Z o sta n ą o n e w yśw ietlone w o knie Solution Explorer,
a ID E um ieści je w folderze Projects\App1\App1.
t umieszczony Teni plik zaw iera kod C#
Ten plik zaw iera kod XAML ^ J ^ ^ k o n t r o l o j ą c y zachowań wykonywany w momencie
definiujący interfejs użytkownika K°Awnej strony aplikacji.
strony gtównej aplikacji- * _

<yid>

</yid>
</p*y>
MainPage.Xaml.cs
App.xaml.cs
MainPage.xaml
V isual Stu d io autom atycznie tw orzy te
w szy stk ie pliki. Oprócz nich tw ó rz/ ta.kie
kilka innych plików! Można j e wszy stk ie
zobaczyć w oknie Solution Explorer.
46 Rozdział 1.
Zacznij pisać programy w C#

Zaostrz ołówek
Wystarczy kilka prostych kroków i okno Visual Studio będzie wyglądało tak jak na poniższym rysunku. Przede
wszystkim musisz się upewnić, że są wyświetlone okna Toolbox (nazywane także przybornikiem) oraz Error
List, w tym celu należy je w ybrać na liście w menu VIEW. Następnie wybierz z menu głównego opcję TOOLS/
Options i w oknie dia lo g o w ym Options w yb ie rz opcję Light z lis ty Color theme. Powinieneś być w stanie
samemu domyślić się przeznaczenia wielu spośród tych okien, na podstawie posiadanej już wiedzy. A zatem,
w pustych miejscach obok rysunku zapisz, jakie jest przeznaczenie poszczególnych elementów IDE. Aby Ci
ułatwić zadanie, wpisaliśmy jedną z odpowiedzi. Sprawdźmy, czy będziesz w stanie omówić znaczenie wszystkich
wskazanych elementów.
U dołu strony um ieściliśm y
en pasek narzędzi pow iększenie t ego okna.,
zawiera przyciski abyś miał w ięcej m iejs ca
odpowiadające na opis jego elementów .
Czynnościom,
które aktualnie
wykonujesz
w Visual Studio

Okno Designer pozwala......


edytować interfejs
użytkownika poprzez
przeciąganie i um ieszczanie
na nim kontrolek.

W ybraliśm y ja sn y sc h em at
kolorów (opcja Light), poniew aż
takie lepiej w yglądały w książce.
Je śli Ci się podoba, to w ybierz
z m enu głów nego opcję TOOLS,
n astęp n ie „ O p tio n s...”, po czym
rozwiń opcję E nvironm ent i kliknij
General', sc h e m a t kolorów m ożesz
w ybrać z listy Color them e
(w każdej chwili m ożesz w rócić
do poprzedniego).

jesteś tutaj ► 47
Poznaj swoje IDE

aostrz ołówek
Rozwiązanie Poniżej podaliśmy opisy poszczególnych elementów Visual Studio C# IDE.
Być może samemu podałeś nieco inne opisy, niemniej jednak sądzimy, że potrafiłeś
domyślić się, jakie jest podstawowe przeznaczenie każdego z okien IDE.

P ^ y c is k lko d p 7 ^ d a j ą c J era
czynnościom, które akt,, i ■
wykonujesz w

App1 Microsoft Visual Studio Express 2012 (or Windows 8 fi _ O X


Ul ICH v*w HKMCT OUUC I LAM PUKN 90MMT TOClł łTOM TUT
O rt C UJ* b lecaiMKtww • Drtoq • Any CPU - 9t
• 9 * 9 x |
jk WOiToaKioi fi Æ o o a e >
* iCoflwwiXAMC«srak Scare* Sduben bplenr (CM«d p -i
i S TcMo
•«1 SoMien ‘App! ’(1 priest)
i n B e *. j •* Appl
Q nunc » fi Prcpmics
GJ CSsskBc. > • • Krf<i«n<n
Cen*c9e« » ■
s * « fl»V«v. : Bi fcrnmon
> O AfęUffT
!S Ond
To j e s t przybornik ID Aee* Trmpcrarytcjrpł.
» ,r> M«nA*9«^4fnl
— okno Toolbox. ED | 3 P - ^ . r p p ------ Tcrf

Zaw iera wiele □ InfV.*« Okno Designer pozw ala......


kontrolek □ « K tw ÿ i edytować interfejs
H y*i»p*"ci
wizualnych, które (T] Tertlork użytkownika poprzez
można przeć<ągać H t enfle przeciąganie i u m ieszczanie
i um ieszczać na k Pe*n«r
na nim kontrolek.
tworzonej s fronie. SI
EJ
Af»*»
B o *.
C9 Button
□ C»WI
O Captuntlcmenł
□ ChwkCei
^ CemfeeBe«
3] ConrmłControJ
|r C«4cnlPrtwn(ci To okno przedstawia
O (»«H.
4» FI?V«W
w łaściw ości elem entu,
Û którą w danej chwil'
9 Ond
jB lindVin» jes?w yb ran y w oknie
E3 »nag«
Designer.
►lnte>*ct>oni
«» im C o M iil
O ccmtfrcscntcr
O In lfla
Jeśli nie w idzisz okna Error L istT u b
Toolbox, to wybierz je z menu VIEW.
2231

C zy w idzisz tę niewielką
ikonę pinezki? Możesz ją
Łdotyczące
i Ł i y aplikacji. S olution Explorer
klikać, by w łączać i w yłączać
automatyczne ukrywanie okna.
is T© - ? a a m
Na przykład okno Toolbox
Search Solution Explorer (Ctrl-*-;)
ma domyślnie włączoną opcję
^ y ś i 7 eet/aneUsa°n ^ P h r *r 5 1 Solution 'App1' (1 project) automatycznego ukrywania.
XAWL oraz Z Tn A [c # ] Appl
autom atycznie ' J Óre ID^
P f* Properties
P o d c z a Z o T jn T a erUje P References
^ ¡ J ° k r Z ^ ° We9° - \ P l i Assets ^ S I r jm o z Z kn\ So,uti'ion
do roz*<ązmtia. Rl'k' należ<lce t> H C om m on 1 ^ ia tia ó ró ż T p T Z
P rJ App.xamll
*E3 App1_Tem poraryKey.pfx
P ^ M ainPagejiam l
la p ackage.appxm anifest

48 Rozdział 1.
Zacznij pisać programy w C#

LNie istnieją. .
głupie pytania

P: : Skoro IDE tworzy za mnie wszystkie P : Wspominaliście coś o łączeniu C# i XAML.


te pliki, to czy nauka C# sprowadza się Czym jest XAML i jaki ma związek z C#?
do opanowania obsługi IDE?
O: XAML (wymawiane jako „zamel") jest językiem
O: Nie. IDE dobrze sobie radzi z generowaniem znacznikowym , którego będziesz używał do
Visual Studio
pewnych fragmentów kodu, jednak nie może dla tworzenia interfejsu użytkownika pełnoekranowych
nas zrobić aż tak dużo. Pod niektórymi względami aplikacji przeznaczonych dla Sklepu Windows. wygeneruje
jest naprawdę świetne, na przykład jeśli chodzi XAML bazuje na języku XML (którego nie będziemy
o automatyczne generowanie punktów wyjściowych
kod, któ re g o
przedstawiać w tej książce), a zatem jeśli znasz HTML,
do dalszej pracy lub automatyczne zmienianie będziesz miał ułatwione zadanie. Oto przykładowy możesz użyć
właściwości kontrolek formularzy. Jednak w tym, co znacznik XAML pozwalający narysować elipsę: jako punktu
jest w programowaniu najtrudniejsze — określaniu, co
< E llip se Fill= "G ray " Height="100" Width="75" />
programy mają robić oraz jak ten cel osiągnąć — żadne wyjściowego
IDE nas nie wyręczy. Chociaż Visual Studio IDE jest Wiadomo, że jest to znacznik, ponieważ rozpoczyna się
podczas
jednym z najbardziej zaawansowanych z dostępnych od nawiasu kątowego <, za którym jest umieszczone
środowisk programistycznych, to jest w stanie zrobić słowo ("E llip s e "); ta kombinacja reprezentuje znacznik tworzenia
tylko tyle. To Ty, a nie IDE, będziesz pisał kod, który otwierający. Przedstawiony powyżej znacznik E llip s e własnych
zapewni faktyczne działanie programu. ma trzy właściwości: pierwsza z nich określa kolor
wypełnienia, a dwie pozostałe, odpowiednio: wysokość aplikacji.
P : A co jeśli IDE wygeneruje kod, i szerokość. Ten znacznik kończy się kombinacją znaków
którego nie będę chciał w swoim projekcie? /> , jednak niektóre znaczniki XAML mogą zawierać Tylko do
w sobie inne znaczniki. Powyższy znacznik można by
O: Możesz go zmienić. IDE jest skonfigurowane zmienić w znacznik kontenera, zastępując kombinację Ciebie należy
w taki sposób, by generowało kod odpowiadający / > znakiem >, dodając kolejne znaczniki (które także zapewnienie,
najczęstszemu sposobowi wykorzystania elementu mogą zawierać jeszcze inne znaczniki) i kończąc to
przeciągniętego lub dodanego do formularza. Jednak wszystko znacznikiem zamykającym, który
że aplikacja
czasami nie będzie to dokładnie to, czego byśmy chcieli. w powyższym przypadku miałby postać: < /E llip s e > . będzie
Wszystko co IDE robi za nas — każdy wygenerowany W dalszej części książki dowiesz się znacznie więcej o tym,
przez nie wiersz kodu oraz każdy dodany plik — można jak działa XAML, i poznasz wiele jego znaczników.
robiła to ,
zmienić; bądź to ręcznie poprzez bezpośrednią edycję co powinna.
pliku, bądź też przez łatwy w obsłudze interfejs IDE. P : Przyglądam się właśnie mojemu IDE
i wygląda ono inaczej niż to pokazane na
P : Czy wystarczy, że pobrałem
rysunkach! Niektóre okna nie są w nim
widoczne, a inne są umieszczone w innych
i zainstalowałem Visual Studio Express?
miejscach. O co chodzi?
Czy wykonywanie przykładów i programów
przedstawionych w tej książce wymaga
zastosowania innej wersji Visual Studio?
O: Jeśli wybierzesz opcję Reset Window Layout z menu
WINDOW, to IDE automatycznie odtworzy domyślny
O : W tej książce nie ma niczego, czego nie dałoby układ okien. Teraz możesz wybrać opcję VIEW/Other
się zrobić, korzystając z darmowej wersji Visual Windows, by nadać swojemu IDE dokładnie taki sam
Studio (którą można pobrać z witryny WWW firmy wygląd.
Microsoft). Podstawowe różnice pomiędzy wersją
Express a pozostałymi nie ujawnią się podczas pisania
kodu C# i tworzenia w pełni funkcjonalnych aplikacji.

jesteś tutaj * 49
Gdyby tylko ludzie nie byli ta k sm akowici..

Obcy atakują!
Cóż, m am y niespodziankę: źli obcy rozpoczęli zm asow any a ta k n a naszą
koch an ą Z iem ię, poryw ając ludzi, by p rzep ro w ad zać sw oje nikczem ne
gastronom iczne eksperym enty. N ik t się tego nie spodziewał!

N Och! Kosmici
wciągają ludzi-
Niedobrze!
Zacznij pisać programy w C#

Tylko T y możesz uratować Ziemię


K to u ratu je ludzkość p rze d zagładą? O sta tn ia nad zieja w Tobie! L udzie z p lan ety Z ie m ia p o trzeb u ją
C ię do n a p is a n ia w C # fa n ta sty c z n e j a p lik a c ji, k tó ra pom ogłaby im skoordynow ać ucieczkę p rzed
zagrożeniem . Czy jesteś gotów p o d jąć się tego wyzwania?

Ratuj ludzi
Coraz więcej złych obcy ch będzie
wypełniać ekran. J e ś li_przeciągn'e sz
swojego człowieka. do jednego
z n ic h - „Gra skończona!"

Przeciągnij człowieka do wyjścia,


zanim upłynie cza s odmierzany
u dołu strony.

Nie przeciągaj
człowieka zb y t
szybko, bo go
stra cisz.

Nasi najwięksi naukowcy wymyślili


zabezpieczające międzywymiarowe
portale w kształcie prostokątów,
które pozwolą zabezpieczyć ludzi.

To do CIEBIE należy
URATOWANIE LUDZI, musisz
ich bezpiecznie przeprowadzić
do docelowych portali.

jesteś tutaj * 51
O to Twój cel

Oto co masz zamiar napisać


B ędziesz p o trzebow ał aplikacji z graficznym in terfejsem użytkow nika,
Pod koniec tego
obiektów , k tó re zapew nią praw idłow e działanie gry, o raz p ro g ram u rozdziału będziesz
w ykonywalnego. W ydaw ać by się m ogło, że to n ap raw d ę dużo pracy, ju ż dobrze znał IDE
lecz stworzysz to wszystko w dalszej części rozdziału, a kiedy dotrzesz i wiedział, ja k zabfdć
s ię do pisania kodu.
do jego końca, będziesz już całkiem d obrze w iedział, ja k używać ID E ,
by p rojektow ać strony i dodaw ać do nich k o d C # .

Poniżej przedstaw iliśm y stru k tu rę aplikacji, k tó rą m asz zam iar napisać.

^Pp c^ iK 7J ontro/^
moz/iu,oś£ g^zytkoLunikoiv,

Główna strona Kontrolki interfejsu


XAML i kontenery użytkownika Windows
A p likacja używ a tych
kontrolek, by narysować cel,
do którego j e s t przeciągany
cz ł° w iek, oraz w yśw ietlacz
reprezentujący licznik c zasu.

Licznik czasu
dotarcia do
portalu sprawdza
właściwości
kontrolki
ProgressBar,
by dowiedzieć się,
czy gracz zdążył
uciec na czas.

Każdy ratowany
człow iek będzie
rysowany przy
użyciu komponentu
StackPanel,
za w ierającego
elip sę oraz
prostokąt.
- ^ y jr s Ä r Ä

nnantu Canvas.

52 Rozdział 1.
Zacznij pisać programy w C#
Zdarza
zai w biurze j e s t
j ak Windows 2003.
Tw orząc aplikację, będziesz pisał dwa różne rodzaje
przypadkach
kodu. W pierwszej kolejności zaprojektujesz tej książce.
4
interfejs użytkownika, używając w tym celu języka
XA M L (Extensible Application Markup Language) N ie m asz W indow s 8? N ie m a p ro b le m u .

— naprawdę elastycznego języka do projektowania. Spokojnie W dwóch pierwszych oraz kilku ostatnich rozdziałach
tej książki zostało przedstawionych wiele projektów
Później zajm iesz się kodem C # , dzięki któremu gra
wymagających użycia Visual Studio 2012 for Windows 8. Jednak
faktycznie będzie działać. Znacznie więcej na temat wiele osób nie dysponuje jeszcze tym systemem operacyjnym.
ję zyka XA M L dowiesz się w drugiej części książki. Na szczęście większość aplikacji przeznaczonych dla Sklepu Windows
można także tworzyć przy wykorzystaniu technologii Windows Presentation
Foundation (WPF), zgodnej z wcześniejszymi wersjami systemu
operacyjnego Windows. Szczegółowe informacje i instrukcje z tym związane
można znaleźć w dokumencie PDF, dostępnym do pobrania na stronie
N a p iszesz kod O
http://www.headfirstlabs.com/hfcsharp. Więcej informacji na ten temat
znajdziesz w dodatku Pozostałości, w poradzie num er 11.
na prowadzenie gry.
Pakiet wdrożeniowy
Kod C#

^ P rocedura o b s łu g i z d a rz e ń ^

U żyjesz dwóch
liczników czasu,
by dodawać obcych
oraz zakończyć
grę, gdy g raczowi
/ « U c is k i skończy s ię czas
_ na ucieczkę.
^Procedura^obsługrźdarzeńCNck^

StartGame() |

AddEnemy() ] ^sk^J& w T T
| AnimateEnemy() \
aP
p hkt7 ania ' dyStrybU
] EndTheGame() ^

jesteś tutaj ► 53
W ypełnij puste miejsca

K 'k S “ W '" " '* '"-Ją .cs, to


Zacznij od pustej aplikacji
K ażda w spaniała aplikacja zaczyna się o d p u steg o p ro jek tu . W ybierz opcję
N ew Project z m en u F IL E . U pew nij się, że w ybrałeś opcję Visual C# /
W indows Store, a n astęp n ie w kolum nie typu p ro je k tu zaznacz opcję B la n k
A p p (X A M L ). W p o lu nazwy — N a m e — w pisz R a tu j lu d z i .

© P u n k tem wyjścia będzie o k n o D e sig n e r. A by je wyświetlić, dw ukrotnie kliknij plik M ainPage.xaml,


wyświetlony w o knie Solution Explorer. O dszukaj rozw ijaną listę pow iększenia u m ieszczoną w lewym
dolnym ro g u o k n a i w ybierz z niej opcję Fit all.

o4 Ratuj ludzi - Microsoft Visual Studio Express 2012 for Windows 8 Quick Launch (Ctrl+Q) p - □ X

FILE EDIT VIEW PROJECT BUILD DEBUG TEAM DESIGN FORMAT TOOLS STORE TEST WINDOW HELP
Q -O U ffllilb i* “P - ę * - ► Local Machine - Debug - Any CPU - _ § fcj

Appjtaml.ci

Okno Desig
pokazuje
podgląd s tr
nad którą
pracujesz
Początkowo
wygląda on
p u s tą stro n
z domyślni
czarnym t

50% » [¡h ][0 S ][5 ] *


/ □ Design ~ mHBi
H <Page
x : Cla s s =” Ra t u j _ l u d z i .M a i n Pag e ”
xm l ns="http:/ / s c h e m a s .microsoft.cotn/winfx/2006/xaml/presentaT:
x m l n s :x=“ bttp://schemas.microsoft.com/winfx/2006/xaml"
xmlns :local="using:Ratuj ludzi"
x m l n s :d=”http://schemas.m i c r o s oft.com/expression/blend/200S"
x m l n s :mc=”http://schemas.o p e n x m l f o r m a t s .o r g / m a r k u p-compatibility/2006"
mc:Ignorable=“ d">
Tych przycisków możesz użyć, aby wyświetlić linie siatki, włączyć
przyciąganie (dzięki czemu kontrolki będą automatycznie
<Grid Background="{StaticResource A p p licationPageBackgroundThemeBrushJ” >
wyrównywane do siebie) oraz włączyć przyciąganie do siatki
</Grid>
< /Page> (dzięki czemu kontrolki będą wyrównywane do linii siatki).

54 Rozdział 1.
Zacznij pisać programy w C#
Jesteś tutaj!
Główna strona
XAM L i kontenery
* Kontrolki interfejsu

U dołu o k n a Designer w yświetlany je st k o d X A M L . W yraźnie po k azu je on, że „p u sta”


To je s t kod XAML pustej
stro n a głów na w cale nie je st p u sta — zaw iera s ia tk ę X A M L. S iatka działa p o d o b n ie siatki wygencrcwany przez
TDE Warto mu się przyjrzeć,
do tablic n a stron ach H T M L o raz w d o k u m en tach p ro g ra m u W ord. M y użyjem y jej do
S i'n ie d łu g o dodamy do mego
określenia u k ład u naszych stro n w taki sposób, by m ogły się pow iększać lub zm niejszać, kilka kolumn i wierszy.
dostosow ując się do różnych w ielkości i p ro p o rcji ekranów .

50% - 1
Cl Design H E XAML Cl
Ę iP age
x:Class=”Ratuj l u d z i . M a i n P a g e ”
xmlns="http://schemas.microsoft.com/winfx/ 2 006 /xaml/presentation"
x m ln s:x="h t t p ://schenas.microsoft.com/winfx/ 2006 /xaml"
x m ln s:local=”using:Ratuj_ludzi”
x m In s:d=”http://schemas.microsoft.com/expression/blend/ 2008 ”
x m ln s:mc=”http://schemas.openxmlformats.org/markup-compatibility/ 2 006 ”
mc :Ignorable="d">

□ <Grid Background=”{StaticResource ApplicationPageBackgroundThemeBrush}-">

< / 5r id > To
lo jje
e s t znacznik otwierający
o tw ierajm y i< ¿.umy
zam ykający
^ a jm y —siatki ----------—
z awierającej
j —^ t r d k i .
</Page> 1^ . ... J, do
Kiedy . siatki
. , i •_______
będziem y dodawać kolumny, l ai, minrcTo
w iersze nr/iT
oraz inne
inne kontrolki
ttonfrM h
Kiedy do sia
odpowiednie znaczniki XAML będą um ies z czane pomiędzy ty mi dwoma
odpowiednie
znacznikami.
LOO %

Kroki wchodzące w skład tej części projektu zostały ponumerowane symbolami o d © d o © .


Przewróć kartkę, by kontynuować tworzenie projektu. ►

CHCESZ SIĘ NAUCZYĆ W P F? NIE SZUKAJ DALEJ!


Większość aplikacji przeznaczonych dla Sklepu Windows przedstawionych w tej książce można stworzyć
przy użyciu technologii WPF ( Windows Presentation Foundation), zgodnej z Windows 7 oraz
wcześniejszymi wersjami systemu operacyjnego Windows. Darmowy poradnik dotyczący stosowania
WPF dołączany do książki C#. Rusz głową można pobrać z witryny http://headfirstlabs.com/hfcsharp
(więcej informacji na ten temat można znaleźć w dodatku Pozostałości, w punkcie 11.).
jesteś tutaj ► 55
Zapewnij sobie szybki start

© T w oja stro n a będzie m usiała m ieć jakiś tytuł, n iepraw daż? I pew nie przyda się jej
Na kilku następnych stronach
także m argines. O bydw a elem en ty m o żn a w ykonać ręcznie w kodzie X A M L , istnieje poznasz wiele różnych możliwości
je d n a k łatwiejszy sposób, by zapew nić swojej aplikacji taki wygląd, jaki m ają n orm alne Visual Studio IDE, gdyż będziemy
aplikacje d o stęp n e w Sklepie W indows. go używali jako potężnego
narzędzia do nauki. Ty sam
Przejdź do okna Solution Explorer i odszukaj w nim pozycję ^ MamPagexami będziesz używał IDE podczas
lektury tej książki do poznawania
Kliknij ją praw ym przyciskiem myszy i w ybierz opcję Delete, aby u s u n ą ć s tro n ę
możliwości języka C#. To jest
M ainP age.xam l. naprawdę efektywny sposób,
by wbić Ci tę wiedzę do głowy!
o (h o - ? (i m [?■
MainPage.xaml
Search Solution Explorer (Ctrl*;} P' :\Users\Public\D(

*
Solution 'Ratuj ludzi' (1 project}
H§ Ratuj ludzi Tworząc aplikacje dla
> A* Properties
1» *-■ References
> Assets Sklepu Windows, często
l> i i Common
D App.xi
będziesz zastępował
I n Packag c* Open
£0 Save the Open With...
Open in Blend...
domyślnie wygenerowaną
O View Code Ctrl+Alt+0
C* View Designer Shift+R stronę główną innymi
Scope to This

<* # New Solution Explorer View szablonami dostarczanymi


Exclude From Project
JęśH okno Solution Explorer
nie j e s t w tä c zn e , to m ożesz
Run Custom Tool

Cut Ctrl+X
przez Visual Studio.
&
je iwyśm etlić, wybierając di Copy Ctrl+C

odPoWiednią oPcję z menu X Delete Del


VIE.W. Korzystając z opcji DC:i Rename F2
d°s t ępnej w menu WINDOW, A Properties
można natomias t przywrócić Jeśli podczas tworzenia projektu
domy ślny uMad i wygląd okna nadałeś mu inną nazwę, to zostanie
V isual Studio IDE. 0na w yświetlona w o knie S ^ u ttr n
Explorer zam ias't „Ratuj ludzi ■

© T eraz m usisz dodać now ą stro n ę głów ną. Ponow nie w oknie Solution Explorer kliknij praw ym
przyciskiem myszy pozycję j 0 Ratuj ludzi (pow inna być druga o d góry), aby w ybrać pro jek t.
N astęp n ie z w yśw ietlonego m en u w ybierz opcję A d d /N e w Item ...

Add New Item... Ctrl-i- Shift-i-A


* □
Add Reference... *□ Existing Item... Shift-t-AH-t-A
Add Service Reference... % New Folder
Store ► V Class... Shift-i-AIH-hC
JA i i h i i ri n

56 Rozdział 1.
Zacznij pisać programy w C#

N a ek ran ie zostanie w yśw ietlone o k n o dialogow e A d d N ew Item . W ybierz w nim opcję B a sic P a g e
i zm ień nazw ę n a M a in P a g e .x a m l. N astęp n ie kliknij przycisk A d d , aby d odać now ą stro n ę do projektu.

Add New Item - RatujJudzi


Installed Sort by: Default Search Installed Templates (Ctrl+E) P ■
■ ü! m
* Visual C# Type: Visual C#
j Blank Page Visual C#
Code
Data
A minim al page w ith layout awareness, a Kiedy za stą p isz
tilg , and a back button control.
General
Basic Page Visual C# domyślną stronę
Web M ainPage.xaml nową
— “ S p li Page Visual C#
W indows Store str-oną typ u B asic
ï ~ 1- Items Page Visual C#
Page, IDE będzie
m usiało dodać do
— S Item Detail Page Visual C# projektu nowe pliki.
Wybierz opcję B asic Ponowne zbudowanie
Page, by dodać do Ü !' Grouped Items Page Visual C# projektu pozwoli
uaktualnić w szy stk ie
j| Group Detail Page Visual C# jego elem enty
B asic Page. i w yśw ietlić nową
I I I
<n>
Resource Dictionary Visual C#
w stronę w oknie
Upewnij się , że nadałeś nowej stro n ie nazw ę Designer.
Name: MainPage.xaml

i
M ainPage.xaml, gdyż m usi ona m ieć tę sam ą
f- nazwę, co strona, którą w cześniej u su n ą łeś.

ID E przypom ni o konieczności d o d an ia nowych plików — k lik n ij p rz y c isk Tak, a b y je d o d a ć . T eraz m usisz trochę
poczekać, aż stro n a zostanie w yśw ietlona w oknie Designer. M oże się w nim pojaw ić napis lr™!'d Mark|iF>
Buiidthe project to update Design »¡ów. ^ zaktualizow ać p ro jek t, w ybierz z m en u B U IL D opcję R ebuild Solution .
T eraz wszystko już b ędzie działać śpiewająco!

Spraw dźm y, jak w ygląda nasz nowy p lik MainPage.xaml. Przew iń p a n e l z kodem X A M L wyświetlony poniżej o k n a Designer,
aż pojaw i się w nim nowy k o d strony. O to siatka, któ rej będziesz używał jak o p u n k tu wyjścia do tw orzenia swojej aplikacji:

40.1% - [iiip M l^ l I ►
□ Design E XAM L d m0ai
T h is g r i d a c t s a s a r o o t p a n e l f o r t h e page t h a t d e f in e s tw o row s
* Row 0 c o n t a in s t h e b a c k b u t t o n and page t i t l e
* Row 1 c o n t a in s t h e r e s t o f t h e page la y o u t Użyje sz IDE do określenia wyglądu aplikacji, Twoja strona
V. modyfikując w tym celiu tę sia tk ę .
powinna zostać
a P< gir i ^ ^ t y l e = ” { S t a tic R e s o u r c e L a y o u tR o o t S t y le } " > wyświetlona
a < G r id . R ow D e fin i t io n s >
< RowDefi n i t i o n H e ig h t = " 1 4 0 " />
w oknie Designer.
< RowDefi n i t i o n H e ig h t = " * ” / >
Zw róciłeś uwagę, że pojawiła s ię zupełnie Jeśli jednak tak
< / G r i d . R o w D e fin itio n s > nowa siatka, ze swoim własnym znacznikiem s ię nie stało,
otwierającym <Grid> oraz zam ykającym </Grid>? to kliknij pozycję
< ! - - B ack b u t t o n and page t i t l e -->
< G rid >
To j e s t nagłówek strony, zaw ierający je j tytuł. MainPage.xaml
< G r id . C o lu m n D e fin itio n s > Ta siatka j e s t jednocześnie um ieszczona w oknie Solution
< C o lum nD efi n i t io n W id t h = " Auto™ /> wewnątrz sia tki głównej, do której będziesz Explorer.
< C o lu m n D e fin itio n W id t h = " * ” / >
dodawał kontrolki.
< / G r i d . Colum n D e fi n i t i o n s >
< B u tto n x:N am e= ” b a c k B u tto n ” C lick= "G o B a ck™ I s E n a b le d = ” { B in d in g Frame .C anG oB ack, E lem e n tN a m e = p a g e R o o t}” 5 t y le = " { S i
< T e x tB lo c k x:N am e= '’ p a g e T itle ™ G rid .C o lu m n = 1' l " T e x t = " { S t a t ic R e s o u r c e AppNam e}1’ S t y l e = " { S t a t i c R e so u rce PageHeaderTr
< / G r id >

jesteś tutaj ► 57
Nie do końca taka pusta

T w oja aplikacja będzie używ ała siatki składającej się z dw óch wierszy Aplikacje dla Sklepu
i trzech kolum n (o raz dodatkow ego w iersza nagłów ka, k tóry w chodzi
w skład szablonu pustej strony). Środkow a k o m ó rk a tego u k ład u będzie Windows muszą
b ardzo duża i to o n a będzie zaw ierać o b szar gry. D efiniow anie wierszy
zacznij od um ieszczenia w skaźnika myszy n a kraw ędzi strony, ta k by wyglądać prawidłowo
pojaw iła się linia i m ały trójkąt:
na dowolnym ekranie,
Jeśli na Umieść wskaźnik na tabletach, laptopach
krawędzi m yszy nad
strony kraw ędzią strony,
nie w idzisz t ak by pojawił
oraz ogromnych
ani liczby & My Application się trójkąt oraz
140, ani 1*, pomarańczowa monitorach,
to kliknij linia...
gdzieś poza
stroną. "/
wyświetlane w układzie
...a następnie
kliknij, aby
poziomym i pionowym.
utworzyć dolny
w iersz siatki.

Po dodaniu w iersza,
linia zm ieni kolor
na niebieski, a na
Określanie układu strony przy krawędzi strony
zo sta n ie wyświetlona
ego wysokość.
użyciu kolumn i wierszy siatki W''ysokość środkowego
w iersza zm ieni się
sprawia, że aplikacja będzie mogła z 1* na inną liczbę
ze znakiem gwiazdki
na końcu.
automatycznie dostosowywać się
do wymiarów ekranu.
Jupie ania ----------------------------------------------------

P : Ale wygląda na to, że w mojej siatce już jest wiele P : Chwileczkę. Ale ja chce się uczyć C#. Dlaczego tracę
wierszy i kolumn. Czym są te szare linie? czas na te wszystkie informacje o języku XAML?

O: Te szare linie są jedynie pomocą wyświetlaną przez Visual O: Ponieważ pisanie aplikacji dla Sklepu Windows w C# niemal
Studio, by ułatwić odpowiednie określanie rozmieszczenia zawsze zaczyna się od utworzenia interfejsu użytkownika, który
elementów na stronie. Można je wyłączyć klikając przycisk UHL jest definiowany w języku XAML. To także powód dla którego
Żadna z linii wyświetlanych w oknie Designer nie będzie Visual Studio dysponuje tak dobrym edytorem XAML — by
widoczna po uruchomieniu aplikacji poza Visual Studio. Jednak zapewnić programistom narzędzia, których potrzebują do tworzenia
kiedy klinkąłeś stronę i utworzyłeś nowy wiersz, zmieniłeś olśniewających interfejsów użytkownika. W tej książce dowiesz
tym samym jej kod XAML, co sprawi, że po skompilowaniu się także, jak pisać w C# dwa inne typy programów — klasyczne
i uruchomieniu aplikacja będzie działała inaczej. aplikacje Windows oraz aplikacje konsolowe, które w ogóle nie
korzystają z XAML. Poznanie tych wszystkich trzech rodzajów
aplikacji pozwoli Ci lepiej zrozumieć pisanie programów w języku C#.

58 Rozdział 1.
Zacznij pisać programy w C#

D o kładnie w ten sam sposób m ożna użyć górnej kraw ędzi stro n y — z tym że w tym przy p ad k u pow inieneś
utw orzyć dwie d o datkow e kolum ny, je d n ą w ąską z lewej strony o raz d ru g ą w ąską z praw ej strony. N a razie
nie przejm uj się ani w ysokością wierszy, ani szerokością kolum n — b ę d ą o n e zależeć o d m iejsca, w którym
klikniesz. P opraw im y je o d pow iednio już za chwilę.

\.
© K1y A p p lica tio n

Nie przejmuj s i ^ jeśli


widoczne wysokości
w ierszy i szerokości
kolumn będą inne;
określimy ich
prawidłowe wartości
na następnej stro n ie .

K iedy już zrobisz co trzeb a, zerknij n a k o d X A M L , a n a stęp n ie n a k o d siatki p o k azan y n a p o p rzed n iej kartce.
Ja k widać, szerokości kolum n i wysokości wierszy w kodzie od p o w iad ają tym wyświetlonym n a górnej i lewej
kraw ędzi strony.

□ Design U 0 XAML ffl G ) H l3


<!-- =1=
This grid acts asa root panel for the page that defines two rows: ▲
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout

<Grid Style="-{StaticResource LayoutRootStyle}">


<Grid.ColumnDefinitions>
<ColumnDefinition Width ' 1! 'i,/ : To je s t szerokość lewej k°lumny utworzonej
<ColumnDefinition Width=' \ w 5 kroku — odpowiada ona szerokośc'
<Column Definition width="i5i*’7 > x\ w .dócznej
w 5- . w oknie Designer. JJeesstt to
to moż|iwe
możliwe
</Grid.Column[>efinitions> dz ieki t em u, że IDE wygenerował0 dla nas
<Grid.RowDefinitions> ^ , VA/WW
<RowDefinition Height*"140"/> ten ko *
<RowOefinition Height="515*"/>
<RowOefinition Height="113*"/>
</Grid.RowDefinitions>

W iersze i kolumny zostały dodane! O

Siatki X A M L są przykładam i k o n tr o le k k o n te n e ró w , co oznacza, że m ogą


zaw ierać inne kontrolki. Siatki składają się z w ierszy i kolum n definiujących
k om órki, a każda z k o m ó re k m oże zaw ierać in n e kontro lk i X A M L
p rezen tu jące przyciski, te k st o raz kształty. Siatki stanow ią doskonałe narzędzie
do określania u k ład u stro n , gdyż ich w iersze i kolum ny m o żn a zdefiniow ać
w taki sposób, by zm ieniały się w zależności o d w ielkości ekranu.

jesteś tutaj ► 59
Określmy obszar pola bitwy

Określ wymiary siatki na stronie


T w oja aplikacja będzie m usiała działać n a w ielu różnych u rządzeniach,
a w ykorzystanie siatki je st doskonałym sposobem , by to umożliwić.
W ierszom i kolum nom siatki m o żn a n a d a ć k o n k retn e w ym iary określone Kiedy zmieniasz tą liczbą ,
w pikselach. O prócz tego m o żn a tak że użyć zapisu z gw iazdką (Star); modyfikujesz siatką (jak
również je j kod XAML),
w takim przypad ku b ęd ą one zachowywały takie sam e p ro p o rcje
— zarów no w zględem siebie, ja k i w zględem całej strony — niezależnie
o d w ielkości ek ran u o ra z jego orientacji.

ft OKREŚL SZEROKOŚĆ LEWEJ KOLUMNY.


Przesuw aj w skaźnik myszy n a d liczbą powyżej lewej kolum ny, ta k by
zostało w yśw ietlone rozw ijane m enu. W ybierz z niego opcję Pixel,
a gw iazdka zostanie zastąp io n a sym bolem kłódki; n astęp n ie kliknij
liczbę i zm ień ją n a 160. T e ra z n ad k o lu m n ą pow inieneś zobaczyć
coś takiego:

ft POWTÓRZ POWYŻSZE CZYNNOŚCI, BY OKREŚLIĆ


SZEROKOŚĆ PRAWEJ KOLUMNWDOLNEGO
WIERSZA.
Z ad b aj, by tak że praw a k o lu m n a i dolny w iersz m iały szerokość
i wysokość 160 pikseli — w o b u p rzypadkach w ybierz z m en u
opcję Pixel i wpisz liczbę 160.

Określając szerokości kolumn i wysokość


wiersza, wybieraj z menu opcje Pixel, N ic n ie szk o d zi, je ś li
<- i . . je sz c z e n ie ra d z is z so b ie
aby podane wymiary były stałe. W ybór Spokojnie z o k re s la n ie m u k ła d u
a p lik a c ji... j a k n u razie.
opcji Star sprawi, że wymiary kolumn
W dalszej części książki dokładniej zajm iem y
i wierszy będą się zmieniać proporcjonalnie się zagad n ien iem p ro jek to w an ia dobrych
do wymiarów całej siatki. Określając te aplikacji. N a razie p okażem y C i, ja k napisać
tę grę. Je d n a k p o d koniec książki będziesz
parametry w oknie Designer, zmieniasz d oskonale ro zu m iał wszystko, o czym tutaj
wartości właściwości W idth oraz Height piszem y!

w kodzie XAML. Jeśli usuniesz z kodu


te właściwości, będzie to równoznaczne
z przypisaniem im wartości 1*.

60 Rozdział 1.
Zacznij pisać programy w C#

ZMIEŃ ŚRODKOWĄ KOLUMNĘ ORAZ ŚRODKOWY


Ję zy k i X A M L o ra z C #
WIERSZ TAK, BY MIAŁY DOMYŚLNY WYMIAR 1* uw zględniają w ielkość liter!
(JEŚLI JESZCZE MAJĄ INNĄ WIELKOŚĆ). Upew nij się, że sposób zapisu
Kliknij liczbę w yśw ietloną n ad środkow ą ko lu m n ą i w pisz 1. N ie lite r w Twoim kodzie odpowiada
używaj rozw ijanego m en u (zostaw zaznaczoną opcję Star), ta k by prezentow anym przykład om .
ko lu m n a w yglądała ja k n a rysunku poniżej. N astęp n ie spraw dź
w ym iary pozostałych kolum n, by upew nić się, że ID E nie zm ieniło
ich szerokości. Jeśli zm ieniło, to p o p raw je n a 160.

Kiedy w piszesz w polu 1*, IDE nada


kolumnie je j domyśln ą sM ro to śc. t
Jednocześnie może t a kże zmodyfikować
wymiary innych kolumn. Je śli _ _
faktycznie tak s ię s ta n ie , _to zmień je
z powrotem na 160 p ikseli.

ft PRZEJRZYJ SWÓJ KOD XAML!


Kliknij siatkę, by upew nić się, że b ędzie zaznaczona, a n a stęp n ie spójrz n a o k n o z k o d em X A M L ,
żeby spraw dzić, co u d ało C i się stworzyć.

This grid acts as a root panel for the page that: defines two rows
* Row 9 contains the back button and page title
* Row 1 contains the rest of the page layout Umieszczony na sam ej górze wiersz
ze znacznikiem <Grid ...> oznacza,
że w szystfco' co j e s t poniżej, będzie
< G rid S t y l e = " { S t a t i c R e s o u r c e L a y o u tR o o t5 ty ie } " > o ^ ś t a t o p o stać i zaw artość siatki.

< G r id ,C o l u m n D e f i n i t i o n s >
cCo l u t r n D e f i n i t i o n Wi d t h = " 1 6 0 " /> To w łaśnie w taki sposób s ą defini°wane k°/umny
siatki w kodzie XAML. Utworzyłeś siałłtę składającą
< C o lu m n D e f i n i ti o n /> <----------------- s ię z trzech w ierszy i trzech ko^iwi, f^ łe g o
~ < L o l u n n u e T i n i t i o ń w i d t h = ', 1 6 0 1V> w kodzie zostały um ieszczone trzy znaczniki _
ColumnOefinition oraz trzy znaczniki R°wDefiniti° n.
< /G rid ,C o lu m n D e f in itio n s >
< G rid , R o w D e f i n i t i o n s :>
<RowDefi n i t i o n H e i g h t = " 1 4 0 '7 > Ten górny w iersz o wysokości 140 p ik se l
pochodzi z szablonu B asic Page.
<R o w D e f i n i t i o n / ;
cRow D ef i n i t i i ^ T l e i g h t = " 1 6 0 lV
W artości właściwości Width oraz Height
< / G r i d . R o w D e f in i t io r określiłeś, korzystając z r°zwij aneg°
menu w oknie Designer.
Ju ż za chwilkę dodasz do tej siatki
kontrolki, które będą um ieszczane tu taj
— poniżej definicji wierszy i kolumn.
jesteś tutaj ► 61
Przejm ij kontrolę nad program em

‘v/ m i j e wyśi
Dodaj kontrolki do siatki korzystając z menu VIEW.
S korzystaj z ,kony ppinezki,
^ ezk i,
aby okno m e było ukrywane.
Z w róciłeś kiedyś uw agę n a to , że aplikacje są w ypełnione przyciskam i, tekstam i,
paskam i p o stęp u , pask am i przew ijania, rozw ijanym i m enu, nie w spom inając o m en u
\
głów nym ? T o wszystko są k o n tro lk i, a te ra z n ad szed ł czas, abyś d o d ał ich tro ch ę do T oolbox T ł X
swojej aplikacji — wewnątrz k o m ó rek zdefiniow anych przez w iersze i kolum ny siatki. Search Toolbox p ’
J C om m on XAML Controls

© R ozw iń sekcję J Common XAM L Controls w o knie T oolbox, a n astęp n ie Pointer

przeciągnij e lem en t ( f l j Button i u p u ść go w lew ej d o ln e j k o m ó rc e siatki. n Border

Button

El CheckBox

m ComboBox
hEP FlipView
Ul Grid
3 ■ Button ■
— =— -i GridView

m Image

m ListView

© RadioButton

□ Rectangle
N astęp n ie spójrz n a okno z k odem X A M L i zobacz, jaki z n a c z n ik StackPanel
H
X A M L w ygenerow ało ID E . Z obaczysz k o d p o d o b n y do teg o poniżej TextBlock
m
— w artości m arginesów b ę d ą nieco inne, zależnie o d m iejsca, w którym EhD TextBox
upuściłeś przycisk, a poszczególne właściwości m ogą być zapisane w nieco
innej kolejności.
To s ą właś c iwości. Każda z nich ma
Kod XAML przycisku rozpoczyna s ,ę nazwę, za którą je s t umieszczony
w tym m iejscu, od znacznika otw ierającego. znak równości i w artość.

u < B u tto n C o n t e n t ^ ' B u t t o n 11 H o r i z o n t a l A l i g n m e n t = ' ' L e f t ' M a r g in = " B 5 J 5 9 , 0 , 0 "


G r i d . R o w = " 2 " V e r t i c a l A l i g n m e n t = l,T o p 'V >

Przeciągnij e lem en t i um ieść go w p ra w e j d o ln e j k o m ó rc e siatki. K od X A M L Twojej


aplikacji będzie w yglądał p o d o b n ie do teg o p rzedstaw ionego poniżej. Spraw dź, czy jesteś w stanie
w skazać, w jaki sposób jest o kreślany w iersz i k olum na, w której została u m ieszczona kontrolka.

Jeśli przybornik nie


jest wyświetlony,
to spróbuj kliknąć
słowo „Toolbox",
wyświetlone u góry
okna IDE, przy jego
lewej krawędzi. Jeśli
go tam nie znajdziesz,
< T e x t B lo c k G r id .C o lu m n = '" 2 " H o r iz o n t a lA lig n m e n t = " L e 'f t " to wyświetl
M a r g in = ” 3 4 J 2 9 J0 , 0 " G r i d . R ow = "2” T e x tW r a p p in g = ” W ra p " przybornik, wybierając
T e x t = " T e x t B ło c k ” V e r t ic a lA lig n m e n t = " T o p " / > opcję Toolbox z menu
VIEW.
Do powyższego kodu dodaliśmy znaki nowego wiersza,
aby poprawić jego czytelność. Ty też m ożesz to zrobić.
Spróbuj sam!

62 Rozdział 1.
Zacznij pisać programy w C#

N astęp n ie rozw iń sekcję t E H B D H w oknie Toolbox. Przeciągnij elem en t I s l i l & s a a a do dolnej


środkow ej kom órki, e le m e n t do praw ej dolnej kom ó rk i (upew nij się przy tym , że um ieściłeś
go poniżej k o n tro lk i K o d Programu, k tó ra ju ż znajduje się w tej sam ej ko m ó rce) o ra z e lem en t do
środkow ej k om órki siatki. A k tu aln ie n a stro n ie pow inny się ju ż znajdow ać tak ie kontro lk i ja k n a poniższym
rysunku (nie przejm uj się, jeśli b ęd ą um ieszczone nieco inaczej; zajm iem y się tym w n astęp n ej kolejności)

-1601--— r '4------- I r # 16 0 -|

© Riatuj ludzi
B- m
tg
P>o dodaniu komponen t
Canvas będzie wyglądał ja k
niewielki p u sty prostokąt. _
Ju ż niebawem za jmiemy się 1
nim dokładniej.
■ * p

i— B — ■ • ■ 0B

« "■ A to j e s t kontrolka
TextBlock dodana
w kroku 2. W tej sam ej
komórce um ieściłeś
także kontrolkę
Oto przycisk ContentControl. ■>
iitw Uroku 1. . .
--- U O O H U g i—w a _________________ _____
V
\ Dodateś także ten pasek postępu

| B u t to n j
4 -
- komponent ProgressBar.
To j e s t kontrolka ContentControl Contort

Jak są d zisz, do czego ona słu ży O

-
-

A k tu aln ie w ybrana je st k o n tro lk a C anvas, gdyż dodałeś ją jak o o sta tn ią (jeśli nie jest, Kiedy przeciągasz
to kliknij ją myszką, aby ją w ybrać i zaznaczyć). Spójrz n a o k n o X A M L:
kontrolkę
<C anvas G r i d . C o lu m n = " l" G r id .R o w = " l" H o r iz o n t a lA lig n m e n t = " L e 'f t " H e ig h t = " 1 0 0 ” z przybornika
i umieszczasz
Z o stał w nim w yróżniony znacznik kontro lk i C anvas. R o zpoczyna się on o d <Canvas
i kończy sekw encją znaków /> , a p om iędzy nim i zn ajdują się tak ie właściwości jak na stronie,
G rid .C o lu m n = "1 " (dzięki której k o m p o n e n t ten je st um ieszczony w środkow ej ID E automatycznie
kolum nie) o raz G rid.R ow = "1" (dzięki któ rej k o m p o n e n t znajduje się w środkowym
generuje kod XAM L,
w ierszu). S próbuj klikać zarów no w oknie Designer, ja k i różne zna czn iki w oknie
prezentującym k o d X A M L . aby umieścić
Spróbuj kliknąć ten przycisk, powoduje kontrolkę tam,
57.33% ’'|! ! ! || ESI 14 -| i on w yśw ietlenie okna Document
gdzie ją upuściłeś.
« ¡ a «
o nim znacznie w ięcej w dalszej
części rozdziału. jesteś tutaj ► 63
W artości właściw ości w a plika cji m ają znaczenie

Używaj właściwości, by zmieniać wygląd kontrolek


V isual Studio ID E zapew nia nam do sk o n ałą k o n tro lę n a d k o ntrolkam i. D o stę p n e w ID E
Edycję te k stu m ożesz
o k n o P roperties pozw ala zm ieniać nie tylko wygląd, lecz także zachow anie k o n tro lek przerwać, naciskając klawisz
um ieszczanych na stronie. Escape. 7o sam o dotyczy
także innych czynności
wykonywanych w IDE.
^ Zmień tekst na przycisku.
Kliknij praw ym przyciskiem myszy k o n tro lk ę przycisku u m ieszczoną w lewej dolnej kom órce
siatki i z w yśw ietlonego m en u w ybierz opcję E d it Text. Z m ień te k st na: S t a r t ! i spraw dź,
jakie zm iany wywołało to w kodzie X A M L:

<Button Content="Start!" HorizontalAlignment="Left" VerticalAlignment="Top"

Podczas edycji te k s tu w y ^ e t h n e g o na
^ ------------------- IDE odpowiednio zm ienia z awai-to ść właściwośc
Content w kodzie XAML.
Użyj pola Name, by zm ienić
nazwę kontrolki na startB utton.
Użyj okna Properties, aby zmodyfikować przycisk.
U pew nij się, że przycisk je st w ybrany, a n a stęp n ie spójrz n a okno
Properties w yśw ietlone w praw ym dolnym ro g u ID E . Użyj go,
by zm ienić nazw ę k o n tro lk i n a s t a r t B u t t o n i w yśrodkow ać ją
w kom órce. K iedy przycisk będzie ju ż w yglądał odpow iednio,
k lik n ij go p raw y m p rz y c isk ie m m yszy i w y b ierz o p c ję View
Source, by p rzejść bezp o śred n io do znacznika < B u tto n >
w kodzie X A M L.

Być może
będziesz Te niew ielkie kwadraty informują, czy wartość właściwości
m u siał z ° s t ata określona. J e śli' wtaściw ość została określona,
to kwadracik będzie wyp e łniony; je ś li będzie p u sty,
rozwinąć
sekcje będzie to oznacz ać, że właściw ość ma wartość domyślną.
Common Krecy u żyłeś opcj i Edit Text wybranej z menu podręcznego,
oraz Layout. IDE au tom atycznie zaktualizowało wartość właściwości
Content.
Skorzystaj z przycisków @ oraz ES,
aby okre ślić w łaściw ości HorizontalAUgnment oraz
VerticalAlignm ent i w yśrodkow ać kontrolkę w komórce.

Kiedy upuściłeś kontrolkę przycisku na stronie,


IDE w ykorzystało w łaściw ość Margin, by um ieścić ją
precyzyjnie w w ybranym m iejscu kom órki. Kliknij
przycisk * i w ybierz opcję Reset, aby nadać
m arginesom w artość dom yśln ą 0.

<Button x:Name="startButton" Przejdź z powrotem do ok.na XAML


w iDE i zobacz, jak. iwyglą^a
Content=”Start!" zmodyfikowany kod XAM L.
Grid.Row=,,2"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
W łaściwości mogą być za p isane w mnej M ^ n n ś d .
64 Rozdział 1. To nie ma znacze nia!
Jesteś tutaj!
Zacznij pisać programy w C#
O statn ią zm ianę m ożna w ycofać, w ybierając z m enu opcję
EDIT/Undo (lub naciskając kom binację klawiszy Ctrl+Z).
W ybierając tę opcję kilka razy, m ożna w ycofać kilka ostatn ich
ł Kod C#
zmian. Jeśli w ybierzesz nie ten elem ent stro n y co trzeba,
m ożesz anulow ać zaznaczenie, w ybierając z m enu EDIT opcję O O Ir
S e le c t N one. Taki sam efekt daje n aciśn ięcie klaw isza E scape.
Jeśli zaznaczona kontrolka była um ieszczona w ew nątrz
jak ieg o ś kontenera, takiego jak S tackP anel lub Grid, to
n aciśnięcie klaw isza E scap e sp ow oduje zaznaczenie tego
kontenera, a zatem być m oże będziesz m usiał n acisn ą ć go
kilka razy, by całkow icie zlikwidować zaznaczenie.

Zmień tekst nagłówka strony.


Kliknij nagłówek strony („My Application”) prawym przyciskiem myszy i wybierz z menu o p q ę View Source, aby przejść do
kodu XAM L tego bloku tekstowego. Przewiń okno kodu XAM L tak, by była w nim widoczna właściwość T ex t:

Text="{StaticResource AppName}"

Ale zaraz! To wcale nie jest widoczny na stronie tekst „My A pplication” — co jest grane?

Otóż szablon Blank Page używa zasobu statycznego o nazwie AppName do określania tekstu wyświetlanego w górnej części strony.
Przewiń okno kodu XAM L ku górze, aż pojawi się w nim sekqa <P age.R esources> zawierająca następujący wiersz kodu:
< x :S trin g x:Key="AppName">My A p p lic a tio n < /x :S trin g >

Zm ień w nim tekst „My A pplication” na: Kontrolki TextBlock oraz


ContentControl u m iescifes
< x :S trin g x:Key=,,AppName', >Ratuj lu d z i< /x :S trin g > u; prawej dolnej komórce
siatk i.
T eraz w górnej części strony powinien widnieć prawidłowy napis:

Nie przejm uj s ię przyciskiem ze strzałką


0 Ratuj ludzi w lewo. D owiesz s ię w szystkiego na jego
tem a t w rozdziale 14. D owiesz s ię w nim
także o zasobach statycznych.

[4 Z aktualizuj kontrolkę TextBlock, zmieniając je j tekst oraz styl.


Użyj opqi Edit Text dostępnej w menu podręcznym, by zmienić tekst w kontrolce TextBlock na
Ich uni kaj. Ponownie kliknij kontrolkę prawym przyciskiem myszy, wybierz opqę lEdit St-vle >1. Kiedy um ieścisz
a następnie |Appiy Resource >j i w końcu wybierz o pqę SubheaderTettStyle, by powiększyć wyświetlany tekst. wskaźnik m yszy na
kontrolce StackPane¡,

[» U żyj kontenera StackPanel, by zgrupować kontrolki TextBlock i ContentControl.


Upewnij się, że kontrolka TextBlock jest umieszczona u góry komórki, a kontrolka ContentControl u dołu.
T eraz wciśnij lewy przycisk myszy i przeciągnij jej w skaźnik tak, by zaznaczyć obie kontrolki, następnie
kliknij je prawym przyciskiem myszy. Z wyświetlonego menu podręcznego wybierz opqę |Group into ~~>j.
a następnie opqę | stackp<mei|.

StackPanel jest dosyć podobna do kontrolek G rid i Canvas — jej zadaniem jest grupowanie innych
kontrolek (dlatego jest określana mianem „kontenera”) i dlatego nie jest widoczna n a stronie. Jednak Ich unikaj
C n n f^ n tf”n n tm l J

ponieważ kontrolka TextBlo ck była umieszczona u góry komórki, a ContentControl u dołu, zatem
utworzony kontener StackPanel wypełnił niemal cały jej obszar. Kliknij pośrodku konentera S tackPan el ,
aby go zaznaczyć; następnie kliknij go prawym przyciskiem myszy i wybierz z menu podręcznego opcję
IReset Layout >| i | a» |. Spowoduje to zastosowanie domyślnych wartości właściwości kontenera, co sprawi,
że właściwości określające wyrównanie w pionie i poziomie przyjmą wartość S tre tc h . W końcu kliknij
także kontrolki TextBlo ck i C ontentControl, i w podobny sposób przywróć domyślne wartości ich układu.
Zaznacz kontrolkę ContentControl i zmień jej właściwości HorizontalAlignm ent i V e rtica lA lig n m e n t ,
przypisując im wartość Center .
jesteś tutaj ► ÓS
Chcesz, by Twoja gra coś robiła, prawda?

To kontrolki sprawiają, że gra działa


K ontrolki służą nie tylko do celów dekoracyjnych, takich ja k p rezen to w an ie tytułów i nagłów ków . M ają o n e kluczowe
znaczenie dla sposobu działania Tw ojej gry. D o d am y zatem kontro lk i, z którym i gracz b ędzie prow adził in terakcję
podczas rozgrywki. O to czym się te ra z zajm iesz:

S tw o rzysz obszar gry, Powiększysz kontfoikę


którego tłem będz ;e ^ ■■■< zaj m iesz s lę dolny m Pi-ogre s s B ar tak, by za jmowała
...i u żyjesz
szablonu, by
sprawić, że
\
^ —• wrogowie gracza
Koniec gry będą wyglądali
w taki sposób.

Z aktualizuj pasek postępu.


Kliknij praw ym przyciskiem myszy k o ntrolkę P ro g r e s s B a r um ieszczoną w środkowej kolum nie Okno Document
dolnego w iersza siatki i z wyświetlonego m en u podręcznego w ybierz opcję Reset Layout, Outline można
a następnie opcję A li; w ten sposób przywrócisz dom yślne w artości wszystkich właściwości także wyświetlić
korzystając z opcji
kontrolki. N astępnie, korzystając z p o la Height um ieszczonego w sekcji L a yo u t o kna Properties, dostępnych
ustaw wysokość kontrolki n a 20. W efekcie ID E usunie z kodu X A M L kontrolki wszystkie w menu VIEW/Other
Windows.
właściwości zw iązane z układem , pozostaw iając jedynie właściwość H eig h t:
Sir
I Document Outline
_ page Root
<ProgressBar Grid.Column="l" G r i d . Row=” 2” Height=”2 0 "/>
A J3 pageRoot
BottomAppBar

Przekształć kontrolkę Canvas w obszar gry. a


H” TopAppBar
::: [Grid! ® o

A ::: [Grid] ® o
Pam iętasz k o n tro lk ę C anvas, k tó rą um ieściłeś w środkow ej k o m ó rce siatki? T eraz
^ backButton ® o

î naw et tru d n o ją zobaczyć, gdyż kontro lk i tego typu są początkow o — p o przeciągnięciu ffl pageTitle ® o
Okno Document Outline można także otworzyć,
klikając kartę widoczną przy lewej krawędzi okna IDE.

*3 startButton ® o
z okna Toolbox — niew idoczne. N a szczęście istnieje pro sty sposób, by ją odnaleźć. a M [StackPanel] ® o

Kliknij niew ielki przycisk ES w yświetlony powyżej o kna z kodem X A M L ; spow oduje to U [TextBlock] ® o
SI [ContentControl] ® o
w yświetlenie o kna D o cu m en t O utline. Kliknij elem en t by zaznaczyć k o ntrolkę. I Ul [Canvas] °
[ProgressBar] ® o

U pew nij się, że k o n tro lk a C anvas jest zaznaczona, a n a stęp n ie użyj p o la N a m e


d ostępnego w o knie Properties, by n ad a ć jej nazw ę p la y A re a .
Kiedy ju ż zm ienis z nazwę kontrolki, będzie ona
p rezento wana w oknie Document Outline jako
elem ent p layArea, a nie [Canvas].
|| Background ™ ■

s ■ a 1 E3 o P o o k reślen iu nazwy kontro lk i C anvas m ożesz już zam knąć o k n o D ocum ent
Color resources O utline. N astęp n ie użyj przycisków [SI i CU] dostępnych w oknie Properties,
Kliknij su w a k z lew ej m R 166 by nad ać w łaściwościom w yrów nania w poziom ie i p ionie w artość S t r e t c h ,
s trony, a n a stęp n ie - przyw róć dom yślne w artości m arginesów i kliknij przycisk , by przypisać
kliknij^początkow y kolor
i w łaściwościom W idth o raz H e ig h t w artość A uto. T e ra z przypisz właściwości
g ra d im tu . N a stęp n ie I
Winkijl suw ak z prawej A ' 00%
Column w arto ść 0, a właściwości ColumnSpan (um ieszczonej o b o k Column)
strony, po czym wybierz
końcowy kolor gradientu. w arto ść 3.

W końcu otw órz sekcję B rush w oknie Properties i kliknij przycisk d , by określić
g rad ien t. O kreśl początkowy i końcowy kolor gradientu, klikając, odpowiednio:
lewy i prawy suwak u dołu edytora kolorów, i klikając wybrany kolor.

66 Rozdział 1.
Zacznij pisać programy w C#

[m Stwórz szablon wroga.


W tw orzonej grze n a ek ran ie będzie się w ałęsało w ielu w rogów - wszyscy pow inni w yglądać ta k sam o. N a szczęście
język X A M L u d o stęp n ia coś takiego ja k szab lo n y , k tó re są doskonałym sposobem n a d a n ia identycznego wyglądu
w iększej liczbie k o ntrolek.

W o knie D ocu m en t O utline kliknij praw ym przyciskiem myszy ele m e n t C ontentControl. W ybierz z m en u opcję E d it
Tem plate, a n a stęp n ie w ybierz opcję Create E m p ty .... T w o rzo n em u szablonow i nadaj nazw ę Enem yTem plate. ID E
d o d a nowy szablon do k o d u X A M L.

Cr«Me ControiTttnpUtr Rnourcr T U


Jak na razie m usimy s ię p°ruszać po
tu m l M
— okno Designer w żaden w w a z d n y s pos ob
nie pokaże szablonu, dopóki nie dodamy do Możes z takż e skorzystać
M m* «
niego jakiejś kontrolki, która ° kreśh j eg° _ z okna Document Outline ,
wysokość i szerokość. Nie przej muj ^ j e śl' ■«O tfnM aw y HmM»*Sit*u»T<
by zazn<xczyć sia tkę, je śli
coś Ci s ię nie uda, to zaw sze inozesz cofnąć nie będzie zaznaczona.
wprowadzone modyfikacje i s pnobowac : « c«- .
je szc ze raz.
1
N ow o utw orzony szablon będzie zaznaczony w ID E . Z am knij teraz o k n o D o cu m en t O utline, by nie p rzesłaniało
przybornika. Twój szablon cały czas je s t niewidoczny, je d n a k to się zm ien i w następnym kroku. Jeśli przez przypadek
kliknąłeś gdzieś p oza kontrolką ContentControl, to w każdej chw ili m ożesz j ą pon o w n ie w ybrać — wystarczy wyświetlić
o kno D ocum en t Outline, klikn ą ć elem ent reprezentujący tę kontrolkę praw ym przyciskiem myszy i wybrać z m enu
podręcznego opcję E d it Template/Edit Current.

Pamiętaj, by nie klikać w żadnym innym m e p w okna Desig netr,


Zm odyfikuj szablon wroga. zanim nie utw orzysz elipsy. Dzię ki tem u szabl°n cały czas
D odaj do szablonu czerw one koło. będzie zaznaczony.
1
D w ukrotnie kliknij e le m e n t I® Ellipse w o knie Toolbox — spow oduje to
do dan ie elipsy.
El
O kreśl w ysokość (H e ig h t) o raz szerokość (W idth) elipsy, przypisując im
w artość 100; dzięki te m u elipsa zostanie w yśw ietlona w kom órce.
Kliknij ten selekto r
Przyw róć dom yślne w artości właściwości H o riz o n ta lA lig n m e n t, k°l°ru i przeciągnij
V e r tic a lA lig n m e n t o ra z M argin, klikając p ro sto k ą t wyświetlony p o ich go do prawego
górnego rogu.
praw ej stro n ie i w ybierając za każdym razem opcję R eset.
P rzejdź do sekcji Brush o k n a Properties i kliknij przycisk H , by wypełnić
kontrolkę jednolitym kolorem .
W ypełnij elipsę ko lo rem czerw onym . W tym celu kliknij pionow y p ase k k o lo ru i p rzesu ń zaznaczenie n a sam ą
górę, n astęp n ie kliknij obszar k o lo ru i przeciągnij zaznaczone p u n k ty do praw ego g órnego rogu.
Przejrzy j zaw artość okna z kodem
A k tu aln ie kod X A M L k o n tro lk i C o n te n tC o n tro l pow inien m ieć n astęp u jącą postać: XAML i przekonaj s ię,^ czy będziesz
w s tanie w skazać m iejsce,
<ContentControl Content="ContentControl" HorizontalAlignment="Center" w którym zo s ta ł zdefiniow any
VerticalAlignment="Center" Template="{StaticResource EnemyTemplate}"/>
s zablon EnemyTempla te . Powinien
/N______ s ię on znajdować bezpośrednio
poniżej zasobu A p p Name.

Użyj okna Document Outline, by zmodyfikować kontrolki StackPanel oraz TextBlock.


Ponow nie wyświetl okno D ocum ent Outline (jeśli na jego górze widzisz elem ent — EnemyTemplate (ContentControl Template) ^
to kliknij przycisk ± , aby pow rócić do prezentacji struktury całej strony). N astępnie wybierz kontrolkę S ta c k P a n e l,
upewnij się, że została o n a wyśrodkow ana w pionie i poziom ie, i usuń m arginesy. D o kładnie w ten sam sposób
zmodyfikuj kontrolkę T ex tB lo c k . 67

Już prawie udało Ci się zakończyć rozmieszczanie elementów strony! Na następnej stronie znajdziesz kilka ostatnich kroków .. . ►
Sprawdź stronę, którą stworzyłeś

[6 Na elemencie Canvas umieść człowieka.

Człowieka m ożesz dodać na dwa sposoby. Pierwszy z nich został opisany w kolejnych trzech akapitach. Drugi, nieco
szybszy, polega na bezpośrednim wpisaniu w ID E odpowiedniego k o d u X A M L . W ybór należy do Ciebie!

Z aznacz kontrolkę Canvas, następnie wyświetl przybornik i rozwiń w nim sekcję A ll X A M L Control. D w ukrotnie kliknij
elem ent Ellipse, by dodać elipsę. N astępnie ponow nie wybierz kontrolkę Canvas i dw ukrotnie kliknij elem ent Rectangle,
by dodać prostokąt. P rostokąt zostanie wyświetlony bezpośrednio n a elipsie, dlatego przeciągnij go nieco w dół.

W ciśnij klawisz Sh ift i kliknij elipsę, aby zaznaczyć obie k ontrolki. Kliknij elipsę praw ym przyciskiem myszy
i z w yśw ietlonego m en u w ybierz opcję G roup In to i S tackP anel. Z azn acz elipsę, zm ień jej tło n a jednolicie białe,
a w łaściwościom W idth i H e ig h t przypisz w artość 10. N a stęp n ie w ybierz k o n tro lk ę Rectangle, zm ień jej tło na
jednolicie białe, a właściwościom W idth i H e ig h t nadaj w artości, odpow iednio: 10 i 25.

Skorzystaj z o kn a D o cu m en t Outline, by zaznaczyć ko n tro lk ę S ta c k P a n e l (upew nij się, że na sam ej górze okna
Properties m ożna zobaczyć inform ację Type StackPanel y Kliknij przyciski ? by przypisać w łaściwościom Wi d th o raz
H e ig h t w artość A uto. N astęp n ie, korzystając z p o la N a m e , zm ień nazw ę k o n tro lk i n a human. O to ja k pow inien
w yglądać w ygenerow any k o d X A M L:
J eśli zd ecyd u jesz s ię wpisać ten kod sam emu
< S t a c k P a n e l x ^ a m e ^ ' h u m a n " O r i e n t a t i o n = ”V e r t i c a l " > w oknie XAML, to upewnij s ię , że u m ieszcza sz go
< E l l i p s e F i l l = “W h i t e " H e i g h t = ” 2 5 ” W i d t h = ”1 0"/> bezpośrednio przed zamykającym znacznikiem </
Canvas>. W taki sposób zdecydujesz, że panel
< R e c t a n g l e Fill='’W h i t e ” Height='’190'' W i d t h = " 1 0 ”/>
człowieka będzie um ieszczony wewnątrz kontrolki
</5tackPanel> Canvas.

Ponow nie wyświetl o k no D o cum en t Outline i spraw dź, ja k w nim p re z en tu je się nowy kod:

T„dj kod

Podczas przeciągania
Dodaj tekst Koniec gry.
K iedy rozgryw ka się zakończy, T w oja g ra pow in n a wyświetlić stosow ny bloku tekstowego
kom unikat. Z ro b isz to , do d ając do strony k o lejn ą k o n tro lk ę T e x tB lo c k ,
w obszarze kontrolki
której n ad asz od pow iednią nazw ę i zm ienisz czcionkę:

★ Z aznacz k o n tro lk ę C anvas, z p rzybornika przeciągnij k o n tro lk ę T e x tB lo c k


Canvas, zmieniane są
i u p u ść ją w ew nątrz k o n tro lk i C anvas. wartości właściwości Left
★ K orzystając z p o la N a m e w o knie Properties, zm ień nazw ę nowej k ontrolki
na gam eO verT ext. oraz Top, określające jego
★ R ozw iń sekcję Text o k n a dialogow ego Properties i zm ień używ aną czcionkę położenie. Zmiana wartości
na A rial B lack o w ielkości 100 pikseli; zaznacz tak że opcje B o ld i Italic.
tych właściwości spowoduje
★ Kliknij k o n tro lk ę T e x tB lo c k , a n a stęp n ie przeciągnij ją n a śro d ek kontrolki
C anvas. przesunięcie kontrolki.
★ Z m ień tek st n a K oniec g ry.

68 Rozdział 1.
Zacznij pisać programy w C#

Dodaj portal docelowy, do którego gracz będzie musiał przeciągnąć człowieka.

D o d o d an ia została Ci jeszcze je d n a kon tro lk a: będzie o n a rep rezen to w ać docelow y p o rtal, do k tórego
będziesz przeciągał człow ieka. (N ie m a znaczenia, w którym m iejscu k o n tro lk i C anvas ją um ieścisz).

W ybierz kontrolkę C anvas, następnie przeciągnij i upuść na nią kontrolkę R e c ta n g le . Użyj przycisku
dostępnego w sekcji Brush o k n a Properties, by w ypełnić ją g rad ien tem . Przypisz w łaściwościom H e ig h t
oraz W idht w arto ść 50.

N astęp n ie przekształć p ro sto k ą t w rom b, o b racając go o 45 stopni. W tym celu rozw iń sekcję Transform
w oknie Properties i o b ró ć p ro sto k ąt, klikając przycisk ** i w pisując w artość 45 w polu^4«g/e.

N a koniec w p o lu N a m e n a górze o k n a Properties w pisz nazw ę k o n tro lk i — t a r g e t .

Gratulujemy — właśnie skończyłeś tworzenie strony głównej swojej aplikacji!

® Ratuj ludzi

Koniec gry

Ich unikaj

jesteś tutaj ► 69
Przejąłeś kontrolę

CO DO CZEGO SŁUŻY?
* • *

T eraz, skoro już utw orzyłeś interfejs użytkow nika swojej aplikacji, pow inieneś już m niej więcej w iedzieć,
do czego służą poszczególne kontro lk i, użyłeś też w ielu właściwości, by określić ich wygląd i dostosow ać go
do p o trzeb gry. P rzek o n ajm y się, czy po trafisz określić, do czego służą poszczególne w łaściwości i w których
sekcjach o kna Properties m o żn a je znaleźć.

Właściwość XAML W którym miejscu Jakie je st jej


IDE można znaleźć tę przeznaczenie^
właściwość?

C o n te n t Na samej górze O k reśla w ysokość kontrolki.

H e ig h t O k reśla kąt o b ro tu kontrolki.


t> Brush

t> Appearance
R o ta tio n U żywasz jej w kodzie C #
do korzystania z kontrolki.
t> C o m m o n

O k reśla k o lo r kontrolki.
F ill
> Layout

U żywasz jej, kiedy chcesz zm ienić


tekst w yświetlany w ew nątrz kontrolki.
x:Name
l> Transform

Rozwiązanie podaliśmy na stronie 79 --------------- ►

Mamy małą podpowiedz: możesz skorzystać z pola do


wyszukiwania — Search Properties — w oknie Properties,
by wyszukiwać właściwości. Jednak niektóre z nich nie są
dostępne dla wszystkich typów kontrolek.

70 Rozdział 1.
Zacznij pisać programy w C#

Stworzyłeś scenę, na której będzie prowadzona gra


A k tu aln ie stro n a głów na gry została ju ż p rzygotow ana i m ożesz rozpocząć pisan ie kodu.
Przygotow ałeś siatkę p e łn iącą ro lę podstaw ow ego u k ła d u strony i dodałeś do niej kontrolki,
k tó re b ęd ą elem en tam i gry.

Jesteś tutaj!
\
Kontrolki interfejsu
P akiet wdrożeniowy
Kod C#

jProowtun oMtugl (dump,


□ PTTk
programu
Obrazek
ekranu
tytułow ego

| StartGanw<) }

f - | A ddEnem yO |

S m i

Pierwszym krokiem, który ju ż N astępnie um ieściteś na


wykonateś, byt° u tworzenie projektu stronie k o n tro li. Koiejny m
i przygotowanie sia tki. krokiem j e s t napisanie kodu,
który będzie z tych tsrnM isk
korzystał.

Visual Studio udostępnia nam użyteczne narzędzia


służące do określania układu stron, jednak
w rzeczywistości jedynie pomagają one w tworzeniu
kodu XAML. W szystko zależy od Ciebie!

jesteś tutaj ► 71
Dalsze prace nad grą

Czym zajmiesz się teraz?


T eraz zaczyna się najciekaw sza część zadania: dodaw anie k o d u , któ ry spraw i, że g ra będzie
działać. Z robisz to w trzech etap a ch : w pierw szej kolejności zajm iesz się anim acją wrogów,
n astęp n ie zapew nisz graczow i m ożliw ość interakcji z g rą i w k ońcu d opracujesz wszystko,
by gra w yglądała lepiej.
P i e r w s i rzeczą ja k ą zrobisz, będzie
dodanie kodu C*, który sprawi, ż !
W pierwszej kolejności zajmiesz się animacją w rogów ...
wrogów w I b l z a n e '"g ^ W etlenie

Wielu program istów tw orzy kod, p isząc g o krótkimi


Komą*, g ry fragm entam i i upew niając się, że każdy z nich działa,
zanim przejdą do kolejnego. To w łaśnie w taki sp o s ó b
n ap isz esz d alsz ą cz ę ść te g o program u. Z aczniesz od
m etody o nazwie AddEnemy(), która będzie dodaw ać
do kontrolki C anvas anim ow anego w roga. Najpierw
połączysz tę m etodę z przyciskiem Start, tak by jeg o
kliknięcie pow odow ało w ypełnienie o b szaru gry
odbijającym i się od sieb ie w rogam i. To będzie stanow iło
p o d staw ę d o stw orzenia reszty gry.

...następnie dodasz obsługę przebiegu gry.

A b y gra zaczęła
działać, p asek postępu
musi' odliczać czas,
człowiek m usi się
poruszać, a gra musi
s ię kończyć, kiedy
j akiś wróg go dotknie
lub człowiek ucieknie
do portalu.

:y łeś szablonu, . i w końcu zmodyfikujesz


u wroaowie mieli
jgląd czerwonych obcych, by wyglądali tak:
łek. Niebawem
ienisz ten szablon,
mieli wygląd głów
ych obcych.

72 Rozdział 1.
Zacznij pisać programy w C#

Dodaj metody, które coś zrobią


N adszed ł czas, by zacząć pisać k o d C # . Pierw szą rzeczą, k tó rą zrobisz, będzie
d o danie m etody — a V isual Studio ID E m oże Ci to ułatw ić, gen eru jąc kod
stanow iący doskonały p u n k t wyjścia.
Podczas edycji strony w ID E m o żn a d w ukrotnie kliknąć dow olną
z um ieszczonych n a niej k o n tro lek , co spow oduje, że ID E autom atycznie
d o d a do p ro je k tu stosow ny kod. U pew nij się, że je st w idoczne o k no Designer,
a n astęp n ie dw ukrotnie kliknij przycisk Start. W efekcie ID E d o d a do p ro jek tu
kod, który będzie wykonywany, kiedy użytkow nik kliknie przycisk. Pew nie
zauważysz, że ID E wyświetli k o d przedstaw iony poniżej:

Kiedy dwukrotnie klikniesz kontrolkę przycisku, IDE


utw orzy tę metodę. Bętdzie ona wykonywana, kiedy
uży tk o wr\ik kliknie p rzy cisk „Sta rt!“ w uruchomionej aplikacji.

p r i v a t e v o i d s t a r t B u t t o n _ C l i c k ( o b j e c t sender, R o u t e d E v e n t A r g s e)
{

y Click="startButton_Click"

V dt C, także tą w taściwość do
U ż y j ID E d o s tw o r z e n ia w ła s n e j m e to d y ą d d n a ^ S l t T ^ a £ y2 U Z iZ m I
dow iesz się, co to j e s t. ej
Kliknij pom iędzy naw iasam i { } i w pisz przedstaw iony poniżej frag m en t kodu,
w łącznie z naw iasam i i średnikiem :

private void startButton_Click(object sender, RoutedEventArgs e)

: - -: : ; Czerwona falista linia j e s t sygnatem , że w kodzie j e s t j a kiś


i problem, natom iast maty niebieski pirostokącik sy gn alizuje,
że być może IDE zna rozwiązanie tisgo pmt/temtu.

Czy zauw ażyłeś czerw oną falistą linię w yśw ietloną tu ż poniżej w pisanego p rz e d chw ilą tek stu ? W te n sposób ID E
sygnalizuje, że coś jest nie tak. K iedy klikniesz tę linię, pojaw i się m ały niebieski p ro sto k ą t — za jego p o m o cą ID E
inform uje, że być m oże będzie w stan ie Ci p o m ó c w rozw iązaniu problem u.

Um ieść wskaźnik myszy nad niebieskim prostokątem i kliknij ikonę a ' , która zostanie wyświetlona p o d nim. N a ekranie pojawi
się okienko umożliwiające utw orzenie szkieletu metody. Jak sądzisz, co się stanie, kiedy je klikniesz? N o dalej, przekonaj się!

iNie jstnieią,
--------------------------------------------- głupie pytania-----------------------------------------------------

P : A czym jest metoda? P : Czy IDE wygenerowało dla mnie tę metodę?

O : M etoda jest blokiem kodu, który ma nazwę. Będziemy się O : Tak... jak na razie. Metody są podstawowymi elementami
nimi zajmowali szczegółowo w rozdziale 2. konstrukcyjnymi programów — będziesz ich pisał bardzo dużo
i wkrótce ich tworzenie nie będzie sprawiało Ci problemów.

jesteś tutaj ► 73
Inteligentny i rozsądny

Podaj kod metody f lf o l ----------------------------------


Kod C# musisz wpisywać dokładnie
N adszedł czas, by zapew nić, że Twój p ro g ram będzie coś ' ' tak, jak go pokazujemy w treści
robić, dysponujesz już naw et świetnym p u n k te m wyjścia. k il ,łk l-
ID E w ygenerow ało sz k ie le t m eto d y : p u n k t wyjścia,
Naprawdę bardzo łatwo można popsuć coś
o d którego m ożesz zacząć p isanie jej kodu.
w kodzie. Dodając kod C# do swojego programu,
musisz zachować wielkość iiter i upewnić się, że
© U su ń zaw artość m eto d y w ygenerow anej p rzez ID E .
prawidłowo zapisałeś wszystkie nawiasy przecinki
i średniki. Jeśii pominiesz choćby jeden, to program
nie będzie działał!

Z aznacz ten fragment k°du i u suń go.


Więcej informacji o wyj ątkach znajd z <esz
w rozdziale t2.

© Zacznij dodaw ać kod. W ew nątrz m eto d y w pisz słowo C o n te n t. ID E wyświetli o k ien k o z sugestiam i;
nosi ono nazw ę o k ie n k a IntelliSense. Z wyświetlonej w nim listy w ybierz opcję C ontentControl.

private void AddEnemy()


{
Content|

^ it _contentLoaded *
f t Content
ContentControl
« *1^ ________________________
* i j ContentPresenter
f t ContentProperty
f t HorizontalContentAlignm ent
f t HorizontalContentAlignm entProperty
* i j ScrollContentPresenter
f t VerticalContentAlignm ent

© D okończ wpisyw anie pierw szego w iersza kodu. P o w pisaniu słowa new kolejny raz zostanie
w yśw ietlone okien k o IntelliSense.

private void A d d E n e m y Q
{
ContentControl enemy = new ContentControl ().;

Ten w iersz kodu tw orzy nowy M e k t ty pu


ContentControl. O biekty oraz s t° w° U tuc^w e
M w p f n a z w rozdziale n a to m M ,w ie r n e
referencyjne, takie ja k enemy, w rozdziale .

74 Rozdział 1.
Zacznij pisać programy w C#

Z an im zakończysz p isanie m eto d y A ddEnem y(), m usisz d odać je d e n w iersz k o d u w górnej części
pliku. O dszukaj w iersz rozpoczynający się o d public sealed p artial c la s s MainPage i dodaj
nowy w iersz za otw ierającym naw iasem klam row ym ( { ):

/// <summary>
I I I A basic page that provides characteristics common to most applications.
I l l </summary>
public sealed partial class MainPage : Ratuj_ludzi.Common.LayoutAwarePage

To j e s t tak zw ane pole, o polach


dow iesz s ię więcej w rozdziale 4.

D okończ wpisyw anie k o d u m etody. Pojaw ią się w nim dwie faliste czerw one linie.
ID E wyróżni w ten sposób w yw ołania m etody A n im ateE nem y(); o bie linie znikną,
kiedy w ygenerujesz szkielet tej m etody.
Ten w iersz kodu P'ayArea^Wri^do P°dljreśleniepo
dodaje utworzoną private void AddEnemy()
1 “P w n lj s le ¿e „ Hy! ° ra X A /*L
przed chwilą {
kontrolkę wroga Convas nazwę p/ayArea W r ° /ce
ContentControl enemy = new ContentControl();
do kolekcji enemy.Template = Resources["EnemyTemplate"] as ControlTemplate; *
o nazwie Children.
AnimateEnemy(enemy, 0, playArea.ActualWidth - 100, "(Canvas.Left)”);
O kolekcjach
AnimateEnemy(enemy, random.Next((int)playArea.ActualHeight - 100),
dow iesz s ię
więcej random.Next((int)playArea.ActualHeight - 100), "(Canvas.Top)");
w rozdziale 8. playArea.Children.A d d (enemy);

Jeśli m u sisz przełączać s ię pomiędzy kodem XAML i edy torem C# , M ainP age.xam l M a in P a g ejta m l.e s -o X
to skorzystaj z tych kart um ieszczonych w górnej części okna.

® Skorzystaj z niebieskiego p ro sto k ącik a i przycisku ® ' , by w ygenerow ać szkielet m eto d y A nim ateEnem yO
(zrób to ta k sam o, ja k w cześniej w ygenerow ałeś m e to d ę AddEnem y()). Tym razem ID E d o dało do m etody
cztery parametry, o nazw ach enemy, p1, p2 o ra z p3. T e ra z będziesz m usiał ręcznie zm odyfikow ać ostatn ie
trzy param etry . P ara m etr p1 zm ień n a from, p a ra m e tr p2 n a to , a p a ra m e tr p3 n a propertyToAnimate .
N astęp n ie zm ień typ i n t n a double .

0 metodach
1 parametrach
dow iesz
s ię więcej
w rozdziale 2.

p r i v a t e v o i d A n i m a t e E n e m y ( C o n t e n t C o n t r o l enemy, (ćiouble^fromj (double) to, s t r i n g p r o p e r t y T o A n i m a t e )

"....... ’ _ .’ _ ” ” ” ”_ ~ _ ~ ” _ ’......
IDE zapew ne wygeneruje szk ie le t metody
zaw ierający param etry ty p u J n f . Zm ień je
na „double . Typy poznasz w rozdziale 4.
Przewróć kartkę, by zobaczyć, jak Twój program działa!

jesteś tutaj ► 75
W porządku, jest naprawdę fajnie

Dokończ metodę i uruchom program C iąg le w id zisz czerw one


lin ie ? ID E p o m o ż e Ci
Twój p ro g ram je st n iem al gotow y do u ruchom ienia! M usisz jedynie Spokojnie o d n a le ź ć ź ró d ło p ro b lem ó w .
uzupełnić kod m etody A nim ateE nem y(). N ie panikuj, jeśli program
nie zadziała. M oże zapom niałeś o jakim ś p rzecin k u albo naw iasie —
program ując, m usisz zw racać n ap raw d ę b aczną uw agę n a te sprawy! N ie przejmuj się, jeśli wciąż widzisz w kodzie
te czerwone faliste linie! N ajpraw dopodobniej
^ Dodaj dyrektyw y using u góry pliku. będziesz m usiał popraw ić jeden lub dwa proste
błędy typograficzne. T e linie świadczą o tym,
Przew iń plik n a sam początek. Z obaczysz tam kilka, że któregoś fragm entu kodu nie wpisałeś
w ygenerow anych p rzez ID E , wierszy, rozpoczynających się prawidłowo. Sprawdziliśmy ten rozdział
od słow a u s in g . Poniżej tej listy dodaj jeszcze je d e n wiersz: w ielokrotnie, korzystając z pom ocy wielu
B u sing System; osób, i na pew no o niczym nie zapomnieliśmy.
using System.Collections.Generic; N a poprzednich stronach n a pew no znajduje się
using System.10; cały kod konieczny do uruchom ienia program u.
Instrukcje using System.Linq;
takie jak te using Windows.Foundation;
pozwalają na using Windows.Foundation.Collections;
korzystanie using Windows.Ul.Xaml;
z kodu
using Windows.Ul.Xaml.Controls; Ten w iersz będzie Ci potrzebny do ptopmiwnego
należącego do
bibliotek .NET, using Windows.Ul.Xaml.Controls.Primitives; działania kodu, który za chwilę M a t t . M ożesz
dostarczanych using Windows.Ul.Xaml.Data; skorzystać z okienka In telliS en se, by go pr-awidłoiwo
wraz z C#. using Windows.Ul.Xaml.Input; w pisać; nie zapomnij o śre dniku ma ktońctu.
D owiesz s ię using Windows.Ul.Xaml.Media;
o nich więcej using Windows.UI.Xaml.Navigation; Ta in strukcja using pozwoli Ci korzystać w s woim
w rozdziale 2. r r r * z należącego do .NET Frdmework
using Windows.Ul.Xaml.Media .Animation; kodu ^obstugująceg ° antmacje — za jego pomocą
będ ziesz p rzesu w a ł wrogów po ekranie, p

[a Dodaj kod, k tó ry utw orzy animację odbijających się wrogów. O inicjalizacji


obiektów dow iesz
Szkielet m etody A nim ateE nem y() w ygenerow ałeś n a p o p rzed n iej stronie. T e ra z d odasz do niego
s ię więcej
kod m etody. Spraw i on, że w róg b ędzie się p o ru szał p o ek ran ie, o d bijając się o d jego kraw ędzi. w rozdziale 4.

private void AnimateEnemy(ContentControl enemy, double from, double to, string propertyToAnimate)
{
Storyboard storyboard = new Storyboard() { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever };
DoubleAnimation animation = new DoubleAnimation()
{
From - from,
Ten kod sprawia, że utworzony wróg
O animacjach i będzie s ię p rzesu w a ł w obszarze
dow iesz I To = to,
s ię w ięcej <. kontrolki playArea. Zm ieniając liczby
Duration - new Duration(TimeSpan.FromSeconds(random.Next(4, 6))) 4 i 6, m o żesz sprawić, że wróg będzie
w rozdziale 16. }J s ię p rzesu w a ł wolniej lub szybciej.
Storyboard.SetTarget(animation, enemy);
Storyboard.SetTargetProperty(animation, propertyToAnimate);
storyboard.Children.Add(animation);
storyboard.Begin();

} J e ś li nie w idzisz okna I


U s t w IDE, to m ożesz
Przejrzyj kod. w y ś w e lić , wybierając '
z: _menu _opcję VIEW/Err
N ie pow inieneś zauważyć żadnych błędów w kodzie, a okno Error L ist pow inno być puste. U s t. Więcej informacji
Jeśli nie jest, to dw ukrotnie kliknij b łąd wyświetlony w o knie Error L ist. ID E p rzejdzie do o liśc ie błędów oraz
debugowaniu znajdzies z
odpow iedniego m iejsca w kodzie, u łatw iając Ci o dszukanie błędu. w rozdziale 2.
76 Rozdział 1.
Zacznij pisać programy w C#

Jeśli w IDE je s t w yśw ietlonych zbyt dużo okien,


to w każdej chwili m ożesz przywrócić jego
dom yślny wygląd i układ, w ybierając z menu opcję
W INDOW /Reset W indow Layout.
[4 Uruchom program.
O dszukaj przycisk ” na p ask u n arzęd zi u góry ID E . K liknięcie tego przycisku u ru ch o m i pro g ram .

* Ratuj ludzi - Microsoft Visual Studio Express 2012 for Windows 8


FILE EDIT VIEW £ROJECT BUILD DEBUG TEAM IOOLS STORE TEST WINDOW HELP

o - ©I © a y B« ? - <? ► Local Machine - Debug - Any CPU - Jjà _ \=i Lfl ^


H
O MainPage.xaml
^
M ainP agej(am l.cs -S X

S' * i; Save_the_Humans.MainPage » startButton_Click(object sender, RoutedEventArgs e)

Ten przycisk uruchamia program.

[4 Teraz Twój program działa!

N ajpierw , przez kilka sek u n d , n a ek ran ie będzie w idoczny duży „X ”, a p o tem pojaw i się głów na stro n a Twojej
aplikacji. K ilka razy kliknij przycisk „S tart!” . K ażde kliknięcie spow oduje w yśw ietlenie now ego koła, które
będzie się poru szało p o ek ran ie, odbijając się o d kraw ędzi o b szaru gry.

Jeśli wrogowie nie odbijają, się


od krawędzi obszaru gry, bądź
jeśli w ogóle s ię nie poruszają, ^
to j eszc ze raz doktadnie sprawdź Ratuj ludzi

e
kod programu. Może z ^ m m a ^ ś
o jakichś nawiasach lub słowie
kluczowym?

Ten duży symbol


to ekran startow y
S
aplikacji. Pod koniec A o n ie c g r y
rozdziału zm ienisz go
na własny.

Nap isate ś coś naprawdę fajnego. I zgodnie


z tym, co oloiecywaliśmy, wcale nie zabrało to
dużo' czasu . Jednak żeby gra dobrze działała,
będziesz je s z c ze m usia ł nad nią popracować.

[4 Zatrzymaj program.

Naciśnij kombinację klawiszyA lt+ T ab, aby wrócić do Visual Studio ID E . W idoczny wcześniej na pasku narzędzi przycisk
► został zastąpiony przez przyciski 11 ■ © , służące do, odpow iednio: wstrzymywania program u, zatrzymywania go oraz
do jego ponow nego urucham iania. Kliknij przycisk z czerwonym kw adratem , aby zatrzymać program .

jesteś tutaj ► 77
Co zrobiłeś, dokąd zmierzasz

Oto co zrobiłeś do tej pory


G ratulujem y! U d ało Ci się n apisać p ro g ram , któ ry rzeczywiście coś robi.
N ie je st to jeszcze gra, w k tó rą m o żn a sobie p ograć, ale b ez w ątp ien ia je st to
dobry początek. Z obaczym y zatem , co u dało Ci się osiągnąć.

Jesteś tutaj!

Główna strona
XAM L i kontenery
t Kontrolki interfejsu
użytkownika Windows
\
Kod C#

Na samy m początku wykonałeś


° ° ° rą r°b °tę , tworząc interfejs
użytkownika... To w ramach tego kroku
n a p iszesz kod, dzięki któremu
...ale wciąż potrzebujes z gra rzeczyw iście zacznie
re szty kodu C #t by aplikacja działać.
fa kttic.znie dobrze dźa-tata..

Visual Studio może wygenerować kod za nas,


ale musisz wiedzieć, co chcesz stworzyć, ZANIM
zaczniesz to robić. IDE nie zrobi tego za Ciebie!

78 Rozdział 1.
Zacznij pisać programy w C#

Poniżej znajdziesz rozwiązanie ćwiczenia „Co do czego służy”


ze strony 70. Zamieszczamy w książce rozwiązania do ćwiczeń,
jednak nie zawsze będziesz je mógł znaleźć na następnej stronie.

:z e g < SŁUŻY?
CO DO CZEGO
*
*
R O Z W IĄ Z A N IE

T eraz, skoro ju ż utw orzyłeś in terfejs użytkow nika swojej aplikacji, pow inieneś m niej więcej w iedzieć,
do czego służą poszczególne k ontrolki, użyłeś też w ielu właściwości, by określić ich w ygląd i dostosow ać go
do p o trzeb gry. P rzekonajm y się, czy po trafisz określić, do czego służą poszczególne właściwości i w których
sekcjach o k n a Properties m o żn a je znaleźć.

Właściwość XAML W którym miejscu Jakie je st jej


IDE można znaleźć tę przeznaczenie^
C o n te n t właściwość?

O k reśla być w ysokość kontrolki.

O k reśla k ą t o b ro tu kontrolki.

U żywasz jej w kodzie C #


do korzystania z kontrolki.

O k reśla k o lo r kontrolki.

U żywasz jej, kiedy chcesz zm ienić


x:Name tek st w yświetlany w ew nątrz kontrolki.

„ a okre śiate ś nazwę kontrolki Ca.n.va s, „playArea ,


w U s u jju Nar;;. w . o ^ ^ M *
^^!lri ^na^t>iw^a^ ^ wJ^iŚĆdy',<l:Nldizrm^ ;iz p isa ł M C# ope ru jący na k o n to to
Canvas reprezentującej obszar gry.

jesteś tutaj ► 79
Tik, tak, tik

Dodaj liczniki czasu zarządzające rozgrywką


W ykorzystaj doskonały p o czątek p ra c n a d g rą i dodaj do niej kolejne elem en ty zw iązane z rozgrywką. W raz z upływ em
czasu w grze m ają się pojaw iać coraz to now i w rogow ie, a p a se k p o stę p u m a się pow oli w ypełniać, gdy gracz będzie
przeciągał człow ieka do p o rtalu . O bie te o p eracje b ę d ą realizow ane przy w ykorzystaniu liczn ik ó w c z a su (ang. timers).

DODAJ KOLEJNE WIERSZE NA SAMYM POCZĄTKU KODU C#. Plik M ainpage.xam l.cs, który
edytuje s z , zawiera kod klasy
Przejdź n a p o cząte k pliku, tam gdzie dodałeś w iersz k o d u rozpoczynający się o nazw ie MainPage. O klasach
dowies z s ię w rozdziale 3 .
od Random. Poniżej niego dodaj kolejne trzy wiersze:
/// <summary>
/// A basic page that provides characteristics common to most applications.
I l l </summary>
public sealed partial class MainPage : Ratuj_ludzi.Common.LayoutAwarePage

Random random = new Random(); "'N Dodaj te trzy w iersze


DispatcherTimer enemyTimer = new DispatcherTimer(); / kodu d o d a n eg o UJCZC^1 ^ jeSZ
DispatcherTimer targetTimer = new DispatcherTimer(); a pola', W ięcej O
bool humanCaptured = false; ) w ro z d zia le ♦

S do daj m e t o d ę d l a je d n e g o ze s w o ic h l ic z n ik ó w c z a s u

O dszukaj ten kod w ygenerow any przez ID E :

this. Initializedodiponent ();

U m ieść k u rso r bezp o śred n io za śred n ik iem i dw ukrotnie naciśnij klawisz E nter, n astęp n ie
wpisz enem yT im er. (z k ro p k ą n a końcu). K iedy tylko w piszesz kro p k ę, zostanie
w yśw ietlone okien k o IntelliSense. W ybierz z niego opcję T ic k i w pisz dalszą część kodu.
G dy tylko w piszesz znaki +=, V isual Studio ID E wyświetli niew ielkie okienko:

n e m y T ira e r .T ic k +®j
Liczniki czasu
e n e m y T im e r T ic k ; (P ress T A B to in sert)

odmierzają go,
N aciśnij klawisz Tab. ID E wyświetli kolejne okienko: wywołując
r T ic k ;
e n e m y T im e r. T i c k +=
określoną metodę
Press TA B to gene ra te h a n d le r le n e m y T im e r_ T ic k l in th is class

za każdym razem,
Jeszcze raz naciśnij klawisz Tab. O to kod, który w ygeneruje ID E : gdy minie zadany okres
public MainPage() czasu. Użyjesz jednego
{ IDE wygenerowało
this.InitializeComponent(); m etodę nazywaną licznika czasu, by co
procedurą obsługi
enemyTimer.Tick += enemyTimer_Tick i zdarzeń ■ Procedury kilka sekund dodawać do
} tego rodzaju
poznasz dokładniej gry nowego wroga oraz
void enemyTimer_Tick(object sender, object e) w rozdziale 15.
{ drugiego, by zakończyć
throw new MotImplementedException();
} grę po określonym

80 Rozdział 1. czasie.
Do nazw m etod j e s t Zacznij pisać programy w C#
dołączana para maw iasów -
\
[4 DOKOŃCZ PISANIE METODY MAINPAGE()
K olejną p ro ce d u rę obsługi zd arzeń T ic k d odasz do drugiego
WYSIL
licznika czasu, dopiszesz także dw a kolejne w iersze kodu. SZARE KOMÓRKI
T a k pow inien w yglądać k o m pletny k o d m eto d y M ain P ag e()
Obecnie kliknięcie przycisku Start powoduje
o raz dw óch m e to d w ygenerow anych p rzez ID E :
dodanie nowego wroga do obszaru gry
public MainPage() Jak sądzisz, co powinieneś zrobić, żeby
{ zamiast tego powodow ało ono faktyczne
this.InitializeComponent(); rozpoczęcie gry?
enemyTimer.Tick += enemyTimer_Tick;
enemyTimer.Interval = TimeSpan.FromSeconds(2); 1
Po zakończeniu g ry
targetTimer.Tick += targetTimer_Tick; spróbuj zm ienić te liczby.
targetTimer.Interval = TimeSpan.FromSeconds(.1); W jaki sp osób wpft/nie to
} na ro zg ryw ką ?
void targetTimer_Tick(object sender, object e)

throw new NotImplementedException();


} IDE wygenerowało te w iersze kodu, kiedy

void enerayTimer_Tick(object sender, object e) o a Z fg ą ś klawi%z Tab‘ by dodać procedurę


obstugi z darzeń Tick; pełnią one rolę
{ tym cza s° wej zaw artości m etody. Z a stą p isz ie
throw new NotImplementedException(); Irodem, który będzie wytronywany za każdym j
} razem, gdy u p ły n ie odmierzany okres czŻsu

¡4 DODAJ METODĘ ENDTHEGAME().


P rzejdź do ko d u m eto d y ta r g e tT im e r _ T ic k ( ) , u su ń um ieszczony w ew nątrz niej w iersz k o d u w ygenerow any
przez ID E i dodaj dwa w iersze p o k azan e poniżej. M oże się zdarzyć, że okien k o IntelliSense nie wyświetli
zupełnie praw idłow ych inform acji:
Czy IDE
uparcie Jeśli zam knąłeś ju ż kartę okna D esigner
zmienia
prezentUjącą kod XAML, to w yśw ietl \a
literę „P"
porrow™, dWukrotnie klikając nazwę pliku
w progressBar
Mainpage .xaml w oknie Solution Exp!orer.
na wielką?
D zieje s ię tak
dlatego, że
w projekcie Czy zauw ażyłeś, że poniżej p r o g r e s s B a r pojaw iło się o znaczenie błęd u ? W szystko je st w p o rząd k u . Z robiliśm y tak
nie ma celow o (i naw et w cale n am z teg o p o w o d u nie je st przykro!), by p o k azać Ci, co się dzieje, kiedy p ró b u jem y używać
niczego kontrolki, k tó ra nie m a nazwy lub której nazw a zo stała błędnie zapisana. W róć do k o d u X A M L (znajdziesz go na
o nazwie
progressBar innej karcie w ID E ), odszukaj k o n tro lk ę P r o g r e s s B a r u m ieszczoną w dolnym w ierszu siatki i zm ień jej nazw ę na
— p rzez małe p ro g re ssB a r.
„p" — dlatego
do tego, co
za p isu jesz, N astęp n ie w róć do o k n a z k odem i w ygeneruj now ą m e to d ę o nazw ie EndTheG am e(). Z ró b to w taki sam sposób,
najlepiej w jaki w cześniej w ygenerow ałeś m e to d ę A ddEnem y(). Poniżej przedstaw iliśm y k o d tej now ej m etody:
p a suje nazwa
typu kontrolki. private void EndTheGame()
Jeśli okaże się , że Ta m etoda pow oduje
{
if (!playArea.Children.Contains(garaeOverText)) gameOverText zostanie zakończenie gry; w tym
{ oznaczony jako błąd, celu zatrzym uje oba
enemyT imer.Stop(); będzie to oznaczało, że nie liczniki czasu, ponow nie
targetT imer.Stop(); określiłeś nazwy kontrolki
TextBlock zaw ierającej te k st w y ś w ie tla przycisk Start
humanCaptured = false;
startButton.Visibility = Visibility.Visible; ,X oniec gry". Wróć od kodu i w y ś w ie tla w obszarze
rplayArea.Children.Add(eameOverText);
J v^ v w w w w w v n * *
XAML i to popraw. gry napis „Koniec gry".

jesteś tutaj > 81


Tak blisko, że już go czuję

Gotowy
Popraw działanie przycisku Start kod
Czy pam iętasz, ja k spraw iłeś, że kliknięcie przycisku Start pow oduje wyświetlenie
Zmuszamy Cię do w p isyw a nia
w obszarze gry czerw onego kółka? T e ra z popraw isz jego działanie, tak by jego całkiem długich fra g m e n tó w kodu.
kliknięcie pow odow ało faktyczne rozpoczęcie gry.
Po zakończeniu lektury tej książki
będziesz wiedział, co robi cały ten
Zadbaj o to, by kliknięcie przycisku Start powodowało kod — w rzeczywistości będziesz
rozpoczęcie gry. w stanie pisać kod taki jak ten,
zupełnie samodzielnie.
O dszukaj dodany w cześniej kod, dzięki k tó re m u kliknięcie przycisku
Start pow odow ało d o d an ie w roga. Z m ień go, by w yglądał ta k ja k ten Jak na razie jednak Twoje zadanie
p rzedstaw iony poniżej: polega na uważnym przepisywaniu
każdego wiersza kodu oraz na
private void startButton_Click(object sender, RoutedEventArgs e) wykonywaniu wszystkich poleceń.
{ To pozwoli Ci przyzwyczaić się do
StartGame():
WVWVWW' ' # wpisywania kodu i poznać tajniki Visual
Zm ienlają c ten w iersz kodu
} s p raw isz, że przycisk S ta r t Studio IDE.
będz ie ' fakty cznie rozpoczynał grę,
a n <e jedy nie ¡wyświetlał wroga. Jeśli jednak utkniesz, to możesz pobrać
kompletną, działającą wersję pliku
MalnPage.xaml oraz MainPage.xaml.cs
[4 Dodaj metodę StartGameO. lub skopiować i wkleić wybrane
metody C# lub fragmenty kodu XAML;
W ygeneruj szkielet m eto d y S ta r tG a m e ( ). Poniżej zam ieściliśm y kod, znajdziesz je na stronie http://www.
który należy w niej um ieścić: headfirstlabs.com/hfcsharp.

private void StartGame()


{
human.IsHitTestVisible = true; 4 O w łaściwości IsH itT estV isible
humanCaptured = false; dow iesz s ię więcej w rozdziale 15.
progressBar.Value = 0;
startButton.Visibility = Visibility.Collapsed;
playArea .Children .ClearQ;
Nie zapom niałeś przez p rz y p a lić o k r r tU ^
playArea.Children.Add(target); n
Rectangle reprezentującej do
playArea.Children.Add(human); S ta ckPanel reprezentującej czł° w <eka? M°z e s z z j f ć °?e
enemyT imer.Start(); treści rozdziału kilka stron wcześn iej, by upewnić s 'ę >ze
targetTimer.Start(); w szy stkie kontrolki m ają prawidłowe nazw y■

Zadbaj, by licznik czasu wrogów dodawał ich do obszaru gry.


Kiedy ju ż przyzw yczaisz się
O dszukaj d o d an ą p rzez ID E m eto d ę e n e m y T im e r_ T ic k (), a n a stęp n ie zastąp jej do pracy z kodem, b ęd ziesz
zaw artość poniższym w ierszem kodu: doskonale radził sobie
z w yszukiwaniem tych
void enemyTimer_Tick(object sender, object e) brakujących nawiasów,
{ średników itd.
AddEnemy();
Czy w oknie Error List wyświetlane są jakieś błędy, które nie mają większego
sensu? Jeden przecinek lub średnik umieszczony w niewłaściwym miejscu
może spowodować pojawienie się dwóch, trzech, czterech, a może nawet jeszcze
większej liczby błędów. Nie marnuj czasu, starając się odszukać wszystkie literów ki!
Po prostu pobierz gotowy kod!
► ftp://ftp.helion.pl/przyklady/cshru3.zip
82 Rozdział 1.
Zacznij pisać programy w C#

Uruchom program, by zobaczyć postępy w pracy o


Alarm!
T w oja gra się rozwija. U ru ch o m ją ponow nie, by p rzek o n ać się, ja k n a b ie ra kształtów. Naii jzpiedzy donojza,
ie ludzie zaczynają budować
Kiedy klikniesz przycisk „S ta rt ! , zm ieni
on, obszar gry zosta n ie wyczyszczony Obszar gry powoli zaczyna się jyrtemy obronne!
ze w szy s tkich wrogów, a p a sek postęp u w ypełniać wrogami, którzy poruszają
powoli zacznie s ię wypełniać. s ię p o nim, odbij ają c s ię od krawędzi.

Ratuj ludzi

Ich unikaj

' •
Kiedy pasek p ostęp u w yśw ietlany u dołu
strony wypełni się, gra zo sta n ie zakończona,
a na e kranie zostanie w yśw ietlony napis
m WYSIL ___
„Koniec gry". fyd SZARE KOMÓRKI
*) .. . Jak sądzisz, co musisz zrobić, by dokończyć pisanie gry
Licznik czasu dotarcia do celu powm ien s ię
i zapewnić jej prawidłowe działanie?
wypełniać powoli, a nowi wrogowie mają s ię
pojawiać co dwie sekundy. Jeśli pomiar upływu
czasu nie działa, to sprawdź, czy dodałeś
do m etody MainPage() w szystk ie w iersze kodu.
Przewróć kartkę, by zobaczyć, jak Twój proyram działa! - - - - - - - - - - - - ►

jesteś tutaj ► 83
W razie jakiegokolwiek zdarzenia..

Dodaj kod obsługujący interakcję Nie zapomnij przełączyć się


z powrotem do IDE
użytkownika z kontrolkami i zatrzymać aplikację
zanim zaczniesz wprowadzać
Przygotow ałeś już człow ieka, któ reg o gracz m usi przeprow adzić do docelow ego
zmiany w jej kodzie.
p o rta lu o raz sam p o rtal, k tóry m usi „wyczuć”, że człow iek do niego dotarł.
N adszedł czas, żebyś d o d ał kod, który sprawi, że g ra b ędzie działać w taki sposób. W ięcej na tem at
procedur obs tugi
zdarzeń dowies z
s ię w rozdziale 4.
© Przejdź do o k n a Designer i skorzystaj z o k n a D o cum en t O utline, by w ybrać k o n tro lk ę human (pam iętasz
zapew ne, że jest to k o n te n e r S ta c k P a n e l zaw ierający k o n tro lk i El 1 i p se o ra z R e c ta n g l e). N astęp n ie
przejdź do o kna Properties i kliknij przycisk 0 , by wyświetlić w nim p ro ced u ry obsługi zdarzeń. O dszukaj
w iersz PointerPressed i dw ukrotnie kliknij p u ste p o le w yśw ietlone z jego praw ej strony.

| Properties

r—, Name human


- ł

' 0
X 1
r
Okno Document Outline
mogło zwinąć w iersze [Grid],
H
Type StackPanel playArea i inne. W takim
Dwukrotnie kliknij to pole. przypadku rozwiń je by
PointerMoved *
w yśw ietlić w iersz human.
Pnint#»rPr#»ccpH ^

PointerReleased

© T eraz wyświetl o k n o z k odem X A M L i spraw dź co now ego pojaw iło się w kodzie reprezentującym
k o ntrolkę S ta c k P a n e l:

<StackPanel x:Name=”human" Orientation="Vertical" PointerPressed="human PointerPressed">

D odatkow o ID E w ygenerow ało szkielet m etody. Kliknij praw ym przyciskiem myszy te k st h u m a n _ P o in te rP re s s e d


w oknie k o d u X A M L i w ybierz opcję Navigate to E ven t H andler, by p rzejść p ro sto do k o d u C # tej m etody:

Możesz skorzystać
© U zupełnij kod m etody: z tych dwóch przycisków,
private void humanPointerPressed(object sender, PointerRoutedEventArgs e) by przełączać okno
{ Properties pomiędzy
if (enemyTimer.IsEnabled) trybem wyświetlania
{ właściwości i zdarzeń.
humanCaptured = true;
human.IsHitTestVisible = false;
}
}
Properties . J L
Jeśli wrócisz z powrotem do okna Designer i ponownie klikniesz
kontrolkę StackPanel reprezentującą człowieka, to przekonasz M
Name human

Type StackPanel
c t)
się, że IDE wyśw ietli nazwę nowej procedury obsługi zdarzeń )interMoved
w oknie Properties. W ten sam sposób będziesz jeszcze PointerPressed human PointerPressed
dodawał wiele innych procedur obsługi zdarzeń. PointerReleased

84 Rozdział 1.
Zacznij pisać programy w C#
Upewnij się, że dodajesz w łaściw ą procedurę obsług i
z darzeń! Dodałeś procedurę obsługi zdarzeń poM erPres sed
do kontrolki human, a teraz dodaje s z p rocedurę obsługi Jeśli okno Properties
zdarzeń PointerEntered do k°n trolki ta rg et. prezentuje procedury
obsługi zdarzeń, możesz
dw ukrotnie kliknąć puste
Użyj o k n a D o cu m en t Outline, aby w ybrać k o n tro lk ę R e c ta n g le o nazwie
pole obok w ybranego
t a r g e t , a n astęp n ie skorzystaj z o k n a Properties działającego w trybie prezen tacji
zdarzenia, aby IDE
zdarzeń, by d odać p ro ce d u rę obsługi zd arzen ia P o in t e r E n te r e d . Poniżej dodało odpow iedni
przedstaw iliśm y kod, k tóry pow inieneś w niej um ieścić: szkielet metody.
private void target_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (targetTimer.IsEnabled && humanCaptured)
{
progressBar.Value = 0;
C a n v a s . SetLeft(target, random.Next(100, (int)playArea.ActualWidth - 100));
C a n v a s . SetTop(target, random.Next(100, (int)playArea.ActualHeight - 100));
C a n v a s . SetLeft(human, random.Next(100, (int)playArea.ActualWidth - 100));
C a n v a s . SetTop(human, random.Next(100, (int)playArea.ActualHeight - 100));
humanCaptured = false;
human.IsHitTestVisible = true;
}

© T e ra z będziesz m usiał d odać jeszcze dwie kolejne p ro ced u ry obsługi zdarzeń. Tym razem b ę d ą o n e zw iązane
z k o n tro lk ą C anvas o nazw ie p la y A re a . M usisz zatem o d naleźć odp o w ied n ią k o n tro lk ę [G rid ] w o knie D ocum ent
O utline (są dwie tak ie k on tro lk i — w ybierz tę w ew nętrzną, w idoczną tro c h ę niżej i p rz e su n iętą nieco w praw o
w zględem głównej siatki określającej u k ład całej strony) i przypisz jej nazw ę g r i d . T e ra z m ożesz d o d ać do
k ontrolki p la y A re a dwie, p rzed staw io n e poniżej, p ro ced u ry obsługi zdarzeń:

private void playArea_PointerMoved(object sender, PointerRoutedEventArgs e)


{ Tu j e s t naprawdę sporo nawiasów!
if (humanCaptured) W pisuj je uw ażnie i upewnij się,
{ że w szystko dobrze zap isa łeś.
Point pointerPosition = e.GetCurrentPoint(null).Position;
Tq dwie Point relativePosition * grid.TransfomToVisual(playArea).TransfonuPoint(pointerPosition);
pionowe kreski if ((Math.Abs( relativePosition.X - Canvas.G e t Left(human)) > human.ActualWidth * 3)
s ą operatorem relativePosition.Y - Canvas.GetTop(human)) > human.ActualHeight * 3))
logicznym.
D owiesz s ię
o nich w ięcej
w rozdziale 2. t
else
humanCaptured * false;
human.IsHitTestVisible * true;
i
Zm niejszając lub zwiększając
{ te liczby (3) możesz sprawić,
Canvas.SetLeft(human, relativePosition.X - human.ActualWidth / 2);
Canvas.SetTop(human, relativePosition.Y - human.ActualHeight / 2); że prowadzenie postaci
} m yszką będzie trudniejsze
} lub łatwiejsze.
}
private void playArea_PointerExited(object sender, PointerRoutedEventArgs e)
{
if (humanCaptured) | P ro p e rtie s - ł X |

EndTheGame(); q Name playArea > 0


} Type Canvas

PointerEntered
Upewnij się , że w pisałeś odpowiedni
Pointę rExited playArea_PointerExited
kod do odpowiedniej procedury
obsługi zdarzeń. PointerMoved playArea_ Pointe rMoved

Poi nterPressed
jesteś tutaj ► 85
Nie możesz uratować wszystkich

Dotknięcie człowiekiem wroga kończy grę


K iedy gracz przeciągnie człow ieka tak, że d o tk n ie o n w roga, g ra pow in n a się zakończyć. D odaj zatem
kod, który zapew ni tak ie działanie aplikacji. P rzejdź do m eto d y AddEnemy() i n a jej k ońcu dodaj
kolejny w iersz. Skorzystaj z o k ien k a IntelliSense, by w pisać e n e m y .P o in te r E n te re d :

private void A d dEnemyQ


{
ContentControl enemy = new ContentControlQ;
enemy.Template = Resources["EnemyTemplate"] as ControlTemplate;
AnimateEnemy(enemy, 0, playArea.ActualWidth - 100, "(Canvas.Left)”);
AnimateEnemy(enemy, random.Next((int)playArea.ActualHeight - 100),
random.Next((int)playArea.ActualHeight - 100), "(Canvas.Top)");
playArea.Children.Add(enemy);

enewy.Pointer To j e s t ostatni w iersz metody AddEnemy ().


U mieść kursor na końcu w iersza i naciśni'j
f P ointerC anceled klawisz Enter, aby dodać nowy w iers z kodu.
f P o in terC a pture L o st

Z acznij wpisyw ać ten w iersz A PointerC aptures


kodu. Gdy tylko w p iszesz 9 I PointerEntered P o in terE ve n tH an d ler U lE lem ent.P ointerEntered
znak kropki, IDE w yświetli f PointerExited O ccurs w he n a p o in te r enters th e h it te s t area o f th is elem ent.
okienko IntelliSense.
P o in te rM o ve d
Rozp ocznij wpisywanie słowa
„p o inter", by przeskoczyć f PointerPressed
do dalszej części listy, do PointerReleased
e lem entów rozpoczynających
s ię od „Pointer...". f P o interW heelC hanged

Z wyświetlonej listy w ybierz P o in t e r E n te r e d . (N ie przejm uj się, jeśli w ybierzesz niewłaściwy e le m e n t listy — p o p ro stu
u suń wszystkie znaki w łącznie z k ropką. N a stęp n ie znow u w pisz k ro p k ę, a ID E wyświetli o k ienko IntelliSense.)

N astęp n ie, w taki sam sposób ja k w cześniej, dodaj p ro ce d u rę obsługi zdarzeń. W pisz +=, p o czym naciśnij klawisz Tab:

W szelkie informacje na tem a t


p rocedur obstugi zdarzeń poznasz
w rozdziale 15.

N astęp n ie ponow nie naciśnij klawisz Tab, co spow oduje w ygenerow anie szkieletu p ro ced u ry obsługi zdarzeń:

T eraz m ożesz przejść do nowej m etody w ygenerow anej przez ID E i uzu p ełn ić jej kod:

void enemyPointerEntered(object sender, PointerRoutedEventArgs e)

86 Rozdział 1.
Zacznij pisać programy w C#

Teraz ju ż można bawić się grą


U ru ch o m grę — ju ż jest niem al gotowa! K iedy klikniesz przycisk Start, z o b szaru gry
są usuw ani wszyscy w rogow ie i p o zo staje n a nim w yłącznie człow iek i docelow y p ortal.
M usisz doprow adzić człow ieka do p o rta lu , zanim zostanie w ypełniony p a se k postępu.
Początkow o jest to p ro ste , je d n a k później, gdy n a ek ran ie pojaw iać się b ę d ą kolejni
groźni w rogow ie, zad an ie to będzie coraz trudniejsze!

Przeciągnij człowieka do bezpiecznego schronienia!


Obcy spędzają czas wyłącznie
na patrolowaniu i poszukiw aniu
4. — poruszających s ię ludzi, dlatego
gra kończy s ię jedynie wtedy, gdy
któryś z nich dotknie człowieka
przeciąganego przez gracza. Kiedy
p u śc isz człowieka, będzie on
chwilowo bezpieczny.

\
Przeglądnij kod i poszukaj w m’m
m iejsca, w którym określana j e s t \wartość
właściwości Is H itT e stV isible. Gdy j e s t ona
włączona, człowiek przechw ytuje zdatrzenm
PointerEntered, gdyż reprezen.tu jąca go
kontrolka StackPanel znajduje s ię p omiętJzy
wskaźnikiem m yszy i konM Irą woga..

Przeciągnij człowieka do portalu, zanim skończy się czas...

...jednak stracisz go, jeśli będziesz to robił zbyt szybko!

jesteś tutaj ► 87
Ozdóbki, wodotryski i obcy

Zadbaj, by wrogowie wyglądali jak obcy


Wyświetlane są
C zerw one kółka nie są szczególnie przerażające. N a szczęście m ożesz skorzystać zdarzenia, a nie
z szablonu. W szystko sprow adza się do w p row adzenia w nim niew ielkich zm ian. właidwoid:

W yśw ietl okno D o cu m en t O utline, kliknij praw ym przyciskiem myszy wyświetlony Korzystając z przycisku klucza
w nim elem en t C ontentControl, a z w yśw ietlonego m en u p o d ręczn eg o wybierz i błyskawicy możesz zmieniać
opcję E d it Tem plate, a n a stęp n ie E dit Current. W ybrany szablon zostanie zawartość prezentowaną
w oknie Properties, * *
wyświetlony w oknie k o d u X A M L . Z m odyfikuj k od X A M L elipsy w tak i sposób,
by m iała szerokość (W idth) 75 pikseli i była w ypełniona ko lo rem szarym (G ray). wyświetlając, odpowiednio,
N astęp n ie dodaj do jej k o d u kolejną właściwość, s t™ ite = ,lB ia c k " (aby dodać właściwościlub zdarzenia.
obram ow anie), i przyw róć dom yślne w artości w łaściwościom w yrów nania w
pionie i poziom ie. T a k pow inien w yglądać ten k o d (m ożesz z niego u su n ąć
wszystkie po zo stałe właściwości, k tó re mogły zostać do niego d o d an e podczas
w prow adzania m odyfikacji):

<Ellipse Fill=”Gray” Height=”100" Width="75" Stroke=”Black” />

[2 Z o k n a Toolbox przeciągnij now ą k o n tro lk ę E l l i p s e i up u ść ją n a ju ż istniejącej elipsie. Z m ień k o lo r w ypełnienia


(k arta Fill) na czarny, ustaw szerokość n a 25, a wysokość n a 35. O pcje w yrów nania i m arginesy ustaw ta k jak
pokazaliśm y na poniższym rysunku:

e lip s ą /a n%stąpnie wkleić do dokumentu je j kopią.

Użyj przycisku x d o stęp n eg o w sekcji Transform okna Properties, aby nieco przekrzyw ić elipsę
(dodając do niej przekształcenie Skew).

z n a 1 y ©

X 10 V o

[4 Przeciągnij z o kna Toolbox jeszcze je d n ą elipsę i ją także um ieść w obszarze starej elipsy. Z m ień kolor w ypełnienia na
B lack , ustaw szerokość na 25, a wysokość na 35. O pcje w yrów nania i m arginesy ustaw zgodnie z poniższym rysunkiem:

HorizontalAlign... ¡Z" | "Ś | ~ | |~ |

VerticalAlignm ... |JT | || 11

Margin 70 ■* 40 Ich unikaj


20 * 3
Teraz Twoi wrogąwie
wyglądają ja k polujący
a opcje przekrzyw ienia następująco: na ludzi obcy.
4

88 Rozdział 1.
Zacznij pisać programy w C#

Dodaj ekran startowy i tytuł Nie masz ochoty samodzielnie tworzyć


ekranu startowego i logo? Pobierz nasze
T en w ielki „X ”, który je st wyświetlany w m o m encie u ru ch am ian ia
z serwera FTP wydawnictwa Helion:
ftp://ftp.helion.pl/przyklady/cshru3.zip.
aplikacji, nosi nazw ę ek ra n u tytułow ego (ang. splash screen). A kiedy
wyświetlim y ek ran startow y system u W indow s, okaże się, że tak że na
nim pojaw ił się sym bol „X ” . Z ajm ijm y się tym.

R ozw iń fo ld er ® Assets w oknie Solution Explorer; poniżej zostaną


w yśw ietlone cztery pliki. D w u k ro tn ie kliknij każdy z nich, by otw orzyć je
w p ro g ram ie Paint. Z m odyfikuj plik SplashScreen.png, by określić postać
e k ra n u tytułow ego w yśw ietlanego podczas u ru ch am ian ia gry. O brazki
u m ieszczone w plikach L ogo.png o raz Sm allLogo.png są z kolei w yświetlane
n a ek ran ie startow ym system u W indows. K iedy aplikacja pojaw ia się
w w ynikach w yszukiw ania (lub w Sklepie W indow s!), w yświetlany jest
z kolei o b razek um ieszczony w plik u StoreLogo.png.

.i * B ez tytułu - P a in t ” D

■ Narrfdru gtowmr MMM ‘ O

□ ; w = ■ ■ ■ ■ ■ ■ ■ " ■ ■ ■
V«H| ' Z im a , M * O 1*04 R O I0»0i «0*0ł l<Wuj
• • • • 1 J .010,r
kM w rt Oer»r N>i7<<ni (u M y (otory

C#. Rusz głową! Niektóre


w ersje
p re z e n ty e V isual Stu d io
używ ają
własnego

Ratuj ludzi edytora


graficznego
za m ia st
programu
M S Paint.

| -i- nj >g«i«w**» ioo% (£) ®

<ControlTemplate x:Key=”Enei*yTemplate" TargetType="ContentControl”>


<Grid>
<Ellipse Fill="Gray” Height="100" Width="75” Stroke="Black"/>
<Ellipse Fill=”Black” HorizontalAlignment="Center” Height="35” Margin="40,20,70,0”
Stroke=”Black" VerticalAlignment="Top" Width="25” RenderTransformOrigin="0.5,0.5”>
<Ellipse.RenderTransform>
, ' ' Ske-.-.-K To j e s t zaktualizow any kod XAML
1,1 ' ' ' ■ zmodyfikowanego przed chwilą szablonu wroga.
</Ellipse>
<Ellipse Fill=”Black” HorizontalAlignment="Center” Height="35” Margin="70,20,40,3”
Stroke=”Black" VerticalAlignment="Top" Width="25” RenderTransformOrigin="0.5,0.5”>
<Ellipse.RenderTransform>
<CoinpositeTransforni SkewX="-10"/>

</ElIipse>
JEST JESZCZE JEDNA RZECZ, KTÓRĄc POWINIENEŚ ZROBIĆ...
ZAGRAĆ W SWOJĄ GRĘ!
</ControlTemplate> ...
I nie zapomnij zatrzym ać s ię nc° chwij k ę i d c ™ 6 t o
S p rawdźmy, czy m ożesz wykazać s ię co w ła ś n i udało O s ię zrobić. a ro
kreatyw nością i zm ienić wygląd człowieka,
docelowego portalu oraz wroga. jesteś tutaj ► 89
Twoja aplikacja trafia do wszystkich
Jesteś tutaj!
Opublikuj swoją aplikację
t
Pow inieneś być n apraw d ę zadow olony ze swojej
aplikacji! T e ra z n ad szed ł czas, żeby ją w drożyć. K iedy ją
opublikujesz w Sklepie W indow s, u d o stęp n isz ją m ilionom
0
potencjalnych użytkow ników . ID E m oże Ci w tym pom óc,
przeprow adzając Cię p rzez wszystkie czynności zw iązane
z publikow aniem aplikacji w Sklepie W indows.

O to co trzeb a zrobić, by opublikow ać aplikację:

U tw orzyć k o n to p rogram isty S klepu W indows.

STORE TEST W INDOW HELP

Open Developer Account...


[2 W ybrać nazw ę aplikacji, określić p rzed ział w ieku
użytkow ników , napisać opis i w ybrać m o d el biznesowy, Reserve A pp Name...
który określa, czy aplikacja jest d o stę p n a bezpłatnie, Acquire Developer License...
korzysta z rek lam , czy też trz e b a za n ią zapłacić.
Edit A pp Manifest

Associate A pp w ith the Store...


P rzetestow ać aplikację, używ ając narzęd zia W indows Capture Screenshots,,,
A pp C ertification K it, aby o d naleźć w niej i popraw ić
ew entualne problem y. Create A pp Packages...

Upload App Packages...

[4 P rz e s ła ć a p lik a c ję d o S k le p u W indow s! K iedy


zostanie zaakceptow ana, m iliony użytkow ników
t
W menu STORE w IDE zo sta ty zgromadzone
n a całym świecie b ę d ą m ogły ją pobrać. w szystk ie narzędzia potrzebne do
publikowania aplikacji w S klep ie Windows.

r
W niektórych wersjach V isual Stu d io opcje
zw iązane ze Sklepem Windows s ą um ieszczone
w menu PROJECT, a nie w odrębnym menu
gtównego poziomu — STORE.
W tej książce będziem y Ci pokazy wać, gdzie może sz
znajdować dodatkowe informacje ma. w itrynie MSDN
— M icrosoft Developer Network. To naprąwdę
bardzo cenne źródto informacjit które może Ci pomóc
w poszerzaniu wiedzy.

Więcej inform acji na tem at publikow ania aplikacji w Sklepie W indows można
znaleźć na stronie http://msdn.microsoft.com/pl-pl/library/windows/apps/jj657972.aspx.

90 Rozdział 1.
Zacznij pisać programy w C#

Użyj programu Remote Debugger,


by uruchomić aplikację na innym komputerze
C zasam i m ożem y chcieć spróbow ać u ru ch o m ić aplikację n a innym k o m p u terze, bez p ublikow ania jej
w Sklepie W indows. P ro ces in stalow ania aplikacji n a k o m p u terze b ez korzystania ze S klepu W indows
nazyw any je st ła d o w a n ie m b e z p o ś re d n im (ang. sideloading), a jednym z najłatw iejszych sposobów , by go
przeprow adzić, jest zainstalow anie n a innym k o m p u terze p ro g ram u V isu a l S tu d io R e m o te D eb u g g er.

W cza sie pisania tej książki wyniki


w yszukiwania zwracaty program
Ja k zainstalow ać aplikację, używ ając p ro g ra m u R em o te D ebugger: „Remote Tools for V isual Studio 2012
Update 2", jednak w przysztości mogą
★ U pew nij się, że n a zdalnym k o m p u terze je st zainstalow any system W indow s 8. s ię pojawić jego kolejne wersje.

★ N a zdalnym kom p u terze wyświetl stro n ę M icrosoft D o w nload C e n te r (http://www.m icrosoft.com/en-us/ ^


dow nload/default.aspx) i w p o lu w yszukiw ania wpisz: Remote T o o ls f o r V is u a l S tu d io 2012.

★ P o b ierz p rogram odpow iadający a rch itek tu rze k o m p u te ra (x 86, x64, A R M ) i u ru ch o m program
instalacyjny.

★ W yśw ietl ek ran startow y system u i u ru ch o m p ro g ram R e m o te D ebugger.

★ Jeśli konieczne je st w p row adzenie zm ian w konfiguracji sieci, to n a ek ran ie m oże zostać wyświetlony
k re a to r, który p o m o że Ci to zrobić. P o u ru ch o m ien iu deb u g g era n a ek ran ie zostanie w yświetlone
okno p ro g ram u V isual S tu d io R em o te D ebugging M onitor:

Visual Studio Rem ote Debugging Monitor


_ n

File lo o ls Help

Date and Time Description

14/6/2013 2:37:34 PM M svsm o n started a new server nam ed 'MY-SURFACE:4016'. W a itin g fo r new conn...

'f
Ten program zo sta t u ru c h o m m y na _
Zanotuj tę nazw ę kom putera gdyż j u ż niebawem Ci s ię Rrrzyoa

★ T e ra z n a zdalnym k o m p u terze działa już p ro g ram V isual Studio R e m o te D ebugging M o n ito r, oczekujący na
p ołączen ia inicjow ane przez V isual Studio działające n a Tw oim roboczym k o m p u terze.

Jeśli używaszjakiejś nietypowej konfiguracji sieciowej, to możesz napotkać problemy


z uruchomieniem zdalnego debuggera. W ich rozwiązaniu mogąCi pomóc informacje zamieszczone
w MSDN, na stronie http://msdn.microsoft.com/pl-pl/library/vstudio/bt727f1t.aspx.

Przewróć kartkę, by zainstalować i uruchomić swoją aplikację na zdalnym komputerze! --------------- ►

jesteś tutaj ► 91
Na razie ludzie są uratowani

Rozpocznij zdalne debugowanie


K iedy na zdalnym k o m p u terze b ędzie już działał p ro g ram V isual S tu d io R e m o te D ebugging
M o n ito r, m ożesz uruch o m ić aplikację w V isual S tudio, aby ją zainstalow ać i u ru ch o m ić na
zdalnym k o m puterze. S pow oduje to au tom atycznie b ezp o śred n ie załadow anie aplikacji na
ten k o m p u ter i um ożliw i u ru ch o m ien ie jej z p o zio m u e k ra n u startow ego.

Z ROZWIJANEJ LISTY DEBUGOWANIA WYBIERZ OPCJĘ „REMOTE MACHINE".


M ożesz skorzystać z rozw ijanej listy D ebug, aby p o inform ow ać ID E , że chcesz u ru ch o m ić aplikację
na zdalnym k o m p u terze. Przyjrzyj się dokładniej przyciskow i ►L°cal MKhmt - , k tó reg o ju ż używałeś
do u ru ch am ian ia aplikacji — z jego praw ej strony m ożesz zobaczyć przycisk do rozw ijania listy ( • ) .
Kliknij go, aby rozw inąć listę, a n astęp n ie w ybierz z niej opcję R em ote M achine:

P> R em ote M a ch in e -
Nie zapomnij zm ienić wybranej tu opcji z powrotem na
► R em ote M achín e Sim ulator przed rozpoczęciem le ktury następnego rozd zia łu -
S im u la to r
N apiszesz w nim sporo pr°gramów, więc ten przycisk
będzie Ci potrzebny, by je uruchamiać-
Local M a chin e

□ R em ote M a ch in e

W URUCHOM SWÓJ PROGRAM NA ZDALNYM KOMPUTERZE.


T eraz m ożesz u ru ch o m ić p ro g ram , klikając przycisk ►. K iedy to zrobisz, ID E wyświetli okno dialogow e,
w którym będziesz m ógł w ybrać k o m p u ter, n a którym chcesz u ru ch o m ić aplikację. Jeśli nie wykryje takiego
k o m p u te ra w sieci, m ożesz ręcznie w pisać jego nazw ę:

Remote Debugger Connections ?


I Filter P -
J e ś li w p r z y s z ło ś c i b ę d z ie s z m u s ia ł
Search in g ...
z m ie n ić k o m p u te r, m o ż e s z t o z ro b ić
1(^) M a nu al C o nfig uratio n w u s ta w ie n ia c h p ro je k tu . W ty m c e lu
A dd ress: M Y -SU R F A C E < ' kliknij p ra w y m p rz y c is k ie m m y s z y n a z w ę
p ro je k tu w o k n ie S o lu tio n E x p lo re r ,
1 A u th en tica tio n M ode: W in d o w s ^
Cał 1nlaac df p pn n1IIC
i p IVIIIVIIIJ
Ic lilc n ii kl\dl
a r tL
pvj. ET ■
i Select
J e ś li w y c z y ś c is z p o le i p o n o w n ie
1($) Subnet / u r u c h o m is z z d a ln y d e b u g g e r , IDE
W pisz nazw ę kom putera, na którym dziafa p o n o w n ie w y ś w ie tli o k n o d ia lo g o w e
program Remote Debugging Momtor-. R e m o t e D e b u g g e r C o n n e c tio n s .

92 Rozdział 1.
Zacznij pisać programy w C#

[4 WPISZ SWOJE DANE UWIERZYTELNIAJĄCE.


Z o stan iesz p o p ro szo n y o p o d a n ie nazwy i hasła swojego
użytkow nika n a zdalnym k o m p u terze. Jeśli chcesz tego uniknąć,
to m ożesz wyłączyć uw ierzytelnianie w p ro g ram ie R em o te
D ebugging M o n ito r (jed n ak nie je st to najlepszy pomysł,
gdyż jeśli to zrobisz, każdy będzie m ógł zdalnie zainstalow ać
i u ru ch am iać pro g ram y n a tym ko m p u terze).

[4 POBIERZ LICENCJĘ PROGRAMISTY.


B ezp łatn ą licencję p rogram isty p o b rałeś już, instalując
V isual Studio. B ędzie Ci o n a p o trz e b n a do bezpośredniego
załadow ania aplikacji n a zdalny k o m p u ter. N a szczęście
p ro g ram R e m o te D ebugging M o n ito r u ru ch o m i k re a to ra ,
który um ożliw i au tom atyczne p o b ra n ie tej licencji.

[4 A TERAZ... SPRÓBUJ URATOWAĆ LUDZI!


K iedy już przejdziesz przez e ta p konfiguracji, Twój program
zostanie u ru ch o m io n y n a zdalnym k o m pu terze. Poniew aż
został on zainstalow any n a zdalnym k o m p u terze, jeśli zechcesz
ponow nie go u ruchom ić, m ożesz to zrobić z p o zio m u ek ra n u
startow ego system u W indows. G ratulujem y, w łaśnie napisałeś
sw oją pierw szą aplikację dla S klepu W indow s i uru ch o m iłeś ją
n a innym kom puterze!

jesteś tutaj ► 93
94 Rozdział 1.
2. To tylko kod
. +
Pod maską

Jesteś programistą, nie jedynie użytkownikiem IDE. IDE może wykonać


za Ciebie wiele pracy, ale na razie jest to wszystko, co może dla Ciebie zrobić. Oczywiście
istnieje wiele po w tarza lnych czynności podczas pisania aplikacji i IDE okazuje się
tu bardzo pomocne. Praca z nim to jednak dopiero poczgtek. Możesz wycisnąć ze swoich
programów znacznie więcej — pisanie kodu C# to właśnie droga, która doprowadzi
Cię do tego celu. Kiedy osiągniesz mistrzowski poziom w kodowaniu, nie będzie żadnej
rzeczy, której Twój program nie umiałby zrobić.

to jest nowy rozdział ► 95


W służbie Tobie

Kiedy robisz to ... W szystkie te standardowe


zadania pociągają za. s obą
konieczność wykonywania
ID E je st potężnym narzędziem , ale w dalszym ciągu je st to tylko narzędzie, którego powtarzalnych czynności
będziesz używał. Z a każdym razem , gdy zm ienisz p ro jek t, przeciągniesz i upuścisz i pracy z e statym kodem.
S ą to te obszary, w których
coś w obszarze ID E — w tle b ędzie autom atycznie generow any kod. ID E je st bardzo IDE j e s t bardzo pomocne.
dobre w pisaniu k o d u sta łe g o lub takiego, k tóry m oże być ponow nie użyty bez
w prow adzania w iększych modyfikacji.

P opatrzm y zatem , co robi ID E w typowych sytuacjach, k tó re m ają m iejsce podczas


tw orzenia aplikacji. Kiedy:

TWORZYSZ NOWĄ APLIKACJĘ


DLA SKLEPU WINDOWS...
Jest kilka typów aplikacji, k tó re m ożesz utw orzyć
za pośred n ictw em ID E , ale skupim y się teraz n a
aplikacjach dla S k lep u W indow s — aplikacje innych
typów poznasz w następnym rozdziale.

W rozdziale 1. stw o rzyteś p u sty projekt aplikacji dla


S k h p u Window s, co sprawiło, że IDE utworzyło p u stą
s tr°nę i' dodało ją do projektu.

PRZECIĄGASZ KONTROLKĘ
Z O KNA TOOLBOX N A STRONĘ,
A NASTĘPNIE KLIKASZ JĄ DWUKROTNIE...
K o n tro lk i um ieszczan e n a stro n a c h um ożliw iają
p o d ejm o w an ie akcji. W tym ro zd ziale będ ziesz używał
k o n tro le k B u tto n , by p o zn aw ać ró żn e elem en ty
języka C # .

USTAWIASZ WŁAŚCIWOŚCI N A S TR O N IE.


O kno Properties w ID E je st n ap raw d ę potężnym
narzędziem , k tó re m oże być użyte do zm iany atrybutów
praw ie każdego e le m e n tu w p rogram ie: wszystkich
w izualnych i funkcjonalnych właściwości k o n tro lek
strony, a naw et opcji sam ego pro jek tu .

Okno Properties w IDE „/


pozwala w prosty, za u tJ m J f,elem ente"'> który
edytować konkretne f r a a n f l * 0? ^ s Pos°b
Pliku M ainPageTa J 2 T l y k° du X^ L
zaoszczędzić sporo czasu J e ś H ^ możerny
zam knięte, m ożesz go otworz 5 t° j e s t
kombinację klaw iszy Ą ^ E n te r m ° ,skan c

96 Rozdział 2.
To tylko kod

...ID E robi to
Z a każdym razem , kiedy d okonasz zm iany w o knie ID E , p rzen o szo n a
je st o n a także n a kod. O znacza to , że zm ieniane są rów nież pliki z tym
kodem . C zasam i m odyfikow anych je st tylko kilka wierszy, innym razem
dodaw ane są całkiem now e pliki.
Te pliki s ą tworzone na podstawie
w cześniej zdefiniowanego
wzorca. Zaw iera on podstawową
[1 „.IDE TW ORZY PLIKI I KATALOGI DLA kod niezbędny do utworzenia
i w yśw ietlenia formularza
PROJEKTU.

<page>*
<grid>

</grid>
</page>

R atu j ludzi M ainPage.xam l M ainPage.xam l.cs SplasbScreen.png P ro p e rtie s


.csp ro j

„.IDE DODAJE DO KODU PLIKU MAINPAGE.XAML KOD


PRZYCISKU, A DO PLIKU MAINPAGE.XAML.CS KOD, KTÓRY
BĘDZIE W YKO N YW AN Y PO KAŻDYM KLIKNIĘCIU PRZYCISKU

M ainPage.xam l
p riv a te void sta rtB u tto n _C lick (o b je ct sender, RoutedEventArgs e)

IDE wie, ja k dodać p u stą metodą do obstugi


kliknięcia przyciski*. Nie w e n a f o m a ^
co wtożyć do środka — fo Twoje zadanie.

„.IDE OTWIERA PLIK MAINPAGE.XAML I MODYFIKUJE M ainpage.xam l.cs


WIERSZ KODU.
IDE przeszfo do tego pliku—

<Button x:Name="startButton"
Content="Start!"
HorizontalAlignment="Center" <page>*
<grid>
VerticalAlignm ent="Center" C lick= "startB u tto n _C lick"/>
</grid>
:/page>

M ainPage.xam l
...i zmodyfikowało ten w iersz kodu.

jesteś tutaj ► 97
Wspaniale, pogadanka

Skąd się biorą programy


P rogram C # m oże n a p o czątk u istnieć jak o zestaw instrukcji zapisanych
w w ielu plikach, ale ostateczn ie staje się p ro g ram em działającym na
Tw oim k o m puterze. Poniżej p rzedstaw iony je st cały te n proces.

Każdy program rodzi się jako zbiór plików z kodem źródłowym


Ju ż zobaczyłeś, ja k edytow ać p ro g ram y o ra z w jaki sposób ID E zapisuje wyniki Twojej
pracy w plikach i katalogach. T o w łaśnie te pliki są Tw oim p ro g ram em — m ożesz je
skopiow ać do innej lokalizacji i otw orzyć. Z najdziesz tam wszystko: strony, zasoby,
kod i co tylko zdołałeś d odać do pro jek tu .

M ożesz traktow ać ID E jak o wymyślny ed y to r plików. A utom atycznie w ykonuje on


za C iebie w cięcia, zm ienia kolory słów kluczowych, dopasow uje nawiasy, a naw et
p o d pow iada w yrażenia, k tó re m ogą w ystąpić w danym kontekście. Je d n a k wszystko,
co robi ID E , sprow adza się do edycji plików składających się n a Twój p rogram .
Nic nie stoi
na przeszkodzie,
ID E zbiera w szystkie części aplikacji w ta k zwanym ro z w ią z a n iu , tw orząc plik byś p isa ł sw oje
rozw iązania (.sln) o ra z katalog, któ ry grom adzi in n e pliki p ro g ram u . Plik rozw iązania programy w Notatniku,
ale zajęłoby to znaczni
przechow uje listę plików p ro je k tu (z rozszerzeniem .csproj), te n ato m iast zaw ierają listę więcej czasu.
w szelkich pozostałych plików pow iązanych z p rog ram em . W tej książce tw orzone będą
rozw iązania posiad ające tylko je d e n p ro jek t, ale w łatwy sposób m ożesz d o d ać do nich
inne, korzystając z o k n a Solution Explorer.

Zbuduj program, aby stworzyć plik wykonywalny


K iedy w ybierasz opcję B uild Solution z m en u B U IL D , ID E k o m p ilu je
Twój program . W ykonuje to, korzystając z k o m p ila to ra — narzędzia,
które odczytuje k o d źródłowy p ro g ram u i zam ienia go w p lik w ykonywalny.
Jest to plik na dysku z rozszerzeniem .exe — to jest faktyczny program
wykonywany przez system Windows. Plik wykonywalny jest tw orzony
podczas budow ania p ro g ram u i um ieszczany w katalogu bin znajdującym się
w ew nątrz katalogu projektu. W ybór opcji Publish dla rozw iązania pow oduje,
że plik wykonywalny (oraz wszystkie inne p o trzeb n e pliki) kopiow any jest
do pakietu, który następnie jest przekazyw any do Sklepu W indows lub
kopiow any bezpośrednio n a inny kom puter.

P o w yborze opcji Start Debugging z m en u D E B U G ID E przystępuje


do kom pilacji p ro g ram u i zam ienia go n a p o stać wykonywalną.
ID E p o siad a kilka bardziej zaaw ansow anych n arzędzi do d e b u g o w a n ia
p ro g ram u . D zięki nim m ożliw e je st zatrzym anie go w trak cie jego działania
i spraw dzenie, co się w nim dzieje.

98 Rozdział 2.
To tylko kod

Platforma .N E T udostępnia właściwe narzędzia do wykonywania pracy


C # to tylko język — sam w sobie niczego nie robi. T o jest w łaśnie to m iejsce, w którym do akcji
w kracza p la tfo rm a .N E T . Czy chodzi o te k o n tro lk i przeciągane z o k n a Toolbox? O n e wszystkie
należą do biblioteki n arzędzi, klas, m e to d i innych użytecznych rzeczy. N ależą do nich także
w izualne k ontrolki X A M L o ra z o biekty D is p a tc h e rT im e r, których użyłeś w grze R a tu j ludzi.

Wszystkie kontrolki, których używałeś, są elem entam i .NET for W indows Store — biblioteki
zawierającej siatki, przyciski, strony oraz wszelkie inne narzędzia służące do tw orzenia aplikacji
przeznaczonych dla Sklepu W indows. Jed n ak w trakcie lektury pierwszych kilku rozdziałów,
zaczynając od rozdziału 3., będziesz się uczył pisać klasyczne aplikacje systemu W indows, tworzone
przy użyciu narzędzi wchodzących w skład biblioteki .NET for W indows D esktop (nazywanych także
czasami „W inForm s”). D oskonale nadają się one do tw orzenia klasycznych aplikacji, mających postać okien
zawierających form ularze z polam i wyboru, przyciskami, listami itd. M ogą one wyświetlać obrazy, odczytywać A P I, czyli
i zapisywać pliki, zarządzać kolekcjami obiektów ... stanowią zbiór narzędzi służących do realizacji bardzo wielu interfejs
programowania
zadań, które program iści muszą wykonywać każdego dnia. Zabaw ne jest to, że dokładnie te sam e czynności aplikacji
m uszą realizować aplikacje przeznaczone dla Sklepu Windows! Jed n ą z rzeczy, których dowiesz się w tej książce, (od angielskich
słów: Application
są różnice w sposobie realizacji różnych zadań w aplikacjach dla Sklepu W indows oraz klasycznych aplikacjach
Programming
systemu Windows. T o właśnie taka w iedza sprawia, że dobrzy program iści m ogą się stać świetnymi program istam i. Interface), j e s t
kolekcją narzędzi
N arzęd zia d o stęp n e w ram ach W indow s R u n tim e o raz p latfo rm y .N E T p o d zielo n e są za po m o cą programowych,
p r z e s trz e n i nazw . W idziałeś je już w cześniej n a p o czątk u k o d u w po staci linijek „using”. Jed n ą których możemy
używać, by
z p rzestrzeni nazw je st W in d o w s.U I.X a m l.C o n tro ls . T o m iejsce, sk ąd b io rą się w szystkie przyciski, p o la u zyskać dostęp
w yboru i form ularze. Z a każdym razem , kiedy w ybierasz utw o rzen ie p ro je k tu typu W indow s S to re, ID E lub kontrolować
sy ste m . Wiele
dodaje wszystkie p o trz eb n e pliki. D zięki te m u p ro jek t m oże zaw ierać form ularz, a wszystkie pliki p o siad ają system ów
n a górze w iersz using W in d o w s .U I.X a m l.C o n tro ls ;. udostępnia
A P I, lecz dla
system ów
Ogólne informacje na tem at .NET Framework używanej w aplikacjach dla Sklepu W indows możesz operacyjnych
znaleźć tutaj: http ://m sd n .m icro so ft.co m /p l-p l/lib ra ry/w in d o w s/a p p s/b r2 3 0 3 0 2 .a sp x. takich jak
Windows mają
one szczególne
znaczenie.

Twój program działa w C LR


K ażdy p ro g ram w W indow s 8 je st u ru ch am ian y w ram ach a rch itek tu ry o nazwie
W indow s R u n tim e. T rze b a w spom nieć, że istnieje „w arstw a” p o śred n ia pom iędzy
system em W indow s R u n tim e a p ro g ram em . N a d a n o jej nazw ę C o m m o n L a n g u a g e
R u n tim e lub w skrócie C L R . W przeszłości, nie ta k daw no tem u , ale jeszcze zanim
pojaw ił się C # , pisan ie p ro g ram ó w było trudniejsze. P ro g ram ista m usiał radzić sobie
ze sprzętem oraz innym i zag adnieniam i niskiego p oziom u, bo n ik t do k ońca nie
w iedział, ja k użytkow nik skonfiguruje swój k o m p u ter. C L R — często zwany także
m a s z y n ą w ir tu a ln ą — p rzejm u je o d C iebie wszystkie te obow iązki i p ełn i rolę
tłum acza pom iędzy p ro g ram em a k o m p u terem , n a którym je st on wykonywany.
Nie m u sisz s ię na razie
W krótce dow iesz się o wszystkich sytuacjach, w których m o żn a liczyć n a p o m o c C LR . przejmować d ziałaniem CLR.
W ystarczy, że by ś w iedziałi
O dpow iada on n a przykład za zarząd zan ie pam ięcią k o m p u tera, w yłapując m om enty, że CLR j e s t i zajm uje s ię
w których pew ne dane p rze sta ją być p o trz e b n e , i usuw ając je za C iebie. T o jest je d n a a u tom atycznie uruchamianymi
programami. Wkrótce p°znasz
z tych rzeczy, o k tó rą daw ni p rogram iści m usieli się troszczyć osobiście i o k tó rą dalsze szczegóły na je g o tem at.
Ty już nigdy więcej nie będziesz się m artw ił. Jeszcze nie zdajesz sobie z tego sprawy,
ale C L R uczyni n au k ę C # znacznie prostszą.
jesteś tutaj ► 99
M ały pomocnik mamy

(DE pomaga Ci kodować


W idziałeś już kilka rzeczy, k tó re p o tra fi zrobić ID E . Przyjrzyjmy
się te ra z nieco bliżej kilku narzędziom u d o stęp n ian y m przez
to środow isko program istyczne.

Q OKNO SOLUTION EXPLORER POKAZUJE CAŁĄ ZAWARTOŚĆ PROJEKTU


Spędzisz dużo czasu, p rzełączając się tam i z p o w ro tem p om iędzy różnym i klasam i.
N ajłatw iejszym sposobem w ykonyw ania tej czynności jest korzystanie z o k n a Solution Explorer.
Poniżej p o k azan o , ja k w ygląda o k n o Solution Explorer p o u tw o rzen iu pustej aplikacji
o nazw ie A p p l:

< śr )
Okno Solution
Explorer
pokazuje pliki
z katalogu
programu.

Q KARTY POZWALAJĄ N A PRZEŁĄCZANIE SIĘ POMIĘDZY OTWARTYMI PLIKAMI.


Program y zazwyczaj składają się z w iększej liczby plików z kodem źródłowym , dlatego m ożna
przypuszczać, że w danej chwili w ID E b ędzie otw artych kilka z nich. W takim w ypadku każdy będzie
p o siad ał sw oją w łasną k a rtę w o knie ed y to ra kodu. ID E d odatkow o w yświetla gw iazdkę (*) obok
każdego pliku, k tóry nie został jeszcze zapisany.

o • o |b a u J1 » ► Local M achine ▼ Debug ▼ A n y CPU

M ainPagejtam l MainPagej<aml.cs -0 X

klaw iszy Control+Tab.

100 Rozdział 2.
To tylko kod

O IDE POMAGA CI PISAĆ KOD.


Czy zw róciłeś uw agę n a m ałe o k ien k a pojaw iające się podczas p isan ia k o d u w ID E ? T o u d o g o d n ien ie
nazywa się IntelliSense i jest n ap raw d ę p rzydatne. Jednym z zad ań teg o m ech an izm u je st sugerow anie
praw idłow ego zak ończenia bieżącego w iersza kodu. K iedy w piszesz random i k ro p k ę, IntelliSense będzie
w iedzieć, że istnieje tylko siedem praw idłow ych zakończeń dan eg o wiersza:

N e X tn 't' ż e ¿ andOm udost<lpn<a m etody Next,


N ex tB y tes,^ ^ t D o u b h i cztery inne. Kiedy
© Equals w p 'szes z N' zo s tanie zaznaczona pozycja N ext
$ GetHashCode
© GetType b f k f J ć m r śnij klaN.isz s Pacji' Tab lub Enter,
© B 2 1 H int Random.Next(int minValue, int maxValue) (+ 2 overload(s)) U JkadaniJD E u zupetnić kod Ny brany m elem entem.
© NextBytes Returns a random number within a specified range. NdOfedn m 0że zaoszczęd zić Ci naprawdę
© NextDouble
Exceptions:
NL* ZNłaszcza gdy p is ze sz dużo kodu,
© ToString System JtrgumentOutOfRangeException użyN ając . nim Nielu dtugich nazw metod.

Oznacza to, że istn ie ją 3 różne m o ^ m o ^


NyNołania metody Random.Next().

Jeśli w ybierzesz m e to d ę N ext i w piszesz (, to m echanizm IntelliSense wyświetli inform acje


o tym , ja k m ożesz uzu p ełn ić dalszą część jej wywołania.

Gdu do uruchamiania programu


używ asz opcji S ta r t Debuggmg,
Twoja aplikacja j e s t włączana
za pośrednictwem IDŁ.
O LISTA BŁĘDÓW POMAGA ZIDENTYFIKOWAĆ
Pierw szą rzeczą, ja k ą ono
BŁĘDY KOMPILATORA. robi, je s t budowanie projektu.
Jeżeli program skompiluje s ią
Jeżeli jeszcze nie doświadczyłeś tego, ja k łatw o zrobić błąd przy pisaniu poprawnie, j e s t u ru ch am iany
kodu C # , to b ard zo szybko się o tym przekonasz! N a szczęście ID E Jeżeli nie, nie zo staje włączony,
u d o stęp n ia kolejne w spaniałe n arzęd zie do rad zen ia sobie z takim i w padkam i. a w oknie Error L ist zostaje
wyświetlona lista błędów.
Podczas b u dow ania rozw iązania każdy p ro b lem , któ ry uniem ożliw ia
kom pilację, zostanie po k azan y w o knie Error L ist w dolnej części ID E .

Brak średnika T © H m ,. tlW list fi •


na końcu instrukcj' Otłcnpbofl fa* * lift* ft C«iu_ ft p»0)*<t ft
j e s t jednym
z n a jczęstszych
btędóN
uniemożliNiających
skompiloNanie
programu!

Kliknij dw ukrotnie błąd, a ID E n atychm iast p rzen iesie k u rso r w o d pow iednie m iejsce w kodzie:

IDE za sto su je czerwone podkreślenie


doktadnie w tym m iejscu, w którym j e s t
btąd. Wskaż je myszką, aby w yśw ietlić
komun ikat o błędzie, ten sam , który
pojawia s ię w oknie Error L ist.

jesteś tutaj ► 101


Program składa oświadczenie
Z a każdym razem, kiedy tw orzysz nowy program,
defm iuje s z dla n iego przestrzeń nazw. Dzięki
tem u jego kod j e s t odseparowany od innuch klas
Anatomia programu platf ° m y .NET oraz klas Windows S to re A P I.

Każdy kod p ro g ram u C # zbudow any je st w d okładnie taki


sam sposób. W szystkie aplikacje używ ają p rz e s trz e n i nazw.
k la s i m eto d , co ułatw ia zarządzanie nimi.

Klasy zaw ierają fragm<snty_k.odu


Twoj ego programu (ch o ^o ż istnieją Kohjność m etod
także bardzo małe aplikacje w klasie nie
składają ce s ię z tylko jednej klasy) ma żadnego
znaczenia -
nic nie stoi na
przeszkodzie,
by M etodę 2
u m ieścić przed
Klasa p osiada jedną lub więcej M etodą 1.
metod. Twoje metody za w sze
będą, um ieszczane wewnątrz klas
a każda z nich będzie s ię składała
J
z ins tr ukcj i - ja k te, które do tej
pory widziałeś.

Popatrzmy z bliska na Twój kod


O tw órz kod z p ro je k tu aplikacji R atuj ludzi, a k o n k re tn ie plik
MainPage.xaml.es. P rzeanalizujem y go k ro k p o kroku.

rp PLIK Z KODEM ROZPOCZYNA SIĘ OD SŁÓW KLUCZOWYCH USING.


N a p o czątku każdego plik u p ro g ra m u znajdziesz g ru p ę wierszy rozpoczynających się o d słowa u s in g . D zięki
nim C # w ie, z których części p latfo rm y .N E T lub W indow s S to re A P I b ędzie korzystał. Jeżeli używasz klas
z innych przestrzen i nazw, dla nich także m usisz d odać odpow iedni w iersz u s in g . W związku z tym , że aplikacje
często korzystają z różnych n arzędzi p latform y .N E T o raz W indow s S to re A P I, podczas tw orzenia nowych stro n
i dodaw ania ich do p ro je k tu ID E autom atycznie dod aje g ru p ę takich wierszy.

using System;
using System .Collections.Generic; Te wiers z e using znajdują s ię na początku
using System.IO; każd eg o pliku z kodem. Z a ich pośrednictwem
mów imy C#, aby używ a ł danych klas .NET. Każdy
using System.Linq; z nich informuj e , że klasy w tym pliku .c s będą
mogły u ży wać w szystkich klas, które znajdują się
using Windows.Foundation; w podanej przestrzeni nazw platformy .NET lub
using Windows.Foundation.Collections; Windows S to re A P I.

using Windows.UI.Xaml;

N ależy p am ięta ć o jed n ej rzeczy: tak n ap raw d ę w cale nie trzeba używać instrukcji u s in g . M o żn a użyć pełnej
nazwy. N a przykład w aplikacji R a tu j ludzi użyłeś następującej instrukcji u sin g :

u s in g W in d o w s.U I.X am l.M e d ia .A n im a tio n ;

Spróbuj um ieścić n a p o czątku tego w iersza znaki k o m en tarza ( / / ) , a następnie spójrz n a błędy wyświetlone w oknie
E rrorList. Jednego z nich m ożesz się pozbyć. O dszukaj typ S to ry b o a rd , który w edług inform acji prezentow anych
przez ID E pow oduje błędy i zm ień go n a W in d o w s.U I.X a m l.M e d ia .A n im a tio n .S to ry b o a rd (choć żeby program
z pow rotem zaczął działać, będziesz m usiał u sunąć dodany wcześniej kom entarz).

102 Rozdział 2.
To tylko kod

PROGRAMY C # PODZIELONE SĄ N A KLASY.


Każdy p ro g ram C # podzielony jest n a k lasy . K lasa m oże ro b ić wszystko, ale zwykle zajm uje się jed n ą,
k o n k re tn ą rzeczą. N a przykład, kiedy tworzyłeś nowy p ro g ram , ID E d o d ało klasę M ainPage, której
zadaniem je st wyśw ietlanie stro n y._______ Kiedy nadałeś programowi nazw ę Ratuj ludzi, IDE utworzyto dla
niego p rzes trzeń nazw Ratuj_ludzi (odstęp zo s ta ł zam ieniony na
k T '
znak p d k i^ e ś ^ w ^ gdyż n<xzwy prrętstrzeni nazw nie mogą zawierać
namespace Ratuj_ludzi odst ęP ów), dodając słowo kluczowe nam espace na początku każdego
p jiku z kodem. W szystko, co znajduje s ię pom iędzy parą nawiasów
{ klamrowych, stało s ię częścią p rzestrzeni nazw R atuj_ludzi.
p u b lic s e a le d p a r tia l c l a s s MainPage : Page
{ 1
To j e s t klasa MainPage. Zaw iera ona caty kod zapew niający f i a t a ™ W w ó Z w l j re
zaraz po tym , ja k zadecydowałeś o utw orzem u n °weg° p roj e k tu aplikacji C# ty p u Wmdows S to re .

KLASY ZAWIERAJĄ METODY, KTÓRE W YKONUJĄ ZADANIA.


K iedy klasa m usi coś zrobić, używa m etody. T a p o b ie ra dane w ejściowe, w ykonuje pew n ą czynność
Zwróć uwagę
na poprawność i czasam i zw raca wyniki operacji. D a n e wejściowe są przekazyw ane do m etody przy użyciu p a ra m e tró w .
dobrania w pary M etody m ogą zachowywać się różnie w zależności o d przekazanych do nich w artości. N iek tó re z nich
nawiasów.
zw racają w artość. K iedy ta k się dzieje, to re z u ltat nazyw am y w a rto ś c ią z w ra c a n ą . Jeżeli widzisz na
Każdy i je s t
ostatecznie p o czątk u słowo kluczow e v o id , znaczy to , że m e to d a nic nie zwraca.
powiązany
z właściwym }. Ta metoda ma dwa parametry
void s t a r t B u tto n _ C lic k (o b je c t sender, o b je c t e) o nazwach sender oraz e.
Niektóre
Ten w iersz wywołuje m etodę
pary mogą {
być zaw arte StartG am e(), która także została
StartGame(); utworzona przez IDE.
w innych.
}

INSTRUKCJA WYKONUJE POJEDYNCZĄ CZYNNOŚĆ.


Jeśli w pisałeś do k o d u p ro g ra m u w iersz S ta r tG a m e ( ) , to ta k n ap raw d ę dodałeś in s tru k c ję . K ażd a m eto d a
składa się z instrukcji. K iedy Twój p ro g ram wywołuje m e to d ę , to w ykonyw ana je st pierw sza um ieszczona
w niej instrukcja, p o te m n astęp n a, n a stę p n a i ta k dalej. G dy wszystkie instrukcje zo stan ą w ykonane
bądź gdy zostanie n a p o tk a n a instrukcja r e t u r n , w tedy m eto d a się kończy, a realizacja p ro g ra m u jest
k ontynuow ana o d instrukcji um ieszczonej b ezp o śred n io poniżej w ywołania m etody.

p r iv a t e void StartGame() ^ To j e s t metoda StartG ameO , Móra j e s t wywotywana


w odpowiedzi na k lik n ię c i przycisku S ta r t.
{
hum an.IsH itTestV isible = true;
humanCaptured = f a l s e ; Metoda StartG am e() zawiera
dziewięć instrukcji. Każda z nich
progressBar.Value = 0; kończy s ię średnikiem.
sta r tB u tto n .V isib ility =
V isib ility .C o lla p sed ;
p layA r e a .C h ild r e n .C le ar (); Nic nie sto i na przeszk.odzie , by dodawać
p layA rea.C hildren.A dd(target); znaki nowego w iersza w celu popn w ien ia
czytelności kodu. Podczas kompilacji k°du
playArea.Children.Add(human); zosta n ą one zignorowane.
enemyTimer.Start();
t a r g e tT im e r .S t a r t () ;
}

To j e s t naw ias zam ykający um ieszczony


na samym końcu pliku M ainPage .xaml c s .
jesteś tutaj ► 103
Uzyskaj kilka odpowiedzi

P : Jak to jest z tymi nawiasami klamrowymi? P : Skąd się biorą błędy wyświetlane w oknie Error List
podczas prób uruchamiania programu? Myślałem, że to się
O : C# używa nawiasów klamrowych do grupowania zdarza tylko w przypadku wyboru „Build Solution” .
instrukcji w b lo ki. Występują one zawsze w parach,
a więc nawias zamykający można zobaczyć tylko wtedy, O : Dzieje się tak, ponieważ pierwszą czynnością wykonywaną
gdy istnieje otwierający. IDE pomaga układać pary — kliknij po wyborze opcji StartDebugging z menu lub naciśnięciu
jeden z nawiasów, a zarówno on, jak i odpowiadający mu przycisku uruchamiającego program na pasku narzędzi jest
drugi nawias z pary zostaną wyróżnione ciemniejszym tłem zapisanie wszystkich plików w rozwiązaniu i próba ich kompilacji.
Kiedy kompilujesz napisany przez Ciebie kod — czy to podczas
uruchamiania, czy w trakcie budowania — IDE, zamiast uruchamiać
program, wyświetla ewentualne błędy w oknie ErrorList.

Wiele błędów wyświetlanych podczas


W e ™ * w oknie Error L ist ^ P ^ ^ ^
t a kŻe w edytorze w formie c z ^ r n y ^ podkreślen
problematycznych fragmentów todu-

A ZATEM IDE NAPRAWDĘ


MOŻE MI POMÓC. GENERUJE KOD,
A OPRÓCZ TEGO UŁATW IA MI
ODNAJDYWANIE BŁĘDÓW.

IDE pomaga nam pisać prawidłowy kod.

D aw no te m u p rogram iści m usieli używać prostych edytorów tekstów ,


takich ja k N o tatn ik . (W rzeczywistości naw et zazdrościli niektórych
m ożliwości N o ta tn ik a , takich ja k m echanizm w yszukiw ania i zastępow ania).
M usieli tak że używać w ielu złożonych aplikacji obsługiw anych z p o ziom u
w iersza p o lece ń w celu kom pilacji, budow ania, u ru ch am ian ia, debugow ania
i w d rażan ia kodu.

W raz z upływ em lat firm a M icrosoft (choć uczciwość w ym aga przyznać, że


tak że w iele innych firm o raz program istów ) o pracow ała w iele użytecznych
rozw iązań, takich ja k w yróżnianie błędów , IntelliSense, edycja stro n w trybie
W Y S IW Y G pozw alająca n a przeciąg an ie i upuszczanie ich elem entów ,
autom atyczne g enerow anie k o d u i w iele innych.

Po w ielu latach ewolucji V isual Studio je st aktu aln ie jednym z najbardziej


zaaw ansow anych n arzęd zi do edycji kodu, jakie kiedykolw iek u d ało się
stworzyć. I n a całe szczęście dla C iebie, jest tak że doskonałym narzędziem
do n auki i p oznaw ania C # o raz sposobów tw o rzen ia aplikacji.

104 Rozdział 2.
To tylko kod

JAKIE JEST M OJE ZADANIE?


Dopasuj każdy fragment kodu wygenerowanego przez IDE do właściwego opisu.
(Niektóre z nich są nowe — spróbuj zgadnąć i sprawdź, czy Ci się udało!).

myGrid.Background =
new S o lid C o lo rB ru sh (C o lo rs.V io le t); Ustawia właściwości dla kontrolki T e x tB lo c k .

Nic — jest to komentarz dodany przez


Ta p ę tla wykonywana je s t trz y razy programistę w celu wyjaśnienia kodu komuś,
\¡L kto będzie go czytał.

Zmiana stanu ikony maksymalizacji (130)


w pasku tytułowym Form1 na nieaktywny

h ello La b el.T ext = "Cześć"; k Specjalny rodzaj komentarza, którego używa IDE
h ello Lab el.Fon tS ize = 24; w celu wyjaśnienia działania bloku kodu.

/ / / <summary>
/ / / Wyświetl obrazek Rovera po Zmienia kolor tła kontrolki G rid o nazwie
I I I n aciśn ięciu przycisku
/ / / </summary> m yG rid .

M etoda wywoływana za każdym razem,


gdy program wyświetla stronę główną.

jesteś tutaj ► 105


Rozwiązanie ćwiczenia

* '*■
JAKIE JEST MO
MOJE ZADANIE?- .

Rozwiązanie

Dopasuj każdy fragment kodu wygenerowanego przez IDE do właściwego opisu.


(Niektóre z nich są nowe — spróbuj zgadnąć i sprawdź, czy Ci się udało!).

myGrid.Background =
new S o lid C o lo rB ru sh (C o lo rs.V io le t); Ustawia właściwości dla kontrolki T ex tB lo c k .

Nic — jest to komentarz dodany przez


| / / Ta p ę tla wykonywana je s t trz y razy~ programistę w celu wyjaśnienia kodu komuś,
kto będzie go czytał.

public sealed p a rtia l c la ss MainPage : Page

p riv a te void InitializeCom ponent()


Zmiana stanu ikony maksymalizacji ( Cl)
{

\ w pasku tytułowym Form1 na nieaktywny

r
Chwileczkę, okno? A nie strona?
Zaczn ie sz od poznania klasycznych
aplikacji sy ste m u Windows, które
mają okna i formularze.

h e llo La b e l.T e xt = "Cześć"; Specjalny rodzaj komentarza, którego używa IDE


hello Lab el.Fon tS ize = 24; w celu wyjaśnienia działania bloku kodu.

/ / / <summary>
/ / / Wyświetl obrazek Rovera po Zmienia kolor tła kontrolki G rid o nazwie
I I I n a ciśn ię ciu przycisku
/ / / </summary> myGrid.

M etoda wywoływana za każdym razem,


gdy program wyświetla stronę główną.

106 Rozdział 2.
To tylko kod

W tej samej przestrzeni nazw


mogą być dwie klasy SomeClasses.cs

R zuć okiem n a te dwa pliki p ro g ra m u PetFiler2.


Z aw arto w nich trzy klasy: Dog, C at i F is h . Z n ajd u ją
się o ne w tej sam ej p rzestrzen i nazw P e t F i l e r 2 ,
dlatego m eto d a D o g .B a rk () m oże wywoływać C a t.
Meow() o raz F is h .S w im (). N ie m a zn aczenia sposób,
w jaki poszczególne klasy i p rzestrzen ie nazw są
p o d zielone w plikach — podczas d ziałania p ro g ram u
b ęd ą trak to w an e tak sam o.

« " r t iS S
u ż y w a ć j e j m e to d .

MoreClasses.cs

U m ieszczenie klas w tej sam ej przestrzeni


nazw powoduje, że w szy stk ie one „widzą się"
wzajemnie — naw et jeśli s ą w różnych plikach.
Klasę można także um ieścić w kilku plikach,
jednak w takim przypadku w je j deklaracji
należy zaw rzeć słowo kluczowe „partial".

f
Klasę m ożesz podzielić m iędzy kilka plików
wyłącznie wtedy, gdy u ży je sz słowa kluczowego
„partial". Najprawdopodobniej nie sko rzy sta sz z tej
możliwości w programach, które n a p iszesz podczas
lektury tej książki, jednak IDE robi to, by podzielić
kod strony i um ieścić go w dwóch plikach:
MainPage.xaml oraz MainPage.xaml.cs.

Na temat przestrzeni nazw oraz deklaracji klas można napisać znacznie


więcej, jednak na razie informacje te nie będą Ci potrzebne. Zagadnienia
te znajdziesz w dodatku „Pozostałości” , w punkcie trzecim.
jesteś tutaj ► 107
Dystans może się zmieniać

Twoje programy używają zmiennych


do pracy z danymi
O gólnie rzecz ujm ując, każdy program służy do wykonywania operacji na
danych. T e przychodzą raz w form ie d okum entu, a raz jako o braz w grze
w ideo lub w postaci kom unikatów . Jak by n a to je d n a k nie patrzeć —
wszystko to są inform acje. W tym m om encie do akcji w kraczają zm ienne.
Z m ienna to coś, czego p rogram używa do przechow yw ania danych.

Zadeklaruj zmienne Czy znasz inne języki


programowania?
Z a każdym razem , gdy d e k la ru je s z zm ienną, określasz jej typ i nazwę.
Jeśli C # zna typ, m ożliw e staje się zatrzym anie kom pilacji, gdy przez Jeśli tak, to kilka zagadnień
p rzypadek napiszesz w yrażenie, k tó re je st bez sensu. N a przykład w tedy, opisywanych w tym rozdziale
może Ci się wydać znajomych.
gdy p róbujesz odjąć „F id o ” o d 48353.
Pomimo to warto jednak
poświęcić czas na wykonanie
To s ą typ y zmiennych. To s ą nazwy tych zmiennych. zamieszczonych tu ćwiczeń,
gdyż może się okazać, że pod
jakimiś względami C# różni
int maxWeight; się od tego, do czego byłeś
przyzwyczajony.
string message;
bool boxChecked;
. / ^ l \ n^ V p r ^ p a d ^ t o d
C * używ a typów zmiennuch i \ l a s używaj nazw, które mają
Z a l Z c ? ^ OWm* se n s i Opisują p rz e z n a c z en i

” ° że Pr ź e i t w y Zw a ! m a

Zmienne się ... zmieniają Za każdym razem,


Z m ien n e przyjm ują różn e w artości w czasie d ziałania pro g ram u . gdy Twój program
Inaczej m ów iąc, w artości zm iennych zm ien ia ją się. (T o dlatego
„zm ienna” je st d o b rą nazw ą). Je st to nap raw d ę w ażne, bo leży u podstaw będzie musiał
każdego p ro g ram u , który napisałeś, lub tego, który d o p iero napiszesz. pracować z liczbami,
Przypuśćm y, że ustaw iłeś w artość zm iennej m yH eight n a 63:

i n t m yH eight = 63;
tekstem, wartościami
Z a każdym razem , gdy m yH eight pojaw i się w kodzie, C # zam ieni
prawda/fałsz lub
to w yrażenie n a w artość 63. Jeśli zm ienisz ją p o te m n a 12: innego rodzaju
m yH eight = 12; danymi, będziesz
C # zastąpi m yH eight w arto ścią 12 — nazw a zm iennej m yH eight
pozo stan ie bez zmian.
używał zmiennych
do przechowywania
tych wartości.
108 Rozdział 2.
To tylko kod

Musisz przypisać do zmiennych wartości,


zanim ich użyjesz
W staw n astęp u jące instrukcje w p ro g ram ie C # :

s t r i n g z; Jeśli w swoim
s t r i n g message = "Odpowiedź brzmi" + z;
kodzie spróbujesz
A te ra z spróbuj go u ruchom ić. Z o stan ie wyświetlony b łąd i ID E
odm ów i kom pilacji T w ojego kodu. D zieje się ta k dlatego, że ID E użyć zmiennej,
spraw dza stan każdej zm iennej, aby upew nić się, że została jej
przypisana jakaś w artość, zanim spróbujesz jej użyć. N ajłatw iejszy
Te wartości są której wartość nie
przypisyw ane
sposób na uniknięcie teg o typu p ro b lem ó w to p o łączen ie deklaracji do zmiennych. została wcześniej
zm iennych z instrukcjam i, k tó re u staw iają ich w artość początkow ą.
określona, to IDE
go nie skompiluje.
int maxWeight = Można w łatw y
stringAmessage sposób uniknąć
bool boxChecked tego typu
błędów poprzez
Każda deklaracja ma typ, połączenie
dokładnie tak samo jak
w cześniej. deklaracji zmiennej
i przypisania
Kilka użytecznych typów jej wartości
K ażda zm ienna p o sia d a typ, k tóry o k reśla rodzaj danych, jakie m oże w jednej instrukcji.
o n a przechow ywać. W iele szczegółowych inform acji dotyczących
typów danych dostępnych w języku C # p o d am y w ro zdziale 4. T eraz
skoncentrujem y się n a trzech najbardziej p opularnych. Liczby całkow ite
przechow uje i n t , s t r i n g zaw iera tekst, bool n a to m ia st przechow uje Juz P o p is a ł e ś
w artości logiczne t r u e / f a l s e (praw da/fałsz). w artość do sw ojej zm iennej,
m c m e sto i na przeszkodzie,
f y M zm ienić. Nie ma zatem
żadnych efektów ubocznych
zmienna, rzeczownik. w daw ania wartości zmiennym
podczas deklaracji.

e łe m e n t; łu b c e c h a , k t ó r a m o ż e u le g a ć z m i a n o m .

Pr°gnozowatiie p ogody byłoby znacznie prostSze,


gdyby meteorolodzy nie musieli uwzględniać tak
wielu zmiennych.

jesteś tutaj ► 109


O peratory w gotowości

Dla programistów słowo


C# używa znanych symboli matematycznych „s trin g ” zawsze oznacza
łańcuch znaków, a „ in t ”
Posiadasz już nieco danych przechow yw anych w zm iennych, co w takim razie
to skrót od angielskiego
m ożesz z nim i zrobić? Pomyślm y. Jeżeli jest to liczba, to p ra w d o p o d o b n ie
słowa „intege r”
będziesz chciał ją dodać, odjąć, pom nożyć lub podzielić. N ad szed ł właściwy
oznaczającego liczbę
m om ent na przedstaw ien ie o p e ra to ró w . Z nasz ju ż kilka podstaw ow ych,
całkowitą.
pom ów m y zatem o następnych. Poniżej zn ajduje się frag m en t kodu,
który używ a o p erato ró w do w ykonyw ania pew nych prostych obliczeń: Trzecia instrukcja zm ienia wartość
number, przypisując je j wynik
Zadeklarowaliśmy
nową zm ienną typu
int number = mnożenia 36 i 15, co daje 540.
Potem wartość ta znów j e s t
int, nazwaliśm y ją zmieniana, tym razem na 12-(42/7),
number i ustaw iliśm y number = number + czego wynikiem j e s t 6.
na 15. N astępnie
dodaliśmy do niej 10 . number = 36 *
Po wykonaniu drugiej
instrukcji je j wartość
wynosi 25.
number = 12 - (42 / Ten operator j e s t nieco inny.
+= oznacza: w eź wartość number
Operator *= j e s t number += 10; i dodaj do niej 10. W naszym
przypadku zmienna j e s t równa 6,
podobny do += z tą więc dodanie do niej 10 skutkuje
różnicą, że za m ia st ^ number *= 3; ustaw ieniem wartości na I6 .
dodawać, mnoży / '
number number = 71 / 3;
Po zakończeniu
instrukcji zmienna .2 ? T \ 3 wT Si 2 3 6 6 6 666. Zauw ażm y,
przyjm uje w artość 48. tu lfo tatakie
M fi, Uczby,
itr b ddlatego
ą 9 a wynik
w'tą 1j emsote przechowywać
tylko t obcinany do 23.
int count =
B ędziem y bardzo często używać typu in t do z iiczania i wówcza.s
Ta instrukcja count++; ope ratory ++ i - - okażą s ię bardzo pomocne. ^++ zw ie s za . c ° u n t,
za p isu je
w kontrolce dodając do wartości jeden, natom ia st -- zm nie jsz a count,
TextBlock
count--; odejmując jeden. Wynikiem tych in stru kcji będ zie zatem 0 .
komunikat „cześć
Kiedy u ży je sz
i znowu cześć". operatora + w odniesiem u
do tekstów , wynikiem
string result = "cześć"; takiego działania
będzie ich połączenie.
result += " i znowu " + result; £- Dodatkowo operacja
zam ieni w szystkie liczby
oznacza pu s ty na odpowiedni łańcuch
łańcuch znaków. O U tp U t Text = result; znaków.
Nie ma tu żadnych
znaków. (Można by go
porównać z liczbą z ero,
r e s u l t = "wartość wynosi: " + count;
tylko odnoszącą s ię do
dodawania tekstó w ). ^ ^ > r e S U l t

r . Nie zaprzątaj sobie


Z m ienna logiczna . głowy uczeniem się
przechowuje bool yesNo = false;
w artość true lub iO tvch
tych operatorów
operatorów
false . Operator
! oznacza NIE.
bool anotherBool = true; na pamięć.
Z a m ienia on true
na false i vice yesNo = !anotherBool; Zapamiętasz je bez problemu, ponieważ
versa. będziesz ich używał wiele razy

110 Rozdział 2.
To tylko kod

Użyj debuggera, by zobaczyć,


jak zmieniają się wartości zmiennych _ Ł Ł Ł<
- P r z e t e s t u j to !
D eb u g g er je st doskonałym narzędziem pozw alającym zrozum ieć działanie pro g ram u .

r
U żyjem y go, by p rzek o n ać się, ja k działa k o d przedstaw iony n a p o p rzed n iej stronie.

UTWÓRZ NO W Y PROJEKT APLIKACJI C# WINDOWS STORE — BANK APP(XAML).


Przeciągnij n a stro n ę k o n tro lk ę T e x tB lo c k i nadaj jej nazw ę o u tp u t. N a stęp n ie dodaj do strony
przycisk i dw ukrotnie go kliknij, aby utw orzć m eto d ę o nazw ie B u tto n _ C lic k ( ) . ID E autom atycznie
wyświetli tę m e to d ę w o knie do edycji kodu. N a stęp n ie p rzepisz do m eto d y cały k o d przedstaw iony
n a p o p rzed n iej stronie.

WSTAW PUNKT PRZERWANIA W PIERWSZYM WIERSZU KODU


Kliknij praw ym przyciskiem myszy pierw szy w iersz k o d u ( i n t num ber = 1 5 ;)
i z w yśw ietlonego m en u p o d ręczn eg o w ybierz opcję B reakpoint /Insert Breakpoint (m ożesz
także nacisnąć klawisz F 9 lub w ybrać z m en u głów nego opcję D EBU G /Toggle Breakpoint).
Komentarze
(zaczynające s ię od
w ) StosowanieDebuggera - Microsoft Visual Studio Express 2012 for Windows 8
co najmniej dwóch
RLE EDIT VIEW PROJECT BUILD DEBUG TEAM TOOLS STORE TEST WINDOW HELP
znaków ukośnika
MsinPageocamLcs -p I MainPage.xaml lub otoczone
*¡5 StosowanieDebuggera.MainPage Button_CKck(object sender, RoutedEventArgs e)
znacznikami /* i */)
/ * D w ukrotne k l i k n i ę c i e p r z y c ik u w o k n ie D e s ig n e r spowodowało u tw o rz e n ie
s ą w yśw ietlane
* p u s te j metody B u tto n _ C lic k ( ) . p rzez IDE jako te k s t
w kolorze zielonym.
N ie m u sisz s ię
p r iv a t e v o id B u t t o n C l i c k ( o b je c t s e n d e r, R outedE ventA rgs e ) przejmować tym,
{ co za p isu je sz
// u n ie ś ć p u n k t p rz e rw a n ia w pierw szym w ie rs z u kodu
number = number + 10;
pomiędzy
number = 36 * 15; znacznikami
number = 12 - (42 / 7 ) J e ś h us tą w isz p u n k t p rzerwania w danym komentarzy,
number += 10; w ierszu k°du , z ostanie on wyróżniony gdyż kompilator
number *=3;
number = 7 1 /3 ; czerw° nym nem , a oprócz tego, na lewym za w sze je ignoruje.
m arginesie e dytora pojawi s ię czerw oni kropka.
in t count = 0;
c o u n t++;
c o u n t--;
Utworzenie nowego
p odcza s debugowania kodu projektu Blank App
s t r i n g r e s u l t = “w ita m ” :
r e s u l t += " ponownie “ -
u ru chomionego wewnątrz IDE, gdy sprawi, że IDE utworzy
o u tp u t.T e xt - r e s u lt; Tylk° wy k° ny wanie programu dotrze nowy projekt z je d n ą
r e s u l t = " w a rto ś c ią j e s t : ™t| do w iersza z p unktem przerwania,
pustą stroną. Pewnie
zo sta n ie ono w strzym ane, a Ty
b o o l yesNo = f a ls e ; będzies z m iał możliwość sprawdzenia zechcesz nadać
b o o l a n o th e rB o o l = t r u e ; i zm ° dy f ikowania wartości jej nazwę ta k ą ja k
yesMo = ! a n o th e rB o o l;
wszy s tk ich zmiennych. Stosow anieDebuggera
(naw iązującą do treści
tej strony). Podczas
lektury tej książki
napiszesz sporo
program ów i być może
później zechcesz
do nich w racać.

► Przejdi na następną stronę i kontynuuj ćwiczenie!

jesteś tutaj ► 111


Przestań mnie zapluskw iać!

ZACZNIJ DEBUGOWAC PROGRAM.


U ru ch o m p ro g ram w debuggerze — kliknij przycisk Start Debugging (m ożesz — IDEalnaporada: “Bh-D —i
także nacisnąć klawisz F 5 lub w ybrać z m en u opcję D E B U G /Start Debugging).
Podczas debugowania aplikacji dla
Twój p rogram zostanie u ru ch o m io n y w no rm aln y sposób, a n a ek ran ie pojaw i
Sklepu Windows możesz przejść do
się form ularz.
debuggera, naciskając kombinację
klawiszy klawisz Windows+D.
g KLIKNIJ PRZYCISK, ABY UAKTYWNIĆ PUNKT PRZERWANIA.
W przypadku korzystania z urządzenia
G dy tylko realizacja p ro g ram u d o trze do w iersza, w którym um ieściłeś p u n k t z ekranem dotykowym przeciągnij
przerw ania, ID E autom atycznie wyświetli e d y to r i w yróżni żółtym kolorem od lewej do prawej krawędzi ekranu.
aktualny w iersz kodu. Teraz możesz wstrzymać lub przerwać
testowanie, naciskając odpowiedni
przycisk na pasku narzędzi Debug
lub wybierając opcję z menu.

g ZACZNIJ OBSERWOWAĆ ZMIENNĄ NUMBER.


Kliknij zm ienn ą num ber praw ym przyciskiem myszy (nie m a znaczenia, które
w ystąpienie tej zm iennej klikniesz) i z w yśw ietlonego m en u w ybierz opcję Dodawanie
- : . W p a n e lu u d o łu o k n a ID E pow inno się pojaw ić o k n o Watch:
zmiennych do
I
W atch 1
IMaime Value Type
okna Watch
■ 0 nu m b er 0 mt
może pomóc
Locals W atch 1 |
Ci śledzić ich
1
wartości w trakcie
W YKONUJ PROGRAM KROK PO KROKU wykonywania
N aciśnij klawisz F10, by k ro k p o k ro k u w ykonywać kolejne instrukcje jramu.
p ro g ram u (m ożesz tak że w ybrać z m en u opcję D E B U G /Step Over lub kliknąć
przycisk Step Over um ieszczony n a p ask u n arzędzi). S pow oduje to w ykonanie
y tworzone
bieżącej instrukcji, k tó ra przypisze zm iennej num ber w arto ść 15. ID E wyróżni programy staną
kolejną instrukcję p ro g ram u , wyświetlając ją n a żółtym tle, a zaw artość okna się bardziej
W atch zostanie zaktualizow ana.
rozbudowane,
Gdy tylko zm ieni się
wartość zmiennej I
W atch 1
Name Value Type
- □ X 1

1
możliwość ta
number (na 15),
zostanie ona
n um ber 1 15 H H B H H E E H I
będzie naprawdę
odpowiednio
zaktualizowana
bardzo przydatna.
Locals W atch 1
w oknie Watch.
Podczas debugowania m ożesz
także w skazać zm ienną my sz k ą
[7 W ZN Ó W REALIZACJĘ PROGRAMU a je j wartość zo sta n ie w yśw ietlona
K iedy będziesz chciał kontynuow ać działanie p ro g ram u , naciśnij klawisz F 5 w formie podpowiedzi... B ęd ziesz
mógł j ą przypiąć, by ciągle byta
(lub w ybierz opcję D E B U G /C ontinue). widoczna.
112 Rozdział 2. ^
To tylko kod

Pętle wykonują czynność wielokrotnie


i—IDEalnaporada: nawiasyklamrowe
W iększość dużych p ro g ram ó w m a pew ną dziw ną właściwość: niem al
zawsze w ielokrotnie w ykonują o n e pew ne czynności. W łaśnie do tego Jeśli nawiasy umieszczone w kodzie
służą p ę tle — um ożliw iają w ykonyw anie ok reślo n eg o zb io ru instrukcji nie będą odpowiednio dobrane w pary,
to programu nie uda się skompilować;
dopóty, dopóki pew ien w aru n ek je st praw dziw y (lub fałszywy).
to z kolei będzie prowadzić do frustrujących

T
To główny powód tego, że wartości
błędów. Na szczęście IDE może Ci pomóc ich
uniknąć! Wystarczy, że umieścisz kursor na
jednym nawiasie klamrowym, a IDE wyróżni
while (x > 5) typu logicznego s ą takie ważne.
Pętle p rzeprowadzają te s t, aby odpowiadający mu drugi nawias z pary:
s p rawdzić, czy powinny być dalej
{ wykonywane.
x = x - 3;

W szystkie instrukcje
pom iędzy nawiasami
klamrowymi pętli Każda pętla for ma trzy instrukcje. Pie rwsza je j wartości
while s ą wykonywane początkowe. Pętla będzie wykonywana tak (ttogo,^ ja k długo druga
tak długo, jak długo in strukcja będzie prawdziwa. Trzecia z instrukcji wykonywana j e s t
warunek w nawiasach po każdym kolejnym przebiegtu pęW .
okrągłych j e s t
prawdziwy.
for (int

{
// Wszystkie instrukcje pomiędzy nawiasami klamrowymi
// zostaną wykonane 4 razy.

}
Użyj schematu kodu do pisania prostych pętli
D osłow nie za m inutę będziesz pisał w łasne p ętle f o r , a ID E może Naciśnij Tab, a kursor
przeskoczy do pola
C i pom óc nieznacznie przyspieszyć i ułatwić kodow anie. W pisz tength. Od tej wartości
f o r i dw ukrotnie naciśnij Tab, a wstawi ono kod autom atycznie. zależy liczba przebiegów
G dy wpiszesz nową zm ienną, ID E sam o zam ieni resztę szablonu. pętli. M ożesz zm ienić
j ą na dowolną liczbę
Naciśnij Tab jeszcze raz, a k u rso r przem ieści się do p o la kod lub zmienną.
program u.

Jeżeli zm odyfikujesz
nazwę zm iennej, szablon
autom atycznie zm ieni
je j pozostałe dwa
w ystąpienia.

jesteś tutaj ► 113


W służbie Tobie

Instrukcje if/else podejmują decyzje


In stru k c je if/else służą do w ykonyw ania pew nych operacji, gdy u stalo n y wcześniej
w a ru n e k jest spełniony (bądź nie). D u ż a ich część spraw dza rów ność dwóch
rzeczy. W takich przypadkach będziesz stosow ał o p e ra to r = = . R óżni się on
o d o p e ra to ra przypisania ( = ) , k tóry służy do ustaw iania w artości zm iennych.

Każda instrukcja
if rozpoczyna się
String message = 1,1 od warunku.

if (someValue == 24)
Instrukcje znajdujące s ię
{ pom iędzy naw iasami klamrowymi
wykonywane s ą ty lko
message = "Wartość jest równa 24."; w przypadku spełnienia warunku-

Z a w sze używ aj dwóch rnak.óiw równości


do porównywania dwóch rzeczy.
if (someValue == 24)

Instrukcje {
typ u if/e ls e są
dość proste. // pomiędzy nawiasami możesz wstawić
Je śli warunek
j e s t spełniony, // dowolną liczbę instrukcji
wykonywane są
operacje zapisane
u; pierw szej
message = "Wartość jest równa 24.";
parze nawiasów
klamrowych. } else {
W przeciwnym
razie wykonywane message = 'Wartość nie jest równa 24.";
są, instrukcje
z drugiej pary
}

U — __________________________________________________________________________________________________
u I Nie myl operatorów ze znakiem równości!

lluiaqal Znaku równości (=) będziesz używał do ustawiania wartości zmiennych, natomiast dwóch znaków równości (==)
do ich porównywania. Nie uwierzysz, jak wiele błędów w programach — nawet takich, które zostały napisane przez
doświadczonych programistów — zostało spowodowanych użyciem = zamiast ==. Jeśli IDE skarży się komunikatem w stylu „cannot
implicitly convert type 'int' to 'bool"', to prawdopodobnie jest to problem tego typu.

114 Rozdział 2.
To tylko kod
Wybierz se n sowną nazwę projektu, gdyż w dalszej części
Kiedy zobaczysz takie adidasy,
książki będziesz je s zc ze do niego wracał.
to będzie znak, że nadsze d ł czas,
że byś sam em u napisał trochę kodu

Utwórz aplikację od samego początku


Ćwiczenie
Praw dziw e działanie p ro g ra m u realizują instrukcje. D ow iedziałeś się już, jak są o n e um ieszczane
n a stronie. A te ra z przyjrzyjm y się p rogram ow i dokładniej, ta k byś zrozum iał działanie każdego
w iersza kodu. Zacznij o d utw o rzen ia nowego p ro je k tu ap lik a c ji W indow s S tore B la n k App w języku
C # . Tym razem , zam iast usuw ać stro n ę M ainPage.xaml u tw o rzo n ą p rzez szablon aplikacji B la n k O t w ó r z tę s t r o n ę
A p p , skorzystaj z narzęd zi ID E , aby ją zm odyfikow ać — dodaj do siatki trzy w iersze i dwie kolum ny,
n astęp n ie um ieść w k o m ó rk ach cztery kontro lk i B u tto n i je d n ą k o n tro lk ę T e x tB lo ck .

Strona zawiera siatkę składającą się z trzech wierszy Na stronie zostały umieszczone cztery kontrolki
i dwóch kolumn. Wysokości wszystkich wierszy są B u tto n , po jednej w każdym wierszu. Użyj
określone jako 1*, czyli ich definicje mogą mieć postać właściwości C o n te n t , by wyświetlić w nich teksty:
< R o w D e fin itio n /> — bez żadnych właściwości. Pokaż komunikat, If/Else, Sprawdzenie kolejnego
Dokładnie tak samo wyglądają definicje kolumn. warunku oraz Pętla.

Ir - - y - .. ...

Każdy p rzycis k j es t wyśrodkowany w komórce.


~Numery wierszy i kolumn określaj przy użyciu
wtaściwości Grid.Row oraz Grid.Column
(ich domyślnymi wartościami j e s t 0).

■ S p ra w d z e ń » lu A jn e g o w aru n k u

Nic t u n ie widać, lecz w rzeczyw istości w tym


mjejscu j es t um ieszczona kontrolka TextBlock.
Nie wy ś w ietfa ona żadnego te k stu , więc nie j e s t
widoczna . Z ° s t ata oma wyśrodkowana w dolnym
wiers z u , w komórce, która dzięki przypisaniu
wtaści'wości Colum nSpan wartości 2 rozciąga s ię
ma dwie kolumny.

W dolnej komórce znajduje się kontrolka T e x tB lo c k Użyj właściwości x:N am e , by nadać przyciskom nazwy
o nazwie m y L ab el . W jej właściwości S t y l e została b u t t o n l , b u tt o n 2 , b u tto n 3 oraz b u tt o n 4 .
zapisana wartość B o d y T e x tS ty le . Po określeniu nazw dwukrotnie kliknij każdy z nich,
by wygenerować procedury obsługi zdarzeń.
Jeśli chcesz użyć opcji Edit S ty le dostępnej w menu^
podręcznym, lecz m asz problemy z wy braniem k° n tr°lki, to
kliknij j ą prawym przyciskiem m yszy w oknie Docum ent O M m e
i wybierz opcję Edit S ty le z wy świetlonego m enu. jesteś tutaj ► 115
Uwaga, gotowi, kodujemy!

Wielu programistów nie używa IDE do pisania


Oto nasze rozwiązanie ćwiczenia. Czy Twoje wy g ląda
podobnie? A może kod j e s t nieco m a cw j p odzielony kodu XAML — robią to samodzielnie. Gdybyśmy
. . na w iersze lub właściwości w s ta ły zap isan e w innej Cię poprosili, żebyś też samodzielnie wpisał kod
Ćwiczenie kolejności? Jeśli tak, to w szystko j e s t w p orządk u XAML bez pomocy IDE, byłbyś w stanie to zrobić?
Rozwiązanie
M ainPage.xaml -o X
E K P age
x:Class="BuildAnApp.MainPage"
xmlns="http://schemas.nicrosoft.com/winfx/2006/xaml/presentation" To s ą znaczniki
xmlns :x="http://schemas.microsoft.com/winf x/2006/xanil" <Page> oraz <Grid>
xmlns:loca1="using:BuildAnApp" wygenerowane przez
xmlns:d="http://schemas.microsoft.con/expression/blend/2008" IDE podczas tworzenia
xmlns:nic="http://schemas.openxmlfomats.org/markup-compatibility/2006" p u ste j aplikacji.
mc:Ignorable="d”>

E <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">


B <Grid.RowDefinitions>
<RowDefinition/>
<RowDefin ition/>
<RowDefin ition/> To s ą definicje w ierszy
</Grid.RowOefinitions> i kolumn: siatka
<Grid.ColumnDefinitions> zawiera trzy w iersze
<ColumnDefinition/> i dw ie kolumny.
<ColumnDefinition/> Kiedy dwukrotnie klikniesz
</Grid.ColumnDefinitions >y każdy z p rzycisków , IDE
wygeneruje m etodę, której
<Button x:Name="buttonl" Content=”Pokaż komunikat" ^ nazwą będzie nazwa przycisku
HorizontalAlignment="Center" Click="buttonl_Click"/> z dodanym _Click.
<Button x:Name="button2" Content="If/Else" HorizontalAlignment="Center"
G r i d .Column="l" Click="button2_Click"/>

<Button x:Name="button3" Content="Sprawdzenie kolejnego warunku" HorizontalAlignment="Center"


G r i d .Row="l" Click=”button3_Click"/>

Ten przycisk j e s t
<Button x :Name="button4" Content="Pętla" HorizontalAlignment="Center"
um ieszczony w drugiej
. Grid.Column="l" Grid.Row="l" Click="button4 Click"/>
kolumnie i w drugim
wiersz u , dlatego te
<TextBlock x:Name="myLabel" HorizontalAlignment="Center" VerticalAlignment="Center"
właściw ości mają
Grid.Row="2" Grid.ColumnSpan="2" Style="{StaticResource BodyTextStyle}"/>
wartości 1.
</Grid>
</Page>
I

100% - 4

WYSIL ______
SZARE KOMÓRKI
Jak sądzisz, dlaczego górny wiersz i lewa kolumna mają numer 0, a nie 1?
Dlaczego można pominąć ustawianie właściwości G rid.R ow oraz
G rid .C o lu m n kontrolek umieszczanych w lewej górnej komórce?

116 Rozdział 2.
To tylko kod

Niech przycisk coś zrobi Kilka przydatnych r a d - - - -


T eraz dow iesz się, w jaki sposób będzie działał Twój p rogram . + Nie zapominaj, że każda instrukcja musi się kończyć
Po każdym kliknięciu przycisku b ędzie aktualizow ał zaw artość średnikiem:
kontro lki T e x tB lo c k wyświetlanej u dołu (której n ad ałe ś nazwę name = "Józek";
m yL abel), wyświetlając w niej inny k om unikat. O siągniesz to,
dodając odpow iedni k o d do każdej z czterech p ro c e d u r obsługi + Komentarze możesz umieszczać w kodzie,
zdarzeń w ygenerow anych p rzez ID E . Z a te m zaczynajmy! poprzedzając ich treść dwoma znakami ukośnika:
/ / ta li n i j k a je s t ignorowana
Kiedy zobaczysz „Zrób to",
uruchom IDE i wykonuj + Zmienne deklarowane są poprzez podanie ich
-4 + opisane czynności.
2Z r ó b to
to ro t? To
na zw y i typ u (istnieje bardzo dużo typów danych,
o których dowiesz się w rozdziale 4.):
r
★ n a co z w ró c ić u w a g ę , byS
j a k n a jw ię c e j s k o r z y s ta t
z p re z e n to w a n e g o p rz y k ła d u
in t weight;
/ / weight je s t zmienną typu całkowitego

+ Kod klas i metod trzeba zapisywać wewnątrz


nawiasów klamrowych:
NIECH PRZYCISK BUTTON1
AKTUALIZUJE ETYKIETĘ. public void go() {
/ / tu ta j je s t niesamowity kod
Przejdź do kod u m eto d y b u tt o n 1 _ C li c k ( ) i w pisz w niej
k od przedstaw iony poniżej. M asz te ra z okazję dobrze
+ W większości przypadków nadmiarowe białe znaki
zrozum ieć, co ro b i każd a z instrukcji i dlaczego pro g ram
są ignorowane:
wyświetli poniższe wyniki:
x = 1234 ;

nazwa to Quentin jest równoważne


x jest równe 51 x = 1234;
d jest równe 1.5707963267949

O to kod obsługujący kliknięcie przycisku:

x j e s t zm ienną,, „int" p riv a te void butto n1_Click(object sender, RoutedEventArgs e)


o z n a c z a , ż e z m ie n n a b ę d z ie
in te r p r e to w a n a ja k o h c z b a {
c a łk o w ita . P o z o s ta ła c z ę ś ć / / to je s t komentarz
in s tr u k c ji u s ta w i a w a r to ś ć I s tn ieje wt>udowana klasa Math
p o c z ą tk o w ą n a 3 . ^ ___ S trin g name = "Quentin"; p o siadająca składową PI.
ź o s to to ona um ieszczona
in t x = 3; w ^przestrzeni nazw S y s te m ,
x = x * 17;
więc napisanie tego fragmentu
Ten w iersz kodu tworzy wymaga dodania w iersza
wyniki pmgr-amu: aktualizuje double d = M ath.PI / 2; u s ing S y stem ; na początku pliku.
te t wy św ie t lany w kontrolce
TextB l°c k o nazwie myLabel. myLabel.Text = "nazwa to
Na szczę ś c ie IDE wygenerowało
+ "\nx je s t równe "
za Ciebie odpowiednią
+ "\nd je s t równe " d; instrukcję using.

\ n zw ane j e s t sekw encją formatującą


Uruchom program i upewnij się, i dodaj e znak nowego w iersza do
teks tu wyświetlanego w oknie
że generuje takie same w yniki z komunikatem.
jak te przedstawione powyżej.
Przewróć kartkę, aby dokończyć proyram!

jesteś tutaj ► 117


Co możesz zrobić

Ustal warunki i sprawdź, czy są prawdziwe


In s tr u k c je ty p u if/else będziesz stosow ał w tedy, gdy pew ne czynności
będ ą m usiały zostać w ykonane tylko w przy p ad k u sp ełn ien ia (lub nie)
Kiedy używasz
ko n k retn eg o w a ru n k u . operatora
warunkowego
Używaj operatorów logicznych do sprawdzania warunków
do sprawdzania
Przyjrzałeś się ju ż o p erato ro w i ==, k tóry służy do porów nyw ania w artości dwóch
zm iennych. Istnieje je d n a k znacznie więcej o p e ra to ró w logicznych. N ie przejm uj się dwóch
tym , że nie w szystkie o d ra zu zapam iętasz — n abierzesz w prawy w ich stosow aniu
podczas lektury kilku następnych rozdziałów .
wartości,
★ O p e ra to r != działa p o d o b n ie ja k ==. R ó żn ica p o leg a n a tym , że zwraca
to czynność
on w arto ść logiczną t r u e , jeśli dwie porów nyw ane rzeczy n ie s ą rów ne. taką nazywamy
★ M ożesz używać < i > do porów nyw ania liczb. W te n sposób spraw dzasz, sprawdzaniem
czy je d n a jest w iększa lub m niejsza niż druga.
warunku.
★ ==, !=, > i < nazyw am y o p e ra to ra m i w a ru n k o w y m i. G dy używasz ich
do spraw dzania dwóch w artości, to czynność ta k ą nazywamy sp ra w d z a n ie m
w a ru n k u .
Upewnij s ify ż e w yłączyłeś
★ M ożesz łączyć pojedyncze w arunki w złożone p o p rzez użycie o p e ra to ra && program, zanim p rzy stą p isz do
jako „i” o raz o p e ra to ra | | jak o „lu b ”. Z g o d n ie z powyższym, gdybyś chciał wp rowadzania zmian, gdyż IDE
nie poz woli Ci na edycję kodu,
spraw dzić, czy i je st rów ne 3 lub j je st m niejsze niż 5, mógłbyś napisać: ap likacj a j e s t uruchomiona.
(i = = 3 ) | | ( j < 5 ). / m że s z tego dokonać poprzez
zam knięcie okna, użycie
przy c is ku S te p na pasku narzędzi
^ USTAW ZMIENNĄ I SPRAWDŹ JEJ WARTOŚĆ. lub wyt>ór S to p Debugging
z menu Debug.
Poniżej przedstaw iony został k o d dla drugiego przycisku. Je st to instru k cja i f / e l s e ,
k tó ra spraw dza, czy z m ie n n a całkow ita x jest rów na 10 .

p r i v a t e voi d b u t t o n 2 _ C l i c k ( o b j e c t s e n d e r , EventArgs e)
{
Najpierw i n t x = 5;
ustaw iam y
wartość if (x == 10)
z miennej x
i przypisujem y {
do niej 5, myLabel . Text = 'x musi być równe 10"; x nie jest równe 10
a potem
sprawdzamy, }
czy j e s t ona i
else
równa 10.
{
myLabel . Text = "x n i e j e s t równe 10";
/
} Oto w ynik działania programu. Sprawdź, czy jesteś w stanie,
zmieniając jedną linię, spowodować wyświetlenie zamiast
tego komunikatu „ x musi być równe 10” .
118 Rozdział 2.
To tylko kod

DODAJ KOLEJNE SPRAWDZENIE WARUNKU.


T rzeci przycisk g en eru je n astęp u jące wyniki. W kodzie m eto d y zm ień w arto ść zm iennej someValue z 4 n a 3.
K o n tro lk a T e x t B l o c k zostaje zm odyfikow ana dwa razy, lecz dzieje się to ta k szybko, że nie m o żn a tego zauważyć.
U m ieść p u n k t p rzerw an ia n a pierw szej instrukcji i w ykonuj kolejne instrukcje m etody; używ ając kom binacji
A lt+ T ab, wyświetlaj aplikację, by u p ew niać się, czy k o n tro lk a T e x t B l o c k została zaktualizow ana.

Ten w iersz sprawdza jednocześn ie,


ten wiersz jest wykonywany bez względu na warunki czy som eValue j e s t m niejsze n iż 3
i czy name j e s t równe j a n ^ “.

p r i v a t e voi d b u t t o n 3 _ C l i c k ( o b j e c t s e n d e r , EventArgs e)
{
i n t someValue = 4;
s t r i n g name = "Kr z y s i ek " ;
i f ((someValue == 3) && ( n a m e . E q u a l s ( " J a n e k " ) ) )
{
myLabel . Text = "someValue j e s t równe 3 i name j e s t równe J a ne k" ;
}
myLabel . Text = " t en w i e r s z j e s t wykonywany bez względu na wa r u n k i " ;
}

[4 DODAJ PĘTLE DO SWOJEGO PROGRAMU


M am y tutaj k o d dla o statniego przycisku. Z aw iera o n dwie p ętle. Pierw sza to p ę tla w h ile, k tó ra p o w tarza instrukcje
znajdujące się pom iędzy dw om a naw iasam i klam row ym i ta k długo, jak długo w aru n ek jest praw dziw y — rób coś,
dopóki to jest praw dą. D ru g a z nich to p ę tla for. Przyjrzyj się fragm entow i i zobacz, ja k działa.

p r i v a t e voi d b u t t o n 4 _ C l i c k ( o b j e c t s e n d e r , RoutedEventArgs e)
{
i n t count = 0;
Ta pętla j e s t Druga część instrukcji for to s prawdzenie e
wykonywana, warunku. Oznacza ona: „d°póki i j e s t mniejsze
dopóki zmienna w h i l e (count < 10) niż 5, pętla powinna by ć wy kony wana . .
count j e s t nWiża r5u,nek j e s t sprawdzany przed wyko nbalonkiem
mniejsza niż 10. { bloku kodu um ieszczonego w pętli, a blok ek
zo sta n ie wykonany tylko wtedy, gdy warunek
count = count + 1;
j e s t spetniony.

Ta instrukcja j e s t ^ konj f
pod koniec każdej iteracji. W tym
en fragment u sta la przypadku zw iększa ona w artość
wartości początkowe zmiennej i o 1. T e n fragm ent pętli
Ha pętli. Przypisuje for je s t nazywany iteratorem, je
artość początkową on wykonywany hęzpośredn . o ę ^
count = count - 1
do liczby całkow itej, zakończeniu realizacji bloku kodu_
która będzie w pę t l< umieszczonego wewnątrz nawiasów
używana.
klamrowych pętli-
myLabel . Text = "Odpowiedź brzmi c ount ;
}
Zanim naciśniesz przycisk, spróbuj wczytać się w kod i odgadnąć, jaki będzie
w ynik działania programu. Potem użyj go i sprawdź, czy miałeś rację!
jesteś tutaj ► 119
Jeszcze jeden i jeszcze raz...

Zaostrz ołówek —
Potrzeba nam trochę ćwiczeń związanych ze sprawdzaniem warunków i z pętlami.
Rzuć okiem na poniższy kod. Zakreśl miejsca sprawdzania warunków i uzupełnij puste pola,
tak aby komentarze poprawnie go wyjaśniały

int result = 0 ; // ta zmienna będzie zawierała końcowy wynik W ypełniliśmy


p ierw sze pole
int x = 6; // zadeklaruj zmienną x i ustaw jej wartość na 6 za Ciebie.

while (x > 3)

{
// wykonuj te instrukcje, dopóki
result = result + x; // dodaj x

x = x - 1; // odejmij ......................................

}
for (int z = l ; z<3; z = z + l )

{
// rozpocznij pętlę poprzez _
// wykonuj pętlę, dopóki ...................................
// po wykonaniu jednego przebiegu
result = result + z; //

}
// następna instrukcja aktualizuje tekst w kontrolce TextBlock

// ...........................................................
myLabel.Text = "Zmienna result jest równa " + result;

Więcej o sprawdzaniu warunków

M ożesz przeprowadzać proste sprawdzanie warunków używając operatorów porównania.


Spos(ób, w jaki będziesz porównywał dwie liczby, x i y, jest nast ępujący;

x < y ( mn i e j sz e ni ż)
x > y (większe n i ż )
x == y (równe - t a k j e s t , dwa znaki równości)

To są operatory, których będziesz używał najczęściej.

120 Rozdział 2.
To tylko kod
ZACZEKAJ!
JEST JAKAŚ NIESPÓJNOŚĆ W TWOJEJ
LOGICE. CO SIĘ STANIE Z MOJĄ PĘTLĄ,
JEŚLI NAPISZĘ WARUNEK, KTÓ RY NIGDY NIE
PRZYJMIE WARTOŚCI FALSE?

Wtedy Twoja pętla będzie działała wiecznie!


Z a każdym razem , gdy p ro g ram spraw dza w aru n ek , rezu ltatem m oże
być albo t r u e , albo f a l s e . Jeżeli je st to t r u e , Twój p ro g ram w ykonuje
pę tlę jeszcze raz. K ażd a p ę tla pow in n a być skon stru o w an a tak , aby
po jej w ykonaniu odp o w ied n ią liczbę razy w aru n e k zm ienił swoją
w artość n a f a l s e . Jeżeli ta k się nie stan ie, będzie o n a wykonywana
bez końca, do czasu gdy zam kniesz p ro g ram lub wyłączysz ko m p u ter.

C zasam i fira a

K 3 1 * * * * chcesz
Z a s to s o w a ć w p ro g ra m ie .

Zaostrz
Z.UU3U ołówek
Zaprezentowano tu kilka pętli. Zanotuj przy każdej z nich, czy będzie wykonywana
w nieskończoność, czy kiedyś się zatrzyma. Jeżeli dojdzie do końca, to ile razy się
przedtem wykona?

PĘTLA NUMER 1 PĘTLA NUMER 3 PĘTLA NUMER 5


int count = 5; int j = 2; int p = 2;
while (count > 0) { for (int i = 1; i < 100; for (int q = 2; q < 32;
count = count 3; i = i * 2) q = q * 2)
count = count * -1; { {
} W przypadku pętli • j = j - i; while (p < q)
numer 3 określ, ile razy while (j < 25) {
zo sta n ie wykon<ma
ta instrukcja. { p = p * 2;
PĘTLA NUMER 2 j = j + 5; }
} q = p q;
int i = 0; W przypadku p ętli
} num er 5 określ, ile irazy
int count = 2;
zo sta n ie wykonana
while (i == 0) { ta instrukcja. Podpowiedz: początkowo
count = count * 3; watrtość zm iennej q wynosi 2;
PĘTLA NUMER 4 zastanów s ię, kiedy zostanie
count = count * -1; wykonany iterator „q = q * 2".
while (true) { int i = 1;}
}

\
Pamię taj, że w pętlach
WYSIL
for warunek za w sze j e s t
sprawdzany przed wykonaniem
SZARE KOMÓRKI
bloku kodu um ieszczonego
wewnątrz pętli, natom ia s t Czy możesz znaleźć jakiś powód, dla którego pisanie
iterator u ruchamiany j e s t pętli nieskończonych ma sens? (Wskazówka: będziesz
po wykonaniu tego bloku. z nich korzystał w rozdziale 13.).

jesteś tutaj ► 121


Jeżeli tylko, ale tylko jeżeli

^Zaostrz ołówek
Potrzeba nam trochę ćwiczeń związanych ze sprawdzaniem warunków i z pętlami.
Rozwiązanie Rzuć okiem na poniższy kod. Zakreśl miejsca sprawdzania warunków i uzupełnij
puste pola, tak aby komentarze poprawnie go wyjaśniały.

int result = 0; // ta zmienna będzie zawierała końcowy wynik


int x = 6; // zadeklaruj zmienną x i ustaw jej wartość na 6
while (x > 3)
{
// wykonuj te instrukcje, dopóki x jest większe niż 3

result = result + x; // dodaj x do zmiennej result

x = x - 1; // odejmij I od wartości x
} Ta pętla wykonywana j e s t dwukrotnie — za pierwszym
z je s t na 1, a za drugim na 2.
for (int z = 1; 6 < 3; z = z + 1) Po o siqgnię ciu wartości 3 nie j e s t ju ż m niejsze niż 3,
więc pętla s ię kończy.

II rozpocznij pętlę poprzez^ zadeklarowanie zmiennej i ^stawi enie, jej na.!....


II wykonuj pętlę, dopóki z jest mniejsze niż 3
// po wykonaniu jednego przebiegu dodaj I do z
result = result + z; // dodaj. wartość z do zmiennej result
} .................................................................................................
// następna instrukcja aktualizuje tekst w kontrolce TextBlock
// Zmienna result jest równa I8
myLabel.Text = "Zmienna result jest równa 11 + result;

r- «^Zaostrz ołówek
Zaprezentowano tu kilka pętli. Zanotuj przy każdej z nich, czy będzie wykonywana
R O Z W ia Z a n ie w nieskończoność, czy się kiedyś zatrzyma. Jeżeli dojdzie do końca, to ile razy się
przedtem wykona?

PĘTLA NUMER 1 PĘTLA NUMER 3 PĘTLANUMER 5


Ta p ę t l a wykona s i ę raz. Ta p ę t l a wykona s i ę siedem razy. Ta p ę t l a wykona s i ę osiem r a z y .

PĘTLA NUMER 2 PĘTLA NUMER 4


Ta p ę t l a b ę d z i e s i ę wykonywała Jeszcze jedna p ę t l a nieskończona,
w nieskończoność.
Poświęć nieco czasu, by dobrze zrozum ieć działanie o statniej p ętli. Stanow i ona doskonatą okazję, byś^sam
przetestow ał działanie debuggera! U staw punkł- p rzerwania na w ierszu za wteraj ącym r n s ł r u j ^ ą p ą,.
Dodaj zmienne! ej i|o do obserwowanych, a następnie wy konuj krok p o kroku kolejne mstru kcje p ętl'.

122 Rozdział 2.
To tylko kod
■ Nie .istnieją.
głupie pytania

P : Czy każdy kawałek kodu musi być O : Istnieje doskonały sposób, abyś się o tym przekonał
umieszczony w klasie? — po prostu spróbuj! Zrób coś w miejscu, gdzie IDE tworzy
za Ciebie kod. Przeciągnij przycisk na stronę, zmień jego
O : Tak. Wszystko, co robią programy napisane w języku C#, właściwości. Spróbuj potem to cofnąć przyciskiem Undo.
sprowadza się do wykonywania instrukcji. Instrukcje te są częścią Co się stało? Generalnie przy prostych rzeczach można zauważyć,
klas, a te z kolei wchodzą w skład przestrzeni nazw. Nawet jeśli że IDE potrafi wycofać zmiany, które samo wprowadziło.
początkowo wydaje się, że coś nie jest częścią klasy — na przykład (W przypadku bardziej skomplikowanych, takich jak dodanie nowej
wtedy, gdy używasz okna Designer do ustawiania właściwości bazy danych SQL do projektu, zostanie wyświetlone ostrzeżenie.
kontrolek — to okazuje się, że dokładniejsze poszukiwania W tej książce nie znajdziesz jednak przykładów tak złożonych
pozwalają znaleźć odpowiednie fragmenty dodane przez IDE operacji).
i że jest to część jednej z klas.

P Czy są jakieś przestrzenie nazw, których używać nie


:
P : Jak daleko idącą ostrożnością powinienem wykazać
się przy pracy z kodem generowanym automatycznie
mogę? Albo takie, których użyć muszę? przez IDE?

O : Tak. Istnieje kilka przestrzeni nazw, których nie powinieneś O : W zasadzie powinieneś być dość ostrożny. Możesz odnieść
używać, choć z technicznego punktu widzenia będą działać. spore korzyści, gdy zrozumiesz, co IDE robi z Twoim kodem.
Zauważyłeś może, że wszystkie wiersze u s in g na początku Przyjdzie na pewno taki moment, gdy wiedza na temat tych
zagadnień przyda się w rozwiązywaniu naprawdę poważnych
plików klas C# zawsze zawierają System? To dlatego, że istnieje
problemów. W większości przypadków wszystko, co będziesz chciał
przestrzeń nazw System, która jest używana przez Windows
zrobić, możesz wykonać za pośrednictwem IDE.
Store API i platformę .NET. To właśnie tam możesz znaleźć
większość ważnych narzędzi dających prawdziwą moc programom
CELNE SPOSTRZEŻENIA --------------------
Zaliczyć można do nich przestrzeń nazw S yste m .L in q , która
pozwala pracować z sekwencjami danych, oraz przestrzeń nazw
System .IO zawierającą narzędzia do korzystania z plików ♦ Działanie programu określasz poprzez pisanie instrukcji.
i strumieni. W większości przypadków możesz wybrać dowolną Instrukcje zawsze są częścią klas, a każda klasa jest
nazwę dla swojej przestrzeni nazw (o ile składa się ona z liter, składową przestrzeni nazw.
cyfr i znaków podkreślenia). Kiedy tworzysz nową aplikację, ♦ Każda instrukcja kończy się średnikiem (;).
IDE automatycznie wybiera ją na podstawie nazwy programu. ♦ Kiedy używasz wizualnych narzędzi Visual Studio

P : W dalszym ciągu nie mogę pojąć, po co korzystać


IDE, w programie jest automatycznie tworzony
i modyfikowany kod.
z tego partial class.
♦ Bloki kodu otoczone są nawiasami klamrowymi: { }.
O : Klasy zadeklarowane z użyciem słowa kluczowego p a r t i a l Klasy, pętle w h ile , instrukcje i f / e l s e i wiele innych
typów instrukcji używa tych bloków kodu.
pozwalają podzielić kod jednej klasy pomiędzy kilka plików. IDE
też robi coś takiego podczas tworzenia strony — przechowuje ♦ Sprawdzenie warunku może zwrócić wartość t r u e
kod, który edytujesz, w jednym pliku (MainPage.xamf), a kod, lub f a ls e . Używasz warunków do określenia, czy pętla
który modyfikuje automatycznie, w innym (MainPage.xaml.cs). powinna się zakończyć oraz który blok kodu powinien się
Nie potrzebujesz stosować takich zabiegów w przypadku wykonać w instrukcji i f / e l s e .
przestrzeni nazw. Jedna może być podzielona na dwa, trzy, ♦ Za każdym razem, gdy Twój program ma przechowywać
kilkanaście lub więcej plików. Po prostu wstaw deklarację dane, używasz zmiennych. Korzystaj z operatora = w celu
przestrzeni nazw na początku pliku, a wszystko wewnątrz pary przypisania do zmiennej wartości oraz z operatora ==
nawiasów klamrowych stanie się jej częścią. Jest jeszcze jedna do sprawdzenia, czy dwie zmienne są sobie równe.
rzecz, na którą musisz zwrócić uwagę: w jednym pliku można ♦ Pętla w h ile wykonuje wszystko wewnątrz bloku
umieścić więcej niż jedną klasę. To samo dotyczy zresztą także (zdefiniowanego przez nawiasy klamrowe), dopóki
przestrzeni nazw. W kilku kolejnych rozdziałach dowiesz się jeszcze wyrażenie warunkowe przyjmuje wartość tru e .
wielu rzeczy na temat klas. ♦ Jeżeli wyrażenie warunkowe jest równe f a ls e , blok

P : Załóżmy, że przeciągnąłem coś na stronę i IDE


w pętli w h ile nie zostanie uruchomiony. Wykonywanie
kodu będzie kontynuowane od miejsca znajdującego się
automatycznie wygenerowało kawałek kodu. bezpośrednio za nim.
Co się stanie z tym kodem, jeśli nacisnę Undo?

jesteś tutaj ► 123


Twój kod... teraz w postaci magnesów

Magnesy z kodem
C z ę ś c i programu C # zo stały pom ieszane i poprzypinane na lodówce. C zy p o tra fis z
p op rzestaw iać fra gm en ty kodu ta k , a b y utw orzyły prawidłowy program C # , który
w yśw ietla pokazany komunikat? N ie k tó re z nawiasów klamrowych spadły na podłogą
i s ą z b y t małe, ab y j e podnieść. W każdej chwili m ożesz dodać dowolną ich liczbą.
(Rada: nie ulega wątpliwości, że nie o b e jd zie sią bez dodania kilku nawiasów
klamrowych. Koniecznie j e dopisz!).

"" to p u sty te k s t — oznacza, że rę s ult


nie zaw iera je s zc ze żadnych znak.ó\u.

K Ten m agnes nie spadt


z lodówki...

Wynik:

To j e s t kontrolka
TextBlock o nazwie
„output", której
zaw artość program
modyfikuje za pomocą
właściwości Text.

- ► Odpowiedzi znajdziesz na stronie 128.


124 Rozdział 2.
To tylko kod

W tej książce b ęd ziesz p isa ł wiele aplikacji,


W t e \ książce dostaniesz do w ykonania wi f lf pod° bnycti lk„ a każdej z nich b ęd ziesz m u sia ł nadać inną,
Ww ' Odpowiedź do każdego z nich będzie p d a n o k , — unikatową nazwę. Sugerujem y, żebyś tę nazwał
s tr o n n a lej. J e ż e li utknąłeś, nie bój s ię d° niej zajrzeć — „Zabawa z instrukcjami if-else". Dzięki temu
to nie oszustw o! b ęd ziesz mógł um ieścić w szystk ie programy
z jednego rozdziału w tym samym katalogu.

Teraz czas na poćwiczenie stosowania instrukcji i f / e l s e . Czy potrafisz napisać taki program?
Jeśli utw o rzysz dwa Dodaj tu p rzycisk i pole w yboru.
Ćwiczenie w iersze i jednem u K o n tro lk ę CheckBox m ożesz znaleźć w o knie Toolbox,
z nich p rzy p isze sz w IDE
w ysokość 1*, to w iersz tu ż poniżej k o n tro lk i B u tto n . K o n tro lce B u tto n nadaj
U tw ó rz tak ą stronę.
ten pozornie zniknie, gdyż nazw ę c h a n g e T e x t, a k o n tro lce CheckBox nazwę
Z aw iera siatkę sk ładającą się z o s tanie zm niejszony do
e n a b le C h ec k b o x . T ek st n a k o n tro lk ach zm ień, klikając
z dw óch wierszy i dw óch kolum n. bardzo mały ch rozmi'arów.
P rzypisz drugiemu je praw ym przyciskiem myszy i w ybierając z m en u opcję
w ierszowi wysokość 1 E d it Text (po w pisaniu tek stu naciśnij klawisz E sc).
a oba znów będą
widoczne. K liknij k ażd ą z k o n tro le k praw ym przyciskiem myszy
i w ybierz opcję R eset L ayout/A ll, a n astęp n ie upew nij
się, że ich właściwości V e r tic a lA lig n m e n t oraz
K liknięcie /m ie n ia e ty k ietę Q W łąc /a /m w n ę etykiety H o r iz o n ta lA lig n m e n t m ają w artość C e n te r.

To je st kontrolka TextBlock.
Jest n iem al ta k a sam a ja k ta, k tó rą um ieściłeś
Naciśnij przycisk, aby zmienić tekst
n a sam ym dole strony w ostatnim projekcie.
N adaj jej nazw ę la b e lT o C h a n g e ,
a właściwości G rid.R ow przypisz w artość 1.

W yśw ietl ten komunikat w kontrolce TextBlock, jeżeli użytkow nik


naciśnie p rzycisk , ale pole w yboru N IE B ĘD ZIE zaznaczone.
O to w aru n ek pozw alający określić, czy p o le w yboru zostało zaznaczone:

enableCheckbox.IsChecked == true
Możliwość zmiany tekstu została wyłączona

Jeśli ten w aru n ek N IE będzie spełniony, to p ro g ram pow inien w ykonać n astęp u jące dwie instrukcje:

labelToChange.Text = "Możliwość zmiany tekstu została wyłączona"; P° dp°wiedź: ten kod


um ieść w bloku else.
labelToChange.HorizontalAlignment = HorizontalAlignment.Center;

Jeżeli użytkow nik naciśnie p rzycisk i pole w yboru B ĘD Z IE zazn aczo ne, zm ień tekst w kontrolce
TextBlock ta k , by za w ie ra ł gdy tekst będzie w yśw ietlany z lewej strony, i I Z prawej
gdy będzie w idoczny po praw ej.
Jeśli właściwość T e x t etykiety m a w artość "Z p ra w e j" , to pro g ram pow inien zm ienić tekst n a "Z le w e j"
i zmienić w artość właściwości H o riz o n ta lA lig n m e n t n a H o riz o n ta lA lig n m e n t.L e f t. W przeciwnym razie tekst m a
być zm ieniony na "Z p ra w e j" , a wartość właściwości H o riz o n ta lA lig n m e n t n a H o riz o n ta lA lig n m e n t.R ig h t.
W ten sposób klikanie przycisku będzie pow odow ało zm ienianie położenia etykiety — jed n ak wyłącznie
w przypadku, gdy p ole w yboru będzie zaznaczone.

jesteś tutaj ► 125


Ta układanka jest trudniejsza, niż myślisz

Zagadkowy basen
T w oim za d a n iem je st p o b ra n ie kolejnych
i n t x = 0;
fragm entów k o d u z b asen u i w staw ienie
s t r i n g poem
ich w o d pow iednie p u ste m iejsca
w kodzie. N ie m ożesz używać tego
w hile ( ) {
sam ego frag m en tu k ilkakrotnie
i nie m usisz w ykorzystać wszystkich
fragm entów . Twój cel to utw orzenie if ( x < 1 ) {
klasy, k tó ra się skom piluje i będzie
działała. N ie daj się zwieść — to zadanie
je st trud n iejsze, niż się wydaje.

if ( ) {
W ynik

a noise annoys an oyster


7* Oto kolejna kontrolka TextBlock
i także je j nadaliśmy nazwą „ o u tp u f.
if ( x == 1 ) {

if ( ) {
Z am ieściliśm y tu ćwiczenia w stylu „Puzzle z basenu“, aby dać
Twojemu um ysłowi znacznie am bitniejsze zadania. J eśli je s te ś
osobą, która choć trochę lubi zakręcone logiczne układanki,
to takie ćw iczenie przypadnie Ci do g u stu . Jeżeli za czym ś
takim nie przepadasz, nic nie szkodzi — spróbuj! Nie bój się
zerknąć na odpowiedź, aby coś sprawdzić. Jeśli pogubisz s ię
przy rozwiązywaniu „Puzzli z basenu“, przejdź dalej.

Przypominamy: każdy
fragment może być użyty
tylko raz!

126 Rozdział 2.
To tylko kod

Teraz czas na poćwiczenie stosowania instrukcji i f / e l s e . Czy potrafisz napisać taki program?

Rozwiązanie Dodaliśm y do niego znaki


ćwiczenia nowych wierszy, by
poprawić jego czytelność.

Oto kod X A M L o kreślający postać siatki:


<Grid Background= "{StaticResource ApplicationPageBackgroundThem eBrush}1
< G rid.R ow D efinitio ns>
<RowDefinition/>
<RowDefinition/>
< /G rid .R o w D efin ition s>
< G rid.C olum nD efinitions>
J eśli dwukrotnie klikn iesz przycisk w oknie Designer,
<ColumnDefinition/>
zanim je s z cze określisz jego nazwę, to IDE może
<ColumnDefinition/> u tworzyć procedu rę obsługi zdarzeń Click o nazwie
< /G rid .C o lum nD efinitio ns> butto n^ _ Ciick(), a nie changeText Click().

«Button x:Name="changeText" C o n te n t= "K lik n ię cie zm ienia e ty k ie tę


H orizontalA lignm ent= "C enter" C lick= "ch a n g e T e xt_C lick"/> ■

<CheckBox x:Name="enableCheckbox" Content="Włącza zmianę e ty k ie ty "


H orizontalA lignm ent= "C enter" IsChecked= "true" Grid.Column="1"/>

< TextBlock x:Name="labelToChange" Grid.Row="1" TextWrapping="Wrap"


T e x t= "N a ciśn ij p r z y c is k , aby zm ienić t e k s t"
H o rizontalA lignm ent= "C enter" V erticalA lig nm en t= "C enter"
Grid.ColumnSpan="2"/>
</Grid>

A oto kod C # obsługujący kliknięcia przycisku:


p riv a te void c h a n g e T e x t_C lick (o b je ct sender, RoutedEventArgs e)
{
if (enableCheckbox.IsChecked == tru e )
{
if (labelTo C hang e.Text == "Z praw ej")
{
labelToC hange.Text = "Z le w e j" ;
labelToC hange.H orizontalA lignm ent = H o riz o n ta lA lig n m e n t.L e ft;
}
e ls e
{
labelToC hange.Text = "Z p ra w e j";
labelToC hange.H orizontalA lignm ent = H o rizo n ta lA lig n m e n t.R ig h t;
}
}
e ls e
{
labelToC hange.Text = "Możliwość zmiany te k stu z o ta ła w yłączo na";
labelToC hange.H orizontalA lignm ent = H o rizo n ta lA lig n m e n t.C e n te r;
}
}

jesteś tutaj ► 127


A plikacje innego rodzaju

Zagadkowy basen.
Rozwiązanie
Magnesy z kodem.
Rozwiązanie

i n t x = 0;
Ten magnes
nie spadł z lodówki... s t r i n g poem = " " ;
while ( x < 4) {
poem = poem + "a";
if (x < 1) {

Podczas pierwszego przejścia


poem = poem + " ";
przez p ętlę x j e s t równe 3, }
więc ten warunek będzie
prawdziwy. poem = poem + "n";

if ( x > 1) {
poem = poem + " oyster";
Ta instrukcja x = x + 2;
powoduje
przypisanie do }
x 2 podczas if (x == 1) {
pierwszego
j 1 podczas poem = poem + "noys ";
drugiego
przebiegu pętli. }
if ( x < 1) {
poem = poem + "oise ";
}

x = x + 1;
}
output.Text = poem;

O trzym ałeś inny kod? W pisz go w IDE i sprawdź,


czy działa! To ćw iczenie ma w ięcej niż jedno
popraw ne rozw iązanie.

T
Jeśli s zu k a sz p rawdziwego wyzwania, to sprawdź, czy
potra fis z 'znaleźć roz wiązanie alternatywne. Oto podpowiedź:
I s tnieje inna p ostać kodu, która daje identyczne wyniki.
Jeśli podałeś inne n zw ią za n ie niż to powyżej, to spróbuj
zrozumieć, dlaczego to działa.
128 Rozdział 2.
To tylko kod

Tworzenie klasycznych aplikacji Windows jest łatwe


W system ie W indow s 8 pojaw iły się now e aplikacje dla S klepu W indow s, a to dało w szystkim całkowicie
now e m ożliw ości korzystania z op ro g ram o w an ia w system ie W indows. N ie je st to je d n a k jedyny rodzaj
aplikacji, jakie m ożn a tw orzyć przy użyciu V isual Studio. M o żn a skorzystać z V isual S tudio fo r W indows
D esktop, by tw orzyć klasyczne aplikacje W indow s (o k reślan e tak że jak o aplikacje W indow s D esktop),
k tó re są u ru ch am ian e n a pulpicie W indow s 8 i p re zen to w an e w oknach.

Start

m , .
* ¡Hfc e a
tttop 10

Twitter
Wfrtt i t OTI_
m A
o > S*>Ow*

Klasyczne aplikacje W indows są doskonałym narzędziem do nauki.

Podczas lektury kilku najbliższych rozdziałów będziesz p isał aplikacje, korzystając Kolejnym ważnym
z V isual S tu d io for W indow s D esk to p , a d o p iero p o te m zajm iesz się tw orzeniem powodem do nauki
tworzenia klasycznych
aplikacji dla S klepu W indows. W ynika to z faktu, że p o d w ielom a w zględam i aplikacji Windows je s t
klasyczne aplikacje W indow s są prostsze. Być m oże nie w yglądają ta k efektow nie, możliwość zobaczenia,
a co w ażniejsze, nie in teg ru ją się rów nie dobrze z system em W indow s 8 i nie ja k można tę sam ą
rzecz zrobić na dwa
ud o stęp n iają tego w spaniałego, spójnego in terfe jsu użytkow nika cechującego różne sposoby.
aplikacje dla S klepu W indow s. N iem niej je d n a k w ydajne tw orzenie aplikacji A to naprawdę szybki
sposób utrwalania
dla S klepu W indow s w ym aga zro zu m ien ia w ielu w ażnych, podstaw ow ych pojęć. w mózgu nowych
A tw orzenie klasycznych aplikacji W indow s je st rew elacyjnym sposobem ich pojęć. Przewróć kartkę
poznaw ania. K iedy ju ż zdobędziesz o d pow iednie podstaw y, w rócim y do tw orzenia by przekonać się,
co mamy na myśli...
aplikacji dla S klepu W indows.

jesteś tutaj ► 129


To w ygląda dziw nie znajom o

Przepisz program jako klasyczną aplikację Windows


U ru ch o m V isual Studio 2012 fo r W indow s D e sk to p i u tw órz nowy p ro jek t. Tym razem
do w yboru będziesz m iał inne typy pro jek tó w niż wcześniej. Kliknij opcję Visual C # ,
a n astęp n ie W indows i u tw órz now ą aplikację W indow s F o rm s A pplication.

New Project

Sort by: Default lr [ ji Search Installed Templates (Ctrl+ E} P -

Windows Forms Application Type: Visual C#


A project fo r creating an application with a
Windows Forms user interface
1 Visual Basic
a
Bi Console Application

Visual C++
m Kiedy tw orzysz nowy
projekt w V isual Z azw yczaj
Samples
ss Stu d io 2012 Express powinieneś
for Windows Desktop, nadawać projektom
m asz do wyboru lepsze nazwy niż
te opcje. Wybierz „Rozdział 2 -
Windows Forms Program 4“, jednak
Application. celowo użyliśm y
nazwy zawierającej
Name Rozdział 2 - Program 4 znaki odstępu
Locatior c:\users\p8r\documents\visual studio 2012\Projects
i myślnik, żebyś
mógł przekonać
Solutior Rozdział 2 - Program 4 [s/l Create directory fo r solution
I I Add to source control
s ię ja k IDE określi
p rzestrzeń nazw
dia tego programu.

S APLIKACJE TYPU WINDOWS FORMS ZAW IERAJĄ FORMULARZ,


KTÓREGO WIELKOŚĆ MOŻESZ ZMIENIAĆ.
T w oja now a aplikacja p o siad a o k n o głów ne, k tó reg o zaw artość m ożesz o kreślać w ID E , w specjalnym
p ro jek tan cie form ularzy. Zacznij o d n a d an ia m u w ym iarów 5 0 0 x 1 3 0 . W o knie Designer odszukaj uchwyt
i przeciągnij go, by zm ienić w ielkość fo rm ularza. P odczas p rzeciąg an ia uchw ytu kontro lu j zm ieniające się
liczby wyświetlane n a p ask u stanu, inform ujące o nowej w ielkości form ularza. Przeciągaj uchw yt ta k długo,
aż u d a Ci się uzyskać w ym iary PSBE

Przeciągaj ten uchw yt aż


do uzyskania zamierzonej
wielkości formularza.
Ta k powinien wyglądać f o r m ^ ^
y po nadaniu mu odp°wiedniei
wielkości.

130 Rozdział 2.
To tylko kod

ZMIEŃ TYTUŁ FORMULARZA.


A k tu aln ie form u larz m a dom yślny tytuł — Form1. M ożesz Upewnij się, że
go zm ienić, klikając form ularz, aby go zaznaczyć, a n astęp n ie używasz odpowiedniej
klikając właściwość T e x t w yśw ietloną w o knie Properties. Uu)2k^l w ersji Visual Studio

Properties ▼ □ X
Jeśli używasz Visual Studio 2012 Express
Form l System.Windows.Forms.Form ▼
Edition, to będziesz musiał zainstalowaćjego
dwie wersje. Do tworzenia aplikacji dla Sklepu
i» ]? * \ & \ f *
Windows używałeś Visual Studio 2012 for
RightToLeft No A
Windows 8. Terazjednak będziesz potrzebował
RightT oLeftLayout False
| Text 1 Moja aplikacja Windows Visual Studio 2012 for Windows Desktop.
1 UseWaitCursor False Na szczęście obie te wersje Visual Studio można
bezpłatnie pobrać z witryny firmy Microsoft.

DODAJ PRZYCISK, POLE WYBORU I ETYKIETĘ.


O tw órz okno Toolbox (nazyw ane tak że p rzybornikiem ) i przeciągnij
n a form ularz k o n tro lk i B u t t o n , CheckBox o raz Label .

T oolbtM

r O k n o Toolbox m ożesz wyświetlić, w ybierając opcję Toolbox z m enu

I — V IE W , b ąd ź też klikając k a rtę Toolbox u m ieszczoną z lewej strony ID E .

M ożesz tak że uniem ożliw ić chow anie tego okna, klikając ikonę pinezki
(E3) w idoczną w jego praw ym górnym rogu. M ożesz też przeciągnąć
całe o k n o Toolbox, ta k że będzie cały czas w idoczne p o n a d oknem
całego ID E .

Te linie od^ępóiw pomagają w um iejscaw ianiu kontrolek podczas


ich p rzeciągania w oknie formularza.

N a n astępn ej stro n ie użyjesz o k n a Properties, by zm ienić


tekst w yświetlany n a każdej z dodanych k o n tro le k o raz
zm ienić stan p o la w yboru (żeby było o n o zaznaczone).
awdź, czy jesteś w stan ie domyślić się, ja k to zrobić,
bez zaglądania n a n a stę p n ą stronę.

IDE pomaga wyrównywać kontrolki w yświetlając linie wyrównania


podczas przeciągania kontrolek w oknie formularza.

Podpowiedz: będziesz musiał skorzystać


z właściwości A u t o S i z e , by nadać
odpowiedni wygląd kontrolce etykiety

jesteś tutaj ► 131


Już to gdzieś w idziałem

UZYJ O KNA PROPERTIES, BY USTAWIĆ WŁAŚCIWOŚCI KONTROLEK.


Kliknij k o ntrolkę B u tto n , aby ją zaznaczyć. N astęp n ie przejdź do o k n a Properties i o kreśl w artość właściwości T ex t:

K liknięcie zm ienia etykietę| v

Z m ień w artość właściwości T e x t k o n tro le k CheckBox o raz Label tak , by pasow ały do zrzutów e k ran u
przedstaw ionych n a n astęp n ej stro n ie; oprócz tego ustaw właściwość C hecked k o n tro lk i CheckBox n a w arto ść T rue.
N astęp n ie zaznacz k o n tro lk ę Label i ustaw jej w łaściwość T e x tA lig n n a w artość M id d le C e n te r. U żyw ając okna
Properties, o k reśl nazwy k o n tro lek . Przyciskowi nadaj nazw ę c h a n g e T e x t,
p o lu w yboru nazw ę e n ab le C h e c k b o x , a etykiecie lab elT o C h an g e .
Przyjrzyj się dokład n ie kodow i p rzed staw io n em u u dołu strony,
by p rzek o n ać się, w jaki sposób te nazwy są używ ane w kodzie.

N astęp n ie ustaw właściwość A u to S iz e kontro lk i Label n a w artość


F a ls e . E tykiety zazwyczaj autom atycznie dostosow ują się do wielkości
p rezen to w an eg o tek stu . W yłączenie tej opcji — u staw ienie jej n a w artość
F a ls e — spow oduje w yśw ietlenie uchw ytów do p rzeciągania. R ozciągnij
k o n tro lk ę n a c ałą szerokość okna.

DODAJ DO PRZYCISKU PROCEDURĘ OBSŁUGI ZDARZEŃ.


D w ukrotnie kliknij przycisk, aby ID E d o dało do niego p ro c ed u rę obsługi zdarzeń. O to jej kod:

I :: - :: s -

* i; R o z d z i a ł 2 P r o g r a m 4 ,F o r m 1 * c h a n g e T e ) it_ C f tc k (o b je c t s e n d e r , E v e n tA r g s e) ___ -

| B namespace Rozdział 2 Program 4


t
public: pa.-tiai ciass . .. - Kiedy dwukrotnie k/iknąłeś przycisk
IDE wygenerowało tę procedurę
public Forml()
obsługi zdarzeń i nadało j ej naz\wę
t changeText_C/ick(), bazując ma. nazwie
InitializeComponent();
> przycisku — changeTex't.
private void changeText_Click(object sender^ EventArgs e)
t '
ii (enableCheckbox-Checked == true)
i
if (labelToChange.Text == “Z prawej")
i
labelToChange.Text = "Z lewej”;
labelToChange.TextAlign = ContentAlignment.MiddleLeft; To j e s t kod
procedury obsługi
else
zdarzeń. Przyjrzyj
t
labelToChange.Text = "Z prawej”; mu s ię uw ażnie
labelToChange.TextAlign = ContentAlignment.MiddleRight; — czy m ożesz
} wskazać, czym
} różni s ię on od
else
podobnego kodu,
i który dodałeś
labelToChange.Text = "Możliwość zmiany tekstu została wyłączona";
labelToChange.TextAlign = ContentAlignment-MiddleCenter; w ramach
} ćwiczenia?
>

132 Rozdział 2.
To tylko kod

Przetestuj program w IDE.


K iedy to zrobisz, ID E zbuduje p rogram ,
a n a stęp n ie go uru ch o m i, co spow oduje
w yśw ietlenie jego okna. S próbuj klikać
przycisk i p o le wyboru.

Kliknij pole
wyboru, by
włączyć lub
wyłączyć
Kiedy j e s t włączona opcja zm ienianie
zmieniania ety k ie ty i prezen tu je etyk iety.
ona te k s t „Z lewej“ lub
„Z prawej“ zależnie od te g0i
po której stronie okna z o s ta ć
w yświetlona. Po w yłcą:zenis
opcji etyk ieta prezentuje
stosow ny te k st, wyśrodkowany
w oknie programu.

Zaostrz ołówek
Uzupełnij komentarze tak, aby opisywały one poszczególne wiersze
kodu C#, których dotyczą. Uzupełniliśmy pierwsze pole za Ciebie.
Czy potrafisz odgadnąć, co powinno się znaleźć w ostatnim opisie?

Klasy C* używ ają tych ^ in g ,


u sing System;
aby dodać m etody.......................................
u sing System .Linq;
u sing System .T ext; z innych p r z e s t a n i nazw .......................
u sing System.Windows.Forms;

namespace SomeNamespace

{
c la s s MyClass {
p u b lic s t a t i c v oid DoSomething() {
MessageBox.Show("To j e s t wiadom ość.");

}
}
} A oto podpowiedź: jeszcze nie używałeś klasy MessageBox, lecz korzysta
z niej bardzo wiele klasycznych aplikacji Windows. Analogicznie do wielu
innych klas i metod, także ona ma sensowne nazwy.

Rozwiązanie na stronie 137 ►


jesteś tutaj ► 133
W idok z bliska K lasyczne a p lik acje W indow s
są nieco in n e i dobrze n a d a ją
Spokojnie się do n au k i.
Twój program wie, gdzie zacząć Klasyczne aplikacje Windows nie są tak
fajne jak aplikacje dla Sklepu Windows, ponieważ
Gdy stworzyłeś nowy proj ekt Windows F o r ms Application, odtworzenie w nich tak złożonego interfejsu użytkownika,
I D E dodało plik Program .cs. Przejdź do o k n a Solution Explorer jaki można tworzyć w aplikacjach dla Sklepu Windows,
jest znacznie trudniejsze (choć nie niemożliwe). I dobrze się
i kliknij go dwukrotnie. Wewnątrz niego zadekl arowana
składa! Ponieważ są one proste i nieskomplikowane, stanowią
została klasa Program, zawierająca me t od ę M a i n ( ) . Me t od a jednocześnie doskonałe narzędzie do poznawania języka C#
ta jest p u n k te m w e jśc ia , miejscem, o d którego zaczyna się i jego pojęć; a to ułatwi Ci zrozumienie aplikacji dla Sklepu
wykonywanie całego programu. Windows, kiedy wrócimy do nich w dalszej części książki.

TUÓ: ?n E dT sią tr° chą k0du wygenerowanego


ZnU v W P°PrEednim rozdziale. Można g o *
o d m ie ć w pliku Program.cs. a K o d p o d lu p ą

using System; i
using System.Collections.Generic; IDE wygenerowało tę przestrzeń naziu na
p o s ta w ie nazwy projektu. N a d a jm y
using System.Linq; ^Rozdzial 2 - Program 4", a z atem to dlate.go .IDE
using System.Threading.Task; ^ g e n e ro w a ło ta ką przestrzeń ^ ¿ J j T m a S M k
wybraliśmy nazw ę zaw ierają cą o d stępy i myślnik'
using System.Windows.Forms;A by pokazać, że IDE zam ienia te znaki iw
podkreślenia.
namespace Rozdzial 2 Program 4
W iersze, które rozpoczynają s ię od dwóch lub
static class Program w iększej liczby ukośników, s ą traktowane jako
komentarze i mogą być wstaw iane, gdzie tylko
{
I I I <summary> ir chcesz. Ukośniki nakazują C# zignorować je.

I I I The main entry point for the application.


I I I </summary>
[STAThread] K T s W S Z a ^każdym gdy Twój program j e s t
wykonywany, roz poczyna s ię to tutaj,
static void Main() w ptjnkcie w ejścia.
{
Application.EnableVisualStyles();
[^Application.SetCompatibleTextRenderingDefault(false)
Application. Run (new Forml()) ; ^ ------- Ta instrukcja tw orzy i w yśw ietla
} formularz, po zam knięciu
formularza program s ię kończy.
}
}

Deklai-uję!

'U
Pamiętaj , _to dop iero początkowy etap
Pierwszy wiersz każdej klasy i metody p oznawania kodu. Z anim jednak przejdziem y
nazywamy deklaracją. daiej, m u sisz wiedzieć, na co patrzysz.

134 Rozdział 2.
To tylko kod

T w orzenie klasycznych aplikacji W indow s m a swoje tajniki. Z ajm iesz się nim i n a kilku następnych
stronach, dzięki czem u będziesz w iedział, co dzieje się za kulisam i. N iem niej je d n a k znaczna część
p racy n ad aplikacjam i tego typu b ędzie w ykonyw ana p o p rzez przeciąg an ie k o n tro le k z o k n a T oolbox
i um ieszczanie ich n a form ularzu.

W miarę ja k podczas lektury t:ej k siążki


C # I .NET POSIADAJĄ WIELE WBUDOWANYCH będziesz poznawał kolejne możliwości
MOŻLIWOŚCI. języka C# i platformy .NET, Twoje
program y będą w ykorzystyw ały coraz
P od o b n e w iersze m ożesz znaleźć n a p o czątk u praw ie każdego w ięcej przestrzeni nazw.
pliku z klasam i C # . S y st e m. Wi ndows. For ms to przestrzeń
nazw. W iersz u s i n g S y st e m. Wi ndows. For ms um ożliw ia
program ow i korzystanie z w szelkich dobrodziejstw w niej Gdybyś na początku pliku nie
u s ing, to każde odwołanie do czegokolwiek by
dostępnych. W tym p rzy p ad k u p rzestrzeń nazw u d o stęp n ia należącego do tej p r z e s u w na.zw^ mu s iałoby
liczne elem en ty w izualne, takie ja k przyciski i form ularze. zosta ć poprzedzone przez S ds te m ■Wlndows■■:o^ms.

IDE WYBIERA PRZESTRZEŃ N A ZW ZA CIEBIE. P .


P ?strzen le nazw pozwalają na stosowcrnie
T o je st p rzestrzeń nazw, k tó rą ID E utw orzyło za C iebie, w ybrało Tych sam ych nazw w różnych cz ę śc iach
ją n a podstaw ie nazwy p ro jek tu . Je st w niej um ieszczony cały um iesm zóne iw t j s a m ^ nie zo sta ły
kod program u.

KOD PRZECHOWYWANY JEST W KLASACH.


Tej kon k retn ej klasie n a d an o nazw ę Progr am. Z o sta ła ona
utw orzona przez ID E i zaw iera kod, który u ru c h a m ia aplikację Z technicznego punktu widzenia istnieje
możliwość utworzenia w j e dnym programieich
i w yświetla form u larz o nazw ie Forml .
kilku metod Main() i w skazania, która z nich
ma być punktem wej ścia p n g rw iJ.--
Na raz ie jednak nie będziesz musia ł robić
W KODZIE M O ŻN A W YRÓŻNIĆ JEDNĄ METODĘ,
KTÓRA POSIADA TRZY INSTRUKCJE. S, czegoś ta kiego .

P rzestrzeń nazw zaw iera klasy, a te zaw ierają m etody. W ew nątrz Każdy program C#
każdej z tych ostatn ich zn ajduje się zb ió r instrukcji. W tym
p ro g ram ie instrukcje odpow iedzialne są za u ru ch am ian ie musi mieć dokładnie
fo rm ularza listy kontaktow ej. Ju ż wiesz, że ro lą m e to d jest
w ykonywanie czynności — k ażd a coś robi.
jedną metodę Main().
Jest ona punktem
KAŻDA APLIKACJA POSIADA SPECYFICZNĄ wejścia w Twoim
METODĘ N A ZYW A N Ą PUNKTEM WEJŚCIA.
K ażda aplikacja C # musi m ieć d okładnie je d n ą m etodę
kodzie.
o nazw ie Main. P om im o tego, że w skład Tw ojego p ro g ram u
m oże w chodzić w iele m eto d , tylko je d n a z nich m oże być Kiedy uruchamiasz
w ykonyw ana jak o pierw sza. Je st to w łaśnie m e to d a Main. C #
spraw dza wszystkie klasy w Tw oim kodzie w poszukiw aniu swój kod, instrukcje
m etody zadeklarow anej jak o s t a t i c v o i d M a i n ( ) . Później,
podczas u ru ch am ian ia p ro g ram u , w ykonyw ana je st pierw sza
w metodzie Main()
instrukcja w tej m eto d zie, a w dalszej kolejności instrukcje wykonywane są jako
um ieszczone poniżej.
PIERWSZE.
jesteś tutaj ► 135
Klasowe rzeczy

Możesz zmienić punkt wejścia programu


O ile tylko Twój p rogram m a p u n k t w ejścia, nie m a znaczenia, w jakiej

r
klasie została u m ieszczona m e to d a p u n k tu wyjścia ani co robi. W jej
d z ia ła n iu n ie m a n iczeg o ta je m n ic z e g o a n i m ag ic zn e g o , p o d o b n ie ja k Z r ó b to !
w d z ia ła n iu sam ej aplik acji. M o ż esz to so b ie u d o w o d n ić , z m ie n iają c
p u n k t w ejścia p ro g ra m u .

P rzejdź z p o w ro tem do plik u P r o g r a m . c s i zm ień nazw ę m eto d y Main


n a NotMain. Spróbuj teraz zbudować i uruchomić p ro g ram . C o się stanie? Kliknij prawym przyciskiem
m yszy projekt w oknie
Solution Explorer,
U tw órzm y te ra z nowy p u n k t wejścia. Dodaj nową klasę A n o th e r C l a s s .c s .
a następnie wybierz opcje
Z a d a n ie to sprow adza się do kliknięcia praw ym przyciskiem myszy nazwy „Add" i „C lass...“.
p ro je k tu w o knie Solution Explorer i w y b ran ia A d d > > Class... N adaj nowej
klasie nazw ę A n o th e r C l a s s .c s . ID E d o d a ją do p ro g ra m u i nazw ie zgodnie J
z Tw oim życzeniem . T a k będzie w yglądał utw orzony p rzez ID E plik:

us i ng System; Te standardowe w iersze using


zo sta ty dodane do pliku.
us i ng S y s t e m . C o l l e c t i o n s . G e n e r i c ;
us i ng Syst em. Linq;
us i ng S yst e m. Te xt ;
us i ng S y s t em. Th r e a d i ng . T a s ks ; Ta k/asa znajduje s ię w przestrzeni
nazw, którą IDE dodało podczas tworzenia
projektu Windows Forms Application.
namespace Rozdzi al _2 Program_4
{
c l a s s Anot herCl as s
{
} IDE autom atycznie nazwało
Klasę, bazując na nazwie pliku.
}

D odaj n a p o czątk u plik u nowy w iersz u s i n g : using System.Windows.Forms;.


N ie zapom nij o śred n ik u n a jego końcu!

D odaj now ą m e to d ę do klasy AnotherClass , w pisując ją p om iędzy naw iasam i klam row ym i:

c l a s s Anot herCl as s
{
MeSsageBox to klasa um ieszczona
p u b l i c s t a t i c voi d Main()
w przestrzeni nazw S y s tem .Windows-f° rm s.
To dlatego m usiałeś dodać w iersz u s 'ng —v {
w punkcie 3. SShow () to met
h ow() m e M a , która Je s t
częścią klasy M essageBo*. MessageBox.Show("Bum!")
}
}
C# zwraca uwagę na wielkość lite r! Upewnij się, że wielkość lite r w Twoim kodzie
jest dokładnie taka sama ja k w przykładzie.
136 Rozdział 2.
To tylko kod

Klasyczne aplikacje Windows używają metody


MessageBox.Show(), by wyśw ietlać małe okienka
z kom unikatami lub ostrzeżeniam i.

Co się stało ?
Z am ia st u ru ch o m ien ia aplikacji, k tó rą napisałeś, pojaw iło się
o k ienko z k o m u n ik atem . Pisząc m e to d ę M a i n ( ) , wyposażyłeś
p ro g ram w now y p u n k t w ejścia. T e ra z pierw szą rzeczą, k tó rą robi,
je st w ykonanie instrukcji w tej m eto d zie — oznacza to , że zostanie
w ykonana instru k cja Me s sa g e B o x . Sh o w( ) . N ie m a w tej m etodzie
nic innego, zatem kliknięcie przycisku O K spow oduje w yczerpanie
się listy instrukcji i zakończenie p ro g ram u .

Wymyśl sposób n a nap raw ien ie p ro g ram u , aby znów wyświetlał się Podpowiedź: wys t arczy ,
form ularz. że zm ien isz dwa w iersze

„Zaostrz ołówek
Rozwiązanie
Uzupełnij komentarze tak, aby opisywały one poszczególne wiersze
kodu C#. Uzupełniliśmy pierwsze pole za Ciebie.

Klasy C# używ ają ty ch w iersz]ś


using, aby dodać metody
u sin g System ; z innych przestrzeni nazw.
u sin g System .Linq;
u sin g System .T ext;
u sin g System.Windows.Forms;
Cały kod m ieści s ię w klasach,
w ięc program potrzebuje tutaj
namespace SomeNamespace jednej z nich. Ta klasa ma jedną
metodę. M etoda ta nosi
{ nazwę DoSomething,
a je j wykonanie
c la s s MyClass { spowoduje w yśw ietlenie
okienka informacyjnego.
p u b lic s t a t i c v o id DoSomething() {
MessageBox.Show("To j e s t wiadomość

}
To j e s t instrukcja .
} Jej wykonanie
spow oduje w yśw ietlenie
} n iewielkiego okienka
z komunikatem.

jesteś tutaj ► 137


Kopmy głębiej

Kiedy zmieniasz coś w ID E, zmieniasz także swój kod


ID E je st d o b re w p isan iu kodu, ale nie w ierz nam n a słowo. O tw órz V isual Studio,
utwórz nowy projekt Windows Forms Application i zobacz wszystko n a w łasne oczy. ^ %
Z r ó b to!
OTW ORZ KOD WYGENEROW ANY PRZEZ FORM DESIGNER.
O tw órz plik Form1Designer.cs. Tym razem , zam iast o tw ierać go p rzez F o rm D esigner,
kliknij praw ym przyciskiem myszy p lik w o knie Solution Explorer i w ybierz View Code.
P o p atrz n a sposób deklaracji klasy:

partial c l a s s Forml : Form

OTW ÓRZ FORM DESIGNER I DODAJ KONTROLKĘ PICTUREBOX DO FORMULARZA.


Przyzwyczajaj się do pracy z w iększą liczbą k art. Przejdź do o k n a Solution Explorer i otw órz F o rm D esigner
p o p rzez d w u krotne kliknięcie pliku Form1.cs. Przeciągnij nową kontrolkę P i c t u r e B o x n a form ularz.
K o n tro lk a P i c t u r e B o x wyświetla o braz, k tóry m o żn a zaim portow ać z plik u n a dysku.

Zaznacz przycisk opcji ,L ° cal fescnunce" i kliknij


przycisk Import, by w yśw ietlić olmo dia h g owe
pozwalające zaimportować p lik graficzny do projekt u .
Ot>razek w yśw iet lany w kontrolce PictureBox m ożesz
w ybrać zaznaczając ją i klikając tącze „Choose Im age...
w oknie Propert ies . Spow oduje to w yśw ietlenie okna v
pozwalają cego na wytiranie i wczytanie obrazka. Można -— Import... — Clear
wybrać dowolny plik g raficzny dostępny na komputerze!
' i \ \\
ZNAJDZ I ROZW IŃ KOD WYGENEROW ANY PRZEZ FORM DESIGNER
DLA KONTROLKI PICTUREBOX.
P rzejdź do zakładki Form1.Designer.cs, przew iń ją w dół i spróbuj o d naleźć tak i w iersz kodu:

Kliknij ten znak plus.

Windows Form Designer generated code|

Kliknij + p o lewej stronie wiersza, aby rozw inąć kod. Przew iń ek ran w dół i odszukaj następujący fragm ent:
// J eśli dwukrotnie klikniesz Forml.resx w oknie Solution Explorer, zobaczysz
'' zaimportowany obrazek. IDE zaimportowało go i nadało mu nazwę „pictureBoxl.Image"
/ / pictureBoxl — poniżej przedstaw iliśm y kod, który zo s ta ł wygenerowany w celu wczytania
// i wyśw ietlenia tego obrazka w kontrolce PictureBox.

t h i s .p i c tu re B o x l .I m a g e = ((System.Drawing.Image)(resources.GetObject("pictureBox1.Image")));
t h i s . p i c t u r e B o x l . L o c a t i o n = new S y s t e m. D r a w i n g . P o i n t ( 2 7 6 , 28);
Nie zwracaj uwagi na to,
this.pictureBoxl.Name = "pictureBoxl"; że liczby w wierszach b ize
t h i s . p i c t u r e B o x l . S i z e = new S y s t e m . D r a w i n g . S i z e ( l 0 0 , 50 ) ; i Location Twojego k° duQ ,
s ą nieco inne od tych. B ędą
t h i s . p i c t u r e B o x l . T a b l n d e x = 0; s ię one zmieniać zaleznie od
miejsca, w którym umieści feś
this.pictureBoxl.TabStop = false;
kontrolką PictureBox.

138 Rozdział 2.
To tylko kod

Zaraz, zaraz! Co tam jest napisane?


Przew iń ek ran do góry dosłow nie n a m in u tę. Z obacz, co je st n ap isan e n a samym
W iększość komentarzy
p o czątku sekcji w ygenerow anej przez W indow s F o rm D esigner: z aczyna s ię tylko dwoma
ukośnikami ( / / ) . IDE
/// <summary> posłu g u je się czasem notacją
z trzem a. ^
/// R e q u ire d m ethod f o r D e s ig n e r s u p p o r t - do n o t m o d ify
/// th e c o n te n ts o f t h i s m ethod w ith th e code e d i t o r .
/// </summary> S ą to komentarze XML, których
m ożesz używać do tw °rzenia
dokumentacji s w o ^ todu.
N ie m a nic bardziej atrakcyjnego dla dziecka niż w ielki napis „N ie dotykaj teg o !”. N ie D owiesz s ię wię cej na ich_
daj się długo prosić, m usisz u lec p o k u sie ... Z m odyfikujm y zatem zaw artość tej m etody, te m at w punkcie 2 . dodatku
„Pozosta ło ści" na końcu książki.
używ ając do tego ed y to ra kodu! Dodaj do formularza przycisk o nazwie b u t t o n l
(będziesz musiał w tym celu przejść do projektanta formularzy), a potem zrób tak:

ZMIEŃ FRAGMENT KODU, KTÓRY USTAWIA WARTOŚĆ WŁAŚCIWOŚCI


TEXT DLA PRZYCISKU BUTTON1. JAK MYŚLISZ, CO STANIE SIĘ Z OKNEM
PROPERTIES W IDE?
Zaryzykujm y — zobaczymy, co będzie! P rzejdź te ra z do o k n a Form Designer i spraw dź właściwość
Nie m usisz
Text. Z m ien iła się? zapisyw ać
formularza ani
POZOSTAŃ W OKNIE FORM DESIGNER I UŻYJ O KNA PROPERTIES, uruchamiać
BY ZMIENIĆ WŁAŚCIWOŚĆ NAME N A DOW OLNĄ IN N Ą WARTOŚĆ. programu, aby
dostrzec zmiany.
Spraw dźm y, czy po trafisz znaleźć sposób, w jaki ID E zm ienia w łaściwość N a m e . Z n ajd u je się
Po prostu
o n a na sam ej górze o k n a Properties p o d nazw ą „ (N am e)”. Co się stało z ko d em ? Co się stało wprowadź
z um ieszczonym w kodzie k o m en tarzem ? inne wartości
w edytorze kodu,
ZMIEŃ KOD, KTÓRY USTAWIA WŁAŚCIWOŚĆ LOCATION, N A WARTOŚĆ (0 ,0 ) a następnie
przełącz s ię na
ORAZ WŁAŚCIWOŚĆ SIZE TAK, ABY PRZYCISK STAŁ SIĘ NAPRAWDĘ DUŻY. kartę projektanta
Czy to działa? formularzy —
zmiany powinny
PRZEJDŹ Z POWROTEM DO O KNA FORM DESIGNER I ZMIEŃ WARTOŚĆ być wprowadzone
natychm iast.
WŁAŚCIWOŚCI BACKCOLOR N A JAKĄŚ IN N Ą.
Przyjrzyj się dokładnie, jak w ygląda k o d plik u Form1.Designer.cs. Czy zostały d o d an e jakieś w iersze?

Zawsze jest ła tw ie j modyfikować kod generowany przez narzędzie Form Designer przy użyciu IDE,
jednak w szystkie zm iany wprowadzane w ten sposób i ta k stają się w efekcie zmianami w kodzie
źródłowym programu.
iNie .istnieją. .
- głupie pytania -------------------------
P: Nie do końca rozumiem, czym jest punkt wejścia. pogrupowane i należą do różnych klas. A zatem skąd wiadomo,
od której instrukcji zacząć wykonywanie programu?
Czy możecie mi to jeszcze raz wytłumaczyć?
Właśnie do tego celu służy punkt wejścia do programu. Kompilator
O: Twój program składa się z bardzo wielu instrukcji, jednak nie skompilowałby kodu programu, gdyby nie było w nim
nie wszystkie są wykonywane jednocześnie. Realizacja dokładnie jednej metody M a in ( ), to właśnie ona jest nazywana
programu rozpoczyna się od jego pierwszej instrukcji, która jest punktem wejścia. Działanie programu rozpoczyna się
wykonywana, następnie wykonywana jest druga instrukcja, od wykonania pierwszej instrukcji metody M a in ( ).
potem trzecia i tak dalej. Wszystkie te instrukcje są zazwyczaj

jesteś tutaj ► 139


Ach, pięknie!

Tworzenie animacji w klasycznych aplikacjach Windows jest znacznie trudniejsze niż w aplikacjach dla Sklepu
Windows. Aby się o tym przekonać, zróbmy coś błyskotliw eg o! Zacznij od utworzenia w IDE nowego
Ćwi czenie projektu typu W ind ow s Forms A pplication.

TO JEST FORMULARZ DO NAPISANIA.

M n nf : ¿e0e« d e“_a^s! S ł a : ; : a e ; n ąn t:e ta ;ą;? a tp di|otto:‘jeddnle .


— for 0 "• ł. . . i.amodnie z powyższym , jeśli dwie
nawia s°m iklam row dm ! tej pę .j. e r n e i t o albo m u sisz zadeklarowad j ą

mógł deklarować je j ponownie w ewnątrz nich.


e>
Powiększ przycisk,
SPRAW, ABY FORMULARZ W YGLĄDAŁ wskazując uchwyt w rogu
PSYCHODELICZNIE! i przeciągając go.
Spraw , by k o lo r tła fo rm u larza zm ieniał się p o kliknięciu
przycisku! Stw órz p ętlę, k tó ra będzie zm ieniać w artość zm iennej c ,
przypisując jej k olejno w artości z zak resu o d 0 do 253. T a k będzie
w yglądał frag m en t k o d u um ieszczony p om iędzy naw iasam i
klam rowymi:
- c, c ) ;
t h i s . B a c k C o l o r = C o l o r . F r o m A r g b ( c , 255
Application.DoEven t s ( ) ;

A a -t'-
wyglądu formularza, sprawdzenie Z J ■ J odśw ieżenie V om M m ó ' 5* ’
czegoś m yszką, itd. Spróbuj go ^ s u n a ó ^ nfa klikn^
co S ię Stanie. Wygląd formularza n Z h J ? r09rarnu <zobacz, „ p po® * j ak
program, za m ia st odpowiadać na z d n ^ zrnienic,t- gdyż
zakończenie pętli. ° zdarzeń,a, będzie czeka t na

Na razie s korz y s ta s z z wywołania Appiication.DoEventa()i r i w i e r


by zapew nić pop rawne działanie formularza — by odpowiaddł on
m zdarzenia ay atem owe podczas wykonywania pętli. N iem nie
jednak j e s t to pewnego rodzaju sztu czka , której nie powinieneś
stosow ać nigdzie z wyjątkiem takich małych testow ych programów \ n\eb\sstóe 80 '
jak te n .' W M szej a ę ś c ’ ksią;żki poznasz znacznie lepsze sposobu
pozwalające programom wykonywać wiele czynności jednocześnie.

^ ZW OLNIJ NIECO.
Z m niejsz nieco te m p o błyskania, do d ając taki oto w iersz
po A p p l i c a t i o n . D o E v e n t s ( ) :

System.Threading.Thread.Sleep(3);
Ta instrukcja P ° ^ J % ^ S u n d y .

O
’ X & . & v s s s 'j S '

140 Rozdział 2.
To tylko kod

Pamiętaj, że w celu utworzenia kiasy cznej ^ap iikacji


typu Windows Forms A p p m usis z korzy sta ć z V isu a l
Studio for Windows D esktop.

[4 SPRAW, ŻEBY PRZEJŚCIA BYŁY PŁYNNE.


Z róbm y tak , aby sekw encja kolorów w racała do p u n k tu , w którym się rozpoczęła.
D odaj d ru g ą p ętlę , w której zm ien n a c będzie przyjm ow ać w artości z zakresu
o d 254 do 0. W naw iasach klam row ych użyj tego sam ego kodu.

NIECH PĘTLE DZIAŁAJĄ CAŁY CZAS.


Gdy ja ka ś
O tocz n ap isan e wcześniej dwie p ę tle jeszcze jed n ą, k tó ra będzie w ykonyw ana cały pętla zawiera
czas i nigdy się n ie zatrzym a. W te n sposób, kiedy przycisk zostanie w ciśnięty, tło s ię w drugiej,
wtedy mówimy
zacznie zm ieniać kolory. (P odpow iedź: p ę tla w h i l e ( t r u e ) m oże posłużyć jako o niej „pętla
p ę tla nieskończona!). ¡wewnętrzna"

O j, oj! Program nie chce się zatrzymać!

Uruchom swoją aplikację w IDE i rozpocznij wykonywanie pętli. Teraz zamknij okno.
Poczekaj minutę — IDE nie przechodzi w tryb edycji kodu! Zachowuje się tak, jakby
program cały czas działał. Musisz zatrzymać go, klikając przycisk stop z kwadratem
(lub wybrać opcję Stop Debugging z menu DEBUG).

6) ZATRZYMAJ GO.
Spraw , aby p ę tla d o d a n a w p u n k cie 5. kończyła się p o zam knięciu
p rogram u . Z m ień zew nętrzną p ę tlę n a następującą:

w h ile (V is ib le ) Podpowiedź: operator &&


U ru ch o m te ra z aplikację i naciśnij przycisk X w rogu. O k n o się znaczy „i" To za jego pomocą
m ozesz potączyć kilka
zam knie, a p ro g ram się zakończy! C h o ciaż... n astąp i pew ien odstęp warunków w jedno duże
czasowy, zanim ID E przełączy się z p o w ro tem w tryb edycji. wyrażenie warunkowe. B ędzie
ono prawdziwe wtedu, ody
zarówno pierw szy jego czton,
^ w a ^ ć logiczną, na przykład ja k i drugi, trzeci i kolejne
y ę b l e . w m s t r u ^ if lub w pętli, może kusić
oędą prawdziwe. Okaże s ię
j e d n u yciek notacji (V isible == true). Może s z To pomocne w rozwiązaniu
je ° nak catkowicie pominąć true" — w ystarczy
um igści ćtam wartość logiczną. poniższego problemu.

/
Podczas pracy z formularzem
lub kontrolką wartość Visible
decyduje o tym, czy s ą one
widoczne. Jeśli u sta w isz tę Czy domyślasz się, co może powodować takie opóźnienie?
wtaściwość na false, kontrolka Czy możesz poprawić program w ta k i sposób, aby kończył
lub formularz znikną.
się natychmiast po zamknięciu okna?

jesteś tutaj ► 141


W służbie Tobie

Gdy IDE tworzyło tę metodę, przed nawiasem Mamirowym


dodato także znak przejścia do n °wego wiersza. Czas ami
Rozwiązanie wstaw iam y nawias klamrowy w tym samym w ierszu, aby
zaoszczędzić m iejsca — C# nie przej muj e s ię ż adny m
ćwiczenia dodatkowym m iejscem, więc j e s t to catkiem popraw ne.
Czasami nie pokazujem y Ci catego kodu
rozw iązania, a tylko te fragmenty, które uległy Spój ność i kons ekwencj a s ą bardzo ważne, gdyż ułatwiają
zm ianie. Cata h gika projektu „Coś błyskotliwego“ innym czyt anie i analizę kodu. My jednak celowo
zaw iera s ię w m etodzie buttonlJC lickO , którą IDE pokazuj em y t u różne s pos° by jego formatowania, gdyż
dodato w momencie dwukrotnego kliknięcia przycisku m u sisz przyzw yczaić s ię do analizowania kodu pisanego
w projektancie formularzy. p rze z różnych programistów.

\
p r i v a te void b u tto n l C lic k (o b je c t sender, EventArgs e ) M

/ whi l e (Visible) {
Zew nętrzna
pęt/a cbiata, for (int c = 0; c <= 2 5 4 && V i s i b l e ; c++)l {
dopóki formu/arz
je s t w
Zaraz t h i s . B a c k C o l o r = Colo r.From A rgb(c, 255 - c , c);
z amknięciu
właści A pplication.D oEvents();
V isib/e zm ienia Pierw sza pętla zmienia
wartość na fa/se kolory w jedną stronę, druga
i pęt/a System .Threading.Thread.Sleep(3); odwraca kolejność. Dzięki temu
p rzestaje być animacj a j e s t płynna.
wykon
}
Z astosow
wyrażenie for (int c = 254; c >= 0 && V i s i b l e ; c--) {
Visible, a nie
&& V isib t h i s . B a c k C o l o r = Colo r.From A rgb(c, 255 - c , c);
true. To taka
różnica, j
za m ia st p A pplication.D oEvents(); Naprawiliśmy problem dodatkowego
„Czy to widać?“ opóźnienia za pomocą o p i o r ą
dzięki któremu w każdej p ętli for
zadać py System .Threading.Thread.Sleep(3);
„Czy to prawda, sprawdzana j e s t wtaściw ość y i^ b to .
że to je s W ten sposób pętla isończy s ^ zriraz
widoczne } gdy wtaściwość ta p rzyjm ą wartość fa lse .
w obu przypadkach
znaczeni } Czy domyślasz się, co może powodować takie opóźnienie?
takie samo.
Czy możesz poprawić program w ta k i sposób, aby kończył
} się natychmiast po zamknięciu okna?

O p ó źn ien ie spow odow ane je st tym , że p ę tle w ew nętrzne m uszą


zakończyć sw oje działanie, zanim p ę tla w h i l e pono w n ie spraw dzi w artość
właściwości V i s i b l e . M ożesz to p opraw ić p rzez d o d an ie && V isib le
do instrukcji w arunkow ej w każdej pętli.

Czy Twój
i kod ubył
y i nieco iinny
n n y niż nasz? Istnieje więcej
v v i ę v _ c | niż jeden sposób na
iii

rozwiązanie każdego problemu programistycznego — mogłeś na przykłć przykład


użyć pętli while zamiast f o . Jeśli Twój program działa, to znaczy, że ćwiczenie
wykonałeś poprawnie!

142 Rozdział 2.
3. Obiekty: zorientuj się!

Tworzenie kodu ma sens

Każdy pisany przez Ciebie program rozwiązuje jakiś problem. Rozpoczynając


pisanie programu, zawsze warto zastanowić się, jaki problem ma on rozwiązywać. Właśnie
do tego przydają się obiekty. Pozwalają one tworzyć strukturę kodu tak, by odpowiadała ona
rozwiązywanemu problemowi, dzięki czemu będziesz mógł skoncentrować się na nim samym,
a nie na mechanice tworzenia kodu. Prawidłowe użycie obiektów spowoduje, że proces pisania
kodu stanie się bardziej intuicyjny, a jego późniejsza analiza i modyfikacja — znacznie łatwiejsze.

to jest nowy rozdział ► 143


M aciek podróżuje

W jaki sposób Maciek myśli o swoich problemach


M aciek je st p ro g ram istą, któ ry w łaśnie w ybiera się n a rozm ow ę kwalifikacyjną.
N ie m oże się już doczekać chwili, w której z ap rezen tu je sw oją doskonałą znajom ość
języka C # . Je st je d e n p ro b lem — m usi się tam najpierw dostać, a je st ju ż późno!

Maciek próbuje znaleźć drogę, którą dojedzie na rozmowę.

PRZEJADĘ PRZEZ
MOST PONIATOWSKIEGO, POTEM
SKIERUJĘ SIĘ N A ULICĘ WOLNOŚCI
I PRZEJADĘ PRZEZ ALEJĘ LOTNIKÓW.
Maciek wyzn a czyt cel
■' znalazł drogę.

Dobrze, że miał włączone radio. Usłyszał o dużym korku


na drodze, przez który na pewno by się spóźnił!

Maciek d o staf nową WITA PAŃSTWA FRANEK GŁOŚNY Z NASZYM


informacją ° ulicy, CODZIENNYM RAPORTEM O STANIE DRÓG.
której musi unikać.
W ZW IĄZKU Z KARAMBOLEM N A ULICY
WOLNOŚCI POWSTAŁ KOREK, KTÓRY CIĄGNIE
SIĘ AŻ DO ULICY KOPERNIKA.

,3 ) Maciek wymyśla nową drogę, którą dostanie


się na rozmowę w założonym czasie.

144 Rozdział 3.
Obiekty: zorientuj się!

W jaki sposób system nawigacyjny w samochodzie Maćka


rozwiązuje jego problemy
M aciek zbudow ał swój w łasny system nawigacyjny G PS, To j e s t diagram klasy N a v ig a to r
któ reg o używ a do p o ru szan ia się p o mieście. programu Maćka.
Pokazuje je j nazwę na SetCurrentLocation()
górze i metody na d°le . SetDestination()
ModifyRouteToAvoid()
ModifyRouteToInclude()
SetD estination("skrzyżow anie P o w s t a ń c ó w Wars zawy i Legionów");
GetRoute()
strin g route; To j e s t wynik dziafania GetTimeToDestination()
r n i i t p = R p tR o u tp M - m e t o d y G etR o u teO -~ j e s ^ TotalDistance()
route betKOUtey, fa ń c u c h znaków, który _
zaw iera w szystkie punkty
na drodze do celu.

S y s te m nawigacyj-nydronę " J e d ź p r z e z most P o n i a t o w s k i e g o , u l i c ą Wolności


ustala cel i zwiram dmgę .
a l e j ą Lotnikow"

S y s tem nawigacyjny
pobiera dodatkową
informację o miejscach
których m usi unikać.

ModifyRouteToAvoid("ulica W olności");

Teraz może odnaleźć


nową drogą do celu-

string route;
route = GetRouteQ ;

"Jedź u l i c ą M ickiewicza, p r z e z m o s t P a r k o wy , ulicą Poznańską"

t GetRo ute() zwraca


nową drogę bez
ulicy , Idórą Maciek
chciał ominąć.

System nawigacyjny Maćka rozwiązuje


problem w ten sam sposób co on.
jesteś tutaj ► 145
Ustal m etody i zm odyfikuj trasy

Klasa Navigator napisana przez Maćka


posiada metody do ustalania i modyfikacji tras
K lasa N a v ig a to r p o sia d a m etody, k tó re zajm ują się całą akcją. Jed n a k w przeciw ieństw ie do
m eto d typu b u t t o n _ C l i c k ( ) w utw orzonym p rzez C iebie fo rm u larzu wszystkie o n e skupiają
się n a rozw iązyw aniu pojedynczego pro b lem u : odnajdyw ania najlepszej drogi przez m iasto.
T o dlatego M aciek zgrom adził je w jednym m iejscu i um ieścił w klasie nazw anej N a v ig a to r.

M aciek zaprojektow ał klasę naw ig ato ra w taki sposób, aby łatw o było tworzyć i m odyfikow ać
trasy. A by p o b rać drogę, M aciek wywołuje m e to d ę S e t D e s t i n a t i o n ( ) służącą do u stalan ia
celu, a n astęp n ie używa m eto d y G e t R o u t e ( ) do uzyskania trasy w po staci tekstow ej. Jeśli
p o trzeb u je ją zm ienić, jego p ro g ram wywołuje m e to d ę Mo d i f y Ro u t e To Av o i d ( ) i M aciek
p o d aje m u ulicę, k tó rą trze b a om inąć. P o tem m e to d a G e t R o u t e ( ) je st w ykonywana Maciek tak dobrał nazwy
metod, aby miały one sen s
ponow nie i p o b ie ra n a jest now a trasa. dla kogoś, kto będzie chciał
korzystać z jego klas y do
public class Navigator { nawigacji po m ieście.

public void SetCurrentLocation(string locationName) { ... }


public void SetDestination(string destinationName) { }
public void ModifyRouteToAvoid(string streetName) { }
public string GetRoute() { ... }
}
może
ty M art°SĆ, z a p is u ac te .k tf s trin g route =
do zm iennej typu strina. tekS* zauj'eraj<icy trasą
nic nie zwraca. typem j e s t yoid, to metoda GetRouteQ;

Niektóre metody zwracają wartość


K ażda m eto d a składa się z instrukcji, k tó re coś robią. N ie k tó re m eto d y zwyczajnie
w ykonują instrukcje i kończą swoje działanie, inne p o siad ają je d n a k wartość wynikową
lub w artość, k tó ra jest obliczana albo gen ero w an a w ew nątrz m etody, a n astęp n ie To j e s t przyktad metody,
przesyłana z pow rotem do w yrażenia, k tó re ją wywołało. Typ w artości wynikowej w której określono typ
wartość i wynikowej
(na przykład s t r i n g lub i n t ) nazyw am y typem wynikowym . — zw raca ona wartoSC
typ u int. Metoda używa
Instru kcja return pow oduje natychm iastow e zakończenie Twojej m etody. Jeśli m eto d a dwóch param etrów do
niczego nie zw raca — czyli jeśli w jej deklaracji jak o typ w artości wynikowej p o d a n o obliczenia wyniku.
void — to instrukcja r e t u r n kończy się średnikiem . C o w ięcej, w takim p rzy p ad k u jest
o n a o p cjonalna i nie trz e b a jej um ieszczać w m etodzie. Jeśli je d n a k w deklaracji m etody
p o d an o jakiś typ w artości w ynikowej, to użycie instrukcji r e t u r n je st konieczne.

p u b l i c i n t M u l t i p l y T wo N u m b e r s ( i n t f i r s t N u m b e r , i n t secondNumber) {
i n t r e s u l t = f i r s t N u m b e r * secondNumber; Ta instrukcja return przekazuje
return result; wartość z powrotem do instrukcji
która wywołała metodę.
}
W wywołaniu metody można
A tu jest w yrażenie, k tó re wywołuje m eto d ę do m n o żen ia dwóch liczb. Z w raca ona przekazać liczby 3 1 5 . Można
w artość typu int: także przekazywać do m ej
w artości, używając zmiennych.
i n t my Re s u l t = Mu l t i pl y T wo N u mb e r s( 3 , 5 ) ;

146 Rozdział 3.
Obiekty: zorientuj się!

CELNE SPOSTRZEŻENIA

■ Klasy zawierają metody, a te z kolei zawierają instrukcje wykonujące pewne operacje. Dobierając sensowne i opisowe nazwy
metod, możesz tworzyć klasy, które będą łatwe w użyciu.

■ Niektóre metody mają określony ty p w a rto ści w y n ik o w e j . Jest on określany w deklaracji metody. Na przykład metoda,
której deklaracja zaczyna się od p u b l i c i n t , zwraca wartość całkowitą typu i n t . A to przykład instrukcji zwracającej
wartość całkowitą: r e t u r n 37.

■ Jeśli w deklaracji metody określono jakiś typ wartości wynikowej, to musi ona zawierać instrukcję r e t u r n , a co więcej
- instrukcja ta musi zwracać wartość, której typ odpowiada typowi użytemu w deklaracji. A zatem jeśli deklaracja
metody zaczyna się od p u b l i c s t r i n g , to w kodzie metody będziesz musiał umieścić instrukcję r e t u r n zwracającą
łańcuch znaków.

■ Natychmiast po wykonaniu instrukcji r e t u r n program wróci do instrukcji, która wywołała metodę.

■ Nie wszystkie metody muszą zawierać określenie typu wartości wynikowej. Metoda, której deklaracja zaczyna się
od p u b l i c v o id , niczego nie zwraca. W takim przypadku także można użyć instrukcji r e t u r n , by bezzwłocznie
zakończyć metodę; oto przykład: i f ( c h c e S k o n c z y c ) { r e t u r n ; }.

Wykorzystaj to, czego się nauczyłeś,


do napisania prostego programu używającego klas
Z r ó b to !
Połączm y fo rm ularz z klasą i sprawm y, aby przycisk wywoływał jej m etodę.

U tw órz w ID E nowy p ro jek t W indow s F o rm s A p plication. N a stęp n ie dodaj do niego p lik klasy i nazwij go
T a l k e r . c s . W tym celu kliknij praw ym przyciskiem myszy p ro je k t w o knie Solution Explorer i w ybierz Class...
z m en u A d d . P o tym kiedy w piszesz nazw ę „T alker.cs”, ID E autom atycznie utw orzy nowy plik o p o d an ej nazwie,
um ieści w nim początkow y k o d klasy T a l k e r i otw orzy go w nowej zakładce.

D odaj w iersz u s i n g S y s t e m. Wi n d o ws . F o r ms ; n a sam ej górze pliku z kodem klasy.


D opisz do klasy następ u jący kod:

c la s s Talker {
p u b lic s ta tic in t BlahBlahBlah(string thingToSay, in t numberOfTimes) {
W tym m iejscu
deklarujemy s trin g fin a lS trin g =
z mienną finalString fo r ( in t count = 1; count <= numberOfTimes; count++) {
i ustaw iam y
je j wartość fin a lS tr in g = fin a lS tr in g + thingToSay + M\n ";
Ten w iersz kodu dodaje do
początkową na
} tańcucha znakóiw przechowywanego
p u sty łańcuch
w zm iennej f ina/String zaw artość
znaków. MessageBox.Show(finalString); zm iennej thing To S a y oraz znak
return fin a lS trin g .L e n g th ; nowego w iersza.

} Metoda BlahSlahSlahO zwraca wartość typu To j e s t tak zwana w taściw ość .


int określającą całkowitą długość w yśw ietlanej Każdy łańcuch znaków posiada
} w taściw ość Length. Podczas
wiadomości. M ożesz dodać „.L ength“ do każdej ^—
zm iennej typu string w celu pobrania długości tekstu . określania długości tańcucha "\n"
j e s t liczone jako jeden znait..
- ► Przewróć kartkę, by kontynuować!
jesteś tutaj ► 147
W prowadzenie do obiektów

+
Co właśnie stworzyłeś
N ow a klasa zaw iera je d n ą m e to d ę B l a h B l a h B l a h ( ) , k tó ra p o b ie ra dwa param etry.
*
A by w yłączyć
Pierwszy je st typu s t r i n g i decyduje o tym , co zostanie pow iedziane. D ru g i określa, ile
przycisk m inim alizacji
razy trzeb a to pow iedzieć. W m o m en cie w ywołania w yśw ietlane je st o k n o MessageBox i m aksym alizacji
z w iadom ością p o w tó rzo n ą o k reślo n ą liczbę razy. W arto ścią wynikow ą jest długość fo rm u la rza , przypisz
w yśw ietlanego tekstu. M eto d a w ym aga p o d a n ia w artości tekstow ej dla p a ra m e tru w artość False jego
t h i n g T o S a y i liczby całkow itej jak o p a ra m e tru number Of Ti mes. O b a b ę d ą p o b ieran e właściw ościom
z form ularza. W ykorzystam y k o n tro lk ę TextBox, k tó ra pozw ala n a w pisanie tekstu, M axim izeBox oraz
o raz k o n trolkę NumericUpDown do u sta le n ia liczby pow tórzeń. M inim izeB ox.

A te ra z dodajm y form ularz, który skorzysta z naszej now ej klasy.


Ustaw domyślny napis
kontrolki TextBox na „W itaj! ,
używ ając w łaściw ośd Tex t
U tw órz taki form ularz.

N astęp n ie kliknij dw ukrotnie m yszką przycisk i wpisz poniższy kod, który wywoła m etodę
B l a h B l a h B l a h ( ) i zapisze zw róconą przez n ią w artość w zm iennej l e n : To j e s t kontrolka
NtxmericUpDown. Ustaw
p r i v a t e voi d b u t t o n l _ C l i c k ( o b j e c t s e n d e r , EventArgs e) je j wtaściw ość Minimum
na h Maximum na 10,
{ natom ia st Value na 3.
i n t l en = T a l k e r . B l a h B l a h B l a h ( t e x t B o x l . T e x t , ( i n t ) numericllpDownl.Value);
MessageBox.Show("Długość wiadomości t o " + l e n ) ;

T eraz uru ch o m swój program ! Kliknij przycisk i przyjrzyj się dwóm Długość wynosi 21, gdyż
wyświetlonym okien k o m MessageBox. Pierw sze z nich, zaw ierające tekst, „Wit aj!" ma długość 6 znaków,
\ n to kolejny jeden znak;
zostało w yśw ietlone p rzez klasę, n a to m ia st drugie — przez form ularz. to w su m ie daje 7x3 = 21.-

Kiedy metoda
z wraca wartość,
Metoda BlahBlahBlah() formularz wyśw ietla
wyśw ietla to okno takie oto okno O *
M essageBox na p odstawie z komunikatem.
przekazanych jej
parametrów.

Możesz dodać klasę do projektu i pozwolić,


by inne klasy wchodzące w jego skład mogły
korzystać z jej m etod.

148 Rozdział 3.
Obiekty: zorientuj się!

TO BY BYŁO GENIALNE,
Maciek ma pewien pomysł GDYBYM MÓGŁ
PORÓWNYWAĆ KILKA TRAS
R ozm ow a kw alifikacyjna poszła świetnie! P oranny
I ROZSTRZYGAĆ, KTÓRA
k o rek zm usił je d n a k M aćka do zastanow ienia się
JEST NAJSZYBSZA.
n ad ulep szeniem naw igatora.

Może on utworzyć trzy klasy nawigatora...


M aciek mógłby skopiow ać k o d klasy N a v ig a to r i w kleić go do dw óch innych klas.
P rogram m ógłby w tedy przechow yw ać trzy trasy jednocześnie.
Ta ta b elka to diagram klasy.
Pokazuje on wszystkie
jej metody i je s t fatwym
sposobem na określenie jej
przeznaczenia. Wystarczy
N a v ig a to r
r z u c ić okiem -

i
SetCurrentLocation()
N a v ig a to r 2
SetDestination()
ModifyRouteToAvoid() SetCurrentLocation()
N a v ig a to r 3
ModifyRouteToInclude() SetDestination()
GetRoute() ModifyRouteToAvoid() SetCurrentLocation()
GetTimeToDestination() ModifyRouteToInclude() SetDestination()
TotalDistance() GetRoute() ModifyRouteToAvoid()
GetTimeToDestination() ModifyRouteToInclude()
TotalDistance() GetRoute()
GetTimeToDestination()
TotalDistance()

NIE M A MOWY, TO NIE MOŻE BYĆ


DOBRE! CO WTEDY, GDY ZECHCĘ
ZMIENIĆ METODĘ? MUSIAŁABYM
ZMODYFIKOWAĆ JĄ WE WSZYSTKICH
TRZECH MIEJSCACH.

Tak jest! Zarządzanie trzema kopiami tego samego kodu


nie jest dobrym rozwiązaniem. W iększość pro b lem ó w zm usza nas
do rep rezen to w an ia jed n ej rzeczy kilka razy. W tym p rzy p ad k u je st to kilka
dróg, ale m oże to być tak że kilka osób, kosm itów , plików m uzycznych lub
czegokolw iek innego. W szystkie pro g ram y m ają je d n ą w spólną cechę: zawsze
pow inny trak to w ać te sam e rzeczy w ten sam sposób, bez w zględu n a to,
ilom a ich re p rezen tacjam i b ę d ą się zajmowały.

jesteś tutaj ► 149


Na przykład..

Maciek może użyć obiektów do rozwiązania swojego problemu


Obiekty są narzędziem C # służącym do pracy z zestawem
podobnych rzeczy. M aciek m oże oprogram ow ać klasę N a v ig a to r

kluczowe new i nazwa klasy.

Navigator navigatorl = new Navigator();


navigatorl.SetDestination("Skrzyżowanie Powstańców Warszawy
^ i Legionów");
string route;
route = navigator1.GetRoute('

2 L -
Teraz możesz użyć obiektu! Gdy tw orzysz
obiekt jakiejś klasy, ma on wszystkie je j metody.

150 Rozdział 3.
Obiekty: zorientuj się!

Używasz klasy do utworzenia obiektu


K lasa to coś takiego ja k p ro je k t obiektu. Jeśli chcesz zbudow ać
pięć identycznych podm iejskich dom ów , to n ie prosisz arch itek ta
o pięć identycznych projektów . U żyjesz je d n eg o do budow y
pięciu domów.

Kiedy d efiniujesz klasę,


d efiniujesz także je j metody,
podobnie ja k projekt de finiuje
układ pom ieszczeń w domu .

M ożesz użyć jednego projektu do


zbudowania dowolnej liczby domów
M ożesz również przy użyciu
jednej klasy utw orzyć każdą liczbę
obiektów.

Obiekt korzysta z metod swojej klasy


P o napisaniu klasy m ożesz utw orzyć przy użyciu instrukcji new tyle
obiektów , ile tylko chcesz. K iedy ju ż to zrobisz, wszystkie m etody
Twojej klasy stan ą się częścią każdego z nich.

H o u se

GiveShelter() ° 6/e k t\\o ^


GrowLawn()
MailDelivered()
ClogDrainPi pes()
AccruePropertyTaxes()
NeedRepairs()

jesteś tutaj ► 151


O biekty ulepszają Twój kod

Kiedy tworzysz obiekt na podstawie klasy,


to taki obiekt nazywamy instancją klasy
Z gadnij c o ... Już znasz te rzeczy! W szystko w o knie Toolbox jest klasą: je st klasa
B u t t o n , klasa T ext Box, klasa Label i inne. K iedy przeciągasz przycisk z o kna
Toolbox, ID E autom atycznie tw orzy in stancję klasy B u t t o n i nazyw a ją b u t t o n l .
G dy przeciągniesz kolejny przycisk, u tw o rzo n a zostanie n a stę p n a in stancja o nazwie
b u t t o n 2 . K ażda instancja klasy B u t t o n p o siad a swoje w łasne właściwości i m etody,
ale w szystkie przyciski zachow ują się dokład n ie w ten sam sposób — są bow iem
instancjam i tej sam ej klasy.

Przed'. To j e s t obraz
pam ięci komputera
na początku programu.

Twój program
wykonuje

r
instrukcję new.

H ouse m a p l e D r i v e 1 1 5 = n e w House();

Po: Teraz w pamięci


znajduje się
instancja k/asy
House.
ulica
Kolonowa
115

Sprawdź to sam! f " ' " Z r ó b to !

O tw órz dow olny p ro je k t używający przycisku b u t t o n l ,


a n astęp n ie skorzystaj z ID E do p rzeszu k an ia go w celu instancja
o dnalezienia frazy button l = new. Z n ajd ziesz frag m en t
Pojedyncze wystąpienie.
k odu dodany przez p ro je k ta n ta form ularzy, k tó reg o zadaniem
je st tw orzenie instancji klasy B u t t o n . Po analizie kodu okazało się, że błqd
polegałna tworzeniu zbyt wielu in s ta n c ii
klasy Person.

152 Rozdział 3.
Obiekty: zorientuj się!

GUI j e s t skrótem
Lepsze rozwiązanie... uzyskane dzięki obiektom! ° d Graphical User Interface
i oznacza graficzny
M aciek nap isał nowy p ro g ram porów nujący trasy, k tóry używa obiektów do interfejs użytkownika.
J e s t to w szystko, co
odnalezienia najkrótszej sp o śró d trzech dró g prow adzących do tego sam ego celu. tw orzys z podczas budowania
O to sposób, w jaki zbudow ał swoją aplikację. formularza w projektancie
formularzy.

M aciek utw orzył G U I z p o lem tekstow ym t e x t B o x l , k tó re zaw iera cel wszystkich trzech tras.
r N astęp n ie d o d ał ele m e n t t e x t B o x 2 zaw ierający ulicę, k tó rą je d n a z w yznaczonych tras m a omijać .
W staw ił także p o le t e x t B o x 3 z nazw ą ulicy, k tó rą trzecia z w yznaczonych tra s m usi zawierać .

U tw orzył o b iek t N a v ig a to r i ustalił w nim cel swojej podróży. O biekt n a v ig a M


/navigatorlN ^ '^ a to r .
' ■ 3 , 5 kr n j £
N a v ig a to r

SetCurrentl_ocation() _____)
SetDestination()
ModifyRouteToAvoid()
ModifyRouteToInclude() string destination = textB oxl.T ext;
GetRoute()
GetTimeToDestination() N a v i g a t o r n a v ig a to r l = new N a v i g a t o r Q ;
TotalDistance()
n a v ig a to r l . S e t D e s t i n a t i o n ( d e s t i n a t i o n ) ;

string r o u t e = n a v ig a to r l . G e t R o u t e Q ;

N astęp n ie M aciek d o d ał drugi o b iek t Navigator i nazw ał go W s z y s tk ie m eto d y :


S e t D e s ti n a tio n O ,
n a v i g a t o r 2 . W ywołał m eto d ę S e t D e s t i n a t i o n ( ) teg o o b iektu M odifyR outeT oA voidO o ra z
dla u stalen ia celu, a n astęp n ie m e to d ę Mo d i f y Ro u t e T o A v o i d ( ) . M o d ify R o u teT o In clu d eO ,
p r z y jm u ją p a r a m e tr
t y p u s tr in g .

[ 4 T rzeci o b iek t Navigator został nazw any n a v i g a t o r 3 . M aciek ustaw ił cel,


a n astęp n ie wywołał m eto d ę M o d i f y R o u t e T o I n c l u d e ( ) .

Za każdy razem,
gdy tworzysz
nowy obiekt
na podstawie
T eraz M aciek m oże wywołać m eto d ę T o t a l D i s t a n c e ( ) każdego klasy, nazywamy
obiektu, by znaleźć n ajk ró tszą trasę. N ajw ażniejsze jest to , że m usiał
napisać kod nie trzy razy, a tylko raz! to tworzeniem
instancji klasy.
jesteś tutaj ► 153
Kilka tajem nych porad „rusz g ło w ą "

o CHWILA! NIE PODALIŚCIE MI


WSZYSTKICH INFORMACJI
NIEZBĘDNYCH DO STWORZENIA
PROGRAMU NAWIGACYJNEGO.

To prawda, nie podaliśmy. P rogram nawigacyjny to coś n apraw dę


złożonego. Je d n a k skom plikow ane pro g ram y są tw orzone w edług tych
sam ych w zorców co te p ro ste. P ro g ram M aćka jest przykładem pokazującym ,
w jaki sposób należy używać obiektów w rzeczywistych aplikacjach.

Teoria i praktyka
S koro już m ow a o w zorcach, to zaraz przedstaw im y w zorzec, k tóry b ędzie w ielokrotnie
używany w tej książce. N ow e pojęcia lub koncepcje (takie ja k obiekty) będziem y opisywali
n a kilku kolejnych stro n ach , d em o n stru jąc je przy użyciu rysunków i niew ielkich fragm entów
kodu. D zięki tem u będziesz m iał możliw ość cofnąć się i spróbow ać zrozum ieć, o co chodzi,
bez konieczności zap rzątan ia sobie głowy p isaniem i u ru ch am ian iem program u.

House mapleDrive115 = new House();


Ulica
Gdy wprowadzamy nowe pojęcie (takie Klonowa
ja k obiekt), zwracaj uwagę na rysunki
i fragmenty kodu ta kie ja k te .
115
Qk t Ho°-
Po w prow adzeniu pojęcia dam y Ci okazję, by Twój m ózg dobrze je sobie utrw alił.
C zasam i będziem y w tym celu postęp o w ać zgodnie z te o rią sugerującą u trw alanie
inform acji p o p rzez pisan ie ćwiczeń — takich ja k Z aostrz ołów ek zam ieszczone na
n astępnej stronie. C zasam i je d n a k będziem y przechodzić b ezp o śred n io do p isan ia kodu.
T ak a kom binacja teo rii i p raktyki je st b ard zo efektywnym sposobem pozw alającym
Kiedy wykonując
przyswoić sobie inform acje i utrw alić je. jakieś ćwiczenie
Rada dotycząca ćwiczeń obejmujących pracę z kodem
obejmujące pisanie
Poniżej podaliśm y kilka inform acji, których zap am iętan ie pow inno Ci znacznie ułatw ić
kodu, napotkasz
i przyspieszyć w ykonywanie ćwiczeń obejm ujących pisan ie kodu. problemy, nie
# B ardzo łatw o m o żn a n a p o tk ać p ro b lem y zw iązane z b łędam i składniow ym i wstydź się zajrzeć
takim i jak pom in ięcie naw iasu lub znaku cudzysłowu. O puszczenie naw et jednego
naw iasu klam row ego m oże spow odow ać pojaw ienie się w ielu błędów .
do rozwiązania.
# P oszukanie rozw iązania je st zawsze zn a czn ie lepszym pom ysłem niż p o p ad a n ie Możesz także
we frustrację. K iedy jesteś sfrustrow any, Twój m ózg nie chce się uczyć.
pobrać kody
# W szystkie kody zam ieszczone w tej książce zostały p rzetesto w an e i n a pew no
działają praw idłow o w V isual Studio 2012! N iem niej je d n a k k ażd em u zdarza przykładów
się p o p ełn iać p ro ste błędy typograficzne podczas przepisyw ania k o d u (takie jak z serwera
w pisanie m ałej litery L zam iast dużej).
# Jeśli Twój p ro je k t nie chce się skom pilow ać, spróbuj p o b ra ć przykładow e kody
wydawnictwa
z serw era F T P w ydaw nictw a H elion: ftp://ftp.helion.pl/przyklady/cshru3.zip. Helion.
154 Rozdział 3.
Obiekty: zorientuj się!

Zaostrz ołówek
Przejdź przez te same etapy, przez które musiał przejść Maciek
podczas pisania kodu tworzącego klasę N a v ig a to r i jej metody

s t r in g d e s t in a tio n = te x tB o x l.T e x t; p rzeds taw iliśm y tuta j początek


zadania. Tak wygląda kod, który
s t r in g r o u te 2 S t r e e t T o A v o id = te x tB o x 2 .T e x t; nap isat M aciek. S tu ż y on do
s t r in g r o u t e 3 S t r e e t T o I n c lu d e = te x tB o x 3 .T e x t; pobrania celu i nazw ulic z pól
tekstow ych.

N a v ig a t o r n a v i g a t o r l = new N a v ig a t o r Q ; A tu j e s t kod potrzebny


n a v ig a t o r l. S e tD e s t in a tio n ( d e s t in a t io n ) ; do utworzenia ot>¡e k tu
nawigatora, usta w ¡en ¡a celu
in t d is ta n c e l = n a v ig a to r l.T o ta lD is ta n c e Q ; pobrania odtegtośd.

r — — — — — — — — — — — — — — — — — i
1. Stwórz obiekt navigator2, ustaw jego cel, wywołaj jego metodę M odifyR outeT oA void() Iuzyj metody
| TotalD istance() do ustawienia zmiennej całkowitej distance2. |

I Navigator navigator2 = ............................................. I

I navigator2........................................................... I

I navigator2........................................................... I

| int distance2 = ..................................................... |


L ___ J

*~2. Stwórz obiekt navigator3, ustaw jego cel, wywołaj jego metodę M odifyR outeToInclude() i uzyj metody
I TotalD istance() do ustawienia zmiennej całkowitej distance3. |

Wbudowana metoda^ C # HUM ) ^ n równ S ó £ S j ^ j o “ j


z n ich. Maciek u żył je j do ° dnalezien 'a najkrótszej 9 ^

in t s h o r t e s t D is t a n c e = M a t h . M in ( d i s t a n c e l , M a t h . M in ( d is t a n c e 2 , d is ta n c e 3 ) ) ;

jesteś tutaj ► 155


Statyczne trzym anie

Zaostrz ołówek
V
Sb- Z .U U 91I
Przejdź przez te same etapy, przez które musiał przejść Maciek
Rozwiązanie podczas pisania kodu tworzącego klasę N a v ig a to r i jej metody.

s tr in g d e s t in a t io n = t e x t B o x l. T e x t ;
P n a d su m U śm y tu ta j początek
s tr in g ro u te 2 S tr e e tT o A v o id = te x tB o x 2 .T e x t; za dania. Tak wy g ląda kod, który
nap isat M aciek, by pobrać nazwę
s tr in g ro u te 3 S tr e e tT o In c lu d e = te x tB o x 3 .T e x t; celu i ulicy z pól tekstow ych.

N a v ig a to r n a v ig a t o r l = new N a v ig a t o r ( ) ; A to j e s t kod słu żą cy do utworzenia


obiektu Navigator, u s ^ w i ^ a . jego
n a v ig a t o r l. S e t D e s t in a t i o n ( d e s t in a t io n ) ;
celu i pobrania odle głości-
i n t d is t a n c e l = n a v ig a t o r l. T o t a l D is t a n c e ( ) ;

1. Stwórz obiekt navigator2, ustaw jego cel, wywołaj jego metodę M odifyR outeT oA void() I użyj metody
TotalD istance() do ustawienia zmiennej całkowitej distance2.

Navigator navigator2 = new N avigator();.......

navigator2. SetDestination(destination);

navigator2. ModifyRouteToAvoid(route2StreetToAvoid);

int distance2 = navigator2.TotalDistance();

2. Stwórz obiekt navigator3, ustaw jego cel, wywołaj jego metodę M odifyR outeToInclude() I użyj metody
TotalD istance() do ustawienia zmiennej całkowitej distance3.

Navigator navigator3 = new Navigator();

navigator3.SetDestination (destination);

navigator3. ModifyRouteToInclude (route3StreetToInclude);

int distanced = navigator3. TotalDistance();

Wbudowana matada C # ^
z nich. Maciek u żył je) do ndnaiezierna najkrótszej g i

in t s h o rte s tD is ta n c e = M a th .M in (d is ta n c e l, M a th .M in (d is ta n c e 2 , d ista n ce 3 ));

156 Rozdział 3.
Obiekty: zorientuj się!

NAPISAŁEM JUŻ KILKA KLAS,


ALE JESZCZE ANI RAZU NIE UŻYŁEM OPERATORA „NEW ",
BY UTWORZYĆ INSTANCJĘ OBIEKTU! CZY TO OZNACZA, ŻE M OŻNA
W YW OŁYW AĆ METODY BEZ TWORZENIA OBIEKTÓW?

Tak! To dlatego używałeś słowa kluczowego s t a t i c w swoich metodach.

Z w róć jeszcze raz uw agę n a klasę T a lk e r n a p isan ą kilka stro n w cześniej:


c la s s T a lk e r
{
p u b lic s t a t i c i n t B la h B la h B la h (S trin g th in g T o S a y, i n t numberOfTimes)
{
s t r in g f in a lS t r in g =
K iedy wywoływałeś m eto d ę, nie tw orzyłeś w tym celu jed n o cześn ie nowej instancji klasy T a lk e r .
R o b iłeś p o p ro stu coś takiego:
T a lk e r.B la h B la h B la h (" W ita j, w i t a j , w ita j", 5);
T a k w łaśnie wywołuje się m eto d y s t a t i c i w dalszym ciągu będziesz je wywoływał w te n sposób.
Jeśli usuniesz słowo kluczow e s t a t i c z deklaracji m etody B la h B la h B la h ( ) , to w celu jej wywołania
będziesz m usiał utw orzyć instancję klasy T a lk e r . Z tym drobnym w yjątkiem m etody statyczne
działają tak sam o ja k m eto d y obiektu. M ożesz przekazyw ać do nich p aram etry , m ogą o n e zw racać
w artości i przechow yw ane są w klasach.

Je st jeszcze je d n a rzecz, k tó rą m ożna zrobić ze słowem kluczowym s t a t i c . M ożesz nim oznaczyć


całą klasę . W ted y wszystkie jej m eto d y muszą być tak że zad ek laro w an e jak o s t a t i c . Jeżeli
sp róbujesz d odać do niej m eto d ę, k tó ra n ie jest statyczna, kom pilacja się n ie uda.

Ii Nie.istnieią.
Nie .istniej .
głupie pytania

P: : Kiedy myślę o czymś „static” , wydaje mi się, P : Do czego są mi potrzebne metody, które wymagają
że jest to coś, co się nie zmienia. Czy to oznacza, utworzenia instancji? Dlaczego nie mogę zrobić samych
że metody bez static mogą się zmieniać, a te ze static metod statycznych?
nie mogą? Czy zachowują się one inaczej?
O : Ponieważ gdy posiadasz obiekty przechowujące pewne
O : Nie. Zarówno metody zadeklarowane ze słowem dane — także jak instancje klasy N a v ig a to r Maćka
kluczowym static, jak i te bez takiej deklaracji zachowują się przechowujące dane dotyczące różnych tras — możesz używać
dokładnie tak samo. Jedyna różnica polega na tym, że te metod instancji do pracy z nimi. Tak więc gdy Maciek wywołuje
pierwsze nie potrzebują instancji klasy, natomiast metody bez metodę M o d ify R o u te T o A v o id () instancji n a v ig a to r2 ,
static jej potrzebują. Wielu ludzi ma problemy z zapamiętaniem to zmiany dotyczą tylko trasy umieszczonej właśnie w niej.
tego, ponieważ słowo „static" nie jest dobrane intuicyjnie w tym Nie ma to wpływu na dane w obiektach n a v ig a t o r l
kontekście. i n a v ig a to r3 . To właśnie w ten sposób mogliśmy pracować
z trzema różnymi trasami w tym samym czasie i program mógł
P : Czy w takim razie nie mogę używać klasy, przechowywać jednocześnie wszystkie dane.
dopóki nie utworzę instancji obiektu?

O : Możesz używać jej metod statycznych. Jeśli posiadasz


P To w jaki sposób instancja przechowuje dane?
:

metody, które nie są statyczne, ich użycie wymaga O : Przewróć stronę i sam się o tym przekonaj.
wcześniejszego utworzenia instancji tej klasy.

jesteś tutaj ► 157


Sprawy dotyczące stanów obiektów

Instancja używa pól do przechowywania informacji


W zasadzie nazywamy
Z m ieniając tek st n a przycisku, posługujesz się właściwością T e x t w ID E .
to u sta w ien iem wtaściwośr.i
W trakcie tej czynności ID E dod aje k o d p o d o b n y do następującego: W tościwość j e s t bardzo
podobna do pola —
b u tt o n l.T e x t = "T ek st na p rz y c is k u " ; opowiem y o tym bardziej
T eraz już wiesz, że przycisk b u t t o n l je st in stancją klasy B u tto n . szczegótowo nieco później.
Z aprezen to w an y kod p o p ro stu m odyfikuje pole tej instancji. P o la m ożna
także dodać do diagram u klasy — w tym celu narysuj w połow ie jego wysokości
p oziom ą linię. P o la należy zapisywać powyżej tej linii, a m eto d y poniżej.

To wtaśnie tutaj
diagram klasy
pokazuje pola..
Każda instancja
klasy przechowuje Dodaj tę linię,
tu sw ój stan. aby oddzielić pola
od metod.

Metody to to, co obiekt robi. Pola to to, co obiekt wie


G dy M aciek utw orzył trzy instancje klasy N a v ig a to r , p ro g ram utw orzył trzy obiekty. K ażdy z nich posłużył
do przechow an ia danych n a te m a t jednej trasy. K iedy p ro g ram d o d ał in stancję n a v ig a t o r 2 i w ykonał
jej m eto d ę S e t D e s t i n a t i o n ( ) , to ustalił cel tylko dla tej jednej instancji. N ie m iało to żadnego wpływu
na instancje n a v i g a t o r l i n a v ig a to r 3 .

N a v ig a to r Każda instancja klasy Nav'iga tor


zna swój cel i sw oją tra sę.
Destination
Route

Set Current Location()


SetDestination()
ModifyRouteToAvoid() Ubiekt Navigator pozwala na
ModifyRouteToInclude() ustalenie celu, z modyfikowanie
GetRoute() tras y 1 p obranie informacji na
je j tem at.
GetTimeToDestination()
TotalDistance()
Zachowanie obiektu zdeterminować ¡est
przez metody, które ten obiekt p ° siada.
Po la służą do przechowywania ¡ego stanu.

158 Rozdział 3.
Obiekty: zorientuj się!

Stwórzmy kilka instancji! Pamiętaj! Jeśli na początku


m etody z obaczysz słowo
D odaw anie p ó l do klasy jest b ard zo p ro ste. W ystarczy kluczowe void, znaczy to,
że metoda ta nie zwraca
zadeklarow ać zm ienne um ieszczone p o za jej m eto d am i żadnej wartości.
K iedy to zrobisz, każd a in stan cja klasy będzie
dysponow ać własnym i kopiam i tych zm iennych. public class Clown {
public string Name;
public int Height;

p u b lic void TalkAboutYourselfQ {


Me s sa g e B o x . Sh o w( " Na z y wa m s i ę 11
+ Name + 11 i mam 11
+ H e i g h t + 11 c e n t y m e t r ó w w z r o s t u .

Chcąc tworzyć instancje }


s wojej klasy, nie używ aj słowa } Pamiętaj! Operator *= każe C#
kluczowego sta tic w deklaracji pobrać wartość znajdującą s ię
klasy ani w deklaracji metod. po jego lewej stronie i pomnożyć j ą
przez to, co j e s t p° p raw ej.

Zaostrz ołówek

v Uzupełnij treść każdego komunikatu wyświetlanego w oknie


MessageBox po wywołaniu wyróżnionej instrukcji.

Clown oneCl own = new C l o w n ( ) ;


oneCl own. Name = " B o f f o " ;
on e C lo w n.H eight = 35;
oneC low n.TalkA boutY ourself(); „Nazywam się i m am _centym etrów w zrostu.”

Clown a n o t h e r C l o w n = new C l o w n ( ) ;
anotherClown.Name = " B i f f " ;
a n o th erC lo w n.H eig ht = 40;
anotherC low n.T alkA boutY ourself(); „Nazywam s i ę ___________im a m ____________centym etrów w zrostu.”

Clown c l o w n 3 = new C l o w n ( ) ;
c l o w n 3 . N a me = a n o t h e r C l o w n . N a m e ;
clow n 3.H eigh t = oneClown.H eight - 3;
clow n3.T alkA boutY ourself(); „Nazywam s i ę ___________im a m ____________centym etrów w zrostu.”

a n o t h e r C l o w n . H e i g h t *= 2 ;
anotherC low n.T alkA boutY ourself(); „Nazywam s i ę ___________ im a m ____________centym etrów w zrostu.”

jesteś tutaj ► 159


Um ieszczanie na stercie pom aga obiektom

Dzięki za pamięć
G dy Twój p rog ram tw orzy obiekty, są o n e przechow yw ane
w specjalnym obszarze pam ięci k o m p u te ra zwanym stertą .
G dy tylko Twój kod utw orzy o b iek t za p o m o cą w yrażenia new,
C # natychm iast rezerw uje n a stercie p rzestrzeń n iezb ę d n ą do
przechow yw ania jego danych.
To j e s t ilustracja s te r ty przed uruchomieniem
projektu. Z auw aż, że j e s t ona. piasfo.

Przyjrzyjmy się bliżej temu, co się tu stało

ę. Naostrz ołówek
Rozwiązanie' Uzupełnij treść każdego komunikatu wyświetlanego w oknie
MessageBox po wywołaniu wyróżnionej instrukcji.

C w z ^ wy razeń n e ? tworzy instancję klasy


Cteiw, rezerwują c dla obiektu fragment pam ięci *
Clown oneCl ownV? new C l o w n Q ; na s t e rcie i wypełniając go danymi. n p ęci
oneCl own. Name = " B o T f u " ; ----------
o n e C lo w n .H e i g h t = 35;
oneC low n.TalkA boutY ourself(); N azywam się Boffo i m am 35 _centym etrów w zro stu .”

Clown a n o t h e r C l o w n
anotherClown.Name = " B i t t " ;
a n o t h e r C l o w n . H e i g h t = 40;
anotherC low n.T alkA boutY ourself();
N azywam się Bi'ff i m am 40 centym etrów w zro stu .”
I"
Clown c l o w n 3 = ( n e w C l o w n ( ) ;
c l o w n 3 . Na me = a n o t n e r u o w n T N a m e ;
clow n 3.H eig ht = oneClown.Height - 3;
clow n3.T alkA boutY ourself();
„Nazywam się B iff i m am 28 centym etrów w zro stu .”

a n o t h e r C l o w n . H e i g h t *= 2 ;
anotherC low n.T alkA boutY ourself(); B iff 80
„Nazywam się i m am _centym etrów w zro stu .”

Gdy Twój program tworzy nowy obiekt, jest on dodawany do sterty.

160 Rozdział 3.
Obiekty: zorientuj się!
Ten obiekt j e s t instancją
klasy Clown.
Co Twój program ma na myśli
N ow a instancja klasy Clown w Tw oim p ro g ram ie tw orzona jest
w następujący sposób:
Clown myl ns t an c e = new Clown();
Są to w zasadzie dwie instrukcje p o łączo n e w jed n ą. Pierw sza z nich
d ek laruje zm ienną typu Clown (Clown m y l n s t a n c e ; ), a druga
tw orzy nowy o b iek t i przypisuje go do w łaśnie utw orzonej zm iennej
(m y l n s t a n c e = new C l o w n ( ) ; ). W ygląd sterty p o każdej z tych
instrukcji będzie przed staw iał się następująco:

^ Clown on eCl o wn = new C l o w n ( ) ;


oneCl o wn . Na me = " B o f f o " ;
a je g o pola s ą % * t C \ oVl
on e C lo w n.H eight = 35; ustaw iane.

oneClown.TalkAboutYourselfO;

° 6 /e k tC \° '

Clown a n o t h e r C l o w n = new C l o w n ( ) ;
anotherClown.Name = " B i f f " ; Te . instrukcje tw orzą drug i
obie k t i wypełniają go
an oth erC lo w n .H e ig h t = 40; danymi.
anotherClown.TalkAboutYourself();

Clown c l o w n 3 = new C l o w n ( ) ;
c l o w n 3 . N a me = a n o t h e r C l o w n . N a m e ;
clow n 3.H eig ht = oneClown.Height
N astępnie tworzony '/e /c t CN0
clown3.TalkAboutYourself();
i uzupetniany j e s t
trzeci obiekt.

a n o t h e r C l o w n . H e i g h t *= 2 ;
anotherClown.TalkAboutYourself();

W tym fragmencie kodu n ie ma


instrukcji new, a zatem nie j e s t
tworzony żaden nowy obiekt.
Modyfikowany j e s t tylko jeden, z ty ch
obiektów, które s ą ju ż w p amięc i.
% ż * t C \° '1

jesteś tutaj ► 161


Tworzenie m etod ma sens

Możesz używać nazw klas i metod


w celu uczynienia kodu bardziej intuicyjnym /
Świetni programiści
K iedy piszesz kod m eto d , decydujesz o stru k tu rze p ro g ram u . Czy używasz jed n ej m etody,
czy dzielisz kod pom iędzy kilka? A m oże w ogóle nie p o trzeb u jesz żadnej m etody? piszą kod, k tó ry
W ybory do k o n an e n a tym e tap ie decydują o tym , czy k o d b ędzie później intuicyjny,
można łatwo
czy, jeśli n ie jesteś w ystarczająco ostrożny, znacznie bardziej zawiły i pogm atw any.
zrozumieć.
M am y tutaj fragm ent ładnego, zw ięzłego kodu. Jest to część p ro g ram u do kontro li Komentarze mogą
m aszyny w ytw arzającej batony. w ty m pomóc,
M etoda chkTemp() zwraca liczbę
całkow itą ... ale do czego ona
jednak nic nie jest
właściw ie słu ży ?
= m.chkTemp(); w stanie pobić
„obj", „ics" i „m" > 160) {
to okropne nazwy!
obj = new T ( ) ;
M etoda clsTrpV intuicyjnych nazw
Nie mamy zielonego ma jeden parametr,
pojęcia, co one robią. obj.clsTrpY(2); ale nie wiem y, metod, klas,
W dodatku po co j e s t do czego on j e s t
ics.Fill();
ta klasa T?
ics.V ent();
przeznaczciny. zmiennych i pól.
m.airsyschk();

Popatrz przez chwilę na ten kod. Czy jesteś w stanie odgadnąć, do czego on służy?

T akie instrukcje n ie d ają Ci żadnej w skazówki odn o śn ie do tego, co p ro g ram ro b i i w jaki sposób. W tym
przypadku p ro g ram ista był n iezm iern ie zadow olony z wyników, bo wszystko u d ało m u się zaw rzeć w jed n ej
m etodzie. T rzeb a je d n a k w iedzieć, że m aksym alizacja zwięzłości k o d u ta k n ap raw d ę nie je st użyteczna! Podzielm y
zatem ten frag m en t n a kilka m eto d , aby ułatw ić jego późniejszą analizę, i zadbajm y przy tym , by klasy i m etody
otrzym ały bardziej sensow ne nazwy. N ajpierw je d n a k sp róbujem y dow iedzieć się, co k o d pow inien robić.

W iaki sposób możemy G e n e r a l E le c tro n ic s — m a s z y n a d o r o b t e n t e b a t o n ó w , t y p 5 .


określić, co kod powinien
S p e c y f ik a c ja p r o d u k t u
robić? Cóż, każdy kod
je s t pisany w określonym Z au t om atyzow any system m a spraw dzać t e m p e ra tu rę n u g atu
celu. Od Ciebie zależy, co 3 m in u ty. Jeżeli przekracza ona 16°° Q b a t° n je s t za g° r ący
czu go poznasz. M ożesz
sprawdzić w specyfikacji i system m usi przeprowadzić procedurę opróżniania systemu
dołączonej przez chłodzenia izolacji batonów (SCIB):
p r o g ra m is tę .
• Z am knij zaw ór regulacyjny trnb in y 2.
• U zup ełnij izolow any system chłodzen ia stru m ien iem wady.
• O próżnij z wody.
• U p ewnij się, że w system ie n ie m a p owie trz a .

162 Rozdział 3.
Obiekty: zorientuj się!

T a stro n a z instrukcji znacznie ułatw ia zrozum ienie kodu. D aje nam dodatkow o w spaniałe wskazówki,
ja k popraw ić kod, by ułatw ić jego analizę. T eraz ju ż wiemy, do czego służy w aru n ek porów nujący zm ienną
t z w artością 160. W instrukcji je st n ap isan e, co oznacza te m p e ra tu ra powyżej 160°C — n u g at je st zbyt gorący.
D ochodzim y do w niosku, że mm usi być klasą k o n tro lu jącą m aszynę do pro d u k cji słodyczy. P o siad a o n a statyczne
m etody odpow iedzialne za spraw dzanie te m p eratu ry n u g atu i system u k o n tro li pow ietrza. U m ieśćm y zatem
spraw dzanie te m p e ra tu ry w osobnej m eto d zie i nadajm y klasom i m eto d o m tak ie nazwy, aby o k reślen ie ich
p rzeznaczenia było p roste.

^NougatTooHot() {
Typ wartości w ynikow y i n t temp = Ma ker . CheckNougatTemperatur e();
m etody IsNougatT°oHot- i f (temp > 160)
,N azywając klasę Maker, a je j metod. \
return
} else {
return
true;

false;
t
CheikNougatTemperature, czynim y kod
łatw iejszym do zrozum ie'n ia-

}
i W artość wynikowa tej metody
j e s t typ u Boolean, co oznacza,
ż e będzie to true lub fa lse .

3 C o specyfikacja każe zrobić w przy p ad k u p rzek ro czen ia te m p e ratu ry n u g atu ? N ak azu je p rzep ro w ad zen ie p ro ced u ry
o p ró żn ian ia system u chłodzenia izolacji b ato n ó w (SC IB ). Stw órzm y zatem in n ą m eto d ę, nadajm y oczywistą nazwę
klasie T (k tó ra ok azała się być o b iek tem kontrolującym tu rb in ę) i klasie i c s (k tó ra zaw iaduje system em chłodzenia
i p o siad a dwie m etody — do n ap ełn ia n ia i o p ró żn ian ia system u):

p u b l i ^ 7 v° i d ) DoCICSVentProcedure() {
T urbi ne t u r b i n e C o n t r o l l e r = new T u r b i n e ( ) ;
Typ zwracany void
turbineController.CloseTripValve(2);
oznacza, że metoda nie
będzie zwracała żadnej Is olationCoolingSystem.Fill();
wartości. IsolationCoolingSystem.Vent();
Maker . CheckAir System();
}

, 5^ T eraz kod jest znacznie bardziej intuicyjny! N aw et jeśli nie wiesz, że p ro c e d u ra o p ró żn ian ia SC IB m usi być
u ru ch am ian a, gdy n u g at je st za gorący, to znacznie łatwiej jest określić przeznaczenie tego kodu :

if (IsNougatTooHot() == t r u e ) {
DoCICSVentProcedure();
i

Możesz utworzyć kod znacznie łatwiejszy w czytaniu


i pisaniu, myśląc o problemie, k tó ry ma być za jego pomocą
rozwiązywany. Jeśli dobierzesz nazwy metod tak, by były
zrozumiałe dla kogoś, k to znaproblem, to Twój kod będzie
wyraźnie łatwiejszy do rozszyfrowania... i napisania!
jesteś tutaj ► 163
N aturalne klasy

Nadaj swojej klasie naturalną strukturę


Z atrzym aj się dosłow nie n a sek u n d ę i przypom nij sobie, dlaczego chcesz, by T w oje m etody
były bardziej intuicyjne. Dlatego, że każdy program rozwiązuje problem lub ma przyczynę.
T o nie m usi być w cale p ro b lem biznesowy — czasem przyczyną je st to (n a przykład w przypadku
p ro g ram u FlashyThing), że chcem y m ieć coś fajnego i zabaw nego! Je d n a k b ez w zględu n a to,
co robi Twój p ro g ram , im bardziej będzie o n p rzypom inał p ro b lem , który starasz się rozw iązać,
tym łatwiejszy będzie do p isan ia (a tak że czytania, napraw y, u trz y m an ia ...).

Stwórzmy diagram klasy


Przyjrzyj się jeszcze raz instrukcji i f w pun k cie 5. n a p o p rzed n iej stronie. Już wiesz, że instrukcje
zawsze m uszą być u m ieszczone w m eto d ach , k tó re z kolei zaw ierają się w klasach, praw da?
W tym p rzypad k u instrukcja i f została u m ieszczona w m eto d zie D o M a i n t e n a n c e T e s t s ( ) ,
k tó ra je st częścią klasy C a n d y C o n t r o l l e r . T e ra z p o p a trz n a k o d i n a diagram klasy.
Czy widzisz zw iązek p om iędzy nim i?

public class CandyController {

p u b lic void DoMaintenanceTestsQ {

if QsN ougatTooHotQ == t r u e ) {
DoCICSVentProcedureQ;
C a n d y C o n tr o lle r

DoMaintenanceTests()
DoCICSVentProcedure()
IsNougatTooHot()
p u b l i c v o id DoCICSVentProcedureQ ...

public Boolean IsNougatTooHotQ ...

164 Rozdział 3.
Obiekty: zorientuj się!
Zaostrz ołówek

V Kod systemu kontroli wyrobu słodyczy widoczny na poprzednich


stronach wywołuje metody trzech innych klas. Przeanalizuj go jeszcze raz,
a następnie uzupełnij diagramy klas.

W kodzie przedstawionym
na poprzednich stronach
m ^ m znaleźć je s zc ze
je dną klasę. W pisz tu
jej nazwę i metody.

jesteś tutaj ► 165


Zobrazuj swoje klasy

Diagramy klas pozwalają


w sensowny sposób zorganizować klasy
Pisanie diagram ów ułatw ia dostrzeganie p o tencjalnych p roblem ów w klasach, jeszcze zanim
zabierzesz się za pisanie ich kodu. M yślenie o klasach n a wysokim p oziom ie abstrakcji p rzed
zagłębieniem się w ich d etale p o m ag a zap rojektow ać ich właściwą stru k tu rę , k tó ra z kolei
da nam pew ność, że ko d b ędzie dotyczył tych problem ów , k tó re m a rozwiązywać. T w orzenie
diagram ów pozw oli Ci oderw ać się n a chwilę o d p ro g ram o w an ia i upew nić, że nie planujesz
pisania niepotrzebnych lub niepraw idłow o zbudow anych klas i m eto d , a te, k tó re piszesz,
b ęd ą intuicyjne i łatw e w użyciu.

D is h w a s h e r D is h w a s h e r
Klasa nazywa się
CleanDishes() Dishwasher, więc wszystkie CleanDishes()
AddDetergent() AddDetergent()
jej metody powinny dotyczyć
SetWaterTemperature() SetWaterTemperature()
w mycia naczyń. Jedna z nich -w
ParkTheCar()
— ParkTheCar() — nie ma z tym
jednak nic wspólnego. Powinna
zatem zostać stąd usunięta
i przeniesiona do innej klasy.

Zaostrz ołówek
_____. _______. Kod systemu kontroli wyrobu słodyczy widoczny na ^ .
- v Rozwiązanie poprzednich stronach wywołuje metody trzech innych klas. ±e Maker je s t^ k la s ^ '
Przeanalizuj go jeszcze raz, a następnie uzupełnij diagramy klas. bo pojawia s ię przed
kropką w instrukcji
M aker.CheckAirSystem (),

T u rb in e IsolationCoolingSystem Maker

Fill()
CloseTripValve() CheckNougatTemperature()
Vent()

CheckAirSystem()

166 Rozdział 3.
O biekty: zorientuj się!
Zaostrz ołówek ________________________________________________________________________
Każda z tych klas zawiera poważny błąd projektowy. Napisz, co według Ciebie
jest nieprawidłowe w każdej z nich i w jaki sposób naprawiłbyś taki błąd.

T a klasa je st częścią w cześniejszego system u w ytw arzania słodyczy.


C la ss 2 3

CandyBarWeight()
PrintWrapperO
GenerateReport()
Go()

T e dwie klasy są częścią system u, k tóry jest używany p rzez pizzerię


D e liv e ry G u y
do śledzenia dostaw pizzy.
AddAPizza()
PizzaDelivered()
TotalCash()
ReturnTime()

D e liv e ry G irl

AddAPizza()
PizzaDelivered()
TotalCash()
ReturnTime()

K lasa C a s h R e g i s t e r je st częścią program u używanego przez


C a s h R e g is te r
system autom atycznych kas w sklepie wielobranżowym.
MakeSale()
NoSale()
PumpGas()
Refund()
TotalCashinRegister()
GetTransactionList()
AddCash()
RemoveCash()

jesteś tutaj ► 167


Stwórz klasę

Zaostrz ołówek
Oto jak naprawiliśmy klasy. Jest tylko je d e n sposób p o praw ienia
Rozwiązanie błędów , choć istnieje o g ro m n a liczba innych m ożliwości zap ro jek to w an ia
klas w zależności o d ich p rzeznaczenia.

T a klasa je st częścią w cześniejszego system u w ytw arzania słodyczy.


V
C andyM aker

CandyBarWeight()
Nazwa, klasy .nie. .oddawała.jej przeznaczenia.-. Programista.' . PrintWrapper()
GenerateReport()
który, zobaczy .wiersz. kodu .taki lak. .ę.lass2?.-.Go()., .nie będzie .
MakeTheCandy()
miał. pojęcia.,. co .on. robi-. .Zmieniliśmy .także nazwę .metody .
na. bardziej .opisową— .wy.bra.lis.my M.akeXhe.ę.andy()., ale .m.oż.e .
to .być także .coś innego-.............................................................

T e dwie klasy są częścią system u, k tóry je st używany przez pizzerię


do śledzenia dostaw pizzy.

Wygląda .na.to, .że .kiasy. Peliye.ryBoy. .i.PeljyeryGirJ .działają .


.dokładnie tak .samo .—.śledzą. pracę. osoby, dost.ar.czaiącęj pizzę .
do .klientów:.Lepiej zaprojektowany, diagram .zawierałby. zamiast .
nich jedną. klasę .i dodatkowe, pole .określające płeć........................

Dodaliśmy pou ^ ' ¿ S ~ , akiś


UWflid odmiennego traktow ania k o b i e t /
powód od™'e ™e3 , ądu „a który /
1 ,m? S f ¿.kozafo si« utw orzenie

K lasa C a s h R e g is te r je st częścią p ro g ra m u używ anego przez C a s h R e g is te r


system autom atycznych kas w sklepie w ielobranżow ym .
MakeSale()
NoSale()
Wszystkie .metody .w .klasie. zajmują się .czymś związanym . Refund()
TotalCashInRegister()
z .kasą— .sprzedażą.,. pobieraniem .listy, transakcji,. dodawaniem GetTransactionl_ist()
gotówki itd :.— z .wyjątkiem. iednei: .tankowanie paliwa- . AddCash()
RemoveCash()
Najlepszym. rozwiązaniem .jest .usunięcie tej .metody .
i .przeniesienie .jej do .innej .kiasy,................................................

168 Rozdział 3.
Obiekty: zorientuj się!

public p a rtia l c l a s s Forml : Form


{
p r i v a t e voi d b u t t o n l _ C l i c k ( o b j e c t s e n d e r , EventArgs e)
{ Zagadkowy basen
string result =
Tw oim za d a n iem je st p o b ra n ie kolejnych
Echo e l = new Echo();
fragm entów k o d u z b asen u i w staw ienie
ich w o d pow iednie p u ste m iejsca
i n t x = 0; w kodzie. Możesz używać tego
while ( sam ego frag m en tu k ilkakrotnie
r e s u l t = r e s u l t + e l . H e l l o ( ) + "\n"; i nie m usisz w ykorzystać wszystkich.
Twój cel to utw o rzen ie klasy, k tó ra
się skom piluje, b ędzie działała i będzie
if ( w yświetlała p o k azan y kom unikat.
e 2 . c o u n t = e 2 . c o u n t + 1;

Okno programu
if (
e2.count = e2.count + e l.c o u n t :

x = x + 1;
}
MessageBox. Show(r esul t + " L i c z n i k : " + e2.count);
}

p u b l i c c l a s s _____________ {
p u b l i c i n t ________ 0; Pytanie dodatkowe!
public strin g G dyby o statn i w iersz k o m u n ik atu
return " w it a a a j.. ." ; zaw ierał 24 zam iast 10 , to w jaki
sposób rozw iązałbyś zagadkę?
M ożesz to zrobić, zm ieniając
}
tylko je d n ą instrukcję.
Przypominamy:
każdy fragment
x < 4
z basenu może być
x x < 5 E ch o
użyty więcej niż raz!
y x > 0 T e ster
e2 x > 1 E ch o ( ) e2 = e1;
count C o u n t( ) E ch o e2;
e1 = e1 + 1;
H e llo ( ) E ch o e2 = e1;
e l = co u n t + 1 ; x == 3
E ch o e2 = new E ch o ( ); x == 4
e l.c o u n t = co u n t + 1 ;
e 1 .co u n t = e 1 .count + 1 ;

- - - - - - - - - ► Odpowiedzi znajdziesz na stronie 179.


Ta zagadka ma dwa rozwiązania. Czy potrafisz podać oba? jesteś tutaj ► 169
D ziałająca klasa Guy

Utwórz klasę do pracy z kilkoma facetami


Jo e i B ob cały czas pożyczają sobie pieniądze. Stw órzm y zatem klasę
do śledzenia tych wym ian. Z acznijm y o d om ów ienia tego, co m am y zrobić.

Stworzymy klasę Guy i dodamy dwie instancje do formularza.


F o rm u larz będzie m iał dwa p ola: je d n o o nazw ie j o e (do śledzenia pierw szego
o b iektu), a drugie o nazw ie bob (do śledzenia drugiego obiektu).

instrukcje new tworzące


e dwie instancje są
x,mieszczone w kodzie
wykonywanym zaraz po
utworzeniu formularza. Metodom nadaliśmy sensowne
Tak będzie wyglądała nazwy. M etodę GiveCash()
sterta po jego w czytaniu. obiektu Guy wywołujemy,
by kazać facetowi oddać
część posiadanych przez
niego pieniędzy, natom iast
m etodę ReceiveCash(),
ve k t j eśli chcemy mu podarować
ja k ą ś sum kę. Moglibyśmy
nadać im inne nazwy —
GiveCashToSomeone() oraz
Ustawimy pola Cash i Name obiektów Guy. ReceiveCashFromSomeone()
D w a obiekty re p re z e n tu ją różnych facetów , z których każdy m a swoje imię — ale byłyby one z b y t długie,
i o k reślo n ą ilość p ieniędzy w kieszeni.

Każdy fa cet posiada pole


Name do przechowywania
je go im ienia i pole Cash,
w kłórym zapisana j e s t
ilość gotówki w jego
kieszeni.

^ e / c f & S\

Damy facetom kasę i im ją zabierzemy.


Gdy u zyska sz instancję klasy
U żyjem y m eto d y G iv e C a sh () do zm niejszania kw oty pieniędzy, jakie Guy i wywołasz je j m etodę
dany facet p o siad a, o raz m eto d y R e c e iv e C a sh () do jej zw iększania. ReceiveCash(), jalio parametr
podasz ilość piem ędzy do
Formularz wywołuj e m tfo d ę R ^ ^ ^ ł - otrzymania. wywoic''"'"'
obiektu. Nosi ona nazw ę Re cetveCas h, gdyz joe.R eceiveC as h(25 doda
sprawia, że fa c et otrzym uje p ienlądze, więc Joem u 25 z t

\
__________ jo e .R eceiv eC a sh (2 5 );_________

M etoda zwraca ilość


° 6'ekt 0 ^ pieniędzy, którą obiekt dodał
do swojego pola Cash.

170 Rozdział 3.
Obiekty: zorientuj się!

Utwórz projekt dla facetów


Poniew aż będziem y używać fo rm u larza, u tw órz nowy p ro jek t
W indow s F orm s A pplication. N astęp n ie w o knie Solution Manager Z r ó b to !
dodaj now ą klasę. Nazwij ją Guy. U pew nij się, że dodałeś
u s in g S y stem .W in d o w s.F o rm s; n a górze pliku z klasą Guy.
/Z
T eraz wypełnij ją kodem , który b ędzie w yglądał następująco:

Klas a Guy posiada dwa pola. Pole Name j e s t


tyPu s t ring i będzie zawierało nazwę faceta
(,Jo e “). p ole Cash j e s t natom iast typ u int
będzie śledził° bieżącą ilość gotówki w jego
kieszeni
class Guy { Metoda GiveCash() posiada jeden
public string Name; parametr, a m o u n t którego b ę tt ii ^ z
używ at do określania il°ści pie'męd zyl
public int Cash; jaka ma zo sta ć przekazana■

public int GiveCash(int amount) {


if (amount <= Cash && amount > 0) {
Używa ona instrukcji if do
\
Cash -= amount; sprawdzenia, czy ma w ystarczającą
Facet upewnia return amount; ilość gotówki — j eśli tak, wyciąga ,
się, czy prosisz podaną kwotę z kieszeni i przekazuje
go o dodatnią } else { ją w postaci wartości zwracanej.
ilość pieniędzy. MessageBox.Show(
W przeciwnym
r a z ie d o d a fb y ją "Nie mam wystarczającej ilości pieniędzy, aby Ci dać 11
za m ia st odjąć
od swojego
^ + amount,
budżetu. Name + 11 powiedział...");
return 0; Jeżeli G uy nie ma wy s ta rczającej
po
i o śc i .p ieni^dZy, powie Ci o ty m '
} za pomocą okna Me \essageBox,
} a m etoda GiveCash zwróci 0■

Metoda ReceiveCash() działa p odotinie


public int ReceiveCash(int amount) { do GiveCash() — przekazuje kwotę
if (amount > 0) { w postaci parametru, sprawdza, czy
j e s t ona w iększa od zera, i dodaj e ją
Cash += amount; do kieszeni danego faceta.
return amount;
} else {
MessageBox.Show(amount + 11 to nie jest ilość pieniędzy,
^ jaką mogę wziąć ", Gdy kwota j e s t w iększa od zera,
Name + " powiedział..."); me toda ReceiveCash() zwraca ilość
return 0; dodany ch p ieniędzy. Jeśli j e s t to
^ --- - zero lub wartość ujemna, facet
} w yśw ietla k°m unikat i zwraca 0.
}
3 ądź uważny p rzy s t osowaniu nawiasów klamrowych Co się stanie, kiedy w wyw ołaniu
— bardzo tatwo w pi'sać nieprawidłową ich liczbę. Upewnij metody ReceiveCash() lub GiveCash()
%ię' ,że każdy _nawias o twierają cy ma odpowiedni zam ykający.
} Jeśli ws zy s t kie będą s p arowane, IDE zrobi autom atycznie obiektu Guy przekażesz wartość
wcięcia w momencie wpisywania ostatniego nawiasu mniejszą od zera?
klamrowego.
jesteś tutaj ► 171
Joe pyta: „G dzie są m oje pieniądze?"

Utwórz formularz do interakcji z facetami


K lasa Guy je st genialna, ale to d o p iero p o czątek. Stw órzm y te ra z form u larz, który
będzie używał dw óch jej instancji. B ędzie o n p o siad ał p o la tekstow e pokazujące
im iona facetów i ilość p o siadanych przez nich p ieniędzy o raz przyciski, które S tw o r z to !
b ęd ą używ ane do daw ania i o d b iera n ia im gotów ki. Faceci m uszą skądś wziąć
- L'
pieniądze, zanim b ęd ą je m ogli sobie przekazyw ać, dlatego też będziem y m usieli ■
dodać także bank.

Dodaj dwa przyciski i trzy pola tekstowe do formularza.


D w ie gó rn e etykiety pok azu ją, ile p ieniędzy p o siad a każdy z facetów . D o d am y do fo rm u larza tak że zm ienną
bank — trzecia ety k ieta będzie określała, ile je st w nim pieniędzy. W dalszej kolejności zam ierzam y
pozm ien iać nazwy etykiet przeciągniętych n a form ularz. M ożesz tego dok o n ać p o p rzez kliknięcie
każdej etykiety, której nazw ę chcesz zm ienić, i modyfikację wiersza „(Name)” w o knie Properties.
T o sprawi, że k o d będzie znacznie łatwiejszy w czytaniu, bo będziesz m ógł używać nazw j o e s C a s h L a b e l
i bobsCash Label zam iast l a b e l 4 i l a b e l 5 .

Nazw ij górne pole tekstow e


joesC ashLabel, pole poniżej
bobsCashLabel, a dolne
bankCashLabel. M ożesz
pozostaw ić ich wartości
teksto w e b ez zm ian
— dodamy odpowiednią
m etodę formularza
w celu ich ustaw ienia.

Ten przycisk będzie wywoływał


m etodę ReceiveCash() obiektu
jo e , przekazując 10 jako Ten przycisk będzie wywoływał
parametr am ount i odejmując m etodę GiveCash() obiektu
tę iwartość od zm iennej bank. bob, przekazując jako parametr
am ount wartość 5. Pieniądze
w zięte od Boba zostaną
dodane do zm iennej bank.

Dodaj do formularza zmienne.


F o rm u larz m usi śledzić zm iany n a k o n tach dwóch facetów , więc p o trz e b a n am zm iennej dla każdego
z nich. Nazwij je j o e i bob. D odaj tak że zm ien n ą bank o kreślającą ilość pieniędzy, ja k ą p osiada
form ularz. Z kw oty tej m o żn a daw ać p ien iąd ze faceto m i trafia do niej o d e b ra n a im gotów ka.

namespace Your_Project_Name {
p u b l i c p a r t i a l c l a s s Forml Form
W zw iązku z tym
ż e używam y
obiektów Guy
do śledzenia I h ś ć gotówki
zmian w s tanie w banku zw iększa s ię
posiadania Boba i zm n iejsza w zależności
i Joego, m us'm y od te go, ile pieniędzy
zadeklarować p u b l i c Forml() { z o s tało przyznanych lub
zm ienne, u żywając InitializeComponent(): odebranych obiektom
typu Guy. typ u Guy.
}

172 Rozdział 3.
Obiekty: zorientuj się!

Dodaj metodę do formularza w celu odświeżania pól tekstowych.


P ola tekstow e p o lewej stro n ie pok azu ją, ja k dużo p ieniędzy je st w p o siad an iu Joego, B oba i ile jest
aktualn ie w zm iennej bank. M usisz zatem d odać m eto d ę U p d ate F o rm (), k tó ra będzie służyć do
odśw ieżania ich zaw artości — upewnij się, że typem wartości zwracanej jest v o id . W ten sposób C #
będzie w iedział, że nie zam ierzam y zw racać żadnej w artości. W pisz tę m eto d ę w kodzie fo rm u larza zaraz
p o d dek laracją zm iennej bank:
Ta nowa metoda j e s t prosta.
p u b lic v o id U pdateForm () { Ofewiezcx zaw artość trzech
Zauw aż, że etyk iety e ty kie t , u s tawiając ich
s ą uzupełniane jo e s C a s h L a b e l.T e x t = joe.N am e + " ma " + jo e .C a s h + wta ściwości Text. M u sisz ją
na podstaw ie pól Name wy wotywać w ramach obsługi
b o b sC ash L ab el.T ex t = bob.Name + " ma " + bob.C ash +
i Cash obiektów typu Guy. khkmgti każdego z trzech
b an k C ash L ab el.T ex t = "Bank ma " + bank + " z ł " ; p rzyc is ków, aby caty czas
pokazy wane byty aktualne dane.
}

Kliknij dwukrotnie każdy z przycisków i dodaj kod do interakcji z obiektami.


U pew nij się, że przycisk p o lewej stro n ie nazyw a się b u t t o n l , a ten p o praw ej b u tto n 2 . P o tem dw ukrotnie
kliknij każdy z nich — kiedy to zrobisz, ID E au tom atycznie w ygeneruje dwie m eto d y o nazwach:
b u t t o n l _ C l i c k ( ) i b u t t o n 2 _ C li c k ( ) . D odaj do nich następ u jący kod:

Już wiesz, że - p r i v a t e v o id b u tto n l_ C l ic k ( o b j e c t se n d e r EventA rgs e) {


możesz określać if (bank >= 10 ) { Gdy uży t k ownik klika przycisk ,Daj 10 z ł
nazwy kontrolek. \ J ° em u“, formularz wywołuje metodę
bank -= jo e .R e c e iv e C a s h (lO );
Czy b u t t o n l T Receive Cash() obiektu jo e — ale tylko wtedy,
U pdateF orm (); gdy bank ma wystarczającą ilość pieniędzy.
i b u tto n 2 ,
to naprawdę } e ls e
najlepsze nazwy, M essageBox.Show("Bank n ie p o sia d a t a k i e j i l o ś c i p ie n ię d z y "
które możesz
} Bank potrzebuje przynajmniej 1° zł,
wymyślić?
aby dać je Bobowi. Jeśli tyto m’e ma,
Jakie nazwy Ty w yśw ietla stosow ny komunikat.
byś nadał tym
przyciskom? - p r i v a t e v o id b u tto n 2 _ C lic k (o b je c t s e n d e r , EventA rgs e) {
bank += b o b .G iv eC a sh (5 );
U pdateF orm (); iN . Przyc isk _„W eź_5 j t od Boba’
nie musi spraw dzać, ile
} pieniędzy j e s t w banku t Jeśli Bob nie ma
ponieważ dodaj e do niego to, pieniędzy, to metoda
co dat Bob. GiveCash() zwróci z e r°.

Utwórz Joego ze stanem konta 50 zł i Boba ze stanem 100 zł.


Tylko od Ciebie zależy, czy znajdziesz sposób na to , aby Joe i Bob w y s ta rto w a li z popraw nie
uzupełnionym i polam i Cash i Name. Umieść odpowiedni kod zaraz pod I n i ti a li z e C o m p o n e n t( )
w formularzu. Będzie on częścią specjalnej metody, która jest wywoływana tylko raz podczas inicjalizacji
formularza. Jeśli już go wpisałeś, kliknij przyciski kilka razy, upewniając się, że jeden zabiera 10 zł z banku
i przekazuje je Joemu, a drugi zabiera 5 zł z konta Boba i dodaje je do banku.

p u b lic Form l() {


Dodaj tu taj linijki kodu do
In itia liz e C o m p o n e n t( ) ; utw orzenia dwóch obiektów
i ustaw ienia ich pól Name
/ / z a in ic ja liz u j tu ta j zmienne joe i bob! i Cash.
}
Ćwiczenie
jesteś tutaj ► 173
Rozwiązanie ćwiczenia

Tylko od Ciebie zależy, czy znajdziesz sposób na to, aby Joe i Bob w y s ta rto w a li z popraw nie
u zupełnionym i polam i Cash i Name. Umieść kod zaraz pod I n it ia liz e C o m p o n e n t ( ) .
Rozwiązanie
ćwiczenia
p u b l i c Forml() {
InitializeComponentQ;

To t u t a i u s ta w ia m y p ie rw sz ą ,
in s t a n c ję klasy Guy. P ie r w s z y \ bob = new Guy();
w ie r s z tw o rz y o b ie k t, d w a
p o z o s ta te w y p e łn ia ją je g o p o la . bob.Name = "Bob";
bob.Cash = 100;

joe = new Guy();


joe.Name = "Joe"; Potem robimy to samo dla
Upew nij się, że w ywołujesz drugiej instancji klasy Guy.
UpdateForm(). Dzięki tej m etodzie joe.Cash = 50;
pola tekstow e będą zawierały
poprawne dane podczas pierwszego
uruchomienia. UpdateForm();
}

Nie zapomnij o za p isa n iu


projektu — wrócimy do
i Nie.istnieią. niego kilka stron dalej.
głupie pytania

P : Dlaczego rozwiązanie nie zaczyna się od „Guy bob = P A co się stanie, jeśli nie pominę tego pierwszego
:
new Guy()” ? Z jakiego powodu opuściłeś pierwsze „Guy” ? „Guy” ? Co się stanie, kiedy w kodzie metody wpiszę Guy
bob = new Guy() zamiast bob = new Guy()?
O : Ponieważ już zadeklarowałeś zmienną bob na górze
formularza. Pamiętasz, że instrukcja i n t i = 5; jest O : Będziesz miał problemy — formularz nie będzie działał,
równoważna dwóm instrukcjom: i n t i ; oraz i = 5;? Tutaj jest ponieważ nie będzie nawet w stanie ustawić zmiennej bob.
podobnie. Mógłbyś zadeklarować zmienną bob w jednej linii w ten Jeśli masz na górze formularza taki kod:
sposób: Guy bob = new G u y ();,a le posiadasz już pierwszą p u b lic p a r t i a l c l a s s Forml : Form {
część instrukcji (Guy b o b ;) w górnej części klasy formularza.
Guy bob;
Potrzebujesz więc tylko drugiej połowy wiersza, czyli części, która
a gdzieś dalej, wewnątrz metody, wpisany jest taki fragment:
ustawia zmienną bob i tworzy nową instancję klasy G u y ().
Guy bob = new G u y();

P : Dobrze, dlaczego w takim razie nie mogę usunąć znaczy to, że masz zadeklarowane dwie zmienne. Jest to trochę
wiersza „Guy bob;” na górze kodu formularza? mylące, ponieważ obie mają tę samą nazwę. Pierwsza z nich jest
jednak widoczna dla całego formularza, a druga — ta nowa, którą
O : Wtedy zmienna bob istniałaby tylko wewnątrz tej specjalnej dodałeś — obowiązuje tylko wewnątrz metody. Następny wiersz
metody p u b l i c F o r m l( ) . Kiedy deklarujesz zmienną wewnątrz (bob.Name = " B o b " ; ) modyfikuje zawartość samej zmiennej
jednej metody, nie możesz uzyskać dostępu do niej z żadnej innej. lokalnej i nie ma żadnego wpływu na zmienną formularza.
Jeśli jednak zadeklarujesz ją na zewnątrz metody, ale wewnątrz Kiedy więc spróbujesz uruchomić program, otrzymasz straszny
formularza lub innej dodanej klasy, to możliwy jest dostęp do niej komunikat o błędzie („NullReferenceException was unhandled"),
z poziomu każdej innej metody w obrębie formularza lub który oznacza, że próbowałeś użyć obiektu, zanim go utworzyłeś
tej klasy. za pomocą słowa kluczowego new.

174 Rozdział 3.
Obiekty: zorientuj się!

Jest jeszcze prostszy sposób inicjalizacji obiektów Inicjalizatory


Praw ie każdy obiekt, k tóry utw orzysz, będzie m usiał zostać w jakiś obiektów
sposób zainicjalizow any. O b iek t klasy Guy nie stanow i tu taj w yjątku — jest
bezużyteczny, dopóki nie zo stan ą ustaw ione jego p o la Name i Cash. Inicjalizacja oszczędzają
p ó l to ta k częsta czynność, że C # u d o stę p n ia skró co n ą form ę zapisu, zw aną
inicjalizatorem obiektu . P om oże Ci tu tech n o lo g ia IntelliSense ID E .
czas, czynią
kod bardziej
zwięzłym
T o je st oryginalny kod, który n apisałeś w celu inicjalizacji i ułatwiają jego
ob iek tu j o e typu Guy.
czytanie...
a IDE pomaga Ci
je pisać.

U suń drugi i trzeci w iersz, śred n ik w pierwszym p o słowie GuyO


i dodaj otw ierający naw ias klam rowy.

joe = new Guy() {

^3 N aciśnij spację. G dy to zrobisz, ID E wyświetli o k n o IntelliSense,


k tó re p o k aże Ci wszystkie m ożliw e p o la do inicjalizacji.

N aciśnij Tab, aby d odać p o le Cash. U staw jego w artość n a 50.

jio e = new G u y ( ) { C a s h = 50

W pisz przecinek. Pojaw i się w tedy kolejne pole.

U zupełnij do k ońca inicjalizator obiektu.


3
joe = n e w Guy() { C ash = 50* N ame = "3oe"

Ten nowy rodzaj deklaracji rot)i M ła d n u s


to sam o co trzy oryginalne w iersze kodu
napisane w cześniej. J e s t P° prostu
krótszy i ła tw iejszy do c.zytam a .

jesteś tutaj ► 175


Kilka pom ocnych wskazówek

Kilka pomysłów na projektowanie intuicyjnych klas


* Tworzysz swoje programy do rozwiązywania problemów.
Pośw ięć tro ch ę czasu n a przem yślenie p ro b lem u . Czy daje się on łatw o rozłożyć n a m niejsze
części? W jaki sposób wyjaśniłbyś go kom uś inn em u ? T o są w ażne pytania, n a d którym i należy się
zastanow ić przy p ro jek to w an iu klas.

BYŁOBY WSPANIALE,
GDYBYM MÓGŁ PORÓWNAĆ KILKA
TRAS I SPRAWDZIĆ, KTÓRA JEST
NAJKRÓTSZA...

\
* Jakich przedmiotów z życia codziennego będzie używał Twój program?
A plikacja pom ag ająca pracow nikow i zoo przy plan o w an iu k arm ien ia m ogłaby zaw ierać
klasy dla różnych typów je d ze n ia i różnych gatunków zwierząt.

^
* Używaj opisowych nazw klas i metod.
K toś patrzący n a nazwy klas i m e to d pow inien być w stan ie odgadnąć,
jakie jest ich przeznaczenie.

* Szukaj podobieństw w klasach.


C zasam i dwie klasy m ogą zostać po łączo n e w jed n ą, jeżeli są n ap raw d ę p o d o b n e. System
do tw orzenia słodyczy m ógłby m ieć trzy lub cztery turbiny, ale i ta k istniałaby tylko je d n a
m eto d a do zam ykania zaw oru p o b ierająca n u m e r turb in y w p ostaci p aram e tru .

B lo c k e d R o a d
Name C lo s e d R o a d
Duration StreetName
ReasonltsClosed
FindDetourO
CalculateDelay()

176 Rozdział 3.
Obiekty: zorientuj się!

Dodaj przyciski do programu „Zabawa z Joem i Bobem", aby możliwe było


przekazywanie pieniędzy pomiędzy nimi.
Ćwiczenie

UŻYJ INICJALIZATORA OBIEKTU, ABY USTAWIĆ POLA OBIEKTU KLASY GUY


REPREZENTUJĄCEGO BOBA.
Już to zrobiłeś w p rzy p ad k u Joego. Spraw teraz, aby in stan cja B o b a tak że używ ała in icjalizatora obiektu.
Jeżeli ju ż kliknąłeś przycisk, to po prostu
ao usuń, je s z c z e rax d°daj d° h m t u h n a
i zm ień j ego nazwę. Usuń teraz
button3_Click(), którą wc ześn <ej dodało
i używaj nowej m etody.
Dodaj jeszcze dwa przyciski do formularza.
Pierwszy przycisk każe Jo em u dać 10 zł Bobowi, drugi nakazuje Bobow i oddać
5 zł Joem u. Zanim dwukrotnie klikniesz któryś przycisk, przejdź do okna
Properties i zm ień nazw ę każdego z nich, używając w iersza Name — znajduje się
on na samej górze listy właściwości. Nazwij pierwszy przycisk joeGivesToBob,
a drugi bobGivesToJoe.

Ten przycisk każe Joemu


dać 10 z t Bobowi, d h te g 0
powinieneś u żyć elem entu
Name w oknie Properties
do zm iany jeg o nazwy
na joeG ivesToBob. Ten przycisk każe
Bobowi oddać 5 z t
Joem u. Nazwij go
bobGivesToJoe.

SPRAW, ABY PRZYCISKI DZIAŁAŁY.


Kliknij dw ukrotnie przycisk jo eG iv esT o B o b w p ro jek tan cie form ularzy. ID E d o d a m etod ę
do fo rm u larza i nazw ie ją jo e G iv e s T o B o b _ C lic k (). B ędzie o n a u ru ch a m ia n a zawsze
po kliknięciu przycisku. U zupełnij tę m e to d ę tak, aby Jo e przekazyw ał 10 zł Bobowi.
T eraz dw ukrotnie kliknij drugi z przycisków i wypełnij m eto d ę b o b G iv e s T o J o e _ C lic k ()
u tw orzo n ą p rzez ID E , ta k aby B ob daw ał 5 zł Jo em u . U pew nij się, że fo rm u larz odśw ieża
swoją zaw artość p o każdej zm ianie ilości posiadanych p rzez nich pieniędzy.

A to jest porada dotycząca pro je kto w a n ia form ularzy. Możesz skorzystać


z przedstaw ionych poniżej przycisków dostępnych na pasku narzędzi pro je kta nta
form ularzy, by w y ró w n y w a ć ko n tro lki, nadać im identyczne w ym iary, w yró w n a ć
odległości m iędzy nim i, bądź też w y ś w ie tlić jedną kon tro lkę ponad pozostałym i.

I- = -I F W i i | w I K i ' I* = | if . ' jesteś tutaj ► 177


Rozwiązanie ćwiczenia

Dodaj przyciski do programu „Zabawa z Joem i Bobem", aby możliwe było


przekazywanie pieniędzy pomiędzy nimi.
Rozwiązanie
ćwiczenia

public p a rtia l class Forml Form lo s ą imcjaliz a tory obiektów dla


Guy j o e ; dwóch instancji klasy Guy. Bob
Guy bob; inicjalizowan¥ j e s t z w artością
i n t bank = 100; 100 z ł i sw o im imieniem.

p u b l i c Forml() {
InitializeComponent();
bob = new Guy() { Cash = 100, Name = "Bob" };
joe = new Guy() { Cash = 50, Name = "Joe" };
UpdateFormQ;

p u b l i c v o i d Up d a t e F o r mQ {
joesCashLabel.T ext = j oe . Na me + " ma " + j o e . C a s h + zV ' A b y Joe dał
bobsCashLabel.Text = bob.Name + " ma " + b o b . C a s h + zT" pieniądze Bobowi,
bankCashLabel.Text = "Bank ma " + bank + " z l " ; wywołujemy metodę
GiveCash() obiektu
jo e i przekazujem y
je j wynik do metody
p r i v a t e v o i d b u t t o n l _ C l i c k ( o b j e c t s e n d e r , Ev e n t Ar g s e) {
ReceiveCash() Boba.
i f (bank >= 10) {
bank -= j o e . R e c e i v e C a s h ( l O ) ;
UpdateFormQ;
} else {
MessageBox. Show("Bank n i e p o s i a d a t a k i e j i l o ś c i p i e n i ę d z y " ) I
Przyjrzy j s ię
dokładnie sposobowi
wywoływania metod.
Cała zabawa p r i v a t e void b u tto n 2 _ C lic k (o b je c t sender, Ev e n t Ar g s e) { Wyniki zwrócone
polega na bank += b o b . G i v e C a s h ( 5 ) ; przez GiveCash() są
tym , aby p rzekazywane w prost
zorientow ać UpdateFormQ;
do ReceiveCash()
się, kto daje w postaci
pieniądze parametru.
i kto je p r i v a t e void joeGivesToBob_Click(object sender, Ev e n t Ar g s e)
otrzym uje. bob.ReceiveCash(joe.G iveCash(10));
UpdateFormQ;

p r i v a t e void bobGivesToJoe_Click(object sender, Ev e n t Ar g s e)


joe.ReceiveCash(bob.G iveCash(5));
UpdateFormQ;

Zanim przejdziesz dalej, poświęć m inutkę, by zajrzeć do punktu 2. dodatku


„Pozostałości” — przedstawiliśm y tam pewną prostą składnię stosowaną
w języku C#, któ re j tu nie opisaliśmy. Nie jest ona niezbędna, by kontynuować
178 Rozdział 3. le kturę książki, niemniej jednak w arto ją poznać.
Obiekty: zorientuj się!

Zagadkowy basen. Rozwiązanie


Tw oim zadaniem było p o b ra n ie kolejnych fragm entów
ko d u z b asen u i w staw ienie ich w odpow iednie
p u ste m iejsca w kodzie. Celem było
utw o rzen ie klasy, k tó ra się skom piluje, będzie
działała i będzie w yśw ietlała p o kazany
kom unikat.

p u b lic p a r t i a l c l a s s Forml : Form


{
p r iv a t e v o i d b u t to n l_ C lic k ( o b je c t sen de r, EventArgs e)

{ To jest poprawna odpowiedź.


s t r in g r e s u lt =
A tu jest odpowiedź na pytanie
Echo e l = new Echo( ) ;
dodatkowe!
Echo e2 - new Echo();______
Echo e2 = e1; |
i n t x = 0;
while ( x <4 ) {
r e s u lt = r e s u lt + el.Hello() + "\n"
e l.co un t = e1.count + 1 ;______
if ( x==3 )
e 2 .c o u n t = e 2 .c o u n t + 1;

}
if ( x >0 )
e 2 .c o u n t = e 2 .c o u n t + e l.c o u n t

}
x = x + 1;

}
M essageBox.Show (result + " L i c z n i k : " + e2.count);

}
}
p u b l ic class Echo_____ {
public in t count = 0;
public s trin g Hello ( ) {
r e tu r n "w itaaaj...";

}
}

jesteś tutaj ► 179


180 Rozdział S.
4. Typy i referencje
Jest 10:00.
^ Czy wiesz, gdzie są Twoje dane?
+

Typ danych, baza danych, dane komandora porucznika... wszystko to są


ważne rzeczy. Bez danych Twoje programy są bezużyteczne. Potrzebujesz in fo rm acji
dostarczanych przez użytkowników i na ich podstawie wyszukujesz lub tworzysz nową informację
i zwracasz im ją. W rzeczywistości prawie wszystko, co robisz podczas programowania, sprowadza
się do pracy z danym i w taki czy w inny sposób. W tym rozdziale dowiesz się o różnych aspektach
ty p ó w danych C#, nauczysz się pracować z danymi w programie, a nawet odkryjesz kilka pilnie
strzeżonych sekretów o b ie k tó w ( psssst... obiekty to także dane).

to jest nowy rozdział ► 181


Nie m ój typ

Typ zmiennej określa rodzaj danych, W szystkie program y przedstaw ione


w tym rozdziale są pro je kta m i typu
jakie zmienna może przechowywać W ind ow s Forms A pp lication . Jeśli
poprosim y Cię w tym rozdziale o
Język C # u d o stęp n ia kilkanaście w budow anych t ypów d a n y c h , a każdy u tw o rze n ie now ego projektu, lecz
przechow uje inny rodzaj inform acji. W idziałeś ju ż kilka bardziej nie określim y jego typu , załóż, że
użytecznych z nich i wiesz, ja k się je stosuje. Istnieje je d n a k kilka typów, chodzi o projekt W ind ow s Forms
A p p lica tio n tw o rz o n y przy użyciu
których jeszcze nie poznałeś, a k tó re także m ogą być b ard zo pom ocne.
Visual Studio fo r W ind ow s Desktop.

Typy danych, których będziesz używał cały czas Liczba cafkowita


N ie będzie dla C iebie n iespodzianką, że i n t , s t r i n g , bool i d o u b l e są najpow szechniej nie ma przecinka.
stosow anym i typam i.

+ i n t m oże przechow yw ać liczby całkowit e z zakresu o d - 2 147 483 648


do 2 147 483 647.
+ s t r i ng m oże przechow yw ać tek st dow olnej długości (w łączając w to te k st pusty
+ bool je st w artością logiczną — m oże być rów ny t r u e lub f a l s e .
+ d o u b l e m oże przechow yw ać liczbę rzeczywi st ą z zakresu o d ±5,0-10 '324
„ flo a f to skrót
do ±1,7-10 308 z m aksym alnie 16 cyfram i znaczącym i. Z a k re s ten m oże wydawać
od „ffoat-ing p o in t' (liczba
się nieco dziwny i skom plikow any, ale w rzeczywistości jest całkiem prosty. zmiennoprzecinkowa) —
Sform ułow anie „cyfry znaczące” oznacza precyzję liczby. 35 048 410 000 000, j e s t to przeciw ieństw o
liczby „fixed p o in t' (liczba
1 743 059, 14,43857, 0,00004374155 — wszystkie te liczby m ają siedem cyfr stałop rzecinkowa), która
znaczących. 10308 oznacza, że m ożesz przechow yw ać liczby ta k w ielkie ja k 10308 zaw sze ma taką sam ą
liczbę cy fr po przecinku.
(1 i 308 zer), o ile m ają tylko 16 cyfr znaczących. N a drugim k ońcu zak resu m am y
10'324, co znaczy, że m ożesz przechow yw ać liczby ta k m ałe ja k 10 '324 (lub 323 zera
po p rzecin k u i 1 ) ... ale, ja k ju ż się pew nie dom yślasz, d opóki m ają tylko szesnaście
lub m niej cyfr znaczących.

W wielu przypadkach
używ a sz tych
Więcej typów dla liczb całkowitych typów, bo chcesz
rozw iązyw ać
Kiedyś, daw no, daw no tem u , p am ięć k o m p u tera była b ard zo droga, a p rocesory były p roblemy, w których
napraw dę w olne. M ożesz w ierzyć lub nie, ale użycie niepraw idłow ego typu m ogło w yraźnie pi-zydatny ^j e s t efe k t
„p rzepełnienia".
spow olnić działanie całego p ro g ram u . N a szczęście czasy się zm ieniły i w większości
Pr-zeczy ta s z o tym
przypadków do przechow yw ania liczb całkow itych m ożesz p o p ro stu użyć i n t . C zasam i za kilka m inut.
je d n a k napraw d ę będziesz p o trzeb o w ał czegoś w ię k s z e g o . i raz n a jakiś czas także czegoś
m niejszego. T o dlatego C # daje Ci znacznie w ięcej opcji:

* b y t e m oże przechow yw ać liczby całkowit e z zak resu o d 0 do 255.


* s b y t e m oże przechow yw ać liczby całkowit e z zak resu o d -1 2 8 do 127.
* s h o r t m oże przechow yw ać liczby całkowit e z zak resu o d -3 2 768 do 32 767.
„u" w u in t * u s h o r t m oże przechow yw ać liczby ca ł kowi t e z zakresu o d 0 do 65 535. „s" w nazw ie ty p u sb y te j e s t
j e s t skrótem * skrótem od angielskusgo s t° wa
od „unsigned". L* u i n t m oże przechow yw ać liczby całkowit e z zak resu o d 0 do 4 294 967 295. „signed" (ze znakiem ), m oznacza>
* l o n g m oże przechow yw ać liczby całkowit e p om iędzy - 9 i + 9 m iliardam i. że liczba może być ujem na.

* u l o n g m oże przechow yw ać liczby całkowit e p om iędzy 0 i około 18 m iliardam i.

182 Rozdział 4.
Typy i referencje

Typy do przechowywania naprawdę DUŻYCH i naprawdę małych liczb


W niektórych przypadkach si edem cyfr znaczących nie jest wystarczająco dokł a dną reprezentacją.
Możesz wierzyć lub nie, ale czasami nawet 1038 nie jest wystarczająco duże, a 10'45 wystarczająco małe.
Większość p r o g r a mó w napisanych w celach finansowych lub dla przepr owadzeni a ba d a ń naukowych
boryka się z tymi probl emami. C # ud o st ę p n i a więc dodat kowe typy:
Typ double
Jeśli Twój program * f l o a t mo ż e przechowywać liczby z zakresu o d ±1,5-10'45 do ±3,4-1038 z si edmi oma je s t używany
operuje na kwotach cyframi znaczącymi. znacznie częściej
pieniężnych, niż float. Wiele
to zazwyczaj * doubl e mo ż e przechowywać liczby z zakresu o d ±5,0-10'324 do ±1,7-10308 posiadające wtaściwości
będziesz chciat 15 - 16 cyfr znaczących. XAML ma
przechowywać wartości typu
je jako w artości * d e c i mal może przechowywać liczby z zakresu o d ±1,0-10"28 d o ±7,9-1028 double.
typu decimal. v ^ posiadające 28 - 29 cyfr znaczących.
h S * ------------- Literat oznacza liczbę, k t ó r ą i ^ s u j e s z ^ " ^
Gdy w staw iasz „ m t i o, , J Kiedy używ ałeś
Także literały mają typy właściwości
Vdlue w kontrolce
Kiedy wpisujesz liczbę bezpośr ednio w kodzie p r o g r a m u C # , używasz literału... a każdemu NumericUpDown,
literałowi jest automatycznie przypisywany typ. Możesz sam się o tym przekonać, wstawiając korzystałeś z typu
decimal.
wiersz kodu, który przypisuje literał 1 4 . 7 do zmiennej i n t :

i n t myl nt = 14. 7;
r Description

Te r az spróbuj zbudować program. Dostani esz taki komunikat: ® 1 C a n n o t im p lic itly c o n v e r t t y p e 'd o u b l e 1 t o 'i n t 1. A n e x p lic it c o n v e r s io n
e x is ts ( a r e y o u m is s in g a c a s t? )

T o ten sam błąd, który pojawi się podczas przypisywania wartości i n t do zmiennej typu
d o u b l e . I D E w tym mo me n c i e próbuj e Ci powiedzieć, że literał 1 4 . 7 m a typ — jest to doubl e .
Jeśli spróbujesz
Możesz zmienić go n a f l o a t , dopisując F na końcu (14. 7F) . 14.7M będzie z kolei traktowane przypisać literat
równoznacznie z t ypem d e c i m a l . typu float do
Lo era to ^ r ó t od angielskiego wartości double lub
Nieco więcej użytecznych typów wbudowanych s 0'e a “money (pieniądze) — serio! literat typ u decimal
do wartości float,
Czasami będziesz chciał zapisać w zmiennej pojedynczy znak, taki jak Q, 7 bą d ź $. W takich to IDE w yśw ietli
przypadkach możesz skorzystać z typu c h a r . Wart ości literału dla typu c h a r są zawsze pomocną wiadomość
przypominającą
zapisywane pomi ędzy znakami apostrofów ( ' x ' , ' 3 ' ) . W apostrofach możesz także umieścić o dodaniu
sekwencje formatuj ące ( ' \ n ' to zn a k nowej linii, ' \ t ' to tabulacja). Są o n e zapisywane odpowiedniej
w kodzie C # jako ciąg dwóch znaków, j e d n a k pr o g r a my zapisują je w pamięci jako j e d en znak. końcówki. Św ietnie!

W rozdziale 9
I w końcu przechodzi my do jeszcze jednego, niezwykle ważnego typu: o b ject . Już widziałeśs, dowiesz się
w jaki sposób mo ż n a tworzyć obiekty, tworząc instancje wybranej klasy. Cóż, każdy z tak znacznie więcej
na tem a t
utworzonych obiekt ów mo ż n a zapisać w zmiennej t ypu o b j e c t . W dalszej części tego rozdziału
związków
dowiesz się wszystkiego o obiektach oraz zmiennych odwołujących się do nich. pom iędzy typami
c har i byte.

WYSIL Program K a ^ M o r - dostępny w sy ste m ie Windows oferuje bardzo ciekawą


opcję widok ,P rogramisty “, w ktiórym jednocześnie pokazywana je s t
SZARE KOMÓRKI wartość zap isana w postaci d ziesiętn ej i binarnej.
Możesz użyć kalkulatora Windows do przełączania się pomiędzy liczbami dziesiętnymi (zwykłe, o podstawie 10) i binarnymi
(liczby o podstawie 2 złożone wyłącznie z zer i jedynek). W tym celu przełącz go w tryb programisty, wpisz liczbę i naciśnij
przycisk opcji Bin, aby przejść na wartości binarne. Wybierz następnie Dec, aby przeprowadzić konwersję odwrotną. W pisz
teraz kilka górnych i dolnych granic dla liczb ca łko w itych (na przykład -3 2 768 i 255) i zamień je na wartość binarną.
Czy już wiesz, dlaczego C# udostępnia takie zakresy?

jesteś tutaj ► 183


Poproszę gałkę lodów na wynos

Zmienna jest j alc Icu M z Ornymi


na stercie . Typy wartościowe
W szystkie Tw oje dan e zajm ują kaw ałek przestrzen i w pam ięci.
P rzypom inasz sobie ste rtę z p o p rzed n ieg o rozdziału? C zęścią Twojej pracy
je st m yślenie o tym , ile m iejsca będziesz p o trzeb o w ał do użycia w p ro g ram ie
tek stu lub liczby. Jest to je d e n z pow odów używ ania zm iennych. Pozw alają
o ne zarezerw ow ać w ystarczającą ilość p am ięci do przechow yw ania danych.

W yobraź sobie zm ienn ą jak o kubek, w którym d an e b ę d ą przechow yw ane.


C # używ a całego zestaw u kubków do przechow yw ania różnych rodzajów
danych. M ożna dostać ró żn e kubki w kaw iarni i, analogicznie, m o żn a m ieć
różne rozm iary zm iennych.
dla T c zb c a łk o w ity c h . P rz e c h o w u je
liczb y o w a r to ś c ia c h do
2 147 483 647.

_ s h o r t b ę d z ie p rz e c h o w y w a ł
aS liczb y c a łk o w ite
o w a r to ś c ia c h do 32 767.
Liczb long
b ędziesz
używa t w tedy,
gdy będą one b yte przechowuje liczby
naprawdę pom iędzy 0 i 255.
duże.

„czba bitów, m / p o d c z a s „ .U r w a n i a należy z a r . z . r w ^ a ó dla ^ n l.n n .j w


To

Liczby, k tó re po siad ają m iejsca d ziesiętne (po p rzecinku), przechow yw ane


są w nieco inny sposób niż liczby całkow ite. W w iększości zastosow ań
liczb zm iennoprzecinkow ych w ystarczy typ f l o a t , który je st najm niejszym
spośród typów rzeczywistych. G dy chcesz być bardziej precyzyjny, użyj
float double decimal
d o u b l e , a jeśli piszesz aplikację finansow ą, k tó ra b ędzie posługiw ać się
w artościam i w alutow ym i, to bez w ątp ien ia będziesz chciał zastosow ać 32 64 128
typ d e c i m a l .

N ie dotyczy to tylko liczb. (N ie spodziew ałbyś się n a pew no gorącej kawy


w plastikow ym kubeczku lub zim nej w papierow ym ). K o m p ilato r C # m oże
także obsługiw ać znaki i typy nienum eryczne. Typ c h a r p o tra fi przechow ać
jed en znak, n ato m iast s t r i n g je st używ any przy w ielu połączonych ze sobą
znakach. N ie m a ustalo n eg o ro zm iaru dla zm iennej s t r i n g . R ozszerza
się o n a w ten sposób, aby pom ieścić w szystkie d an e, k tó re chcesz w niej
m ieć. Typ bool jest stosow any do przechow yw ania w artości t r u e i f a l s e .
Są to te sam e w artości, których używałeś w instrukcjach i f .
bool char string
8 16 jego wielkość
zależy
od rozmiaru
tekstu
184 Rozdział 4.
Typy i referencje

10 kilogramów danych w pięciokilogramowej torebce

K iedy d eklarujesz zm ien n ą danego typu, decydujesz o sposobie,


w jaki będzie o n a trak to w an a p rzez ko m p ilato r. N aw et wtedy,
gdy w artość je st daleko o d górnej granicy zadeklarow anego typu,
k o m p ilato r i tak b ędzie w idział tylko kubek, w którym je st ona
przechow yw ana, a nie liczbę w ew nątrz. N astępujący frag m en t k o du
nie będzie w ięc działał:

i n t leaguesUnderTheSea = 20000;
s h o r t s ma l l er L e a g u e s = l eaguesUnderTheSea;

20 000 zm ieściłoby się w typie s h o r t — b ez p ro b lem u . D opóki


je d n a k zm ien n a l e a g u e s U n d e r T h e S e a je st zad ek laro w an a ja k o i n t ,
k o m p ilato r uzn aje ją za zbyt dużą i nie chce jej um ieścić w kubeczku
s h o r t . N ie p rzeprow adzi on tych konw ersji za C iebie. Sam musisz
się upew nić, że stosujesz praw idłow y typ dla danych, z którym i
pracujesz.

ynnstrz ołówek
Trzy z podanych instrukcji nie skompilują się, ponieważ próbują
do mniejszej zmiennej wrzucić zbyt dużo danych lub przypisać do niej
wartość nieprawidłowego typu. Zakreśl je.

int h o u r s = 24; string taunt = "Twoj a m a t k a " ;

s h o r t y = 78000; b y t e days = 365;

bo ol isDone = y e s ; long r a d i u s = 3;

short RPM = 3 3 ; char in itia l = 's ';

i n t b a l a n c e = 345667 - 567; s t r i n g months = "12";

jesteś tutaj ► 185


Potrzeba rzutow ania

Nawet wtedy, gdy liczba ma prawidłowy rozmiar,


nie możesz przypisać jej do każdej zmiennej
Spraw dźm y, co się stan ie przy p ró b ie przypisania w artości typu d e c i mal
do zm iennej i n t .
Z r ó b to !

r» U tw órz nowy p ro je k t typu W indow s F orm s A p p licatio n i dodaj do niego


przycisk. N a stęp n ie w pisz poniższe w iersze k o d u do jego m eto d y C l i c k ( ) :
4
decimal myDecimalValue = 10;
i n t myIntValue = myDecimalValue;

MessageBox.Show("myIntValue j e s t równe " + myI nt Val ue) ;

,2l Spróbuj zbudow ać swój pro g ram . U p s ... — dostaniesz k o m u n ik at taki jak ten:

Spraw dź, w jaki


s p o s ó b ID E odkryto,
że prawdopodobnie
zapom niałeś rzutować.
1 Cannot im p lic itly convert type 'decim al' to 'in t'. A n explicit conversion
exists (are you missinq a cast?)

^ U su ń b łąd p o p rzez rzutowanie typu d e c i mal n a i n t . P o tym ja k zam ienisz drugi


w iersz n a zaprezentow any poniżej, p ro g ram skom piluje się i b ędzie działał.

i n t myIntValue = ( i n t ) myDecimalValue;
To j e s t m iejsce, gdzie rzu tu jesz
. ł - w artość decimal na m t.
Co się sta ło ?
Poświęć m inutę
K om pilator nie pozw oli Ci przypisać w artości do zm iennej, k tó ra m a niepraw idłow y typ — naw et i przejdź d° początku
poprzedniego rozdziału.
wtedy, gdy bez problem ó w m ogłaby o n a p rzechow ać ta k ą w artość. Skutkow ałoby to n iezliczoną ilością S p ra wdź, w ja ki sposót
błędów w p rogram ie, a k o m p ilato r po m ag a, zm uszając C ię do p o stęp o w an ia w praw idłow y sposób. rzutow ałeś podczas
K iedy rzutujesz, składasz w ażną obietn icę — ośw iadczasz kom pilatorow i, że wiesz, iż dwa typy są p rzekazyw ania wartości
NumericUpOown.Va/ue
różne, je d n a k w tym ko nkretnym przy p ad k u przypisanie danych do now ej zm iennej jest praw idłow e. do formularza
Test gadania.
Zaostrz ołówek _______________________________________________________

V Rozwiązanie
Trzy z podanych instrukcji nie skompilują się, ponieważ próbują
do mniejszej zmiennej wrzucić zbyt dużo danych lub przypisać do niej
wartość nieprawidłowego typu. Zakreśl je.

Tup sh o rt przechowuje
w artości od - 3 2 768 b y te days = 365;
do 32 767. Ta liczba
*
j e s t za duża!
Typ byte może przechowywać
liczby m niejsze niż 256
Do ty p u bool można Do tego przypisania
Przy p isać tylko wartości p otrzebujesz typu short.
„■true" i „false".
Więcej informacji na temat typów wartościowych można znaleźć
na stronie http://msdn.m icrosoft.com/pl-pl/library/s1ax56ch.aspx.
186 Rozdział 4.
Typy i eferencje

s p e łn ij to S 9 tn l
Kiedy rzutujesz wartość, która jest zbyt duża,
C# dopasowuje ją automatycznie ™ o w n i e o Z S Z ' ,m 'iak

Już w idziałeś, że typ d e c i ma l m oże być rzutow any n a i n t . Jak


się okazuje, każda liczba m oże być rzu to w an a n a dowolną inną. ^ dziele n/a 365 przez 7 ^ ,,y ' P cz reszt?

N ie znaczy to w cale, że wartość p o zo staje w tedy ta k a sam a. P i a s k u „M od"rkt6rf d60 (tUŻyWf ^ C


G dy rzutujesz zm ien n ą i n t o w artości 365 n a typ b y t e , 365 jest O trzym aszw artośćS g0SłUZyX
liczbą dla niego zbyt dużą. Z am ia st w yśw ietlenia b łęd u w artość
zostaje obcięta z uw zględnieniem efek tu p rzep ełn ien ia: n a przykład
256 rzutow ane n a b y t e będzie m iało w artość 0, 257 zostanie
przekonw ertow ane n a 1, 258 n a 2 i ta k dalej aż do liczby 365, Zaostrz ołówek
k tó ra stanie się w artością 109 . Jeśli jeszcze raz dojdziesz do 255,
zw iększenie w artości spow oduje p rzep ełn ien ie i p o w ró t do zera. Nie zawsze możesz rzutow ać
d o w o ln y typ na każdy inny. Utwórz
nowy projekt, przeciągnij przycisk na formularz,
HEJ! ŁĄCZYŁEM LICZBY dwukrotnie go kliknij i wpisz poniższe instrukcje do
jego metody. Teraz zbuduj program — spowoduje to
Z TEKSTEM W MOICH
powstanie pokaźnej listy błędów. Wykreśl te instrukcje,
OKNACH MESSAGEBOX, ZANIM
które są ich przyczyną. To pozwoli Ci przekonać się,
JESZCZE NAUCZYŁEM SIĘ PĘTLI
które typy mogą być rzutowane, a które nie!
W ROZDZIALE 2 .! CZY PRZEZ CAŁY
TEN CZAS KONWERTOWAŁEM?
i n t myl nt = 10;

b y t e myByte = ( b y t e ) my l nt ;

d oubl e myDouble = (double)myByte;


Tak! Operator + robił to za Ciebie.
bool myBool = (bool)myDouble;
Po p ro stu używ ałeś o p e ra to ra + , który
s t r i n g mySt ri ng = " f a l s e " ;
automatycznie przeprowadza za Ciebie
operacje konwersji, ale je st w tym wyjątkowo myBool = ( b o o l ) my S t r i ng ;
sprytny. K iedy używasz + w celu d o d an ia liczby
mySt ri ng = ( s t r i n g ) m y l n t ;
lub w artości logicznej do tek stu , d o k o n u je on
jej autom atycznej konw ersji n a typ s t r i n g . Jeśli mySt ri ng = m y I n t . T o S t r i n g ( ) ;
używasz + (czy też *, / lub - ) przy dw óch różnych myBool = (bool)myByte;
Kiedy typach, to o p e ra to r automatycznie konwertuje
p rzyp isu jesz do myByte = (byte)myBool;
float wartość mniejszy typ na większy. T u taj je st przykład:
liczbową, m usisz s h o r t myShort = ( s h o r t ) m y l n t ;
na końcu liczby i n t myl nt = 36;
um ieścić F. —<" c h a r myChar = ' x ' ;
f l o a t myFloat = 16.4F
Dzięki tem u
kompilator myFloat = myl nt + myFl oat; mySt ri ng = ( s t r i ng ) my Ch a r ;
będzie wiedział,
ż e j e s t to float, long myLong = (l o n g ) my l nt ;
W zw iązku z tym , że i n t m oże się zmieścić
nie double.
w typie f l o a t , a f l o a t nie m oże się zmieścić decimal myDecimal = (decimal)myLong;
w typie i n t , o p e ra to r + będzie rzutow ał zm ienną mySt ri ng = mySt ri ng + myl nt + myByte
m y l n t n a typ f l o a t , zanim d o d a ją do m y F l o a t . + myDouble + myChar;

jesteś tutaj ► 187


Prawdziwa konwersja

C# przeprowadza niektóre rzutowania automatycznie


Istn ieją dwie w ażne konw ersje, k tó re nie w ym agają jaw nego rzutow ania.
Pierw sza z nich jest p rzep ro w ad zan a autom atycznie za każdym razem ,
gdy w ykonujesz operacje m atem atyczne, ta k ja k w tym przykładzie:

long l = 139401930; Operator — odjąf short


od long, natomias t
s h o r t s = 5 1 6; operator = spowod°wat ^ Zaostrz
¿.UUaLF ołówek
double d = l

d = d / 123.456;
- s;
przekonwertowanie wyniku
na double.
V Rozwiązanie
Nie zawsze możesz rzutow ać d o w o ln y typ
na każdy inny. Utwórz nowy projekt, przeciągnij
M e s s a g e B o x . S h o w ( " O d p o w i e d ź b r z mi "^+Jd); przycisk na formularz i wpisz poniższe instrukcje
do jego metody. Teraz zbuduj program — spowoduje
Operator + j e s t wystarczająco to powstanie pokaźnej listy błędów. Wykreśl te
sprytny, aby zam ienić decimal instrukcje, które są ich przyczyną. To pozwoli Ci
na string. przekonać się, które typy mogą być rzutowane,
Jest jeszcze je d n a sytuacja, w której C # p rzep ro w ad za a które nie!
autom atyczną konw ersję typów — jest n ią k o n k a te n a c ja , czyli
łączenie łańcuchów znaków (co oznacza, że je d n a w artość typu i n t myInt = 10;
s t r i n g je st przyklejana n a kon iec drugiej — ta k sam o, jak
b y t e myByte = ( by t e) my I nt ;
to robiłeś w oknach M essageBox). K iedy używasz + do łączenia
łańcucha znaków z danym i in nego typu, liczby są autom atycznie doubl e myDouble = (double)myByte;
konw ertow ane na tekst. M am y tu taj tego przykład. D w a
--bool myBool - (bool)myDouble,
początkow e w iersze są p o p raw n e, ale trzeci nie skom piluje się:
s t r i n g mySt r i ng = " f a l s e " ;
lo n g x = 139401930;
—myBool = ( b o o l ) my S t r i ng ;
M e s s a g e B o x . S h o w ( " O d p o w i e d ź b r z mi 11 + x ) ; mySt ri ng = ( s t r i n g ) m y l n t ;

MessageBox.Show(x); mySt ri ng = m y I n t . T o S t r i n g ( ) ;

myBool - (bool)myByte;
K om pilator C # zgłosi b łąd mówiący o niepraw idłow ym
argum encie (w języku C # arg u m e n te m nazyw am y w artość myByte - (byte)niyBool,
przekazyw aną do p a ra m e tru m etody). T o dlatego, że p a ra m e trem s h o r t myShort - ( s h o r t ) m y l n t ;
MessageBox.Show() je st s t r i n g , a p o d an y k o d przekazuje
c h a r myChar - ' x ' ;
l o n g , co je st niepraw idłow ym typem dla tej m etody. K onw ersja
n a typ s t r i n g jest je d n a k b ard zo p ro sta i sprow adza się do mySt ri ng = ( s t r i ng ) my C h a r ,
w ywołania m etody T o S t r i n g ( ) . Jest o n a częścią każdego typu
long myLong - ( l o n g ) my l nt ;
w artościow ego i obiektu. (W szystkie klasy, k tó re stworzyłeś,
także m ają m eto d ę T o S t r i n g ( ) i zw racają nazw ę klasy). decimal myDecimal - (decimal)myLong;
M ożesz przekonw ertow ać x n a coś, co jest używ ane przez mySt ri ng - mySt ri ng + myInt + myByte
Me s s a ge Box. Show( ) , w sposób następujący: + myDouble + myChar;

MessageBox.Show(x.ToString());

188 Rozdział 4.
Typy i referencje

Parametr to zm ienna, którą


Kiedy wywołujesz metodę, definiujes z w metodzie.
A rgume n t to zm ienna lub
zmienne muszą pasować do typów parametrów wartość, którą przekazuje s z
w wy wołan iu tej metody. Jeśli
metoda ma parametr typu int,
Spróbuj wywołać M essageB ox.S how (123), a więc przekazać m etodzie M essageB ox.Show () to w je j ¡wywołaniu można
literał 123 zam iast o b iek tu s t r i n g . ID E nie pozw oli Ci zbudow ać p ro g ram u . Z am iast przekazać argum ent typu byte.
tego wyświetli w oknie błędów k om unikat: „A rg u m en t ‘1’: c a n n o t convert from ‘in t’
to ‘strin g ’.”. C zasam i C # będzie w stan ie przeprow adzić konw ersję autom atycznie
Kiedy
— n a przykład kiedy m eto d a oczekuje p a ra m e tru typu i n t , a zostanie do niej
p rzek azan a w artość s h o r t . N ie je st to je d n a k m ożliwe w przy p ad k u zastosow ania ko m p ila to r
typów całkow itych o ra z łańcuchów znaków.
wyświetla
M essageB ox.Show () nie je st jed y n ą m eto d ą, k tó ra w yświetla błędy k o m p ilato ra błąd „invalid
w przypadku p rzek azan ia jej zm iennych niepasujących do typu param etró w . Wszystkie
m etody ta k robią, naw et te, k tó re n apisałeś sam odzielnie. P rzejdźm y dalej. Spróbuj
arguments", znaczy
w pisać tę w p ełni po p raw n ą m e to d ę do klasy: to , że próbowałeś
p u b lic i n t M yM ethod(bool y e sN o ) {
w yw ołać m etodę
if (y esN o ) { ^
ze zmiennymi,
p r z y p o m n ie n ie : kod, k W ią j u ży
re tu rn 45; k tó re nie pasują
} e ls e {
re tu rn 61;
do ty p ó w
} jej parametrów.
kod metody.
}

M eto d a będzie działała praw idłow o, jeśli p rzekażesz jej p a ra m e tr typu, k tó reg o się
spodziew a (b o o l) — wywołaj M y M eth o d (tru e) lub M y M e th o d (fa lse ) i wszystko
będzie się kom pilow ało bez problem u.
Do zm iennej lub
C o się je d n a k stanie, gdy p rzekażesz zam iast teg o i n t lub s t r i n g ? ID E wyświetli p arąm etru typu object
moż e s z przypisać
b łąd podobny do tego, k tóry pojaw ił się p o p rzek azan iu 123 do M essageB ox.S how (). w szystko.
S próbuj te ra z jako arg u m en t p o d ać w arto ść logiczną, ale przypisz w arto ść w ynikową
do zm iennej typu s t r i n g lub zastosuj ją w w ywołaniu m eto d y M essageB ox.S how ().
T o także nie będzie działało — m e to d a zwróci typ i n t , a nie lo n g lub s t r i n g , którego
oczekuje M essageB o x .S h o w ().

Instrukcja if zawsze s prawdza, czy cos je st true


r Czy zwróciłeś uwagę, że zapisaliśmy instrukcję if w ten sP°sób:
U żyłeś ju ż
tego w kodzie if (yesNo) {
ap/ikacji Ratuj , = = true)". To dlatego, że f zawsze ^ ¿ 2 5 ? ^ »
/udzi — wróć
do rozdziału 1 . Nbyrdowwedmy£rm r czdokiad n ^ e e s yWa=a^u^;z r ^ °p e z iiobadNSTmy"sfo(soWaNozapZnf ,eosNo)"
i przekonaj się,
czy będziesz s a i m o ^ f S i i i i i ę t e m y jawnie sprawdzać, czy war t o « lo giczna jest r e w - tru- 1">> <alse
w sta n ie zna/eźć
ten fragment.

jesteś tutaj ► 189


, _„ chce s z użyć so w a kluczowego jako nazwy zmiennej,
Zarezerwowana tablica p0p,rZ edź j e znakiem @ — ty lko na tyle pozwoli Ci kompilator, lepszego
efekt u j u ż m<e uzy ska sz. JeśU zechcesz, to w ten sam sposób m ożesz
r ' postępow ać z im y mi naz wami', które nie s ą słowami kluczowymi.

W C# istnieje około 77 zarezerw ow anych s łó w nazywanych słow am i klu czo w ym i . Są to słowa


zarezerwowane przez kompilator C# i nie możesz ich używać do nazywania zmiennych. Będziesz znał
je wszystkie dość dobrze po zakończeniu lektury tej książki. Poniżej wymieniliśmy kilka, których już
Ćwiczenie używałeś. Napisz, co według Ciebie robią one w C#.

namespace

for

class

public

else

new

using

if

while

► Odpowiedzi znajdziesz na stronie 221.


190 Rozdział 4.
Typy i referencje

Stwórz kalkulator zwrotu kosztów podróży służbowej. Powinien on umożliwiać użytkownikowi wpisanie
początkowego i końcowego przebiegu pojazdu. Na podstawie tych dwóch liczb program będzie obliczał
ilość przejechanych kilometrów i określał, jaką kwotę należy zwrócić pracownikowi przy założeniu,
Ćwiczenie że firma płaci 39 groszy za każdy przejechany kilometr

ROZPOCZNIJ OD UTWORZENIA PROJEKTU WINDOWS FORMS APPLICATION.


Stw órz form ularz p o d o b n y do tego:

m k U suń p r z y c is k i
m in im a liza cji
m a k sy m a liza cji.
Ta etykieta ma
rozmiar 12 p t
i j e s t pogrubiona

Z akres ty ch pól, określany przy


u życiu właściwości Minimum
1i Max imum, powinien wynosić
- 999 999. *

K iedy ukończysz form ularz, kliknij dw ukrotnie przycisk


i dodaj do p ro je k tu kod.

DODAJ POLA, KTÓRYCH POTRZEBUJE KALKULATOR.


W staw definicje p ó l n a górze klasy Form1. P otrzebujesz dwóch p ó l typu całkow itego do określenia
początkow ego i końcow ego stanu licznika. Nazwij je s t a r ti n g M i le a g e i e n d in g M ile a g e . Potrzeba
Ci także trzech liczb, któ re m ogą przechow ywać cyfry p o przecinku. U staw ich typ n a d o u b le
i nazwij je m ile s T r a v e le d , re im b u rs e R a te i amountOwed. U staw w artość re im b u rs e R a te n a 0.39.

SPRAW, ŻEBY TWÓJ KALKULATOR DZIAŁAŁ.


D odaj k o d do m eto d y b u t t o n 1 _ C l i c k ( ) :

★ U pew nij się, że p o le początkow ego sta n u licznika zaw iera m niejszą w arto ść niż p o le stanu
końcow ego. Jeśli je st inaczej, wyświetl k o m u n ik at M essageB ox: „Początkow y stan licznika
m usi być m niejszy niż końcow y”. U staw n a p ask u tytułow ym o k n a te k st „N ie m ogę obliczyć
odległości”.

★ O dejm ij w artość p oczątkow ą o d końcow ej i p o m n ó ż w ynik p rzez w spółczynnik kilom etrow y


przy użyciu poniższych wierszy kodu:

m ile s T ra v e le d = e nd in g M ileag e -= s ta r tin g M ile a g e ;


amountOwed = m ile s T ra v e le d *= re im b u rse R a te ;
la b e l4 .T e x t = am ountO w ed.T oS tring() + " z ł " ;

URUCHOM GO.
U pew nij się, że p o d ajesz praw idłow e liczby. S próbuj zm ienić w artość p oczątkow ą na w iększą
niż końcow a. Spraw dź, czy p ro g ram w yświetla praw idłow y kom unikat.

jesteś tutaj ► 191


Coś jest źle...

Zostałeś poproszony o napisanie kalkulatora zwrotu kosztów podróży służbowych.


Poniżej przedstawiliśmy pierwszą część jego kodu.
Rozwiązanie
ćwiczenia

p u b lic p a r t i a l c la s s F orm l : Form

{ in t działa bardzo dobrze


z Ucztaami całkowitymi,
i n t s ta rtin g M ile a g e ;
które mogą przyjmować
wartości do 999 999.
i n t e n d in g M il e a g e ; sh o rt i by te obcięłyby je .
d o u b le m i l e s T r a v e l e d ;

d o u b le r e i m b u r s e R a te = .3 9 ;

d o u b le am ountO w ed;

p u b lic F o rm 1 () {

In itia liz e C o m p o n e n t( );

} Czy pam iętałeś'


że m usisz
p riv a te v o id b u t t o n 1 _ C l i c k ( o b j e c t s e n d e r , E v e n tA rg s e ) { zmienić w artość
decimal kontrolki
s ta rtin g M ile a g e = ( i n t) n u m e r ic U p D o w n l.V a lu e ; <=" NumericUpDown
na int?
e n d in g M ile a g e = (in t)n u m e ric U p D o w n 2 .V a lu e ;

if ( s t a r t i n g M i l e a g e <= e n d in g M ile a g e ) {
Ten blok kodu ma
m ile s T r a v e le d = e n d in g M ile a g e -= s t a r t i n g M i l e a g e ; za zadanie obliczyć
liczbę przejechanych
am ountOw ed = m i l e s T r a v e l e d *= r e i m b u r s e R a t e ; kilometrów,
a następnie
l a b e l 4 . T e x t = a m o u n tO w e d .T o S trin g () + 11 z ł " ; pomnożyć ją przez
wspótczynnik
} e ls e { kilometrowy.
M e ssa g e B o x .S h o w (

" P o c z ą tk o w y s t a n l i c z n i k a m usi być m n ie js z y n iż końcow y"

"N ie mogę o b l i c z y ć o d l e g ł o ś c i " ) ;

V
Użyliśm y tutaj
alternatywnego sposobu
} wywotywania metody
M e ssageBox.Show().
} p odaliśmy dwa parametry:
pierws z y to w yśw ietlany
} komunikat, drugi to te k s t
Wydaje się, że przycisk działa prawidłowo, na p a sku tytułow ym okna.
ale jest jeden w ielki problem. Czy potrafisz go znaleźć?

192 Rozdział 4.
Typy i referencje

Przetestuj kalkulator zwrotu kosztów


Z ró b to !
W k alkulatorze coś je st nie tak , ja k pow inno być. Z aw sze, gdy k o d nie działa zgodnie z naszym i
oczekiw aniam i, coś m usi być tego pow odem , a Tw oim zadaniem je st o k reślen ie przyczyny
problem ów . Zobaczym y, gdzie w k alk u lato rze w kradł się b łąd i ja k m o żn a go popraw ić.

DODAJ TERAZ DO FORMULARZA DRUGI PRZYCISK.


Spróbujm y te ra z zlokalizow ać nasz problem . W tym celu dodaj do fo rm u larza drugi przycisk,
który b ędzie wyświetlał liczbę przejechanych kilom etrów . (M ożesz tak że użyć debuggera.)

K lik n ię cie tego p rz y c isk u


po o b liczen iu k w oty pow inno
w y ś w ie tlić okno M e s s a g e 8 o x
licz°bT n katemL P o ł a w i a j ą c y m
liczb ą p rzejech a n y ch kilom etrów

K iedy uzupełnisz form ularz, kliknij dw ukrotnie przycisk


„Pokaż odległość”, aby d odać k o d do pro jek tu .

JEDEN WIERSZ POWINIEN TO ZAŁATW IĆ.


W zasadzie p o trzeb u jem y tylko wyświetlić w arto ść zm iennej m ile s T r a v e le d , praw da?
T en w iersz pow inien tego dokonać:

p r i v a t e v o id b u t t o n 2 _ C l i c k ( o b j e c t s e n d e r , E v e n tA rg s e ) I

M e s s a g e B o x .S h o w ( m ile s T r a v e le d + 11 k i l o m e t r ó w " , " P r z e b y ta o d l e g ł o ś ć " ) ;

URUCHOM PROGRAM.
W pisz kilka w artości i spraw dź, co się będzie działo. N ajpierw w pisz początkow y stan licznika, p o tem
końcowy, a n a stęp n ie kliknij przycisk Oblicz . T eraz kliknij przycisk Pokaż odległość, aby wyświetlić
w artość zm iennej m ile s T r a v e le d .

W HMM, COŚ JEST NIE TAK...


N iezależnie o d w pisanych w artości liczba kilom etrów zawsze pokryw a się z kw otą do zw rotu. D laczego?

jesteś tutaj ► 19S


O peratory są gotowe

Połączenie = z operatorem
Przyjrzyj się dokładnie o p erato ro w i, k tó reg o użyliśmy do odjęcia początkow ego
stan u licznika o d końcow ego (-= ). P ro b lem p o leg a n a tym , że oprócz odejm ow ania
o p e ra to r przypisuje wynik do zm iennej p o lewej stronie. T o sam o dzieje się w w ierszu
m nożącym liczbę przejechanych kilom etrów p rzez w spółczynnik kilom etrow y.
Pow inniśm y zastąpić -= i *= o p e ra to ra m i - i *:

p riv a te v o id b u t t o n 1 _ C l i c k ( o b j e c t s e n d e r , E v e n tA rg s e )
To są, o p era to ry
zfożone. Ten
{ tutaj odejmuje
startingM ileage
s t a r t i n g M i l e a g e = ( in t) n u m e r ic U p D o w n l.V a lu e ; od endingM ileage,
ale jednocześnie
e n d in g M ile a g e = (in t)n u m e ric U p D o w n 2 .V a lu e ;
przypisuje też
n ow ą w a rto ść
if ( s t a r t i n g M i l e a g e <= e n d in g M ile a g e ) { do endingM ileage
i milesTraveled.
m i l e s T r a v e l e d = e n d in g M ile a g e -= s t a r t i n g M i l e a g e ;

amountOw ed = m i l e s T r a v e l e d *= r e i m b u r s e R a t e ;

l a b e l 4 . T e x t = a m o u n tO w e d .T o S trw ig () + 11 z ł " ;

} e ls e {

M e s sa g e B o x .S h o w (" P o c z ą tk o w y s t a n l i c z n i k a m u si

być m n ie js z y n iż » ic o w y "

"N ie mogę o b l i c z y ć o ^ e g t o ś c i " ) ;

Tak j e s t lepiej - teraz milesTraveled = endingMileage - startingM ileage;


Twój kod nie bądzie
modyfikowaf endingM ileage
amountOwed = milesTraveled * reimburseRate;
i milesTraveled.

Czy dobrze dobrane nazwy zmiennych mogą tutaj pomóc? N atu raln ie! Przyjrzyj się
d okładnie p rzezn aczen iu każdej zm iennej. Ju ż z sam ej nazwy m i le s T r a v e le d m o żn a uzyskać
część inform acji — wiesz, że je st to zm ienna, k tó rą fo rm u larz wyświetla niepraw idłow o,
i m asz pom ysł, ja k p o w inna być obliczana. M ożesz z tych w niosków skorzystać podczas
przegląd an ia k o d u i szukania źródła błędu. Z n aczn ie tru d n iej jest o d n aleźć b łąd w kodzie,
który wygląda ta k ja k te n poniżej:

mt = eM -= sM
Takie n a zw y w ża den sp o só b
aO = mT *= rR ; n ie pom agają nam o k re ś lić
p rzezn a czen ia zm ienn ych.

194 Rozdział 4.
Typy i referencje

Także obiekty używają zmiennych


D o tej p o ry patrzyliśm y n a obiekty w o d erw an iu o d innych typów. Są o n e jed n ak
takim i sam ym i typam i ja k wszystkie inne. Twój k o d trak tu je je p o d o b n ie ja k liczby,
łańcuchy znaków i w artości logiczne. Używa zm iennych do pracy z nim i:

Użycie int Użycie obiektu


N apisz instrukcję i zadeklaruj liczbę całkow itą. N apisz instrukcję i zadeklaruj obiekt.

int mylnt; Dog spot; G d y m asz kla są


ta k ą ja k Dog,
u ż y w a sz jej ta k
samo jak ty p u
podczas d e k la ra cji
zm iennej.
© Przypisz w artość do nowej zm iennej. Przypisz w artość do obiektu.

mylnt = 3761; spot = new Dog();

Użyj w artości całkow itej w swoim kodzie. Spraw dź je d n o z p ó l obiektu.

while (i < mylnt) { while (spot.IsHappy) {

...WIĘC NIE M A ZNACZENIA,


CZY PRACUJĘ Z OBIEKTEM,
CZY Z WARTOŚCIĄ. JEŚLI ZOSTANĄ ONE
UMIESZCZONE W PAMIĘCI, A MÓJ PROGRAM ICH
POTRZEBUJE, TO U ŻYW AM ZMIENNEJ.

Obiekt to po prostu kolejny typ zmiennej,


którego Twój program może używać.
Jeśli Twój p ro g ram m a pracow ać z n ap raw d ę w ielkim i
liczbam i, użyj lo n g , a gdy p o trzeb u jesz m ałej liczby
całkow itej — s h o r t . Jeżeli zależy Ci n a w artości
tak/nie, skorzystaj z typu logicznego b o o l. Jeżeli
n ato m iast p o trzeb u jesz czegoś, co szczeka i siada,
użyj Dog. N iew ażne, z jakim typem Twój pro g ram
p racu je — używa do tego zm iennych.

jesteś tutaj ► 195


Pobierz referencję

Korzystaj ze swoich obiektów


za pomocą zmiennych referencyjnych
Nazywamy
to tw orzeniem
K iedy tworzysz nowy o b iek t, używasz k o d u w stylu new Guy(). Je d n a k to nie wszystko. instancji obie k tu .
N aw et jeśli tw orzy on nowy o b iek t Guy n a stercie, to nie daje Ci możliw ości dostępu
do niego. P otrzebujesz referencji do obiektu. Tworzysz w ięc zmienną referencyjną :
zm ienną typu Guy z nazw ą n a przykład jo e . joe jest zatem re feren cją do utw orzonego
o b iek tu typu Guy. Z a każdym razem , gdy chcesz uzyskać do stęp do k o n k retn eg o faceta,
m ożesz odw ołać się do niego za p o m o cą zm iennej referencyjnej nazw anej jo e .

K iedy w ięc tworzysz zm ienną, której typ jest obiektem , tworzysz tym sam ym zm ienną
referencyjną: referen cję do k o n k retn eg o obiektu. P o p atrz n a to:

To je s t sterta przed
uruchomieniem programu.
Nic na niej nie ma.

p u b li c p a r t i a l c l a s s Forml : Form
{
Ta zmienna
nazywa się Guy joe;
joe i będzie A
reprezentowała /
p u bl ic Form1()
Guy. Tworzenie referencji j e s t ja k t ^ r z e m e
{ e ty k ie t, ty/e ż e zamiast: przyhpia.ma
I n i ti a l i z e C o m p o n e n t ( ) ; ich do różnych rzeczy iużyw asz ich_do
oznaczania obiektów, abyś mógł później
uzyskać do n ich do stę p.
joe = new Guy();
}
To j e s t zmienna ■■■a to j e s t obiekt
referencyjna... reprezentowany
prze z zm ienną joe.

To j e s t sterta
po uruchomieniu programu.
Znajduje s i ę tu jeden obiekt,
do którego odwotuje s i ę JEDVNVM sposobem na
zmienna referencyjna joe. odwołanie s ię do tego
obiektu Guy j e s t użycie
zm iennej referencyjnej
o nazw ie joe.
° 6 'e k \ 0 ^

196 Rozdział 4.
Typy i referencje

Referencje są jak etykiety do Twoich obiektów


N ajpraw dopodobniej m asz gdzieś w swojej kuchni p o jem niki n a cukier i sól. G dybyś
pozam ieniał etykiety, mógłbyś otrzym ać dość nieprzyjem ny posiłek p o m im o tego,
że choć zm ieniłyby się opisy pojem ników , ich zaw artości pozostałyby bez zmian. Kiedy Twój
R eferencje są j a k etykiety. M ożesz przylepiać je w różnych m iejscach, dołączać
do różnych rzeczy, ale to obiekt decyduje o tym , k tó re m eto d y i dane są do stęp n e,
program musi
nie referencja. pracować
o o T a d l bUtt0nl~ Cl'ck formularza
posiada zm ienną o nazwie ,,/oe" z obiektem
Ten obiekt j e s t typu Gijy. w skazującą na ten obiekt.
J e s t to POJEDYNCZY obiekt w pamięci,
z WIELOMA referencjami. M m m m m m
potrzebuje
referencji.
Jej typem
Guy dad = jo e ;
jest nazwa
klasy obiektu.
Referencja jest jak
etykieta, której
używa Twój kod
do komunikacji
Instancja klasy Guy
przechowuje referencję do
te go obiektu w zm iennej
.
Każda z tych
z określonym
nazwanej „dad". e ty k ie t j e s t zm ienną
refer-encyjną, ale
obiektem.
wsz y stk ie one
odnoszą s ię do
TEGO SAMEGO
obiektu Guy.

N igdy nie odnosisz się b ezp o śred n io do obiektu. N ie m ożesz na przykład napisać
k o d u Guy.GiveCash() , gdzie Guy jest nazw ą typu. K o m p ilato r C # nie wiedziałby,
k tóry obiekt m asz na myśli, bo m ożesz m ieć kilka instancji Guy na stercie. Istn ie je wiele różnych
referencji do tego sam ego
P otrzebujesz więc zm iennej referencyjnej, n a przykład jo e , k tó ra odnosi się
obiektu Guy, ponieważ
do k o nkretnej instancji, ta k ja k Guy jo e = new Guy(). wiele różnych m etod używ a
g° do siwoich celów. Każda
T eraz m ożesz wywoływać m etody, n a przykład joe.G iveC ash() . jo e odno si się z referencji ma inną nazwę,
która ma se n s w swoim
do k o nkretnej instancji klasy Guy, a k o m p ilato r C # dokład n ie wie, której instancji kontekście.
m a użyć. P o n ad to , ja k pew nie zauw ażyłeś, m ożesz m ieć wiele etykiet w skazujących
na je d n ą instancję. M ożesz n apisać Guy dad = jo e , a p o te m wywołać dad.GiveCash()
T ak też jest dobrze — to w łaśnie ro b i dziecko Jo eg o każdego dnia.

jesteś tutaj ► 197


To ekspert do spraw porządków. Dziękujemy bardzo

Jeżeli nie ma ju ż żadnej referencji,


Twoje obiekty są usuwane z pamięci Aby obiekt
G dy wszystkie etykiety zo stan ą zdjęte z o b iek tu , w tedy nie m a możliw ości d o stan ia
mógł być
się do niego. O znacza to , że C # m oże zakw alifikow ać go do p ro ced u ry oczyszczania przechowywany
pamięci (określanej tak że jak o o dśm iecanie, ang. garbage collection). T o w łaśnie
podczas w ykonyw ania tej p ro ced u ry C # usuw a obiekty, do których nie m a referencji,
na stercie, musi
i zw alnia zajm ow aną p rzez nie p am ięć podczas d ziałania p rogram u. istnieć do niego
3 Oto fragment kodu, który tworzy obiekt. referencja.
Guy jo e = new Guy()
Kiedy ostatnia
{ Name = " J o e " , Cash = 50 }; z nich zniknie,
znika także
Używając instrukcji °biekt 0 d obiekt.
każesz utworzyć nowy obiekt.
Kiedy kojarzysz z tym obiektem
zmienną referencyjną tak£
jak „joe", to jakbyś przylep.af
na nim etykietką.

Stwórzmy teraz drugi obiekt.


Guy bob = new Guy()
{ Name = "Bob", Cash = 75 };

^ 6 'e k t
k fa s u G u ^ ^ w i e in s t a n c jo
*[asy Guy , dwie zm ie n n i
referencyjne: po j ^ j d l
każdego obiektu Guy.

Weźmy teraz referencję do pierwszego obiektu i zmieńmy t


Nie ma ju ż jednak żadnej
ją tak, aby wskazywała na drugi obiekt. . p r e f e r e n c j i do pierwszego
obiektu G uy—
oe = bob;
j
Teraz jo e w skazuje na
ten sam obiekt co bob.
— dlatego C#
kwalifikuje go
do p rocedury
oczyszczania
pamięci
i n iszczy go.
Obiekt znika!

198 Rozdział 4.
Typy i referencje

Referencje wielokrotne i ich efekty uboczne


M usisz być ostrożny podczas p racy ze zm iennym i referencyjnym i.
W w iększości przypadków m oże się w ydawać, że zapisujesz w zm iennej
referen cję do innego obiektu, tym czasem m ożesz doprow adzić do tego,
że usuniesz wszystkie referen cje do któ reg o ś z nich. T o n ie jest złe,
ale n ie zawsze m usi być tym , o czym myślałeś. P o p atrz n a to:

r« Dog ro v e r = new D o g ();


ro v e r.B re e d = "C h a rt" ;

Obiekty: _

Referencje: _ rover j e s t obiektem typ u Dog


z polem Breed ustawionym
na Chart.

Dog lu c k y = new D og();


lu c k y .B re e d = "Jam nik";
fid o = ro v e r;
/
B u m !
O b ie kty: 2_ lucky j e s t trzecim
obiektem , a/e fido
w skazuje teraz na obiekt
/ \ V
Referencje:__ __
num er 1. Numer 2 nie ma ro
zatem referencji. Z punkto
widzenia programu tego £
obiektu ju ż nie ma.

jesteś tutaj ► 199


Naszkicuj obiekty i etykiety

Zaostrz ołówek
Teraz Twoja kolej. Dany jest jeden długi fragment kodu. Określ, jak dużo
obiektów i referencji znajduje się na każdym etapie. Po prawej stronie
naszkicuj obiekty i etykiety na stercie.

Dog ro v e r = new D og();


ro v e r.B re e d = " C h a rt" ;
Dog rin T in T in = new Dog();
Dog fid o = new D og();
Dog q u e n tin = f id o ;

O b ie kty:________

Referencje:

Dog sp o t = new D og();


sp o t.B re e d = "Jam nik";
sp o t = ro v e r ;

O b ie kty:________

Referencje:________

Dog lu c k y = new D og();


lu c k y .B re e d = "B eag le";
Dog c h a r l i e = f id o ;
fid o = ro v e r ;

O b ie kty:________

Referencje:________

[4 rin T in T in = lu c k y ;
Dog la v e r n e = new D og();
la v e r n e .B r e e d = "Mops";

O b ie kty:________

Referencje:________

c h a r l i e = la v e r n e ;
lu c k y = r in T in T in ;

O b ie kty:________

Referencje:

200 Rozdział 4.
Typy i referencje

Stwórz program z klasą reprezentującą słonia. Utwórz dwie instancje klasy E le p h a n t i pozamieniaj ich
referencje. Upewnij się, że instancje te nie zostaną zakwalifikowane do procesu oczyszczania pamięci.
Ćwiczenie
n j ?S t diagram klasu
(V Stwórz nowy projekt Windows Forms Application.
Z b ud u j fo rm u larz p o d o b n y do tego:
2 E £ £ kKrą
6 . E le p h a n t
Kliknięcie P ^ ^ ^ d n d a .W h o A m K ) ,
wywołuje metodę ' ' Name
która w yśw ietla to_okno.
EarSize

i WhoAmI()

M etoda WhoAmI() powinna


w yśw ietlić takie okienko
z komunikatem. Upewnij s ię,
że komunikat zaw iera rozmmr
ucha słonia, a jego imię
zostało wyświetlone na pasku
tytułu.
Utwórz klasę Elephant.
D odaj do p ro jek tu klasę E le p h a n t. Przypatrz się jej diagram ow i — potrzebujesz p o la i n t o nazwie E a rS iz e
i p ola typu s t r i n g o nazw ie Name (upewnij się, że o b a zostaną zadeklarow ane jako publiczne). N astępnie
dodaj m eto d ę WhoAmI() wyświetlającą okno MessageBox, któ re pokazuje nazwę słonia i rozm iar jego ucha.

Utwórz dwie instancje klasy Elephant i ich referencje.


D odaj dwa p o la typu E le p h a n t do klasy Form1 (nieco poniżej jej deklaracji) i nazwij je l l o y d i lu c in d a .
Z ainicjalizuj je popraw nym i w artościam i określającym i nazw ę słonia i ro zm iar jego ucha. T a k będą
w yglądały inicjalizatory obiektów typu E le p h a n t, k tó re m usisz d odać do form ularza:

lu c in d a = new E le p h a n t() { Name = "L u c in d a ", E a rS iz e = 33 };


llo y d = new E le p h a n t() { Name = "L lo y d ", E a rS iz e = 40 };

[4 Spraw, żeby przyciski Lloyd i Lucinda działały.


Postaraj się, aby przycisk L lo yd wywoływał llo y d .W h o A m I(), a przycisk L u cin d a lu cin d a.W h o A m I().

[ 5) Oprogramuj przycisk Zamień.


To jest najtrudniejsza część . Spraw , aby przycisk Z a m ie ń zam ieniał obie referen cje. P o jego kliknięciu
zm ienne ll o y d i l u c i n d a pow inny zm ienić obiekty i pow inno się wyświetlić o k n o O biekty zam ienione.
Przetestu j p ro g ram , naciskając Z a m ie ń , a n astęp n ie klikając p o zo stałe dwa przyciski. P o pierwszym
kliknięciu przycisk L lo yd pow inien w yświetlać k o m u n ik at L u cin d y , n ato m iast przycisk L u cin d a ko m unikat
L loy d a. Jeżeli klikniesz przycisk Z a m ień jeszcze raz, w szystko pow inno w rócić do stan u początkow ego.

Mechanizm oczyszczania pamięci usuwa wszystkie obiekty, do których nie istnieje


żadna referencja. Oto podpowiedź: jeśli chcesz przelać szklankę piwa do drugiej
szklanki, która jest pełna wody, potrzebujesz trzeciej szklanki, do której
wylejesz wodę...

jesteś tutaj ► 201


Czy dużo to za dużo

Zaostrz ołówek Teraz Twoja kolej. Dany jest jeden długi fragment kodu. Określ, jak dużo
obiektów i referencji znajduje się na każdym etapie. Po prawej stronie naszkicuj
obiekty i etykiety na stercie.
Rozwiązanie

Dog ro v e r = new D og();


ro v e r.B re e d = "C h a rt" ;
Dog rin T in T in = new Dog()
Dog f id o = new D og();
Dog q u e n tin = f id o ; Tworzony j e s t jeden
obiekt Dog, ale sp o t
Obiekty: 3 to jedyna referencja
do niego. Gdy sp o t
je s t ustaw iany na
Referencje: 4 = rover, o biekt ten
z o s taje usu n ięty.

Dog s p o t = new Dog() i/


s p o t.B re e d = "Jam nik";
sp o t = ro v e r;

Obiekty: 3

Referencje:

Dog lu ck y = new D og(); ^ w i o n y M fido, gdy


lu c k y .B re e d = "B e a g le "; ten odnosił s ię je szc ze
Dog c h a r l i e = f i d o ; do obiektu numer 3.
Potem fido zosta ł
fid o ro v e r; przeniesiony do M e k fa
numer 1, poz°stawiaJąc
Obiekty: 4 referencję charlie na
swoim m iejsc u .
Referencje:__
Dog num er 2
stra cił sw oją
o statnią
rin T in T in = lu c k y ; referencję
Dog la v e r n e = new Dog() i zo sta ł
la v e r n e .B re e d = "Mops"; usunięty.

Obiekty: 4 IV s Kiedy rinTinTin


zo sta ł przeniesiony
do obiektu lucky,
Referencje: 8 sta ry obiekt
rinTinTin zniknął.
Tutaj przestaw iane
c h a r l i e = la v e r n e ; s ą referencje, ale
lu ck y = rin T in T in ; nie j e s t tworzony

V
żaden nowy obiekt.
Obiekty: 4 U staw ienie lucky
na rinTinTin nic
nie spow oduje, bo
Referencje: _8_ _ te dwie zm ienne
ju ż w cześniej
wskazyw ały na
ten sam obiekt.

202 Rozdział 4.
Typy i referencje

_ _ Stwórz program z klasą reprezentującą słonia. Utwórz dwie instancje klasy E le p h a n t i pozamieniaj ich
R o z w iąz a n ie właściwości. Upewnij się, że instancje te nie zostaną zakwalifikowane do procesu oczyszczania pamięci.
ć w ic z e n ia
To j e s t definicja
klasy Elephant
u sin g System .W indows.Form s;
i je j kod w pliku
Elephant.cs dodanym
c l a s s E lep h an t { Jeśli chcesz, instrukcję using możesz także umieścić do projektu.
w e w n ą trz na w ia só w przestrzeni nazw. Nie zapomnij
p u b lic i n t E a rS iz e ; o w ierszu „using
p u b lic s t r i n g Name; S ystem . Windows,
form s” znajdującym
p u b lic v o id WhoAmI() { s ię na górze klasy.
M essageBox.Show("Moje uszy m ają + E arS ize + " centym etrów s z e r o k o ś c i . 1 Bez niego nie będzie
działała instrukcja
Name + " m ó w i..." ) ; /Vlessage 8 ox.
}
} p u b lic p a r t i a l c l a s s Forml : Form {

E lep h an t lu c in d a ;
E lep h an t llo y d ;
p u b lic Form1()
{
In itia liz e C o m p o n e n t( );
lu c in d a = new E le p h a n t()
{ Name = "L ucinda" , E a rS iz e = 33 };
To j e s t kod klasy formularza llo y d = new E le p h a n t()
z pliku Forml.cs.
{ Name = "L lo y d ", E arS ize = 40 };
}
p r i v a t e v o id b u tto n 1 _ C lic k (o b je c t s e n d e r , EventA rgs e) {
lloyd.W hoA m I();
}
Gdybyś zwyczajnie
przypisał Hoyd do lucinda, p r i v a t e v o id b u tto n 2 _ C lic k (o b je c t s e n d e r , EventA rgs e) {
utraciłbyś w szystkie lucinda.W hoA m I();
referencje w skazując }
na Lloyda i jego obiekt
zostałby usunięty- p r i v a t e v o id b u tto n 3 _ C lic k (o b je c t s e n d e r , EventA rgs e) {
To dlatego potrzebujesz
E le p h an t h o ld e r ; . ... „ ,
referencji holder do Nie ma tuta j insfriAcji new p rzy
obiektu lloyd, zanim \ h o ld e r llo y d , N referencji, ponieważ nie chcemy |
pojawi s ię przy mm llo y d = lu c in d a ; tw orzyć nowej instancji Masy t o n i z m -
Lucinda. lu c in d a = h o ld e r ;
M essageB ox.Show ("O biekty z a m ie n io n e " );

WYSIL
SZARE KOMÓRKI
Jak myślisz, dlaczego nie dodaliśmy metody Swap() do klasy Elephant ?

jesteś tutaj ► 203


Dwie referencje — dw a sposoby

Dwie referencje oznaczają DWA sposoby w

na zmianę danych obiektu r ó b to !

!
Jeśli w p ro g ram ie istnieje w iele referen cji do o biektu,
to ryzykujesz nie tylko u tra tę ich wszystkich, lecz także
przypadkow ą zm ianę sta n u obiektu. Inaczej m ów iąc, jed n a
referen cja m oże go zm ien ić, podczas gdy d ru g a n ie będzie
m iała pojęcia, że coś zostało zm ienione. P o p a trz n a to:

r » D odaj do fo rm u larza jeszcze jed e n przycisk.

3 D odaj następujący k o d do przycisku. Czy jesteś w stan ie o d gadnąć, co się stan ie p o jego kliknięciu?

p r i v a t e v o id b u tto n 4 _ C lic k (o b je c t s e n d e r , EventA rgs e) Po wykonaniu tego kodu


zarówno zmienna lloyd,
{ ja k i lucinda odnosi
llo y d = lu c in d a ; s ię do TEGO SAMEGO
Ta instrukcja obiektu Elephant.
► llo y d .E arS ize = 4321;
ustaw ia pole
EarSize dowolnego lloyd.W hoA m I();
obiektu, na który
w danej chwili
w skazuje
referencja lloyd,
na 4321. C^ ale lloy d w skazuje na
^ •T ę sam ą rzecz co lucinda.

D obrze, przejdźm y dalej. N aciśnij te ra z nowy przycisk. Z aczekaj, to p rzecież okno °t=iek
MessageBox , k tó re wywołuje Lucinda . Czy nie wywoływaliśmy m eto d y WhoAmI()
należącej do Lloyda ?

- ale u staw iliśm y


to pole EarSize za
lom unikatu o b iek tu '
, pomocą referencji
lu c in d a - lloyd! Co nam to dato?

Zauw aż,
że dane NIE s ą
nadpisyw ane —
Referencje lloyd i ,u cin d ^ ° 9^ c h ma°jednaT
zam iennie. Zmiana w je d n e j z jedyną, rzeczą,
która s i ę zm ienia,
s ą referencje.
1 'Ę ^ y i? i o S a z C s f f l , one
TFN SAM OBIEKT.

204 Rozdział 4.
Typy i referencje

Łańcuchy zna ków i tab lice różnią się od


Specjalny przypadek: tablice pozostałych ty p ó w danych, które m iałeś okazję
poznać, gdyż jako jedyne nie mają określonego
Jeżeli m usisz zarządzać dużą ilością danych teg o sam ego typu, pow iedzm y rozm iaru (za sta n ó w się nad tym przez chw ilę).
listą wysokości lub g ru p ą psów , m ożesz tego dok o n ać przy użyciu ta b lic.
T o, co czyni tablice tak wyjątkowymi, to ich zdolność do przechow yw ania M ożesz połączyć deklarację zm iennei
g ru p y zm ien n y ch , k tó re są trak to w an e jak o je d e n obiekt. T ablica T w 7 a ż d ,J e J in ic ^ n - t T J

um ożliw ia grom adzen ie i zm ianę większej liczby danych bez konieczności


indyw idualnego przechow yw ania każdej z nich. Podczas jej tw orzenia
deklarujesz ją tak sam o jak każd ą in n ą zm ienną, określając nazw ę i typ: boolU myArray = new bool[151;

D eklarujesz
tablicę poprzez Ta tablica zawiera
określenie typu bool[] myArray; 15 e le m e n tó w .
i użycie nawiasów
prostokątnych
myArray = new bool
U żyw asz stówa ^ -------------------------- - ¿ r
kluczowego new do myArray[4] = true;
utworzenia tablicy, bo
j e s t to obiekt. Zmienna Ten w iersz u staw ia w artość
przechowująca tablice piątego elem entu myArray na
j e s t więc zm ienną tr ue. J e s t to elem ent p iąty,
referencyjną. bo pierwszym je s t myArray[0],
drugim myArray[1] i' tak dalej.

Używaj każdego elementu tablicy tak,


jakby to była zwyczajna zmienna
K iedy chcesz skorzystać z tablicy, w pierw szej kolejności m usisz W pamięci tab,i<?
z a d e k la ro w a ć z m ie n n ą re fe re n c y jn ą w skazującą n a nią. N astęp n ie za jm u je
pomimo tego, ze j
m usisz u tw o rzy ć o b ie k t ta b lic y przy użyciu o p e ra to ra new, tam wiele zmiennych
określając przy tym jej rozm iar. P o tem m ożesz o k re ś lić w a rto ś c i typu int.
poszczególnych jej elem en tó w . Poniżej przedstaw iliśm y przykładow y
kod, który d eklaruje tablicę i w ypełnia ją liczbam i, o ra z rysunek
obrazujący stan sterty podczas w ykonyw ania tych operacji.
Pierwszy ele m e n t tablicy zawsze m a in d e k s 0.

Typ każdego nazwa


-*■ int[] heights;
elem entu
w tablicy. heights = new int[7];
heights[0] = 68;

J
D ostęp
do tych
heights[1] = 70;
elem entów
uzyskujesz heights[2] = 63;
poprzez
indeks, ale
heights[3] = 60;
pracujesz
heights[4] = 58;
z nimi
dokładnie tak
heights[5] = 72; Zauważ, że tablica j e s t obiektem,
samo jak
z normalną chociaż siedem je j elementów j e s t ty pu
zm ienną int.
heights[6] = 74; wartościowego — tak sam o jak te na
p ier wszych dwóch stronach tego rozdziału.

jesteś tutaj k 205


Siedem psów

Tablice mogą także zawierać


grupę zmiennych referencyjnych
M ożesz utw orzyć tablicę referen cji do obiektów w te n sam sposób, w jaki tworzyłeś
tablice liczb lub łańcuchów znaków. T ablicom nie ro b i różnicy, jak i typ zm iennej
Gdy ustawiasz
przechow ują — zależy to w yłącznie o d C iebie. M ożesz m ieć tablicę liczb typu i n t lub pobierasz
czy tablicę obiektów Duck — nie m a z tym żadnego problem u.
element
Poniżej zaprezentow an o kod, któ reg o zad an iem je st u tw o rzen ie tablicy siedm iu
zm iennych typu Dog. W iersz, k tóry inicjalizuje tablicę, tw orzy jedynie zm ienne
z tablicy, liczbę
referencyjne. W związku z tym , że istnieją dw a w iersze new D o g (), są tw orzone w nawiasach
tylko dwie instancje klasy Dog.
. ____ Ten w iersz deklaruje ^ n n ą . prostokątnych
Dog[] dogs = new Dog[7]; tablicę, referencji
tudU Dog, i rezerwuje miejsce
nazywamy
dogs[5] = new Dog(); na siedem elementów.
indeksem.
dogs[0] = new Dog();
Pierwszy
Te dwa w iersze tworzą
nowe instancje klasy element
Dog i za p isu ją je
w komórkach tablicy
o indeksach 0 i 5 .
ma indeks 0.

P ierw szy w iersz kodu


tworzy b/lko tablice, m e
I S c ^ k la sy . Tablica j e s t
lis tą siedm iu zmiennych
referen cy jn y ch typu Dog.

O b \e ^ O b \^
tedenientfM

7 zmiennych Dog

to
u iv ć W
je i L edw ość
u e r f W w ł a S +n
im ninm ninini
w nte\ ^ 7 — ° zn^ l l 0 '¿o 6- 0 1 Z 3 t 5 <>
Dog Dog Dog Dog Dog Dog Dog

W szystkie elem enty znajdujące s ię w tablicy są


referencjami, natom iast ona sam a j e s t obiektem.

206 Rozdział 4.
Typy i referencje

Witamy w barze Niechlujny Janek — najtańsze kanapki w mieście!


N iechlujny Ja n e k m a ste rtę m ięsa, całą m asę chleba i różne
inne składniki, k tó re m ożesz m ieszać b ez ograniczeń. N ie m a jed n ak
Z r ó b to ! M enuM aker
m en u ! Czy p o trafisz n apisać p ro g ram , który będzie tworzył
dla niego losową k artę dań każdego dnia?
★ Randomizer
Meats
Condiments
Utwórz nowy projekt i dodaj do niego klasę MenuMaker. Breads
D o stw orzenia m en u p o trzeb u jesz składników . T ablice b ę d ą do tego doskonałe.
P otrzeb u jem y także sp o so b u na losow e w ybieranie składników i łączenie ich
GetMenuItem()
w celu stw orzenia k anapki. N a szczęście .N E T p o siad a w b udow aną klasę Random,
k tó ra g en eru je liczby losowe. B ędziem y zatem m ieli w naszej klasie cztery pola:
R andom izer, k tó re b ędzie przechow yw ało o b iek t Random, o ra z trzy tablice
Klasa posiada trzy pola
obiektów s t r i n g do przechow yw ania m ięs, dodatków i rodzajów chleba. do przechowywania trzech
To pole różnych tablic z elem entami
o nazwie typu string. B ędziemy
Randomizer p u b lic c l a s s MenuMaker { ich używ ać do tworzenia
przechowuje p u b lic Random Randomizer; losowych kart dań.
referencję
do obiektu
Random. s t r i n g [ ] Meats = { "P ieczo n a w ołow ina", "S a la m i", " In d y k ", "S zynka", "Karkówka" };
Wywołanie je g o s t r i n g [ ] Condiments = { " ż ó łta m u sz ta rd a , "brązowa m u s z ta rd a " , "m u sztard a miodowa",
m etody Next() "m ajonez", p rzy p raw a", "so s f r a n c u s k i" } ;
generuje liczbę
losową. s t r i n g [ ] Breads = { "ch leb ryżow y", "c h le b b ia ły " "c h le b zbożow y", " p u m p e rn ik ie l" ,
"c h le b w ło s k i" , " b u łk a "} ;
Pamiętaj, aby uzyskać dostęp
do elem entów tablicy, trzeba uży ć
nawiasów prostokątnych. Na. pirzykład
w artością Breads[2] j e s t „chleb z b ^ i ^ y " .

. 2) Dodaj do klasy metodę GetMenuItem() tworzącą losowe kanapki.


C elem klasy jest g enerow anie k anapek. D odajm y więc odp o w ied n ią m etodę. B ędzie o n a używała m etody
uw ag ę rÓC' i e N e x t() o b iek tu Random do w ybrania losow ego m ięsa, d o d a tk u i ch leb a z tablic. K iedy p rzek ażesz w jej
na sposób w ywołaniu p a ra m e tr i n t , zwróci o n a losow ą w artość m niejszą o d niego. Z g o d n ie z powyższym, jeśli o biekt
fa biit f J e s t tych Random nazywa się R andom izer, wywołanie R a n d o m iz e r.N e x t(7 ) zwróci liczbę losow ą p om iędzy 0 a 6.
on nazywany
inicjalizatorem Skąd je d n a k m asz w iedzieć, jaki p a ra m e tr w pisać w w yw ołaniu m eto d y N e x t() ? T o łatw e — p rzek aż jej
kolekcji — po p ro stu długość tablicy, czyli p o le L ength. M e to d a zwróci w tedy indeks jej losow ego elem en tu .
dowiesz się
więcej na
p u b lic s t r i n g GetMenuItem() {
jego tem at
w rozdziale 8. s t r i n g randomMeat = M ea ts[R a n d o m iz e r.N e x t(M e a ts.L e n g th )];
s t r i n g randomCondiment = C o n d im en ts[R a n d o m iz er.N e x t(C o n d im en ts.L en g th )];
Metoda s t r i n g randomBread = B re a d s[R a n d o m iz e r.N e x t(B re a d s.L e n g th )];
G etM enuItem ()
z wraca typ string retu rn randomMeat + " , " + randomCondiment + " , " + random Bread;
który zawiera
}
kanapkę zrobioną
z trzech losowych
elementów
umieszczonych
t Metoda zwraca losowy elem ent z tablicy M eate
przekazując uprzednio M eats.Length do m e ftd y N ext()
z miennej ; ,
R m d M . W z w<ązku
z tym, że w tablicy M eats j e s t pięć e l e m e n t , M e a ts.Length j e s t i-owne 5,
a Next(5) zwróci losową liczbę z zakresiu od 0 do 4.
w trzech tablicach.

jesteś tutaj ► 207


Niechlujny Janek odparł: „To nie jest stare, to jest klasyczne

if y J a k to d z ia ła * • •

i ! i ' ? d MR:a? f K :S K ;'V<,x '(7) “ " “ a " c z b i losow i m niejszą


ź w S Mpe a tS;Length,vzwr° ca ''czbę e/ementów w tab/icy 1
Mteate; IW o w z e r/V e x tfM e a tsX e n q th ) zwraca w ab '

^ — ------------------------------------
Meats[Randomizer.Next(Meats.Length)]

^ iVleats j e «
\ Z a w ie r a równoznaczne

co „Szynka".

Stwórz formularz.
D odaj do fo rm u larza sześć etykiet, o d l a b e l l do la b e l 6 . N astęp n ie uzupełnij każd ą z nich
za p o m o cą właściwości T e x t i o b iek tu MenuMaker. B ędziesz m usiał zainicjalizow ać obiekt,
używ ając do tego nowej instancji klasy Random. T a k w ygląda kod:

p u b lic Form1() { Użyj micya/izatora obiektu do ustaw ienia


Po/a Randomizer obiektu MenuMaker na
In itia liz e C o m p o n e n t( ); nowi m stancję k/asy Random.

MenuMaker menu = new MenuMaker() { Randomizer = new Random()


To j e s t coś, nad
la b e ll.T e x t menu.GetMenuItem(); czym powinieneś
label2.Text menu.GetMenuItem(); s ię zastanow ić.
Teraz wszystko j e s t ju ż Co by s ię stało,
label3.Text menu.GetMenuItem(); gotowe do wygenerowania gdybyś zapomniał
sześciu różnych kanapek zainicjalizować pole
label4.Text menu.GetMenuItem(); za pomocą metody Randomizer obiektu
label5.Text menu.GetMenuItem(); GetM enuItem (). M enuMaker? Czy
potrafisz wymyś/ić,
label6.Text menu.GetMenuItem(); jak zapobiec temu
zagrożeniu?

Kiedy uruchomisz
program, sześć
etykiet pokaże
sześć różnych
losowych kanapek

208 Rozdział 4.
Typy i referencje

Obiekty używają referencji do komunikacji między sobą


D o tej p o ry w idziałeś fo rm u larze, k tó re kom unikow ały się z o biektam i. W ywoływały ich m etody
oraz spraw dzały ich p o la za p o m o cą zm iennych referencyjnych. O biekty tak że m ogą wywoływać
swoje m etody przy użyciu referencji. W zasadzie nie m a niczego, co m oże wywoływać form ularz,
a czego nie m ogą wywoływać obiekty, gdyż formularz także jest obiektem . K iedy obiekty
k om unikują się ze sobą, do akcji w kracza b ard zo użyteczne słowo t h i s . Z a każdym razem ,
gdy o b iek t używa tego słowa kluczow ego, zw raca się do sam ego siebie — jest to referen cja,
k tó ra w skazuje n a o b iek t wywołujący.

[p TAK WYGLĄDA METODA, KTÓRA KAŻE SŁONIOWI MÓWIĆ.


D odajm y m eto d ę do klasy E le p h a n t. Jej pierwszym p a ra m e tre m są słow a słonia. D rugim je st sam słoń,
który je w ypowiedział:

p u b lic v o id T e llM e (s trin g m essag e, E lep h an t w h o S aid It) {


M essageBox.Show(w hoSaidIt.N am e + " mówi: " + m essag e);
}

Poniżej przedstaw iliśm y w ywołanie tej m etody. M ożesz je d odać do m eto d y b u tt o n 4 _ C li c k ( ) ,


pam iętaj jed n ak , by um ieścić je przed przypisaniem referencji ( l l o y d = l u c i n d a ; ).

llo y d .T e llM e (" C z e ś ć " , lu c i n d a ) ;

W ywołaliśm y m e to d ę T e llM e () o b ie k tu lloyd i wstawiliśm y do niej dwa p aram etry : „C ześć” i referen cję
do ob iek tu lucinda. M eto d a używa p a ra m e tru w h o S a id It w celu uzyskania d o stęp u do p o la Name
dow olnego słonia, k tóry został p rzek azan y do niej jak o drugi p aram etr.

^ A OTO METODA, KTÓRA WYWOŁUJE INNĄ METODĘ.


D odajm y tera z do klasy E le p h a n t m eto d ę S p e a k T o (). Używa o n a specjalnego słow a kluczow ego t h i s .
Jest to referen cja, k tó ra pozwala obiektowi mówić o samym sobie .

p u b lic v o id S peak T o (E lep h an t whoToTalkTo, s t r i n g m essage) {


w hoToT alkT o.T ellM e(m essage, t h i s ) ;
} Ta metoda klasy Elephan 't wyw ołuje inną
P r 7vir 7yirny sie dnkU dnie iak to d z i a ^ m etodę TeUMe z tej sam ej klasy . Pozwala
Przyjrzyjmy się d o k ład n ie, ja k to działa: ona słoniom komunikować s ię m iędzy sobą.

lu c in d a .S p e a k T o (llo y d , " W ita j" ) ;


K iedy wywoływana je st m e to d a S p e ak T o () o b iek tu lu c in d a , jed n o cześn ie używ ana je st referen cja w postaci
p a ra m e tru whoToTalkTo w celu w yw ołania m eto d y T e llM e () Lloyda.

whoToTalk T o.T ellM e(m essage, t h i s ) ;

Lucinda używa param etru \ t h i s jest zamieniane


whoToTalkTo (k tó ry posiada na r e f e w j ę
referencję do Lloyda), \ d o obiektu lucinda.
w aby wywołać TellMe().

llo y d .T e llM e (m e ssa g e , [ r e f e r e n c j a do l u c i n d a ] ) ;

Lloyd działa w ięc tak , jakby był wywoływany z ( " W it a j" , lu c i n d a ) , i p o kazuje
taki kom unikat:

jesteś tutaj ► 209


Nic też coś znaczy

Tam, gdzie obiektówjeszcze nie było


Jest jeszcze jed n o w ażne słowo kluczow e, k tó reg o będziesz używał w pracy
z obiektam i. G dy tworzysz now ą referen cję i nie ustaw iasz jej n a żad en o biekt,
w tedy także p o siad a o n a w artość — je st ustaw iona n a n u ll , co oznacza, że nie
w skazuje nic.
Teraz istnieje
tylko jeden obiekt.
Referencja fido j e s t
Dog fido; u sta wiona na null.

Dog lucky = new Dog();

Teraz fido w skazuje


obiekt, nie j e s t j uż
równe null.

fido = new Dog();

U staw iliśm y referencję


lucky na null, więc nie
w skazuje ju ż ona na
żaden obiekt. Tym samym
j e s t eliminowana przez
mechanizm oczyszczania
lucky = null; p am ięci.

° 6 /e k \^
/ V, \
i Nie. istnieją.
głupie pytania

P : Jeszcze raz — czy mój formularz


z najbardziej popularnych jest sprawdzenie
warunku:
O: Pamiętasz, jak mówiliśmy o Common
jest obiektem? Language Runtime (lub CLR) na początku
if (llo y d == n u ll ) { drugiego rozdziału? To maszyna wirtualna,
O : Tak, jest! To dlatego kod formularza Wartością wyrażenia będzie tr u e , jeżeli która wykonuje wszystkie programy
rozpoczyna się od deklaracji klasy. referencja llo y d jest ustawiona na null. .NET, a jednocześnie jest sposobem na
Otwórz kod formularza i sam się o tym odizolowanie działających programów
n u ll możesz stosować także wtedy, gdy
przekonaj. Otwórz także plik Progrnm.cs od reszty systemu operacyjnego. Jedną
chcesz zakwalifikować obiekt do procedury
dowolnego programu, który napisałeś z wykonywanych przez nią czynności jest
usuwania elementów bezużytecznych. Jeśli
do tej pory, i zajrzyj do środka metody zarządzanie używaną pamięcią. Oznacza to,
posiadasz referencję do obiektu i skończyłeś
In itia liz e C o m p o n e n t( ) — znajdziesz że maszyna wirtualna śledzi wszystkie Twoje
już z nim pracować, możesz ustawić ją na
tam new Form1(). obiekty, potrafi określić, kiedy znika ostatnia
n u l l . Obiekt zostanie oznaczony i będzie
mógł zostać usunięty (chyba że istnieje do referencja do obiektu, i usuwa go, zwalniając
P : Do czego mógłbym używać niego inna referencja). zajmowaną przez niego pamięć.
wartości null?

O : Istnieje kilka sposobów jej użycia P: : Cały czas mówisz o procedurze


oczyszczania pamięci. Co ona
w typowych programach. Jednym
właściwie robi?
210 Rozdział 4.
Typy i referencje
■ Nie .istnieją.
głupie pytania

P : W dalszym ciągu nie jestem P : Wciąż nie rozumiem, jak P: Przypomnij mi jeszcze raz
pewien, czy dobrze zrozumiałem to jest z tymi różnymi typami — co robi „ th is ” ?
działanie referencji. przechowującymi wartości o różnej
wielkości. O co w tym chodzi? O : t h i s to specjalna zmienna, której
O : Referencje to sposób na korzystanie możesz użyć tylko wewnątrz obiektu.
z metod i pól obiektu. Gdy utworzysz O : A więc tak. Problem polega na tym, Kiedy jesteś wewnątrz klasy, t h i s pozwala
referencję do obiektu Dog, będziesz mógł że zmienne mają pewien rozmiar bez Ci odwołać się do pola lub metody tej
jej użyć podczas wywoływania jego metod. względu na przechowywaną wartość. konkretnej instancji. Jest to naprawdę
Jeśli posiadasz niestatyczną metodę Jeśli więc masz zmienną i nadasz jej typ pomocne podczas pracy z klasą, której
nazwaną D o g .B a rk () lub D og.B eg(), lo n g , chociaż liczba ta jest naprawdę metody wywołują inne klasy. Jeden obiekt
możesz utworzyć referencję sp o t. Wtedy mała (powiedzmy równa 5), CLR może użyć tego słowa kluczowego w celu
będzie możliwy dostęp do tych metod przydzieli odpowiednią ilość pamięci do przekazania innemu obiektowi referencji
za pomocą odwołań s p o t.B a rk ( ) jej przechowywania, na wypadek gdyby do samego siebie. Gdy s p o t wywołuje
i s p o t.B e g ( ) . Używając referencji, możesz kiedyś stała się duża. Jeśli trochę nad tym metody obiektu ro v e r, przekazując jako
także zmodyfikować informacje w polach pomyślisz, to uznasz, że jest to bardzo parametr t h is , to ro v e r otrzymuje
obiektu, na przykład zmienić pole Breed użyteczne. A poza tym, przecież nazywamy referencję do obiektu spot.
za pomocą odwołania sp o t.B re e d . je zmiennymi dlatego, że mogą się zmieniać
cały czas. Jeżeli posiadasz
P Zaczekaj, czy to nie oznacza,
: CLR przyjmuje, że wiesz, co robisz, klasę, dla której
że kiedy zmieniam wartość poprzez dobierając typ, i ufa, że nie wybierasz
referencję, to zmieniam ją dla
takiego, którego nie potrzebujesz. instancje mogą być
wszystkich referencji wskazujących
na dany obiekt?
Nawet jeśli liczba nie jest teraz duża,
to istnieje szansa, że po kilku operacjach
tworzone, możliwe
O : Tak. Jeśli ro v e r jest referencją do tego matematycznych się zmieni. CLR przydziela jest użycie zmiennej
wystarczającą ilość pamięci, aby poradzić
samego obiektu co sp o t, to zmieniając
sobie z każdą zmienną danego typu.
this w celu uzyskania
ro v e r.B re e d na „Beagle", sprawisz,
że s p o t.B re e d także będzie równe referencji do tej
„Beagle".
właśnie instancji.
CELNE SPOSTRZEŻENIA
^ J e s t jeden szczególny przypadek, w którym typ zm iennej nie j e s t
! de klarowany — dow iesz s ię o nim w rozdziale 14- podczas poznawania
t/ słowa kluczowego var.
Gdy deklarujesz zmienną, podajesz jej typ i nazwę ■ Istnieje kilka typów, które C# potrafi konwertować
Czasem łączysz to z ustawieniem jej wartości. automatycznie (przykładem może być konwersja
s h o rt na in t) . Kiedy kompilator nie pozwoli Ci zapisać
■ Istnieją typy wartościowe dla liczb, które pozwalają na
w zmiennej wartości innego typu, będziesz musiał
pracę z różnymi ich zakresami. Największe liczby powinny
zastosować rzutowanie.
mieć typ lo ng , natomiast najmniejsze (nie większe niż 255)
mogą być deklarowane jako byte. Istnieją pewne słowa, które są zarezerwowane przez język
C# i których nie możesz używać do nazywania zmiennych.
Każdy typ wartościowy posiada rozmiar i nie możesz
Są to między innymi fo r , w h ile , using, new oraz inne,
przypisać wartości typu większego do typu mniejszego.
które mają określone zadanie w języku.
Nie ma tu znaczenia aktualna zawartość zmiennej.
■ Referencje są jak etykiety. Możesz mieć ich tak dużo,
Kiedy korzystasz z literałów, możesz użyć przyrostka F do
jak chcesz, i wszystkie będą odnosiły się do tej samej rzeczy
określenia typu float (15.6F) i M dla typu decimal (36.12M).
Jeżeli obiekt nie posiada referencji, jest niszczony przez
mechanizm oczyszczania pamięci.

jesteś tutaj ► 211


Który słoń ma największe uszy
Zaostrz ołówek ____
Poniżej zaprezentowano tablicę obiektów E le p h a n t oraz pętlę, której zadaniem
jest wyszukanie słonia z największymi uszami. Jaka jest wartość zmiennej
b i g g e s t E a r s . E a r S i z e po każdej iteracji pętli fo r?

p riv a te v o id b u t t o n l C l i c k ( o b j e c t s e n d e r , E v e n tA rg s e )

{ Tworzymy tablicę sied miu


referencji Eleph an t.
E le p h a n t[] e le p h a n ts = new E l e p h a n t [ 7 ] ;

e le p h a n ts [ 0 ] = new E l e p h a n t Q Name " L lo y d " , E a r S iz e = 40 } ; Każda tablica


rozpoczyna
e le p h a n ts [l] = new E l e p h a n t ( ) Name " L u c in d a " , E a r S iz e = 33 }; s i ę od indeksu
O — pierw szy
e le p h a n ts [ 2 ] new E l e p h a n t ( ) Name "L a rry ", E a r S iz e = 42 } ; obiekt Elephant
z n a jd u je s i ę
e le p h a n ts [ 3 ] new E l e p h a n t Q Name " L u c ille " , E a r S iz e = 32 }; w elephantsłOJ.

e le p h a n ts [ 4 ] new E l e p h a n t Q Name "L a rs", E a r S iz e = 44 } ;

e le p h a n ts [ 5 ] new E l e p h a n t Q Name " L in d a " , E a r S iz e = 37 } ;

e le p h a n ts [ 6 ] new E l e p h a n t Q Name "H u m p h re y ", E a r S iz e = 45 } ;

Iteraq a num er 1: b ig g e s tE a rs .E a rS iz e =
E le p h a n t b i g g e s t E a r s = e le p h a n ts [0 ];

fo r (in t i = 1; i < e le p h a n ts .L e n g th ; i+ + )

{
Iteraq a num er 2: b ig g e s tE a rs .E a rS iz e =
if ( e le p h a n ts [ i] .E a r S iz e > b ig g e s tE a r s .E a r S iz e )

b ig g e s tE a rs = e le p h a n ts [i];
Iteraq a num er 3: b ig g e s tE a rs .E a rS iz e =
} Ten w iersz u staw ia referencję
b iggestE a rs na ten elem ent
t ablicy, na który wskazuje
} elephantsfij.
M e s s a g e B o x .S h o w ( b i g g e s t E a r s . E a r S i z e . T o S t r i n g Q ) ;
Iteraq a num er 4: b ig g e s tE a rs .E a rS iz e =
}

Bądź ostrożny — ta pętla Iteraq a num er 5: b ig g e s tE a rs .E a rS iz e =


rozpoczyna sw oje działanie
od drugiego elem entu tablicy
(znąjdującego s ię pod indeksem 1)
i wykonuje umieszczony wewnątrz
niej blok kodu sz eść razy, aż
i będzie równe je j długości. Iteraq a num er 6: b ig g e s tE a rs .E a rS iz e =

-► R o zw ią za n ie znajdziesz na stronie 222.

212 Rozdział 4.
Typy i referencje

Magnesiki z kodem
Kod obsługujący kliknięcie przycisku j e s t porozrzucany na
drzw iach lodówki. C zy p o tra fis z zrekonstruow ać go tak , aby
metoda działała i wyświetlała okno zaprezentow ane poniżej? | refNum = in d e x [ y ] ; |

in d e x [0 ] - 1'» l V \~ \

in d e x tH = 3; 1

1 in d e x [2 ] = ° ’> l

1 in d ex [3] = z > -1

| S tr in g [ ] 'is la n d s = new S t r i n g [ 4] ;
- I

| r e s u l t += "\nWyspa = 11;

► R o zw ią za n ie znajdziesz na stronie 223.


jesteś tutaj ► 213
Puzzle z basenu

c l a s s T ria n g le To ie s t punkt w ejścia aplikacji.


Zagadkowy basen
{
Tw oim za d a n iem je st p o b ra n ie fragm entów d o u b le a r e a ;
I „using” na górze.
kodu z b asen u i w staw ienie ich i n t h e ig h t;
w p u ste m iejsca. Możesz użyć i n t le n g th ; U'
teg o sam ego frag m en tu więcej niż p u b lic s t a t i c v o id M a in (S trin g [] a rg s )
raz i nie m usisz wykorzystać ich {
wszystkich. Celem je st n apisanie s trin g re s u lts =
klasy, k tó ra się skom piluje,
będzie działała i wyświetli
w h ile (
k o m u n ik at zap rezen to w an y poniżej.
{

W ynik: .h e ig h t = (x + 1) * 2;
.le n g th = x + 4;

r e s u l t s += " t r ó j k ą t 11 + x + " , p o le " ;


r e s u l t s += .a r e a + " \n " ;

x = 27; podpowiedź'.
T ria n g le t5 = t a [ 2 ] ; setAreaO NIE
je st metodą
t a [ 2 ] . a r e a = 343; statyczną. Wróć
r e s u l t s += "y = " + y ; do rozdziatu 3 .,
Pytanie dodatkowe! M essag eB o x .S h o w (resu lts + aby odświeżyć
informacje na
" , t5 p o le = " + t 5 . a r e a ) temat stówa
A by uzyskać dodatkow e p unkty, użyj } kluczowego
fragm entów z b asen u do w ypełnienia v o id s e tA re a () static.
pustych m iejsc w oknie z w ynikami. {
(h e ig h t * le n g th ) / 2;

Przypominamy: każdy
fragment z basenu
może być użyty
więcej niż raz.

► R o zw ią za n ie znajdziesz na stronie 224.


214 Rozdział 4.
Typy i referencje

Napisz grę w literki


D o tarłeś do końca pew nego e ta p u ... W iesz ju ż n a tyle dużo, że m ożesz n apisać grę! O to n a czym będzie
o n a polegać. W fo rm u larzu b ę d ą w yśw ietlane losow e litery. Jeśli użytkow nik naciśnie klawisz z je d n ą z nich,
literk a zniknie, a w spółczynnik dokładności zostanie zwiększony. Jeśli je d n a k gracz w pisze n ieodpow iednią
literkę, w skaźnik dokładności będzie zm niejszany. W raz ze w pisyw aniem kolejnych lite rek te m p o gry będzie
coraz szybsze, dzięki czem u z każd ą p o p raw n ie w pisaną literk ą g ra będzie staw ać się coraz trudniejsza.
G ra skończy się, kiedy fo rm u larz zostanie w całości w ypełniony literkam i. Z ró b 10!
!

[p STWÓRZ fo r m u lar z.
T a k pow inien w yglądać nasz form u larz w yświetlony w o knie Form Designer:

O to co m usisz zrobić, p ro jek tu jąc form ularz:


# W yłącz przyciski m aksym alizacji i m inim alizacji. W e właściwości F o rm B o rd e rS ty le w ybierz w artość
Fixed3D . D zięki tem u gracz nie będzie w stan ie przypadkow o przeciągnąć k tórejś z kraw ędzi
fo rm u larza i zm ienić jego wym iarów. N astęp n ie zm ień w ielkość fo rm u larza tak , by był długi i wąski
(my nadaliśm y m u w ym iary 876 n a 174).
# Przeciągnij k o n tro lk ę L istB o x z o k n a Toolbox i u p u ść ją n a form ularzu. W e właściwości Dock w ybierz
w artość F i l l , a we właściwości M ultiC olum n w artość T ru e. Z m ień ustaw ienia właściwości F o n t w taki
sposób, by k o n tro lk a używ ała po g ru b io n ej czcionki o w ielkości 72 punktów .
# W o knie Toolbox rozw iń u m ieszczoną n a sam ej górze g ru p ę A ll Windows Forms. D zięki tem u
wyświetlisz w iele dostępnych ko n tro lek . O dszukaj k o n tro lk ę T im er i dw ukrotnie ją kliknij, by um ieścić
ją n a form ularzu.
# N a stęp n ie odszukaj k o n tro lk ę S t a t u s S t r i p i tak że dw ukrotnie ją kliknij — w ten sposób dodasz
do sw ojego fo rm u larza p a se k stanu. K iedy ju ż to zrobisz, w szarym obszarze u d o łu o k n a Form Designer
pow inny być w idoczne dwie ikony: S t a t u s S t r i p i T im er.

C zy widzisz, jak możesz skorzystać z kontrolki Timer, by Twój


formularz wykonywał więcej niż jedną czynność w danej chwili?
Poświęć minutkę, żeby zajrzeć do czwartego punktu dodatku
Pozostałości” , w którym opisano inne rozwiązanie zapewniające
takie same możliwości.
jesteś tutaj ► 215
o
Stwórz coś zabawnego
, Będziesz używał

5 SKONFIGURUJ KONTROLKĘ STATUSSTRIR


Przyjrzyj się dokładniej paskow i sta n u w idocznem u u dołu
^ trzech nowych
kontrolek, ale ich
stosowanie jest
fo rm u larza przed staw io n eg o n a p o p rzed n iej stronie. bardzo łatwe!
Z lewej strony p ask a została u m ieszczona g ru p a etykiet:
Choć jeszcze nie miałeś okazji poznać
Prawidłowych: 22 Błędów: 20 Wszystkich: 42 Dokładność: 52% kontrolek L is tB o x , S t a t u s S t r i p ani
T im e r, to jednak już wiesz, jak można
nato m ia st z drugiej znajduje się ety k ieta i p a se k postępów : określać ich właściwości i używać ich w kodzie
programu. W kilku kolejnych rozdziałach
Poziom trudności:
dowiesz się znacznie więcej na ich temat.

T eraz dodaj now ą k o n tro lk ę S ta tu s L a b e l do p ask a stanu.


Kliknij um ieszczoną n a nim ikonę rozw ijanej listy i w ybierz z niej opcję
S t a t u s L a b e l . N astęp n ie wykonaj poniższe czynności:

# W oknie Properties we właściwości (Name) p ask a stan u wpisz c o r r e c t L a b e l , a


w e właściwości T e x t „Prawidłowych: 0 ”. D odaj do p ask a trzy kolejne kontrolki
S ta tu s L a b e l o nazwach: m is s e d L a b e l, t o t a lL a b e l i a c c u ra c y L a b e l, a ich
właściwościom T e x t przypisz w artości, odpow iednio: „Błędów: 0 ”, „Wszystkich: 0 ” oraz „D okładność 0 % ”.
# D odaj k o lejn ą k o n tro lk ę S t a t u s L a b e l . Jej właściwości S p rin g przypisz w artość T ru e , właściwości
T e x tA lig n w arto ść M id d le R ig h t, a w e właściwości T e x t wpisz „Poziom tru d n o ści:”. W k ońcu dodaj
do p ask a sta n u k o n tro lk ę P r o g r e s s B a r i nadaj jej nazw ę d i f f i c u l t y P r o g r e s s B a r .
# We właściwości S iz in g G r ip kontro lk i S t a t u s S t r i p w ybierz w arto ść F a ls e (jeśli zaznaczyłeś p o d rzę d n ą
kon tro lk ę S ta tu s L a b e l lub P ro g r e s s B a r, to naciśnij klawisz E sc , by ID E zaznaczyło głów ną kon tro lk ę
S ta tu s S trip ).

SKONFIGURUJ KONTROLKĘ TIMER.


Czy zauw ażyłeś, że k o n tro lk a T im er nie została w żad en w izualny sposób p rzed staw io n a n a form ularzu?
W ynika to z fak tu , że T im er je st k o n tro lk ą niewidoczną — w żad en sposób nie zm ienia o n a fo rm u larza ani
nie wpływa n a jego wygląd. M a o n a tylko je d n o p rzezn aczen ie — b ezu sta n n ie wywołuje tę sam ą m eto d ę.
Przypisz właściwości In terv a l swojej k o n tro lk i T im er w artość 800; dzięki te m u w skazana m eto d a będzie
wywoływana co 800 m ilisekund. N a stęp n ie dw u k ro tn ie klik n ij w idoczną u do łu fo rm u la rza ikonę tim e r l.
W odpow iedzi ID E zrobi to , co zawsze ro b i p o dw ukrotnym kliknięciu k ontrolki: d o d a do k o d u fo rm u larza
m eto d ę. W tym przy p ad k u będzie to m e to d a o nazw ie timer1_Tick . Poniżej przedstaw iliśm y jej kod.

p r i v a te v o id tim e r 1 _ T ic k (o b je c t s e n d e r , EventA rgs e)


{
/ / Dodajemy losow ą l i t e r k ę do k o n tr o lk i L istB ox Pole o nazw ie „random“
dodasz ju ż niebawem.
lis tB o x l.Ite m s .A d d ((K e y s )ra n d o m .N e x t(6 5 , 9 0 ) ) ; Czy potrafisz odgadnąć
i f ( lis tB o x l.I te m s .C o u n t > 7 ) _ jakiego będzie typu ?
i *—
l i s t B o x 1 .I te m s .C le a r ( ) ;
lis tB o x l.Ite m s .A d d (" K o n ie c g r y " ) ;
t i m e r 1 .S to p ( ) ;

Klasa Time r dysponuje metodą S tartO , jednak


w tym p rojekcie ni'e będziesz m u siał je j
uz:yw ać. Z am ias f te go przypiszesz właściwości
Enabled konfrolki w artość True, co spraw i, że
au t° matycznie zacznie działać.

216 Rozdział 4.
Typy i referencje

DODAJ KLASĘ, KTÓRA BĘDZIE PRZECHOWYWAĆ


STATYSTYKI GRACZA.
Skoro form ularz m a w yświetlać sum aryczną liczbę klawiszy naciśniętych przez
użytkow nika, liczbę naciśniętych praw idłow ych klawiszy, liczbę pom yłek oraz
w spółczynnik dokładności gracza, to będziem y m usieli te inform acje w jakiś sposób
przechow ywać. W ygląda zatem n a to , że będziem y p o trzebow ali nowej klasy! D odaj
do swojego p ro je k tu klasę o nazw ie S t a t s . B ędzie o n a m iała cztery p o la typu i n t
o nazw ach T o ta l , M issed , C o r r e c t i A c c u ra c y o raz je d n ą m eto d ę o nazwie
U p d a te () p o siad ającą p a ra m e tr typu b o o l. W w yw ołaniu tej m eto d y będziem y
przekazyw ali w artość t r u e , jeśli gracz nacisnął praw idłow y klawisz odpow iadający
jednej z lite r um ieszczonych w k o n tro lce L istB o x , o raz w arto ść f a l s e w przeciw nym przypadku.

c la s s S ta ts
{
p u b lic in t
p u b lic in t
p u b lic in t 0;
p u b lic in t

p u b lic voi

T o tal+ + ;

if (¡c o rre c tK e y )
{
M issed++;
Każde wywołanie metody
} Update() powoduje
e ls e przeliczenie liczby
{ prawidłowych naciśnięć
C o rrec t+ + ; i zapisanie nowego wyniku
w polu Accuracy.
}

A ccuracy = 100 * C o rre c t / T o ta l;

DODAJ DO FORMULARZA POLA,


KTÓRE BĘDĄ PRZECHOWYWAĆ OBIEKTY STATS I RANDOM.
A by faktycznie przechow yw ać inform acje, będziesz p o trzeb o w ał nowej instancji klasy
S t a t s , a zatem dodaj do fo rm u larza p o le o tej sam ej nazw ie. Ju ż w cześniej dow iedziałeś się,
że będziesz tak że p o trzeb o w ał p o la o nazw ie random — zapiszesz w nim o b iek t typu Random.

D odaj te dw a p o la n a sam ym p o czątk u k o d u form ularza: Zanim przejdziesz dalej, powinieneś ustawić
wartości trzech właściwości. Właściwości
p u b lic p a r t i a l c l a s s Forml : Form
E n a b le d kontrolki T im er przypisz wartość T rue.
{ Właściwości Maximum kontrolki P ro g r e s s B a r

% Random random = new Random();


Stats s ta ts = new S ta ts ();
przypisz wartość 701, a właściwości K eyPreview
formularza — wartość T rue. Zastanów się przez
chwilę, dlaczego te właściwości są potrzebne.
Co się stanie, jeśli nie ustawisz ich wartości?

jesteś tutaj ► 217


Podstawa wspaniałej gry

ZADBAJ O OBSŁUGĘ NACISKANYCH KLAWISZY.


P ozostała jeszcze je d n a rzecz, o ja k ą T w oja g ra m usi zadbać: za każdym razem , gdy gracz naciśnie klawisz,
gra m usi spraw dzić, czy je st on praw idłow y (czyli czy odp o w iad a jed n ej z liter um ieszczonych w kontrolce
L istB o x ), i odpow iednio zaktualizow ać inform acje w yśw ietlane n a p ask u stan u
Kliknij ten przycisk,
W róć zatem do o k n a Form Designer i w ybierz cały by zm ienić sp osób
prezentacji
form ularz. N astęp n ie p rzejd ź do o k n a Properties
zaw artości w ° knie
i kliknij przycisk z ik onką błyskawicy. Przewijaj Properties. Przycisk
zaw artość o k n a, aż odnajdziesz opcję KeyDown, widoczny z lewej
strony klikniętego
i dw ukrotnie ją kliknij. W ten sposób każesz przywróci oryginalny
ID E d o d ać do fo rm u larza m e to d ę o nazwie wygląd okna.
Forml_KeyDown, k tó ra b ędzie wywoływana za
każdym razem , gdy użytkow nik naciśnie jakiś
To s ą tek zwane
klawisz. Poniżej zam ieściliśm y jej kod. zdarzenia.
Poznasz je
p r iv a t e void Forml_KeyDown(object sender, KeyEventArgs e) szczegółowo
{ w dalszej części
J e ś l i gracz n a c isn ą ł k law is z l i t e r k i dostępnej w kontro lce książki.
Ta instrukcja if L is t B o x , to usuwamy j ą i zwiększamy tempo gry
sprawdza, czy ( l i s t B o x l. I t e m s . C o n t a i n s ( e .K e y C o d e ) )
w kontrolce
ListB ox j e s t listB o x l.Ite m s .R e m o ve (e .K e y C o d e );
um ieszczona litera lis tB o x 1 .R e fre s h ();
odpowiadająca Ten fragment kodu odpowiada za zw iększanie
i f (tim e rl.In te rv a l
naciśniętem u poziomu trudności gry wraz ze zw iększa n iem się
t im e r 1 .I n t e r v a l
klawiszowi. Jeśli liczby p rawidłowy ch klaw iszy naciśniętych przez
if (tim e rl.In te rv a l gracza. M° z e s z nieco ułatw ić grę, zm niejszając
taka litera zostanie t im e r 1 .I n t e r v a l
odnaleziona, to wartości odejmowane od właściwości timer1.Interval,
if (tim e rl.In te rv a l lub dodatkowo j ą utrudnić, zw iększając je .
metoda ją usuw a
i zw iększa poziom t i m e r 1 .I n t e r v a l
trudności gry. d i f f i c u l t y P r o g r e s s B a r .V a l u e = 800 - tim e r1 .In te r v a l;

/ / Gracz n a c isn ą ł prawidłowy k la w is z , a k tu alizu jem y zatem s t a t y s t y k i


/ / g ry , wywołując metodę Update() i przekazując do n i e j wartość tru e
s ta ts.U p d a te (tru e );
Kiedy gracz
naciśnie ja kiś e lse
klawisz, metoda {
Form1_KeyDown() / / Gracz n a c isn ą ł nie właściwy k la w is z , zatem a k tu alizu jem y s t a t y s t y k i ,
zaktualizuje / / przekazując w wywołaniu metody Update() wartość f a l s e
jego sta ty sty k i, s ta ts.U p d a te (fa lse );
wywołując }
m etodę Update() Grę można uruchomić tylko
obiektu S ta ts , / / Aktualizuje m y e t y k i e t y na pasku stanu
a następnie raz. Czy wiesz, co zrobić,
c o m e c t L a b e l.T e x t = "Prawidłowych: " + s t a t s . C o r r e c t ;
w yśw ietli nowe m issed La b el.T ex t = "Błędów: " + s t a t s .M i s s e d ; by gracz mógł ją ponownie
sta ty sty k i na
pasku stanu.
t o t a l L a b e l . T e x t = "W szystkic h: " + s t a t s . T o t a l ; uruchomić po wyświetleniu
a c c u ra c y L a b e l.T e x t = "Dokładność: " + s t a t s .A c c u r a c y ^ komunikatu „Koniec gry” ?
}

Fri URUCHOM GRĘ.


I to wszystko — g ra jest gotow a! W ypróbuj ją i spraw dź, ja k wywiązałeś się ze swego zadania. Być m oże
będziesz m usiał po zm ien iać nieco w ielkość czcionki używanej w k o n tro lce L istB o x , by zm ieściło się w niej
dokładnie siedem liter, b ąd ź dopasow ać poziom trudności, zm niejszając nieco w artości o dejm ow ane
w m etod zie Forml_KeyDown() o d właściwości t i m e r 1 . I n t e r v a l .

218 Rozdział 4.
Przejmij kontrolę nad kontrolkami Typy i referencje

Kontrolki to też obiekty, podobne do innych Z r ó i fo l


U tw orzyłeś już kilka form ularzy, p rzeciągając k o n tro lki z o k n a Toolbox. O kazuje się jed n ak , że te wszystkie
k ontrolki są zwyczajnymi o biektam i. A to oznacza, że m ożesz dodaw ać do nich referen cje i używać ich tak
sam o, ja k używasz instancji swoich w łasnych klas. Przyjrzyjm y się p rak ty czn em u zastosow aniu tych możliwości
n a przykładzie p ro g ram u , k tóry będzie anim ow ał k o n tro lk i L a b e l, odbijając je o d kraw ędzi form ularza.

u sin g System .W indows.Form s;


(— >
B ędziesz
potrzebował c l a s s LabelBouncer { Ta klas a ma pote t yp u Label o nazwie MyLabel,
tej instrukcji oznacza f°, że będzie °n ° przechowywało referencję
p u b lic Label MyLabel; do obi^Wu Label. Podobnie jak w szystkie inne
„using",
gdyż w tej referencje, t akże ta będzie mieć początkowo w artość
przestrzeni p u b lic bool GoingForward = t r u e ; w » . Z ap iszemy w nim referencję do jednej z etykiet
nazw została umieszczonych na formularzu.
umieszczona
klasa Label. p u b lic v o id Move() {
Ta w artość logiczna zmienia s ię z tru e na false
i f (MyLabel != n u ll) { i ponownie na true, gdy kontrolka odbija s ię od
i f (G oingForward == t r u e ) { praw ej i lewej krawędzi formularza.
M etoda Move() określa, M y L abel.L eft += 5;
czy kontrolka zderzyła ^ i f (M yL abel.L eft M y L ab el.P aren t.W id th - M yLabel.W idth)
s ię z praw ą krawędzią
formularza, używając GoingForward = f a ls e ;
operatora >=, który } Aby przesuwać kontrolkę na formularzu, wystarczy, że
spraw dza, czy w artość }
właściwości Left kontrolki utworzysz nową instancję klasy LabelBouncer, określisz
e ls e wartość jej właściwości MyLabel, zapisując w niej jedną
j e s t większa lub równa od
szerokości formularza. { z kontrolek Labę/ umieszczonych na formularzu,

r
Jak sądzisz, dlaczego
od szerokości formularza
M yL abel.L eft -= 5;
i f (M yL abel.L eft <= 0) {
GoingForward = t r u e ;
a następnie będziesz wywoływał metodę Move().
Każde wywołanie metody Move() sprawi, że obiekt
LabelBouncer szturchnie etykietę, zmieniając wartość jej
właściwości Left. Jeśli pole GoingForward będzie mieć
trzeba odjąć szerokość 1 wartość true, kontrolka będzie popychana w prawo poprzez
kontrolki? Kiedy przeciągasz k o n tro lę dodanie do właściwości Left liczby 5; w przeciwnym razie
} i um ieszczasz j ą w formularzu, IDE kontrolka będzie popychana w lewo poprzez odjęcie liczby 5.
} u staw ia je j właściwości Top i Left.
Twoje programy mogą korzystać Każda kontrolka ma właściwość Parent, zawierającą referencję
z tych właściwości, aby przesuw ać do formularza, gdyż formularz także jest obiektem!
kontrolki na formularzu.

jesteś tutaj ► 219


Wpisz tutaj

A oto kod fo rm u larz a. C iekaw e, czy będziesz p o tra fił zrozum ieć, co się w nim dzieje. K orzysta o n z tablicy
obiektów L ab e lB o u n c e r, by przesuw ać etykiety tam i z p o w ro tem w o knie form u larza, a m e to d a obsługująca
zdarzenia T ic k kontro lk i T im er b ezu stan n ie wywołuje m eto d y M ove() tych obiektów L a b elB o u n cer.

Formularz przechowuje referencję do tablicy obiektów LabelBouncer


p u b lic p a r t i a l c l a s s Form1 : Form { w polu o nazwie bouncers. Kiedy zostanie wywołana metoda
ToggleBouncing(), używa ona parametru index, by sprawdzić
p u b lic Form1() { element tablicy. Jeśli ma on wartość n u ll, metoda tworzy obiekt
I n itia liz e C o m p o n e n t( ) ; LabelBouncer i zapisuje w tablicy jego referencję; w przeciwnym razie
} element tablicy jest czyszczony poprzez zapisanie w nim nu ll.

L abelB o u n cer[] bouncers = new L a b e lB o u n c er[3 ];

p r i v a t e v o id ToggleBouncing( i n t in d e x , Label labelT oB ounce)


i f (b o u n c e rs[in d e x ] == n u ll ) {
{ Ponieważ
Każdy przycisk b o u n c e rs [in d e x ] = new L a b e lB o u n c e r(); kontrolki są
wyw ołuje metodę b o u n c ers[in d e x ].M y L a b e l = labelT oB ounce;
ToggleBouncing(),
przekazując do niej } obiektami,
indeks tablicy oraz e ls e {
referencję do jednej b o u n c e rs [in d e x ] n u ll: możesz
z kontrolek Label
um ieszczonych na
formularzu. }
}
przekazywać
p r i v a t e v o id button1_C lick ( o b je c t s e n d e r , EventA rgs e) {
referencję
Czy dokładnie
rozum iesz,
co s ię dzieje
T o g g leB o u n cin g (0 , l a b e l l ) ; do nich jako
}
w procedurach
obsługujących parametr
kliknięcia p r i v a t e v o id button2_C lick ( o b je c t s e n d e r , EventA rgs e) {
przycisków?
M asz zrozum ieć,
T o g g leB o u n cin g (1 , la b e l 2 ) ; m etody oraz
}
w jaki sposób
przyciski włączają zapisywać je
i wyłączają p r i v a t e v o id button3_C lick ( o b je c t s e n d e r , EventA rgs e) {
przesuw anie
T o g g leB o u n cin g (2 , la b e l 3 ) ; w tablicach,
e tyk iet.
}
polach
p r i v a t e v o id tim er1_T ick ( o b je c t s e n d e r , EventA rgs e)
f o r ( i n t i = 0 ; i < 3 ; i++) {
{
i zmiennych.
i f ( b o u n c e r s [i] != n u ll ) {
b o u n c e rs [i] .M o v e ();
}
} Odbijające się etykiety - n
labell

} Iabel2
E tykiety odbijają się
label 3 od krawędzi formularza
Kontrolka Timer używa pętli, by wywoływać
Kl,kn'j przy cisk button 1, żeby rozpocząć naw et je ś li go poszerzi
m etodę Move() każdego obiektu button 1
lub zw ęzim y.
LabelBouncer, jednak robi to wyłącznie, przesuwanie etykiety labell Ponowne
je śli w tablicy j e s t zapisana referencj a,
button2 kliknięcie przycisku zatrzyma etykietę.
a nie null. Z apisanie w elem encie t ablicy | button3 |
pozostate przyciski kontrolują
wartości null spowoduje, ż e kontrolka pizesuwan iddwóch pozostałych etykiet.
przestanie być animowana.

220 Rozdział 4.
Typy i referencje

W C# istnieje około 77 zarezerw ow anych słó w nazywanych sło w am i k lu czo w ym i . Są to słowa


zarezerwowane przez kompilator C# i nie możesz ich używać do nazywania zmiennych. Będziesz znał je
wszystkie dość dobrze po zakończeniu lektury tej książki. Poniżej wymieniliśmy kilka, których już używałeś.
Rozwiązanie
Napisz, co według Ciebie robią one w C#.
ćwiczenia

namespace Przestrzenie nazw pozwalają zadbać o to , by nazwy w programie nie kolidowały


ze stosowanymi w .NET Framework lub innych zewnętrznych klasach używanych przez aplikację.
W szystkie klasy i m e to d y programu znajdują się w przestrzeniach nazw.

for Um ożliwia tworzenie pętli wykonującej t r z y instrukcje. W pierwszej deklaruje się zmienną,
k tó ra będzie używana. Druga w kolejności sprawdza pewien warunek w ykorzystujący tę zmienną.
Trzecia instrukcja m odyfikuje jej wartość.

class Za pomocą klasy definiujesz obiekty. Klasy posiadają m e to d y i właściwości.


W łaściwości świadczą o ty m , co klasa wie, a m etod y określają, co robi.

public Klasa public m oże być używana przez każdą inną w projekcie. Kiedy zmienna lub m etoda
jest określona jako public, m oże być używana i wywoływana przez klasy i m etody
mieszczące się na zewnątrz klasy, w k tó re j została zadeklarowana.

else Kod rozpoczynający się od else będzie wykonywany wtedy, gdy warunek
instrukcji i f nie będzie spełniony.

new Używasz go do utworzenia nowej instancji klasy.

using Jest t o sposób na wyszczególnienie wszystkich przestrzeni nazw używanych w programie.


Użycie tego słowa kluczowego umożliwia stosowanie klas p la tfo rm y .NET i wcześniej
utw orzonych klas pochodzących z innych źródeł, w ty m również napisanych samodzielnie.

if Jest t o jeden ze sposobów zapisania instrukcji warunkowej w programie.


Oznacza: jeśli pewna rzecz jest prawdziwa, z ró b to.

while Pętle while są wykonywane, dopóki warunek umieszczony na ich początku przyjm uje wartość true.

jesteś tutaj ► 221


Rozwiązanie ćwiczenia

ą. Zaostrz ołówek
_ . . Poniżej zaprezentowano tablicę obiektów El e p h a n t oraz pętlę, której zadaniem jest
K U Z W K jZ U M c wyszukanie słonia z największymi uszami. Jaka jest wartość zmiennej bi g g e s t E a r s .
E a rS iz e po każdej Iteracji pętli fo r?

p r i v a t e v o id b u t t o n 1 _ C l i c k ( o b j e c t s e n d e r , E v e n tA rg s e )

E le p h a n t[] e le p h a n ts = new E e p h a n t [ 7 ] ; Czy pam iętasz, że pętla


rozpoczyna dziafanie
e le p h a n ts [ 0 ] = new E le p h a n t { Name = " L l o y d " , E a r S iz e = 40 }; od drugiego elem entu
m tablicy? Jak myślisz,
e le p h a n ts [l] new E le p h a n t { Name = " L u c i n d a " , E a r S iz e = 33 } ; dlaczego tak je s t?
e le p h a n ts [ 2] new E le p h a n t { Name = " L a r r y " , E a r S iz e = 42 };

e le p h a n ts [3 ] new E le p h a n t { Name = " L u c i l l e " , E a r S iz e = 32 } ;

e le p h a n ts [4 ] new E le p h a n t { Name = " L a r s " , E a r S iz e = 44 } ;

e le p h a n ts [5 ] new E le p h a n t { Name = " L i n d a " , E a r S iz e = 37 };

e l e p h a n t s [ 6] new E le p h a n t { Name = "H u m p h re y ", E a r S iz e = 45 } ;

Iteraq a num er 1: b ig g e s tE a rs .E a rS iz e =
E le p h a n t b i g g e s t E a r s = e le p h a n ts [0 ];

fo r (in t i = 1; i < e le p h a n ts .L e n g th ; i+ + )

{ Iteraq a num er 2: b ig g e s tE a rs .E a rS iz e = 42
if ( e le p h a n ts [ i] .E a r S iz e > b ig g e s tE a r s .E a r S iz e )
, Referencja bigge s tEars j e s t używana do określenia,
{ który spośród elem entów sprawdzonych do tej pory
b i g g e s t E a r s = e l e p h a n t s [ i ] ; w pętli for ma najw iększe u szy.
} Spraw dź działanie programu, korzystając I t e r a j num er 3: b ig g e s tE a rs .E a rS iz e =
} z debuggera. Umieść w tym m iejscu piunlct
p rzerwania i sprawdzaj, ja kie wartości
przyjm uje wyrażenie biggestE a rs.E arSize-
M e s s a g e B o x .S h o w ( b ig g e s tE a r s .E a r S iz e .T o S tr in g ( ) ) ;
Iteraq a num er 4: b ig g e s tE a rs.E a rS iz e = 44

Pętla for rozpoczyna przetw arzanie od drug iego s ^ m a


i porównuje go ze słoniem , na którego wskaz uje Iteraq a num er 5: b ig g e s tE a rs .E a rS iz e = 4 -4
biggestEars. Jeśli jego u szy s ą wię k sze , to referencj a
biggestEars j e s t przestaw iana na n iego. p otem
algorytm porównuje następnego słonia, następnego
i tak dalej, i tak dalej... a na końcu p ętli bigges tEars
w skazuje tego z najw iększym i tuszam .
Iteraq a num er 6: b ig g e s tE a rs .E a rS iz e = 45

222 Rozdział 4.
Typy i referencje

Magnesiki z kodem . Rozwiązanie


Kod obsługujący kliknięcie przycisku j e s t porozrzucany na
drzw iach lodówki. C zy p o tra fis z zrekonstruow ać go ta k , aby
metoda działała i wyświetlała okno zaprezentow ane poniżej?

To tutaj
inicjalizowana je s t
tablica indexll.

W/ tej pętli while


wartości pobierane
s ą z tablicy indext],
a następnie używane
do indeksowania
tablicy islandsU.

jesteś tutaj ► 223


Rozwiązanie ćwiczenia

Zagadkowy basen. Rozwiązanie

Z auw aż, że fa Masa zaw iera


<r p unkt we jścia, a/e także
c l a s s T ria n g le tworzy i'ns t a ncję sam ej siebie/
(o c a tkowicie /ega/ne w C#.
{
d o u b le a r e a ;
in t h e ig h t;
Po wykonaniu in t le n g th ;
tego w iersza koidu p u b lic s t a t i c v o id M a in ( s tr in g [ ] a rg s )
mamy ju ż tablicę
czterech referencji {
Triangle, ale n ie s t r i n g r e s u l t s = "";
mamy jeszcze Pętla while
żadnych obiektów int x = 0; tworzy cztery
T r i a n g l e ! ^ ------- Triangle[] ta = new Triangle[4]; instancje
Triangle,
w h ile (x < 4 ) wywołując
{ instrukcję new
Odpowiedź ta[x] = new Triangle(); cztery razy.
na pytanie dodatkowe ta[x] .h e ig h t = (x + 1) * 2;
ta[x] . le n g th = x + 4;
ta[x].setArea();
r e s u l t s += " t r ó j k ą t + x + p o le " :
r e s u l t s += + ta[x] .a r e a + " \n " ;
x = x + 1;
}
int y = x;
x = 27;
T r ia n g le t5 = t a [ 2 ] ;
t a [ 2 ] . a r e a = 343;
r e s u l t s += "y = " + y;
M essag eB o x .S h o w (resu lts +
" , t5 p o le = " + t 5 . a r e a ) ;
Metoda setA rea używa }
pól height i length do
ustaw ienia po/a area. v o id se tA re a ()
Nie j e s t to metoda {
statyczna, d/atego
może być wywoływana area = ( h e ig h t * le n g th ) / 2;
ty/ko za pośrednictwem }
instancji Triang/e.

224 Rozdział 4.
Imię i Nazwisko: Data:

Laboratorium C#

Dzień na wyścigach
Laboratorium zawiera specyfikację opisującą program, który
musisz napisać, wykorzystując wiedzę zdobytą w poprzednich
kilku rozdziałach.
Ten projekt jest większy niż wszystkie, które widziałeś tu
do tej pory. Zanim przystąpisz do pisania, przeznacz więc
chwilę na uważne przeczytanie wszystkiego. Nie przejmuj
się, jeżeli utkniesz w jakimś miejscu — nie ma tutaj niczego
nowego. Możesz przejść do dalszej części książki i wrócić
do laboratorium później.
Uzupełniliśmy część projektu za Ciebie i upewniliśmy się,
że masz wszystkie potrzebne elementy... i nic więcej.
Od Ciebie zależy, czy ukończysz pracę. Nie damy Ci
odpowiedzi w postaci kodu, możesz natomiast pobrać pliki
graficzne, których użyliśmy w naszym projekcie, oraz ostateczną,
gotową wersję programu.

Gdybyś jednak potrzebował ja k iejś porady,


to wiedz, że znaleźli s ię c zyte lnicy, którzy
potwierdzili sw oje próżne p rzechwałki,
publikując rozwiązanie tego laboratorium na
CodePlex, GitHub lub innych witryntzch do
publikacji kodu i współpracy nad mm.

Laboratorium C# 225
Dzień na wyścigach

Specyfikacja: stwórz symulator wyścigów


Janek , B a rte k i A re k uw ielbiają chodzić n a to r wyścigowy, ale ciągła
u tra ta pieniędzy pow oduje u nich frustrację. P o trzeb u ją sym ulatora,
aby m ogli określić zwycięzcę, zanim w yłożą p ien iąd ze n a zakłady.
Jeśli dobrze wywiążesz się z zad an ia, otrzym asz p ro c e n t z ich
wygranych.

O to co m usisz dla nich stw orzyć...

Faceci
Janek, B a rte k i A re k chcą obstaw iać n a wyścigach psów.
Ja n e k rozpoczyna ze stan em k o n ta rów nym 50 zł, B a rte k
m a 75 zł, nato m ia st A re k p o siad a 45 zł. P rzed każdym
wyścigiem decydują oni, czy chcą obstaw iać i ja k dużą kw otę
gotow i są n a to przeznaczyć. Faceci m ogą zm ienić swoje
zakłady aż do m o m e n tu ro zpoczęcia wyścigu. ale gdy już
się zacznie, nie m a możliw ości w ycofania się.

T f W

Dom bukmacherski
D om bukm acherski p rzechow uje inform acje o ilości
y -\ /- -\
gotów ki p osiad an ej p rzez każdego z facetów i w ysokości
W itam y w Domu
aktualnie zaw ieranego zakładu. M inim alna kw ota, v y V y B u km a ch erskim K a ro la
y ■*> Minimalny zakład: 5 zł /- N /- -\
jak ą m ożna przeznaczyć n a zakład, wynosi 5 zł. Jeden zakład na osobę na dany wyścig
Czy masz wystarczającąsumę pieniędzy?

D om bukm acherski
/- N '
um ożliw ia przyjęcie tylko
jed n eg o zakładu o d osoby J V 7

n a dany wyścig.

B ukm acher spraw dza, Bukmacherskim Karola


czy facet, który zam ierza
Minimalny zakład: 5 zł
obstaw iać, po siad a
w ystarczającą sum ę
Jeden zakład na osobę na dany wyścig
pieniędzy — nie m oże on Czy masz wystarczającą sumę pieniędzy?
obstaw ić gonitwy, jeżeli
nie m a odpow iedniej
ilości gotów ki n a pokrycie
zakładu.

226
Dzień na wyścigach

Obstawianie
Wszystkie zakłady:
K ażdy zakład o p ie ra się n a zasadzie „podw ójnie albo nic”
— gracz albo pod w aja swój w kład, albo traci wszystko, podwójnie albo nic
co postaw ił. M inim alna kw ota, za ja k ą m o żn a obstaw ić gonitwę,
M inim alny zakład: 5 zł
to 5 zł. K ażdy facet m oże postaw ić m aksym alnie 15 zł na
jed n eg o charta. Jeżeli c h a rt wygra, obstaw iający otrzym uje M aksym alnie 15 zł na psa
(po zakończeniu wyścigu) kw otę dw ukrotnie w iększą o d tej,
jak ą postaw ił. Jeżeli p rzeg ra, po staw io n a kw ota je st o dejm ow ana
Wygrana: pieniądze zyskane
o d stan u jego konta. Przegrana: pieniądze stracone
Powiedzmy, J e g o t h a r tw S c S ż y t ,
A
Z n fk , 10 zt i dodatkowo otrzymuje 10 zt
^ z f y c i i t u S ) . Je śli P ^ g r a t, °d k° nta
odejmowane j e s t 10 zt.

Wyścig
Jeśli chciałbyś zbudować system
W wyścigu b io rą u d ział cztery ch arty biegnące p o prostym odcinku. zm .em ajęcy sz an se wygranyth
Zw ycięzcą jest te n ch art, który jak o pierw szy przekroczy linię m ety. ' P o ^ z e g ó ln y c h psów - f a j obaw
Wyścig je st całkow icie losowy. N ie m a żadnego p rzeszk ad zan ia lub r r r : s Prób° Mać- Napisanie
w L 9? kodu nie tytko pozwoli Ci
ułatw iania i nie m a w iększego praw d o p o d o b ieństw a, że c h a rt wygra zdobyć dośw iadczenie, lecz także
b ęd zie ś w ie tn ą zabaw ą.
kolejny wyścig tylko dlatego, że w cześniej m u się to częściej udaw ało.

Brzmi fajnie? W krótce więcej szczegółów*

227
Dzień na wyścigach

Potrzebujesz trzech klas i formularza Musisz dodać na górze


„using System .W indows.Form s" w klasach
B ędziesz m usiał utw orzyć w projekcie trzy klasy o ra z graficzny interfejs Greyhound i Guy. Oprócz tego przed
użytkow nika sym ulatora. P ow inieneś m ieć tablicę trzech obiektów Guy deklaracją każdej z klas będziesz m usiał
do zarządzania facetam i i ich w ygranym i o raz tablicę czterech obiektów dodać sło w o kluczow e public.
G reyhound, k tó re b ęd ą brały u d ział w wyścigu. D o d atk o w o każd a instancja
Guy pow inna m ieć swój w łasny o b iek t B et, k tóry będzie przechow yw ał dane
o zakładzie i w ypłacał (lub o d b ierał) p ien iąd ze p o zak ończeniu wyścigu.

R ozpoczęliśm y T w oją p rac ę o d zap rezen to w an ia opisu klas i pew nych szkielet klasy,
fragm entów kodu, n a których m o żn a się oprzeć. M usisz to wszystko dokończyć. polega na i^^upapni^riiu Trn<!to)(zadanie
Upewnij s ię, że do każdej u b li c c l a s s Greyhound { ^
deklaracji klasy dodałeś
p u b lic i n t S ta rtin g P o s itio n ; / / M ie js c e , g d z ie ro zp o czy n a s i ę P ictu re B o x
słowo kluczowe public. —
p u b lic i n t RacetrackLength ; / / Jak d łu g a j e s t t r a s a
p u b lic P ic tu re B o x MyPictureBox = n u l l ; / / Mój o b ie k t P ictu reB o x
p u b lic i n t Location = 0 ; / / Moje p o ło ż e n ie na to r z e wyścigowym
p u b lic Random MyRandom; / / I n s ta n c ja k la s y Random
Będziesz potrzebował tylko jednej instancji Masy Rand°m
— właściwość „random każdego obiektu Greyhound będzie
p u b lic bool Run() { wskazywać ten sam obiekt Random.
/ / P rzesuń s i ę do p rzodu losowo o 1, 2 , 3 lu b 4 p unkty
/ / Z a k tu a liz u j p o ło ż e n ie P ictu reB o x na fo rm u larz u
/ / Zwróć t r u e , j e ż e l i wygrałem w yścig n
}
wieaztat, co m a sz zrobić.
p u b lic v o id TakeS tartingP osition () {
/ / W yzeruj p o ło ż e n ie i ustaw na l i n i i s ta rto w e j

Czy w idzisz, jak diagram


}
odpowiada kodowi klasy. }
zmienną, i zadanie j e s t gotow e.

Twój obiekt może kontrolować różne rzeczy na formularzu...


To działa tak samo ja k klasa
K lasa G reyhound przechow uje isto tn e dane dotyczące swojej pozycji n a trasie podczas LabelBouncer: formularz
wyścigu. O dśw ieża także p o ło żen ie kontro lk i P ic tu re B o x rep rezen tu jącej c h a rta p rzekazuje do obiektu
Grey hound referencję
poruszającego się p o to rze wyścigowym. K ażd a in stancja G reyhound korzysta z p o la do k°nt-rolki PicutureBox,
o nazw ie M yP ictureB o x , k tó re je st używ ane do stero w an ia k o n tro lk ą P ic tu re B o x a obiekt używ a właściwości
Left, by przesuw ać obrazek
wyśw ietlającą ilustrację charta. Przypuśćm y, że zm ien n a d i s t a n c e przechow uje
w formularzu.
w artość, o jak ą c h a rt m a się p rzesu n ąć do p rzo d u . K o d zaprezentow any poniżej będzie
aktualizow ał p o łożenie kontro lk i M y P ictu reB o x , do d ając d i s t a n c e do jej w artości X:
G re y h o u n d A r ra y [0 ] = new G re y h o u n d () {
M y P ic tu re B o x = p i c t u r e B o x l , ^ --------- Ten obiekt Greyhound zarządza
S ta r tin g P o s itio n = p ic tu r e B o x l.L e f t, kontrolką pictureB oxl.
R a c e tr a c k L e n g t h = r a c e t r a c k P i c t u r e B o x . W i d t h - p ic tu r e B o x l.W id th ,
R a n d o m iz e r = M yR andom izer W p °d°t>ny spostib będzies z m usia ł postąpić z każdym obiektem
}; Greyhound w t ablicy. Oprócz teg o b ęd ziesz m usia ł zainicjalizować
sw oje obi e kty Guy . Nie za p°mnij ta kże odpowiednio u staw ić pól
m y RadioButt°n oraz MyLabel każdego z obiektów Guy.

228
Dzień na wyścigach
p u b lic c l a s s Guy {
p u b lic s t r i n g Name; / / Im ię f a c e t a
p u b lic Bet MyBet; / / I n s ta n c ja k la s y Bet p rzech o w u jąca dane o z a k ła d z ie
p u b lic i n t C ash; / / Ja k dużo p ie n ię d z y p o sia d a To pole działa dokładnie
tak samo jak pole MyLabel
w p rogram ie Labe lBouncer
/ / O s ta tn ie dwa p o la s ą k o n tro lk am i GUI na fo rm u la rz u z rozdz ia łu 4 .
p u b lic R adioB utton M yRadioButton; / / Moje p o le wyboru
p u b lic Label M yLabel; / / Moja e t y k i e t a Kiedy w polu MyLabel zapiszesz ju ż referencję I
do jednej z e tykiet formularza, będziesz mdgt
zmieniad je j te k s t przy użyciu wyrażenia
MyLabe|.Te x t. Dokładnie to samo dotyczy pola
p u b lic v o id U p d a te L a b e ls() { MyRadioButton. p
/ / Ustaw moje p o le te k sto w e na o p is z a k ła d u , a n a p is obok
Kiedy inicjalizujesz / / p o la wyboru t a k , aby pokazyw ał i l o ś ć p ie n ię d z y ("Ja n e k ma 43 z ł" )
obiekt Guy, upewnij
} Tutaj dodaj swój kod.
się, że jego pole MyBet
j e s t ustaw ione na nu i l /
Z araz po inicjalizacji p u b lic v o id C le a rB e t() { } / / Wyczyść mój z a k ła d , aby b y ł równy z e ro
wywołaj metodę Pam iętaj, że zakłady s ą
UpdateLabelsO- reprezentow ane p rzez
p u b lic bool P la c e B e t( i n t Amount, i n t DogToWin)
instan cje klasy B et.
/ / U stal nowy z a k ła d i przechow aj go w p o lu MyBet
/ / Zwróć t r u e , j e ż e l i f a c e t ma w y s ta r c z a ją c ą i l o ś ć p ie n ię d z y , aby o b staw ić
}
To j e s t obiekt,
którego używ a Guy
do reprezentowania p u b lic v o id C o l l e c t ( i n t W inner) {
zaktadów w aplikacji. / / Poproś o w y p ła tę z a k ła d u i z a k tu a liz u j e t y k i e t y

/
} Kluczem do rozwiązania j e s t tu taj _użycie obiektu
Be t... Pozwo'l mu wykonywać swoj ą p rac ę -
}
B et
u b li c c l a s s Bet {
Amount p u b lic i n t Amount; / / I l o ś ć p ostaw ionych p ie n ię d z y
Dog p u b lic i n t Dog; / / Numer p s a , na k tó re g o postaw iono
Bettor p u b lic Guy B e t t o r ; / / F a c e t, k tó ry z a w arł z a k ła d

p u b lic s t r i n g G e tD e s c r ip tio n () {
GetDescription()
PayOut() / / Zwraca s t r i n g , k tó ry o k r e ś la , k to o b s ta w ił w y ścig , ja k dużo p ie n ię d z y
/ / p o s ta w ił i na k tó re g o p sa (" Ja n e k p o s ta w ił 8 z ł na p sa numer 4 ").
/ / J e ż e li i l o ś ć j e s t równa z e r o , z a k ła d n ie z o s t a ł z a w arty
// (" Ja n e k n ie z a w arł z a k ła d u " ) fc- , t t dość c zęste zadanie
}
}
p u b lic i n t PayO ut( i n t W inner) {
Podpowiedz: instancję B et
tworzysz w kodzie klasy / / Param etrem j e s t zw y cięzca w y ścig u . J e ż e l i p ie s w y g ra ł,
Guy. Użyje ona słowa / / zwróć w a rto ść p o sta w io n ą . W przeciwnym r a z i e zwróć w a rto ść
kluczowego th i s i w ten
s posdb przekaże referencję / / p o staw io n ą ze znakiem minus
do sam ej siebie do
inicjalizatora obiektu Bet. } Pam iętaj: form ularz przechow uje psy w tablicy, której indeksy zaczyn ają się
od z e ra . Pies numer 1 je st zapisany w komórce o indeksie O , pies numer dwa
— w komórce o indeksie 1 i ta k dalej. B y podać numer zw ycięzcy, będziesz
m usiał dodać do indeksu liczbę 1.
229
Dzień na wyścigach

Tak wygląda architektura Twojej aplikacji Ta blica GreyhoundU z a w ie ra


c z te ru re fe re n c je , z których
każda wskazuje na inną i n s t a n c j i
Pośw ięć nieco czasu n a d o k ład n e zapoznanie się z arc h ite k tu rą
kla sy G reyhoun d.
p ro gram u. W ygląda to n a dość skom plikow aną konstrukcję,
ale w rzeczywistości nie m a tu żadnej rzeczy, której nie znasz.
Tw oim zadaniem je s t odtw o rzen ie teg o p ro je k tu sam odzielnie,
poczynając od um ieszczenia tablic G reyhound o raz Guy

OQQ
w klasie głów nego form ularza.

O''
iek t

Formularz m usi
zainicjalizować obie tablice O I z
podczas uruchamiania
T a b lic a r e f e r e n c ji G r e y h o u n d

Tablica GuyO zawiera


referencje do trzech obiektów
„ z nich P°siada
pole NiyBet, które j e s t
referencją do obiektu B et.

ioioio
Wśród wielu obiektów
/ T a b lic a r e f i Gu y

Q
wizualnych w ykorzystane

O
zostaną także cztery kontrolki
PictureBox do zilustrow ania
psów . Referencje do nich
um ieśc is z w inicjalizatorach
czterech obiektów Greyhound.
Formularz będzie te ż zaw ierał J 0\ \ Ob''6'*'

o o.
trzy kontrolki RadioButton
i trzy etykiety, które zostaną
u żyte w inicjalizatorach trzech
obiektów typ u Guy.

Ob'®
ob\®

Jeśli Twój program nie będzie się chciał skompilować, a komunikat o błędzie będzie informował
o „inconsistent accessibility" (niespójnej dostępności). upewnij się, że przed deklaracją każdej
z trzech klas dodałeś słowo kluczowe public. (Dowiesz się o nim więcej w dalszej części książki).

230
Dzień na wyścigach

Kiedy facet obstawia,


;- w ięc _Guy num er 2 tworzy nowa Nie będziesz jednak używa ł liczb
tworzysz nowy obiekt Bet takich jak 7 lub 3 — skorzy s ta sz
i_ns n c j ę klasy B e t. Używa słowa
z argumentów przekazyw any^
kiuczowego th is' aby poinformować
N a io ie rw fo rm ula rz każe obiektowi obie k t nowego zakładu, że to do) metody PlaceBet: BetA mount
Guy numer 2 p ostaw ić 7 z t na psa właśn ie on obstaw ia . / oraz DogToWin.
numer 3...
MyBet = new B e t()
G u y s [ l] .P la c e B e t( 7 , 3)
{ Amount = 7 , Dog 3 , B e tto r = t h i s };

O 0b'\&
r e t u r n tr u e
O Ob'®

mia wy s tarczającop ie n i ^ , mtg^ zwróciłaby w a r t ^ fa lse).


Formularz używa kontrolki Timer, by psy
biegły aż do momentu wyłonienia zwycięzcy Metoda Run() każdego obiektu Dog
czy ch a rt przekroczył linię
Kiedy użytkownik me ty., Pę tla po w'nna zakończyć się
natychmia s t po ukończeniu wyścigu
decyduje przez jednego z chartów.
o rozpoczęciu
wyścigu, form ularz p r i v a t e v o id tim e r1 _ T ic k (...) {
.u ru c h a m ia
f kontrolkę Timer, f o r ( p ę t la po w s z y stk ic h chartach )
która rozpoczyna i f ( wywołaj metodę Run() psa ) {
1 wyścig- mamy zw ycięzcę!
U staw właściwość wywołaj metodę ttm e r l.S to p ( ) , b y zatrzym ać p sy
Enabled obiektu Timer w yśw ietl kom unikat z inform acją, k to wygrał
na false i używaj

O
metod S tartO każdy f a c e t może odebrać wygraną
i Stop(), by rozpow ąć Obiekt Sys*® }
i zakończyć wyścig .
Dom bukmacherski na formularzu informuje
Obiekt Bet decyduje, każdy z obiektów Guy, który ch a rt wygrał
wyścig. W ten sposób realizowane s ą O’«
czy powinien dokonać wypłaty wy p łaty za zw ycięstw a w zakładzie.
Określając zwycię s kiego
G u y s [l].C o lle c t(w in n in g D o g ) M yBet.PayOut(winningDog) psa, nie zapommj zoZać 1
do indeksu tablicy.

Q 0b'\®
Obiekt Guy dodaje wynik metody B e tP ayOu tO
do swojej gotówki. Jeśli ch art zwy ciężył,
"o; Ob'®
if
O
(m ój c h a r t zw yciężył) {
r e t u r n Amount;
ob'®

funkcja powinna zwrócić Amowl^ w przeciwny m } e ls e {


razie zwróci -A m ount.
r e t u r n -Amount;
}

231
Dzień na wyścigach
Poeksperym entuj trochę z w łaściwością
Tak powinien wyglądać Twój interfejs użytkownika !n terval obiektu Timer, by zm ieniać
szybkość w yścigu.

G raficzny interfejs użytkow nika aplikacji „D zień n a w yścigach” zbudow any jest Do ustaw ienia długości trasy
z fo rm u larza p o d zielon eg o n a dwie części. N a górze znajduje się to r wyścigowy: uży ie szrw łaściwości Width kontrolki
p ictureB ox. Wartość przekażesz
k o n tro lk a P ic tu re B o x dla to ru i d o datkow e cztery dla chartów . D o ln a połow a do p ola RiacetrackLength obiektu
fo rm ularza pokazuje dom bukm acherski, gdzie trzech facetów (Jan ek , B a rte k i A rek) Greyhound. Pozwoli to na określenie
m omentu, w którym p ie s minie linię
m oże obstaw iać re z u lta t wyścigów. Właściwości m e ty . Khlimj p rawym przyciskiem
FormBorderStyle m yszy kontrolkę PictureBox
for-mularza przypisz reprezentują c ą tor i w ybierz opcję
wartość FixedSinngle, „S e nd to B a c k by upewnić się , że
r rt S ‘ ssaw s? P * a właściwości nie będzie ona przesłaniać kontrolek
M aximizeBox oraz prezentujących charty.
M inimizeBox ustaw
na false. ^

Dzień na wyścigach

Zajrzyj ponown ie na sam koniec rozdziału 2 ., by przypomnieć


sobie, ja k wczy ta ć obraz do kontrolki PictureBox.

U staw właściwość S izeM ode na StretchIm age, byś mógł zm ienić


wielkość k° ntr°lki p ictureB ox, a prezentow any w niej obraze k
dostoso wał s ię do je j nowych wym iarów ._______________________

Formularz powinien aktualizować tę etyk ietę, w yświetlając w niej


minimalną wartość zakładu. Użyj do tego właściwości Minimum
kontrolki NumericUpDown.
Dom bukmacherski

m in im u m B e t L a b e l
O ,ie Radio Button

O b ib Radio Button

O '/RadioButton

Ws z y scy trzej faceci m°gą obstawiać, ale Jeżeli w szystkie


i s t m i j tylko jedno okn°, więc w danej chwili Kiedy obiekt Guy zaw iera zakład, zakłady zo stały
zawrzeli ztiMud m°że tylko jeden z nich. nadpisuje każdy poprzudrn. Bieżąpy przyjęte,
kTe przyciski wyboru pozwalają określić to naci śnij ten
pokazywany j e s t przisz te po<a te s to w a .
który.' IDomyśMe włącz Janka, przypisując Każde z nich posiada w łaściwość przycisk, aby
’w łaf ci,woś r i Checked odpowiadp ą , Ę J ? A u to S ize ustaw ioną na M s e rozpocząć wyścig.
kliknii i Rl ?dioBut!on. wartośH true. Dwu krotnie a pole B orderStyle na FixedS ingle .
kliknij każdą z nich, by dodać odpowiedni kod.

Możesz pobrać pliki graficzne pod adresem ftp://ftp.helion.pl/przyklady/cshru3.zip.

232
Dzień na wyścigach
Będzie s z potrzebował
Zawieranie zakładów p ętli, by zainicjować
każd y ot>ie k t Guy, wywołując
D o obstaw iania użyj k o m p o n en tó w w k o n tro lce GroupBox dom u w tym celu jego metodę
ClearB et() (która sprowadza
bukm acherskiego. P roces te n sk ład a się z trzech etapów : s ię do zrobienia zakładu
i n n O zfofyd,), a na£t<dnie
I m et°d ę UpdateLabelst). to polo teksto w e z a
Żadne zakłady nie zostały jeszcze zawarte,
K iedy p ro g ram je s t u ru ch am ian y p o raz pierw szy lub wyścig d o p iero się M y Label. Odświeża
zakończył, w d o m u bukm acherskim nie są zaw arte jeszcze żad n e zakłady. także ilość gotówki na
przyciskach wyboru,
M ożna zobaczyć całkow itą sum ę pieniędzy posiadanych przez facetów k n rrt : ___ _

po praw ej stro n ie ich im ion.

M in im a ln y z a k ła d : 5 zł Z a k ła d y
® Janek ma 50 zł pod(Jj Janek nie zawarł zakładu

O Bartek rW75])ł minimalną, | Bartek nie zawarł zakładu


S ta n konta każdego ---------------— kwotą
faceta w yśw ietlany O Afek ma 45 zł Arek nie zawarł zakładu
j e s t tu ta j.
Jan ek 5 C złn a psa numer 1

Minimalny zakład powinien


odpowiadać minimalnej
wart°ści' w kontrolce zakładu. Z araz po zawarciu
Każdy facet zawiera swój zakład. zakładu p rzez Bartka
A by obstaw ić wyścig, zaznacz przycisk w yboru, obiekt Guy aktualizuje
to pole tekstow e i te k s t
zdecyduj się n a jak ąś kw otę i c h arta, a n astęp n ie przycisku wyboru.
kliknij przycisk „staw ia”. M e to d a P la c e B e t( )
odśw ieży p o la tekstow e i przyciski wyboru.

Przykro nam, B artku, Twój faworyt


Po wyścigu każdy facet otrzymuje wygraną (lub płaci!). przegrał — straciteś 13 z t
U/szystkie zaktady s ą podwajane
Z a ra z p o zakończeniu wyścigu i w yłonieniu zwycięzcy każdy o biekt lub zakfadający s ią tw e . wszystko,
Guy wywołuje sw oją m eto d ę C o l l e c t ( ) i d o d aje w ygraną lub stratę qdyby zatem Bartek wygraf,
do aktu aln eg o sta n u konta.

Upewnij się, że w szystkie obiekty Greyhound będą wspólnie używ ały tego samego obiektu Random!
Gdyby każdy chart u żyw ał własnej instancji tego obiektu, to program mógłby źle działać w przypadku,
gdyby w szystkie charty wygenerowały tę sam ą sekw encję liczb losowych.

233
Dzień na wyścigach

Końcowy produkt
Podczas w y śc ig u c z te ry
Z apew ne wiesz, że T w oja aplikacja „D zień n a wyścigach”
♦ • i a f ■u a v wyścigowym , oz któryś
zostanie u z n an a za skończoną, gdy faceci b ę d ą mogli
zaw ierać zakłady i obserw ow ać wyścigi psów.

obstaM iać' ‘W

w ła ś c iw o ś c i i £ Z ° l l % ° ś X £ .

Możesz pobrać pliki graficzne Nie przedstawiliśmy rozwiązania tego


laboratorium, gdyż jeśli program będzie
potrzebne do wykonania tego dostatecznie duży, będzie go można napisać na
zadania z serwera Helionu: tak wiele sposobów, że trudno wskazać jedno
„dobre" rozwiązanie. Jeśli jednak potrzebujesz
podpowiedzi, to wiele osób potwierdziło swoje
ftp://ftp.helion.pl/przyklady/cshru3.zip przechwałki, publikując własne rozwiązania na
witrynie CodePlex.com lub innych, jej podobnych.

234
5. Hermetyzacja

Co ma być ukryte*
niech będzie ukryte

Czy kiedykolwiek marzyłeś o odrobinie prywatności? Czasami Twoje obiekty czują się tak
samo. Na pewno nie lubisz sytuacji, w których ktoś, komu nie ufasz, czyta Twój pamiętnik lub przegląda
wykazy Twoich operacji bankowych. Dobre obiekty nie pozwalają innym obiektom na oglądanie swoich
pól. W tym rozdziale nauczysz się wykorzystywać potęgę he rm etyzacji . Sprawisz, że dane o b ie k tó w
będą pryw atne , i dodasz metody, które umożliwią Ci zabezpieczenie dostępu do d anych .

to jest nowy rozdział ► 235


Krystyna potrzebuje Twojej pomocy

Krystyna planuje przyjęcia


K rystyna zajm uje się planow aniem przyjęć dla swoich
klientów i robi to nap raw d ę dobrze. O statn io je d n a k

K iedy dzwoni nowy k lien t z p ro śb ą o zorganizow anie przyjęcia, K rystyna


zaczyna p racę od uzyskania niezbędnych inform acji. M usi się dow iedzieć,
ilu będzie gości, jakie rodzaje napojów serw ow ać i jak ie dek o racje pow inna
kupić. W celu oszacow ania całkow itych kosztów w ykonuje o n a dość
skom plikow ane obliczenia bazujące n a schem acie blokow ym używanym
o d w ielu lat. P ro blem p o leg a n a tym , że p o trze b u je dużej ilości czasu,
aby p rzeb rn ąć przez ten schem at. D o d atk o w o podczas szacow ania poten cjaln i
klienci już spraw dzają innych organizatorów .

Twoim zadaniem jest napisać w C # p ro g ram szacujący koszty przyjęć i tym


sam ym ocalić biznes Krystyny. W yobraź sobie, jakie przyjęcie dla Ciebie
zorganizuje, jeżeli Ci się pow iedzie!

236 Rozdział 5.
Hermetyzacja

Co powinien robić program szacujący?


K rystyna spisała podstaw ow e zasady działania jej system u kalkulacji
kosztów przyjęć. T o je st część w ym agań, z którym i przyszła:

Program Krystyny do planowania przyjęć — szacowanie kosztów przyjęcia_

• Za każdą osobę na liście gości pobierana jest o płata w w ysokości 25 zł.

• Klienci mogą wybierać rodzaj napojów. Na w ięk szo ^ przyjęć p o d a n y jest a lkoho l,
który kosztuje 20 zł od osoby. Klienci mogą także wybrać p rzyję a e b e z ^ k o M o w .
Krvstyna nazywa to „opcją zdrową". Kosztuje o na tylko 5 zł od osoby i polega ira
podaniu napojów gazowanych i soku zamiast alkoM u. Zorganizowanie W zdrowej t
jest dla niej znacznie łatwiejsze, dlatego Krystyna daje t akim klientom dodat kow y rabat
w wysokości 5% sumarycznych kosztów przyjęcia.

• Przewidziano dwie opcje dotyczące kosztów dekoracji. JeZeli klie nt wybierz<5dKek° ramoZe
normalna, to opłata wynosi 7,50 zł od osoby plus 30 zł opłaty j ^ r o r a r a w h Klientp 1^^aSZe
zwiększyć liczbę ozdób, wybierając opcję f a n t o w ą — kosztuje to I 5 zł od osoby plus
jednorazowa opłata dekoracyjna w wysokości 50 zł.

Niektóre z tych opcji


Poniżej zaprezentow an o inne p odejście do tego sam ego zestaw u dotyczą z m iany całkowitej
ceny przyjęcia, ja k również
opłat. A lgorytm zo stał p rzełożony n a niew ielki sch em at blokowy, kosztów powiązanych
aby łatw iejsze było zrozum ienie jego działania: z liczbą gości.

Dekoracje
fantazyjne
(15 zł od osoby |
+ 50 zł opłaty
Liczba gości. za dekoracje)
Jedzenie Opcja I
V
(25 zł od zdrowa? u
osoby) Zwykła
dekoracja
(7,50 zł od osoby
+ 30 zł opłaty
za dekoracje)

O ije wię kszość opcji powoduje


z m ianę. kosztów d'la jednego gościa,
to istnieją także koszty naliczane
jednorazowo.

jesteś tutaj ► 237


Sposób, w jaki rozwiążesz problem Krystyny

Napiszesz program dla Krystyny


K iedy przew rócisz k artk ę, ujrzysz ćw iczenie, w którym będziesz m iał za zadanie napisać
p rogram dla Krystyny. Poniżej p o k ró tce przedstaw iliśm y, co będziesz m usiał stworzyć.

S tw o rzy sz fo rm u larz, którego K ry sty n a będzie używ ać


do określania opcji przyjęcia. B ędzie w nim po d aw ała liczbę
gości o raz zaznaczała lub usuw ała zaznaczenie p ó l w yboru
reprezen tu jący ch dek o racje fantazyjne o raz opcję zdrow ą.
Podczas tych czynności koszt przyjęcia będzie odpow iednio
aktualizow any i wyświetlany.

Logika działania klasy zostanie za w a rta w klasie


DinnerParty
0 nazw ie D innerParty. F o rm u larz b ędzie tw orzył o biekt
NumberOfPeople klasy D in n e rP a r ty , zapisywał referen cję do niego w polu
CostOfBeveragesPerPerson 1 używał jego p ó l i m e to d do realizacji obliczeń.
CostOfDecorations

SetHealthyOption()
CalculateCostOfDecorations()
CalculateCost()

Obok pokazaliśm y, ja k będzie p u b lic p a r t i a l c la s s Form l : Form


w yglądał sam początek {
kodu fo rm u larza. Jak w idać, D in n e r P a r ty d i n n e r P a r t y ;
definiuje on pole o nazwie
dinnerParty, które będzie p u b lic Form 1()
w ykonyw ać obliczenia. {
Pierw szą czynnością w ykonywaną In itia liz e C o m p o n e n t();
przez form ularz będzie o k reślen ie d i n n e r P a r t y = new D i n n e r P a r t y ( ) { N u m b erO fP eo p le = 5 };
w artości dom yślnych i w yliczenie d in n e r P a r ty .S e tH e a lth y O p tio n ( f a ls e ) ;
kosztu przy użyciu m etody d in n e r P a r ty .C a lc u la te C o s tO f D e c o r a tio n s ( tr u e ) ;
D is p la y D in n e r P a r ty C o s t( ) . D is p la y D in n e rP a rty C o s t() ;
F o rm u larz będzie wywoływać }
tę m eto d ę za każdym razem ,
gdy użytkow nik zm ieni jakieś
ustaw ienia.

238 Rozdział 5.
Hermetyzacja

Oto ja k będzie d ziałać klasa D in n e r P a r ty . A ktu aln y stan obiektu D in n e rP a rty — czyli w artości
zapisane w jego polach — o kreślają sposób w ykonyw ania obliczeń. W ybór opcji zdrow ej oraz
fantazyjnych dekoracji, czy też zm ian a liczby uczestników przyjęcia pow o d u je zm ianę stan u obiektu,
a ta z kolei spraw ia, że w ywołanie m etody C a l c u l a t e C o s t ( ) zwróci in n ą w artość.

Jeśli u żytko w n ik zaznaczył pole w yb o ru


Do określania liczby uczestników użyjesz „Dekoracje fa n ta z y jn e ", fo rm u la rz będzie
ko n tro lki NumericUpDown, a jej procedura prze kazyw ał w w y w o ła n iu m etody
obsługi zdarzeń będzie ustaw iać wartość
C a lc u la te C o s tO fD e c o ra tio n s ( ) w artość true.
pola o b ie ktu D in n e rP a rty .

Liczba gości. 1
Jedzenie | ^ Opcja i
(25 zł od > zdrowa? [<
osoby) 1
---------- — —.

Koszt jedzenia zawsze w yn osi 25 z ł na


osobę. Dowiesz się, jak używać stałych
do zapisyw ania w artości, które nigdy się Kiedy u żytko w n ik kliknie pole Jeśli u żytko w n ik w yb ierze opcję z drow ą,
nie zm ieniają. w yb o ru „O pcja zd ro w a ", fo rm u la rz to koszty na p o jó w są mniejsze. M etoda
w y w o łu je m etodę o nazw ie S e tH e a lth y O p tio n ( ) a ktu alizuje pole
S e tH e a lth y O p tio n ( ) , która zm ienia o nazw ie C o stO fB e ve ra g e sP e rP e rso n ,
sposób w ylicza n ia kosztó w przyjęcia. przechowujące koszt napojów.

Za każdym razem, gdy u żytko w n ik zaznaczy pole lub zm ieni liczbę uczestników
przyjęcia, m etody obsługujące zdarzenia a ktu a lizu ją pola i w y w o łu ją m etody ob ie ktu
D in n e rP a r ty , by o d p o w ie d n io zaktu alizow a ć jego stan. Następnie w y w o łu ją metodę
C a lc u la t e C o s t ( ) , by określić c a łk o w ity koszt przyjęcia i w y ś w ie tlić go w etykiecie.

Wszystko zrozumiałeś'? No ło bierz się za pisanie programu! - - - - - - - - - - ►


jesteś tutaj ► 239
Dobrze, nie ma problemu Stopniowo będziesz musiał rozwiązywać coraz dłuższe i trudniejsze problemy...

Stwórz program rozwiązujący problem szacowania kosztów przyjęcia.

Ćwiczenie U tw órz nowy p ro je k t W indow s F o rm s A p p licatio n i dodaj do niego p lik z klasą


D in n e rP a rty.cs . N apisz klasę D innerParty n a podstaw ie diagram u
znajdującego się p o lewej stronie. P o siad a o n a trzy m etody:
C alcu la te C o stO fD e co ra tion s() , SetH ealthyO ption() i C a lc u la te C o s t() .
D la p ó l dotyczących kosztów użyj typu decim al , a liczbę osób oznacz jak o i n t .
DinnerParty U pew nij się, że dodałeś M po każdym literale podczas przypisyw ania w artości
do zm iennych typu d ecim al ( 10.0M).
NumberOfPeople
CostOfBeveragesPerPerson I tu taj pojaw ia się b ard zo p o m o cn e n arzęd zie C # . Jeżeli koszt je d ze n ia nie
CostOfDecorations
będzie zm ieniany p rzez p ro g ram , m ożesz zadeklarow ać go jak o stałą. Pole
będzie zachow ywało się praw ie ta k sam o ja k zm ienna, ale nie będziesz m ógł go
nigdy m odyfikow ać. T a k będzie w yglądała deklaracja:

pub lic const in t CostOfFoodPerPerson = 25;


SetHealthyOption()
W róć do p o p rzed n iej strony i upew nij się, że d obrze zrozum iałeś logikę
CalculateCostOfDecorations()
CalculateCost() działania m eto d . T ylko je d n a z nich zw raca w artość ( decim al ) — dwie
p o zo stałe są typu v o id . M eto d a C alculateC ostO fD ecorations() określa
koszt d ekoracji dla p o d an ej liczby osób biorących u d ział w przyjęciu.
C alcu la te C o st() będziesz używał do o k reślen ia jego całkow itych kosztów;
wyznaczysz je, sum ując koszty dekoracji o raz napojów i je d ze n ia dla wszystkich
T | s + diagram dla klasy
uczestników . Jeżeli k lien t zam ów ił opcję zdrow ą, m ożesz zastosow ać ra b a t
stworzyć- w ew nątrz m etody C alcu la te C o st() po o k reślen iu całkow itych kosztów.

D odaj taki o to k o d do sw ojego form ularza:


D eklarujesz pole dinnerParty
DinnerParty dinnerParty; w klasie formularza. N astępnie
Metoda SetHeatthyOptionO dodajesz te cztery w iersze zaraz
P °rametr“ typu bool pub lic Form1() { pod InitializeComponentO.
In itializeC om p on en t();
polaC % ?& m n) W Celu aktualizacji
n n r fj 2o st 0 f? evera9esPerPerson na dinnerParty = new DinnerParty() { NumberOfPeople 5 };
dinnerP arty.S etH ealthyO ption(false);
" ' " f "*blm dinnerParty.CalculateC ostO fD ecorations(true);
DisplayDinnerPartyCost();

T a k pow inien w yglądać form ularz. U staw wartość


Użyj właściwości kontrolki domyślną na 5.
Wartością
NumericUpDown do ustaw ienia minimalną powinno
górnego lim itu liczby gości n a 20, być 1, natom iast
Pola wyboru mają n ato m iast dolnego n a 1. maksymalną 20
nazwy fancyBox oraz U staw jak o w arto ść dom yślną 5. Pole wyboru
healthyBox. W kontrolce — dekoracji
NumericUpDown m ożesz P ozbądź się także przycisków
fantazyjnych
zo sta w ić domyślną nazwę. m aksym alizacji i m inim alizacji powinno mieć
z głów nego okna. właściwość Checked
ustawioną, na true.
kontrolka Label z w łaściw ością Text
To j e s t po P r ^ X ^ T o ^ r S t y l e ustaw iona

240 Rozdział 5.
Naszym celem j e s t pomóc Ci w s ta niu s ię
doskonałym program istą C #, a najszybszy m
..gdyż wiemy, że sprostasz wyzwaniu! sposobem dotarcia do niego je s't rozw iązyw an ia Hermetyzacja
problemów takich ja k ten.

Z a m ia st używać przycisku do obliczania ceny przyjęcia, fo rm u larz te n ak tualizuje ety k ietę z kosztem
autom atycznie zaraz p o użyciu p o la w yboru lub zm ianie w artości kontro lk i NumericUpDown.
Pierw szą rzeczą, k tó rą pow inieneś zrobić, je st d o d an ie do klasy fo rm u larza m etody, której
Ta metoda będzie zad an iem b ędzie w yświetlanie kosztów.
wywoływana przez
w szystkie inne
utworzone w klasie D odaj n a stęp u jącą m e to d ę do klasy Form l. B ędzie o n a wywoływana podczas kliknięcia k ontrolki
formularza. NumericUpDown:
To w ten sposób Dodaj tę m etodę do klasy formularza —
uaktualniana p r i v a te v o id D isp la y D in n e rP a rty C o s t() obliczy ona k° s z t przyjęcia i um ieści wynik
będzie treść w etyk iecie costLabel.
etyk iety z kosztem {
przyjęcia, jeśli decim al C ost = d in n e rP a rty .C a lc u la te C o s t(h e a lth y B o x .C h e c k e d
cokolwiek ulegnie
zmianie. c o s tL a b e l.T e x t = C o s t.T o S tr in g ( " c " ) ;
Wartość będzie wynosiła t ru e >
} j eśli na formularzu z ^ t r n t e
. c
Zm ień nazw ę e ty kiety, zaznaczone pole wyboru
która w yśw ietla koszt, Przekazanie “c“ do m etody ToString()
s powoduj e wy ś w ietlenie wartości „Opcja zdrowa".
na costLabel.
w formacie walutowym. Jeśli
w opcjach reg ionalnych i językowych
masz: b a w i o n ą Polskę, b ę ^ i e
widoczn y symbol złotego.

W ykorzystaj te ra z k o n tro lk ę NumericUpDown do ustaw ienia zm iennej N um berO fPeople, k tó rą


Funkcji obsługi utw orzyłeś w klasie D in n e rP a r ty , i wyświetl koszt n a fo rm ularzu. Kliknij d w ukrotnie kon tro lk ę
z darzeń u żyw asz ju ż NumericUpDown — ID E autom atycznie d o d a metodę obsługi zdarzenia do T w ojego kodu.
°d dłuższego czasu Je st to m e to d a , k tó ra będzie u ru ch am ia n a za każdym razem , gdy w arto ść k o n tro lk i zostanie
— kiedy dwukrotnie
klikasz przycisk, zm ieniona. U staw i o n a liczbę osób n a przyjęciu. U zupełnij jej kod, ta k ja k podaliśm y poniżej:
!DE dodaje do kodu
funkcję obsługi p r i v a t e v o id numericUpDown1_ValueChanged(
zdarzenia o nazwie
Click. Teraz ju ż o b je c t s e n d e r , EventA rgs e)
wie sz, ja k s ię takie
funkcje nazywają.
d in n erP arty .N u m b e rO fP eo p le = (in t)n u m ericU p D o w n l.V alu e;
D is p la y D in n e rP a rty C o s t(); M
M u sisz rzutować wartość numericUpDown‘l .Va|ua
} na int, ponieważ j e s t ona ty p u decim aL

Oj, oj — jest pew ien problem z tym kodem.


Czy p o tra fisz go dostrzec? Nie przejm uj się,
jeśli nie w id zisz w tej c h w ili błędu.

Z ^ dMÓCh m etod składa sie


jodynie z dwóch w ierszu kod(j
W artością przekazaną z hrmtularzci do metody
będzie fancyBox.Checked. Z o sta n 'e ona
przekazana jako logiczny parametr motody w yśw ietla całkowitą cenę I fo r m u l a ^ .

Kliknij d w ukrotnie p o le w yboru fantazyjnych dekoracji n a fo rm ularzu. U pew nij się, że m e to d a


najpierw w ykonuje C a lc u l a te C o s t O f D e c o r a t io n s ( ) , a n a stęp n ie D is p la y D in n e r P a r ty C o s t( ) .
W dalszej kolejności dw ukrotnie kliknij p o le w yboru opcji zdrow ej. Spraw dź, czy zostały wywołane
m eto d y S e tH e a lth y O p tio n ( ) z klasy D in n e r P a r ty o raz D is p la y D in n e r P a r ty C o s t( ) .

jesteś tutaj ► 241


Rozwiązanie ćwiczenia

T a k w ygląda k o d z plik u DinnerParty.cs.

Rozwiązanie Użycie sta łe j d,a Ułatwia


ćwiczenia
S T Ä - S Ä “ “¡ » i i » *■ “
pozostanie taka sam a.
p u b lic c la s s D in n e rP a r ty j
p u b lic const in t C o s tO fF o o d P e r P e rs o n = 2 5 ; Kiedy form ularz po raz pierwszy
tworzy obiekt, używa inicjalizatora
p u b lic i n t N u m b e rO fP e o p le ; do ustaw ienia NumberOfPeople.
p u b l i c d e c im a l C o s tO f B e v e r a g e s P e r P e r s o n ; Potem wywołuje SetHealthyOptionO
oraz C alculateCostOfD ecorations^
p u b l i c d e c im a l C o s tO f D e c o r a t io n s = O; do u staw ienia pozostałych pól.

p u b l i c v o id S e t H e a l t h y O p t i o n ( b o o l h e a lth y O p tio n ) j
if (h e a lth y O p tio n ) j
C o s tO f B e v e r a g e s P e r P e r s o n = S.OOM;
I e ls e j
Skorzysta/iśm y z „if (fency) z a m iast
C o s tO f B e v e r a g e s P e r P e r s o n = 2O.OOM;
wpisywania „if (fancy == tru e ) ,
I ponieważ in stru kcj a if zaw sze
spraw dza, czy warunek je s t prawdziwy.
I

p u b l i c v o id C a l c u l a t e C o s t O f D e c o r a t i o n s ( b o o l fa n c y ) j
if (fan cy )
j
C o s tO f D e c o r a t io n s = (N u m b e rO fP e o p le * 1S.OOM) + SOM;
I e ls e j
C o s tO f D e c o r a t io n s = (N u m b e rO fP e o p le * 7.SOM) + 3OM;
I
I
p u b l i c d e c im a l C a lc u la te C o s t(b o o l h e a lth y O p tio n ) j
d e c im a l t o t a l C o s t = C o s tO f D e c o r a t io n s +
( ( C o s tO f B e v e r a g e s P e r P e r s o n + C o s tO fF o o d P e r P e rs o n )
* N u m b e rO fP e o p le );
Uży/iśmy nawiasów, aby mieć pewność,
że ob/iczenia m atematyczne ze sła n ą
if (h e a lth y O p tio n ) j wykonane poprawnie.
re tu rn to ta lC o s t • .95M ;
I e ls e j
re tu rn to ta lC o s t;
Ten fragm ent wprowadza 5% rabatu na
I całkowity koszt imprezy, jeżeli wybrana
I z o stała opcja bezalkoholowa.

I
Nie m usisz dodawać instrukcji „usi'ng Sy s tem .Windows . Fo n n s; ^<fo
klasy DinnerParty, gdyż nie wyświetla ona komunikatów przy użyciu
metody M essageBox.Show() ani' ni'e korzysta z żadnych i'nnych klas
dostępnych w tej przestrzeni nazw .N T Framework.

242 Rozdział 5.
Hermetyzacja

Użyliśmy typu decimal dla cen, ponieważ jest on zaprojektowany do obliczeń pieniężnych. Po prostu upewnij się,
że zawsze dołączasz M po literale — jeśli chcesz przechować 35,26 zł, to sprawdź, czy napisałeś 35.26M.
Możesz to łatwo zapamiętać, bo M pochodzi od angielskiego Money, oznaczającego pieniądze!

p u b lic p a r t i a l c la s s F orm l : Form { Z araz po wczytaniu formularza


wy wołamy DisplayDinnerPartyCost
D in n e rP a r ty d in n e r P a r ty ;
w celu uakfualnienia pola tekstowego
p u b lic F o rm 1 () { zaw ierającego koszt.
I n itia liz e C o m p o n e n t( );
d i n n e r P a r t y = new D i n n e r P a r t y ( ) { N u m b e rO fP e o p le = 5 } ;
d in n e r P a r ty .C a lc u la te C o s tO f D e c o r a tio n s ( f a ls e ) ;
d in n e r P a rty .S e tH e a lth y O p tio n (tr u e );
Zmiany w polach wyboru formularza us tawiaj ą
D is p la y D in n e rP a rty C o s t() ; p aram etry healthyOption oraz fancy na t rue
} lub false w metodach S etH ealth i^p h o n O
i CalculateCostOfDecorations().

p r i v a t e v o id fa n c y B o x _ C h e c k e d C h a n g e d (o b je c t s e n d e r , E v e n tA rg s e ) {
d in n e r P a r ty .C a lc u la te C o s tO fD e c o r a tio n s (f a n c y B o x .C h e c k e d );
D is p la y D in n e rP a rty C o s t(); Nazwaliśmy nasze pola wyboru „healthyBox" oraz
} „fancy Box", więc pow inieneś wiedzieć, co s ię
dz ieje wewnąt rz ich procedur obsługi zdarzeń.

p r i v a t e v o id h e a lt h y B o x _ C h e c k e d C h a n g e d ( o b je c t s e n d e r , E v e n tA rg s e ) {
d in n e r P a rty .S e tH e a lth y O p tio n (h e a lth y B o x .C h e c k e d );
D is p la y D in n e rP a rty C o s t() ;
}

p r i v a t e v o id n u m e ric U p D o w n 1 _ V a lu e C h a n g e d (o b je c t s e n d e r , E v e n tA rg s e ) {
d in n e r P a r ty .N u m b e r O f P e o p le = ( in t) n u m e r ic U p D o w n l.V a lu e ;
D is p la y D in n e rP a rty C o s t(); koszty przyjęcia muszą byf powt<5mie
obliczone i wyświetlone za każdym razem,
} gdy zmieni s ię liczba g o ^ łub zaznaczen.e
w polach wyboru.
p r i v a t e v o id D i s p l a y D i n n e r P a r t y C o s t ( ) {
d e c im a l C ost = d in n e r P a rty .C a lc u la te C o s t(h e a lth y B o x .C h e c k e d );
c o s tL a b e l.T e x t = C o s t.T o S tr in g ( " c " ) ;
}
}
Formatowanie łańcuchów znaków
Widziałeś iuŁwfaki sposób ro ż ra («^wertować dowolny obiekt na łańcuch znaków, używaiac
|ego m ^ d y ToSt r ing(>. Jeże|i jako paramet r p rz e je s z "c", to wartość zostanie zamieniona^
b w tę w |oka|nei wa|ucie. Możesz t akże przekazać "f3". Zmienna zostanie wtedy zapisana w postaci
^kk tei cziY z t rzl ma miejscami p° przecinku. "0" (to jest zero) zamieni wartość na liczbę
całlkow itą 0 % na liczbę w pos,taci procent owej, natomiast "n" wyświetli ją, wstawiając separatory
clla_poszczegó|nych rzędów wie|kości. Poświęć minutę i zobacz, jaki będzie efekt zastosowania
każdego z tych parametrów w Twoim programie!

jesteś tutaj ► 243


Coś poszło wyjątkowo źle

Jazda próbna Krystyny

TO JEST ŚWIETNE! Robert j e s t jednym z u h u b ^ y c h


klientów Krystyny. W » » V "
SZACOWANIE JEST TERAZ roku zo r g a n izo w a ła je g o przyjęcie
ZDECYDOWANIE PROSTSZE. ślubne, a te ra z p la n u je dla m ego
kolejną, ważną imprezę.

Gdy (uruchamiasz
program, pole
wyboru Dekoracje
Robert (przy telefonie) : Cześć, Krystyna. Ja k id ą przygotow ania ftircłazyjne powinno
do m ojego przyjęcia? być zaznaczone,
gdyż przypisałeś
je go właściwości
Krystyna: Św ietnie. Byliśmy dzisiaj ran o p oszukać dekoracji i jestem
Checked wartość
pew na, że będziesz zadow olony z organizacji przyjęcia. true. Po wybraniu
10 uczestników
Robert: T o super. Słuchaj, w łaśnie zadzw oniła ciotka m ojej żony. kos z t przyjęcia
powinien w ynieść
O n a i jej m ąż zam ierzają do nas przyjechać n a kilka tygodni. Czy 575 zł.
m ożesz m i pow iedzieć, ja k b ę d ą w yglądały szacunkow e koszty p o
zw iększeniu liczby gości z dziesięciu do dw unastu?

Krystyna: N atu raln ie! Poczekaj m in u tk ę i wszystko ci pow iem .

n t, okna pro 9ramu zo s ta ł wykonany Zm iana liczby osób z 10


w Polsce , zatem za kwotą pojaw ił się na 12 i wciśnięcie klawisza
sy rn M „ze Gdyt}yśm y t>yli w U SA, Anglii Enter sku tk u je pokazaniem
j bądz inny m kraju, to pojawiłby się całkowitego kosztu 665 zł.
znak dolara, _funta, euro lub inny. Dzieje Hmm, wy daje s ię , że j e s t
s ię tak, poniew aż ko szt j e s t w yśw ietlany to trochę mało...
przy uży c iu (wywołania ToString(“c’‘), które
konwertuje wartość decimal na łańcuch
zawierający kwo tę pieniężną.

Krystyna: O K. W ygląda n a to , że całkow ite koszty im prezy


zw iększą się z 575 do 665 zł.

Robert: R ó żn ica tylko 90 zł? T o brzm i nap raw d ę zachęcająco!


A co się stan ie, jeśli zrezygnuję z fantazyjnych dekoracji?
Ile to będzie w tedy kosztow ało?

244 Rozdział 5.
Hermetyzacja

Planista przyjęć ^ 3

U sunięcie zaznaczenia Ilość osób


pola Dekoracje fantazyjne [12 :
redukuje k o szt zaledwie
1 1 Dekoracje fantazyjne
o 5 zt. To nie może buć
prawda! ___ 1 1 Opcja zdrowa

Koszt 660.00 zl

Krystyna: H m m , w ygląda n a to, ż e ... hm m , 660 zł.

Robert: 660 zł? M yślałem , że d ekoracje k osztują 15 zł o d osoby. Z m ieniłaś m oże


cen n ik usług czy coś? Jeżeli to je st tylko 5 zł różnicy, to zdecydow anie skłaniam y się
ku d ekoracjom fantazyjnym . A ta k swoją d rog ą to te n cennik je st jakiś dziwny.

Krystyna: W łaśnie napisaliśm y nowy p ro g ram do szacow ania kosztów. W ygląda


je d n a k n a to , że je st tu pew ien p ro b lem . Poczekaj chwilę, dodam fantazyjne dekoracje
z p o w ro tem do rachunku.

Kiedy z p o w ro te m w fą c z y sz
D e k o r a c je fa n t a z y jn e , k! f o t °
w z r a s t a 1 a ż d o 770 z \T e < c z b y
s ą p o p r o s t u n ie p r a w id ło w e -

Krystyna: R o b ercie , w ydaje mi się, że p o p ełn iłam błąd. W ygląda n a to, że koszty
z dekoracjam i fantazyjnym i podskoczyły do 770 zł. P raw dę m ów iąc, m a to większy
sens. Z aczynam m ieć je d n a k pew n e obaw y i p rzestaję u fać te m u program ow i.
Z am ierzam go o desłać do p o p raw ien ia błędów i przygotow ać kosztorys ręcznie.
Czy m ożem y w rócić do rozm ow y ju tro ?

Robert: N ie chcę płacić 770 zł tylko dlatego, żeby d odać dwie osoby do listy gości.
C ena, k tó rą p o d ałaś m i w cześniej, była znacznie bardziej przystępna. M ogę ci zapłacić
665 zł, tyle, ile pow iedziałaś m i za pierwszym razem , ale ani grosza więcej!

WYSIL __________________
SZARE KOMÓRKI
Dlaczego po każdej zmianie wprowadzonej przez Krystynę w programie pojawiają się błędne wartości?

jesteś tutaj ► 245


Tego się nie spodziewaliśmy

Każda opcja powinna być obliczana osobno Nie przejmuj


się! W tym
P om im o tego, że upew niliśm y się co do sposobu obliczania kosztów przypadku to nie
przez K rystynę, nie przew idzieliśm y sytuacji, w której ludzie decydują była Twoja wina.
się n a zm ianę tylko jednej opcji form ularza.
Wprowadziliśmy jeden mały, nieprzyjemny
K iedy urucham iasz p ro g ram , fo rm u larz m a ustaw io n ą liczbę gości błąd do kodu, by pokazać Ci, jak łatwo
na 5, n ato m iast dekoracje fantazyjne przyjm ują w arto ść t r u e . O pcja 0 problemy w przypadku nieprawidłowego
zdrow a nie je st zaznaczona, a obliczona cen a przyjęcia wynosi 350 zł. użycia pól obiektów przez inne obiekty...
Początkow e koszty wyliczane są w sposób następujący: 1jak trudno dostrzec taki błąd.

5 osób
2 0 z ł od osoby za napoje C ałkow ity ko szt napojów = 100 z ł
25 z ł od osoby za jedzenie - C ałkow ity ko szt jedzenia = 125 z ł
15 z ł od osoby za dekoracje C ałkow ity ko szt dekoracji = 125 z ł D° tej poru
plus opłata 50 z ł w szystko gm .

100 z ł + 125 z ł + 125 z ł = 350 z ł


J
K iedy zm ienisz liczbę gości, aplikacja p o w inna obliczyć szacunkow y
koszt m niej więcej w te n sam sposób, ale tego nie robi:

10 osób

2 0 z ł od osoby za napoje C ałkow ity ko szt napojów = 2 0 0 z ł


25 z ł od osoby za jedzenie C ałkow ity ko szt jedzenia = 250 z ł
15 z ł od osoby za dekoracje C ałkow ity ko szt dekoracji = 2 0 0 z ł
plus opłata 50 z ł

i
2 0 0 z ł + 250 z ł + 2 0 0 z ł = 650 z ł

To j e s t kwota/
catkowita, którą
Usuń znacznik z pola wyboru powinniśmy Program dodaje s ta r ą cenę dekoracji' do nowego
Dekoracje fantazyjne, otrzymać. A le nie kosztu jedzenia i napojów.
a następnie ponownie je zaznacz. otrzym aliśm y... Liczy on tak: 2 0 0 zł + 2 5 0 zł + 125 zł = 575 z ł.
Spow oduje to aktualizację w artości p o la / / S ta re dekoracje.
C o s tO fD e c o ra tio n s o b ie k tu D in n e rP a r ty Nowy k o szt jedzenia i napojów.
i w yśw ietlenie praw idłow ej ceny przyjęcia
wynoszącej 650 zł.

246 Rozdział 5.
Hermetyzacja

P r o b le m z b lis k a

Przyjrzyj się dokładnie m eto d zie, k tó ra obsługuje zm iany w artości w k o ntrolce num eri cUpDownl.
W staw ia o n a w artość p o la V alu e do zm iennej N um berO fPeople, a n a stęp n ie wywołuje m etodę
D i s p l a y D i n n e r P a r t y C o s t( ) . W tedy m o żn a już tylko p olegać n a niej, w ierząc, że zajm ie się
pow tórnym przeliczeniem wszystkich kosztów.
Ten w iersz zmienia pole
p r i v a t e v o id nu m ericU p D o w n 1 _ V alu eC h an g ed N u m b e r O f P e o p l e w tej

(o b je c t se n d e r, E v e n tA rg s e ) {
\fy z fo rm u /a rz a .
d in n e r P a r ty .N u m b e r O f P e o p le (in t)n u m e ric U p D o w n l.V a l ui e ;

D is p la y D in n e rP a rty C o s t() ;

Ta metoda w ynotuje CalculateCost(), ale nie uruchamia


metody CalculateCostOfDecorations().

Jeśli w ięc dokonasz zm ian w p o lu N um berO fPeople, ta m eto d a nigdy nie zostanie w ykonana:

p u b l i c v o id C a l c u l a t e C o s t O f D e c o r a t i o n s ( b o o lfa n c y ) {
Ta z m ienna ustaw iana j e s t na 125 podczas
if (fa n c y ) { pierw sz ego wywotania i dopóki nie zostanie
uruchomiona j e szc ze raz, nie zm ieni się.
C o s tO f D e c o r a t io n s (N u m b e rO fP e o p le * 15.00M ) + 50M;

} e ls e { To dlatego w artość poprawi


się , kiedy powtórnie włączysz
C o s tO f D e c o r a t io n s (N u m b e rO fP e o p le * 7.50M ) + 30M; dekoracje. Kliknięcie_pola
wyboru spowoduje, że metoda
CalculateCostOfDecorations()
} z o s ta nie wywołana ponownie.

}
N ie jest to jed n ak jedyne miejsce pro g ram u , w którym pojaw iają się problem y. O ba p o la w yboru działają niespójnie: jed n o
z nich wywołuje m eto d ę m odyfikującą stan obiektu, nato m iast w drugim stan jest przekazyw any jako argum ent wywołania
m etody. Każdy program ista próbujący zrozum ieć działanie tego p ro g ram u uzna, że jest to totalnie nieintuicyjne!

ZACZEKAJ! ZAŁOŻYŁAM , ŻE KRYSTYNA


Czy m iałeś ZAWSZE BĘDZIE USTAWIAŁA WSZYSTKIE
trochę problemów
z określeniem, jak TRZY OPCJE JEDNOCZEŚNIE!
działa ten przykład?
Nie bądz dla siebie
z b y t surowy, je ś li tak ...a czasami
Ludzie nie zawsze będą używać Twoich klas tym i „ludZmi"
w łaśnie było. Przyczyną
mógł być fakt, że w taki sposób, jak się tego spodziewasz... używającymi
poprosiliśm y Cię Twoich klas
o napisanie programu, N a szczęście C # daje Ci p o tę ż n e n arzęd zie, k tó re pozw ala mieć b ęd ziesz Ty
w którym w ystępow ały pew ność, że p ro g ram będzie działał praw idłow o — n aw et wtedy, sam! Może się
u sterki koncepcyjne! zdarzyć, że
gdy ludzie b ę d ą robili tak ie rzeczy, o jakich naw et nie śniłeś.
Pod koniec rozdziału dziś p is ze sz
n a p iszesz znacznie O k reśla się je term in em hermetyzacja i je st o n o b ard zo p o m o cn ą klasę, z której
lepszą i prostszą tech n ik ą w p racy z obiektam i. jutro b ęd ziesz
w ersję tego programu. korzystał.

jesteś tutaj ► 247


Chroń swoje obiekty

Bardzo łatwo przez przypadek


źle skorzystać z obiektów
K rystyna sprow adziła n a siebie kłopoty, poniew aż jej fo rm u larz zignorow ał
w ygodną m eto d ę C a lc u l a te C o s t O f D e c o r a t io n s ( ) i skorzystał
bezpośrednio z p ó l klasy D in n e rP a r ty . P om im o tego, że T w oja klasa
D in n e r P a r ty działała p o p raw n ie, form ularz wywołał ją w nieoczekiw any
sp o só b ... i to spow odow ało problem y.

J W JAKI SPOSÓB KLASA DINNERPARTY M IAŁA BYĆ W YW O ŁYW AN A.


K lasa D in n e r P a r ty u d o stę p n iła form ularzow i w sp an iałą m e to d ę do obliczania kosztów
dekoracji. W szystko, co n ależało zrobić, to ustaw ić liczbę osób i wywołać m eto d ę
C a l c u l a te C o s t O f D e c o r a t io n s ( ) , a n a stęp n ie C a l c u l a t e C o s t ( ) , k tó ra zwróciłaby
praw idłow ą cenę.
N u m b e rO fP e o p le = 1 0 ;
r a i r.ul a te C o s t O f D e c o r a t i o n s ( t r u e ) ;

& k t P '*
C alcul a te C o s t O zw raca 650 zł

W JAKI SPOSÓB KLASA DINNERPARTY BYŁA W YW O ŁYW AN A.


F o rm u larz ustaw ił liczbę osób, ale wywołał tylko m e to d ę C a l c u l a t e C o s t ( ) ,
bez w cześniejszego doliczenia ceny dekoracji. W ten sposób p o m in ięto fragm ent
obliczeń, a K rystyna dolała oliwy do ognia, przekazując R o b erto w i niepraw idłow ą
wycenę.

N u m b e r O f P e o p l e j^ l O j_________

¿5
°A . mi Jr
'& k t P '*

C a l c u l a t e C o s t ( ) zw raca 575 zł Pomimo tego, że formularz ;


u sta w ił właściw ie klasy, me
CalculateCos t() zwróciła jed
w artość— i nie było możliw.
aby Krys t yna dowiedziała s i
o błędnej kwocie.

248 Rozdział 5.
Hermetyzacja

Hermetyzacja oznacza,
Wyciągnij korzyści
że niektóre dane w klasie są prywatne z e swojego lenistwa
— je ś li pom iniesz
.p riva te '1 lub „public",
Istnieje b ardzo p ro sta m eto d a unik n ięcia pro b lem ó w teg o typu: upew nij się, c # uzna, że Twoje
pole j e s t domyślnie
że istnieje tylko je d e n sposób n a użycie Twojej klasy. N a szczęście i w tym prywatne.
przypad ku C # ułatw ia całą spraw ę, um ożliw iając tw orzenie p ó l prywatnych .
D o tej p o ry w idziałeś tylko p o la publiczne. Jeżeli po siad asz o b iek t z takim i polam i,
to każdy inny o b iek t m oże je odczytywać lub zm ieniać. G dy uczynisz te p o la
pryw atnym i, dostęp do nich będzie możliwy tylko z wewnątrz danego obiektu
(lub z innego o b iek tu tej samej klasy).
Oprócz tego, także sta tyczn e metody klasy
mają dostęp do prywatnych pól tej sam ej klasy. Jeśli chcesz, aby Twoje pole było prywatne, m u sisz
uzyć s f°wa kiuczowego p rivate do jego d e k la r a j

public class DinnerParty { ¡ n s j ! * p D ekaŻens z C*" że je ś li posiadasZ '


'n Z ^ e - ję kla s y ? mnerParty, ^ pole numberOfPeople
private int numberOfPeople; moze być od czytyw ane lub ustaw iane tylko przez te

public void SetPartyOptions(int people, bool fancy) {


numberOfPeople = people;
CalculateCostOfDecorations(fancy);
}
z dobrych r o z w i q g m4tody

public int GetNumberOfPeople() {


ret urn numberOfPeople; c jfc u lr f^ ¡cTsię Mczba

}
ten denerwujący btąd.

Poprzez uczynienie pola przechowującego liczbę gości Hermetyczny


na przyjęciu prywatnym udostępniliśmy formularzowi
tylko jeden sposób na ustawienie tej liczby w klasie — nieprzenikalny, szczelnie zamknięty,
DinnerParty — dzięki temu mamy pewność, że koszty nieptzepuszczający powietrza.
dekoracji obliczane są w sposób poprawny. Kiedy N urkowie przebywajq w herm etycznej
decydujesz, że pewne dane są prywatne, a następnie kapsule, ¡ecz mogą z niej wychodzić
piszesz kod do korzystania z takich danych, to taką p rzez specjalną śluzę.
czynność nazywamy hermetyzacją.

jesteś tutaj ► 249


Szpieg przeciwko szpiegowi

Użyj hermetyzacji wcelu kontroli dostępu


do metod i pól Twojej klasy
K iedy uczynisz wszystkie p o la i m eto d y publicznym i, k ażd a in n a klasa będzie m iała
do nich dostęp. W szystko, co klasa ro b i i w ie, stan ie się o tw artą księgą dla każdej
innej klasy w Tw oim p ro g ra m ie ... i p rzek o n ałeś się, że takie p o stęp o w an ie m oże SecretA gent
doprow adzić do niespodziew anego działania p ro g ram u . H erm ety zacja pozw ala
Alias
Ci decydow ać, co jest w spółdzielone, a co m usisz uczynić pryw atnym , dostępnym RealName
w ew nątrz danej klasy. Z obaczm y, ja k to działa. Password

Superszpieg H e rb Jo n es je st w ojow nikiem o życie i w olność oraz AgentGreeting()


poszukiw aczem szczęścia działającym w Z S R R . Jego o b iek t ciaAgent
je st instancją klasy SecretAgent.

Prawdziwe imię i nazwisko: Herb Jones


Pseudonim operacyjny: Dash Martin
Hasło: wrona la ta o północy
Enem yA gent
Borscht
Vodka

^ A g en t Jo n es m a p lan , który m a m u p o m ó c w unik n ięciu w rogich ContactComrades()


agentów K G B . D o d ał o n m eto d ę A g en tG re etin g() , któ ra OverthrowCapitalists()
jak o p a ra m e tr przyjm uje hasło. Jeśli hasło, k tó re otrzym a, jest
niepraw idłow e, ujaw nia tylko swój p seu d o n im , D ash M artin.

3 W ydaje się, że ten niegłupi sposób pozw ala chronić tożsam ość agenta,
praw da? D o p ó k i o b iek t wywołujący m eto d y nie p o d a właściwego hasła,
jego nazw isko jest bezpieczne.

A g e n t KG B u ż y w a
O b ie k t c i a A g e n t j ^ t i n s t a n c j ą . w p o z d r o w ie n iu
zewnątrz ) n i e p r a w id ło w e g o h a s t a .

"Dash M a rtin
c/a A 9
\
KGB o trzym uje tylko
Ps e u donim agenta CIA.
Doskonałe, nieprawdaż?

250 Rozdział 5.
Hermetyzacja

Ale czy jego prawdziwa tożsamość


jest NAPRAWDĘ chroniona?
D opó ki K G B nie zna h asła ag en ta C IA , jego praw dziw e im ię i nazwisko
je st bezpieczne. Co je d n a k z dek laracją p o la RealName:

Ustawienie zmtennej na
public oznacza, że m°żna
uzyskać do niej -
public string RealName;
a naw et zmienić j ą
z zewnątrz klasy.

ZOSTAWIŁ SWOJE POLE


ZADEKLAROWANE JAKO
PUBLIC... DLACZEGO MIAŁBYM niej dostęp o n uzy skać do
z Zmienić ń
SAM SOBIE STWARZAĆ
PROBLEMY, ODGADUJĄC JEGO
HASŁO? MOGĘ OTRZYMAĆ JEGO
NAZWISKO BEZPOŚREDNIO! strin g name = cia^en^ReaUlame,

c / a A 9 e'

A g en t Jones m oże użyć p ó l z atrybutem priv a te do zachow ania swojej Obiekt kgbAgent nie
może uzyskać dostępu do
tożsam ości w tajem nicy p rzed ag en tam i obcych służb specjalnych. P o tym jak prywatnych pól ciaAgent,
zadeklaruje on swoje p o le jak o pryw atne, jedynym sposobem n a p o b ra n ie jego p°nieważ s ą to instancje
dwóch różnych klas.
danych będzie wywołanie metod, które m ają dostęp do obszarów prywatnych
klasy. Plany ag en ta K G B zostały w ięc pokrzyżow ane! Ustawienie pól i metod jako
prywatnych pozwala mieć
Po pro stu zamień public pewność, że żaden kod nie
na private i spokój. Twoje będzie modyfikował używanych
pola s ą teraz ukryte przed private string realName; przez Ciebie wartości, kiedy
ś w iatem. s i ę tego nie spodziewasz.

Redziesz także chciaf s i ę upewnić czy


i WYSIL
p o t przechowujące hasto j e s t prywntn®
W przeciwnym razie agent obcych sfuzb
SZARE KOMÓRKI
może s i ę do mego dobrać. Jak sądzisz, dlaczego nazwa pola publicznego zaczynała się od dużej litery R,
natomiast po zmianie pola na prywatne zmieniliśmy ją na małe r?

jesteś tutaj ► 251


Trzymanie w tajem nicy

Dostęp do prywatnych pól i metod


można uzyskać tylko z wnętrza klasy
Jest tylko je d e n sposób, w jaki o b iek t m oże się dostać do danych przechow yw anych
w pryw atnych polach innych obiektów : użycie p ó l publicznych i m eto d , k tó re zw racają Teraz, kiedy pola s ą prywatne,
dane. O ile K G B i M I5 m uszą używać m etody A g e n tG r e e tin g ( ) , to zaprzyjaźnieni istnieje ty/ko jeden sposob
pozwalający agentowi MI5
szpiedzy m ogą w idzieć wszystko — każd a klasa m oże widzieć prywatne pola w innych poznać prawdziwą tożsam ość
jej instancjach . agenta CIA.

m iSA gent j e s t
instancją klasy
B ritishAgent,
także. nia
więc także nie AgentGreetingC'wrona 1 a t a o p ółn ocy")
ma dostępu do
prywatnych pól
ciaAgent.

r
Tylko inny obiekt
ciaA gent m oże je
^/5 A < ł
widzieć.

i Nie .istnieją.
głupie pytania

P:: Dobrze. Mogę więc dostać się do zmiennych skutkiem generować kolejne liczby losowe. Podczas
tworzenia instancji klasy Random nie będziesz widział Jedyny spo­
prywatnych poprzez metody publiczne. Co się
tej tablicy. To dlatego, że jej po prostu nie potrzebujesz
jednak stanie, jeżeli klasa z polami prywatnymi
— gdybyś miał do niej dostęp, mógłbyś powstawiać
sób, w jaki
nie umożliwi mi pobrania tych danych, a mój
obiekt potrzebuje ich użyć? tam inne wartości. Skutkowałoby to generowaniem o b ie k t m oże
O : Nie masz wtedy dostępu do tych danych spoza
liczb, które losowe by nie były. Ziarna są zatem uzyskać
całkowicie przed Tobą ukryte.
obiektu. Kiedy piszesz klasę, zawsze powinieneś dostęp do
się upewnić, czy dajesz innym obiektom możliwość
pobrania danych, których potrzebują. Pola prywatne
P : Hej, właśnie zauważyłem, że wszystkie danych prze­
procedury obsługi zdarzeń, których używałem,
są bardzo ważnym elementem hermetyzacji, ale są zostały zadeklarowane z użyciem słowa chowywanych
tylko częścią historii. Pisanie klasy z hermetycznymi kluczowego p riv a te . Czy one też są prywatne?
danymi oznacza też praktyczny, łatwy dostęp do
w prywatnych
danych z poziomu innych obiektów, bez udostępniania O : Formularze C# są napisane w taki sposób, polach innego
ważnych informacji, których Twoja klasa potrzebuje. aby tylko ich kontrolki mogły wywoływać zdarzenia.
obiektu,
Kiedy umieścisz słowo kluczowe p r iv a t e na
P Dlaczego miałbym tworzyć pole, do
: początku metody, będzie ona mogła być używana polega
którego inna klasa nie będzie mieć dostępu?
tylko wewnątrz klasy. Gdy IDE dodaje procedurę
na użyciu
O : Czasami klasa musi przechowywać informacje
obsługi zdarzenia do programu, deklaruje ją jako
prywatną. Zabezpiecza w ten sposób metody przed publicznych
niezbędne do prawidłowego działania, ale takie,
których inne obiekty w ogóle nie powinny widzieć. niepowołanym dostępem z poziomu innych obiektów. m etod,
Może jakiś przykład. Kiedy komputery generują liczby Nie ma żadnej reguły mówiącej, że procedury obsługi
losowe, używają do tego specjalnych wartości zwanych zdarzeń muszą być prywatne. W zasadzie możesz to
k tó re
ziarnami. Nie musisz wiedzieć, jak to wszystko działa, sprawdzić samodzielnie — kliknij dwukrotnie przycisk, zwracają dane.
ale każda instancja klasy Random zawiera tablicę a następnie zmień deklarację procedury na p u b lic .
kilkudziesięciu liczb, której obiekt używa, aby z lepszym Kod w dalszym ciągu będzie się kompilował i działał.

252 Rozdział 5.
Hermetyzacja
m- Zaostrz ołówek
r v Daną mamy klasę z kilkoma polami prywatnymi. Zakreśl te instrukcje z poniższej listy,
które nie skompilują się , jeśli będą uruchamiane spoza klasy przy użyciu instancji obiektu
nazwanego mySuperChef.

p u b lic class SuperChef


{
p ub lic s trin g cookieRecipe;
p riva te s trin g se cre tlng re die nt;
p riva te const in t loyalCustomerOrderAmount = 60;
p ub lic in t Temperature;
p riva te s trin g ingredientS upplier;

p ub lic s trin g GetRecipe(int orderAmount)


{
i f (orderAmount >= loyalCustomerOrderAmount)
{
return cookieRecipe + " " + se cre tlng re die nt;
}
else
{
return cookieRecipe;
}
}
}

1. s trin g ovenTemp = mySuperChef.Temperature;

2. s trin g su p p lie r = mySuperChef.ingredientSupplier;

3. in t loyalCustomerOrderAmount = 54;

4. mySuperChef.secretlngredient = "kardamon";

5. mySuperChef.cookieRecipe = "Weź 3 ja jk a , 2,5 szklanki mąki, 1 łyżeczkę s o li,


1 łyżeczkę w a n ilii i 1,5 szklanki cukru. Wszystko wymieszaj. Piecz przez 10 minut
w temperaturze 200 sto pn i. Smacznego!";

6. s trin g recipe = mySuperChef.GetRecipe(56);

7. Ja k a będzie w arto ść zm iennej re cip e p o u ru ch o m ien iu wszystkich w ierszy kodu, k tó re się skom pilują?

jesteś tutaj ► 253


Zostawmy pole do popisu dla wyobraźni

Zaostrz ołówek ---------------


Daną mamy klasę z kilkoma polami prywatnymi. Zakreśl te instrukcje
Rozwiązanie z poniższej listy, które nie skompilują się , jeśli będą uruchamiane spoza
klasy przy użyciu instancji obiektu nazwanego mySuperChef.

p u b l i c c l a s s SuperChef
{
p u b l i c s t r i n g co o k ieR ec ip e;
p riv ate strin g secretln g red ien t;
p r i v a t e c o n s t i n t loyalCustom erOrderAmount = 60;
p u b l i c i n t Tem perature;
p riv ate strin g in g re d ie n tS u p p lie r;

p u b l i c s t r i n g G e t R e c i p e ( i n t orderAmount)
{
i f (orderAmount >= loyalCustom erOrderAmount)
{
r e t u r n co ok ieR ecip e + " " + s e c r e t l n g r e d i e n t
}
else
Jedynym sposobem na pobranie sekretnego
{ sktadnika j e s t zam ówienie ^ r o m n e j ^ d z ^ .
r e t u r n co o k ie R ecip e; ciasteczek. Nie m °żesz
s ie do tego pola z z e w n ą te .
}
}

Numer i nie skom piluje sie,


1 . s t r i n g ovenTemp = mySuperChef.Temperature poniewaz nie m ozesz przypisać
wartości typu in t do string.

2 . s t r i n g s u p p l i e r = m y S u p e r C h e f . in g r e d ie n tS u p p l ie r ;
Numer 2 i numer 4 nie
3. i n t loyalCustomerOrderAmount = 54;

V
skom pilują s i ę ponieważ
ingredientSu pplier
4. m y S u p e r C h e f . s e c r e t I n g r e d i e n t "kardamon"; i secretIngredient
s ą prywatn e.

5. m ySuperC hef.cookieR ecipe = "Weź 3 j a j k a , 2 ,5 s z k l a n k i mąki, 1 ły ż e c z k ę s o l i ,


1 ły ż e c z k ę w a n i l i i i 1,5 s z k l a n k i c u k ru . Wszystko w ym ieszaj. P iecz p rz e z 10 minut
w t e m p e r a t u r z e 200 s t o p n i . Smacznego!"; Chociaż utw orzyłeś zm ienną lokdną o nazw ie
loyalCustomerAmount i przypisałeś j ej 54,^
to nie zm ieniłeś wartości p ola M ^ t u , któm w ciąż
6 . s t r i n g r e c i p e = m y S up erC hef.G etR ecip e(56 ); w ^ 60; dlatego te ż w wynikach zwróconych przez
m etode G etRecipe() nie pojawi s ie tajemny s kładnik.
7. Ja k a będzie w arto ść zm iennej r e c i p e p o u ru ch o m ien iu wszystkich w ierszy kodu, k tó re się skom pilują?

W eź 3 jajka, 2 ,5 szklanki mąki, 1 ły żeczk ę soli, 1 ły żec zk ę wanilii i 1,5 szklanki cukru. W szy stk o wymieszaj. i
Piecz przez 1C> m inut w tem p eratu rze 2 0 0 stopni. Smacznego!

254 Rozdział 5.
Hermetyzacja
COŚ TU JEST NIE TAK. JEŚLI ZMIENIĘ JAKIEŚ POLE NA
PRYWATNE, TO JEDYNYM EFEKTEM BĘDZIE BRAK MOŻLIWOŚCI
SKOMPILOWANIA SIĘ INNYCH KLAS PROGRAMU, KTÓRE BĘDĄ
CHCIAŁY SKORZYSTAĆ Z TEGO POLA. JEŚLI JEDNAK PONOWNIE
ZMIENIĘ JE Z PRYWATNEGO N A PUBLICZNE, TO PROGRAM ZNOW U
BĘDZIE SIĘ KOMPILOWAĆ. CZYLI DODANIE MODYFIKATORA PRIVATE
PSUJE PROGRAM. DLACZEGO ZATEM MIAŁABYM CHCIEĆ Z NIEGO
W OGÓLE KORZYSTAĆ?

Ponieważ czasami będziesz chciała ukryć pewne


informacje w klasie przed całą resztą programu.
W iele osób podczas pierw szego sp o tk an ia z h erm etyzacją uw aża
ją za nieco dziw ną, gdyż pom ysł ukryw ania pól, właściwości
i m e to d nie je st intuicyjny. M ożna je d n a k w skazać wiele
pow odów , dla których w arto przem yśleć, jakie inform acje
przechow yw ane w klasie pow inny być u d o stę p n io n e całej
reszcie pro g ram u .

Hermetyzacja sprawia, że klasy s ą ...


* Łatwe w użyciu
Już wiesz, że klasy używ ają p ó l do przechow yw ania inform acji o swoim stanie. Hermetyzacja
W iele z nich aktualizuje te p o la przy w ykorzystaniu m e to d — takich, których polega na
nigdy nie b ęd ą wywoływały żad n e in n e klasy. B ard zo często zd arza się, że klasa
p o siad a p ola, m eto d y i właściwości, z których inne klasy nigdy nie b ę d ą korzystać. ukrywaniu
Jeśli takie składow e zadeklarujem y jak o pryw atne, to później, podczas korzystania informacji
z klasy w innych frag m en tach k o d u p ro g ram u , nie b ę d ą o n e w yświetlane
w o kienku IntelliSense.
w klasie przed
innymi klasami.
* Łatwe do utrzymania
Czy pam iętasz b łąd w p ro g ram ie dla Krystyny? Był on spow odow any tym,
Stosowanie
że form u larz odw oływ ał się do p o la bezp o średn io , a nie przy użyciu m etody herm etyzacji
służącej do ustaw iania jego w artości. G dyby to p o le zostało zad ek laro w an e jako
pozwala
pryw atne, moglibyśm y teg o uniknąć.
unikać błędów.
* Elastyczne
N a pew no w iele razy będziesz w racał do napisanych w cześniej program ów ,
by dodać do nich jakieś now e możliwości. Jeśli zadbasz o właściwą herm etyzację
klas, to w przyszłości nie będziesz m iał żadnych p ro b lem ó w z ich stosow aniem .

WYSIL ____
CO SZARE KOMÓRKI
Dlaczego tworzenie niehermetycznych klas może w przyszłości
utrudnić modyfikowanie programu?

jesteś tutaj ► 255


Bałagan u Maćka
Geocaching to sp o r t polegający
na wy korzystaniu odbiorników GPS
Program nawigacyjny Maćka do poszukiwania pojemników, które
mogą być ukrywane w dowolnym

mógłby lepiej wykorzystywać hermetyzację miejscu na świecie. Maciek j e s t bardzo


zainteresowany technologią GPS, więc
łatwo zrozumieć, dlaczego tak bardzo
polubił geocaching.
Czy pam iętasz p ro g ram nawigacyjny M aćka, który przedstaw iliśm y w rozdziale 3.?
M aciek przyłączył się do grupy m iłośników geocachingu i uw aża, że jego pro g ram
do nawigacji m oże m u się te ra z przydać. Je d n a k już o d jakiegoś czasu n ad nim nie
' ECH™ NIE MOGĘ SOBIE
pracow ał i te ra z m a z nim pew n e problem y. P ro g ram te n p o siad a klasę Route
PRZYPOMNIEĆ, CZY MIAŁEM
przechow ującą inform ację o jed n ej trasie pom iędzy dw om a p u n k tam i. T e ra z /
UŻYW AĆ POLA STARTPOINT,
pojaw ia się w nim cała m asa błędów , gdyż M aciek nie p am ięta, ja k tej klasy
CZY METODY S E T S T A R T P O IN T ()
należy używać! O to co się stało, gdy spróbow ał zm odyfikow ać k o d V
ALE PRZECIEŻ TO WSZYSTKO
sw ojego naw igatora:
WCZEŚNIEJ PIĘKNIE DZIAŁAŁO!
# M aciek przypisał właściwości S t a r t P o i n t w spółrzędne G PS sw ojego dom u,
a n astęp n ie właściwości E n d P o in t w spółrzędne sw ojego b iu ra i spraw dził w artość
właściwości L e n g th . O kazało się, że długość trasy wynosi 15,3 km . K iedy je d n ak
wywołał m eto d ę G e tR o u te L e n g th () , zw róciła o n a w artość 0.
# M aciek użył m eto d y S e t S t a r t P o i n t ( ) , by ustaw ić p u n k t początkow y n a w spółrzędne
sw ojego dom u, o raz m eto d y S e tE n d P o in t( ) , by ustaw ić p u n k t końcow y n a w spółrzędne
biura. M eto d a G e tR o u te L e n g th () zw róciła w arto ść 9,51, n a to m ia st właściwość Length
— w artość 5,91.
# K iedy spróbow ał ustaw ić w spółrzędne p u n k tu początkow ego przy użyciu właściwości
S t a r t P o i n t , a p u n k tu końcow ego przy użyciu m eto d y S e tE n d P o in t( ) , to zarów no
właściwość L e n g th , ja k i m eto d a G e tR o u te L e n g th () zwróciły w artość 0.
# K iedy n ato m iast spróbow ał określić w spółrzędne p u n k tu początkow ego, korzystając z m etody
S e t S t a r t P o i n t ( ) , a p u n k tu końcow ego przy użyciu właściwości E n d P o in t , to właściwość
L e ng th m iała w artość 0, a w ywołanie m eto d y G e tR o u te L e n g th () spow odow ało aw arię
p ro g ram u w yw ołaną jakim ś błęd em m ającym coś w spólnego z dzieleniem przez zero.

ołówek ____________________________________________________
Oto klasa Route z programu nawigacyjnego Maćka. Gdybyś sam miał sobie ułatwić jej
stosowanie, to które z jej właściwości lub metod zadeklarowałbyś jako prywatne?

R o u te
StartPoint ...............................................................................................................................................................
EndPoint
Length

GetRouteLength()
GetStartPoint()
GetEndPoint() ...............................................................................................................................................................
SetStartPoint()
SetEndPoint() ...............................................................................................................................................................
ChangeStartPoint()
ChangeEndPoint()

Ten problem można rozwiązać na wie/e różnych sposobów, a wszystkie z nich mogą być prawidłowe!
Z ap isz tu ten, który według Ciebie j e s t najlepszy.

256 Rozdział 5.
Hermetyzacja

Wyobraź sobie obiekt jako czarną skrzynkę Kiedy powracasz do kodu,


którym nie zajmowałeś się
C zasam i m ożesz usłyszeć, że p rogram iści o k reślają o b iek t jako czarn ą od dłuższego czasu, to łatwo
skrzynkę. I faktycznie je st to dobry sposób w yobrażania sobie obiektów . możesz zapomnieć, jak miałeś
G dy wywołujem y jakąś m e to d ę o b iek tu , ta k n ap raw d ę nie in teresu je nas go używać. I właśnie w takich
sposób jej działania — a przynajm niej nie w tedy. O bchodzi nas tylko to, sytuacjach hermetyzacja może
że m eto d a p o b iera inform acje, k tó re przek azu jem y w jej w ywołaniu, i robi Ci znacznie ułatwić życie!
z nim i, co trzeba.

W IEM , ŻE MÓJ OBIEKT ROUTE DZIAŁA


DOBRZE! TERAZ INTERESUJE M NIE W trzecim rozdziale książki Maciek
skoncentrował s ię jed yn ie na napisaniu
TYLKO, JAK M AM PRZYPOMNIEĆ SOBIE, swojego programu nawigacyjn e go. ^W tedy
JAK MOGĘ Z NIEGO SKORZYSTAĆ w iedział i pam iętał, ja k działa obiekt
W MOIM PROGRAMIE DO GEOCACHINGU. Route. A le to było ju ż ja k iś czas te m u .

Jeśli już dziś I


Od tam tego czasu jego nawigator d ziałał
zadbasz i Maciek korzystał z niego bez problemów.
M aciek wie, że działa on na tyle
o odpowiednią dobrze, by z powodzeniem zastosow ać
go w nowym programie do geocachingu,
a zatem teraz chciałby pono\m ie u ży ć
hermetyzację klasy Route.
swoich klas, to
w przyszłości I
Gdyby tylko Maciek pomyślał
o harmatyzacji sw ojej klasy Route
będziesz w momencie je j tworzenia! Gdyby
to zrobił, to je j ponowne u życie nie
miał znacznie przyprawiałoby go tera z o ból głowy!

mniej prob­
lemów z ich
używaniem.
Obecnie Maciek chce wyobrazić sobie
Punkt swój obiekt Route jako czarną skrzynkę.
początkow y Chciałby przekazać do niego współrzędne
i odczytać długość trasy. Nie chce
zastanaw iać się , w jaki sposób obiekt
ją oblicza. przynajmniej na razie.

Długość trasy

Punkt końcowy

jesteś tutaj ► 257


Dobre pomysły na dobrą hermetyzację

A ZATEM HERMETYCZNA KLASA


ROBI DOKŁADNIE TO SAMO
CO KLASA, KTÓRA NIE JEST
HERMETYCZNA!

Dokładnie! Różnica pomiędzy nimi polega na tym,


że klasa hermetyczna została stworzona w sposób,
któ ry pomaga uchronić się przed błędami i ułatw ia jej
ponowne wykorzystanie.
W b ard zo łatwy sposób m ożna zm ienić klasę herm etyczną n a taką,
k tó ra herm ety czn a nie będzie: w ystarczy wykorzystać narzędzia
d o stęp n e w każdym edytorze i zam ienić słow a kluczow e p riv a te
n a p u b lic .

Słowo p riv a te m a je d n ą śm ieszną cechę: zazwyczaj m o żn a wziąć


dow olny p ro g ram i korzystając z m echanizm u zastępow ania,
zm ienić je n a p u b lic , a k o d d a się skom pilow ać i będzie działać
w d okładnie taki sam sposób ja k w cześniej. T o w łaśnie z tego
po w odu niektórzy p rogram iści m ają p ro b lem y ze zrozum ieniem
herm etyzacji.

W szystko, czego się nauczyłeś do tej pory, koncen tro w ało się
n a zapew nieniu tego, by p ro g ram coś robił — wykonywał
pew ne działania. W herm etyzacji chodzi je d n a k o coś innego.
N ie zm ienia o n a działania p ro g ram u . B ardziej przypom ina
„szachow ą n a tu rę p ro g ram o w an ia” — p o p rzez ukrycie
pew nych inform acji posiadanych przez klasy ju ż n a e tap ie ich
pro jek to w an ia i tw o rzen ia jesteśm y w stan ie określić strateg ię ich
późniejszego w ykorzystywania. Im ta strateg ia będzie lepsza, tym
bardziej elastyczne i łatw e w pielęgnacji b ęd ą nasze pro g ram y
i tym w iększej liczby błędów będziem y m ogli uniknąć.

I, podobnie ja k w szachach, istn ieje niemal


nieograniczona liczba możliwy ch s tra teg ii
herm etyzacji klas!

258 Rozdział 5.
Hermetyzacja

Kilka sugestii dotyczących hermetyzacji

★ Pomyśl, w którym miejscu pola klasy mogą być


nieprawidłowo użyte.
Co może pójść nie tak, jeśli pola nie zostaną właściwie ustawione?

★ Czy wszystko w Twojej klasie jest publiczne?


Jeżeli wszystkie metody i pola w Twojej klasie są publiczne, to znak,
że prawdopodobnie powinieneś pomyśleć przez chwilę nad hermetyzacją.

★ Które pola wymagają pewnego przetwarzania lub obliczeń


podczas ustawiania?
To są pierwsi kandydaci do hermetyzacji. Jeśli ktoś napisze metodę, która
zmieni wartość jednego z tych elementów, może to przeszkodzić w dalszej
pracy programu.

f KOSZTY DEKORACJI MUSZĄ Z O S T A c l


( OBLICZONE W PIERWSZEJ K O LE JN O Ś C I.'"N o
GDY TA WARTOŚĆ JEST JUŻ ZN A N A , \
i MOŻESZ ZW YCZAJNIE DODAĆ JĄ )
l DO CENY n a p o jó w I je d z e n ia , J
OTRZYMUJĄC W TEN SPOSÓB
^C A ŁK O W IT E KOSZTY PR ZYJĘC IA./

★ Powinieneś tworzyć pola i metody publiczne tylko wtedy,


gdy musisz.
Jeżeli nie masz powodu do deklarowania czegoś jako dostępnego dla
wszystkich, nie rób tego. Możesz sobie naprawdę wszystko pomieszać,
ustawiając swoje pola jako public . Ale nie czyń też wszystkiego private .
Poświęcenie chwili na przemyślenie problemu i zadecydowanie, które
pola mają być publiczne, a które nie, na samym początku pracy pozwala
zaoszczędzić sporo czasu na jej późniejszych etapach.

jesteś tutaj ► 259


Weź ją, ustaw ją, pobierz ją, dobrze
U żyliśm y notacji w ielbłądziej do

Hermetyzacja utrzymuje Twoje dane deklaracji pól pryw atnych i notacji


Pascal dla pól publicznych. Notacja
wnieskazitelnym stanie Pascal polega na zapisyw aniu
w szystkich słów tw orzących
C zasam i w artość p o la zm ienia się, gdy p ro g ram w ykonuje zaplanow ane i oczekiw ane nazw ę zm iennej w ielką literą.
czynności. Jeśli nie pow iesz m u jasno, żeby p ow tó rn ie ustaw ił zm ienną, obliczenia Notacja w ielbłądzia je st podobna
m ogą być w ykonywane przy użyciu jej starej w artości. G dy natrafisz n a tak i przypadek, — jedyna różnica polega na tym ,
będziesz zapew ne chciał wywoływać pew ne instrukcje za każdym razem , gdy w artość że pierw sza litera n azw y jest
danego p o la się zm ieni — dokład n ie tak, ja k to m iało m iejsce w p ro g ram ie Krystyny, zaw sze m ała. To w łaśnie dlatego
gdzie koszt pow inien być p o w tó rn ie obliczany p o zm ianie liczby osób. M ożesz uniknąć duże lite ry w nazw ie mogą
takich problem ów stosując p o la pryw atne. U d o stęp n im y m e to d ę , k tó ra będzie przypom inać garby w ielbłąda.
służyła do p o b ieran ia w artości p o la, o ra z in n ą, k tó ra posłuży do jego u staw iania
i w ykonyw ania niezbędnych obliczeń.
K o lj e s t z naczn ie bar<dziej przejrzysty,
gdy używ a sz spójnego sposobu zapisu
Szybki przykład hermetyzacji nazw pól, właściw ości, zmiennych oraz
metod. Tej konwencji używ a bardzo
K lasa F arm er używ a p o la do przechow yw ania liczby krów i m noży ją przez pew ną wielu programistów.
w artość służącą do określan ia liczby w orków paszy potrzeb n y ch do ich w ykarm ienia:

Lepiej ustawmy to pole


c la s s F a rm e r prywatne. Dzięki temu rukt n.e
' będzie mógt go zmienić bez
I równoczesnego ^ w . e n . a po «
p riv a te i n t num berO fC ow s; BagsOffeed - je śl'
i

G dy tworzysz form ularz, aby pozw olić użytkow nikow i n a w pisanie liczby krów
w kontrolce, m usisz m ieć tak że możliw ość zm iany tej w artości w p o lu numberOfCows.
A by tak było, m ożesz utw orzyć m eto d ę, k tó ra zw raca w artość p o la do o b iek tu fo rm u larza

p u b lic co n st in t F e e d M u l t i p l i e r = 3G;
p u b lic i n t G etN um berO fC ow s() Dodamy m etodę, aby
Roln ik potrzebuje inne klasy mogły
30 worków do I uzyska ć dostęp do
wykarmienia liczby krów.
każdej krowy. re tu rn num berO fC ow s;

i
p u b l i c v o id S e tN u m b e rO fC o w s (in t newNumberOfCows)

I
num berO fCow s = new Num berOfCow s;
B a g sO fF ee d = num berO fCow s * F e e d M u l t i p l i e r ; Tu je s t metoda do u s tawiania
liczby krów, która jednocześnie
i zm ienia pole B ^ s ^ F ^ d .
Teraz nie ma możliwości,
numberOFCows j e s t polem prywatnym, aby te dwie zm ienne nie były
zatem do za p isu jego nazwy z synchronizowane.
wy korzystaliśm y notację wielbłądzią.

260 Rozdział 5.
Hermetyzacja

Właściwości sprawią, że hermetyzacja będzie łatwiejsza


M ożesz używać właściwości, czyli m eto d , k tó re w ykonyw ane są
za każdym razem , gdy p o le je st u staw iane lub gdy je st z niego
p o b ie ra n a w artość. N azyw am y je w tedy polem wewnętrznym .
Ustawimy nazwą prywatnego pola na

dla właściwości NumberOfCows.


private int numberOfCows;
public int NumberOfCows widoczna deklaracja pola NumberOfCo
~ow§.
{
get
{ ^ P £ śprywatnego
w artość * £ = pola
return numberOfCows;
}
■ g d y " t a ś c w o s ć z a *pźdym razem'
set
” 's t ^ r is ^ r V
{ usf"™ ” »ł s
numberOfCows = value;
BagsOfFeed = numberOfCows * FeedMultiplier;
}
}

Używasz akcesorów g et i s e t dokład n ie ta k sam o ja k pól. T a k będzie w yglądał k o d dla Kiedy ten w iersz ustaw ia
przycisku, który ustaw ia liczbę krów i p o b ie ra o k reślo n ą liczbę w orków paszy: NumberOfCows na 10,
w tedy akcesor s e t ustaw ia
prywatne pole numberOfCows
p r i v a t e v o id b u t t o n 1 _ C l i c k ( o b j e c t s e n d e r , E v e n tA rg s e ) { i aktualizuje publiczne pole
F a rm e r m y F arm er = new F a r m e r ( ) ; BagsOfFeed.

m yF arm er.N um berO fC ow s = 1 0 ;

i n t howManyBags = m y F a rm e r.B a g s O fF e e d ; Ponieważ akcesor


< - s e t NumberOfCows
za k tualizował pole
m yF arm er.N um berO fC ow s = 2 0 ; BdgsOfFeed, m ożesz

howManyBags = m y F a rm e r.B a g s O fF e e d ;
T bez pr-oblemu pobrać
tę wartość.
}
Pomimo tego, że kod traktuje NumberOfCows tak s a m ° J ak
pole, uruchaminni/ j e s f akcM or s e t i PfzeokJ ay w anadfFeed
ia r to ś ć 2 0 . Gdy pobierana j e s t P o o B ^ e eOO-
uruchamia s ię akcesor g e t który zwraca 2 0 30, c z y l 600

jesteś tutaj ► 261


Właściwość pryw atna (wstęp wzbroniony)

Z r ó b to !
Stwórz aplikację do przetestowania klasy Farmer
* Y
U tw órz nową aplikację W indows F o rm s Application, której użyjem y w celu
przetestow ania klasy Farm er i o b ejrzen ia właściwości w akcji. Skorzystaj W yniki kierowane na
z m etody C o n s o l e . W r i t e L i n e ( ) do w ypisania wyników w o knie O utput ID E . konsolę są wyświetlane
Uuidędl w oknie Output.
r« D odaj do p ro je k tu klasę F a rm e r: Kiedy w aplikacji typu Windows Forms Application
używasz metody Console.WriteLinej), jej wyniki
p u b l i c c l a s s Farmer {
są wyświetlane w IDE, w oknie Output. Aplikacje
p u b l i c i n t BagsOfFeed;
WinForms zazwyczaj nie prezentują wyników
p u b l i c c o n s t i n t F e e d M u l t i p l i e r = 30; swego działania w ten sposób, niemniejjednak
my będziemy z niego często korzystalijako
p r i v a t e i n t numberOfCows; z narzędzia do nauki.
p u b l i c i n t NumberOfCows {
/ / (dodaj a kceso ry g e t i s e t z p o p rze d n ie j stro n y )
}

) Stw órz taki form ularz:

Nazwij ten przycisk calculate


— używ a on publicznych Ustaw wartość pola Value
danych obiektu Farmer do kontrolki NumericUpDown
w ypisania jednej linijki na 15, właściwość Minimum
na 5, a Maximum na 300.
w oknie O utput.

T a k w ygląda k o d klasy form ularza. U żyw a on m eto d y C o n s o l e . W r i t e L i n e ( ) do w yśw ietlenia wyników


w oknie O utput (k tó re m ożesz uaktyw nić, w ybierając opcję O utput z m en u V IE W ). D o m etody
W r i t e L i n e ( ) m ożesz p rzek azać kilka p a ra m e tró w — pierw szy z nich to łańcuch znaków do wypisania.
Jeżeli w ew nątrz niego um ieścisz {0}, W r i t e L i n e ( ) zam ieni to n a pierw szy p a ram e tr. Z am ien i także {1}
n a drugi p a ra m e tr, {2 } n a trzeci i ta k dalej.

p u b l i c p a r t i a l c l a s s Forml : Form {
Farmer farm er;
p u b l i c Form1() {
In itializeC o m p o n en t();
farm er = new Farm er() { NumberOfCows = 15 };
}
p r i v a t e void numericUpDown1_ValueChanged(object s e n d e r , EventArgs e) {
farmer.NumberOfCows = (int)num ericU pD ow nl.V alue;
}
p r i v a t e void c a l c u l a t e _ C l i c k ( o b j e c t s e n d e r , EventArgs e) {
C o n s o l e . W r i t e L i n e ( " P o t r z e b u j ę {0} worków paszy do wykarmienia {1} krów"
farm er.B agsO fF eed, farmer.NumberOfCows);
} / WriteLine() z a s t ę p u j i °0i } . ,
, Y w y „ w o * Console.WdteUnW)
do wyświetlenia wiersza tekstu
w oknie O utput w IDE.
Nie zapominaj, ze kontrolki muszą byc „podłączone” do swoich procedur
obsługi zdarzeń! Dwukrotnie kliknij kontrolki Button oraz NumericUpDown
262 Rozdział 5. w oknie projektanta formularzy, aby IDE wygenerowało odpowiednie metody.
Hermetyzacja

Użyj automatycznych właściwości do ukończenia klasy


W ygląda n a to, że k alk u lato r paszy działa w yśm ienicie. Spraw dź to — u ru ch o m go i kliknij przycisk.
Z m ień te ra z liczbę krów n a 30 i kliknij jeszcze raz. P ow tórz to sam o dla p ięciu krów , a p o te m dla 20.
T a k m niej więcej pow inno w yglądać okno Output:

Show output f ro m: Debug


Potrzebuję 459 worków paszy do wykarmienia 15 krów
Potrzebuję 999 worków paszy do wykarmienia 39 krów
Potrzebuję 159 worków paszy do wykarmienia 5 krów
Jeśli nie w idzisz okna O u tp u t w IDE, Czy rozum iesz,
Potrzebuję 609 worków paszy do wykarmienia 29 krów m ożesz j e w yśw ietlić wybierając dlaczego może
odpowiednią opcję z menu VIEW . to prowadzić
do powstawania
Output Locals Watch 1
naprawde
denerwujących
błedów w pisanych
programach?
Jest je d n a k pew ien p ro b lem z klasą. D odaj przycisk, który w ykonuje ta k ą o to instrukcję:

farmer.BagsOfFeed = 5;
T eraz uru ch o m p rog ram jeszcze raz. D ziała dobrze, dopóki nie użyjesz now ego przycisku. W ciśnij go jed n ak , a zaraz
p o nim kliknij ponow n ie przycisk Oblicz . T eraz k o m u n ik at w o knie Output sugeruje, że p o trzeb u jesz p ięciu w orków
paszy — bez względu na liczbę posiadanych krów ! G dy tylko zm ienisz w arto ść w k o n tro lce NumericUpDown, przycisk
Oblicz ponow nie zacznie generow ać praw idłow e wyniki.

Poddaj klasę Farmer pełnej hermetyzacji


Problem poleg a na tym , że T w oja klasa n ie j e s t całkow icie h e r m e t y c z n a . P o d d ałeś herm etyzacji właściwość
NumberOfCows, ale BagsOfFeed je st w dalszym ciągu publiczne. Jest to dość częsty p ro b lem . W rzeczywistości ta k częsty,
że C # u d o stęp n ia m echanizm pozw alający radzić sobie z nim w sposób autom atyczny. P o p ro stu zm ień publiczne pole
BagsOfFeed w e w łaściw ość a utom atyczną. ID E b ard zo ułatw ia to zadanie.
R obi się to w ten sposób:
. / autom atyczną właściwość
do Twojego kodu.
U su ń p o le BagsO fFeed z klasy F a rm er. W staw k u rso r w m iejsce, gdzie się znajdow ało, w pisz prop
i dw ukrotnie naciśnij T ab. ID E d o d a taki o to w iersz do T w ojego kodu:
p u b l i c i n t M y P ro p e rty { g e t ; set; }

N aciśnij klawisz T ab — k u rso r przeskoczy do M y P r o p e rty . Z m ień tę nazw ę n a B agsO fFeed:


p u b l i c i n t BagsOfFeed { g e t ; set; }

T eraz m asz ju ż właściwość zam iast p ola. K iedy C # widzi coś takiego, trak tu je to ta k sam o, jakbyś używał
p o la w ew nętrznego (takiego ja k pryw atne p o le numberOfCows z publiczn ą właściwością NumberOfCows).
T o jeszcze nie napraw iło p ro b lem u . Istn ieje je d n a k b ard zo p ro ste rozw iązanie — u staw ienie w łaściwości
tylko d o o d c z y tu :
p u b l i c i n t BagsO fFeed { g e t ; p rivate s e t ; }
Spróbuj teraz przebudow ać kod — pojawi się kom unikat o błędzie informujący, że ak ce so r set j e s t nied ostęp ny .
T eraz nie m ożesz zmodyfikować w artości pola BagsOfFeed spoza klasy Farmer. A by kod program u ponow nie
się skompilował, musisz pozbyć się wiersza, który próbuje to zrobić, usuń zatem drugi przycisk oraz metodę
obsługującą zdarzenia jego kliknięcia. T eraz Twoja klasa Farmer jest bardziej hermetyczna!

jesteś tutaj ► 263


Ustaw ją

Co wtedy, gdy chcemy zmienić pole mnożnika wyżywienia?


Stworzyliśm y K alk u lato r krów , używ ając c o n s t przy m nożn ik u paszy. A co, gdybyśmy chcieli użyć Z '\> f !
tej sam ej klasy Farm er w innych p ro g ram ach , k tó re p o trze b u ją innych m nożników ? Już zauw ażyłeś, ’
jakie problem y m ogą pow stać przez niepraw idłow ą herm etyzację, kiedy p o la jed n ej klasy są zbyt Jk
łatw o dostęp n e dla innych. T o dlatego pow inieneś tworzyć pola i metody publiczne tylko wtedy,
gdy ich naprawdę potrzebujesz . D o p ó k i K alk u lato r krów nie aktualizuje p o la F e e d M u l t i p l i e r ,
nie m a potrzeby, aby in n a klasa m ogła je ustaw iać. Z m ień m y je zatem n a autom atyczną właściwość
tylko do odczytu. Ta właściw ość działa ja k zw yczajne pole typu
in t z tą różnicą, że zam iast przechowywać
¡a kaś wartość, zwraca wartość pola
wewnętrznego f e e d M u ^ p ^ r Nie utworzyJ \ Ś n ydo
U su ń te n w iersz ze sw ojego p ro g ram u : M o r a J t , zatem j e s t to ^ a M f d l k° do
. — odczytu. Ma ona publiczny akcesor ge t y '? : !
p u b lic const i n t FeedM ultiplier 30; czem u w szystk ie inne Masy rnogą odczy t y wać
wartość pola FeedMultiplier. Właśc<wość
Użyj sekw encji p ro p - T a b - T a b w celu d o d an ia właściwości ¡e s t tylko do odczytu — je j wartość rnozna.
tylko do odczytu, je d n a k zam iast właściwości autom atycznej określić wyłącznie za pośrednictwem instancji
klasy Farmer.
u tw órz p o le w ew nętrzne:

p riv a te in t feed M u ltip lier;


p u b lic in t F eedM ultiplier { g et { re tu rn fe e d M u ltip lie r; } }
X 1
Ponieważ za stą p iliśm y sta łą publiczną FeedMultiplier polem prywatnym,
- teraz zaczyna s i ę ona małą literą „f". To standardowa k°nwencJa nazeew m i ^ ktorej tiędniimy
używ ać w teJ k s ią ż ę .
P ójdź o k ro k dalej i w prow adź tę zm ianę do sw ojego kodu.
T eraz u ru ch o m go. O j, oj — coś je st źle! BagsOfFeed zawsze zwraca 0 worków!

Z aczekaj, to m a sens. f e e d M u l t i p l i e r nigdy nie został zainicjalizow any. R ozpoczyna on


działanie z dom yślną w artością zero i nigdy nie je st zm ieniany. K iedy je st m nożony
p rzez liczbę krów , w dalszym ciągu daje zero. D odaj w ięc inicjalizator obiektu: Sprawdzaj okno Error List
w poszukiwaniu
p u b l i c Form1() { użytecznych ostrzeżeń
In itializeC o m p o n en t(); generowanych przez
IDE, takich jak informacje
farm er = new Farm er() { NumberOfCows = 15, f e e d M u l t i p l i e r = 30 };
o próbach użycia
zmiennej, zanim została
O j, oj — kod nie chce się kompilować! P ow inieneś otrzym ać taki o to błąd: zainicjowana.

Error List ▼ nx
T » IO 1 Error | Search Error List fi ”
Description File ^ Line ^ Colu... Project ^

O 1 'K alkulatoH crow.Farm er.feedM urtiplier' is inaccessible due to its p rotection level Form1.cs 19 56 K alkulator krow

W inicjalizatorze obiektu mogą znaleźć się tylko publiczne pola i właściwości. W jaki
sposób można prawidłowo zainicjalizować obiekty, jeżeli niektóre pola do ustawienia
mają atryb ut private?

264 Rozdział 5.
Hermetyzacja

Użyj konstruktora do inicjalizacji pól prywatnych


Jeśli m usisz zainicjalizow ać obiekt, ale n ie k tó re przezn aczo n e do inicjalizacji p o la są pryw atne, Wszystko, czego
to inicjalizator o b iek tu sobie z tym nie p o rad zi. N a szczęście istnieje specjalna m eto d a, k tó rą potrzebujesz, aby
m ożesz dod ać do klasy. N azyw am y ją konstruktorem . G dy klasa p o siad a k o n stru k to r, je st on dodać konstruktor
pierwszym elementem, który zostaje wykonany podczas tw orzenia jej nowej instancji do klasy, to napisanie
za p o m o cą instrukcji new. M ożesz p rzek azać do niego p aram etry , aby u d o stęp n ić m u w artości metody, która ma
p o trzeb n e do inicjalizacji. K o n stru k to r nie posiada wartości wynikowej, poniew aż nie taką samą nazwę
będziesz go wywoływał bezp o śred n io . P rzekazujesz jego p a ra m etry do instrukcji new. Już jak ona i nie posiada
w iesz, że new zw raca o biekt — nie m a zatem możliwości, aby k o n stru k to r zw racał cokolw iek. wartości wynikowej.
[h DODAJ KONSTRUKTOR DO KLASY FARMER.
T en k o n stru k to r m a tylko dw a w iersze, ale dzieje się w nich b ard zo dużo. P rześledźm y zatem wszystko k ro k po
kroku. Ju ż wiesz, że p o trzeb u jem y liczby krów i m nożnika paszy dla klasy — przekażem y te w artości w postaci
p aram etró w . P oniew aż zm ieniliśm y fe e d M u ltip lie r ze stałej w p o le typu i n t , m usim y określić jego początkow ą
w artość. N ie zapom nij zatem p rzek azać jej jak o p a ra m e tru k o n stru k to ra. Z re sz tą k o n stru k to ra używamy także do
określenia początkow ej liczby krów.
Zauw aż, że nie ma tu żadnego „void' ani „ m f czy innego typu po modyf ikatorze public.
Sfowo kluczowe To dlatego, że konstruktory nie p o siadaj ą żadnej wartości wyn*koweJ.
this” w wyrażeń iu Y*
public Farmer(int numberOfCows, int feedMultiplier) {
informuje
kompilator C# , this.feedMultiplier = feedMultiplier; w ukonf? cz^ ności^ J«ką musimy
że masz na myśli wykonać, je s t ustawienie mnożnika
pole obiektu, a me wyżywienia. Musimy to zrobTć
param etr o tej NumberOfCows = numberOfCows; z <mim wywołamy akcesor s e t
samej nazwie. właściwości NumberOfCows.
G dybyśmy zw yczajnie przypisali wartość do pola _numee rOfCkWS,
} akcesor s e t nigdy nie zostałby wywotany . U staw ienie JeJ za
To j e s t błąd, pomocą NumberOfCows daje nam p e w n ^ że tak s ię nie s t a n ie .
który otrzym asz
jeżeli Twój __ ^ Error Lisi
konstruktor / — *
- n X
ma parametry, I Y - I O 2 Errors 1 ! 0 W arn in gs 1 O 0 Messages Search Error List p-
natom iast Description File -*■ L... a . c. *!*■
instrukcja new
takowych nie ( x ) 2 'K alkula tor_kro w .F arm e r' does n o t c o n ta in a c o n s tru c to r th a t takes 0 a rg u m e n ts Form 1.cs 21 22 1
posiada. 1Error List Output

ZMIEŃ TERAZ FORMULARZ W TAKI SPOSÓB, ABY UŻYW AŁ KONSTRUKTORA.


Jedyną rzeczą, k tó rą m usisz zrobić, je st zm iana k o d u fo rm ularza. In stru k cja new b ędzie tera z tw orzyła o biekt
Farmer, używ ając k o n stru k to ra, a nie za p o m o cą in icjalizatora obiektu. P o zm ianie instrukcji new o b a błędy znikną,
a kod będzie się kom pilow ał i działał praw idłow o.

public Form1() { J u ż w iesz, że form ulaij j e s t M M ^ .


Faktem je s t, ż e on także
InitializeComponent(); konstruktor! To dlateg° istn ieje taka ,
metoda — zwróć uwagę, że ją
\|/ f a ł'm Q ł'
farmer =— new
nau ra rm a fiK • Form1 (tak ja k kla sę ) i nie jo s ia a ona
/|C farmer Farmer(15, BO);
new Farmer(15, 30), żadnej wartości wynikowej.

} To tuta j instrukcja new wywołuje konstruktor. J e s t ona taka sam a jak każda inna
instrukcja new z jednym wyjątkiem — posiada param etry, które s ą przekazyw ane do m etody
konstruktora. Podczas ich wpisywania zwróć uwagę na w yśw ietlane okno In telliS en se
— wygląda ono dokładnie tak jak w przypadku innych metod.

jesteś tutaj ► 265


Rekonstrukcja konstruktorów

o n s fru k to ry

w p r z y b liż e n iu Przyjrzyjmy się bliżej konstruktorowi klasy Farm er, abyśmy dokładnie dowiedzieli
się, jakie zjawiska zachodzą w środku.

Konstruktory nic nie Ten konstruktor ma dwa parametry, które dz iałaj ą tak s a ™
zwracają, więc nie mają ja k zwykle. Pierw szy z nich przekazuje liczbę krów, drugi
typ u wartości wynikowej. określa mnożnik paszy.
sb
public Farmer(int numberOfCows, int feedMultiplier) {
Mu sim y najpierw usta w ić mnożnik,
this.feedMultiplier = feedMultiplier;

C
ponieważ druga instrukcja wywołuje
akcesor s e t właściwości NumberOfCows,
NumberOfCows = numberOfCows; ^ który z kolei potrzebuje użyć wartości pola
feedM ultiplier do ustaw ienia BagsOfFeed.
} Potrzebna nam j e s t możliwość rozróżnienia Poniew aż s łowo „th is" j e s t referencją do aktualnego
pola o nazwie feedM ultiplier od parametru obiekt u , t h is.feedM ultiplier odwołuje s ię do pola.
o t ej sam ej nazwie. To właśnie w t akich Gdy by ś pominął „this", nazwa feedM ultiplier zostałaby
s y tuacjach niezwykle przydatne okazu je potraktowana jako parametr. A zatem pierw szy w iersz
s i ę słowo kluczowe „this". konstruktora zap isu je wartość drugiego parametru
wywołania w prywatnym polu o nazwie feedM ultiplier.

■Nie .istnieją.
głupie pytania

P Czy można mieć konstruktor bez parametrów?


:
Jest ona metodą wywoływaną z konstruktora. Dzięki temu
kontrolki inicjalizowane są zaraz po utworzeniu obiektu.

O : Oczywiście. Jest to dość powszechna praktyka i klasy często (Pamiętaj, każdy formularz jest tylko kolejnym obiektem,
który do wyświetlania okien, przycisków i innych kontrolek używa
mają takie konstruktory. W zasadzie to już widziałeś jeden
przykład — konstruktor Twojego fo rm ularza. Zajrzyj do środka metod udostępnianych przez platformę .NET i przestrzeń nazw
świeżo utworzonego formularza Windows i znajdź deklarację jego System .W indows.Forms).
konstruktora:

p u b lic F orm 1() {


I n it ia liz e C o m p o n e n t ( ) ;
Kiedy nazwa parametru
} metody jest taka sama jak
nazwa pola, wtedy parametr
Jest to konstruktor obiektu Twojego formularza. Nie pobiera przesłania pole.
on żadnych parametrów, ale wykonuje ogrom pracy. Poświęć
Czy zauważyłeś, że parametr konstruktora f e e d M u l t i p l i e r
minutę i otwórz plik F o rm 1 .D e sig n e r.cs. Znajdź metodę
wygląda dokładnie tak samo jak wewnętrzne pole właściwości
In itia liz e C o m p o n e n t( ) , klikając symbol plus obok Windows
F e e d M u ltip lie r ? Gdybyś chciał użyć w konstruktorze
Form Designer generated code.
pola wewnętrznego, musiałbyś napisać „ t h i s . "
Ta metoda inicjalizuje wszystkie kontrolki i ustawia ich właściwości. — f e e d M u l t i p l i e r odnosi się do parametru, natomiast
Gdy przeciągniesz w IDE nową kontrolkę na formularz i ustawisz niektóre t h i s . f e e d M u l t i p l i e r to sposób na dostanie się do
z jej właściwości w oknie Properties, będziesz mógł zaobserwować prywatnego poia.
pewne zmiany wprowadzone w In itia liz e C o m p o n e n t().

Oto wygodny sposób zapam iętania znaczenia słowa


kluczowego „this": myśl o n im jako o „tej in sta ncji
266 Rozdział 5. (ang.: this instance).
Hermetyzacja

P Dlaczego potrzebuję całej tej


: P Skoro akcesor zawsze pobiera
:
Gdy utworzysz właściwość z akcesorem set,
ale bez ge t, pole może być tylko ustawiane.
skomplikowanej logiki i akcesora get? parametr value, to dlaczego jego
Nie da się z niego nic odczytać. Klasa
Czy to nie jest po prostu sposób na deklaracja nie ma nawiasów z „int
S ecre tA gen t mogłaby używać takiego
tworzenie pola? value” , tak jak to ma miejsce przy
pola do przechowywania hasła. Inni szpiedzy
deklaracjach innych metod z tym
O : Dlatego, że czasami podczas ustawiania parametrem?
potrafiliby coś do niego wpisać, ale nie
mogliby z niego nic odczytać:
pola musisz wykonać pewne obliczenia lub
przeprowadzić jakąś akcję. Pomyśl o problemie O : Ponieważ C# został zaprojektowany
Krystyny — wpadła w tarapaty, bo jej formularz w taki sposób, żebyś nie musiał wpisywać p u b lic s t r in g Password {
nie uruchomi metody do ponownego obliczenia dodatkowych informacji, których kompilator set {
kosztów dekoracji po ustawieniu nowej liczby nie potrzebuje. Parametr jest więc if (v a lu e == secretC ode) {
gości w klasie D innerP arty. Po zamianie deklarowany bez jawnego podawania jego
name = "Herb Jo n e s";
pola na akcesor set uzyskalibyśmy pewność, typu. Nie jest to może dużo, gdy masz wpisać
}
że ponowne przeliczenie ceny ozdób jest jedno lub dwa pola, jednak gdy musisz
wykonywane za każdym razem. (Prawdę wpisać kilkaset, to takie rozwiązanie może }
mówiąc, zrobisz to za kilka stron!). realnie zaoszczędzić czas (nie wspominając
0 zabezpieczeniu przed błędami). Obie te techniki mogą przyjść z nieocenioną
P : Zaczekaj chwilę — jaka jest różnica Akcesor set zawsze ma jeden parametr value,
pomocą podczas hermetyzowania implementacji.
pomiędzy metodą i akcesorami get i set? którego typ jest zawsze zgodny z typem
P : Używam obiektów już od jakiegoś
O : Nie ma żadnej. Akcesory są po prostu
właściwości. C# ma wszystkie informacje o typie
1parametrze zaraz po wpisaniu „s e t {", nie czasu, ale jeszcze nie napisałem
specjalnym rodzajem metod — jedna z nich żadnego konstruktora. Czy to oznacza,
ma zatem potrzeby, abyś pisał cokolwiek więcej.
jest deklarowana wewnątrz właściwości że niektóre klasy ich nie potrzebują?
Prawdę mówiąc, C# nie pozwoli Ci wpisać
i wywoływana w momencie ustawiania pola.
Akcesor get zawsze zwraca wartość, która
więcej, niż potrzebujesz.
O : Nie, oznacza to, że C# automatycznie
tworzy konstruktor bezargumentowy, jeśli
ma dokładnie taki sam typ jak pole, akcesor
set natomiast zawsze pobiera jeden parametr, P : Zaczekaj — to dlatego nie dodaję żaden inny nie zostanie zdefiniowany. Jeśli
zwany value , którego typ jest także zgodny instrukcji return do mojego konstruktora? Ty zdefiniujesz jakiś konstruktor, to C#
z typem pola. Swoją drogą, możesz po prostu
mówić „właściwości" zamiast „akcesor g e t
O : Dokładnie! Twój konstruktor nie ma
nie wygeneruje żadnego innego. To cenne
narzędzie hermetyzacji, gdyż oznacza,
wartości wynikowej, ponieważ jest zawsze
i set". że masz możliwość — lecz nie obowiązek
v o id . Pisanie v o id na początku byłoby
— zmuszenia osób używających klasy
P : Czy w takim razie mogę mieć KAŻDY
zbędne, więc nie musisz tego robić.
do wywołania Twojego konstruktora.
rodzaj instrukcji wewnątrz właściwości?
P : Czy mogę mieć get bez set albo set
W ł a ś c i w o ś c i (akc e s o r y
O : Oczywiście. Wszystko, co możesz bez get?
get i set) są specjalnym
zrobić w metodzie, możesz także zrobić
we właściwościach. Mogą one wywoływać
O : Naturalnie! Jeśli masz akcesor ge t, ale nie
rodzajem m e t o d ,
masz se t, tworzysz pole tylko do odczytu. Na
inne metody, uzyskiwać dostęp do innych pól,
przykład klasa S ecretA gent mogłaby mieć
a nawet tworzyć instancje klas. Wywoływane k t ó r e są w y w o ł y w a n e
takie pole określające nazwisko:
są jednak tylko wtedy, gdy potrzebny jest
s t r in g name = "Dash M a r tin " ; tyl k o w p r z y p a d k u
dostęp do pola, nie ma więc potrzeby
umieszczania wewnątrz żadnych instrukcji, p u b lic s t r in g Name { o d c z y t y w a n i a lub
które nie mają nic wspólnego z ustawianiem g e t { r e tu r n name; }
lub odczytywaniem pól.
modyfikacji właściwości.
}

Przydatna informacja: pierwszy wiersz metody, zawierający m odyfikator


dostępu, wartość zwracaną, nazwę metody oraz jej parametry,
nazywamy sygnaturą metody. Także właściwości mają swoje sygnatury.
jesteś tutaj ► 267
Co znaczy imię?

_ Zaostrz ołówek
Przyjrzyj się dokładnie akcesorom get i set przedstawionym w poniższym przykładzie.
Formularz używający klasy C a b le B ill dysponuje jej nową instancją, zapisaną w zmiennej
o nazwie thisM onth, i po kliknięciu przycisku wywołuje jej metodę C alculateA m ount().
Określ wartość wyświetloną w każdym przypadku w okienku informacyjnym.

class C ableBill {
p riva te in t rentalFee;
p ub lic C a b le B ill(in t rentalFee) {
th is .re n ta lF e e = rentalFee;
discount = fa ls e ;
}

p riva te in t payPerViewDiscount;
p riva te bool discount;
p ub lic bool Discount {
set {
discount = value;
i f (discount)
payPerViewDiscount = 2;
else
payPerViewDiscount = 0;
}
}

p ub lic in t CalculateAmount(int payPerViewMoviesOrdered) {


return (rentalFee - payPerViewDiscount) * payPerViewMoviesOrdered;
}
}

1. C ableBill january = new C a b le B ill(4 ); Jaka wartość


MessageBox.Show(january.CalculateAmount(7).ToString()); zostanie wyświetlona?

2. C ableBill february = new C a b le B ill(7 );


february.payPerViewDiscount = 1;
MessageBox.Show(february.CalculateAmount(3).ToString()); Jaka wartość
zostanie wyświetlona?
3. C ableBill march = new C a b le B ill(9 );
march.Discount = tru e ;
MessageBox.Show(march.CalculateAmount(6).ToString());
Jaka wartość
zostanie wyświetlona?

268 Rozdział 5.
Hermetyzacja

i Niejstnieią. W języku C# wielkość liter ma znaczenie. pierwsze litery wszystkich dodatkowych


głupie pytania
Nic nie stoi na przeszkodzie, byś w tej słów są wielkie, co może przypominać garby
samej metodzie używał dwóch zmiennych wielbłąda).

P Zauważyłem, że używasz dużych


:
o nazwach im prezka i Im prezka. Choć
utrudni to analizę kodu, to jednak z jego
2. Metody i właściwości publiczne powinny
być pisane w notacji Pascal (rozpoczynać się
liter dla niektórych nazw i małych
kompilacją nie będzie żadnych problemów. dużą literą).
dla pozostałych. Czy to ma jakieś
Poniżej przedstawiliśmy kilka wskazówek
znaczenie? 3. Parametry metod powinny być pisane
dotyczących nazewnictwa, które spowo­
O : Tak — ma znaczenie dla Ciebie, dują, że wszystko będzie proste. Nie są
to jedynie słuszne zasady — kompilator
z użyciem notacji wielbłądziej.
4. Niektóre metody, zwłaszcza konstruktory,
nie ma natomiast żadnego znaczenia dla
nie zwraca uwagi na to, czy zmienna jest posiadają parametry dokładnie takie same
kompilatora. C# nie zwraca uwagi na to,
napisana małą, czy dużą literą — ale dobre jak pola. Kiedy natrafisz na taki przypa­
w jaki sposób nazywasz zmienne. Jeśli
sugestie pozwalające pisać kod łatwy dek, parametr przesłoni pole. Oznacza
jednak nadasz im dziwne nazwy, Twój kod
do czytania. to, że instrukcje w metodzie używające tej
będzie trudny do czytania. Czasami można
nazwy będą odnosiły się do niego, nie do
się pogubić, gdy posiada się zmienne 1. Kiedy deklarujesz pole prywatne, powinie­
pola. Problem ten możesz rozwiązać,
nazwane tak samo z wyjątkiem pierwszej neś stosować notację wielbłądzią i zaczynać
poprzedzając nazwę pola słowem kluczo­
litery, która raz jest duża, a raz mała. od małej litery. (Nosi ona takie miano, gdyż
wym t h i s i informując w ten sposób kom­
nazwa zmiennej zaczyna się małą literą, lecz
pilator, że chodzi Ci o pole, a nie o parametr.

_ ¿Zaostrz ołówek ------


V
u in u
Zaprezentowany kod ma pewne usterki. Napisz, co według Ciebie
jest w nim błędne i w jaki sposób można to naprawić.

c la s s GumballMachine {
p r iv a t e i n t g u m b a lls;

p r iv a t e i n t p r ic e ;
p u b lic i n t P ric e
{
get
{
r e tu r n p r ic e ;
}
}
p u b lic G u m b a llM a ch in e (in t g u m b a lls , i n t p r ic e )
{
gum balls = th is .g u m b a lls ;
p r ic e = P ric e ;
}
p u b lic s t r in g D ispenseO neG um ball(int p r ic e , i n t c o in s ln s e r te d )
{
if ( t h is . c o in s ln s e r t e d >= p r ic e ) { / / sprawdź p o le
gu m ba lls -= 1;
r e tu r n "Oto tw o ja guma";
} e ls e {
r e tu r n "Wrzuć w ię c e j m onet";
}
}

jesteś tutaj ► 269


Hermetyzacja zapobiega powstawaniu błędów

Użyj całej swojej wiedzy o właściwościach i konstruktorach, aby naprawić program Krystyny do planowania
Ćwiczenie P rzy jf ć Ten nowy program będzie znacznie prostszy i bardziej spójny od jego pierwszej wersji.

[0 Napraw kalkulator kosztów przyjęć.


Jeżeli chcem y p opraw ić klasę D innerP arty , m usim y znaleźć sposób, aby m eto d a
C alculateC ostO fD ecorations() była wywoływana za każdym razem , gdy zm ieni się
w artość NumberOfPeople. Z ro bim y to , d o d ając do p ro g ram u w łaściwość o nazw ie Cost.

^ N umberOfPeopl e = 1°»

M usim y od nowa przeliczać koszty dekoracji


za każdum razem , gdy zm ieni s ię liczba os<ib.
M ożemy to zrobić, je śli jedynym sp j s j bem
wyliczenia kosztu będzie uży cie właś c iw jś c i. 'A r~ Aj
ek t
u la&
J eśli zadbamy o to, by cena dekoracji była
N astę p n e odw ołanie do właściwości przeliczana s ię podczas każdego odwołania do
Cost zwróci 650 zł. właściwości Cost, to w szystko, co będziem y
m usieli z robić, sprowadzi s ię do ustaw ienia
właściwości obiektu DinnerParty i pobrania
wartości Cost.
Zastosuj właściwości, by określić liczbę uczestników i opcje przyjęcia. DinnerParty
Być m oże zechcesz utw orzyć nowy p ro jek t, gdyż w b ard zo dużym stopniu
NumberOfPeople: int
zm ienisz p o stać klasy DinnerParty. Zacznij o d u tw o rzenia trzech właściwości FancyDecorations: bool
autom atycznych: HealthyOption: bool
p ub lic in t NumberOfPeople { get; set; } Oto diagram nowej Cost: decimal
p ub lic bool FancyDecorations { get; se t; } Masy DinnerParty.
p ub lic bool HealthyOption { get; set; } metody prywatne:
CalculateCostOfDecorations()
B ędziesz także p o trzeb o w ał k o n stru k to ra o następującej sygnaturze; CalculateCostOfBeverages
pozw oli Ci o n u staw iać w artości właściwości: PerPerson();
p ub lic D innerP arty(int numberOfPeople, bool healthyOption,
bool fancyDecorations)

Utwórz prywatne metody do wyliczania kosztów pośrednich.


Poniżej podaliśm y sygnatury m eto d ułatw iających wyliczanie kosztów przyjęcia.
W ew nątrz nich u m ieść o d pow iednie obliczenia:
p riva te decimal CalculateCostOfDecorations() { . . . } B ędą j>ne bardz o p t d b ne do
metod, które napisałeś na
p riva te decimal CalculateCostOfBeveragesPerPerson() { . . . } początku tego rozdziału.

Dodaj właściwość Cost, tylko do odczytu, która będzie wyliczać koszty.


D odaj właściwość Cost, k tó ra będzie wyliczać koszt przyjęcia:
p ub lic decimal Cost { Mała podpowiedz: zacznij od
get { zdefiniowania zm iennej typu decimal
o nazwie totalCost, a następnie,
/ / UzupeJnij kod, by wyliczać koszty przed zwróceniem ostatecznego
} wyniku, użyj operatorów złożonych
} += oraz =.

270 Rozdział 5.
Hermetyzacja

^ Zaktualizuj formularz, by korzystał z właściwości.


Poniżej przedstaw iliśm y k o m p letn y k o d form ularza. U żyw a on k o n stru k to ra o raz trzech właściwości
(N um berO fP eople, F a n c y D e c o ra tio n s o raz H e a lth y O p tio n ), by przekazyw ać inform acje do
ob iek tu D in n e rP a r ty , a n astęp n ie korzysta z jego właściwości C o st, by wyliczyć koszty przyjęcia.

p u b lic p a r t i a l c l a s s Form l : Form


{ Formularz przechowuje instancję DinnerParty _i aktualizuje
D in n e r P a r ty d i n n e r P a r t y ; je j właściw ości za każdym razem, gdy zm ieni s ię liczba
uczestników przyjęcia luti je go opcj e .
p u b l i c Form 1()
{
In itia liz e C o m p o n e n t( );
d i n n e r P a r t y = new D in n e rP a r ty (( in t)n u m e r ic U p D o w n 1 .V a lu e ,
h e a lth y B o x .C h e c k e d , fa n c y B o x .C h e c k e d );
D is p l a y D i n n e r P a r t y C o s t( ) ;
Formularz używa konstruktora klasy D in n e rP a rty , by
zainicjalizować obiekt, używając odpowiednich wartości. Musisz się
upewnić, że klasa DinnerParty faktycznie definiuje ten konstruktor.

p r i v a t e v o id fa n c y B o x _ C h e c k e d C h a n g e d (o b je c t s e n d e r , E v en tA rg s e)
{
d i n n e r P a r ty .F a n c y D e c o r a t io n s = fa n c y B o x .C h e c k e d ;
D is p l a y D i n n e r P a r t y C o s t( ) ;
}

p r i v a t e v o id h e a lth y B o x _ C h e c k e d C h a n g e d (o b je c t s e n d e r , E v en tA rg s e)
{
d in n e r P a r ty .H e a l th y O p tio n = h e a lth y B o x .C h e c k e d ;
D is p l a y D i n n e r P a r t y C o s t( ) ;
}

p r i v a t e v o id n u m ericU p D o w n 1 _ V alu eC h an g ed (o b ject s e n d e r , E v en tA rg s e)


{
d in n e rP a rty .N u m b e rO fP e o p le = (in t)n u m e ric U p D o w n l.V a lu e ;
D is p l a y D i n n e r P a r t y C o s t( ) ;
}

p r i v a t e v o id D is p la y D in n e r P a r ty C o s t( )
{
d e c im a l C o st = d i n n e r P a r t y . C o s t ; Ta metoda aktualizuje k o szt przyjęcia
c o s t L a b e l.T e x t = C o s t . T o S t r i n g ( " c " ) ; w yśw ietlany w formularzu, odwołując
s ię i ^ ^ a w o ś c i C o st za k a ż ó w
} razem, gdy zm ienią s ię opcje wybrane
na formularzu.

Formularz jest teraz prostszy, gdyż nie musi wywoływać metod wykonujących
obliczenia. Obliczenia te są teraz ukryte we właściwości C o s t .

jesteś tutaj ► 271


Taka koncepcja j e s t nazywana „separacją zagadnień" i stanowi doskonały
sposób realizacji programów. Zagadnieniem, którym zajm uje s ię formularz je s t
Rozwiązanie ćwiczenia obsługa interfejsu użytkownika, natom iast zagadnieniem interesującym klasę
DinnerParty j e s t wyliczanie kosztów przyjęć.

±
Czy zauważyłeś, że nasz nowy form ularz nie musi zbyt w iele robić? Jedyne, czym się
zajmuje, to ustawianie właściwości obiektu na podstawie danych wprowadzonych
Rozwiązania przez użytkownika i zmiana w yniku na podstawie tych właściwości. Przeanalizuj,
w jaki sposób kod obsługujący informacje podawane przez użytkownika oraz
ćwiczeń
w yśw ietlane w yniki jest odseparowany od kodu realizującego obliczenia.
class DinnerParty {
p u b lic const in t CostOfFoodPerPerson = 25;

p u b lic in t NumberOfPeople { get; se t; } Wartości tych właściwości s ą określane


w konstru ktorze i aktualizowane przez
p u b lic bool FancyDecorations { get; se t; } fo r m u ^ m s ą one także używane
w obliczeniach kosztów przyjęcia.
p u b lic bool HealthyOption { get; se t; }

p u b lic D innerP arty(int numberOfPeople, bool healthyOption, bool fancyDecorations) {


NumberOfPeople = numberOfPeople;
FancyDecorations = fancyDecorations; To j e s t konstruktor klasu DinnerParb,
HealthyOption = healthyOption; u staw ia on trzy w łaściwości o b ie k tu '
} na podstaw ie przekazanych wartości
p riva te decimal CalculateCostOfDecorations() {
decimal costOfDecorations; Dzięki tem u, że metoda zo sta ła
i f (FancyDecorations) zdefiniowana jako prywatna, mamy
{ pewność, że nie można je j wywołać
costOfDecorations = (NumberOfPeople * 15.00M) + 50M; spoza klasy; dzięki tem u nie _t>ędzie
} można je j używ ać w nieprawidłowy
else sposób.
{
costOfDecorations = (NumberOfPeople * 7.50M) + 30M;
} W początkow ej w e rsji program u
return costOfDecorations;
dostępna była m etoda
}
S e tH e a lth y O p tio n ( ) .
p riva te decimal CalculateCostOfBeveragesPerPerson() { Teraz zm ieniliśm y ją na właściw ość
decimal costOfBeveragesPerPerson; o nazw ie H e a lth y O p tio n .
i f (HealthyOption)
Pryw atne
{ metody Jeśli dysponujesz m etodą, której
costOfBeveragesPerPerson = 5.00M; używane nazwa zaczyna się od „s e t" i która
w obliczeniach usta w ia w artość pola, a następnie
else korzystają
aktu alizuje stan ob ie ktu , to zastąpienie
z właściwości,
costOfBeveragesPerPerson = 20.00M; dzięki czem u jej w łaściw ością może u ła tw ić
} za w sze zrozum ienie, jak należy jej używać.
return costOfBeveragesPerPerson; dysponują
} najśw ieższym i To jeden ze sposobów, w jakie
informacjami herm etyzacja upraszcza klasy i u ła tw ia
p u b lic decimal Cost { z formularza. ich późniejsze w ie lo k ro tn e stosowanie.
get {
decimal to ta lC o st = CalculateCostOfDecorations();
to ta lC o st += ((CalculateCostOfBeveragesPerPerson()
+ CostOfFoodPerPerson) * NumberOfPeople);
i f (HealthyOption)

totalC ost .95M; Teraz, gdy M i a m m ^ są pry w atne i ukryte we w łaściw ości
}
return to ta lC o st; w k£ ? J ns j ^ z g i s ^ ^ ‘"ef apouc™ e ^ a e i^ w
^wry^tyns^ o,!tr^ t^ ^ 3^e^n^ !;^ i '^ i^ ^ ę^auj ^ ^ e e ^ l s^ c>'u cif

272 Rozdział 5.
Hermetyzacja

r- » Zaostrz ołówek
Określ wartość wyświetloną w okienku informacyjnym po wykonaniu tego kodu.
Rozwiązanie
Jaka wartość
1. C ab le B ill j a n u a r y = new C a b l e B i l l ( 4 ) ; zostanie wyświetlona?
M e s sa g e B o x .S h o w (ja n u a ry .C a lc u la te A m o u n t(7 ).T o S tr in g () );
28

2. C a b le B ill f e b r u a r y = new C a b l e B i l l ( 7 ) ; Jaka wartość


fe b ru a ry .p a y P e rV ie w D isc o u n t = 1; zostanie wyświetlona?
M e s s a g e B o x .S h o w ( f e b r u a r y .C a lc u l a te A m o u n t ( 3 ) .T o S t r in g ( ) ) ;
Nie skom piluje

3. C a b le B ill march = new C a b l e B i l l ( 9 ) ;


m a rc h .D isc o u n t = t r u e ; Jaka wartość
MessageBox.Show(m arch.C alculateAmount( 6) . T o S t r i n g ( ) ) ; zostanie wyświetlona?

42

r- .^Zaostrz ołówek
Zaprezentowany kod ma pewne usterki. Napisz, co według Ciebie jest w nim błędne
Rozwiązanie i w jaki sposób można to naprawić.

p ric e pisane matą literą odnosi sie


do param etru konstruktora, nie do oola

Uliar i + ■ z °sta ta ustaw iona' Nie ma


więc z tego zadneao pożutku T»<s/ • •
param etr konstruktora TK i zm ien isz
do parametru.
prawidtowo.

p u b l i c G um ballM achine(int gumballs Param etr przesłania


prywatne pole Price,
{ natom ia st komentarz
gum balls = t h i s . g u m b a l l s ; s ugeruje, że metoda
powinna sprawdzić pole
p ric e = P rice;
wewnętrzne p r i c e .
}
p u b l i c s t r i n g D ispenseO neG um ball(int p r i c e , i n t c o i n s I n s e r t e d )
{
Słowo kluczowe i f (th is.co in sIn serte d p r i c e ) { / / spraw dź p o le
„this “ u sta w ione W y gum balls -= 1 ;
przy jaram etrze, do
którego nie należy. r e t u r n "Oto tw o ja guma"; Poświęć d o d a tko w ą m in utę lub dw ie,
Powinno znajdować } else { by napraw dę do kła dnie przyjrzeć się tem u
s i ę przy p r i c e, kodow i. Przedstawia on błędy najczęściej
r e t u r n "Wrzuć w ię cej monet";
ponieważ je s t to pole
przesłonięte przez } popełniane przez początkujących pro gra m istów
param etr. } zaczynających posługiw ać się obiektam i,
a unikanie ich spraw i, że pisanie kodu będzie
znacznie bardziej satysfakcjonujące.

jesteś tutaj ► 273


274 Rozdział 5.
6. Dziedziczenie

^ Drzewo genealogiczne
Twoich obiektów

Czasami CHCIAŁBYŚ być dokładnie taki sam jak Twoi rodzice. Czy kiedykolwiek natknąłeś się
na obiekt, który robiłby praw ie wszystko, czego byś sobie od niego życzył? Czy kiedykolwiek znalazłeś się
w takiej sytuacji, że gdybyś zm ienił dosłow nie kilka rzeczy, obiekt byłby doskonały? Cóż, to tylko jeden
z wielu powodów, które sprawiają, że dziedziczenie zalicza się do najważniejszych koncepcji i technik
w języku C#. Kiedy skończysz czytać ten rozdział, będziesz wiedział, jak rozszerzać obiekty, by móc
wykorzystywać ich zachowania i jednocześnie dysponować elastycznością, która pozwoli Ci te zachowania
modyfikować. Unikniesz p o w ie la nia kodu, przedstaw isz p ra w d z iw y św ia t znacznie dokładniej,
a w efekcie otrzymasz kod ła tw ie js z y do zarządzania.

to jest nowy rozdział ► 275


Wszystkiego najlepszego, kochanie

Krystyna organizuje także przyjęcia urodzinowe


T eraz, kiedy Twój p ro g ram działa b ez zarzu tu , K rystyna używa go cały czas.
N ie zajm uje się o n a je d n a k tylko zwykłymi przyjęciam i. O rganizuje także
im prezy urodzinow e, ale są o n e w yceniane w nieco inny sposób. Chciałaby,
abyś d odał now ą funkcję do jej p rogram u.

WŁAŚNIE DOSTAŁAM TELEFON


W SPRAWIE ORGANIZACJI PRZYJĘCIA
URODZINOWEGO DLA DZIESIĘCIU OSÓB. CZY
TW ÓJ PROGRAM SOBIE Z TYM PORADZI?

Ta punkty s ą takia
sam a Jak przy zwykłym
przyjęciu.

S zacunkowe koszty przyjęcia urodzm °weg°

25 zł od osoby.
|stnieją dwie opcje kosztów dekoracji. Jeżeli klient sobie a w M rc h
ozdób, koszt wyniesie 7,50 zł od osoby plus dodatlcom o ^ a ta d ^ o r a ^ r a
w wysokości 30 zł. Gdyby chciał rozszerzyć opcję podsta\wową do
fantazyjnych, koszt będzie wynosił 15 zł od ow by plus ^ d n o ra ra ra oprata
w wysokości 50 zł.
j eżeli przyjęcie organizowane jest dla c z te r e j lub mniejszej liczby gości,
użyj tortu 2 0 -centymetrowego (40 zł). W pradcwr^irn razie użyj to rtu
4 0 -centymetrowego (75 zł).

Napis na to rcie to koszt 25 groszy za każdą literę. Na torcie


W iększość zmian 2 0 -centy metrowym może on mieć maksymalnie 16 liter, a na
dotyczy tortów
i napisów. 4 0 -centymetrowym 40.

Aplikacja powinna obsługiwać dwa rodzaje przyjęć. UżYj tontrcdld TabCont ro|,
gdzie każda zakładka będzie przeznaczona cda jednego rodzaju.

n m WYSIL ___________
SZARE KOMÓRKI
W przypadku przyjęć urodzinowych nie ma opcji zdrowej. Czy domyślasz się,
dlaczego może to prowadzić do wystąpienia błędów, gdybyś spróbował skopiować
i wykorzystać klasę D innerParty z poprzedniego rozdziału?

276 Rozdział 6.
Dziedziczenie

Potrzebujemy klasy BirthdayParty


Z m o d yfiko w a n ie pro g ra m u K rystyny w ta k i sposób, aby obliczał B irth d ay P arty
koszt przyjęć urodzinow ych, oznacza dodanie nowej klasy i zmianę NumberOfPeople
fo rm u la rza , aby obsługiw ał oba rodzaje im prez. CostOfDecorations
Z robisz to w ciągu
m inuty, ale najpierw CakeSize
Powinieneś zrobić następujące rzeczy: mus is z zapoznać CakeWriting
s ię z podstawowymi Cost
założeniam i.
STWÓRZ NOW Ą KLASĘ BIRTHDAYPARTY.
T w o ja nowa klasa będzie m usiała zajmować się obliczaniem kosztów,
radzeniem sobie z dekoracjam i oraz sprawdzaniem ro zm ia ru napisu na torcie.

_ DODAJ DO FORMULARZA TABCONTROL.


Każda ka rta na fo rm u la rz u to coś na kształt k o n tro lk i G ro u p B o x, k tó re j używałeś
w L a b o ra to riu m „D z ie ń na wyścigach” do określania, k tó ry z facetów złożył zakład.
Po p ro stu k lik n ij tę z k a rt, k tó rą chcesz w yśw ietlić, i przeciągnij na nią k o n tro lk i.

j) NAZW IJ PIERWSZĄ KARTĘ I WSTAW DO NIEJ KONTROLKI


ZW IĄZANE Z IMPREZĄ OKOLICZNOŚCIOWĄ.
Przeciągnij każdą z k o n tro le k obsługujących im prezę okolicznościow ą na nową kartę.
Będą one działały do kła dnie ta k samo ja k wcześniej, ale będą wyśw ietlane ty lk o wtedy,
gdy wybrana będzie ich karta.

W NAZW IJ DRUGĄ ZAKŁADKĘ I WSTAW DO NIEJ KONTROLKI


ZW IĄZANE Z PRZYJĘCIEM URODZINOWYM.
Z a p ro je k tu j in te rfe js użytko w nika do obsługi przyjęcia urodzinow ego w sposób analogiczny
do in te rfe jsu im prezy okolicznościowej.

POŁĄCZ KLASĘ OBSŁUGUJĄCĄ PRZYJĘCIE URODZINOWE


Z ODPOWIEDNIMI KONTROLKAMI.
W zasadzie musisz ty lk o wstaw ić referencję o b ie k tu B ir t h d a y P a r ty do klasy fo rm ularza
i dodać ko d dla każdej nowej k o n tro lk i, aby korzystała z je j m etod i właściwości.

I1 Nie. istnieją.
istniej .
głupie pytania
P : Dlaczego nie mogę po prostu utworzyć nowej instancji P Skąd mam wiedzieć, co umieścić w nowej klasie?
:
klasy DinnerParty tak jak Michał, gdy chciał porównać
trzy trasy w programie do nawigacji? O : Zanim zaczniesz tworzyć klasę, powinieneś dokładnie
zapoznać się z problemem, który rozwiązujesz. To dlatego
O : Ponieważ po utworzeniu nowej instancji klasy D in n e rP a rty musiałeś porozmawiać z Krystyną — to ona będzie tego programu
miałbyś możliwość zaplanowania dodatkowej imprezy używała. Dobrze, że zrobiłeś obszerne notatki! Na ich podstawie
okolicznościowej. Dwie instancje tej samej klasy mogłyby się możesz utworzyć w klasie metody, pola i właściwości, cały czas
przydać tylko w przypadku, gdybyś chciał zarządzać dwoma mając na uwadze ich zachowanie (co pow inny robić) i ich stan
różnymi kompletami danych tego samego typu. Jeżeli chcesz (co pow inny wiedzieć).
przechowywać dane różnego ty p u , potrzebujesz do tego
różnych klas.

jesteś tutaj ► 277


Inny rodzaj przyjęcia

Stwórz program Planista przyjęć wwersji 2.0 Upewnij się, że w szystkie pola
i właściw ości przechowujące
U tw órz nowy p ro jek t — m am y zam iar n apisać dla Krystyny now ą w ersję p ro g ram u , kwoty będą typ u decimal.
k tó ra będzie w stanie o kreślać koszty zarów no przyjęć, ja k i u rodzin. Z aczniem y
o d d o d an ia praw idłow o herm etyzow anej klasy B irth d a y P a rty odpow iedzialnej BirthdayParty

za now e obliczenia. NumberOfPeople


FancyDecorations
Cost
CakeSize
Z r ó b to ! CakeWriting

CalculateCostOfDecorations()
CakeSize()
DODAJ NOW Ą KLASĘ BIRTHDAYPARTY DO PROGRAMU
MaxWritingLength()
Już wiesz, ja k p o rad zić sobie z właściwościam i NumberOfPeople
i FancyDecorations — są zwykłymi o d pow iednikam i podobnych
elem entó w w klasie D innerP arty . R ozpoczniem y o d u tw o rzen ia nowej
klasy i d o d an ia ich, a n a stęp n ie dołączym y p o zo stałe zachow ania.
★ D odaj stałą CostOfFoodPerPerson o ra z włąściwości:
NumberOfPeople o raz FancyDecorations . B ędzie Ci także
p o trz e b n a pryw atn a w łaściwość typu i n t o nazw ie actualLength .
(O w szem , także właściwości m ogą być pryw atne!).

class B irthdayP arty


{
p u b lic const in t CostOfFoodPerPerson = 25;

p u b lic in t NumberOfPeople { g e t; se t; }

Podczas iniejalieaeji obiektu BirthdayParty m usi


p u b lic bool FancyDecorations { g e t; s e t; }
on poznać liczbę uczestników przyjęcia, rodzaj
dekoracji oraz napis na torcie, aby w momencie
odwołania do w łaściwości Cake mógł wybrać
p u b lic s tr in g CakeWriting { g e t; se t; } odpowiednią wielkość tortu i zwrócić prawidłowy
koszt.

p u b lic B irth d a y P a rty (in t numberOfPeople,


bool fancyDecorations, s tr in g cakeW riting)
{ Konstruktor określa sta n o b ie k tu ^ r z y ^ i c
NumberOfPeople = numberOfPeople; z właściwości, dzięki czem u później obiekt
będzie mógł prawidłowo wyliczyć ko szty
FancyDecorations = fancyDecorations; imprezy.
CakeWriting = cakeW riting;
}

%
278 Rozdział 6.
Dziedziczenie

★ P otrzebu jesz właściwości typu s tr in g o nazw ie CakeW riting , k tó ra będzie


przechow yw ać napis n a torcie. Jeg o akcesor get jedynie zw raca zaw artość
p o la w ew nętrznego o nazw ie cakeW riting .

★ A kcesor set właściwości CakeWriting w pierwszej kolejności ustawia pole


cakeW riting . N astępnie sprawdza, czy napis nie jest zbyt długi, i ustawia pole
actualLength , zapisując w nim faktyczną długość napisu umieszczanego na torcie.

★ A kceso r se t właściwości CakeW riting m usi znać w ielkość to rtu (która


zm ienia się w zależności o d liczby gości) o raz m aksym alną liczbę liter, któ re
m ożn a um ieścić n a to rcie (zależnie o d jego w ielkości). W szystkie te obliczenia
będziesz wykonywał przy użyciu dw óch m etod.

p riv a te in t ActualLength Jeśli napis j e s t zb y t długi


dla wybranego tortu, to
{ w łaściw ość A c tu a lLe ngth
Także właściwości ,, get w yliczy faktyczną liczbę
mogą być y ' liter, które będzie można
{ um ieścić na torcie.
prywatne. Ta
posiada wyłącznie if (CakeWriting.Length > MaxWritingLength())
akcesor get, który return MaxWritingLength();
wylicza faktyczną, else
dtugość napisu na
torcie, używanego return CakeWriting.Length;
w dalszych
} Ten blok i f / e l s e
sp r a w d z a d łu g o ś ć
n a p is u i a k tu a liz u j e
p riva te in t CakeSize() { p ole a c tu a lL e n g th ,
i f (NumberOfPeople <= 4) z a p is u ją c w n im
lic zb ę lite r , k tó re
return 20; b ę d z ie możn a
else u m ie ś c ić na t j r c i e .
C zy z w r ó c iłe ś
u w a g ę n a to, return 40;
ż e p o m in ę liśm y }
n ie k tó re n a w ia s y
klam row e? Kiedy
blok kodu z a w ie r a p riv a te in t MaxWritingLength()
tylko je d n ą {
in s tr u k c ję , _n ie
tr ze b a z a p i ^ ^ w ^ i f (CakeSize() == 8)
go p o m ię d zy return 16;
n a w ia sa m i else
klam row ym i.
return 40;

f c o w e ‘ Poniiei
P ^ w e składnie fcr == ^

fotGnt i = O; i < 10; t+ + ) ^ 5;


P o T lie jo b (i);

jesteś tutaj ► 279


Krystyna pokocha nową wersję programu

Kontynuuj prace nad klasą B irthd ayP arty...

★ Z akończ tw orzenie klasy B i r t h d a y P a r t y , do d ając do niej w łaściwość C o s t . Z am iast


p o b iera ć koszt dekoracji i dodaw ać koszt napojów (co je st ro b io n e w klasie D i n n e r P a r t y ),
dodaje o n a koszt to rtu.

Ta właściw ość zwraca tru e, je ś li n a jis | j es t zb y t


/ " " " dłu gi, by mógł s ię zmieśdtZ na ^ r ^ . U ^ e m y J 31-1,
/ ? u g h . v „ . s t , ni e komunikat: „ Z byt dług i .

p u b l i c bool CakeWritingTooLong
{
get
{ Ta właściw ość definiuje
i f (C akeW riting .L e ng th > M axW ritingLength()) w yłącznie akcesor get, gdyż
w ogóle nie zm ienia stanu
return tru e; obiektu. Używa ona jed ynie pól
else i m etod do wyliczenia wartości
return fa lse ; logicznej.

}
Ta metoda j e s t taka sarna
p r i v a t e decimal C a lc u l a te C o s t O f D e c o r a t io n s ( ) ja k w klasie DinnerParty.
{
decimal c o s t O f D e c o ra t io n s ;
i f (F anc yD ec ora tion s)
c o s t O f D e c o ra t io n s = (NumberOfPeople * 15.00M) + 50M;
else
c o s t O f D e c o ra t io n s = (NumberOfPeople * 7.50M) + 30M;
r e t u r n c o s t O f D e c o r a t io n s ; Klasa BirthdayParty definiuje
} właściw ość Co s t typu
decimal, podobnie jak klasa
DinnerParty. Niemniej jednak
p u b l i c decimal Cost wykonuje ona inne obliczeniai
{ w których j e s t uż ywana
metoda CakeS iz e C) oraz
get właściw ość A c tu a ILength.
{
decimal t o t a l C o s t = C a l c u l a t e C o s t O f D e c o r a t i o n s ( ) ;
t o t a l C o s t += CostO fFoodPerPerson * NumberOfPeople;
decimal c akeC ost;
i f (C akeS ize() == 20)
\
Zerknij na poprzednią stronę
i dokładniej przyjrzyj się , jak w łaściwość
cakeCost = 40M + A ctu alL en gth * .25M; CakeWriting j e s t używana do okreś|enif
else wartości pola ActualLength. Jeśli tekst
cakeCost = 75M + A ctu alL en gth * .25M; j e s t z b y t długi, zwracana j e s t liczba
liter, które faktycznie zm ieszczą s ię na
r e t u r n t o t a l C o s t + cakeC ost; torcie. Kiedy maksymalna liczba liter
zo sta n ie przekroczona, ko szty nie będą
dalej zw iększane.

280 Rozdział 6.
Dziedziczenie

*
2r SKORZYSTAJ Z KONTROLKI TABCONTROL,
BY DODAĆ DO FORMULARZA ZAKŁADKI. Klikaj karty, aby
przełączać s ię między
Przeciągnij k o n tro lk ę TabControl z o k n a Toolbox n a fo rm u larz i zm ień jej ro zm iar tak, nimi. Użyj właściwości
aby zajm ow ała cały jego obszar. Z m ień te k st na każdej karcie, używ ając właściwości TabPages do zmiany
te k s tu każdej kontrolki.
TabPages — przycisk „ . . . ” znajduje się w oknie Properties o b o k właściwości. K iedy go Naciśnij przycisk
klikniesz, ID E wyświetli okno, za p o m o cą k tó reg o w prow adzisz d an e kart. U staw ich i wybierz właściwość
właściwość Text n a „Im p reza okolicznościow a” i „Przyjęcie u ro d zin o w e” . Text każdej karty.

0 PRZECIĄGNIJ KONTROLKI ZW IĄZANE


Z PLANOWANIEM PRZYJĘĆ
N A ODPOWIEDNIĄ ZAKŁADKĘ.
W nowym o knie ID E otw órz p ro je k t Planisty przyjęć utw orzony
w p o p rzed n im rozdziale. Z azn acz wszystkie k o n tro lk i form ularza,
skopiuj je, a n a stęp n ie wklej na nową kartę związaną
z planowaniem imprez okolicznościowych . A by upew nić się,
że k on tro lk i zo stan ą w klejone w odpow iednim m iejscu, będziesz
m usiał kliknąć wewnątrz k arty (w przeciw nym razie ID E wyświetli
k o m u n ik at o błędzie inform ujący o b ra k u m ożliwości d o d an ia
k o n tro le k do k o n te n e ra typu TabC ontrol ).
Kiedy przeciągniesz kontrolki
zw iązane z obsługą m p rez
M usisz przy tym p a m ię ta ć o jednej rzeczy: kiedy kopiujesz k o ntrolkę, okolicznościowych na odpowiednią
a następ n ie w klejasz ją do fo rm u larza, to dodajesz tylko ją sam ą — zakładkę, to będą one widoczne
wyłącznie po je j wybraniu.
wszystkie powiązane z nią procedury obsługi zdarzeń nie zostaną
skopiowane. Co w ięcej, d odatkow o będziesz m usiał spraw dzić
w artości właściwości (Name) dla każdej ze skopiow anych kontrolek.
U pew nij się, że ich nazwy są d okładnie takie sam e jak te, których
używaliśmy w rozdziale 5. W k ońcu d w ukrotnie kliknij k ażd ą z nich,
aby d odać do k o d u p u ste p ro ced u ry obsługi zdarzeń.

STWÓRZ INTERFEJS UŻYTKO W NIKA DLA PRZYJĘCIA URODZINOWEGO.


In terfejs te n b ędzie zaw ierał k o n tro lk ę NumericUpDown do ustaw iania liczby osób, k o n tro lk ę CheckBox
do w ybierania dekoracji fantazyjnych i Label z trójw ym iarow ą obw ódką do w yśw ietlania kosztów.
N a dalszym etap ie dodasz k o n tro lk ę TextBox służącą do p o b ra n ia napisu, któ ry m a się znaleźć n a torcie.

Kliknij kartę „Przyjęcie


urodzinowe", aby dodać
nowe kontrolki.
Karta używa kontrolek
NumericUpDowm CheckB ox
oraz Label p°d°bnie jak Dodaj pole teksto w e o nazwie
zakładka z imprezami cakeWriting do wprowadzania
okolicznościowym i. nap isu na torcie (i e ty k ie tę
Nadaj im nazw y, powyżej, aby użytkownik
odpowiednio'. numberBirthday, dokładnie wiedział, do czego
fancyBirthday, birthdayC o st. °n ° słu ży ). Użyj właściwości
Text do u s tawienia domyślnej
Dodaj kontrolkę Label o nazwie wartości na "Sto lat!".
tooLongLabel zaw ierającą te k s t
„ZBYT DŁUGI" i m ającą czerwone t o .
jesteś tutaj ► 281
Dokończ form ularz

Kontynuuj prace nad kodem obsługi form ularza...

5 POŁĄCZ WSZYSTKO W CAŁOŚĆ. ^


W szystkie elem en ty są ju ż gotow e; aby u ru ch o m ić kontro lk i, m usim y jeszcze n apisać napraw dę
krótki i pro sty kod.

# W klasie fo rm u larza p o trzeb u jesz pól, w których zapiszesz referen cje do obiektów B i r t h d a y P a r t y
i D i n n e r P a r t y . M usisz tak że określić w artości początkow e tych p ó l w k o n stru k to rze form ularza.

# D ysponujesz ju ż k odem do obsługi zd arzeń zw iązanych z k o n tro lkam i n a zakładce im prez


okolicznościow ych — znajdziesz je w pro jek cie z rozdziału 5. Jeśli jeszcze nie kliknąłeś dw ukrotnie
k o n tro le k NumericUpDown o ra z CheckBox n a karcie im p rez okolicznościow ych, to zrób to teraz.
N astęp n ie skopiuj k o d p ro c e d u r obsługi zd arzeń z p o p rzed n iej w ersji p ro g ra m u i wklej go do
now ego p ro jek tu . Poniżej zam ieściliśm y nowy k o d form ularza.

p u b lic p a r t ia l c l a s s Forml : Form { Instancja klasy BirthdayParty j e s t


inicjalizowana w konstruktorze
D in n e rP a r ty d i n n e r P a r t y ; formularza, tak samo ja k instancja
B irthdayP arty b ir th d a y P a rty ; klasy DinnerParty.
p u b l i c Form1() {
In itializeC o m p o n en t();
d i n n e r P a r t y = new D inn erP arty ((in t)n u m e ric U p D o w n 1 .V alu e ,
h ealth yB ox .C h ecked , fancyB ox.C hecked);
D i s p l a y D i n n e r P a r t y C o s t( ) ;

b i r t h d a y P a r t y = new B i r t h d a y P a r t y ( ( i n t ) n u m b e r B i r t h d a y . V a l u e ,
f a n c y B ir th d a y .C h e c k e d , c a k e W r i t i n g . T e x t ) ;
D isp lay B irth d ay P arty C o st();

/ / Procedury o b s ł u g i z d a rz e ń d l a k o n t r o l e k fancyBox, healthyB ox


/ / o raz numericUpDownl, j a k również metoda D i s p l a y D i n n e rP a rt y C o s t() s ą t a k i e same
/ / j a k a n a l o g i c z n e fu n k c j e i metody z astosow ane w p r o j e k c i e w r o z d z i a l e 5.

# D o p ro c e d u r obsługi zd arzeń kontro lk i NumericUpDown dodaj k o d ustaw iający w arto ść właściwości


NumberO fPeople, a n a stęp n ie dodaj k o d obsługujący p o le w yboru Fantazyjne dekoracje:

p r i v a t e vo id num b erB irthd ay_ V alueC han ged (o bject s e n d e r , EventArgs e) {


b ir th d ay P a rty .N u m b erO fP eo p le = (in t) n u m b e r B ir th d a y .V a lu e ;
D ispl a y B i r t h d a y P a r t y C o s t ( ) ; p rocedury obstugi tych kontrolek s ą takie sam e jak
} w p rzy p adku analog icznych kontrolek na zakładce
£ doty czącej imp rez okolicznościowych.

p r i v a t e vo id fa n c y B irth da y_C h eck ed C h an ged (o bje ct s e n d e r , EventArgs e) {


b ir th d a y P a r t y . F a n c y D e c o r a t i o n s = fa n c y B irth d a y .C h e c k e d ;
D isp lay B irth d ay P arty C o st();
}

282 Rozdział 6.
Dziedziczenie

# Skorzystaj ze strony Events w o knie Properties, aby d odać p ro ced u rę


obsługi zd arzen ia TextChanged dla p o la cakeW riting . By p rzejść n a tę
stro n ę, kliknij przycisk z ik o n ą błyskawicy w o knie Properties. Z rozw ijanej
listy u góry teg o o k n a w ybierz k o n tro lk ę cakeWriting, p o czym przew iń
zaw artość, aż pojaw i się zd arzen ie TextChanged. Kliknij je dw ukrotnie,
by d odać now ą funkcję do jego obsługi.
Kiedy wybi e rze sz pole
cakeWriting, odszukaj
na liście zdarzenie
TextChanged i dwukrotnie
kliknij jego w iersz.
W odpowiedzi IDE doda
do kodu formularza nową
funkcję obsługi zdarzenia,
która będzie wywoływana
za każdym razem, gdy
zm ieni s ię zaw artość
p ola tekstow ego.

p riv a te void cakeWriting_TextChanged(object sender, EventArgs e) {


birthdayParty.CakeW riting = cakeW riting.Text;
D isplayBirthdayPartyC ost();
}

# N apisz now ą m eto d ę D isp la yB irth d a yP a rtyC o st() i dodaj ją do wszystkich


p ro c e d u r obsługi zd arzeń pow iązanych z k o n tro lk am i obsługującym i
przyjęcia urodzinow e. D zięki tem u p o le tekstow e z kosztem im prezy będzie
aktualizow ane autom atycznie, ja k tylko zm ienią się jakiekolw iek ustaw ienia.

Kontrolki dysponują Klasa BirthdayParty udostępnia tę w łaściw ość, Dzięki odpowiedniej


w łaściw ością V isible, dzięki czem u formularz może w yśw ietlić o strzeżen ie. herm etyzacji klasy
która określa, czy BirthdayParty kod
s ą widoczne, czy też
znikają z formularza.
p riva te void DisplayBirthdayPartyCost() {
\
tooLongLabel.Visible = birthdayParty.CakeWritingTooLong
is formularza obsługujący
zm iany napisu na torcie
może być naprawdę
prosty. W zasadzie
decimal cost = birthdayP arty.C ost; sprowadza s ię on do
birthdayC ost.Text = c o s t.T o S trin g ("c "); ustaw ienia wartości
właściwości obiektu
na podstaw ie informacji
odczytanych z kontrolek
} Cała inteligencja związana z obsługą napisu na torcie — obiekt zadba
ie, ^
liczby osób oraz wielkości tortu została wbudowana o całą resztę.
w akcesory s e t właściwości Numbe rOfPe j p |e oraz
CakeWriting. Formularz może s i ę ogt-amczyć j edy nia
do ustaw iania i w yśw ietlania ich iw^ ^ śc 1.

To wszystko — form ularz jest gotowy!

jesteś tutaj ► 283


To żyje!

%
NO W A WERSJA PROGRAMU JEST GOTOWA — CZAS JĄ WYPRÓBOWAĆ!
U pew nij się, że p ro g ram działa dokład n ie tak, ja k teg o oczekujesz. Spraw dź, czy wyświetla
się odpow iedni ko m u n ik at, gdy napis n a to rcie je st zbyt długi. Z obacz, czy cen a je st zawsze
popraw n a. Jeżeli wszystko działa praw idłow o, zad an ie m o żn a uzn ać za wykonane!

Uruchom program i przejdź na kartę


„Im preza okolicznościowa". Upewnij
s ię, że działa ona tak samo ja k
w ersja programu z poprzedniego
rozdziału.

Czy obliczenia s ą
wykonywane prawidłowo?
W tym przypadku 10 osób
oznacza 25 złotych od
osoby (250) plus 75 z ł
za 40-centym etrow y tort,
p lus 7,50 z ł od osoby
za zw yczajne dekoracje,
plus dodatkowe 3 0 z ł
opłaty za ozdoby, plus
0,25 z ł za każdą literę
w napisie (je s t ich 24,
co daje 6 zł).
Kliknij kartę „Przyjęcie
urodzinowe". Upewnij s i ę
ż e cena zm ienia s ię po zm ianie
liczby osób lub zaznaczeniu pola
wyboru „Dekoracje fantazyjne".

A zatem mamy:
2 5 0 + 75 + 75 + 3 0 + 6 = 436 zł.
Zadziałało!

Podczas modyfikowania te k s tu w polu


„Napis na torcie" procedura obsługi
zdarzenia TextChanged powinna
aktualizować ko szt urodzin za każdym
razem, gdy dodasz lub u su n iesz m a k.

J eśli nap is j e s t z b y t długi, by mógł się

%
z m ieścić na torcie, klasa BirthdayParty
u s tawia wartość właściwości
CakeWritingTooLong na true, a podczas
obliczania kosztu przyjęcia u żywa
maks y malnej idługości napisu. Formularz
nie m usi wykonywać żadnych obliczeń.

%
284 Rozdział 6.
Dziedziczenie

Jeszczejedna rzecz... Czy możesz dodać opłatę


100 zł za przyjęcia dla ponad 12 osób?
K rystyna zrobiła doskonały in teres, korzystając z T w ojego p ro g ram u . W tej chwili
m oże sobie naw et pozw olić n a p o b ieran ie większych o p ła t o d dużych klientów .
C o zatem należałoby zm ienić, aby p ro g ram doliczał dod atk o w ą o p łatę?

k Z m ień m eto d ę D innerP arty.C ost w sposób następujący:


jeżeli NumberOfPeople je st w iększe niż 12, dodaj kw otę 100 zł.

k Z ró b dokładnie to sam o z m e to d ą B irth d a yP a rty .C o s t .

Pośw ięć m inutę i pom yśl, w jaki sposób d odać o p łatę zarów no do klasy D innerP arty ,
ja k i B irth d a yP a rty . Jak i napisałbyś w tym celu k od? G dzie m ógłby się o n znaleźć?

C ałkiem p ro s te ... ale co by się stało, gdybyś m iał trzy p o d o b n e klasy? L ub cztery?
L ub dw anaście? A lbo gdybyś m usiał zarządzać tym k odem i w prow adzać do niego
p o tem więcej takich zm ian? Co by było, gdybyś m iał w prow adzić dokładnie
taką sam ą zm ianę do pięciu lub sześciu blisko związanych ze sobą klas?

Masz rację! Posiadanie takich samych fragmentów kodu


w różnych klasach jest nieefektywne i może prowadzić
do powstania błędów.
Na szczęście dla nas C# oferuje lepszy sposób na tworzenie klas, które
są ze sobą blisko związane i podobnie się zachowują: dziedziczenie.

jesteś tutaj ► 285


Nie potrzeba złota, gdy coś błyszczącego będzie równie dobre

Kiedy klasy używają dziedziczenia,


kod musi być napisany tylko raz
N ie jest przypadkiem to, że klasy D in n e r P a r ty i B ir t h d a y P a r t y m ają m nóstw o takiego
sam ego kodu. K iedy piszesz pro g ram y w C # , często tworzysz klasy rep rez en tu jąc e rzeczy
ze św iata realnego, a te są zwykle ze so b ą pow iązane. T w oje klasy m ają podobny kod ,
poniew aż zdarzenia w świecie rzeczywistym, k tó re re p re ze n tu ją — przyjęcie urodzinow e
i im preza okolicznościow a — przebiegają podobnie .

DinnerParty BirthdayParty p rzyjęcie urodzinowe


NumberOfPeople NumberOfPeople wymaga przetwarzania
FancyDecorations FancyDecorations liczby osób i kosztów
Cost Cost dekoracji prawie
HealthyOption CakeSize tak samo jak
Krystyna m ^
określić koszt CakeWriting w przypadku imprezy
CalculateCostOfDecorations() okolicznościowej.
swoich przyjęć CalculateCost CalculateCostOfDecorations()
niezależnie od c OfBeveragesPerPerson() CakeSize()
tego, jaki to MaxWritingLength()
rodzaj imprezy.

Zarówno imprezy okolicznościowe, ja k i urodzinowe są przyjęciami


K iedy posiadasz dwie klasy, k tó re są szczegółowym i re p rezen tacjam i czegoś bardziej
ogólnego, m ożesz je dziedziczyć z tej sam ej klasy. G dy zrobisz coś takiego, k ażd a z nich
stanie się podklasą tej sam ej klasy bazowej.

Party Sposób odczytywania


NumberOfPeople liczby osób i wyliczania
FancyDecorations całkowitych kosztów j e s t
Oba rodzaje przyjęć Cost dla obu rodzajów przyjęć
mu s z ą przechowywać podobny, ale różny. Możemy
informacje na tem a t rozdzielić zachowania tak,
liczby osób oraz private methods: aby podobne znajdowały s ię
kosztów dekoracji, CalculateCostOfDecorations() w klasie bazowej, natom iast
więc możes z przerzucić różne zo sta ły um ieszczone
j e do klas y bazowej. w dwóch klasach potomnych.

Ta strzałka
w diagramie DinnerParty BirthdayParty
klas oznacza, Obie klasy
HealthyOption potomne CakeSize
że DinnerParty
Cost dziedziczą CakeWriting
dziedziczy
z klasy Party. obliczenia Cost
dotyczące
dekoracji
private m ethods: z bazowej, private methods:
CalculateCost więc nie CakeSize()
OfBeveragesPerPerson() m uszą ich MaxWritingLength()
zawierać.

286 Rozdział 6.
Dziedziczenie

Zbuduj model klasy, rozpoczynając od rzeczy ogólnych


i przechodząc do bardziej konkretnych
P rogram y C # używ ają dziedziczenia, poniew aż w te n sposób m o żn a naśladow ać
związki w ystępujące w świecie rzeczywistym. Praw dziw e rzeczy zwykle p o siad ają pew n ą
hierarchię , k tó ra szereguje je o d najbardziej ogólnych do szczegółowych. Twoje
p ro gram y m ają sw oją w łasną hierarchię klas , k tó ra służy tym sam ym celom . W Twoim
m o d elu wszystkie klasy p o ło żo n e niżej dziedziczą po tych um ieszczonych wyżej.

Ogólne Ogólne

A W modelu klas s er
może dziedziczyć Każdy ptak j e s t
A
po produkcie zw ierzęciem , ale nie
mleczarskim, każde zw ierzę j e s t
Jed zen ie który z kolei ptakiem. Z w ierzę
moż e dziedziczyć
po jedzeniu.

P r o d u k t m leczarsk i P tak

Dla k°g °ś, kto szuka


zw ierzątka, każdy
Ser ś p iewają c y ptak będzie P tak ś p ie w a ją c y
d°t>ry, jednak dla
°rnit° h g a zgłębiającego
w iedzę na tem a t rodziny
przedrzezn iacz y m ieszanie
przedrzeź n iacz y północnych
Ser c h e d d a r i p ° łudni°wych j e s t nie P rzed rzeźn iacz
do zaakceptowania.

D ojrzały se r c h e d d a r P rz e d rz e ź n ia c z p ó łn o cn y

Coś, co znajduje ^ ^ y s Z a ^ b u t y
dziedziczy
Szczegółowe z klas powyżej Szczegółowe
to samo dotyczy każdego
s i ę w pary, więc
przedrzeźniacza pofnocnego.
J eżeli mas z p rzepis, w którym
w ym ieniony j e s t se r cheddar,
m ożesz użyć dojrzałego sera D ziedziczyć, czasownik
cheddar. Je <jnak gdy przepis
wymaga użycia sera dojrzałego, — przejmować po rodzicach albo przodkach
to nie mo żesz użyć pierwszego
leps zego — p otrzeb u je sz akurat cechy fizyczne i psychiczne. Chciałaby, aby
teg° k°n kretneg°, dojrzałego sera. dziecko odziedziczyło ¡ej duże, brązowe oczy
a nie drobne, niebieskie oczy męża.

jesteś tutaj ► 287


Tam jest zoo

Wjaki sposób zaprojektowałbyś symulator zoo? Terminy rodzic,


klasa nadrzędna
Lwy, tygrysy i niedźw iedzie b ru n a tn e ... o rany! D o tego jeszcze h ipopotam y, wilki i, i klasa bazowa są
gościnnie, koty. Tw oim zad an iem je st zapro jek to w an ie p ro g ra m u sym ulującego zoo. często używane
(N ie ekscytuj się za b ard zo — nie zam ierzam y tworzyć kodu, a jedynie zaprojektow ać zamiennie. Także
klasy rep rezen tu jące zw ierzęta). term iny rozszerzać
i dziedziczyć po
D ostaliśm y listę niektórych zw ierząt, k tó re zn ajd ą się w p ro g ram ie, ale nie wszystkich. oznaczają to samo.
W iem y, że każde będzie rep rezen to w an e p rzez o b iek t, a te n b ędzie się p o ru szał Synonimami są
po sym ulatorze, w ykonując czynności, do których k o n k retn e zw ierzę zostało także term iny
zaprogram ow ane. klasa potomna
oraz podklasa.
N ajw ażniejsze ze wszystkiego jest to, że chcem y aplikacji, k tó ra będzie łatw a
do zarządzania dla innych program istów , ta k aby m ogli oni dodaw ać później
do sym ulatora w łasne zw ierzęta. r
Niektórzy używ ają terminu „klasa
Jaki je st zatem pierw szy krok? Z a n im zaczniem y mów ić o konkretnych zw ierzętach, bazowa" wyłącznie w odniesieniu
do klasy znajdującej s ię na górze
m usim y znaleźć podstawowe cechy, w spólne dla nich, i w yszczególnić abstrakcyjne
drzt w° d - l edzicze nia j e d n a k nie
charakterystyki, k tó re p o siad ają wszystkie stw orzenia. N a dalszym etap ie m ożem y na_SAM£ J gdrze, gdyż każda klas,
um ieścić takie charakterystyki w klasie, z której wszystkie klasy zw ierząt b ęd ą dziedziczy p o klasie O bject lub
jakiej ś innej klasie, która po n iej
dziedziczyły. dziedziczy.

ZNAJDŹ PODOBIEŃSTWA U WSZYSTKICH ZWIERZĄT.


Przyjrzyj się dokład n ie sześciu zw ierzętom . C o lew, h ip o p o tam , tygrys,
kot, w ilk i dalm atyńczyk m ają ze so b ą w spólnego? W jaki sposób są
o n e ze so b ą pow iązane? M usisz odpow iedzieć n a te pytania, aby Twój
m o d el klasy zaw ierał wszystkie m ożliw e związki.

288 Rozdział 6.
Dziedziczenie

Użyj dziedziczenia w celu uniknięcia powielania


kodu w klasach potomnych STWÓRZ KLASĘ BAZOWĄ,
ABY DAĆ ZWIERZĘTOM
Już wiesz, że pow ielanie k o d u je st do niczego. Je st tru d n iejsze i zawsze WSZYSTKO TO, CO MAJĄ
prow adzi do bólu głowy, gdy chcesz osiągnąć jakiś cel. W ybierzm y w ięc p o la
WSPÓLNE.
i m etody dla klasy bazow ej Animal , k tó rą będziesz musiał napisać tylko raz
P ola, właściwości i m etody
i po której każda z klas p o tom nych będzie m ogła dziedziczyć. R ozpocznijm y
klasy bazow ej dad zą wszystkim
o d p ó l publicznych:
zw ierzętom dziedziczącym w spólny
★ Pi c tu re : rysunek, któ ry um ieścisz w k o n tro lce PictureBox . stan i zachow anie. W szystkie
elem en ty są zw ierzętam i, w ięc klasę
★ Food: typ jedzenia, którym żywi się dane zw ierzę. W tej chwili m ogą
bazow ą m o żn a nazw ać Animal.
to być tylko dwie w artości: m ięso i traw a.
★ Hunger: p o le typu i n t rep rez e n tu ją ce poziom głodu zwierzęcia.
Jego zm iany zależą o d tego, kiedy (i ile) zw ierzę jadło.
★ Boundari es : referen cja do klasy przechow ującej w ysokość, szerokość
i położenie w ybiegu, w którym zw ierzę będzie się poruszało.
★ Locati on: w spółrzędne X i Y o kreślające poło żen ie zwierzęcia.

K lasa Animal m a także cztery m etody, k tó re zw ierzęta m ogą dziedziczyć:

★ MakeNoi se ( ) : m e to d a , k tó ra pozw ala zw ierzęciu w ydać dźwięk.


★ Eat ( ) : zachow anie stw orzenia w p rzy p ad k u n ap o tk a n ia p rzez nie
prefero w an eg o ro d zaju po k arm u .
★ Sl eep ( ) : m e to d a pozw alająca zw ierzęciu położyć się i uciąć sobie
drzem kę.
★ Roam(): służy do p o ru szan ia się zw ierząt p o ich w ybiegu w zoo.

ybór klasy bazowej


o Twoja decyzja .
ógłbyś postanowić,
ze będziesz używ ał k a sy
ZodOccupant, która
będzie d efin ijw ała fo s zty
wyżyw ienia i opieki.
Mogłaby to być k h s a ^
A ttraction z m e tjd ami, ^
które określałyby sp osób
skupiania na s j bie uwagi
odwiedzających z ^ .
M yślimy, że klasa A n imal
j e s t w tym przypadku
najbardziej sensow na.
Zgadzasz s ię?

jesteś tutaj ► 289


Ostrzeżenie: nie karmić program istów

Różne zwierzęta wydają różne dźwięki


Lwy ryczą, psy szczekają, a z tego, co wiemy, h ip o p o tam y nie wydają To, ż e metoda lub właściwość j e s t
żadnych dźwięków. K ażd a klasa dziedzicząca p o Animal będzie m iała w klasie bazowej, nie oznacza
m eto d ę MakeNoise() , ale każd a z tych m eto d będzie działała w nieco inny wcale, że każda klasa potomna
m usi używ ać je j w te n sam
sposób i będzie m iała inny kod. K iedy klasa p o c h o d n a zm ienia zachow anie sposób... lub w ogóle je j używaĆ!
jednej z odziedziczonych m eto d , w tedy mówim y, że ją przesłania .

Pomyśl, co chcesz przesłaniać


OKREŚL, CO KAŻDE
K ażde zw ierzę m usi jeść. Pies m oże żywić się niew ielkim i kaw ałkam i ZWIERZĘ ROBI INACZEJ
m ięsa, podczas gdy h ip o p o tam p o trafi spożyć całą m asę trawy. Ja k NIŻ KLASA ANIM AL
w yglądałby kod dla takiego zachow ania? Z aró w n o pies, ja k i h ip o p o tam — LUB CZEGO NIE ROBI
przesłonią m eto d ę E a t() . M eto d a h ip o p o ta m a m ogłaby „spożyć”, W OGÓLE.
pow iedzm y, 6 kilogram ów sian a przy każdym jej wywołaniu. Z drugiej Co takiego ro b i je d e n rodzaj
strony m eto d a Eat() p sa m ogłaby zredukow ać zapasy je d ze n ia w zoo zw ierząt, czego inne nie robią?
o 30 dekagram ów karm y dla psów. Psy je d z ą p o k arm dla psów,

r
w ięc ich m e to d a Eat() będzie
m usiała przesłonić A nim al.Eat() .
H ip o p o ta m pływa, w ięc będzie
Jeśli ju ż posiadasz kla sę m iał dod atk o w ą m e to d ę Swim()
potomną dziedziczącą
po klasie bazowej, to musi n ieo b ec n ą w klasie Animal.
ona dziedziczyć w szy stkie
je j zachowania... a.ie może s z
zmodyfikować je w klasie
potomnej tak, aby nie były
identyczne. To do tego słu ży
przestanianie .

WYSIL
SZARE KOMÓRKI
Już wiemy, że niektóre zwierzęta przesłaniają metody MakeNoise() oraz E a t() .
Które z nich będą przesłaniały Sleep () i Roam()? Czy są takie? Jak będzie
wyglądała sprawa z właściwościami — które zwierzęta będą przesłaniały
pewne z nich?

290 Rozdział 6.
Dziedziczenie

Pomyśl, wjaki sposób pogrupować zwierzęta


D ojrzały ch ed d ar jest ro d zajem sera, k tóry jest p ro d u k te m m leczarskim ,
a ten z kolei jest rod zajem pożyw ienia. D o b ry m o d el je d ze n ia pow inien
to reprezentow ać. N a szczęście dla nas C # pozw ala nam to wszystko robić
w łatwy sposób. M ożesz stworzyć łańcuch klas, k tó re dziedziczą je d n a p o drugiej,
rozpoczynając od klasy n a szczycie h ierarch ii i schodząc pow oli w dół. M ożesz
m ieć klasę Food i klasę p o to m n ą D airyP roduct , k tó ra stan ie się bazow ą dla klasy
Cheese. T a z kolei m oże m ieć klasę p o to m n ą Cheddar, z któ rej dziedziczyć będzie
AgedVermontCheddar.

POSZUKAJ KLAS, KTÓRE M AJĄ ZE


SOBĄ WIELE WSPÓLNYCH CECH.
Czy psy i wilki nie w ydają się dość p o d o b n e?
Jed n e i drugie n ależą do rodziny psow atych
J e s t duże
i m ożna się założyć, że podczas ich obserw acji
^aiwdopodobieństwo,
zauw ażym y sp o ro podobieństw . P raw d o p o d o b n ie że będziem y
jed zą w ten sam sposób i ta k sam o śpią. mogli dodać klasę
Canine, z której
C o w p rzypad k u kotów dom ow ych, tygrysów dziedziczyłyby
i lwów? O kazuje się, że cała tró jk a p o ru sza się za równo psy,
jak i wilki.
w swoim naturalnym środow isku p o dobnie.
Prawd°p°d°bnie mają
Z całą pew nością będziesz w stan ie um ieścić ° ne ja k ie ś wspólne
klasę F eline pom iędzy Animal o raz klasam i zachowania,
takie ja k spanie
tych trzech przedstaw icieli rodziny kotow atych. w leg°w iskach.
Pozw oli to u n ik n ąć n iep otrzeb nej p rodukcji
nadm iarow ego kodu.

Klasy potomne
d ziedziczą po
klasie Animal
w szy stkie
cztery metody,
ale m usim y
przes ł°nić jedynie
M akeN °ise()
oraz Eat().

1
To dlatego
pokazujem y tylko
dw ie metody
w diagramach
klas.

A co by było, gdybyśm y dodali do klasy


H'p p ° m etodę Sw im ()? *

jesteś tutaj ► 291


Rozszerz swoje obiekty

Stwórz hierarchię klas


K iedy tworzysz klasę m ającą jakąś klasę bazow ą o raz w łasne klasy
A n im al
p o to m n e, k tó re rów nież p o siad ają w łasne klasy p o to m n e, to ta k ą
konstrukcję nazyw am y hierarchią klas . T o coś więcej niż tylko u n ik an ie Picture
w ielokrotnego pisania kodu, chociaż je st to niew ątpliw ie znacząca korzyść Food
płynąca z zastosow ania rozsądnej hierarchii. Jeżeli je d n a k p o trak tu jem y Hunger
to b ardzo pow ażnie, okaże się, że najw iększą korzyścią będzie u tw orzenie Boundaries
Location
k odu łatw ego do zrozu m ien ia i zarządzania. P atrząc n a k o d sym ulatora
zoo i w idząc m eto d ę lub w łaściwość zdefiniow aną w klasie F e l i n e , MakeNoise()
będziesz potrafił natychmiast powiedzieć, że m asz do czynienia z czymś, Eat()
co jest w spólne dla wszystkich kotów . T w oja h ierarch ia stan ie się m apą Sleep()
pozw alającą Ci odnaleźć właściwą drogę w pro g ram ie. Roam()

------
[Ą D O K O Ń C Z H IE R A R C H IĘ KLAS.
T eraz, gdy już wiesz, w jaki sposób podzielić Psy i wilki
je d zą i śpią
zw ierzęta, m ożesz d odać klasy F e l i n e o raz C a n in e . w podobny
sposób, jednak
wydają inne
dźwięki.
W zw iązku z tym , że
Feline przestania RoamU,
wszystko, co po niej
dziedziczy, otrzyma nową
metodą RoamO zam iast tej
z klasy Animal.

Trzy koty wędrują


w ten sam sposób,
więc wspótdzielą
dziedziczoną
z Feline m etodę
RoamO. Każdy
z nich w dalszym
ciągu j e co in n e g o ____ £
i wydaje inny dźw ięk, i
więc w szystkie
przestaniają EatQ
oraz MakeNoiseO,
które s ą dziedziczone
z klasy Animal.

292 Rozdział 6.
Dziedziczenie

Każda klasa pochodna Hierarchia, rzeczowmk


rozszerza klasę bazową — układ elementów jakiejś struktury
uporządkowanych od najwyższych
N ie jesteś ograniczony do m eto d , k tó re klasa p o ch o d n a do najniższych według okreś|onego kryterium-
dziedziczy p o b a z o w e j. ale już to wiesz! Swoją drogą, przez
Dyrektor Dynamco rozpoczynał pracę
cały czas tw orzyłeś w łasne klasy. K iedy używasz dziedziczenia od zajmowania się korespondencją' ? teraz
osiągnął szczyt w firm owej hierarchii.
dla jednej z nich, to ją rozszerzasz p o p rzez d o d an ie do niej pól,
właściwości i m e to d z klasy bazow ej. Jeżeli chcesz d odać m eto d ę
F etch() do klasy psa, to je st to całkow icie n o rm aln e. N ie będzie
o n a w tedy niczego dziedziczyła ani przesłan iała, p ies nato m iast
dostanie now ą m eto d ę , k tó ra nie b ędzie m iała wpływu n a klasy
W olf, Canine , Animal, Hippo i wszystkie inne.

tworzy nowy obiekt typu Dog Dog spot = n

wywołuje wersję z klasy Dog spot.MakeNoi

ywołuje wersję z klasy Animal spot.Roam();

ywołuje wersję z klasy Canine spot.Eat();

wywołuje wersję z klasy Canine spot.Sleep()

wywołuje wersję z klasy Dog spot.Fetch()

C# zawsze wywołuje metodę z klasy najbardziej szczegółowej


Jeżeli rozkażesz obiektow i Dog p rzech ad zać się p o okolicy, to istnieje tylko jed n a
m eto d a, k tó ra m oże się w ykonać — ta w klasie Animal. Je d n a k co w tedy, gdy wydasz
p su polecenie w ydania odgłosu? K tó ra w ersja MakeNoise() zostanie w ywołana?

W zasadzie nie je st tru d n o odpow iedzieć n a to pytanie. M e to d a w klasie Dog


zap rezen tu je Ci, jaki odgłos w ydają psy. Jeśli znajduje się o n a w klasie Canine ,
to pozw ala określić odgłos przedstaw icieli rodziny psow atych. G dyby była
to klasa Animal, to m o żn a by pow iedzieć, że definicja zachow ania je st zbyt
ogólna, aby m ogła dotyczyć wszystkich zw ierząt. K iedy w ięc każesz p su dać głos,
C # w pierwszej kolejności b ędzie szukał w klasie Dog, aby znaleźć zachow anie
specyficzne dla psów. Jeśli klasa Dog takow ego nie po siad a, C # spraw dza klasę
Canine , a n astęp n ie Animal.

jesteś tutaj ► 293


Jak nisko możesz zejść?

Aby dziedziczyć po klasie bazowej, użyj dwukropka Kiedy klasa


Kiedy piszesz klasę, możesz użyć dwukropka (:) do dziedziczenia po jej klasie
dziedziczy
bazowej. W ten sposób tworzysz klasę pochodną i dajesz jej wszystkie pola, po klasie bazo­
właściwości i metody z nadrzędnej.
wej, wtedy
Vertebrate p u b lic c la ss V e rte b ra te wszystkie pola,
NumberOfLegs
{ właściwości
p u b lic i n t N u m berO fL eg s;
p u b lic v o id E a t() {
i metody tej
Eat()
// k o d p o z w a la ją c y z w ie r z ę c iu j e ś ć drugiej zostają
} automatycznie
Bird używ a dwukropka do dzied ziczenia
po klasie bazowej Vertebra te. Oznacza
dodane do klasy
to, że w ykorzystuje wsz y s tkie je j
właściwości i m et° dy.
pochodnej.
public class Bird : Vertebrate

{
R°z s z erza sz klasę
p u b l i c d o u b l e W in g s p a n ; poprzez dodanie
dwukr°pka na końcu
p u b lic v o id F ly ( ) { j ej deklaracji,
a następnie
// k o d p o z w a l a j ą c y p ta k o w i la ta ć
wpisanie klasy,
} po której dziedziczy.

p u b lic b u tto n 1 _ C lic k (o b je c t sen d er, E ventA rgs e) {


tw eety j e s t B i r d t w e e t y = new B i r d ( ) ;
instancją Bird,
więc s t andardowo — tw e e ty .W in g sp a n = 7 .5 ; W zw iązku z tym , że kNsa Bird
posiada pola * dziedziczy po V erte b ra te, każda
tw e e ty .F ly ();
i m etody klasy Bird. ¡ej instancja także Po s'ad“f ° “
t w e e t y .N u m b e r O f L e g s = 2; i metody zdefiniowane w klasie
V erte b ra te.
tw e e ty .E a t();

i Nie. istnieją. }
głupie pytania

P : Dlaczego strzałka wskazuje do góry, od klasy ona nawet „świadoma", że jakaś nowa klasa po niej dziedziczy.
pochodnej do bazowej? Czy diagram nie wyglądałby Jej metody, pola i właściwości pozostają dokładnie takie same.
lepiej ze strzałką skierowaną w dół? Klasa pochodna, z drugiej strony, zdecydowanie zmienia swoje

O : Mogłoby to wyglądać lepiej, ale nie byłoby to właściwe.


zachowanie. Każda jej instancja automatycznie pobiera wszystkie
właściwości, pola i metody klasy nadrzędnej. Wszystko to dzieje się
Kiedy tworzysz klasę dziedziczącą po innej, tworzysz do niej relację
po dodaniu dwukropka. To dlatego rysujesz na diagramie strzałkę
— klasa bazowa pozostaje taka sama. Ma to sens, gdy myślisz
wychodzącą z klasy pochodnej i wskazującą na klasę bazową,
o wszystkim z perspektywy klasy bazowej. Jej zachowanie nie jest
po której ona dziedziczy.
w ogóle zmieniane podczas dodawania klasy potomnej. Nie jest

294 Rozdział 6.
Dziedziczenie
«^Zaostrz ołówek
Przyjrzyj się dokładnie modelom klas i deklaracjom, a następnie zakreśl
instrukcje, które nie działają.

pub lic class A ir c ra ft {


p u b lic double AirSpeed;
p u b lic double A ltitu d e ;
p u b lic void TakeOff() { . . . }
p u b lic void Land() { . . . }
}

pub lic class FirePlane : A ir c ra ft {


p u b lic double BucketCapacity;
p u b lic void F illB u c k e t() { . . . }
}

pub lic void F ireF ightingM ission() {


BucketCapacity FirePlane myFirePlane = new FirePlane();
new FirePlane.BucketCapacity = 500;
A ir c r a ft.A ltitu d e = 0;
myFirePlane.TakeOff();
FillBucket() myFirePlane.AirSpeed = 192.5;
m yFireP lane.F illB ucket();
A irc ra ft.L a n d ();
}

pub lic class Sandwich {


Sandwich
p u b lic bool Toasted;
Toasted
SlicesOfBread p u b lic in t SlicesOfBread;
p u b lic in t CountCalories() { . . . }
}
CountCalories()
pub lic class BLT : Sandwich {
p u b lic in t SlicesOfBacon;
p u b lic in t AmountOfLettuce;
p u b lic in t AddSideOfFries() { . . . }
}

pub lic BLT OrderMyBLT() {


SlicesOfBacon BLT mySandwich = new BLT();
AmountOfLettuce BLT.Toasted = tru e ;
Sandwich.SlicesOfBread = 3;
mySandwich.AddSideOfFries();
AddSideOfFries() mySandwich.SlicesOfBacon += 5;
MessageBox.Show("Moja kanapka ma
+ mySandwich.CountCalories + k a lo r ii. " ) ;
return mySandwich;
}

jesteś tutaj ► 295


M ogę wymyślić sposób latania pingwinów...

i— Zaostrz ołówek
Przyjrzyj się dokładnie modelom klas i deklaracjom,
Rozwiązanie a następnie zakreśl instrukcje, które nie działają.

Aircraft pub lic class A ir c ra ft {


AirSpeed p u b lic double AirSpeed;
Altitude p u b lic double A ltitu d e ;
p u b lic void TakeOff() { . . . }
p u b lic void Land() { . . . }
TakeOff() }
Land()
pub lic class FirePlane : A ir c ra ft {
p u b lic double BucketCapacity;
p u b lic void F illB u c k e t() { . . . }
} To n,e jest sposób,

FirePlane pub lic void F ireF ightingM ission() { M Z L g z ; 1' a,°“°


BucketCapacity FirePlane myFirePlane = new FirePlane();
new FirePlane.BucketCapacii
A ir c ra ft.A ltitu d e
m yFirePlane.Takeoff();
FillBucket()
myFirePlane.AirSpeed = 192.5
m yFireP lane.F illB ucket();
A irc ra ft.L a n d () instancji myfireP/ane.

Sandwich pub lic class Sandwich {


Toasted p u b lic bool Toasted;
SlicesOfBread p u b lic in t SlicesOfBread;
p u b lic in t CountCalories() {
}
CountCalories()
pub lic class BLT : Sandwich {
p u b lic in t SlicesOfBacon; je wywołać w nieprawidłowy
sposób, używając nazw klas.
p u b lic in t AmountOfLettuce;
p u b lic in t AddSideOfFries() |
}

pub lic BLT OrderMyBLT() {


SlicesOfBacon
BLT mySandwich = new BLT();
AmountOfLettuce CountCalories jest
BLT.Toasted = tru e ;
Sandwich.SlicesOfBread = 3; T e f°dą‘ ale ¿ in s tr u
nie zaw iera nawiasóu
mySandwich.AddSideOfFries(); P Jej wywotaniu.
AddSideOfFries()
mySandwich.SlicesOfBacon += 5;
MessageBox.Show("Moja kanapka ma
+ mySandwich.CountCalories + " k a lo r ii. " ) ;
return mySandwich;

296 Rozdział 6.
Dziedziczenie

Wiemy, że dziedziczenie dodaje pola, właściwości


i metody klasy bazowej do klasy potomnej...
D ziedziczenie je st p ro ste, jeżeli Twoja p u b lic class Bird {
klasa p o to m n a m a m ieć wszystkie m etody,
p ub lic void F ly() {
właściwości i p o la klasy bazowej.
/ / tu je s t kod, który pozwala ptakowi latać
Bird }
Pigeon je s t kiasą Fly()
pochodną Bird, LayEggs()
w ię c w szy s tk ie PreenFeathers() p ub lic void LayEggs() { . . . }
pola i m etody
te j d ru g ie j p ub lic void PreenFeathers() { •• }
a u tom a tyczn ie
sta ją , s ię c zę ścią }
p ie rw s z e j.
Pigeon p u b lic class Pigeon : Bird {
Coo()
p ub lic void Coo() { . . . }
}

p u b lic class Penguin : Bird {


. a l e niektóre ptaki nie latają! pub lic void Swim() { . . . }
C o byś zrobił, gdyby klasa bazow a p o siad ała }
m eto d ę, k tó rą T w oja klasa p o ch o d n a m usiałaby
zmodyfikować? p u b lic void BirdS im ulator() {
J f s t instancją Pigeon H arrie t = new Pigeon();
Bird klasy Penguin.
Fly() w zw iązku z tym, Penguin Izzy = new Penguin();
LayEggs() z e odziedziczą t
m etodę FlyO, nic H a rrie t.F ly ();
PreenFeathers() Zarówno Pigeon, jak
m e pow strzym a go H arrie t.C o o(); i Penguin dzie d ziczą
przed lataniem.
Iz z y .F ly (); po klasie Bird. Obiekty
¿ 7 obu tych klas mają
} metody Fly(h LayEggs()
oraz PreenFeathers().
Pigeon Penguin
Coo() Swim()

latania. J ^ l Ż k T ^ o ^
w szystko po klasie Bird Vo f ’9“ '’1 d z ie d z ic z y
■pingwinów latających po o k o i c ^ C ^
sytu a cji powinniśmy zrobić? ° * faWaJ

r iS H S & o pró b ie^


WYSIL
d c z a s d z ^ d z |Czen n klasy SZARE KOMÓRKI
geon po kla sie B ird .

Gdyby to był Twój kod do symulacji zachowań ptaków, co byś zrobił,


aby uniemożliwić pingwinom latanie?

jesteś tutaj ► 297


Ręczne przesłanianie

Klasa pochodna może przesłaniać odziedziczone metody


wcelu ich modyfikacji lub zmiany
C zasam i m asz klasę p o ch o d n ą, w której chciałbyś dziedziczyć większość zachow ań
z klasy bazow ej, ale nie wszystkie. K iedy chcesz zm ienić odziedziczone przez klasę
zachow anie, m ożesz przesłonić m etody.

ft DODAJ SŁOWO KLUCZOWE VIRTUAL DO METODY W KLASIE BAZOWEJ.


K lasa m oże przesłaniać jedynie te m etody, k tó re o znaczone są słowem kluczowym v irtu a l.
T o ono um ożliw ia przesłan ian ie m e to d w klasach pochodnych.

public class Bird { D°danie s t°w a kluczowego


v ir tu d do m etody Fly()
pozwala klasom pochodnym
public virtual void Fly() { ją przesłaniać.

// kod pozwalający ptakom latać


}
}
DODAJ METODĘ O TEJ SAMEJ NAZW IE W KLASIE POCHODNEJ.
M usisz po siad ać dokład n ie ta k ą sam ą sygnaturę — m am y n a myśli tę sam ą w artość
zw racaną i p ara m etry — o ra z użyć w deklaracji słowa kluczow ego override .

Aby przestonić m etodę Fly(),


public class Penguin : Bird { dc>daj identyczną m etodę do
klasy p ochodnej i użyj stowa
kfuczowggooverride.
public override void Fly() {
MessageBox.Show("Pingwiny nie potrafią latać!”);
}
A b y dodać m etodę, k tó ra
podmienia analogiczną do siebie
Kiedy przesłan ia sz metodę, Twoja nowa dziedziczoną po klasie nadrzędnej,
metoda mu si mieć dokładnie taką sam ą
sygnaturę jak ta w klasie , po której za to, użyj słowa kluczowego override.
dziedziczysz. W tym p r z y p a d ł o w e,™ to,
ż e powinna s i ę nazywać F t y l zwracać v° ' cI Zanim będziesz m ó g ł przesłonić
i nie powinna mieć parametrów.
m etodę, musisz ją oznaczyć
w klasie bazowej słowem
kluczow ym virtual.
298 Rozdział 6.
Dziedziczenie

W każdym miejscu, gdzie możesz skorzystać Toasted


Sandwich

z klasy bazowej, możesz zamiast niej użyćjednej SlicesO fB re a d

z jej klas pochodnych C o u n tC a lo rie s()

Je d n ą z najbardziej przydatnych opcji podczas p racy z dziedziczeniem je st możliwość


użycia klasy p o chodn ej w m iejscu, w którym m o żn a użyć klasy bazow ej. Jeśli w ięc Twoja
m eto d a Recipe() jak o p a ra m e tr przyjm uje o b iek t Cheese, a Ty po siad asz o b iek t klasy
AgedVermontCheddar, k tó ra dziedziczy p o klasie Cheese, to m ożesz p rzek azać instancję
_____
AgedVermontCheddar do m eto d y R ecipe() . M a o n a d o stęp tylko do pól, właściwości BLT
i m etod, k tó re są częścią klasy Cheese, nie m a je d n a k d o stęp u do żadnych elem entów S licesO fB a co n
specyficznych dla AgedVermontCheddar. A m o u n tO fL e ttu c e

r® Pow iedzm y, że m am y m eto d ę do analizow ania obiektów typu Sandwich:


A d d S id e O fF rie s ()
p u b lic void SandwichAnalyzer(Sandwich specimen) {
i n t c a lo rie s = specim en.C ountC alories();
U p d a te D ie tP la n (ca lo rie s);
Perform BreadC alculations(specim en.SlicesO fBread, specimen.Toasted);
}

[2 M ożesz p rzek azać tej m eto d zie o b iek t Sandwich, ale rów nie dobrze m ożesz jej p rzek azać instancję klasy BLT.
Poniew aż klasa ta je st rodzajem k anapki, ustaw iam y ją jak o dziedziczącą p o Sandwich.

p u b lic void b u tto n 1 _ C lic k (o b je c t sender, EventArgs e) {


BLT myBLT = new BLT();
SandwichAnalyzer(myBLT); Powiem y sobie więcej na
ten te m a t w następnym
} rozdziale!

Z aw sze m ożesz się p o ru szać w dół d iagram u klas — zm ien n a referen cy jn a m oże być
zawsze ustaw io n a n a instancję jed n ej z klas pochodnych. N ie m ożesz je d n a k po ru szać się w górę.

p u b lic void b u tto n 2 _ C lic k (o b je c t sender, EventArgs e) {


Sandwich mySandwich = new Sandwich();
BLT myBLT = new BLT();
Sandwich someRandomSandwich = myBLT;
BLT anotherBLT = mySandwich; / / < - - - TO S IĘ NIE SKOMPILUJE!!!
} ‘

(
L Nie m ożesz jednak przypisać
myS.andMich do zm iennej anotherBLT
P°n“ "to każda kanipka j e t t
typu BLT! To dlatego o s t a w l t r s z
spowoduje wygenerowanie btędu.

jesteś tutaj ► 299


Więcej praktyki

Miesz a ne a = 6
b = 5
56
11
Poniżej zaprezentowano krótki program w C#. Jeden
z jego fragmentów jest wycięty! Twoim zadaniem
wiadomości a = 5 65
jest dopasowanie wycinka kodu (po lewej stronie)
i komunikatu wyświetlanego w oknie MessageBox
Ćwiczenie
aplikacji, które pojawia się po jego dodaniu. Nie wszystkie
linijki wyniku mają zostać użyte, a niektóre mogą być
Instrukcje: wykorzystane więcej niż raz. Narysuj linie łączące
1. W ypełnij cztery puste miejsca w kodzie. fragmenty kodu z powiązanymi z nimi wynikami.
2. Dopasuj fragmenty kodu do oczekiwanych rezultatów.
pub lic class A { p u b lic class C : B {
p u b lic in t iv a r = 7; p u b lic ____________ s trin g m3() {
p u b lic ____________ s trin g m1() { return "C m3, " + (iv a r + 6);
return "A ml, }
}
p u b lic s trin g m2() { }
return "A m2, "; p u b lic class Mixed5 {
} p u b lic s ta tic void M a in (s trin g [] args) {
public s trin g m3() { A a = new A ();
return "A m3, "; B b = new B()
Podpowiedz: Naprawdę, dobrze sie
C c = new C() ^ e t a n ó w , co oznacza ten w ? e rlz\o d u .
} A a2 = new C()
s trin g q = " " ; Fragment kodu
p ub lic class B : A { wstawiany jest
p u b lic ____________ s trin g m1() { tutaj
return "B ml, ";
(trzy wiersze)
}
System.Windows.Forms.MessageBox.Show(q);
}

Fragmenty q += b.m i() Wynik:


kodu: q += c .m2()
A ml, Am2, C m3, 6
q += a.m3() ¡ }
B ml, Am2, A m3,
q += c.m1()
q += c.m2() A ml, Bm2, A m3, 6
q += c.m3() B ml, Am2, C m3, l3

q += a.m1() B ml, Cm2, A m3,


q += b.m2() A ml, Bm2, A m3,
q += c.m3() ¡ }
B ml, Am2, C m3, 6
q += a2.m1(); A ml, Am2, C m3, l3
q += a2.m2()
q += a2.m3(); } (Nie ułatw iaj sobie zadania, wpisując ten kod w IDE
• • • • • • ■•
— nauczysz się znacznie więcej, jeżeli znajdziesz
odpowiedź, korzystając z ka rtki papieru!).

300 Rozdział 6.
Dziedziczenie

Zagadkowy basen
Tw oim zadaniem je st p o b ra n ie fragm entów k o d u z b asen u i w staw ienie
ich w p u ste m iejsca. M ożesz użyć tego sam ego frag m en tu więcej
niż raz i nie m usisz skorzystać ze wszystkich. Celem je st napisanie
zestaw u klas, k tó re się skom pilują i b ę d ą działały razem
w p ro g ram ie . N ie daj się zwieść — to zad an ie je st trudniejsze,
niż Ci się wydaje.

public class Rowboat ............................................. { public class TestBoats {


Podpowiedz:
public ........................................ rowTheBoat() { ........................................ Main(){ To j e s t punkt
return "wiosTuj galerniku"; xyz = wejścia
programu.
} .............................bl = new Boat();
} Sailboat b2 = new ................................( ) ;
Rowboat .............................. = new Rowboat();

public class ........................................ { b2.setLength(32);

private in t ....................................... ; xyz = b l ..................................... ( ) ;

...................................... void (.. xyz += b3...................................( ) ;


.) {
length = len; xyz += ............................... move();
System.Windows.Forms.MessageBox.Show(xyz);
}
public in t getLength() { }
}

}
public class : Boat {
public move() {
public ...............................................( ) {
return " ............................
return " ......................................." ;
}
}

jesteś tutaj ► 301


Zdobądź nieco praktyki

MjeQZa ne a = 6
b = 5
56
11
wQadomoici a = 5 65

Rozwiązania p u b lic class A { p u b lic class B : A {


ćwiczeń
p u b lic virtual s trin g m1() { p u b lic override s trin g m1() {

p u b lic virtual s trin g m3() { p u b lic class C : B {


} p u b lic override s trin g m3() {
Z aw sze m ożesz um ieścić referen cję do klasy p o chodnej q += b.m1()
w m iejscu, gdzie jest oczekiw ana referen cja do klasy q += c.m2()
A m1, A m2, C m3, 6
bazow ej. Innym i słowy, zawsze m o żn a użyć czegoś bardziej q += a.m3()
szczegółow ego w m iejsce czegoś bardziej ogólnego. A zatem B m1, A m2, A m3,
jeśli w jakim ś w ierszu k o d u jest oczekiw ana referen cja do q += c.m1()
o b iek tu klasy Canine , to m ożesz zam iast niej użyć referencji q += c.m2() A m1, B m2, A m3, 6
typu Dog. W iersz kodu: q += c.m3()
B m1, A m2, C m3, 13
A a2 = new C(); q += a.m1()
B m1, C m2, A m3,
oznacza w ięc, że tw orzym y now ą instancję klasy C, a n astęp n ie q += b.m2()
tw orzym y zm ienną typu A o nazw ie a2 i zapisujem y w niej q += c.m3() A m1, B m2, A m3,
referen cję do utw orzonego wcześniej obiektu. N azw y takie jak
A, a2 i C doskonale nadaw ałyby się do jakiejś zagadki, jed n ak q += a2.m1() B m1, A m2, C m3, 6
ze zrozum ieniem ich znaczenia m oże być pew ien problem . q += a2.m2()
q += a2.m3() A m1, A m2, C m3, 13
Poniżej przedstaw iliśm y trzy w iersze kodu
o podobnym znaczeniu, w których zastosow aliśm y je d n a k
nazwy znacznie łatw iejsze do odszyfrow ania.

Sandwich mySandwich = new BLT();


Cheese ingredient = new AgedVermontCheddar();
Songbird tweety = new NorthernMockingbird();

Zagadkowy basen. Rozwiązanie public class TestBoats {


public sta tic void Main(){
public class Rowboat : Boat { string xyz =
Boat b1 = new Boat();
public string rowTheBoat() {
return "wiosTuj galerniku"; Sailboat b2 = new Sailboat ( ) ;
Rowboat b3 = new Rowboat();
}
b2.setLength(32);
}
public class Boat { xyz = b1. move ( ) ;
xyz += b3. move ( ) ;
private in t length ;
xyz += b2 .move();
public void setLength (intlen ) {
length = len; System.Windows.Forms.MessageBox.Show(xyz);
} }
public in t getLength() { }
public class Sailboat : Boat {
return length ;
public override string move () {
}
return "żagle staw ";
public virtual string move() {
return "dryfuje }
} }
}

302 Rozdział 6.
Dziedziczenie

■ Nie .istnieją.
głupie pytania

P : Mam pewne wątpliwości P: : Czy mogę dziedziczyć po klasie, P : Co masz na myśli, mówiąc, że mogę
dotyczące punktu wejścia któi
która zawiera punkt wejścia? przesuwać się w diagramie klas w górę,
w „Zagadkowym basenie” — czy to ale nie mogę przechodzić w dół?
oznacza, że mogę mieć program, który OI: : Tak. Punkt wejścia musi być metodą
nie posiada formularza Form1? statyczną, ale nie musi znajdować się O : Gdy posiadasz diagram z klasą, która
w statycznej klasie. (Pamiętaj, słowo kluczowe jest w hierarchii wyżej niż inna, oznacza to,
O : Tak. Kiedy tworzysz nowy projekt s t a t ic oznacza, że nie można tworzyć że jest ona bardziej abstrakcyjna niż ta
Windows Forms Application, IDE tworzy instancji klasy, ale jej metody są dostępne na dole. Bardziej szczegółowe i konkretne
za Ciebie wszystkie jego pliki, włączając zaraz po rozpoczęciu programu. W aplikacji klasy (takie jak S h i r t lub Car) dziedziczą
w to P rogram .cs (który zawiera statyczną z ćwiczenia „Zagadkowy basen" możesz po bardziej abstrakcyjnych (takich jak
klasę z punktem wejścia) oraz Form1.cs wywoływać T e stB o a ts.M a in () z każdej C lo th in g lub V e h ic le ). Jeśli pomyślisz
(zawierający pusty formularz o nazwie innej metody bez deklarowania zmiennej o tym w ten sposób, to z łatwością
Form1 ). referencyjnej lub tworzenia zrozumiesz, że gdy potrzebujesz jakiegoś
Spróbuj tego: zamiast tworzyć nowy projekt nowej instancji obiektu za pomocą pojazdu, to równie dobrze możesz użyć
Windows Forms Application, utwórz projekt instrukcji new). samochodu, jak i motocykla. Jeśli natomiast
pusty, wybierając EmptyProject w miejsce potrzebujesz samochodu, motocykl nie
Windows FormsApplication. Następnie dodaj P W dalszym ciągu nie mogę pojąć,
: będzie dla Ciebie satysfakcjonujący
plik klasy w oknie Solution Explorer i wpisz dlaczego te metody nazywają się Dziedziczenie działa dokładnie tak samo.
wszystko, co znajduje się w rozwiązaniu „virtual” — wydaje mi się, że są Gdy masz metodę z parametrem V e h ic le ,
„Zagadkowego basenu". W związku z tym, rzeczywiste!
a klasa M o to rc y c le dziedziczy z V e h ic le ,
że Twój projekt używa okna MessageBox,
musisz dodać referencję. Kliknij prawym
O : Nazwa „virtual" jest związana
to możesz do niej przekazać instancję klasy
M o to rc y c le . Jeśli jednak funkcja jako
ze sposobem, w jaki .NET radzi sobie
przyciskiem myszy References w oknie parametr przyjmuje obiekt M o to rc y c le ,
z metodami wirtualnymi w tle. Używa
Solution Explorer, wybierz Add Reference, nie możesz przekazać do niej żadnego
w tym celu czegoś, co nazywamy tablicą
przejdź do zakładki .NET i znajdź System. obiektu V e h ic le , ponieważ mógłby on być
metod wirtualnych (lub v ta b le ).
Windows.Forms. (To jest kolejna z wielu rzeczy, instancją klasy Van. Gdybyś mimo wszystko
Jest to tablica zarządzana przez .NET
które IDE robi za Ciebie automatycznie tak zrobi, C# mógłby mieć spore problemy
i przechowująca informacje związane
podczas tworzenia projektu Windows Forms z dostępem do właściwości Handlebars!
z dziedziczeniem. Wie, które metody zostały
Application). Na samym końcu wybierz
odziedziczone, a które przesłonięte. Nie
Properties z menu Project i jako typ aplikacji
wybierz WindowsApplication.
przejmuj się — nie musisz wiedzieć, jak Zawsze m ożesz
to działa, aby używać metod wirtualnych!
Teraz spróbuj to uruchomić... a zobaczysz
rzekazać instancję
wszystkie wyniki! Gratulacje, właśnie C l a s s V iew ▼ nx asy pochodnej
o o |a -
stworzyłeś program C# od podstaw. %
do każdej metody,
< S e a rc h > P ±
t ,
Jeżeli potrzebujesz odśw ieżyć
.
informacje
a [c5] A n a l i z a t o r K a n a p e k
> li P ro je c t R e fe re n c e s
- która spodziewa
ia tem a t metody Mam 1 punktu wejścla i a {> A n a l iz a to r K a n a p e k się parametru
wróć do początku rozdziału 2 -! a % BLT
a li B a s e T y p e s w postaci instancji
Okno Class View można w yśw ietlić przy >
k
F o rm !
M n---- VS. ▼ klasy bazowej.
użyciu menu VIEW, a jest to kolejne C o u n tC a lo rie sQ

udostępniane przez IDE narzędzie A S lic e sO fB re a d


A T o a s te d
ułatwiające poznawanie C#. Zazwyczaj jest z katalogu B ase Types
ono umieszczone w ew nątrz okna Solution w oknie Class View, aby przejrzeć
Manager i pozwala na przeglądanie klas
S o l u tio n E x p lo re r C la s s V ie w hierarchię dziedziczenia klasy.
dostępnych w rozwiązaniu — co może się Kliknij klasę widoczną
okazać bardzo przydatne. w oknie Class V'teiw, by
w yśw ietlić je j sk tadowe-
jesteś tutaj ► 303
Naprawdę ich potrzebujesz
SŁUCHAJ, NAPRAWDĘ NIE ROZUMIEM, DO CZEGO MOGĄ MI BYĆ
POTRZEBNE TE SŁOWA KLUCZOWE „VIRTUAL" I „OVERRIDE". JEŚLI ICH
NIE UŻYJĘ, IDE WYŚW IETLA OSTRZEŻENIE, ALE NIE M A ONO ŻADNEGO
ZNACZENIA — PROGRAM I TAK DZIAŁA! SKORO ZASTOSOWANIE
TYCH SŁÓW KLUCZOWYCH JEST PRAWIDŁOWYM ROZW IĄZANIEM , TO
UMIESZCZĘ JE W KODZIE, ALE M AM W RAŻENIE, ŻE TO PRZYPOMINA
RZUCANIE KŁÓD POD NOGI.

Istnieje poważny argument przemawiający za stosowaniem tych słów kluczowych!

Słow a kluczow e v ir t u a l i o v e rrid e nie służą jedynie do dekoracji. N ap raw d ę są o n e w stanie


zm ienić działanie naszego p ro g ram u . N ie m usisz je d n a k w ierzyć nam n a słowo — n a kilku
kolejnych stro n ach przedstaw im y przykład, k tóry zad em o n stru je ich działanie.

Z a m ia st aplikacj ty p u W rn d w s
Forms Application tym r n z ^
stw orzym y aplikację kjm sj|jw ą•
Z ró b t Oznacza to, że nie będzie

i mieć żadnego formularza.

UTW ÓRZ NOW Ą APLIKACJĘ KONSOLOWĄ I DODAJ DO NIEJ KLASY.


Kliknij praw ym przyciskiem myszy w o knie Solution Explorer i w no rm aln y sposób dodaj do nowej
aplikacji klasy. M a ich być pięć: Jewels , Safe , Owner, Locksmith i Jew elT hief .

DODAJ KOD NOWYCH KLAS.


Poniżej o raz n a n astęp n ej stro n ie zam ieściliśm y
kod wszystkich nowych klas. Aplikacje konsolowe
class Jewels me korzystają z formularzy
{ Jeśli zamiast Windows Forms Application
p u b lic s trin g Sparkle()
wybierzesz C °ns°le Application, to IDE
{
return "Lśnimy i błyszczymy!"; utw 0rzy projekt Uwierający tylko ¡eden
plik - Program.cs - a w nim pustą metodę
} O biekt S a fe przechowuje w swoim polu
} contents referencję do obiektu Jew els. Nie MainP stanowiącą punkt wejścia programu.
zwraca je j, chyba że zo sta n ie wywołana Uruchomienie takl’ej aplikacji spowoduie
metoda ° p en() z właściwą kombinacją. wyświetlenie wiersza poleceń, w którym
class Safe { ^ będą prezentowane wyniki jej działania.
Zwróć uwagę, p riv a te Jewels contents = new Jewels(); podczas lektury kilku następnych rozdziałów
ż e dzięki p riv a te s trin g safeCombination = "12345"; nabierze,sz więcej wprawy w tworzeniu
zastosow aniu p u b lic Jewels Open(string combination) aplikacji konsolowych.
słowa
kluczowego
{
private zarówno i f (combination == safeCombination)
zaw artość sejfu, return contents;
ja k i kombinacja else
do niego są return n u ll;
ukryte.
} Obiekt Locksmith m oże pobrać
p u b lic void PickLock(Locksmith lockpicker) { i zapisać kod do sejfu , wywołując
lockpicker.WriteDownCombination(safeCombinat io n ); m etodę PickLock() i przekazując
} do niej referencję do samego siebie.
O biekt S a fe wywołuje jego metodę
WriteDownCombination(), przekazując
do niej kod do otwarcia sejfu .

304 Rozdział 6.
Dziedziczenie

class Owner {
p riv a te Jewels returnedContents;
p u b lic void ReceiveContents(Jewels safeContents) {
returnedContents = safeContents;
Console.W riteLine("Dziękuję za zwrócenie klejnotów! safeContents.SparkleO ):
}
}
[4 KLASA JEWELTHIEF DZIEDZICZY PO LOCKSMITH.
Locksmith
Z łodzieje klejnotów to ślusarze, którzy zeszli n a złą drogę. P o tra fią o tw ierać sejfy,
lecz zam iast oddaw ać um ieszczone w nich klejnoty właścicielom , k ra d n ą je.

class Locksmith {
p u b lic void OpenSafe(Safe safe, Owner owner) {
O p e n S a fe ()
sa fe .P ickL o ck(th is);
W rite D o w n C o m b in a tio n ()
Jewels safeContents = safe.Open(writtenDownCombination); R e tu rn C o n te n ts()
ReturnContents(safeContents, owner); M etoda O penSafe() obiektu Locksm ith
} ,p obiera kod do otworzenia sejfu , otwiera
g°, po czy m zwraca jego zaw artość
właścicielowi.
p riv a te s trin g writtenDownCombination = n u ll;
p u b lic void WriteDownCombination(string combination) { JewelThief
p rivate sto le n Je w e ls
writtenDownCombination = combination;
}

p u b lic void ReturnContents(Jewels safeContents, Owner owner) {


owner.ReceiveContents(safeContents); R e tu rn C o n te n ts()

}
}

class JewelThief : Locksmith {


p riv a te Jewels stolenJewels = n u ll;
p u b lic void ReturnContents(Jewels safeContents, Owner owner) {
stolenJewels = safeContents;
Console.WriteLine("Kradnę zawartość s e jfu ! stolenJew els.S parkleQ );
} Klasa Jew elT hief dziedziczy metody O penSafe()
} oraz WriteD°wnCombination(). Jednak metoda
R eturnC°nte n tsO tej klasy za m ia st oddawać
U) A OTO METODA M A IN () KLASY PROGRAM. klejn ° ty ¡właścicielowi, kradnie je!
A le nie urucham iaj je j jeszcze ! Z an im uru ch o m isz pro g ram ,
spróbuj określić, jaki k o m u n ik at zostanie wyświetlony
w oknie konsoli.
class Program {
s ta tic void M a in (s trin g [] args) {
Owner owner = new Owner(); P rzeanalizuj k o d p ro g ram u . Z an im go
Safe safe = new S afe(); uruchom isz, zapisz, jaki k o m u n ik at zostanie
JewelThief jew e lT h ief = new Jew elThief(); w edług C iebie w yświetlony w o knie konsoli.
jewelThief.OpenSafe(safe, owner);
(P odpow iedź: O kreśl, co klasa Jew elThief
Console.ReadKey();
dziedziczy p o klasie Locksmith !).
}
Metoda ReadKey() czeka, _aż uży tko° n,k
} naciśnie ja k iś klawis z . Wywołują c j ą
nie pozwalamy zakończyć programie.

* jesteś tutaj ► 305


Ukryj i szukaj

Klasa pochodna może ukrywać metody klasy bazowej


T eraz u ru ch o m nasz p ro g ram . Poniew aż je st to aplikacja konsolow a, zam iast wyśw ietlania kom unikatów
w oknie Output, zostanie otw o rzo n e now e o k n o w iersza p o lece ń i to w nim b ę d ą się pojaw iać wszystkie
generow ane kom unikaty. O to co w nim zobaczym y:

< file:///C:/Users/Public/Documents/Visual Studio 2012/ProjecL..


D z ię k u ję z a z w r ó c e n ie k le jn o to w i L ś n im y i b ły s z c z y m y !

Czy sądziłeś, że p ro g ram w ygeneruje in n e wyniki? M oże spodziew ałeś się takiego kom unikatu:

Kradnę zawartość s e jfu ! Lśnimy i błyszczymy!

W ygląda n a to, że złodziej zachow ał się jak praw o rząd n y ślusarz! C óż zatem się stało?

Ukrywanie a przesłanianie metod


Pow odem , który spraw ił, że w m o m encie w yw ołania m etody ReturnContents() o b iek t klasy Jew elThief
zachow ał się ja k o b iek t klasy Locksmith , był sposób zad ek laro w an ia tej m eto d y w klasie Jew elThief .
D o sk o n ałą podpow iedzią, o co chodzi, jest o strzeżenie w ygenerow ane podczas kom pilacji p ro g ram u .

Error L ist ^ n x
T » in 1 W a rn in g | © 0 Messages Search Error List p -

D e scrip tio n File ^ Line

A 1 'Z lo d z ie jJ d e jn o to w J e w e lT h ie f.R e tu rn C o n te n ts iZ lo d z ie jJ d e jn o to w J e w e ls , Z lo d z ie jJ d e jn o to w .O w n e r)' h ides in h e rite d m e m b e r Jew eIThief.cs 14


'Z lo d z ie jJ d e jn o to w .L o c k s m ith .R e tu rn C o n te n ts iZ lo d z ie jJ c le jn o to w J e w e ls , Z lo d z ie jJ d e jn o to w .O w n e r)'. Use th e n e w k e y w o rd if
h id in g was in te n d e d .

Poniew aż klasa Jew elThief dziedziczy m e to d ę R eturnContents() p o klasie


Locksmith i zastępuje ją, m o żn a by sądzić, że m e to d a ta je st przesłan ian a. Je d n ak Jeśli klasa pochodna
w rzeczywistości ta k się nie dzieje. Z ap ew n e oczekiw ałeś, że klasa Jew elThief dodaje metodę
przesłoni tę m eto d ę (co niebaw em opiszem y), tym czasem okazuje się, że zostaje o tej samej nazwie
o n a w niej ukryta.
co metoda z klasy
Istnieje o grom na różnica m iędzy ukryciem a przesłonięciem . K iedy klasa bazowej, to metoda
p o ch o d n a ukryw a jakąś m e to d ę ze swojej klasy bazow ej, to ją zastępuje tej ostatniej
(a technicznie rzecz ujm ując, „ponow nie d ek la ru je ”). W efekcie klasa p o ch o d n a zostaje ukryta,
dysponuje dw iem a różnym i m eto d am i o tej sam ej nazwie: pierw szą odziedziczoną
po klasie bazow ej i dru g ą zdefiniow aną w danej klasie.
a nie przesłonięta.

306 Rozdział 6.
Dziedziczenie

Stosuj inne referencje, by wywoływać ukryte metody


K lasa Jew elThief jedynie ukryw a m eto d ę R eturnC ontents() (nie przesłan ia jej) i z tego pow odu, kiedy
jej o b iek t zostanie zastosow any jak o o b iek t Locksmith , będzie się zachowywał ja k Locksmith . K lasa
Jew elThief dziedziczy je d n ą w ersję m eto d y ReturnContents() p o klasie Locksmith , a d ru g ą definiuje
sam a. Z naczy to, że istnieją w niej dwie ró żn e m eto d y o tej sam ej nazw ie, a to z kolei oznacza, że klasa ta
będzie p o trzebow ała dw óch różnych sposobów wywoływania tych dw óch m etod.

I w łaśnie ta k się dzieje. Jeśli będziem y dysponow ać o b iek tem klasy Jew elThief i wywołamy jego m eto d ę
R eturnC ontents() , używ ając przy tym zm iennej referencyjnej typu Jew elThief , to wywołamy jej now ą
w ersję. Jeśli je d n a k skorzystam y ze zm iennej referencyjnej typu Locksmith , to zostanie w yw ołana ukryta
w ersja m etody zdefiniow ana w klasie bazowej.

/ / Klasa JewelThief ukrywa metodę z klasy bazowej Locksmith,


/ / a zatem ten sam obiekt może się zachowywać w różny sposób, w zależności od
/ / typu zastosowanej zmiennej re fe re n cyjn e j!

/ / Odwołanie się do obiektu JewelThief przy użyciu re fe re n c ji typu Locksmith


/ / spowoduje wywołanie metody ReturnContents() pochodzącej z klasy bazowej.
Locksmith calledAsLocksmith = new Jew elThief();
calledAsLocksmith.ReturnContents(safeContents, owner);

/ / Z kolei zapisanie obiektu JewelThief w zmiennej referencyjnej tego samego


/ / typu spowoduje wywołanie nowej w ersji metody, gdyż wersja z klasy bazowej
/ / została przez n ią ukryta.
JewelThief calledAsJewelThief = new Jew elThief();
calledAsJewelThief.ReturnContents(safeContents, owner);

W razie ukrywania metod użyj słowa kluczowego new


Przyjrzyj się uw ażnie w yśw ietlonem u o strzeżeniu. Z ap ew n e zazwyczaj nie p rzeglądasz o strzeżeń generow anych przez
ID E , praw da? Je d n a k tym razem je przeczytaj: Aby bieżąca składowa przesłoniła wcześniejszą implementację,
dodaj słowo kluczowe override. W przeciwnym razie dodaj słowo kluczowe new.

A zatem w róćm y do naszego p ro g ra m u i dodajm y słowo kluczow e new.

new p u b lic void ReturnContents(Jewels safeContents, Owner owner) {

G dy tylko dodasz new do deklaracji m eto d y ReturnContents() w klasie Jew elT hief , ostrzeżenie zniknie,
je d n a k p ro g ram w ciąż nie będzie działać tak, ja k byśmy teg o oczekiwali! N ad a l wywoływana b ędzie w ersja m etody
ReturnC ontents() z klasy Locksmith . D laczego? Poniew aż jest o n a wywoływana z poziom u metody zdefiniowanej
w klasie Locksm ith — a k o n k retn ie z m eto d y Locksm ith.OpenSafe() . Fakt, że cały p ro ces został zainicjow any
przez o b iek t Jew elT hief , nie m a tu żadnego znaczenia. Jeśli klasa Jew elThief jedynie ukrywa m eto d ę
R eturnC ontents() , to jej w łasna w ersja tej m eto d y nigdy nie zostanie wywołana.

Czy domyślasz się, co należy zrobić, aby metoda R eturnC ontents() została przesłonięta,
a nie ukryta? Spróbuj zastanowić się nad tym , zanim przejdziesz na następną stronę!

jesteś tutaj ► 307


I właśnie dlatego potrzebujesz tych słów kluczowych

Używaj override i virtual, by dziedziczyć zachowania


N apraw dę chcem y, by klasa J e w e lT h ie f zawsze korzystała z w łasnej w ersji m eto d y R e tu r n C o n t e n ts ( ) ,
i to niezależnie od tego, jak zostanie o n a wywołana. W przew ażającej w iększości przypadków
oczekujem y, że dziedziczenie b ędzie działać w łaśnie w taki sposób. O kreślam y je jak o przesłanianie .
B ardzo łatw o m ożem y sprawić, by klasa J e w e lT h ie f p rzesłan iała m eto d ę R e tu r n C o n te n ts ( ) odziedziczoną
po klasie L ocksm ith. P rzed e w szystkim w deklaracji tej m etody w klasie J e w e lT h ie f należy d odać słowo
kluczow e override :

class Jewe!Thief:Locksmith {

override public void ReturnContents


(Jewels safeContents, Owner owner)

A le to jeszcze nie wszystko. Jeśli dodam y tylko słowo kluczow e o v e r r id e ,


to ID E w ygeneruje następujący błąd:

Error List ~nx


T ~ ©1 Error | 0 W arnings Search Error List fi *
Description File ^ Line Colu... Project ^

Q1 ,Zlodziej_klejnotow Jew elThief.R eturnC ontents(Zlodziej_klejnotow Jew els/ Z lodziej_klejnotow .O w ner)': JewelThief.es 12 30 Złodziej kle jn o tó w
c a nn ot override inherited m e m b e r 'Zlodziej_klejnotow .Locksm ith.R eturnC ontents
(Zlodziej_klejnotow Jew els, Zlodziej_klejnotow .O w ner)' because it is n o t marked virtu a l, abstract, or
override

T akże teraz dokładnie p rzeanalizuj wyświetlony k om unikat. In fo rm u je on, że klasa J e w e lT h ie f nie


m oże przesłonić odziedziczonej m etody R e tu r n C o n t e n ts ( ) , gdyż klasa L o ck sm ith nie oznaczyła jej jako
v i r t u a l , a b s t r a c t bądź o v e r r i d e . Cóż, ten b łąd m o żn a n apraw ić b ardzo łatwo! W ystarczy d odać do
deklaracji m etody R e tu r n C o n te n ts ( ) w klasie L o ck sm ith słowo kluczow e v i r t u a l :

class Locksmith {

virtual public void ReturnContents


(Jewels safeContents, Owner owner)

T eraz ponow nie spróbuj u ru ch om ić p ro g ram . O to jaki re z u lta t pow inieneś uzyskać:

file:///C;/UserVPublk/Docum ents/Visijal Studio 2012/Project...

I w łaśnie takich wyników oczekiwaliśmy!

308 Rozdział 6.
Dziedziczenie

Jeśli chcesz
przesłonić metodę
z klasy bazowej,
to zawsze oznacz
użyciu
Dokładnie. W większości przypadków
będziesz chciał przesłaniać metody,
ale możesz je też ukrywać.
O kluczowego
virtual , a w jej
R ozszerzając jakieś klasy, w tw orzonych klasach
pochodnych będziesz zapew ne częściej używał
nowej wersji
przesłaniania niż ukryw ania m eto d . K iedy zatem w klasie pochodnej
zauważysz, że k o m p ilato r o strzega Cię p rzed
ich ukryw aniem , nie ignoruj tego! U pew nij się, użyj słowa
że n apraw dę chcesz ukryć jak ąś m eto d ę, a nie
jedynie zapom niałeś użyć słów kluczowych v i r t u a l
kluczowego
i o v e r r id e . Jeśli zawsze będziesz praw idłow o override . Jeśli
stosow ał słow a v i r t u a l , o v e r r i d e i new, to już
nigdy nie nap o tk asz takich p roblem ów ja k ten,
tego nie zrobisz,
który przedstaw iliśm y w tym przykładzie. możesz niechcący
ukryć metodę
klasy bazowej.

jesteś tutaj ► 309


Objazd: roboty drogowe

Klasa potomna może uzyskać dostęp do klasy bazowej,


używając słowa kluczowego base
Vertebrate
N aw et w tedy, gdy przesłonisz m e to d ę lub w łaściwość klasy bazow ej, N u m b erO fLeg s

w każdej chwili m oże C ię najść o ch o ta, aby skorzystać ze starej funkcji.


N a szczęście m ożesz użyć słowa kluczow ego base , k tó re pozw ala n a odw ołanie się
do dow olnej m etody w klasie bazowej.
E a t()
S w a llo w ()
W szystkie zw ierzęta jedzą, w ięc klasa V e rteb ra te p o siad a m eto d ę E a t() ,
D ig e st()
k tó ra jak o p a ra m e tr p o b ie ra o b iek t typu Food.
p u b lic class Vertebrate {
pub lic v irtu a l void Eat(Food morsel)
Swallow(morsel);
D ig e st();

K am eleony zdobyw ają jed zen ie, łapiąc ow ady językiem . K lasa Chameleon
dziedziczy więc p o V e rteb ra te i p rzesłan ia m eto d ę E at() .
p u b lic class Chameleon : Vertebrate {
pub lic override void Eat(Food morsel) {
CatchWithTongue(morsel)
(morsel
Swallow(morsel)
D ig e st(); K Z 'd !e0n mU5i p ofy kać 1 traw ić jed zen ie tak jak
} " J POOW einf ZWJ e rzę - Czy naprawdę m usim y w ięc
p °w ielać ten fragm ent kodu?
}

Z am iast pow ielać kod, m ożem y użyć słow a kluczow ego base do w yw ołania m etody,
k tó ra została p rzesło n ięta. T e ra z m am y do stęp zarów no do starej, ja k i do nowej jej wersji.
p u b lic class Chameleon : Vertebrate {
pub lic override void Eat(Food morsel) {
CatchWithTongue(morsel);
base.Eat(morsel); Ten w iersz wywołuje m etodę Eat() z klasy
} bazowej dziedziczonej przez Cbameleon-
}
T eraz, kiedy ju ż m iałeś okazję p o zn ać n iek tó re pojęcia zw iązane z dziedziczeniem , m am y dla C iebie p roblem
do przem yślenia. Oczywiście w ielo k ro tn e wykorzystywanie k o d u je st dobrym sposobem , by zaoszczędzić sobie
n iep o trzeb n eg o n aciskania klawiszy, je d n a k kolejną w ielką z ale tą dziedziczenia je st u łatw ienie późniejszego
zarządzania kodem i jego pielęgnacji. Czy potrafisz podać a rg u m en ty potw ierdzające tę tezę?

310 Rozdział 6.
Dziedziczenie

Jeśli Twoja klasa bazowa posiada konstruktor,


klasa pochodna też musi go mieć W/staw te n dodatkowy w iersz na końcu
defc/aracji konstruktora Twojej klasu
Jeśli T w oja klasa p o siad a k o n stru k to ry p o b ierające jakieś p a ram etry , to każda Pochodnej. W/ ten sposób p rJe k a ż ttz

klasa dziedzicząca p o niej musi wywoływać jeden z tych konstruktorów . klasu h mUS' wy konać konstruktor
klasy bazowej za każdym razem,
K o n stru k to r klasy p o ch o d n ej m oże m ieć inne p aram e try niż k o n stru k to r bazowej. gdy j e s t tworzona klasa pochodna

public class Subclass : BaseClass {


public Subclass(lista parametrów)
T A
To j e s t base(Hsta parametrów konstruktora klasy bazowej) {.^
konstruktor
klasy II w pierwszej kolejności wykonywany jest konstruktor klasy bazowej,
pochodnej. I I a potem wykonywane są wszystkie pozostałe instrukcje
}
}
Konstruktor klasy bazowej je st wykonywany
przed konstruktorem klasy pochodnej Z r ó b to ! Można używać instrukcji
new bez zapisywania
N ie przyjm uj bezkrytycznie wszystkiego, co m ów im y — spraw dź sam! utworzonego przez nią
obiektu w jakiejś zmiennej.
Utwórz klasę bazową z konstruktorem, który wyświetla okno MessageBox. Poniższa instrukcja
D odaj do fo rm u larza przycisk, k tóry utw orzy klasę bazow ą i po k aże o k n o MessageBox : tw o rzy instancję klasy
MySubclass:
p u b lic c la s s MyBaseClass {
new M y S u b c la s s ();.
p u b lic M y B a s e C la s s (s trin g baseC lassN eedsThis) {
Ten obiekt zostanie szybko
MessageBox.Show("To j e s t k la s a bazowa: " + baseC lassN eedsT his);
usunięty z pamięci, gdyż
} To j e s t param etr, k tó r e g o __________ ^ nigdzie w programie nie
} klasa bazowa potrzebuje.
będzie przechowywana
Spróbuj dodać klasę pochodną, ale nie wywołuj konstruktora. referencja do niego.
W n astępnej kolejności dodaj do fo rm u larza przycisk, który utw orzy instancję
klasy p o chod n ej i po k aże o k n o MessageBox :
Wy p atruj tego tajemniczego
p u b lic c la s s MySubclass : MyBaseClass{ błędu. Oznacza on, że
p u b lic M y S u b c la s s (s trin g baseC lassN eedsThis, i n t a n o th e rV a lu e ) { Twoja klasa pochodna
nie dziedziczy konstruktora
MessageBox.Show("To j e s t k la s a pochodna: " + baseClassNeedsThis w sposób w łaściw y.
+ " i " + a n o th e rV a lu e );
sp ow odu je to powstanie
'
z tego w ie rsia kodu.
3 Popraw błąd, tworząc konstruktor, który wywołuje swój odpowiednik z klasy bazowej.
U tw órz now ą instancję klasy poch o d n ej i sprawdź kolejność w yśw ietlania się dwóch k o m unikatów MessageBox !

p u b lic c la s s MySubclass : MyBaseClass{


p u b lic M y S u b c la s s (s trin g baseC lassN eedsThis, i n t a n o th e rV a lu e )
W taki oto sposób
base(baseC lassN eedsThis) 4s~ . Dodaj ten wiersz, aby C# m e z apomnia ł(w y w ™
przekazujem y - ------ konstruktora klasy bu zw eij. ^ s ^ d a on listę
potrzebny {
param etr do
parametrów, która pokazuje, co zohs tzaałbo iedgoach
konstruktora
// R e szta kodu k la s y pochodnej j e s t taka sama konstruktora przekazami. Po takich zabi
tom unikat o błędzie zmk.nie , a przycnsk. tiędzie
klasy bazowej. mógł w yśw ietlić dwa k o m n a t y f a s s ^ B m -

jesteś tutaj ► 311


Krystyna w dalszym ciągu potrzebuje naszej pomocy

Teraz jesteś ju ż gotowy do dokończenia zadania Krystyny

Kiedy ostatni raz widziałeś się z Krystyną, miałeś ukończoną klasę do szacowania Jeśli dobrze to rozegramy,
kosztów przyjęć urodzinowych. Teraz chciałaby ona, aby program wprowadzał powinniśmy być w stanie
dodatkow ą o p łatę w wysokości 100 zł za p rzyjęcia powyżej 12 o sób . Wydawać by zmienić dwie klasy bez
się mogło, że musisz napisać ten sam kod dwukrotnie, po jednym razie w każdej dokonywania żadnych zmian
klasie. Teraz, gdy już wiesz, jak działa dziedziczenie, możesz wyprowadzić obie w form ularzu!
klasy z klasy bazowej, która będzie zawierała cały współdzielony kod. Musisz więc
napisać to wszystko tylko raz.

Dokończ program dla Krystyny — utwórz klasę bazową P a r t y , która


będzie zawierać wszystkie wspólne elementy klas D in n e r P a r ty oraz Spójrz na te dwie
B irth d a y P a rty . klasy um ieszczone obok
Ćwiczenia sieb ie. Które metody
i właściwości są
dostępne w każdej
z nich?

PRZEMYŚL NOW Y MODEL KLAS. D in n e rP a r ty B ir th d a y P a r ty


Pierwszym krokiem na drodze do NumberOfPeople: int
NumberOfPeople: int
napisania dobrego programu jest FancyDecorations: bool
FancyDecorations: bool
przem yślenie jego p ro je k tu . Wciąż
Cost: decimal Cost: decimal
będziemy w nim używali klas D in n e rP a rty CakeWriting: string
HealthyOption: bool
oraz B ir th d a y P a r ty , jednak tym razem CakeWritingTooLong: bool
będą one dziedziczyły po klasie bazowej private ActualLength: int
P a r t y . Obie klasy będą miaiy dokładnie metody prywatne:
te same właściwości, dzięki czemu nie CalculateCostOfDecorations() metody prywatne:
będziemy musieli wprowadzać żadnych CalculateCostOfBeverages CalculateCostOfDecorations()
PerPerson(); CakeSize()
zmian w formularzach.
MaxWritingLength()

Pierw szą rzeczą,


którą powinieneś
O d o d a j k l a sę b a z o w ą pa rty . zrobić, je s t dodanie
p u stej klasy Party
Utwórz nową aplikację typu W indow s i z m °dyfikowanie
F orm s A p p licatio n . Dodaj do niej klas y DinnerParty
oraz BirthdayParty
klasę P a r t y . Następnie dodaj klasy tak, by dziedziczyły
D in n e r P a r ty oraz B ir t h d a y P a r t y po niej. M ożesz ju ż
z projektu przedstawionego na początku zbu dować program,
gdy ż każda klasa
rozdziału i zmodyfikuj je tak, może rozszerzać
by dziedziczyły po klasie P a r t y . p u stą klasę.

312 Rozdział 6.
Dziedziczenie

PRZENIEŚ WSPÓLNE ELEMENTY DO KLASY BAZOWEJ PARTY.


Zaznacz i wytnij stałą C o s tO fF o o d P e rP e rso n , właściwości N um berO fPeople i F a n c y D e c o ra tio n s oraz
metodę C a lc u l a te C o s t O f D e c o r a t io n s ( ) z klasy D in n e r P a r ty bądź B ir t h d a y P a r t y (gdyż w obu klasach
są one identyczne), a następnie wklej je do klasy P a r t y . Upewnij się, że usunąłeś je z obu klas pochodnych.

W klasie P a r t y zdefiniuj właściwość C o st i oznacz ją jako wirtualną (v i r t u a l ); do definicji tej samej


właściwości w obu klasach pochodnych dodaj słowo kluczowe o v e r r i d e .

Choć w obu Idasach k o szt przyjęcia


P a rty będzie wyliczany nieco inaczej, to
Obie klasy u żywają jednak obie używ ają te j sam ej
NumberOfPeople: int metody CalculateCostOfDecorations().
właściwości NumberOfPeople
oraz FancyDecora-fions FancyDecorations: bool Z o s tała ona jednak zadeklarowana
j a ko prywatna, a zatem klasy
w dokładnie taki s a m s posób- virtual Cost: decimal
Z atem przeniesienie ich d° _ p ochodne nie mają do niej dostępu!
klasy bazowej i dziedziczenie
ich po niej ma s e ns.
metody prywatne:
CalculateCostOfDecorations()
Na s zczę ście możemy skorzystać
Oto rozszerzona właściw ość z dziedziczenia. Zadeklarujem y
Cost. W je j deklaracji zostało właściw ° ść C ost w klasie bazowej,
dodane słowo kluczowe uży wają c virtual, a następnie
override, a oprócz tego będzie rozszerzym y ją w klasach pochodnych.
ona wywoływać właściw ość
base.C ost.
D in n e rP a r ty B irth d a y P a rty
HealthyOption: bool CakeWriting: string
override Cost: decimal CakeWritingTooLong: bool
override Cost: decimal
private ActualLength: int

metody prywatne: metody prywatne:


CalculateCostOfBeverages CakeSize()
PerPerson(); MaxWritingLength()

Najtrudniejszą częścią tego zadania jest określenie, które części obu właściwości C o st z klas pochodnych
należy przenieść do klasy bazowej P a r t y . Trudność polega na tym, że można to zrobić na wiele sposobów.
Można utworzyć w klasie bazowej P a r t y automatyczną właściwość C o st i pozostawić właściwości C o st
w obu klasach pochodnych w niezmienionej postaci. Jednak w tym ćwiczeniu Twoim zadaniem jest
przeanalizowanie właściwości C o st w klasach D in n e r P a r ty oraz B ir t h d a y P a r t y , wskazanie ich elementów
wspólnych i przeniesienie możliwie jak największego fragmentu kodu do klasy bazowej.
A oto niewielka podpowiedź: W obu klasach, D in n e r P a r ty oraz B ir t h d a y P a r t y , właściwość C o st
powinna się zaczynać w następujący sposób:
o v e r r id e p u b lic decim al C ost {
get {
decim al t o t a lC o s t = b a s e .C o s t;
Nie zapomnij uwzględnić we właściwości C o st w klasie P a r t y , że do ceny imprez mających powyżej
12 uczestników należy d odać 100 zł.

jesteś tutaj ► 313


Rozwiązanie ćwiczenia

Sprawdź to! Zmieniłeś klasy D in n e r P a r ty i B ir t h d a y P a r t y w taki sposób, aby dziedziczyły


po tej samej klasie bazowej P a r t y . Mogłeś wtedy zmienić sposób obliczania kosztów, dodając
100 zł. Oprócz tego sprawiłeś, że nie trzeba wcale zmieniać formularza. Sprytne!
Rozwiązanie
ćwiczenia

c l a s s P a rty Te właściwości i sta ła były


{ identyczne w klasach DinnerParty
p u b lic c o n s t i n t C ostO fF oodP erP erson = 25; i BirthdayParty, zatem zo stały
z nich u su n ięte i przeniesione p rosto
do ich klasy bazowej.
p u b lic i n t NumberOfPeople { g e t ; s e t ; }

p u b lic bool F an cy D eco ratio n s { g e t; s e t ; }

p r i v a t e decim al C a lc u la te C o s tO fD e c o ra tio n s () Także ta metoda była identyczna w obu


klasach pochodnych, zatem ją także
{ przenieśliśm y do klasy Party.
decim al c o stO fD e c o ra tio n s ;
i f (F a n cy D eco ratio n s)
Nie zapomnij c o stO fD e c o ra tio n s = (NumberOfPeople * 15.00M) + 50M
oznaczyć e ls e
właściwości
C ost słowem c o stO fD e c o ra tio n s = (NumberOfPeople * 7.50M) + 30M;
kluczowym r e t u r n c o stO fD e c o ra tio n s ;
virtual! }
Te dwa w iersze kodu by ły
v ir tu a l p u b lic decim al C ost identyczne w ot>u oryg inalnych
klasach, DinnerParty
{ i BirthdayParty, dla tego ,
get { przenieśliśm y j e do właściwości
decim al t o t a lC o s t = C a lc u la te C o s tO fD e c o ra tio n s () ; C o st w klasie bazow ej.
t o t a lC o s t += C ostO fF oodP erP erson * NumberOfPeoplee . Przenieśliśm y do niej m°ż liwie
ja k najw iększy fragment
wspólnego kodtu.
if (NumberOfPeople > 12)
t o t a lC o s t += 100; Teraz, kiedy imprezy okolicznościowe
i urodzinowe mają ju ż sw oje własne klasy
dziedziczące po klasie bazowej Party, bardzo
re tu rn to ta lC o s t;
łatwo można pow iększyć k o szt o 100 złotych,
gdy liczba uczestników imprezy przekroczy
12 osób. W ystarczy dodać odpowiedni kod
do klasy bazowej, a obie klasy pochodne
odziedziczą zm odyfikowane zachowanie.

c l a s s B irth d a y P a rty : P a rty Klasa BirthdayParty d z ^ d z ^ z y po P ^ t y .


{
p u b lic B ir t h d a y P a r t y ( in t num berO fPeople,
bool fa n c y D e c o ra tio n s , s t r i n g c ak eW ritin g )
{
NumberOfPeople = num berOfPeople
’ p K.on s tr u k to r kla sy B irth d a y P a r ty p o z o s ta je
F an c y D eco ratio n s = fa n cy D eco rat i o n s > n ie z m ieniony , choć u s ta w ia w a rto ś c i
C akeW riting = ca k e W ritin g ; w ła śc iw o śc i z d e fin iow anych w k la s ie b a zo w e j.

314 Rozdział 6.
Dziedziczenie

p u b lic s trin g CakeWriting j get; set; I


Właściwości CakeWriting oraz
p riva te in t ActualLength A c tu alLeng th s ą używ ane wyłącznie
w klasie Bn-dndrxyParty, a nie Party;
j
get dlate go te ż pozostały zdefiniowane
w klasie BirthdayParty.

i f (CakeWriting.Length > MaxWritingLength())


return MaxWritingLength();
else
return CakeWriting.Length;

p riva te in t CakeSize() {
i f (NumberOfPeople <= 4)
return 20;
else
return 40;
}

p riva te in t MaxWritingLength() { W ła śc iw o śc i
i f (CakeSize() == 20) C a keW ritin g
return 16; i A c tu a lL e n g th or<xz
u ż y w a n e p r z e z n ie
else 4- m e to d y p o zo s t a ły
return 40; w k la s ie B ir th d a y P a rty .
} P odobnie ja k
w ła śc iw o ść
p ub lic bool CakeWritingTooLong { CakeWritingTooL-omg.
get {
i f (CakeWriting.Length > MaxWritingLength())
return tru e ;
else
return fa ls e ;
I
I Pierw sze dwa w iersze kodu
właściwości C ost przenieśliśm y
override p u b lic decimal Cost j d o k l a s y bazowej, 9 ^ . ^
get j identyczne w klasach D mnerParty oraz
decimal to ta lC o st = base.Cost ;
decimal cakeCost;
i f (CakeSize() == 20) k la sy B ir th d a y P a r ty j e s t
b a s e . C o s t w celu wykonania tych
cakeCost = 40M + ActualLength * .25M;
dwóch w ierszy kodu.
else
cakeCost = 75M + ActualLength * .25M;
return to ta lC o st + cakeCost;
I
I

► Dalszy ciąę na stronie 316.

jesteś tutaj ► 315


Doskonała robota!

To j e s t o sta tn ia klasa w rozwiązaniu dla Krystyny.


(Nie ma żadnej zm iany w kodzie formularza). Wtaś c iwość HealthyOption j e s t używana
wytącznie podczas wyliczania kosztów
c la s s D innerParty : P arty { imp rez okolicznościowych, lecz nie
Rozwiązanie u T ur-odzinowych; dlatego pozostała
p u b lic bool HealthyOption { g e t; s e t ; }
ćwiczenia, w klasie DinnerParty.
kontynuacja p u b lic D in n e rP a rty (in t numberOfPeople, bool healthyO ption,
bool fancyD ecorations) {
NumberOfPeople = numberOfPeople; Metoda CalculateCostOfBeveragesPerPerson()
FancyDecorations = fan cyD eco ration s; oraz konstruktor pozo sta ją w kla&e .
HealthyOption = healthyO ption; DinnerParty, gdyż klasa BirthdayParty ich
} nie używ a.

p riv a te decimal C alculateCostO fBeveragesPerPerson() {


decimal costOfBeveragesPerPerson;
i f (HealthyOption)
costOfBeveragesPerPerson = 5.00M;
e lse
costOfBeveragesPerPerson = 20.00M; W łaściwość C ost dziada. tak samo jak
re tu rn costOfBeveragesPerPerson; w klasie DinnerParty. Wy wołuj e _ona.
} base.C ost, by wykonać instrukcje we
właściwości P a rty.C o st a z ^ o m y wynik
używ a jako p unkt wyjśc ia do dokoncz e ma
o v e rrid e p u b lic decimal Cost {
get {
decimal to ta lC o s t = b a se .C o st; ( własnych obT\c.ze n .

to ta lC o st += C alculateCostO fBeveragesPerPerson() * NumberOfPeople;


i f (HealthyOption)
to ta lC o s t *= .95M;
return to ta lC o s t;

}
PROGRAM JEST PERFEKCYJNY. TERAZ JEST M I Z N AC ZN IE ŁATW IEJ
PROW ADZIĆ INTERES. W IELKIE DZIĘKI!

Gdy zachowania klas pokrywają się w możliwie jak najmniejszym stopniu,


są one zgodne z ważną zasadą projektową nazywaną separacją zagadnień.
Jeśli już dziś prawidłowo zaprojektujesz swoje klasy, to w przyszłości będziesz mógł je łatwiej modyfikować.
Dodanie dodatkowej opłaty w wysokości 100 złotych za imprezy mające powyżej 12 uczestników wymagałoby
dużego nakładu pracy, gdyby klasy D in n e r P a r ty i B ir t h d a y P a r t y były od siebie zupełnie niezależne. Jednak po
przeprojektowaniu programu i wykorzystaniu dziedziczenia zaimplementowanie tej dodatkowej opłaty wymaga­
ło jedynie dodania dwóch wierszy kodu. Ta zmiana była bardzo łatwa, gdyż do właściwości C o st klasy bazowej
przeniosłeś w yłącznie zachow anie w spólne d la w łaściw ości C o s t obu k la s p ochodnych .
Zastosowane rozwiązanie stanowi przykład se p a ra c ji z a g a d n ień , gdyż każda klasa zawiera wyłącznie kod doty­
czący tylko jednego, konkretnego problemu rozwiązywanego przez program. Kod związany z wyliczaniem kosz­
tów imprez okolicznościowych został umieszczony w klasie D in n e r P a r ty , kod związany z kosztami imprez uro­
dzinowych został umieszczony w klasie B i r t h d a y P a r t y , a wspólny kod przenieśliśmy do klasy P a r t y .
To coś, o czym warto pomyśleć. Oddzieliliśmy także zagadnienie obsługi interfejsu użytkownika, umieszczając zwią­
zany z nim kod w obiekcie Form. Obiekt ten nie wykonuje obliczeń samodzielnie — wszystkie obliczenia są herme­
tycznie ukryte we właściwościach C o st klas D in n e rP a r ty oraz B ir th d a y P a r ty . Uznaliśmy jednak, że konwersja
kosztu przekazywanego w formie liczby typu d ecim al na łańcuch znaków jest zagadnieniem, którym powinien się
zajmować formularz, a nie klasy wykonujące obliczenia. Czy uważasz, że jest to słuszna decyzja? ^ ^
Pamie ta \ - każdy program można. nap isać na w ' f le,
6 różnych s posobów, a zazw yczaj nie ma tego „jedyn'e.
316 s łusznego". N aw et je śli zosfanni on op'sany w ks'ążce-
Dziedziczenie

Stwórz system zarządzania ulem


Królowa pszczół potrzebuje Twojej pomocy! Jej ul jest poza wszelką
kontrolą. Potrzebuje ona programu, który pomoże jej w zarządzaniu. Ma ul
pełen robotnic i całą masę pracy do wykonania wokół niego. W jakiś sposób
straciła jednak kontrolę i nie wie, co robią jej poszczególne pszczoły oraz
czy ich potencjał wystarczy do wykonania wszystkich niezbędnych prac.

Twoim zadaniem jest zbudowanie systemu zarządzania ulem, który pozwoli


kontrolować pracę robotnic. Program będzie działał w sposób następujący:

[o KRÓLOWA PRZYPISUJE POSZCZEGÓLNYM


ROBOTNICOM ZADANIA DO W YKONANIA.
Istnieje sześć rodzajów prac, jakie mogą być wykonywane przez
robotnice. Niektóre wiedzą, jak zbierać nektar i wytwarzać miód,
a inne dbają o porządek w ulu i chronią go przed wrogami. Są też
pszczoły, które mogą wykonywać każdy rodzaj pracy. Twój program
powinien umożliwiać królowej przypisywanie pszczołom zadań,
które są one w stanie wykonywać. Pszczoły pracują
w s y s tem ie zmianowym,
a w iększość prac
wy maga poświęcenia
w ięcej niż jednej
zm a n y . W zw iązku
z powyższym królowa
wprowadza liczbę
zmian przeznaczoną
na wykonanie zadania
i naciska przycisk
„Przy p is z tę pracę
pszczole".

J eśli istn ieje ja ka ś


pszczoła, która
Ta lista rozwijana pokazuje sz e ść zadań, które m g ą może wykonać
być wykonywane przez r°b°tnice. Królowa wie, j akie zadanie, program
prace m u szą zostać wykonane, i nie przejm uj e s ię tym, p rzypisuje je j
które pszczoły będą realizowały p osz cz ególne z adania. dany obowiązek
Wybiera ona po prostu m isję — pmgira-m sam określa, ^ i powiadamia
czy j e s t ja k iś wolny osobnik, który może s ię tym zdjąp, królową, że j e s t
i przypisuje mu tę pracę. robotnica, która się
zajm ie tą sprawą.

JEŚLI WSZYSTKIE ZADANIA ZOSTAŁY PRZYPISANE,


CZAS ZAJĄĆ SIĘ PRACĄ.
Po dokonaniu przez królową przydziału do poszczególnych prac należy przekazać
pszczołom wszystkie zadania i rozpocząć nową zmianę, naciskając przycisk
Przepracuj następną zm ianę. Program wygeneruje wtedy raport zmiany określający,
które pszczoły wykonywały pracę, jakie zadania zostały zrealizowane i ile jeszcze
zmian zajmie pszczołom wykonanie powierzonych im obowiązków.
Raport zmiany numer 1
Robotnica numer 1 robi Wytwarzanie miodu'jeszcze przez 2 zmiany

jesteś tutaj ► 317


Uwaga na pszczeli wosk

W jaki sposób stworzysz system zarządzania ulem


Ten projekt został podzielony na dwie części. Pierwsza stanowi raczej powtórzenie, a w jego ramach stworzysz prosty
system do zarządzania ulem. Będą go tworzyły dwie klasy: Queen oraz W orker . Stworzysz także formularz dla swojego
systemu i użyjesz w nim tych dwóch klas. Oprócz tego z ad b asz o praw idłow ą h erm etyzację klas, by nie przeszkadzały
Ci, gdy później zajmiesz się pracami nad drugą częścią projektu.

To je s t m odel o b ie k tó w , k tó r y stw o rz y s z . N ie każda robotnica potrafi robić


F o rm u la rz będzie d y s p o n o w a ł re fe re n c ją ws zystko . Każdy obiekt Worker
posi'ada tablicę łańcuchów znaków
do in s ta n c ji k la s y Q ueen, k tó ra z k o le i o nazw ie jo bsICanDo, której używ a do
b ędzie zarządza ła in s ta n c ja m i k la s y W o rke r p rzechowyw ania informacji o pracach,
które dana pszczoła potrafi wykonywać.
p rz e c h o w y w a n y m i ja k o ta b lic a re fe re n c ji.
i
Formularz przechowuje
[0] Zbieranie nektaru
re ferencję do obiektu Queen [1] Wytwarzanie miodu
w polu o nazwie queen.

P ielęgn acja ja j
Nauczanie p szczółek
Ta robotnica potrafi wykonywać dwie
prace: pielęgnować j aja i uczyć,
mate pszczółki; dlatego je j tablica
jobsICanDo zaw iera dwa elem enty .

Utrzymanie ula
Patrol z żądłami
Królowa dysponuje polem
o nazw ie workers,
w którym przechowuje To bardzo wszechstronna
obiekty w szystkich robotnica. Potrafi
pszczół w ulu. wykonywać aż sze ść
różnych prac.

Zbieranie nektaru
Tablica robotnic j e s t prywatna, gdyż Wytwarzanie miodu
żaden inny obiekt nie powinien mieć
możliwości mówienia pszczołom, co P ielęg n a cja ja j
mają robić; z tego względu w szystkie Nauczanie p szczó łek
robotnice m uszą być utworzone
w konstruktorze. Utrzymanie ula
Patrol z żądłami

318 Rozdział 6.
Dziedziczenie
F o rm u la rz tw o rz y ta b lic ę p szczó ł ro b o tn ic . N a s tę p n ie , d la ka żd e j z n ich ,
tw o rz y n o w y o b ie k t W o rk e r i d o d a je go do ta b lic y . Konstruktor każdego M ^ t i J W ^ k a r pobiera
iedern parametr — tablicę ł a ń c u c h zntztow
określającą prace, kt<5re p s z r n fo j e s t w s to n u
Worker[] workers = new Worker[4]; wykonywać.
workers[0] =new Worker(new str in g [] { "Zbieranie nektaru", "Wytwarzanie miodu" });
workers[l] =new Worker(new str in g [] { "Pielęgnacja ja j" , "Nauczanie pszczółek" });
workers[2] =new Worker(new str in g [] { "Utrzymanie ula", "Patrol z żądłami" });
workers[3] =new Worker(new str in g [] { "Zbieranie nektaru", "Wytwarzanie miodu"
"Pielęgnacja ja j" , "Nauczanie pszczółek", "Utrzymanie ula", "Patrol z żądłami" });
queen = new Queen(workers);
t
Formularz z aw i'era pole przechowują c e referencję do obiektu klasy Queen, który Kiedy z o staje Miknięty przycisk
j e s t inicjalizowany poprzez p rzekazanie do konstruktora tablicy obiektów Worker przydz'ielający ps zczołom pracę,
zo sta je wywołana m etoda
A s s ignWork() klasy Queen, która
spraw dza, c zy s ą dostępne
psz c zoty.
K ró lo w a s p ra w d z a ka żd ą
z ro b o tn ic , by p rze ko n a ć AssignWork("Utrzvmanie u la ", 41 • .
się , k tó ra z n ich je s t
w s ta n ie w y k o n a ć
k o n k re tn ą p ra cę .
Formularz wywołuje metodę AssignWork()

i
M etoda AssignW ork() obiektu królowej
oblektu Icrólowiel l<tóra przegląda dostępne
robotnice I dla każdej z nich wywołuje metodę
D°Th isJ°b( ) ; sprawdzanie kończy się, gdy
6/ekt
przegląda tablicę robotnic, wywołując uda się znaleźć robotnicę, która potrafi wykonać
dla każdej z nich m etodę DoThisJob(); zadani'e. Jeśli takiej robotnicy nie uda się znaleźć,
robi to tak długo, aż znajdzie pszczołę, metoda AssignWork() zwraca f a l se.
która wykona zadanie.
Królowa pyta pszczołę, czy
j e s t w sta n ie wykonywać pracę
utr-zyrncm^ ula" p rzez cztery zmiany.
V
K ró lo w a m oże D o T hisJob("Utrzymanie u l a "
p rz y p is y w a ć
zad a n ia ro b o tn ic o m ,
a n a s tę p n ie kaza ć Jeśli robotnica już wykonuje jakieś zadanie, zwraca w artość
im p rz e p ra c o w a ć 6/ekt fa ls e . W przeciwnym razie sprawdza swoją tablicę
j ° bsICanD° . Jeśli znajdzie w niej tę Samą pracę, zwraca true e / r t M'J0
k o le jn ą zm ianę.
w przeciwnym razie zwraca f a l se.

K ró lo w a każe ka żd e j ro b o tn ic y p rz e p ra c o w a ć n a s tę p n ą Metoda DidVouFinish() klasy Worker


zm ianę , a n a s tę p n ie z b ie ra in fo rm a c je o n ich i g e n e ru je sprawia, że robotnica przepracowuje
m stąpną. zm ianę, po czym zwraca true,
ra p o rt zm iany. je śli wy kcmywane zadanie zostało zakoń
zakończone.
WorkTheNextShift() DidYouFinish()

Jeśli robotnica wykonuje jakąś pracę, A.


<" 's-Królowa'dodaje wiersz dotyczący
odejmuje! od liczby zmian, jakie jeszcze
robotnicy do raportu zmiany, po czym
pozostają jej do przeprac° wania- ekt \N°
zwraca raport w formie łańcucha
znaków.
jesteś tutaj ► 319
Pomóż królowej

Królowa potrzebuje Twojej pomocy! Wykorzystaj wszystko, czego się nauczyłeś o klasach i obiektach, żeby
napisać system do zarządzania ulem, który pomoże jej zarządzać pszczołami. W tej pierwszej części projektu
zaprojektujesz formularz, dodasz do niego klasy Queen oraz W orker i uruchomisz podstawową wersję systemu
Ćwiczenia
Czasami diagramy klas pokazują pola pryw atne i typy.
P ro g ram p o siad a jed en o b iek t Queen, który zarząd za w ykonyw aną pracą.
Q ueen
★ K lasa Queen używa tablicy obiektów W orker do k o n tro lo w an ia każdej pszczoły
private workers: Worker[]
ro b o tn icy i n a jej podstaw ie m oże stw ierdzić, czy m ają o n e przypisane zadania.
private shiftNumber: int
W artości przechow yw ane są w pryw atnym p o lu W o rk er[] o nazw ie w o rk e rs.
★ F o rm u la rz wywołuje m eto d ę A s s ig n W o rk (), p rzek azu jąc do niej łańcuch
znaków określający zad an ie do w ykonania o raz liczbę typu i n t oznaczającą
AssignWork() ilość zm ian. M eto d a zwróci t r u e , jeże li o d n ajd zie w olną ro b o tn icę do
WorkTheNextShift() w ykonania pracy, lub f a l s e , gdy nie będzie m ogła żadnej znaleźć.
★ Przycisk Przepracuj następną zm ianę wywołuje m eto d ę W o rk T h e N e x tS h ift().
N ak azu je o n a pszczołom wykonywać pow ierzone im z a d a n ia i zw raca gotowy
do w yśw ietlenia ra p o r t zm iany. Przycisk każe każd em u obiektow i typu W orker
pracow ać p rzez je d n ą zm ianę, a n astęp n ie spraw dza jego statu s, aby możliwe
było d o d an ie kolejnego w iersza do rap o rtu .

CurrentJob i S h ifts L e ft s ą ★ Przyjrzyj się uważnie zrzutow i e k ra n u zam ieszczonem u n a n astęp n ej stronie, by
właściwościami tylko do odczyto. określić, jakie są efekty działania m etody W o rk T h e N e x tS h ift(). W pierwszej
kolejności generuje o n a łańcuch znaków („R ap o rt zm iany n u m er 20”). N astępnie
W o rk e r używa pętli f o r , by dla każdego o biektu W orker w tablicy w o rk e rs [ ] wykonać
dwie instrukcje i f . Pierwsza z nich spraw dza, czy robotnica zakończyła aktualne
CurrentJob: string zadanie („R obotnica n u m er 2 zakończyła swoje zadanie”). D ru g a instrukcja i f
ShiftsLeft: int spraw dza, czy robotnica aktualnie pracuje, a jeśli tak, to wyświetla, ile zmian
zajm ie jej jeszcze wykonywanie bieżącej pracy.
private jobsICanDo: string[] K rólow a używa tablicy obiektów W orker do zarząd zan ia g ru p ą ro b o tn ic i pracam i
private shiftsToWork: int p rzez nie wykonywanymi.
private shiftsWorked: int ★ C u r r e n tJ o b je st właściwością tylko do odczytu, k tó ra pozw ala królowej
określić p ra c ę a k tu a ln ie w ykonywaną przez d a n ą ro b o tn icę (Patrol z żądłam i,
DoThisJob()
Utrzymywanie ula i in n e). Jeżeli ro b o tn ica nie w ykonuje żadnego zadania,
WorkOneShift()
zostaje zw rócony pusty łańcuch znaków.
★ O b iek t Queen p ró b u je przypisać zad an ie robotnicy za p o m o cą jej m etody
D o T h is J o b ( ). Jeżeli je st o n a ak tu aln ie w olna i je st to p raca, k tó rą dany typ
pszczoły p o trafi wykonywać, to z a d an ie zo staje zaakceptow ane, a m e to d a
zw raca t r u e . W przeciw nym razie otrzym ujem y f a l s e .
★ Kiedy wywoływana jest m etoda W o rk O n eS h ift(), pszczoła pracuje przez
je d n ą zmianę. Przechowuje ona pole określające, ile jeszcze zmian potrzeba do
zakończenia aktualnej pracy. G dy zadanie zostaje wykonane, czyści pole określające
nazwę bieżącej pracy, dzięki czem u może przyjąć następne zadanie. M etoda zwraca
tr u e , jeśli pszczoła zakończyła zadanie, lub f a l se — w przeciwnym razie.

S tring.IsNullOrEmpty()

W: | » o h ' k £i p z ^ p n c a h o c j; a lz y u n ^ is n iw a n r o ib ia p o ^ w i^ w ^

Cu"-ent J ° b ,est łańcuchem pust ym lub zawiera wartość null, a false - w przeciwnym razie.
320
Dziedziczenie

STWÓRZ FORMULARZ.
Formularz jest dość prosty — cała jego logika znajduje się w klasach Queen i Worker. Posiada on prywatne
pole Queen i dwa przyciski, które wywołują metody AssignWork() oraz W orkTheN extShift() . Będziesz musiał
dodać kontrolkę ComboBox dla zadań realizowanych przez pszczoły (spójrz na odpowiedni zrzut, aby zobaczyć
je wszystkie), kontrolkę NumericUpDown, dwa przyciski oraz wielowierszowe pole tekstowe do wyświetlania
raportu zmiany. Będziesz także potrzebował konstruktora klasy formularza — znajduje się pod zrzutem ekranu.

Przycisk n extS h ift


Kontrolka NumericUpDown o nazwie sh ifts wywołuje metodę
_________________ WorkTheNextShiftO,
System zarządzania ulem która zwraca obiekt
To j e s t kontrolka ComboBox
o nazw ie workerBeeJob. string zawierający
aport zm iany.
Użyj je j właściwości Przydział prac robotnicom
Ite m s do ustaw ienia listy Zadanie robotnicy Zini^Ty
Przyjrzyj s ię dokładnie
rodzajów zadań oraz u s taw Zbieranie nektaru IT j T
i tem u raportowi zm iany,
właściw ość DropDownSty le który j e s t generowany
Przypisz tę pracę pszczole
na DropDownList. Dzięki prze z obiekt klasy
tem u użytkownik będzie Queen. Rozpoczyna się
mógł wybrać tylko z adania Raport zmiany numer 20
on numerem zmiany,
Robotnica numer 1 zakończy ’Wytwarzanie miodu' po tej zmianie
znajdujące s ię na liście. Robotnica numer 2 robi 'Pielęgnacja jaj' je szcze przez 1 zmiany a następnie okre śla ,
Kliknij elem ent Item s Robotnica numer 3 nie pracuje co robią poszcze gólne
w oknie Properties, by Robotnica numer 4 robi 'Zbieranie nektaru'jeszcze przez 2 zmiany
robotnice. W celu
dodać do listy w szystkie dodania znaku przejścia
s z e ść prac. Użyj kontrolki GroupBox,
by narysować ramkę wokół do nowej linii użyj
s e k wencji formatujących
pozostałych kontrolek. Określ
te k s t w yśw ietlany na ramce, „ \r\n". B ędzies z
potrzebował pę tli,
Nazwij to pole TextBox „rep o rt" podając go we w łaściwości Text by przejrzeć tablicę
i us taw Je<go właściwość robotnic, i instrukcji if
M ultiline na true.
do generowania tek stó w .

p u b lic Form1() Oto kompletny konstruktor formularza. Umieściliśmy w nim kod


{ z poprzedniej strony. Zaw iera ta kże te n w'ierrs z >
InitializeC om ponent(); który w yśw ietla w kontrolce C °mb°B°x p ierws z ą opcj ę _(.dzięki
workerBeeJob.SelectedIndex=O; czem u, po w yśw ietleniu formularza, l ^ m M ^ n ie będzie p u sta ).
Worker[] workers = new W orker[4];
workers[0] =new Worker(new s tr in g [] { Zbieranie nektaru", "Wytwarzanie miodu" } ) ;
w o rke rs[l] =new Worker(new s tr in g [] { Pielęgnacja j a j " , "Nauczanie pszczółek" } ) ;
workers[2] =new Worker(new s tr in g [] { Utrzymywanie u la ", "Patrol z żądłami" } ) ;
workers[3] =new Worker(new s tr in g [] { Zbieranie nektaru", "Wytwarzanie miodu",
"Pielęgnacja j a j " , "Nauczanie pszczółek" "Utrzymywanie u la ", "Patrol z żądłami" } );
queen = new Queen(workers);
Twój formularz będzie potrzebował pola typ u Queen o nazwie queen.
Przek.a.ze s z tę t ablicę referencji ot>iektó w Wor-ker do konstruktora obiektu Quee n .

UTW ÓRZ KLASY WORKER ORAZ QUEEN.


Wiesz już prawie wszystko, co musisz, o klasach Worker i Queen. Pozostało jeszcze kilka drobnych szczegółów.
Queen.AssignWork() przetwarza wszystkie obiekty tablicy workers klasy Queen i próbuje przypisać zadanie
do każdego obiektu typu Worker za pomocą metody DoThisJob() . Obiekt Worker sprawdza swoją tablicę
łańcuchów znaków jobsICanDo , aby określić, czy dane zadanie może być przez niego wykonane. Jeśli tak,
ustawia swoje prywatne pole shiftsToW ork na czas pracy liczony w zmianach, CurrentJob na zadanie,
a pole shiftsW orked na zero. Kiedy zakończy jedną zmianę, zwiększa shiftsW orked o jeden. Właściwość
tylko do odczytu S h ifts L e ft zwraca shiftsToW ork - shiftsW orked — królowa używa jej do określenia,
jak dużo zmian pozostało jeszcze do zakończenia zadania.

jesteś tutaj ► 321


Rozwiązanie ćwiczenia

p u b lic class Worker {


Kons tr uktor ustaw ia
p u b lic W o rker(strin g[] jobsICanDo) {
Rozwiązania this.jobsICanDo = jobsICanDo;
w łaściwość jobsICanDo,
która j e s t tablicą
ćwiczeń } obiektów typu strin g.
J e s t ona prywatna,
S h ifts L e ft j e s t w łaściw ością p u b lic in t S h ifts L e ft { ponieważ chcemy, aby
tylko do odczytu, która get { królowa raczej prosiła
oblicza, ile zmian pozostało - return shiftsToWork shiftsWorked; robotnicę o realizację
do zakończenia aktualnego zadania, niż osobiście
zadania. } sprawdzała, czy p szc zoła
} p otrafi je wykonać.
p riv a te s trin g currentJob =
p u b lic s trin g CurrentJob {
get {
return currentJob;
C u r r e n tjo b t a k ż e j e s t }
w ła ś c iw o ś c ią ty lk o d o }
o d c z y t u . P r z e k a z u j e ona
k r ó lo w e j, k t ó r e z a d a n ie p riv a te s tr in g [] jobsICanDo;
musi zostać ukończone. p riv a te in t shiftsToWork;
p riv a te in t shiftsWorked;

l ic bool DoThisJob(string jo b , in t numberOfShifts)


Królowa używ a metody
i f (JString.IsN ullO rEm pty(currentJob)) ---------
'■
DoThisJob() robotnic w celu
p rzy p isyw ania im nowych zadań return fa ls e ;
— s p rawdza ich pole jobsICanDo, fo r ( in t i = 0; i < jobsICanDo.Length i++)
aby określić, czy dana pszczoła i f (jobsICanDo[i] == job) {
potrafi wykonywać ten rodzaj currentJob = jo b ;
pracy. this.shiftsToW ork numberOfShifts;
shiftsWorked = 0;
return tru e ; Użyliśmy ! — ope ratoro. NOT —
} aby dow iedzieć się , czy obiekt
return fa ls e ; typu string NIE j e s t równy nu"
} lub p u sty . Jest' to po pro stu
sprawdzenie, czy coś j e s t równe
publ ic bool DidYouFinish() { false.
i f (String.IsN ullO rEm pty(currentJob))
return fa ls e ;
Królowa korzysta z metody shiftsWorked++;
DidYouFinish() należącej i f (shiftsWorked > shiftsToW ork)!
do robotnic, aby nakazać shiftsWorked 0;
im rozpoczęcie kolejnej shiftsToWork 0;
zm iany. Zwraca ona tftue currentJob =
tylko w tedy, gdy j e s t to
return tru e ;
naprawdę o sta tn ia zm iana
z przeznaczonych na }
else Zwróć szczególną uw agę na za sto so w aną
wykonanie zleconej p racy. tutaj log ikę. W pierw szej kolejności
Dzięki tem u królowa może return fa ls e ;
s p rawdz ane je s t pole currentJob: gdy
dodać do raportu linijkę robotnica nie ma przydzielonego zadania,
i poinformować, że p s z c z ó ł
po tej zm ianie zakończy zwraca false i m etoda s ię kończy. Jeżeli ma,
z wię ksza s ię wartość shiftsW orked. Nieco
sw oją pracę.
p<5żniej porównywana j e s t ona z shiftsToWork
w celu s prawdzenia, czy praca ju ż została
wykom m . Jeśli t ak, metoda zwraca true.
J e ś li nie, otrzym ujem y false.

322 Rozdział 6.
Dziedziczenie

p u b lic c l a s s Queen {
p u b lic Q ueen(W orker[] w o rk ers) {
th i s .w o r k e r s = w o rk e rs; Króhwa dba o prywatność tablicy r° b ° tnic, _p ^ ^ a z j ^
} K ó p w a a tto wym ustaw ieniu żadna klasa nie p w r n m
n í ć ° m ° ż n Z iCm zm iany tych wartości... Nie pow ^ a ich
p r i v a t e W orker[] w o rk e rs; n a w e t widzieć, bo t y k króh w (może wydawać roz tiaz y .
p r i v a t e i n t sh iftN u m b er = 0; Konstruktor ustaw ia w r f o t t pola.

p u b lic bool A ssig n W o rk (strin g j o b , i n t nu m b erO fS h ifts) {


f o r ( i n t i = 0 ; i < w o rk e rs.L e n g th ; i++)
i f (w o rk e rs [i] .D o T h is J o b (jo b , n u m b e rO fS h ifts))
re tu rn tr u e ;
re tu rn f a ls e ; Kiedy kroiowa p rzy p is uje siu o w rot>otnicom zadania, rozpoczyna od
} p ierwszej pszczohy i p rót>uje je j zlecić daną czynność. Jeśli ta nie
m iZe j e j ■w ykonać kró,lowa p rzechodzi do następnej pszczoły.

p u b lisch isf tN1unmbeW


r+ + ;T h eN eX tS h ift() { ^ y u y j n1 ' j e s J t s t f u %%%%> .
s t r i n g r e p o r t = "R ap o rt zmiany numer " + sh iftN u m b er + " \ r \ n " ;
f o r ( i n t i = 0 ; i < w o rk e rs.L e n g th ; i++)

Metoda i f (w o rk e rs [i] .D id Y o u F in is h ( ))
WorkTheN extShift() r e p o r t += "R o b o tn ica numer " + ( i + l ) + " z a k o ń c z y ła sw oje z a d a n i e \ r \ n " ;
obiektu Queen i f ( S tr in g .I s N u llO rE m p ty (w o rk e rs [i] .C u r re n tJ o b ))
nakazu je każdej r e p o r t += "R o b o tn ica numer " + (i + 1) + " n ie p r a c u j e \ r \ n " ;
robotnicy pracować e ls e
prze z jedną zm ianę
i f ( w o r k e r s [ i ] .S h i f t s L e f t > 0)
i dodać w iersz do
raportu w zależności r e p o r t += "R o b o tn ica numer " + (i + 1) + " ro b i '" + w o r k e r s [ i] .C u r r e n tJ o b
od je j s ta tu su . + " ' je s z c z e p rz e z " + w o r k e r s [ i ] . S h i f t s L e f t + " z m ia n y \r \n " ;
e ls e
r e p o r t += "R o b o tn ica numer " + ( i + l ) + " zakończy '"
+ w o r k e r s [ i] .C u r r e n tJ o b + " ' po t e j z m i a n ie \ r \ n " ;
}
r e t u r n r e p o r t;
}
}
Formularz używ a sw oje g o pola
Już pokazaliśmy Ci konstruktor. Tu jest reszta kodu klasy formularza: queen w celu p r z e c h o w a n ia
referencji do obiektiu klasy Q ueen
p r i v a t e Queen queen; który z kolei p o siada tablicę _
referencji do obiektów nob°tn ic .
p r i v a t e v o id a s s ig n J o b _ C lic k ( o b je c t s e n d e r , EventA rgs e) {
i f (queen .A ssig n W o rk (w o rk erB eeJo b .T ex t, ( i n t ) s h i f t s . V a l u e ) == f a l s e )
M essageBox.Show("Nie ma d o stęp n y ch r o b o tn ic do w ykonania z a d a n ia '"
+ w o rk erB eeJo b .T ex t + ...... , "Królowa p s z c z ó ł m ó w i..." ) ;
e ls e
M essageB ox.Show ("Zadanie '" + w o rk erB eeJo b .T ex t + " ' b ę d z ie ukończone za
+ s h i f t s .V a l u e + " zm iany", "Królowa p s z c z ó ł m ó w i..." ) ;
} Przycisk assignJob wywottuje
m etodę AssignW ork() stu żącą
p r i v a t e v o id n e x tS h i f t _ C l ic k ( o b je c t s e n d e r , EventA rgs e) { do przypisyw ania robotnicom
r e p o r t.T e x t = q u ee n .W o rk T h e N e x tS h ift(); zadań i wyśw ietlania
stosownego komunikatu
M essageB ox w zależności
od tego, czy udato s ię zn aleźć
robotnicę zdolną do wykonania
pracy, czy nie.

jesteś tutaj ► 323


Jesteśmy tylko pszczołami

Użyj dziedziczenia,
aby rozszerzyć system zarządzania pszczołami
Teraz, gdy masz już uruchomiony system podstawowy, użyj dziedziczenia do kontroli konsumpcji
miodu przez każdą pszczołę. Różne typy pszczół spożywają różne jego ilości, ale i tak to
królowa spożywa najwięcej ze wszystkich. Użyjesz zatem technik, które poznałeś podczas nauki
dziedziczenia, do utworzenia klasy bazowej Bee oraz klas pochodnych Queen i Worker.

Dodasz klasę B e e po której i dziedziCz ą !e Czasami w diagramie


klasy Queen oraz Worker. Klasa ta bę dzie klas pokazywane s ą także
dysponować podstaw ow ym sktadowym' . wartości zwracane oraz
związanymi z w y lic za łe m konsumpcji mlodu. składowe prywatne.

Podczas każdej kolejnej z m r n y


iH etio <
/o!d !Horl<!!/ż:o n s u m p ti0 n R a te ( ) w y lic za , poświęconej na wykonyw anie
^m ann s p o z y wa p s z c z o ^ p o d c z a s je d n e j zadania robotnice zużyw a ją w ięcej
miodu. Klasa Worker będzie
dziedziczyć po B ee i prze s łaniać
m etodę HoneyConsumptionRate ().

....- ..... ..
Q ueen W o rk e r
Także klasa królowej private workers: Worker[] CurrentJob: string
będzie m usiała
dziedziczyć po klasie private shiftNumber: int ShiftsLeft: in t
B ee i wywoływać metodę private jobsICanDo: string[]
PoneyConsumptionRate(), private shiftsToWork: in t
by dodać do raportu private shiftsW orked: in t
informacje o miodzie AssignWork()
spożytym podczas WorkTheNextShift()
zmiany. DoThisJob()
DidYouFinish()
override
C HoneyConsumptionRateQ

Add Existing Item 1( dobrym rozwiązaniem jest

Z aw SM gdy ^
utworzenie nowe9 ° F™ ' ,(i nie5pOC?ziewanie zajdzie ta k a P ° j L em Y nazwy projektu
pierwszego rozw,?zan^ e P t prawym przyask,e ^ ^ 5tat-ego
powtórne wykorzystanie p 1 -j Acy Existing Item, prze) katalogu projektu

;,o

g z s s u & s &: nazw.


r * 1* * < o,m h nv CD

324 Rozdział 6.
Dziedziczenie

Jeszcze nie skończyliśmy! Królowa odebrała właśnie telefon od swojej kwatermistrzyni, z informacją że
musi wiedzieć, ile miodu wydaje ul na potrzeby żywieniowe swoich robotnic. Jest to wspaniała okazja
Ćwiczenia do wykorzystania Twoich nowych umiejętności dziedziczenia! Dodaj klasę Bee i użyj jej do wyliczania
spożycia miodu podczas każdej zmiany

UTWÓRZ KLASĘ BEE I ZMIEŃ KLASY QUEEN I WORKER, BY PO NIEJ DZIEDZICZYŁY.


Klasa Bee definiuje metodę HoneyConsumptionRate(), która wylicza, ile miodu pszczoła spożywa podczas
jednej zmiany. Twoim zadaniem jest zmodyfikowanie klas Queen oraz Worker, tak by rozszerzały tę klasę.
class Bee {
pub lic const double HoneyUnitsConsumedPerMg = .25;

pub lic double WeightMg { get; p riv a te s e t; }


Konstruktor klasy B ee pobiera jeden
pub lic Bee(double weightMg) { parametr — wagę p s z w h y wyrtaoną,
WeightMg = weightMg; w miligramach — j e s t on nas tęp nie _
używ any podczas wylicz ania sp(jzyc ia m ^d ij.

v irtu a l p u b lic double HoneyConsumptionRate() {


return WeightMg * HoneyUnitsConsumedPerMg;

3 ZMODYFIKUJ KLASY QUEEN I WORKER, BY DZIEDZICZYŁY PO KLASIE BEE.


Klasy Queen oraz Worker będą dziedziczyć podstawowe zachowanie związane ze spożywaniem miodu po
swojej nowej klasie bazowej Bee. Będziesz musiał zmienić ich konstruktory, tak by wywoływały konstruktor
klasy bazowej.

★ Zmodyfikuj klasę Queen, by dziedziczyła po Bee. Będziesz musiał dodać do jej konstruktora parametr
typu double o nazwie weightMg, który będziesz następnie przekazywał do konstruktora klasy bazowej.

★ Zmodyfikuj także klasę Worker, by dziedziczyła po Bee — w jej konstruktorze będziesz musiał
wprowadzić taką samą zmianę jak w konstruktorze klasy Queen.
P°dp°wiedź: M ożesz w ykorzystać k o i v u n i ^ btę du „nie
zaw iera konstruktora“, który z o b a c z y ć ju ż w p °p rz e dním
rozdziale! Zmodyfikuj klasę Worker, by d z ^ d ^ c z y ^ po
Bee, a n astęp n ie zbuduj projeM. K'iedy IDE w yśw iett'i btąo,
dwukrotnie go kliknij, a IDE a u to m a ty c z n i przej dz ie p r n s i
do konstruktora klasy Worker. To b a i-fa wyg odne!

ZMODYFIKUJ FORMULARZ TAK, BY PODCZAS TWORZENIA OBIEKTÓW


QUEEN I WORKER OKREŚLANA BYŁA W AG A PSZCZÓŁ.
Ponieważ zmieniłeś konstruktory klas Queen i Worker, będziesz musiał zm ienić tak że k o n stru k to r
fo rm u larza. Musisz zadbać o to, by podczas tworzenia nowych obiektów Queen i Worker, w wywołaniach ich
konstruktorów była przekazywana waga pszczół. Robotnica numer 1 ma ważyć 175 mg, robotnica numer 2:
114 mg, robotnica numer 3: 149 mg, robotnica numer 4: 155 mg, a królowa: 275 mg.

Teraz kod powinien się skompilować.

jesteś tutaj ► 325


Jesteśmy tylko pszczołami

[4 PRZESŁOŃ METODĘ HONEYCONSUMPTIONRATE() KLASY WORKER.


Królowa spożywa miód tak jak zwyczajne obiekty klasy Bee. A robotnice spożywają tę samą
ilość miodu... ale wyłącznie gdy nie pracują! Kiedy pracują, podczas każdej kolejnej zmiany
ilość spożywanego miodu zwiększa się o 0,65.
Oznacza to, że klasa Queen może używać metody HoneyConsumptionRate() odziedziczonej
po klasie Bee, natomiast klasa Worker musi ją przesłonić i podczas każdej przepracowanej
zmiany dodawać 0,65 jednostki do spożycia miodu. Możesz także dodać stałą
honeyUnitsPerShiftWorked , by w zrozumiały sposób wyrazić, co robi ta nowa wersja metody.

Możesz użyć IDE, by ułatwić sobie pracę. Przejdź do klasy Worker i wpisz p u b lic o verrid e
— kiedy wpiszesz znak odstępu na końcu, IDE automatycznie wyświetli listę wszystkich metod,
które możesz przesłonić:
public override
0 Equals(object obj)
O GetHashCodeQ
0 |_______ double Bee.HoneyConsumptionRateQ
0 ToStringO

Wybierz metodę HoneyConsumptionRate() z okienka In te lliS e n s e . Kiedy to zrobisz, IDE


wstawi do kodu szkielet metody zawierający wywołanie metody klasy bazowej. Zmodyfikuj
ją tak, by pobierała wynik zwrócony przez wywołanie base.HoneyConsumptionRate(),
a następnie powiększała go o 0,65 jednostki dla każdej przepracowanej zmiany.

[4 DODAJ INFORMACJE O SPOŻYCIU MIODU DO RAPORTU ZMIANY.


Będziesz musiał zmodyfikować metodę W orkTheNextShift() klasy Queen tak, by śledziła
zużycie miodu przez obiekt Queen oraz wszystkie obiekty Worker, wywołując w tym celu
metodę HoneyConsumptionRate() każdego z nich i sumując uzyskane wyniki. Następnie
powinna dodać do raportu wiersz o następującej treści (gdzie XXX zostanie zmienione
na liczbę spożytych jednostek miodu):
C a łk o w ite s p o ż y c ie m iodu: XXX Jed no ste k
jed yn ie trzy w iersze tedtu.

M WYSIL
C5CJ SZARE KOMÓRKI
Wszystkie pszczoły dysponują metodą HoneyConsumptionRate(),
a zarówno obiekty Queen, jak i Worker są pszczołami, gdyż ich klasy
dziedziczą po Bee. Czy zatem nie powinno być jakiegoś jednego spójnego
sposobu wywoływania tej metody dla dowolnego obiektu Bee, niezależnie
od tego, jakiego typu by on nie był?

326 Rozdział 6.
Dziedziczenie

Rozwiązania K?ns t r u kt? r p ° s i a da te r a z n o w y p a r a m e tr


Dziedziczenie
ćwiczeń k % ry jeas t prz e k a z y w a n y do k o n stru k to r a '
k las y b a zo w e j. p o z w ala on na p rz e k a za n ie ułatwiło
do tw o rzo n e g o o b ie k tu p s z c z o ty j e j u n i
zaktualizowanie
c la s s W orker : Bee kodu i dodanie
{
p u b lic W o r k e r ( s tr in g [] jobsIC anD o, do ub le weightMg) nowych
: base(w eightM g)
{ zachowań
th is .jo b s IC a n D o = jobsIC anD o;
związanych
co n st double h o n e yU n ltsP e rS h lftW o rke d = .6 5 ;
z konsumpcją
p u b lic o v e r rid e do ub le HoneyConsumptionRate()
miodu do klas
{
double consum ption = base.HoneyConsum pt1onRate();
Queen i Worker.
consum ption += s h lfts W o rk e d * h o n e yU n ltsP e rS h lftW o rke d ; Znacznie
r e tu r n consum ption;
>
trudniej byłoby
Kj a s a Worke r p r z e s ła n ia m e to d ę
H o n e y ^ s ^ p - t u n R a t e O , d o d a ją c do
wprowadzać
m ej z w ię k s z o n e z u ż y c i e m io d u p r z e z
p ra cu ją c e p s z c z o ły . wspomniane
// P o zo sta ła część k la s y W orker n ie z o s ta ła zm ieniona
zmiany, gdyby
/ / ... kod był
powtarzany
w kilku klasach.
W formularz u ty lko kons truktor uległ zm ianie
— p ^ s M a czę ść jego klasy j e s t \ k a s w a .
p u b lic Form1() {
In itia liz e C o m p o n e n t( ) ;
W o rke r[] w orkers = new W o rk e r[4 ];
w o rk e rs [0 ] = new W orker(new s t r i n g [ ] { Z b ie ra n ie n e k ta ru "W ytw arzanie m iodu" } , 175
w o r k e r s [ l] = new W orker(new s t r i n g [ ] { 1P ie lę g n a c ja j a j " , "N auczanie p s z c z ó łe k " } ,C 114 "
w o rk e rs [2 ] = new W orker(new s t r i n g [ ] { 1Utrzym ywanie u la " " P a tro l z ż ą d ła m i" } ~149
w o rk e rs [3 ] = new W orker(new s t r i n g [ ] { Z b ie ra n ie n e k ta ru , "W ytw arzanie miodu"
"P ie lę g n a c ja j a j " , "N auczanie p s z c z ó łe k " "U trzym yw anie u la " , "P a tro l z żą d ła m i" } , 155 )
queen = new Q ueen(w orkers, 2 7 5 );

Jedyną zm ianą w formularzu j e s t to, że do

V
konstruktorów obiektów typów Worker i Queen
m usi zo sta ć dodana waga p szczoty.

jesteś tutaj ► 327


Rozwiązanie ćwiczenia

class Queen : Bee


Konstruktor klasy Queen zo sta t
{ zmodyfikowany tak samo jak
p u b lic Queen(Worker[] workers, do ub le w eightM g ) konstruktor klasy Worker.
: base(w eightM g)
{
this.w orkers = workers;
}

p riv a te Worker[] workers;


p riv a te in t shiftNumber = 0;

p u b lic bool AssignW ork(string jo b , in t numberOfShifts)


Ten kod nie zo sta t zmieniony.
{
fo r ( in t i = 0; i < workers.Length; i++)
i f (w orkers[i].D oThisJob(job, numberOfShifts))
return tru e ;
return fa ls e ;

p u b lic s trin g WorkTheNextShift()


Obliczenie spożycia miodu
{ w ciągu zm iany m usi się
double honeyConsumed = HoneyConsumpt1onRate(); rozpocząć od uwzględnienia
miodu spożytego przez królową.
shiftNumber++;
s trin g report = "Raport zmiany numer #" + shiftNumber + " \ r \ n " ;
fo r ( in t i = 0; i < workers.Length; i++)
Podczas cnclizcw cnic każdej
honeyConsumed += workers[1].HoneyConsumpt1onRate(); kolejnej pszczoły je j spożycie
j e s t dodawane do spożycia
i f (w orkers[i].D idY ou F in ish())
report += "Robotnicza numer " + ( i + 1)
+ " zakończyła swoje z a d a n ie \r\n ";
i f (String.IsN ullO rE m pty(w orkers[i].C urrentJob))
report += "Robotnicza numer " + ( i + 1) + " nie p ra c u je \r\n ";
else
i f (w o rk e rs [i].S h ifts L e ft > 0)
Także ten report += "Robotnicza numer " + ( i + 1) + " robi
kod nie ulegt + w orkers[i].C urrentJob + " ' jeszcze przez "
żadnym
+ w o rk e rs [i].S h ifts L e ft + " zm iany\r\n";
zmianom.
else
report += "Robotnicza numer " + ( i + 1) + " zakończy '"
+ w orkers[i].C urrentJob + " ' po te j z m ia n ie \r\n ";
}

rep ort += "Całkowite spożycie miodu: 11 + honeyConsumed + " je d n o s te k \r\n ";

ret urn report ; Po dodaniu do raportu infcrmacji o sta n ie wszy stk ich
robotnic królowa m usi jed yn ie M t ó w iersz
z informacją o całkowitym sp czy ciu miodu.

328 Rozdział 6.
7. Interfejsy i klasy abstrakcyjne

Klasy, które dotrzymują


swoich obietnic

Czyny potrafią powiedzieć więcej niż słowa. Czasami potrzebujesz pogrupować


swoje obiekty na podstawie tego, co ro b ią , zamiast tego, po jakiej klasie dziedziczą. To jest
moment, w którym należy powiedzieć o in te rfe jsa ch . Pozwalają one na pracę z każdą
klasą, która jest w stanie wykonać daną czynność. Jednak wraz z w ie lk im i m ożliw ościam i
przychodzi w ie lk a odpow iedzialność i każda klasa, która implementuje interfejs, musi
w yp e łn ić w szystkie swoje zob o w ią za n ia ... albo kompilator połamie Ci nogi, zrozumiałeś?

to jest nowy rozdział ► 329


Robotnice, łączcie się!

Wróćmy do pszczelej korporacji


Korporacja General Bee chce, abyś zmienił system zarządzania ulem
napisany w poprzednim rozdziale w superkosmiczny symulator.
Poniżej zaprezentowano ogólny zarys specyfikacji dla nowej wersji
programu.

e ym..iato r ula korporacji G ^ r a l Bee

Aby lepiej reprezentować życie w ulu, musimy dodać ^egalne z d o ln a


pszczołom robotnicom.
• Wszystkie pszczoły spożywają miód i mają swoją masę-
Wydaje się , ż e klasy
• Królowe przypisują pracę, monitorują raporty zmianowe i rozkazują B ee i Worker nie
pszczołom rozpocząć nową zmianę. potrzebują wielu
zm ian. M ożemy
• Wszystkie robotnice pracują w systemie zmianowym. rozszerzyć klasy,
które ju ż mamy,
zdolność ostrzenia swoich ^dei, aby dodać nowe
Patrole z żądłami powinny miec
możliwości.
wyszukiwania wrogów i żądlenia ick
Pszczoły zbierające nektar są odpowiedzialne za WaBzacE kw|atów,
zbieranie nektaru i powrót z nim do ula.

d j , s ię ' ze i d z i e m y m usieli
przechowywać różne dane robotnic
zależności od wykonywanej
przez nie pracy.

Większość rzeczy pozostaje bez zmian


Pszczoły w nowym symulatorze ula także będą konsumowały miód
i będą to robiły w ten sam sposób. Królowa w dalszym ciągu będzie
mogła wydawać rozkazy, przypisywać zadania robotnicom i oglądać
raporty zmianowe, które pokazują, co kto robi. Robotnice, tak jak
wcześniej, będą pracowały w trybie zmianowym. Jedyne, co się zmieniło,
to fakt, że ich prace zostały nieco rozbudowane.

330 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Możemy użyć dziedziczenia


do utworzenia klas dla różnych typów pszczół
Poniżej zaprezentowano hierarchię klas wraz z Worker i Queen,
które dziedziczą po Bee. Klasa Worker posiada klasy potomne
N e cta rC o lle cto r oraz S tin g P a tro l .

Czy p a m iętasz,
że królowa potrzebuje
To tutaj i^datkow ej ilości
przechowywane są mi°d u ? W tym m iejscu
informacje na tem at p rze s toniliś my je j metodę
wag i i spożycia GetHoneyConsumption().
miodu.

Klasy StingPatrol oraz


Tutaj
Tak będą wyglądaty N ^ a r C d ^ t o r dziedziczą
przechowywane po klasie Worker.
są informacje nowe klasy p°ch°dne .
o liczbie zmian
pozostatych
do (ukończenia c la s s S tin g P a tro l : Worker
/
z adania.
Worker Queen p u b lic in t AlertLevel { get; p riv a te set; }
Job Worker[]
p u b lic in t StingerLength { get; set; }
ShiftsToWork ShiftNumber
ShiftsWorked p u b lic bool SharpenStinger(int Length)
ShiftsLeft { ... }
DoThisJob() AssignWork() p u b lic bool LookForEnemies(){...}
WorkOneShift() WorkTheNextShift() p u b lic void S tin g (s trin g Enemy){...}
GetHoneyConsumption()
}

c la s s N e c ta rC o lle c to r : Worker
{
p u b lic in t Nectar { get; se t; }
StingPatrol NectarCollector
p u b lic void F in d F lo w e rs (){...}
StingerLength Nectar
AlertLevel p u b lic void G a th e rN e c ta r(){...}
p u b lic void R eturnT oH ive(){...}
}
SharpenStinger() FindFlowers()
LookForEnemies() GatherNectar()
Sting() ReturnToHive()

<W> WYSIL
1Ą te klasy p rzechow ują'
SZARE KOMÓRKI
informacje specyficzne dl Co by się stało, gdybyś m iał pszczołę,
danego rodzaju pracy.
która musiałaby żądlić i zbierać nektar?

jesteś tutaj ► 331


Interfejsy dla zadań

Interfejs daje klasie do zrozumienia, że musi ona


zaimplementować określone metody i właściwości Używasz
Klasa może dziedziczyć tylko po jednej klasie bazowej. Utworzenie dwóch niezależnych interfejsu, aby
klas potomnych dla pszczół typu StingPatrol i N ectarC ollector nie rozwiązało
problemu tego, co dzieje się, gdy pszczoła wykonuje oba rodzaje prac.
zmusić klasę
do posiadania
Metoda królowej DefendTheHive() może wydać polecenie obrony ula tylko obiektom
S tingP atrol. Chciałaby ona przeszkolić pozostałe pszczoły w zakresie zaawansowanych wszystkich
technik posługiwania się żądłem, ale nie ma możliwości wydania im rozkazu ataku.
metod i właś­
c la s s Queen {
p r iv a t e v o id D e fe n d T h e H iv e (S tin g P a tro l p a t r o ll e r ) {...}
ciwości, które
} zostały w nim
Chciałabym ,
wyszczegól­
m oje p a n ie,
a b y ś c ie pom ogły
nione — jeżeli
0
w o b ro n ie ula. klasa takowych
0 ¿ /e k t Q ^
nie posiada,
Teraz dysponujemy obiektami NectarCollector, które wiedzą, jak zbierać nektar
z kwiatów, oraz obiektami StingP atrol, które potrafią ostrzyć swoje żądła i patrolować
kompilator
okolice ula w poszukiwaniu nieprzyjaciół. Jednak nawet jeśli królowa mogłaby nauczyć generuje błąd.
pszczoły typu N ectarC ollector obrony ula, na przykład poprzez dodanie do ich definicji
metod SharpenStinger() i LookForEnemies(), to i tak nie byłaby w stanie przekazać ich
w wywołaniu metody DefendTheHive(). Trzeba by zatem użyć dwóch odrębnych metod:
N aw et jeśli królowa doda m etody
p r iv a t e v o id D e fe n d T h e H iv e (S tin g P a tro l p a t r o l l e r ) ;
zw iązane z patrolowaniem
do obiektu NectarCollector,
p r iv a t e v o id A lte rn a te D e fe n d T h e H iv e (N e c ta rC o lle c to r p a t r o l l e r ) ; to i tak nie będzie mogfa go
przekazać do sw ojej metody
DefendTheHive(), gdyż oczekuje
Nie jest to jednak szczególnie dobre rozwiązanie. Obie te metody byłyby takie same, gdyż ona referencji typu StingP atroll
wywoływałyby te same metody obiektów przekazanych w ich wywołaniach. Jedyna różnica a obiektu NectarCollector nie
można przekazać jako referencji
polegałaby na tym, że pierwsza metoda pobierałaby obiekt typu S tin gP atrol, a druga S tingPatrol.
— obiekt N ectarC ollector zmodyfikowany o metody niezbędne do patrolowania okolic
Królowa mogłaby dodać
ula. A sam już wiesz, jak bolesne jest posiadanie dwóch identycznych metod. drugą metodę, o nazwie
A lternateDefendTheHive(), do której
Na szczęście C# umożliwia nam skorzystanie z interfejsów, które z powodzeniem można by przekazać referencję
NectarCollector, jednak takie
można w takich sytuacjach stosować. Pozwalają Ci one zdefiniować zestaw metod, rozwiązanie byłoby trudne
które klasa m u si posiadać. i dziwaczne.

Interfejs w ym aga tego, aby klasa miała określone metody. Chodzi generalnie ;
o to, że kom pilator generuje błąd, jeśli nie znajdzie wszystkich metod wymaganych wię i ej' obie m etod y > DefendTheHive()
oraz ^Aj ternateDe fendTheHive(), różniłyby
przez interfejs w każdej z klas, które go implementują. Metody mogą być napisane f ę tje dynie typem param etru. Gdyby
bezpośrednio w danej klasie lub mogą być dziedziczone po klasie bazowej. Interfejs królowa chciała nauczyć obrony pszczoły
z ajm ujące s ię op ieką nad jajam i lub
nie zajmuje się tym, w jaki sposób one i właściwości się tam znalazły. Ważne jest, utrzym yw aniem ula, to konieczne byłoby
że są dostępne podczas kompilacji kodu. dodanie kolejnych metod. Powstałby
stra szn y bałagan!

332 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Użyj słowa kluczowego i n t e r f a c e do zdefiniowania interfejsu


Dodawanie interfejsu do Twojego programu jest bardzo podobne Nazwy interfejsów zaczynaj ą się od I
do dodawania klas z wyjątkiem tego, że nigdy nie napiszesz w nim
żadnych metod. Po prostu definiujesz metodę oraz określasz, jaki Zawsz.e gdy tworzysz interfejs, powinieneś
typ zwraca i jakie ma parametry. Zamiast jednak umieszczać blok rozpocząć jego nazwę °d dużego I-Nie ma
z instrukcjami wewnątrz nawiasów klamrowych, po prostu kończysz żadnej zasady, która nakazywałaby u tak
wiersz średnikiem. postąpić, ale w ten sposób uczynisz kod znacznie
łatwiejszym do zrozumtenU- Możesz sam . .
Interfejsy nie przechowują danych, więc nie możesz dodać żadnych
sprawdzić, o ile łatwiejsze może b y c j w j ży?®
pól. M ożesz natomiast dodać definicje właściwości. Jest tak dlatego,
dzięki zastosowaniu się do tej sugestii- Przeldź D
że akcesory to po prostu nieco inny rodzaj metod, a interfejsy
jakiejkolwiek pustej linijki wewnąt rz metody w 'DŁ
zmuszają klasy do posiadania metod o określonych nazwach, typach
i wpisz „I". Zobaczysz, jak W e fa is e ^ w ie ™
i parametrach. Jeśli więc chcesz, aby Twój interfejs posiadał pole
cały zestaw interfejsów wbudowanych w
o określonej nazwie i danym typie, to użyj zamiast niego właściwości
— uzyskasz ten sam efekt.
p u b lic I n t e r f a c e IS tln g P a tr o l
Interfejs deklaruje. Każda klasa, która
s ię w ten sposób: {
implem entuje ten interfejs,
i n t A le rtL e v e l { g e t; } m usi mieć w szystk ie te
i n t S tin g e rL e n g th { g e t; s e t ; } metody i właściwości.
Interfejsu nie przechowują W przeciwnym razie program
danych. W zw iązku bool LookF orE nem ies(); nie skom piluje się.
ZJ y m nie mah żadnych i n t S h a rp e n S tin g e r(in t L e n g th );
poi ale mogą posiadać
wtaściw ości . }
W szystko
Każda klasa, która im plem entuje
interfejs, będzie potrzebowała
p u b lic in te r fa c e IN e c ta r C o lle c to r
Nie p is ze sz w interfejs ie
w publicznym
metody SharpenStinger() {
przyjmującej param etr typu int. v o id F in d F lo w e rs ();
żadnego kodu dla m etod —
po prostu w sta w ia sz t am
interfejsie jest
v o id G a th e rN e c ta r();
v o id R e tu rn T o H iv e ();
ich nazwy. Kod tw orzysz
wewnątrz klas, które ten
automatycznie
}
interfejs implementują.
publiczne,
W jaki sposób dodanie interfejsów pomaga królowej? Teraz może ona posiadać jedną ponieważ
metodę pozwalającą na przekazanie dowolnego obiektu, który wie, jak należy bronić ula:
będziesz go
p riva te void DefendTheHive(IStingPatrol p a tro lle r)
Ponieważ metoda pobiera re
używał do
ypu IS t i ngPatrol, b ęd ziesz mógł
przekazać w je j wywołaniu DOWOLN obiekt implem entujący ten interfejs. definiowania
Królowa uzyskała więc jedną metodę, do której można przekazać obiekt typu S tin g P a tro l ,
N e cta rC o lle cto r bądź też dowolny inny rodzaj pszczoły, który wie, w jaki sposób należy
publicznych
bronić ula. To, jakiej klasy będzie obiekt przekazany do metody, nie ma znaczenia. O ile tylko metod
implementuje on interfejs IS tin g P a tro l , metoda DefendTheHive() może mieć pewność,
że posiada on wszystkie właściwości i metody niezbędne do tego zadania.
i właściwości
każdej klasy,
TERAZ, KIEDY JUŻ MOŻESZ która go
OBRONIĆ UL, WSZYSCY BĘDZIEMY
ZNACZNIE BEZPIECZNIEJSI!
implementuje.

jesteś tutaj ► 333


Trochę NectarCollector i trochę StingPatrol

Teraz możesz utworzyć instancję NectarStinger,


P : Wciąż nie do końca rozumiem, w jaki sposób
która będzie wykonywała dwa rodzaje zadań interfejsy poprawiają kod programu do zarządzania
ulem. I tak musimy utworzyć klasę NectarStinger
i nie unikamy powielania kodu, prawda?
Do implementacji interfejsu używasz operatora dwukropka, tak samo jak
postępowałeś w przypadku dziedziczenia. Wygląda to następująco: pierwszą 0 : Zadaniem interfejsów nie ma być chronienie nas przed
rzeczą po dwukropku jest klasa, po której chcemy dziedziczyć. W dalszej powielaniem kodu. Mają nam one pozwolić wykorzystywać
kolejności znajdują się interfejsy — chyba że nie dziedziczymy po żadnej klasę w wielu różnych sytuacjach. Naszym celem jest
stworzenie jednej klasy reprezentującej pszczołę, która jest
klasie. W takim przypadku znajduje się tam tylko lista interfejsów (bez
w stanie wykonywać dwie różne prace. Wciąż będziemy
żadnej szczególnej kolejności).
musieli zaimplementować te klasy, ale nie o to chodzi.
Ta klas a dziedziczy po Worke r
Im plem entujesz interfejs za pomocą i imp lemen tu je interfejsy Interfejsy dają nam możliwość utworzenia klasy pszczół,
operatora dwukropka, tak samo jak INecta rCollector oraz IStingPatrol. które będą mogły wykonywać dowolnie wiele prac. Załóżmy,
podczas dziedziczenia. że dysponujemy metodą PatrolTheH ive() pobierającą
obiekt S tingPatrol oraz metodą C o llectN ectar()
c la s s N e c ta rS tin g e r : W orker, IN e c ta rC o lle c to r ,
pobierającą obiekt N ectarCollector. Nie chcemy jednak,
IS tin g P a tr o l {
by klasa StingPatrol dziedziczyła po N ectarCollector
p u b lic i n t A le rtL e v e l Możesz użyć więcej
niż jednego interfejsu, lub odwrotnie — każda z nich ma publiczne metody
Klasa { g e t; p r iv a t e s e t ; } ale m usisz je 1właściwości, których druga mieć nie powinna. A teraz
N ectarStinger .-----
im plementuje oba rozdzielić przecinkami. zastanów się przez chwilę, w jaki sposób można utworzyć
interfejsy, zatem jedną klasę, której instancje można by przekazać do obu
p u b lic i n t S tin g e rL e n g th
potrzebuje metod metod. Poważnie... odłóż książkę i pomyśl nad tym przez
i wfaściwości { g e t; s e t ; }
chwilę! Jak to można zrobić?
z każdego z nich.
Problem rozwiązują interfejsy. Dzięki nim można utworzyć
p u b lic i n t N e cta r { g e t; s e t ; } referencję IS tin g P atro l wskazującą na obiekt
implementujący interfejs IS tin g P a tro l, i to niezależnie
Każdej metodzie p u b lic bool LookForEnem ies() { . . . } od tego, jakiej klasy faktycznie będzie ten obiekt. Może ona
w interfejsie wskazywać na obiekt S tin g P a tro l, N ectarCollector
p u b lic i n t S h a rp e n S tin g e r(in t Length)
odpowiada albo nawet na całkowicie inny, niezwiązany z pszczołami.
metoda Jeśli dysponujesz referencją IS tin g Patro l wskazującą na
w klasie. .}
W przeciwnym jakiś obiekt, to masz pewność, że możesz używać wszystkich
razie p u b lic v o id F in d F lo w e rs () { . . . } metod i właściwości tego interfejsu niezależnie od faktycznej
kompilacja m e p u b lic v o id G a th e rN e c ta r() {...} klasy obiektu.
pow iedzie się .
p u b lic v o id R e tu rn T o H ive () {...} Jednak interfejs jest tylko częścią rozwiązania. Wciąż musimy
utworzyć nową klasę, która będzie go implementować,
}
gdyż on sam nie ma żadnego kodu. W interfejsach nie
/ chodzi o eliminację konieczności tworzenia nowych klas lub
Kiedy utw orzysz obiekt NecUr^rngrur, ^ d z e on
powielania kodu. Ich zadaniem jest zapewnienie możliwości
w sta n ie wykonywać zadanńa z arówno pszczoty typ u
NectarCollector, ja k i ^ m g ^ M . stworzenia klasy, która będzie mogła wykonywać wiele
różnych zadań bez konieczności stosowania dziedziczenia.
Mamy tutaj klasę, która implementuje interfejs i zachowuje się tak To ostatnie ma bowiem swoją cenę — dziedzicząc, klasa
samo jak każda inna klasa. Możesz utworzyć jej instancję za pomocą uzyskuje wszystkie pola, właściwości i metody, a nie tylko te
new i skorzystać z jej metod: niezbędne do wykonania konkretnego zadania.
Czy potrafisz wymyślić, jak można uniknąć powielania kodu
NectarStinger bobTheBee = new N ectarS tinger();
podczas stosowania interfejsów? Można by utworzyć nową
bobTheBee.LookForEnemies ( ); klasę o nazwie S tin g er lub Proboscis i w niej umieścić
bobTheBee. FindFl owers (); kod odpowiedzialny za żądlenie lub zbieranie nektaru.
Klasy NectarStinger oraz N ectarC ollector mogłyby
To jedno z najtrudniejszych zagadnień, które Twój mózg będzie zawierać prywatną instancję klasy Proboscis i korzystać
musiał przyswoić. Jeśli jeszcze nie do końca je rozumiesz, czytaj z jej metod i właściwości za każdym razem, gdy miałyby
dalej. W dalszej części rozdziału znajdziesz w iele przykładów. zbierać nektar.

334 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Klasy implementujące interfejsy


muszą zawierać W S Z Y S T K IE ich metody
Implementowanie interfejsu oznacza, że musisz umieścić w klasie metodę dla każdej
właściwości i metody, która jest w nim zadeklarowana — jeśli nie znajdą się tam
wszystkie, to kod się nie skompiluje. Gdy klasa implementuje więcej niż jeden interfejs,
muszą się w niej znaleźć wszystkie właściwości i metody każdego z nich.
Nie wierz nam jednak na słowo... Z 'b t !

f ^ ^
(o STWÓRZ NOW Ą APLIKACJĘ KONSOLOWĄ
I DODAJ NO W Y PLIK KLASY ISTINGPATROL.CS.
IDE doda plik zawierający wiersz c la ss IS tin gP atrol. Zastąp go wierszem z kodem in t e r f a c e
I S t in g P a t r o l i wpisz w nim zawartość interfejsu IStingPatrol przedstawiony dwie strony wcześniej.
Właśnie dodałeś interfejs do swojego projektu! Teraz będziesz już mógł skompilować program.

^ DODAJ DO PROJEKTU KLASĘ BEE.


Nie dodawaj jeszcze żadnych metod i właściwości. Po prostu spraw, aby klasa implementowała interfejs
IStingPatrol:

public c la s s Bee : IStingPatrol


{
}

S SPRÓBUJ SKOMPILOWAĆ PROGRAM.


Wybierz R ebuild Solution z menu B U IL D . Oj, oj — kompilator nie pozwoli Ci tego zrobić:

T ’ | © 4 Errors | ! 0 Warnings | 0 0 Messages Search Error List

Description File
© 1 'ImplementacjajnterfejsuJStingPatro Bee1does not implem nt interface lember '1 ti pl ementa cjajnterfejsi. JStingPatrol,IStingPatrol,SharpenStinger(¡nt}' Bee.cs

© 2 'ImplementacjajnterfejsuJStingPatro Bee1does not implem nt interface lember '1Tiplementacja_interfejsLJStingPatrol.lStingPatrol.LookForEnemiesQ1 Bee.cs
«
© 3 'ImplementacjaJnterfejsuJStingPatro Bee1does not implem nt interface lember '1 ti pl ementa cjaJnterfejsL J StingPatrol. 1Stin g Patro1,Sting erLength1 Bee.cs
«
© 4 'ImplementacjaJnterfejsuJStingPatro Bee1does not implem nt interface lember '1 ti pl ementa cjaJnterfejsL JStingPatrol.lStrngPatrol,AlertLevel' Bee.cs

Z obaczysz jeden błąd „does not im p lem en f dla każdej_sktadowej


IStingPatrol, która nie j e s t zaim plem en t°w ana w kla sie . Kompijat°r
naprawdę „chce", by zaimplementowana w s to fo każda metoda interre js u.

Z) DODAJ DO KLASY BEE METODY I WŁAŚCIWOŚCI.


Wstaw metody LookForEnemies() i SharpenStinger(). Upewnij się, że sygnatury metod odpowiadają
tym z interfejsów. A zatem metoda LookForEnemies() ma zwracać typ bool, a metoda SharpenStinger()
pobiera parametr typu in t (możesz wybrać jego nazwę) i zwraca typ int; na razie obie metody nie muszą
niczego robić, więc zwracaj z nich dowolne wartości. Dodaj właściwość typu in t o nazwie AlertLevel
posiadającą akcesor get (może zwracać dowolną liczbę) oraz automatyczną właściwość typu in t o nazwie
StingerLength posiadającą oba akcesory — get i set.
I jeszcze jedno: upewnij się, że składowe klasy Bee zostały zadeklarowane jako publiczne. Teraz program się
skompiluje!

jesteś tutaj ► 335


Błaznowanie

Poćwicz trochę z interfejsami


Interfejsy są naprawdę łatwe w użyciu, ale najlepszy sposób na ich
zrozumienie to po prostu rozpoczęcie pracy z nimi. Utwórz zatem Z ró b to !
nowy p ro je k t C onsole A pplicatio n i zaczynaj! y

r® Tak wygląda klasa TallGuy i kod metody Main umieszczonej w pliku Program.cs, który tworzy jej instancję
za pomocą inicjalizatora obiektu oraz wywołuje jej metodę T alkA bo utY ou rself() . Nic nowego — będziemy
jej używać za chwilę:
p u b lic class TallGuy {
p ub lic s trin g Name;
p ub lic in t Height;
p ub lic void TalkAboutYourself() {
Console.WriteLine("Nazywam się " + Name + " i mam "
+ Height + " centymetrów w z ro s tu .");
}
s ta tic void M a in (s trin g [] args) {
TallGuy ta llG uy = new TallGuy() { Height = 74, Name = "Adam" };
tallG uy.T alkA boutY ourself();
}
}

Już wiesz, że wszystko wewnątrz interfejsu musi być publiczne. Nie przyjmuj tego jednak w ciemno.
Dodaj do projektu nowy interfejs IClown , dokładnie w taki sam sposób, w jaki dodawałeś klasę: kliknij
prawym przyciskiem myszy nazwę projektu widoczną w oknie Solution Explorer , wybierz opcję A d d /N ew
Item , a n a stęp n ie w ybierz * 0 imerface _Upewnij się, że plik będzie mieć nazwę IClown.cs. IDE utworzy plik
interfejsu zawierający następującą deklarację:
in te r fa c e IClown
{
Nie m u sisz wpisywać
Teraz spróbuj zadeklarować wewnątrz niego metodę prywatną: „public" wewnątrz
interfejs u , ponieważ
p r iv a t e v o id H o nk(); każda jego metoda
oraz właściwość
Wybierz w IDE B U IL D /B U IL D Solution. Zobaczysz taki oto błąd: autom atycznie sta je
s ię publiczna.
O 1 The modifier 'private' is not valid for this item

Przejdź dalej i u s u ń m o d y fik a to r d o stę p u p r i v a t e — błąd zostanie zlikwidowany, a program będzie się
kompilował poprawnie.

Zanim przejdziesz do następnej strony, sprawdź, czy potrafisz utworzyć resztę interfejsu IClown
i zmodyfikować klasę TallGuy tak, aby poprawnie go implementowała. Twój nowy interfejs IClown powinien
posiadać metodę void o nazwie Honk, która nie przyjmuje żadnych parametrów, oraz właściwość tylko do
odczytu typu s tr in g o nazwie FunnyThingIHave, która ma akcesor g et , ale nie posiada akcesora s e t .

336 Rozdział 7.
Interfejsy i klasy abstrakcyjne

^4 | Tak wygląda interfejs — czy wykonałeś go prawidłowo?


Oto p rz y ktad interfejsu, który posiada
akcesor g e t ale nie ma akcesora se t.
pub lic in te rfa ce IClown Pam ięt aj, i^tei^gjsy nie mogą zawierać
p ó1, ale j eśli zaim plem entujesz w klasie
{
t aką właściw ość tylko do odczytu, dla
s trin g FunnyThinglHave { get; innych obiektów będzie ona wyglądała
void Honk(); tak samo ja k pole.
}
Dobrze, zmodyfikuj teraz klasę TallG uy , aby implementowała IClown . Pamiętaj,
po operatorze dwukropka zawsze podawana jest klasa bazowa (o ile jakaś jest), a po
niej lista interfejsów do implementacji oddzielonych przecinkami. W związku z tym,
że nie ma żadnej klasy bazowej i jest tylko jeden interfejs do zaimplementowania,
deklaracja będzie wyglądała następująco: IDE próbuje Ci
powiedzieć, że
Klasa TallGuy będzie implementowała interfejs IClown. w momencie
zadeklarowania klasy
pub lic class TallGuy : IClown im plementującej
interfejs IClown
obiecałeś dodać
Upewnij się teraz, że reszta klasy jest taka sama, włączając w to dwa pola i metodę. w szystk ie jego
właściwości i metody..
Wybierz B u ild Solution z menu B U IL D , a IDE spróbuje skompilować i zbudować po czym sw oją
program. Zobaczysz dwa błędy, a jeden z nich będzie taki: obietnicę złamałeś!

Błędy zostaną wyeliminowane zaraz po tym, jak dodasz wszystkie metody i właściwości
zdefiniowane w interfejsie. Przejdź zatem dalej i zaimplementuj go. Dodaj właściwość
typu s tr in g tylko do odczytu o nazwie FunnyThingIHave z modyfikatorem dostępu
g et , który będzie zawsze zwracał łańcuch znaków duże buty . Następnie dopisz metodę
Honk() , która będzie wyświetlała w oknie konsoli napis z zawartością „Tut tuut!”.

Tak mniej więcej powinno to wyglądać: Interfejs

r s s - y g y r a r « r w ^ m sssssm ^
pub lic s trin g FunnyThinglHave {
get { return "duże b u ty"; } nie będzie tego podziew asz.
} jak s ię tego po nich sp
pub lic void Honk() { In terfejs informuje, że p otrzebujesz publicznej
m etody o nazwie Honk, ale nie mówi, co
Console.W riteLine("Tut t u u t ! 1 powinna ona robić. Może nie robić nic — bez
} względu na to, jeżeli metoda ma prawidłową
sygnaturę, kod będzie s ię kompilował.

Teraz Twój kod się skompiluje! Zmodyfikuj kod metody M ain( ), tak by wywoływała
metodę Honk() obiektu TallGuy i wyświetlała na konsoli napis „Tut tuut!”.

jesteś tutaj ► 337


Interfejsy nie tw orzą obiektów

Nie możesz utworzyć instancji interfejsu,


ale możesz uzyskać jego referencję
M ożesz utworzyć tablicę referencj i
Powiedzmy, że posiadasz metodę, która potrzebuje obiektu typu IWorker, nie możissz jrednak.
będącego w stanie wykonać metodę F ind F lo w ers() . Każdy obiekt stw orzyć instancji te go in terfejsu .
implementujący interfejs IN e c ta rC o lle c to r byłby wystarczająco Można natom ias t s prawići by
referencje te ws kozy wały na
dobry. Mógłby to być obiekt Worker, ale też Robot lub Dog, o ile instancje klas implementujący ch
implementowałyby one powyższy interfejs. interfejs. Dzięki tem u ¡sędAenz
dysponować tablicą z a wierającą
obiekty wielu różnych mdzajótu.
Przychodzi pora na przedstawienie referencji interfejsów . Możesz
użyć jednej z nich w celu odwołania się do obiektu, który implementuje
potrzebny interfejs. Zawsze będziesz miał pewność, że dysponujesz
odpowiednimi do Twoich celów metodami — nawet jeśli poza tym
\
Jeśli spróbujesz utw orzy ć
niewiele o tych obiektach wiesz. instancję in terfejsu >
kompilator zaprotestuje .
To nie będzie działało...

IStingPatrol dennis = new IStingPatrolO;


O 1 C annot create an instance o f the abstract class or interface 'Im p lem en ta cja Jn te rfejsu JStin g P atro l.lStin g P atro r (

Nie możesz użyć słowa kluczowego new w odniesieniu do interfejsu. Ma to


pewien sens, metody i właściwości nie mają bowiem żadnej implementacji.
Gdybyś mógł utworzyć interfejs jako obiekt, skąd miałby on wiedzieć, jak się
zachować?

.ale to zadziała:

Pam iętasz, jak


NectarStinger fred = new NectarStinger();
przekazyw ałeś ^^IStingPatrol george = fred; Pomimo tego,
referencję BLT
do każdej metody, że obiekt mógfby
Pierwszy wiersz jest zwykłą instrukcją new, która tworzy referencję zrobić znacznie
która spodziewała
s ię obiektu o nazwie fre d wskazującą na obiekt N ecta rS tin g e r . więcej, za
typu Sandwich, pośrednictwem
ponieważ BLT interfejsu
W drugim wierszu dzieją się rzeczy naprawdę interesujące. Tworzy ona m asz dostęp
dziedziczył
po Sandwich? nową zmienną referencyjną, używając IS tin g P a tro l . Na początku może tylko do metod,
To j e s t ten sam to wyglądać nieco dziwnie, ale spójrz na to: które s ą w nim
przypadek — zadeklarowane.
m ożesz użyć NectarStinger ginger = fred;
N ectarStinger
w każdej metodzie, Już wiesz, co robi trzecia instrukcja — tworzy nową referencję N ectarS tinger
która spodziewa o nazwie g in g e r i wskazuje na ten sam obiekt co fre d . Referencja george
s ię IStingPatrol.
używa IS tin g P a tro l w ten sam sposób.

Co się zatem stało?


Istnieje tylko jedna instrukcja new, więc został utworzony tylko jeden obiekt.
Druga linijka tworzy zmienną referencyjną o nazwie george , która może
wskazywać na instancję każdej klasy implementującej IS tin g P a tro l .
'^ N e ^

338 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Referencje interfejsów działają tak samo jak referencje obiektów


Już wiesz, że obiekty leżą sobie na stercie. Gdy pracujesz z referencją
interfejsu, można to uznać za kolejny sposób uzyskiwania dostępu do obiektu.

OBIEKTY SĄ TWORZONE W NORMALNY SPOSÓB.


Obie te klasy implementują interfejs IS tin g P a tro l .

StingPatrol b i f f = new S tin g P a tro l();


N ectarC ollector bertha = new N e ctarC o llecto r();
„ . ./K . .
Przyjmij my, że klasa StingPatrol im plementuje
int erfe js I^ n g P a tr o l, a klasa NectarCollector
interfejs INectarCollector.

3 DODAJ REFERENCJE ISTINGPATROL


ORAZ INECTARCOLLECTOR.
Możesz użyć referencji interfejsów w taki sam sposób,
w jaki używałeś innych typów referencji.

IS tingP atrol defender = b i f f ;


IN ectarC ollector cutieP ie = bertha;
% % I T 0X
^ a rC o ^
V Te dwie instrukcje uży wają 0 * '“
V -------- utworzenia nowych referencji do oltwMow j u ż _
s i e j ą c y c h . M ożesz w skazywać n f u n u j m
ty lk o te obiekty, które dany implem en tu ją .

3 REFERENCJA in t e r f e j s u
BĘDZIE UTRZYM YW AŁA OBIEKT PRZY ŻYCIU
Gdy nie ma żadnych referencji wskazujących na obiekt,
wtedy taki obiekt znika. Nie ma zasady mówiącej, że wszystkie
z tych referencji muszą być tego samego typu!
s t i n ^
Referencja interfejsu jest w takich przypadkach równie dobra.
Jeśli bierzemy pod uwagę aspekt przechowywania obiektów,
to działa ona tak samo jak ich własna referencja.

b i f f = n u ll;
Ten obiekt nie znika,
ponieważ defender
w dalszym ciągu
PRZYPISZ NOW Ą INSTANCJĘ na niego wskazuje.
DO REFERENCJI INTERFEJSU.
W zasadzie nie potrzebujesz referencji do obiektu.
Możesz stworzyć nowy obiekt i przypisać go wprost
do zmiennej referencyjnej interfejsu.

IN ectarC ollector gatherer = new N ecta rS tin g e r();

jesteś tutaj ► 339


Spodziewamy się dużego dziedziczenia

i Nie istnieją, .
Za pomocą „is" możesz sprawdzić, głupie pytania

czy klasa implementuje określony interfejs P : Chwileczkę. Gdy umieszczam


właściwość w interfejsie, wygląda
Czasami musisz sprawdzić, czy dana klasa implementuje określony interfejs. ona jak właściwość automatyczna.
Czy to oznacza, że podczas
Przypuśćmy, że wszystkie nasze robotnice zapisaliśmy w tablicy o nazwie Bees .
implementowania interfejsów
Możemy zadeklarować ją tak, aby przechowywała obiekty klasy W o rke r , ponieważ
mogę używać jedynie właściwości
każda z robotnic będzie typu W orker lub jednej z klas pochodnych. automatycznych?

Ale które z pszczół potrafią zbierać nektar? Inaczej mówiąc, chcemy wiedzieć, O : Ależ skąd. To prawda, że właściwości
która z klas implementuje interfejs I N e c t a r C o lle c t o r . Możemy użyć w interfejsach wyglądają bardzo podobnie
słowa kluczowego i s , a otrzymamy dokładnie to, czego potrzebujemy. do właściwości automatycznych —
widać to na przykładzie właściwości Job
lub S h if t s L e f t interfejsu IWorker
W szystkie robotnice znajdują s ię Mamy tablicę pszczół przedstawionego na następnej stronie.
w tablicy B ees . Użyjemy „is" typ u Worker, które s ą
w celu określenia, jakim typem Jednak absolutnie nie są to właściwości
zdolne w yruszyć na
robotnicy j e s t dana pszczoła. m isję zbierania nektaru. automatyczne. Właściwość Job mógłbyś
Stwórzmy zatem pętlę, zaimplementować następująco.
Worker[] Bees = new Worker[3]; której zadaniem będzie
przeglądnięcie wszystkich
elementów tablicy i użycie public Job { get; p riv a te s e t; }
Bees[0] = new NectarCollector(); „is" do określenia, które
z nich posiadają, metody

Bees[1] = new StingPatrol(); i właściwości odpowiednie Prywatny akcesor set jest nam potrzebny,
do wykonania zadania. gdyż właściwości automatyczne muszą
Bees[2] = new NectarStinger(); mieć oba akcesory (nawet jeśli byłyby
one prywatne). Równie dobrze mógłbyś
for (int i = 0; i < Bees.Length; i++) jednak zaimplementować tę właściwość
is pozwala porównywać w poniższy sposób:
{ interfejsy ORfcZ- inne typy-
public Job { get { return
if (Bees[i] is INectarCollector) "Księgowa"; } }

too mniej więcej t / ^ - je że li ta pszczoła ^ i kompilator też byłby usatysfakcjonowany


m i Z n tuje lnterfejS INectarCollector... niech 'j ) Mógłbyś także dodać do niej akcesor set;
interfejs wymaga akcesora get, jednak
Bees[i].DoThisJob("Zbieranie nektaru", 3); nie oznacza to wcale, że nie możesz
dodać również tego drugiego. (Gdybyś
} zdecydował się zastosować właściwość
automatyczną, mógłbyś wybrać,
} czy akcesor set będzie prywatny,
j . j więc przypisać to zadanie. czy publiczny).

WYSIL ___________________
SZARE KOMÓRKI
Gdybyś miał inną klasę, która nie dziedziczyłaby po klasie W orker, ale im plem entowałaby Interfejs
IN e c t a r C o lle c t o r , ona także mogłaby wykonywać zadaną pracę! Klasa ta nie dziedziczyłaby po W orker,
więc nie mogłaby być przechowywana w tablicy z innymi pszczołami. Czy potrafisz wymyślić jakiś sposób
na obejście problemu i stworzenie tablicy do przechowywania pszczół wraz z obiektami tej nowej klasy?

340 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Rysując na diagramie
Interfejsy mogą dziedziczyć po innych interfejsach klas interfejs,
dziedziczenie
Kiedy jedna klasa dziedziczy z drugiej, to otrzymuje wszystkie metody i właściwości
zaznaczamy
klasy bazowej. Dziedziczenie interfejsów jest jeszcze prostsze. W związku z tym,
linią przerywaną.
że nie ma w nich ciała żadnej metody, nie musisz się martwić o wywoływanie metod
i konstruktorów bazowych. Dziedziczące interfejsy gromadzą po prostu wszystkie
metody i właściwości interfejsów, z których dziedziczą.

S tw orzyliśm y interfejs
public interface IWorker JWorker, z któreg0 będą_
dziedziczyły inne in terfejsy. —
{
string Job { get; }
int ShiftsLeft { get; }
void DoThisJob(string job, int shifts);
void WorkOneShift();
}
(interface) (interface)
IStingPatrol INectarCollector
Każda klasa, która implementuje interfejs StingerLength Nectar
AlertLevel
dziedziczący po IWorker, musi, zaimplementować
jego metody i właściwości
SharpenStinger() FlndFlowersQ
Gdy klasa implementuje interfejs, musi zawierać wszystkie metody, które LookForEnemies() GatherNectarQ
się w nim znajdują. Jeśli interfejs ten dziedziczy po innym, to wszystkie Sting() ReturnToHive()
te właściwości i metody także muszą zostać zaimplementowane.

p u b lic in te r fa c e I S tin g P a tr o l : IW orker


To j e s t ten s am interfejs IStingPatrol,
{
I w te raz ,d fledziczy on po interfejsie
i n t A le r tL e v e l { g e t ; } IW orker. W ygada to na niewielką
modyfikację, ale wprowadza
i n t S tin g e rL e n g th { g e t; s e t ; } ogromne zm iany w każdej klasie
implem entującej IStingPatrol.
bool LookF orE nem ies();
i n t S h a rp e n S tin g e r(in t L e n g th );

}
Klasa implementująca
IStingPatrol musi
zaw ierać nic tylko
te m eto d y -
...a h także metody
interfejsu IWorker,
po którym dziedziczy.

jesteś tutaj ► 341


Ichcialbymkupiccheeseburgera

RoboBee 4 0 0 0 może wykonywać zadania pszczół


bez potrzeby spożywania cennego miodu
Stwórzmy nową pszczołę RoboBee 4000, która będzie zasilana paliwem.
Będzie ona mogła dziedziczyć po interfejsie IWorker, a więc będzie
zdolna do wykonywania tego, co potrafi zwykła pszczoła.

To je s t n asza podstawowa
public class Robot klasa Robot. Pszczoły roboty
{ mogą, dziatać na benzynę.

public void ConsumeGas()


Klasa RoboBee
} dziedziczy po Robot
i implementuje
IW orker. Oznacza
public class RoboBee : Robot, IWorker to, że je j obiekt j e s t
robotem, ale może
{ wykonywać zadania
robotnicy. Doskonale!
private int shiftsToWork;
private int shiftsWorked; RoboBee
K la s a
implementuje
„ sz y stk ie metody
public int ShiftsLeft interfejsu IWorker.

{ get { return shiftsToWork - shiftsWorked; } }


public string Job { get; private set; }
public void DoThisJob(string job, int shiftsToWork) {...}
public void WorkOneShift() {...}
}
skompilować.

Pamiętaj, dla innych klas aplikacji nie ma żadnej różnicy


w funkcjonowaniu pomiędzy RoboBee a zwykłą robotnicą.
Każda klasa może
Obie klasy implementują interfejs IWorker, więc biorąc pod uwagę implementować
resztę programu, obie mogą zachowywać się jak robotnice.
DOWOLNY interfejs,
Możesz jednak rozróżnić te typy poprzez użycie is:
0 ile dotrzyma
Możemy sprawdzić, jaki
i f (workerBee is Robot) {
/ / teraz wiemy, że workerBee
interfejs implementuje obietnicy implementacji
w o r k e r B e e lu b P ° J a ™e )

/ / je s t obiektem typu Robot


k/asie d z i e d z i c z y ,
za pomocą Js ■
wszystkich jego metod
} 1właściwości.
342 Rozdział 7.
Interfejsy i klasy abstrakcyjne

is określa, co obiekt implementuje,


as mówi kompilatorowi, jak go traktować
Czasami chcesz wywołać metodę, którą obiekt pobiera z implementowanego interfejsu.
Co wtedy, gdy nie masz pewności co do jego typu? Aby go poznać, korzystasz z i s . Potem
możesz użyć a s , aby traktować obiekt — o którym wiesz, że ma właściwy typ — jakby miał
metodę, którą chcesz wykonać.

IWorker[] Bees = new IWorker[3];


W szystkie te pszczotu
implem entują IWorker, ale nie
Bees[0] = new NectarStinger();
ta k Z y ' kt6re Z nich 'mPlem entują
Bees[1] = new RoboBee();
Bees[2] = new Worker();
Przeglądamy w p ęt li w szy stk ie ps zcz°ty... Nie możemy wywoływać

for (int i =
V.
0; i < Bees.Length; i++) {
m etod INectarCollector
w odniesieniu do obiektów
pszczół. S ą one typu
IW orker i nic o nich
if (Bees[i] is INectarCollector) { nie wiedzą.

. i sprawdzamy, INectarCollector thisCollector;


czy im plem entują
interfejs
INectarCollector. thisCollector = Bees[i] as INectarCollector;
thisCollector.GatherNectar(); Używamy »»as’ ,
aby p o w ie d z ie ć :
... f „Traktuj ten obiekt
■■■TERAZ możemy wywoływać JAKO implementację
metody INectarCollector. INectarCollector".

r Zaostrz ołówek

v Przyjrzyj się tablicy zamieszczonej po lewej stronie. Dla każdej z tych instrukcji napisz,
jaka wartość i spowoduje powstanie wyrażenia o wartości t r u e . Dodatkowo dwa
wiersze nie skompilują się — wykreśl je.

IWorker[] Be e s = new I W o r k e r [ 8 ] ; 1. (Bees[i] is IN ectarCollector)


Bees[0] = new N e c t a r S t i n g e r ( ) ;
Bees[1] = new R o b o B e e ( ) ;
2. (Bees[i] is
Bees[2] = new W o r k e r ( ) ;
Bees[3] = Bees[0] as IWorker;
Bees[4] = IStingPatrol; 3. (Bees[i] is
Bees[5] = nuli;
Bees[6] = Bees[0];
Bees[7] = new I N e c t a r C o l l e c t o r ( ) ;

jesteś tutaj ► 343


W ygląda na to samo, ale w rzeczywistości jest czymś innym!

Ekspres do kawy także jest urządzeniem


Gdy chcesz znaleźć sposób na ograniczenie zużycia energii i zmniejszenie miesięcznych
rachunków, nie ma dla Ciebie znaczenia, co robi konkretne urządzenie. Interesujesz się
tylko tym, ile pobiera prądu. Jeśli więc tworzysz program do monitorowania jego zużycia,
prawdopodobnie napiszesz klasę Appliance . Jeżeli jednak zechcesz odróżnić ekspres
do kawy od piekarnika, będziesz musiał zbudować hierarchię klas. Dodasz metody
i właściwości charakterystyczne dla ekspresu do kawy i piekarnika do klas, odpowiednio:
CoffeeMaker oraz Oven. Będą one dziedziczyły po klasie Appliance , która będzie
posiadała wspólne metody i właściwości.

public void MonitorPower(Appliance appliance) {


// kod dodający dane do bazy
// danych domowego zużycia energii Tu znajduje
s ię metoda do
} Ten kod pojawi s ię w dalszej c zę ści m m ^m w a rn a ^
programu i będzie o d p o w ied zia ły za s z y c ia . ener9".
monitorowanie zużycia prądu przez w gospodarstwie
ekspres do kawy. d° mowym.

CoffeeMaker misterCoffee = new CoffeeMaker();


MonitorPower(misterCoffee);
W idziałeś to ju ż
w poprzednim
P°mim° tego, że metoda rozdziale podczas
W h n ifo t-P w ^ ) ptóiera referencję przekazywania do
do obiektu Appliance, m ożesz je j m etody referencji
p rzekaz ać referencję CoffeMaker, ' BLT za m ia st
bo j e s t to klasa potomna spodziewanej
Appliance. Sandwich.
Zaostrz ołówek
Rozwiązanie Przyjrzyj się tablicy zamieszczonej po lewej stronie. Dla każdej z tych Instrukcji
napisz, jaka wartość i spowoduje powstanie wyrażenia o wartości true.
Dodatkowo dwa wiersze nie skompilują się — wykreśl je.

IWorker[] Bees = new IWorker[8]; 1 . (Bees[i] is INectarCollector)

Bees[0] = new NectarStinger() < n


0, 3 i 6
Bees[1] = new RoboBee(); N ectarStinger() ......
implem entuje 2
Bees[2] = new Worker(); interfejs (Bees[i] is IStingPatrol)
Bees[3] = Bees[0] as IWorker; IStingPatrol.

Bees[4] = IStingPatrol; 0, 3 i 6
Bees[5] = null; 3 . (Bees[i] is IWorker)
Bees[6] = Bees[0];
Bees,,, = new ,Ne,,a,,o,,e,,or,,- 0, 1, 2, 3 i 6

344 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Rzutowanie w górę
działa w odniesieniu do obiektów i interfejsów
Kiedy zastępujesz klasę bazową klasą potomną — tak jak wtedy, gdy zamieniałeś urządzenie na ekspres
do kawy lub zamiast Sandwich wstawiałeś BLT — postępowanie takie nazywamy rzutowaniem w górę .
To naprawdę potężne narzędzie pomocne podczas budowania hierarchii klas. Jedyną niedogodnością
przy rzutowaniu w górę jest możliwość stosowania tylko właściwości i metod klasy bazowej. Inaczej
mówiąc, kiedy traktujesz ekspres do kawy jako urządzenie, nie możesz zażyczyć sobie przyrządzenia
kawy lub wypełnić go wodą. M ożesz natomiast sprawdzić, czy jest podłączony do gniazdka — o ile
oczywiście jest to coś, co można zrobić z urządzeniem (w tym przypadku właściwość PluggedIn jest
częścią klasy Appliance ).

[n STWÓRZMY KILKA OBIEKTÓW.


Możemy utworzyć instancje klas CoffeeMaker oraz Oven w taki sam sposób jak zawsze:

CoffeeMaker misterCoffee = new CoffeeMaker(); fbetdÓW^ypu Uven


Oven oldToasty = new Oven(); CoffeeMaker.
• iZrobimy to tak ja k zwykle.

^ CO WTEDY, GDY CHCEMY UTWORZYĆ TABLICĘ URZĄDZEŃ?


Nie możesz wstawić CoffeeMaker do tablicy Oven[] ani instancji Oven do tablicy
C offeeM aker[] . Oba typy możesz jednak umieścić w tablicy A p p lia n c e [] :

Appliance[] kitchenWare = new A ppliance[2];


kitchenWare[0] = m isterCoffee;
kitchenWare[1] = oldToasty;

NIE MOŻESZ JEDNAK TRAKTOWAĆ URZĄDZENIA JAK PIEKARNIKA.


Gdy posiadasz referencję A ppliance , możesz uzyskać dostęp tylko do metod i właściwości,
które dotyczą urządzeń. Nie możesz za jej pośrednictwem używać metod i właściwości ekspresu
do kawy, naw et gdy wiesz, Że je s t to obiekt C offeeM aker. Poniższe instrukcje wykonają się powerConsumer
j e s t referencją
poprawnie, ponieważ traktują one obiekt CoffeeMaker jako obiekt Appliance . Appliance, która
Appliance powerConsumer = new CoffeeMaker(); T w skazuje na obiekt
CoffeeMaker.
powerConsume r . ConsumePower( ) ; p O T e l^ a W Z c S O S e j

Jeżeh jednak spróbujesz użyć g° jako ekspresu do kawy: ^Zk^ t<


a^ fU
e '^t^^acCaiy /A^ pjd0anlCye,^0^ ania
powerConsumer.MakeCoffee(); — / czynności związanych z Appliance.

. .Twój kod nie będzie się chciał skompilować, a IDE wyświetli błąd:

o<~ Urzddzenid.Applidnie' doEis not contdin d defmition for MdkeCuffee |

Stanie się tak, ponieważ po wykonaniu rzutowania w górę, na klasę bazową, możesz mieć
dostęp tylko do właściwości i metod, które są zgodne z referencją używaną podczas
korzystania z obiektu.

jesteś tutaj ► 345


Rzutowanie w dół jest ła tw e

Rzutowanie w dół pozwala zamienić urządzenie


z powrotem w ekspres do kawy
Rzutowanie w górę to wspaniałe narzędzie, ponieważ pozwala używać ekspresu do kawy oraz piekarnika wszędzie tam, gdzie
potrzebujemy jakiegoś urządzenia. Posiada ono jednak jedną wadę: używając referencji A p p lia n c e , która wskazuje na obiekt
C offeeM aker , możesz korzystać tylko z metod i właściwości należących do klasy A p p lia n c e . To jest moment, w którym
pojawia się rzutowanie w dół: to dzięki niemu pobierasz referencję wcześniej rzutowaną w górę i przekształcasz ją w postać
początkową. Możesz sprawdzić, czy A p p lia n c e jest w rzeczywistości obiektem C offeeM aker , używając słowa kluczowego i s .
Kiedy już się tego dowiesz, możesz przekonwertować A p p lia n c e z powrotem na C offeeM aker przy użyciu słowa kluczowego as .

ROZPOCZNIEMY OD EKSPRESU DO KAW Y To j e s t referencja


KTÓRY RZUTOWALIŚMY W GÓRĘ. Appliance, któm
w skazuje na
Tak wygląda kod, którego użyliśmy: obiekt CoffeeMak.er
z poprzedniej stro n y.
A p p lia n c e powerConsumer = new C o ffe e M a k e r();
powerConsumer.ConsumePower();

CO WTEDY, GDY CHCEMY Z POWROTEM ZAMIENIĆ


APPLIANCE N A COFFEEMAKER?
Pierwszym krokiem podczas rzutowania w dół jest użycie słowa kluczowego is
i sprawdzenie, czy taka możliwość w ogóle istnieje.
#
if (powerConsumer is CoffeeM aker)
/ / U tedy możemy rzu to w a ć w d ó ł
Referencja javaJoe
wska zu je na ten sam
TERAZ, GDY JUZ WIEMY, ZE JEST TO EKSPRES DO KAWY, ob iek t CoffeeMaker co
UŻYJEMY GO JAKO EKSPRESU. powerConsumer. J e s t
to jednak referencja
Słowo kluczowe i s to pierwszy krok. Gdy masz już pewność, że jest to referencja A p p lia n c e CoffeeMaker, więc
wskazująca na obiekt C o ffe e M a ke r , możesz użyć as w celu rzutowania w dół. Pozwoli Ci może wywoływać
metodę MakeCoffee().
ono na użycie metod i właściwości klasy C o ffe e M a ke r . W związku z tym, że C o ffe eM ake r
dziedziczy po A p p lia n c e , metody i właściwości tej drugiej w dalszym ciągu będą dostępne.
if (powerConsumer is C offeeM aker) {
CoffeeM aker ja va Jo e = powerConsumer as CoffeeM aker;
ja v a J o e .M a k e C o ffe e ();
}

Kiedy rzutowanie w dół nie powiedzie się, a s zwróci nuli


ó 'e h C o ^
Co się stanie, gdy spróbujesz użyć as do przekonwertowania obiektu
typu Oven na C o ffe e M a k e r ? Operator zwróci n u ll — jeżeli spróbujesz P0WerConSUmer NIE j e s t ob,ektem typu Oven.
Kiedy s p róbuje s z rzu tować ją w dół za pomocą
go użyć, .NET zatrzyma program. ref erre ncja foodWarmer zakończy instrukcję
Oj, oj — to
z wćart o ą j a w i o n ą na null. Gdy s p r ó b u j
i f (powerConsumer is CoffeeMaker użyć tej p u ste j referencji, sta n ie s ię to...
Oven foodWarmer = powerConsumer
foodWarmer.Preheat( ) ;

i ^ !

346 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Rzutowanie w górę i w dół działa także w odniesieniu do interfejsów


Już wiesz, że i s i a s działają w odniesieniu do interfejsów. Zastosujmy więc wszystkie Każda klasa,
która imple­
triki z rzutowaniem w górę i w dół. Dodajmy interfejs ICooksFood do każdej klasy, która m entuje
może podgrzewać jedzenie. Dodamy też klasę Microwave — zarówno ona, jak i klasa Oven ICooksFood,
implementują ICooksFood . Istnieją teraz trzy różne sposoby dostępu do obiektu Oven . (interface) j e s t urzą­
ICooksFood dzeniem
IDE IntelliSense pomoże Ci określić, co możesz, a czego Ci nie wolno przy każdym podejściu. potrafiącym
Capacity
podgrzewać
jedzenie.
Oven m isterToasty = new Oven();
m isterTo asty.|
¡Capacity" int Oven.Capacity
HeatUp()
Reheat() W
Z araz po
y Color

w pisaniu kropki
w yśw ietli s ię okno
Consum ePower
Equals
m isterToasty j e s t referencją
Oven, więc m ożesz za je j pomocą
F

IntelliSense, któm GetHashCode uzys kać dostęp do w szystkich
Oven
♦♦ A Microwave
będzie zawierato m etod i właściwości... ale
GetType Capacity Capacity
w szystkie sktadowe ponieważ j e s t to najmniej ogólny
możliwe do u życia . H eatUp ty p , może w skazyw ać tylko
Pluggedln na obiekty typ u Oven.
Preheat Preheat() HeatUp()
HeatUp() Reheat()
Reheat() MakePopcornQ

I C o o ks F oo d c o o k e r ;
if (m isterToasty is ICooksFood) {
coo ker = m i s t e r T o a s t y as ICooksFood;
cooker.

ł* C ap acity int ICooksFood.Capacity Trzy różne


$ Equals
$ GetHashCode cooker j e s t referencją ICooksFood
w skazującą na te n s am M e k -t ° ven-
referencje,
$ GetType
O HeatUp
Może uzyskać dost’ę p do składowych
ICooksFood, ale moż e także wskazywać
które wskazują
O Reheat
$ ToString
na obiekt typu M icf°w ave-
na ten sam
obiekt, mogą
A p p l i a n c e p o w e r C o n s u me r ;
if (m isterToasty is Appliance) {
uzyskać
p o w e r C o n su m e r = m i s t e r T o a s t y ; dostęp do
p o w e r C o n s u me r . różnych metod
powerConsumer j e s t referencją
A ppliance. Umożliwia ona
¡Color int Appliance.Color i właściwości
ConsumePower
dos tę p ty lko do publicznych
pó1, m etod i właściwości
Equals w zależności
GetHashCode
klasy A ppliance. Z a pomocą
referencji tego typu m ożesz GetType
od ich typu.
także w skazać obiekt Pluggedln
CoffeeMaker. O czywiście
jeśli tego chcesz. ToString

jesteś tutaj ► 347


Bez głupich pytań

P : Powtórzmy — powiedziałeś, że zawsze P : Co się stanie, gdy umieszczę ciało


Teraz możesz wstawić do niej referencję
do każdego zwierzęcia, o ile implementuje
mogę rzutować w górę, ale nie zawsze mogę metody w interfejsie? Czy jest to poprawne?
interfejs IP u lle r .
rzutować w dół. Dlaczego?

O : Ponieważ kompilator może ostrzegać, jeśli


O : Nie, kompilator nie pozwoli Ci tego
zrobić. Umieszczanie jakichkolwiek instrukcji
P : Czy jest jakiś prostszy sposób
implementowania interfejsów?
rzutowanie w górę jest złe. Jedyną sytuacją, w interfejsie jest całkowicie zabronione. Nawet
To bardzo dużo pisania!
w której rzutowanie w górę nie zadziała, jest gdy używasz operatora dwukropka do jego
próba rzutowania obiektu do klasy, po której
on nie dziedziczy, lub do interfejsu, którego
implementacji, nie jest to równoznaczne
z dziedziczeniem klasy. Zaimplementowanie
O : Oczywiście, że jest! IDE udostępnia
Ci bardzo przydatny skrót, dzięki któremu
nie implementuje. Jednak kompilator może interfejsu nie owocuje dodaniem do klasy
automatycznie implementuje za Ciebie
natychmiast wykryć takie nieprawidłowe żadnego zachowania ani nie wprowadza
interfejs. Po prostu zacznij pisać swoją klasę:
rzutowanie i zgłosić błąd. żadnych zmian. Głównym jego zadaniem
jest jedynie uzyskanie pewności, że klasa p u b lic c la s s
Z drugiej strony kompilator nie jest w stanie
posiada wszystkie metody, które według niego M icrowave : ICooksFood
sprawdzić, czy rzutowanie w dół z obiektu
lub referencji interfejsu do referencji obiektu zawierać powinna. { }
jest prawidłowe. Dzieje się tak dlatego, Kliknij ICooksFood — ujrzysz mały prostokąt
że umieszczenie dowolnej klasy lub interfejsu P : W takim razie po co miałbym używać poniżej I. Najedź na niego myszką, a zobaczysz
po prawej stronie słowa kluczowego as interfejsu? Wychodzi na to, że wprowadzam ikonę, która pojawi się poniżej:
jest całkowicie legalne. Jeżeli rzutowanie tylko ograniczenia, nie zmieniając w ogóle
mojej klasy. IC o o k sF o o d
jest nieprawidłowe, to instrukcja as zwraca
n u l l . Bardzo dobre jest to, że kompilator
nie powstrzymuje przed takim działaniem,
O : Jeżeli Twoja klasa implementuje interfejs,
£ ) '
Czasami kliknięcie ikony może
jego referencja może wskazywać na każdą okazać s ię kłopotliw a na
ponieważ istnieje wiele przypadków,
jej instancję. Jest to naprawdę użyteczne — pewno łatw iej będzie nacisnąć
w których jest ono celowe. klaw isze Ctrl+. (kropka).
pozwala Ci utworzyć jeden typ referencji,

P : Ktoś mi powiedział, że interfejs jest jak


który może współpracować z ogromną liczbą
różnego rodzaju obiektów.
Kliknij ikonę i wybierz z menu Implement
Interface 'ICooksFood'. IntelliSense
kontrakt, ale naprawdę nie wiem dlaczego.
Może jakiś krótki przykład. Koń, wół automatycznie doda każdą składową, która
Co to może oznaczać?
i muł mogą ciągnąć wózek, ale w naszym jeszcze nie została zaimplementowana.
O : Tak, my także to słyszeliśmy — większość symulatorze zoo każde z tych zwierząt byłoby Wszystkie one posiadają pojedynczą instrukcję
th ro w powodującą zatrzymanie się programu.
ludzi mówi, że interfejs jest jak kontrakt. reprezentowane przez inną klasę. Powiedzmy,
(To dość częste pytanie podczas rozmów że organizujesz w zoo zawody w ciągnięciu Jest to pewien sposób zwrócenia Twojej uwagi
kwalifikacyjnych). I jest to do pewnego wózków i chcesz utworzyć tablicę, która na to, że zapomniałeś zaimplementować jedną
stopnia prawda. Kiedy tworzysz klasę, która przechowa każde zwierzę do tego zdolne. z nich (dowiesz się więcej na temat throw
implementuje interfejs, składasz kompilatorowi Oj, oj — nie możesz tak po prostu umieścić w rozdziale 10.).
obietnicę, że umieścisz w niej określone ich wszystkich w jednej tablicy. Gdyby
metody. Na pewno dopilnuje on, żebyś jej zwierzęta dziedziczyły po tej samej klasie
dotrzymał. bazowej, mógłbyś stworzyć tablicę takich
Interfejs jest jak
Uważamy, że łatwiej jest zapamiętać sposób obiektów. Okazuje się jednak, że tak nie jest. lista zadań, którą
Co w takim razie począć?
działania interfejsu, jeśli wyobrazimy go sobie
jako listę zadań. Kompilator sprawdza kolejne jej To właśnie w tym momencie pomocne okazują
kompilator przeglą­
punkty i upewnia się, czy umieściłeś wszystkie się interfejsy. Możesz utworzyć interfejs da, aby upewnić
metody interfejsu w danej klasie. Jeśli tego nie
zrobisz, zbombarduje Twój dom i nie pozwoli Ci
I P u lle r posiadający metody potrzebne
do ciągnięcia wózków. Następnie możesz
się, że Twoja klasa
kompilować! zadeklarować tablicę w sposób następujący: zaimplementowała
I P u lle r [] p u lle r A r r a y ;
określony zestaw
metod.

348 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Rozszerz interfejs IClown i skorzystaj z klas, które go implementują.


Ćw icz
Ćwiczenia W tym celu dodaj nowy kod do stworzonej wcześniej aplikacji konsolowej.

S Rozpocznij od interfejsu IClown z ostatniego Z rób to! na stronie 336.


public interface IClown {
string FunnyThinglHave { get; }
void Honk();
}
Rozszerz IClown, tworząc nowy interfejs IScaryClown, który będzie
po nim dziedziczył. Powinien on posiadać dodatkową właściwość 53

typu str in g o nazwie ScaryThingIHave z akcesorem get, FunnyFunny IScaryClown
ale bez akcesora s e t oraz metodę void o nazwie FunnyThingIHave (interface)
S ca reL ittleC h ild ren (). ScaryThingIHave

3 Stwórz następujące klasy: Honk() ScareLittleChildren()

★ Klasę zabawnego klowna o nazwie FunnyFunny, która będzie


używać prywatnej zmiennej typu strin g do przechowywania
zabawnej rzeczy oraz zawierać konstruktor z jednym parametrem
funnyThingIHave do ustawiania pola prywatnego. Metoda Honk()
powinna powiedzieć „Cześć dzieciaki! Mam ” połączone z zabawną rzeczą,
którą posiada. Akcesor g et FunnyThingIHave powinien zwracać
tę samą rzecz.
★ Klasę przerażającego klowna o nazwie ScaryScary, która będzie używać
prywatnej zmiennej do przechowywania liczby całkowitej przekazanej
w postaci parametru konstruktora o nazwie numberOfScaryThings.
Akcesor get ScaryThingIHave powinien zwracać łańcuch znaków
zawierający liczbę z konstruktora połączoną z tekstem „pająków”,
zaś metoda S careL ittleC h ildren() ma wyświetlać tekst „Buu! Mam cię!”.

9 Oto nowy kod metody Main(), który nie działa. Czy jesteś w stanie powiedzieć dlaczego?
sta tic void Main(string[] args) {
ScaryScary fingersTheClown = new ScaryScary("duże buty", 35);
FunnyFunny someFunnyClown = fingersTheClown;
IScaryClown someOtherScaryClown = someFunnyClown;
someOtherScaryClown.Honk();
Console.ReadKey();
}
Zwróć uwagą na to, ze
klown j e s t przerażający. LEPIEJ, ZEBYS ZROBIŁ TO
ĆWICZENIE POPRAWNIE!
W P R Z E C IW N Y M R A Z IE ..

jesteś tutaj ► 349


Nie, nie! Nieee! Niee! Tylko nie przerażające klowny

Rozszerz interfejs IClown i skorzystaj z klas, które go implementują.

Rozwiązania
ćwiczeń in te rfa ce IClown {
s trin g FunnyThinglHave { get; }
void Honk();
}

in te rfa ce IScaryClown : IClown {


s trin g ScaryThingIHave { get; } Metoda Honk()
void S c a re L ittle C h ild re n (); po prostu używa
} akcesora g e t do
wyśw ietlenia
komunikatu Mógtbyś
class FunnyFunny : IClown { — nie ma je s zc ze raz
p u b lic FunnyFunny(string funnyThingIHave) { potrzeby p isani<° zaim plementować
this.funnyThingIHave = funnyThingIHave; dwa razy te go metodę
samego kodu. i wtaściwość
} z interfejsu
p riv a te s trin g funnyThingIHave; IClown, ale
p u b lic s trin g FunnyThingIHave { dlaczego nie
get { return "Cześć d z ie c ia k i! Mam funnyThingIHave; } dziedziczyć po
prostu po klasie
}
FunnyFunny?
p u b lic void Honk() {

}
Console.W riteLine(this.FunnyThingIHave):
i
W z w iązku z tym, że S c aryS ca ry j e s t klasą pochodną FunnyFunny,
ta natomias t imp lemen tu je interfejs IClown, to ScaryScary także
go im plem entuje. J
class ScaryScary : FunnyFunny, IScaryClown {
p u b lic ScaryScary(string funnyThingIHave, in t numberOfScaryThings)
: base(funnyThingIHave) {
this.numberOfScaryThings = numberOfScaryThings;
}
p riv a te in t numberOfScaryThings;
p u b lic s trin g ScaryThingIHave {
get { return "Mam " + numberOfScaryThings pająków"; }
} M ozesz u sta w ić referencję FunnyFunny
p u b lic void S c a re L ittle C h ild re n () {
Z Z ar!0ść ° bie ktu S c a ry S c a r y : p o n ew a z
Console.WriteLine("Buu! Mam c ię ! 1 i T J r j y Ldziedziczy po FunnyFunny.
} Nus moze s z jednak p rzy p isać referencji
1ScaryC/own do j akiegokolwiek klowna,
}
T o n e w e nle wie s z , czy j e s t on przerażający.
To d/atego m u sisz uzyć słowa kluczowego a l
s ta tic void M a in (s trin g [] args) {
ScaryScary fingersTheClown = new ScaryScary("duże b u ty", 35)
FunnyFunny someFunnyClown = fingersTheClown;
1
IScaryClown someOtherScaryClown = someFunnyClown as ScaryScary
someOtherScaryClown.Honk();
Console.ReadKey();
M ożesz także wywotać m etodę S c a reLittleChildren(),
} używ ając zmiennej referencyjnej someOtherScary Clown
— nie m ożesz jednak tego zrobić przy użyciu zmiennej
someFunnyClown.

350 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Jest coś więcej niż tylko public i private


M etody, pola
Już wiesz, jak ważne jest słowo kluczowe p rivate, w jaki sposób go używać i czym się i wta ściw o ści klasy
ono różni od public. C # ma dla tych słów specjalną nazwę: są one modyfikatorami nazy wamy je j składowym i.
dostępu. Nazwa ma sens, ponieważ wstawienie modyfikatora dostępu zarówno Każda sk tadowa może być
o z n a k o m modyfikatorem
do właściwości lub metody — czyli składowej — jak i do całej klasy zmienia ich dostępu public lub
dostępność dla innych klas. Istnieje nieco więcej modyfikatorów, których będziesz priva te.
używał, ale rozpoczniemy od tych, które już znasz:
(O ile tylko ma dostęp do obiektu
Q p u b l ic oznacza, że każdy może uzyskać pełny dostęp. klasy deklaruJąceJ składow ą)-
Kiedy oznaczasz klasę lub jej składową słowem kluczowym public, informujesz C#, że każda
instancja innej klasy może uzyskać do niej pełny dostęp. Jest to najmniej restrykcyjny
z modyfikatorów dostępu. Już widziałeś, w jaki sposób mogą ze stosowania go wyniknąć
problemy — oznaczaj składowe klasy modyfikatorem public tylko wtedy, gdy masz powód.
Dzięki temu uzyskasz pewność, że klasy są hermetyczne. J e ś li podczas
deklarowania
O p r iv a te oznacza, że dostęp mogą uzyskać tylko inne składowe te* same klasy. składowej klasy
Gdy oznaczasz składową klasy słowem kluczowym private, można uzyskać dostęp do niej pom iniesz
tylko z wewnątrz lub z innej instancji tej klasy. Nie możesz oznaczyć tym modyfikatorem modyfikator do stępu,
to domyślnie zostan ie
klasy, chyba że została ona zadeklarowana wewnątrz innej; w takim przypadku dostęp do ona uznana za
niej będzie możliwy wyłącznie w instancjach klasy zewnętrznej. Taka klasa jest domyślnie ^— składową prywatną.
prywatna, a jeśli chcemy, by była publiczna, musimy ją jawnie jako taką zadeklarować.
J e śli deklarując
O p r o te c t e d oznacza p u b l ic dla klas potomnych i p r i v a t e dla pozostałych. kla sę lub in terfejs,
Już widziałeś, że klasa potomna nie może uzyskać dostępu do prywatnych pól klasy bazowej pominie sz modyfikator
dostępu, to domyślnie
— aby dostać się do składowych publicznych, musiała ona użyć słowa kluczowego base. Czy z o stanie zastosow any
nie byłoby wygodne, gdyby mogła korzystać z tych prywatnych pól? To właśnie do tego służy właśn ie modyfikator
modyfikator dostępu protected. Do każdej składowej nim oznaczonej można uzyskać dostęp internal. W w iększości
przypadków j e s t to
z każdej składowej danej klasy lub jej pochodnej. dobre rozwiązanie —
d zięki temu w sz y stk ie
O in te r n a l oznacza public tylko dla innych klas zestawu. inne klasy należące
Wbudowane klasy .NET Framework nazywamy zestawami. Są to biblioteki klas, które do zestaw u będą
znajdują się na liście referencji projektu. Możesz zobaczyć listę zestawów, klikając prawym miały do nich dostęp.
J e ś li nie korzystasz
przyciskiem myszy References w oknie Solution Explorer i wybierając A d d Reference... Kiedy z wielu zestaw ów ,
tworzysz nowy projekt Windows Forms Application, IDE automatycznie umieszcza w nim to modyfikator ten
zapewni podobne e fe kty
wszystkie referencje, które są potrzebne do skompilowania aplikacji Windows. Tworząc
ja k p riva te. Wypróbuj
zestaw, możesz użyć słowa kluczowego in te r n a l, aby jego klasy stały się prywatne. Istnieje go — wróć do któregoś
więc możliwość udostępniania tylko tych klas, które udostępnić chcemy. Możesz połączyć z poprzednich
projektów, dodaj
ten modyfikator dostępu z protected — wszystko, co oznaczysz jako protected in te r n a l, go do niektórych
będzie dostępne wyłącznie wewnątrz zestawu lu b w klasach pochodnych. klas i spraw dź,
co s ię sta n ie.
© s e a le d oznacza, że z klasy nie można dziedziczyć.
Istnieje kilka klas, z których nie można dziedziczyć. Dotyczy to sporej liczby klas platformy S e a l e d j e s t
.NET. Spróbuj utworzyć klasę pochodną, dziedzicząc po strin g (to ta klasa, której metody m o d y f i k a t o r e m , j e d n a k
n i e j e s t to m o d y f i k a t o r
IsNullOrEmptyO używałeś w poprzednim rozdziale). Co się stało? Kompilator nie pozwolił
d o s t ę p u . W y n i k a to
Ci zbudować kodu — wyświetlił błąd „cannot derive from sealed type ‘string’”. Podobnie z f a k t u , że m a on
możesz ograniczać własne klasy — po prostu dodaj sealed po modyfikatorze dostępu. w p t y w jedynie
na dziedziczenie,
a nie na możliwości
dostępu do klas.
Na temat wszystkich tych m odyfikatorów można napisać znacznie więcej.
Zajrzyj do punktu trzeciego dodatku „Pozostałości” , aby dokładniej je poznać.
jesteś tutaj ► 351
Świeży, m iętowy zasięg

Wprowadź te dw ie zm iany do
Modyfikatory dostępu zmieniają widoczność wtasnego r°zw iązania ćwiczenia.
Na stęp nie zm ień modyfikator
dostęp u protected z powrotem
Przyjrzyjmy się bliżej modyfikatorom dostępu oraz sposobowi, w jaki zmieniają na private i sprawdź, ja kie
one zasięg różnych składowych klasy. Wprowadziliśmy dwie zmiany: wewnętrzne p ow stały btędy.
pole funnyThingIHave ma teraz modyfikator protected. Zmieniliśmy także
metodę S careL ittleC hild ren () tak, aby używała pola funnyThingIHave.

O Oto dwa interfejsy. IClown definiuje klowna, który gra na swojej trąbce
i ma jakąś śmieszną rzecz. IScaryClown dziedziczy po IClown. Straszny
klown ma wszystko to, co zwyczajny, a dodatkowo ma jakieś przeraźliwe
rzeczy i straszy dzieci. (To się nie zmieniło od poprzedniego przykładu).
Stowo kluczowe th is _t akźe a n io m a to , eto
interface IClown { czego odnosi s ię zm ienna. M o w C# coś
ta kiego: „Spójrz na bieżącą instancję klasy
string FunnyThingIHave { get; } i znajdź cokolwiek, co j e s f z m ą z w ^ a n e
— naw et w tedy, gdy je s t to 29°™?
void Honk(); z parametrem lub zm ienną fo tefin .
}
I
To dość powszechny sp °s°b
interface IScaryClown : IClown { użycia this, ponieważ
parametr i pole w ew nętrzne
string ScaryThingIHave { get; } mają tę sa m ą nazw ę.
funnyThingIHave odn° si się
void ScareLittleChildren(); do parametru, podczas gdy
this.funnyThingIH ave dotyczy
} pola w ewnętrznego.

Klasa FunnyFunny implementuje interfejs IClown. Zadeklarowaliśmy pole funnyThingIHave


jako protected, tak by miały do niego dostęp dowolne instancje klas pochodnych.

class FunnyFunny : IClown {


Dodanie thi public FunnyFunny(string funnyThingIHave) {
sygnalizuje
C#, że ma this.funnyThingIHave = funnyThingIHave;
na myśli
w ewnętrzne } Z m ieniliśm y to na protected.
pole, a nie S p ójrz, w ja ki sposób wpływa
param etr o j protected string funnyThingIHave; to na metodę ScaryScary.
sam ej nazwie. ScareLittleChildren().
public string FunnyThingIHave {
get { return "Cześć dzieciaki! Mam + funnyThingIHave; }
}
public void Honk() {
Console.WriteLine(this.FunnyThingIHave);
}
} Kiedy u żyw a sz th is w °dniesieniu do
właściwości, nakazuj e s z tym samym
uruchomić akcesor g e t lub s e t .

352 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Klasa ScaryScary implementuje interfejs IScaryClown.


Dziedziczy po FunnyFunny, a ponieważ klasa ta implementuje ^ M o d y fik a to r y
IClown, zatem ScaryScary także to robi. Przyjrzyj się, w jaki
sposób metoda S careL ittleC h ild ren () odwołuje się do pola d o s tę p u z b lis k a
wewnętrznego funnyThingIHave — jest to możliwe dzięki
zastosowaniu modyfikatora dostępu protected. Gdybyśmy
zamiast niego zastosowali modyfikator private, to kodu
nie można by było skompilować.
Składowa
class ScaryScary : FunnyFunny, IScaryClown { numberOfScaryThings j e s t
prywatna, co j e s t typowe
public ScaryScary(string funnyThingIHave, dla pól wewnętrznych.
int numberOfScaryThings) Tylko inna instancja
S c a ryS cary będzie zdolna
: base(funnyThingIHave) { ją zobaczyć.
this.numberOfScaryThings = numberOfScaryThings; J

}
private int numberOfScaryThings;
public string ScaryThinglHave {
get { return "Mam 11 + numberOfScaryThings pająków"; }
} S t°w°^ k/ucz°w e protected nakazuje
uczynić to po/e prywatnym d/a
public void ScareLittleChildren() { kazż iego z wyj ą tk iem instancji k/as
potomnych.
Console.WriteLine("Nie możesz mieć mojego
+ base.funnyThinglHave); i
} Stowo kluczowe base
} nakazuje C# użyć wartości p ozostaw i/i po/e funnyThingIHave
z klas y tiazowej, lecz w tym U a t°,rem p r ^ ^ kompi/ator wygenerowałby
} przy padku moglibyśm y użyć w m o m e n t gdy zm ieni/iśm y je na
takż e this . Czy potrafisz ? ° ° ^ ^ • F ^ y F ^ ! ,; ‘"0 d o ,,ę p '" ‘ d a ka k l a s ,
pow iedzieć dlaczego?

l4 Poniżej znajduje się kod metody Main(), który tworzy instancje klas FunnyFunny i ScaryScary. Zwróć uwagę
na sposób zastosowania słowa kluczowego as do rzutowania zmiennej someFunnyClown w dół na referencję typu
IScaryClown.
private void Main(string[]args) {
ScaryScary fingersTheClown = new ScaryScary("duże buty", 35);
FunnyFunny someFunnyClown = fingersTheClown;
IScaryClown someOtherScaryClown = someFunnyClown as ScaryScary;
someOtherScaryClown.Honk();

e
Console.ReadKey(); Pí°klWlJ;Śćmy ■clomkodu dodat kową instrukj by

W zw iązku z tym , że pokazać, ze m ozesz rzutować obiekt ScarySc


metoda Main() nie j e s t na typ Funny Funny, a następnie rzutować go
częścią FunnyFunny ani w dół na typ IS c aryC/own. Jednak te trzy w
ScaryScary, nie może .i-
J e s t poza tym i klasami, więc instrukcje ko(dui można by z a s tąpić jednym . Czy w iesz, ja
ona uzyskać dostępu z wewnątrz mogą uzyskaćna/eżatoby
d ostęp tyto
lkozrobić?
do
do chronionego pola publicznych składowych obiektów FunnyFunny
funnyThingIHave. oraz ScaryScary.

jesteś tutaj ► 353


Eee, powielany kod!

■Nie .istnieją.
głupie pytania

P : Dlaczego miałbym używać P : Po co mam korzystać z właściwości?


Interfejsu, możesz utworzyć tablicę, która
pozwoli CI przekazywać Informacje do
interfejsów, zamiast po prostu napisać Nie mogę po prostu użyć pól?
metod ICarryPassenger I z powrotem
wszystkie potrzebne metody w klasie?
O : Dobre pytanie. Interfejs definiuje tylko
bez względu na to, czy będziesz pracował
O : Pisząc coraz bardziej skomplikowane sposób, w jaki klasa powinna wykonać
z obiektem ciężarówki, konia, jednośladu,
czy samochodu. Sposób, w jaki obiekty te
i rozbudowane programy, mógłbyś skończyć określone zadanie. W rzeczywistości nie jest
wykonują zadanie, jest prawdopodobnie nieco
z ogromną liczbą różnych klas. Interfejsy to żaden obiekt, nie możesz więc utworzyć
odmienny. Posiadając referencje interfejsów,
pomagają Ci pogrupować je na podstawie jego instancji i nie może on przechowywać
masz jednak pewność, że wszystkie one mają
czynności, które one wykonują. Pozwalają też informacji. Gdybyś dodał pole, byłoby ono
te same metody, które przyjmują te same
upewnić się, że każda klasa przeznaczona do deklaracją zmiennej, C# musiałby za tym
parametry i zwracają wartość tego samego
wykonywania określonej czynności używa gdzieś tę informację umieścić — a interfejs
typu. Dzięki temu możesz wywoływać je
tych samych metod. Klasa może wykonywać sam w sobie służyć do tego celu nie może.
i przekazywać do nich informacje w dokładnie
pracę, a dzięki obecności interfejsu nie musisz Właściwość umożliwia utworzenie czegoś, co
taki sam sposób.
się martwić, w jaki sposób to robi. Po prostu ją dla innych klas będzie wyglądać jak pole, lecz,
wykonuje.
Przykład: Możesz posiadać klasę ciężarówek
ponieważ jest metodą, w rzeczywistości nie
będzie przechowywać żadnych informacji.
P : Dlaczego powinienem tworzyć
zmienne protected zamiast private
i klasę żaglówek, które implementują interfejs
ICarryPassenger. Powiedzmy, że określa on P : Jaka jest różnica między zwykłą
lub public?

pewne reguły — każda z implementujących go


klas musi mieć metodę ConsumeEnergy().
referencją do obiektu a referencją
interfejsu?
O : Ponieważ pozwala to w lepszy sposób
hermetyzować implementację klasy Często
Twój program mógłby użyć do przewozu
pasażerów dowolnej z nich. Nie ma znaczenia,
O : Już wiesz, w jaki sposób działa zwykła
zachodzi taki przypadek, że klasa pochodna
musi uzyskać dostęp do pewnej wewnętrznej
referencja do obiektu. Gdy utworzysz instancję
że metoda ConsumeEnergy() klasy żaglówek części klasy bazowej. Na przykład gdy chcesz
klasy Skateboard i nazwiesz ją vertBoard,
używa siły wiatru, natomiast ta sama metoda przesłonić właściwość, to dość powszechnym
a następnie dodasz nową referencję o nazwie
klasy ciężarówek oleju napędowego. sposobem jest użycie pola wewnętrznego
halfPipeBoard, obie będą wskazywały
Wyobraźmy sobie teraz, że nie posiadasz w akcesorze get — w ten sposób możesz
na ten sam obiekt. Jeśli jednak Skateboard
interfejsu ICarryPassenger. W takim zwrócić nieco zmodyfikowaną wartość.
implementuje interfejs iS tre e tT ric k s ,
przypadku programowi byłoby trudno określić, Kiedy tworzysz klasy, powinieneś umieszczać
a Ty utworzysz zmienną referencyjną o nazwie
które pojazdy mogą przewozić ludzi, a które modyfikator dostępu p u b lic tylko tam,
s treetB o ard do obiektu typu skateBoard,
nie. Musiałbyś przeglądnąć każdą klasę, której gdzie tego naprawdę potrzebujesz. Użycie
to będziesz wiedział tylko o tych metodach tej
Twoja aplikacja mogłaby użyć, i określić, czy modyfikatora p ro te cte d pozwala udostępnić
klasy, które znajdą się także w interfejsie.
jest w niej dostępna metoda do przewożenia pola tym klasom pochodnym interfejsu.
Wszystkie trzy referencje w zasadzie wskazują Dla pozostałych klas w dalszym ciągu będą
ludzi z jednego miejsca do drugiego. Następnie
na ten sam obiekt. Jeżeli spróbujesz użyć to składowe prywatne.
musiałbyś wywoływać dla każdego pojazdu
ja lfP ip e B o a rd lub vertBoard, będziesz
odpowiednią metodę, która akurat w danej
w stanie uzyskać dostęp do każdej metody
klasie została zdefiniowana do przewozu
pasażerów, W związku z tym, że nie miałbyś
i właściwości obiektu. Jeśli natomiast Referencje
standardowego interfejsu, metody te mogłyby
skorzystasz z referencji streetB oard,
będziesz miał dostęp tylko do metod
interfejsów wiedzą
nazywać się dość oryginalnie lub znajdować
się gdzieś pomiędzy wieloma innymi. Od razu
i właściwości zdefiniowanych w interfejsie.
tylko o metodach
widać, że sprawa dość poważnie by się
skomplikowała.
P : Po co w takim razie używać i właściwościach,
referencji interfejsu, skoro ograniczają one
możliwości pracy z obiektem? które zostały
O : Referencje interfejsu umożliwiają pracę zdefiniowane
z zestawem różnych obiektów, które robią
tę samą rzecz. Używając typu referencji w interfejsach.

354 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Shopper
Obiekty niektórych klas TotalSpent
CreditLimit
nigdy nie powinny być tworzone
ShopTill YouDropO
Pamiętasz hierarchię klas w symulatorze zoo? Na pewno skończyłeś swoją zabawę, BuyFavouriteStuff()
tworząc grupy hipopotamów, psów i lwów. Co jednak z klasami Canine i Fenine ?
Co z klasą Animal ? Okazuje się, że istnieją klasy, których instancje w ogóle nie
powinny być tworzone... i w zasadzie nie miałoby to sensu, gdyby mimo wszystko
powstały. Mamy przykład.

Rozpocznijmy od podstawowej klasy studenta robiącego zakupy w księgarni.

class Shopper {
z\ Engineering
Student

p u b lic void ShopTillYouDrop() {


BuyFavouriteStuff()
w hile (TotalSpent < C re d itL im it)
B uyF avouriteS tuff();
}
p u b lic v irtu a l void BuyFavouriteStuff() {
/ / Nie ma tu ta j implementacji - nie wiemy,
/ / co nasz student lubi kupować
/
Obie klas y , A rtS tu d e n t
i EngineeringStudent,
} przesłaniają metodę
BuyFavouriteStuff(),
} ale s tu denci obu
To jest klasa A rtS tudent — pochodna klasy Shopper: tych rodzajów kupują
z u pełnie inne rzeczy.
class A rtS tu d e n t : Shopper {
p u b lic override void BuyFavouriteStuff() {
BuyArtSupplies();
BuyBlackTurtlenecks();
BuyDepressingMusic();
}
}
A to klasa EngineeringStudent, która także dziedziczy po Shopper:

class E n g in e e rin g S tu d e n t : Shopper {


p u b lic override void BuyFavouriteStuff() {
BuyPencils();
BuyGraphingCalculator();
BuyPocketProtector();
}
}

Co się stanie, gdy utworzysz instancję klasy Shopper ? Czy jest w ogóle sens to robić?

jesteś tutaj ► 355


Nie mogę uwierzyć, że to nie jest interfejs

Klasa abstrakcyjna jest jak


skrzyżowanie klasy i interfejsu
Przypuśćmy, że chcesz mieć coś na kształt interfejsu, coś, co wymusi na klasach
implementację konkretnych metod i właściwości. Musisz w tym jednak umieścić trochę
kodu, aby niektóre ze składowych nie musiały być implementowane w każdej klasie
potomnej. Potrzebujesz klasy abstrakcyjnej. Zapewnia ona możliwości interfejsu,
ale będziesz mógł w niej także pisać kod, jak w zwykłych klasach.
która ma deklarację, ale nie
posiada żadnych instrukcji ani ciała,
nazy wana j e s t m etodg abstrakcyjną
O KLASA ABSTRAKCYJNA JEST JAK ZW YKŁA KLASA. Kla sy dzie dziczące m uszą
z a implementować w szystkie metody
Klasę abstrakcyjną definiujesz tak samo jak zwykłą. Posiada ona pola abstrakcyjne tak ja k w przypadku
i metody, a także może być dziedziczona po innych klasach dokładnie dziedziczenia interfejsu.
tak samo jak zwykła. Nie ma tu prawie niczego nowego do opanowania,
ponieważ znasz już wszystko, co robi klasa abstrakcyjna!

Tylko klasy abstrakcyjne mogą


zaw ierać abstrakcyjne metody. Jeśli
umj e ś c isz taką m etodę w klasie ,
będ zie sz m usiat oznaczyć tę klasę
jako abstrakcyjną — w przeciwnym
raz ie to d nie t>ędzie s ię kompilował.
za m inutę dow iesz się,
O KLASA ABSTRAKCYJNA JEST JAK INTERFEJS. w ja ki s p ^ t o oznaczyć, że klasa
j e s t abstrakcyjna.
Kiedy tworzysz klasę, która implementuje określony interfejs,
zgadzasz się zaimplementować wszystkie właściwości i metody w nim
zdefiniowane. Klasa abstrakcyjna działa w taki sam sposób — może
zawierać deklaracje właściwości czy metod i tak jak interfejs musi zostać
zaimplementowana przez klasy pochodne.

Przeciwieństwem
metody abstrakcyjnej
j e s t metoda konkretna.
To taka, która posiada
ciało. W szystkie klasy,
Q NIE M O ŻN A JEDNAK TWORZYĆ z którymi pracowałe ś do
INSTANCJI KLASY ABSTRAKCYJNEJ t ej pory, były konkretne.

Największa różnica pomiędzy klasą abstrakcyjną a klasą


konkretną polega na tym, że nie możesz użyć operatora new do
utworzenia jej instancji. Jeśli to zrobisz, to podczas kompilacji kodu
C # wygeneruje błąd.

Q 1 Cannot create an instance of the abstract class or interface 'MyClass'


Ten błąd pojawił s ię dlatego,
ż e posiada sz abstrakcyjne metody
b ez ża dnego kodu! Kompilator
nie pozwoli Ci utworzyć klasy
z brakującym kodem tak sam o ja k
nie pozwolił Ci utw orzyć instancji
interfejsu.

356 Rozdział 7.
Interfejsy i klasy abstrakcyjne

o
ZACZEKAJ! CO TY MÓWISZ? KLASA, KTÓREJ
INSTANCJI NIE M O ŻNA UTWORZYĆ? A NIBY PO CO
MIAŁABYM PISAĆ COŚ TAKIEGO?

Ponieważ chcesz udostępnić część kodu, a jednocześnie wymusić,


aby klasy potomne uzupełniły resztę.

Czasami dzieją się złe rzeczy, gdy chcesz utworzyć obiekty, które nigdy nie powinny być
tworzone. Klasa na szczycie diagramu klas posiada zwykłe pola, które w założeniu mają
być ustawiane przez klasy potomne. Klasa Animal mogłaby zawierać obliczenia zależne
To je s t klasa Klubu od wartości logicznej HasTail lub V e rte b ra te , ale nie ma możliwości samodzielnego
A strofizyki z Obiektowa ustawienia tych pól. A stro fizycy mają dwie m isje —
używana do wysSyiW JUM
ytania - jedną m Marsa, drugą na Wenus.
rakie t na różne planety. M a m y tutaj przykład..
class P la n e tM is s io n { Nie ma se n su ustaw ianie tych \
c la s s Venus : Plan etM issio n {
p u b lic long RocketFuelPerMile pól w klasie bazowej, ponieważ
nie w iemy, jakiej rakiety p u b lic Venus() {
p u b lic long RocketSpeedMPH; będziemy używ ać i na j aką M ilesTo Planet = 40000000;
p u b lic in t MilesToPlanet; j p lanetę j ą wyślemy.
R o cketFuelPerM ile = 100000;
RocketSpeedMPH = 25000;
p u b lic long UnitsOfFuelNeeded() {
}
return MilesToPlanet * RocketFuelPerMile;
}
} c la s s Mars : Plan etM issio n {
p u b lic in t TimeNeeded() {
p u b lic M ars() {
return MilesToPlanet / ( in t) RocketSpeedMPH;
M ilesTo Planet = 75000000;
}
R o cketFuelPerM ile = 100000;
p u b lic s trin g FuelNeeded() {
RocketSpeedMPH = 25000;
return "Będziesz potrzebował "
}
+ UnitsOfFuelNeeded()
} Konstrukt° ry klas pochodnych Mars oraz
+ " jednostek paliwa, aby się tam dostać. Zajmie Ci to Venus u s t awiają trzy pola odziedziczone
+ TimeNeeded() + " g od zin ."; z klasy PlanetM ission. Pola te nie są
u s t awiane , jeżeli bezpośrednio tw orzysz
m ^ n c j ę klasy bazowej. Co s ię w takim
} razie dzieje, gdy metoda FuelNeeded()
próbuje ich u żyć?
p riva te void button1_C lick(object sender, EventArgs e) {
Mars mars = new Mars();
MessageBox.Show(mars.FuelNeeded());
}

p riva te void button2_C lick(object sender, EventArgs e) {


Venus venus = new Venus();
MessageBox.Show(venus.FuelNeeded());
}

p riva te void button3_C lick(object sender, EventArgs e) {


PlanetMission planet = new PlanetM ission();
Zanim przejdziesz na następną stronę,
MessageBox.Show(planet.FuelNeeded());
spróbuj określić, co się stanie,
gdy użytkownik kliknie trzeci przycisk.

jesteś tutaj ► 357


Klasy abstrakcyjne pozwalają uniknąć tego bałaganu

Jak wspominaliśmy, obiekty niektórych klas


nigdy nie powinny być tworzone
Problemy pojawiają się z chwilą tworzenia nowej instancji klasy PlanetM ission. Jej metoda
FuelNeeded() spodziewa się, że pola będą ustawiane przez klasę pochodną. Ustawiane jednak nie są
i otrzymują swoją domyślną wartość — zero. Kiedy C # próbuje przez tę wartość podzielić...

private void button3_Click(object sender, EventArgs e) { Klasa PlanetM ission nie


zostata napisana w celu
tworzenia je j obiektów.
M ieliśm y po niej tylko
dziedziczyć. To wtaśnie dlategi
pojawiły s ię problemy.

Rozwiązanie: użyj klasy abstrakcyjnej. Dodanie stowa kluczowego abstract


do deklaracji klasy informuje C# , że
Kiedy oznaczysz klasę słowem kluczowym a b s tra c t , C # nie j e s t to klasa abstrakcyjna i w zw iązku
z tym nie można tw orzyć je j ^ ra n c M .
pozwoli Ci napisać kodu tworzącego jej instancję. Rozwiązanie
to jest bardzo zbliżone do interfejsu — działa on jak pewien
wzorzec dla klas, które po nim dziedziczą.

abstract class PlanetMission {


public long RocketFuelPerMile;
Teraz C# będzie
odmawiat kompilacji public long RocketSpeedMPH;
programu, dopóki
nie u su n iesz public int MilesToPlanet;
w iersza, który
tworzy nową public long UnitsOfFuelNeeded() {
instancję
planetM ission. return MilesToPlanet * RocketFuelPerMile;
}
// Tu taj zdefiniowana j e s t p o z o s t a ł a c z ę ś ć k l a s y
}
WYSIL ___________
SZARE KOMÓRKI
Zajrzyj do przedstawionego w poprzednim rozdziale programu dla Krystyny, służącego
do wyliczania kosztów przyjęć. Przeanalizuj jego hierarchię klas. Czy w jakiejkolwiek
sytuacji miałoby sens tworzenie obiektu klasy Party, czy może sensowniejsze byłoby
zadeklarowanie jej jako klasy abstrakcyjnej, by nie dopuścić do tworzenia jej instancji?
358 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Metoda abstrakcyjna nie ma ciała Każda metoda zadeklarowana


w interfejsie jest automatycznie
Już wiesz, że interfejsy zawierają jedynie deklaracje metod i właściwości, traktowana jako metoda abstrak­
lecz nie zawierają ich implementacji, prawda? Jest tak dlatego, że wszystkie cyjna, więc nie musisz poprze­
metody umieszczane w interfejsach są metodami abstrakcyjnymi. Za każdym dzać ich słowem kluczowym
razem, gdy jakaś klasa dziedziczy po klasie abstrakcyjnej, musisz pamiętać a b s tr a c t . Potrzebne jest ono
o tym, by przesłonić wszystkie zadeklarowane w niej metody abstrakcyjne. jedynie w klasach abstrakcyj­
Na szczęście IDE bardzo ułatwia to zadanie. Wystarczy wpisać „public nych. Takie klasy mogą deklaro­
override” — gdy tylko naciśniesz klawisz odstępu, IDE wyświetli okienko wać metody abstrakcyjne, choć
zawierające listę wszystkich metod, które można przesłonić. Wybierz metodę mogą posiadać także konkretne.
S e tM is s io n In fo () i uzupełnij jej kod, tak jak zrobiliśmy to poniżej.

abstract class PlanetMission {


public abstract void SetMissionInfo(
int MilesToPlanet, int RocketFuelPerMile,
long RocketSpeedMPH);
// Po zo st ał a c z ę ś ć k l a s y
Ta cxbstrakcyjrra met°d a j es t ja k interfejs — nie ma ciała, ale każda
k . raT dziedziczy po PlanetM ission, m usi zaimplementować
S etM issionInfo() — w p rze c iwnym razie program s ię nie skom piluje.

Jeżeli dodamy taką metodę i spróbujemy zbudować


program, IDE wyświetli następujący błąd:

© 1 M isjaM iedzyplanetam a.V enus' d oes not im p lem ent inherited abstract m em b er
M isjaM iedzyplanetarna.PlanetM ission.SetM issionlnfo(int, int, lo n g)1

W takim razie zaimplementujmy ją! Kiedy już to zrobimy, błąd zniknie.

p u b lic class Venus : PlanetMission { Jeśli klasa dziedziczy po


klasie abstrakcyjny, t°
p u b lic Venus() { musi przestonte w szystkie
SetMissionInfo(40000000, 100000, 25000); zadeklarowane w niej
m etody abstrakcyjn e -
}
p u b lic override void S e tM issio n In fo (in t milesToPlanet, in t rocketFuelPerM ile, long rocketSpeedMPH) {
this.M ilesToPlanet = milesToPlanet;
this.RocketFuelPerM ile = rocketFuelPerMile;
this.RocketSpeedMPH = rocketSpeedMPH;
}
}
Klasa Mars wygląda dokładnie tak samo jak Venus, a różni się od niej wyłącznie
zastosowanymi wartościami. Co sądzisz o takiej hierarchii klas? Czy deklarowa­
nie metody S e tM issio n In fo () jako abstrakcyjnej ma sens? Czy nie powinna to być
konkretna metoda zdefiniowana w klasie P lanetM ission ? , ca
jestes tutaj ► 359
W arte tysiąca słów

m. Zaostrz ołówek
Masz szansę zaprezentować swoje zdolności artystyczne. Po lewej stronie znajdziesz deklaracje
v Interfejsów i klas. Twoim zadaniem jest narysowanie z prawej strony odpowiadających im
diagramów klas. Zrobiliśmy za Ciebie pierwszy z nich. Nie zapomnij o użyciu linii przerywanej
do oznaczenia implementacji interfejsu oraz linii ciągłej do oznaczenia dziedziczenia.

Jak będzie wyglądał rysunek?


Dane:

1) interface Foo { }
1)
class Bar : Foo { }

2)
2) in terface Vi n n { }
abstract class Vo u t : Vi n n { }

3)
3) abstract class Muffie : Whuffie { }
class Fluffie : Muffie { }
i n t e r f a c e Whuffie { }

4)
4) class Zoop {}
class Boop : Zoop { }
class Goop : Boop { }

5)
5) class Gamma : D e l t a , Epsilon { }
interface Epsilon { }
interface Beta { }
class Alpha : Gamma, B e t a { }
class Delta { }

360 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Po lewej stronie znajdziesz zestawy diagramów klas. Twoim zadaniem jest przełożenie ich
na prawidłowe deklaracje C#. Numer 1 zrobiliśmy za Ciebie.

Dane: Jaka jest deklaracja?


Click 1) public class Click { }
1
public class Clack : Click { }

Top
/ 2
2)
Clack

Tip
3)

4 4)

Bar
5)
Zeta
5 y
Baz
Beta 1
Alpha
KLUCZ
T rozszerza

implementuje
S

Delta 1 C la c k 1 klasa

1 Clack 1

C la c k
-1

jesteś tutaj ► 361


Ich walka na słowa

Pogawędki przy kominku


Dzisiejsza rozmowa: Klasa abstrakcyjna oraz in te rfe js dyskutują
na te m a t bardzo dra żliw y: „K to jest w ażniejszy?".

Klasa abstrakcyjna: Interfejs:


— Myślę, że oczywiste jest, kto z naszej dwójki jest ważniejszy.
Programiści potrzebują mnie do wykonywania pracy. Spójrzmy
prawdzie w oczy. Nawet do pięt mi nie dorastasz.
— Pięknie. Nieźle się zapowiada.

— Nie możesz nawet myśleć, że jesteś ważniejszy niż ja.


Nawet nie używasz prawdziwego dziedziczenia — jesteś tylko
implementowany.
— No super. I znowu to samo. Interfejsy nie używają
prawdziwego dziedziczenia. Interfejsy są tylko implementowane.
Ten zarzut obnaża twoją ignorancję. Implementacja jest tak samo
dobra jak dziedziczenie, a w zasadzie lepsza!
— Lepsza? Chyba śnisz. Jestem znacznie bardziej elastyczna niż
ty. Mogę mieć zarówno metody abstrakcyjne, jak i konkretne.
Mogę nawet posiadać metody wirtualne, jeśli zechcę.
Oczywiście nie można tworzyć moich instancji, ale twoich też
nie. Ponadto mogę robić wszystko, co potrafi robić zwykła klasa.
— Czyżby? A co wtedy, gdy chcesz mieć klasę dziedziczącą po
tobie i twojej koleżance? Nie można dziedziczyć po dwóch
klasach. Trzeba wybrać jedną i jest to wielka niedogodność! Nie ma
natomiast żadnego ograniczenia co do liczby implementowanych
interfejsów. Pomówmy nieco o elastyczności: ja pozwalam
programistom tworzyć klasy, które mogą zrobić dosłownie wszystko.
.Zaostrz ołówek

Rozwiązanie
2) (interfejs) 5) I (interfejs)
K in te rffi 4 Z oop
Vinn W huffie 1 D elta f
L 1
\f \
~7*s

Boop I (interfejs)
V out Muffie G am m a Beta

A s
/ 1S

Fluffie G oop [ A lpha I


Jak będzie
wyglądał rysunek?

362 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Klasa abstrakcyjna: Interfejs:


— Być może nieco przeceniasz swoją moc.

— Myślisz, że przez sam fakt posiadania kodu stajesz się


najważniejszą rzeczą od czasu odkrycia chleba krojonego.
Nie zmienisz jednak tego, że program może dziedziczyć
jednocześnie tylko po jednej klasie. Jesteś więc nieco
ograniczona. Prawda, nie mogę zawierać żadnego kodu,
ale myślę, że kod jest przeceniany.
— Takich właśnie bredni spodziewałam się po interfejsie.
Kod jest niesłychanie ważny! To dzięki niemu programy działają.

— Prawie zawsze programista chce mieć pewność, że obiekt


ma określone właściwości i metody, ale nie zwraca uwagi na to,
jak zostały zaimplementowane.

— Naprawdę? Nie zgodziłabym się z tym — programistów


zawsze interesuje to, co jest w ich metodach i właściwościach.

— Dobrze, dobrze, czasem może tak być. Pomyśl jednak


o wszystkich przypadkach, gdy programista pisze metodę
pobierającą obiekt, który musi mieć pewną metodę. W tym
momencie naprawdę nie zwraca on uwagi na to, jak została
napisana. Ważne, że ona tam jest. I koniec! Programista musi
tylko napisać interfejs i problem rozwiązany!

Tak, oczywiście, powiedz programiście, że nie może pisać.

— Jak chcesz!

2) abstract class Top { } 3) abstract class Fee { }


class Tip : Top { } abstract class Fi : Fee { }

4) interface Foo { } 5) interface Zeta { }


class Bar : Foo { } class Alpha : Zeta { } D e lta d z i e d z i c z y
po A lp h a
class Baz : Bar { }

Jaka jest deklaracja?

jesteś tutaj ► 363


Dziedziczenie w ielokrotne jest do kitu

W DALSZYM CIĄGU JESTEM PRZECZULONA N A BRAK


MOŻLIWOŚCI DZIEDZICZENIA PO DWÓCH KLASACH JEDNOCZEŚNIE.
JEŚLI NIE MOGĘ DZIEDZICZYĆ PO DWÓCH LUB WIĘKSZEJ LICZBIE
KLAS, TO MUSZĘ KORZYSTAĆ Z INTERFEJSÓW, A TO DOŚĆ DUŻE
OGRANICZENIE C #, PRAWDA?

To nie ograniczenie, to zabezpeczenie.


Gdyby C# pozwolił Ci na dziedziczenie po kilku klasach bazowych,
umożliwiłby Ci otwarcie prawdziwej puszki Pandory. Kiedy jakiś
język na to pozwala, możliwość taką nazywamy dziedziczeniem
wielokrotnym. C#, poprzez udostępnienie interfejsów,
zabezpiecza Cię przed ogromnym chaosem, który nazywamy...

m diamentem Śmierci!

Klasy Television oraz


M ovieTheater dziedziczą
z MoviePlayer i przesłaniają
m etodę ShowAM ovie(). Obie
dziedziczą także właściwość Television MovieTheater
ScreenW idth. ' prZt o ś T J CoMs % s ta n ie , je ż e li k la sa

ShowAMovie() ShowAMovie() u żyć obu z nic■ ^ rów n0c z e ś n ie


w te d y , g d y 3 o rocj u k o w a n e n a

1 " * " » ' “ bu,or’ ” ’

na obiekcie HomeTheater? ShowAM ovie()

Unikaj niejednoznaczności!
Języki, które pozwalają na utworzenie piekielnego diamentu śmierci, mogą doprowadzić
do pewnych nieprzyjemnych konsekwencji, ponieważ będziesz potrzebował dodatkowych
reguł przy rozstrzyganiu niejednoznacznych sytuacji... co wymaga większego nakładu pracy
podczas tworzenia Twojego programu! C # zabezpiecza przed pojawieniem się tego typu
sytuacji poprzez zastosowanie interfejsów. Gdyby T elevision oraz MovieTheater były
interfejsami, a nie klasami, ta sama metoda ShowAMovie() mogłaby usatysfakcjonować
oba z nich. Interfejs wymaga tylko tego, aby metoda była dostępna.

364 Rozdział 7.
Interfejsy i klasy abstrakcyjne
Zagadkowy basen
Twoim zadaniem jest pobranie fragmentów kodu z basenu i wstawienie ich
w puste miejsca. Możesz użyć tego samego fragmentu więcej niż raz
'i -— —^ i nie musisz wykorzystać ich wszystkich. Celem jest napisanie zestawu
klas, które się skompilują, będą działały i wyświetlą komunikat
zaprezentowany poniżej.

class : {
p u b lic ................... Nose { p u b lic Acts() : base("Acts") { }
p u b lic override ......................... {
s trin g Face { get; } return 5;
} }
Tu j e s t p u n kt wejścia
}
— to j e s t kompletnu
abstract class .............. :............. program C#.
p u b lic v irtu a l in t Ear() class : {
{ p u b lic override s trin g Face {
return 7; get { return "Of76"; }
} }
p u b lic Picasso(string face) p u b lic s ta tic void M a in (s trin g [] args) {
{ s trin g re s u lt = " " ;
................... = face; Nose[] i = new Nose[3];
} i[0 ] = new A c ts ();
p u b lic v irtu a l s trin g Face i[1 ] = new Clowns();
{ i[2 ] = new Of76();
{ ................. fo r ( in t x = 0; x < 3; x++)
} {
s trin g face; re s u lt += ( ^ II II

} + . + "\n ";
}
class ................. : { C onsole.W riteLine(result);
p u b lic Clowns() : base("Clowns") { } Console.ReadKey();
}
LJ filey//CyUsers/ - W ynik
5 Acts
Przypom inam y: 7 Clowns
7 Of 76
k a żd y fragment
z basenu może
zostać u żyty
w ięcej niż ra z

► Odpowiedź znajdziesz na stronie 383. jesteś tutaj ► 365


W postaci... grupy orłów!

DOBRZE, MYŚLĘ, ŻE JUŻ , » iw r a c f i buta dość


WIEM, W JAKI SPOSÓB RADZIĆ ¡ T ° 'U
3 /Jna M czasach pierwszego
je j zastosow ania, ale to w ten
SOBIE
Z OBIEKTAMI! sposób tw orzysz w szystk ie sw oje
programy w C# M o ż lsz o tum
m yśleć ja k o czym ś zwyktym.

Jesteś programistą obiektowym.


Istnieje pewne określenie na to, co teraz robisz. Nazywamy
to programowaniem obiektowym lub OOP. Zanim powstały
języki takie jak C#, ludzie nie korzystali z obiektów i metod
podczas pisania kodu. Używali po prostu funkcji (czyli czegoś
na kształt metod w programach nieobiektowych), które były
zgromadzone w jednym miejscu. To tak, jakby cała aplikacja
była jedną wielką statyczną klasą zawierającą tylko metody
statyczne. Znacznie trudniej było wtedy utworzyć program
modelujący rozwiązywane problemy. Na szczęście już nigdy nie
będziesz musiał pisać programów bez OOP, ponieważ stało się
ono integralną częścią C#.

Cztery zasady programowania obiektowego


Kiedy programiści rozmawiają na temat OOP, wskazują na cztery ważne zasady.
Powinny one wydać Ci się znajome, ponieważ z każdą z nich zdążyłeś się już
Hermetyzacja oznacza
zetknąć. Pierwsze trzy rozpoznasz po ich nazwach. Są to dziedziczenie, abstrakcja tworzenie obiektu,
i hermetyzacja. Ostatnią z nich nazywamy polimorfizmem. Brzmi to trochę dziwnie, który przechowuje swój
ale wkrótce okaże się, że o tej zasadzie także wiesz wszystko. wewnętrzny stan za
pomocą prywatnych pól
i używ a publicznych
m etod i wtaściwości, abu
umożliwić innym klasom
Oznacza posiadanie pracę tylko z częścią jego
interfejsu lub klasy,
wewnętrznych danych,
które dziedziczą po innym które s ą im potrzebne.
interfejsie lub klasie.

^ Dziedziczenie <-
J
Hermetyzacja

Stowo „polimorfizm" ^
oznacza „wiele postaci

% Abstrakcja
Czy m ożesz wyobrazić
sobie ta ki przypadek,
że obiekt przyjmuje
U żyw asz je j podcza s tw orzenia w \ g Ig p o s ta c i
modelu klas. Rozpoczynasz u; Twoim kodzie?
od klasy najbardziej Polimorfizm
ogólnej — abstrakcyjn ej —
a następnie tw orzysz klasy
bardziej szczegółowe
po niej dziedziczące.

366 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Polimorfizm oznacza, że jeden obiekt


może przyjmować wiele różnych postaci
Korzystasz
Za każdym razem, gdy wprowadzałeś przedrzeźniacza w miejsce zwierzęcia
lub używałeś dojrzałego sera cheddar w przepisie wymagającym sera, z polimorfizmu
stosowałeś polimorfizm. To jest właśnie to, co robisz podczas rzutowania
w górę i w dół. Mechanizm pobiera obiekt i używa go w metodzie lub
wtedy, gdy
instrukcji, która spodziewa się czegoś innego. pobierasz
W następnym przykładzie wypatruj polimorfizmu! instancję jednej
Wkrótce przystąpisz do wykonywania naprawdę dużego ćwiczenia klasy i używasz
— największego z tych, które widziałeś do tej pory — i będziesz w nim używał
polimorfizmu. Poniżej zaprezentowano listę typowych jego zastosowań.
jej w instrukcji
Do każdego z nich podaliśmy przykład (chociaż akurat tych konkretnych lub metodzie,
wierszy kodu nie ujrzysz w ćwiczeniu). Jak tylko zobaczysz coś podobnego
w kodzie, który będziesz pisał, zaznacz odpowiedni element listy.
która spodziewa
się innego typu,
na przykład
□ Zapisywanie w zmiennej referencyjnej pewnego typu instancji
innej klasy. klasy bazowej
NectarStinger bertha = new N ectarS tinger(); lub interfejsu,
IN ectarC ollector gatherer = bertha; który klasa
implementuje.
□ Rzutowanie w górę poprzez umieszczenie klasy potomnej w instrukcji
lub metodzie oczekującej klasy bazowej.

spot = new Dog(); A a h f .l l F e ° d A n A n > ™ l( ) o c z e k u j e

zooKeeper.FeedAnAnimal(spot); & T o t i y r 1' a °°9 ^ d z i c z y


fe e d A ^ L in wHwot^ i » m etody
o b ie k t^ o g P ^ z a S

□ Tworzenie zmiennej referencyjnej, której typem jest interfejs,


i przypisywanie do niej obiektu, który ten interfejs implementuje.

IS tingP atrol defender = new S tin g P a tro l(); - ś - To te ż j e s t rzutowanie w gorą!

I I Rzutowanie w dół za pomocą słowa kluczowego as. Me toda Mainta inTheHive() jako
parametr p r z y j m j _M o rker. Używ a
void MaintainTheHive(IWorker worker) { ona as do u sta w ienia referencji
worker na HiveM aintainer.
i f (worker is HiveMaintainer) {
HiveMaintainer m aintainer = worker as HiveMaintainer;

jesteś tutaj ► 367


Zaczynajmy

Długie ćwiczenie
Zbudujm y dom! Stwórz model domu, używając do tego celu klas
reprezentujących pokoje i lokalizacje oraz interfejsu dla każdego miejsca,
które posiada drzwi.

Rozpocznij od modelu klas.


Każdy pokój lub lokalizacja w Twoim domu będzie reprezentowana
przez oddzielny obiekt. Pomieszczenia wewnętrzne będą dziedziczyły po
klasie Room, natomiast miejsca znajdujące się na zewnątrz po Outside.
Obie z tych klas będą posiadały wspólną klasę bazową Location. Będzie
ona miała dwa pola: Name będzie nazwą lokalizacji (np. „Kuchnia”),
a E xits będzie tablicą obiektów Location, z którymi dana lokalizacja
ma połączenie. Wartość diningRoom.Name będzie więc równa
„Jadalnia”, natomiast diningRoom.Exits będzie równe tablicy
{ livingRoom, kitchen }.

^ U tw ó rz p r o je k t W in d o w s F o rm s A p p lic a tio n i d o d a j do niego


k la s y L o c a tio n , Room o ra z O u ts id e .

Potrzebujesz projektu domu. Lokalizacje wewnętrzne


Ten dom posiada trzy pokoje, podwórko przed i za nim i ogród. posiadają we wtaściwości
Ma dwoje drzwi wejściowych: frontowe łączą duży pokój z podwórkiem tylko do odczytu ja k iś
rodzaj dekoracji.
przed domem, a tylne kuchnię z podwórkiem za nim.
Na zew nątrz może być
gorąco, więc k.lasa
Outsid e posiada logiczną
Sa/on łączy wtaściw ość o nazw ie hot.
s ię z jadahnią,
która z koiei
ma połączerne
z kuchnią.
Moż e s z poruszać się
bezpośrednio pomiędzy
podwórkiem przed
domem i podwórkiem
za nim. Oba te
m iejsca potączone są
z ogrodem.

Ten sym bol oznacza w ejściowe drzw


pomiędzy podwórkiem przed domem
i dużym pokoje m. Istn ieją także W szystkie pokoje posiadają drzwi,
drzwi wejściow e m iędzy kuchnią ale tylko niektóre z nich mają
i tymym podwórkiem. drzwi wejściow e, które prowadzą
do domu lub na zew nątrz.

Użyj interfejsu IHasExteriorDoor dla pokoi z drzwiami wejściowymi. \


W domu znajduje się para drzwi wejściowych: frontowe i tylne. Każda lokalizacja
posiadająca jedne z nich (podwórko przed domem, podwórko za domem, duży pokój
oraz kuchnia) powinna implementować interfejs IHasExteriorDoor. Właściwość tylko
do odczytu DoorDescription zawiera opis drzwi (dla frontowych ma on postać „drzwi
dębowe z mosiężną klamką”, dla tylnych „drzwi zasuwane”). Właściwość DoorLocation
zawiera referencję do obiektu Location, który określa, dokąd drzwi prowadzą (kitchen).

368 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Tak wygląda klasa L o c a tio n .


Zaprezentowana tu klasa Location ułatwi Ci start:
a b stract c la ss Location { Konstruktor ustaw ia pole name, które
j e s t po lem wewn^ trznym właściwości
public Lo catio n (strin g name) { tylko d° odczytu Name.
Description j e s t Name = name;
m etodą wirtualną. }
Będz>es z m usiał ją Publiczne pole E xits j e s t tablicą referencji
przesłonić. public Location[] E x it s ; Locat ion p rzechowującą listę innych m iejsc,
z który mi istn ieje po łączenie z danej lokalizacji.
public strin g Name { get; p riv a te s e t; )
Klasa Room
public v irtu a l strin g Description { przesłania
i rozszerza
get { Description,
s trin g description = "S to isz w: Name aby dodać
dekoracje. Klasa
W łaściwość Description + Widzisz w y jścia do następujących lo k a liz a c ji:
O utside dodaje
zw raca fańcuch znaków fo r (in t i = 0; i < E x its.L e n g th ; i++) { tem peraturę.
opisujący pokój
i zaw ierający jego nazwą description += 11 11 + E x its[i].N a m e ;
oraz listę wszystkich i f (i != Exits.Len g th - 1)
lokalizacji, z którymi s ią
on fączy (i które można d escription += K , Pam iętaj, ^Location j e s t klasą
odnaleźć w polu ExitsJ. } abstrakcyjną — m ożesz po niej
Je j klasy pochodne d escription += dziedziczy ć' i huorzyć referencje
m uszą tylko nieznacznie typu Location, ale nie m ożesz
zmienić opis, będą zatem return d e scrip tio n ; tw orzyć je j instancji.
przeciągać tę metodę.
}
}

^ Utwórz klasy.
Na początku utwórz klasy Room oraz Outside na podstawie modelu klas. Następnie dodaj dwie kolejne:
OutsideWithDoor, która dziedziczy po Outside i implementuje IHasExteriorDoor, oraz RoomWithDoor,
która jest klasą pochodną Room i także implementuje IHasExteriorDoor.

Aby nieco Ci pomóc w tym zadaniu, napisaliśmy deklaracje klas:


Zacznij od takich klas. Więcej
c la ss OutsideWithDoor : Outside, IHasExteriorDoor ^ s zczegótów na id h te mat podamy
{ na następnej stronie.

/ / T u ta j z n a jd z i e s i ę w ła ściw o ść t y l k o do odczytu DoorLocation


Zanosi się na naprawdę
/ / T u ta j z n a jd z i e s i ę w ła ściw o ść D o o rD escrip tio n
duże ćwiczenie...
} ale obiecujemy,
że będzie to fajna
c la ss RoomWithDoor : Room, IHasExteriorDoor
zabawa! Po jego
{ ukończeniu definitywnie
/ / T u ta j z n a jd z i e s i ę w ła ściw o ść t y l k o do odczytu DoorLocation
poznasz wszystkie
/ / T u ta j z n a jd z i e s i ę w ła ściw o ść D o o rD escrip tio n
opisywane tu techniki.
}

- - - - - - - - - - - - - ►Jeszcze nie skończyliśmy — przewróć stronę!

jesteś tutaj ► 369


Obserwuj swoje obiekty podczas pracy!

Długie ćwiczenie — ciąg dalszy


Teraz, gdy już masz model klas, możesz utworzyć obiekty dla wszystkich
części domu, a następnie dodać formularz w celu ich zbadania.

W jaki sposób działają obiekty Twojego domu.


Poniżej zap rezen to w an o arch ite k tu rę dw óch obiektów : fro n tY a rd o ra z livingRoom . W związku z tym,
że każdy z nich m a drzwi, m uszą o n e być instancjam i klas im plem entującym i interfejs IH asExteriorD oor .
W łaściw ość DoorLocation przechow uje referen cję do lokalizacji znajdującej się p o drugiej stro n ie drzwi.
frontYard to obiekt
OutsideWithDoor IMngRoom j e s t instancją
RoomWithDoor, która dziedziczy

garden , . DacKTara \ będący klasą potomną


O utside im plementującą
IHasExteriorDooir.
po^ klasie Room i implem entuje
iHdsExteriorDoor.
diningRoom

Qs>—

frontYard
F^r
>v— 1 , —
efa Q ü ^ /e ^ R 0 o ^
DoorLocation
Rozpocząteś od utworzenia interfejsu O
Exits[] IH asExteriorDoor i dodania tych dwóch k|as, które Exits[] J
go implementują. Jedna z nich dz ied ziczy po Room E xits j e s t tab/icą
druga j e s t klasą potomną Outs id e . N adszedt czas referencji Location.
na ich dokończenie. /ivingRoom ma jedno
w yjście, więc je go
Dokończ budowanie klas i utwórz ich instancje. tab/ica Exits będzie
miała długość 1.
M asz już wszystkie klasy. N ad szed ł czas n a ich d okończenie i utw o rzen ie obiektów .
★ B ędziesz m usiał zadbać o k o n stru k to r dla klasy O utside , k tóry będzie ustaw iał w łaściwość tylko
do odczytu hot i przesłan iał w łaściwość D e s c rip tio n , aby d odać te k st „T utaj jest b ard zo g o rąco .”,
gdy hot będzie m iała w artość tru e . G o rąco je st n a p o d w ó rk u za dom em . N ie je st n a to m ia st gorąco
na p o d w ó rk u p rz ed nim i w ogrodzie.
★ K o n stru k to r klasy Room m usi ustaw ić p o le Décoration i pow inien p rzesłonić w łaściwość D e s c rip tio n ,
aby d odać „W idzisz tu taj (dekoracje)’’. W salonie znajduje się antyczny dywan, w jad aln i kryształowy
żyrandol, a w kuchni m o żn a zobaczyć nierdzew ne stalow e sztućce i rozsuw ane drzwi prow adzące
w prost n a p o dw órko za dom em .
★ F o rm u larz m usi utw orzyć wszystkie te o biekty i przechow yw ać referen cję do każdego z nich.
D odaj w ięc do niego m e to d ę C reateO bjects() i wywołaj ją w jego k o n stru k to rze.
E xits j e s t Każda
tab/icą ★ Stw órz in stancje obiektów dla każdej z sześciu lokalizacji dom u. T a k będzie w yglądał lokalizacja
referencji je d e n z tych wierszy: ma sw oje
do obiektów własne pole
Location. Ten RoomWithDoor livingRoom = new RoomWithDoor("Salon", w klasie
w iersz tw orzy formularza.
"antyczny dywan", "dębowe drzwi z mosiężną klamką");
taką tab/icę
i um ieszcza ★ T w oja m e to d a C reateO bjects() pow in n a w ypełnić p o le E x its [] dla każdego obiektu:
w niej dwa
To s ą naw iasy klamrowe. W szystko
e/em enty. fron tY ard.E xits = new Location[] { backYard, garden inne spowoduje pow stanie błędu.

370 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Stwórz formularz do zbadania domu.


Stwórz prosty formularz, który pozwoli Ci zgłębić tajemnice domu. Będzie on posiadał wielowierszową
kontrolkę TextBox o nazwie descrip tion do pokazywania opisu bieżącego pomieszczenia. Kontrolka
ComboBox o nazwie e x it s wyświetli wszystkie możliwości opuszczenia danego pomieszczenia. Oprócz tego
na formularzu umieścimy dwa przyciski: goHere będzie przechodził do pomieszczenia wskazanego przez
kontrolkę ComboBox, natomiast drugi przycisk, goThroughTheDoor, będzie dostępny tylko wtedy, gdy będą
istniały drzwi wejściowe.
To tu ta j u stalisz
sposób wypełniania
kontrolki ComboBox-

Zbadaj dom
ComboBox zaw iera listę
To j e s t wielowierszową kontrolka w szystkich wyjść, więc
TextBox, która wyśw ietla wartość Przyjm u je nazwę e x its.
m etody D escription() dla bieżącej Upewnij s ię, że je j
lokalizacji. Nazwaliśmy ją właściwość DropDownStyle
Kliknij przycisk goHere, description. j e s t ustawiona na
aby przenieść s ię DropDownList.
do innej lokalizacji. /

Idź tutaj: To j e s t ComboBox.


i Ten przycisk pojawia się
tylko wtedy, gdy z n a j d u j e
s ię w pom ieszczeniu
■z drzwiami w ejśc i° wy m i. ^
Przejdź przez drzwi
« M ożesz spraw ić, że będzie
on widoczny lub nie, poprzez
u staw ienie w taściw° ści
Teraz musisz tylko sprawić, aby formularz zaczął działać! V isible odpowiednio na frtue
a lub false. Pole ma na.zw ę
Posiadasz już wszystkie elementy. Musisz je tylko poskładać w całość. goThroughTheDoor.

★ W klasie formularza będziesz potrzebował właściwości o nazwie currentLocation


służącej do przechowywania informacji o bieżącym położeniu.
★ Dodaj metodę MoveToANewLocation(), która jako parametr przyjmuje obiekt Location. Powinna
ona najpierw ustawić pole currentLocation na nową lokalizację. W dalszej kolejności musi wyczyścić
zawartość kontrolki ComboBox, korzystając przy tym z jej metody Item s.C lear(), a następnie dodać
nazwę każdego pomieszczenia z tablicy E x its [], używając do tego metody Items.Add() kontrolki listy.
Na końcu zaktualizuj stan kontrolki ComboBox, aby wyświetlała pierwszy element na liście, ustawiając
właściwość Selectedlndex na zero.
Ustaw pole tekstowe tak, aby posiadało opis bieżącej lokalizacji.
Użyj słowa kluczowego i s do sprawdzenia, czy bieżąca lokalizacja posiada drzwi. Jeśli tak, spraw,
aby przycisk Przejdź przez drzwi stał się widoczny, używając do tego właściwości Visible. Jeśli nie,
zadbaj o to, aby pozostał ukryty.
Jeżeli przycisk Id ź tutaj: zostanie naciśnięty, przemieść się do miejsca wybranego w kontrolce ComboBox.
Gdy zostanie kliknięty przycisk Przejdź przez drzwi, idź do miejsca, które jest za drzwiami.
Kolejna p ^ p ^ i ^ ż - Pole currentLocation klasy formularza j e s t

t
Podpowiedz: Gdy w ybierzesz elem ent referencją UMMfim. A za te m, pomimo tego, ż e w skazuje ono na
na liście, jego indeks będzie s<ę . o biekt imp lem ent ujący I HasExte riorDoor, nie m ożesz po prostu wpisa ć
pokrywał z indeksem °ćlpow<edntej "curren c.ation.P oorLocation'', ponieważ DoorLocation nie j e s t składową
lokalizacji w tablicy Exitsll. klaf y Location. B ęd zie sz m u sia ł użyć rzutowania w dół, aby wydobyć
z obiektu m iejsce, do którego można p rzejść przez drzw i.

jesteś tutaj ► 371


Rozwiązanie ćwiczenia

Długie ćwiczenie
— rozwiązanie
Tak będzie wyglądał kod tworzący model domu. Użyliśmy klas do reprezentowania pokoi
i lokalizacji oraz interfejsu dla każdego miejsca, które posiada drzwi.

in te r fa c e IH a s E x te rio rD o o r j
To je s t interfejs IHasExteriorDoor.
s t r in g D o o rD e s c rip tio n j g e t; J
L o c a tio n D o orL ocatio n j g e t; s e t ; J
J

c la s s Room i L o c a tio n j
p r iv a t e s t r in g d e c o ra tio n ; Klas a Room dz iedziczy z Location
i dodaje wewnętrzne pole dla
p u b lic R o o m (strin g name, s t r in g d e c o ra tio n ) w łaściw oś c i tylko do odczytu
Decora tion. J e j i n s t r u k t o r j e u sta w ia.
i base(name) j
t h is . d e c o r a tio n = d e c o ra tio n ;
J

p u b lic o v e rr id e s t r in g D e s c rip tio n {


get {
r e tu r n b a s e .D e s c rip tio n + " W id zisz t u t a j " + d e c o ra tio n +
J
J
J

c la s s RoomWithDoor i Room, IH a s E x te rio rD o o r j


p u b lic R oom W ithD o or(strin g name, s t r in g d e c o ra tio n , s t r in g d o o rD e s c rip tio n )
i base(name, d e c o ra tio n )
j
D o o rD e s c rip tio n = d o o rD e s c rip tio n ;
J

p u b lic s t r in g D o o rD e s c rip tio n j g e t; p r iv a t e s e t; J Klasa RoomWithDoor dziedziczy po klasie


Room i im p le m e n t j IH a s E x te r<or P oor .
Robi w szystko to, co z wykiy aie
p u b lic L o c a tio n D o orL ocatio n j g e t; s e t ; J oprócz tego dodaje w konstru k torze ^
op is drzwi w ejściow ych. Dodaje także
DoorLocation, referencję
do której prowadzą dane dirzw . _
DoorDescription oraz DoorLocation s ą
wymagane przez IH a sExteriorD oor.
Zastosowałeś może pola wewnętrzne
zamiast właściwości automatycznych?
Także to rozwiązanie jest całkowicie
poprawne.

372 Rozdział 7.
Interfejsy i klasy abstrakcyjne

c la s s O u tsid e : L o c a tio n {
p r iv a t e bool h o t;
Klasa O utside j e s t podobna_do
p u b lic O u ts id e ( s tr in g name, bool ho t) Room — dziedziczy po klasie
Lo cation i dodaje wewnętrzne
: base(name) pole dla w taściw ości Hot, która
{ j e s t w ykorzystywana w metodzie
D escription (), rozszerzonej
t h is . h o t = h o t;
w s t o s unku do klasy bazow ej.
}

p u b lic o v e r rid e s t r in g D e s c r ip tio n {


get {
s t r in g N e w D e scrip tion = b a s e .D e s c rip tio n ;
if (h o t)
N e w D e scrip tion += " T u ta j j e s t bardzo g o rą c o ." ;
r e tu r n N e w D e scrip tio n ;
}
}
}

c la s s O utsid eW ithD o or : O u ts id e , IH a s E x te rio rD o o r {


p u b lic O u ts id e W ith D o o r(s trin g name, bool h o t, s t r in g d o o rD e s c rip tio n )
: base(name, h o t)
{
Ou t s i<deWithDoor (dziedziczy po O utside
t h is .D o o r D e s c r ip tio n = d o o rD e s c rip tio n ;
i m phm tm tuje IH asExteriorD oor
}
W! i y ą R j m w o £ L e ja k w p ^ “
p u b lic s t r in g D o o rD e s c rip tio n { g e t; p r iv a t e s e t ; }
W łaściw ość D escription klasy bazowej
zaw iera informację, czy w danej
p u b lic L o c a tio n D o orL ocatio n { g e t; s e t; } lokalizacji j e s t gorąco, cxy n ie .
W ykorzystuje ona w łaściw ość De sc rip tion
sw o jej klasy bazowej (Lo c a tion), Irtóira
p u b lic o v e r rid e s t r in g D e s c r ip tio n { dodaje podstawowy o p is i w yjśda -
get {
r e tu r n b a s e .D e s c rip tio n + " W id zisz te ra z " + D o o rD e s c rip tio n +
}
}

►Jeszcze nie skończyliśmy — przewróć stronę!

jesteś tutaj ► 373


Rozwiązanie ćwiczenia

Długie ćwiczenie
— rozwiązanie (ciąg dalszy)
Tak wygląda kod formularza. Wszystko znajduje się w pliku Form1.cs, wewnątrz deklaracji Form l.

To w ten sposób forrwuhrz


p u b lic p a r t i a l c la s s Forml : Form przechowuje informację o tym,
Pole E x its jest publiczne
{ które pom ieszczenie je s t
a ktualnie w yśw ietlane. i zawiera tablicę obiektów
L o c a tio n c u rre n tL o c a tio n ;
L o ca tio n . To nie jest najlepszy
przykład hermetyzacji! Inny
RoomWithDoor livin g R o o m ; obiekt bez trudu mógłby
Room diningRoom; zmodyfikować zawartość tej
Używa on tych zmiennych
RoomWithDoor k itc h e n ; referencyjnych do tablicy. W następnym rozdziale
przechowywania w szy stk ich poznasz znacznie lepszy sposób
O utsideW ithD oor fro n tY a rd ; pom ieszczeń domu. zapewniania dostępu do
O utsideW ithD oor backYard; sekwencji łańcuchów znaków
O u tsid e garden; lub obiektów.

p u b lic Form1() { Konstruktor formularza tworzy


In itia liz e C o m p o n e n t( ) ; obiekty, a następnie używa
metody M oveToANewLocation.
C re a te O b je c ts ();
M oveToAN ew Location(livingR oom ); Kied y formularz tworzy
obiekty, mus i najpierw
} utworzyć instancje klas
i przekazać w łaściw e
p r iv a t e v o id C re a te O b je c ts () { inform acje do konstruktora
każdej z nich.
livin g R o o m = new Room W ithD oor("Salon", "a n ty c z n y dywan",
"dębowe d rzw i z mosiężną k la m k ą ");
diningRoom = new R o o m ("J a d a ln ia ", " k ry s z ta ło w y ż y r a n d o l" ) ;
k itc h e n = new R oom W ithD oor("Kuchnia", "n ierd zew ne s ta lo w e s z tu ć c e ", "rozsuwane d rz w i"

fro n tY a rd = new O utsideW ithD oor("P odw órko przed domem" f a ls e , "dębowe d rzw i z m osiężną klam ką")
backYard = new O utsideW ithD oor("P odw órko za domem", tr u e "rozsuwane d r z w i" ) ;
garden = new 0 u ts id e ( " 0 g r ó d " , f a ls e ) ;
To tu taj przekazuj emy
o p isy drzwi do
d in in g R o o m .E x its = new L o c a tio n [] { liv in g R o o m , k itc h e n konstruktorów
OutsideW ithDoo r.
liv in g R o o m .E x its = new L o c a tio n [] { diningRoom } ;
k it c h e n . E x it s = new L o c a tio n [] { diningRoom };
fr o n t Y a r d . E x it s = new L o c a tio n [] { backYard, garden } Tutaj wypełniana j e s t tablica
E xits[] dla każdej instancj i .
b a c k Y a rd .E x its = new L o c a tio n [] { fro n tY a rd , garden }
M usim y poczekać do m om en t,
g a rd e n .E x its = new L o c a tio n [] { backYard, fro n tY a rd } gdy w sz y stk ie instancje zostaną
utworzone — w przeciwmyrn
razie nie mielibyśm y czego
liv in g R o o m .D o o rL o c a tio n = fro n tY a rd ; u m ieścić w każdej z tworzonych
fro n tY a rd .D o o rL o c a tio n = livin g R o o m ; tablic!
Dla obiektów
k itc h e n .D o o rL o c a tio n = backYard; IH asExteriorD oor
musim y u sta w ić
b a ckY a rd .D o o rL o ca tio n = k itc h e n ; potoż enie drzw i.

374 Rozdział 7.
Interfejsy i klasy abstrakcyjne

p r iv a t e v o id M oveToANewLocation(Location new Location) {


< - )
c u rre n tL o c a tio n = ne w L ocatio n;
M etoda M °veToAN ewLocation()
wy ś w ie tla na formularzu nową
e x it s . I t e m s . C le a r ( ) ; lokalizację.
fo r ( in t i = 0; i < c u r r e n tL o c a tio n .E x its .L e n g th ; i+ +
N ajpierw musimy wyczy ś cić zaw artość
e x its .Ite m s .A d d ( c u r r e n tL o c a tio n .E x its [i].N a m e ) ; lis ty wyboru, a następnie dodtó
e x its .S e le c te d ln d e x = 0; do niej nazwy w szystkich lokalizacji.
Na końcu ustawiam y indeks wy branego
elementu na zer° . w ten s p ° s ób
d e s c r ip tio n .T e x t = c u r r e n tL o c a tio n .D e s c r ip tio n ; w yśw ietlany będzie ptetwazy ej em ent
lis ty Nie zapomnij usta w ić wta ściw ości
DropDownStyle kontrolki C ° mboB °x
if (c u rre n tL o c a tio n is IH a s E x te rio rD o o r)
na JD ro p D o w n Lisf — dzię ki te mu
go T h ro u g h T h e D o o r.V isib le = t r u e ; użytkownik nie będz ie mógt niczego
e ls e w niej wpis a ć ■

t
g o T h ro u g h T h e D o o r.V isib le = f a ls e ;
} To spraw ia, że p rzycisk „Przejdź przez drzwi" sta je s i ę _niewidoczny,
jeżeli
je ż e li hieżaca
bieżąca lokalizacja nie imp i m plem
le m e n t m terfe
entuje t e r f ^jsuu W a sEx^ to rO ooir.

p r iv a t e v o id g o H e re _ C lic k (o b je c t sen de r, EventArgs e) {


M o v e T o A N e w L o c a tio n (c u rre n tL o c a tio n .E x its [e x its .S e le c te d In d e x ]); Kiedy użytkownik kliknie
przycisk „Idź tu ta j. ,
}
zostanie przeniesiony
do lokalizacji wybranej
p r iv a t e v o id g o T h ro u g h T h e D o o r_C lick(o b je ct se n d e r, EventArgs e) { w kontrolce ComboBox.
IH a s E x te rio rD o o r hasDoor = c u rre n tL o c a tio n as IH a s E x te rio rD o o r;
M oveToA N ew Location(hasD oor.D oorLocation);
}

M usim y użyć stowa kluczowe9o as


w celu dokonania rzutowania w dot
currentLocation na W a sE x te riorDoor■
Uzyskam y w ten s p o sót> dostęp
do pola DoorLocation.

%
Jeszcze nie skończyliśmy!
Fajnie było stworzyć m od el dom u, ale czy nie byłoby jeszcze le pie j, gdybyśmy zam ien ili
go w grę? Z ró b m y to! Zagrasz w chowanego przeciw ko ko m p u te ro w i. Będziesz m usiał
dodać klasę Opponent i zapewnić je j m ożliw ość ukrycia się w jednym z pomieszczeń.
M u sim y stworzyć znacznie większy dom. Będziem y także po trze bo w ali m iejsc do ukrycia!
D o da m y nowy in te rfe js, dzięki czemu n ie któ re p o koje będą m ia ły kryjó w ki. N a końcu
zm od yfikujem y fo rm u la rz, aby u m o ż liw ia ł ich sprawdzanie i przechowywanie danych
na tem at liczby ruchów w ykonanych podczas p ró b odszukania przeciw nika. B rzm i
zachęcająco? Oczywiście!
-►Zaczynajm y!

jesteś tutaj ► 375


Stwórz swojego przeciw nika Przed Tobą największe ja k dotąd wyzwanie w tej książce. Uważnie przeczytaj
opis zadania. Zaglądania do rozwiązania nie uznamy za oszukiw anie!

Czas na zabaw ę w chowanego! Rozbuduj oryginalny program, dodając większą


liczbę pokoi, kryjówki oraz przeciwnika, który będzie się przed Tobą chował.

U twórz nowy projekt i użyj opc ji Add E * jsting Item


UtwÓru d o Z i a klas z pierw szej częś c ć w iczenia.
Dodaj interfejs IH id in g P la c e . Tym razem nie
N ie m usim y tu ta j ro b ić żadnych cudów. Każda klasa p o to m n a L o c a tio n im plem entująca przedstawiliśmy
IH id in g P la c e posiada miejsce, gdzie p rze ciw n ik może się ukryć. Potrzebny jest tylko diagramu klas, więc
łańcuch znaków do przechow yw ania nazwy k ry jó w k i („w szafie ściennej” , „p o d łó żkie m ” itp.). musisz narysować
go sam. U łatw i Ci
In te rfe js m a deklarow ać akcesor g e t, lecz nie m a m ieć akcesora s e t — po le będzie ustawiane
to zrozumienie
w kon stru ktorze, a po ustaw ieniu k ry jó w k i w danym pom ieszczeniu m ożliwość programu, który
je j zm iany nie będzie potrzebna. masz napisać.

^ Dodaj klasy implementujące I H id in g P la c e .


Będziesz po trze bo w ał dwóch nowych klas: O u ts id e W ith H id in g P la c e (dziedziczącej po O u ts id e ) oraz
R o o m W ith H id in g P la ce (dziedziczącej po Room). D o d a tko w o spraw, aby każde pom ieszczenie z drzw iam i posiadało
kryjów kę. W ten sposób zamiast po Room będziesz dziedziczył po R o om W ithH idingP lace . Każde pom \e sz cze r i e
z drzwiami wej ścio w y mi
O Dodaj klasę reprezentującą Twojego przeciwnika. będzie zawierato kryjówkę:
O b ie kt O pponent znajdzie sobie losową kryjó w kę, a T w o im zadaniem będzie go odszukać. w kuchni

★ Będzie on potrze bo w ał pryw atnego po la typu L o c a tio n (m y L o c a tio n ), aby w iedział, gdzie jest.
Będzie także wyposażony w po le typu Random używane podczas przem ieszczania się w losowe miejsce.
★ K o n s tru k to r po b ie ra lokalizację początkow ą i przypisuje je j w artość do po la m y L o c a tio n , a także zapisuje
w p o lu random nową instancję klasy Random. R ozpoczyna od po d w ó rka przed dom em (ustaw im y to w klasie
fo rm u la rza ), a następnie przechodzi losowo pom iędzy m iejscam i z kryjów ką. N a początku gry przemieszcza
się 10 razy. K ie d y napotyka drzw i wejściowe, rzuca m onetą. N a podstaw ie w yn iku określa, czy przez nie
przejść, czy nie.
★ D o da j m etodę M o ve (), k tó ra przemieszcza prze ciw n ika z bieżącej lo ka liza cji do in ne j. Po pierwsze, jeżeli
znajduje się on w p o k o ju z drzw iam i, rzuca m onetą. G dy ra n d o m .N e x t(2 ) jest rów ne 1, przechodzi
przez drzw i; je śli nie, zostaje w miejscu. W ybie ra w ted y losowe wyjście z aktualnej lo ka liza cji i przez
nie przechodzi. Jeżeli pom ieszczenie nie m a żadnej kryjó w ki, cała pro ced ura jest pow tarzana — T w ój
przeciw nik znów w ybiera losowe wyjście z aktualnej lo kalizacji i przechodzi przez nie. W ykonu je tę
czynność, aż znajdzie od po w ie dn ią kryjów kę.
★ D o da j m etodę C h e c k (), k tó ra przyjm uje ja k o p a ra m e tr lokalizację i zwraca t r u e , je że li p rze ciw n ik się
w niej ukryw a, lu b f a ls e — w przeciw nym razie.

Dodaj do domu więcej pomieszczeń.


Z m o d y fik u j m etodę C r e a te O b je c ts ( ) , aby dodać więcej pomieszczeń:

★ D o da j s ch o d y z drew nianą poręczą, k tó re połączą salon z k o r y t a r z e m n a g ó r z e . K o ry ta rz będzie posiadał


obrazek z psem oraz szafę ścienną, w któ re j będzie można się skryć.
★ K o ryta rz na górze łączy się z trzem a pom ieszczeniam i: d u ż ą s y p i a l n i ą z dużym łóżkiem , d r u g ą sy p i al n i ą
z m ałym łó żkiem oraz ł a z i e n k ą z um yw alką i toaletą. K toś m ógłby się skryć po d łóżkiem w każdej z sypialni
lu b pod prysznicem.
★ P odw órka przed dom em i za n im łączą się z d r o g ą d o j a z d o w ą , gdzie p rze ciw n ik może się u kryć w garażu.
D o d a tko w o m ożna się także schować w szopie mieszczącej się w o g r o d z i e .

376 Rozdział 7.
Pamiętaj, że każdy problem programistyczny można rozwiązać na wiele sposobów. Interfejsy i klasy abstrakcyjne
Jeśli Twoje rozwiązanie działa, lecz jest inne niż nasze — to świetnie!

Dobrze, czas zmodyfikować formularz.


Będziesz m usiał dodać do fo rm u la rza k ilk a przycisków. B ardziej zaw iły stanie się
sposób sterow ania ich widocznością w zależności od stanu gry.
Środkowy przycisk nazwano check.

\
Tego przycisku będzie sz uży wat
do sprawdzania kryjów ki w danym
pom ieszczeniu. B ę dzie on widocz ny
tylko w m iejscu , które j ą p° s ia da..
Gdy ąra uruchamiana j e s t po Gdy będzie pokazywrny, w taściwof ć
raz pierw szy, w yśw ietlany j e s t Text zmieniona z ° sta n ie z „
tylko przycisk X r y j sie!" Kiedu na stowo „Spraw dź" z dodaną nazwą
90 klikniesz, formularz w p o t ? m iejsca do ukrycia — dla pokoju
t a t o w y m odliczy do d ziesięciu z kryjówką pod t<5źkiem te k st
przycisku przyjm ie postać
M m e n fr l wy wota metodę
„Spraw dź pod tóżkiem“ .

[ ó) Spraw, aby przyciski działały.


D o fo rm u la rza należy dołączyć dwa dodatkow e przyciski.
Przejdź
z powrotem ★ Ś rodkow y przycisk sprawdza kryjó w kę w bieżącym pom ieszczeniu i jest dostępny ty lk o wtedy,
do rozdziału 2. gdy znajdujesz się w p o k o ju z m iejscem do ukrycia. A b y sprawdzić, czy jest w nim przeciw nik,
i przypomnij
so bie, ja k wyw oływ ana jest m etoda C h e c k (). Jeżeli go znajdziesz, gra zaczyna się od początku.
działają metody
Przycisk na dole pozw ala u ru cho m ić grę. O dlicza do dziesięciu, pokazuje w p o lu tekstow ym „1 ...” ,
DoEyentsO
i S le e p () — czeka 200 m iliseku nd , następnie pokazuje „2...” , potem „3...” i ta k dalej. Po każdej liczbie nakazuje
okażą s ię p rze ciw n iko w i przesunąć się, w yw ołując m etodę M o v e (). N a końcu w yśw ietla przez p ó ł sekundy
bardzo pomocne .
„G o to w y czy nie — nadchodzę!” . Po tym wszystkim gra się rozpoczyna.

17) Dodaj metody do przerysowania formularza oraz do rozpoczęcia nowe* gry.


D o d a j m etodę R edraw Form (), k tó ra będzie umieszczała od po w ie dn ią frazę w p o lu tekstow ym , sterowała
widocznością przycisków oraz ustaw iała o d po w ie dn i tekst na środkow ym z nich. N astępnie dodaj m etodę
R esetG am e(), k tó ra będzie urucham iana po odnalezieniu przeciw nika. P rzyw róci ona początkow y stan jego
ob ie ktu , dzięki czemu zacznie on znów od po dw órka przed dom em — u kryje się, je że li u żytko w n ik klik n ie
przycisk K ryj się!. M e to d a po w in na pozostaw ić fo rm u la rz pusty, z w yjątkiem p o la tekstow ego i przycisku Kryj się!,
któ re będą widoczne. Pole tekstowe po w in n o in fo rm ow a ć, gdzie odnalazłeś prze ciw n ika i ilu ruchów na to
potrzebowałeś.

Q Przechowuj Informację o liczbie wykonanych przez gracza posunięć.


U p e w n ij się, że pole tekstow e w yśw ietla in fo rm ację o tym , ile razy gracz spraw dził
kryjó w kę lu b przem ieścił się pom iędzy pom ieszczeniam i. K ie d y znajdziesz przeciw nika,
p o w in ie n w yśw ietlić się k o m u n ik a t MessageBox wyglądający następująco: „O dnalazłeś
m nie w X ruch ach !” .

^ Zadbaj o poprawny wygląd programu podczas pierwszego uruchomienia.


K ie d y urucham iasz program po raz pierwszy, pow inieneś zobaczyć puste po le tekstowe
i przycisk Kryj się!. Zabawa zaczyna się dopiero po jego naciśnięciu!

jesteś tutaj ► 377


Rozwiązanie ćwiczenia

Rozbuduj oryginalny program, dodając większą liczbę pokoi, kryjówki oraz przeciwnika,
który będzie się przed Tobą chował.

Rozwiązania
ćwiczeń ^ ---- - I Hidir,gp la ce- Posiada
ktik0 J P stn n 9 z akcesorem get,
in te r fa c e IH id in g P la c e { ^ ZWraca nazwę m ieJsca do “ krycia.
s t r in g HidingPlaceName { g e t; }
}

c la s s Room W ithHidingPlace : Room, IH id in g P la c e {


p u b lic R o o m W ith H id in g P la c e (s trin g name, s t r in g d e c o r a tio n , s t r in g hidingPlaceNam e)
: base(name, d e c o ra tio n )

{ M -j- n1 m u - j- m m Klasa RoomWithHidingPlace dziedziczy po


H id in gPlaceName = h id in g PlaceName; Room i im plementuje* IH idingPlace poprzez
} dodanie w ła ściw ości HidingPlaceName.
Konstruktor usta w ia Je j wewnę trzne po le.

p u b lic s t r in g HidingPlaceName { g e t; p r iv a t e s e t ; }
p u b lic o v e r r id e s t r in g D e s c rip tio n {
get {
r e tu r n b a s e .D e s c rip tio n + " Ktoś może ukrywać s ię " + HidingPlaceName +
}
}
}

c la s s RoomWithDoor : Room W ithH idingPlace, IH a s E x te rio rD o o r {


p u b lic R oom W ithD o or(strin g name, s t r in g d e c o ra tio n ,
s t r in g hidingPlaceN am e, s t r in g d o o rD e s c rip tio n )
: base(name, d e c o r a tio n , hidingPlaceNam e)

D o o rD e s c rip tio n = d o o rD e s c rip tio n ; ^ p onieważ zdecydowaliśm y,


} że każdy pokóJ z drzwiami
posiada także m iejsce do
u kry c ia, uży liśm y dziedziczenia
p u b lic s t r in g D o o rD e s c rip tio n { g e t; p r iv a t e s e t ; } po Ro ° mWithHidingPlace.
J edy ną zmianą w klasie
J e s t sposób ustaw iania
p u b lic L o c a tio n D o orL ocatio n { g e t; s e t; } pól. Konstruktor - pobie ra
} dodatkowo nazwę kryjówki
i prze kazuje j ą do konstruktora
RoomWithHidingPlace.

Będziesz także potrzebow ał klasy


OutsideW ithDoor, która będzie taka
sama ja k w programie „Zbadaj dom” .

378 Rozdział 7.
Interfejsy i klasy abstrakcyjne

c la s s O u ts id e W ith H id in g P la c e : O u ts id e , IH id in g P la c e j
p u b lic O u ts id e W ith H id in g P la c e (s trin g name, bool h o t, s t r in g hidingPlaceNam e)
: base(name, h o t)

HidingPlaceName = hidingPlaceN am e; ^ . _
I K lt s t OuTsldeW lThHldlrgPltce
I dziedziczy po O utside i implementuje
IH idingPlace, Ttk stm o jt k czyni To
p u b lic s t r in g HidingPlaceName j g e t; p r iv a t e s e t ; I RoomWiThHid ir gp lt c e.

p u b lic o v e r rid e s t r in g D e s c rip tio n {


get {
r e tu r n b a s e .D e s c rip tio n + " Ktoś może ukrywać s ię + HidingPlaceName +
I
I
Konstruktor klasy Opponent p M ^
I lokalizację początkową. Tworzy on
nową instancję klasy Random, której
używa do losowego poruszania s ię
c la s s Opponent j
między pom ieszczeniam i.
p r iv a t e Random random;
p r iv a t e L o c a tio n m yLo catio n;
p u b lic O pp one nt(Lo catio n s t a r tin g L o c a tio n ) j
m yLocation = s t a r t in g L o c a t io n ;
random = new Random(); M etoda M ove() w p ierw sze j ko/ejności
s prawdza za pomocą słowa k/uczowego is ,
I
czy dany pokój posiada drzwi — je ś/i tak,
p u b lic v o id Move() j przeciw nik ma 50% sza n s na p rz e jście przez
bool hidden = f a ls e ; n ie. N astępnie zm ierza do /osowej /oka/izacji.
Całą procedurę kontynu uje aż do odna/ezienia
w h ile (¡h id d e n ) j
m iejsca nadającego s ię na kryjówkę.
if (m yLocation is IH a s E x te rio rD o o r) j
IH a s E x te rio rD o o r lo c a tio n W ith D o o r =
m yLocation as IH a s E x te rio rD o o r;
if (ran do m .N ext(2) == 1)
Kluczowym elem entem
m yLocation = lo c a tio n W ith D o o r.D o o rL o c a tio n ; metody M oveO j e s t ta
I pętla while. Wykon.ywana
j e s t ona tak dtugo, dopóki
i n t rand = ra n d o m .N e x t(m y L o c a tio n .E x its .L e n g th );
zmienna hidden nie będzie
m yLocation = m y L o c a tio n .E x its [ra n d ]; równa tru e — a przyjm u je
if (m yLocation is IH id in g P la c e ) ona tę w artość z chwilą
odnalezienia odpowiedniego
hidden = t r u e ; mie jsc a na kryjówkę.
I
I
p u b lic bool C h e ck(L o ca tio n lo ca tio n T o C h e ck) j
if (lo ca tio n T o C h e ck != m yLocation) Metoda Check() porównuje lokalizację
przeciwnika z lokalizacją przekazaną
r e tu r n f a ls e ;
do niej w posta ci referencji Location.
e ls e Je ż e li je s t to ten sam obiekt, znaczy
r e tu r n t r u e ; to, że przeciwnik z o sta t odnaleziony!
I

-►Jeszcze nie skończyliśmy — przewróć stronę!

jesteś tutaj ► 3T9


Rozwiązanie ćwiczenia

Poniżej przedstawiliśmy cały kod


formularza. Żadnym zmianom nie uległy in t Moves;
jedynie metody g o H e r e _ C lic k () oraz
Rozwiązania g o T h ro u g h T h e D o o r_ C lic k (). L o c a tio n c u rr e n tL o c a tio n ;
ćwiczeń
— ciąg dalszy To s ą w sz y stk ie pola w k>a sie Form1.
RoomWithDoor livin g R o o m ;
Formularz używa ich do przech° w ;w wnla
informacji o l°kalizacjach, przeciw niku ora.z Room W ithHidingPlace diningRoom;
liczbie posunięć wy^ntm ydr przez gruczd. RoomWithDoor k itc h e n ;
Room s t a i r s ;
Konstruktor Form1 obiekty, u sta w ia przeciwnika
przx/wrac a grę do stan u początkowego. A b y komumikat Room W ithHidingPlace h a llw a y ;
o z iw y c ię t - ^ b y w y ^ e t/ a n y ty/ko po odnaiezieniu Room W ithHidingPlace bathroom ;
m e Z u p / J G p ^ o c z ę c i u ¡ry , Room W ithHidingPlace masterBedroom;
metoóy R e s e tGdme() param etr typu logicznego.
Room W ithHidingPlace secondBedroom;

p u b lic Form1() {
In itia liz e C o m p o n e n t( ) ; O utsideW ithD oor fro n tY a rd ;

C re a te O b je c ts (); O utsideW ithD oor backYard;

opponent = new O p p o n e n t(fro n tY a rd ); O u ts id e W ith H id in g P la c e garden;

R e setG am e(false); O u ts id e W ith H id in g P la c e d riv e w a y ;

}
Opponent opponent;
p r iv a t e v o id M oveToANewLocation(Location new Location) {
Moves++;
c u rre n tL o c a tio n = ne w L ocatio n;
RedrawForm();
}
Metodo M oveToAN ewLoctTion() u s T tlt nowe
p r iv a t e v o id RedrawForm() j położenie i przerysow uje formularz.
e x it s . I t e m s . C le a r ( ) ;
fo r ( in t i = 0; i < c u r r e n tL o c a tio n .E x its .L e n g th ; i+ + )
e x its .Ite m s .A d d ( c u r r e n tL o c a tio n .E x its [i].N a m e ) ;
e x its .S e le c te d In d e x = 0;
d e s c r ip tio n .T e x t = c u r r e n tL o c a tio n .D e s c r ip tio n + " \ r \ n ( r u c h numer + Moves + " ) " ;
if (c u rre n tL o c a tio n is IH id in g P la c e ) j
IH id in g P la c e h id in g P la c e = c u rre n tL o c a tio n as IH id in g P la c e ; P ^ rz ^ u je m y nazwy
kryjówki, bo na razie
ch e c k .T e x t = "Sprawdź " + h id in g P la ce .H id in g P la ce N a m e ; mamy jed y n ie obiekt
c h e c k .V is ib le = t r u e ; currentLocation, który
nie ma w łaściw ości
I
Hidingp/aceName. Możemy
e ls e w tym mie jsc u użyć as.
c h e c k .V is ib le = f a ls e ; Dzię ki rzutowaniu w dół
otrz; m am ; referencję do
if (c u rre n tL o c a tio n is IH a s E x te rio rD o o r) zm iennej IH idingPlace.
go T h ro u g h T h e D o o r.V isib le = t r u e ;
e ls e
go T h ro u g h T h e D o o r.V isib le = f a ls e ; RedrawFormO wypetnia rozwijaną listę , ustaw ia
teks t (dodają c liczbę posu nięć), a następnie
w yśw ietla lub ukrywa przyciski w zależności
od tego, czy pom ieszczenie posiada drzwi oraz
j akie ś m iejsce do ukrycia.

380 Rozdział T.
Interfejsy i klasy abstrakcyjne

E kstra — mogłeś dodać całe skrzydto domu,


w staw iając zaledw ie kilka w ierszy tekstu! To dlatego
p r iv a t e v o id C re a te O b je c ts () { hermety czne klasy i ohiekty s ą naprawdę u żyteczne.
livin g R o o m = new R oom W ithD oor("Salon", "a n ty c z n y dywan",
"w s z a fie ś c ie n n e j" , "dębowe d rzw i z m osiężną k la m k ą ");
diningRoom = new R o o m W ith H id in g P la c e ("J a d a ln ia ", "k ry s z ta ło w y ż y r a n d o l" ,
"w w y s o k ie j s z a f ie " ) ;
k itc h e n = new R oom W ithD oor("Kuchnia", "n ierd zew ne sta lo w e s z tu ć c e ",
"w s z a fc e ", "rozsuwane d r z w i" ) ;
s t a ir s = new Room("Schody", "d rew n ia na p o rę c z " );
h a llw a y = new R o o m W ith H id in g P la ce ("K o ryta rz na g ó rz e ", "O brazek z psem",
"w s z a fie ś c ie n n e j" ) ;
bathroom = new R o o m W ith H id in g P la ce ("Ł a zie n ka ", "umywalka i t o a le t a " ,
"pod p ry s z n ic e m ");
masterBedroom = new Room W ithHidingPlace("Duża s y p ia ln ia " , "duże łó ż k o " ,
"pod łó ż k ie m " );
secondBedroom = new R oom W ithH idingPlace("D ruga s y p ia ln ia " , "małe łó ż k o " ,
"pod łó ż k ie m " );

fro n tY a rd = new O utsideW ithD oor("P odw órko przed domem", f a ls e , " c ię ż k ie dębowe d r z w i" ) ;
backYard = new O utsideW ithD oor("P odw órko za domem", t r u e , "rozsuwane d r z w i" ) ;
garden = new O u ts id e W ith H id in g P la c e ("O g ró d ", f a ls e , "w s z o p ie " ) ;
d riv e w a y = new O u ts id e W ith H id in g P la c e ("D ro g a do ja zdow a", t r u e , "w g a ra ż u ");

d in in g R o o m .E x its = new L o c a tio n [] { liv in g R o o m , k itc h e n };


liv in g R o o m .E x its = new L o c a tio n [] { diningR oom , s t a ir s };
k itc h e n . E x it s = new L o c a tio n [] { diningRoom };
s t a ir s . E x it s = new L o c a tio n [] { liv in g R o o m , h a llw a y };
h a llw a y .E x its = new L o c a tio n [] { s t a i r s , bathroom , masterBedroom, secondBedroom } ;
b a th ro o m .E x its = new L o c a tio n [] { h a llw a y } ;
m aste rB e d ro o m .E xits = new L o c a tio n [] { h a llw a y } ;
secondB edroom .E xits = new L o c a tio n [] { h a llw a y } ;
fr o n t Y a r d . E x it s = new L o c a tio n [] { backYard, ga rde n, d riv e w a y } ;
b a c k Y a rd .E x its = new L o c a tio n [] { fro n tY a rd , ga rde n, d riv e w a y } ;
g a rd e n .E x its = new L o c a tio n [] { backYard, fro n tY a rd };
d riv e w a y .E x its = new L o c a tio n [] { backYard, fro n tY a rd } ;

liv in g R o o m .D o o rL o c a tio n = fro n tY a rd ;


fro n tY a rd .D o o rL o c a tio n = livin g R o o m ;

Nowa metoda C reateO bjects() tworzy


k itc h e n .D o o rL o c a tio n = backYard;
w szy stk ie obiekty potrzebne do zbudowania
backY ard.D oorLocation = k itc h e n ; domu. J e s t ona podobna do te j, którą
napisaliśm y w cześniej, ale posiada znacznie
}
w ięcej m iejsc, do których można s ię udać■

------------- ► W dalszym ciąyu nie skończyliśmy— przewróć stronę!

jesteś tutaj ► 381


Rozwiązanie ćwiczenia

Oto reszta kodu formularza. M etody ° b s ługują ce tchtrmęda


przycisków goHere oraz g°Thr°u gh TheD ° ° r s ą d °kładnie
takie sam e ja k w p ierw szej części ćw iczenia. A by je
Rozwiązania ćwiczeń zobaczyć, cofnij s ię ° kilka s tr°n.
— ciąg dalszy
p r iv a t e v o id ResetGame(bool displayM essage) {
if (displayM essage) {
M essageBox.Show("O dnalazłeś mnie w " + Moves + " r u c h a c h !" ) ;
IH id in g P la c e fo u n d L o c a tio n = c u rre n tL o c a tio n as IH id in g P la c e ;
d e s c r ip tio n .T e x t = "Z n a la z łe ś p rz e c iw n ik a w " + Moves
+ " ruch ach ! U kryw ał s ię " + fo u ndL oca tion.H idingP lace N am e
}
Moves = 0; M eto<
ća R e se tG ame () przywraca
początko wy s ta n gry. W yśw ietla ona
h id e . V is ib le = t r u e ; komunikat końc° wy i ukrywa w szystkie
g o H e re .V is ib le = f a ls e ; przycisk i z wy ją tk iem „Kryj s i ę 1“
c h e c k .V is ib le = f a ls e ;
g o T h ro u g h T h e D o o r.V isib le fa ls e ;
(Jhcemy wy św ie tlić nazwę kryjówki,
e x i t s . V is i b l e = f a ls e ; f e cu rre ntLocation j e s t referencją
} U ica ^ m , więc nie daje m ożliwości
d °s t ę pu d° _pola HidingPlaceName.
Na sz c z ę ś c ie możemy użyć słowa
p r iv a t e v o id c h e c k _ C lic k (o b je c t sen de r, EventArgs e) { kluczowego as w celu rzutowania
Moves++; w dół. Uzys k ujem y w ten sposób
referencj ę iH tóingPlace, która
if (o p p o n e n t.C h e c k (c u rre n tL o c a tio n )) w skazuje rui ten sam obiekt.
R esetG am e(true);
e ls e
RedrawForm();
Kiedy klikasz przycisk check,
} program spraw dza, czy
przeciwnik nie ukrywa s ię
p r iv a t e v o id h id e _ C lic k ( o b je c t sen de r, EventArgs e) { w danym pom ieszczeniu.
J e ś li tak, gra rozpoczynana
h id e . V is ib le = f a ls e ; je s t ° d początku. J e ś li nie,
odświeżana je s t zaw artość
formularza (aby zaktualizować
fo r ( in t i = 1; i <= 10; i+ + ) {
liczbę wykonanych posunię ć) .
o p p o n e n t.M o ve ();
d e s c r ip tio n .T e x t = i + " . . . "; C zy pamię tas z metodę D oEvents()
z progrnmu Coś błyskotliwego, który
A p p lic a tio n .D o E v e n ts (); nap isa liś my w rozdziale 2 .? Gdyby
S y s te m .T h re a d in g .T h re a d .S le e p (2 0 0 ); nie ona, pole tekstow e nie byłoby
odśw ieżane, a program sp ra w iałby
}
wrażenie, jakb y p rze sta ł działać.

d e s c r ip tio n .T e x t = "Gotowy czy n ie n a dch od zę!": P rzycisk hide j e s t tym, który uruchamia
A p p lic a tio n .D o E v e n ts ( ); grę. P ierw szą rzeczą, j a ką r ° b i, j est
S y s te m .T h re a d in g .T h re a d .S le e p (5 0 0 ); ukrycie sie b ie samego. N astępnie
odlicza on do d z ie się ciu i nakazuje
przeciwnikowi wykonać ruch. Na k°ńcu
g o H e re .V is ib le = t r u e ; pokazuje pierw szy p rzycisk oraz pole
wyboru i um ieszcza gracza w salonie.
e x i t s . V is i b l e = tru e ;
M etoda M °veT °A N ew Loca ti°n () w yw °łuje
M oveToAN ew Location(livingR oom ); RedrawF°rm ().

382 Rozdział 7.
Interfejsy i klasy abstrakcyjne

Zagadkowy basen. Rozwiązanie ćwiczenia ze s tro n y 365.


T w o im zadaniem jest po bra nie fragm entów ko d u z basenu i w staw ienie ich
w puste miejsca. Możesz użyć tego samego fragm e ntu więcej niż raz
i nie musisz wykorzystać ich wszystkich. Celem jest napisanie
zestawu klas, k tó re się skom pilują, będą działały i w yśw ietlą
przedstaw iony kom u nikat.

^ klasa A c t s wy wotu je konstruktor klasy


n ic a o ? A p°" dziedziczy. Przekazuje on a ido
g t”^ c.t s ' które j e s t potem przechowywane
we w taściw ości Face. ^ y wane

p u b licinterface Nose { p u b lic c la s s Acts : Picasso


int Ear() ; p u b lic A c ts () : b a s e ("A c ts ") { }
s t r in g Face { g e t; } p u b lic o v e rr id e int Ear() {

} r e tu r n 5;

}
p u b lic a b s tra c t c la s s Picasso : Nose }
p u b lic v i r t u a l i n t E a r() {
r e tu r n 7; publ ic c la s s Of76 : Clowns {

} p u b lic o v e r rid e s t r in g Face {

Wtaściwości g e t { r e tu r n "O f7 6 "; }


p u b lic P ic a s s o ( s tr in g fa ce ) { mogą pojawiać }
s ię w dowolnym
this.face = fa c e ; m iejscu klasy! p u b lic s t a t i c v o id M a in ( s t r in g [ ] a rg s) {
Ła tw iej czytać
} kod, je ż e li znajdują s t r in g r e s u lt = " " ;
p u b lic v i r t u a l s t r in g Face { s ię na górze, N ose[] i = new N o s e [3 ];
ale catkowicie
get { return face; } poprawne j e s t i[0 ] = new A c t s ( ) ;
um ieszczenie ich
} i[1 ] = new C lo w n s ();
w dolnej c z ę ś c i
s t r in g fa ce ; klasy Picasso. i[2 ] = new O f7 6 ();

} fo r ( in t x = 0 ; x < 3 ; x++)

{
p u b lic c la s s Clowns : Picasso { r e s u lt += ( i[x].Ear() + " "
p u b lic C low ns() : b a se("C lo w ns") { } +i[x].Face) + "\n " ;
} }
M essageB ox.S how (result)

Wynik } fa c e j e s t akcesorem get,


5 Acts
7 Clowns który zwraca w artość
7 Of76 pola face. Oha elem enty
zde f iniowane s ą w klasie
P icasso i dziedziczone
przez klasy pochodne.

jesteś tutaj ► 383


S84 Rozdział T.
8. Typy wyliczeniowe i kolekcje

Przechowywanie a
^ dużej ilości danych

Z deszczu pod rynnę. W rzeczywistym świecie nie musisz się zwykle zajmować danymi
w małych ilościach i niewielkich fragmentach. Nie, Twoje dane przychodzą do Ciebie w grupach,
stosach, pękach, kopach. Potrzebujesz jakiegoś potężnego narzędzia do ich zorganizowania.
Nadszedł czas, aby przedstawić kolekcje. Pozwalają one przechow yw ać i sortow ać dane,
a także zarządzać tymi z nich, które Twój program musi przeanalizować. Dzięki temu możesz
myśleć o pisaniu programów do pracy z danymi, a samo ich przechowywanie zostawić kolekcjom

to jest nowy rozdział ► 385


Rekiny pielęgniarki i m rów ki stolarze

Łańcuchy znaków nie zawsze sprawdzają się


przy kategoryzowaniu danych
Przypuśćmy, że masz k ilk a pszczół ro b o tn ic, wszystkie reprezentowane
przez o b ie kty W orker. W ja k i sposób napisałbyś k o n stru kto r, k tó ry jako
p a ram e tr przyjm ow a łb y rodzaj w ykonywanej pracy? Gdybyś użył typu
s t r i n g do określenia nazwy czynności, mógłbyś skończyć, posiadając kod
podobny do tego poniżej.

N asz kod pozwalałby przekazywać


Nasze oprogramowanie za^ " j ^ | dʓ 5 ""
te w artości do konstruktora, chociaż
p r°g ram umożliwia jed yn ie obsługę
Z Ż y ta m i" czy Z b ie ra c z nektaru . patrolu z żądłam i, zbierania nektaru
i mnych r n d ^ w pracy, które mogą
być wykonywane przez p szczoły.

Worker buzz = new Worker("Pełnomocnik generała");


Worker clover = new Worker("Opiekun do psów");
Worker gladys = new Worker("Prezenter wiadomości");

s
P « ..» » przylm
?
„ 30 rrjdzaju
M
<*» o - y
J

M ógłbyś pra w d opo do bn ie dodać do ko n stru kto ra kod, k tó ry sprawdzałby


każdy łańcuch znaków i określał, czy jest to pra w id ło w y rodzaj pracy, chociaż
gdybyś do da ł nowe zadanie, k tó re pszczoły m ogłyby wykonywać, musiałbyś
go zm ienić i po no w nie skom pilow ać klasę W orker. Jest to, ja k widać,
rozw iązanie krótkow zroczne. Co by było, gdybyś m ia ł inne klasy, które
m usiałyby sprawdzać typy ro b o tn ic w celu określenia, ja kie fun kcje mogą one
pełnić? W ta kim przypadku musiałbyś p o w ie lić ten sam kod, a jest to swego
rodzaju ślepa uliczka.

P otrzebujem y czegoś, co pow ie: „H e j, w tym m iejscu akceptujem y tylko


określone w a rto ści!” . P otrzebujem y m ożliw ości wyliczenia wartości,
których w danym kontekście m ożna użyć.

386 Rozdział 8.
Typy wyliczeniowe i kolekcje

Typy wyliczeniowe pozwalają Ci Pola znajdujące się wewnątrz


typu wyliczeniowego nazywamy
wyliczyć prawidłowe wartości listą wyliczeniową, a każde
z osobna je s t elementem
wyliczeniowym. Catość stanowi
Typ w yliczeniow y, enum, jest typem danych, k tó ry pozw ala użyć tylko określonych typ wyliczeniowy.
w artości. Możesz zdefiniow ać typ w yliczeniow y o nazwie J ob i określić w nim

zestaw dozw olo” ych


Większość ludzi mówi
na to wyliczenie.
public enum Job {
O statni elem ent W y z tych elementów
wyliczenia nie wymaga
obecności przecinka, NectarCollector, j e s t prawidłowym rodzajem
Pracy i m oże być J y ^
ale jego użycie jako w artość Job.
ufatw ia późniejsze
przestawianie pozycji StingPatrol,
za pomocą, w ytnij
i wklej. HiveMaintenance,
Oddziel każdą wartość
. przecinkiem i zakończ
BabyBeeTutoring, całość nawiasem
klamrowym.
EggCare,
HoneyManufacturing,

}
nazwa typu
T eraz możesz odw ołać się do tych w artości w następujący sposób:
Na końcu znajduje

/ s '<3 wartość z typu


wyliczeniowego,

Worker nanny = new Worker(Job.EggCare);


Z m ieniliśm y konstruktor Worker,
aby akceptował jako parametr
Jednak nie możesz po p ro stu w prow adzić nowej w artości typu wartość typ u Worker.Job.
w yliczeniowego. Jeśli ta k zrobisz, pro gra m od m ów i kom p ila cji.

private void b u tto n 1 _ C lic k (o b je c t sender, E v e n t A r g s e)

{
Wo r k e r b u z z = new W o r k e r ( J o b . A t t o r n e y G e n e r a l ) ;
Tu je s t htąd, który
zostanie wychwycony
przez kompilator.

O ^ ^ ^ V p ^ ^ | jc z * m o w e J o b ^ jo e 5 n o t c o n t a | n ^ d e f m r t i o n f o r ^ A t t o m e y G e n e r a r ^ ^

jesteś tutaj ► 387


Nazwy są lepsze niż liczby

Typy wyliczeniowe pozwalają


na reprezentowanie liczb za pomocą nazw
Czasami ła tw iej pracow ać z liczbam i, je że li m am y dla nich odpow iednie nazwy. D o kolejnych p ó l typu wyliczeniowego
możesz przypisać w artości liczbow e i odwoływać się do nich za pom ocą nazw. D z ię k i tem u nie będziesz posiadał ogrom nej
ilości nieokreślonych liczb poniewierających się po całym kodzie. Poniżej zaprezentowano typ w yliczeniow y do określania
liczby p u nktów za sztuczki w ykonywane na zawodach psów.
Możesz rzutować liczbę in t
To j e s t typ wy/iczeniowy
TrickScore. na typ wyliczeniow y; możesz
także rzutow ać wartości typu
public enum TrickScore { wyliczeniowego skojarzone
Elem enty te Sit = 7, z liczbam i na typ in t. ^
nie mu szą,być , gen = 25 Podaj nazwą, potem
utożone w żadnym J D C U a na kohcu liczbą, którą
ta nazwa reprezentuje. T ; p ; w ;/iczeniow e pozwalają także
porządku. Możesz
RollOver = 50, na kojarzenie w artości z liczbami
także przypisać Fetch = 10, mnych ty p fa , taftich ja k byte lub lonq,
różne nazwy do co pokaz a/iśm; w przykładzie u dołu 3
takich samych ComeHere = 5, s torony — również w tych przypadkach
m
w a rto ści. moż/iwe j e s t rzutowanie.
Speak = 30,
Rzutowanie (in t) nal^azuje ^ Ż enić
} tę w artość na odpow^drną liczbę . W związku
z tym, że elem ent T r ic k S c o r e .M ch pos lada
A o to fragm e nt m etody używającej typ u w yliczeniowego wartość 10, (in V T ric k S c o re .M c h z amienia go
T ric k S c o re . D e m on stru je on rzutow anie w artości tego typu na tę /iczbę.
na liczby typu i n t oraz i n t na T ric k S c o re .
Fetch ma w artość 10, zatem ta
int value = (int)TrickScore.Fetch * 3; in strukcja z a p isze w zmiennej
value w artość 30.
MessageBox.Show(value.ToString());
M ożesz rzutować warto ść int
TrickScore score = (TrickScore)value; z powrotem na typ Tri'c k S c ore .
Ponieważ w artość zm iennej
MessageBox.Show(score.ToString()); value wynosi 30, w zm iennej
score zostanie zapis am " a ^ ś ć
Możesz rzutow ać typ w yliczeniow y na liczbę i przeprowadzać na tej w artości obliczenia, T rick S co re.S p ea k . To_z kolei
ale możesz także użyć m etody T o S t r in g ( ) , aby p o tra ktow a ć nazwę ja k o łańcuch oznacza, że " y ^ ^ n ą
znaków. Jeśli do elem entów z listy nie przypiszesz żadnych liczb, otrzym ają one swoje sco re .T o S trin g () zwróci
w artość S p ea k .
domyślne w artości. Pierwszy otrzym a w artość 0, drugi 1 i ta k dalej.

Co się je dn ak stanie, gdy będziesz chciał użyć naprawdę dużych liczb dla jednego
z typów wyliczeniowych? D om yślnym typem w artości w yliczeniow ych jest i n t . W takim
przypadku musisz w ięc określić go jaw nie, używając do tego o p era to ra :, ta k ja k tutaj:

■1 ° ™ k a z u je kompilatorowi
public enum TrickScore : long { traktować w artości typu
Sit = 7, wyliczeniowego TrickScore
jako typ fong, nie j ako inf
Beg = 2500000000025
}
Gdybyś spróbował skompilować ten kod bez określenia typu jako long, otrzymałbyś taki oto komunikat:
Cannot i m p l i c i t l y c o n v e rt ty p e 'lo n g ' to 'in t '.

388 Rozdział 8.
Typy wyliczeniowe i kolekcje

Wykorzystaj całą swoją wiedzę o typach wyliczeniowych w celu utworzenia klasy przechowującej kartę do gry.

Ćwiczenia

UTW ÓRZ NO W Y PROJEKT I DODAJ KLASĘ CARD.


Będziesz potrze bo w ał dwóch p ó l publicznych: S u it (któ re będzie przyjm ow ało
w artości Spades, C lu b s, Diamonds i H e a rts ) oraz V a lu e (Ace, Two, T h re e ...
Ten, Jack, Queen, K in g). Przyda się także właściwość ty lk o do odczytu o nazwie Name
(Ace o f Spades, F iv e o f Diamonds).

UŻYJ DWÓCH TYPÓW W YLICZENIOW YCH


DO ZDEFINIOW ANIA KOLORÓW I WARTOŚCI KART.
Dodaj je, używając dobrze znanej o p c ji Add/Class. Pamiętaj, by w utworzonych plikach zm ienić słowo c la s s na enum.
U pew nij się, że ( in t) S u its .S p a d e s jest równe 0, a następna w kolejności jest wartość C lubs (równa 1), potem
Diamonds (2) oraz H e a rts (3). Spraw, aby wartości p ó l odpowiadały wartościom kart: ( in t) V a lu e s .A c e pow inno być
równe 1, Two ma się równać 2, Three 3 i tak dalej. Pole Jack pow inno być równe 11, Queen 12, a King 13.

DODAJ WŁAŚCIWOŚĆ DLA N A ZW Y KARTY.


Name pow inno być właściwością tylko do odczytu, a akcesor g e t ma zwracać łańcuch znaków, któ ry opisuje kartę.
K o d ten będzie urucham iany w klasie form ularza — pobierze wartość właściwości Name dla danej karty i w yśw ietli ją:

Card card = new C ard (Suits.Spades, V alues.A ce);


A by to zadziatato,
s trin g cardName = card.Name;
Twoja klasa potrzebuje
W artością cardName p o w in no być Ace o f Spades. konstruktora, który będzie
przyjm owat dwa param etry.
DODAJ DO FORMULARZA PRZYCISK,
KTÓRY BĘDZIE W YŚW IETLAŁ NAZW Ę LOSOWEJ KARTY.
Tw ój program może wyświetlać karty z przypadkowym kolorem i wartością. Wystarczy w tym celu zrzutować losową
wartość z przedziału 0 - 3 na S u its i drugą z zakresu 1 - 13 na V alues. A b y tego dokonać, możesz skorzystać
z wbudowanych możliwości klasy Random. U m o żliw ia ona wywołanie m etody N e x t() na trzy różne sposoby:

Kiedy istn ie je w ięcej niż Random random = new Random();


jeden sp°sób wywotania in t numberBetween0and3 = random.Next(4);
metody, nazywamy
to p o d ą ż a n ie m . W ięcej in t numberBetween1and13 = random.Next(1
(1 , 14);
na ten tem at wkrótce...
in t anyRandomInteger = random.Next();
T
■ Nie. istnieją. To nakazuje obiektowi klasy Random zwrócić wartość większą lub równą 1, ale mniejszą od 14.
głupie pytania

P : Zatrzymaj się na chwilę. Kiedy pisałem ten kod,


wszystkich przeciążonych metod. Strzałki w górę i w dół obok „3 of 3"
pozwalają Ci się przełączać pomiędzy tymi sugestiami. To naprawdę
to zauważyłem, że okno IntelliSense podczas wpisywania Random.
użyteczne narzędzie podczas pracy z metodami, które mają dziesiątki
Next() wyświetliło coś w stylu „3 of 3” . Co to jest?
przeciążonych definicji. Jeżeli z niego korzystasz, to upewnij się,
że wybrałeś właściwe przeciążenie metody N e x t()! Nie przejmuj się
O : Zobaczyłeś metodę, która była przeciążona. Gdy klasa
tym jednak za bardzo w tej chwili — powiemy sobie więcej na temat
posiada metodę, której możesz użyć na kilka sposobów, nazywamy
przeciążania w dalszej części niniejszego rozdziału.
to przeciążaniem. Podczas używania takiej klasy IDE wyświetla wszystkie
możliwości, dając Ci wybór W tym przypadku klasa Random posiadała u_Mext(T

i 3 of 3 ▼ injRandoim.Mextflnt m inValue int maxValue)


trzy różne metody N e x t (). Zaraz po wpisaniu „Next(" w oknie kodu IDE
Returns a random n um ber w ithin a specified range.
wyświetli okno IntelliSense, w którym można zobaczyć parametry dla minValue: The inclusive lower bound o f the random number returned.

jesteś tutaj ► 389


Tablice... kto by ich potrzebow ał?

Talia kart to doskonały przykład na to, że ograniczenie wartości jest bardzo ważne. Nie ma osoby,
która chciałaby zmienić swoje karty i zmierzyć się z Joker o f Clubs lub 13 of Hearts. Oto sposób,
Rozwiązania w jaki napisaliśmy klasę Card:
ćwiczeń
enum Suits {
Spades,
J e ś li nie o k reślisz w artości, to pierw szy
Clubs, elem ent nu nś d e j e s t równy 0, drugi 1
Diamonds, trzeci 2 i tak dalej. '
Hearts
}

enum Values {
Naszym typom wyliczeniowym nadaliśmy
Ace = 1, To tu taj ustaw iam y wart° ś ć nazwy w liczbie mnogiej — S u its oraz
Two = 2, V a lu es.A ce m 1 V alues — natomiast właściwościom
Three = 3, klasy Card, w których wartości tych
Four = 4, typ ó w są przechowywane, nadaliśmy
Five = 5, nazwy w liczbie pojedynczej: S u it
i Value. Jak sądzisz, dlaczego tak
Six = 6,
postąpiliśmy? Przyjrzyj się nazwom
Seven = 7, typ ó w wyliczeniowych stosowanych
Eight = 8, w innych przykładach w tej książce.
Nine = 9, Czy zastosowanie typó w S u it i Value
Ten = 10, byłoby lepszym rozwiązaniem?

Jack = 11,
Queen = 12,
King = 13
}

class Card {
public Suits Suit { get; set; }
public Values Value { get; set; }

public Card(Suits suit, Values value) {


Akcesor g e t w ła ściw ości Name ™ ż e skorzysta ć
this.Suit = suit; Z 1 , , w iaki dziata metoda ToStnngU
this.Value = value;
na w artość typu string.
}
public string Name { To tu ta j użyliśm y
get { return Value.ToStrin + " of " + Suit.ToString(); }
} To j e s t kod dla przy cisk u , który wygenerowania
losowej liczby, która
1 w yśw ietla nazwę losowej karty .
zostanie zrzutowana
na odpowiednią w artość
Random random = new Random(); typu wyliczeniowego.
private void button1_Click(object sender, EventArgs e) {
Card card = new Card((Suits)ra ndom.Next(4), (Values)random.Next(1, 14));
MessageBox.Show(card.Name);
}

390 Rozdział 8.
Typy wyliczeniowe i kolekcje

Możesz użyć tablicy, aby utworzyć talię kart...


Co zrobić, gdy zechcesz utw orzyć klasę reprezentującą ta lię kart? Będzie ona
potrzebow ała sposobu na przechowywanie każdej ka rty i będzie m usiała wiedzieć,
w ja k im p o rząd ku są one ułożone. D z ię k i użyciu tab licy Card m ożna by zastosować pewną
sztuczkę — k a rta znajdująca się na w ierzchu ta lii m ogłaby m ieć indeks 0, kolejn a indeks 1
i ta k dalej. Z aczniem y od tego — klasa Deck rozpoczyna z kom p le te m kart.

Deklaracja tej tablicu


będzie wygląda ta
class Deck { podobnie dla catej
talii A b y zaoszczędzić
private Card[] cards = { trochę m iejsca, nieco
new Card(Suits.Spades, Values.Ace), ją skróciliśmy.

new Card(Suits.Spades, Values.Two),


new Card(Suits.Spades, Values.Three),
// ...
new Card(Suits.Diamonds, Values.Queen),
new Card(Suits.Diamonds, Values.King),
};

public void PrintCards() {


for (int i = 0; i < cards.Length; i++)
Console.WriteLine(cards[i].Name);
}
}

. a l e co wtedy, gdy chcesz zrobić coś więcej?


Pom yśl o wszystkim , co mógłbyś kiedyś zrobić z talią. G dy grasz w karty,
dość często zmieniasz ich ułożenie. Często też dokładasz lu b usuwasz
pewną kartę z ta lii. Z e zwykłą tab licą nie będzie to takie łatwe.

m WYSIL ______________
ra SZARE KOMÓRKI
Jak dodać do klasy Deck metodę S h u f f l e ( ) , która przestawia karty całkowicie losowo?
A co z metodą, która pobierałaby z talii pierwszą kartę? W jaki sposób dodałbyś do niej
jedną z kart?

jesteś tutaj ► 391


Niezłe kolekcje

Z tablicami ciężko się pracuje


T ablica jest dobra w p rzypadku przechowywania stałej liczby w artości lu b referencji.
Jeśli je d n a k je j elem enty muszą być często przestawiane lub próbujesz dodać ich więcej,
niż tab lica jest w stanie pomieścić, to wszystko zaczyna się kom plikow ać.

fw Każda tab lica posiada swoją długość i aby z nią pracować, musisz tę długość znać.
Chcąc um ieścić w niej k ilk a elem entów pustych, możesz użyć re fe re n cji n u l l .

In d eksy 3 , 4, 5 i 6
s ą równe nuli, wiąc
nie przechowują
żadnych kart.
° 6 'e k t
Tablica ma długość 7, ale
przechow uje tylko 3 karty

M usisz przechowywać wartość, k tó ra określa, ile k a rt jest w danej c h w ili w użyciu. Potrzebujesz do tego
p o la typ u i n t — nazwiem y je to p C a rd . Będzie ono określało indeks ostatniej ka rty w tablicy.
Nasza tab lica trzech k a rt m ogłaby m ieć długość 7, ale ustaw im y to p C a rd na 2.

A b y po s iadać informację o liczbie


kart, musimy dodać pole topCard.
Kraóy elem ent o indeksie powyżej
tej w artości będzie przechowywał
p u s tą referencję.

Okazuje s ię , że istn ieje


wbudowana w .N£T met°da
A rra y .R e siz e (h która
robi dokładnie to, czego
potrzebujem y.

T eraz je d n a k sprawy się ko m p liku ją . Dość łatw o jest dodać m etodę P e e k (), k tó ra zwraca
referencję do ka rty znajdującej się na górze ta lii — w ten sposób możesz ją podejrzeć. Co się
je d n a k stanie, gdy zechcesz dodać kartę? G dy to p C a rd jest m niejsze niż długość tablicy, możesz
ją wstaw ić po d wspom nianym indeksem i dodać do to p C a rd 1. Jeżeli je d n a k tab lica jest pełna,
w ted y musisz utw orzyć nową, większą i przekopiow ać do niej istniejące elem enty. U sunięcie karty
jest dość proste — musisz potem ty lk o zadbać o to, aby zmniejszyć to p C a rd o 1 i pod je j indeksem
wstaw ić n u l l . A co się stanie, gdy będziesz chciał pozbyć się elem entu ze ś ro d k a lis ty ? Jeśli
usuniesz kartę n u m er 4, będziesz m usiał przesunąć n u m er 5 na je j miejsce, a następnie to samo
zrob ić z ka rtą n u m er 6, po tem 7 ... o rety, co za bałagan!

392 Rozdział 8.
Typy wyliczeniowe i kolekcje

Listy ułatwiają przechowywanie kolekcji... czegokolwiek


P la tfo rm a .N E T posiada zestaw klas k o le k c ji, któ re w yko nu ją całą czarną rob otę związaną
z dodawaniem lub usuwaniem elem entów tablic. N a jba rdziej p o pu larn ym rodzajem ko le kcji
jest L is t< T > . Po u tw o rze n iu o b ie k tu L is t< T > możesz w łatw y sposób wstawiać elem enty, usuwać
wybrane z dow olnego miejsca, podglądać ich zawartość, a nawet przenosić je z jednego miejsca
w inne. L ista działa w sposób następujący: Czasam i będziem y
pomijać fragment
„<T>", gdyż dągte
Na początku utwórz instancję L is t< T > . jego dodawanie
Każda tab lica posiada typ — nie możesz pow iedzieć p o prostu, że posiadasz tablicę. mogtoby u trudnić
lekturę książki. J e d i
Posiadasz tablicę typ u i n t , Card i ta k dalej. L is ty fu n kcjo n u ją w ten sam sposób.
zatem zobaczysz
M usisz okre ślić typ o b ie któ w lu b w artości, któ re lista będzie przechowywała. w te k śc ie L i s t
D okonujesz tego za pom ocą nawiasów tró jką tn ych podczas je j tw orzenia p rzy użyciu zamień je w my ślach
na List<T>.
słowa kluczowego new.

L ist< C a rd > cards = new L is t< C a rd > ();


I
Znaki <T> na końcu
N apisałeś <Card> p°d cz as List<T> oznaczają,
tworzenia /is fy , więc że chodzi nam o typ ogólny.
te raz przechowuje ona
wyłącznie referencje do
ohiektów typu Card. W kodzie programu litera „T" zostanie zastąpiona
jakimś typem — zatem L i s t < in t > będzie oznaczać
6/e k t kolekcję L i s t zawierającą wartości typu i n t . Na kilku
następnych stronach będziesz miał okazję dokładnie
przećwiczyć stosowanie typów ogólnych.
Teraz możesz do listy coś dodać.
G dy ju ż utworzysz o b ie k t L is t< T > , możesz dodać do niego tyle elem entów , ile ty lk o chcesz
Co oznacza,
że można je (o ile ich typ jest polim orficznie zgodny z tym określonym podczas tw orze nia nowej listy).
przypisać
do zmiennej cards.A dd(new C a rd (S u its.D ia m o n d s, V a lu e s .K in g ));
określonego typu,
takiego jak: cards.A dd(new C a rd (S u its .C lu b s , V a lu e s .T h re e )); L is ta przechowuje swoje
interfejs, klasa cards.A dd(new C a rd (S u its .H e a rts , V a lu e s .A c e )); elem enty w określonej
abstrakcyjna, kolejności tak samo ja k
klasa bazowa itd. tablica. King of Diamonds
je s t na pierwszym m ie jscu
Three o f Clubs na drugim.
Na trzeciej pozycji znajduje
się Ace o f H ea rts.
M ożesz dodać do listy
tyle kart, ile tylko chcesz
— po p rostu wywotaj je j
metodę A dd(). Upewni
s ię ona, czy posiada
w ystarczającą liczbę
„okienek" dla elementów.
Gdy zacznie brakować
dla nich m iejsca, lista
automatycznie s ię
rozszerzy.

Ace ekt C(S?


o f Hearts
Or \
m ekt C c T jesteś tutaj ► 393
Super, to je st dopiero udoskonalenie!

Listy są bardziej elastyczne niż tablice


Klasa L i s t jest wbudow ana w .N E T F ram ew ork. Pozwala ro b ić z o b ie kta m i różne
rzeczy, także takie, których nie możesz zrobić przy użyciu zwykłej tablicy. Popatrz,
o to k ilk a m ożliw ości je j wykorzystania:
Na sterci*: tworzony

Możesz ją utworzyć. g \ j« <


List< E gg> myCarton = new L is t< E g g > ();

Dodać coś do niej. e99

Egg x = new E gg ();


m y C a rto n .A d d (x );

Dodać do nie* coś jeszcze.


■1 znowu
Egg y = new E gg ();
m y C a rto n .A d d (y );
y

Sprawdzić, ile jest w niej elementów.


i n t th e S iz e = m yC arton.C ount;

Teraz m ożesz w yszukiw ać obiektu


Sprawdzić, czy zawiera konkretny element. Dyg w liśc ie . To wyrażenie ^
z pew nością zw róci true.
bool is I n = m y C a rto n .C o n ta in s (x );

Określić, w którym mie*scu się on znajduje


x będzie miał indeks 0,
in t id x = m y C a rto n .In d e x O f(y ); y będzie miał indeks 1.

Usunąć z nie* coś.


m yC arton. Remove( y ) ;

x Po u su n ięciu y zo sta ł tylko x.


L is ta skurczyła s ię!

394 Rozdział 8.
Typy wyliczeniowe i kolekcje

¡ ^ Z a o s t r z O łó w e k Wypełnij pozostałą część poniższej tabeli na podstawie kodu obsługującego listę


P° lewej stronie. Napisz, jak wyglądałby kod, gdybyś zamiast tego używał zwykłej tablicy.
»t, Nie spodziewamy się, że wszystko zrobisz dobrze, ale zrób to najlepiej, jak potrafisz.

O t° kilka w ierszy k° du pr° gramu■P rzyjm ij‘


że w szy stk ie in stru kcje wykonywane s ą po
kolei, jedna za drugą, a zmienne zosta ły
zadeklarowane ju ż wcześ n iej ■ Uzupełniliśm y kilka
z nich za C iebie —
Lista Zwykła tablica
L i s t < s t r i n g > myList = string [] m y L i s t = new s t r i n g [ 2 ] ;
new L i s t < s t r i n g > ( ) ;

string a = "Ach!"; string a = „Ach!";

myList.Add(a);

string b = "Bum"; string b = „Bum";

myList.Add(b);

int th e S ize = myList.Count;

string o = myList[1];

bool isIn = m yList.C ontains(b);

jesteś tutaj ► 395


Jeden rozm iar pom ieści wszystko

^.Zaostrz ołówek
Twoim zadaniem było wypełnienie pozostałej części poniższej tabeli na podstawie
Rozwiązanie kodu obsługującego listę po lewej stronie i napisanie, jak wyglądałby kod, gdybyś
zamiast tego używał zwykłej tablicy.

Lista Zwykła tablica


L is t< s t r in g > m y L is t = s t r in g [] m y L is t = new s t r i n g [ 2 ] ;
new L i s t < s t r in g > ( ) ;

s t r in g a = "A c h !"

m y L is t.A d d (a ); m y L is t[0 ] = a;

s t r in g b = "Bum"; s t r in g b = "Bum";

m y L is t.A d d (b ); m yList[1] = b;

i n t th e S iz e = m y L is t.C o u n t; in t theSize = m yList.Length;

s tr in g o = m y L is t[1 ]; string o = m yL ist[1 ];

bool is I n = m y L is t.C o n ta in s ( b ); bool isIn = false;


fo r (in t i = 0 ; i < m yList.Length; i+ + ) {
i f (b = = m y L is t[i]) {
isIn = true;
}
}

ł t
L is ty są ob ie kta m i korzystającym i z m etod W p rzypadku tab licy jesteś nieco ograniczony.
ta k samo ja k wszystkie inne klasy, których Musisz ustalić je j ro zm ia r podczas je j tw orzenia
używałeś do tej pory. M ożesz w yśw ietlić w ID E i wszystkie przeprowadzane na niej operacje
spis dostępnych m etod, w pisując . o b o k nazwy muszą zostać napisane ręcznie.

t
listy. Param etry do nich rów nież przekazujesz
identycznie ja k do zwykłych m etod w klasach,
któ re sam utworzyłeś. Platforma .N ET posiada klasę
A rray ufatw iającą niektóre z tych
cZynn° ś c i . my jednak skoncentrujem y
s ię na obiektach L is t , ponieważ s ą
one znacznie ta tw iejsze w użyciu .

396 Rozdział 8.
Typy wyliczeniowe i kolekcje

Listy kurczą się i rosną dynamicznie


Jedną z najlepszych właściwości lis t jest to, że nie musisz znać ich ro zm ia ru podczas tworzenia.
Lista autom atycznie rozszerza się i kurczy zależnie od zawartości. Poniżej zam ieściliśm y przykład
w ykorzystania k ilk u m etod, k tó re sprawiają, że praca z ob ie kta m i klasy L i s t jest znacznie łatwiejsza
niż z tab lica m i. U tw ó rz nowy p ro je k t typ u C o n s o l e A p p l i c a t i o n i um ieść poniższy ko d w m etodzie
M a in ( ) . N ie wyśw ietla ona żadnych k o m u n ika tó w — musisz s ko r zy s t a ć z d e b u g g e r a , by k ro k po
k ro k u wykonać kolejn e in stru kcje pro g ra m u i przekonać się, co się w n im dzieje.

Z a p a r o w a liś m y listę
List<S hoe> shoeC loset = new L is t< S h o e > (); obiektów Sh oe o nazwie
M ożesz użyć in s tru kcj i s hoeCloset.
new wewnątrz metody
shoeC loset.A dd(new Shoe() L is t.A d d ().
{ S ty le = S ty le .S n e a k e rs , C o lo r = "C zarny" foreach jest specjalnym rodzajem
shoeC loset.A dd(new Shoe() pętli dla listy. Instrukcje są
{ S ty le = S ty le .C lo g s , C o lo r = "Brązowy" }); wykonywane dla każdego
shoeC loset.A dd(new Shoe() umieszczonego w niej obiektu.
{ S ty le = S ty le .W in g tip s , C o lo r = "C zarny" } ) ; Przedstawiona tu pętla tw orzy
shoeC loset.A dd(new Shoe() id e n tyfika to r o nazwie shoe —
{ S ty le = S ty le .L o a fe r s , C o lo r = " B ia ły " });
w miarę ja k przechodzi ona przez
kolejne elementy listy, id e n tyfika to r
shoeC loset.A dd(new Shoe()
ustawiany jest na pierwszy z nich,
{ S ty le = S ty le .L o a fe r s , C o lo r = "Czerwony" })
potem drugi, trzeci itd ., aż do
shoeC loset.A dd(new Shoe()
wyczerpania się obiektów.
{ S ty le = S ty le .S n e a k e rs , C o lo r = " Z ie lo n y " })

Ta instrukcja
i n t numberOfShoes = sh o eC lose t.C o un t ; zw raca całkowitą Pętle foreach potra fią także ° p e r° wać
liczbę obiektów na tablicach! W zasadzie p °tra fią ° ne
fo re a ch (Shoe shoe in sho eC lose t) Shoe umieszczonych pracować z każdą kolekcją.
s h o e .S ty le = S t y le . F lip f lo p s ; na liście .
Tu j e s t klasa Shoe oraz typ
s h o e .C o lo r = "Pomarańczowy"; wyliczeniowy S ty le , których
Pętla foreach używam y...
} wykonuje s ię raz dla
M etoda Rem ove() usuwa
obiekt na podstaw ie każdego obiektu S h °e
w liśc ie sh °e C l° se t . p u b lic c la s s Shoe {
j e go referen cji. Metoda
Rem oveA t() robi to na p u b lic S ty le S ty le ;
podstaw ie indeksu.
p u b lic s t r in g C o lo r;

M etoda C lear() }
shoeC loset.R em oveAt(4) .
u su wa z listy
wsz y stk ie obiekty.
p u b lic enum S ty le {
Shoe th ird S h o e = s h o e C lo s e t[2 ];
Z a p isa liśm y refem ncje Sneakers,
Shoe secondShoe = sh o e C lo s e t[1 ] do dwóch obiektów
Shoe tu ż przed L o a fe rs ,
s h o e C lo s e t.C le a r();
wyczyszczeniem lis ty S andals,
Dodaliśmy jeden z n nich
r
F lip f lo p s ,
s ^ e H o s e U M ^ M r^ o e ); ^ ^ j i f f i j u t t a rn
W in g tip s ,
if (sh o e C lo se t.C o n ta in s(se co n d S h o e )) nie ma.
C logs,
C o n s o le .W rite L in e ("A to c i n ie s p o d z ia n k a ." );
Ten w e rs z nigdy nie z ° s tanie wykonany, ponieważ Contains() }
Ten wj
zwrócJ fa ls e . Do wy czyszczonej lis ty dodaliśmy tylko obiekt
th irdS hoe, n i e m a tam natom iast secondShoe.
jesteś tutaj ► 397
Członkowie m ają swoje przyw ileje
CELNE SPOSTRZEŻENIA —
Typy generyczne mogą przechowywać każdy typ
■ L i s t jest klasą platformy .NET
W idziałeś już, w ja k i sposób L i s t może przechowywać łańcuchy znaków ■ Lista dynam icznie zm ienia swój rozm iar
lub o b ie kty klasy Shoe . Możesz także utw orzyć listę liczb całkow itych do osiągnięcia odpowiedniej wielkości.
lub dowolnych wym yślonych przez C iebie obiektów . T a właściwość czyni Posiada określoną pojemność, jeśli jednak
listę kol e kc j ą g e n e r y c z n ą . K ie d y tworzysz now y o b ie k t L i s t , wyraźnie dodasz do niej pewną liczbę elementów,
określasz jego typ: możesz m ieć listę liczb całkow itych, łańcuchów rozszerzy się ona, aby je pomieścić.
znaków lu b o b ie któ w Sh o e . Przez to praca z lista m i staje się o wiele ■ Aby dodać coś do listy, użyj A d d ( ) .
łatw iejsza — po ich u tw o rze n iu do kła dnie znasz typ przechowywanych Aby coś z niej usunąć, skorzystaj
w nich elem entów. z R em ove() .
Nie oznacza to, że dodałeś literę „T ". J e s t to sposób za p isu , ■ Możesz usuwać elementy, używając ich
który w idzisz za każdym razem, gdy klasa lub in terfejs
może opero w a ć'na dowolnym typie danych. C zęść <T> indeksu , z wykorzystaniem R e m o ve A t() .
oznacza, że może sz w to m iejsce w staw ić typ, na przykład
■ Typ listy deklarujesz za pomocą
L is t<Shoe>. Ograniczy s z w ten sposób zakres typów,
z którymi lista pracu je. pa ram e tru ty p u , który jest ujęty
w nawiasy trójkątne. Na przykład
L i s t < F r o g > oznacza, że lista będzie
List<T> name = new List<T>(); mogła zawierać tylko obiekty typu Frog.
^ , •
■ Aby odnaleźć coś na liście (jeśli dany
L is ty mogą być
z ^ J r o T ć to samo co tablice
element jest na niej umieszczony),
możesz użyć I n d e x O f( ) .
i jeszcze kilka dodatkowych rzeczy.
■ Aby pobrać liczbę elementów listy,
użyj właściwości C o u n t .
P la tfo rm a .N E T o fe ru je zestaw in te rfe jsó w generycznych, któ re
pozwalają tw orzonym przez C iebie ko le kcjo m pracować z każdym ■ Możesz skorzystać z metody
C o n t a in s ( ) , aby określić, czy dany
typem danych. Klasa L i s t im p lem e ntuje wszystkie te interfejsy.
obiekt wchodzi w skład listy
D z ię k i tem u możesz utw orzyć listę liczb całkow itych i pracować
z nią praktycznie ta k samo, ja k pracujesz z listą o b ie któ w Shoe . ■ fo re a c h jest specjalnym rodzajem pętli,
która pozwala na przejrzenie wszystkich
Sprawdź to sam . W pisz w I D E słowo L i s t , k lik n ij je prawym elementów listy i wykonanie kodu z ich
przyciskiem myszy, a następnie w ybierz opcję Go To Definition. udziałem. Składnia foreach wygląda
Zostaniesz przeniesiony do dekla racji klasy L i s t . Im ple m en tu je następująco: fo r e a c h ( s t r i n g s
ona k ilk a interfejsów : i n S t r i n g L i s t ) . Nie musisz jawnie
nakazywać pętli zwiększania licznika
o jeden — iteracja zostanie wykonana
To stą d pochodzą metody Rem oveAt(),
IndexO f() oraz In se rt().
automatycznie dla każdego elementu.

public class List<T>


ICollection<T>, IEnumerable<T>, IL ist, Nie można modyfikować
IC ollection, IEnumerable zawartości kolekcji podczas jej
I
Ten interfejs pozwala przetw arzania w pętli fo r e a c h !
S tąd biorą s ię metody
między innymi na użycie
Add(), C lear(), CopyTo()
pętli f oreach. Jeśli spróbujesz to zrobić, zostanie zgłoszony błąd. Na
oraz Rem ove(). J e s t
to podstawa dla w szystkich szczęście zawsze można zrobić kopię kolekcji. Każdy obiekt
kolekcji generycznych. typu IEnu me r a b l e posiada metodę T o L i s t ( ) , której
można użyć, by bezpiecznie przejrzeć jego zawartość.

398 Rozdział 8.
Typy wyliczeniowe i kolekcje

Magnesiki z kodem
Czy potrafisz tak poukładać magnesy z kodem,
aby utworzyć działający projekt Windows Form,
który po naciśnięciu przycisku wyświetli
komunikat zaprezentowany poniżej?

p r i v a te void b u tto n 1 _ C lic k (o b je c t sender,


EventArgs e ) {

strin g zilch = "zero";


strin g f i r s t = "jeden";
s t r i n g se c o n d = "dwa";
string th ird = "trzy";
strin g fourth = "4.2";
s t r i n g twopointtwo = " 2 . 2 " ;

jesteś tutaj ► 399


Rozwiązanie ćwiczenia C zy pam iętasz, ja k w rozdziale 3. pisaliśmy
o stosowaniu intuicyjnych n azw ? C ó ż ... to dobra
zasada w odniesieniu do tw orzenia kodu, lecz jej
Magnesiki z kodem . Rozwiązanie stosowanie sprawiłoby, że nasze zagadki byłyby
zb yt łatw e. Pam iętaj, by w praktyce nie stosować
takich nazw ja k „ p rin tL ” .

private void b u tto n 1 _ C lic k (o b je c t sende^


E v e n t A r gi!»
s e ) {n
L i s t < s t r i n g > a = new L i s t < s t r i n g > ( ) ;
L
-— i
|

strin g zilch = "zero";


strin g f i r s t = "jeden";
s t r i n g s e c o n d = "dwa";
string th ird = "trzy";
strin g fourth = "4.2";
s t r i n g twopointtwo = "2 .2 " ;
<e
a . Add ( z i I c h ) ;
a.A dd(first); Czy je s t e ś w stanie
a.Add(second); pow iedzieć, dlaczego „2 .2 “
rngdy me j e s t dodawane
a.A dd(third);
do listy , chociaż j e s t tutaj
if (a.C ontains("trzy")){ zadeklarowane?

a.A dd("cztery");
}
a. R e mo v e A t ( 2 ) ;

if (a.IndexO f("cztery") != 4) {
RemoveAtO usuwa a.A dd(fourth);
elem ent znajdujący }
s ię pod indeksem 2
— je s t to trzecia if ( a . C o n t a i n s C ' d w a 11) ) ( M etoda printL() uży wa
pozycja listy. p ę tli foreach, aby prz e jść
a.A dd(tw opointtwo); przez ca łą lis tę tańctuM iu
znaków i dodać je j e lementy
do jednego dużego obiektu
typu string . Po wszy stkim
wyśw ietlany j e s t komunikat
MessageBox.

p u b lic void p rin tL (L ist<string> a ) {


Pętla foreach
przechodzi przez
wszyst'kie elementy
lis t y i w yśw ietla je . ' uieden istring elem ent in

result + = -Xn" + e l e m e n t
}

MessageBox.Show(result);

400 Rozdział 8.
Typy wyliczeniowe i kolekcje

P : Po co w takim razie miałbym


wartości o konkretnej długości, to tablica
będzie się idealnie nadawać do Twoich celów. O : Nie, każda lista, a w zasadzie każda
stosować enum zamiast listy? Czy nie kolekcja generyczna (dowiesz się o innych
Na szczęście jesteś w stanie w łatwy sposób
rozwiązują one tego samego problemu? kolekcjach generycznych dosłownie za minutę),
zamienić listę na tablicę przy użyciu metody
O : Typy wyliczeniowe trochę się od list
T o A rra y(). Możesz także przekształcić tablicę
w listę, używając jednego z przeciążonych
musi mieć powiązany ze sobą typ. C# posiada
listy niegeneryczne nazwane A rra y L is t,
różnią. Przede wszystkim są typami, natomiast które potrafią przechowywać każdy rodzaj
konstruktorów obiektu List<T>.
listy — obiektami. obiektów. Jeśli chcesz ich używać, musisz
Możesz myśleć o typach wyliczeniowych
jako o listach stałych, do których będziesz
P : Nie mogę pojąć, skąd nazwa
najpierw dołączyć do kodu wiersz
„using S y s te m .C o lle c tio n s ; ".
„generyczne” . Dlaczego mówimy na
się odnosił za pomocą nazw. Oddają one to kolekcja generyczna, zamiast nazywać Najprawdopodobniej jednak nigdy nie
nieocenione usługi przy zwiększaniu czytelności ją tablicową? będziesz musiał z nich korzystać, gdyż
kodu. Pozwalają także uzyskać pewność,
że zawsze używasz prawidłowej nazwy O : Kolekcja generyczna jest tablicą obiektów
lista zadeklarowana jako L is t< o b je c t>
w zupełności Ci wystarczy!
zmiennej podczas dostępu do wartości, (lub wbudowanego obiektu pozwalającego
z których korzystasz nader często. przechowywać grupę innych obiektów
Lista może przechowywać praktycznie i zarządzać nimi), która została przystosowana Kiedy tworzysz
wszystko. W związku z tym, że jest to kolekcja
obiektów, każdy jej element ma swoje
do przechowywania tylko jednego typu
(lub większej ich liczby, o czym przekonasz się
nowy obiekt
metody i właściwości. Z drugiej strony, typy za chwilę). List , zawsze
wyliczeniowe muszą przyjąć wartości typów
wartościowych C# (była o nich mowa na P : Dobrze, to wyjaśnia nazwę określasz typ
pierwszej stronie rozdziału 4.). Nie możesz „kolekcja” . Ale co sprawia, że jest ona
za ich pomocą przechowywać zmiennych generyczna? — w ten sposób
referencyjnych.
Typy wyliczeniowe nie potrafią też dynamicznie
O : Dawniej w sklepach sprzedawano przekazujesz C#,
produkty zapakowane w duże białe pudełka
zmieniać swojego rozmiaru. Nie mogą z czarnym napisem określającym ich zawartość jaki rodzaj danych
implementować interfejsów ani posiadać („Prażynki", „Herbata", „Mydło" itp). Towary
metod i musisz wykonywać rzutowania, nie miały swojej marki i ważne było tylko to, co będziesz tam
aby wartość pola wyliczeniowego mogła być
przechowywana w innej zmiennej. Biorąc pod
jest w środku. Były one „ogólne" (ang. generic).
To samo można powiedzieć o generycznych,
przechowywał.
uwagę to wszystko, istnieje naprawdę duża
różnica pomiędzy tymi dwoma metodami
czyli ogólnych, typach danych. Twój obiekt Lista może
List<T> będzie zawsze działał tak samo bez
przechowywania danych. Na swój własny
sposób oba te elementy C# są jednak
względu na to, co jest w środku. Lista obiektów zawierać typy
Shoe, Card, wartości in t, long czy nawet
bardzo użyteczne.
innych list w dalszym ciągu będzie działała na wartościowe
P : Dobrze, wygląda na to, że listy są
poziomie kontenera. Zawsze możesz dodawać,
usuwać i przeglądać elementy — nie ma
(na przykład int,
naprawdę znakomite. Po co w takim razie
miałbym używać tablic?
znaczenia, co znajduje się wewnątrz listy,
booi lub string)
O : Jeśli wiesz, że będziesz operował na P : Czy mogę mieć listę, oraz klasy.
konkretnej, niezmiennej liczbie elementów, bądź która nie posiada żadnego typu?
y\ też jeśli potrzebujesz ściśle określonej sekwencji
Tablice zajmują mniej miejsca w pamięci Określenie „generyczny" To dlatego znajduje s ię tam coś ta k i e g o
°dn°si się d° faktu, że ° ile j a k <T>. To t u p r z y p i s u j e s z o k r e ś o n ą
i w mniejszym st°p n iu °bc iążają
procesor, niemniej jednak m żn'^ określona instancja L is t m°że i n s t a n c j ę l i s t y d o d o n c g o ^ ty p u . K la s a
te są naprawdę niewielkie■Je śli przechowywać tylko jeden,
określony typ, to ogólna klasa ż f p i ? r a V ep m c o w a ć ^ K A ŻD Y M typem.
musisz wykonać tę samą czynność na
L is t może pracować z każdy m. To d l a t e g o k o le k c je generyczne rozn-ą
przykład miliony razy w ciągu sekundy,
. s i ę o d w s z y s tk ie g o , z czym d o ty c h c z a s
to zapewne wybierzesz w tym celu
tablicę, a nie listę ■Je śli jednak Tw<5j m iałeś do czynienia.
program działa w°ln°, to je s t raczej
mało prawdopodobne, by zamiana list
na tablice rozwiązała pr°b|em. jesteś tutaj ► 401
Tu jesl początek

Inicjalizatory kolekcji działają tak samo


jak inicjalizatory obiektu
C # udostępnia skrócony zapis, k tó ry pozw ala nieco zmniejszyć ilość wpisywanego ko d u koniecznego do utw orzenia
listy i jednoczesnego dodania do niej k ilk u elem entów. T w orząc now y o b ie k t L i s t , m ożem y skorzystać z i n i c j a l i z a t or a
kolekcj i , by określić je j początkow ą zawartość. Z ostanie ona dodana do listy bezpośrednio po je j utw orzeniu.

Widziałeś ten kod kilka stron w cześniej — tworzy


on nowy obiekt L is t <Shoe> i wypełnia go instancjam i
List<Shoe> shoeCloset = new List<Shoe>(); klasy S h o e .

shoeCloset.Add(new Shoe() { Style = Style.Sneakers, Color = "Czarny" });


shoeCloset.Add(new Shoe() { Style = Style.Clogs, Color = "Brązowy" });
shoeCloset.Add(new Shoe() { Style = Style.Wingtips, Color = "Czarny" });
shoeCloset.Add(new Shoe() { Style = Style.Loafers, Color = "Biały" });
shoeCloset.Add(new Shoe() { Style = Style.Loafers, Color = "Czerwony" });
shoeCloset.Add(new Shoe() { Style = Style.Sneakers, Color = "Zielony" });

Czy zw ró ciłeś uwagę, ż e każdy M ożesz utworz y ć inicjalizator


obiekt Shoe j e s t tworzony przy kolekcji, biorąc to, co w cześniej
uży ciu własnego inicjalizatora? A°st \ o um ieszczone w metodzie
M ożesz j e z agnieżdżać wewnątrz A dd(), i um ieszczając w instrukcji
inicj alizatora kolekcji, tak ja k t worzącej listę .
pokazaliśm y.

List<Shoe> shoeCloset = new List<Shoe>() {


i
Po instru kcji tworzącej new Shoe() { Style = Style.Sneakers, Color = "Czarny" },
lis tę w stawione s ą
naw iasy klamrowe, new Shoe() { Style = Style.Clogs, Color = "Brązowy" },
które zaw iera ją osobne
instru k c je new oddzielone new Shoe() { Style = Style.Wingtips, Color = "Czarny" },
przecinkam i.
new Shoe() { Style = Style.Loafers, Color "Biały" },
new Shoe() { Style = Style.Loafers, Color "Czerwony" },
W inicjalizatorze nie
je s t e ś ograniczony new Shoe() { Style = Style.Sneakers, Color "Zielony" },
do używania
in stru kcji new —
m ożesz tu taj także
u m ieścić nazwy
zmiennych. Inicjalizator kolekcji sprawia, że Twój
kod jest bardziej zwięzły, gdyż łączy
tworzenie listy z dodaniem do niej
początkowego zestawu elementów.
402 Rozdział 8.
Typy wyliczeniowe i kolekcje

Stwórzmy listę kaczek z ób to ! ^

M am y daną klasę Duck , k tó ra przechow uje in fo rm acje


o ogrom nej ko le k c ji kaczek. (Z pewnością zbierasz kaczki,
praw da?). U tw ó rz nową aplikację konsolową i dodaj do niej
nową klasę Duck oraz typ w yliczeniow y KindOfDuck .

Każda kaczka ma swój


rozm iar — ta tutaj ma
17 centymetrów długości.

N iektóre z nich
to kaczki krzyżówki.

public class Duck {


public in t Size;
J e s t tu też
parę drewnianych public KindOfDuck Ki nd;
Mamy także kaczek wabików.
kilka piżmówek }
amerykańskich.
i I
Klasa posiada dwa
pubhczne pola. Oprócz
nich ma też kilka metod,
ale nie będą one tutaj
pokazane.

p u b l i c enum KindOfDuck {
M allard,
Tak będzie wyglądał inicjalizator Mus c ovy,

M am y sześć kaczek, utw orzym y zatem o b ie k t L i s t <Du c k > , któ ry Decoy


będzie posiadał in ic ja liz a to r ko le k c ji z sześcioma instrukcjam i. }
Każda z nich u tw o rzy nową kaczkę, używając do tego in icja liza to ra
ob ie ktu , k tó ry w yp e łn i po la S i z e oraz Kind każdej instancji Duck .
Użyliśmy typu
Dodaj ten kod do m etody Ma i n( ) w p lik u Program.cs: wyliczeniowego
o nazwie KindOfDuck
do rozróżnienia rodzajów
List<Duck> ducks = new L i s t < D u c k > ( ) { kaczek w kolekcji.

new Duck( ) Kind = K i n d O f D u c k . M a l l a r d , Size = 17 },


K
new Duck( ) Kind = K i n d O f D u c k . Mu s c o v y , Size = 18 }, Dodaj klasę Duck
i typ KindOfDuck
new Duck( ) Kind = K i n d O f D u c k . D e c o y , S i z e = 14 }, do swojego projektu.
new Duck( ) Kind = K i n d O f D u c k . Mu s c o v y , Size = 11 }, Kod dodamy do metody M ain(), a wyniki będą
new Duck( ) Kind = K i n d O f D u c k . M a l l a r d , Size = 14 }, wyświetlane w konsoli. Nie zapomnij dodać
tego wy wołania na samym końcu metody
new Duck( ) Kind = K i n d O f D u c k . D e c o y , S i z e = 13 }, — dzię ki niemu program będzie działał do
momentu naciśnięcia dowolnego klawisza.
}; \ f
// Dzięki wywołaniu C o ns o le .R e a d K e y () wyniki nie znikną, dopóki ich n ie p rz eczy tasz
Console.ReadKey();

*
jesteś tutaj ► 403
U staw ianie kaczek w rzędzie

Listy są proste, ale SORTOWANIE


może być skomplikowane
N ie tru d n o jest wym yślić sposób sortow ania liczb lu b lite r. Co je d n a k zrob ić wtedy,
gdy musisz posortow ać dwa obiekty, zwłaszcza je ś li m ają w iele pól? W niektórych
przypadkach mógłbyś uporządkow ać je na podstaw ie w artości pola, k tó re przechowuje
ich nazwę, a in nym razem większy sens m ia łob y posegregowanie o b ie któ w pod
względem ich wysokości lu b daty urodzenia. Istn ie je w iele sposobów porządkow ania
różnych rzeczy. L is ty radzą sobie z każdym z nich.

Możesz posortować listę kaczek na podstawie ich rozm iaru...


Posortowane od najm niejszej do najw iększej..

.lub pod względem typu.


Posortowane według rodzaju kaczki

L i s t y w ie d z ą / w j a k i s p ° sÓ b s o r t o w a ć Z technicznego punktu w widzenia


rz e n ia to nie
obiekt L i s t<T> w 'ie, ja k należy sortow
sortow ać
Każda lista wyposażona jest w m etodę S o r t ( ) , k tó ra przestawia wszystkie swoją z a w artość. Z a sortowanie
elem enty i u kład a je we właściwym porządku. L is ty p o tra fią sortow ać °itp 'w ''ada in te re j s JC om pa^ KT^
któny poznasz ju ż za chwilę.
większość w budowanych typów danych i klas, a co więcej bardzo ła tw o
m ożna je nauczyć sortow ania klas, k tó re będziesz pisał sam.

/ kaczka
T \
11-cm

k N- \u
ekt

Po posortowaniu listy
kaczek w obiekcie
znajdują s ię ciągle
\ 7
kaczka
\ ekt te sam e elementy ^ k t
— s ą po p ro stu
14-cm inaczej poukładane.

k N. 'i
'ekt
404 Rozdział 8.
Typy wyliczeniowe i kolekcje

(Comparable<T> pomoże Ci posortować listę kaczek


M e to d a L i s t . S o r t ( ) ju ż w ie, w ja k i sposób sortow ać dow olny typ im p le m e n tu ją c y
in te rfe js IC om parable<T >. M a on ty lk o jedną składową — m etodę o nazwie
Możesz spra­
Compar eTo( ) . S o r t ( ) korzysta z niej w celu p o rów nan ia danego o b ie ktu z in n ym i i używa wić, że każda
w artości zwracanej ( i n t ) do określenia ich porządku.
klasa będzie
Czasami musisz je d n a k porów nać obiekty, k tó re nie im p le m e n tu ją ICompar able<T> .
.N E T posiada na taką okoliczność in n ą m etodę. Możesz przekazać S o r t ( ) instancję
współpracowa­
klasy, k tó ra im p lem e ntuje IComparer<T> . In te rfe js ten także posiada je dn ą m etodę. ła z wbudowaną
M e to d a lis ty S o r t ( ) używa jego m etody Compare() do zbadania re la cji pom iędzy
dwom a o b ie kta m i i określenia, w ja kie j kolejności po w in n y zostać umieszczone na metodą Sort()
posortow anej liście. listy, poprzez
Metoda CompareTo() obiektu porównuje go z innym obiektem zaimplemento­
Jednym ze sposobów posortow ania o b ie któ w w liście jest zm odyfikow anie klasy Duck wanie interfejsu
i zaim plem entow anie w niej in te rfe jsu IComparable<Duck> . A b y tego dokonać,
dodam y m etodę Comp a r e To ( ) , k tó ra p o biera ja ko pa ra m e tr referencję Duck . Jeżeli
IComparable<T>
porów nyw ana kaczka po w in na pojaw ić się na liście za aktualną, to CompareTo() zwróci oraz dodanie
wartość dodatnią.
metody
Z a k tu a liz u j klasę Duck , dodając do niej im plem entację in te rfe jsu IComparable<Duck> ,
k tó ra po zw o li na sortow anie kaczek ze względu na ich rozm iar: CompareTo() .
class Duck : I Co mp a r ab l e < D u c k > { Kiedy im plem entujesz ICom parable,
określasz typ, który j e s t
public in t Size; porównywany w klasie podczas
sortowania.
public KindOfDuck Ki n d ;

Większość metod CompareTo0


public int C o mp a r eTo ( Du c k d u c k To Co mp a r e ) {
Metoda porównuje jedno pole H .r , . /
Size klasy Duck z innym. if (this.S ize > duckToCompare.Size)
Je ż e li dana kaczka je s t
miększa od tej
return 1;
m wywołaniu, zwraca 1. Jezen
e ls e if ( t h i s . S i z e < duckToCompare.Size)
je st mniejsza, zwraca -1 .
Gdy m ają taki sam rozmiar, return -1;
otrzymujemy z mej O.
e ls e Je ż e li ch cesz posortować kaczki od najm niejszej
do najw iększej, metoda CompareTo() zw róci wartość
return 0; dodatnią, gdy obiekt porównywany je s t z większą
kaczką, lub liczbę ujemną, gdy porównywany j e s t
}
z kaczką m niejszą.
}
D o d a j p o n iższy w ie rs z k o d u na końcu m etody M a i n ( ) , tuż przed w yw ołaniem C o n s o l e . R e a d Ke y ( ) — spowoduje
on po sortow anie listy kaczek. Skorzystaj z debuggera, by sprawdzić, ja k działa sortow anie; w tym celu u m ie ść p u n k t
p rz e rw a n ia w ew nątrz m etody Co mp a r e To ( ) .

D ucks.Sort();

jesteś tutaj ► 405


Posortuj to po swojemu

Użyj interfejsu IComparer, Sposób


aby powiedzieć liście, jak ma sortować sortowania
L is ty L is t< T > m ają specjalny w budow any w .N E T F ra m e w o rk in te rfe js, pozwalający Twojej listy
C i utw orzyć oddzielną klasę, k tó ra pom oże posortow ać je na podstaw ie składowych
przechowywanych obiektów . Poprzez im p le m e n ta c ję in te rfe js u ICom parer<T>
jest zależny od
możesz do kła dnie okre ślić swojej liście, w ja k i sposób m a sortow ać swoją zawartość. implementacji
D okonujesz tego dzięki specjalnej m etodzie C om pare() tego interfejsu. Pobiera ona
dwa param etry, x i y, a zwraca w artość i n t . Jeżeli x jest m niejszy niż y, m etoda interfejsu
po w in na zw rócić wartość ujem ną. G dy są rów ne, zw róci zero. Jeżeli x jest większy,
IComparer .
po w in na zw rócić wartość dodatnią.

Poniżej zaprezentowano przykład, w któ rym w idoczny jest sposób dekla racji klasy
porów nującej ro zm ia ry o b ie któ w Duck. D o da j go do swojego p ro je k tu ja ko nową klasę:

Klasa imp lem entuje ICom parer


i określa typ obiektów, które
może °n s °rto w a ć; tu s ą t°
obiek ty Duck■

public class DuckComparerBySize : IComparer<Duck>


T° z aw sze będzie zgodne:
{ oba typy będą takie same.
public int Compare(Duck x, Duck y)
{ Metoda CompareO zwraca
if (x.Size < y.Size) S ? 7 posiada dwa parametry
o takim samym typie jak
W m e to d z ie m o ż e s z
return -1; sortowane obiekty.
u m i e s z c z a ć d o w o ln e
sp o so b y porów nań.
if (x.Size > y.Size) Każda w artość ujemna °zn aczd,
że obiekt x p°w inien znatoźić
return 1; ę=__ s ię przed ° b ie ktem y ■x je s t
„m niejszy niż" y.
„mni
return 0;
} (
} 0 oznacza, że obiekty JG St " W ię k s z y n iż “ u. y

To j e s t metoda służąca (na podstawie tej Dodaj tę metodę do lih s y _Program


do w yśw ietlania kaczek metody porównania). w swoim projekcie; dzięki temu .
zapisanych na liście będziesz mógł wy św ietla ć kaczki
List<Duck>. zapisane na !iś c ie .

Zm odyfikuj metodę M ain() w taki sposób,


p u b lic s t a t i c v o id P rin tD u c k s (L is t< D u c k > ducks)
by wywoływała tę metodę zarówno przed,
{ j ak i p ° sor-toiwamu lis ty kaczek — rdzięki
temu będziesz mógł zobaczyć jego efekty!
fo re a ch (Duck duck in ducks)
C o n s o le .W rite L in e (d u c k .S iz e .T o S trin g () + "-ce ntym etrow a kaczka " + d u c k .K in d .T o S tr in g ( ) ) ;
C o n s o le .W rite L in e ("K o n ie c k a c z e k !" );

406 Rozdział 8. *
Typy wyliczeniowe i kolekcje

Utwórz instancję obiektu porównującego Pominęliśm y kod, który


w idziałeś ju ż kilka stron
w cześn iej, słu żą cy do
inicjalizacji listy . Nie zapomnij
K ie d y chcesz sortow ać p rzy użyciu in te rfe jsu ICom parer<T>, musisz n a jp ie rw utw orzyć zainicjalizow ać je j, zanim
nową instancję klasy, któ ra go im p lem e ntuje. O b ie k t ten będzie is tn ia ł ty lk o w jednym sp ró b u jesz j ą p o so rto w a ł
J e ś li tego nie zrobisz, pm gram
celu — aby pom óc L i s t . S o r t ( ) w okre śle niu sposobu sortow ania tablicy. Jednak, ja k
zgłosi w yjątek.
w przypadku każdej (niestatycznej) klasy, je j instancja m usi być utw o rzon a

DuckComparerBySize sizeComparer = new DuckComparerBySize();


ducks.Sort(sizeComparer); P rzekazu jesz metodzie
referencję obiektu D u c k C o m p m i- ^ ^ o
^ PrintDucks(ducks); jako param etr.

Dodaj to wywołanie do sw o jej metody


M ain(), by przekonać s ię , ja k ie efekty Posortowane od najm niejszej do n a jw iększej...
dało sortow anie kaczek.

Wiele implementacji IComparer,


wiele sposobów na sortowanie obiektów
Możesz utw orzyć w iele klas ICom parer<Duck> z różną lo g iką sortow ania w zależności
od tego, co chcesz zrobić. A b y sortow ać o b ie kty w określonym porządku, będziesz T a klasa porównująca so rtu je ,
m usiał wyw oływ ać różne klasy porów nujące. Poniżej m am y inną im plem entację klasy przyjm ując za kryterium tup kaczki
Pamiętaj, podczas porównywania
porów nującej kaczki, k tó rą także powinieneś dodać do swojego p ro je ktu . pól typu wyliczeniowego Kind
porów nujesz w artości ich indeksów
class DuckComparerByKind : IComparer<Duck> {
public int Compare(Duck x, Duck y) { IV
M allard znajdzie s ię
if (x.Kind
.
< / y.Kind) kaczki wedtug wtaśc.wośc. Kmd, przed typem M uscovy,
który z kolei będzie
return -1; przed Decoy.
if (x.Kind > y.Kind) typu wyliczeniowego
return
else i m e > z y ^ i t S2^ niż" °ra z
return znaczenie. inne 4
< ' >do p o ró w n a j *?ndJXy -0peratory
} w artości tuou / lndeksów y
» p o ^ Z „„7
} Porządek Sortowania

DuckComparerByKind kindComparer = new DuckComparerByKind();


ducks.Sort(kindComparer);
PrintDucks(ducks); Posortowane wedtug typu

Kolejny kod so rtu ją cy


kaczki, który możesz

jesteś tutaj ► 407


W ybierz kartę, dow olną kartę Jeśli w w yw oła niu metody
S o r t ( ) nie przekażesz obiektu
implementującego interfejs
(Comparer może wykonywać złożone porównania IComparer<T>, to zastosuje ona
jego domyślą implementację, która
potrafi sortować typy wartościowe
Jedną z korzyści płynących z utw o rzen ia oddzielnych klas do sortow ania kaczek jest
i referencje. Zajrzyj do szóstego
m ożliwość w prow adzenia do nich znacznie bardziej skom plikow anej lo g ik i — możesz punktu dodatku „Pozostałości",
także dodać składowe, które będą określały sposób sortow ania. by dowiedzieć się nieco więcej na tem at
porów nywania obiektów.
p u b lic enum S o r t C r it e r ia {
S izeThenK ind, Tutaj mamy znacznie bardziej
K indThenS ize, Ten typ wyliczeniow y określa, w ja k i skomplikowaną klasę do porównywania
} ^sposób obie k t będzie sorto w ał kaczki. kaczek. J e j metoda Compare() p o b ita
takie sam e param etry, ale sp ra wdza
publiczne pole S o rtB y , aby określić
p u b lic c la s s DuckComparer : IComparer<Duck> { sposób sortowania.
p u b lic S o r t C r it e r ia S ortB y = S o r tC rite r ia .S iz e T h e n K in d ;

p u b lic i n t Compare(Duck x , Duck y ) {


i f (S o rtB y == S o rtC rite ria .S iz e T h e n K in d )
i f (x .S iz e > y .S iz e )
To ins trukcja if sprawdza pole S o rtB y . Gdy
j es t ono us t awione na SizeThenKind, wtedy
*
r e tu r n 1; kaczki w p ierwszej kolejności s ą sortowane
e ls e i f ( x .S iz e < y .S iz e ) na podstaw ie ich wielkości, a następnie,
w ramach każdego rozmiaru, porządkowane
r e tu r n - 1 ; według rodzaju.
e ls e
i f (x .K in d > y .K in d )
r e tu r n 1; Za m ia st po prostu zwracać 0 , jeżeli
e ls e i f (x .K in d < y .K in d ) dwie kaczki m ają ten sam rozmiar, klasa
porównująca spraw dza ich rodzaj i z wraca
r e tu r n -1 ;
0 tylko wtedy, gdy zarówno rozmiar, jak
e ls e 1 rodzaj kaczki j e s t taki sam.
r e tu r n 0;
else
if (x .K in d > y .K in d )
r e tu r n 1;
e ls e i f (x .K in d < y .K in d ) Jeżeli pole S ortB y nte je s t
r e tu r n - 1 ; ustaw ione na KindThenS i ze, ktasa _
e ls e porównująca w p ierw szej kolejn o ści
sp ra wdza rodzaj kaczek. Gdy dwie
i f ( x .S iz e > y .S iz e )
kaczki s ą tego samego rodzaju,
r e tu r n 1; to porównywany je s t ich rozmia-r.
e ls e i f (x .S iz e < y .S iz e )
r e tu r n -1 ;
e ls e
r e tu r n 0;
W ten oto sposób uży wamy ot>iektu
porównującego. Najpierw tworzymy je go
instancję, tak jak. z wy Mt». Potem,
wywołamy d u c k s .S o rta możemy usta w 'ć
¡eao pole SortBy. Możesz te raz zm ^m ó
DuckComparer comparer = new DuckCom parer(); sp osób sortowania kaczek, zm ieniając
wartość jednego pola obiektu- Dodaj_ten
com p a re r.S o rtB y = S o rtC rite r ia .K in d T h e n S iz e ; kod do swojego proje k tu , umieszc/zając
d u c k s .S o rt(c o m p a re r); go na s a mym końcu metody M ain().
N astępnie spróbuj posortow ać kaczki
P rin tD u c k s (d u c k s );
na każdy z możliwych sp °s° bow !

com p a re r.S o rtB y = S o r tC rite ria .S iz e T h e n K in d ;


d u c k s .S o rt(c o m p a re r);
P rin tD u c k s (d u c k s );
408 Rozdział 8.
*
Typy wyliczeniowe i kolekcje

Stwórz pięć losowych kart i posortuj je.

Ćwiczenia

O NAPISZ KOD, KTÓRY UTW ORZY LOSOWY ZESTAW KART.


U tw ó rz now y p ro je k t Console Application, a następnie dodaj do m etody M a in () kod, k tó ry stw orzy pięć
losowych o b ie któ w Card. Po u tw o rze n iu każdego z nich użyj w budow anej m etody C o n s o le .W r ite L in e ( ) ,
aby wypisać jego nazwę na wyjściu. D o d a j na końcu w yw ołanie m etody C o n s o le .R e a d K e y (), by okn o nie
zniknę ło po zakończeniu program u.

^ UTWÓRZ KLASĘ, KTÓRA IMPLEMENTUJE ICOMPARER<CARD > DO SORTOWANIA KART.


Masz w spaniałą okazję, aby użyć skrótu, k tó ry im p le m e n tu je in te rfe js w ID E :

p u b lic c la s s CardComparer_byValue : IComparer<Card>


K lik n ij teraz IC om parer i przesuń kurso r na I. Zobaczysz pojaw iający się poniżej pro sto ką tn y obszar.
K ie d y w niego klikniesz, I D E w yśw ietli okno:

Czasami wyśw ietlen ie tego okienka


może pi-zysporzyć pewnych problemów,
dlatego te ż ID E udostępnia specjalny
sk rót, który ułatwia to zadanie
— w ysta.rczy nacisnąć kombinację
klaw iszy Ctrl+. (kropka).

Jeżeli kliknie sz Implement interface 'ICom parer<Card> ', I D E autom atycznie wstaw i do klasy wszystkie m etody
i właściwości, k tó re musisz zaim plem entować. W tym przypadku u tw o rzy pustą m etodę C o m p a re (), k tó ra
posłuży do porów nyw ania dwóch kart, x i y. U z u p e łn ij ją tak, aby zw róciła 1, je że li ka rta x jest większa niż y,
-1 , gdy jest mniejsza, a 0, je że li są to te same karty. U p e w n ij się, że każdy k ró l będzie um ieszczony za w aletem .
Każdy w a le t z k o le i p o w in ie n znaleźć się za czwórką, a całość za dow olnym asem.

S UPEWNIJ SIĘ, ŻE W YN IKI SĄ PRAWIDŁOWE.


T a k m niej więcej po w in n o wyglądać okno pro g ra m u po k lik n ię c iu przycisku.

Kiedy używ asz wbudowanej


metody C onsole.W riteLine(), file:///c:/Users/Public/Documents..
Obiekt ICom parer
(do tego okna dodawany j e s t Pi^c losowych kart: m usi posortować
w iersz te k stu . D zięki wywołaniu King of Diamonds
karty na podstaw ie
metody Console.ReadKey() Queen of Spades
Five of Diamonds ich w artości, tak
program będzie czekał na aby te z mniejszymi
Three of Diamonds
nac iśn ię cie klawisza, zanim Jack of Diamonds znajdowały s ię na
s i ę z a kończy i zamknie okno. początku listy .
Te same karty posortowane:
Three of Diamonds
Five of Diamonds
Dack of Diamonds
Queen of Spades
King of Diamonds

jesteś tutaj ► 409


Popatrz na to

Stwórz pięć losowych kart i posortuj je.


Tutaj znajduje się catu „siln ik "
sortowania kart, który używa
igu dow a n ej metody L is t.S o rtO .
Rozwiązania S o rt() pobiera obiekt ICom parer,
ćwiczeń który ma jedn ą metodę:
CowpareO. Ta implementacja
p u b lic c la s s CardComparer_byValue : IComparer<Card> { k o tT n Z ^ 1 w P ie s z e j
kolejności porównuje ich w artości
p u b lic i n t Compare(Card x , Card y ) { a następnie kolor.
Jeżeli x ma w iększą if (x .V a lu e < y .V a lu e ) {
warto ść, zw róć 1.
re tu rn -1 ;
J e ż e li w artość x j e s t
mnie jsz a , zwróć —1.
Pamię taj , każda z tych if (x .V a lu e > y .V a lu e ) {
instru k c ji return
natychm iast kończy re tu rn 1;
metodę.
Te instrukcje wykonywane

\
if ( x . S u it < y . S u it ) { leżeli x i W m ają tę sam ą
re tu rn -1 ;
}
if ( x . S u it > y . S u it ) {
T a r!? * , oo “ S * “
re tu rn 1; _ wrześnie jszych instrukcji
re tu n f riie została wykonana.
}
re tu rn 0;
Je ż e li ż adna z instru kcji retu rn
nie została wy konana, oznacza to,
że karty m uszą być takie sam e —
zw racane j e s t zero.
To j e s t lista generyczna
s t a t ic v o id M a in ( s tr in g [] args) obiektów Card, która
przechowuje karty.
{ Kiedy ju ż s ię na niej
Random random = new Random(); znajdą, sortow anie ich
C o n s o le .W rite L in e ("P i§ c losowych k a r t : " ) za pomocą IComparer
je s t proste.
L ist< C a rd> cards = new L is t< C a rd > ();
f o r ( i n t i = 0 ; i < 5; i++)
{
cards.Add(new C a rd ((S u its )ra n d o m .N e x t(4 ),
(V a lu e s )ra n d o m .N e x t(l, 1 4 ) ) ) ;
C o n s o le .W rite L in e (c a rd s [i].N a m e );
}
Metody Console,ReadKey() używamy, by aplikacja
konsolowa nie została zamknięta po wykonaniu
C o n s o le .W rite L in e (); tego, co miała zrobić. Rozwiązanie to jest
C o n s o le .W rite L in e ("T e same k a r ty po so rto w a n e :1 doskonałe podczas nauki, lecz zupełnie się nie
sprawdza w przypadku tworzenia prawdziwych
c a rd s .S o rt(n e w C ardC om parer_byV alue());
programów uruchamianych z poziomu wiersza
fo re a c h (Card card in cards) poleceń. Jeśli chcesz, to naciśnij w IDE kombinację
{ klawiszy Ctrl+F5 — program zostanie uruchomiony
C o n so le .W rite L in e (ca rd .N a m e ); bez debugowania, a po jego zakończeniu IDE
wyświetli komunikat „Press any key to continue..."
} ( Naciśnij dowolny klawisz, by kontynuować...)
C onsole.R eadKey(); m ----------------------------------------- i będzie czekać na naciśnięcie dowolnego
klawisza. Pamiętaj, że w tym przypadku nie jest
uruchamiany debugger, a zatem punkty przerwania
i obserwowanie zmiennych nie będą działać.

410 Rozdział 8.
Typy w yliczeniow e i kolekcje

Przesłonienie metody ToString() pozwala obiektom przedstawiać się


K ażdy o b ie k t w p la tfo rm ie .N E T posiada m etodę T o S t r in g ( ) , k tó ra k o n w e rtu je go n a ła ń c u c h znaków . D om yślnie
zwraca ona nazwę klasy (M y P ro je c t.D u c k ). M e to d a ta jest dziedziczona po klasie O b je c t (pam iętaj, że jest to klasa
bazowa wszystkich klas w .N E T ). Jest ona naprawdę użyteczna i bardzo często stosowana. N a p rzykła d o p e ra to r + służący
do kon katen acji łańcuchów znaków w yw ołuje ją a u to m a ty c z n ie . R o b ią to także m etody C o n s o le .W r ite L in e ( ) oraz
S t r in g . F o r m a t ( ) , gdy przekażesz do nich jakiś o b ie k t — rozw iązanie to może okazać się naprawdę wygodne, szczególnie
gdy chcesz zam ienić o b ie k t na łańcuch znaków.
W ró ćm y zatem do naszego pro g ra m u do sortow ania kaczek. U m ieść p u n k t przerw ania w m etodzie M a in () w dow olnym
m iejscu za in icja liza cją listy, po czym uru cho m debugger. U m ieść w skaźnik myszy na d o w o ln e j z m ie n n e j ducks,
ta k by p o ja w iło się okno z je j w artością. Z a każdym razem , gdy w debuggerze sprawdzasz zm ienną zawierającą referencję
do listy, możesz w yśw ietlić je j zawartość, klika ją c m ały znak „ + ” :
A zatem zam iast
przekazywać
0 0 d u c k s {M y P ro je c t.D u c k [6 }}
w wywołaniach
0 ^ [0 ] {M y P ro je c t.D u c k } metod Console.
0 # [1 ] {M y P ro je c t.D u c k } W riteLin e() lub
Kiedy ID E w yśw ietla obiekt w oknie Watch, wy w° łuje 0 M [2 ] {M y P r o je c tD u c k } S trin g .F °rm a t()
metodę T °S trin g (). M etoda ta °dziedzicz°na. przez klasę
Duck p ° klasie O bject zwraca jednak j e dy nie na.zwę kla sy■
0 M [3 ] {M y P ro je c t.D u c k } (i innych) w a rt°śc i,
0 M [4 ] {M y P ro je c t.D u c k } możesz przekazać
M etoda T °S trin g () zwracająca w ięcej inf ° m acji ° ° b ie k c it
0 ^ [5 ] {M y P ro je c t.D u c k } obiekt — spowoduje
mogłaby być naprawdę bardzo użytecxna..
to automatyczne
wywołanie jego
H m , ta m etoda nie jest aż ta k przydatna, ja k tego oczekiwaliśm y. M ożem y jedynie zobaczyć, że na liście metody T°S trin g ().
znajduje się sześć o b ie któ w Duck (M y P ro je c t to używana w pro gra m ie przestrzeń nazw). Jeśli k likn ie m y Rozwiązanie t°
działa analogicznie
przycisk „ + ” um ieszczony z lewej strony każdego z nich, to będziem y m o g li zobaczyć w artości jego p ó l
w przypadku
K in d i S iz e . Czy nie byłoby je d n a k w ygodniej, gdybyśmy m o g li obejrzeć je wszystkie naraz? przekazywania
typów
N a szczęście m etoda T o S t r in g ( ) klasy O b je c t jest m etodą w irtu a ln ą . A zatem jedyne, co m usim y wartościowych,
zrobić, to ją prze sło nić — kie dy ju ż to zrobim y, w y n ik i zobaczymy natychm iast w okn ie W atch ID E ! takich ja k int, oraz
O tw ó rz klasę Duck i zacznij dodawać nową m etodę, w pisując o v e r r id e . G dy ty lk o naciśniesz klawisz wyliczeniowych.
spacji, I D E w yśw ietli okie n ko ze w szystkim i m etodam i, któ re możesz przesłonić:

override
Equals(object obj)
GetHashCodeO
iToStringO 1 string object.ToStringO
Returns a string that represents the current object.

K lik n ij m etodę T o S t r in g ( ) , by p o in fo rm o w a ć ID E , że chcesz dodać właśnie ją. Zastąp je j zawartość tak,


ja k pokazaliśm y poniżej.

public override s trin g ToString()


{
return S ize + "-centymetrowa kaczka " + K in d .T o S trin g ();
}

U ru ch o m program i po no w nie przyjrzyj się liście. T eraz I D E w yśw ietla zawartość o b ie któ w Duck!

Kiedy debugger ID E pokazuje ja k iś


obiekt, wywołuje jego metodę
wy
() i w
ToSfringO wyświe
yśw ietla zwrócony przez
nią łańcuch znaków.

jesteś tutaj ► 411


Pętle foreach

Zmień pętle foreach tak,


by obiekty Duck i Card same się opisywały
Poznałeś ju ż dwa różne program y, k tó re przeglądały listę o b ie któ w i dla każdego z nich w yśw ietlały jakiś
ko m u n ika t, używając do tego celu m etody C o n s o le .W r ite L in e ( ) . O to jeszcze jeden p rzykład p ę tli fo re a c h
w yświetlającej wszystkie ka rty na liście L is t< C a rd > :

fo re a c h (Card card in card s)


{
C o n s o le .W rite L in e (c a rd .N a m e );

} Mo±e s z f cikże pominąć


wy wołanie " . ToString()",
M e to d a P r in tD u c k ( ) spełniała podobne zadanie, wyśw ietlając umieszczone na liście o b ie kty Duck: C operator + wy w° t a
tę mefodę autom atycznie.
fo re a c h (Duck duck in ducks)
{
C o n s o le .W rite L in e (d u c k .S iz e .T o S trin g () + "-ce n tym e tro w a kaczka " + d u c k .K in d .T o S tr in g ( ) ) ;
}

Często postępujem y w ten sposób z obiektam i. Jednak teraz, kie dy nasza klasa Duck dysponuje ju ż m etodą
T o S t r in g ( ) , pow in niśm y ją w ykorzystać w m etodzie P r in tD u c k s ( ) :

p u b lic s t a t i c v o id P rin tD u c k s (L is t< D u c k > ducks) {


fo re a ch (Duck duck in ducks) {
C o nso le.Wr it e L in e ( d u c k ) ; J e ś li w wywołaniu metody C onsole.W riteLin e()
} przekażesz referencję do ° b iektu, to automatycznie
^ C o n s o le .W rite L in e ("K o n ie c k a c z e k !" ) ; ^ y ^ ła ona jego metodę ToString().

D o da j tę m etodę do swojego pro g ra m u i po no w nie go uruchom . W yśw ietlone w y n ik i będą takie same. Teraz,
gdybyś kiedyś chciał, na przykład, dodać do o b ie ktu Duck właściwość Gender, wystarczy zaktualizow ać m etodę
T o S t r in g ( ) , a zm iany zostaną uw zględnione we wszystkich miejscach, w ja kich jest ona używana (w tym także
w m etodzie P r in tD u c k s ( ) ) .

Dodaj metodę ToString () także do kiasy C ard sposób,

T w o ja klasa Card posiada ju ż właściwość Name zwracającą nazwę karty: f 0 korie t t n e^ b i Zop e ra torS *+“
wy wołuj e ją autom atycznie.
p u b lic s t r in g Name
{
g e t { r e tu r n V a lu e .T o S trin g () + " o f " + S u it . T o S t r in g ( ) ; }
}

W łaśnie ta k p o w in na działać m etoda T o S t r in g ( ) . A zatem dodajm y ją do klasy Card:

p u b lic o v e rr id e s t r in g T o S tr in g () Ła tw ie jsz a identyfikacja obiektów w ID E to nieje d yny


{ powód, dla którego metoda ToString( ) j e s t niezwy kle
użyteczna. Uważaj podczas lektury kilku kolejnych
r e tu r n Name; rozdziałów, a przekonasz s ię , ja k bardzo przydatna j e s t
} możliwość konwertowania obiektów na łańcuch znaków.
To w łaśnie te powody spraw iają, ż e metoda ta je s t
T eraz ła tw iej C i będzie debugować o b ie kty Card. dostępna w każdym o b ia c ie .

412 Rozdział 8.
Typy wyliczeniowe i kolekcje

Pisząc pętlę foreach, P ę t l ęę fl o r e a c h

używasz IEnumerab!e<T> p o d lu p ą

Przejdź do ID E , znajdź w n im zm ienną L is t< D u c k > i skorzystaj z IntelliSense, In icja liza to ry kolekcji działają
na dowolnych obiektach
by przyjrzeć się je j m etodzie G e tE n u m e ra to r(). Z acznij wpisywać „.G e tE n u m e ra to r” IEnumerable<T> — o ile tylko
i zobacz, co się pojaw i: dysponują one metodą A dd().

d u c ks .G e tE n
GetEnumerator List< Duck> .Enum erator List<Duck> .GetEnumeratorO
Returns an enum erator th a t iterates th ro u gh th e System.Collections.Generic.List<T>.

D o d a j wiersz tw orzący nową tablicę o b ie któ w Duck:

D uck[] d u ckA rray = new D u c k [6 ];


N astępnie wpisz d u c k A rra y .G e tE n u m e ra to r — także tablice dysponują tą m etodą.
D zie je się ta k dlatego, że zarówno listy, ja k i tablice im p le m e n tu ją in te rfe js IE num erable<T >,
k tó ry zawiera ty lk o je dn ą m etodę — G e tE n u m e ra to r() — zwracającą o b i e k t E num erator.

T o właśnie ten o b ie k t pozw ala na przeglądanie listy w ja kiejś kolejności. Poniżej pokazaliśm y
p rzykład p ę tli fo re a c h przeglądającej listę L is t< D u c k > i przechowującej aktualnie
analizow any elem ent w zm iennej duck. Kiedy kolekcja
fo re a c h (Duck duck in ducks) { implementuje
Console.WriteLine(duck);
interfejs
}
A o to faktyczny, u k ry ty przed program istą w ygląd tej samej pętli:
IEnumerable<T>,
daje Ci możliwość
IEnumerator<Duck> enum erator = d u cks.G e tE n u m e ra to r();
w h ile (en u m e ra to r.M o ve N e xt()) {
przejrzenia
Duck duck = e n u m e ra to r.C u rre n t; swojej zawartości
Console.WriteLine(duck); w określonym
} porządku.
ID is p o s a b le d is p o s a b le = enum erator as ID is p o s a b le ;
if (d is p o s a b le != n u ll) d is p o s a b le .D is p o s e ();
Formalnie rzecz b io rą c to j e sz c z e
(N a razie nie zaprzątaj sobie głowy dw om a osta tn im i w ierszam i tego kodu. nie w szystko, ale pewnie złapałeś ,
In te rfe js ID is p o s a b le poznasz do kła dniej w rozdziale 9.). o co chodzi...

D w ie przedstawione powyżej pętle w yśw ietlają te same kaczki. Możesz sam się o tym przekonać,
kie dy je uruchom isz — obie w yśw ietlą identyczne w yn iki.

A o to co się w nich dzieje. K ie d y przeglądasz zawartość listy lu b tab licy (bądź ja k ie jk o lw ie k innej ko le k c ji),
m etoda M ove N e xt() zwraca t r u e , je śli istnieje ko le jn y elem ent, w przeciw nym razie zwracana jest wartość
f a ls e . W łaściwość C u rre n t zawsze zwraca referencję do bieżącego elem entu.
D o d a j to wszystko razem, a otrzym asz pętlę fo re a c h !
Sprób uj poeksperym entować, modyfikując metodę ToStringO klasy Duck w taki sposób, by inkrementowała
w łaściw ość S iz e . Uruchom debugger i wskaż zmienną Duck m yszką. N astępnie wskaż ją ponownie.
Pam iętaj, że za każdym razem, gdy to robisz, ID E wywołuje metodę ToStringO obiektu.
Jak myślisz, co by się stało, gdyby w trakcie wykonywania p ę tli fo r e a c h
metoda ToStringO zm ieniała jedno z pól obiektu?

jesteś tutaj ► 413


Nie m a tu nikogo oprócz nas — kaczek

Używając lEnumerable, możesz rzutować całą listę wgórę


Czy pam iętasz w ja k i sposób możesz rzutow ać o b ie k t w górę, do jego klasy bazowej? O tó ż kiedy
dysponujesz listą ob ie któ w , możesz rzutow ać w górę ją całą. M ożliw ość ta nosi nazwę k o w a r i a n c j i ,
a jedynym elem entem niezbędnym do je j uzyskania jest in te rfe js IEnum erable<T>.
Z r ó b to !
Utwórz nowy proj ekt Console Application i dodaj do niego klasę bazową B ir d
(k tó rą klasa Duck będzie rozszerzać). Następnie u tw ó rz klasę Penguin. D o każdej
z klas dodam y m etodę T o S tr i ng ( ) , któ ra u ła tw i nam rozpoznawanie obiektów .
7 *
c la ss Bird {
public s trin g Name { get; s e t; )
public v irtu a l void F ly () {
C o n s o le .W rite L in e (" F rr... f r r . . . " ) ;
)
public override s trin g ToString () {
return "Ptak " + Name;
)
)

c la ss Penguin : Bird D j ® ird oraz Penguin. k tó a p o niej dziedziczy


do no, we?° proj ektu typu Console A pplication
public override void F ly () { a ™ stępnie, skopiuj do niego klasę Duck. Zm ień iei '
Console.W riteLine("Pingw iny nie l a t a ją ! " ) ; deklarację, by także ona dziedziczyła po B i rd.
}
public override s trin g ToStrin g () {
return "Pingwin " + base.Name; cla ss Duck : Bird , IComparable<Duck> {
} / / Reszta klasy je s t taka sama ja k wcześniej
S ko p iu j tu
ten sam
inicjalizator
Poniżej przedstaw iliśm y k ilk a początkow ych wierszy m etody M a in ( ) , k tó re in ic ja liz u ją listę i r z u t u j ą j ą w g ó r ę . kolekcji,
którego
List< D uck> ducks = new L is t< D u c k > () { / * zw yczajna i n i c j a l i z a c j a l i s t y * / } używ ałeś
IE num erable< B ird> upcastDucks = ducks; do utworzenia
lis ty kaczek.
P rzyjrzyj się uw ażnie dru giem u w ierszow i kodu. P obieram y w n im referencję do listy L is t< D u c k > i przypisujem y ją
do zm iennej typ u IE n u m e ra b le < B ird > . M ożesz sprawdzić w debuggerze, że wskazuje ona na ten sam obiekt.

Umieść wszystkie ptaki na jednej liście


K ow aria ncja jest bardzo przydatna w sytuacjach, gdy chcemy zgrom adzić kolekcję o b ie któ w i um ieścić je na jednej bardziej
ogólnej liście. O to przykład: je śli dysponujesz listą o b ie któ w B ir d , to możesz do niej dodać o b ie k t Duck, w ykonując jedną
prostą operację. W poniższym kodzie zastosowaliśmy m etodę L is t.A d d R a n g e (), któ re j możesz użyć, by dodać zawartość
jednej listy do innej.

L is t< B ir d > b ir d s = new L is t< B ir d > ( ) ;

b ird s .A d d (n e w B ir d ( ) { Name = "F e a th e rs " });


b ird s.A d d R a n g e (u p ca stD u cks);
b ird s .A d d (n e w P engu in() { Name = "George" })

fo re a ch ( B ird b ir d in b ir d s ) {
C o n s o le .W rite L in e ( b ird );
Kiedy kaczki zo sta ły _
} ju ż zrzutowane w górą
do I£numerable<8ird>,
możemy je dodać
do lis ty obiektów Bird.

414 Rozdział 8.
Typy wyliczeniowe i kolekcje

Możesz tworzyć własne przeciążone metody tno ^ sz folć e użyć ^ instrukcji using.
Jeśli chcesz Z w ied zieć się czegoś
D o tej p o ry używałeś p r z e c i ą ż on y c h m e t o d , a nawet przeciążonych ko n stru kto ró w , poświęć k i lica mi m t^ y iz a jr z e ć W o
któ re były częściami wbudowanych klas i o b ie któ w .N E T F ram ew ork. M ożesz ju ż punktu trzecieg° dodtntku „Pozostałości"
umieszczonego na końcu książki.
ocenić, ja k bardzo są one użyteczne. Czy nie byłoby świetnie, gdybyś m óg ł tworzyć
m etody przeciążone we własnych klasach? W zasadzie możesz — i jest to naprawdę
łatw e! M usisz tylko napisać dwie lu b więcej m etod, k tó re będą posiadały tę samą
nazwę, ale inne param etry. Z ró b to !

(0 Utwórz nowy projekt aplikacji konsolowej i dodaj do niego klasę C ard .


Możesz to ła tw o zrobić, klika ją c praw ym przyciskiem myszy w okn ie Solution Explorer i wybierając Existing
Item z m enu A d d . I D E u tw o rzy kop ię klasy i doda ją do p ro je ktu . P lik będzie m ia ł w d a l s z y m c i ą g u tę s a m ą
p r z e s t r z e ń n a z w co w s t a r y m pr o j e k c ie . Przejdź w ięc do górnej części p lik u Card.cs i zm ień wiersz namespace
tak, aby był zgodny z nazwą nowego. N astępnie, w ten sam sposób, dodaj typy w yliczeniow e V a lu e s oraz S u it s .
Jeżeli tego nie zrobisz, to będziesz mógł uzy s k°ć dostęp_do
klasy Cord tylko poprzez wyszczególnienie j ej przestTzeni
Dodaj do klasy Card kilka przeciążonych metod. nozw (no przy kłod s ^ m ^ s ^ nN^ w.^ d).
U tw ó rz dw ie m etody s t a t i c D o e sC a rd M a tch (). Pierwsza będzie sprawdzać k o lo r k a rt, a druga ich wartość.
O bie po w in ny zwracać t r u e , je że li k a rta spełnia zadany warunek.

p u b lic s t a t i c bool DoesCardMatch(Card CardToCheck, S u its S u it) {


if (C ardT oC heck.S uit == S u it) {
r e tu r n t r u e ; Metody przeciążone nie muszą być
} e ls e { statyczne, ale dobrze je s t nobroć
re tu r n f a ls e ; " pisoniu włośnie takich.
}
}
p u b lic s t a t i c bool DoesCardMatch(Card CardToCheck, V alues V a lue ) {
if (CardToCheck.Value == V alue) {
r e tu r n t r u e ;
} e ls e {
r e tu r n f a ls e ;
}
}

. 3) Dodaj do formularza przycisk, który będzie używał powyższych metod.


W m etodzie M a in () w p lik u Program.cs um ieść następujący fragm e nt kodu:
Card cardToCheck = new C a rd (S u its .C lu b s , V a lu e s .T h re e );
bool d o e sItM a tch = Card.DoesCardM atch(cardToC heck, S u its .H e a r ts ) ;
M essageBox.Show (doesItM atch);

Z araz po w pisaniu DoesC ardM atch( I D E p o in fo rm u je Cię, że rzeczywiście utw orzyłeś przeciążoną m etodę:

C a rd . DoesCardMatch (|
A 1 of 2 ▼ b o o l C a rd .D o e sC a rd M a tch (C a rd cardToCheck, Suits su it) I

Poświęć chw ilę na eksperym entowanie z tym i dwoma m etodam i, aby przyzwyczaić się do przeciążania.

jesteś tutaj ► 415


Cała ta lia w ręku

Zdobądź nieco praktyki w posługiwaniu się listami — utwórz klasę umożliwiającą


przechowywanie talii kart oraz formularz, który będzie z niej korzystał.

Ćwiczenia
UTW ÓRZ FORMULARZ, KTÓRY POZWOLI
N A PRZENOSZENIE KART POMIĘDZY DW OMA TALIAM I.
Klasę k a rty utw orzyłeś ju ż wcześniej. Nadszedł czas na stworzenie klasy do przechowywania dow olnej ich liczby.
N azw iem y ją Deck. W rzeczywistości ta lia ma 52 karty, ale klasa Deck może przechowywać każdą ich liczbę.
M oże także nie m ieć żadnej karty.
Następnie stworzysz fo rm u la rz, k tó ry będzie pokazyw ał zawartość dwóch o b ie któ w Deck. Podczas pierwszego
uru ch o m ie n ia p ro g ra m u zestaw 1. będzie posiadał do 10 losowych kart, a zestaw 2. będzie zaw ierał pełną ich
talię, czyli 52 karty. O ba zestawy będą posortow ane w e dłu g k o lo ru oraz w artości — będziesz m óg ł przyw rócić
początkow y stan każdego z nich, klika ją c jeden z dwóch przycisków Przywróć. F o rm u la rz będzie także posiadał
przyciski (nazwane o d po w ie dn io „ < < ” oraz „ > > ” ) służące do przenoszenia k a rt pom iędzy zestawami.
P rzyciski te m ają na stęp ują ce nazwy: moveToDeck2 (górny) i _moveToDecki
(dolny). P rzem ieszczają one karty z jedn ego zestaw u do drugiego.
A b y pokazać zaw artość zestaw ów ,
M ożesz u żyć w łaściw ości Name użyj kontrolek Li'stB ox. Po kliknięciu
przycisków w celu określenia przy cisk u moveToDeckl wybrana
k<xrta. z zes t awu 2 . j e s t przenoszona
ich n azw i ułatw ienia an alizy
do zesta w u 1.
kodu. Po dwukrotnym
kliknięciu p rzycisku funkcja
obsługi zdarzenia przyjm ie
Te przyciski nazwano shufflel
odpowiednią nazw ę. i sh u ffle2 . Wywotują one metodę
D eck.ShuffleO odpowiedniego
zestawu i przerysowują ten zesta .
Każdy z przycisków re se tl
i re se t2 w p ierw sze j
k o h jn o śd iwywotuje metodę
ResetD eck(), a następnie
RedrawDeck().

O prócz procedur obługi zdarzeń dla sześciu przycisków będziesz jeszcze potrzebował dwóch m etod dla formularza.
N ajpierw dodaj m etodę R e se tD e ck(), która będzie przywracać początkowy stan zestawu. Jako param etr przyjmie
i n t : jeżeli zostanie przekazana liczba 1, przywracany będzie początkowy stan zestawu 1. Sprowadzać się to będzie
do wyczyszczenia listy i wylosowania do 10 kart. G dy przekazana zostanie liczba 2, przywracany będzie stan zestawu
2. — drugi ob ie kt Deck będzie znów zawierał pełną talię 52 kart. Teraz dodaj taką metodę:
p u b lic v o id RedrawDeck( i n t DeckNumber) {
i f (DeckNumber == 1) {
Z wróć uw agę lis tB o x 1 . I te m s .C le a r ( ) ; Metoda RedrawDeck()
aktualizuje dwie
wykorzyót an ia foreach. ( s t r in g cardName in deck1.G etCardNam es()) kontrolki L is tB o x bez
lis tB o x l.Ite m s .A d d (c a rd N a m e ); względu na zaw artość
pętli foreach - ^
w ce lu dodania la b e ll. T e x t = "Zestaw 1. (" + d e c k l.C o u n t + " k a r t ) " ; obiektów Deck.
każdej karty } e ls e {
z zestaw u do lis tB o x 2 . I te m s .C le a r ( ) ;
listy . fo re a ch ( s t r in g cardName in deck2.G etCardNam es())
M stB o x2 .Ite m s.A d d (ca rd N a m e );
la b e l2 .T e x t = "Zestaw 2. (" + deck2.C ount + " k a r t ) " ;

416 Rozdział 8.
Typy wyliczeniowe i kolekcje

D eklarację klasy bez imp lementacji


Utwórz klasę Deck. nazywamy szkieletem.
Poniżej zaprezentowano szkielet klasy Deck. K ilk a m e to d u zu pe łniliśm y za Ciebie. M usisz dokończyć p ro je kt,
w ypełniając m etody S h u f f l e ( ) i G etC ardN am es(), oraz zm usić do działania m etodę S o r t ( ) . D o da liśm y także
dwa pom ocne p r z e c i ą ż o n e k o n s t r u k t o r y : jeden tw orzy pełną ta lię 52 k a rt, d ru gi po b ie ra tablicę o b ie któ w Card
i wczytuje ją do zestawu.
Deck przechow uje karty w formie lis ty — aby zadbać Deck
o prawidł° wą hermetyzację , j e s t ona przechowywana Count
c la s s Deck { w skład° wej' pry watnej.
p r iv a t e L ist< C a rd > ca rd s ;
p r iv a t e Random random = new Random(); Add()
Je ż e li do konstruktora
nie przekażesz parametrów, Deal()
Param etr p u b lic Deck() { to tworzona będzie pełna talia GetCardNames()
konstruktora j e s t typu cards = new L is t< C a rd > (); 52 kart. Shufflef)
I£numerable<Card>,' f o r ( i n t s u i t = 0; s u i t <= 3 ; s u it+ + ) Sort()
dzięki czemu
f o r ( i n t v a lu e = 1; v a lu e <= 13; value++)
możemy przekazać
do niego dowolną cards.A dd(new C a r d ( ( S u it s ) s u it , (V a lu e s )v a lu e ))
toteteję lub tablicę, }
a nie jed y n ie obiekt Ten przeciążony konstruktor
List<T>. przyjm uje j e den param etr —
p u b lic Deck(IEnum erable<Card> in it ia lC a r d s )
taolicę kart, które wczytywane są
cards = new L is t< C a r d > ( in itia lC a r d s ) ; do początkowego zesta w u .
}
Podpowiedz: W ła ś c i w i
S elected In d ex konf-ro/W
p u b lic i n t Count { g e t { r e tu r n c a rd s .C o u n t; } } ListBox będzie taka
sam a ja k indeks karty
na liśc ie . M ożesz
p u b lic v o id Add(Card cardToAdd) {
p rzekazać j ą bezpośrednio
ca rd s.A d d (ca rd T o A d d ); do metody DeaK). J e ż eTi
M etoda Deal pobiera
} nie wybierzesz żadnej
jedn ą kartę z ze sta w u —
karty, metoda zw róci
u s u wa z niego obiekt Card
w artość m niejszą zera.
p u b lic Card Deal( i n t in d e x) { i zw raca jeg o referencję.
W takim przypadku
Card CardToDeal = c a r d s [in d e x ] ; M ożesz pobrać kartę
przycisk m°veT° Deck
z góry, przekazując 0,
ca rd s.R e m o ve A t(in d e x); nie powinien dzm tai.
lub ze ś r odka, przekazując
r e tu r n CardToDeal; indeks pobieranej karty.
}
Także w tym
przypadku,
choć metoda p u b lic v o id S h u ffle () {
GctCardNamesO / / Ta metoda ta s u je k a r ty , u sta w ia ją c j e w losow ej k o le jn o ś c i.
zwraca tablicą,
}
to Typ u/artości
wynikowej zo sta t
zadeklarowany jako p u b lic IE n u m e ra b le < s trin g > GetCardNames() {
IEnumerable<string>. / / Ta metoda zwraca t a b lic ę łańcuchów znaków za w ie ra ją cą nazwę każdej k a rty .
}
M usisz napisać metodę ShiuffhO,
metodę GetCardN am es() oraz d °dać
p u b lic v o id S o rt () { klasę implementującą IC °mparer,
c a rd s .S o rt(n e w C a rd C o m p a re r_ b y S u it()); aby uruchomić mechanizm
} sortowania. M usisz także dodró
klasę Card, którą ju ż wcześniej
} napisałeś. Gdy wybierzesz
Podpowiedź : Formularz zdecy dowanie ułatwia testow anie metody S h u ffle(). K lika Add Existin g Item , nie zapommj
przy cisk „ Przywróć zes t aw 1." tak długo, aż otrzym asz zesta w z trzema kartami o zmianie je j przestrzeni' maziw.
Pozwoh Ci to znacznie up ro ścn; obs erwacj ę i kontrolę działania funkcji losu jącej.

jesteś tutaj ► 417


Rozwiązanie ćwiczenia

Utwórz klasę do przechowywania zestawów kart wraz z formularzem, który będzie jej używał.

Rozwiązania
ćwiczeń Tu j e s t konstruktor, który tw orzy pełną
ta lię kart. Używa do tego zagnieżdżonej
c la s s Deck { w nim pętli for. Pętla zewnętrzna
p r iv a t e L ist< C a rd > c a rd s ; przechodzi przez kolory. Oznacza to,
p r iv a t e Random random = new Random(); że wewnętrzna przechodzi przez 13
w artości cztery razy, raz dla każdego
koloru.
p u b lic Deck() {
cards = new L is t< C a rd > ();
f o r ( i n t s u i t = 0; s u i t <=3; s u it+ + )
f o r ( i n t v a lu e = 1; v a lu e <=13; value++)
cards.A dd(new C a r d ( ( S u it s ) s u it , (V a lu e s ) v a lu e ));
}
Tu je s t kolejny konstruktor
p u b lic Deck(IEnum erable<Card> in it ia lC a r d s ) { ta klasa posiada dwa przeciązon
cards = new L is t< C a r d > ( in itia lC a r d s ) ; konstruktory, każdy z innymi
} param etram i.

p u b lic i n t Count { g e t { r e tu r n c a rd s .C o u n t; } }

p u b lic v o id Add(Card cardToAdd) {


ca rd s.A d d (ca rd T o A d d );
}

p ro ste Ąd? ° ° raZ D eal() Są dość


p u b lic Card D e a l( in t in d e x) { n ZywcUą metod Ustu
Card CardToDeal = c a r d s [in d e x ]; Z + '■° eal() kartę
Z listy , natom iast A d d () dodaje
c a rd s.R e m o ve A t(in d e x);
r e tu r n CardToDeal;
}

p u b lic v o id S h u f fle ( ) { M et oda S h u ffteO tworzy nową instancję


L ist< C a rd > NewCards = new L is t< C a rd > (); L ist<Card> o nazwie NewCards .
N astępnie wy c iąga losowe karty
w h ile (c a rd s.C o u n t > 0) {
z p o la 'cards aż do momentu jego
i n t CardToMove = ra n d o m .N e x t(c a rd s .C o u n t); op róznienia i w sta wia j e do NewCards
NewC ards.Add(cards[C ardToM ove]); Po iwykmaniu zadania do pola cands
cards.Rem oveAt(CardToM ove); przyp isy wana j e s t nowa instancja.
} S ta ra traci w sz y stk ie w skazujące
cards = NewCards; na nią referencje, w ięc obiekt
z o sta je z akwalifikowany do procedury
} oczyszczania pam ięci.

p u b lic IE n u m e ra b le < s trin g > GetCardNames() {


s t r i n g [ ] CardNames = new s t r in g [c a r d s . C o u n t] ;
f o r ( i n t i = 0 ; i < c a rd s .C o u n t; i+ + )
CardNam es[i] = c a rd s [i].N a m e ;
r e tu r n CardNames; Twoj a metoda Ge tCardNam es() mus i
} u tworzyć tablicę w ystarczająco dużą
do przechowania nazw w szystkich
kart. Używa ona pętli for, ale równie
p u b lic v o id S o r t( ) {
fdoorberazceh mogłaby posługiw ać s ię pętlą
c a rd s .S o rt(n e w C a rd C o m p a re r_ b y S u it());
}

418 Rozdział 8.
Typy wyliczeniowe i kolekcje

c la s s CardCom parer_bySuit : IComparer<Card>


{
p u b lic i n t Compare(Card x , Card y)
{
Sortow anie na podstaw ie
if ( x . S u it > y . S u it )
koloru j e s t podobne do
re tu r n 1; so rtowania pod względem
if ( x . S u it < y . S u it ) w a rtości. Jed yn ą rnżrncą j e s t
to, że kolory porównywane
r e tu r n -1 ; s ą w p ierw sze j kolejnośc i,
if (x .V a lu e > y .V a lu e ) a warto ści tylko wtedy , gdy
re tu r n 1; kolory s ą takie sam e.

if (x .V a lu e < y .V a lu e )
r e tu r n -1 ;
Z a m ia st if/ e ls e if uży liśm y
re tu r n 0; zestaw u in strukcji if.
To działa, ponieważ każda
}
z nich j e s t wyk°nywana
tylko wtedy, gdy poprzednie
się nie wykonały —
w przeciwnym wypadku
wcz e śn ie jsza in stru kcja
Deck d e c k l; return kończy metodę■
Deck deck2;
Random random = new Random();

p u b lic Form1() {
In itia liz e C o m p o n e n t( ); Konstruktor formularza musi
R e s e tD e c k (l); ponownie u sta w ić dwa
R esetD eck(2); zesta w y kart i wy św ie tlić j e .
R edraw D eck(l);
RedrawDeck(2);
}

p r iv a t e v o id R e s e tD e c k (in t deckNumber) {
if (deckNumber == 1) {
i n t numberOfCards = random .N ext(1, ll) ;
deck1 = new Deck(new C a rd [] { });
fo r ( in t i = 0; i < numberOfCards; i+ + )
deck1.Add(new C a rd ((S u its )ra n d o m .N e x t(4 ),
(V a lu e s)ra n d o m .N e xt(1 , l4 ) ) ) ;
d e c k 1 .S o r t( ) ;
} e ls e
deck2 = new D e ck(); A b y przyw rócić zesta w 1 metnrln •
} wywołuje random N ext() uu ro i, u P.,ertvszej kolejności
« następnie tworzy ^ ^ ^ kart’
wylosowaną liczbę kart dodać do niego
M etoda RedrawDeck() się posortowaniem zestaw u PrS^ fo r- Działanie kończy
napisana została je s t łatwe - po p ro ttu Z o S y raCanie ze^ w u 2 . *
po p rostu tworzona j e s t instancja Deck.
w instrukcjach
do ćw iczenia.

-►Jeszcze nie skończyliśmy— przewróć stronę!

jesteś tutaj ► 419


Rozwiązanie ćwiczenia

Tu j e s t dalszy ciąg
Nadanie kontrolkom od po w ie dn ik nazM kodu formularza.
Rozwiązania zdecydowanie ułatwia czytanie Twojego kodu.
ćwiczeń — cd Gdubu nazywały s ię b u ttm T ^ H c b butt ° n2 _ C|ick
i tak dalej, nie w iedziałbyś, na tod którego
przycisku patrzysz!

p r iv a t e v o id re s e t1 _ C lic k ( o b je c t sen de r, EventArgs e) {


R e s e tD e c k (l);
R edra w D e ck(l);
)

p r iv a t e v o id re s e t2 _ C lic k ( o b je c t sen de r, EventArgs e) { Te przyciski s ą dość


R e setD e ck(2); pro ste — najpierw
przyw racają stan
RedrawDeck(2); ze sta w u lub ta su ją karty,
) a następnie w yśw ietlają
wyniki.

p r iv a t e v o id s h u ffle 1 _ C lic k ( o b je c t se n d e r, EventArgs e)


d e c k 1 .S h u ffle ( ) ;
RedrawDeck(1);
)

p r iv a t e v o id s h u ffle 2 _ C lic k ( o b je c t se n d e r, EventArgs e)


d e c k 2 .S h u ffle ( ) ;
RedrawDeck(2);
)

p r iv a t e v o id m ove T o D e ck1_C lick(ob je ct sen de r, EventArgs e) {


if (lis tB o x 2 .S e le c te d In d e x >= 0)
if (deck2.C ount > 0) {
d e c k 1 .A d d (d e c k 2 .D e a l(lis tB o x 2 .S e le c te d In d e x ));
)
R e d ra w D e ck(l);
RedrawDeck(2);
}

p r iv a t e v o id m ove T o D e ck2_C lick(ob je ct sen de r, EventArgs e) {


if ( lis tB o x l. S e le c t e d ln d e x >= 0) M ożesz użyć w łaściw ości
if (d e c k l.C o u n t > 0) { S e lectedIndex kontrolki ListBox
d e c k 2 .A d d (d e c k 1 .D e a l(lis tB o x 1 .S e le c te d In d e x )); w celu określenia karty wybranej
przez użytkownika, a następnie
} przen ieść ją z jednego zesta w u do
R edra w D e ck(l); drugiego (jeżeli indeks j e s t m niejszy
RedrawDeck(2); niż zero, oznacza to, że żadna karta
nie zosta ła wybrana i przycisk
nic nie zrob i). Po przen iesieniu
karty oba z esta w y m uszą zo sta ć
przerysow ane.

420 Rozdział 8.
Typy wyliczeniowe i kolekcje

Użyj słownika do przechowywania kluczy i wartości


O b ie kt L i s t jest ja k w ie lka strona pełna nazwisk. Co je d n a k w tedy, gdy do każdego z nich chcesz
m ieć jeszcze adres? Lu b gdybyś chciał dla każdego sam ochodu znajdującego się na liście w garażu
przechowywać dodatkow e szczegóły? Potrzebujesz sł o w n i k a . Pozwala on na przechowywanie
specjalnej w artości — k l u c z a — i pow iązanie go z zestawem danych — w a r t o ś c i ą . Jest jeden
w arunek: określony klucz może się w sło w niku p o j awi ć tylko r a z .

To jest klucz. Słownik


W/ ten właśnie - zbiór wyrazów ułożonych i opracowanych według
sposób szukasz
definicji pewnej zasady, zwykle objaśnianych pod względem
w słowniku. znaczeniowym.

To jest wartość. Są to dane


skojarzone z konkretnym kluczem.

S ło w n ik w C # deklaruje się w następujący sposób:

Dictionary <Tkey, TValue> kv = new Dictionary <TKey, TValue>();


To jest jak List<T>. <T> °znacza typ, który_ Te elementu reorezentuą
należy tam umieścić. Możesz zadeklarować ^ Pienusaof tuo zadsanu
¡„ d J d lz klucza, a inny „a
dotyczy kluczy, natomiast drugi
— wartości słownika.
A ta k wygląda D ic t io n a r y w akcji:

p riv a te void b u tto n l C lic k (o b je c t sender, EventArgs e)


{
Słownik posiada elementy typu
D ictio n a ry< strin g , strin g > w ordD efinition = string dla kluczy i dla wartości
M etoda AddO new D ictio n a ry< strin g , s trin g > (); P rz y p in a to zwykły słownik:
słowo i definicja.
pozwala na
d o d a w a n ie ^ --
kluczy wordDef1n1t^on.Add y lSłownikM, "z b ió r wyrazów ułożonych 1 opracowanych według pewnej 11
i wartości
do słownika. + "zasady, zwykle objaśnianych pod względem znaczeniowym");
wordDef1n1t1on.Add("Klucz", "metoda lub rzecz pozwalająca na " Metoda AddO
pobiera klucz,
+ "osiągnięcie lub zrozumienie czegoś"); ^ — a następnie wartość.
wordDef1n1t1on.Add("Wartość", "lic z b a określająca, 1le Jednostek zawiera dana wielkość
+ "fizyczna lub wlelkośó mogąca zastąpić wyrażenie algebraiczne");

i f (wordD efinition.C ontainsKey("Klucz")


MessageBox.Show(wordDefinition["Klucz"]); Metoda ContainsKeyO określa,
czy k lu c z znajduje s ię w s town.ku.
}
Przydatne, prawda?
> w ten sposób pobierasz

“ S ó r S o S ;™ j4 « o .9osie
pod tym indeksem .

jesteś tutaj ► 421


Klucze i w artości

Wybrane funkcjonalności słownika


S ło w n iki są podobne do lis t — oba narzędzia są elastyczne i pozw alają na pracę
z różn ym i typa m i danych. U d ostęp nia ją także dużą liczbę wbudowanych fun kcji.
O to k ilk a podstawowych m etod klasy D ic tio n a r y :

★ Dodanie elementu.
Możesz dodać do słow nika dany elem ent, przekazując klucz i w artość do jego m etody A d d ().

D ic tio n a r y < s t r in g , s t r in g > m y D ic tio n a ry = new D ic tio n a r y < s t r in g , s t r in g > ( ) ;


m y D ic tio n a ry .A d d (" ja k iś k lu c z " , "ja k a ś w a r to ś ć " ) ;

'A' Wyszukanie wartości na podstawie klucza.


Najważniejszą rzeczą, ja ką będziesz ro b ił ze słow nikiem , jest w yszukiwanie w artości — ma to sens, poniew aż
przechowujesz je w n im właśnie po to, aby uzyskać do nich dostęp przy użyciu unikatow ego klucza. W słow niku
zadeklarow anym ja k o D i c t i o n a r y < s t r in g , s t r in g > będziesz w yszukiw ał je na podstaw ie łańcucha znaków,
a pobierane warości także będą łańcucham i.

s t r in g lo oku pV alue = m y D ic tio n a r y [" ja k iś k lu c z " ] ;

★ Usunięcie elementu.
T a k ja k w przypadku listy, możesz usunąć elem ent ze słow nika za pom ocą m etody R em ove().
A b y z niej skorzystać, musisz ty lk o przekazać nazwę klucza. U su nię ty zostanie zarówno on,
ja k i jego wartość. Klucze w słowniku są unikatowe. Każdy z nich pojawia s ię
dokładnie raz. W artości mogą pojawiać s ię dowolną liczbę
m y D ic tio n a ry .R e m o v e ("ja k iś k lu c z " ) ; razy, a więc dwa klucze mogą m ieć tę sam ą w artość.
W ten sposób podczas wyszukiwania lub usuwania klucza
"K" Pobranie listy kluczy. słownik dokładnie wie, do czego ma s ię odwołać.
Możesz pobrać listę wszystkich kluczy słow nika przy użyciu właściwości Keys oraz przejrzeć je,
korzystając z fo re a c h . Z w ykle będziesz używ ał ko le k c ji kluczy w ta k i o to sposób:

fo re a ch ( s t r in g key in m y D ic tio n a ry .K e y s ) { ... } . . . „ ł„ wnik

★ Określenie liczby par w slow niku. s a r s i -s g w s s j « £ &


W łaściwość C ount zwraca liczbę p a r klucz-wartość, k tó re w danej ch w ili są przechowywane w słowniku.

in t howMany = m y D ic tio n a ry .C o u n t;

Klucze i wartości w słowniku mogą być różnych typów


S ło w n iki są naprawdę bardzo wszechstronne i m ożna w nich przechowywać n iem a l wszystko, zaczynając
od łańcuchów znaków i liczb, a na obiektach kończąc. Poniżej przedstaw iliśm y p rzykład słownika, którego
klucze są liczbam i całkow itym i, a w artości o b ie kta m i typ u Duck.
Słow niki kojarzące
liczby całkowite D ic tio n a r y < in t , Duck> d u c k D ic tio n a ry = new D ic tio n a r y < in t , D u ck>();
zczS k^ ćżna d u c k D ic tio n a ry . Add(376, new Duck()
w sytu acjach ,' gdy { Kind = K in d O fD u c k .M a lla rd , S ize = 15 } ) ;
tworzonym obiektom
przypisyw ane
s ą unikatowe
identyfikatory.

422 Rozdział 8.
Typy wyliczeniowe i kolekcje

Napisz program korzystający ze słownika


Poniżej przedstawiony został prosty program , któ ry na pewno spodoba się każdemu m iłośnikow i
drużyny baseballowej N ew Y o rk Yankees. K iedy ważny gracz przechodzi w niej na sportową emeryturę,
zatrzymuje num er koszulki. Napiszmy program , który będzie wyświetlał takie słynne numery,
nazwiska graczy, do których były przypisane, oraz rok, w którym dany gracz zakończył karierę.
Poniżej znajduje się kod klasy służącej do przechowywania info rm acji o numerach na koszulkach.
Sb to !
c la s s JerseyNumber {
r ' -
p u b lic s t r in g P la y e r { g e t; p r iv a t e s e t; }
p u b lic i n t Y e a rR e tire d { g e t; p r iv a t e s e t; }

p u b lic J e rs e y N u m b e r(s trin g p la y e r , i n t nu m be rR etire d) {


P la y e r = p la y e r ;
Y e a rR e tire d = nu m be rR etire d;
}
}
W jednej drużynie numer 8 miał Yogi Berra,
A o to form ularz: a w innej Cal Ripken, Jr. Jednak w obiekcie
Dictionary z konkretną wartością może
być powiązany tylko jeden klucz, dlatego
będziemy przechowywać numery koszulek
tylko jednej drużyny. Czy potrafisz wymyślić
sposób przechowywnia dla graczy z różnych
drużyn?
Poniżej znajduje się jego kod.

p u b lic p a r t i a l c la s s Forml : Form {


D ic tio n a r y < in t , JerseyNumber> re tire dN u m be rs new D ic tio n a r y < in t , JerseyNumber>() {
{3 , new JerseyNumber("Babe R u th ", 1 9 4 8 )},
{4 , new JerseyNum ber("Lou G e h rig ", 1 9 3 9 )},
{5 , new JerseyN um ber("Joe D iM agg io ", 1 9 5 2 )}, W ykorzystaj inicjaliza’tor
{7, new JerseyN um ber("M ickey M a n tle ", 1 9 6 9 )}, kolekcji, t y zapisd ć
{8 , new JerseyN um ber("Y ogi B e rra ", 1 9 7 2 )}, w słowniku obiekty
{1 0 , new Je rse yN u m b e r("P h il R iz z u to " , 1 9 8 5 )}, JerseyN um ber.
{2 3 , new JerseyNumber("Don M a t t in g ly " , 1 9 9 7 )},
{4 2 , new Je rse yN u m b e r("Ja ckie R o bin son ", 1 9 9 3 )},
{4 4 , new JerseyN um ber("R eggie Ja ckso n ", 1 9 9 3 )},
};
Każdy klucz ze słownika
p u b lic Form1() { za p isz w kolekcji Item s
In itia liz e C o m p o n e n t( ) ; kontrolki ComboBox.
fo re a ch ( i n t key in re tire d N u m b e rs .K e y s ) {
n u m b e r.Ite m s .A d d (k e y );
}
}

p r iv a t e v o id nu m be r_S ele cte dInd exC han ge d(o bje ct se n d e r, EventArgs e) {


JerseyNumber jerseyN um ber = re tire d N u m b e rs [(in t)n u m b e r.S e le c te d Ite m ] ;
nam eLabel.Text = je rs e y N u m b e r.P la y e r;
y e a rL a b e l.T e x t = je rs e y N u m b e r.Y e a rR e tire d .T o S trin g (); Wtaś.c iw o ść S e lec te d I’n!je x kontrolki
} C °mto>B ox j e s t typu O bject. Ponieważ
klucze naszego s townika mają być liczbami
ze zdarze~:~ typu int' zatem za nim skorzystam y z te j
k<° zy s tamy ze zdarzenia właściwości w celu pobrania obie ktu ze
^ słownika, m us imy z L t o n r t % ' w a ^ tć
ComboiB ox w ce lu aktualizacji e ty k ie t na typ int
na formularzu i w yśw ietlenia w nich
informac ji z obiektu JerseyN um ber
pobranego ze stownika. je s te ś t u t a j ► 423
Idź na ryby!

Długie ć w ic z e n i e ____________________________________________________________________________

H L Utwórz grę Idź na ryby!, w którą będziesz mógł grać przeciwko komputerowi

To ćwiczenie jest trochę inne...


Być może uczysz się C # , poniew aż chcesz zostać profesjonalnym program istą. T o m iędzy in n ym i dlatego
zaprojektow aliśm y to ćwiczenie w stylu profesjonalnym . K ie d y pracujesz ja ko pro gra m ista w większej grupie, zwykle
nie piszesz całego pro gra m u od początku do końca. Z am ia st tego tworzysz fragment większej całości. Z am ierzam y
w ięc pokazać C i k ilk a gotowych elem entów naszej u kład anki. Cały ko d fo rm u la rza znajduje się w k ro k u n u m er 3.
Musisz go ty lk o przepisać — wydaje się to dobrym sposobem na rozpoczęcie działań, ale oznacza równocześnie,
że T w o je klasy będą musiały z tym kodem współpracować. T o jest do piero praw dziw e wyzwanie!

R O Z P O C Z N IJ M Y O D SPECYFIKACJI.
Każdy pro fe sjo nalny p ro je k t in fo rm atyczny rozpoczyna się od specyfikacji i nie m a tu ta j żadnych w yjątków . Będziesz
tw orzył klasyczną grę karcianą I d ź n a ryby! R ó żn i lu dzie grają w nią na nieco odm iennych zasadach. Poniżej
zaprezentowano reguły, do których będziesz się stosował:

★ G ra rozpoczyna się z pełną ta lią pięćdziesięciu dwóch kart. Każdem u graczowi


rozdaje się po pięć. Zestaw k a rt, k tó ry pozostaje po rozdaniu, nazywamy p u l ą . Jeżeli, jeszcze
Gracze rozpoczynają kolejkę , żądając w artości („C z y masz jakieś sió de m ki?” ).
Każdy, k to trzym a takie karty, m usi je oddać. Jeżeli n ik t ta kie j nie ma, to gracz
zanim zaczniesz,
m usi „iść na ryby” i wyciągnąć kartę z puli. nie wiesz,
★ Celem gry jest u tw orzenie grup. G ru p a składa się z wszystkich czterech k a rt o tej co tw o rz y s z ,
samej w artości. G racz z największą liczbą grup na końcu jest zwycięzcą. G dy
uczestnik gry uzbiera grupę, kładzie ją na stole, aby wszyscy in n i gracze m og li t o skąd będziesz
w idzieć, k to ja k ie grupy w danej ch w ili posiada.
wiedział, że już
★ K ie d y gracz umieszcza na stole grupę, to może m u zabraknąć kart. Jeżeli ta k się
skończyłeś?
stanie, m usi pobrać pięć z ku p ki. Jeśli na kupce znajduje się m niej niż pięć kart,
w tedy p o biera wszystkie. G ra kończy się z chw ilą wyczerpania p u li. Zwycięzcą To dlatego
jest ten, k to do tego czasu zgrom adził największą liczbę grup.
większość
★ K om p u te ro w a w ersja Idź na ryby! um o żliw ia grę dwóch graczy kom puterow ych
i jednego zwykłego. Każda run da rozpoczyna się od w ybrania przez profesjonalnych
rzeczywistego gracza jednej z k a rt, któ re trzym a w ręce — są one w yśw ietlane
programów
przez cały czas. D o ko n u je on tego poprzez zaznaczenie swojej karty
i po tw ie rdzen ie w yboru. N astępnie każdy z dwóch graczy kom puterow ych także kom puterow ych
żąda jednej wartości. W y n ik i każdej ru n d y są wyświetlane. Cała procedura
pow tarzana jest aż do w yłonien ia zwycięzcy. rozpoczyna się
★ G ra będzie m usiała obsłużyć całą w ym ianę k a rt i autom atyczne wykładanie
od specyfikacji,
grup. Zakończy się ona w yłonien iem wygranego. Program w yśw ietli zwycięzcę
k tó ra określa,
(lub zwycięzców w przypadku rem isu). O d tego m om e ntu żadna dodatkow a
czynność nie będzie m ogła zostać w ykonana — gracz, aby rozpocząć nową grę, co zamierzasz
będzie m usiał ponow nie u ru ch o m ić program .
zrobić.

424 Rozdział 8.
Typy wyliczeniowe i kolekcje

UTW ÓRZ FORMULARZ


U tw ó rz fo rm u la rz dla gry Id ź na ryby! P ow inien on posiadać k o n tro lk ę L is tB o x dla k a rt znajdujących się
w ręku gracza, dwie k o n tro lk i T e xtB o x do w yśw ietlania postępów w grze i przycisk, k tó ry p o zw o li graczowi
zażądać karty. A b y rozpocząć grę, użytkow nik w ybierze je dn ą z ka rt z rę k i i naciśnie przycisk, żądając
określonej w artości od p rze ciw n ikó w kom puterow ych.

Kontrolka TextBox powinna m'eć


ustaw ioną swoją właściwość Name Ustaw w łaściw oś ć Name tego
na textNam e. Na tym zrzu cie ekranu przycisk u na bu tton Start.
j e s t nieaktywna, a>e po uruchomten,u Na zrzu cie ekranu j e s t nieaktywny,
programu powinna by ć d °stę p na. ale na początku gry powinien być
dostępny. Wyłącza s ię dopiero
po j e j uruchom ieniu.

A ktualny zestaw
kart trzymanych
przez gracza
Te kontrolki w yśw ietlany je s t
TextBox w te j kontrolce
noszą nazwy L is tB o x o nazwie
textP rogress listH and. M ożesz
i textBo oks. u sta w ić je j nazwę
przy użyciu
w łaściw ości
Name.

Zm ień w ła ściw ość Name tego


p iz y d s k u na buttonAsk i u sta w jego
U staw właściw o ść ReadOnly w ła ściw ość Enabled na fa lse. W ten
tych dwóch kontrolek TextBox sposólo sta n ie s ię on nieaktywny,
na tru e — w ten sposób s taną co oznacza, że nie będzie można go
s ię one polami tekstowym i ty lko klikną<5. Formularz uaktywni go zaraz
oo ° dczy tu . D°d atkowo przyp isz po rozpoczęciu gry.
ich w ła ściw ości M ultiline
w artość true.

►Jeszcze nie skończyliśmy — przewróć stronę!

jesteś tutaj ► 425


O to kod form ularza

Długie
D łu ćwiczenie
ciąg dalszy
TAK WYGLĄDA KOD DLA FORM ULARZA.

Jfe' W pisz go do kła dnie tak, ja k widzisz. Pozostała część kodu,


k tó rą sam napiszesz, będzie m usiała z n im współpracować.

p u b lic p a r t i a l c la s s Forml : Form {


p u b lic F o rm l() {
In itia liz e C o m p o n e n t( ) ;
} To je s t jedyna klasa, z którą ws pótpracuj e
formularz. Z a jm u je s ię otefugą catej g ry-
p r iv a t e Game game;

p r iv a t e v o id b u tto n S ta rt_ C lic k (o b je c t sen de r, EventArgs e) {


W łaściw ość i f (S trin g .Is N u llO rE m p ty (te x tN a m e .T e x t)) {
Enabled
MessageBox.Show("Wpisz swoje im ię " , "N ie można je s z c z e rozpocząć g ry .
uaktywnia
lub blokuje re tu rn ;
kontrolkę na }
formularzu. game = new G am e(textN am e.Text, new L is t< s t r in g > { "J a n e k ", "B a rte k " } , te x tP r o g re s s );
b u tto n S ta rt.E n a b le d = f a ls e ;
textN am e.E nabled = f a ls e ;
b u tto n A sk.E n a b le d = t r u e ;
U pdateForm ();

Kiedy ^ urucham iasz nową grę, tworzona
j e s t nowa instancja klasy Game,
} akty wny s t aj e s ię p rzy cisk buttonAsk,
blokuje s ię p rzycisk do rozpoczynania
gry i przerysowywana j e s t zaw artość
p r iv a t e v o id UpdateForm() { formularza.
Ta metoda
lis tH a n d . It e m s . C le a r ( ) ;
cz y ści i ponownie U życie w ła ściw ości S e le c tio n S ta rt
uzupełnia fo re a c h ( S t r in g cardName in gam e.G etPlayerCardNam es())
oraz metody ScrollToC aret()
kontrolkę lis tH a n d .Ite m s .A d d (c a rd N a m e ); w przedstaw iony sposób p rze su wa
L is tB o x , która te x tB o o k s .T e x t = ga m e.D e scribe B oo ks(); widok na koniec pola tekstow ego.
przechowuje karty Je ż e li w kontrolce znajduje s ię
znajdujące s ię te x tP ro g re s s .T e x t += ga m e .D e scrib e P la ye rH a n d s();
więcej te k stu , niż j e s t ona
w ręku gracza. te x tP r o g r e s s .S e le c tio n S ta r t = te x tP ro g re s s .T e x t.L e n g th w sta n ie w yśw ietlić za jednym
A ktu a lizu je także te x tP r o g r e s s .S c r o llT o C a re t( ); razem , je j zaw artość przewijana
zaw artość pól } j e s t na sam dół.
tekstow ych. }

p r iv a t e v o id buttonAsk_C lick (o b je c t se n d e r, EventArgs e)


te x tP ro g re s s .T e x t = " " ;
{ . *
W iersz S e le c tio n S ta rt
przesuw a m igający kursor pola
i f (lis tH a n d .S e le c te d In d e x < 0) { tekstowego na sam koniec.
Potem wywoływana j e s t metoda
MessageBox.Show("W ybierz k a r t ę . " ) ;
ScrollToC aret(), która przew ija
re tu rn ; zaw artość pola tekstowego
} w jeg o m iejsce.
if (g a m e .P la yO n e R o u n d (listH a n d .S e le cte d In d e x)) {
te x tP ro g re s s .T e x t += "Z w ycięzcą j e s t . . . " game.GetWinnerName();
te x tB o o k s .T e x t = ga m e.D e scribe B oo ks();
b u tto n A sk.E n a b le d = f a ls e ;
}
e ls e Ą V- Gracz wybie ra jedn ą z kart i klika przycisk
,Z a ż ądaj karty ", aby spraw dzić, czy żaden
U pdateForm ();
z pozo s tałych graczy nie posiada kart
o t akiej sam ej w artości. Kolejne rundy
w grz e rozpoczynane s ą za pomocą metody
PlayOneRound().

426 Rozdział 8.
Typy wyliczeniowe i kolekcje

POTRZEBUJESZ TAKŻE TEGO KODU


Potrzebujesz kodu, k tó ry napisałeś wcześniej dla klasy Card, typów w yliczeniow ych S u its
i V a lu e s , a także dla klas Deck oraz C a rdC om parer_byV alue. M usisz je d n a k dodać k ilk a
m etod do klasy Deck... i zanim zaczniesz ich używać, będziesz m usiał je zrozumieć.

Metoda PeekO pozwala obejrzeć


jedn ą to rtę bez pobierania j ej .
p u b lic Card Peek( i n t cardNumber) {
re tu r n cards[cardN um ber] ;
}
Ktoś przeciążył Deal(), aby u a y n to p m g ^
p u b lic Card Deal () { łatw iejszym w czytomtu. je ż e li ^ p M e ra ta i j
P r e tu r n Deal(O ) ; ^ S T * ” ^ ^

p u b lic bool ContainsValue (V alues v a lu e ) {


fo re a c h (Card card in card s)
M etoda ContainsValue() p rze szu kuj e cały
if (c a rd .V a lu e == v a lu e ) z esta w kart pod kątem określonej wartości
r e tu r n t r u e ; i zwraca tru e, je ż e li taką znajdzie. Czy
r e tu r n f a ls e ; p o tra fisz powiedzieć, w którym m° menc ie ^
będzie wykorzystywana w grze „Id ź m ryhyT"?
}

p u b lic Deck PullOutValues (V alues v a lu e ) {


Deck deckToReturn = new Deck(new C a rd [] { });
fo r ( in t i = ca rd s.C o u n t - 1; i >= 0 ; i- -
M etody P u llOu tV a lu es() będziesz
if ( c a r d s [i]. V a lu e == v a lu e ) u żyw ał podczas pobierania grupy kart
d e c k T o R e tu rn .A d d (D e a l(i)); z z e s taw u. Wysz u ku je ona każdą kartę,
ktÓra posiada taką sam ą w artość
r e tu r n deckToR eturn;
ja k przekazany param etr, wyciąga
} j ą z ze sta w u i zwraca nowy zesta w
zaw ierający takie właśnie karty.
p u b lic bool HasBook(Values va lu e )
i n t NumberOfCards = 0;
fo re a c h (Card card in card s)
M etoda HasBookO spmwdza. czy
if (c a rd .V a lu e == v a lu e ) zestaw posiada grupę czterech, kart
NumberOfCards++; o dowolnej wartość, przekazanej
if (NumberOfCards == 4) w postaci parametru. Jeze''
w zestawie znajduje się grupa,
r e tu r n t r u e ; zwraca true, jeżeli nie, zwraca
e ls e false.
r e tu r n f a ls e ;
}

Metoda S ° rtB yV a lu e() so rtu je


p u b lic v o id SortByValue () { Z fsto w przy użyciu klasy
c a rd s .S o rt(n e w C a rdC o m pa rer_byV a lu e()); CardCom parer_byValue.
}

-►Jeszcze nie skończyliśmy — przewróć stronę!

jesteś tutaj ► 427


Bierz ich, tygrysie!

Długie
D łu ćwiczenie
ciąg dalszy
TERAZ TRUDNA CZĘŚĆ: UTW ÓRZ KLASĘ PLAYER.

* D la każdego z trzech graczy istnieje instancja klasy P la y e r.


Są one utw o rzon e przez fun kcję obsługi zdarzenia b u t to n S t a r t .

c la s s P la y e r
{
Popatrz uważnie na każdy z komentarzy —
p r iv a t e s t r in g name;
p o w ied zą Ci one dokładnie, co każda metoda
p u b lic s t r in g Name { g e t { r e tu r n name; } } ¡Powinna rot>ić . Two im zadaniem j e s t ich
p r iv a t e Random random; napisan ie.
p r iv a t e Deck cards ;
p r iv a t e TextBox textBoxOnForm;

p u b lic Player ( S t r in g name, Random random, TextBox textBoxOnForm) {


// K o n stru k to r k la s y P la y e r i n i c j a l i z u j e prywatne p o la i dodaje
// do k o n tr o lk i TextBox w ie rs z , k tó r y ma p o s ta ć : „ Janek
// d o łą c z y ł do g ry " - u ż y j je d n a k prywatnego p o la name i n ie zapomnij
// dodać znaków nowej l i n i i na końcu każdego dodawanego
// w ie rsza .
}
p u b lic
p u b lic Values GetRandomValue() {
// Ta metoda p o b ie ra losową w a rto ść, a le musi s i ę ona znajdować w z e sta w ie .
}
p u b lic Deck DoYouHaveAny(V alues v a lu e ) {
// To t u t a j p rz e c iw n ik sprawdza, c zy masz k a r ty o o k r e ś lo n e j w a rto śc i.
// W artości wyciągane są za pomocą metody D e c k .P u llO u tV a lu e s(). Dodaj do k o n tr o lk i
// TextBox n a p is „ Janek ma 3 s z ó s t k i" - u ż y j nowej s t a ty c z n e j metody C a r d .P lu r a l( ).
}
p u b lic v o id AskForACard( L is t< P la y e r> p la y e rs , i n t m yIndex, Deck s to c k ) {
// Tu j e s t p rze cią żo n a w e rsja AskForAC ard() - w ybierz z zestawu losową w artość
// p rz y u życiu GetRandomValue() i zażądaj j e j za pomocą A skFo rA C a rd ().
}
p u b lic v o id AskForACard( L is t< P la y e r> p la y e rs , i n t m yIndex, Deck s to c k , Values v a lu e ) {
// Zażądaj o k r e ś lo n e j w a rto ści od innych g ra czy . Na początku dodaj do p o la tekstowego w ie rsz
// o p o s t a c i: „ Janek p y ta , czy k to ś ma damę". N astępnie p r z e jd ź p rz e z l i s t ę g ra czy przekazanych
// do metody w p o s ta c i parametrów i s p y t a j każdego z n ic h , czy ma daną w a rto ść, p rz y u życiu
// metody DoYouHaveAny(). Przekaże ona zestaw k a rt - dodaj j e dob ieżącego zestaw u.
// Sprawdź, i l e k a rt z o s ta ło dodanych. J e ż e l i n ie b y ło ż a d n e j, to p o c ią g n ij
// je d n ą k a rtę z kupki (ona także z o s ta ła przekazana w p o s ta c i param etru). Na końcu
// pow in ien eś dodać do k o n tr o lk i TextBox w ie rs z o p o s t a c i: „ Janek p o b ra ł k a rtę z k u p k i".
}
/ / Je st je s z c z e w ła ściw ość i k ilk a k r ó tk ic h metod, k tó re ju ż z o s t a ły za C ie b ie napisane.
p u b lic i n t CardCount { g e t { r e tu r n c a rd s .C o u n t; } }
p u b lic v o id TakeCard(Card ca rd ) { c a rd s .A d d (c a rd ); }
p u b lic IE n u m e ra b le < s trin g > GetCardNames() { r e tu r n cards.G etC ardN am es(); }
p u b lic Card Peek( i n t cardNumber) { r e tu r n ca rd s.P eek(ca rdN u m be r); }
p u b lic v o id SortHand () { c a rd s .S o rtB y V a lu e (); }

428 Rozdział 8.
Typy wyliczeniowe i kolekcje

DO KLASY PLAYER DODAJ TAKŻE TĘ METODĘ.


Oto kod metody PullOutBooks klasy Player. Przegląda ona w pętli wszystkie 13 wartości karty. Dla każdej Dwa dodatkowe
zagadnienia
z nich zlicza liczbę kart o danej wartości dostępnych w polu cards obiektu gracza. Jeśli gracz posiada wszystkie do przem yślenia.
cztery karty o danej wartości, znaczy to, że tworzą one grupę — w takim przypadku metoda dodaje wartość

/
do zwracanych grup i usuwa karty tej wartości z ręki gracza.
public IEnumerable<Values> PullOutBooks () {
List<Values> books = new List<Values>(); M etoda Peek(), którą dodaliśm y do klasy Deck,
for (int i = 1; i <= 13; i++) {
j e s t bardzo pomocna. Pozwala ona programowi
Values value = (Values)i; obejrzeć dowolną kartę w z esta w ie , ale,
int howMany = 0; w p rzeciw ieństw ie do Deal(), nie usuw a je j.
for (int card = 0; card < cards.Count; card++)
if (cards.Peek(card).Value == value)
howMany++;
if (howMany == 4) {
1
books.Add(value); Będziesz m u s ia ł utw orzyć DWIE przeciążone w ersje
for (int card = cards.Count - 1; card >= 0; card--) m etody AskForACard(). Pierw sza z nich będzie używana
cards.Deal(card); p rzez gracza podczas żądan ia — obejrzy on karty
} trzym ane w ręce i znajdzie ^ o któ rą p ro si p rze c iwnik.
Druga będzie używ ana p rzez żądające go danej w artośd .
return books; Obie m etody będą chcia ły od KAŻD e g o (z arówno
od grac zy kom puterow ych, ja k i od człow ieka) kart,
}
które s ą zgodne z przekazaną w artością.

MUSISZ DODAĆ TĘ METODĘ DO KLASY CARD.


To statyczna metoda, która zwróci nazwę w postaci zależnej od liczby kart. W ten sposób program będzie wypisywał formę 0 szóstek,
1 szóstka, 2 szóstki, a nie 0 szóstek, 1 szóstek, 2 szóstek. Zastosowana technika wymaga kilku tablic pomocniczych. Oto kod:
public partial class Card {
private static string[] namesO = new string[]
Zdecydow aliśm y s ię
{"", "asów", "dwójek", "trójek", "czwórek", "piątek", "szóstek", "siódemek",
dodać tę m etodę
"ósemek", "dziewiątek", "dziesiątek", "waletów", "dam", "króli"}; do programu, używ ając
private static string[] namesl = new string[]
przy tym klasy
{"", "asa", "dwójkę", "trójkę", "czwórkę", "piątkę", "szóstkę", "siódemkę", częściow ej, by u łatwić
"ósemkę", "dziewiątkę", "dziesiątkę", "waleta", "damę", "króla"}; Ci analizę i zrozum ienie
private static string[] names2AndMore = new string[] kodu. Nie m u sisz jednak
{"", "asy", "dwójki", "trójki", "czwórki", "piątki", "szóstki", "siódemki", tego robić — je śli
"ósemki", "dziewiątki", "dziesiątki", "walety", "damy", "króle"}; chcesz, m ożesz dodać
tę m etodę i dane,
public static string Plural(Card.Values value, int count) { których używ a,
if (count == 0) bezpośrednio do pliku
return names0[(int)value]; z kodem klasy Card.
if (count == l)
return namesl[(int)value];
return names2AndMore[(int)value];
}
}
Podobną technikę zastosujemy do zmiany nazewnictwa kart. Właściwość Name klasy Card również skorzysta z pomocniczej
tablicy. Całość będzie teraz wyglądała następująco:
private static string[] suits = new string[] { "pik", "trefl", "karo", "kier" };
private static string[] names = new string[]
{"", "As", "Dwójka", "Trójka", "Czwórka", "Piątka", "Szóstka", "Siódemka",
"Ósemka", "Dziewiątka", "Dziesiątka", "Walet", "Dama", "Król"};
p u b l i c s t r i n g Name {
get { return names[(int)Value] + " " + suits[(int)Suit]; }
}

Kie d y ju ż n a p isze sz procedurę obsługi zdarzeń


klikn ięcia dla p rzycisku „Zażądaj m o żesz
je j t akże użyć do obsługi zdarzeń dw ukrotnego
kliknięcia kontrolki ListB ox, tak żeb yś mógł
dw ukrotnie kliknąć wybraną kartę, aby je j za żądać

- ► J u ż prawie koniec — czytaj dalej!

jesteś tutaj ► 429


U tw órz grupy. Pawcio

Dłu
Długie ćwiczenie
ciąg dalszy
W POZOSTAŁA CZĘŚĆ ZADANIA: STWÓRZ KLASĘ GAME.
F o rm u la rz przechow uje je dn ą instancję klasy Game. Zarządza ona całą
J f e '
rozgrywką. P opatrz do kła dnie, w ja k i sposób jest wykorzystyw ana w form ularzu .

c la s s Game { Klasy Play e r oraz Game używ ają referencji do


p r iv a t e L is t< P la y e r> p la yers ; w ielowierszow ej kontrolki TextBox um ieszczonej na
formularz u, aby wyśw ietla ć komunikaty o grze. Nie
p r iv a t e D ic tio n a ry < V a lu e s , P la ye r> books;
z apomni’j d° dać w iersza using System .W in dow s.
p r iv a t e Deck sto ck ; Forms do plików z ich kodem.
p r iv a t e TextBox textBoxOnForm;
p u b lic Game( s t r in g playerNam e, IE n u m e ra b le < s trin g > opponentNames, TextBox ;xtE textBoxOnForm) {
Random random = new Random();
th is .te x tB o x O n F o rm = textBoxOnForm; Stosowanie typu IEnumerable<T>
p la y e rs = new L is t< P la y e r > ( ) ; do deklarowania publicznych
p la ye rs.A d d (n e w P la ye r(p la ye rN a m e , random, textB oxO nF orm )); składowych klas jest doskonałym
fo re a ch ( s t r in g p la y e r in opponentNames) sposobem na zwiększenie ich
elastyczności,a jest to czynnik, który
p la ye rs.A d d (n e w P la y e r ( p la y e r , random, textB oxO nF orm ));
musisz mieć na uwadze, jeśli Twój
books = new D ic tio n a ry < V a lu e s , P la y e r> () ;
kod będzie musiał być wielokrotnie
s to c k = new D e c k (); Rozwiązanie to poprawia także używany. Dzięki temu inna osoba
D e a l(); herm etyczność klasy. J e ś li ud° s tępn<sz może utworzyć n o w ą instancję klasy
składową typu IEnum erable<T>, a nie Game, przekazując do niej dane typu
p la y e r s [0 ].S o r tH a n d ( ) ;
, na przykład L ist< T > , to nikt m e będzie
string[], List<string> lub jeszcze inne.
‘ mógł przypadkowo n a p i s a ć kodu, który
zm odyfikuje zaw artość kolekcji.
p r iv a t e v o id D e a l() {
/ / To tu ta j rozpoczyna s i ę g ra - metoda wywoływana j e s t ty lk o na j e j p oczą tku .
/ / Tasuje t a l i ę , ro z d a je każdemu graczowi po p ię ć k a r t , a n a stęp n ie
/ / używa p ę t l i fo re a c h do wywołania metody P u llO u tB o o ks() każdego g ra cza .
}
p u b lic bool PlayOneRound( i n t s e le c te d P la y e rC a rd ) {
/ / R o zeg raj je d n ą rundę g r y . Parametrem j e s t k a rta , k tó rą wybrał z r ę k i Twój
/ / g ra cz - p o b ie rz j e j w a rto ść. P r z e g lą d n ij potem w sz y stk ic h uczestn ikó w g ry i wywołaj
/ / metodę AskForAC ard() każdego z n ic h , począwszy od użytkow nika, k tó r y zn a jd u je
/ / s i ę pod indeksem ze ro na l i ś c i e g ra czy - upewnij s i ę , że poszukiwana j e s t
/ / zaznaczona w a rto ść. N astępnie wywołaj P u llO u tB o o ks() - j e ż e l i zw róci t r u e , oznacza to ,
/ / że Twojemu graczowi sk o ń c z y ły s i ę k a rty i p o trz e b u je nowych. Po wykonaniu ruchu p rze z
/ / przeciw ników p o s o r tu j k a rty gracza (aby ła d n ie wyglądały nafo rm u la rz u ).
/ / Sprawdź p u lę , aby o k r e ś lić ', c zy n ie j e s t p u sta . J e ż e l i ta k ,
/ / zm od yfiku j t e k s t k o n tr o lk i TextBox, aby w y ś w ie tla ła : „ Na kupce n ie ma żadnych k a r t .
/ / Gra sk o ń czo n a !", i zwróć tr u e . W przeciwnym r a z ie zwróć f a l s e .
}
p u b lic bool PullOutBooks(P la y e r p la y e r) {
/ / Wyłóż grupy w sz y stk ic h u czestn ikó w g ry . Zwróć t r u e , gdy graczowi b ra kn ie k a r t .
/ / W przeciwnym r a z ie zwróć f a l s e . Każda grupa dodawana j e s t do sło w n ik a books. Graczowi
/ / kończą s i ę k a r t y , j e ż e l i u ż y ł ich w sz y stk ic h do tw orzen ia grup - wygrywa wtedy g rę .
}
p u b lic s t r in g DescribeBooks() {
/ / Zwróć d łu g i n a p is o k r e ś la ją c y grupy każdego g ra cza , p rze g lą d a ją c sło w n ik books:
/ / „ Janek ma grupę s z ó s t e k , (nowa li n ia ) Edek ma grupę asów".
}

430 Rozdział 8.
Typy wyliczeniowe i kolekcje

ES? • u rn a s p
f ! a :z j s ,
p w ^ ł j^ y s i ju t r ¿ ¡ 2 s z . n r i- w ą s u
tańcich d akó w gruD jaką wyznaczy łeś w drugie 'j pę™ , i na tej podstawie wygeneruje odpowiedni

p u b lic s t r in g GetWinnerName() {
// Ta metoda wywoływana j e s t na końcu g ry . Używa ona swojego własnego sło w n ika
// (D ic tio n a ry < s trin g , int> w in ners) w c e lu o k r e ś le n ia lic z b y grup
// będących w posia da n iu każdego z g ra czy na końcu ro zg ry w ki. Na początku k o rz y sta z p ę t l i
// fo re a c h w o d n ie sie n iu do b oo ks.K eys - foreach (Values value in books.Keys) - aby w ypełn ić
// sło w n ik winners lic z b ą grup każdego z u czestn ikó w . N astępnie p rzeg lą d a ten
// nowy sło w n ik , aby z n a le ź ć gracza z n a jw ię kszą ic h lic z b ą . Na końcu
// po raz o s ta tn i p rzeg lą d a zmienną winners i tw orzy l i s t ę zw ycięzców w fo rm ie łańcucha
// znaków podobnego do te g o : „ Janek i Ed ek". J e ż e l i i s t n i e j e ty lk o je d e n zw ycię zca ,
// zwracany j e s t n a stęp u ją cy c ią g znaków: „E d e k : 3 g ru p y". N przeciwnym r a z ie
// p r z y b ie ra on p o s ta ć : „Remis pomiędzy Janek i Ed ek: 2 g ru p y".
}

//Tu j e s t je s z c z e k ilk a małych metod, k tó re n a p isa liśm y za C ie b ie .

p u b lic IE n u m e ra b le < s trin g > GetPlayerCardNames() {


re tu r n p la y e rs [0 ].G e tC a rd N a m e s ();
}
p u b lic s t r in g DescribePlayerHands () {
s t r in g d e s c r ip tio n = " " ;
f o r ( i n t i = 0 ; i < p la y e rs .C o u n t; i+ + ) {
d e s c r ip tio n += p la y e rs [i].N a m e + ma + p la y e r s [i].C a r d C o u n t;
i f ( p la y e rs [i].C a rd C o u n t == 1)
d e s c r ip tio n += " k a r t ę . \ r \ n "
e ls e i f ( p la y e rs [i].C a r d C o u n t == 2 || p la y e rs [i].C a rd C o u n t == 3 | | p la y e rs [i].C a rd C o u n t == 4)
d e s c r ip tio n += " k a r t y . \ r \ n "
Przej dź do okna Watch i w pisz
e ls e w nim (in t) \ r ’, by zrzutow ać
d e s c r ip tio n += " k a r t . \ r \ n " ; znak \ r ‘ na liczbę. Okaże się,
że ma on w artość 13. Znak \ n ‘
}
ma z kolei w artość 10. Każdy znak
d e s c r ip tio n += "Na kupce p o z o s ta ło k a r t : " + s to c k .C o u n t + " \ r \ n " można zam ienić na unikatową
re tu r n d e s c r ip tio n ; liczbę nazywaną jego kodem
Unicode. W następnym rozdziale
dow iesz s ię w ięcej na ten temat.

. . . .
Użyj stałej Ewii-Mment.NewLine, by dodać znak nowego wiersza
^
p0 ry Używałeś \ n/ by dodawac znak nowe9° wiersza w wyświetlanych tekstach. Jednak .NET
wyg°t^nć ^ kt ó ra daie anal°g iczne, możliwości. Jest nią Environment.NewLine. Zawsze zawiera
W j satT7ą,vvartł 05,c — \ r \ n . .Gdy byś sPrawdził znaki, k+óre są używane w plikach tekstowych! w systemie
W indows okaaałoby si?' że .na końcu każdego a wierszy znajdują się zawsze te same dwa: ' \ r ' i '\n'. Inne
M le a i2 S X !S l takie l;lk U nix' o znaczalą konj ec wiersza t ekstu za pomocą jednego znaku: '\n'. Metoda
MessageBox.Show() jest natyle sprytna, że potrafi automatycznie zamienić znak '\n' na prawidłowe znaki
nowego wierna, jednał* stał el Environment.NewLine może zwiększyć przejrzystość kodu.
Console^HteUneO 5 ^ dodawana na kodcu każdego wiersza wyświetlanego przy użyciu metody
431
Rozwiązanie ćwiczenia

Długie ćwiczenie
Rozwiązanie
Oto wypełnione metody klasy Game:

» Metoda Deal() zostaje wywołana na początku


p r iv a t e v o id Deal () { gry ~ tasuje ta|ię i rozdaje każdemu
s t o c k . S h u f f le ( ) ; z 9racf y po pięć kart- Następni'e wykłada
wszystkie grupy, które udało jej się
fo r ( i n t i = 0; i < 5; i ++) przypadkiem Utworzyć.
fo re a c h (P la y e r p la y e r in p la y e rs )
p la y e r.T a k e C a r d ( s to c k .D e a l( ));
fo re a ch (P la y e r p la y e r in p la y e rs )
P u llO u tB o o k s (p la y e r);
}

p u b lic bool PlayOneRound( i n t s e le c te d P la y e rC a rd ) {


Values cardToAskFor = p la y e rs [0 ].P e e k (s e le c te d P la y e rC a rd ).V a lu e ;
f o r ( i n t i = 0; i < p la y e rs .C o u n t; i+ + ) {
i f ( i == 0)
p la y e rs [0 ].A s k F o rA C a rd (p la y e rs , 0, s to c k , card T o A skF or);
e ls e
p la y e r s [i].A s k F o rA C a rd ( p la y e rs , i , s to c k ) ;
karty^przze™ i f ( P u llO u tB o o k s ( p la y e r s [i]) ) {
jednegozgraczy ( textB oxO nF orm .T ext += p la y e rs [i].N a m e + c ią g n ie k a r ty " + E nvironm ent.N ew Line;
gra wykłada i n t card = 1;
wszystkie
w h ile (ca rd <= 5 && s to c k .C o u n t > 0) {
f
utworzone grupy. Po kliknięciu przycisku „Zróądaj karty"
Jeżeti graczowi p la y e r s [i]. T a k e C a r d ( s t o c k .D e a l( ) ) ; gra wywołuje AskForACar-d() żądającego<
zabraknie kart, card++; przekazując w postaci argumentu wybraną
dobiera do ręki kartę. Następnie wywoływana jest mtfoda
}
pięć kart z puti. } AskForACard() dta każdego z przeciwników.
p la y e r s [0 ].S o r tH a n d ( ) ;
i f (s to c k .C o u n t == 0) {
textBoxO nForm .Text "Na kupce n ie ma żadnych k a r t . Gra skoń czon a!" + E nvironm ent.N ew Line;
r e tu r n t r u e ;
p o rozegramu rundy gra sortuje karty, które
}
pozostały w , ręce gracza, aby zadbać o ich
} spaw w dzT1 kotejność na formutarzu. Potem metoda
r e tu r n f a ls e ; sprawdza, czy gra się zakończyła. Jeżeti tak
zwracana jest wartość true
}

p u b lic bool PullOutBooks(P la y e r p la y e r) {


IEnum erable<Values> b o o ksP u lle d = p la y e r.P u llO u tB o o k s ();
fo re a ch (Values v a lu e in b o o ksP u lle d )
b o o k s .A d d (v a lu e , p la y e r ) ;
i f (p la y e r.C a rd C o u n t == 0) PullOutBooksO przeglqda rgk^ gracza,^ aby^
r e tu r n t r u e ; ' ------- - prawdzić, czy posiada on cztery Iwrty o tej
samej wartości. Jeśli tak z o ra ją ^ J Z oZ1*™
r e tu r n f a ls e ; do słownika books. Jeżeti po tym g m m nie
} zostanie żadna karta, metoda, ziwrtwcci trpe.

432 Rozdział 8.
Typy wyliczeniowe i kolekcje

formularz musi wyświetlić listę grup,


więc używa DescribeBooksO, aby zamienić
elementy słownika books na słowa.

p u b lic s trin g DescribeBooks() {


s trin g whoHasWhichBooks =
foreach (Values value in books.Keys)
whoHasWhichBooks += books[value].Name + " ma grupę "
+ C ard.P lural(value, 0) + Environment.NewLine;
return whoHasWhichBooks;
}

p u b lic s trin g GetWinnerName() {


D ictio n a ry< strin g , in t> winners = new D iction ary< string
foreach (Values value in books.Keys) {
s trin g name = books[value].Name;
i f (winners.ContainsKey(name)) Po zabraniu ostatniej karty program musi
rozstrzygnąć, kto wygrał. Do tego służy
winners[name]++; metoda GetWinnerName(). Używa _ona w tym
else celu słownika winners. Nazwa każdego gracza
winners.Add(name, 1); jest w nim kluczem, a wartością jest liczba
grup utworzonych przez niego w trakcie gry.
}
in t mostBooks = 0;
foreach (s trin g name in winners.Keys)
i f (winners[name] > mostBooks)
mostBooks = winners[name];
bool t i e = fa ls e ;
\ Na dalszym etapie gra przegląda słownik
i wyszukuj e gracza z największą liczbą
s trin g w innerList = " " ; grup. Otrzymana wartość wstawiana jest
foreach (s trin g name in winners.Keys) do zmiennej mostBooks.
i f (winners[name] == mostBooks) {
i f (!S tring.IsN ullO rE m pty(w innerList)) {
w innerList += " i ";
t i e = tru e ;
}
w innerList += name;
}
w innerList += + mostBooks + " grupy
i f (tie ) (lub zwycięzców;-
return "Remis pomiędzy + w inn erList;
else
return w inn erList;

►Jeszcze nie skończyliśmy — przewróć stronę!

jesteś tutaj ► 433


Rozwiązanie ćwiczenia

D łu g ie ć w i c z e n i e

»
R o zw iązan ie (c d .)

Oto wypełnione metody w klasie Player.

pub lic P layer(S tring name, Random random, TextBox textBoxOnForm) {


this.name = name;
this.random = random; 'S . To jes!_konstruktor_klasy Player. Ustawia on
this.textBoxOnForm = textBoxOnForm; prywatne pola i dodaje do pda tekstowego
th is .c a rd s = new Deck(new Card[] { } ) ; z postępami gry wiersz informm cy o nowym graczu.
textBoxOnForm.Text += name + " przyłączy? się do gry " + Environment.NewLine;
}

pub lic Values GetRandomValue() { Metoda GetRandomValueO u±y\ua


Card randomCard = cards.Peek(random.Next(cards.Count)); Peek() do podglądwędtz fosowej
return randomCard.Value; karty w ręce gracza.
}

I T " DoYouHaveAny() używa


pub lic Deck DoYouHaveAny(Values value) { metody PulOutValues()
Deck cardslHave = cards.PullO utValues(value); w celu pobrania
textBoxOnForm.Text += Name + " ma " + cardslHave.Count + " " i zwrócenia wszystkich
+ C ard.P lural(value, cardslHave.Count) + Environment.NewLine; kart, które są zgodne
return cardslHave; z parametrem.

pub lic void AskForACard(List<Player> players, in t mylndex, Deck stock) {


i f (stock.Count > 0) {
Jeśli przeciwnik oddał ostatnią kartę metoda
i f (cards.Count == 0) ^ ---------------- GetRandomValue() spróbuje wywołać metodę Deal()
cards.Add(stock.D eal()); na pustej kolekcji karty. Ta instrukcj a if zapobiega
Values randomValue = GetRandomValue(); takiej sytuacji.
AskForACard(players, mylndex, stock, randomValue); Istnieją dwie przeciążone metody
AskForACard(). Ta tutaj jest
używana przez komputerowych
} przeciwni'ków — pobiera losową
kartę z ręki i wywołuje drugą
pub lic void AskForACard(List<Player> players in t mylndex, metodę AskForACard().
Deck stock, Values value) {
textBoxOnForm.Text += Name + " pyta, czy ktoś ma "
+ C ard.P lural(value, 1) + Environment.NewLine;
in t totalCardsGiven = 0;
fo r ( in t i = 0; i < players.Count; i++) {
i f (i != mylndex) {
Player player = p la y e r s [i];
Deck CardsGiven = player.DoYouHaveAny(value); Ta _metoda AskForACard() przegląda
totalCardsGiven += CardsGiven.Count; każdego gracza (oprócz tego, który
w hile (CardsGiven.Count > 0) wykonuje ruch), wywołuje jego
cards.Add(CardsGiven.Deal()); metodę D°YouHaveAny() i M a ie
wszystk<e pobrane karty do jego ręki.
}
}
i f (totalCardsGiven == 0) {
textBoxOnForm.Text += Name + pobraî kartę z ku pki." + Environment.NewLine;
cards.Add(stock.D eal());
} Jeżeli żadna karta nie zostanie
dodana, gracz pobiera jedną
}
_______ z kupki za pomocą metody Deal().
Dodatkowe minizadanie: Czy możesz wskazać, jak poprawić hermetyzację iprojekt klasy Player
poprzez zastąpienie w tych dwóch metodach typu List<Player> przez IEnumerable<Player>
w taki sposób, by w ogóle nie zmienić działania kodu? Informacje, które m o g ą Cl pomóc w znalezieniu
434 odpowiedzi na to pytanie, możesz znaleźć w ó s m y m punkcie dodatku „Pozostałości".
Typy wyliczeniowe i kolekcje

I jeszcze W IĘCEJ typów kolekcji...


Obiekty L is t oraz D ic tio n a ry są dwiema wbudowanymi kolekcjam i generycznymi
stanowiącymi część platformy .NET. Listy i słowniki są bardzo elastyczne — za ich pomocą
możesz uzyskać dostęp do danych w dowolnym porządku. Czasami jednak musisz ograniczyć
sposób działania programu, ponieważ reprezentowana przez niego rzecz zachowuje się
w świecie rzeczywistym w określony sposób. W takich okolicznościach powinieneś użyć kolejek
(Queue) lub stosów (Stack). To dwie inne kolekcje generyczne podobne do listy. Są one
wyjątkowo przydatne wtedy, gdy dane muszą być przetwarzane w określonej kolejności.

U żyj Queue, gdy pierw szy U ży j Stack, jeżeli chcesz skorzystać


z przechow yw anych obiektów będzie z obiektu wstawionego na samym
p ierw szym , z którego sko rzystasz. końcu. Są to na przykład :
Są to na przykład :
★ meble ładowane do naczepy ciężarówki,
★ samochody poruszające się
po jednokierunkowej ulicy, ★ stos książek, gdy w pierwszej kolejności
chcesz przeczytać te najnowsze,
★ ludzie czekający w kolejce,
★ osoby wsiadające do samolotu lub
★ klienci czekający na wsparcie techniczne, wychodzące z niego,

★ wszystko, co jest realizowane według ★ piramida czirliderek, gdy ta na samej górze


zasady pierwszy przyszedł, pierwszy schodzi pierwsza (wyobraź sobie, co by się
wyszedł. stało, gdyby jako pierwsza poszła sobie ta
na dole!).
Kotejka działa według zasady pierwszy
przyszedł, pierwszy wyszedł, co
oznacza, że pierwszy obiekt, który Stos działa według zusiJdy p^w szy
do niej wstawi7eś, będzie pierwszym, przyszedł, ostatni wyszedł — p ^ ^ s z y
który z mej wyciągniesz. obiekt, który został do niego wstawiony,

Kolekcje generyczne są ważną częścią platformy .N E T


Są naprawdę pomocne — tak bardzo, że IDE automatycznie dodaje
taką oto instrukcję na górze każdej klasy dodanej do projektu: Kolejka jest podobna
using System .Collections.Generic;
do listy, która pozwala
Prawie każdy duży projekt, nad którym będziesz pracował,
skorzysta w jakiś sposób z kolekcji generycznych, ponieważ
wstawiać obiekty na
program musi przechowywać dane. Kiedy pracujesz ze zbiorem końcu i używać tych na
podobnych rzeczy w świecie rzeczywistym, prawie zawsze
w sposób naturalny kwalifikują się one do pewnej kategorii,
początku. Stos pozwala
która odnosi się do jakiegoś rodzaju kolekcji. na uzyskanie dostępu
Możesz użyć pętli foreach
tylko do ostatnio
do wyliczenia etementów k°lejki
tub stosu, ponieważ imp|ementują wstawionego obiektu.
one interfejs IEnumerabl ei

jesteś tutaj ► 435


Czy nie denerw uje Cię czekanie w kolejkach?

Kolejka działa według reguły:


pierwszy przyszedł, pierwszy wyszedł
Kolejka jest podobna do listy. Różni się tym, że nie możesz dodawać ani usuwać elementów znajdujących się pod
dowolnym indeksem. Aby dodać obiekt, wpisujesz go do ko le jki . W ten sposób umieszczasz go na jej końcu. Możesz
pobrać pierwszy obiekt. Kiedy to zrobisz, zostanie on usunięty z kolejki, a reszta obiektów przesunie się do przodu.

Stwórz
nową kolejkę Queue<string> myQueue = new Queue<string>();
łańcuchów
znaków. \ To tutai dodajemy do kolejki
myQueue.Enqueue("pierwszy w kolejce"); / cztery elementy. Kiedy próbujemy
myQueue.Enqueue("drugi w kolejce");
myQueue.Enqueue("trzeci w kolejce"); \ porządku, w jakim zostały
\ ustawione.
myQueue.Enqueue("ostatni w kolejce");
Peek() pozwata
„rzucić okiem" string takeALook = myQueue.Peek(); 0
na pierwszy pierwsze Dequeue() wyciąga
etement string getFirst = myQueue.Dequeue(); @
w kotejce bez
usuwania go. string getNext = myQueue.Dequeue(); 0 z, kotejki pierwszy etement. Drugi
etement przesuwa się na pierwsze
miejsce — następne wywołanie
Dequeue() wyciągnie go.
int howMany = myQueue.Count; 0
myQueue.Clear();
MessageBox.Show("Peek() zwróciło: " + takeALook + "\n"
Metoda Clear() + "Pierwsze Dequeue() zwróciło: " + getFirst + "\n"
usuwa wszystkie
elementy kolejki. + "Drugie Dequeue() zwróciło: 1 + getNext + "\n"
+ "Count przed Clear() było równe 1 + howMany + "\n"
+ "Count po Clear() jest równe " + myQueue.Count); 0

Właściwość Count kolejki


zwraca liczbę znajdujących się
w niej elementów.

“ H I

PeekO zwróciło: pierwszy w kolejce


Obiekty w kotejce , ^ ^ ^ ^ ^ ^ 2
muszą czekać.
Pierwszy, który z ^3)
się w niej znatazł, ■_ iu . i-. .'. ^ 4
będzie pierwszym, '.j un; m: ^U,:i >..A [i"--nv. !. <#5
który ją opuści.

OK

436 Rozdział 8.
Typy wyliczeniowe i kolekcje

Stos działa według reguły:


ostatni przyszedł, pierwszy wyszedł
Stos jest naprawdę podobny do kolejki, ale jest jedna poważna różnica. Kładziesz na nim element,
a kiedy chcesz go zabrać, to go ze stosu podnosisz. Gdy coś pobierasz, to zwracany jest element, który
jako ostatni został na stosie położony. To tak jak ze stosem talerzy, czasopism lub czegokolwiek innego
— możesz na niego coś rzucić, ale musisz to później ściągnąć, jeśli chcesz się dostać do czegoś, co jest
pod spodem.
Tworzenie sto su wygląda tak samo
ja k tworzenie każdej innej k° |ekcj|
Kiedy kładziesz ge nerycznej .

w o^dy p o ^ a łe Stack<string> myStack = new Stack<string>();


p P ^ a - ją myStack.Push("pierwszy na stosie");
P o lj^ n o w y myStack.Push("drugi na stosie");
um ies z czan y j e s t myStack.Push("trzeci na stosie");
na sam ej górze.
myStack.Push("ostatni na stosie");
(ft string takeALook = myStack.Peek(); Kiedy ściągasz coś
ze stosu, to otrzymujesz
2 string getFirst = myStack.Pop(); element, który został tam
dodany jako ostatni.
string getNext = myStack.Pop();
int howMany = myStack.Count;
myStack.Clear();
ii Równie dobrze
MessageBox.Show("Peek() zwróciło + takeALook + "\n'
mógłbyś tu
+ "Pierwsze Pop() zwróciło: + getFirst + "\n" zastosować stałą
Environment.NewLine,
+ "Drugie Pop() zwróciło: " + getNext + "\n" my jednak chcieliśmy,
by kod był możliwie
+ "Count przed Clear() było równe " + howMany + "\n krótki i zwięzły.
+ "Count po Clear() jest równe 1 + myStack.Count);

Ostatni obiekt, który


położyłeś na stosie, jest
pierwszym, który z niego
ściągniesz.

OK

jesteś tutaj ► 437


Naleśniki i drw ale

ZACZEKAJ CHWILĘ, COŚ MI SIĘ TU NIE PODOBA. NIE POKAZAŁEŚ


MI ŻADNEJ RZECZY, KTÓRĄ MOGĘ ZROBIĆ ZE STOSEM LUB KOLEJKĄ,
A KTÓREJ NIE MOŻNA ZROBIĆ ZA POMOCĄ LISTY — W ZASADZIE
ZAOSZCZĘDZIŁY MI ONE TYLKO KILKA WIERSZY KODU. NIE MOGĘ JEDNAK
DOSTAĆ SIĘ DO ELEMENTÓW ZNAJDUJĄCYCH SIĘ W ŚRODKU KOLEJKI LUB
STOSU, A W PRZYPADKU LISTY ROBIĘ TO BEZ ŻADNEGO PROBLEMU!
DLACZEGO MIAŁABYM Z TEGO ZREZYGNOWAĆ
DLA N IEW IELK IEJ W Y G O D Y ?

Nie przejmuj się — podczas korzystania z kolejki lub stosu


nie musisz niczego tracić...
Naprawdę łatwo jest zamienić obiekt Queue na L is t . Jest to tak proste
jak zamiana listy na kolejkę, kolejki na stos... W zasadzie możesz utworzyć
listę, kolejkę lub stos z każdego innego obiektu, który implementuje interfejs
IEnumerable. Wystarczy, że użyjesz przeciążonego konstruktora, który
Utwórzmy stos z czterema pozwoli Ci przekazać kolekcję do skopiowania w postaci parametru. W ten
elementami — w tym sposób możesz zyskać elastyczność i wygodę reprezentowania danych przez
przypadku stos łańcuchów
znaków. kolekcję, która najlepiej pasuje do sposobu, w jaki będziesz jej używał.
(Pamiętaj jednak, że w takim przypadku tworzysz jej kopię, czyli całkowicie
nowy obiekt, który zostanie umieszczony na stercie).

Stack<string> myStack = new Stack<string>();


myStack.Push("pierwszy w rzędzie");
myStack.Push("drugi w rzędzie");
Bardzo łatwo zamienić ten
myStack.Push("trzeci w rzędzie"); stos na k°lejkę, następnie
przekopiować kolejkę I'tsty,
myStack.Push("ostatni w rzędzie"); a listę do innego s^stu.

Queue<string> myQueue = new Queue<string>(myStack);


List<string> myList = new List<string>(myQueue);
Stack<string> anotherStack = new Stack<string>(myList);
MessageBox.Show("myQueue ma 11 + myQueue.Count + 11 elementy\n"
+ "myList ma 11 + myList.Count + 11 elementy\n"
+ "anotherStack ma 11 + anotherStack.Count + 11 elementy\n");

W szystkie cztery
elem enty zostafy
eiememy —J
przekopiowane
■ ___ i-i do
rln ...i zawsze możesz użyć pętli
nowych kolekcji. foreach , aby uzyskać dostęp
do wszystkich elementów
zarówno stosów, ja k i kolejek!

438 Rozdział 8.
Typy wyliczeniowe i kolekcje

Napisz program, który pomaga stołówce pełnej drwali wydawać naleśniki. Rozpocznij od klasy
Lumberjack, uzupełniając ją brakującym kodem. Następnie zaprojektuj formularz i dodaj procedury
Ćwiczenia obsługi zdarzeń do przycisków.
3 Dana jest klasa Lumberjack . Uzupełnij akcesor get FlapjackCount
oraz metody TakeFlapjacks i E atF lapjacks . public enum Flapjack {
Chrupkiego,
pub lic class Lumberjack { Wilgotnego,
p riv a te s trin g name; Rumianego,
p u b lic s trin g Name { get { return name; } } Bananowego
p riv a te Stack<Flapjack> meal; }
p u b lic Lumberjack(string name) {
this.name = name;
meal = new Stack<Flapjack>();
}
p u b lic in t FlapjackCount { get { / / Zwróć Ilo ś ć . } }
p u b lic void TakeFlapjacks(Flapjack food, in t HowMany) {
/ / Dodaj o k re ślo n ą lic z b ę n a leśn ików do sto s u meal.
}
p u b lic void EatFlapjacks() {
/ / U ypisz w yniki w o knie k o n s o li.
}

Stwórz taki oto formularz. Pozwala on na wpisywanie imion drwali w polu tekstowym, przez co dostają się oni
do kolejki śniadaniowej. Drwalowi znajdującemu się na początku możesz dać talerz naleśników, a następnie
powiedzieć mu, żeby poszedł je zjeść, klikając przycisk N astępny drwal. Napisaliśmy procedurę obsługi kliknięcia
dla przycisku D odaj naleśniki. Użyj kolejki o nazwie breakfastLine w celu przechowywania informacji o drwala

Gdy użytkownik kliknie „Dodaj drwala“,


dodaj jego i'mię z pola edycji name do Czy zwróciłeś uwagę, że wartości typu
kolejki breakfastLine. wyliczeniowego Flapjack zaczynają się
od dużej litery (np. Bananowy), natomiast
Kolejka do śniadania
Nakarm drwala
Kiedy przeciągasz te kontrolki Radio8utt°n w wyświetlanych wynikach rodzaje naleśników
1 Edek
2.Zyga 2 : do komponentu Group8ox, hm uhrz są zapisane małą literą (bananowy)? Oto mała
3. Bolek
4. Ferdek O Chrupkiego automatycznie łączy je ze sobą i _pozwala
5. Stachu podpowiedź, jak uzyskać prawidłowy zapis.
O Wilgotnego na zaznaczenie w danym momencie
<§> Rumianego tylko jednej z nich. Spójrz na metodę Metoda ToString() zwraca obiekt string,a jedna
O Bananowego
f t addFlapjacks_Click, aby okreśhć, jak z jego publicznych metod — ToLower() — zwraca
Dodaj naleśniki
naleśniki powinny zostać nazwane. łańcuch zaczynający się małą literą.
Ta lista Edek ma Snaleśników

nazywa p riva te void addFlapjacks^Cl1ck(object sender, EventArgs e) {


się line. Flapjack food;
Następny drwal

i f (crispy.Checked== true)
food = Flapjack.Chrupkiego Zwróć uwagę na
specjalną składnię
else i f (soggy.Checked == true)
Ten przycisk powinien pobrać food = Flapjack.Wilgotnego „else if'.
następnego drwala z kolejki, else i f (browned.Checked == true)
wywołać jego metodę EatFlapjacks() food = Flapjack.Rumianego; Peek() zwraca referencję
i zaktualizować kontrolkę List8°x. else do pierwszego drwala
food = Flapjack.Bananowego; w kolejce.
Będziesz musiał dodać metodę
RedrawList(), aby wypełnić kontrolkę Lumberjack currentLumberjack = breakfastLine.Peek();
List8ox aktualną zawartością kolejki. currentLumberjack.TakeFlapjacks(food,
Wszystkie trzy przyciski będą ją
(int)howMany.Value);
wywoływały. Mamy podpowiedź:
używa ona pętli foreach. j RedrawList(); Kontrolka NumericUpDown nazywa się howMany.
Pole tekstowe nosi nazwę nextInLine.
Ten program w yśw ietla w yniki w oknie konsoli, a zatem
aby ją zobaczyć, będziesz m usiał w yśw ietlić w IDE okno Output. jesteś tutaj ► 439
Rozwiązanie ćwiczenia

p riva te Queue<Lumberjack> breakfastLine = new Queue<Lumberjack>();


p riva te void addLumberjack_Click(object sender, EventArgs e) {
i f (String.IsNullOrEmpty(name.Text)) re tu rn ;
Rozwiązania breakfastLine.Enqueue(new Lumberjack(name.Text));
ćwiczeń name.Text = " " ;
RedrawList();
}
p riva te void RedrawList() « s .™ '
in t number = 1;
Metoda RedrawListO lin e . Items. Clear()
korzysta z pętli foreach (Lumberjack lumberjack in breakfastLine) {
foreach i za jej line.Items.Add(number + + lumberjack.Name);
pomocą pobiera number++;
z kolejki drwali.
Umieszczani są oni } Ta instrukcja uzupełnia P°'®
i f (breakfastLine.Count == 0) tekstowe informacjami o pierwszym
w kontrolce ListBox. groupBoxl.Enabled = fa ls e ; drwala w kolejce.
nextlnLine.Text = " " ;
} else {
groupBoxl.Enabled = tru e ;
Lumberjack currentLumberjack breakfastLine.Peek();
nextlnLine.Text = currentLumberjack.Name + " ma "
+ currentLumberjack.FlapjackCount + " naleśników";
}
}
p riva te void nextLumberjack_Click(object sender, EventArgs e) {
i f (breakfastLine.Count == 0) re tu rn ;
Lumberjack nextLumberjack = breakfastLine.Dequeue();
nextLum berjack.EatFlapjacks();
nextlnLine.Text = " " ;
RedrawList();
}
class Lumberjack {
p riva te s trin g name;
p ub lic s trin g Name { get { return name; } }
p riva te Stack<Flapjack> meal;

p ub lic Lumberjack(string name) {


this.name = name;
meal = new Stack<Flapjack>();
}
p ub lic in t FlapjackCount { get { return meal.Count; } }
Metoda TakeFlapjacks()
aktualizuje stos meal. p ub lic void TakeFlapjacks(Flapjack food, in t HowMany) {
fo r ( in t i = 0; i < HowMany; i++) {
To wtaśnie w tym miejscu
meal.Push(food); wartość typu Flapjack zostaje
} zamieniona na łańcuch znaków
} zaczynający się matą literą.
p ub lic void EatFlapjacks() { Poświęć chwilkę, by zrozumieć,
Metoda ^ Console.WriteLine(name + " je n a le ś n ik i1 co się tu dzieje.
Eatflapjacks() / w hile (meal.Count > 0) { y
<fywa pętli while ^ Console.WriteLine(name + " zjadT " kT
oo wypi sani a + m eal.Pop().ToString().ToLower() + naleśnika")
posiłków drwali.
} ^ -----------------------
} Wywołanie meal.Pop() zwraca wartość typu wyliczeniowego, jej metoda ToString()
} zwraca obiekt string, a jego metoda ToLower() zwraca kolejny obiekt string.

440 Rozdział 8.
9. Odczyt i zapis plików

Zachowaj te bajty dla mnie!

Czasem opłaca się być Irwałym. Do tej pory wszystkie programy były krótkotrwałe.
Uruchamiały się, działały przez chwilę i były zamykane. Czasami nie jest to wystarczające,
zwłaszcza jeżeli zajmujesz się ważnymi danymi. Musisz mieć możliwość zapisania swojej pracy.
W tym rozdziale pokażemy sposób zapisywania danych do pliku, a następnie wczytywania
tych informacji z powrotem do programu. Dowiesz się co nieco o klasach strumieni .NET
i zetkniesz się z tajemnicami systemów szesnastkowego i dwójkowego .

to jest nowy rozdział ► 441


W ysepki na strum ieniu

C# używa strumieni do zapisu i odczytu danych Jeżeli chcesz


Strumienie są jednym ze sposobów przesyłania danych do i z programu.
zapisać dane
Za każdym razem, gdy aplikacja C # zapisuje lub odczytuje plik, łączy się
z innym komputerem za pomocą sieci lub wykonuje operację związaną
do pliku lub
z w ysyłaniem lub o d b ie ra n ie m bajtów — używa strumieni. odczytać
je z niego,
powinieneś
użyć obiektu
Powiedzmy, że masz prosty program — form ularz Stream .
z procedurą obsługi zdarzenia, któ ra musi odczytać
inform acje z pliku. Używasz w nim obiektu Stream .

input = stream.Read(.

^ /a rz
input zawiera dane
odczytane ze strumienia ..•a strumień pracuje
Używasz bezpośrednio z plikiem.
obiektu Stream...

Gdy Twój program musi zapisać dane do pliku,


może użyć innego obiektu Stream .

stream .W rite(output);
output zawiera dane
zapisywane do pliku

^°'% y f
Używasz innego
obiektu strumienia,
ale proces wygląda
tak samo.

442 Rozdział 9.
Odczyt i zapis plików

Różne strumienie zapisują i odczytują różne rzeczy


Każdy strumień jest klasą pochodną abstrakcyjnej klasy Stream. Istnieje cały zestaw wbudowanych klas
strumieni do wykonywania różnych zadań. Skoncentrujemy się na zapisie i odczycie zwykłych plików,
ale wszystko, co zostanie pokazane w tym rozdziale, może być z łatwością przeniesione na grunt plików
skompresowanych lub zaszyfrowanych, jak również mieć zastosowanie przy strumieniach sieciowych,
które w ogóle plików nie będą używały.

F ileStream MemoryStream Obiekt NetworkStream GZipStream obsługuje


pozwala Ci umożliwia Ci pozwala Ci odczytywać kompresję danych.
odczytywać odczytywanie i zapisywać dane na Dzięki niej zajmują
i zapisywać dane i zapisywanie danych innych komputerach lub one mniej miejsca i są
w plikach. w obszarach pamięci. urządzeniach w sieci. łatwiejsze do pobrania
i przechowywania.

Oto co możesz zrobić ze strumieniami:

[n Z A P IS A Ć D A N E D O S T R U M IE N IA .
Strumienie
Do strumienia możesz wpisać dane tekstowe i binarne, używając pozwalają na
w tym celu jego metody W r ite ( ) . zapis i odczyt
O D C Z Y T A Ć D A N E Z E S T R U M IE N IA .
danych. Używaj
Aby pobrać dane z pliku, pamięci, sieci lub czegokolwiek innego, co korzysta ze strumieni, strumienia
możesz użyć metody Read(). Można odczytywać dane z naprawdę dużych plików, nawet właściwego
tak dużych, że się nie mieszczą w pamięci.
dla danych,
Z M IE N IA Ć P O Ł O Ż E N IE W E W N Ą T R Z S T R U M IE N IA . z którymi
Większość strumieni udostępnia metodę Seek() , która umożliwia określenie położenia pracujesz.
w strumieniu i wykonywanie w tym miejscu operacji odczytu lub zapisu danych.

jesteś tutaj ► 443


Znacznie ła tw ie j

FileStream odczytuje dane z pliku i zapisuje je w nim


Kiedy Twój program musi zapisać kilka wierszy tekstu do pliku,
wtedy zdarzyć się musi wiele rzeczy.
Upewnij się, że dodałeś using
System.IO do każdego programie,
który używa strumieni.
.A Utwórz nowy obiekt FileStream i każ mu zapisać dane do pliku.

% r-/!
FiteStream może być
p°dłącz°ny w danej
chwiti tytko do jednego
ptiku.

[2 FileStream podłącza się do pliku.

2 Strumienie zapisują do plików bajty, więc musisz przekonwertować


łańcuch znaków na tablicę bajtów.
69 1 1 7 1 1 4 1 0 1 1 0 7 97 33

Nazywamy to kodowaniem
Eureka! » la im n io m io in i
(powiemy sobie więcej na ten O l 1 3 4 - 5 4
temat nieco ptóm ej-J. V

[2 Wywołaj metodę W rite () strumienia i przekaż jej tablicę bajtów.

’W
Zamknij plik, aby inne programy miały do niego dostęp.

razie będzie on zablokowany i do


czasu zamknięcia strumienia inne
programy nie będę miały do mego
%,r y *
dostępu.
444 Rozdział 9.
Odczyt i zapis plików

W jaki sposób zapisać tekst do pliku StreamWriter


w trzech prostych krokach automatycznie
C # udostępnia bardzo wygodną klasę StreamWriter, która radzi sobie z tym
tworzy obiekt
w prosty sposób. Wystarczy, że utworzysz nowy obiekt Stream W riter i przekażesz FileStream
mu nazwę pliku. Automatycznie utworzy on obiekt FileStream i otworzy
wskazany plik. Następnie możesz użyć metod W rite () oraz W rite L in e () klasy
i zarządza nim.
Stream W riter , aby zapisać wszystko w wybranej lokalizacji.

Użyj konstruktora StreamWriter do otwarcia lub utworzenia pliku.


Do konstruktora S tream W riter() możesz przekazać nazwę pliku. Jeśli tak zrobisz, klasa automatycznie
go otworzy. Stream W riter posiada także przeciążony konstruktor przyjmujący wartość b oo l . tru e oznacza,
że chcesz dodać tekst na końcu istniejącego pliku, fa ls e natomiast, że chcesz usunąć plik, a w jego miejsce
wstawić nowy o tej samej nazwie.
StreamWriter w rite r = new S tream W riter(@ "C :\now epliki\piekarnik.txt", tru e );

Wstawienie @ na początku
nazwy pliku nakazuje C#
traktować łańcuch znatów
bezpośrednio i nie zwracać
uwagi na sekw enj f°rmatujące
takie jak \ t dla tabulacji Iub \ n
dla nowej linii.

[ 2^ Aby zapisać dane w pliku, użyj W rite() oraz W riteLine() .


Wymienione metody działają dokładnie tak samo jak te z klasy Console: W rite () zapisuje tekst,
W rite L in e () zapisuje go i dodaje znak końca linii. W łańcuchu znaków możesz także umieścić {0 } , { 1 } ,
{2} itd. W ten sposób można skorzystać z dodatkowych parametrów: {0} zostanie zastąpione pierwszym
znajdującym się po wpisanym łańcuchu znaków parametrem, {1} następnym i tak dalej.
w r i t e r . W r i t e l _ i n e ( " { 0 } j e s t us t a wi ony na t e m p e r a t u r ę {1} s t o p n i . " , a p p l i a n c e , t emp) ;

i? ) Metoda C lose() zwalnia plik.


Jeśli zostawisz strumień otwarty i powiązany z danym plikiem, to nie będzie on mógł być wykorzystywany
przez inne programy. Upewniaj się zatem zawsze, że zamykasz otwierane pliki!
w rite r.C lo s e ();

jesteś tutaj ► 445


Zapisz to
Można być zdania,
że zapisywanie plików
w gfównym katalogu
Kanciarz wymyślił nowy diabelski plan na dysku nie jest dobrym
pomysłem; istnieje także
Mieszkańcy Obiektowa długo żyli w strachu przed Kanciarzem. Używa on teraz ryzyko, że używany system
operacyjny w ogóle na to
Stream W riter do implementacji kolejnego chytrego planu. Sprawdźmy, co się nie pozwoli. Dlatego wybierz
dzieje. Utwórz nową aplikację typu Console Application i dodaj poniższy kod do inny katalog, w którym
zapiszesz tworzony plik.
jej metody M a in () :
ś cieżka rozpoczyna się
od znaku @. Dzięki temu
StreamWriter nie będzie
Ten wiersz tworzy obiekt StreamWriter
interpretował „\" jako początku
i określa lokalizację pliku■ sekwencji formatującej

[ S
Stream W riter sw = new S tre a m W rite r(@ " c :\ ta jn y p l a n . t x t " ) ;

^ --» s w .W r it e L in e CW j a k i sposób pokonać K a p itan a W sp a n ia łe g o ?");

s w .W r it e L in e (" K o le jn y g e n ia ln y , t a jn y plan K a n c ia r z a ." ) ;


WriteLineO
dodaje znak sw.W r it e ( "Stw orzę arm ię klonów, " );
nowego w i e r s z o ^ ^ r
na ^0*cuWriteO sw .W rite L in e (" u w o ln ię j e i w ystaw ię przeciw ko mieszkańcom O b ie k to w a ." );
napisu
po prostu Czy potrafisz określić,
w y syta tekst s t r in g lo c a t io n = "centrum h an d lo w e."; co się dzieje ze zmienną
— bez żadnych location w tym kodzie?
dodatkowych fo r ( i n t number = 0 ; number <= 6 ; number++) {
znaków.
sw .W rite L in e (" K lo n numer { 0 } a ta k u je { 1 } " , number, lo c a t i o n ) ;

if (lo c a t io n == "centrum hand low e.") { lo c a t io n = "centrum m iasta^ )'; }


Możesz użyć {}
e ls e { lo c a t io n = "centrum han d lo w e."; } wewnątrz tekstu
w celu przekazania
} zmiennych. {0} jest
Close() likwiduje w^ ^ 10 zastępowane przez
połączenia z plikiem i zwajnia zasobi^ pierwszy parametr
Sw. Clo s e ( ) ; zajmowane przez S^^i^Mir^^. znajdujący się
Tekst nie zostanie wp1^ ^ , jeśli nie po łańcuchu znaków,
zamkniesz staumienia. {1} przez następny
i tak dalej.

Taki właśnie tekst generowany jest


przez powyższy kod■

Klasa Stream W riter


należy do pakietu
S y ste m .IO , dlatego
nie zapomnij
umieścić na samym
początku tego
programu wiersza
„using System.IO;”

446 Rozdział 9.
Odczyt i zapis plików

Magnesiki StreamWriter sw.W riteLine(Zap); __


Zap = "czerwono-pomaranczowa
Przypuśćmy, że masz taki kod dla metody b u tto n l_ C lic k () return true; ____
ja k podany poniżej. Twoim zadaniem je s t użycie magnesików
do utworzenia kodu klasy Flobbo, ta k aby po wykonaniu się
procedury obsługi zdarzenia tworzony był plik pokazany
w dolnej części strony. Powodzenia! m .
sw.W riteLine(Zap);
p riva te void button l_C H ck(ob je ct sender, EventArgs e) {
sw.Close();
Flobbo f = new Flobbo("n1eb1esko-żółtaM) ; return fa ls e;
StreamWriter sw = f.Snobbo();
f.B lobbo(f.B lobbo(f.B lobbo(sw ), sw), sw);
public bool Blobbo
} (bool Already, StreamWriter sw) {

public
bool Blobbo (StreamWriter sw)

sw.WriteLine(Zap);
Zap = "zielono-purpurowa";
return fa ls e ;

p riva te s trin g Zap;

public Flobbo(string Zap) {


t h is . Zap = Zap;
}

public StreamWriter Snobbo() {

W ynik:

Przyjmij, że na początku wszystkich


plików jest umieszczona instrukcja
using System.IO;.
jesteś tutaj ► 447
W czytaj to

Magnesiki StreamWriter. Rozwiązanie Jeśli faktycznie użyjesz tego


kodu, to plik a ra .tx t zostanie
Twoim zadaniem było poskładanie z magnesików klasy Flobbo,
umieszczony w folderze b in \
ta k aby utworzyła ona oczekiwany plik.
D ebug, wewnątrz folderu projektu
p riva te void buttonl_C l1ck(object sender, EventArgs e) { — to właśnie w nim będzie
Flobbo f = new Flobbo("n1eb1esko-żółtaM) ; bowiem wykonywany program.
StreamWrlter sw = f.Snobbo();
f.B lobbo(f.B lobbo(f.B lobbo(sw ), sw), sw);
}

W ramach przypomnienia:
publ ic class Flobbo
| pub w na.szych zagadkach używamy
dziwacznych nazw zmiennych
p riva te s trin g Zap; i metod cetowo — gdybyśmy
używati naprawdę dobrych
p u b lic Flobbo(string Zap) nazw, to zagadki byłyby
th is.Z ap = Zap; zbyt proste! Pamiętaj,
by nie używać takich nazw
} w swoim kodzie, dobra?

p u b lic StreamWriter Snobbo() Przyjmij, że na początku


return new wszystkich plików jest

U
S tre a m W rite r("a ra .tx t"); r umieszczona instrukcja
using System.IO;.

p u b lic bool Blobbo(StreamWriter sw) {


]< 5 r
sw.WriteLine(Zap);
Zap = "zielono-purpurowa";
Metoda Btobbo()
return fa ls e ; | jest przeciążona
i — posiada dwie
u _ r dektaracje z różnymi
parametrami.
p u b lic bool Blobbo

(bool Already, StreamWriter sw)


i f (Already) {

Jeśti uruchomisz ten


Upewnij się, kod w IDE, utworzy
że po zakończeniu pracy on ptik ara.txt
zamknąłeś plik. w katatogu bin\Debug.

sw.WriteLine(Zap);
W ynik:
Zap = "czerwono-pomarańczowa"
return tru e ;

448 Rozdział 9.
Odczyt i zapis plików

Krótka uwaga. Trochę tu igramy


Zapis i odczyt wymaga dwóch obiektów z prawidłowym znaczeniem słowa „strumień”
(ang. stream). StreamReader (dziedzicząca
po TextReader) jest klasą odczytującą
Przeczytajmy tajny plan Kanciarza za pomocą innego strumienia
ze strumienia kolejne znaki. Nie jest to
— będzie to StreamReader. Działa on dokładnie tak samo jak jednak strumień. Kiedy przekazujesz w jej
Stream W riter . Różnica polega na tym, że zamiast nazwy pliku do konstruktorze nazwę pliku, klasa ta tworzy
zapisu do konstruktora przekazujesz nazwę pliku do odczytu. Metoda strumień, który następnie zamyka w momencie
wywołania metody Close().Udostępnia
ReadLine() zwraca łańcuch znaków zawierający kolejny wiersz tekstu.
także przeciążony konstruktor, umożliwiający
Możesz napisać pętlę, która będzie odczytywała kolejne wiersze z pliku przekazanie obiektu Stream.
aż do momentu przyjęcia przez pole EndOfStream wartości tru e — Rozumiesz, jak to działa?
czyli dotąd, aż plik się skończy.
Ta metoda zwraca ścieżkę do folderu ¡Aoje dokumenty. Sprawdź typ wyliczeniowy
Stnng folder = ^ ^ ^ M c t e r by dowiedzieć się, jakie inne katalogi pozwala znajdowić. *
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
StreamReader reader = <" konstruktora StreamReaderprzekaż
Do ko
nazwę pliku, który chcesz odczytać.
new StreamReader(folder + @"\tajny_plan.txt"); Tym razem nie będziemy zapisywać
StreamWriter writer = do folderu C:\.

new StreamWriter(folder + @"\e-maildoKapitanaWspaniałego.txt");


^ ---------- programużywa StreamReader do odczytania planu Kanciarza oraz StreamWriter
do utworzenia pliku, który zostanie przesłany do Kapitana Wspaniałego.
writer.WriteLine("To: KapitanWspanialy@obiektowo.net");
writer.WriteLine("From: Komisarz@obiektowo.net");
writer.WriteLine("Subject: Czy możesz ocalić świat... po raz kolejny?");
writer.WriteLine(); ^ ___ -pusta metoda W^ ^ n a()
zapisuje pusty wiersz.
writer.WriteLine("Odkryliśmy plan Kanciarza:");
EndOfStream j e s t właściwością,
while (ireader.EndOfStream) { która określa, czy " strumieniu
pozostały je sz c z e ja k ieś dane
string lineFromThePlan = reader.ReadLine(); do odczytania.

writer.WriteLine("Plan -> " + lineFromThePlan);


} Ta pętla C zytu je linijkę
z reader i zapisuje ją
writer.WriteLine(); za pomocą writer.
writer.WriteLine("Czy możesz nam pomóc?");
writer.Close();
reader.Close();

Upewnij się, że zamknąłeś


każdy strumień, nawet wtedy,
gdy tylko czytasz z pliku.

t
Podczas tworzenia obiektów
StreamReader oraz StreamWriter
otworzyły one własne strumienie.
Wywołanie ich metod Close()
nakazuje im je zamknąć. jesteś tutaj ► 449
Nie w ychodź ze strum ieni

Dane mogą przechodzić przez więcej niż jeden strumień


Jedną z korzyści w pracy ze strumieniami .NET jest możliwość przesyłania danych Stream
do celu przez większą ich liczbę. Jednym z wielu typów strumieni udostępnianych
przez .NET jest klasa CryptoStream. Pozwala ona zaszyfrować dane, zanim zrobisz C lo s e ()
z nimi cokolwiek innego: R e a d ()
S e e k ()
W rite ()

w przypadku zastosow an ia normalnego CryptoStream


strum ienia FileStream Twoje dane
zapisyw an e s ą bezpośredn.o do phku
C lo s e ()
w postaci tek stu .
R e a d ()
S e e k ()
W rite ()

CryptoStream
dziedziczy
po abstrakcyjnej klasie
Stream tak samo jak
inne klasy strumieni.

C ry p to S tre a m j e s t Teraz Fi'feStream zapisuje


połączony z F ile S tre a m zaszyfr<°wany tekst do pfiku.
i przekazuje do niego
Do CryptoStream ten sam tek st, a/e
zapisujesz zwykty zaszyfrowany.
tekst. *

% \i <#

Możesz tworzyć ŁAŃCUCHY


strumieni. Jeden może
zapisywać do innego, a ten
z kolei do następnego...
Często na końcu występuje
strumień plikowy lub sieciowy.
450 Rozdział 9.
Odczyt i zapis plików

Zagadkowy basen
Twoim za d a n iem jest pobranie fragmentów kodu
z basenu i wstawienie ich w puste miejsca.
Możesz użyć tego samego fragmentu
więcej niż raz i nie musisz wykorzystać
ich wszystkich. Celem jest napisanie
programu, który wyświetli komunikat
zaprezentowany po prawej stronie. p u b lic class Pizza
p riv a te ______
p u b lic class Pineapple { p u b lic Pizza(_
const __________ d = "dostaw a.txt"; .w r ite r = w rite r;
p u b lic ______________________ }
{ North, South, East, West, Flamingo } p u b lic void .Fargo f)
p u b lic s ta tic void M a in (s trin g [] args ) { w rite r. _ (f);
____ o = new ___________________ ("zam 0w ienie.txt") w rite r. ();
Pizza pz = new Pizza(new _______________(d, tru e )):
}
pz. ___________(Fargo.Flamingo);
fo r ( w = 3; w >= 0; w--) {
Pizza i = new Pizza
p u b lic class Party
(new ______________(d, fa ls e ) );
p riv a te _______ reader;
i.Idaho((Fargo)w );
p u b lic Party(_ reader)
Party p = new Party(new __________ (d ));
.reader = reader;
p. _______________(o );
I
I
p u b lic void HowMuch(______ . q)
o. ______________("To wszystko, kochani!");
o. () q. __________ (reader. ());
reader. ();

} }
Przypominamy:
każdy fragment
z basenu może
zostać użyty
więcej niż raz!

jesteś tutaj ► 451


Praw dziw y dialog

Zagadkowy basen. Rozwiązanie

Ten typ wyliczeniowy


(a zwłaszcza jego metoda
ToStrmgO) jest używany
do generowania znacznej
części wyników programu.

p ub lic class Pineapple { I


const s trin g d = "d ostaw a .txt"; M/
p u b lic enum Fargo { North, South, East, West, Flamingo }
p u b lic s ta tic void M a in (s trin g [] args ) {
StreamWriter o = new Stream W riter("zam ów ienie.txt");
Tu znajduje się punkt wej ścia Pizza pz = new Pizza(new StreamWriter(d, tr u e ) );
programu. Tworzy on obiW pz.Idaho(Fargo.Flamingo);
StreamWriter, który następni®
przekazuje do k|asy Party. fo r ( in t w = 3; w >= 0; w--) {
Przegląda też wszystkie Pizza i = new Pizza(new StreamWriter(d, fa ls e ) );
składowe Fargo, przekazuj ąc i.Idaho((Fargo)w );
każdą z nich do w yporna
w metodzie Pizza.Idaho(). Party p = new Party(new StreamReader(d));
p.HowMuch(o);
}
o.W riteLine("To wszystko, ko cha ni!");
o.C lose();

I
Klasa Pizza obiekt
p ub lic class Pizza {
p riv a te StreamWriter w rite r; IdahoO za p°mocę
p u b lic Pizza(StreamWriter w rite r) j
th ls .w r ite r = w rite r; metoda WriteLineO wywofuje
automatycznie.
I
p u b lic void Idaho(P1neapple.Fargo f) j
w rite r.W rite L in e (f);
w rite r.C lo s e ();
I

pub lic class Party {


Klasa Party posiada p riv a te StreamReader reader;
pole StreamReader oraz p u b lic Party(StreamReader reader) j
metodę HowMuch(), która
odczytuje wiersz z tego th ls .re a d e r = reader;
obiektu StreamReader I
i zapisuje go do
StreamWriter. p u b lic void HowMuch(StreamWriter q)
q.W riteLine(reader.ReadL1ne());
reader. Close ();
I

452 Rozdział 9.
Odczyt i zapis plików

Użyj wbudowanych obiektów do wyświetlenia


standardowych okien dialogowych
Podczas pracy z p ro g ram em , który odczytuje i zapisuje pliki, istnieje duże
praw dopodobieństw o , że będziesz m usiał wyświetlić o k n o dialogow e w yboru pliku.
T o dlatego p latfo rm a .N E T d o starczan a je s t z obiek tam i, k tó re wyświetlają
standardow e o kna tego ro d zaju w W indows.

To je s t okno
dialogowe
fo ld e r B r o w s e D ia lo g .

■NET posiada ¡wbudowane okna


dialogowe. Tutaj pokazano
OpenFileD'ialog, które stuży
do otwierania plików.

ShowDialog() wyświetla okno dialogowe


P rzejdziesz przez
W yśw ietlenie o k n a dialogow ego je s t proste. tych kilka etapów
za chwilą.
O to co m usisz zrobić:

U tw órz instancję o b iek tu o k n a dialogow ego. M ożesz teg o d o k o n ać w kodzie


za p o m o cą new lub przeciągając odpo w iedn ią ikonę o k n a n a form ularz.

U staw właściwości o b iek tu o k n a dialogow ego. D o tych najw ażniejszych n ależ ą T it le


(k tó ra ustaw ia te k st n a p ask u tytułow ym ), I n it ia lD ir e c t o r y (określająca katalog
początkow y) o ra z FileName (dla o kien dialogowych O twórz i Z apisz).

W ywołaj m eto d ę ShowDialog() . W yświetli o n a o k n o dialogow e i zatrzym a


w ykonywanie p ro g ram u aż do jego zam knięcia albo kliknięcia przez użytkow nika
przycisku O K lub A n u lu j.

M eto d a ShowDialog() zw raca w arto ść D ialog R esu lt , k tó ra je st typem


w yliczeniowym. Z aw iera on m iędzy innym i p o la OK (oznaczające, że użytkow nik
nacisnął O K), Cancel, Yes i No (dla o kien dialogowych Tak/Nie).

jesteś tutaj ► 453


Okna dialogow e to także obiekty

Okna dialogowe są kolejnymi kontrolkami .N ET


Możesz dodać standardowe okna dialogowe wyboru plików Windows, przeciągając
Słowo „niewizualna“
odpowiednią ikonę na formularz — po prostu złap k o n tro lk ę O p e n F ile D ia lo g i upuść oznacza, że nie będzie się
ją na nim. Zamiast pokazywać ją na formularzu, IDE wyświetli ją w obszarze nieco ona pojawiała na formularzu
po jej przeciągnięciu z okna
poniżej. To dlatego, że jest to k o m p o n e n t — specjalny rodzaj k o n tr o lk i n ie w iz u a ln e j,j Toolbox.
która nie pojawia się bezpośrednio na formularzu. W dalszym ciągu możesz jej jednak
używać w jego kodzie, tak samo jak to robiłeś z pozostałymi kontrolkami.

Gdy przeciągniesz komponent


z ¿kna Toolbox na form/Jldfz, ID t
będzie go wyświetla.^ w obszai'ze
poniżej edyto/:

Właściwość I nitialDirectory zmienia nazwę


folderu, który jest wyświetlany podczas
pierwszego otwarcia okna dialogowego. Właściwość Filter pozwala
zmienić filtry wyświetlane
y / w dolnej części okna
o p e n F ile D ia lo g l.I n itia lD ir e c to r y " c :\M ó jF o ld e r \D o m y ś ln e \" ; dialogowego (są to między
o p e n F ile D ia lo g l.F ilte r " P l ik i tekstow e ( * . t . x t . ) |* . t . x t . |" ^ p l i k ó w pokazywanych

+ " P lik i z w a r to ś c ia m i o d d z ie lo n y m i p rz e c in k ie m ( * .c s v ) |* .c s v |

^ W s z y s tk ie p lik i

o p e n F ile D ia lo g l.F ile N a m e = " p lik _ d o m y S ln y .tx t" ;


Te właściwości pozwalają oknu
o p e n F ile D ia lo g l.C h e c k F ile E x is ts = tru e ; C dial°g°wemu wyświetlić komunika!-
o błędzie podczas prdby ^ a r n tz pliku
o p e n F ile D ia lo g l.C h e c k P a th E x is ts = fa ls e ; \ lub lokalizacji, które nie istnieją-

D ia lo g R e s u lt r e s u l t = o p e n F i l e D i a l o g 1 .S h o w D i a l o g ( ) ;

if ( r e s u l t = D ia lo g R e s u lt.O K ) {

O p e n S o m e F il e ( o p e n F i le D ia l o g l.F il e N a m e ) ;

}
Z
Okna dialogowe wyświetla się za pomocą ShowDialog().

5 ; = ^ % zn o s|

n ao w zo śast
zostało Anuluj, będzie ona równa DialogResult.Cancel.

454 Rozdział 9.
Odczyt i zapis plików

Okna dialogowe także są obiektami


Obiekt OpenFUeDialog pokazuje standardowe okno dialogowe Windows służące
do otwierania plików, SaveFUeDialog jest natomiast standardowym oknem do
zapisywania. Możesz je wyświetlić, tworząc nową instancję, ustawiając właściwości
obiektu i wywołując metodę S h o w D ia lo g () . Zwraca ona typ wyliczeniowy
D ia lo g R e s u lt (niektóre okna dialogowe mają więcej niż dwa przyciski lub rezultaty,
więc zwykła wartość bool nie byłaby wystarczająca).
Kiedy przeciągasz okno dialogowe zapisu na
formularz, IDE^doda^ wiersz podobny do tego
s a v e F i l e D i a l o g l = new S a v e F i l e D i a l o g ( ) ; metodzie InitializeComponent().

s a v e F ile D ia lo g l.I n itia lD ir e c to r y = @ " c :\M ó jF o ld e r \D o m y ś ln e \" ; Nietrudno domyślić się,


jak należy określać
s a v e F i le D ia l o g l.F i lte r = " P lik i te k s to w e ( * .t x t ) |* .t x t |" wartość właściwości Filter.
Wystarczy porównać to,
+ " P lik i z w a r to ś c ia m i o d d z ie lo n y m i p rz e c in k ie m ( * .c s v ) |* .c s v | i co znajduje się pomiędzy
znakami „¡“ w tańcuchu
^ W s z y s tk ie p li k i ( * .* j |* .* " ; znaków, z tym, co jest
wyświetlane w oknie
D ia lo g R e s u lt r e s u l t = s a v e F ile D ia lo g 1 .S h o w D ia lo g () ; dialogowym.

if ( r e s u l t == D ia lo g R e s u lt.O K ) {
' Metoda ShowDialogO oraz
S a v e T h e F ile (s a v e F ile D ia lo g l.F ile N a m e );
V M 9 - dokładnie taksamojak w przypadku
obiektu OpenFUeDialog.
}

Zakładamy tu, że w pi-ogramie


jest zdefiniowana metoda Obiekt SaveFUeDialog
o nazwie SaveT^^I^X do l? ra standardowe
okno Windows
której jest przekazyiuomo-
nazwa pliku. „Zapisywanie jako...

Zapisywaniejako

î J¿ « M ó jF o ld e r ► D o m y ś ln e P rz e sz u k a j: D o m y ś ln e
Właściwość Title
pozwala zmienić O rg a n iz u j T N o w y f o ld e r

ten tekst. N azw a D a ta m o d y f ik a c ji Typ


1i Ç K o m p u te r
Ż a d n e e l e m e n t y n ie p a s u ją d o k r y te r ió w w y s z u k iw a n ia .
* i a i D y sk lo k a ln y ( Û )

P B b A rc h iw u m

J J O M ó jF o ld e r

D o m y ś ln e

P L?ls M u lt im e d ia Gdy użytkownik wybiera Zmień listą „Zapisz


Metoda plik, jego pełna ścieżka
) . P e rfL o g s
zapisywana jest jako typ” przy użyciu
ShowDialog() i> ^ P r o g r a m F iles
właściwości Filter.
wyświetla t> tm p we właściwości FileName
okno dialogowe l> J . U ż y tk o w n ic y

i otwiera katalog t> J e W in d o w s

określony b J ll x a m p p

właściwością > « - s S v s te m f O ł V < Wartość DialogResult


InitialDirectory. N a z w a p lik u : ^ .
zwrócona przez
metodę ShowDialog()
Pliki te k s t o w e (*.txt)
pozwala określić,
który z przycisków
* U k ry j f o ld e r y Z a p is z A n u lu j zostat naciśnięty.

jesteś tutaj ► 455


Towarzystwo katalogu

Używaj wbudowanych klas File


oraz Directory do pracy z plikami i katalogami
Podobnie do Stream W riter , klasa F ile tworzy strumienie plików niejako w tle.
Możesz użyć jej metod do wykonania większości popularnych czynności bez potrzeby
wcześniejszego tworzenia FileStream . Obiekt D irectory pozwala na pracę z pełnymi
plików katalogami.

Co można zrobić z File:

S P R A W D Z I Ć , C Z Y IS T N IE J E .
Możesz sprawdzić, czy plik istnieje, korzystając Fiłefnfo działa prawie
z metody E x is ts ( ) . Zwróci ona tru e , jeżeli jest taki fak samo ja k File
plik, lub fa ls e , jeżeli go nie ma.
Jeżeli zamierzasz intensywnie pracować z plikiem
O D C Z Y T A Ć C O Ś L U B Z A P IS A Ć D O P L IK U rozsądnym rozwąz^em jest utworzenie instancji
Możesz użyć metody OpenRead(), aby pobrać dane aSy ,Fi,le In fo zamiast korzystania ze statycznych
metod klasy F ile . y ^"ych
z pliku, oraz C reate() lub O penW rite() , aby je
zapisać. K|asa Ffelnfo posłuży do wykonywania tych samych
czynności co F ile , a|e najpierw musisz utworzyć
D O D A Ć D O P L IK U T E K S T . jej instanqę. Mcvesz to zrobić, a następnie uzyskać
Metoda AppendAllText() pozwala na dodanie dostęp do jej metody E x is ts () lub OpenRead()
tekstu do już istniejącego pliku, natomiast gdy nie ma w dokładnie taki sam sposób jak w przypadku F ile .
go jeszcze w chwili jej wywołania, zostaje utworzony. Jedyną różnicą jest to, ze klasa F ile jest szybsza dla
niewielk'ej liczby operaq l ii Fi le In fo le piej sprawuje
P O B R A Ć I N F O R M A C J E O P L IK U się przy dużych zadaniach uje
Metody GetLastAccessTime() oraz
GetLastW riteTime() zwracają datę i czas ostatniego
dostępu do pliku lub ostatniej modyfikacji.

File jest klasą statyczną,


a zatem stanowi jedynie zbiór
Co można zrobić z Directory: metod pozwalających na pracę
z plikami. Natomiast- AM nn
jest obiektem — możemy
U T W O R Z Y Ć N O W Y KATALOG. utworzyć jego instancję, a jej
metody będą analogiczne do
Katalogi można tworzyć przy użyciu metody C reateD irectory().
metod klasy File.
Wystarczy, że przekażesz odpowiednią ścieżkę, a metoda zrobi resztę.

& P O B R A Ć L IS T Ę P L I K Ó W Z N A J D U J Ą C Y C H SIĘ W K A T A L O G U
Możesz utworzyć tablicę plików umieszczonych w katalogu, używając do tego
metody G e tF ile s () — po prostu przekaż w jej wywołaniu nazwę katalogu,
o którym chcesz pobrać informacje, a reszta zostanie wykonana automatycznie.

^ U SU N Ą Ć KATALOG.
Usuwanie katalogu jest także wyjątkowo proste. Użyj metody D e le te () .

456 Rozdział 9.
Odczyt i zapis plików

■ Nie .istnieją.
głupie pytania

P W dalszym ciągu nie mogę pojąć tego { 0 } i {1}


:
StremReader oraz Stream W riter konwertują bajty na znaki
— procesy te określane są mianem kodowania i dekodowania.
w StreamWriter .
Pamiętasz z rozdziału 4., że zmienna byte może przechowywać
O : Gdy zapisujesz do pliku łańcuchy znaków, dość często zachodzi
dowolną wartość pomiędzy 0 i 255? Każdy plik na dysku twardym
jest jedną długą sekwencją liczb z tego zakresu. Od programów
konieczność wstawienia do nich zestawu zmiennych. Możesz na
czytających i zapisujących zależy sposób interpretacji bajtów jako
przykład napisać coś takiego:
danych, które mają jakieś znaczenie. Gdy otwierasz plik w Notatniku,
w rite r.W rite L in e (" M a m na im ię " + name + " i mam każdy pojedynczy bajt konwertowany jest na odpowiedni znak — na
" + age + " l a t . " ) ; przykład wartość 69 zamieniana jest na E, natomiast 97 to a (choć
Używanie + do łączenia łańcuchów znaków jest bardzo męczące wynikowa litera zależy także od zastosowanego kodowania... ale o tym
i może spowodować pojawienie się pewnych błędów. Łatwiej jest powiemy za chwilę). Kiedy wpisujesz tekst i zapisujesz go w Notatniku,
skorzystać z formatu {0 } i {1 }: ten konwertuje każdy znak, zamieniając go z powrotem na bajty
w rite r.W rite L in e (" M a m na im ię {0 } i mam {1 } l a t . " , i zachowując na dysku. Gdy w ten sam sposób chcesz zapisać zmienną
name, a g e ); typu s t r in g do strumienia, wykonywana jest podobna operacja.

Znacznie lepiej czyta się taki kod, zwłasza jeśli zmienne zapisane są
w jednym wierszu.
P : Używam StreamWriter do zapisywania plików. Po co
mam wiedzieć, że tworzy on za mnie obiekt FileStream ?
P : Dlaczego umieściłeś @ na początku łańcucha znaków, O : Jeśli tylko zapisujesz lub odczytujesz z pliku kolejne wiersze
który zawiera nazwę pliku? w określonym porządku, musisz jedynie wiedzieć o klasach

O : Metody W rite () oraz W rite L in e () obsługują sekwencje


Stream Reader i S tre a m W rite r. Jeżeli jednak chcesz zrobić
z plikami coś bardziej skomplikowanego, będziesz musiał
formatujące takie jak \ r i \n. Przez to znacznie trudniej jest wprowadzać
popracować z innymi strumieniami. Jeśli kiedykolwiek będziesz
nazwy plików zawierające znaczną liczbę odwrotnych ukośników. Jeżeli
musiał do pliku zapisać dane w postaci liczb, tablic, kolekcji lub
na początku łańcucha znaków umieścisz @,to C# nie będzie interpretował
obiektów, to S tre a m W rite r może nie wystarczyć. Nie przejmuj się,
sekwencji formatujących. Dodatkowo będzie umieszczał we wpisywanym
dostarczymy Ci znacznie więcej szczegółów dotyczących działania
fragmencie przejścia do nowego wiersza. Możesz zatem nacisnąć
strumieni dosłownie za minutę.
podczas pisania Enter, a znak nowego wiersza zostanie automatycznie
dodany na wyjście:
s t r in g tw o Lin e = @"To j e s t Łańcuch znaków,
P : A gdybym chciał utworzyć swoje własne okna
dialogowe? Czy mogę to robić?
k tó ry b ę d zie zajm ował dwie l i n i e . " ;
O : Oczywiście, że tak. Masz możliwość dodania do projektu
P A co oznacza to \n i \ t , możesz powtórzyć?
: nowego formularza i zaprojektowania go w taki sposób, aby
wyglądał dokładnie tak, jak chcesz. Potem możesz utworzyć nową
O : Są to sekwencje formatujące. \n jest wstawieniem nowego instancję tego okna za pomocą new (tak samo jak tworzyłeś obiekt
wiersza, \ t to znak tabulacji. \ r jest znakiem powrotu lub połową O p e n F ile D ia lo g ). Od tego momentu będziesz mógł wywoływać
powrotu w Windows — w plikach tekstowych Windows wiersze metodę S h ow D ialo g () i będzie ona działała dokładnie tak samo
muszą się kończyć sekwencją \ r \ n (o czym już wspominaliśmy jak przy każdym innym oknie dialogowym.
w rozdziale 8., gdy przedstawialiśmy stałą E n v iro n m e n t.
N ew Line). Jeżeli w łańcuchu znaków musisz użyć odwrotnego P : Dlaczego powinienem pamiętać o zamykaniu strumieni
ukośnika, a nie chcesz, aby był interpretowany jako początek po zakończeniu pracy z nimi?
sekwencji formatującej, to powinieneś skorzystać z dwóch
odwrotnych ukośników: \ \ . O : Czy kiedykolwiek miałeś do czynienia z sytuacją, gdy edytor
tekstu nie pozwoli Ci na otwarcie pliku, ponieważ był on używany
P : Na początku była mowa o konwersji łańcucha znaków przez inny program? Kiedy jakaś aplikacja korzysta z pliku, system
Windows blokuje do niego dostęp i uniemożliwia innym programom
na tablicę bajtów. Jak to może działać?
korzystanie z niego. Będzie to robi także wtedy, gdy to Twoja
O : Pewnie wielokrotnie słyszałeś, że pliki na dysku reprezentowane aplikacja otworzy plik. Jeżeli nie wywołasz metody C lo s e ( ) ,
są przez bity i bajty. Oznacza to, że podczas ich zapisu system zostanie on prawdopodobnie zablokowany aż do momentu
operacyjny traktuje je jako jedną długą sekwencję bajtów. Obiekty zakończenia programu.

jesteś tutaj ► 457


N otatnik — zrób to sam

m. Zaostrz ołówek
" .NET udostępnia dwie klasy z zestawem metod statycznych do pracy z plikami
_ i katalogami. Klasa F ile umożliwia pracę z tymi pierwszymi, natomiast klasa
' D irectory z drugimi. Napisz, co według Ciebie robi każda z tych linijek kodu.

Kod Co robi kod


i f (!D irectory.E xists(@ "c:\S Y P ")) {
D irectory.C reateDirectory(@ "c:\SYP");
}
i f (Directory.Exists(@ "c:\SYP\Bonk")) {
Directory.Delete(@ "c:\SYP\Bonk");
}
Directory.CreateDirectory(@ "c:\SYP\Bonk");

Directory.SetCreationTime(@"c:\SYP\Bonk",
new DateTime(1976, 09, 2 5));

s tr in g [] f ile s = D irectory.G etFiles(@ "c:\w indow s\",


" * .lo g " , S earch O ptio n .A llD irectories);

F ile.W riteA llText(@ "c:\S Y P \B onk\dziw ak.txt",


To je s t pierwsza lin i j k a ,
to je s t druga lin i j k a ,
a to je s t o statnia l i n i j k a . " ) ;

File.E ncrypt(@ "c:\S YP \Bonk\dziw ak.txt");


Sprawdź, czy potrafisz odgadnąć,
co robi ta metoda — jeszcze jej
nie widziałeś.
File.Copy(@ "c:\SYP\Bonk\dziwak.txt",
@ "c:\S Y P \kopia.txt");

DateTime myTime =
Directory.GetCreationTime(@"c:\SYP\Bonk");

File.SetLastW riteTim e(@ "c:\SYP\kopia.txt", myTime);

File.D elete(@ "c:\SYP \Bonk\dziw ak.txt");

458 Rozdział 9.
Odczyt i zapis plików

Oto sztuczka, która pozwala wyświetlić kontrolkę


Używaj okien dialogowych TextBox tak, by zajęła cały obszar formularza.
Przeciągnij komponent Tabl eLayoutPanel z palety
do otwierania i zapisywania plików Containers na formularz, a następnie przypisz
jego właściwości Dock wartość Fill,a właściwości
Rows iColumns ustaw tak, by kontrolka miała dw a
(wszystko za pomocą kilku linijek kodu) wiersze ijedną kolumnę. Do górnej komórki układu
przeciągnij kontrolkę TextBox., a je właściwości
Możesz napisać program, który będzie otwierał pliki tekstowe. Będzie Dock przypisz wartość Fill. Do dolnej natomiast
on też pozwalał na wprowadzanie w nich zmian oraz ich zapisywanie, przeciągnij komponent FI owLayoutPanel,przypisz
a wszystko to przy wykorzystaniu prostego kodu i standardowych jego właściwości Dock wartość Fill,ustaw
właściwość FlowDirection na RightToLeft,
kontrolek .NET. A oto jak to zrobić: po czym umieść na nim d w a przyciski. Ustaw wielkość
Z ró b to ! górnego wiersza komponentu Tabl eLayoutPanel
Utwórz prosty formularz. na 100% izmień wielkość dolnego tak, by przyciski
dobrze się w nim mieściły. Teraz Twój edytor będzie
Będziesz potrzebował tylko pola edycyjnego i dwóch przycisków.
doskonale reagował na zmiany wielkości okna!
Przeciągnij i upuść na formularz kontrolki OpenFileDialog
i S aveF ileD ialog . Kliknij je dwukrotnie, aby utworzyć procedury
obsługi zdarzeń, i dodaj do formularza prywatne pole typu P ro s ty e d y to r te k s tó w

str in g o nazwie name. Nie zapomnij o wstawieniu na początku


pliku instrukcji using System.IO . T° jest wietotiniowe
pote edycji, którego
[ 2) Połącz przycisk Otwórz z OpenFileDialog. właściwość Dock ma
Przycisk O twórz wyświetla OpenFileD ialog , a następnie używa wartość Fitt
F ile .R e a d A llT e x t() do odczytania zawartości pliku i wstawienia jej
Otwórz Zapisi
do wielowierszowego pola tekstowego:
p riv a te void open_Click(object sender, EventArgs e) {
i f (openFileDialog1.ShowDialog() == DialogResult.OK) {
name = openFileDialogl.FileName;
te x tB o x l.C le a r();
textB oxl.T ext = File.ReadAllText(name); Kliknięcie przycisku
Otwórz pokazuje okno
} dialogowe OpenFileDialog.
}
Połącz teraz z odpowiednią metodą przycisk Zapisz.
Przycisk Z apisz do zachowywania pliku używa metody F ile .W r ite A llT e x t( ) :
Metody ReadAllText() oraz
p riv a te void save_C lick(object sender, EventArgs e) { WriteAllText() są częściam'[
i f (saveFileDialog1.ShowDialog() == DialogResult.OK) { klasy File. Zajmiemy się nią
na następnej stronie. Na.
name = saveFileDialogl.FileName; kolejnych zamieścimy więceJ
File.W riteAllText(nam e, te xtB o x l.T e x t); szczegółów.
}
}
[ 4) Pobaw się innymi właściwościami okien dialogowych.
★ Użyj właściwości T i t l e obiektu SaveFileD ialog do zmiany tekstu
wyświetlanego na pasku tytułowym.
Jeśti nie dodasz fittra, tisty
rozwijane w dotnej części
★ Ustaw właściwość I n it ia lD ir e c t o r y , aby okno dialogowe okien diatogowych odczytu
OpenFileDialog otwierało określony katalog. ' zapisu będą puste. Spróbuj
użyć tego: „Pliki tekstowe
★ Zmień filtr okna dialogowego OpenFileDialog tak, aby wyświetlane (*.txt)j*.txt".
były tylko pliki tekstowe. Użyj właściwości F i lt e r .
jesteś tutaj ► 459
W yrzuć do w łaściw ego pojem nika

Zaostrz
¿u u su ołówek

V R O Z W ią Z Q nie NET udostępnia dwie klasy z zestawem metod statycznych do pracy z plikami
i katalogami. Klasa F ile umożliwia pracę z tymi pierwszymi, natomiast klasa
D irectory z drugimi. Twoim zadaniem było napisanie, co robi każda linijka kodu.

Kod Co robi kod


i f (!D irectory.E xists(@ "c:\S Y P ")) { Sprawdź, czy katalog c:\SYP istnieje.
D irectory.C reateDirectory(@ "c:\SYP"); Jeśli nie, utwórz go.

}
i f (Directory.Exists(@ "c:\SYP\Bonk")) { Sprawdź, czy katalog c:\SYP\Bonk istnieje.
Directory.Delete(@ "c:\SYP\Bonk"); Jeśli tak, usuń go.

}
Directory.CreateDirectory(@ "c:\SYP\Bonk"); Utwórz katalog C:\SYP\Bonk.

Directory.SetCreationTime(@"c:\SYP\Bonk", Ustaw czas stworzenia katalogu c:\SYP\Bonk


new DateTime(1976, 09, 2 5)); na 25 września 1976 roku.

s tr in g [] f ile s = D irectory.G etFiles(@ "c:\w indow s\", Pobierz listę plików w c:\windows, które są
" * .lo g " , S earch O ptio n .A llD irectories); zgodne z wzorcem *.log, włączając w to pliki
we wszystkich podkatalogach.

F ile.W riteA llText(@ "c:\S Y P \B onk\dziw ak.txt", Utwórz plik o nazwie dziwak.txt (o ile jeszcze nie
To je s t pierwsza lin i j k a , istnieje) w katalogu c:\SYP\Bonk i zapisz do niego
to je s t druga lin i j k a , trzy wiersze tekstu.
a to je s t o statnia l i n i j k a . " ) ;
File.E ncrypt(@ "c:\S YP \Bonk\dziw ak.txt"); Skorzystaj z wbudowanego mechanizmu
To je st alternatywa szyfrowania Windows w celu zakodowania pliku
dla CryptoStream. dziwak.txt, używając do tego danych z konta
zalogowanego użytkownika.
File.Copy(@ "c:\SYP\Bonk\dziwak.txt", Skopiuj plik c:\SYP\Bonk\dziwak.txt
@ "c:\S Y P \kopia.txt"); do c:\SYP\kopia.txt.

DateTime myTime = Zadeklaruj zmienną myTime i przypisz jej czas


Directory.GetCreationTime(@"c:\SYP\Bonk"); utworzenia katalogu c:\SYP\Bonk.

File.SetLastW riteTim e(@ "c:\SYP\kopia.txt", myTime); Zmodyfikuj czas ostatniego zapisu pliku kopia.txt
w katalogu c:\SYP tak, aby był równy czasowi
przypisanemu do zmiennej myTime.

File.D elete(@ "c:\SYP \Bonk\dziw ak.txt"); Usuń plik c:\SYP\Bonk\dziwak.txt.

460 Rozdział 9.
Odczyt i zapis plików

Dzięki IDisposable obiekty usuwane są prawidłowo


Zadeklaruj
Wiele klas .NET implementuje wyjątkowo użyteczny interfejs o nazwie IDisposable . obiekt w bloku
Posiada on tylko jedną składową: metodę o nazwie D ispose() . Implementując go,
klasa informuje, że w celu prawidłowego zlikwidowania obiektu musi wykonać jakieś using, a metoda
ważne operacje. Zazwyczaj są one związane z koniecznością zwolnienia przydzielonych Dispose()
wcześniej zasobów, których obiekt nie odda, dopóki jawnie nie każesz mu tego zrobić.
Metoda Dispose() pozwala poinformować obiekt, że ma zwolnić swoje zasoby.
zostanie
wykonana
Możesz użyć opcji Go To D efinition. W ten sposób zostaniesz przeniesiony
w miejsce oficjalnej definicji ID isposable w C#. Przejdź do projektu i wpisz
automatycznie.
„IDisposable” gdziekolwiek wewnątrz kodu. Kliknij wpisany tekst prawym
przyciskiem myszy i z menu wybierz Go To D efinition. Otworzysz nową kartę
z kodem. Rozwiń jego fragment, a zobaczysz coś takiego:

namespace System Większość klas alokuje ważne zasoby,


na przyktad pliki, pamięć czy inne °biekty.
Oznacza to, że przejmują nad nimi kontrolę
{ i oddają je dopiero wtedy, gdy wyk°nywa.ne
na nich operacje zostaną ukończone.
// Summary:
// De fi ne s a method to r e l e a s e a l l o c at ed r e s o u r c e s .
public interface IDisposable

{
// Summary:
// Performs a p p l i c a t i o n - d e f i n e d tasks
// a s s o c i a t e d with f r e e i n g , releasing, or
// r e s e t t i n g unmanaged r e s o u r c e s .

void DisposeQ;
Każda klasa implementująca IDisposable
zwalnia zarezerwowane wcześniej zasoby
zaraz po wywotaniu Dispose(). Jest
to prawie zawsze ostatnia operacja Alokować, czasownik
wykonywana na obiekcie.
przydzielac przyda środki na różne cele.
Go To Definition Dział programistyczny zirytowało
postępowanie szefa, ponieważ
zaalokował on wszystkie pokoje
f s o ,2 an ^e : j b s po ^ o uyom j b T
pr z , l
konferencyjne na potrzeby zbędnego
seminarium zarządu.
s ę wdo‘m o p r;.^ ^ sk ;rgo dysznyu;I,^ ^ ^ ^ m ^ ;;<wy b^ '^ d y^ ° !;:inDEwsabmanp ^ 5 :
t a kże nacisnąć klawisz F12. y iera° ° P < j Z m e n u m ° ż e s z

jesteś tutaj ► 461


To dużo spotkań z w eterynarzem

Unikaj błędów systemu plików, korzystając z instrukcji using


Przez cały rozdział wspominaliśmy o konieczności zamykania strum ieni. To dlatego,
że większość najbardziej pospolitych błędów popełnianych przez programistów
i dotyczących plików powstaje na skutek niewłaściwego kończenia pracy z nimi.
Na szczęście C# udostępnia wspaniałe narzędzie, dzięki któremu mamy pewność,
że nigdy Ci się to nie przytrafi: ID isposable oraz metodę D ispose() . Jeśli otoczysz kod
związany ze strumieniem instru kcją using , to strumień ten zostanie automatycznie . Iw using różnią się
od tych, które umieszczasz
zamknięty. Wystarczy tylko, że zadeklarujesz jego referencję , korzystając z using , na początku kodu.
a po niej umieścisz blok kodu (wewnątrz nawiasów klamrowych), który tej referencji
używa. Gdy to zrobisz, instrukcja using automatycznie wywoła metodę Dispose()
strum ienia zaraz po zakończeniu bloku kodu. Wygląda to następująco : ■ a potem blok kodu
umieszczony wewnątrz
Za instrukcją using z<xwsze nawiasów klamrowych.
następuje deklaracja, °biektu■■■

using (StreamWriter sw = new StreamWriter("tajny_plan.txt")) {

sw.WriteLine("W jaki sposób pokonać Kapitana Wspaniałego?");


Te instrukcje mogą
sw.WriteLine("Kolejny genialny, tajny plan"); używać obiektów
utworzonych
sw.WriteLine("Kanciarza"); w instrukcjach
using, tak samo
''S jak używają każdego
} K l y mstrukcja using
innego obiektu.
się zaitchczy, uruchomiona
zo^ ^ 11 metoda DisposeO
używanegn obiektu.
D i s o t spowoduje jego W szystkie
Każdy strum ień posiada metodę zamknięcie. strumienie
D is p o s e () , któ ra go zamyka.
A zatem jeśli zadeklarujesz go
implementują
w ew nątrz in strukcji u sin g , to IDisposable,
na pewno zostanie on zam knięty. zatem zawsze
gdy ich używasz,
Używaj wielokrotnych instrukcji using przy pracy z wieloma obiektami powinieneś
Możesz umieścić w kodzie wiele instrukcji using jedna nad drugą — nie potrzebujesz BEZ^VZGLĘDNIE
do tego żadnych dodatkowych nawiasów ani wcięć. Umicś^ jć j e -----------
using (StreamReader reader = new S tream R eader("tajny_plan.txt")) Wewnątrz instrukcji
using (StreamWriter w rite r = new S tre a m W rite r("e -m a il.tx t"))
using. W ten sposób
// I n s t r u k c je , k tó re używają obiektów re a d e r i w r it e r .
uzyskasz pewność,
, że zostaną
Nie musisz prawidłowo
p °automatycznie.
zrobi to za Ciebie - r ż K S f usm9
zamknięte!
462 Rozdział 9.
Odczyt i zapis plików

Kłopoty w pracy
Poznaj Damiana. Lubi on swoją pracę programisty C #, ale uwielbia także od czasu do
czasu skorzystać z urlopu na żądanie. Jego szef nie toleruje sytuacji, w których jego
podwładni urządzają sobie wolny dzień. Damian musi więc znaleźć dobrą wymówkę.

PRZEPRASZAM, TO JUZ DZIEW IĄTA


ALE MUSZĘ WYJŚĆ W IZ Y T A OD M AR C A,
WCZEŚNIEJ, PANIE SYNU JEŚLI DOW IEM SIĘ,
KIER O W N IKU M ÓJ KOT Z e m n ie o s z u k u j e s z ,
M A U M Ó W IO N Ą W IZYTĘ TO LEPIEJ, ZEBYŚ Z N A L A Z Ł
U W E TER YN AR ZA. SOBIE IN N Ą PRACĘ!

Możesz pomóc Damianowi w napisaniu


programu zarządzającego jego wymówkami
Użyj całej swojej wiedzy na temat odczytywania i zapisywania plików
do stworzenia programu zarządzającego wymówkami. Dzięki niemu
Damian będzie mógł określić, których wymówek używał ostatnio i jak
zadziałały one na szefa.

Czasami Damian
jest zbyt teniwy,
Program do zarządzania wymówkami aby wymyśtać nowe
Damian chce .
przechowywać wszystkie usprawiedtiwienie.
Dodajmy zatem
swoje wymówki
w jednym miejscu. przycisk, który
wczyta tosową
Pomóż mu w takim
razie wybrać do tego wymówkę z jego
odpowiedni folder. katatogu.

jesteś tutaj ► 463


Dam ian potrzebuje w ym ów ek

Utwórz program zarządzający wymówkami, aby Damian mógł mieć nad nimi kontrolę. Excuse
Description: string
Ćwiczenia Results: string
(n UTW ÓRZ FORMULARZ. LastUsed: DateTime
ExcusePath: string
Ten formularz ma kilka specjalnych możliwości:
★ Podczas pierwszego uruchomienia programu powinien być aktywny tylko przycisk OpenFile(string)
Save(string)
Folder — pozostałe trzy mają być nieaktywne aż do momentu wybrania folderu.
★ Kiedy formularz będzie otwierał lub zapisywał wymówkę, czas zapisu pliku powinien być
wyświetlany przez kontrolkę Label z właściwością AutoSize ustawioną na False
i właściwością BorderS tyle ustawioną na Fixed3D.
★ Po zapisaniu wymówki formularz wyświetli okno MessageBox „Wymówka zapisana”.
★ Przycisk Folder będzie wyświetlał okno dialogowe wyboru folderu. Gdy użytkownik wybierze folder,
aktywne staną się przyciski Zapisz, O twórz oraz L osow a wymówka.
★ Formularz będze potrafił określić, czy zmiany zostały zapisane. Jeżeli nie będzie niezapisanych zmian,
to na pasku tytułowym będzie wyświetlany napis „Program do zarządzania wymówkami”. Kiedy
użytkownik wprowadzi zmiany do jednego z trzech pól, formularz doda do paska tytułowego
symbol gwiazdki (*). Zostanie on usunięty, w przypadku gdy dane zostaną zapisane
lub zostanie otworzona nowa wymówka. Kiedy przeciągniesz
★ Formularz będzie musiał przechowywać informacje o bieżącym folderze oraz o tym, na formularz pole
tekstowe i klikniesz
czy aktualnie wyświetlana wymówka została zapisana. Możesz określić, czy tak się nie stało, j e dwukrotnie,
korzystając z procedur obsługi zdarzenia Changed dla trzech kontrolek. utworzysz dla niego
procedurę obstugi
UTW ÓRZ KLASĘ EXCUSE I PRZECHOWUJ JEJ INSTANCJĘ W FORMULARZU zdarzenia Changed.
Dodaj do formularza pole currentExcuse w celu przechowywania aktualnej wymówki. Będziesz potrzebował
trzech przeciążonych konstruktorów : jednego wywoływanego podczas ładowania formularza, jednego
do otwierania pliku i jednego dla losowej wymówki. Dodaj metodę OpenFile() do otwierania wymówek
(używaną przez konstruktor) oraz Save() do ich zapisu. Dopisz jeszcze metodę UpdateForm(), która będzie
aktualizowała zawartość kontrolek (uzyskasz dzięki niej wskazówki dotyczące budowy klasy):
p riv a te void UpdateFormfbool changed) Ten parametr określa, czy zawartość
i f (¡changed) {
Pamiętaj, th is .d e s c rip tio n .T e x t = currentExcuse.D escription rormularza nie ulegta zmianie. Będziesz
! oznacza NIE t h is .r e s u lt s .T e x t = cu rre n tExcu se .R esu lts; musiat dodać w formularzu pole stużące
— to wyrażenie th iL^ astU sed .V alu e = currentExcuse.LastUsed; do przechowywania tego statusu
pozwala i f_fi!S iring .IsN u llO rEm p ty(cu rren tExcu se.Excu sePath ))
sprawdzić, leD ate.Text = File.G etLastW riteT im e (cu rren tE xcu se .Excu seP a th ).T o String ();
czy ścieżka "Program do zarządzania wymówkami"; Dwukrotnie kliknij trzy kontrolki do wprowadzania
do katalogu
wymów }
danych. IDE utworzy wtedy za Ciebie i c h procedury
NIE je else obstugi zdarzeń Changed. Będą one w pierwszej
pusta łub t h is .T e x t = "Program do zarządzania wymówkami*"; kolejności zmieniaty instancję Excuse, a _następnie
równa nuli. this.formChanged = changed; wywotywaty UpdateForm(true) — potem ju ż tyfco
od Ciebie zależy zmiana pól na fomtuhrztu.
Nie zapomnij także aktualizować w konstruktorze formularza pola LastUsed obiektu wymówki:
public Forml() {
In itializeC o m p o nent();
currentExcuse.LastUsed = lastU sed.Value;
}
SPRAW, ABY PRZYCISK FOLDER OTW IERAŁ OKNO WYBORU FOLDERU
Kiedy użytkownik naciśnie przycisk Folder, formularz powinien wyświetlić okno dialogowe wyboru folderu.
Będzie musiał przechowywać folder w jednym z pól, aby inne okna dialogowe mogły go używać. Gdy formularz
wczytywany jest po raz pierwszy , przyciski Z a p isz , O twórz i L osow a wym ów ka powinny być nieaktywne . Zostaną
one jednak uaktywnione, kiedy użytkownik wybierze odpowiedni folder.

464 Rozdział 9.
Odczyt i zapis plików

SPRAW, ABY PRZYCISK ZAPISZ ZAPISYWAŁ BIEŻĄCĄ W YMÓW KĘ DO PLIKU.


Kliknięcie przycisku Zapisz powinno skutkować wyświetleniem okna dialogowego Zapisz jako .

★ Każda wymówka zapisywana jest w oddzielnym pliku. Pierwszym jego wierszem jest wymówka,
drugim jej skutek, a trzecim czas ostatniego użycia (skorzystaj z metody T o S trin g () kontrolki
DateTimePicker ). Klasa Excuse powinna posiadać metodę Save() i za jej pośrednictwem
zapisywać wymówkę do określonego pliku.
★ Po otwarciu okna dialogowego Zapisz jako powinien wyświetlić się ten katalog, który został
wybrany przy użyciu przycisku Folder. Nazwa pliku powinna być ustawiona na wymówkę połączoną
z rozszerzeniem „.txt”.
★ Okno dialogowe powinno posiadać dwa filtry: Pliki tekstowe (*.txt) oraz Wszystkie pliki (*.*) . Jeśli
użytkownik spróbuje zapisać bieżącą wymówkę, ale pozostawił puste pole jej samej lub jej rezultatu,
to formularz powinien wyświetlić okno dialogowe z ostrzeżeniem:

M ożesz w yśw ietlić ci(^ onej


£ .w ° ,
S ilo 'S V "
M essag®BoxIcon.

NIECH PRZYCISK OTWÓRZ OTW IERA ZAPISANĄ W YM ÓW KĘ.


Kliknięcie przycisku Otwórz powinno spowodować wyświetlenie okna dialogowego Otwórz.

★ Kiedy okno dialogowe jest otwierane, to katalog początkowy powinien być ustawiony na ten, który został
wybrany za pomocą przycisku Folder.
★ Dodaj do klasy Excuse metodę Open(). Jej zadaniem będzie wczytywanie wymówki z pliku.
★ Do odczytania zapisanej daty użyj Convert.ToDateTime() . Wynik wpisz do kontrolki DateTimePicker .
★ Gdy użytkownik próbuje otworzyć jakąś wymówkę, a bieżąca nie została zapisana, wyświetlane jest
okno dialogowe:

Ok d!at°9°we W M e może zostać pokazane


przy uzyąu p ^ d ą ^ n e j metody MessageBox
ShcusO, kóra pozwata przekazywać parime™.
/SeSs : ^ ^ Y esNo. Jeśti użytkownik
ktiknie „Nie , ShowO zwróci DiatogResutt.No.

I W KOŃCU SPRAW, ABY PRZYCISK LOSOWA W YM ÓW KA W CZYTYW AŁ


PRZYPADKOWE USPRAWIEDLIWIENIE.
Gdy użytkownik klika przycisk Losowa wymówka, w wybranym katalogu wyszukiwane są wymówki.
Jedna spośród nich zostaje wybrana i wczytana.

★ Formularz musi zapisać obiekt Random w pewnym polu i przekazać go do jednego z przeciążonych
konstruktorów obiektu Excuse.
★ Jeżeli bieżąca wymówka nie została zapisana, powinno zostać wyświetlone to samo okno dialogowe,
które zostało pokazane dla przycisku Otwórz.

jesteś tutaj ► 465


Rozwiązanie ćwiczenia

Stwórz program zarządzający wymówkami, aby Damian mógł mieć nad nimi kontrolę.

Rozwiązania
ćwiczeń Ten formularz używa pól do przechowywania
obiektu Excuse i wybramg0 folderu, a także
p riv a te Excuse currentExcuse = new Excuse()
pamięta, czy bieżąca wymówka Się zmienita-
p riv a te s trin g selectedFolder = PrzeChowuje również obiekt Random dla
p riv a te bool formChanged = fa ls e ; przycisku Losowa wymówka■
Random random = new Random();

p riv a te void folder_C H ck(object sender, EventArgs e) {


folderBrowserDialogl.SelectedPath = selectedFolder;
DialogResult re s u lt = folderBrowserDialogl.ShowDialog();
i f (re s u lt == DialogResult.OK) { Jeśli użytkownik wybrat folder,
selectedFolder = folderBrowserDialogl.SelectedPath; formularz zapisuje jegonazwę
save.Enabled = tru e ; i uaktywnia pozostate trzy przyc
open.Enabled = tru e ;
randomExcuse.Enabled = tru e ;
}
prawdą, Z I, S
m U T jeżeli ' . jest opSLUB
pusty LUB rezu/taT
— to jest

p riv a te void save_C lick(object sender, EventAn


i f (S trin g .IsN ullO rE m pty(d escriptio n.T e xt)F J|Y string .IsN u llO rE m p ty(re sults.T e xt))
MessageBox.Show("Określ wymówkę i re z u rta t"
"Nie można zapisać p lik u " , MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
re tu rn ; To tutaj ustawiane są filtry dla
} . ,r okna dialogowego Zapisz jako■
s a v e F ile D ia lo g l.In itia lD ire c to ry = selectedFolder;
s a v e F ile D ia lo g l.F ilte r = " P lik i tekstowe ( * .tx t) |* .tx t|W s z y s tk ie p lik i ( * . * ) | * . * "
saveFileDialogl.FileName = d escrip tio n.T e xt + " . t x t " ;
DialogResult re s u lt = saveFileDialog1.ShowDialog();
i f (re s u lt == DialogResult.OK) {
currentExcuse.Save(saveFileDialogl.FileName);
t
Spowoduje to pojawienie się
UpdateForm(false); dwóch opcji w rozwijanej liście
MessageBox.Show("Wymówka zapisana"); „Zapisz jako typ" w dolnej części
okna dialogowego: jednej dla
} plików teks'towych (*.txt), drugiej
} dla wszystkich (*.*).
p riv a te void open_Click(object sender, EventArgs e)
i f (CheckChanged()) {
o p e n F ile D ia lo g l.In itia lD ire c to ry = selectedFolder;
o p e n F ile D ia lo g l.F ilte r = " P lik i tekstowe ( * .tx t) |* .tx t|W s z y s tk ie p lik i (
openFileDialogl.FileName = d escrip tio n.T e xt + " . t x t " ;
DialogResult re s u lt = openFileDialog1.ShowDialog();
i f (re s u lt == DialogResult.OK) { Użyj typu wyliczeniowego
currentExcuse = new Excuse(openFileDialoglTFileName) DialogResult zwracanego
UpdateForm(false); przez okna dialogowe Otwórz
} i Zapisz jako, aby pliki byty
otwierane lub zapisywane tylko
} wtedy, gdy użytkownik kliknie
„OK", a nie .Anuluj".
p riv a te void randomExcuse_CHck(object sender, EventArgs e)
i f (CheckChanged()) {
currentExcuse = new Excuse(random, selectedFolder);
UpdateForm(false);
}
}

466 Rozdział 9.
Odczyt i zapis plików

p riv a te bool CheckChanged() {


i f (formChanged) {
DialogResult re s u lt = MessageBox.Show(
"Bieżąca wymówka nie została zapisana. Czy kontynuować?",
"O strzeżenie", MessageBoxButtons.YesNo MessageBoxIcon.Warning);
i f (re s u lt == DialogResult.No)
return fa ls e ; V' ’
} MessageBox.ShowO także zwraca typ wyliczeniowy
return tru e ; DialogResult, który możemy sprawdzić.

To są trzy procedury obsługi


p riv a te void descr1pt1on_TextChanged(object sender, EventArgs e) zdarzenia dla trzech pól
currentExcuse.Description = d e scrip tio n .T e x t; formularza pobierających
UpdateForm(true); wartości. Jeśli którakolwiek
} z nich zostanie wykonana,
będzie to oznaczało,
p riv a te void results_TextChanged(object sender, EventArgs e) że wymówka została
currentExcuse.Results = re s u lts .T e x t; zmieniona. Aktualizujmy
UpdateForm(true); wobec tego instancję
} Excuse, wywołujemy
Updateform(), dodajemy
znak gwiazdki do paska
p riv a te void lastUsed_ValueChanged(object sender, EventArgs e) { tytułowego i ustawiamy
currentExcuse.LastUsed = lastUsed.Value; Changed na true.
UpdateForm(true); Przekazanie true do UpdateformO
} oiznacza, że dane na formularzu
zmieniły się, ale nie należy
aktualiznwać kontrolek.
class Excuse
public s trin g Description { get; set; }
public s trin g Results { get; set; }
public DateTime LastUsed get; set;
public s trin g ExcusePath get; set; wpyc'sk Losoo/a wymówka używa Directoru.Getfiles()
public Excuse() { t c d U pobrania. do tablicy nazw wszystkich plików
ExcusePath weeks yCh z określonego katalogu, a następnie
} wybiera losowy indeks ehmentu do otwarcia.
p u b lic Excuse(string excusePath)
OpenFile(excusePath);
} Upewniliśmy się, ze używamy
p u b lic Excuse(Random random, s trin g fo ld e r) instrukcji using podczas każdej
s tr in g [] fileNames = D ire c to ry .G e tF ile s (fo ld e r, ^ .t x t
operacji na strumieniach.
W ten sposób nasze pliki
OpenFile(fileNames[random.Next(fileNames.Length)]): zawsze będą zamykane.
}
p riv a te void O penFile(string excusePath) {
this.ExcusePath = excusePath;
using (StreamReader reader = new StreamReader(excusePath))
D escription = reader.ReadLine();
Results = reader.ReadLine();
LastUsed = Convert.ToDateTime(reader.ReadLine());
}
}
p u b lic void Save(string fileName) {
using (StreamWriter w rite r = new StreamWriter(fileName)) {
w rite r.W rite L in e (D e s c rip tio n );
w rite r.W rite L in e (R e s u lts );
w riter.W riteLine(LastU sed);
} } Czy używałeś wywołania L astU sed .T o S trin g () ? Pamiętaj,
metoda W riteL ine() wywołuje T o S trin g () automatycznie!

jesteś tutaj ► 467


To ja decyduję

Zapisywanie danych do plików wymaga wielu decyzji


Napiszesz mnóstwo programów, które będą pobierały dane wejściowe — być może
z pliku. Będą one musiały na ich podstawie zadecydować, jaką akcję wykonać. Poniżej
zaprezentowano kod, który używa jednej długiej instrukcji i f , co jest dość typowe.
Sprawdza on zmienną p a r t i zapisuje do pliku różne linijki w zależności od używanego
typu wyliczeniowego. Istnieje wiele możliwości, jest więc wiele sekwencji e l s e i f :

enum B o d y P a rt { To je s t typ wyticzeniowy — chcemy


p°równać zmienną z każdą jego
H ead, składową i zapisać za pomocą
StreamWriter okreśtony wiersz
S h o u ld e rs ,
w zateżności od wyniku. Napiszemy
K n e e s, również coś całkiem innego, na
wypadek gdyby wartość nie była
T oes zgodna z żadnym potem.
}

p riv a te v o id W r i t e P a r t I n f o ( B o d y P a r t p a r t , S tr e a m W r ite r w r i t e r ) {
if ( p a r t == B o d y P a rt.H e a d )
w rite r.W rite L in e ( " g T o w a j e s t o w ło s io n a " );
e ls e if ( p a r t == B o d y P a r t .S h o u l d e r s )
w rite r .W r ite L in e ( " r a m io n a s ą s z e r o k ie " ) ;
Używamy serii instrukcji
e ls e if ( p a r t == B o d y P a r t.K n e e s ) ^ if/else. Powstaje dtuga
sekwencja „if (part —
w r ite r .W r ite L in e ( " k o la n a s ą g u z o w a te " ) ; [opcja])".
e ls e if ( p a r t == B o d y P a r t.T o e s )
w rite r.W rite L in e ( " p a lc e są m a lu tk ie " ); Mamy tutaj końcowe 6 ^ na wypadek
gdyby żadna z poprzednich wartości' nie byta
e ls e <£.--------------------------------------------------- prawidłowa.
w r ite r .W r ite L in e ( " n ie o k r e ś lo n a część j e s t n ie o k r e ś lo n a " );

WYSIL ___________
CC' SZARE KOMÓRKI
Co może się stać podczas pisania kodu z wieloma instrukcjami i f / e l s e ? Pomyśl
o prostych literówkach, błędach spowodowanych przez nawiasy klamrowe,
pojedyncze znaki równości itp.

468 Rozdział 9.
Odczyt i zapis plików

Użyj instrukcji switch


Instrukcj a switch nie jest w żaden
szczególny sposób powiązana
do wybrania właściwej opcji z plikami. To po prostu pomocne
narzędzie C#, którego możemy
tutaj użyć.
Porównywanie zmiennej do zestawu różnych wartości jest dość
powszechną praktyką, z którą będziesz się często spotykał. Jest
ono szczególnie często stosowane podczas zapisu i odczytu
plików. Rozwiązanie to jest tak popularne, że C # posiada
specjalną instrukcję, którą stosuje się w takich sytuacjach.

Instrukcja sw itch pozwala Ci porównać zmienną z kilkoma


Instrukcja
innymi wartościami w sposób, który jest łatwy do odczytania switch
i zwięzły. Wykonuje ona dokładnie to samo zadanie co sekwencja * •
instrukcji i f / e l s e użyta na poprzedniej stronie: porównuje
JEDNĄ zmienną
Rozpoczynasz ją od wpisania słowa z WIELOMA
kluczowego switch■ Zaraz po n^ .
umieszczasz zmienną, która będzie
porównywana z zestawem możliwych możliwymi
wartości■
wartościami.
p riv a te vo^a W rite P a r tIn f o (B o d y P a rt p a r t , S tr e a m W r ite r w r i t e r )
{
s w itc h (p a r t) { Ciatem instrukcji jest
. c a se B o d y P a r t.H e a d : seria przypadków,
które porównywane
Kaądkończyćmus' w r ite r.W rite L in e ( " g ło w a j e s t o w ło s io n a " ); są z wartością
instrukcją b rea k ; występującą zaraz
„break;' , dz<ęk< c a se B o d y P a r t . S h o u l d e r s : po stowie kluczowym
której C# wie, switch.
gdzie kończysię w rite r .W r ite L in e ( " r a m io n a s ą s z e ro k ie " );
jeden blok case, b re a k ; Każdy z tych przypadków
a zaczyna. drugi. c a se B o d y P a r t.K n e e s : zawiera stowo kluczowe case,
po którym występuje wartość do
w r ite r .W r ite L in e ( " k o la n a s ą g u z o w a te " ) ; porównania wraz z dwukropkiem.
Blok case można b rea k ; Dalej znajduje się seria
także zakończyć instrukcji oraz „break;".
instrukcją return c a se B o d y P a r t.T o e s : Instrukcje zostaną wykonane
— programsię w r ite r .W r ite L in e ( " p a lc e s ą m ło d e " ); tylko wtedy, gdy przypadek
skompiluje, będzie zgodny z porównywaną
b rea k ;
o ile tylko jeden wartością.
z bloków case d e f a u lt :
nie będziemógt w r ite r .W r ite L in e ( " n ie o k r e ś lo n a c z ę ść j e s t n ie o k r e ś lo n a " ) ;
„przejść" do
następnego. b rea k ;

} Instrukcje switch mogą


kończyć się instrukcją
„default:" — blok ten jest
wykonywany wtedy, gdy żadne
z _poprzednich porównań case
nie byto prawidtowe.

jesteś tutaj ► 469


Zasypiając ze switch

Użyj instrukcji switch, aby pozwolić zestawowi kart


wczytywać informacje z pliku i je do niego zapisywać
Instrukcja
Zapisywanie kart w pliku jest dość proste — napisz pętlę, która zapisuje
do niego nazwę każdej z nich. Oto metoda, którą musisz dodać do obiektu Deck.
switch
Wykonuje dokładnie to, o co nam chodzi: pozwala
p u b lic void W riteC ards(string FileName) { porównać
using (StreamWriter w rite r = new StreamWriter(FileName)) {
fo r ( in t i = 0; i < cards.Count; i++) { daną wartość
}
w rite r.W rite L in e (ca rd s[i].N a m e );
z zestawem
} przypadków
}
Co jednak z wczytaniem pliku? To już nie jest takie proste. W tym momencie pomocna
i wykonać
okazuje się instrukcja switch.
Instrukcja switch rozpoczyna się
różne
c . . . . /
o<d wartości, z którą poszczegótne przypadki
będą f^ iw y w a n e . Ta instrukcja ¡est
działania
Suits suit; wywofywana z metody, która p6siada kotor
w zależności
przechowywany w postaci łańcucha znaków.
switch (suitString) {
od rezultatów
case "Spades":
tych
suit = Suits.Spades;
porównań.
break;
case "Clubs": Każdy z tych wierszy c ^ e
porównuje pewną wartość
z wartością podaną w nawiaaach
suit = Suits.Clubs; zaraz za słowem switch. Jeżeti są
one takie same, to wykonywane
break; są kotejne instrukcje aż do
napotkania break.
case "Hearts":
suit = Suits.Hearts;
break;
case “Diamonds” : Wiersz defautt znajduje się
na samym końcu. Gdy żaden
z przypadków nie pasuje, wtedy
suit = Suits.Diamonds; wykonywane są instrukcje
umieszczone po tym słowie.
break;
default:
MessageBox.Show(suitString + nie jest prawidłowym kolorem!”);
break;

470 Rozdział 9.
Odczyt i zapis plików

Dodaj przeciążony konstruktor Deck(),


który wczytuje karty z pliku
Instrukcji switch możesz użyć między innymi do utworzenia nowego konstruktora dla
napisanej w poprzednim rozdziale klasy Deck. Konstruktor wczytuje plik i sprawdza każdy
wiersz w poszukiwaniu karty. Każda prawidłowa karta dodawana jest do zestawu.

Istnieje pewna metoda dostępna w każdym łańcuchu znaków, która okaże się bardzo
pomocna. Jest to S p l i t ( ) . Pozwala ona przetworzyć łańcuch na tablicę jego fragmentów,
które powstają po podzieleniu tekstu początkowego. Jako parametr przyjmuje tablicę
char[] zawierającą znaki używane do podziału łańcucha.

p u b lic Deck(string FileName) { znaków_nextCard przy użyciu znaku


cards = new List<Card>(); sp acji jako s e Paratora. Łańcuch “S ix o f
StreamReader reader = new StreamReader(FileName) Uiamonds z ostanie rozbity na {“S ix“ “of“
Diamonds“}. '
w hile (!reader.EndOfStream) {
bool invalidCard = fa lse ;
s trin g nextCard = reader.ReadLine();
s tr in g [] cardParts = nextCard.Split(new char[]
Values value = Values.Ace;
switch (cardP arts[0]) {
case Ace": value Values.Ace; break;
case Two": value Values.Two; break;
case Three : value = Values.Three; break; Ta instrukcja sw itch
case Four" value = Values.Four; break; porównuje p ierw sze
case Five" value = Values.Five; break; jłnw n w w ierszu
i s p rawdza, czy j e s t
case S ix": value = Values.Six; break; to j edna z e znanych
case Seven : value = Values.Seven; break; wartnści. Jeśli tak,
case Eight : value = Values.Eight; break; odpowiednia wartość
case Nine" value = Values.Nine; break; p rzypisyw ana j e s t do
case Ten": value = Values.Ten; break; zm iennej v a lu e .
case Jack" value = Values.Jack; break;
case Queen : value = Values.Queen; break;
case King" value Values.King; break;
d e fa u lt: invalidCard = tru e ; break;
}
Suits s u it = Suits.C lubs;
switch (cardP arts[2]) {
case "Spades": s u it = Suits.Spades; break;
case "Clubs": s u it = Suits.C lubs; break;
case "H earts": s u it = S uits.H earts; break;
case "Diamonds": s u it = Suits.Diamonds; break;
d e fa u lt: invalidCard = tru e ; break;

i f (¡in validC ard ) {


cards.Add(new C ard(su it, va lu e ));

jesteś tutaj ► 471


PS Znajdę sw oją żabę

C A ŁY TEN KOD JEST POTRZEBNY


DO W C Z Y T A N IA JEDNEJ PROSTEJ KARTY?
TO Z A DUŻO ROBOTY! A GDYBY MÓJ OBIEKT POSIADAŁ
C A Ł Ą MASĘ PÓL I WARTOŚCI? CHCESZ M I POWIEDZIEĆ,
ŻE MUSZĘ NAPISAĆ INSTRUKCJĘ SWITCH DLA KAŻDEJ
Z NICH?

Istnieje znacznie łatw iejszy sposób na przechowywanie


obiektów w plikach. Nazywamy go serializacją.

Zamiast zmagać się ze żmudnym zapisywaniem każdego pola


do pliku linijka po linijce, możesz zapisać obiekt w inny sposób,
a mianowicie przesłać go do strumienia. S erializacja to coś
podobnego do spuszczenia powietrza z obiektu, tak aby zmieścił
się w pliku. Z drugiej strony możesz zastosować deserializację.
Polega ona na wczytaniu obiektu z pliku i napompowaniu go.

No dobrze, dta porządku musimy to dodać.


Istnieje metoda o nazwie Enum.Parse() —
poznasz ją w rozdziate 14. — która potrafi
przekonwertować łańcuch znaków „Spades"
na wartość typu wyticzeniowego Suita.Sprdea.
Jednak w tym przypadku aerirlizrcjr i tak jest
bardziej sensownym rozwiązaniem. Już niebawem
dowiesz się na jej temat znacznie więcej.

472 Rozdział 9.
Odczyt i zapis plików

Co dzieje się z obiektem podczas serializacji?


Wydaje się, że coś niesłychanie tajemniczego dzieje się z obiektem, który kopiowany jest
ze sterty i wrzucany do pliku. Jest to jednak wyjątkowo proste.

O biekt po serializacji.

Kiedy tworzysz instancję obiektu, Podczas serializacji obiektu C#


posiada on jakiś stan . Wszystko, co zapisuje cały jego stan . Dzięki temu
obiekt „wie”, jest tym, co odróżnia może później na stercie powstać
jedną instancję danej klasy od innej. identyczna instancja (obiekt).

Wartości sktadowych
Width oi-az Height
instancj i zapisywane
dv«a
o b ie k t m a ________ są do pliku file.dat
Ten razem z dodatkowymi
01000110
informacjami, których
f e n * / .,» • potrzebuje CLR do
jej późniejszego
przywrócenia (takimi
jak typ obiektu
i każdego z jego pól).
file.dat
Width Height
Obiekt na stercie jeszcze raz

Później...

Później — być może za kilka dni i w innym


programie — możesz zastosować deserializację .
Dzięki niej odzyskasz oryginalną klasę
bezpośrednio z pliku i przywrócisz ją dokładnie
taką, jaką była , ze wszystkimi polami
i wartościami w nienaruszonym stanie.

jesteś tutaj ► 473


Zapisz czirliderkę

Czym w istocie J E S T stan obiektu?


Co musi zostać w nim zapisane?
Już wiemy, że obiekt przechowuje swój stan w polach . Gdy jest on serializowany, każde
z tych pól musi zostać zapisane do pliku.

Serializacja zaczyna być interesująca podczas pracy ze skomplikowanymi obiektami. Znaki,


liczby całkowite, zmiennoprzecinkowe oraz inne typy wartościowe są reprezentowane przez
bajty, które mogą zostać zapisane w pliku w swojej oryginalnej postaci. Co się jednak dzieje,
gdy obiekt posiada pole, które jest referencją do obiektu? Co wtedy, gdy ma pięć zmiennych
referencyjnych? A co, jeśli te zmienne referencyjne zawierają kolejne?

Pomyśl nad tym przez minutę. Która część obiektu może być unikatowa? Wyobraź sobie,
co musi zostać przywrócone, aby obiekt był dokładnie taki sam jak ten wcześniej zapisany.
W jaki sposób wszystko to, co jest obecne na stercie, musi zostać zapisane do pliku?

W YTĘŻ
U M Y SŁ

Co musi się stać z tym obiektem Car, aby mógł on zostać zapisany,
a następnie przywrócony do swojego oryginalnego stanu?
Powiedzmy, że samochód ma trzech pasażerów, trzylitrowy silnik
oraz opony radialne całosezonowe... Czy te rzeczy nie są częścią
stanu? Co powinniśmy z nimi zrobić?

Obiekt Engine jest


prywatny. Czy on
także powinien zostać
r oosiad® fl) :zapisany?

"K * «
,r. Wszysl co Si«

Każdy z obiektów
Passenger yoaiada
referencje do mnych
obiektów. Czy je także
powinniśmy zap^rt?

474 Rozdział 9.
Odczyt i zapis plików

Kiedy obiekt jest serializowany, serializowane są


także wszystkie obiekty z nim powiązane...
...i wszystkie obiekty, do których się one odnoszą, i wszystkie, do których odwołują
się tam te, i tak dalej, i tak dalej. Nie przejmuj się tym — może to wyglądać na bardzo
skomplikowany proces, ale wszystko odbywa się automatycznie. C# rozpoczyna
przetwarzanie od obiektu, który chcesz zserializować, a następnie sprawdza wszystkie jego
pola w poszukiwaniu obiektów. Potem robi to samo dla każdego z nich. Każdy pojedynczy
obiekt zostaje zapisany do pliku wraz z informacjami, które są niezbędne do poprawnego
odtworzenia go podczas deserializacji.

Kiedy poprosisz 'C#


o serializacją obiektu

pole posiadające referencją


do innego obiektu.

Jedno z pól obiektu


Kennel je s t typu
List<Dog>. Zawiera
ono dwa obiekty
typu Dog, więp C#
także i je będzie
serializowat.
'ek t List*’
Każdy z dwóch obiektów
Dog zawiera referencje
do obiektów DoggylD oraz
Collar. Muszą one zostać
zserializowane razem z nimi.

Obie kt DoggylD oraz CoUair


znajdują się na samym
końcu — nie mają żadnych
referencji do innych
obiektów.

jesteś tutaj ► 475


Serializow ane dla Twojego bezpieczeństwa

Serializacja pozwala Ci zapisywać Skopiowanie


lub odczytywać całe grafy obiektów naraz obiektu do pliku
i późniejsze
Nie jesteś ograniczony do zapisywania i odczytywania wierszy tekstu z plików. Możesz
użyć serializacji, aby Twoje programy kopiowały do nich całe obiekty i z powrotem jego wczytanie
je wczytywały... a wszystko to za pomocą zaledwie kilku wierszy kodu! Musisz jednak
wykonać niewielką pracę przygotowawczą — dodać jeden wiersz [S e ria liz a b le ]
zajmuje niewiele
na górze każdej klasy przeznaczonej do serializacji. Gdy to zrobisz, wszystko będzie czasu. Możesz
gotowe do zapisu.
zastosować
Potrzebujesz obiektu BinaryFormatter w tym celu
Jeśli chcesz zserializować obiekt — dow olny obiekt — to pierwszą czynnością
jest utworzenie instancji klasy B inaryF orm atter . Jest to bardzo łatwe — wymaga
serializację
tylko jednego wiersza kodu (oraz jednego dodatkowego wiersza using na górze i deserializację.
pliku klasowego).

using System.Runtime.Serialization.Formatters.Binary;

BinaryFormatter formatter = new BinaryFormatter();

Teraz utwórz strumień i czytaj lub zapisuj obiekty


Metoda File.CreateO tworzu
Użyj metody S e r ia liz e ( ) obiektu BinaryForm atter
w celu zapisania dowolnego obiektu do strumienia.

using (Stream output = File.Create(filenameString)) {


formatter.Serialize(output, objectToSerialize);
} Metoda Serialize() pobiera obiekt i _zapytuje
go do strumienia. To znacznie tatwiejsze niż
wtasnoręczne tworzenie metody do zapisywania!

Jeśli już dokonałeś serializacji i obiekt znalazł się w pliku, możesz użyć metody
D e s e ria liz e () obiektu B inaryForm atter do jego powtórnego wczytania. Metoda
zwraca referencję, więc musisz zrzutować zwracaną wartość na typ zgodny z typem
zmiennej, w której chcesz ją zapisać.

using (Stream input = File.OpenRead(filenameString)) {


SomeObj obj = (SomeObj)formatter.Deserialize(input);
} Kiedy używasz Deserialize() do o^zytyiM^wa
obiektów ze strumienia, to nie zapomnij
zastosować rzutowania. Dzięki temu wartość
będzie zgodna z typem wczytywanego obiektu.

476 Rozdział 9.
Odczyt i zapis plików

Jeżeli chcesz umożliwić serializację klasy, Atrybuty to sposób


dodawania informacji
to musisz oznaczyć ją atrybutem [Serializable] do deklaracji klas
i składowych. Atrybut
Atrybut to specjalny znacznik, który możesz dodawać na górze każdej klasy. C# przechowuje [S e r ia liz a b le ]
jest zdefiniowany
w ten sposób metadane dotyczące kodu, czyli informacje o sposobie jego traktowania lub
w przestrzeni nazw
wykonywania. Jeśli na górze klasy, tuż powyżej jej deklaracji, dodasz [S e r ia liz a b le ],
System.
powiesz C #, że można zastosować dla niej mechanizm serializacji. Używa się go tylko wtedy,
gdy klasa posiada albo typy wartościowe (na przykład i n t , s tr in g lub enum), albo inne klasy
zdolne do serializacji. Jeżeli do klasy, którą chcesz serializować, nie dodasz tego atrybutu lub jeśli
dodasz do niej pole typu, którego nie można serializować, program wyświetli błąd podczas próby
jego uruchomienia. Sp ra w d ź sa m ...
f Z ró b to !
Utwórz klasę i wykonaj serializację.
Zastosujmy serializację w odniesieniu do obiektu jo e , abyśmy mogli przechować plik, który będzie zawierał
informacje o ilości pieniędzy w jego kieszeniach, nawet po zamknięciu programu. Otwórz projekt Zabaw a
z Joem i B obem z rozdziału 3. i zmodyfikuj klasę Guy:

[S e ria liz a b le ] Musisz dodać ten atrybut na początku


kodu k|asy, aby można ją było serializować.
class Guy

Następnie dodaj do formularza przyciski Z a p isz Joego i Wczytaj Joego. Oto kod ich procedur obsługi,
który serializuje obiekt jo e w pliku Plik_faceta.dat oraz wczytuje go z niego:
using System.IO; ^ ----- Będziesz potrzeb^^
tych dwóch linijek using.
using System .R untim e.Serialization.Form atters.Binary; Pierwsza jest potrzebna dla
metod związanych z plikami
i strumieniami, druga dotyczy
p riva te void saveJoe_Click(object sender, EventArgs e) serializacji.
{
using (Stream output = F ile .C re a te ("P lik _ fa c e ta .d a t")) {
BinaryFormatter form atter = new BinaryForm atter();
fo rm a tte r.S e ria liz e (o u tp u t, jo e );
}
}
p riva te void loadJoe_C lick(object sender, EventArgs e)
{
using (Stream input = File.O penR ead("P lik_faceta.dat"))
BinaryFormatter form atter = new BinaryForm atter();
joe = (G uy)fo rm a tte r.D e serialize(inp ut);
}
UpdateForm();
}
Uruchom program i pobaw się nim przez chwilę.
Gdyby Joe miał 200 zł z transakcji z Bobem, zaoszczędzonych podczas działania programu, to szkoda byłoby
mu utracić te pieniądze tylko dlatego, że program musiał zostać zakończony. Teraz aplikacja może zapisać
Joego do pliku i przywrócić jego stan w dowolnej chwili.
Co się stanie, jeśli usuniesz p lik P likjaceta.dat z folderu bin/Debug, a następnie klikniesz przycisk Wczytaj Joego?

jesteś tutaj ► 477


Lubię m oje zserializow ane ja

W jaki sposób serializować i deserializować zestaw kart


Weź zestaw kart i zapisz go do pliku. C # sprawia, że serializacja obiektów jest rzeczą naprawdę
prostą. W celu ich zapisania musisz tylko utworzyć strumień i zapisać w nim obiekty.
^ ' Z ró b to !

U T W Ó R Z N O W Y P R O JE K T I D O D A J K LA SY D ECK O R A Z C A R D .
Kliknij prawym przyciskiem myszy w oknie Solution Explorer i wybierz Add/Existing Item . Dodaj klasy
Card oraz Deck (a także typy wyliczeniowe S uits oraz Values i interfejsy CardComparer_bySuit
i CardComparer_byValue), których używałeś w rozdziale 8. Będziesz także potrzebował dwóch klas
do porównywania kart, ponieważ używa ich Deck. IDE skopiuje pliki do nowego projektu — upewnij się tylko,
że zmieniłeś wiersz namespace na górze każdego pliku klasowego tak, aby był zgodny z jego przestrzenią nazw.

O Z N A C Z W S Z Y S T K IE K L A S Y A T R Y B U T E M [S E R IA L IZ A B L E ].
Jeżeli tego nie
Dopisz atrybut [S e r ia liz a b le ] do każdej klasy dodanej do projektu. zrobisz, C# nie
pozwoli Ci na
serializację klas
do pliku.
D O D A J D O F O R M U L A R Z A K IL K A U Ż Y T E C Z N Y C H M E T O D .
Metoda RandomDeck() tworzy losowe zestawy kart. DealCards()
rozdaje wszystkie karty i wypisuje je w oknie konsoli.
Ten fragment tworzy
Random random = new Random(); pusty zestaw i dodaje
p riv a te Deck RandomDeck(int Number) { kilka losowych kart,
używając do tego klasy
Deck myDeck = new Deck(new Card[] { ) ) ; Card z poprzedniego
fo r ( in t i = 0; i < Number; i++) { rozdziatu.
myDeck.Add(new Card(
(Suits)random.Next(4),
(Values)random.Next(l, 14)))
}
return myDeck;

p riv a te void DealCards(Deck deckToDeal, s trin g T itle ) {


C o n sole .W rite Line (T itle );
w hile (deckToDeal.Count > 0) ^ rozdaje wszystkie kartu
l / z zestawu i wypisuje
{
Card nextCard = deckToDeal.Deal(O);
Console.WriteLine(nextCard.Name);
}
C onsole.W riteLine("--------------------------------------") ;
} Nie zapomnij w yśw ietlić w IDE okna O utput,
by oglądać w nim w yn iki generowane przez program.
478 Rozdział 9.
Odczyt i zapis plików

D O B R Z E , P R Z Y G O T O W A N IA Z A K O Ń C Z O N E ...
P R Z Y S T Ą P M Y D O S E R IA L IZ A C J I T E G O Z E S T A W U
Rozpocznij od dodania przycisków służących do serializacji losowego zestawu do pliku i wczytania
go z powrotem. Sprawdź informacje wyświetlone na konsoli, aby przekonać się, czy odczytałeś
to samo, co zapisałeś.
p riv a te void b u tto n l_ C lic k (o b je c t sender, EventArgs e) {
Deck deckToWrite = RandomDeck(5); O b ie k t B in a r y F o r m a t t e r
przyjmuje d o w o ln y obiekt
Na poprzedniej using (Stream output = File.C reate("Z estaw 1.dat")) o zn a c zo n y a tr y b u te m
kartce znajdziesz BinaryFormatter b f = new BinaryForm atter(); S e r ia liz a b le — w ty m
instrukcję b f.S e ria liz e (o u tp u t, deckToWrite); p r z y p a d k u D eck . Z ap isu je
using, którą rqo zdo
r strum
. < r /i n n m o
ienia za pomocą,
należy dodać do }
DealCards(deckToWrite, "To, co zapisałem do p lik u ") metody SerializeO-
formularza■
}

p riv a te void button2_C lick(object sender, EventArgs e) {


Deck deckFromFile;
using (Stream input = File.OpenRead("Zestaw1.dat")) { Metoda Deserialize() klasy
BinaryFormatter b f = new BinaryForm atter(); Bi naryF°rmatter zwraca typ Object,
deckFromFile = ^D e ck)b f.D e s e ria liz e (in p u t); który jest typem ogólnym. Wszystkie
inne _ot>iekty C# po nim dziedziczą.
DealCards(deckFróS0Te, "To, co z p lik u odczytałem"); Musimy w takim razie zastosować
} rzutowanie na obiekt Deck.
}

D O K O N A J S E R IA L IZ A C J I W I E L U Z E S T A W Ó W D O T E G O S A M E G O P L IK U
Po otwarciu strumienia możesz zapisywać tyle danych, ile tylko chcesz. Możesz umieszczać w tym
samym pliku dowolną liczbę obiektów. Dodaj zatem jeszcze dwa przyciski, które będą zapisywały do
pliku losową liczbę zestawów. Sprawdź okno konsoli, aby upewnić się, że wszystko wygląda dobrze.
p riv a te void button3_C lick(object sender, EventArgs e) {
using (Stream output = File.C reate("Z estaw 2.dat")) { Zwróć uwagę na sposób,
BinaryFormatter b f = new BinaryForm atter(); w jaki ten wiersz odczytuje
fo r ( in t i = 1; i <= 5; i++) { pojedynczy zestaw i używa
(Deck) do zrzutowania
M ożesz z s e r ia liz o w a ć Deck deckToWrite = RandomDeck(random.Next(l, 10)); wartości zwracanej przez
za pomocą tego b f.S e ria liz e (o u tp u t, deckToWrite); Deserialize() na typ Deck.
sam ego strum ienia DealCards(deckToWrite, "Zestaw numer " + i + " zapisany" Dzieje się tak dlatego,
} że Deserialize() zwraca
} obiekt, nie zna jednak
doktadnie jego typu.
}
p riv a te void button4_C lick(object sender, EventArgs e)
using (Stream input = File.OpenRead("Zestaw2.dat")) { Dopóki rzutujesz obiekty odczytane
BinaryFormatter b f = new BinaryForm atter(); z pliku na wtaściwy typ, dopóty nie
fo r ( in t i = 1; i <= 5; i++) { ma żadnych ograniczeń liczby obiektów,
które można poddać serializacji.
Deck deckToRead = (D e c k )b f.D e se ria lize (in p u t);
DealCards(deckToRead, "Zestaw numer " + i + " odczytany"
}
}
}
R Z U Ć O K I E M N A Z A P I S A N E P L IK I.
Otwórz w Notatniku plik Zestaw1.dat. (Metoda F ile .C re a te () utworzyła go w katalogu
bin\Debug wewnątrz katalogu projektu). Nie jest to może podobne do czegoś, co czytasz
na plaży, ale zawiera wszystkie informacje niezbędne do odtworzenia całego zestawu kart.
jesteś tutaj ► 479
Tworzenie znaków
ZACZEKAJ CHWILĘ. JAKOŚ NIE PRZEKONUJE
MNIE TO ZAPISYWANIE OBIEKTÓW DO DZIWNYCH PLIKÓW,
KTÓRE PO OTWARCIU WYGLĄDAJĄ JAK JEDNO WIELKIE ŚMIETNISKO. GDY
ZAPISYWAŁAM ZESTAW KART W POSTACI ŁAŃCUCHÓW ZNAKÓW, MOGŁAM
ZOBACZYĆ WYNIKI W NOTATNIKU I OGARNĄĆ TO, CO BYŁO W ŚRODKU.
CZY ZADANIEM C # NIE JEST UPROSZCZENIE WSZYSTKIEGO TAK, ABY
BYŁO DLA MNIE ZROZUMIAŁE?

Podczas serializacji obiektów do pliku zapisujesz je w form acie binarnym .


Nie oznacza to wcale, że nie można ich rozszyfrować — są po prostu zwięzłe. To dlatego, otwierając
plik po serializacji, jesteś w stanie rozpoznać łańcuchy znaków w nim umieszczone: są one najbardziej
zwięzłym sposobem zapisu danych do pliku przez C#. Zapisywanie liczby w postaci łańcucha znaków
może być jednak marnotrawstwem. in t może być przechowywany na czterech bajtach. Niepotrzebne
i dziwne wydaje się zapisywanie liczby, powiedzmy 49 369 144, w postaci łańcucha składającego się
z 8 znaków — lub 10, jeśli włączymy w to spacje — które możesz odczytać. To marnowanie miejsca!

W dalszej części książki poznasz mniej zwarty i bardziej czytelny (i nadający się do edycji!)
format serializacji danych.

.NET używa do kodowania znaków lub ich łańcuchów systemu Unicode. Na szczęście Windows
posiada małe, użyteczne narzędzie, które pomaga zrozumieć jego działanie. Otwórz Tablicę znaków
(aby ją uruchomić, wystarczy wyświetlić ekran startowy systemu Windows, następnie pasek funkcji, Za kulisami
wybrać na nim opcję Szukaj, bądź nacisnąć Windows-R i wpisać charm ap.exe).

Kiedy patrzysz na te wszystkie symbole i litery używane w wielu różnych językach na całym świecie, to zdajesz sobie
sprawę, jak wiele rzeczy musi być zapisanych w pliku, aby przechować tekst. To dlatego .NET koduje wszystkie
łańcuchy znaków i znaki w formacie zwanym Unicode. Kodowanie oznacza, że logiczne dane (na przykład litera „H”)
są pobierane i zamieniane na bajty (numer 72). Takie postępowanie jest konieczne, ponieważ litery, liczby, typy
wyliczeniowe i inne dane muszą kiedyś skończyć, przyjmując postać bajtów na dysku lub w pamięci. I to między innymi
dlatego Tablica znaków jest przydatna — pokazuje sposób kodowania liter przez odpowiednie liczby.

Wybierz czcionkę Arial i przewiń Unicode to standard


ekran w dół aż do napotkania alfabetu
hebrajskiego. Znajdź literę szin i ^kny ją. przemysłowy
wprowadzony
przez organizację
non-profit o nazwie
Unicode Consortium.
Zaraz po kliknięciu litery jej numer Działa on na wielu
Unicode pojawia się w dolnym pasku różnych platformach
^ a u 1' -Heb/ a jsk.a litera szin ma numer komputerowych.
uot9. Jest to liczba szesnastkowa — Poświęć trochę
czasem określana jako hex czasu i zajrzyj
na jej stronę
i internetową:
http://unicode.org/.
Mozesz zamienić ją na liczbę
dZ!esiętną> uzywając Kalkulatora
Windows. Otwórz go, przełącz
W t?*13 programisty, kliknij przycisk
Wyboru Hex i wpisz „05EW. m n ij
teraz Dec — otrzymasz 1513.

480 Rozdział 9.
Odczyt i zapis plików

.N E T automatycznie konwertuje tekst do postaci Unicode


D w a podstaw ow e typy służące do przechow ywania tekstu — s tr in g i char — przechow ują
swoje dane w form acie U nicode. K iedy zapisywane są one do pliku w postaci bajtów, odbywa
Z ró b
* to !
się to w łaśnie z w ykorzystaniem tego form atu. U tw órz zatem nowy pro je k t i przeciągnij na
form ularz trzy przyciski. Użyjemy m eto d F ile .W rite A llB y te s () o raz F ile .R e a d A llB y te s () ,
aby uzyskać ogólne pojęcie n a te m at sposobu zapisu danych w form acie U nicode.

R Z A P I S Z Z W Y K Ł Y Ł A Ń C U C H Z N A K Ó W D O P L IK U I W C Z Y T A J G O Z P O W R O T E M .
Użyj tej sam ej m etody W r ite A llT e x t( ) , której używałeś w edytorze tek stu , aby pierw szy przycisk zapisywał łańcuch
znaków Eureka! do plik u o nazw ie eureka.txt. N astęp n ie stw órz now ą tablicę bajtów o nazw ie eurekaBytes ,
wczytaj do niej d an e z plik u i wypisz wszystkie w czytane bajty.
F ile .W rite A llT e x t("e u re k a .tx t", "E ureka!");
b yte [] eurekaBytes = F ile .R e a d A llB yte s("e u re k a .tx t");
foreach (byte b in eurekaBytes) ^ Metoda ReadAlBytesO zwraca referencję do
Consol e .Wr i t e ( "{0} ", b) ; n ^ e j tablicy bajtów, którazawiera wszystkie
C onsole.W riteLine(); bajtyodczytane z pliku.
W o knie Output zobaczysz n astęp u jące w artości: 69 117 114 101 107 97 33. Otwórz teraz p lik w Prostym
edytorze tekstu , który n apisałeś w cześniej w tym rozdziale. W yświetli o n „E u re k a!”.

S S P R A W , A B Y D R U G I P R Z Y C IS K W Y Ś W I E T L A Ł B A J T Y W P O S T A C I S Z E S N A S T K O W E J .
N ie tylko Tablica znaków p o tra fi w yświetlać liczby w po staci szesnastkow ej. Praw ie wszystkie p ro g ram y m ające coś
w spólnego z kodow aniem , których używasz do czytania danych, b ę d ą je wyświetlały w tym system ie. W arto zatem
p oznać zasady pracy z nim . U tw órz w p ro g ram ie p ro ce d u rę obsługi zd arzen ia analogiczną do pierwszej. Z m ień
tylko w iersz C on sole .W rite () n a następujący:

C onsole.W rite("{0:x2} ", b);


W ten sposób nakażesz m eto d zie W rite () wypisać p a ra m e tr 0 (pierw szy do w ypisania p o łań cu ch u znaków )
w postaci kod u szesnastkow ego składającego się z dw óch znaków . W ynik będzie p o d o b n y do p o kazanego
w p u nk cie 1. D a n e zo stan ą w ypisane w system ie szesnastkow ym zam iast, tak ja k w cześniej, w dziesiętnym :
45 75 72 65 6b 61 21. System szesnastkowy używa cyfr od 0 do 9
— ^ ------------------------------------------------- oraz liter od A do F do reprezentowania liczb
o podstawie 16. 6B będzie więc równe 107.
S S P R A W , A B Y T R Z E C I P R Z Y C IS K W Y P I S Y W A Ł L IT E R Y H E B R A J S K IE .
Przejdź pono w n ie do Tablicy znaków i kliknij dw ukrotnie zn ak szin (lub naciśnij przycisk Wybierz). Z o stan ie
on dodany do p o la „Z n ak i do skopiow ania”. Z ró b to sam o z pozostałym i literam i w yrazu „Szalom ” : lam ed
(U + 0 5 D C ), waw (U + 0 5 D 5 ) i m em finale (U + 0 5 D D ). D odaj te ra z k o d do p ro ced u ry obsługi zd arzen ia trzeciego
przycisku. B ędzie o n w yglądał ta k sam o ja k dla przycisku drugiego z je d n ą zm ianą. Kliknij przycisk Kopiuj w Tablicy
znaków i wklej w ybrane litery zam iast „ E u re k a!”, aby w yglądały ta k ja k tu:
F ile .W rite A llT e x t("e u re k a .tx t", "Dibw", Encoding.Unicode);
Czy zauw ażyłeś, że litery w klejane są w odwrotnej kolejności? T o dlatego, że w języku hebrajskim piszem y
od praw ej strony do lewej. G dy ID E n ap o ty k a w U n ico d e litery hebrajskie, w yświetla je w spak. U m ieść kursor
w ew nątrz tego w yrazu — działanie klaw isza strzałki w lewo i w p raw o je st odw rócone! D zięki te m u znacznie łatwiej
je st pisać w tym języku. U ru ch o m te ra z swój ko d i przyjrzyj się d okładnie rezu ltato m : f f fe e9 05 dc 05 d5 05
dd 05. Pierw sze dwie p ary — f f fe — in fo rm u ją nas, że pracu jem y z łańcuchem dw ubajtow ych znaków . P ozostałe
bajty to litery h eb rajsk ie — są o n e je d n a k odw rócone: U + 0 5 E 9 pojaw i się jak o e9 05. O tw órz te ra z plik w Twoim
Prostym edytorze tek stu — wszystko w ygląda praw idłowo!
jesteś tutaj ► 481
Weź sobie jeden b a jt

C# może użyć tablicy bajtów do przesyłania danych


W związku z tym, że wszystkie dane mogą zostać zakodowane
w postaci bajtów , nabiera sensu traktowanie plików jako
Oto kod pozwalający “ tworzyć
dużych tablic bajtów . Już wiesz, w jaki sposób je odczytywać i zapisywać. t a b lic e b a jt ó w , o tw o r z y ć p b k
o r a z w c z y ta ć d o t a b li c y b a j t y

od 0 do 6.

byte[] greeting;
greeting = File.ReadAllBytes(filename);

ininininininini
7 zmiennych ty pu byte

/ O
„87
l
105
z
116 97
i *
106
ę
33
t
33

Jf liczby to wa,
To je st statyczn a metoda Unicode dla zna
tablic, która odwraca
w napisie „Wita
porządek bajtów. Używamy
L i tylko po to, aby pokazać,
ż e z m ia n y w p r o w a d z o n e
do tablicy bajtów zo sta ją Array.Reverse(greeting);
dokładnie zap isan e w pliku.
File.WriteAllBytes(filename, greetir

Kiedy program za p isu je do

Tcwze -------------

Teraz kolejność bajtów


została odwrócona.

Odwracanie k o h jm ^ bajt<iw w stewie „Witaj!!" zadziatato prawidtowo tylko dlatego,


z nąi nA i K tr ^ s * ? j s e hebrajskiego
w przypadku rj . c ? słowa aifw?
482 Rozdział 9.
Odczyt i zapis plików

Do zapisywania danych binarnych używaj klasy BinaryWriter


Ot>iekt StreamWriter także
Przed zapisaniem do plików m ożesz zakodować łańcuchy znaków, znaki oraz liczby koduj e dane. Specjalizuje
zmiennoprzecinkowe w postaci tablic bajtów, ale byłoby to trochę męczące. To dlatego .NET się j ednak w operacjach
na tekście i kodowaniu
udostępnia bardzo użyteczną klasę o nazwie B in aryW riter , która automatycznie koduje dane znaków.
i zapisuje je w pliku. Musisz tylko utworzyć obiekt FileStream i przekazać go do konstruktora
klasy B in a ry W rite r . Będziesz mógł wtedy wywoływać jego metody i zapisywać dane.
Utwórz zatem nowy projekt typu Console A p p lic a tio n , w którym użyjesz klasy
Z r ó b to !
B in a ry W rite r w celu zapisania w pliku danych binarnych.

Rozpocznij od utworzenia nowej aplikacji i przygotowania danych przeznaczonych do zapisu.


in t intValue = 48769414; Qdu uż,ńesz Fii„ n ^ ...
Gdy użyjesz File.Create(), to utworzysz
s trin g stringValue = " W ita j!" ; nowy plik — j^ h taki już istnieje,
b yte [] byteArray = { 47, 129, 0, 116 } ; powstani^crtMem Zo^ $ 0!* fokc
flo a t floatV alue = 491.695F; ,t<spna. metoda FileOpenWrite(), która
otwiera istniejący plik i rozpoczyna
char charValue = 'E 1; nadpisywanie go od początku.
Aby użyć obiektu B in a ry W rite r , musisz najpierw otworzyć nowy strumień za pomocą F ile .C r e a te ( ) :
using (FileStream output = F ile.C reate("danebinarne.dat"))
using (BinaryW riter w rite r = new B inaryW riter(output)) {

Wywołaj teraz jego metodę W r ite () . Za każdym razem nowe bajty dodawane są na końcu pliku.
Zawierają one zakodowaną wersję dowolnych wartości przekazanych w postaci parametrów.
w rite r.W rite (in tV a lu e ); Każda instrukcja Write() koduje
odpowiednią wartość do p°staci ba.jtów
w rite r.W rite (s trin g V a lu e ); i przesyła je do obiektu FileStream. Obiekt klasy FileStream
w rite r.W rite (b y te A rra y ); Możesz do nich przekazać ka±dy zapisuje bajty na kmcu
typ wartościowy, a z°stanie m pliku.
w rite r.W rite (flo a tV a lu e ); automatycznie zakodowany.
w rite r.W rite (ch a rV a lu e );

r Zaostrz ołówek
Użyj teraz tego samego kodu, z którego korzystałeś wcześniej, w celu odczytania
zapisanego pliku.
Oto podpowiedz: Łańcuchy
b yte [] dataW ritten = File.R eadAllB ytes("danebinarne.dat"); znaków mogą mieć różną
długość, więc muszą
foreach (byte b in dataW ritten) zaczynać się od liczby,
która ją określa. Możesz
C onsole.W rite("{0:x2} ", b); odnaleźć kody znaków
Console.W riteLine(" - {0} bajtów ", dataW ritten.Length); Unicode, używając
Tablicy znaków.
Console.ReadKey();
Zapisz wynik z okna O utput w pustych polach poniżej. Czy potrafisz określić, które bajty
dotyczą poszczególnych instrukcji W rite () ? Oznacz każdą ich grupę nazwą zmiennej.

bajtów

jesteś tutaj ► 483


Połączenie danych
# Zaostrz ołówek
Wartości float i int zapisane do pliku zajmują
cztery bajty. Gdybyś użył typu long lub doublei
każda z nich zajmowałaby po osiem.
v Rozwiązanie

8 ó 2 9 e 8 0 2 0 ó 5 7 ó £ 7 4 6 1 ó d 2 1 2 £ 8 1 0 0 7 4 f ó d 8 f 5 4 1 4 5 - 2 0 bajtów
L-
intValue stringValue byteArray floatValue charValue

char przechowuje
Pierwszym bajtem w łańcuchu znaków
jest 6 — jest to jego długość. Możesz
użyć Tablicy znaków do wyszukanJa
£ Jeśli użyjesz Kalkulator Windows
do zamiany tych bajtów z postaci
szesnastkowej na dziesiętną
znaki Unicode.
zajmuje tylko jeden
bajt — zakodowany
każdego znaku w napisie „Witaj!“ — to przekonasz się, że są to
wartości z tablicy byteArra.y. jest wartością
rozpoczyna się on od U+0057, a kończy U+0045.
na U+0021.

Aby wczytać dane z powrotem, użyj BinaryReader


Klasa BinaryReader działa tak samo jak BinaryWriter. Tworzysz strumień, łączysz go Nie wierz nam na
słowo. Zamień wiersz
z obiektem BinaryReader, a następnie wywołujesz jego metody. Reader jednak nie wie, odczytujący float na
jakie dane znajdują się w p lik u ! Nie ma takiej możliwości, aby wiedział. Wartość flo a t wywołanie ReadInt32()
(będziesz musiał
491.695F została zakodowana do postaci f6 d8 f5 43. Jednak te same bajty pozwalają zmienić typ floatRead
zakodować całkowicie poprawną wartość in t — 1140185334. Będziesz więc musiał obiektowi na int). Na własne
BinaryReader dokładnie określić, jakich typów spodziewasz się podczas odczytywania plików. oczy przekonasz się,
co zostało
Dodaj do formularza jeszcze jeden przycisk i odczytaj dane, które przed chwilą zapisałeś. odczytane z pliku.

rA Rozpocznij od utworzenia obiektów FileStream oraz BinaryReader:

using (FileStream input = File.OpenRead("danebinarne.dat"))


using (BinaryReader reader = new BinaryReader(input)) {

Powiedz obiektowi BinaryReader, jakich typów danych oczekujesz, wywołując jego różne metody.
int intRead = reader. Readlnt32 (); R?py artośc!owy ma w obiekcie
DinaryReader swoją własną metodę, która
string stringRead = reader. ReadStr1ng(); zwraca jego dane. Większość z nich nie
byte[] byteArrayRead = reader. ReadBytes(4 ); ( potrzebuje żadnych parametrów, ale ReadBytes()
flo a t floatRead = reader. ReadS1ngle(); l ieMo w r s in f^ ^ T ćbę bajtów
char charRead = reader. ReadChar(); do odczytania.

Wyświetl teraz wczytane wartości, aby przekonać się, że wszystko działa prawidłowo.
Jeśli dodajesz
ten kod na Console.Write("int: {0} string: {1} bajty: ", intRead, stringRead);
końcu programu foreach (byte b in byteArrayRead)
z poprzedniej Console.Write("{0} ", b);
strony, nie
zapomnij dodać Console.Write(" flo a t: {0} char: {1} floatRead, charRead);
do niego kolejnego }
SK ^O , C°nsole. ReadKey ( ) ;
któremu okno
konsoli zostanie Tak będzie wyglądał rezultat wyświetlony w oknie konsoli:
zamknięte dopiero
po naciśnięciu int: 48769414 string: Witaj! bajty: 47 129 0 116 flo a t: 461,695 char:
j akiegoś klawisza.

484 Rozdział 9.
Odczyt i zapis plików

Pliki utworzone dzięki serializacji


można także zapisywać i odczytywać ręcznie
Pliki powstałe w procesie serializacji nie wyglądają dobrze po otwarciu ich
w Notatniku. Wszystkie je znajdziesz w katalogu bin/D ebug projektu. Poświęćmy Z r róó b to !
minutę i zapoznajmy się z wnętrzem takiego pliku.
f ^
Przeprowadź serializację dwóch obiektów Card do dwóch różnych plików.
Użyj kodu do serializacji, który napisałeś wcześniej, aby zapisać trójkę trefl do pliku karta1.dat. Zapisz także
szóstkę kier do karta2.dat. Upewnij się, że oba pliki zostały zapisane, znajdują się w katalogu i mają taki
sam rozmiar. Otwórz jeden z nich w Notatniku:

. karta 1 — N otatn ik
W pliku można
P lik E d y c ja F o rm a t W id o k Pom oc
wyróżnić kilka
I ■■‘‘li [[ MRecznaSerializacjaKart., Version=1.0,0.0, C u l t u r e s eut rai., PublicKeyToken=null£[
stów, jednak jeg° [RecznaSerializacjaKart.CardC Q<5uit>k BackingField[<Value>k BackingField[[RecznaSerializac jaKart. Suits]
przeważająca. część RecznaSerializac jaKart .ValuesD 0 Dy " " 'RecznaSerializacjaKart .SuitsC [value DO 0
jest nieczytelna- [ ii ‘ ‘ ‘ReeznaSerializacjaKart.ValuesO flvalue DO [ D
Nie zapomnij
■— y ' o dwóch
instrukcjach
using.

Napisz pętlę do porównywania dwóch plików binarnych.


Użyliśmy metody ReadAllBytes() do odczytania ze strumienia wszystkich bajtów; zwraca ona referencję do
ich tablicy. Skorzystaliśmy także z pola Length tablicy, aby mieć pewność, że porównaliśmy wszystkie dane.
b yte [] f ir s t F il e = F ile .R e a d A llB y te s ("k a rta l.d a t");
b yte [] secondFile = F ile .R e a d A llB yte s("ka rta 2 .d a t");
fo r ( in t i = 0; i < firs tF ile .L e n g th ; i++)
i f ( f i r s t F i l e [ i ] != s e co n d F ile [i])
C onsole.W riteLine("Bajt numer {0 }: {1} i {2 }",
i , f i r s t F i l e [ i ] , s e c o n d F ile [i]);

Ta pętlo sprawdza pierwsze bajty plików


i porównuje je . Potem to samo robi z drugimi,
trzecimi i tak dalej. Kiedy znajdzie różnicę,
informacja wypisywana jest na konsolę.

Dwa pliki wczytywane są do


dwóch różnych tablic bajtów,
więc mogą być porównywane
bajt po bajcie. W związku
z tym, że serializacji zostata
poddana ta sama klasa, Gdy zapisujesz dane do pliku,
są one niemal identyczne... nie zawsze zaczynasz od zera!
ale sprawdżmy, bardzo są
do siebie podobne.
Bądź ostrożny podczas korzystania z F i l e . O p e n U r i t e ( ) .
Metoda nie usuwa piiku — po prostu zaczyna nadpisywać dane, zaczynając
od początku. To diatego używaiiśmy F i l e . C r e a t e O — metody która
zawsze tworzy nowy plik.

Jeszcze nie skończyliśmy — przewróć kartkę! jesteś tutaj ► 485


Pokażmy nasze różnice

Sprawdź, gdzie pliki się różnią,


i użyj tej informacji do ich zmiany
Pętla dokładnie określiła punkty, w których pliki zawierające dwa
serializowane obiekty Card różnią się od siebie. W związku z tym, że jedyną
różnicą pomiędzy dwoma obiektami były pola S u it oraz Value , powinny
być to także jedyne różnice w ich plikach. Jeśli zatem znajdziemy bajty,
które przechowują kolor oraz wartość kart, to powinniśmy być w stanie je
zmienić i utworzyć nową, dowolną ka rtę !

Rzuć okiem na rezultaty wypisane w oknie konsoli i zwróć uwagę na różnice.


W oknie konsoli powinny zostać wypisane dwa różniące się bajty:

Bajt numer 307: 1 i 3


Bajt numer 364: 3 i 6
To powinno mieć sens! Przejdź z powrotem do typu wyliczeniowego S u its z poprzedniego rozdziału
i przekonaj się, że wartością Clubs jest 1, natomiast wartością Hearts jest 3. To jest pierwsza różnica.
Druga — sześć i trzy — dotyczy oczywiście wartości karty. Możesz naturalnie zobaczyć inne numery bajtów.
Nie powinno to być dla Ciebie zaskoczeniem, gdyż możesz na przykład używać różnych przestrzeni nazw,
które zmieniają długość pliku.

Pamiętasz, ze przestrzeń nazw jest częścią pliku


Hmm, jeśli bajt numer 307 reprezentuje
hmS T f ■Tw°j° P a tr z e ń nazw?jest kolor, to powinniśmy być w stanie go
^ to te numery b jó w t'akZe mogą się różnić
zmienić, wczytując plik, podmieniając
ten jeden bajt i zapisując go ponownie.
(Pamiętaj, Twój plik po serializacji mże
przechowywać kolor w innym miejscu).

Napisz kod do ręcznego tworzenia nowego pliku, który zawiera króla pik.
Weźmiemy jedną z odczytanych tablic, zmodyfikujemy ją tak, aby zawierała nową kartę,
i zapiszemy ją z powrotem.

Jeśli fir s tF ile [3 0 7 ]j= (byte)Suits.Spades;


w punkcie 3. fir s tF ile { 3 6 5 y = (byte)Values.King;
znajdziesz
inne numery F ile .D e le te ("k a rta 3 .d a t");
bajtów,
zastąp nimi F ile .W rite A llB y te s ("k a rta 3 .d a t", f i r s t F il e ) ;
te tutaj.
Zastosuj deserializację ka rty z p liku k a rta 3 .d a t i sprawdź,
czy otrzymałeś króla pik. Teraz, gdy już wiesz,
w którym miejscu zapisane
są kolor i wartość karty,
możesz zmienić tylko te bajty
w tablicy. Potem wystarczy
zapisać wszystko do pliku
karta3.dat.

486 Rozdział 9.
Odczyt i zapis plików

Praca z plikami binarnymi może być skomplikowana


C o zrobić, jeżeli m asz plik, ale nie jesteś pew ien, co jest w środku? N ie m asz pojęcia,
k tó ra aplikacja go utw orzyła, a chcesz coś o nim w iedzieć. Je d n a k gdy otw ierasz go
w N o tatn ik u , w ygląda ja k k u p a śm ieci. Co zrobić jeśli w yczerpałeś ju ż wszystkie inne
możliwości, a n apraw d ę m usisz się dow iedzieć, co je st w ew nątrz? P atrząc n a poniższą
ilustrację, łatw o dojść do w niosku, że N o ta tn ik nie je st właściwym n arzędziem .
To jest karta po serializacji otwarta w Notatniku.
1/ Wygląda ma to, że nie będzie z tego pożytku.

karta3 — Notatnik -
I P lik E d y c ja F o rm at W id o k Pom oc

I |D ‘ ' '0 [[ MR ec zn aS er ia li za cj aK ar t, Version=1.0.6.0., Cu lture=neutral, PublicKeyToken=nullGO


I [RecznaSerializaćjaKart.CardO B<5uit>k BackingFieldO<Value>k BackingField[[RecznaSerializaćjaKart.SuitsO
I Rę cz na Se ri al iz ać ja Ka rt.Values[ [ Dy’ "'RęcznaSerializaćjaKart.SuitsO Dvalue [[
I D u ‘’’RecznaSerializacjaKart.ValuesO [value Hi D

Możesz dostrzec kilka rzeczy — na przyMad nazwy typu wyliczeniowego

t („Suits“ i „Values“) oraz nazwę używanej przestrzeni nazw


(„RecznaSerializacjaKart” ). Nie jest to jedmlc wcale pomoce.

A le jest inne rozw iązanie — istnieje pew ien sposób prezen tacji o kreślany jak o w idok szesnastkow y (ang. hex du m p ),
k tó reg o użycie stanow i dość pow szechną tech n ik ę p rzeg ląd an ia danych binarnych. Je st o n z pew nością bardziej
przydatny niż przegląd an ie plików w N o tatn ik u . F o rm a t szesnastkow y — lub hex — to wygodny sposób w yświetlania
bajtów . K ażdy z nich w ym aga do zapisu dw óch znaków , w ięc n a stosunkow o niew ielkiej przestrzen i m ożesz
zobaczyć w iele danych. Je st to tak że fo rm at, w którym m o żn a łatw o d ostrzec pow tarzające się w zorce. Przydaje się
do w yśw ietlania danych binarnych, k tó re m ają długość 8, 16 lub 32 bajtów . W iększość tych danych m a te n d en c ję do
łączenia się w grupy 4, 8, 16 lub 32 b ajtó w ... ta k ja k w szystkie typy w C # . N a przykład in t zajm uje 4 bajty i podczas
zapisu do pliku w łaśnie tyle w ymaga. T a k o to w ygląda te n sam plik w yświetlony w p ostaci szesnastkow ej za po m o cą
jed n eg o z w ielu darm ow ych pro g ram ó w tego typu dostępnych dla system u W indows:

Możesz Wiersz polecenia


natychmiast 0 0 0 0 : 0 0 0 1 0 0 00 0 0 ff ff ff -- ff 01 00 00 00 00 00 00 A
dostrzec wartość 0 0 1 0 : 0 0 0C 0 2 0 0 0 0 0 0 4d 52 -- 65 63 7a 6 e 61 53 65 72 ......MRecznaSer
liczbową każdego 0 0 2 0 : 69 61 6 c 69 7a
0030: 65 72 73 69 6 f
61 63
6 e 3d
6a
31
--
--
61
2e
4b
30
61 72 74 2 c
2 e 30 2 e 30
2 0 56
2 c 20
ializ a c j a K a r t , V
e r s i o n = l .0 .0 .0 ,
bajtu w pliku. 0040: 43. *75 72 65 3d -- 6e 65 75 74 72 61 6c 2c Culture=reutral,
Bil EL m ►J 69 63 4b -- 65 79 54 6f 6b 65 6 e 3d PublicKe y Token=
6 e 75 6 c 'rrr ©5 0 1 00 0 0 -- 00 lb 52 65 63 7a 6 e 61 n u l i ...... Ręczna
65 72 69 61 6 c 69 7a -- 61 63 6a 61 4b 61 72 74 SerializacjaKart
2 e 43 61 72 64 0 2 00 0 0 -- 00 15 3c 53 75 69 74 3e Card <5uit>
0090: 6 b 5f 5f 42 61 63 6 b 69 -- 6e 67 46 69 65 6c 64 16 k__ Backin g F i e l d .
Liczba na początku 0 0 a0 : 3c 56 61 6c 75 65 3e 6 b -- 5f 5f 42 61 63 6b 69 6 e <V a l u e >k__Backin W dalszym ciągu
każdego wiersza 0 0 b0 : 67 46 69 65 6c 64 04 04 -- lc 52 65 63 7a 6e 61 53 g F i e l d ...RecznaS widzisz tekst
jest przesunięciem 0 0 c0 : 65 72 69 61 6c 69 7a 61 -- 63 6a 61 4b 61 72 74 2 e er i a l i z a c j a K a r t . oryginalny, ale
0 0 d0 : 53 75 69 74 73 02 00 0 0 -- 0© Id 52 65 63 7a 6 e 61 S u i t s . .... Ręczna
(lub odległością 00eO: 53 65 72 69 61 6 c 69 7a -- 61 63 6a 61 4b 61 72 74 SerializacjaKart
wszystkie znaki
od początku pliku) 0 0 f0 : 2 e 56 61 6c 75 65 73 0 2 -- O© 00 00 02 00 00 0 0 05 .Values ......... uznane za śmieci
jego pierwszego 0 10 0 : fd ff ff ff 1C 52 65 63 -- 7a 6 e 61 53 65 72 69 61 .....RecznaSeria zostały zastąpione
0 110 : 6 c 69 7a 61 63 6 a 61 4 b -- 61 72 74 2 e 53 75 69 74 lizacjaKart.Suit
bajtu. 0 12 0 : 73 0 1 0 000 0 0 07 76 61 -- 6c 75 65 5f 5f 0 0 08 0 2 s .....value ...
kropkami.
0130: 00 00 00 00 0 0 0 0 0 0 05 -- fc ff ff ff Id 52 65 63 .............Rec
0140: 7a 6 e 61 53 65 72 69 61 -- 6c 69 7a 61 63 6a 61 4b znaSerializacjaK
0150: 61 72 74 2 e 56 61 6 c 75 -- 65 73 0 1 0 0 0 0 00 07 76 ar t . V a l u e s . ....v
0160:
0170:
61 6 c 75 65
Ob
5f 5f 0 0 08 -- 0 2 00 00 00 0 d 00 00 00
.
alue
.lue
...........
........... ^
y*.

C:\temp>H
V

jesteś tutaj ► 487


41 6c 65 20 6a 61 7a 64 61 2c 20 63 6 f 20 6e 69 65 3 f

Użyj strumieni plików do utworzenia widoku


w postaci szesnastkowej
Szesnastkowy widok zawartości plików jest dość powszechnym sposobem stosowanym
przez programistów w celu dokładnego przeglądania ich wewnętrznej struktury. Większość
systemów operacyjnych udostępnia do tego specjalne narzędzie. Niestety Windows nie.
Stwórzmy zatem taki program!

W jak i sposób otrzymać wartości szesnastkowe


Rozpocznijmy od znanego zdania:
Wchodząc do te j Europy, musimy pamiętać, że tam są plusy dodatnie i ujemne.
Oto szesnastkowy zapis, jaki mógłby powstać na podstawie tego tekstu:
I znów możesz natychmiast dostrzec
wartość liczbową każdego bajtu w pliku.'—N.
kO
CO

OJ
O

kO
LO
0000: 57 63 6f 64 7a(b9^€3 64 6 f 20 74 6a 20 Wchodząc do te j
I
I

0010: 45 75 72 6f 70 79 2c 20 -- 6d 75 73 69 6d 79 20 70 Europy, musimy p


0020: 61 6d 69 ea 74 61 e6 2c -- 20 bf 65 20 74 61 6d 20 amiętać, że tam
0030: 73 b9 20 70 6c 75 73 79 -- 20 64 6 f 64 61 74 6e 69 są plusy dodatni
0040: 65 20 69 20 75 6a 65 6d — 6e 65 2e e i ujemne.
Będziemy
także musieli
używając przesunięcia pierwszego bajtu w tym wierszu. zamienić
niepotrzebne
Każda z tych „liczb” — 57, 63, 68, 6F — jest zapisem jednego bajtu pliku. Powodem, dla którego część znaki na kropki.
z nich zawiera litery, jest ich postać szesnastkowa (lub hex). To po prostu jeden ze sposobów zapisu.
Zamiast używać dziesięciu cyfr, od 0 do 9, w systemie szesnastkowym korzystamy z szesnastu znaków:
cyfr od 0 do 9 oraz liter od A do F.

Każdy wiersz w formacie szesnastkowym reprezentuje szesnaście znaków z wejścia, które zostały użyte
do jego wygenerowania. W naszym przypadku pierwsze cztery znaki są odległością od początku pliku —
pierwszy wiersz rozpoczyna się znakiem 0, następny znakiem 16 (lub 10 szesnastkowo), potem następuje
znak 32 (szesnastkowo 20) i tak dalej (inne programy tego typu mogą wyświetlać dane nieco inaczej,
ale taka postać będzie dla nas satysfakcjonująca).

Praca z systemem szesnastkowym


Liczby szesnastkowe możesz wstawiać bezpośrednio w programie
— po prostu dodaj przed nimi znaki 0x (cyfrę zero i literkę „x”):

in t j = 0x20;
MessageBox.Show("Wartość je s t równa " + j ) ;

Kiedy używasz operatora + do łączenia liczby z łańcuchem znaków, liczba ta jest Strimg.Format() używa takich
samych parametrów jak
zamieniana na wartość dziesiętną. Możesz użyć statycznej metody S trin g .F o rm a t() Console.WriteLine(), więc
do zamiany danej liczby na łańcuch znaków w postaci szesnastkowej: nie będziesz musiał się
wiele uczyć, aby tę metodę
s trin g h = S trin g .F o rm a t("{0 :x2 }", j ) ; stosować.

488 Rozdział 9.
Odczyt i zapis plików

StreamReader i StreamWriter będą do tego odpowiednie


Nasz program do wizualizacji postaci szesnastkowej będzie zapisywał wyniki do pliku.
W związku z tym, że zapisuje on tekst, S tr e a m W r ite r będzie odpowiedni. Możemy Metoda ta nosi nazwę
także odnieść korzyść ze stosowania metody ReadBlock() obiektu S tre a m R e a d e r . ReadBlock(), ponieważ
w momencie jej
Wczytuje ona blok znaków, które chcesz odczytać, do tablicy char. Określasz, ile ma wywołania „blokuje“
ich być, i metoda albo wczyta taką ich liczbę, albo wszystkie pozostałe, jeśli w pliku działanie programu
zostało ich mniej. Ponieważ w każdym wierszu znajduje się 16 znaków, będziemy (co oznacza, że metotJa
jest wykonywana,
czytać bloki o takim właśnie rozmiarze. uniemożliwiając dalsze
wykonywanie programu)
Dodaj w takim razie jeszcze jeden przycisk do programu i wstaw w nim kod zrzucający
aż do momentu
dane szesnastkowe do pliku. Zmień dwa początkowe wiersze tak, aby wskazywały odczytania zadanej liczby
prawdziwe pliki na Twoim dysku. Rozpocznij pracę od pliku z obiektem Card znaków lub odczytania
wszystkich danych
po serializacji. Sprawdź, czy jesteś w stanie zmodyfikować program, aby korzystał ze strumienia.
z okien dialogowych O twórz i Z a p isz ja ko .

u sin g ( S tr e a m R e a d e r r e a d e r = new S t r e a m R e a d e r ( @ " c : \ p l i k i \ p l i k W e j s c i o w y . t x t " ) )


u sin g ( S t r e a m W r it e r w r i t e r = new S t r e a m W r i t e r ( @ " c : \ p l i k i \ p l i k W y j s c i o w y . t x t " , fa ls e ))
Pole EndOfStream obiektu StreamReader- zwraca
{
false, jeżeli w pliku pozostały jeszcze j akieś znaki
in t p o s itio n = 0;
To wywołanie ReadBlock()
w h ile ( ! r e a d e r . EndOfStream) { wczytuje maksymalnie 16 znaków
ch a r[] b u f f e r = new c h a r l~ 1 6 l; do^tabjicy char^ ________

in t c h a r a c t e r s R e a d = r e a d e r . fie a d B lo c k ib u T T e rT '0 , lO )^ Statyczna metoda String.Format


zamienia hczby na łańcuchu
w rite r.W rite ( " { 0 } : " , S t r in g .F o r m a t ( " { 0 :x 4 } p o s itio n )); znaków. „{0:x4}“ nakazuje
p o s itio n += c h a r a c t e r s R e a d ;
metodzie Format() wypisać
drugi parametr — w tym
fo r (in t i = 0; i < 16; i+ + ) { przypadku position — jako
liczbę szesnastkową w postaci
if (i < c h a ra c te rs R e a d ) { czterech znaków.

W sZyltUzMk? * “ s t r i n 9 h ex = S t r i n g . F o r m a t ( " { 0 : x 2 } (b y te )b u ffe r[i]);


wypisuje je w r ite r .W r ite ( h e x + " " ) ;
w postaci wiersza
na wyjście. }
Możesz zamienić
tablicę char[] na
e ls e , r “; łańcuch znaków,
w rite r.W rite ( " ");
K ' ï - Æ ? « » l~ f przekazując ją
do przeciążonego
if (i == 7) { w rite r .W r ite ( " - -
konstruktora string.
"); }
if (b u ffe r[i] < 32 || b u ffe r[i] > 250) { b u ffe r[i] }
}
s trin g b u ffe rC o n te n ts : new s t r i n g ( b u f f e r ) ;
w r ite r .W r ite L in e ( " ' + b u f f e r C o n t e n t s . S u b s t r in g ( 0 , c h a r a c te rs R e a d )) ;

s . .
knidu tańcuch znaków posiada metodę która
Kwżr<
acataec!ochrazament. W tym p rz y p a d k o w a ° n*Pee™sze
charactersRead znaków, rozpoczynając od .
cPopatrz na początek_pętli^i znajdź m<ej sce, .gdz<e j e t _
(P°pat"„.';a c h & „ r : R £ i; ^ o c »
I p:o,cz';S’k
ustawiana zmienna
zwraca liczbę znaków czytanych do tablicy).
jesteś tutaj ► 489
G enerow anie w idoku szesnastkowego

Użyj Stream.Read() do odczytywania bajtów ze strumienia


Z r ó b to!
Program do generowania widoku szesnastkowego działa bardzo dobrze na plikach tekstowych.
Jest jednak pewien problem. Spróbuj użyć metody F i l e . W r i t e A l l B y t e s ( ) , by zapisać w pliku
zawartość tablicy bajtów o wartościach większych od 127, a następnie wyświetl ten plik przy użyciu
programu do tworzenia widoku szesnastkowego. Ups... — wszystkie bajty zostały wyświetlone jako „fd”!
Stało się tak dlatego, że klasa S tre a m R e a d e r zo sta ła utw o rzo n a z m yślą o odczytyw aniu plików
tekstow ych , w których wartości poszczególnych bajtów nie przekraczają 128. A zatem poprawmy nasz
program — wystarczy, że będziemy odczytywać bajty bezpośrednio ze strumienia, używając do tego celu
metody S tr e a m .R e a d ( ) . Jednocześnie upodobnimy naszą aplikację do faktycznych narzędzi tego typu:
zapewnimy możliwość podania nazwy pliku w postaci a r g u m e n tu w y w o łan ia p r o g r a m u .

Utwórz nowy projekt typu Console Application i n azw ij go hexdum per . Jego kod zamieściliśmy
na następnej stronie. Poniżej przedstawiliśmy wyniki generowane przez nasz program.
Jeśli uruchomisz program bez podcnw^a
żadnych argumentów, to wyświetli on Kod błędu zostanie także zwrócony
komunikat o błędzie i zakończy ^ a łrn i^ w przypadku przekazania nazwy
zwracając stosowny kod błędu. pliku, który nie istnieje.

Wiersz poler^. iia


Jeśli do programu
C :\ t e m p > h e x d u m p e r ^ przekażesz prawidłową
S p o s ó b u życia: h e x d u m p e r p l i k - d o - w y ś w
nazwę pliku, to
C :\ t e m p > h e x d u m p e r p l i k _ k t o r e g o _ n i e _ m a . d a t wyświetli on w oknie
Pl i k n i e istni e j e : p l i k _ k t o r e g o _ n i e _ m a . d a t wiersza polecenia
szesnastkowy widok
C :\ t e m p > h e x d u m p e r karta3.dat
0000 : 0 0 01 O O 0 0 00 f f f f ff
jego zawartości.
0010

t
: 0 0 0c 0 2 0 0 00 0 0 4 d 52 ....... M R e c z n a S e r
0020: 69 61 6c 69 7a 61 6 3 6a ializacjaKart, V
0030: 65 72 7 3 69 6f 6 e 3d 31 e r s i o n = l . 0.0. 0 ,
0040: 4 3 75 6c 74 75 72 65 3d Culture=neutrai,
0050: 20 50 75 62 6c 69 63 4b PublicKeyToken=
Zazwyczaj w celu wyświetlania
0060: 6e 75 6c 6c 05 01 00 00 nuli Ręczna
0070: 53 65 72 69 61 6c 6 9 7a SerializacjaKart
tekstów w oknie wiersza
0080: 2e 4 3 61 72 64 02 00 00 .Card <Suit> polecenia używamy metody
0090: 6b 5f 5 f 42 61 6 3 6 b 69 k BackingF i e l d . Console.WriteLine(), jednak
00a0: 3c 56 61 6c 75 65 3 e 6b <Value>k Backin
0 0 b0: 67 4 6 6 9 65 6c 64 0 4 04 g F i e l d . . . R e cznaS
tym razem do wyświetlania
00C0: 65 72 6 9 61 6c 6 9 7a 61 erializacjaKart. komunikatów o błędach
00d0: 53 75 6 9 74 73 02 0O 00 Suits Ręczna skorzystamy z metody
0 0 e0: 53 65 72 69 61 6c 6 9 7a SerializacjaKart
Console.Error.WriteLine(),
00f0: 2e 56 61 6c 75 65 7 3 02 . V a l u e s ..........
0100 : fd ff f f ff lc 52 65 63 RecznaSeria
by nie były one przekierowywane,
0110: 6c 69 7a 61 63 6a 61 4 b lizaćjaKart.Suit jeśli użyjemy znaków > lub >>
0120: 73 01 0 0 0 0 00 0 7 7 6 61 s value ... do przekierowania wyników
0130: 00 00 00 00 00 0 0 0 0 05 . . . . . . . . . . . . . Rec generowanych przez
0140: 7a 6e 61 53 65 72 6 9 61 znaSerializaćjaK
0150: 61 72 74 2e 56 61 6c 75 art .Values v
nasz program.
0160: 61 6c 75 65 5f 5f 0 0 0 8 alue .......... .
0170: 0b

Podawanie argumentów w wierszu polecenia


Zawsze gdy tw o rzy sz nowy projekt Console Application, Visual Studio tw o rz y klasę zawierająca m etod ę
MarnO zadeklarowaną w następujący sposób: static void Main(String[] args). Jeśli uruchomisz taki
prograrn, przekazując jakieś param etry w wierszu polecenia, t o zostaną one umieszczone w tablicy
args. Dzieje się ta k nie t ylko w przypadku aplikacji konsolowych; identyczne rozwiązanie znajdziesz
w pliku Pro gram .cs w każdym projekcie ty pu Windows Forms Application.
Będziesz chciał przekazywać argum enty w wierszu polecenia tak że podczas testowania aplikacji.
nazaWad topwbprzypadku testowania pr o gramów w IDE, wybierz opcję PROJECT/Properties i wpisz je

490 Rozdział?.
Jeśli właściwość args.Length ma
Argumenty z wiersza polecenia wartość różną od 1, to w wierszu Odczyt i zapis plików
zostaną przekazane do programu polecenia nie przekazano żadnych
przy użyciu parametru args. argumentów lub ich liczba była
większa od jednego.

s ta tic void M a in (s trin g [] args) kaĄay

{
if (args.Length != 1) Zwróć uwagę, że używamy tu
ST"
metody Console.Error.WriteLine().

Console.Error.WriteLine("Sposób użycia: hexdumper p lik-do -w yśw ietle nia "


System .Environm ent.Exit(l);

Upewnijmy się,
f (!F ile .E x is ts (a rg s [0 ])) że została przekazana
prawidłowa nazwa
C o n sole .E rror.W rite Line ("P lik nie is tn ie je : { 0} ", a rg s [0 ]); pliku. Jeśli taki plik nie
istnieje, wyświetlimy inny
System.Environment.Exit(2); komunikat, a program
Nie potrzebujemy zwróci inny kod błędu■
obiektu StreamReader,
using (Stream input = File.0penRead(args[0])) gdyż odczytujemy
bajty bezpośrednio
in t p o sitio n = 0; ze strumienia.
Aby odczytywać bajty ze
b yte [] b u ffe r = new b yte[16 ]; strumienia i zapisywać je
w buforze, używamy metody
while (p o sitio n < input.Length) Stream.Read(). Zwróć uwagę,
{ że tym razem buforem jest
in t charactersRead = input.R ead(buffer, 0, b uffe r.L e n g th ); tablica typu byte. To ma sens
— odczytujemy bajty, a nie
i f (charactersRead > 0) znaki z pliku tekstowego.
{
C onsole.W rite("{0}: ", S tring.F orm at("{0:x4} p o s itio n ));
p o sitio n += charactersRead;

fo r ( in t i = 0; i < 16; i++)


{
if (i < charactersRead)
Ten fragment {
programu
jestprawie s trin g hex = S trin g .F o rm a t("{0 :x 2 }", ( b y te ) b u ffe r [i]) ;
identyczny — Console.Write(hex + " " ) ;
jedyną różnicą }
jest to, że bufor
zawiera teraz else
bajty, a nie Console.W rite(" " );
znaki (jednak
w obu tych
przypadkach if (i == 7)
metoda C onsole.W rite("-- " ) ;
String.Format()
będzie działać
prawidłowo). if ( b u ffe r [i] < 32 || b u ff e r [i] > 250) { b u ff e r [i] = (b y te )1. 1; }
}
s trin g bufferContents = Encoding.UTF8.GetString(buffer);
C o n so le .W rite L in e ("X + bufferC ontents.S ubstring(0, charactersRead));

Jeśli w celu uruchomienia aplikacji konsolowej w ybrałeś opcję Start


} Without Debugging (lub nacisnąłeś kombinację klawiszy Ctrl+FS),
} To prosty i szybki sposób przekomwertowania tobficy
to po jej zakończeniu wygenerowane wyniki zostaną zachowane
bajtów na łańcuch zn°ków. Metoda. ta. i ^ ż i ASC II w oknie, a poniżej nich będzie widoczny komunikat: „Press any
mOsk Encoding.UTF8 (lub kodow°nia ASC11
bądź dowolnego innego^ gdyż inne key to continue...” (naciśnij dowolny klawisz, by kontynuować).
powodować zamianę tablicy bajtow na zupełnie inny
łańcuch znaków. jesteś tutaj ► 491
Bez głupich pytań

P : Dlaczego nie muszę korzystać P : Kiedy powinienem używać File,


powiązane ze sobą — pomiędzy 0 i 65535,
co w formacie szesnastkowym będzie miało
z metody Close() do zamknięcia pliku a kiedy FileInfo?
postać FFFF Plik musi więc w jakiś sposób
po użyciu File.ReadAllText() oraz
File.WriteAllText()? O : Główna różnica pomiędzy klasami F ile
powiedzieć danemu programowi, że będzie
zawierał znaki o wysokich wartościach. Dlatego
i F ile In f o polega na tym, że metody F ile
O : Klasa F i l e posiada kilka użytecznych zapisywana jest na jego początku specjalna
są statyczne, więc nie musisz tworzyć instancji
metod statycznych, które otwierają plik, zarezerwowana sekwencja: „FF FE". Nazywamy
tej klasy, F ile In f o natomiast wymaga
ją znacznikiem kolejności bajtów. Gdy
odczytują lub zapisują dane i automatycznie utworzenia instancji na podstawie nazwy
go zamykają . Uzupełnieniem
program ją zobaczy, będzie wiedział, że znaki
pliku. W niektórych przypadkach mogłoby
R e a d A llT e x t() i W rite A llT e x t() zakodowane są za pomocą dwóch bajtów
to być nieefektywne, na przykład gdybyś
są metody R ead A llBytesO (E będzie zakodowane jako 00 45 — wraz
chciał przeprowadzić pojedynczą operację na
z zerami na początku).
i W rite A llB y te s O , które pracują pliku (powiedzmy usunąć go lub przenieść).
z tablicami bajtów, oraz R e a d A llL in e s ()
i W r it e A llL in e s () , które odczytują
Z drugiej strony, jeśli chcesz na danym
pliku wykonać wiele operacji, to większą
P : Dlaczego nazywamy to znacznikiem
kolejności bajtów?
i zapisują tablice łańcuchów znaków, przy czym efektywność uzyskasz, stosując F ile In fo ,
każdy łańcuch jest jednym wierszem pliku.
Wszystkie te metody automatycznie otwierają
ponieważ jego nazwę będziesz musiał
przekazać tylko raz. Powinieneś zdecydować,
O : Pamiętasz, że Twoje bajty były
odwrócone? Wartość Unicode U+05E9 znaku
i zamykają strumienie, więc możesz wykonać której klasy użyć, w zależności od sytuacji,
szin została zapisana do pliku jako E9 05.
całą operację w jednej instrukcji. w której się znajdujesz. Inaczej mówiąc, jeżeli
Nazywamy to little endian. Wróć do kodu
wykonujesz na pliku pojedynczą operację, użyj zapisującego wspomniane bajty i dodaj do
P : Skoro FileStream ma metody F ile . Jeśli natomiast przeprowadzasz ich wiele
metody W rite A llT e x t () trzeci parametr:
do odczytywania i zapisywania plików, pod rząd, skorzystaj z F ile In fo .
Encoding.BigEndianUnicode. Nakazuje
to po co miałbym używać StreamReader
i StreamWriter? P : Zaczekaj. Dlaczego „Eureka!” została
on metodzie zapisać dane w formacie „big
endian", który nie zamienia porządku bajtów.
zapisana do pliku w postaci jednego bajtu
O : Klasa File S tre a m jest naprawdę Zostaną one tym razem zapisane w postaci
na znak, natomiast litery hebrajskie
pomocna podczas odczytywania i zapisywania „05 E9". Będziesz także widział inny znacznik
zostały zapisane przy użyciu dwóch
bajtów do plików binarnych. Metody, których kolejności bajtów: „FE FF". Twój Prosty edytor
bajtów? Co robi to „FF FE” na początku
w tym celu używa, operują na bajtach i ich tekstu jest wyjątkowo sprytny i potrafi
ich sekwencji?
tablicach. Większość programów pracuje odczytać oba typy plików!
jednak wyłącznie z plikami tekstowymi — O : To, co widzisz, to różnica pomiędzy
na przykład pierwsza wersja Generatora dwoma blisko ze sobą związanymi
wymówek, który do plików zapisywał tylko kodowaniami Unicode. Zwykłe litery łacińskie, Jeżeli zapisujesz łańcuch,
łańcuchy znaków. To właśnie w takich cyfry i standardowe znaki (na przykład k tó ry posiada tylko
przypadkach StreamReader oraz nawiasy klamrowe, znak & i inne, które są
Stream W riter okazują się pomocne. na klawiaturze) mają bardzo niskie wartości znaki Unicode o niskich
Posiadają one metody przystosowane w Unicode — pomiędzy 0 i 127. (Jeśli używałeś numerach, to do pliku
specjalnie do czytania i zapisywania wcześniej ASCII, to są to te same znaki co
tekstu. Bez nich wczytanie wiersza tekstu tam). Gdy plik zawiera tylko Unicode z niskimi
zapisywany jest tylko
wymagałoby wczytania tablicy bajtów numerami, znaczy to, że zapisane zostały tylko jeden bajt na znak. Jeśli
i utworzenia pętli wyszukującej w niej znaku te znaki. jednak posiada on znaki
przejścia do nowego wiersza. Łatwo więc Wszystko nieco się komplikuje po dodaniu
domyślić się, że dzięki tym klasom życie staje
o numerach wysokich,
do sekwencji znaków Unicode o większych
się znacznie łatwiejsze. wartościach. Jeden bajt może przechowywać to każdy z nich będzie
wartości w zakresie od 0 do 255, a dwa wymagał dwóch bajtów.

To kodowanie nosi nazwę UTf-8 i .NET używa go domyślnie.


Możesz zażądać, by metoda File.WriteAllText() zast°s° wata mire
kodowanie, przekazując do niej odpowiedni parametr. _Więcej
informacji na temat Unicode możesz znaleźć na stronie
http://unicode.org.

492 Rozdział 9.
Odczyt i zapis plików

Zmień Program do zarządzania wymówkami Damiana tak, aby używał poddanych serializacji
Ćwiczenia plików binarnych z obiektami Excuse zamiast plików tekstowych.

(O Spraw, aby klasa Excuse była zdolna do serializacji.


Oznacz klasę Excuse atrybutem [S e r ia liz a b le ] , aby mogła zostać zserializowana.
Będziesz także musiał dodać taki oto wiersz using :
using S yste m .R u n tim e .S e ria liz a tio n .F o rm a tte rs .B in a ry ; Podpowiedz-. Jakiego
słowa kluczowego
Zmień metodę E xcuse.S ave() tak, aby serializowała wymówkę. możesz użyć wewnątrz
Kiedy metoda Save() zapisuje plik do katalogu, to zamiast korzystać z obiektu re e 'encję do ^ iej
Stream W riter , otwórz plik i przekaż do serializacji bieżący obiekt. Będziesz musiał stzmej?
wymyślić sposób, w jaki dana klasa będzie mogła skorzystać z deserializacji.

[3 ) Zmień metodę E x c u se .O p e n F ile () tak, aby umożliwiała deserializację wymówki.


Będziesz potrzebował tymczasowego obiektu Excuse, do którego zostaną wpisane rezultaty
deserializacji z pliku, by następnie skopiować jego pola do bieżącego obiektu.

[4 Zmień teraz formularz tak, aby korzystał z nowego rozszerzenia.


Jest jeszcze jedna mała zmiana, którą musisz wprowadzić do formularza. W związku z tym,
że już nie pracujemy z plikami tekstowymi, nie powinniśmy używać rozszerzenia .txt. Zmień
okna dialogowe, domyślne nazwy plików oraz wzorzec do wyszukiwania w folderze, tak aby
pracowały teraz z plikami posiadającymi rozszerzenie *.excuse.

SUPER, TO BYŁO NAPRAWDĘ PROSTE! CAŁY KOD DO


ZAPISYWANIA I OTWIERANIA PLIKÓW ZNAJDOWAŁ SIĘ W EWNĄTRZ
KLASY EXCUSE. WYSTARCZYŁO, ŻE ZMIENIŁEM JĄ — W ZASADZIE LEDWIE
DOTKNĄŁEM KODU FORMULARZA. WYCHODZI N A TO, ŻE FORMULARZA NIE
INTERESUJE SPOSÓB ZAPISU DANYCH PRZEZ KLASĘ. PO PROSTU PRZEKAZUJE ON
NAZWĘ PLIKU I WIE, ŻE WSZYSTKO ZOSTANIE WŁAŚCIWIE Z A P IS A N E .^

Tak jest! Bardzo ła tw o poszło Ci zmienianie Twojego kodu,


ponieważ klasa była herm etyczna. Pamiętasz,
że hermetyzacja
jest jedną
Kiedy posiadasz klasę, która ukrywa wewnętrzne operacje przed resztą z czterech
programu i udostępnia tylko te elementy, które muszą być jawne, wtedy podstawowych
zasad
mówimy, że implementacja klasy jest hermetyczna . W Programie programowania
do zarządzania wymówkami formularz nie posiada żadnych informacji obiektowego?
o sposobie zapisu wymówek do pliku. Po prostu przekazuje jego nazwę Oto przykład,
który pokazuje,
do klasy wymówek, a ta zajmuje się całą resztą. Dzięki temu bardzo łatwo że ich stosowanie
wprowadzić wszelkie zmiany dotyczące sposobu pracy klasy z plikami. zwiększa jakość
Im lepiej ukryjesz jej implementację, tym łatwiej później będzie Ci Twoich programów.
z nią pracować.
jesteś tutaj ► 493
Rozwiązanie ćwiczenia

Zmień Program do zarządzania wymówkami Damiana tak, Musisz zmienić tylko cztery
aby używał poddanych serializacji plików binarnych wiersze w kodzie formularza: dwa
Rozwiązania z obiektami E xcuse zamiast plików tekstowych. w procedurze obsługi zdarzenia C lic k
ćwiczeń przycisku Zapisz oraz dw a w przycisku
Otwórz. Zmienią one okna dialogowe
w ten sposób, aby używ ały nowego
p r iv a t e v o id s a v e _ C lic k ( o b je c t sen de r, EventArgs e) { rozszerzenia .excuse.
/ / is t n i e ją c y kod
s a v e F 1 le D 1 a lo g l.F 1 lte r = "P l1k1 wymówek (* .e x c u s e )|* .e x c u s e |W s z y s tk 1 e p l i k i ( * . * ) |
saveF1leD 1alogl.F1leN am e d e s c r lp tlo n . T e x t + ".e x c u s e "
/ / is t n i e ją c y kod
Standardowe okna
} dialogowe Otwórz
i Z apisz jako robią
p r iv a t e v o id o p e n _ C lic k (o b je c t sen de r, EventArgs e) { tu ta j sztuczką.
/ / is t n ie j ą c y kod
o p e n F 1 le D 1 a lo g l.F 1 lte r =
"P l1k1 wymówek ( * .e x c u s e )|* .e x c u s e |W szystk1e pl1k1 (*
openF1leD 1alogl.F1leN am e = d e s c r lp tlo n . T e x t + ".e x c u s e ";
/ / is t n ie j ą c y kod
}

[ S e r ia liz a b le ] A <°to i klasa E xcu se . J edyną z mianą w formularzu


c la s s Excuse { j e s t mody fika cja rozszerzeń
p u b lic s t r in g D e s c rip tio n { g e t; s e t; plików przekazywanych do
p u b lic s t r in g R e s u lts { g e t; s e t; } Masy E xc u se .
p u b lic DateTime LastUsed { g e t; s e t;
p u b lic s t r in g ExcusePath { g e t; s e t;
Na początku pliku klasy Excuse będziesz
p u b lic Excuse() {
musiał umieścić dw ie instrukcje using: u sin g
ExcusePath =
S ystem .IO ; oraz u s in g System .R untlm e.
}
p u b lic E x c u s e (s trin g excusePath) { S e r1 a l1 z a t1 o n .F o rm a tte rs .B 1 n a ry ;.
O p e n F ile (e x c u s e P a th );
}
p u b lic Excuse(Random random, s t r in g f o ld e r ) {
s t r i n g [ ] file N am e s = D ir e c t o r y . G e t F ile s ( f o ld e r ,
O p e n F ile (file N a m e s [ra n d o m .N e x t(file N a m e s .L e n g th )]); Konstruktor wczytu jący
} losową wymówkę musi_
p r iv a t e v o id O p e n F ile ( s trin g excusePath) { teraz w yszukiw ać pliki
th is .E x c u s e P a th = excuseP ath; z rozszerzeniem .excu se
B in a ry F o rm a tte r f o r m a tte r = new B in a ry F o rm a tte r( ); zam iast plików *.tx't.
Excuse tempExcuse;
u s in g (Stream In p u t = F lle .O pe nR ead (excu seP a th )) {
tempExcuse = (E x c u s e )fo rm a tte r.D e s e r1 a l1 z e (1 n p u t);
}
D e s c rip tio n = te m p E x c u s e .D e s c rip tio n ;
R e s u lts = te m p E xcu se .R e su lts;
LastUsed = tem pExcuse.LastUsed;
}
p u b lic v o id S a v e (s trin g file N a m e ) {
B in a ry F o rm a tte r f o r m a tte r = new B in a r y F o rm a tte r() ;
u s in g (Stream o u tp u t = F Ile .O p e n W c iie iflle N a m e )) {
fo r m a tte r .S e r 1 a l1 z e ( o u tp u t,^ th T s ) ^ Przekazujem y „ th is",
}____________________________________________ _____ ponieważ chcemy,
} aby ta klasa była
serializow ana.

494 Rozdział 9.
Imię i Nazwisko: Data:

Wyprawa
Laboratorium zawiera specyfikację opisującą program, który
musisz napisać, wykorzystując wiedzę zdobytą w poprzednich
kilku rozdziałach.
Ten projekt jest większy niż te, które widziałeś do tej pory.
Przeczytaj więc uważnie wszystko, zanim przystąpisz do pisania,
i przeznacz na to chwilę czasu. Nie przejmuj się, jeżeli utkniesz
w miejscu — nie ma tutaj niczego nowego. Możesz przejść
do dalszej części książki i wrócić do laboratorium później.
Uzupełniliśmy część detali projektu za Ciebie i upewniliśmy się,
że masz wszystkie potrzebne elementy... i nic więcej.
Do Ciebie należy ukończenie pracy. Ten program można
napisać na bardzo wiele sposobów, a żaden z nich nie jest tym
„jedynie słusznym”. Gdybyś jednak potrzebował podpowiedzi,
to wiedz, że znaleźli się czytelnicy, którzy potwierdzili swoje
próżne przechwałki, publikując rozwiązanie tego laboratorium
na CodePlex, GitHub lub innych witrynach do publikacji kodu
i współpracy nad nim.

A
Laboratorium C# 495
Wyprawa

Specyfikacja: utwórz grę przygodową Można napisać tradycyjną, okienkową aplikację dla
systemu Windows, która będzie się automatycznie
T w o im zadaniem jest utw orzenie gry przygodow ej, w któ re j dostosowywać do dowolnej wielkości ekranu; jednak
potężny w o jo w n ik wyrusza na misję i dzielnie walczy, poziom wykracza to poza ramy materiału, który chcemy przekazać
za poziom em , ze śm ie rte lnie niebezpiecznym i wrogam i. w tej książce. (Dowiesz się, jak to robić przy użyciu XAML,
Utw orzysz sy stem tu ro w y . Oznacza to, że n a jp ierw ruch w następnym rozdziale, niemniej jednak nie pomoże Ci to
w ykonuje gracz, a następnie przeciw nik. G racz może w tw orzeniu aplikacji WinForms). Oznacza to, że kontrolki
przesunąć się lub zaatakować; po tem m ożliw ość ruchu
P ictureB ox, GroupBox oraz T ableL ayoutP anel używane
do prezentacji inw entarza gracza mogą dobrze wyglądać
i ataku dostaje każdy z w rogów. G ra toczy się do czasu,
w oknie Designer, lecz po uruchomieniu programu pojawić
aż gracz p o kon a wszystkich p rze ciw n ikó w na wszystkich
się w dziwnych miejscach. Wystarczy je poprzeciągać tak,
siedm iu poziom ach lu b zginie.
by wyglądały odpowiednio na Twoim ekranie.

P rzeciw nicy mają lekką przewagę — po ru szają s ię


w każdej tu rze i po każdym ruchu mogą atakować,
je ś li oczy w iśc ie gracz j e s t w ich za sięg u .
Okno gry pokazuje widok
z góry na lochy, w których G racz może na sw ojej
gracz walczy z wrogami. drodze podnosić bronie Gracz i przeciw nicy
i magiczne e liksiry. po ru szają s ię po lochach.

Gra p o c z u je liczbę punktów życia Te cztery przyciski


To jes t s p is inwentarza. gracza.. Pok.azu je , gracza i przeciwników. Gdy gracz stu żą do atakowania
jakie przedmioty p o M d s t, i w yśw ietla porusza s ię
atakuje wroga, liczba punktów przeciwników oraz
za pomocą
prostokąt otaczający obecnie używany ż y c ia tego drugiego zm niejsza s ię . p icia magicznych
elem ent ekwipunku. Gracz wybiera ie tych czterech
w razie spadku te j w artości do eliksirów . (G racz może
przedm iot za pomocą klikn ięcia. W ciśnięcie przycisków
zera przeciw nik lub gracz ginie. w ypić elik sir, klikając
przycisku ataku pow °duje j e go u ż y c ie . ruchu.
którykolwiek z nich).

496
Wyprawa

Gracz zbiera broń...


Po lochach porozrzucana jest b ro ń i magiczne eliksiry.
tryólżknoy ws tjoepdin
eyńmo brażeń u wroga, którego uderzą.
G racz może je podnosić i używać ich do w a lk i z wrogiem .
W ystarczy, że w ejdzie na broń, a ta zniknie z p o d ło g i
i po ja w i się w jego ekw ipunku.

. i przy je j użyciu atakuje wrogów


Nietoperz znajduje
się po prawej stro nie
N a każdym pozio m ie gry znajduje się bro ń, którą
gracza, w ięc w cis ka on A t ak powoduje spadek
gracz może podnieść i k tó re j może użyć do w a lki prawy p rzycisk ataku. liczby punktów życia
z przeciw nikam i. Z araz po wejściu na nią pow inna nietoperza, w tym
ona zniknąć z podłogi. przypadku z 6 do 2.

Atak

0
1 S0 M™ >

Wyższe poziomy oznaczają większą liczbę wrogów Upiór porusza s i ę


w stron ę gracza szybko
i podczas ataku zab'ie ra _
Istn ie ją trzy różne typy przeciw ników : nietoperz, duch i up ió r.
dużą liczbę punktów ż y c ia .
N a pierwszym pozio m ie jest ty lk o nietoperz. N a ostatnim , siódm ym
po zio m ie m ożna spotkać wszystkie trzy rodzaje wrogów.

N ietoperz lato
wokół czegoś
w sp o só b losowy.
Kied y znajduje
s i ę w pobliżu
gracza, powoduje Duch powoli porusza s ię
u tra tę niew ielkiej w stron ę gracza. Gdy jtuż
liczby jego znajdzie s ię w jeg o pob^żtu,
pu nktów ż y c ia . atakuje i odbiera średn ią liczbę
punktów życia.

497
Wyprawa

Projekt: utwórz formularz Użyj właściwości BackgroundImage formularza,


by wyświetlić grafikę lochu. Teraz, jeśli właściwości
F o rm u la rz nadaje grze niepow tarzalny wygląd. U żyj jego
BackgroundColor poszczególnych kontrolek przypiszesz
właściwości BackgroundIm age, aby w yśw ietlić rysunek wartość Transparent, to tło będzie pod nimi widoczne.
lochów. W celu pokazania e kw ipu nku używanego przez gracza Dodatko właściwości BackgroundImageLayout
oraz b ro n i i p rze ciw n ikó w w lochach skorzystaj z zestawu formularza przypisz wartość S tre tc h , a właściwości
k o n tro le k P ic tu re B o x . A b y widoczne były p u n k ty życia gracza, Form BorderStyle wartość FixedS ingle. Następnie
nietoperza, ducha i u p io ra oraz przyciski do poruszania się powiększ formularz na tyle, by zmieściły się w nim
i atakowania, użyj k o n tro lk i T a b le L a y o u tP a n e l. wszystkie kontrolki GroupBox oraz Buttons.

Sam loch j e s t statycznym obrazkiem


wyświetlanym w tle przy użyciu
wtaś c i wciści B ackgroundImage formularza. W łaściwościom
BackgroundCohr
kontrolek GroupB ox
W yp ra w a oraz TableLayoutpanel
przypisz wartość
Tran spa rent tak
.;V by byto widoczne
tto, na którym s ą
um ieszczone .

W
Gracz, jego wrogowie, e liksiry oraz
i
bronie s ą wyśw ietlane w osobnych Czy zw róciłeś
kontrolkach P ictu reB ox. uwagę na to, że
kontrolki P ictu reBox,
TableLayoutPanel
Punkty życia gracza. i j ego wrogow z punktami życia
s ą kontrolkami Label u m ę c z o n y m i oraz kontrolki
wewnątrz kontrolki TableLayoutPanel GroupBox
z przyciskam i
s ą um ieszczone
•w lH Jg rT - ' łjdtH ltPPftltij
□uch .jghpstH rtP ojrtsJ w dziwnych m iejscach?
To wtaśnie w te
Upiór
miejs c a m usieliśm y
j e przeciągnąć, by
L p4 aplikacja wyświetlona
• ! : -
na naszym ekranie
wyglądataa p r
prawidłowo.

f
-
□ 1 - H
!_____i
Każda z tych ikon to P ictu reB ox. y O czyw iście można
nap isa ć ten program
w taki sposób,
Każda z tych grup przycisków że będzie wyglądał
Znaki strzałek możesz znaleźć
zostata um ieszczona we
wtasnej kontrolce GroupBox.
i w programie Tablica z nak-ów
(od U+2190 do U+2193),
skopiować i w kleić do
prawidłowo na
każdym ekranie,
jednak wykracza
to poza informacje,
w ła ściw ości T ext przycisk ó w . które chcieliśm y
Ci przekazać w tej
Pobierz obraz tła oraz rysunki broni, Itsiążce. Nie martw s ię
j ednak — pokażemy Ci,
j a k to robić, podczas
przeciwników i gracza z serwera Helionu: tw orzenia aplikacji dla
S klep u Windows.
ftp ://ftp .h elio n .p l/p rzy k la d y /csh ru 3 .zip .

498
Wyprawa

W szystko, co znajduje się w lochach, je s t kontrolką PictureBox


M ożesz przypisa ć w łaściw ości
Gracze, bro nie oraz w rogow ie p o w in n i być reprezentow ani przez ikony. BackColor kontrolki Pictui-eBox w artość
D o da j dziewięć k o n tro le k P ic tu re B o x i ustaw ich właściwość V is i b le na f a ls e . Color.Transparent, by kolor lub obraz
N a dalszym etapie T w o ja gra będzie m ogła zajmować się n im i i przełączać tę tła formularza byt widoczny przez
w sz y stkie przezroczyste piksele
właściwość w zależności od potrzeb. wyświetlanego w mej obrazka.

Dodaj do lochów dziew ięć kontrolek


P ictu reB o x. Użyj w ła ściw ości
S iz e , aby u sta w ić ich rozmiar
na 3 0 x3 0 . Nie ma znaczenia, gdzie
je u m ieścisz — formularz i fak
rozm ieści j e na każdym poziomie
losowo. Użyj matej czarnej strz a W ,
która pokazuje s ię po kliknięciu
kontrolkę P ictu reB o x, aby dla każdej
z nich u sta w ić obrazek pobrany
Po dodaniu dziew ięciu kontrolek P ictu reB o x kliknij z serw era ftp Heliontu.
ikonę gracza prawym przyciskiem m yszy i wybierz
Bring t ° Frnnt. Na s tępnie zaznacz trzy ikony
z bronią i wy bierz S en d to B ack. D zięki temu Kontrolki w ID E mogą na sieb ie
uzyskamy pew ność, że gracz zaw sze będzie sta t zachodzić. Formularz musi w iedzieć,
nad przedmiotami, które podnosi. która j e s t z przodu, a która z tytu.
To wtaśnie do tego używa s ię komend
Bring to Front oraz S en d to Back
projektanta formularzy.
Inwentarz także zawiera ko n tro lki PictureBox
Możesz reprezentow ać ekw ipu ne k gracza, używając do tego p ię ciu k o n tro le k P ic tu re B o x o rozm iarze
5 0 x 5 0 . U staw ich właściwość B a c k C o lo r na C o lo r .T r a n s p a r e n t. (Jeśli ustawiasz właściwości w oknie
Properties, to wystarczy, że wpiszesz tę wartość w w ierszu B a ckC o lo r). W zw iązku z tym , że p lik i
z obrazkam i będą m ia ły przezroczyste tło , będziesz w id zia ł za n im i zwój oraz lochy.
B ę d ziesz potrzebowat
dodatkowych p ięciu kontrolek
P ictu reB o x o wymiarach
5 0 x5 0 w ekwipunku.

Gdy gracz używa którejś z broni,


formularz powinien u sta w ić w łaściw ość
B o rd e rS tyle je j ikony na FixedSingle,
a w taściwo ści pozostałych ikon na None.
Stwórz okno statystyk
P un kty życia znajdują się w ko n tro lce T a b le L a y o u tP a n e l; podo bn ie ja k przyciski
ataku i obrony. D la p u n k tó w życia u tw ó rz w panelu dw ie ko lu m n y i przesuń lin ię
pom iędzy n im i nieco w lewo. Stw órz cztery rzędy, każdy zajm ujący 25% całej
wysokości. D o każdej z ko m ó re k dodaj k o n tro lk ę Label .

— V □ --- ►O Każda komórka zaw iera


Dwie kolumny, ¡It... playerHit Points kontrolką Label, M ożesz
cztery rzędy— o s ie m Nietoperz b a tU t P o rts aktualizować te w artości
komórek dla sta ty sty k w trakcie SHJ-
Duch ghost h i^ o in ts
dotyczących punktów
Upiór ghoul Hit Points
życia. □

499
Wyprawa

Architektura: użycie obiektów To tylko °g ólny z a ry s. Pokażemy znacznie w ięcej


szczegółów dotyczących technik poruszania s ię
gracza i prze ciw ników. Zadem onstrujem y także
sposób, w ja k i przeciwnik określa, czy j e s t
Będziesz potrze bo w ał w grze k ilk u typów ob ie któ w : o b ie k tu P la y e r,
w pobliżu gracza, i tym podobne.
k ilk u po dtypó w o b ie ktu Enemy i k ilk u po dtypó w Weapon. Będziesz
także używ ał jednego o b ie ktu Game do śledzenia wszystkiego, co się
w grze dzieje.

Formularz nigdy nie operuje


bezpośrednio na obiektach
Ob iek t Game pobiera dane
gracza, wrogów oraz broni.
we jścio w e z formularza
i za jm uje s i ę obiektom

Obiekt Game
ObieN^ za rzą d za graczem ,
O biekt Game zajmuje się tura m i bronią, i lis tą
Is tn ie je tylko jedna broń na p r z e c iw n ik ó w .
każdym poziomie, więc gra
K ie d y jeden z przycisków ruch u fo rm ularza
potrzebuje jed yn ie j e j referencji,
zostaje k lik n ię ty , ten w yw ołuje m etodę M ove() a nie catej h sty . Gracz jednak
o b ie ktu Game. Pozwala ona graczowi w ykonać p °sia d a List<Weapon> do
przechowywania ekwipunku.
ruch, a następnie um o żliw ia przemieszczenie się
każdem u z przeciw ników . D o o b ie ktu Game należy
W diagramie pominęliśm y param etry.
zatem zarządzanie turow ym systemem gry. Każda metoda Move() j ako ^argumem
przyjmuj e kierunek. Niektóre z nich
O to p rzykła d sposobu działania przycisków ruchu: pobierają także obie k t R-wdom.

e(
2. p la y e r.M o v e ()

Ć lik n ifty
przycisk
T
Kiedy gracz klika jeden
ruchu z czterech przycisków M etoda M ove() obiektu
ruchu, formularz Game w p ierw sze j
wywołuje metodę M ove() kolejności wywołuje O b ie V ^
Ob\eV^
obiektu Game. analogiczną metodę gracza,
i ł« » i m i il/n n /if r-t ir ^ n

4. if (N e a rP la y e r())

q;
enem y.M ovef)
g a m e .H itP la y e r();

Po wykonaniu ruchu
przez gracza obiekt Game

ObieVN
nakazuje za pomocą
M ° y e ( ') uczynić to samo
każdemu z przeciwników.
O b te ^
Je ż e li którykolwiek z wrogów
znajdzie s ię w pobliżu gracza
po wykonaniu sw ojego ruchu,
Q * O b te ^
to p rzystą p i do ataku.

500
Wyprawa

Zarządzanie rozgrywkąjest umieszczone wobiekcie Game


Poruszanie się, atak oraz ekw ipu ne k — wszystko to zaczyna się od form ularza. K likn ię cie
przycisku ruchu, ataku lu b elem entu wyposażenia w yw ołuje w n im pewien kod. G rą i in n ym i
o b ie kta m i zarządza je d n a k ob ie kt Game. F o rm u la rz m usi w ięc przekazywać m u wszystko,
co zaszło podczas gry, on natom iast zajm uje się pozostałym i zadaniam i. Obiekt formularza wywotuj e
metodę M ove() giry, a następnie
Game.Move () wyw otuje metody metodę UpdateCharactersO , aby
M ove() przeciwników. W szystkie zaktualizować potożenie obiektow
przyjm u ją referencję random na ekranie . ^
Jak działa poruszanie się ^
^ o ^ n g - c t i o n . R ig h t- Game zajm uje s ię
aktualiz acją położenia.
Podcz a s wywoływania
Użyj typu wyliczeniowego
UpdateC haracters()

Q
Direction dla czterech obiekty przesuw ane są
przycisków kierunku. w inne m iejsce.

2. U p d a te C h a ra c te rs ();

Ta metoda U pdateC haracters() j ie s t c z ę ścią


formularz a . w cz y tu je ona położenie gracza,
Kiedy gracz uderza wroga, przeciw ników i broni, która j e s t porozrzucana
zadaje mu losowe obrażenia w lochach. Przesuw a również kontrolki
(n ie przekraczając limitu P ic tu reB ox zgodnie z tymi danymi.
maksymalnych uszk.odzrsńl

Jak działa atak Atakowanie je s t


ivtt a c k ( D ir e c t io r | . R ight^ _ podobne do poruszania
s i ę — Formularz
wywołuje metodę
A tta ck () obiektu Game,
ten natom iast zajm uje
s ię pozostałymi
czynnościam i
U p d a te C h a ra c te rs (); związanymi z atakiem.

M etoda U pdateC haracters()


s p rawdza także inwentarz gracza
i dba o prawidłowe w yśw ietlanie
ikon na zwoju z ekwipunkiem .
Z w ój inwentarza wyśw ietla
w sz y stk ie ikony powiązane
z przedmiotami, które znajdują
s ię w posiadaniu gracza.
Jak działa zwój z ekwipunkiem
r^ e .C h e c k P 1 a y e r I r v e r t o r y ( ' l u ^ , | ,

inwentarza
in v e n to ry B o w .B o rd e rS ty le =
Q bieV-t B o rd e rS ty le .F ix e d S irg le ;
O b 'te ^
Obramowania irv e rto r y S w o r d .B o rd e rS ty le =
w szystkich innych W łaściw ość B o rd erS tyle
---------------------------- B o rd e rS ty le .N o re ; zaznacza aktywny przedmiot
broni należy ukryć.
w ekwipunku gracza.

501
Wyprawa

Tworzymy klasę Game


R ozpoczęliśm y T w o ją pracę od pokazania kod u klasy Game znajdującego się poniżej.
M usisz zrobić w iele rzeczy — przeczytaj zatem dokładnie zaprezentowany kod, przejdź
do I D E i przygotuj się do w ykonania zadania. eni
B ę d ziesz potrzebował klasy Rectangle z p r z ^ t r z ^
nazw System.Drawing. Upewnij s ię , że ¿¡odałeś c ° ś
u s in g S y s t e m . D r a w in g ; ^ takiego na gorze klasy .
Możemy tu zastosow ać w ła ściw o& i
publiczne, pod warunkiem że Em m y
oraz Weapon będą herm etyczne—
c la s s Game { Ina czej mówiąc, upewnij s ię , ż.e
p u b l i c IE nu m erab le< E n em y> Enem ies { g e t ; p r i v a t e s e t ; } formularz nie robi z nimi rzeczy
niew łaściw ych.
p u b l i c Weapon W eaponInRoom { g e t ; p r i v a t e s e t ; }

Gra przechowuje prywatne pole Player. Formularz będzie s ię


y z 'nim komu nikował ty lk° za pośrednictwem metod klasy Game;
nie będzie s ię do niego odwoływał bezpośrednio.
p r iv a te P la y e r p l a y e r ;
p u b lic P o in t P la y e r L o c a t io n { get { re tu rn p la y e r .L o c a tio n ; } }
p u b lic in t P la y e r H i t P o in t s { get { re tu rn p la y e r .H it P o in ts ; } }
p u b lic IE n u m e r a b le < s tr in g > P la ye rW e a p o n s { get { re tu rn p la y e r.W e a p o n s ; } }
p r iv a te in t le v e l = 0;
p u b lic in t Level { get { re tu rn le v e l; } } Obiekt Rectangle posiada pola Top, Bottom.
L e ft oraz Right i sprawdza się doskonale
jako reprezentacja całego ° bszaru gry.
p r iv a te R e c ta n g le b o u n d a r ie s ;
p u b lic R e c ta n g le B o u n d a rie s { get { re tu rn b o u n d a r ie s ; } }

p u b lic Game( R e c ta n g le b o u n d a r ie s ) { Gra rozpoczyna s ię od ustaw ienia


t h is . b o u n d a r ie s = b o u n d a r ie s ; obsza ru dla lochów i utworzenia
nowego obiektu Player.
p l a y e r = new P l a y e r ( t h i s ,
new P o i n t ( b o u n d a r i e s . L e f t + 1 0 , b o u n d a r ie s .T o p + 7 0 );
}
p u b lic v o id M ove( D i r e c t i o n d ir e c t io n , Random random ) {
p la y e r .M o v e ( d ir e c tio n ) ;
Poruszanie s i ę j e s t p ro ste: przesuń gracza
fo r e a c h (Enemy enemy in E nem ies) w kieru nku przekazanym przez formularz,
a przeciwnika przem ieść w kierunku losowym.
e n e m y .M o v e (ra n d o m );
}
p u b lic v o id E q u ip ( s t r i n g weaponName) { To w szystko s ą
p la y e r.E q u ip (w e a p o n N a m e ) ;
------------------------- w spaniałe przykłady
herm etyzacji... Game
nie ma pojęcia, w ja k i
} sposób obiekt Player
p u b lic bool C h e c k P la y e r I n v e n t o r y ( s t r i n g weaponName) { wy konuje te czynności.
Po p rostu przekazuje
re tu rn p la y e r .W e a p o n s .C o n ta in s (w e a p o n N a m e ); wymagane informacje
i pozwala mu wykonać
}
całą re sztę .
p u b lic v o id H i t P l a y e r ( i n t maxDamage, Random random ) {
p la y e r .H it(m a x D a m a g e , ra n d o m ); _________________
}
502
Wyprawa

public void I n c r e a s e P la y e r H e a lt h ( in t health, Random random) {


player.IncreaseHealth(health, random);
M etoda Att-ackO j e s t praw ie taka sama ja k
} M °v e (). G racz po czym każdy przeciwnik
,------- .--------------------------------- m ożliwość wykonania sw ojego ruchu.

public void A t t a c k (Direction d ire c tio n , Random random) {


p la y e r.A tta ck(d ire ctio n , random);
foreach (Enemy enemy in Enemies)
enemy.Move(random); M etoda GetRandom Location() będzie pomocna
} w metodzie N ew Level(), która będzie je j _używata
do określania położenia broni i p rzeciwników-

p rivate Point G e tR a n d o m L o c a tio n (Random random) {


return new Point(boundaries.Left +
random.Next(boundaries.Right / 10 - boundaries.Left / 10) * 10,
boundaries.Top +
random.Next(boundaries.Bottom / 10 - boundaries.Top / 10) * 10);
} To po prostu matematyczny trik
słu żą cy do uzyskania losowego
położenia wewnątrz prostokąta
public void N e w L e v e l (Random random) { reprezentującego obszar lochów.
level++;
switch (level)
Dodaliśmy kod tylko dla poziomu 1.
{ Twoim zadaniem j e s t napisanie kodu
case 1: --------- dla pozostałych.
Enemies = new List<Enemy>();
Enemies.Add(new B a t(th is , GetRandomLocation(random));
WeaponInRoom = new Sword(this, GetRandomLocation(random));
break;
} w «k«iP™ w mamy 'n ’k° i ‘ d2 „ S “ . . T
} „ ¡ .K « [ ¡ . j Æ Æ S - E Ï Ï *
}
(to sarno dotyczy m ikstuiy niebieskiej).
Dokończ pozostałe poziomy
T w o im zadaniem jest dokończenie m etody
le ż e li niebieska mikstura
NewLevel(). O to dane dotyczące każdego poziom u: ■ ,, o w dal szum ciągu
z P °z|om“ ^ ekwipunku gracza,
znajduje s i ę w I a w i s ję nic.
Poziom Przeciwnicy Bronie na tym poziomie m e pojawi s ę

2 Duch Niebieska mikstura


3 U piór Łuk
4 N ietoperz, Duch Łuk, jeżeli nie zo sta ł podniesiony na poziom ie 3.;
w przeciwnym razie niebieska mikstura
To pojawi s ię
5 N ietoperz, Upiór Czerwona mikstura tylko wtedy, gdy
6 Duch, U piór Buława czerwona m ikstura
z poziomu 5. została
7 N ietoperz, Duch, U piór Buława, jeżeli nie została podniesiona na poziom ie 6.;
wykorzystana.
w przeciwnym razie czerwona m ikstura ----------- "
8 Nie d o ty c z y Nie d o ty c z y — zakończ grę p rzy użyciu Application.E xit()

503
Wyprawa

Wyszukiwanie wspólnych zachowań: poruszanie się


Już wiesz, że w ie lo k ro tn e pisanie tego samego ko d u jest złe i często m a miejsce wtedy,
gdy dwa lu b więcej o b ie któ w dzieli to samo zachowanie. D zie je się ta k także w tej
grze... Z a ró w n o gracz, ja k i przeciw nicy poruszają się.

Stwórzmy klasę Mover, aby przenieść wspólne zachowanie w jedno miejsce.


P la y e r oraz Enemy będą po niej dziedziczyły. Chociaż bronie raczej się nie poruszają, to
je dn ak także one m ają swoje położenie i muszą zostać umieszczone w którym ś miejscu
lochów; dlatego także one będą dziedziczyć p o klasie Mover. Mover posiada metodę
M ove(), któ ra służy do poruszania się, oraz właściwość tylko do odczytu L o c a tio n , Aby U ła tw ić C i a n a liz ą
której będzie używał fo rm u la rz w celu ustawienia położenia klasy pochodnej.
klas d o ! ? d° d 'a9 rarr>óvj
Mas dodaliśmy w artości
wynikowe oraz param etry.

M over j e s t kla s ą Mover i -


abstrakcyjną, Nearby() pobiera punkt
(abstrakcyjna) i określa, cz y znajduje s ię
w ięc nie można
tw orzyć żadnych Location: Point on w określonej odległości
je j instancji’. od obiektu.
Nearby(locationToCheck: Point,
distance: int): bool
Move pobiera kierunek oraz
Move(direction: Direction, aranice lochów i na ich
boundaries: Rectangle): Point podstawie oblicza punkt
końcowy przesunięcia.

Zarówno Player,
ja k i Enemy dziedziczą
po M over.

Player Enemy Weapon


(abstrakcyjna) (abstrakcyjna)
Weapons: Enumerable <Weapon>
HitPoints: int HitPoints: int PickedUp
Location
Attack(direction: Direction, random: Random)
Move(random: Random)
Hit(maxDamage: int, rando m: Random) PickUpWeapon()
Hit(maxDamage: int, DamageEnemy()
Equip(weaponName: straing)
Move(direction: Direction) random: Random)

r
Klasa Player przeciąża Przeciw nicy
metodę M ove(), wewnątrz M ożesz teraz wywotać nie potrzebują
której wywoływana j e s t Nearby() i /VloveQ na metody A tta ck (),
metoda M ove() klasy bazoiue). obiekcie Enemy oraz Player. ponieważ ich
atak zo sta ł
wbudowany
Dodaj typ wyliczeniowy D irection w metodę
M ove().
Klasa M over, podo bn ie ja k k ilk a innych klas, po trze bu je typu
w yliczeniow ego D ir e c t io n . U tw ó rz go zatem i wstaw do niego
cztery w artości: Up, Down, L e f t oraz R ig h t.

504
Wyprawa

Kod źródłowy klasy Mover


M ożesz zm ienić w artość w łaściw ości
O to ko d klasy Mover:

1 M s z j ,u „ woM . ,

l b s t ; ar“ act l a ScSorM


s tveirr t 1 M o v e In te rv a l = .0 ;
p ro te c te d P o in t lo c a t io n ;
p u b lic P o in t L o c a tio n { g e t { r e tu r n lo c a t io n ; } }
p ro te c te d Game game;

p u b lic Mover(Game game, P o in t lo c a t io n ) { In sta n cje M over pob^rają, obiekt


Game oraz aktualne położenie.
th is .g a m e = game;
t h is . lo c a t io n = lo c a t io n ;
}

p u b lic bool N e a rb y (P o in t lo c a tio n T o C h e c k , in t d is ta n c e ) {


i f (M a th .A b s (1 o c a tio n .X lo ca tio n T o C h e ck.X ) < d is ta n c e &&
(M a th .A b s (1 o c a tio n .Y lo ca tio n T o C h e ck.Y ) < d is ta n c e ) ) {
r e tu r n t r u e ;
} e ls e { M ^ d a . _Nearby z e s taw ia lokalizację punktu z aktualnym
r e tu r n f a ls e ; ° b iektu . J e że h' odiegtość m iędzy nimi j e s t m niejsza
niż d is ta n c e zwraca true. Jeżeli nie, zwraca false.
}
}
p u b lic P o in t M o v e (D ire c tio n d i r e c t io n , R e cta ng le b o u n d a rie s) {
Me t°da Move() próbuje
P o in t new Location = lo c a t io n ; wykonać przesunięcie o je d en
s w itc h ( d ir e c t io n ) { krok we wskazanym kierunku.
case D ir e c tio n .U p : J eżeli taka możliwość is tn ie je
i f (n e w L ocatio n.Y - M o v e In te rv a l >= b o u n d a rie s.T o p )
zwraca nowy punkt, natom ia st
je śli powoduje to w yjście poza
new L ocatio n.Y -= M o v e In te rv a l; granice lochów, zwraca p unkt
b re a k; początkowy.
case D ire c tio n .D o w n :
i f (n e w L ocatio n.Y + M o v e In te rv a l <= bo u n d a rie s.B o tto m )
new L ocatio n.Y += M o v e In te rv a l;
b re a k; Gdy położenie końcowe
case D ir e c t io n . L e f t : wypada poza granicami
i f (n e w L ocatio n.X - M o v e In te rv a l >= b o u n d a rie s .L e ft) ^ lochów, w tedy nowa
new L ocatio n.X -= M o v e In te rv a l; lokalizacja j e s t ta ka
sama ja k w y j ś c i a .
b re a k;
case D ir e c t io n . R ig h t :
i f (n e w L ocatio n.X + M o v e In te rv a l <= b o u n d a rie s .R ig h t)
new L ocatio n.X += M o v e In te rv a l;
b re a k;
d e f a u lt : b re a k;
}
r e tu r n ne w L ocatio n; Na końcu zwracane j e s t nowe
położenie (które w dalszym
ciągu może być takie samo
jak początkowe!).

505
Wyprawa

Klasa Player przechowuje informacje o graczu


O to początkow a wersja klasy P la y e r. W pisz ten ko d w ID E
Obiekty play e r oraz Enemy m uszą
i przygotuj się do dodania nowych elem entów. znaj do wać s ię w obrębie lochu,
co oznacza, że m uszą znać współrzędne
ograniczająp e obszar gry. S ko rzy sta j
z met° dy Conta in s() obiektu Rectangle
c la s s P la y e r : Mover { by s p rawdzić, czy znajdują s ię one
p r iv a t e Weapon equippedWeapon; w odp°wiednim obszarze.

p u b lic i n t H itP o in ts { g e t; p r iv a t e s e t; }

p r iv a t e List<Weapon> in v e n to ry = new List< W eapon>();


p u b lic IE n u m e ra b le < s trin g > Weapons {
get {
L is t< s t r in g > names = new L is t < s t r in g > ( ) ; Właściwość Weapons
zwraca kolekcję tańcuchów
fo re a ch (Weapon weapon in in v e n to ry ) znaków zaw ierających
names.Add(weapon.Name); nazwy brow.
r e tu r n names;
Player dziedziczy
} po M over, w ięc
} przekazuje
instancję Game
oraz potożenie
p u b lic Player(Game game, P o in t lo c a tio n ) do klasy bazowej.
: base(game, lo c a t io n ) {
Kons trukto r klasy Player wywołuje
H itP o in ts = 10;
konstruktor klasy bazowej, a następnie
} u s tawia liczbę punktów życia na 10.

p u b lic v o id H i t ( i n t maxDamage, Random random) { Gdy przeciw nik atakuje gracza,


H itP o in ts -= ran do m .N ext(1, maxDamage); < odbiera mu losową liczbę punktów
ży c ia . Kiedy magiczny napój tę liczbę
} z w i e s z a podnoszona j e s t ona także
o losową w artość.
p u b lic v o id In c r e a s e H e a lth ( in t h e a lth , Random random) {
H itP o in ts += ran do m .N ext(1, h e a lth ) ;
}
M etoda E q u ip () nakazuje graczom
p u b lic v o id E q u ip ( s tr in g weaponName) { wybrać jeden elem ent z ekwipunku.
Obiekt Game wywołuje ją , je ż e li
fo re a c h (Weapon weapon in in v e n to ry ) { zostanie kliknięta jedna z ikon
if (weapon.Name == weaponName) inwentarza.
equippedWeapon = weapon;
pomimo tego, że magiczne
} napoje raczej pomagają
graczowi, niż ranią przeciwnika,
}
to przez grę traktowane s ą
Obiekt typu Player może w danej chwiti jako broń. W ten sposób
używać tylko jednego obiektu Weapon. inwentarz może mieć postać
List<Weapons> i gra może
wskazyw ać taki obiekt
referencją WeaponInRoom.

506
Wyprawa

Napisz metodę Move() klasy Player


Game w yw ołuje m etodę Move() klasy Player, aby gracz w ykonał D zieje s ię tak, gdy
ruch w określonym kie ru n ku . Move() ja ko argum ent przyjm uje zostanie u żyty jeden
z przycisków ruchu.
k ie ru n e k przesunięcia (określony za pom ocą typ u wyliczeniowego
Direction , k tó ry ju ż pow inieneś m ieć dodany). T a k wygląda
początkow a postać tej m etody:

public void Move(Direction d ire c tio n ) {


base.location = Move (d ire c tio n , game.Boundaries);
if (!game.WeaponInRoom.PickedUp) { M ove znajduje s ię
w klas ie bazowej M over.
/ / Sprawdź, czy broń jest w pobliżu. Jeśli tak, podnieś ją,
}
}
Kiedy gracz podnosi broń, powinna
ona zniknąć z fochu i pojaw ić s ię
w inwentarzu.
M usisz uzu pe łnić pozostałą część m etody. Sprawdź, czy b ro ń nie znajduje się
w p o b liżu gracza (w odległości jedn ej je d n o stki od niego). Jeśli tak, podnieś ją
i
i dodaj do inwentarza.

Jeżeli b ro ń jest jedynym prze dm io tem , k tó ry posiada gracz, spraw, aby była a ¿ e »
autom atycznie w ybierana. D z ię k i tem u będzie on m ógł użyć je j natychm iast za d a n io w a H a ll P h y ln
w następnej turze.

Dodaj także metodę Attack()


Następna w kolejce jest m etoda Attack() . W yw oływ ana jest ona w m om encie
naciśnięcia jednego z przycisków ataku i ja k o argum ent rów nież przyjm uje kie ru n e k
Je żie li broń.
(znów typu w yliczeniow ego Direction ). T a k w ygląda je j sygnatura: je s t magicznym
napojem, A ttackO
public void Attack(Direction d ire c tio n , Random random) { zaraz po jego
w ypiciu usuwa go
/ / Tutaj wstaw swój kod. z inwentarza.
}
G dy gracz nie m a w ybranej żadnej b ro n i, m etoda ta nic nie zrobi.
Jeśli m a w ybraną broń, to p o w in ie n wyw ołać je j m etodę Attack() .

M agiczne e liksiry są tu ta j traktow a ne w yjątkow o. Jeżeli gracz użyje jednego z nich,


to należy usunąć go z ekw ipu nku , bo nie będzie ju ż dłużej dostępny.

M agiczne napoje implementują in terfejs IPotion


(w ię cej o nim za chw ilę), więc m ożesz użyć
stowix „ is “ , izby s prawdzić, czy obiekt Weapon
j e s t jeg o implementacją.

507
Wyprawa

Nietoperze, duchy i upiory dziedziczą po klasie Enemy


Z ade m on stru jem y jeszcze je dn ą użyteczną klasę: Enemy. Każdy rodzaj
w roga posiada swoją własną klasę po niej dziedziczącą. Poszczególni
przeciw nicy poruszają się odm iennie, w ięc klasa Enemy pozostaw ia m etodę
Move() abstrakcyjną — trzy klasy pochodne będą m usiały zaim plem entować
ją w różny sposób w zależności od sposobu poruszania się.

abstract class Enemy Mover {


private const in t N e a r P la y e r D is ta n c e = 25;

public in t H i t P o in t s { get; p rivate set; }


public bool Dead { get { -------- Formularz może używać tego pola

tylko do odczytu, aby spraw dzać,
i f (hitPoints <= 0) return true; czy przeciwnik pow inien być
else return fa ls e ; widoczny w lochach.
}
Każda klasa }
pochodna public Enemy (Game game, Point lo ca tion , in t hitPoints)
Enemy
implemen­ base(game, location) { th is .h itP o in ts = h itP o in ts ; }
tu je to Kiedy gracz atakuje
public abstract void M ove (Random random); przeciwnika, wywołuje
je go metodę H it(), która
odejm uje losową liczbę
public void H i t (in t maxDamage, Random random) { punktów życia.
HitPoints -= random.Next(1, maxDamage);
} M etoda Nearplay e r( ) używa metody
é ? ------ f a w . t o a b O . at>y o kreślić, cz y przeciwnik
znajduje s ię w pobliżu gracza.
protected bool N e a r P la y e r () {
return (Nearby(game.PlayerLocation, NearPlayerDistance));
}
protected Direction FindPlayerDirection(Point playerLocation) {
Direction directionToMove;
i f (playerLocation.X > location.X + 10)
directionToMove = Direction.R ight;
else i f (playerLocation.X < location.X - 10) J e ż eli przekażesz lokalizację
directionToMove = D ire c tio n .L e ft; gracza do metody
F indPlayerDirection(), to u żyje
else i f (playerLocation.Y < location.Y - 10) ona pola location klasy bazowej
directionToMove = Direction.Up; do określenia jego położenia
względem przeciwnika i zwróci
else wartość typu wyliczeniowego
directionToMove = Direction.Down; D irection. Na te j podstaw ie będzie
można określić kierunek, w którym
return directionToMove; p rzeciw nik będzie m usiał s ię
} poru szać, aby przem ieścić s ię
w stron ę gracza.
}

508
Wyprawa

Napisz klasy pochodne Enemy


W szystkie trzy klasy pochodne Enemy są dość proste. Każdy p rze ciw n ik posiada odm ienną
liczbę początkow ych p u n któ w życia, charakterystycznie się porusza i zadaje różne
obrażenia podczas ataku. Będziesz m usiał do każdego ko n stru kto ra klasy bazowej Enemy
przekazać różne pa ram e try startingHitPoints , a także napisać różne m etody Move()
dla każdej z klas pochodnych.

Poniżej zaprezentowano przykładow y wygląd tych klas.

class B a t : Enemy {
public B a t (Game game, Point location)
: base(game, location, 6) N ietoperz rozpoczyna g ^
posiadając s z e ś ć punktów
{ } P r ^ ^ p ^ ^ r x ie nie b ęd ziesz potrzebował życia, w ięc do konstruktora
^ y t utaj ^żadnego konstruktora; klasa bazowa klasy bazowej przekazuje 6.
zajm ie s ię w szystkim .
public override void M ove (Random random) {
/ / Tutaj umieść swój kod.
} N ietoperz porusza. s ię
} częściow o w spo3Ób
losowy. Używa w ięc
klasy Random, aby r ­
Każda z klas poch^nych przez połowę czasu
klasy bazowej E nemy przem ieszczać s ię
pośrednio dziedziczy taMżo w przypadkowym L S - » to * * » “ f“ “
po M over. kierunku. ^

N ie to p e rz rozpoczyna grę z sześcioma p u n k ta m i życia. Będzie poruszał


M usim y s ię
się w stronę gracza i atakow ał go ta k długo, d o p ó k i będzie m ia ł ic h upewnić,
je d e n lu b w ięcej. Podczas jego ruch u istnieje 50% szans na to, że będzie że formularz
sprawdza
poruszał się w stronę gracza, oraz 50% na to, że przesunie się w kie ru n ku
podczas każdej
losowym. Po w yko na niu ruch u nietop erz sprawdza, czy nie znajduje się tu ry, czy
w p o b liżu gracza — je śli tak, to atakuje go, zabierając m aksym alnie dwa przeciwnik
powinien być
p u n k ty życia. widoczny.

D u ch jest trudniejszy do po kon an ia niż nietoperz, ale podo bn ie ja k


on będzie poruszał się i atakował, d o p ó ki liczba jego p u n któ w życia
będzie większa od zera. R ozpoczyna grę, posiadając ich osiem. K ie d y się
przemieszcza, to istnieje szansa rów na 1/3, że przesunie się w kie ru n ku
gracza, oraz szansa rów na 2/3, że pozostanie w miejscu. G dy znajdzie Duch i upiór
używają
się w p o b liżu gracza, to atakuje, zadając uszkodzenia nieprzekraczające Random, aby
trzech punktów . móc poruszać
i T s ię wolniej
niż gracz.
U p ió r jest najbardziej w ytrzym ałym przeciw nikiem . R ozpoczyna grę,
m ając w posiadaniu 10 p u n któ w życia. A ta k u je i porusza się ty lk o wtedy,
gdy ich liczba jest większa niż zero. K ie d y się przemieszcza, istnieje szansa
rów na 2/3, że przesunie się w k ie ru n k u gracza, oraz szansa rów na 1/3,
że pozostanie w miejscu. G dy znajduje się w p o b liżu gracza, to atakuje,
zadając uszkodzenia o m aksym alnej w artości czterech punktów .

509
Wyprawa

Weapon dziedziczy po Mover,


każda broń dziedziczy po Weapon
P otrzebujem y klasy bazowej Weapon, ta k samo Weapon dziedziczy
ja k potrzebow aliśm y klasy Enemy. Każda b ro ń przechowuje po M over, ponieważ
w DamageEnemy()
swoje położenie. Posiada także właściwość, k tó ra określa,
używa metod
czy jest ona podniesiona. T a k wygląda klasa bazowa Weapon: Nearby() oraz M ove().
y
a b s tra c t c la s s Weapon : Mover
Broń podniesiona nie
O ś w ie tla n a ... Formularz
p u b lic bool PickedUp { g e t; p r iv a t e s e t; } tego akcesora g e t do sprawdzenia,
czy gracz podnióst bron.

p u b lic Weapon(Game game, P o in t lo c a t io n ) {

: lo c a t io n ) Konstruktor wywołuje konstruktor klasy


{ bazowej M over (określający w artości pól
PickedUp = f a ls e ; game oraz Location), a ¡następnie ustaw ia
. wart° ś ć w ła ściw ości PickedUp na false
} (9dy ż broń nie została je s z c z e podniesiona).

p u b lic v o id P1ckUpWeapon() { PickedUp = t r u e ; }

p u b lic a b s tra c t s t r in g Name { g e t; } ^ —

Każda
w łaściw ość p u b lic a b s tra c t v o id A t ta c k ( D ir e c tio n d ir e c t io n , Random random);
Name broni
zwraca Każda broń p o sa d a
je j nazwę p ro te c te d bool DamageEnemy(Direction d i r e c t io n , i n t ra d iu s , określony zas i ęg
(„M iecz“ , i n t damage, Random random) { i sposób ataku, więc
„Buława“ , różnie implementu je
P o in t ta r g e t = g a m e .P la y e rL o c a tio n ;
„Łuk“). metodę A tta ck ().
f o r ( i n t d is ta n c e = 0; d is ta n c e < r a d iu s ; d is ta n c e + + ) {
fo re a ch (Enemy enemy in game.Enemies) {
if (N e a rb y(e n e m y.L o ca tio n , t a r g e t , d is ta n c e ) ) {
enem y.H it(dam age, random);
r e tu r n t r u e ;
} M etoda DamageEnemy() wywoływana
} j e s t przez A tta ck (). Najpierw
próbu je odszukać przeciwnika we
t a r g e t = M o v e (d ire c tio n , t a r g e t , gam e.Boundaries) wskazanym kierunku i w określonej
} odległo ści. Je ż e li znajdzie, wywołuje
r e tu r n f a ls e ; je go metodę H it() i zwraca true.
J e ś li nie, zwraca fa lse.
}

Metoda N earby() klasy M over() pobiera jedynie dwa parametry — obiekt P o in t oraz liczbę i n t
— i porównuje obiekt z bieżącym położeniem, zwracając wartość true, jeśli punkt znajduje się blisko
niego. Ze względu na obliczenia wykonywane przez metodę DamageEnemy() będziesz musiał dodać
przeciążoną wersję metody N e a rb y (), która pozwoli porównać dwa punkty i zwróci wartość tru e ,
jeśli będą one położone w określonej odległości od siebie. Będziesz także musiał przeciążyć metodę
Move(), tak by pozwalała przesunąć punkt w określonym kierunku i zwracała nowy obiekt P oint.
Ciekawe, czy uda Ci się wymyślić, jak należy zmodyfikować przedstawione wcześniej metody
N earby() i M ove(), by przeciążone metody nie powielały żadnego kodu.

510
Wyprawa

Różne bronie zadają obrażenia wróżny sposób


Każda klasa pochodna Weapon posiada swoją własną nazwę oraz sposób zadawania obrażeń.
T w o im zadaniem jest zaim plem entow anie tych klas. Poniżej zaprezentowano podstaw owy ich szkielet.

Klasy pochodne reprezentu ją trzy


c la s s Sword : Weapon {
rodzaje broni: m iecz, łuk oraz btufawę.

p u b lic Sword(Game game, P o in t lo c a tio n )


Każda,_klasa pochodna polega podczas
: base (game, lo c a t io n ) { } t^ncja/izacjina kla sie bazowej.

p u b lic o v e rrid e s t r in g Name { g e t { r e tu r n "M ie c z "; } }


^ ______ „ ja k ą ma nazwą.

p u b lic o v e rrid e v o id A t ta c k ( D ir e c tio n d ir e c t io n , Random random) {


// T u ta j wstaw sw ó j kod.

} Obiekt Game będzie


Gracz może wielokrotnie przekazywdt kierunek ataku.
używ ać tego samego oręża
— nigdy nie zostanie on
u pu szczony ani zu żyty.

M ie cz jest pierwszą bronią, ja ką podniesie gracz. D ysponuje on dużym


zasięgiem. Podczas w ykonyw ania ruch u pró b u je znaleźć przeciw nika
w k ie ru n k u ataku — je żeli go tam nie ma, poszukuje go w kie ru n ku
zgodnym z ruchem wskazówek zegara. Jeśli i tam nie m a żadnego wroga, Pomyśl o tym
spokojnie.
w yszukiwany jest on w k ie ru n k u przeciw nym , poczynając od pozycji Co j e s t
wyjściow ej. Zasięg m iecza wynosi 10. Pow oduje on uszkodzenia równe na prawo
trzem p u n kto m życia. od lewego
kierunku?
Co j e s t
na lewo
Ł u k charakteryzuje się wąskim zakresem ataku, choć posiada duży od góry?
BOW j S
Name / zasięg — wynosi on 30, ale uszkodzenia zadawane p rze ciw n iko m to tylko
jeden p u n kt. W przeciw ieństw ie do użycia miecza, k tó ry pozwala na atak
A tta c k (T ^ w trzech kie run kach (ponieważ gracz bierze solidny zam ach), wystrzelenie
z łu k u w daną stronę pow oduje posłanie strzały ty lk o i wyłącznie tam.

B uław a jest najpotężniejszą b ro n ią w lochach. N ie m a znaczenia kie run ek,


poniew aż obraca się ona w pełnym zakresie. A ta k u je każdego przeciw nika
w p ro m ie n iu rów nym 20 i zadaje uszkodzenia sięgające sześciu punktów .

? Poszczególne typy broni będą wy wotywały Damage£nemyO


w różny sposób. Buław a pozwala na atak w każdym
kierunku. Je ż e li gracz atakuje w praw ą stronę, wy w ° łu je
Dam ageEnem y(Direction.Right, 2 0 , 6, random). J e ś li n\e tuderzg
przeciw nika w ten sposób, wykonywany j e s t atak w górę. Gdy
i tam go nie ma, przeszukiw any j e s t kierunek lewy, a potem dót
— w ten sposób wykonany zo sta je pełny obrót.

511
Wyprawa

Mikstury magiczne implementują


interfejs IPotion
Istn ie ją dwa rodzaje m ik s tu r magicznych: niebieska i czerwona. O bie zwiększają
liczbę p u n któ w życia gracza. Reprezentow ane są ta k samo ja k broń — można
je znaleźć w lochach i podnieść, wybrać je poprzez klik n ię c ie w inw entarzu oraz IPotion
użyć ic h , k lik a ją c je d e n z p rz y c is k ó w a ta k u . Jest to poważna przesłanka, aby (interfejs)

dziedziczyły one po klasie Weapon. Used

M ik s tu ry działają je d n a k nieco od m iennie, będziesz w ięc m usiał utw orzyć


in te rfe js IP o t io n w celu dodania specjalnej fu n k c ji: zwiększania liczby p u nktów
życia gracza. In te rfe js ten jest naprawdę prosty. M ik s tu ry magiczne muszą jedynie A
dodawać właściwość ty lk o do odczytu o nazwie Used, k tó ra zwraca f a ls e , jeśli
gracz jeszcze m ikstu ry nie użył, i t r u e , je że li z niej skorzystał. F o rm u la rz będzie
je j używ ał do określania, czy wyśw ietlać od po w ie dn ią iko nę w ekw ipunku.
T
RedPotion BluePotion

N am e N am e
p u b lic in te r fa c e IP o tio n {
bool Used { g e t; } A tt a c k () A tt a c k ()

}
M agiczne m ikstury d z i c z ą po klapoeóh e a p
ponieważ s ą używane w U h sam sposob j ak
I ^ t b n pozwala użyć
magicznej m ikstury pronie - gracz klika ich ik°n y w ekw ipunku>
ty lko raz. Z a pomocą aby j e wybrać, a następnie* korzysta z jedn ego
„ if (weapon is IP o tio n T z przycisków ataku, aby ich tażyć.
można także określić, Pow inieneś być w stanie
czy obie k t Weapon je s t napisać te Masy, używając
miks t u rą. Zawdzięczam y d iagramu kla s i poniższych
to interfejso w i. informacji.

W łaściwość Name klasy B lu e P o tio n po w in na zwracać łańcuch


znaków „N iebieska m ikstu ra ” . Jej m etoda A t t a c k ( ) będzie
wyw oływ ana w m om encie użycia niebieskiej m ikstu ry i będzie
zwiększała liczbę p u n któ w życia gracza m aksym alnie o pięć
je dn ostek poprzez w yw ołanie m etody In c r e a s e P la y e r H e a lth ( ) .
Po w ykorzystaniu magicznej m ikstu ry właściwość Used pow inna

tru e .

¡„stancje BluePotion.

Klasa R e dP o tion jest bardzo podobna z w yjątkiem tego, że jej


RedPotion
właściwość Name zwraca łańcuch znaków „C zerw ona m ikstu ra ” ,
Nam e
a m etoda A t t a c k ( ) zwiększa liczbę p u n któ w życia gracza o 10.

A tt a c k ()

512
Wyprawa

Formularz wszystko łączy


Użycie Rectangle
Istn ie je je dn a instancja klasy Game i jest ona przechowywana
w pryw atnym p o lu form ularza. T w orzo na jest w jego zdarzeniu Load, Znajdziesz wiele pro sto kątów podczas
a różne inne fun kcje obsługi zdarzeń używają p ó l i m etod o b ie ktu Game, pracy z forrmdarzami. Możesz u tw o rzyć
aby można było toczyć rozgrywkę. instancję Rectang|e, przekazując w artośd

W szystko zaczyna się od obsługi zdarzenia Load, k tó re przekazuje X, Y, szer° k°5ć oraz wysokość lub dwa
o b ie kto w i Game p ro sto ką t określający obszar lochów. T a k w ygląda kod, punkty (w przeciwlegjych rogach). Po jej
k tó ry p o zw o li C i rozpocząć pracę: utworzeniu możesz uzyskać dostęp do
jej składowy ch Le ft, Right, Top i B o tto m ,
p rivate Game game;
a także wartości X , Y, W id th i Height.
p rivate Random random = new Random();
p rivate void Form1_Load(object sender,
EventArgs e) {
game = new Game(new Rectangle(78, 57, 420, 155));
game.NewLevel(random);
To s ą wymiary rysunku lochów umieszczonego w tle, który
UpdateCharacters(); pobrn^ ś z ftp i w sta w iłeś do formularza. B y ć może będziesz
m usiat trochę poeksperymentować, aby znaleźć w artości
} odpowiadające położeniu lochu w Twojej ap likacji.
__ i
iloi
Pam iętaj o podwójnym kliknięciu

V All hazdej z kontrolek P ictu reB ox.


w ten sposób ID E doda dla nich
oddzielne funkcje obsługi zdarzeń.

F o rm u la rz posiada oddzielną funkcję obsługi zdarzeń dla każdego klik n ię c ia k o n tro lk i Pi ctureBox.
G dy u żytko w n ik k lik a iko nę miecza, gra w pierwszej kolejności sprawdza, czy rzeczywiście znajduje się on
w ekw ipu nku , używając m etody CheckPlayerInventory() o b ie ktu Game. Jeżeli gracz przechow uje tę broń,
fo rm u la rz w yw ołuje game.Equip(), aby ją wybrać. Następnie ustawia właściwość BorderStyle każdej
k o n tro lk i, aby narysować obw ódkę przy m ieczu i zadbać o to, by przy innych ikonach ra m ki były niewidoczne.

Istn ie ją także fun kcje obsługi zdarzeń dla każdego z czterech przycisków ruchu.
Są one naprawdę proste. Każda z nich w pierwszej kolejności w yw ołuje m etodę
game.Move() z właściwą w artością Direction , a następnie m etodę form ularza
UpdateCharacters().
Upewnij s ię , że zm ien iłeś z powrotem
widoczność i nazwy przycisków
po wybraniu przez gracza miecza,
łuku lub buławy.

C ztery fun kcje obsługi zdarzeń dla przycisków ataku także są proste.
K ażdy w yw ołuje game.Attack() , a następnie m etodę UpdateCharacters()
form ularza . Jeżeli gracz w yb ra ł m agiczną m iksturę , to m etoda jest wywoływana
w ten sam sposób — poprzez game.Attack() — choć ta b ro ń nie m a kie run ku.
Spraw zatem, aby przyciski L e w o , Praw o i D ó ł w m om encie trzym ania magicznej
m ikstu ry były niewidoczne, a napis na przycisku Góra zam ieniał się na Wypij.

513
Wyprawa

Metoda UpdateCharacters() formularza przesuwa


kontrolki PictureBox na właściwe pozycje
O statnim elem entem tej układ anki jest m etoda U p d a te C h a ra c te rs (). Po w ykonaniu ruchu
i akcji przez wszystkie obiekty należy zaktualizować f o r m u la r z . więc ko n tro lka PictureBox
broni, któ ra została podniesiona, pow inna m ieć właściwość V is i b le ustawioną na f a ls e ,
gracz i jego przeciwnicy p o w in n i być w idoczni w odpow iednich miejscach (zabity w róg
pow inien być niewidoczny), a inw entarz pow inien zostać zaktualizowany.

O to czego potrzebujesz:

ZAKTUALIZUJ POZYCJĘ GRACZA I JEGO STATYSTYKI.


&
Pierwszą rzeczą, ja ką musisz zrobić, jest zaktualizow anie położenia k o n tro lk i P ic tu re B o x
gracza i po la tekstowego, k tó re pokazuje liczbę jego p u n któ w życia. Będziesz także
po trze bo w ał k ilk u zm iennych, aby sprawdzić, czy pokazałeś każdego z przeciw ników .

p u b lic v o id U p d a te C h a ra cte rs() {


P la y e r.L o c a tio n = g a m e .P la y e rL o c a tio n ;
p la y e r H itP o in ts .T e x t =
g a m e .P la y e r H itP o in ts .T o S tr in g ( );

bool showBat = f a ls e ; Je ż e li kontrolka P ic tureB ox nietoperza


będzie widoczna, to do zmiennej show Bat
bool showGhost = f a ls e ; wstawim y tru e. To samo dotyczy
bool showGhoul = f a ls e ; show Ghost i show Gh°u l.
i n t enemiesShown = 0;
// T u ta j z n a jd u je s i ę p o z o s ta ła c z ę ś ć kodu.

ZAKTUALIZUJ POZYCJĘ KAŻDEGO Z PRZECIWNIKÓW


I JEGO PUNKTY ŻYCIA.
K ażdy p rze ciw n ik p o w in ie n znaleźć się w nowym po ło że n iu i posiadać właściwą liczbę
p u n któ w życia. M usisz zaktualizow ać ich dane po ustaw ieniu pozycji gracza:

fo re a c h (Enemy enemy in game.Enemies) { To znajdzie s ię zaraz


za kodem zaprezentowanym
i f (enemy is B a t) {
powyżej.
b a t.L o c a tio n = e n em y.Lo catio n;
b a tH itP o in ts .T e x t = e n e m y .H itP o in ts .T o S trin g () Ten fragment
będzie miał wpływ
if (e n e m y .H itP o in ts > 0) { na widoczność
showBat = t r u e ; kontrolek P ictu reBox,
które reprezentują
enemiesShown++;
przeciwników.

B ę d z ie sz potrzebował je s z c z e dwóch tego typu


}
ins tru kcji w pętli foreach — jed n ej dla ducha
// I ta k d a le j... i jed n ej dla upiora.
}

Po w yko na niu się p ę tli przeglądającej wszystkich prze ciw n ikó w na danym p o zio m ie sprawdź
zm ienną showBat. Jeśli n ietop erz został zabity, showBat w dalszym ciągu będzie rów ne f a ls e .
W ta kim przyp ad ku powiązana z n im k o n tro lk a P ic tu re B o x po w in na być niewidoczna, a pole
tekstow e z liczbą p u n któ w życia wyczyszczone. T o samo zrób z showGhost oraz showG houl.

514
Wyprawa

ZAKTUALIZUJ KONTROLKI PICTUREBOX DLA BRONI.


Z a d e kla ru j zm ienną w e a p o n C o n tro l i użyj długiej in s tru k c ji s w itc h , aby ustawić je j wartość
na k o n tro lk ę P ic tu re B o x powiązaną z b ro n ią znajdującą się w pom ieszczeniu.

s w o rd .V is ib le = f a ls e ; Upewnij s ię , że nazwy
pokrywają s ię z tymi nanuam'.
b o w .V is ib le = f a ls e ; Bardzo łatwo d o p r^ a d z ’^ do
r e d P o tio n .V is ib le = f a ls e ; trudnych do u su n ięcia btędow,
b lu e P o tio n .V is ib le = f a ls e ; j e ś li nie s ą one zgodne.
m a c e .V is ib le = f a ls e ;
C o n tro l weaponControl = n u l l ;
s w itc h (game.WeaponInRoom.Name) { B ę d z ie sz m iał więcej
case "M ie c z ": < -------------------- przypadków dla każdego
weaponControl = sword; b re a k; typu broni.

Pozostałe przyp ad ki po w in ny ustawiać zm ienną w e a p o n C o n tro l na od po w ie dn ią k o n tro lk ę form ularza.


Po in s tru k c ji s w itc h ustaw w e a p o n C o n tr o l.V is ib le na t r u e , aby w yśw ietlić w ybraną ikonę.

USTAW WŁASCIWOSC VISIBLE KAŻDEJ KONTROLKI PICTUREBOX INWENTARZA.


Skorzystaj z m etody C h e c k P la y e r In v e n to r y ( ) o b ie ktu Game, by określić, k tó re z ik o n prze d m io tó w m ają
być wyświetlane.

TUTAJ ZNAJDUJE SIĘ POZOSTAŁA CZĘŚĆ METODY.


Pozostały fragm e nt m etody rea lizu je trzy zadania. W pierwszej kolejności sprawdza, czy u żytko w n ik
p o dn iósł broń znajdującą się w pom ieszczeniu, i na tej podstaw ie określa, czy ma ona być wyświetlana,
czy nie. N astępnie sprawdza, czy gracz nie został zabity. W końcu m etoda upew nia się, czy gracz po kon ał
wszystkich przeciw ników . Jeśli faktycznie wszystkie p o tw o ry zostały zabite, może on przejść na następny
poziom .

w e a p o n C o n tro l.L o c a tio n = game.WeaponInRoom.Location;


if (game.WeaponInRoom.PickedUp) { Każdy poziom posiada
w e a p o n C o n tro l.V is ib le = f a ls e ; jed n ą broń. J e żeli z o stata
podniesiona, to powinniśmy
} e ls e {
uczynić je j ikonę n U rn d ^ zn ^
w e a p o n C o n tro l.V is ib le = t r u e ;
}
if (g a m e .P la y e rH itP o in ts <= 0) {
M essageBox.Show("ZostaJeś z a b it y " A p p lica tio n .E xit() natychm iast w y c h ^ _z pmgramtu.
A p p li c a t io n . E x it ( ) ; J e s t cz ę ś c ią System .W indow s.Form s, \uięc t>ędziesz
potrzebował w ła ściw ej in stru kcji u s ing, j e ś 0
} zech cesz użyć je j poza klasą formularza.
if (enemiesShown < 1) {
MessageBox.Show("PokonaJeś p rze ciw n ikó w na tym p o z io m ie " );
gam e.NewLevel(random );
J e ż e li na danym poziomie nie ma już
Upd a te C h a ra c te r s (); p rzeciw ników ; oznacza to, że gracz
} ws z y s t kich pokonał i można p rze jść
do ¡następnego etapu .

515
Wyprawa

Zabawa dopiero się zaczyna!


Siedem poziom ów , trzech p rze ciw n ikó w ... to dość przyzw oita gra. Możesz ją je d n a k uczynić znacznie lepszą.
O to k ilk a pom ysłów na początek:

Spraw, aby przeciwnicy b yli sprytniejsi


Czy wiesz, w jaki sposób zmodyfikować metodę M ove() przeciwników, aby byli oni trudniejsi do pokonania?
Sprawdź, czy potrafisz zamienić ich stałe na właściwości i umożliwić ich zmianę w trakcie gry

Dodaj więcej poziomów


Gra wcale nie musi się kończyć na siedmiu poziomach. Sprawdź, czy jesteś w stanie dodać kolejne.
Czy wiesz, co zrobić, aby rozgrywka mogła toczyć się bez końca? Jeżeli gracz zwycięży, wstaw fajną
animację końcową z tańczącymi nietoperzami i duchami! Poza tym gra kończy się dość nagle po zabiciu gracza.
Czy możesz wymyślić zakończenie bardziej przyjazne dla użytkownika? Może lepiej pozwolić mu ponownie rozpocząć
grę lub powtórzyć ostatni poziom?

Dodaj różne rodzaje przeciwników


Wcale nie musisz ograniczać zagrożenia do upiorów, duchów i nietoperzy. Sprawdź, czy potrafisz dodać
do gry większą liczbę przeciwników.

Dodaj więcej broni


Gracz zdecydowanie będzie potrzebował większej pomocy podczas walki z nowymi przeciwnikami, których dodasz.
Pomyśl nad nowymi sposobami wykorzystania broni do ataku lub różnymi możliwościami, które dają magiczne
mikstury. Skorzystaj z faktu, że Weapon jest klasą pochodną Movei— stwórz magiczne bronie, które gracz będzie
musiał gonić po pomieszczeniu!

Dodaj większą ilość g ra fik i


Możesz zajrzeć na ftp://ftp.helion.pl/przyklady/cshru3.zip, aby znaleźć więcej plików graficznych reprezentujących
przeciwników i broń oraz inne obrazki, które rozpalą Twoją wyobraźnię.

Zmień program w grę akcji


To naprawdę interesujące wyzwanie. Czy potrafisz wymyślić, jak skorzystać ze zdarzeń KeyDown oraz Timer, których
używałeś w grze w literki zaprezentowanej w rozdziale 4., by zmienić naszą grę turową w grę akcji?

To jest Twoja szansa na pokazanie prawdziwych umiejętności!


Czy stworzyłeś nową, lepszą wersję gry? Dołącz do forum
oryginalnego wydania Head First C# i pochwal się swoimi
osiągnięciami: w w w .h e a d fir s tla b s .c o m /b o o k s /h fC s h a r p /.
516
10. Projektowanie aplikacji dla Sklepu Windows z użyciem M L

Jesteś już gotów, by wkroczyć do zupełnie nowego świata tworzenia aplikacji.


Korzystanie z technologii WinForms do tworzenia klasycznych aplikacji dla systemu Windows jest
doskonałym sposobem nauki ważnych rozwiązań języka C#, niemniej jednak możesz pójść znacznie
dalej. W tym rozdziale dowiesz się, jak używać języka XAM L do projektowania aplikacji przeznaczonych
dla Sklepu Windows, nauczysz się tworzyć aplikacje działające na d o w o lnych urządzeniach,
integrow ać dane ze stronami przy użyciu w ią za n ia danych i używać Visual Studio do ujawniania
tajemnic stron XAML poprzez badanie obiektów tworzonych na podstawie kodu XAML.

to jest nowy rozdział ► 517


W końcu nowoczesny wygląd

Damian używa Windows 8


S tarom odne aplikacje D a m ian a dla systemu W indow s w yglądają archaicznie! D am ian
ma ju ż dosyć ciągłego k lik a n ia m a lu tkich p ó l w yboru. A chciałby, żeby jego program do
zarządzania w ym ów kam i był aplikacją z prawdziwego zdarzenia. Czy m ożem y m u w tym
pom óc?

Program do zarządzania wymówkami


Damiana działa, jednak p rz e starzały
klasyczny program dla s y stem u
Windows nie j e s t żadną konkurencją
dla praw dziw ej, fantastycznej aplikacji,
w stu procentach przeznaczonej dla
Sklepu Windows.

518 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

C hcesz szybko wttoczyć te pomy sty


là kulisami
do sw o jej głowy? Zatem z ń b
opisane poniżej rzeczy, zanim
zaczn iesz czytać ten rozdziat!

Aplikacje przeznaczone dla Sklepu W ind ow s są bardziej skom plikow ane niż program y WinForms. Dlatego też
program y W inForms są napraw dę e fe ktyw n ym narzędziem do nauki, jednak nie radzą sobie ró w nie dobrze,
gdy chodzi o napisanie niezw ykle o d lo to w e j aplikacji. Czasami w a rto cofnąć się o krok i pomyśleć o tym ,
jak się uczymy, gdyż to pomoże nam robić to bardziej efe ktyw nie . A zatem spróbujm y to zrobić teraz.

Stw orzyłeś już sobie doskonałe podstaw y, zdobyw ając po dstaw ow ą w iedzę o języku C#, obiektach, kolekcjach
i innych narzędziach .NET. Teraz jednak w ró cim y do tw o rz e n ia aplikacji dla Sklepu W ind ow s przy w yko rzysta niu
języka XAML. Na kilku następnych stronach spróbujem y skorzystać z m ożliw ości IDE do badania o b ie któ w , które
tw o rz ą i któ rym i zarządzają program y WinForms. Kiedy później użyjesz IDE do badania o b ie k tó w tw orzo nych
w aplikacjach dla Sklepu W ind ow s pisanych przy użyciu języka XAM L, staraj się w y c h w ytyw a ć różnice
— oraz, co jest ró w n ie w ażne, podobieństw a pom iędzy nimi.

W y k o n a j p o n iż s z e c z y n n o ś c i, z a n im z a c z n ie s z d a ls z ą le k tu r ę te g o ro z d z ia łu

Cały rozd zia ł 1. i znaczną część rozd zia łu 2. przeznaczyliśm y na przedstaw ienie tw orzenia a p lika cji przeznaczonych
dla Sklepu W indow s przy w ykorzystaniu języka X A M L oraz p la tfo rm y .N E T F ra m e w o rk fo r W indow s Store.
T eraz wykorzystam y wiedzę zdobytą w tam tych dwóch rozdziałach. Jeśli chcesz w m ożliw ie ja k największym stopniu
skorzystać z le k tu ry tego rozdziału, sugerujemy, żebyś w yko na ł k ilk a przedstaw ionych poniżej czynności.

★ D la nie któ rych przesiadka z W in F o rm s na stosowanie języka X A M L jest całkow icie bezproblem ow a,
innych natom iast może drażnić. Te czyn n o ści przygotow aw cze po m og ą C i szybciej p rzysw o ić sobie
n a jw a żn ie jsze idee.

★ W ró ć do rozd zia łu 1. i ponow nie, od samego początku stwórz aplikację R a tu j ludzi. T ym razem up ew nij się,
że wpisałeś ręcznie cały kod.

★ I nie zapom nij przea n a lizo w a ć k o d u a p lika cji ! W ciąż znajdują się w n im fragm enty, k tó ry m i jeszcze nie
zajm ow aliśm y się szczegółowo, choć pow inieneś ju ż rozpoznawać je na tyle dobrze, byś m ó g ł rozpocząć
tw orzenie m entalnych podstaw.

★ Spróbuj dobrze zrozum ieć ta jn ik i działania gry. N ie staraj się je d n a k ro b ić tego za wszelką cenę. Jak ju ż
w spom inaliśm y — wciąż pozostało sporo zagadnień, k tó ry m i w tej książce jeszcze nie zajm ow aliśm y się.

★ Z w ró ć szczególną uwagę na zm iany w prowadzane w szablonie B a s ic Page oraz na sposób, w ja k i


zastąpiliśm y n im dom yślną stronę M ainPage.xam l — w tym rozdziale będziesz bow iem to ro b ił k ilk a razy.

★ Jeszcze ra z w y k o n a j p ro je k t X A M L z rozd zia łu 2. N ie zapom nij zrob ić także ćwiczenia. T eraz pow inieneś
być gotowy!

W tej książce nie p rze dstaw iliśm y w szystkich m ożliw ości aplikacji WinForms. W rzeczyw istości korzystają one
z silnika graficznego określanego jako GDI+, potrafiącego generować zaskakująco do bry in te rfe js graficzny
i m a te ria ły do druku oraz doskonale obsługiw ać interakcję z u żytko w n ikie m (choć w ym aga ona znacznie
w iększego nakładu pracy niż w przypadku korzystania z języka XAML). Jednym z najważniejszych sposobów nauki
po dstaw ow ych zasad program ow ania jest przeanalizow anie te j samej rzeczy w ykonanej na dw a różne sposoby.

jesteś tutaj ► 519


Potrafisz je znaleźć?

Od czasu pierwszego projektu, Ratuj ludzi, przedstawionego w rozdziale 1., poznałeś już całkiem
sporo ważnych pojęć związanych z językiem C# i nabyłeś dużo praktyki w posługiwaniu się nimi.
Teraz nadszedł czas, by założyć kapelusz, wziąć lupę i przetestować swoje detektywistyczne
umiejętności. Sprawdź, czy będziesz w stanie odszukać wszystkie elementy C# w kodzie
aplikacji Ratuj ludzi. Aby ułatwić Ci początki poszukiwań, podaliśmy jedną z odpowiedzi.
Czy jesteś w stanie znaleźć pozostałe?

(Na niektóre z pytań jest więcej niż jedna prawidłowa odpowiedź).


D e te k ty w is ty c z n e
p o sz u k iw a n ia !
□ Z a s to s o w a n ie ła ń c u c h a z n a k ó w ja k o k lu c z a p o d cza s p o b ie ra n ia
o b ie k tu z k o le k c ji D i c t i o n a r y .

□ Z a s to s o w a n ie in ic ja liz a t o r a .

□ D o d a n ie d o te j s a m e j k o le k c ji o b ie k tó w d w ó c h ró ż n y c h ty p ó w .

□ W y w o ła n ie m e to d y s ta ty c z n e j.

520 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

□ Z a s to s o w a n ie m e to d y p ro c e d u ry o b s łu g i z d a rz e ń .

□ U ż y c ie s ło w a k lu c z o w e g o as w c e lu r z u to w a n ia o b ie k tu w d ó ł.

□ P rz e k a z a n ie d o m e to d y r e fe r e n c ji d o o b ie k tu .

^ U tw o rz e n ie in s ta n c ji o b ie k tu i w y w o ła n ie je d n e j z je g o m e to d .

W ..metodzde. mę!t:<?jE.in.^.:niS)i^). . tw o rzo n y ...¡<^>it. .o b iekt. k la s y ..................

storyB°.?.rd,..a..n a stę p n ie . . .wyiii?Jyw.i’ni’.j e s t .jego...m etoda...................


B egin().

□ U ż y c ie ty p u w y lic z e n io w e g o w c e lu p r z y p is a n ia w a rto ś c i.

jesteś tutaj ► 521


Rozwiązanie detektywistycznych poszukiwań

Od czasu pierwszego projektu, Ratuj ludzi, przedstawionego w rozdziale 1., poznałeś już całkiem
sporo ważnych pojęć związanych z językiem C# i nabyłeś dużo praktyki w posługiwaniu się nimi.
Teraz nadszedł czas, by założyć kapelusz, wziąć lupę i przetestować swoje detektywistyczne
umiejętności. Sprawdź, czy będziesz w stanie odszukać wszystkie elementy C# w kodzie
aplikacji Ratuj ludzi. Aby ułatwić Ci początki poszukiwań, podaliśmy jedną z odpowiedzi.
Czy jesteś w stanie znaleźć pozostałe?
(Na niektóre z pytań jest więcej niż jedna prawidłowa odpowiedź).

D
k U . ł. poniże\ zam ieściliśm y te , które
e te k ty w is ty c z n e ^ m udało s ię znaleźć; Ty
mogłeś znaleźć zu pełnie inne!
p o sz u k iw a n ia ! ¡j
E Z a s to s o w a n ie ła ń c u c h a z n a k ó w ja k o k lu c z a p o d cza s p o b ie ra n ia
R o z w ią z a n ie o b ie k tu z k o le k c ji D i c t i o n a r y .

.....W .drugim. .w ie rs zu . .m etody. .fi.ddEne.my(.). .używany. ..ii^s.t. .tańęu.ch. ....


znaków „Enem yTem pJate", przy. u ży c iu którego z e słow nika

R e so u rc e s pobt'erany je s t' o b ie k t C o n tro T em p la te.

Z a s to s o w a n ie in ic ja liz a t o r a .

W . tm io d z te . A m m a .te E m m y d .podczas. .¡n.i.cj.a||zQc.j¡. .o tie M u .................

Doublefinim.a.tion. .o.k.redi.an.e. . s ą ..wartości. .trzech.. .............

.....fro m ..To. . o ra z .Du.ration.... . ..

A D o d a n ie d o te j s a m e j k o le k c ji o b ie k tó w d w ó c h ró ż n y c h ty p ó w .

...W .!m ?t°dzi?. .Sta.rtGa.met).. do...kolekcji. .pla yA rea C h ild ren . ....
doda wa ny j e s t ob e k t S ta c kPa ne. (czto w ie k) oraz ob ie kt

tang le (docelow y porta l).

^ W y w o ła n ie m e to d y s ta ty c z n e j.

. .. .W. .procedurze. .o b słu g i .zd a r z e ń ..ta.rget_Po. i.nte.rEnter.edQ. ... .


w yw oływ ane s ą d w ie s ta ty c zn e m eto d y klasy C a n va s:

S e tL e ft( ) o ra z S etT o p ().

522 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Z a s to s o w a n ie m e to d y p ro c e d u ry o b s łu g i z d a rz e ń .

W . oknie. .Psope.rt.ies . zo s ta ta . .określona . p ro ced u ra . o)?.$.tMgj........

zd a rzeń ..P ointerE ressed. . o b iektu . .S tackP anel..re p re ze n tu ją ce g o


czło w ieka .

^ U ż y c ie s ło w a k lu c z o w e g o as w c e lu r z u to w a n ia o b ie k tu w d ó ł.

Słow n ik. .R esp u rces. zw raca..o b ie k t . ty p u ..<ot>jęct,.. ohject.b..................

.....a. ..zatem. .w arto.ść.. odwo ^afTn.i.^. .R eso.yrces.E neim yT em plate"J....


.j^ ^t rzutow ana w dół na konkretny ty p C ontroiTem plate.

P rz e k a z a n ie d o m e to d y r e fe r e n c ji d o o b ie k tu .

.R efere n cja . do . o b iektu .. C ortenfC ontro! .j e s t .p rz e k a zy w a n a ....................

.jako.. p ie r w s zy . .p aram etr. .metody. .Anim.?.t?.Ęin?.my().■. ....

^ U tw o rz e n ie in s ta n c ji o b ie k tu i w y w o ła n ie je d n e j z je g o m e to d .

W .m.?.trdzi?. A.n!m?tejj!M jny(). . tw o rzo n y ..j¡<íis.t. .o kieM . M a s y .....................

S.toryBoa.rí:!.i..a..n a stę p n ie .. !¡¡j!/!jjrt!/w.?n¡!. j e s t .je g o .m e to d a ......................

....B egin i ):...

U ż y c ie ty p u w y lic z e n io w e g o w c e lu p r z y p is a n ia w a rto ś c i.
W m e to d zie EndTheG am e() .j€isit' używ any. ty p w y/iczen io w y

V is ik ility : a konkretnie w ta ściw o ści s ta r tB u tto n .V is ik i/ity

je s t' p rzyp isy w a n a w a rto ść V is ik i/ity .V is ik i/e .

jesteś tutaj ► 523


Za kulisami W indows Forms

Technologia Windows Forms korzysta z grafu obiektówstworzonego przez IDE


Podczas tw orze nia klasycznej a p lika cji przeznaczonej dla systemu W indow s I D E przygotow uje fo rm u la rz i generuje jego
kod, k tó ry umieszcza w p lik u Form 1.D esigner.cs. A le co właściw ie jest umieszczane w tym p lik u ? N ie jest to bynajm niej
nic tajem niczego. Już wiesz, że wszystkie k o n tro lk i umieszczane na fo rm u la rz u są ob ie kta m i, wiesz także, że referencje do
o b ie któ w m ożna zapisywać w polach. A zatem w ja kim ś m iejscu wygenerowanego ko d u m usi się znaleźć deklaracja pola
reprezentującego każdy z o b ie któ w form ularza , ko d służący do utw o rzen ia tego o b ie ktu oraz ko d do w yśw ietlenia go na
form ularzu . S próbujm y odszukać te wiersze kodu, byśmy m o g li do kła dnie sprawdzić, co się dzieje w form ularzu .

r® O tw ó rz p ro je k t Prosty edytor tekstów, k tó ry napisałeś w rozdziale 9., i otw ó rz p lik Form 1.D esign er.cs. Przewiń
jego zawartość w d ó ł i odszukaj deklaracje pól. Powinieneś ujrzeć po je dn ej dekla racji po la dla każdej k o n tro lk i
umieszczonej na form ularzu .

Bl Iwindows Form Designer generated code]

private System.Windows.Forms.OpenFileDialog openFileDialogl;


private System.Windows.Forms.SaveFileDialog saveFileDialogl;
private System.Windows.Forms.TextBox textBoxl;
private System.Windows.Forms.Button open;
private System.Windows.Forms.Button save;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanell;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanell;

^2 R o zw iń sekcję ko d u wygenerowanego przez p ro je kta n ta fo rm u la rzy i odszukaj ko d tw orzący k o n tro lk i:

ID E mogło private void InitializeComponentQ


wygenerować te í
w iersze kodu w nieco f this.openFileDialogl = new System.Windows.Forms.OpenFileDialog();
innej kolejności, this.saveFileDialogl = new System.Windows.Forms.SaveFileDialogO;
niem niej jednak na
this.textBoxl = new System.Windows.Forms.TextBoxQ;
pew no znajdzie s ię
this.open = new System.Windows.Forms.ButtonQ;
tu po jednym w ierszu
kodu dla każdej this.save = new System.Windows.Forms.ButtonQ;
kontrolki um ieszczonej this.tableLayoutPanell = new System.Windows.Forms.TableLayoutPanel();
na formularzu. this.flowLayoutPanell = new System.Windows.Forms.FlowLayoutPanel();

K o n tro lk i takie ja k T ableL ayou tP ane l oraz F low La youtP a nel, mogące zawierać inne kon tro lki, dysponują właściwością
o nazwie C o n tro ls . Jest to obiekt typu C o n tr o lC o lle c tio n , któ ry pod bardzo wielom a względami przypom ina obiekt
typu L is t< C o n tro l> . Każda kon tro lka umieszczana na form ularzu jest obiektem klasy pochodnej klasy C o n tr o l,
a dodanie jej do kolekcji C o n tro ls panelu spowoduje, że zostanie ona wyświetlona wewnątrz niego. Przewiń zawartość
p lik u w dół, do miejsca, w którym do panelu Flow LayoutPanel dodawane są przyciski Zapisz i Otwórz:

this.flowLayoutPanell.Controls.Add(this.save);
this.flowLayoutPanell.Controls.Add(this.open);

Panel F lo w L a yo u tP a n e l został z k o le i um ieszczony w kom órce pa ne lu T a b le L a y o u tP a n e l.


Odszukaj miejsce kodu, w któ rym on oraz pole tekstow e są dodawane do pa ne lu zewnętrznego:
this.tableLayoutPanell.Controls.Add(this.textBoxl, 0, 0);
this.tableLayoutPanell.Controls.Add(this.flowLayoutPanell, 0, 1)

Także sam o b ie k t Form jest po je m n ikie m , do którego zostaje dodany o b ie k t pa ne lu T a b le L a y o u tP a n e l:

this.Controls.Add(this.tableLayoutPanell);

524 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

B ę d z ie sz m usiał otworzyć plik Form 1.Designer.cs z projektu


Prostego edytora te tefó w , który na pisałeś w rozdziale 9. —s

r Zaostrz ołówek +
Przyjrzyj się instrukcjom new oraz wywołaniom metody C o n t r o l s . A dd()
wygenerowanym przez IDE w formularzu Prostego edytora tekstu
i narysuj graf obiektów tworzonych podczas wykonywania tego kodu.

Do narysowania grafu obiektm^ b & ó W


potrzebował w szystkich tych e l e m e n t o w . lne
P oczyw iście linii, które p o u c z ą p o m 3 o n
obiekty- U łatw iliśm y Ci zadan<e, iy s ując
O b ie k t ek t O ? trzy pierw sze obiekty grdfu .

FLOWLAYOUT PANE L1 T ABL E L AYOUTPANE L l

6/e k t Te.^ ó/e k t O $

’/ e k t C o * ' ^ 'e k t s e f i Ó'e k f ^

jesteś tutaj ► 525


Formularze WinForms są świetnym narzędziem dydaktycznym

Przyjrzyj się instrukcjom new oraz wywołaniom metody C o n tr o ls .A d d ( )


* Zaostrz ołówek
N. Rozwiązanie
wygenerowanym przez IDE w formularzu Prostego edytora tekstu
i narysuj graf obiektów tworzonych podczas wykonywania tego kodu.

- «75*

p o s ia d a ją c y m U ^ ^

ek t
O b ie k t

Dwa obiekty Button zostały


dodane do kolekcji Controls
To Ci s ię naprawdę przyda, kiedy będziesz panelu flow LayoutPanel,
wykonywał ćw iczenia. P rzyjrzyj s ię także d ltfego też zaw iera on
drugiej m etodzie dostępnej w kla sie Debug. referencje do każdego z nich.
Referen cje do nich s ą także
zapisane w polach open
~f &L Podpowiedź do debugowania i sa ve formularza.
M etoda S y s te m .D i a g n o s tic s .D e b u g .U r i t e L in e ( ) pozw ala w yśw ie tla ć tekst
w oknie w y n ik ó w w trakcie sesji debugow ania. M ożna jej używ ać ta k samo jak
m etody C o n s o le .W r ite L in e ( ) w aplikacjach W ind ow s Forms.

526 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Użyj IDE do przejrzenia grafu obiektów


W ró ć do p ro je k tu Prostego edytora tekstu i umieść p u n k t przerw ania na w yw oła niu m etody
I n it ia liz e C o m p o n e n t ( ) , w ko n stru kto rze form ularza . Następnie uru cho m debugowanie
program u. G dy zostanie ono przerw ane w punkcie przerw ania, naciśnij klawisz F10,
by wejść do m etody, po czym p rz e jd ź do o k n a W atch i w p isz t h is , by w yśw ietlić
r Z r ó b to ! ^

i przejrzeć g ra f obiektów .

Name Value Type


-i ■ {S im pleTextE d ito r.F o rrrS im ple T e xtE d ito r.F o rm l
* base {SimpleTextEditor.ForrrSystem.Windows.Forms.Form {SimpleTextEditor.F
© com ponents Cannot fetch the value System.ComponentModel.IContainer K lik n ij p r z y c i s k w id o c z n y
** flowLayoutPanc {System.Windows.Forrr System.Windows.Forms.FlowLayoutPanel z le w e j s t r o n y „ t h i s , ę y
**name null string r o z w in ą ć z a w a r t o ś ć o b ie k tu
i w y ś w ie tlić w s z y s tk ie je g o
"*open (Text = "Open"} System.Windows.Forms.Button
p o la , w y g e n e r o w a n e p r z e z
** openFileDialog’ {System.Windows.Forrr System.Windows.Forms.OpenFileDialog ID E w c e l u p r z e c h o w y w a n ia
-»save {Text = "Save") System.Windows.Forms.Button r e f e r e n c j i d o k o n tro le k
* saveFileDialogl {System.Windows.Forrr System.Windows.Forms.SaveFileDialog u m ie s z c z o n y c h n a f o r m u la r z u .
-•tableLayoutPan {System.Windows.Forrr System.Windows.Forms.TableLayoutPanel
-*textBox1 {Text = System. Windows.Forms.TextBox

Locals Watch 1

D o da j do okna Watch po le ta b le L a y o u tP a n e l1 .C o n tr o ls i rozw iń pozycję Results View,


by zobaczyć o b ie kty dodane do panelu:

I Watch 1 ^ n X
Name Type
Kolekcja Controls IQ ta b le L a y o u tP a n « H1.Controls jSystem .W indows.Forms.Ta b le la yo u tC o n tro lC o lle ctio n H
kontrolki * base S ystem .W indow s.Form s.C ontrol.C ontrolC ollection {Sysi
TdbteLayouPanel * Container System .W indows.Form s.TableLayoutPanel
z awiera dwie * N o n-P u b lic m em bers
kontrolki: TextBox * Results V iew
oraz FfowLayoutPanel * .[0] o b je ct {System .W indows.Forms.TextBoxj
za w ierają c ą przyciski * •[!] o b je ct {System .W indows.Form s.FlowLayoutPanell
Z a p isz i Otwórz. Locals Watch 1

Okazuje się, że klasa System .W indows.Form nie R o zw iń pozycję „base” , by w yśw ietlić właściwości, k tó re ob ie kt
posiada właściwości C o n tro ls . Właściwość ta jest odziedziczył po swojej klasie bazowej:
dziedziczona po jej klasie bazowej, C o n ta in e rC o n tro l,
I W a tch 1 ▼ □X
która z kolei dziedziczy ją po swojej klasie bazowej, Name Type
S c r o lla b le C o n t r o l, a ta od swojej klasy - th is SimpleTextEditor.Forml
bazowej C o n tro l. R ozwijaj pozycję IE * b a s e --------------- —^ © * base System.Windows.Forms.Form {SimpleTextEditi
- base System.Windows.Forms.ContainerControl {Sin
w oknie Watch, aby przejść hierarchię dziedziczenia,
s * base System.Windows.Forms.ScrollableControl (Sirr
docierając aż do klasy S yste m .W in d o w s.F o rm s.C o n tro l. S y s te m .W in d o w s .F o rm s .C o n tro l (S im p le T e x tE c W
(T o właśnie po niej klasa Form dziedziczy kolekcję Locals Watch 1
C o n tro ls ). Następnie rozwiń pozycję Results View
kolekcji C o n tro ls . Znajdziesz w niej po jednym obiekcie
dla każdej fc m to lM umieszczonej na f orm ularzu! To . . jes t Twoje ostatn ie sp otkaw e z ap likacjamk .WinFor" 'S ^ d u iS T one

n ^ h.s o !^ ^ :;! ^ d !i ia u i!'zcp: z a -w a n > - C *-


jesteś tutaj ► 527
Zbadajm y język XAML

Aplikacje dla Sklepu Windows używają XAML -Ą -

Z r ó b to !
do tworzenia obiektów interfejsu użytkownika
Używając kodu X A M L do tworzenia interfejsu użytkow nika aplikacji przeznaczonych dla Sklepu W indows, w rzeczywistości
tw orzym y g ra f obiektów . Podobnie ja k w aplikacjach W inF orm s, także i w tym przypadku można skorzystać z I D E oraz jego
okna Watch do przeglądnięcia tych obiektów . O tw órz program , którego w ro zd zia le 2. u żyw a liśm y do „za b a w z in s tru k c ją
if-else” . Następnie otw órz p lik M ainPage.xam l.cs, umieść p u n k t przerw ania w konstruktorze, w wierszu zawierającym
w ywołanie m etody In it ia liz e C o m p o n e n t( ) , a następnie sko rzysta j z m o żliw o ści ID E , b y zbadać u tw o rzon e w a p lik a c ji
o b ie k ty in te rfe js u u żytko w n ika .

Uruchom debugowanie, a następnie naciśnij klawisz F1 0 , aby wejść do metody In itia liz e C o m p o n e n t() . Visual Studio 2012
fo r W indows 8 posiada nieco inny układ okien od Visual Studio przeznaczonego do tworzenia tradycyjnych aplikacji, gdyż dysponuje
większymi możliwościami; na przykład pozwala na otwieranie w ielu okien Watch (co przydaje się, kiedy chcemy śledzić większą
liczbę danych). W yświetl to okno, wybierając z menu opcję DEBUG/Windows/W atch/Watch 1, a następnie wpisz w nim t h is :

W a tc h 1 ▼ n X
N am e ^Fypë -

th is C hapter2P rogram 2.Main Page


ffl * base W indow s.Ul.Xaml.C ontrols.Page {C hapter2Program 2.Main Page}
contentL oaded bool
^c h a n g e T e x t W indows. Ul.Xam l.C ontrols.Button Ato*,CV\a

/ffl ^>enableCheckbox W indows.Ul.Xam l.Controls.CheckBox


11 ‘MabelToChan W indow s. Ul. Xaml. Controls.TextBlock

^2 ) A teraz przyjrzyj się kod ow i X A M L de finiują cem u postać strony:


Kod X A M L
< G rid B a ckg ro u n d = "{S ta ticR e so u rce A pplicationP ageB ackgroundThem eB rush}">
< G rid .R o w D e fin itio n s >
definiujący k o n tro lk i
< R o w D e fin itio n />
< R o w D e fin itio n />
umieszczone na
< /G rid .R o w D e fin itio n s >
< G rid .C o lu m n D e fin itio n s >
stronie zostaje
< C o lu m n D e fin itio n />
przekształcony
< C o lu m n D e fin itio n />
< /G rid .C o lu m n D e fin itio n s > w o b ie k t Page,
< B u tto n x:Name="changeText" C o n te n t= " K lik n ię c ie zm ie n ia e t y k ie t ę " k tó re g o pola
H o riz o n ta lA lig n m e n t= "C e n te r" C lic k = "c h a n g e T e x t_ C lic k "/>
i właściwości
<CheckBox x:Name="enableC heckbox" C ontent="W Iącza zmianę e t y k ie t y "
H o riz o n ta lA lig n m e n t= "C e n te r" Is C h e c k e d = "tru e " G rid .C o lu m n = "1 "/> zawierają referencje
< T e xtB lo ck x:Nam e="labelToChange" G rid.R ow = "1" TextW rapping="W rap" do ko n tro le k
T e x t= " N a c iś n ij p r z y c is k , aby z m ie n ić t e k s t "
H o riz o n ta lA lig n m e n t= "C e n te r" V e rtic a lA lig n m e n t= "C e n te r" interfejsu
G rid.C o lu m n S pa n= "2 "/>
< /G rid > użytkow nika.
528 Rozdział 10.
Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

D o da j do okna Watch k ilk a właściwości k o n tro lk i la be lT oC h an ge:

Watch 1 T □ X

Name Value Type


A labelToChange.Text "Naciśnij przycisk, aby zm ienić tekst" d, ’ string
A labelToC hange.H orizontalAlignm ent Center W indows.UldCaml.HorizontalAlig
A labelToChange.VerticalAlignm ent Center W indows.UDCam l.VerticalAlignrr
A labelToChange.TextWrapping Wrap W indows.UDCaml.TextWrapping ▼

A p lik a c ja autom atycznie okre śli w artości właściwości na podstaw ie ko d u X A M L :

< T e xtB lo ck x:Nam e="labelToChange" G rid.R ow = "1" TextW rapping="W rapM


T e x t= " N a c iś n ij p r z y c is k , aby z m ie n ić t e k s t " ^ ----------------
H o riz o n ta lA lig n m e n t= "C e n te r" V e rtic a lA lig n m e n t= "C e n te r"
G rid.C o lu m n S pa n= "2 "/>

Spróbuj je d n a k dodać do okna Watch właściwość la b e lT o C h a n g e .G rid lu b labelT oC hange.C olum nS pan. K o n tro lk a
la b e lT o C h a n g e jest typ u W in d o w s .U I.C o n tro ls .T e x tB lo c k , a klasa ta nie deklaruje żadnej z tych właściwości.
Czy jesteś w stanie odgadnąć, co się dzieje w tym kodzie X A M L ?

Z atrzym aj pro gra m , o tw ó rz p lik M ainPage.xam l.cs i odszukaj w n im deklarację klasy M ainPage. Przyjrzyj się jej
dekla racji — ja k widać, dziedziczy ona po klasie Page. U m ieść w skaźnik myszy nad słowem Page, ta k by I D E w yśw ietliło
pełną nazwę klasy:
przesuń ws kaź n ik m yszy nad słowo
Page> by wy św ie tlić nazwę k/asu.
public seal e d partial class M a i n P a g e : Page ,. J

^
public M a i n P a g e O
class Windows.UI.Xaml.Controls.Page ^
Encapsulates a page o f content that can be navigated to.
t h i s . I n i t ia liz e C o m p o n e n t ( ) ;

T eraz ponow nie u ru cho m pro gra m i naciśnij klawisz F 1 0 , by wejść do m etody I n it ia liz e C o m p o n e n t ( ) . Przejdź
do okna Watch i rozw iń elem enty t h i s , następnie base i jeszcze raz base, przechodząc tym samym nieco w górę
h ie ra rc h ii dziedziczenia.

IW atch 1 ▼ nX
N am e Type -

■this Chapter2Program2.MainPage
base W indows.UI.Xaml.Controls.Page {Chapter2Program2.MaínPage}
base W indows.Ul.Xaml.Controls.UserControl {Chapter2Program2.MainPa
Rozwiń te
ffl * base W indows. Ul.Xaml.Controls.Control {Chapter2Program2. Main Page)
elem enty, by
w yśw ietlić ~ & Content W indows.Ul.Xaml.Ul Element {Windows.UI.Xaml.Controls.Grid}
klasy bazowe. S:a:'c '--e -o e rs Rozwiń elem ent Content i spraw dź jego w ęzeł [W indows.UI.Xam l.Cont rols .G ridJ.

Poświęć chw ilę, by do kła dnie zbadać o b ie kty wygenerowane na podstaw ie kod u X A M L . Przyjrzym y się im nieco do kła dniej
w dalszej części książki. N a razie po p ro stu je prze jrzyj, byś u św iad om ił sobie, ja k w iele o b ie któ w tw orzy T w o ją aplikację.

jesteś tutaj ► S29


Stare staje się nowym

Przeprojektuj formularz Idź na ryby!,


zmieniając go waplikację dla Sklepu Windows
G ra Id ź na ryby!, k tó rą napisałeś w rozdziale 8., byłaby fantastyczną aplikacją dla Sklepu W indows. U ru cho m zatem Visual
Studio 2012 fo r W indows 8 i utw órz w nim nowy p ro je k t W in d o w s S tore (ta ki sam ja k w przypadku aplikacji R a tu j ludzi).
N a k ilk u następnych stronach przeprojektujesz ten fo rm u la rz w fo rm ie strony X A M L , k tó ra będzie p o tra fiła dostosować się
do urządzeń z ekranam i o różnych rozm iarach. Z am iast korzystać z k o n tro le k W indows Form s umieszczanych na form ularzu,
skorzystasz z k o n tro le k charakterystycznych dla aplikacji przeznaczonych dla Sklepu W indows, umieszczanych na stronie.

To zostaje przekształcone To zostaje przekształcone Użyjem y panelu StackP anel o układzie


w element w element poziomym, aby zgrupować kontrolki
Z rö t to TextBox oraz Button, tak by można je było
w yśw ietlić w jedn ej komórce układu.

To zostaje
przekształcone
w element
<ListBox/>

To zostaje przekształcone
w element
<ScrollViewer/>

To zostaje przekształcone
To je s t kolejna kontrolka dostępna w przyborniku.
w element To zostaje przekształcone
Prezentuje ona sekw encję łańcuchów znaków,
dodając do nich pionowe i poziome paski w element
<ScrollViewer/>
przew ijania, je ś li te k st będzie w iększy od <Button/>
rozmiarów kontrolki.
530 Rozdział 10.
Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

T a k te k o n tro lk i będą wyglądać na głów nej stronie aplikacji:

Idź na ryby!
Im ię Ręka

Edek 1 Rozpocznij o»«' |


Piętka karo Wię k sz o ść kodu
<TextBox/> <Button/> obsługującego
Postępy g ry Piętka kier przebieg gry
pozostanie
w niezmienionej
pos ta ci, zmieni
s ię natom iast
kod obsługujący
M e r fe js aplikacji.

K o n tro lk i te zostaną umieszczone w siatce, k tó re j wiersze i ko lu m n y będą się zm niejszać i powiększać w zależności
od w ielko ści ekranu. D z ię k i tem u gra będzie m ogła się zm niejszać i powiększać, dostosowując się do ekranu.
D o określania różnych k o n fig u ra c ji ekranu możesz skorzystać z okna D evice dostępnego w ID E .

Device - n

View © n c ■ Przyciski View pozwalają w yśw ietlić stronę w układzie


Visual state FullScreenLandscape poziomym, pionowym oraz w układzie podzielonego ekranu.
n Enable state recording

Display
r— 1 .3 6 6 , 768
1______ 1140dpi # 100% wjtr
Różne opcje dostępne na liście Display pozwalają wyśw ietlać stronę
Theme
przy w ykorzystaniu różnych rozdzielczości oraz proporcji ekranu.
Default

Show chrome
Usuń zaznaczenie pola „Show chrome", by ukryć
Override scaling □
obrazek reprezentujący oprawę ekranu urządzenia.
Clip to display □

jesteś tutaj ► 531


A oto i strona

Określanie postaci strony rozpoczyna się od dodania kontrolek


Technologie X A M L oraz W inF orm s m ają jedną wspólną cechę: w obu do określania postaci interfejsu użytkow nika
używane są ko n tro lki. Strona gry Id ź na ryby! posiada dwa przyciski, ko n tro lkę L is tB o x służącą do w yśw ietlania kart
w ręce, pole T extB ox, w którym u żytko w nik może wpisać swoje im ię, oraz cztery etykiety T e x tB lo c k . Interfejs użytkow nika
dopełniają dwie k o n tro lk i S c r o llV ie w e r o białym tle, służące do prezentowania postępów gry oraz odłożonych grup.

To je s t nagłówek szablonu Basic page . Pirzycte^ w lewo znikn'ie,


® Idź na ryby! podobnie ja k w przypadku likcj„ Ratuj
p
a

CD
Postępy gry

J e ś li okno aplikacji będzie bardzo wy s ° kie, to ta k°n trolka


Scro llV ie w e r zostanie powiększona, by w y ^ M ć d° s tę pny
pionowy obszar. J e ś li p rezen t°wany w niej te k st bę dzie zbyt
długi, to zostaną w niej w yśw ietl°n e paski przew ija.nia..
Także ta kontrolka
L is tB o x powinna s ię
Grupy rozszerzać, w razie
gdy okno aplikacji
sta n ie s ię wyższe,
Ta kontrolka S cro llV ie w e r m usi być na tyle wypełniając cały je j
wysoka, by były w niej widoczne odkryte obszar dostępny
grupy kart; także w niej, w razie potrzeby, J j w pionie.
powinny być wyśw ietlane pa ski przew ijania.

Szablon B a sic Page zaw iera siatkę składającą się z dwóch wierszy. Jej górny wiersz zaw iera nagłów ek z nazwą aplikacji.
Z k o le i w drugim w ierszu um ieszczony jest obszar treści, zde finiow a ny przez poniższą siatkę. Cała ta siatka jest
um ieszczona w w ierszu 1. u kła d u (oraz w jego jedynej ko lu m n ie o num erze 0).

< G rid G n d .R o w = "l" M a rg in = "1 2 0 ,0 ,6 0 ,6 0 "> <


Marg ines y określają w cięcie sia tk i, dzięki
Oto znacznik 4 ' < T e xtB lo ck T e x t= "Im ię " M a rg in = "0 ,0 ,0 ,2 0 " czemu j e s t oidsijnięta od krawędzi strony.
otw ierający, S ty le = "{S ta tic R e s o u rc e S u b h e a d e rT e x tS ty le }"/> Lew y margi'nes zaw sze wynosi 120 p ik se li.
rozpoczynający
sia tk ę .
Zastosujem y k o n tro lk ę typ u S ta c k P a n e l , aby w jednej kom órce sia tki um ieścić pole T e x tB o x
do podania nazwy gracza oraz przycisk rozpoczynający grę:

<StackPanel O r ie n ta tio n = " H o r iz o n ta l" G rid.Row="1


Ta w łaściw ość dodaje wokół
<TextBox x:Name="playerName" F ontS ize= "24 " kontrolek TextBox i Button margmes
W idth= "500" M inW idth="300" /> o szerokości 2 0 p ik seh . Kiedy
w łaściw ość M argin zaw iera dwie
liczby, określają one, ° dpowiednio:
Q < B u tto n x :N a m e = "s ta rtB u tto n " M a rg in = "2 0 ,0 "( m arginesy w poziomie ( lewy i prawy)
C o n te n t= "R o zp o czn ij g r ę !" 7 > ~ " '', oraz w pionie (górny i dolny) .
< /S ta ckP a n e l>

532 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

NjW
'

Każda etykie ta um ieszczona na stronie ( „ Im ię ” , „Postępy gry” itd .) jest k o n tro lk ą T e x tB lo c k posiadającą
n ie w ie lk i margines u góry oraz u dołu, ja k rów nież określoną w artość właściwości S u b H e a d e rT e x tS ty le :

< T e xtB lo ck T e xt= "P o stę p y g ry "


S ty le = "{S ta tic R e s o u rc e S u b h e a d e rT e xtS tyle }"
M a rg in = "0 ,2 0 ,0 ,2 0 " G rid .R o w = "2 "/>

K o n tro lk a S c r o llV ie w e r wyśw ietla postępy gry i dysponuje paskam i przew ijania, któ re zostaną w yśw ietlone,
kie dy tekst stanie się zbyt dług i i przestanie się m ieścić w obszarze k o n tro lk i:

< S c ro llV ie w e r G rid.R ow = "3" F ontS ize= "24 "


Background="W hite" F o reg rou nd= "B lack" />

O to kolejn e k o n tro lk i, T e x tB lo c k oraz S c r o llV ie w e r , służące do w yśw ietlania grup kart. D om yślną
w artością w yrów nania k o n tro le k S c r o llV ie w e r w p io n ie oraz w po zio m ie jest S t r e t c h i okaże się, że jest
ona naprawdę przydatna. S kon figu ruje m y wiersze i ko lu m n y sia tki w ta k i sposób, by k o n tro lk i S c r o llV ie w e r
rozszerzały się, dostosowując się do ekranów o różnych wielkościach.

< T e xtB lo ck T ext= "G ru py" S ty le = "{S ta tic R e s o u rc e S u b h e a d e rT e xtS tyle }"
M a rg in = "0 ,2 0 ,0 ,2 0 " G rid .R o w = "4 "/>

O
w < S c ro llV ie w e r F o n tS ize = "2 4 " Background="W hite" F oreg rou nd= "B lack"
G rid.R ow = "5" G rid.RowSpan="2" />

P ośrodku sia tki dodaliśm y kolum n ę o szerokości 40 pikseli, by zapewnić odstęp pom iędzy k o n tro lk a m i
w obu kolum nach. Oznacza to także, że pozostałe k o n tro lk i — L is tB o x oraz B u tto n — muszą zostać
umieszczone w trzeciej ko lu m n ie . K o n tro lk a L is tB o x zajm uje wiersze do 2. do 6., a zatem zastosowaliśmy
w niej właściwości G rid .R o w = "1 " oraz G rid .R o w S p a n = "5 ". D z ię k i tem u rozw iązaniu k o n tro lk a ta będzie się
powiększała i zmniejszała, dostosowując do w ielko ści strony.
Pamięta j, że numeracja
< T e xtB lo ck Text="R ęka" S ty le = "{S ta tic R e s o u rc e S u b h e a d e rT e x tS ty le }" w ierszy i kolumn rozpoczyna
G rid.R ow = "0" G rid.C o lu m n = "2" M a rg in = "0 ,0 ,0 ,2 0 " /> s ię od zera , a zatem
w przypadku kontrolek
umieszczonych w trzeciej
< L is tB o x x:N am e="cards" Background="W hite" F on tS ize = "2 4 " H e ig h t= "A u to " kolumnie należy użyć
M a rg in = "0 ,0 ,0 ,2 0 " G rid .R o w = "l" Grid.RowSpan="5" G rid .C o lu m n = "2 "/> w ła ściw ości Grid.Column=“2 ‘ .

W przycisku Z a żą d a j karty, we właściwościach określających w yrów nanie w p io n ie i w poziom ie,


zastosowaliśmy wartość S tr e t c h , dzięki czemu w yp ełn i on cały obszar k o m ó rk i. D o d a n y do lis ty margines
do ln y o wysokości 20 p ikse li zapewni n ie w ie lk i odstęp pom iędzy listą i przyciskiem .

< B u tto n x:Name="askForACard" C o ntent= "Z ażądaj k a r ty "


H o riz o n ta lA lig n m e n t= "S tre tc h " V e r tic a lA lig n m e n t= " S tre tc h "
G rid.R ow = "6" G rid .C o lu m n = "2 "/>

Tworzenie siatki zakończymy na następnej stronie. jesteś tutaj ► 533


Kurczy się i rozszerza — wszędzie pasuje

Wiersze i kolumny mogą zmieniać wielkość, dostosowując się do rozmiarów strony


S ia tki są niezw ykle efektyw nym narzędziem do określania u kła d u stron, gdyż u ła tw ia ją pro je kto w a n ie stron, k tó re mogą
być wyśw ietlane na w ie lu różnych urządzeniach. W ysokości oraz szerokości zakończone znakiem * są a u to m a tyczn ie
dostosowywane do ekranów o różnej geometrii. Strona gry Id ź na ryby! składa się z trzech kolum n. Pierwsza ma szerokość 5*,
a trzecia 2 *, a zatem będą się p ro p o rc jo n a ln ie kurczyły i rozszerzały, zachowując zawsze p ro p o rcje 5:2. D ru g a kolum na
ma ustaloną szerokość wynoszącą 40 p ikse li i służy do wizualnego oddzielenia dwóch pozostałych kolum n . Poniżej
zam ieściliśm y schemat przedstaw iający u k ła d wierszy i ko lu m n strony (oraz k o n tro lk i um ieszczone w kom ó rkach siatki):

<ColumnDefinition Width="40"/>
<ColumnDefinition Width="5*"/> <ColumnDefinition Width="2*"/>
<RowDefinition
Height="Auto"/> <TextBlock/> <TextBlock
Grid.Column= "2"/>
Row="1" oznacza drugi w iersz, gdyż
ich numeracja zaczyna s ię od 0.

<RowDefinition
Height="Auto"/> <StackPanel Grid.Row="1"> <ListBox
<TextBlock/> Grid.Column="2"
<Button/> Grid.RowSpan="5"/>
</StackPanel>
Ta kontrolka L is tB o x
<RowDefinition zajm uje p ięć kolejnych
Height="Auto"/> <TextBlock Grid.Row="2"/> w ierszy, w tym także
wiers z czw arty, który
będzie s ię powiększał,
zajm ując cały wolny
obsza r stron y. Dzięki
<RowDefinition/> <ScrollViewer temu lista będzie s ię
Ten w iersz ma domyślną wysokość 1*, pow iększać, zajm ując
Grid.Row="3"/> a umieszczona w te j komórce kontrolka całą prawą kolumnę
S cro llV ie w e r ma domyślne w artości wyrównania strony.
w poziomie i w pionie, czyli S tre tc h . Oznacza
to, że będzie s ię pow iększać i zm niejszać,
dostosow ując s ię do w ielkości strony.
<RowDefinition
Height="Auto"/> <TextBlock Grid.Row="4"/>

<RowDefinition
Height="Auto" <ScrollViewer Grid.Row="5" Grid.RowSpan="2">
MinHeight="150"/>
t
W te j kontrolce S cro llV ie w e r użyliśm y w ła ściw ości
G rid.R ow Span o w artości “2 “ , by zajmowała ona dwa
.................... są sia d u ją ce w iersze. U m ieściliśm y ją w szóstym
<RowDefinition
Height="Auto"/> w ierszu (czyli w ła ściw ość G rid.R ow ma w artość "6", <Button
gdyż numeracja w ierszy w język u X A M L zaczyna Grid.Row="6"
s ię od 0), którego minimalna w ysokość wynosi
150; dzięki czemu mamy pewność, że kontrolka Grid.Column="2" />
S cro llV ie w e r nie będzie niższa od te j w artości.
ł-
Numeracja w ierszy i kolumn w języku X A M L rozpoczyna s i ę <°d t0, dlatego też w tej kmttmhe B u tton wier-sz zo sta ł okre ś lony
jako 6, a kolumna jako 2 (aby pominąć kolumnę środkową). W łaściwości określające wyrównanie kontrolki w pionie i poziomie
mają wartość S tretch , dzięki czemu przycisk zajm ie cały obszar komórki. Wysokość w iersza zostą ła o k ^ h n a . jako
dzięki czemu zostanie ona dostosowana do wymiarów zaw artości (czyli przy cisk u oraz jego marginesów).

534 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

O to ja k w ygląda de fin icja wierszy i ko lu m n dla takiego u kła d u strony:

Pierwsza kolumna zawsze będzie 2,5


razy szersza od trzeciej (proporcja 5:2),
a obie będą od siebie oddzielone drugą
kolumną o stałej szerokości 40 pikseli.
< G rid .C olu m n D e fin ition s>
W kontrolkach S c ro llV ie w e r
<Colum nDefinition W idth="5*"/> oraz L is tB o x , używanych do
prezentacji danych, właściwości
<Colum nDefinition W idth="40"/> H o riz o n ta lA lig n m e n t przypisano
wartość " S tr e tc h " , dzięki czemu
<Colum nDefinition W idth="2*"/> w ypełnią one całą szerokość kolumn.

</G rid .C olu m nD e fin ition s>

< G rid .R ow D e fin itio n s>

<Row Definition H eight="Auto"/> Czwarty wiersz ma domyślną wysokość 1*,


dzięki czemu będzie się powiększał
<Row Definition H eight="Auto"/> i zmniejszał, zajmując cały dostępny
obszar, który nie został zajęty przez
<Row Definition H eight="Auto"/> pozostałe wiersze. Kontrolki
L is tB o x oraz S c ro llV ie w e r są
<RowDefinition/> ^ w yśw ietlone także w tym wierszu,
dzięki czemu także one będą się
<Row Definition H eight="Auto"/> powiększać i zmniejszać.

<Row Definition Height="Auto" M inHeight="150" /

<Row Definition H eight="Auto"/>

</G rid .R ow D efin ition s>

D efinicje w ierszy i kolumn można dodawać Wysokość niemal wszystkich wierszy ma


wewnątrz sia tk i, powyżej lub poniżej tego kodu. wartość Auto. Tylko jeden wiersz siatki
Tym razem dodaliśmy j e poniż e j. będzie się powiększał i zmniejszał
i wszystkie kontrolki w yśw ietlone w tym
wierszu będą się zachowywały podobnie.

=/Grid>

Oto znacznik zam ykający sia tk i. Cały kod X A M L


1 strony połączysz w ca łość pod koniec rozdziału,
kiedy zakończysz przerabianie gry na aplikację dla
Sklep u Windows.

jesteś tutaj k 535


Strony działające na dowolnym ekranie? Niewiarygodne!

Skorzystaj z systemu siatki, Jeśli już w rozdziale 1. nie użyłeś


tych przycisków, umieszczonych
by określić układ stron aplikacji u dołu okna Designer i służących,
odpowiednio, do wyświetlania
Czy kiedykolw iek zauw ażyłeś, że ró żn e aplikacje p rzezn aczo n e dla S klepu W indows linii siatki, przyciągania oraz
przyciągania do siatki, to
m ają pod o b n y wygląd? D zieje się ta k dlatego, że korzystają z system u s ia tk i , by
skorzystaj z nich teraz.
nad ać aplikacjom coś, co p ro jek tan ci z firm y M icrosoft o k reślają m ianem „spójnego
zarysu”. S iatka składa się z kw adratów , nazyw anych jed n o stka m i (ang. units) o raz
podjednostkam i (ang. subunits) — m iałeś ju ż okazję się z nim i spotkać, gdyż są one
wykorzystywane w V isual Studio ID E .

Siatka składa się z kw adratów o wielkości Każda jednostka jest podzielona


Nagłówek strony powinien 20 x20 pikseli, nazywanych jednostkami. na podjednostki o wielkości 5x5 pikseli.
mieć wysokość 7 jednostek,
tekst powinien się zaczynać
6 jednostek od lewej
krawędzi strony, a jego
i i
dolna krawędź powinna się
znajdować na wysokości
5 jednostek poniżej górnej
krawędzi.

S '
® Idź na ry
W iersz n a g łó w ka jest
l u to m a ty c z n ie d o d a w a n y do
sza b lo n u Basic Poge, który
zastosujesz w d a ls z e j części
ro z d z ia łu , k ie d y g ra I d z na ry b y.
Imię
b ę d z ie j u ż d z ia ła ć .

W aplikacji Idź na ryby! w k o n tro lce < G rid > zaw ierającej wszystkie pozostałe
kontrolki aplikacji skorzystam y z właściwości M a rg in , by stworzyć o d stęp od
kraw ędzi strony. W arto ścią tej właściwości m oże być: je d n a liczba (określająca
w ielkość wszystkich czterech m arginesów : lew ego, g órnego, praw ego i dolnego),
dwie liczby (pierw sza z nich ok reśla w ielkość m arg in esu lew ego i praw ego, a druga:
górnego i dolnego) bądź też cztery liczby o k reślające, odpow iednio, w ielkość
Strona używa marginesów, by dodać odstęp
m arginesu lew ego, górnego, praw ego i dolnego.
o wielkości jednej jednostki pomiędzy
T w oja aplikacja Idź n a ryby! m a lewy m argines o w ielkości 120 pikseli (czyli etykietam i i pozostałymi elementami.

6 jed n o stek ), nie m a górnego m arginesu, n a to m ia st praw y i dolny m argines


m ają w ielkość 60 pikseli (3 jed n o stek ):

< G rid G n d .R o w = "l" M a rg in = "1 2 0 ,0 ,6 0 ,6 0 ">

O prócz tego pom iędzy etykietam i o raz innym i e lem en tam i strony tak że zostały
d o dane m arginesy o w ielkości jed n ej jednostki:

< T e xtB lo ck Text="B ooks"


S ty le = "{S ta tic R e s o u rc e S u b h e a d e rT e xtS tyle }"
M a rg in = "0 ,2 0 ,0 ,2 0 " G rid .R o w = "4 "/>

536 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

■ Nie. istnieją.
głupie pytania

P : Jaki jest efekt przypisania wysokości wiersza


jako wartości właściwości Margin, Height, Width
oraz innych jest: jednostki niezależne od urządzenia.
lub szerokości kolumny wartości „Auto” ?
Aplikacje przeznaczone dla Sklepu Windows muszą
OPrzypisanie wartości
: Auto właściwości Height
działać na ekranach o różnych wielkościach i kształtach,
a zatem niezależnie od tego, jak duży lub mały jest ekran
wiersza lub właściwości Width kolumny powoduje, że
urządzenia, na którym została uruchomiona aplikacja,
wielkość tego wiersza lub kolumny będzie się zmieniać
jedna jednostka niezależna od urządzenia zawsze będzie
i dostosowywać do wymiarów jego zawartości.
miała wielkość 1/96 cala. Kwadrat pięć na pięć takich
Samemu możesz spróbować, jak to działa, Utwórz
jednostek tworzy jedną podjednostkę układu strony,
nowy projekt Blank App i zmodyfikuj siatkę na stronie
a kwadrat cztery na cztery podjednostki tworzą jedną
MainPage.xaml, dodając do niej kilka wierszy i kolumn,
jednostkę układu strony. Te wszystkie jednostki układu
których wysokości i szerokości zostały określone jako
oraz jednostki niezależne od urządzenia są nieco mylące,
Auto , W oknie Designer nie zobaczysz niczego, gdyż
dlatego też te drugie będziemy nazywali pikselami
wiersze i kolumny są puste, więc zostały zmniejszone
do wysokości i szerokości 0 pikseli, Kiedy jednak Wszystkie wysokości oraz szerokości w języku
dodasz jakieś kontrolki do różnych wierszy i kolumn, XAML można także wyrazić w innych jednostkach,
przekonasz się, że powiększyły się one, dostosowując dodając do liczb odpowiednie określenia: in (cale),
się do wymiarów tych kontrolek, cm (centymetry) lub p t (punkty, chodzi tu o punkty
typograficzne o wielkości 1/72 cala). Spróbuj określić
P : A zatem czym to się różni od określenia układ strony, używając cali lub centymetrów,
A następnie weź linijkę i sprawdź, czy system Windows
wysokości wiersza lub szerokości kolumny jako 1*,
2 * lub 5*? nadał kontrolkom prawidłowe wymiary.

OZastosowanie
: * w określeniu wysokości wiersza P : Czy jest jakiś prosty sposób, by mieć
lub szerokości kolumny sprawia, że wiersze lub pewność, że moja aplikacja będzie dobrze
kolumny będą powiększane proporcjonalnie, wyglądać na różnych monitorach?
wypełniając cały obszar siatki, Jeśli siatka zawiera
trzy kolumny o szerokościach 3 * , 3* i 4 * , to każda O : Tak, IDE udostępnia przydatne narzędzie, które
z dwóch pierwszych kolumn będzie zajmowała 30% zapewnia takie możliwości. Okno Designer pozwala
szerokości całej siatki pomniejszonej o szerokości sprawdzać, jak projektowane strony XAML będą
kolumn o ustalonej lub automatycznie wyznaczanej wyglądały na różnych urządzeniach, i to na kilka
(Auto ) szerokości; trzecia kolumna (4 * ) będzie sposobów. Można skorzystać z okna Device, by
zajmowała 40% szerokości siatki, wyświetlić stronę w różnych rozdzielczościach oraz
trybach podziału ekranu. W dalszej części książki
Jest pewien powód, dla którego domyślna szerokość
pokażemy, jak można uruchamiać aplikację w
i wysokość określona jako 1* , ma sens, Jeśli wszystkie
symulatorze, zapewniającym możliwość prowadzenia
wiersze i kolumny będą miały te domyślne wartości, to
z nią interakcji w symulowanych urządzeniach
niezależnie od wielkości siatki wielkości poszczególnych
wyposażonych w ekrany o różnej wielkości.
wierszy i kolumn zawsze będą równe,

P : „Piksele” . Ciągle używacie tego słowa.


Nie sądzę, że oznacza ono to, co wy uważacie,
że ono oznacza.
Kiedy szerokość wiersza lub
wysokość kolumny ma wartość
OWiele osób korzystających z języka XAML używa
:
Auto, oznacza to, że ich
terminu piksel, ale masz rację — z technicznego punktu
widzenia nie używasz tych samych pikseli, które widzisz wymiary będą dostosowywane
na ekranie, Technicznym określeniem liczb podawanych do zawartości.

W ięcej in fo rm acji na te m a t określania u kła d ó w stron można znaleźć w w itry n ie MSDN,


na stronie h ttp://m sdn.m icrosoft.com /pl-pl/library/w indow s/apps/hh 872191.aspx.

jesteś tutaj ► 537


Te program y w yglądają znajomo

Użyj języka XAML, by przeprojektować każdą z klasycznych aplikacji Windows na aplikację przeznaczoną
dla Sklepu Windows. Dla każdej z nich utwórz nowy projekt Blank App i zmień stronę główną na stronę
utworzoną przy użyciu szablonu Basic Page (dokładnie tak samo jak w aplikacji Ratujludzi). Następnie
Ćwiczenia zmodyfikuj strony, wprowadzając odpowiednie zmiany w siatce i dodając do niej kontrolki. Te nowe
aplikacje nie muszą działać. Wystarczy, że utworzysz odpowiedni kod XAML, tak by pasowały one
wyglądem do poniższych formularzy.

Odszukaj odpowiednie miejsce w kodzie XAML strony Basic Page i dodaj tam
nową kontrolkę G rid lub S ta c k P a n e l, w której umieścisz pozostałe kontrolki
strony. Tę nową siatkę powinieneś umieścić w drugim wierszu (G rid .R o w = 'T ")
nowo utworzonej strony Basic Page.
AAML dość elastycznie
<Grid Style="{StaticResource LayoutRootStyle}">
podchodzi do kolejności
<Grid.RowDefinitions> znaczników.
<RowDefinition Height="140"/> Popros iliśm y C ię, b yś dodawał
<RowDefinition Height=" kod'X A M L nowego układu strony
Oto kontrolka <Grid> umieszczona poniżej defin ic ji w ierszy, gdyż
</Grid.RowDefinitions» . na tej stronie. Zwinęliśmy ją dzięki temu łatw iej go będzie
______________________________ w edytorze XAML. odnaleźć w pliku. Niektórzy
<Grid| Grid.Row="l" Margin=”120,0".,T]> program iści lubią zapisyw ać kod
A/ńm*' w takiej sam ej kolejności,
<!-- Back button and page title --> w ja k ie j posziczególne kontrolki
i-,:; Odszukaj ten komentarz, s ą umies zcz ane na stro n ie. Mogą
je um ieszczać za zamykającym
<Grid.ColumnDefinitions> Z o d a f n O w ą ^ t n Ę <0%°'. z nacznikiem </ G r id>, kończącym
<ColumnDefinition Width="Auto"/> sia tk ę z a wi'e ra jącą p rzycisk ze
%trz a!ką w. lewo oraz ty tu ł aplikacji.
Kadzimy, żeby ś poeksperym entował,
z ap isu ją c kod w różnych m iejscach,
gdyż warto, by ś samemu określił,
które miejs c e będzie Ci s ię
wydawało najbardziej intuicyjne.
538 Rozdział 10.
Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Zastosuj kontrolki S ta c k P a n e l, by zaprojektować układ tego formularza. Składa się on z dwóch grup kontrolek. W nagłówkach tych
grup został zastosowany styl G ro u p H e a d e rT e x tS ty le , sam e grupy oddziela od siebie margines o wysokości 40 pikseli, a poniżej
nagłówków został dodany margines o wysokości 20 pikseli. W etykietach umieszczonych nad kontrolkami został zastosowany styl
B o d y T e x tS ty le , a nad kontrolkami dodano margines o wysokości 10 pikseli. Pomiędzy kontrolkami dodano poziomy margines
o szerokości 20 pikseli.

<StackPanel Grid.Row=“1” Marain=“120.0”>


System zarządzania ulem To j e s t kontrolka B u tton, <TextBlock/>
„________ W której z o sta t użutu s ty l
P r ^ prac robotni™, j TextB utto n Style. <StackPanel Orientation=“Horizontal”>
<StackPanel> <StackPanel> <Button/>
<TextBlock/> <TextBlock/>
v To j e s t kontrolka <ComboB ox>, a je j zawarto ś ć o k r e ś lają <ComboBox> <TextBox/>
znJczn iki <ComboB o x Ite rn/>,
określany j e s t przy uzy ciu w te śa w o ś n C o n ^ n t <CombiboxItem/> </StackPanel>
<CombiboxItem/>
Raport ze zmiany
... 4 dodatkowe ...
</ComboBox>
UZyj w ła ściw ości Content, by dodać te k st do tej </StackPanel>
kontrolki S cro llV iew er. Sym bol &#13; pozwoli p rze jść
do następnego w iersza. Nadaj je j wysokość 2 5 0 p ikseli <Button/>
W faściw ości S elected ln d ex
<TextBlock/> k° ntr°lk i ComboBox p rzy p isz
przy tym w ła ściw ości B orderThickness oraz BorderBru sh .
<ScrollViewer/> w artość O, by z o sta ł wyświetlony
</StackPanel> p ierw szy elem ent listy .

Do zaprojektowania tego formularza użyj kontrolki G rid . Ma mieć


osiem wierszy, a we wszystkich właściwość H e ig h t ma mieć wartość <Grid Grid.Row=“1” Margin=“120,0”>
A uto, żeby dostosowywały się do zawartości. Zastosuj kontrolki
S ta c k P a n e l, żeby umieścić kilka kontrolek w jednym wierszu. <TextBlock/>
<TextBox/>
Śniadanie dla drwali <TextBlock/>
<ListBox>
<ListBoxitem/>
To je s t kontrolka ListB o x. Je j zawartość <ListBoxitem/>
określają znaczniki <ListBoxItem/>, podobnie jak ... 4 dodatkowe ...
zawartość kontrolki ComboBox określają znaczniki
<ComboBoxItem/>. Nie określaj je j wysokości, </ListBox>
Zeby kontrolka mogta s ię powiększać wraz
<TextBlock>
z dodawaniem kolejnych elementów.
<StackPanel Orientation=“Horizontal”>
<TextBox/>
<ComboBox> ... 4 elementy ... </ComboBox>
<Button/>
</StackPanel>
<ScrollViewer/>
<StackPanel Orientation=“Horizontal”>
Zadbaj o to, by strony wyglądały tak samo jak na tych zrzutach, <Button/>
dodając do kontrolek przykład ow e d a ne , które w normalnej aplikacji <Button/>
zostałyby określone przy użyciu metod i właściwości klas. </StackPanel>

Może się zdarzyć, że kiedy spróbujesz w ybrać opcję N e w Item ..., by dodać do projektu now ą stronę
Basic Page, IDE w y ś w ie tli w oknie D esig n e r kom unikat o błędzie — Visual Studio będzie bow iem oczekiwać,
że projekt zostanie zbudowany. W ta kim przypadku w yb ie rz opcję Rebuild S olution, a błąd zniknie. jesteś tutaj ► 539
Rozwiązanie ćwiczenia

Użyj języka XAML by przeprojektować każdą z klasycznych aplikacji Windows na aplikację przeznaczoną
dla Sklepu Windows. Dla każdej z nich utwórz nowy projekt Blank App i zmień stronę główną na stronę
utworzoną przy użyciu szablonu Basic Page (dokładnie tak samo jak w aplikacji Ratujludzi). Następnie
Rozwiązania
zmodyfikuj strony wprowadzając odpowiednie zmiany w siatce i dodając do niej kontrolki. Te nowe
ćwiczeń aplikacje nie muszą działać. Wystarczy, że utworzysz odpowiedni kod XAML, tak pasowały one wyglądem
do poniższych formularzy.
_____________To j e s t m argines, który m iałeś zastosow ać.
O puszczenie w artości prawego i dolnego
<StackPanel G rid .R o w = "l" M a rg in = "1 2 0 ,0 "> m arginesu s praw i, ±e będą one miały takie same
< T e xtB lo ck T e x t= " P rz y d z ia ł pra c ro b o tn ic o m " w ielkości ja k marg inesy lewy i górny 0 2 0 i O).
S ty le = "{S ta tic R e s o u rc e G ro u p H e a d e rT e x tS ty le }"/>
<StackPanel O r ie n ta tio n = " H o r iz o n ta l" M a rg in = "0 ,2 0 ,0 ,0 ">
<StackPanel M a rg in = "0 ,0 ,2 0 ,0 ">
< T e xtB lo ck T e xt= "Z a d a n ie r o b o tn ic y " M a rg in = "0 ,0 ,0 ,1 0
S ty le = "{S ta tic R e s o u rc e B o d y T e x tS ty le }"/>
<ComboBox S e le c te d In d e x = "0 ">
<ComboBoxItem C ontent= "N auczanie p s z c z ó łe k "/>
<ComboBoxItem C o n te n t= "P ie lę g n a c ja j a j " / >
<ComboBoxItem C ontent= "U trzym yw anie u la " / >
<ComboBoxItem C o ntent= "W ytw arzanie m io d u "/> Czy Twój kod XAM L w yg lą d a inaczej
<ComboBoxItem C o n te n t= "Z b ie ra n ie n e k ta ru " /> niż ten? XAM L pozw ala na tw o rze n ie
stron o bardzo podobnym (lub naw et
<ComboBoxItem C o n te n t= "P a tro l z ż ą d ła m i"/>
identycznym ) w yg lą d zie na w iele
</ComboBox>
różnych sposobów.
</S tackP a ne l>
<StackPanel>
< T e xtB lo ck T e x t= " S h ifts " M a rg in = "0 ,0 ,0 ,1 0 "
S ty le = "{S ta tic R e s o u rc e B o d y T e x tS ty le }"/>
<TextBox/>
</S tackP a ne l>
< B u tto n C o n te n t= "P rz y p is z to zadanie r o b o tn ic y " M a rg in = "2 0 ,2 0 ,0 ,0 "
S ty le = "{S ta tic R e s o u rc e T e x tB u tto n S ty le }" />
To j e s t nagłówek drugiej
< /S ta ckP a n e l> grupy, powyżej niego j e s t
< B utto n C o n te n t= "P rz e p ra c u j następną zm ianę" M a rg in = "0 ,2 0 ,0 ,0 " /> margines o w ysokości 40
p ik se li, a poniżej margines
o w ysokości 2 0 pikseh.
< T e xtB lo ck T e xt= "R a p o rt ze zm iany" M a rg in = "0 ,4 0 ,0 ,2 0 "
S ty le = "{S ta tic R e s o u rc e G ro u p H e a d e rT e x tS ty le }"/>
< S c ro llV ie w e r B o rd e rT h ickn e ss= "2 " B ord erB ru sh= "W h ite" H e igh t= "2 50 "
C ontent= "
R aport zmiany numer 20&#13;
R obotnica numer 1 ro b i 'Z b ie r a n ie n e k ta r u 1 je s z c z e prze z 2 zmiany&#13;
R obotnica numer 2 za ko ń czyła swoje zadanie&#13;
R obotnica numer 2 n ie p ra cuje& #1 3; — . Oto przykładowy te k st, który
R obotnica numer 3 ro b i 'P a tr o l z ż ą d ła m i' je s z c z e prze z 3 zmiany&#13; u m ieściliśm y w polu rapoHu.
W łaściw ość Content ignoruje
R obotnica numer 4 r o b i 'N auczanie p s z c z ó łe k ' je s z c z e prze z 4 zmiany znaki nowych w ierszy dodaw<me
" /> w kodzie X A M L — dodaliśmy je
tu ta j, by poprawić czytelnoś ć
< /S ta c k P a n e l
rozwiązania.

540 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

< G rid G rid .R o w = "l" M a rg in = "1 2 0 ,0 ">


< G rid .R o w D e fin itio n s >
< R o w D e fin itio n H e ig h t= "A u to "/> < R o w D e fin itio n H e ig h t= "A u to "/>
< R o w D e fin itio n H e ig h t= "A u to "/> < R o w D e fin itio n H e ig h t= "A u to "/> Z tych defin icji usunęliśm y
znaki nowych w ierszy,
< R o w D e fin itio n H e ig h t= "A u to "/> < R o w D e fin itio n H e ig h t= "A u to "/> by cały kod rozwiązania
< R o w D e fin itio n H e ig h t= "A u to "/> < R o w D e fin itio n H e ig h t= "A u to "/> z m ieścił s ię na jedn ej
stron ie.
< /G rid .R o w D e fin itio n s >

< T e xtB lo ck T e x t= "Im ię d rw a la " M a rg in = "0 ,0 ,0 ,1 0 "


S ty le = "{S ta tic R e s o u rc e B o d y T e x tS ty le }"/>
<TextBox G rid .R o w = "1 "/>

< T e xtB lo ck G rid.R ow = "2" T e x t= "K o le jk a do ś n ia d a n ia " M a rg in = "0 ,2 0 ,0 ,1 0 "


S ty le = "{S ta tic R e s o u rc e B o d y T e x tS ty le }"/>
< L is tB o x G rid.R ow = "3">
< L is tB o x Ite m C o n te n t= "1 . E dek"/>
< L is tB o x Ite m C o n te n t= "2 . Z yga"/> Żeby ws zys t ko byto j asme : te przykładowe elementy
h sty miateś dodać w ramach ćw iczenia, żeby strona
< L is tB o x Ite m C o n te n t= "3 . B o le k "/> przypominata' wy g ląd używanej aplikacji. W dalszej
< L is tB o x Ite m C o n te n t= "4 . F e rd e k"/> cz ę ści książki dow iesz s ię , ja k powiązać kontrolki takie
< L is tB o x Ite m C o n te n t= "5 . S ta c h u "/> ja k L is t B ox z w łaściw ościam i swoich klas.

< L is tB o x Ite m C o n te n t= "6 . R o b e rt"/>


< /L is tB o x >

< T e xtB lo ck G rid.R ow = "4" Text="Nakarm d rw a la " M a rg in = "0 ,2 0 ,0 ,1 0 "


S ty le = "{S ta tic R e s o u rc e B o d y T e x tS ty le }"/>
<StackPanel G rid.R ow = "5" O r ie n ta tio n = " H o r iz o n ta l" >
<TextBox T e x t= "2 " M a rg in = "0 ,0 ,2 0 ,0 " />
<ComboBox S e le c te d In d e x = "0 " M a rg in = "0 ,0 ,2 0 ,0 ">
<ComboBoxItem C o n te n t= "c h ru p k ie g o "/>
<ComboBoxItem C o n te n t= "w ilg o tn e g o "/>
<ComboBoxItem C o n te n t= "ru m ia n e g o "/>
<ComboBoxItem C ontent="bananow ego"/>
</ComboBox>
< B u tto n C ontent="D odaj n a le ś n ik i" S ty le = "{S ta tic R e s o u rc e T e x tB u tto n S ty le } " />
< /S ta ckP a n e l>
^ — Kolejna przykładowa zaw artość...

< S c ro llV ie w e r G rid.R ow = "6" M a rg in = "0 ,2 0 ,0 ,0 " C ontent="Edek ma 7 n a le śn ikó w "


B o rd e rT h ickn e ss= "2 " B o rd e rB ru s h = "W h ite "/>

<StackPanel G rid.R ow = "7" O r ie n ta tio n = " H o r iz o n ta l" M a rg in = "0 ,4 0 ,0 ,0 ">


< B u tto n C ontent="D odaj d rw a la " M a rg in = "0 ,0 ,2 0 ,0 " />
< B u tto n C o ntent= "N astępny d rw a l" />
< /S ta ckP a n e l>
Co sądzisz o układzie tej strony? Czy nie byłoby lepiej
przenieść przyciski Dodaj drwala i Następny drwal
< /G rid >
1 /i na standardowy pasek aplikacji dostępny w systemie
S Z A R E K ^ ^ ^ M^ ^ R K I Windows 8?

jesteś tutaj > 541


Nigdy więcej przykładowych danych

Wiązanie danych kojarzy strony XAML z klasami


K o n tro lk i T e x tB lo c k , S c r o llV ie w e r , T extB ox oraz w iele innych zostały stworzone po to, by wyświetlać dane. W aplikacjach
W inF orm s wyświetlanie danych w polach oraz dodawanie elem entów do list wymagało korzystania z właściwości. W podobny
sposób można także postępować w aplikacjach korzystających z języka X A M L , choć istnieje także inne rozwiązanie: można
skorzystać z w ią z a n ia d a n ych (ang. data binding ), by automatycznie zapisywać dane w kon tro lka ch umieszczonych na stronie.
Co więcej, można nawet skorzystać z wiązania danych, by zapisywać w klasach dane wpisywane w kontrolkach.

K O N TE K ST DANYCH

0/e/ct
/
Kontekst danych je s t
zw yczajną referencją,
zapisyw an ą we w faściw ośc
DataContext kontrolki. % >ść & W iązanie danych operuje
w yłącznie na właściw ościach.
Jeśli spróbujem y w ykorzystać
w tym celu publiczne pole,

Kontekst, ścieżka i wiązanie w kontrolce nie po ja w ią się żadne


dane — co w ięcej, nie zostanie
także w y ś w ie tlo n y żaden b łą d !
W iązanie danych w języku X A M L jest relacją pom iędzy w łaściw ością źródłow ą o b ie ktu
dostarczającego danych dla k o n tro lk i oraz w łaściw ością docelową k o n tro lk i prezentującej te Ś cie ż k ą wiązania zastosow aną
dane. A b y określić takie wiązanie, w k o n te k ś c ie d a n y c h k o n tro lk i należy zapisać referencję w te j kontrolce TextBlock j e s t
w ła ściw ość C ash. Kontrolka będzie
do o b ie ktu z danym i. Z k o le i w ią z a n ie m usi zawierać ścieżkę w ią z a n ia , czyli właściwość
w yśw ietlać w artość tej w łaściw o ś c i,
o b ie ktu zawierającą dane. K ie d y wszystkie te in fo rm a cje zostaną określone, k o n tro lk a odczytywaną z dowolnego obiektu,
będzie autom atycznie odczytywać i wyśw ietlać w artość właściwości źródłow ej. z którym zostanie pow^ązo-na..

A b y określić w iązanie danych w kodzie X A M L , należy określić właściwość źródłow ą w fo rm ie { B in d in g Ś c i e ż k a } :

<TextBlock x:Name="walletTextBlock" Text="{Binding Cash}"/>

O prócz tego po trze bn y jest ob ie kt, z któ ry m k o n tro lk a zostanie powiązana — w tym przypadku jest to o b ie k t Guy zapisany
w zm iennej jo e , którego właściwość Cash m a wartość 3 2 5 .5 0 . K o n te kst jest określany przez zapisanie re fe re n cji do
o b ie ktu Guy we właściwości D a ta C o n te x t k o n tro lk i.
Kontekstem danych dla tej kontrolki

Guy joe = new Guy("Józek\ 47, 325.50M); x G n o jk a f j * t^ M e k tu


w szy stk ie powiązane w ła ściw o ści.
walletTextBlock.DataContext = joe;

T eraz pow iązanie zostało ju ż określone! Podałeś kon tekst danych będący instancją klasy Guy i określiłeś ścieżkę
pow iązania odw ołującą się do właściwości Cash. Skoro zastosowane pow iązanie m a postać Cash, zatem k o n tro lk a
T e x tB lo c k prze jrzy o b ie k t danych, p o s z u k u ją c w n im w ła ściw o ści o na zw ie Cash.

M ożn a także po m in ąć określanie ścieżki pow iązania i zapisać we właściwości k o n tro lk i je dyn ie wyrażenie { B in d in g } .
W ta k im przypadku w yśw ietlony zostanie w y n ik zw rócony przez w yw ołanie m etody T o S t r in g ( ) klasy Guy.

542 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Powiązanie dwukierunkowe pozwala odczytywać


i zapisywać wartość właściwości źródłowej
W iązanie danych pozw ala odczytywać w artość z o b ie k tu danych. Jednak, korzystając z p o w ią z a n ia d w u kie ru n ko w e g o ,
można także m odyfikow ać w artość właściwości źródłow ej:

<TextBox x:Name="ageTextBox" T e x t= " {B in d in g Age, Mode=TwoWay}"/>


Ścieżka pow iązania zastosowana w tej ko n tro lce TextBox odw ołuje się do właściwości Age, natom iast samo powiązanie
działa w trybie dw ukierunkow ym . W m om encie w yśw ietlania strony k o n tro lk a odczyta i w yśw ietli w artość właściwości
Age dowolnego ob ie ktu , z któ rym została związana. Jeśli je d n a k zm ie nim y wartość tej k o n tro lk i, w yw oła ona akcesor set
Age i zaktualizuje je j wartość.
właściwości
M echanizm w ią za n ia danych zo sta ł opracow any
P O W IĄ Z A N IE
w ta k i sposób, by przysparzać nam jak najm niej
DWUKIERUNKOWE ó

i
problem ów . Jeśli ścieżka po w ią zan ia będzie się
o d w o ływ a ć do w łaściw ości, która nie jest dostępna
w kontekście danych, to kon tro lka nie będzie
w yśw ie tla ć ani a ktu alizow a ć danych, a jednocześnie
C/^ o ś ć C/WO' nie w ystą p ią żadne problem y w d zia ła n iu programu.

ObservableCollection pozwala tworzyć powiązania z kolekcjami


N ie k tó re k o n tro lk i, takie ja k TextBlock lub TextBox, w yśw ietlają łańcuchy znaków. Inn e, takie ja k ScrollViewer ,
w yśw ietlają zawartość ob ie ktu . N ie m n ie j je d n a k spotkałeś się ju ż z k o n tro lk a m i w yśw ietlającym i kolekcje, ta k im i ja k
ListBox oraz ComboBox. T o właśnie z tego względu .N E T F ra m e w o rk udostępnia klasę ObservableCollection<T>,
reprezentującą kolekcję opracowaną specjalnie po d kątem m echanizm ów w iązania danych. Pod w zględem sposobu
działania przyp om in a ona klasę List<T> (przekonasz się o tym na następnej stronie).

Utworzenie
powiązania między
POWIĄZANIE w ła ściw ością
« m ííím ím ííím ím ííím ííííííííííííííííííííííííííííí; Ite m sS o u rc e kontrolki
ItemsSource="{ B in d i n g } " Li'stBox oraz kolekcją
ObservableCollection
$ sp ra w i, że kontrolka
wy św ie tli w szystk ie
elem enty kolekcji.

Utwórz powiązanie w kodzie (bez użycia kodu XAML!)


Jeśli spróbujem y sprawdzić ko n tro lk ę , nie znajdziem y w niej właściwości o nazwie Binding. C # nie pozw ala pobierać
re fe re n cji do właściwości ob ie ktu , a je dyn ie do całego ob ie ktu . O kazuje się, że de fin iu ją c pow iązanie danych w kodzie
X A M L , określam y je , tworząc o b ie k t k la s y B in d in g , zawierający nazwę właściwości źródłow ej podaną w fo rm ie łańcucha
znaków. O to kod, k tó ry tw orzy ob ie kt Guy i wiąże go z k o n tro lk ą TextBlock o nazwie walletTextBlock , w ta k i sposób,
że właściwość Text k o n tro lk i będzie powiązana z właściwością Cash obiektu:
Is tn ie je klasa o nazwie DependencyProperty,
Guy joe - new Guy( Joe , 47, 325.50M); a klasa TextBlock defin iuje całą masę
Binding cashBinding = new Binding(); statycznych w ła ściw ości będących M ^ c z m i
cashBinding.Path = new PropertyPath("Cash"); tej kk'asy- J edna z nich nosi nazwę
TextProperty.
cashBinding.Source = joe;
walletTextBlock.SetBinding(TextBlock.TextProperty, cashBinding);
jesteś tutaj ► 543
Nie mogę objąć swojej ekscytacji

Kontrolki XAML mogą zawierać tekst... i nie tylko


Porozm aw iajm y tro ch ę o k o d zie z n a czn iko w ym X A M L (w k ońcu to jego rep re z e n tu je lite ra M w skrócie X A M L ,
a odnosi się on do wszystkich znaczników definiujących stro n ę) o ra z o k o d z ie u k ry ty m (ang. code-behind; czyli kodzie C #
stanow iącym uzupełnien ie k o d u X A M L i um ieszczanym w plikach .cs).

K iedy używasz k o n tro lek G rid lub S ta c k P a n e l , inne, um ieszczane w ew nątrz nich k o n tro lk i są zapisyw ane pom iędzy
ich znacznikiem otw ierającym i zam ykającym . W taki sam sposób m o żn a także p ostępow ać, używ ając innych kontrolek.
N a przykład w artość właściwości T e x t k o n tro le k T e xtB o x lub T e x tB lo c k m o żn a określić, zapisując łańcuch znaków
pom iędzy ich znacznikiem otw ierającym i zamykającym:
Ten z a p is j e s t odpowiednikiem
określenia w ła ściw ości Te x t.
<TextBlock>To jest prezentowany tekst</TextBlock>
W takim przypadku now e w iersze m ożna tworzyć, używ ając znaczników < L in e B re a k /> , a nie sym boli & # 1 3 ; . W obu
przypadkach sprow adza się to do zastosow ania znaku U n ico d e o w artości U + 0 0 1 3 , k tóry je st in terp re to w an y jak o znak
now ego w iersza. M ożna go tak że zapisać w form ie szesnastkow ej — & #xD ; ; z kolei sym bol &#xA3; pozw ala um ieścić
w łańcuchu znak £ (pam iętasz p ro g ram T ablica znaków ?).

<TextBlock>Pierwszy wiersz<LineBreak/>Drugi wiersz</TextBlock>


S próbuj dodać tę k o ntro lk ę T e x tB lo c k do k o d u strony, a n a stęp n ie w ybierz opcję E d it Text, by zm ienić jej zaw artość
i naciśnij kom binację klawiszy S h ift+ E nter, by d odać nowy w iersz. W efekcie ID E d o d a do strony następ u jący frag m en t
k odu X A M L :

<TextBlock>
<Run Text="Pierwszy wiersz"/>
<LineBreak/>
<Run Text="Drugi wiersz"/>
</TextBlock>
K ażde z tych trzech rozw iązań będzie w yglądało n a ek ran ie ta k sam o, lecz jed n o cześn ie spow oduje w ygenerow anie innego
grafu obiektów . K ażdy znacznik <Run> je st przekształcany w o d ręb n y o b iek t łań cu ch a znaków , a każdy z tych obiektów
m oże m ieć sw oją w łasną nazwę:

<Run Text="Pierwszy wiersz" x:Name="firstLine" />


Tej nazwy m ożna następ n ie użyć w kodzie C # obsługującym stro n ę X A M L do zm iany w yśw ietlanego łańcucha:

firstLine.Text = "To jest nowa zawartość pierwszego wiersza";


K ontrolki z zaw artością, tak ie ja k S c r o llV ie w e r , dysponują właściwością C o n te n t (a nie T e x t ), k tó ra działa w inny sposób
— m oże zaw ierać dow olne k ontrolki. A takich k o n tro le k z treścią je st całkiem sporo. Je d n ą z bardziej przydatnych jest
k o n tro lk a B o rd e r , której m o żn a używać, by dodaw ać tło lub obram o w an ie do innych k o n tro lek , k tó re n o rm aln ie ich nie
m ają, takich ja k T e x tB lo c k :

<Border Background="Blue"
BorderBrush="Green" BorderThickness="3">
</Border>
Kontrolka S cro llV ie w e r dziedziczy po (lo n te n fco n M ,'
czyli tej sam ej kontrolce, której uży te ś, by s tworzyć
obcych w grze Ratuj ludzi. Utworzona, przez C ieb\e
kontrolka ContentControl zaw ierała konfrofaę Grid,
a w niej trzy kontrolki E llip se .

544 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

■Nie .istnieją.
głupie pytania

P : Moja strona zawiera siatkę, w której jest umieszczona inna P : Dlaczego niektóre kontrolki, takie jak TextBlock,
siatka zawierająca StackPanel. Czy istnieje ograniczenie liczby mają właściwość Text , a niektóre właściwość Content?
kontrolek, które można umieszczać w innych kontrolkach?
OPonieważ mogą wyświetlać wyłącznie tekst; właśnie dlatego
:
O Nie. Można umieszczać kontrolki wewnątrz innych kontrolek,
: dysponują właściwością Text typu S trin g , a nie właściwością Content
a z kolei te w jeszcze innych. W dalszej części rozdziału nauczysz się typu o b je c t. Jest to tak zwana domyślna właściwość kontrolki.
tworzyć własne kontrolki, zaczynając od utworzenia kontenera W przypadku kontrolek takich jak Grid oraz StackPanel tą domyślną
i dodając do niego dalsze kontrolki. Na przykład siatkę można umieścić właściwością jest kolekcja C hildren.
w dowolnej innej kontrolce z treścią — zrobiłeś już to w grze Ratuj
ludzi, tworząc wroga z kontrolki Grid i trzech kontrolek E llip se . P : Czy powinienem wpisywać kod XAML ręcznie, czy raczej
To jedna z mocnych stron stosowania języka XAML do projektowania używać okna Designer IDE i przeciągać kontrolki z przybornika?
aplikacji — pozwala ona na tworzenie złożonych stron przy użyciu
prostych kontrolek. OWarto spróbować obu tych sposobów i wybrać ten, który Ci
:
bardziej odpowiada. Wielu programistów korzysta niemal wyłącznie
P : Gdybym mógł określić układ strony, używając kontrolki Grid z okna Designer, choć jest też sporo takich, którzy go niemal wcale nie
używają, gdyż przekonali się, że wpisywanie kodu XAML jest szybsze.
lub StackPanel, to której z nich powinienem użyć?
Dzięki technologii IntelliSense wpisywanie kodu XAML jest faktycznie
O : To w dużym stopniu zależy od sytuacji. Na to pytanie nie ma wyjątkowo łatwe.
jednej dobrej odpowiedzi: czasami lepszym rozwiązaniem będzie użycie
kontrolki S tack P an el, czasami Grid, a czasami połączenia ich obu. P : Przypomnijcie mi jeszcze raz, dlaczego miałem poznać
A nie są to bynajmniej nasze jedyne możliwości. Można skorzystać technologię WinForms? Czemu nie zacząłem od razu uczyć się
z kontrolki Canvas (której użyłeś w grze Ratuj ludzi), pozwalającej na języka XAML i tworzenia aplikacji dla Sklepu Windows?
wyświetlanie innych kontrolek w miejscach określonych przy użyciu
właściwości C anvas.L eft oraz C anvas.Right. Wszystkie te trzy OPonieważ istnieje wiele pojęć, których znajomość znacznie
:
kontrolki są klasami pochodnymi klasy Panel , a jedną z odziedziczonych ułatwia zrozumienie języka XAML. Na przykład przyjrzyjmy się kolekcji
po niej możliwości jest dodawanie i prezentowanie dowolnej liczby C hildren. Gdybyś nie rozumiał, czym są kolekcje, to czy odpowiedź
innych kontrolek. na trzecie pytanie zamieszczone na tej stronie miałaby dla Ciebie
jakikolwiek sens? Może. Jednak znacznie łatwiej ją zrozumieć, wiedząc
P : Czy to oznacza, że są kontrolki, w których można umieszczać czym są kolekcje. Z drugiej strony przeciąganie kontrolek z przybornika
i umieszczanie ich na formularzu jest naprawdę łatwe. Korzystanie
tylko jedną inną kontrolkę?
z technologii WinForms wymaga naprawdę znacznie mniej wiedzy niż
OOwszem. Spróbuj umieścić na stronie kontrolkę ScrollV iew er.
: projektowanie stron z użyciem języka XAML (co jest zrozumiałe, gdyż
A następnie spróbuj umieścić wewnątrz niej dwie inne kontrolki. XAML jest znacznie nowszą i znacznie bardziej elastyczną technologią).
Oto co zobaczysz: Dzięki temu, że poświęciliśmy kilka rozdziałów, prezentując WinForms,
nabrałeś nieco praktyki w projektowaniu aplikacji z graficznym
interfejsem użytkownika i tworzeniu interesujących projektów. A to
^ T h e p ro p e rty 'C o n te n t' is set m e r e th a n o nce. z kolei pozwoliło Ci przyswoić sobie wiele ważnych pojęć. Oprócz tego,
poznanie dwóch sposobów utworzenia tych samych projektów jest
Dzieje się tak dlatego, że w tym przypadku XAML określa wartość bardzo wartościowe. To właśnie z tego powodu wróciliśmy do kilku
właściwości Content kontrolki ScrollV iew er, a ta jest typu o b ject. projektów z poprzednich rozdziałów: poznając dwa sposoby napisania
Spróbuj jednak zastąpić kontrolkę S crollV iew er kontrolką Grid: tej samej aplikacji, będziesz mógł lepiej zrozumieć zarówno technologię
WinForms, jak i aplikacje dla Sklepu Windows.

Technologia WinForms jest doskonałym


Okaże się, że wszystko jest w porządku. W tym przypadku zagnieżdżone
narzędziem do nauki i poznawania języka
kontrolki są bowiem dodawane do kolekcji C hildren. (W grze Ratuj C#, jednak język XAML jest znacznie
ludzi używałeś tej kolekcji od dodawania wrogów). lepszym narzędziem do tworzenia
elastycznych i efektywnych aplikacji.

jesteś tutaj ► 545


Niechlujny Janek w Sklepie Windows

Użyj wiązania danych, by usprawnić aplikację Niechlujnego Janka


Pamiętasz pro gra m generujący m enu dla N iechlujneg o Janka, k tó ry napisałeś w rozdziale 4? Cóż, Janek
zainstalow ał sobie teraz system W indow s 8 i chciałby, żeby jego aplikacja do generowania m enu została
przystosowana dla Sklepu W indow s. Z ró b m y zatem to, o co prosi.

Oto strona, którą mamy zamiar stworzyć.


Będzie ona korzystać z jedn okie run kow e go w iązania danych, by w yświetlać in fo rm a cje w ko n tro lce L is tV ie w
oraz w jednym z o b ie któ w Run um ieszczonych w ew nątrz k o n tro lk i T e x tB lo c k oraz dw ukierunkow ego wiązania
danych w ko n tro lce TextB ox.

Witamy u Niechlujnego Janka


Wielkość menu

|io X W ygeneruj nowe menu

Będzie nam potrzebny obiekt,


którego właściwości użyjemy
do zdefiniowania powiązania. M enuM aker
NumberOfItems'»««
O b ie kt Page będzie zaw ierał instancję klasy -------
MenuMaker, k tó ra z k o le i dysponuje trzem a '"'Menu “
właściw ościam i pu blicznym i: właściwością lti GeneratedDate °Q
t
Num berO fItem s typ u i n t , kolekcją Menu % /c tT e +
typu O b s e rv a b le C o lle c tio n zawierającą UpdateMenu()
m enu oraz właściwością G en erated D a te
typu DateTime.
O 1
546 Rozdział 10.
Projektowanie aplikacji dla Sklepu Windows z użyciem XAML
M enultem to p ro ste obiekty
Obiekt Page tw orzy instancję z danymi, metoda ToStnngO
została przesłon ięta, by
klasy M enuM aker i używa jej odpowiednio generować te k st
M enultem
jako kontekstu danych. Meat w yśw ietlany w kontrolce
Condiment L is tV ie w .
W kon stru ktorze klasy Page, we
Bread
właściwości D a ta C o n te x t k o n tro lk i
S ta c k P a n e l, zapiszemy referencję do
o b ie k tu MenuMaker. Samo powiązanie
zostanie w całości zdefiniow ane
w kodzie X A M L .

£
. £>
ek t W ?

Kontrolka T e x tB o x korzysta
z dwukierunkowego powiązania,
by określać wielkość menu.
Oznacza to, że w ko n tro lce T e xtB o x nie trzeba
podawać właściwości x:Name. Ponieważ k o n tro lk a ta Dwukierunkowe
powiązanie
jest powiązana z właściwością Num berO fItem s o b ie ktu utworzone
MenuMaker, nie m usim y pisać żadnego kod u C # , w kontrolce TextBox
oznacza, że
k tó ry by się do niego odwoływał. początkowo zostanie
w niej wyświetlona
warto ść w łaściw ości
Num berOfItem s
oraz ż e później,
kiedy użytkownik
wprowadzi nową
warto ść w kontrolce,
wa rtość w łaściw ości
zostan ie odpowiednio
zmodyfikowana.

Także obiekty
L is tV ie w oraz
TextBlock zostały
powiązane
z właściw ościam i
obiektu
M enuMaker.

Przycisk powoduje aktualizację obiektu M enuM aker .


K likn ię cie przycisku pow oduje wywołanie m etody UpdateMenu()
o b ie ktu MenuMaker, k tó ra aktualizuje menu, usuwając całą zawartość
kolekcji O b s e rv a b le C o lle c tio n , a następnie dodając do niej nowe
ob ie kty MenuItem. K o n tro lk a L is tV ie w będzie automatycznie
' e k t T e -' aktualizowana zawsze, gdy zm ieni się zawartość kolekcji.

Oto w yzw an ie dla programistów. B azu jąc na podanych do tej pory inform acjach, powiedz, ja k w iele z nowej,
poprawionej aplikacji dla Niechlujnego Janka jesteś w stanie napisać bez zaglądania na następną stronę?

jesteś tutaj ► 547


Niechlujny Janek 2: legenda zakręconej frytki

r ó b to !
3 U tw órz nowy projekt i zastąp stronę MainPage.xaml stroną
utworzoną według szablonu Basic Page.
U tw ó rz nowy p ro je k t a p lik a c ji ty p u W indows Store. N astępnie usuń z niego p lik M ainPage.xam l , u tw ó rz now ą
s tro n ę n a p o d sta w ie sz a b lo n u B a sic Page i n a d a j je j n azw ę M ainPage.xam l . Po w prow adzeniu tych zmian
będziesz m usiał po no w nie zbudować p ro je k t. T o są do kła dnie te same czynności, k tó re wykonałeś, tworząc
aplikację R a tu j ludzi (zajrzyj do rozd zia łu 1., je śli musisz sobie odświeżyć pam ięć).

Dodaj nową, poprawioną klasę MenuMaker.


Przebyłeś ju ż długą drogę od m om entu, gdy zakończyłeś le ktu rę rozd zia łu 4. N apiszm y zatem p ra w id ło w o
herm etyzow aną klasę, k tó ra dzięki zastosowaniu właściwości p o zw o li nam zapisywać i pobierać określone
inform acje. W je j ko n stru kto rze utw orzym y kolekcję O b s e r v a b le C o lle c tio n o b ie któ w typu M enuItem , któ ra
będzie aktualizow ana za każdym razem, gdy zostanie w yw ołana m etoda U pdateM enu(). M e to d a ta będzie także
aktualizow ać właściwość G e n e ra te d D a te typ u DateTim e, określającą czas generacji bieżącego menu. D odaj
poniższą klasę MenuMaker do p ro je ktu :

u s in g S y s te m .C o lle c tio n s .O b je c tM o d e l; B ę dziesz potrzebował tej


Kliknij prawym
in strukcj i using, gdyż klasa
przyciskiem
m yszy nozwę c la s s MenuMaker { Obsery a bleCollection<T> została
projektu zdefiniowana w te j przestrzen i nazw.
wyświetloną p r iv a t e Random random = new Random();
w oknie Solution p r iv a t e L is t< S tr in g > meats = new L is t< S tr in g > ( )
Explorer i dodaj { "P ieczona w o ło w in a ", "S a la m i", " In d y k " , "Szynka "Karkowka" } ;
nową klasę, tak
p r iv a t e L is t< S tr in g > condim ents = new L is t< S t r in g > ( ) ■ ż ó łt a m u szta rd a ",
samo ja k robiłeś
to w innych "brązowa m u s z ta rd a ", "m usztarda m iodow a","m ajonez "p rz y p ra w a ","s o s f r a n c u s k i" } ;
projektach. p r iv a t e L is t< S tr in g > breads = new L is t< S t r in g > ( ) {" c h le b ryżo w y",
"c h le b b i a ł y " , "c h le b zbożow y", " p u m p e rn ik ie l" , "c h le b w ło s k i" " b u łk a " } :
W artości tych p u b lic O bse rva b le C o lle ctio n < M e n u Ite m > Menu { g e t; p r iv a t e s e t; }
w łaściw ości p u b lic DateTime GeneratedDate { g e t; p r iv a t e s e t; }
będziesz
w yśw ietlał na p u b lic i n t NumberOfItems { g e t; s e t ; }
stron ie, używając p u b lic MenuMaker() {
Nowa metoda C reateM enu Item () zwraca
wiązania danych. Menu = new O b se rva b le C o lle ctio n < M e n u Ite m > ()
W przypadku obiekt M enuItem , a nie łańcuch znaków.
NumberOfItems = 10; W ten sposób łatw iej nam będzie zm ieniać
w łaściw ości wygląd elementów listy , gdyby pojawiła s ię
NumberOfItems UpdateM enu();
taka konieczność.
u żyjesz }
powiązania p r iv a t e MenuItem CreateM enuItem () {
dwukierunkowego. s t r in g randomMeat = m e a ts [ra n d o m .N e x t(m e a ts .C o u n t)];
s t r in g randomCondiment = c o n d im e n ts [ra n d o m .N e x t(c o n d im e n ts .C o u n t)];
s t r in g randomBread = b re a d s [ra n d o m .N e x t(b re a d s .C o u n t)];
r e tu r n new MenuItem(randomMeat, randomCondiment, random Bread);
} .Przyjrzyj s i ę. d oM ad ..., jak « . ¡ a ta
p u b lic v o id UpdateMenu() {
M e n u .C le a r();
f o r ( i n t i = 0 ; i < NumberOfItems; i+ + ) { j ^ W c z a s o w i zaw artość,
M enu.A dd(C reateM enuItem ()); do niej nowe elem enty.
}
GeneratedDate = DateTime.Now;
Używajty p u Da te Time , b y operow ać na datach
}
T
p . s .e sta n ie, kiedy w łaściw ości t s i u n edo
NumberOfItems zo sta n ie przypisana
0 efiniu je on sta tż czną w łaściw ość o nazwie Now k « ra z w m ™ u ■
w artość m niejsza od zera.

z s ws ; , ¡aiksA ddSd r z ^
548 Rozdział 10.
Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

^ Dodaj klasę M e n u Ite m .


Przekonałeś się już, że przechowywanie danych w obiektach i klasach, a nie w łańcuchach znaków,
pozw ala tw orzyć bardziej elastyczne program y. O to prosta klasa służąca do przechow yw ania jednej
kan ap ki dostępnej w menu. D o da j ją do swojego p ro je ktu :

c la s s MenuItem { Trzy tańcuchy znaków określające


p u b lic s t r in g Meat { g e t; p r iv a t e s e t; } kanapkę s ą przekazywane do
k° nstruktora i zapisyw ane
p u b lic s t r in g Condiment { g e t; p r iv a t e s e t ; } w automatycznych właściw ościach
p u b lic s t r in g Bread { g e t; p r iv a t e s e t ; } przeznaczonych tylko do odczytu.

p u b lic M e n u lte m (s trin g m eat, s t r in g con dim ent, s t r in g bread)


Meat = meat;
Condiment = condim ent;
Bread = bread; Przesłoń metodę ToStnngO,
} aby obiekty M enuItem
wiedziały, w ja k i sposób
chcemy j e w y św ietlać.
p u b lic o v e rrid e s t r in g T o S tr in g () {
r e tu r n Meat + " , " + Condiment + + Bread;
}

U tw órz stronę XAML.


O to zrzut ekranu aplikacji. Czy p otrafiłbyś ją utworzyć, korzystając z k o n tro le k S tackP anel ? K o n tro lk a
T extB ox m a szerokość 100 pikseli. W dolnej kontrolce T e x tB lo c k został zastosowany styl B o d y T e x tS ty le ;
zawiera ona dwa znaczniki <Run> (w drugim jest wyświetlana wyłącznie data i godzina).
Tym razem nie dodawaj
żadnych danych
Witamy u Niechlujnego Janka przykładowych. Inform acje
do prezentacji zostaną
dostarczone przez
Nie zapomnij zmtennf n a g ^ k n stirony . p o ,^ ą
powiązanie danych.
celu zmień w artość z asobu A ppName> P °daną
w se k cji <Page.Resources> na gorzü stm ny .
Indyk, mafoncz. chłeb biały

jzynka, bryzowa musztarda, pumpemikiel

Szynka, sos francuski, chleb zbożowy

Salami, przyprawa, chleb ryżowy


To j e s t kontrolka L is tV ie w . J e s t ona bardzo podobna
Salami, musztarda miodowa, ctilcb włoski do kontrolki L is tB o x — w rzeczy w isto ści obie
dziedziczą po tej sam ej klasie bazowej, dlatego
Indyk, bryzowa musztarda, chleb włoski dysponują tymi samymi możliwościami zaznaczania
elementów lis t. Jednak aplikacje dla S klep u Windows
Szynka, brązowa musztarda, bułka korzystają zazw yczaj z kontrolek L is tV ie w , a nie
L is tB o x , gdyż sposób przew ijania ich zaw artości
Salami « x francuski, chleb włoski
(z pewną inercją) oraz inne cechy ich interfejsu
użytkownika bardziej przypominają „prawdziwe"
Karkówka. rrvyonez. bułka
aplikacje system u Windows 8. Robiąc ten z rzu t
Kjrkuwka. sos francuski, chleb włoski
ekranu, zaznaczyliśm y p ierw szy elem ent listy i, jak
widać, z o sta ł on zaznaczony w charakterystyczny
sposób.

Czy potrafisz samodzielnie stworzyć taką stronę, bazując wyłącznie na tym zrzucie ekranu?

jesteś tutaj ► 549


Powiązane i zdeterminowane

Do kodu XAML dodaj nazwy obiektów i pozostałe parametry wiązania danych


O to ko d X A M L , k tó ry należy dodać do strony MainPage.xam1. U p e w n ij się, że umieścisz go w zew n ę trzn e j
siatce b ezp o śred n io p rz e d k o m e n ta rz e m X A M L < ! - - B ack b u t to n and p a ge t i t l e --> , dokładnie
ta k samo ja k na stronie głów nej ap lika cji R a tu j ludzi. Przyciskowi nadaliśm y nazwę newMenu. Ponieważ
w ko n tro lka ch L is tV ie w , T e x tB lo c k oraz T e xtB o x w ykorzystaliśm y m echanizm w iązania danych, zatem
nie m usieliśm y określać ich nazw. (D odatkow e ułatwienie. Tak naprawdę nie m usieliśm y naw et określać nazwy
przycisku; zrobiliśmy to tylko p o to, by ID E mogło autom atycznie dodać do niego procedurę obsługi zdarzeń
o nazwie new M enu_Clicked, w w yniku dwukrotnego kliknięcia tej kontrolki. Przekonaj się sam!).

<StackPanel G rid .R o w = "l" M a rg in = "1 2 0 ,0 " x:Name=MpageLayoutS tackP anelM>


<StackPanel O r ie n ta tio n = " H o r iz o n ta l" M a rg in = "0 ,0 ,0 ,2 0 ">
Do odczytu i u sta w iania
<StackPanel M a rg in = "0 ,0 ,2 0 ,0 ">
liczby kanapek w menu
< T e xtB lo ck S ty le = "{S ta tic R e s o u rc e B o d y T e x tS ty le }" przy użyciu kontrolki
T e x t= "W ie lk o ś ć menu" M a rg in = "0 ,0 ,0 ,1 0 " /> TextBox konieczne było
<TextBox W idth= "100" H o r iz o n ta lA lig n m e n t= "L e ft" zastosow anie powiązania
T e x t= " {B in d in g NumberOfItem s, Mode=TwoWay}" /: dwukierunkowego.

Oto kontrolka < /S ta ckP a n e l>


L is tV ie w . < B utto n x:Name="newMenu" V e rtic a lA lig n m e n t= "B o tto m " C lick= "ne w M en u_C lick"
Sprób uj z a stą p ić C ontent="W ygeneruj nowe menu" M a rg in = "0 ,0 ,2 0 ,0 " />
j ą kontrolką < /S ta ckP a n e l>
L is tB o x < L is tV ie w Ite m s S o u rc e = "{B in d in g Menu}" M a rg in = "0 ,0 ,2 0 ,0 " />
i spraw dź,
< T e xtB lo ck S ty le = "{S ta tic R e s o u rc e C a p tio n T e x tS ty le }" > To w łaśnie w takich m iejscach
co s ię zmieni
na stron ie. <Run T ext= "D ata g e n e ra c ji menu: " /> przydają s ię z n a c z n i <Run>. _Dz'ęk '
<Run T e x t= " {B in d in g G e n e ra te d D a te }"/> nim w ystarczyło uży ć jedn ej _lioin^olh
< /T e x tB lo c k > TeXtBlock i zdefiniować pową^ante
jed yn ie z fragmentem te k stu .
</S tackP a ne l>

Do pliku MainPage.xaml.cs dodaj kod obsługujący stronę.


W ko n stru kto rze strony tw orzona jest kolekcja zawierająca całe m enu, o b ie k t MenuMaker oraz określany
kon tekst danych k o n tro le k w ykorzystujących m echanizm w iązania danych. W ym aga to dodatkow o
zdefiniow ania po la typ u MenuMaker o nazwie menuMaker.

MenuMaker menuMaker = new MenuMaker();


W pliku M ainPage.xam l.cs pojawiło s ię
pole typu M enuM aker, które posłu ży jako
p u b lic M ainPage() { kontekst danych dla kontrolki StackPanel
t h is . I n it ia liz e C o m p o n e n t ( ) ; zaw iera jącej w szy stk ie powiązane kontrolki
prezentujące dane na stron ie.
p a ge La youtS tackP a ne l.D ataC on te xt = menuMaker;

M usisz okre ślić kon tekst danych dla k o n tro lk i S ta c k P a n e l. D z ię k i tem u zostanie on przekazany do
wszystkich k o n tro le k umieszczonych w ew nątrz niej.

W końcu, d w u kro tn ie k lik n ij przycisk, aby wygenerować szkielet pro ced ury obsługi zdarzeń C lic k . O to cały
ko d obsługi tych zdarzeń — ogranicza się on do zaktualizow ania menu:
p r iv a t e v o id n e w M e n u _C lick(o b je ct sen de r, RoutedEventArgs e) {
menuMaker.UpdateMenu();

} Jest pewien sposób pozw alający na łatw ą zm ianę n azw y procedury obsługi
zd arze ń , ta k by jednocześnie zaktu alizo w ać zarów no kod C # ja k i X AM L.
Z a jrz y j do punktu 8 . dodatku „Po zostało ści” , by dowiedzieć się więcej
550 R ° z d z ia ł 10. o narzędziach refakto ryzacji dostępnych w V isual Studio IDE.
Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

A teraz uru cho m pro gra m ! Spróbuj zm ienić liczbę w p o lu T extB ox. W pisz w n im wartość 3 i k lik n ij przycisk
— pro gra m wygeneruje nowe m enu zawierające trzy kanapki.

W ita m y u N iechlujnego Janka


Wielkość menu

Wygeneruj nowe menu

Pieczona wołowina, so s francuski, chleb zbożow y

Szynka, majonez, bułka


Czy pam iętałeś, by zm ienić tańcuch zna.ków
AppName zdefiniowany w sekcji' <page .
indyk, brązowa musztarda, chleb biały
Resources> ? J e ś li nie, to je go kod X A M L
znajdziesz w punkcie 3 . na następnej kartce.
Data generacji menu: 10/3/2013 3:26:03 PM

T eraz możesz ju ż wypróbow ać pow iązania danych, by przekonać się, ja k bardzo są one
elastyczne. Spróbuj wpisać w p o lu tekstow ym „xyz” lu b zostawić je puste. N ic się nie stanie! Zw róć uwagę na
W pisując co ko lw ie k w ko n tro lce T e xtB o x, umieszczamy w niej łańcuch znaków. K o n tro lk a wygenerowaną datę. Nie
z mienia s i ę ona, choć
całkiem in te lig e n tn ie określa, co m a z tym łańcuchem zrobić. W ie, że ścieżka pow iązania menu j e s t aktualizowane.
ma postać N um berO fItem s, zatem przegląda kon tekst danych, sprawdzając, czy jest w nim Cóż, chyba wciąż
pozosta je nam je sz c z e
dostępna ja k a k o lw ie k właściwość o tej nazwie, a następnie stara się w m ożliw ie najlepszy
co ś do zrobienia.
sposób skonwertować łańcuch znaków na typ tej właściwości.

MOJA WŁAŚCIWOŚĆ TEXT


ZOSTAŁA POW IĄZANA Z WŁAŚCIWOŚCIĄ
NUMBEROFITEMS. NO I PATRZ, W MOIM
KONTEKŚCIE DANYCH JEST WŁAŚCIWOŚĆ
NUMBEROFITEMS! CZY MOGĘ ZAPISAĆ W NIEJ
ŁAŃCUCH ZNAKÓ W „3"? W YGLĄDA NA
TO, ŻE MOGĘ! ek t

H M M ... WŁAŚCIWOŚĆ
NUMBEROFITEMS MOJEGO KONTEKSTU
DANYCH JEST TYPU IN T, ALE NIE W IEM,
JAK SKONWERTOWAĆ ŁAŃCUCH „X Y Z "
N A LICZBĘ. W TAKIM RAZIE CHYBA LEPIEJ
NIC NIE BĘDĘ ROBIĆ.

ó 'e k t T e -n

jesteś tutaj ► 551


Umieść swoje dane w kontekście

Korzystaj z zasobów statycznych, by deklarować obiekty w kodzie XAML


T w orząc stronę w języku X A M L , w rzeczywistości tworzysz g ra f ob ie któ w , posługując się przy tym ta k im i o b ie kta m i
ja k S ta c k P a n e l, G rid , T e x tB lo c k czy też B u tto n . Jak się ju ż przekonałeś, nie ma w tym niczego magicznego
ani tajem niczego — dodanie do ko d u X A M L znacznika <TextB ox> sprawi, że w obiekcie strony po ja w i się pole typu
T e xtB o x, w któ rym zostanie zapisana referencja do in sta ncji klasy T extB ox. D o da jąc do tego znacznika właściwość
x:Name, sprawim y, że w kodzie u krytym będziem y m o g li używać tej nazwy, by odwoływać się do k o n tro lk i.

D o k ła d n ie w ten sam sposób m ożna tw orzyć instancje niem al wszystkich klas i zapisywać je w polach o b ie ktu strony.
Pozwalają na to ta k zwane zasoby sta tyczn e dodawane do kod u X A M L . Co w ięcej, m echanizm w iązania danych bardzo
dobrze w spółpracuje z ta k im i zasobami statycznym i, zwłaszcza je śli w ykorzystam y także okn o D esigner ID E . W ró ć zatem
do p ro gra m u dla N iechlujneg o Janka i przekształć o b ie k t MenuMaker w zasób statyczny.

Z PLIKU KODU U KRYTEGO USUŃ POLE MENUMAKER.


P : Ale zaraz! Ta aplikacja nie ma przycisku
Masz zam iar utw orzyć o b ie k t klasy MenuMaker oraz kon tekst danych Zamknij! W jaki sposób mogę z niej wyjść?
w kodzie X A M L , a zatem usuń z ko d u C # dwa w yróżnione wiersze:
O : Aplikacje przeznaczone dla Sklepu Windows
MenuMaker menuMaker ■ new MenuMaker ( ) ; domyślnie nie mają żadnego przycisku do zamykania,
gdyż zazwyczaj z większości spośród nich nigdy się
p u b lic MainPage() { nie wychodzi. Aplikacje tego typu działają zgodnie
t h is . I n it ia liz e C o m p o n e n t ( ) ; z cyklem życia aplikacji, który definiuje trzy stany:
nie działa, działa, wstrzymana. Aplikacje mogą zostać
pageLayoutStackPane l.D a ta C o n tex t ■ menuMaker ; wstrzymane, jeśli użytkownik z nich wyjdzie lub gdy
system Windows wykryje, że w urządzeniu zaczyna
}
brakować prądu. Oprócz tego, jeśli system będzie
musiał odzyskać pamięć, to może zakończyć działanie
aplikacji. W dalszej części książki nauczysz się
tworzyć aplikacje działające zgodnie z tym cyklem.

SPRAWDŹ, JAK WYGLĄDA PRZESTRZEŃ NAZW TW OJEJ APLIKACJI.


S pójrz na sam początek kod u X A M L strony, a przekonasz się, że w je j znaczniku otw ierającym jest
umieszczonych k ilk a właściwości xm ln s. Każda z nich d e fin iu je przestrzeń nazw. Poszukaj takiej,
k tó ra zaczyna się od x m ln s :lo c a l i odpow iada przestrzeni nazw p ro je ktu . Pow inna wyglądać m niej więcej tak:

To j e s t w ła ściw ość p rz e b rz m i J e ś li w artość przestrzen i nazw rozpoczyna s ię od „u sin g :", oznacza


nazw X M L . Z a w iera ona to, że odwołuje s ię ona do jedn ej z przestrzen i nazw dostępnych
łańcuch „xm lns:" zakończony w projekcie. M oże się także rozpoczynać od „ h ttp :/ / " , co będzie
identyfikatorem , którym w tym oznaczać standardową przestrzeń nazw X A M L .
przypadku j e s t J ocal .

xmlns:local="using:NiechlujnyJanekRozdzial10"

B ę d z ie sz uźywat tego identyfikatora T


podczas tworzenia obiektów Ponieważ nasza aplikacja nosi nazwę NiechlujnyJanekRozdzial10,
w Prz e strzeni nazw projektu.
zatem IDE u tw orzył tę przestrzeń nazw na nasze potrzeby.
Odszukaj przestrzeń nazw używaną w Twojej aplikacji,
gdyż to właśnie w niej będzie istniał obiekt MenuMaker.

552 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

DODAJ DO PLIKU XAML ZASÓB STATYCZNY I OKREŚL KONTEKST DANYCH.


Odszukaj w kodzie strony sekcję < P age .R eso urce s> i wpisz < lo c a l: — spowoduje to wyśw ietlenie
okien ka IntelliSense:

<Page. R esources> Zasoby statyczne można tw orzyć wyłącznie


w przypadku, gdy ich klasa definiuje konstruktor
< lo c a li
bezparametrowy. To ma sens! Gdyby konstruktor miał
O App
parametry, to skąd strona XAML miałaby wiedzieć,
O Menultem
jakie argum enty przekazać w jego wywołaniu?
o |

< !--TODO: Delete this line if the key AppName is declared in A p p . x a m l -->
<x:String x :Key="AppName">Witamy u Niechlujnego Danka</x:String>
< /P a g e . R esources>

W o k ie n k u zostaną w yśw ietlone wszystkie klasy dostępne w przestrzeni nazw, których możesz używać.
W ybie rz MenuMaker i nadaj je j nazwę menuMaker:

<local:MenuMaker x:Name="irienuMaker"/>

Teraz strona zaw iera statyczny zasób typ u MenuMaker o nazwie menuMaker.

OKREŚL KONTEKST DANYCH KONTROLKI STACKPANEL


I WSZYSTKICH KONTROLEK W EW NĄTRZ NIEJ.
Odszukaj zewnętrzną k o n tro lk ę S ta ckP a n e l i okre śl je j właściwość D a ta C o n te x t w następujący sposób:

<StackPanel Grid.Row=Ml " M argin ="1 20 ,0"


DataContext=”{StaticResource ResourceKey=menuMaker}”>

T w ó j pro gra m będzie działał do kła dnie ta k samo ja k wcześniej. A le czy zauważyłeś, co się stało w ID E , kie dy do
ko d u X A M L dodałeś kon tekst danych? G dy ty lk o to zrobiłeś, I D E u tw o rzyło instancję klasy MenuMaker i użyło
je j właściwości do określenia zaw artości powiązanych ko n tro le k. W okn ie Designer natychm iast p o ja w iło się
wygenerowane m enu — jeszcze zanim zdążyłeś u ru ch o m ić aplikację. N iezłe!

Wielkość menu

Wygeneruj n o w e m e n u W oknie D esigner natychm iast


zostanie wyśw ietlone wygenerowane
menu, je sz c z e zanim zdążysz
uruchomić aplikację.
Salami, sos francuski, chleb włoski

Karkówka, sos francuski, chleb ryżowy Indyk, sos francuski, chleb biały

Pieczona wołowina, sos francuski, pumpernikiel Szynka, majonez, chleb ryżowy

Indyk, przyprawa, chleb biały


H m ... co ś tu nie j e s t w porządku. Zosta ła
w yśw ietlona liczba kanapek w menu, samo
menu, ale nie data. O co chodzi?
jesteś tutaj ► 553
Zmień w ygląd listy

Wyświetlaj obiekty, używając szablonów danych


Podczas w yśw ietlania elem entów listy prezentowana jest zawartość k o n tro le k L is tV ie w Ite m (używanych w kontrolkach
L is tV ie w ), L is tB o x Ite m lub ComboBoxItem, powiązanych z obiektam i zapisanymi w kolekcji O b s e rv a b le C o lle c tio n .
Każda ko n tro lk a L is tV ie w Ite m w aplikacji N iechlujnego Janka jest powiązana z obiektem M enuItem przechowywanym
w ko le kcji Menu. D om yślnie ob ie kty L is tV ie w Ite m w yw ołują m etodę T o S t r in g ( ) obiektów MenuItem, możesz jednak
zastosować szablon danych, któ ry skorzysta z m echanizm u w iązania danych, by wyświetlać inform acje z właściwości
powiązanych obiektów.

Zmodyfikuj znacznik < L is t V ie w > , dodając do niego prosty szablon danych. Używa on
prostego wyrażenia { B i n d i n g } , by wywoływać metodę T o S t r in g ( ) elementów listy.

< L is tV ie w Ite m s S o u rc e = "{B in d in g Menu}" M a rg in = "0 ,0 ,2 0 ,0 "> Nie zm ieniaj nic wewnątrz
To naprawdę znacznika L is tV ie w , tylko
< L is tV ie w .Ite m T e m p la te >
prosty szablon za stą p /> znakiem > i dodaj
danych, co w ięcej, <DataTemplate> poniżej zam ykający znacznik
wygląda on bardzo < T e xtB lo ck T e x t= " { B in d in g } " /> </ListView>. N astępnie pomiędzy
podobnie do znacznikami L is tV ie w dodaj
</D ataTem plate> znacznik ListV iew .Item T em p la te
standardowego
szablonu < /L is tV ie w .Ite m T e m p la te > zaw ierający szablon danych.
używanego do < /L is tV ie w >
w yświetlania
elementów Dodanie {Binding} bez żadnej ś c ie ż ki p ° woduje
L istV ie w Ite m . wywołanie metody ToStringO powiązanego obiektu.

Zmień szablon danych, dodając do niego trochę kolorów.

<DataTemplate> M ożesz powiązać poszczególne z naczniki Run. M ożesz


£ -------------w nich zm ieniać czcionkę, kohr- oedz inne wła śc iw ośc i .
< T e xtB lock>
Zm ień znacznik
<Run T e x t= "{ S inding M ea t}" F oreground="B lue"/><R un T e x t= ", " />
<DataTemplate> wraz
z zaw artością, lecz <Run T e x t= "{ S inding B rea d}" F o n tW e ig h t= "L ig h t"/> < R u n T e x t= ", " />
re sztę znacznika <Run T e x t= "{ 3in d in g C ondim ent}" Foreground="Red" F o n tW e ig h t= "E x tra B o ld "/>
L is tV ie w pozostaw
bez zmian. < /T e x tB lo c k >
, chleb zbożowy, prz} 'prawa
</D ataTem plate>

, pumpernikiel, sos f ¡ncuski

Zaszalej! Szablon danych może zawierać zupełnie dowolne kontrolki.


<DataTemplate> Właściwość Content obiektu
<StackPanel O r ie n ta tio n = " H o r iz o n ta l" > DataTemplate może zaw ierać
tylko jeden obiekt, a zatem,
<StackPanel>
chcąc u m ieścić w szablonie
< T e xtB lo ck T e x t= " {B in d in g B re a d }"/> danych w ięcej kontrolek,
< T e xtB lo ck T e x t= " {B in d in g B re a d }"/> będziesz m usiał użyć
ja k ieg o ś kontenera, takiego
< T e xtB lo ck T e x t= " {B in d in g B re a d }"/> jak StackP anel.
< /S ta ckP a n e l>
< E llip s e F ill= " D a r k S la te B lu e " H e ig h t= "A u to " W idth= "1 0" M a rg in = "1 0 ,0 "/>
< B u tto n C o n te n t= "{B in d in g C ondim ent}" FontFam ily="Segoe S c r ip t " / >
< /S ta ckP a n e l>
pumpernikiel ------------------------
</D ataTem plate>
pumpernikiel W fn u ru ^ k i
pumpernikiel f
554 Rozdział 10.
Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

■Nie .istnieją.
głupie pytania

P : A zatem do określania układu mogę używać kontrolek O : Zastosowanie właściwości x:Key powoduje, że zasób statyczny
StackPanel lub Grid. Mogę tworzyć zasoby statyczne w kodzie jest dodawany do kolekcji Resources i kojarzony z podanym kluczem,
XAML lub używać pól definiowanych w kodzie ukrytym. Mogę natomiast nie jest tworzone pole (a zatem nie możesz użyć identyfikatora
ustawiać właściwości kontrolek lub używać wiązania danych. AppName w kodzie C#, musisz odwoływać się do takiego zasobu, używając
Po co jest tyle sposobów na realizację tego samego celu? kolekcji Resources). W razie użycia właściwości x:Name zasób jest
dodawany do kolekcji Resources, a oprócz tego jest dodawany jako
O: Ponieważ C# oraz XAML są niezwykle elastycznymi narzędziami pole do obiektu Page. To dzięki temu byłeś w stanie wywołać metodę
do tworzenia aplikacji. Ta elastyczność pozwala projektować bardzo UpdateMenu() na rzecz statycznego zasobu MenuMaker.
szczegółowe strony działające na wielu różnych urządzeniach z różnymi
ekranami. Dzięki nim dysponujesz obszernym zestawem narzędzi, P : Czy ścieżka powiązania musi wskazywać na właściwość
których możesz używać do tworzenia odpowiednich stron. Nie postrzegaj zawierającą łańcuch znaków?
zatem tej sytuacji jako źródła zamieszania, wyobraź sobie, że dysponujesz
wieloma opcjami, spośród których możesz wybrać te najlepsze. O: Nie. Można wiązać właściwości dowolnych typów, O ile tylko można
skonwertować typ właściwości źródłowej i docelowej, to powiązanie
P : Wciąż nie do końca rozumiem, jak działają zasoby statyczne. będzie działać. W przeciwnym razie dane zostaną zignorowane. Pamiętaj
także, że nie wszystkie właściwości kontrolek są łańcuchami znaków.
Co się dzieje, kiedy umieszczę znacznik wewnątrz sekcji
<Page.Resources>? Załóżmy, że w kontekście danych jest dostępna właściwość logiczna
0 nazwie EnableMyObject. Możesz ją powiązać z dowolną właściwością
O: Dodając tam znacznik, aktualizujesz obiekt Page. Odszukaj zasób typu logicznego, taką jak IsEnabled. W takim przypadku kontrolka
AppName, w którym podałeś nową wartość, by zmienić nagłówek strony: będzie aktywowana i dezaktywowana zależnie od wartości właściwości
EnableMyObject:
<x:String x:Key="AppName">Witamy u Niechlujnego Janka</x:String>
IsEnabled="{Binding EnableMyObject}"
A teraz przejrzyj kod, który IDE dodało podczas tworzenia szablonu Basic
Page, i sprawdź, gdzie ten zasób jest używany: Oczywiście, jeśli powiążesz ją z właściwością tekstową, to będzie
wyświetlany jedynie tekst True lub F alse (co, jeśli się nad tym nieco
<TextBlock x:Name="pageTitle" Grid.Column="1" zastanowić, będzie całkowicie zrozumiałe).
Text="{StaticResource AppName}"
Style="{StaticResource PageHeaderTextStyle}"/>
P : Dlaczego IDE wyświetla dane na formularzu, kiedy utworzę
zasób statyczny i powiązanie w kodzie XAML, lecz nie kiedy zrobię
Strona używa go, by określić tekst. A zatem, co się tak naprawdę to w kodzie C#?
dzieje? Możesz się o tym przekonać, używając IDE. Umieść punkt
przerwania w procedurze obsługi kliknięć przycisku, uruchom O: Ponieważ IDE rozumie kod XAML, zawierający wszystkie informacje
aplikację i kliknij przycisk. Do okna Watch dodaj wyrażenie niezbędne do utworzenia obiektów koniecznych do wyświetlenia strony
this.Resources["A ppNam e"] — zauważysz, że zawiera ono Kiedy tylko utworzyłeś w kodzie XAML zasób MenuMaker, IDE utworzyło
referencję do łańcucha znaków. W taki sposób działają wszystkie zasoby instancję tej klasy Nie było w stanie zrobić tego na podstawie instrukcji
statyczne — dodanie takiego zasobu do kodu powoduje utworzenie new w konstruktorze, gdyż mógł on zawierać wiele innych instrukcji
obiektu i zapisanie referencji do niego w kolekcji o nazwie Resources. 1także one musiałyby zostać wykonane. IDE wykonuje kod ukryty C#
wyłącznie w wyniku uruchomienia aplikacji. Jeśli jednak dodasz do
P : Czy mogę używać zapisu {StaticResource} we własnym strony zasób statyczny, to IDE go utworzy, tak samo jak tworzy instancje
kontrolek TextBlock, StackPanel i wszystkich innych. Ustawia także
kodzie, czy tylko w szablonach, takich jak Basic Page?
właściwości tych kontrolek, by je wyświetlić w oknie Designer, a zatem,
O : Oczywiście — możesz tworzyć i używać zasobów w taki sposób. kiedy określisz kontekst danych oraz ścieżki powiązań, także one zostaną
W szablonie Blank Page nie ma niczego szczególnego, podobnie jak użyte i w efekcie menu pojawi się w oknie IDE.
w innych szablonach używanych w tej książce. Zawierają one zwyczajny
kod C# i XAML i nie robią niczego, czego nie mógłbyś zrobić samemu.
Zasoby statyczne dodawane do strony
P : Użyłem właściwości x:Name, by określić nazwę zasobu są tworzone w momencie wczytywania
MenuMaker, natomiast zasób AppName używa właściwości x:Key.
Czym one się różnią?
strony, a później mogą być używane
przez dowolne obiekty aplikacji.
Nazwa „zasób statyczny" jest _niec°
myląca. Są one tworzone dla każdej
instancji i w żadnym przypadku nie jesteś tutaj ► 555
przypominają pó statycznych-
Z... z... z... zmiany

Interfejs INotifyPropertyChanged pozwala powiązanym obiektom


przesyłać aktualizacje
Kiedy klasa MenuMaker aktualizuje menu kanapek, powiązana z nią kontrolka ListView zostaje zaktualizowana. Jednak
dokładnie w tym samym momencie klasa MenuMaker zmienia także wartość właściwości GeneratedDate. Dlaczego
zatem nie jest aktualizowana powiązana z nią kontrolka TextBlock? Wynika to z faktu, że każda zmiana kolekcji typu
ObservableCollection powoduje zgłoszenie zdarzenia, które informuje dowolną powiązaną kontrolkę o pojawieniu się
zmian. W analogiczny sposób kliknięcie przycisku powoduje zgłoszenie zdarzenia Click, a upłynięcie czasu odmierzanego
przez obiekt Timer powoduje zgłoszenie zdarzenia Tick. Zawsze gdy dodasz lub usuniesz jakieś elementy kolekcji
ObservableCollection, zgłosi ona odpowiednie zdarzenie.

Możesz zadbać, by obiekty danych informowały o swoich zmianach właściwości docelowe i powiązane kontrolki. Należy
w tym celu zaimplementować interfejs INotifyPropertyChanged, zawierający tylko jedno zdarzenie: PropertyChanged.
Wystarczy zgłosić to zdarzenie po każdej zmianie wartości właściwości i obserwować, jak powiązana kontrolka zostanie
automatycznie zaktualizowana.

Obiekt danych zgłasza


zdarzenie PropertyChanged,
by powiadomić dowolną
powiązaną z nim kontrolkę
o fakcie zmiany wartości
właściwości. • • • • • • •

' zdarzenie1 IPropertyChanged


w

K O N TEK ST DANYCH

^ ° Ó /^ ^

Q Q . .

*
! < • • • •

Kontrolka odbiera zdarzenie i odświeża


swoją właściwość docelową, odczytując je
z powiązanej z nią właściwości źródłowej.

Kolekcje działają niemal w ten sam sposób co obiekty danych


W rzeczywistości kiasa O b s e r v a b le C o lle c tio n < T > nie implementuje interfejsu
lluiaęal I N o tify P r o p e r ty C h a n g e d . Zamiast tego implementuje ściśle z nim związany interfejs
INotifyCollectionChanged , który zamiast zdarzeń P ro p e rty C h a n g e d generuje
zdarzenia C o lle c tio n C h a n g e d . Kontrolka wie, że powinna oczekiwać na te zdarzenia, gdyż klasa
O b s e r v a b le C o lle c tio n implementuje interfejs I N o tify C o lle c tio n C h a n g e d . Zapisanie we
w łaściw ościD ataC ontext obiektu implementującego ten interfejs sprawi, że kontrolka będzie reagować
na zdarzenia C o lle c tio n C h a n g e d .

556 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Zmodyfikuj klasę MenuMaker, by informowała Cię, To pierwszy raz,


kiedy zgłaszasz
gdy zmieni się właściwość GeneratedDate
zdarzenia.
Interfejs INotifyPropertyChanged został zdefiniowany w przestrzeni nazw
Procedury obsługi zdarzeń piszesz już od
System.ComponentModel, a zatem powinieneś zacząć od dodania na samym
samego początku tej książki, jednak pierwszy
początku pliku klasy MenuMaker następującej instrukcji using: raz masz okazję samemu zgłaszać zdarzenia.
using System.ComponentModel; Wszystkiego na temat zgłaszania zdarzeń oraz
tego, jak one działają, dowiesz się w rozdziale
Zmodyfikuj klasę MenuMaker, dodając do jej deklaracji interfejsu 15. Jak na razie wystarczy, żebyś wiedział, że
INotifyPropertyChanged, a następnie skorzystaj z możliwości IDE, interfejs może deklarować zdarzenia oraz
by go zaimplementować: że Twoja metoda OnPropertyChanged()
class MenuMaker ; IN o tify P r o p e r ty C h a n g e d
jest zgodna ze standardowym wzorcem
{ o- ' przekazywania zdarzeń do innych obiektów,
Implem ent interface 'INotifyPropertyChanged' stosowanym w C#.
Explicitly im plem ent interface 'INotifyPropertyChanged'

Będzie to wyglądało nieco inaczej od przykładów, które widziałeś w rozdziałach 7. i 8. Nie będziesz musiał dodawać
żadnych metod ani właściwości. Zamiast tego dodasz zdarzenie:
public event PropertyChangedEventHandler PropertyChanged;

A następnie dodasz poniższą metodę OnPropertyChanged(), której będziesz używał do zgłaszania zdarzenia PropertyChanged:
private void OnPropertyChanged(string propertyName) j
PropertyChangedEventHandler propertyChangedEvent = PropertyChanged; To jest standardowy
wzorzec zgłaszania
i f (propertyChangedEvent != null) { zdarzeń stosowany
propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName)) ; w .NET Framework.
I
I

Teraz jedyną rzeczą, którą musisz zrobić, by poinformować powiązaną kontrolkę o zmianie właściwości, jest wywołanie
metody OnPropertyChanged() i przekazanie do niej nazwy właściwości, która uległa zmianie. Chcemy, by po każdej
zmianie menu była aktualizowana kontrolka TextBox powiązana z właściwością GeneratedDate; w tym celu wystarczy,
że dodamy do metody UpdateMenu() jeden wiersz kodu:
public void UpdateMenu() {
Menu.Clear();
for (in t i = 0; i < NumberOfItems; i++) {
Menu.Add(CreateMenuItem()); Nie zapomnij zaimplementować
I interfejsu IN otifyPropertyChanged .
GeneratedDate = DateTime.Now;
Mechanizm wiązania danych działa tylko wtedy,
gdy kontrolki im plem entują ten interfejs. Jeśli do
OnPropertyChanged(MGeneratedDateM) ;
deklaracji klasy nie dodasz : IN o tify P r o p e r ty C h a n g e d ,
}
to pow iązane kontrolki nie będą aktualizowane
Teraz po wygenerowaniu nowego menu powinna się
— i to naw et jeśli obiekt danych będzie zgłaszał zdarzenia
także zmienić data.
P ro p e rty C h a n g e d .

jesteś tutaj ► 557


Aplikacja Idź na ryby spotyka XAML

Zakończ przerabianie gry Idź na ryby! do postaci aplikacji dla Sklepu Windows. Będziesz musiał zmodyfikować
kod XAML przygotowany wcześniej w tym rozdziale, dodając do niego powiązania danych, skopiować wszystkie
klasy i typy wyliczeniowe z aplikacji napisanej w rozdziale 8. (lub pobrać je z serwera FTP wydawnictwa Helion)
Ćwiczenia i zaktualizować klasy Player oraz Game.

^ Dodaj istniejące pliki klas i dostosuj deklarowaną w nich przestrzeń nazw do bieżącej aplikacji.
Do swojego projektu dodaj następujące pliki, pochodzące z gry Id ź na ryby!, którą napisałeś w rozdziale 8.:
Values.cs, Suits.cs, Card.cs, D eck.cs, CardComparer_bySuit.cs, CardComparer_byValue.cs, G am e.cs oraz Player.cs.
Możesz skorzystać z opcji A d d Existing Item dostępnej w oknie Solution Explorer, jednak w każdym z tych plików
będziesz musiał zmienić przestrzeń nazw, dostosowując ją do przestrzeni używanej w projekcie (jak już robiłeś
w przypadku kilku innych projektów w tej książce).
Spróbuj zbudować projekt. W plikach G am e.cs oraz Player.cs powinny pojawić się błędy, przypominające te
przedstawione poniżej:

© 2 T h e typ e or nam espace nam e 'TextBex could not be fo u nd (are you m issing a using directive or an assem bly reference?)
© 3 T h e typ e or nam espace nam e 'TextBox' could not be fo u nd (are you m issing a using directive or an assem bly reference?)

.2 Usuń odwołania do wszystkich klas i obiektów WinForms i dodaj do klasy Game


odpowiednie instrukcje u sin g .
Nie znajdujemy się już w świecie WinForms, dlatego też musisz usunąć z plików Game.cs oraz Player.cs instrukcje using
System.Windows.Forms. Będziesz także musiał usunąć wszystkie odwołania do kontrolek TextBox. Oprócz tego konieczne
będzie dodanie do klasy Game interfejsu INotifyPropertyChaned oraz kolekcji ObservableCollection<T>, a to oznacza,
że na początku pliku Game.cs będziesz musiał dodać następujące instrukcje using:
using System.ComponentModel;
using System.Collections.ObjectModel;

^ Dodaj instancję klasy Game jako zasób statyczny i określ kontekst danych.
Zmodyfikuj kod XAML strony, dodając do niego instancję klasy Game jako zasób statyczny, i użyj jej jako kontekstu
danych dla siatki zawierającej stronę Idź na ryby!, którą przygotowałeś we wcześniejszej części rozdziału. Oto kod XAML
definiujący zasób statyczny: <local:Game x:Name="game"/>. Będziesz także potrzebował nowego konstruktora w klasie
Game, gdyż zasoby mogą być tworzone tylko wtedy, gdy klasa definiuje konstruktor bezargumentowy:
p u b lic Game() {
PlayerName = "Edek";
Hand = new O b s e rv a b le C o lle c tio n < s trin g > ();
ResetGam e();
}

[ 4) Do klasy Game dodaj publiczne właściwości, których użyjesz do zdefiniowania powiązań danych
z kontrolkami.
Oto właściwości, które powiążesz z kontrolkami na stronie:
p u b lic bool GamelnProgress { g e t; p riv a te se t; }
p u b lic bool GameNotStarted { get { re tu rn ¡Gam elnProgress; }}
p u b lic s t r in g PlayerName { g e t; s e t ; }
p u b lic O b serva b le C o llectio n < strin g > Hand { g e t; p riv a te s e t ; }
p u b lic s t r in g Books { get { re tu rn D e sc rib e B o o k s(); } }
p u b lic s t r in g GameProgress { g e t; p riv a te s e t ; }

558 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Użyj mechanizmu wiązania danych, by aktywować i dezaktywować kontrolki TextBox ,


ListBox oraz Button .
Chcesz, by pole tekstowe Im ię oraz przycisk Rozpocznij grę były aktywne wyłącznie wtedy, gdy gra jeszcze nie została
rozpoczęta, oraz by kontrola ListBox R ęka i przycisk Zażądaj karty były aktywne tylko wtedy, gdy gra została już
rozpoczęta. Dodasz do klasy Game kod określający wartość właściwości GamelnProgress. Przyjrzyj się właściwości
GameNotStarted. Przekonaj się, jak ona działa, a następnie dodaj następujące powiązania danych do kontrolek
TextBox, ListBox oraz dwóch kontrolek Button.
Każdej z nich
będziesz sEnabled= "{Binding Gam eInProgress}1 IsEn abled= "{Bin din g GameNotStarted}1
musiat użyć sEnabled= "{Binding Gam eInProgress}1 IsEn abled= "{Bin din g GameNotStarted}1
dwa razy. I

Zm odyfikuj klasę P layer , by zmuszała klasę Game do wyświetlania postępów w grze.


Wcześniejsza wersja klasy Player, używana w aplikacji WinForms, używała kontrolki TextBox przekazywanej
w wywołaniu jej konstruktora. Zmień ją, by przekazywana była referencja typu Game, zapisywana następnie
w polu prywatnym. (Spójrz na przedstawioną poniżej metodę StartGame(), by zobaczyć, jak ma być używany
ten nowy konstruktor podczas dodawania graczy). Odszukaj wiersze zawierające odwołania do kontrolki TextBox
i zastąp je wywołaniami metody AddProgress() obiektu Game.
Zm odyfikuj klasę Game.
Zmień typ wynikowy metody PlayOneRound() z bool na void, a następnie zmodyfikuj jej kod tak,
by postępy w grze były wyświetlane przez metodę AddProgress(), a nie poprzez odwoływanie się do kontrolki
TextBox. Jeśli gracz wygrał, wyświetl postęp gry, przywróć ją do stanu początkowego i zakończ działanie metody.
W przeciwnym razie odśwież kolekcję Hand i wyświetl informacje o kartach gracza.
Będziesz także musiał dodać lub zaktualizować poniższe cztery metody i określić, do czego one służą i jak mają działać.
p u b lic void StartGame() { p u b lic void ClearProgress() {
C le a rP ro g r e s s (); GameProgress = S trin g .E m p ty ;
GamelnProgress = t r u e ; OnPropertyChanged("GameProgress"
O nPropertyChanged("Gam eInProgress"); }
OnPropertyChanged("GameNotStarted");
Random random = new Random();
p la y e rs = new L is t< P la y e r > ();
players.A dd(new Player(PlayerN am e, random, t h i s ) ) ;
Będziesz także musiał zaimplementować
players.A dd(new P la y e r (" B a r t e k " , random, t h i s ) ) ; interfejs INotifyPropertyChanged
players.A dd(new P la y e r(" Ja n e k " , random, t h i s ) ) ; i dodać tę samą metodę
D e a l( ); OnPropertyChanged(), którą dodałeś
p la y e rs [0 ].S o rt H a n d ();
H a n d .C le a r(); do klasy MenuMaker. Używają jej
foreach (S trin g cardName in GetPlayerCardNam es()) zaktualizowane metody i będzie jej także
Hand.Add(cardName); używać metoda PullOutBooks().
i f (¡Gam elnProgress)
A d d P ro g re ss(D e scrib e P lay e rH an d s());
OnPropertyChanged("Books");
p u b lic void ResetGame() {
} GameInProgress = f a l s e ;
OnPropertyChanged("Gam eInProgress");
p u b lic void AddProgress(string progress)
OnPropertyChanged("GameNotStarted");
{
GameProgress = progress + books = new D ictio n a ry < V a lu e s, P la y e r> ();
Environment.NewLine + stock = new D e c k ();
GameProgress; H a n d .C le a r();
OnPropertyChanged("GameProgress"); }

jesteś tutaj ► 559


Rozwiązanie ćwiczenia

Poniżej przedstawiliśmy cały kod ukryty, który miałeś napisać:


private void startButton_Click(object sender, RoutedEventArgs e) {
Rozwiązania } 9 ame.StartGame();

CWICZen private void askForACard_Click(object sender, RoutedEventArgs e) {


i f (cards.Selectedlndex >= 0)
game.PlayOneRound(cards.Selectedlndex);
}
private void cards_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) {
i f (cards.Selectedlndex >= 0)
game.PlayOneRound(cards.Selectedlndex);
}
A oto zmiany, które miałeś wprowadzić w klasie Player:
class Player {
private string name;
public string Name { get { return name; } }
private Random random;
private Deck cards;
p riva te Game game;
public Player(String name, Random random, Game game) {
this.name = name;
this.random = random;
this.game = game;
th is.card s = new Deck(new Card[] { });
game.AddProgress(name + 11 przyłączył się do g ry ");
}
public Deck DoYouHaveAny(Values value)
{
Deck cardslHave = cards.PullOutValues(value);
game.AddProgress(Name + " ma " + cardslHave.Count + " " + C ard.P lural(value, cardslHave.Count));
return cardslHave;
}
public void AskForACard(List<Player> players, int mylndex, Deck stock, Values value) {
game.AddProgress(Name + " pyta czy ktoś ma 11 + C ard.P lural(value, 1 ));
int totalCardsGiven = 0;
for (in t i = 0; i < players.Count; i++) {
i f (i != mylndex) {
Player player = p la y ers[i];
Deck CardsGiven = player.DoYouHaveAny(value);
totalCardsGiven += CardsGiven.Count;
while (CardsGiven.Count > 0)
cards.Add(CardsGiven.Deal());
}
}
i f (totalCardsGiven == 0) {
game.AddProgress(Name + 11 pobrał kartę z k u p k i.");
cards.Add(stock.Deal());
}
}

/ / . . . reszta kodu klasy Player nie została zmieniona . . .

560 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Oto zmiany w kodzie XAML strony:


<Grid Grid.Row="l" Margin="120,0,60,60" DataContext="{Stat1cResource ResourceKey=game}" >
<TextBlock Text="Imię" M argin="0,0,0,20" Kontekstem danych dla siatki jest klasa
Style="{StaticResource SubheaderTextStyle}"/> Game, gdyż wszystkie powiązania
<StackPanel O rientation="H orizontal" Grid.Row="1"> odwołują ~się do~właściwości tej klasy.
Ta kontrolka <TextBox x:Name="playerName" FontSize="24" Width="500" MinWidth="300M
TextB°x używa Text="{Binding PlayerName, Mode=TwoWay}" IsEnabled="{Binding GameNotStarted}" />
p
dwwuli^'arnilk0wego <Button x:Name="startButton" Margin="20,0" IsEnabled="{Binding GameNotStarted}"
z właściwością Content="Rozpocznij g rę !" C lick= "sta rtB u tto n _C lick" />
PlayerName.
</StackPanel> To jest procedura obsługi zdarzeń Click przycisku,
<TextBlock Text="Postępy gry" który rozpoczyma grę.
Style="{StaticResource SubheaderTextStyle}" Margin="0,20,0,20" Grid.Row="2" />
<ScrollViewer Grid.Row="3" FontSize="24" Background="White" Foreground="Black"
Content="{Binding GameProgress}" /> -------
<TextBlock Text="Grupy" Style="{StaticResource SubheaderTextStyle}" Kontrolki ScrollViewer
Margin="0,20,0,20" Grid.Row="4"/> prezentujące postępy gry
oraz grupy są powiązane
<ScrollViewer FontSize="24" Background="White" Foreground="Black" z właściwościami
Grid.Row="5" Grid.RowSpan="2" Content="{Binding Books}" /> Progress oraz Books.
<TextBlock Text="Ręka" Style="{StaticResource SubheaderTextStyle}"
Grid.Row="0" Grid.Column="2" M argin="0,0,0,20"/>
<ListBox Background="White" FontSize="24" Height="Auto" M argin="0,0,0,20"
x:Name="cards" Grid.Row="1" Grid.RowSpan="5" Grid.Column="2"
ItemsSource="{Binding Hand}" IsEnabled="{Binding GamelnProgress}"
DoubleTapped="cards_DoubleTapped" />
<Button x:Name="askForACard" Content="Zażądaj ka rty" HorizontalAlignm ent="Stretch"
VerticalA lignm ent="S tretch" Grid.Row="6" Grid.Column="2"
Click="askForACard_Click" IsEnabled="{Binding GameInProgress}" />
<Grid.ColumnDefinitions>
<ColumnDefinition W idth="5*"/>
Właściwość IsEnabled aktywuje
<ColumnDefinition Width="40"/> i dezaktywuje kontrolkę. Jest to właściwość
<ColumnDefinition W idth="2*"/> typu Boolean, można ją zatem powiązać
z właściwością tego samego typu dostępną
</Grid.ColumnDefinitions> w kontrolce, by włączać ją i wyłączać na
<Grid.RowDefinitions> podstawie wartości właściwości.
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="150
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid>

jesteś tutaj ► 561


Rozwiązanie ćwiczenia

Poniżej przedstawiliśmy wszystkie zmiany, które należało wprowadzić w kodzie klasy Game,
włącznie z kodem, który podaliśmy już w instrukcjach do ćwiczenia.
Rozwiązania
ćwiczeń using System.ComponentModel;
using System.Collections.ObjectModel; Te instrukcje są niezbędne,
by móc korzystać z interfejsu
I NotifyPropertyChanged i klasy
class Game : INotifyPropertyChanged { Ot>servableCollection.
private List<Player> players;
private Dictionary<Values, Player> books;
private Deck stock;
p ub lic bool GameInProgress { get; p riv a te s e t; }
Te właściwości p ub lic bool GameNotStarted { get { retu rn !GameInProgress; } }
są używane p ub lic s trin g PlayerName { g e t; s e t; }
przez mechanizm p ub lic ObservableCollection<string> Hand { g e t; p riv a te se t; }
wiązania danych. p ub lic s trin g Books { get { retu rn DescribeBooks(); } }
p ub lic s trin g GameProgress { get; p riv a te s e t; }
To jest nowy konstruktor klasy
p ub lic Game() { Game. Tworzymy tylko jedną
PlayerName = "Edek"; kolekcję i czyścimy ją w momencie
Hand = new O bservableC ollection<string>(); przywracania początkowego stanu
ResetGame(); gry. Gdybyśmy tworzyli nowy
obiekt, to formularz straciłby
} odwołanie do niego, a aktualizacje
Te metody
zapewniają zostałyby przerwane.
działanie p ub lic void AddProgress(string progress) {
mechanizmu GameProgress = progress + Environment.NewLine + GameProgress;
wiązania OnPropertyChanged("GameProgress");
danych w grze.
Nowe wiersze }
są dodawane
na górze, p ub lic void ClearProgress() { Wszystkie programy, które napisałeś
dzięki czemu GameProgress = String.Empty; podczas lektury tej książki, można
wcześniejsze OnPropertyChanged("GameProgress"); przerobić na aplikacje dla Sklepu
zdarzenia są Windows napisane przy użyciu języka
przesuwane ku }
dołowi kontrolki
XAML. Można je jednak napisać
ScrollViewer. p ub lic void StartGame() { na bardzo wiele sposobów, co jest
ClearProgress(); szczególnie prawdziwe w przypadku
GamelnProgress = true ;
korzystania z języka XAML! To właśnie
dlatego pokazaliśmy Ci tak dużo kodu
OnPropertyChanged(MGameInProgressM);
w ramach opisu ćwiczenia.
z 71 OnPropertyChanged(MGameNotStartedM);
Oto metoda Random random = new Random();
StartGame(), którą players = new List< P la yer> ();
pokazaliśmy już players.Add(new Player(PlayerName, random, th is ) ) ;
wcześniej. Czyści players.Add(new PlayerC'Bartek", random, t h is ) ) ;
ona postępy gry,
tworzy graczy, players.Add(new Player("Janek", random, th is ) ) ;
rozdaje karty, D eal();
a następnie players[0].S ortH and();
aktualizuje Hand.Clear();
postępy gry
foreach (S trin g cardName in GetPlayerCardNames())
i 9rupy.
Hand.Add(cardName);
i f (¡GamelnProgress)
AddProgress(DescribePlayerHands());
OnPropertyChanged("Books");
}

562 Rozdział 10.


Projektowanie aplikacji dla Sklepu Windows z użyciem XAML

Ta metoda wcześniej zwracała wartość typu bool, dzięki której formularz mógł
aktualizować przebieg gry. Teraz musi ona jedynie wywoływać metodę AddProgress(),
a o resztę zatroszczy się mechanizm wiązania danych.
public void PlayOneRound(int selectedPlayerCard) {
Values cardToAskFor = players[0].Peek(selectedPlayerCard).Value;
for (int i = 0; i < players.Count; i++) {
i f (i == 0)
players[0].AskForACard(players, 0, stock, cardToAskFor);
else
players[i].AskForACard(players, stock);
i f (PullOutBooks(players[i])) {
AddProgress(players[1].Name + ma nową grupę");
int card = 1;
while (card <= 5 && stock.Count > 0) {
players[i].TakeC ard(stock.Deal());
card++;
I
I Grupy uległy zmianie, a formularz musi
OnPropertyChanged("Books"); o tym wjedzieć, by mógł zaktualizować
kontrolki ScrollViewer.
players[0].SortHand();
i f (stock.Count == 0) {
AddProgress("Na kupce nie ma już żadnych k a rt. Gra skończona!");
AddProgress("Zwyc1ęzcą J e s t... " + GetW1nnerName());
ResetGame();
re tu rn ;
Oto zmiany wprowadzone w metodzie
I PlayOneRound(), które aktualizują
I postęp gry, gdy została_on(j
Hand.Clear(); zakończona, bądź aktuahzują rękę
foreach (S trin g cardName in GetPlayerCardNames()) i grupy, jeśli gra jeszcze trwa■
Hand.Add(cardName);
i f (!GameInProgress)
AddProgress(DescribePlayerHands());
I

p u b lic void ResetGame() {


GamelnProgress = fa ls e ;
A to jest metoda ResetGame()
OnPropertyChanged("GameInProgress"); z instrukcji do ćwiczenia.
OnPropertyChanged("GameNotStarted"); Czyści °ma grupy, rękę i kupkę kart.
books = new Dictionary<Values, Player>();
stock = new Deck();
Hand.Clear();
} To jest standardowy
wzorzec użycia
p u b lic event PropertyChangedEventHandler PropertyChanged; właściwości
p riva te void OnPropertyChanged(string propertyName) { PropertyChanged,
przedstawiony już
PropertyChangedEventHandler propertyChangedEvent = PropertyChanged; wcześniej w tym rozdziale.
i f (propertyChangedEvent != n u ll) {
propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
}
}

II reszta kodu klasy Game nie została zmieniona

jesteś tutaj ► 563


564 R o zd ział 10.
11. Async, await i serializacja kontraktu danych

Przepraszam, że przerywam

Nikt n ie lubi być zm u szan ym d o o c z e k iw a n ia ... z w ła sz c z a użytkownicy.


Komputery są doskonałe w wykonywaniu wielu rzeczy jednocześnie, nie m a zatem żadnego
powodu, aby Twoje aplikacje nie mogły tego robić. W tym rozdziale dowiesz się, jak sprawić,
by dzięki zastosowaniu metod asynchronicznych Twoje aplikacje reagowały błyskawicznie.
Nauczysz się także korzystać z wbudowanych narzędzi do wybierania plików, wyświetlać
okienka z komunikatami oraz asynchronicznie zapisywać i odczytywać dane z plików
bez „zawieszania" aplikacji. Połączysz te wszystkie możliwości z serializacją kontraktu danych
i opanujesz tworzenie bardzo nowoczesnych aplikacji.

to je st n o w y ro z d z ia ł ► 565
Gdzie one się podziały?

Damian ma problemy z plikami


Damian przygotował swój kod XAML, określił powiązania danych i jest już gotów,
by zacząć przerabiać swój program do zarządzania wymówkami na aplikację dla Sklepu
Windows. Wszystko szło świetnie aż do momentu, gdy...

O
CHWILECZKĘ, CO JEST?
GDZIE SĄ KLASY DO
OBSŁUGI PLIKÓW?

SPRAWDZAŁEM WSZĘDZIE
W PRZESTRZENI NAZW SYSTEM.IO,
ALE NIE MOGĘ ZNALEŹĆ KLAS DO
OBSŁUGI PLIKÓW! NIBY JAK MAM TERAZ
ZAPISYWAĆ DANE W PLIKACH
I ODCZYTYWAĆ JE?

System.10.
BinaryReader
BinaiyWriter
{} Com pression
^ EndOf5treamException
FileNotFoundException
lnvalidDataExceptior
^5 IOExceptior ni. „„ ż«*.jJ*»#
« , M em oiyStream
Path

566 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

NIE MOGĘ
TAKŻE ZNALEŹĆ KLASY
BINARYFORMATTER. W JAKI SPOSÓB
MAM TERAZ SERIALIZOWAĆ SWOJE
O
OBIEKTY?

System.Runtime.Serialization.
C ol Iecti on Data C ontractAttri butę
ContractNam espaceAttribute
To wygląda całkiem obiecująco. DataC ontractAttri butę
DataC ontractResalver
DataC ontractSerializer
DataC ontractSerializerSettings
DataMemberAttribute
DateTim eForm at
# EmrtTypelnformation

APLIKACJE DLA SKLEPU WINDOWS


POPRAWIŁY WIELE MOŻLIWOŚCI, JAKIMI
DYSPONOWAŁEM, TWORZĄC APLIKACJE TYPU WINFORMS.
ZAŁOŻĘ SIĘ, ŻE TAKŻE W TYM PRZYPADKU SĄ DOSTĘPNE
JAKIEŚ DOBRE NARZĘDZIA... I DOBRE POWODY
UZASADNIAJĄCE BRAK WCZEŚNIEJSZYCH
ROZWIĄZAŃ.

A p lik a c je d la S kle p u W in d o w s d y s p o n u ją
d o s k o n a ły m i n a rzę d zia m i do o b s łu g i w e jś c ia -w y jś c ia .
Tworzone przez nas aplikacje dla Sklepu Windows muszą być intuicyjne, K’>edy zobaczysz
spójne i muszą błyskawicznie reagować na działania użytkowników. To wskaźnik
w kształcie
z tych powodów .NET Framework for Windows Store Apps zawiera klasy klepsydry, będzie
i metody pozwalające na wyświetlanie okienek dialogowych związanych ^ _ to oznaczało, że
używasz programu,
z wybieraniem plików oraz obsługę operacji wejścia-wyjścia w sposób który się zablokowa
asynchroniczny — co oznacza, że nie wstrzymują one działania aplikacji i przestał reagować
w czasie, gdy okienko jest widoczne lub podczas zapisywania pliku. Co a użytkownicy tego
nienawidzą! (Ty
więcej, dzięki przeprowadzaniu serializacji przy wykorzystaniu kontraktu również, prawda?).
danych, aplikacje mogą zapisywać pliki, na których można wygodniej
pracować i które są znacznie łatwiejsze do zrozumienia.

jesteś tutaj ► 567


Nie zmuszaj mnie do czekania

Aplikacje dla Sklepu Windows używają await,


by błyskawicznie reagować
Co się dzieje, kiedy w aplikacji typu WinForms wywołasz metodę MessageBox.Show() ? Wszystko się zatrzymuje,
a program przestaje odpowiadać aż do momentu zamknięcia wyświetlonego okienka dialogowego. Takie
rozwiązanie jest, w dosłownym znaczeniu, zaprzeczeniem programu, który dobrze reaguje na poczynania
użytkownika! A aplikacje dla Sklepu Windows zawsze powinny reagować błyskawicznie, nawet wtedy, gdy oczekują
na dane wprowadzane przez użytkownika. Jednak niektóre operacje, takie jak oczekiwanie na zamknięcie okienka
dialogowego, zapisanie lub odczytanie wszystkich bajtów z pliku — mogą zabierać dużo czasu. Sytuację, w której
jakaś metoda działa, zmuszając resztę aplikacji do oczekiwania na jej zakończenie, programiści określają terminem
zablokowanie. To właśnie ono jest głównym powodem, dla którego aplikacje nie reagują błyskawicznie na
działania użytkownika.

Aplikacje dla Sklepu Windows zapewniają sobie wrażliwość i szybkość reakcji, używając operatora await
oraz modyfikatora async. Możesz się przekonać, jak one działają, obserwując sposób wyświetlania okienka
dialogowego MessageDialog, które w żaden sposób nie blokuje działania aplikacji:

Obiekt ^ Skonfiguruj obiekt


MessageDialog M e s s a g e D ia lo g d i a l o g = new M e s s a g e D ia lo g ( " K o m u n ik a t" ) ; MessageDialog,
tworzy się przekazując do niego
tak samo d ia lo g .C o m m a n d s .A d d (n e w U IC om m and("O dpow iedź n r 1 " ) ) komunikat oraz
jak obiekty dodając odpowiedzi.
wszystkich d ia lo g .C o m m a n d s .A d d (n e w U IC om m and("O dpow iedź n r 2 " ) ) Każda odpowiedź
innych klas. d ia lo g .C o m m a n d s .A d d (n e w U IC om m and("O dpow iedź n r 3 " ) ) musi być obiektem
UICommand.
d ia lo g .D e f a u ltC o m m a n d ln d e x = 1 ;
UlCommand r e s u l t = aw ait d ia lo g .S h o w A s y n c ( ) a s UICommand;

Operator aw ait sprawia, że metoda, w której został umieszczony ten kod, zatrzyma się i będzie oczekiwać na
zakończenie wywołania metody ShowAsync() — a ona zostanie zablokowana aż do momentu wybrania przez
użytkownika jednego z poleceń. Jednak w tym samym czasie reszta aplikacji będzie reagow ać n a in n e zdarzenia.
Gdy tylko metoda ShowAsync() zostanie zakończona, zostanie wznowione wykonywanie metody, która ją
wywołała (choć może to nastąpić dopiero po zakończeniu obsługi innych zdarzeń, których wykonywanie zostało
rozpoczęte wcześniej).

Jeśli metoda używa operatora await, koniecznie musi zostać zadeklarowana przy użyciu modyfikatora async:

public async void ShowADialog() {


II... j a k i ś kod . . .
UICommand resu lt = await dialog.ShowAsync() as UICommand;
11 . . . d a lsz y kod:
}

Kiedy w deklaracji metody zostanie użyty modyfikator async, zyskasz możliwość określania, w jaki sposób metoda
ta będzie wywoływana. Możesz ją wykonać w standardowy sposób. W takim przypadku, gdy zostanie napotkany
operator await, realizacja wróci do kodu wywołującego, dzięki czemu aplikacja nie zostanie zablokowana.

568 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

M ożesz się sam em u p rzeko n ać, ja k to działa; wystarczy, że utw orzysz nowy p ro je k t B la n k App
i dodasz do niego następ u jący k o d X A M L: -Ą r ^
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Center"> V - Z r ó b to ! ^
<Button Click="Button_Click_l" FontSize="36">Czy je ste ś szczęśliwy?</Button>
<TextBlock x:Name="response" FontSize="36"/>
<TextBlock x:Name="ticker" FontSize="36"/>
</StackPanel>
A o to kod ukryty aplikacji. B ędziesz tak że m usiał d odać instrukcję using Windows.UI.Popups;,
gdyż do tej p rzestrzen i nazw n ależą klasy MessageDialog o raz UlCommand.

DispatcherTimer timer = new DispatcherTimer();


private void Button_Click_l(object sender, RoutedEventArgs e) {
timer.Tick += timer_Tick;
tim er.Interval = TimeSpan.FromMilliseconds(50);
tim er.S tart();
Spr<Sbuj przenieść wiersz timer.Stop()
w to miejsce. Zegar zostanie zatrzymany
od razu, gdyż sterowanie zostaje przekazane
int i = 0; do kodu wywotującego od razu po wywołaniu
void timer_Tick(object sender, object e) { kodu oznaczonego przez await.
ticker.T ext = "Chwila nr " + i++;
}
private async void CheckHappiness() {
MessageDialog dialog = new MessageDialog("Czy je ste ś szczęśliwy?");
dialog.Commands.Add(new UICommand("Szczęśliwy i radosny!"));
dialog.Commands.Add(new UICommand("Smutny jak listopadowa pogoda."));
dialog.DefaultCommandlndex = 1;
UlCommand result = await dialog.ShowAsync() as UlCommand;
i f (result != null && result.Label == "Szczęśliwy i radosny!")
response.Text = "Użytkownik je s t szczęśliw y.";
e lse
response.Text = "Użytkownik je s t smutny.";
tim er.Stop();
}
K iedy uruchom isz aplikację, przekonasz się, że licznik upływających chwil zm ienia wyświetlaną w artość, kiedy okienko
dialogowe jest widoczne. Tw oja aplikacja cały czas reaguje n a działania! Upływ ające chwile będą zliczane aż do
m om entu kliknięcia jed n eg o z przycisków w okienku dialogowym, kiedy to działanie m etody zostanie wznowione.

jesteś tutaj ► 569


W ybierz plik, dowolny plik

Używaj klasy FileIO do odczytywania i zapisywania plików


Aplikacje WinForms do odczytu i zapisu zawartości plików używają klasy S y s t e m . I O . F i l e , jednak jak już się przekonałeś,
klasa ta nie jest dostępna w wersji .NET Framework przeznaczonej dla aplikacji dla Sklepu Windows. I bardzo dobrze,
że nie jest! Gdybyś użył metody F i l e . W r i t e A l l T e x t ( ) , by zapisać gigantyczny plik, który zajmie znaczną część
twardego dysku, doprowadziłoby to do zablokowania aplikacji, która przestałaby reagować na działania użytkownika.

Do zapisu oraz odczytu danych z plików aplikacje dla Sklepu Windows używają klas Windows.Storage. Ta przestrzeń
nazw zawiera między innymi klasę FileIO, która, jak pokazuje okienko IntelliSense, dysponuje metodami, które mogą
nam coś przypominać.

FileIO.
© AppendLinesA sync Te metody przypominają metody dostępne
w klasie File. Na przykład klasa FileIO
© AppendTextAsync
definiuje metody AppendLinesAsync() oraz
© Equals ReadTextAsync(), a w klasie F ile są dostępne
ResclBufferAsync metody AppendLines() oraz ReadText().
Różnica polega na tym, że wszystkie metody klasy
ReadLinesAsync
FileIO zostały zdefiniowane z wykorzystaniem
ReadTextAsync modyfikatora async, a do wykonywania
Referent eEquals faktycznych operacji na plikach używają
© W riteEufferAsync
operatora await. Dzięki temu możemy pisać kod,
który operując na plikach, nie doprowadza do
© WriteEpytesAsync blokowania aplikacji.

Używaj specjalnych narzędzi do określania ścieżek dostępu do plików


Nie tylko klasa MessageBox pozwala na wyświetlanie okien dialogowych, powszechnie używanych w aplikacjach
WinForms. Istnieją także okna dialogowe służące do obsługi plików. Ich odpowiedniki są dostępne także
w aplikacjach przeznaczonych dla Sklepu Windows — pozwalają one na wybieranie plików i folderów, przy czym
działają asynchronicznie (czyli nie powodują blokowania aplikacji). Poniżej pokazaliśmy, w jaki sposób można
utworzyć i wyświetlić narzędzie F il e O p e n P i c k e r , by odszukać i otworzyć plik, a następnie odczytać całą
jego zawartość, używając metody R e a d T e x tA s y n c ( ) :
Właściwości narzędzia FileOpenpicker
można skonfigurować, używając
F ile O p e n P ic k e r p i c k e r = new F ile O p e n P icke r { inicjalizatora obiektu. W tym przypadku
zostało ono skonfigurowane tak, by pliki
ViewMode = P ic k e rV ie w M o d e .L is t,
były wyświetlane w formie listy oraz by
S u g g e s te d S ta r tL o c a tio n = P ic k e r L o c a t io n ld .D o c u m e n t s L ib r a r y początkowo prezentowana była zawartość
folderu dokumentów.
};
p ic k e r.F ile T y p e F ilte r.A d d (".tx t"); s —
Narzędzie FileOpenPicker dysponuje kolekcją
IS to ra g e F ile f i l e = a w a it p i c k e r . P i c k S i n g l e F i l e A s y n c ( ) ; o nazwie FileTypeFilter, określającą typy plików,
które można wybierać i wczytywać.
if (file != n u l l ) {
s trin g f i l e C o n t e n t s = a w a it F i le lO . R e a d T e x t A s y n c ( f ile )

}
Referencji typu IStorageFile można bezpośrednio
W przypadku wybrania jednego pliku zwracany użyć w wywołaniu FileIO.ReadTextAsync(), by
jest obiekt typu IStorageFile. Na kilku kolejnych odczytać zawartość pliku.
stronach dowiesz się o nim znaczmie więcej.
570 Rozdział 11.
Async, aw ait i serializacja kontraktu danych

Tak po wyświetleniu wygląda


strona narzędzi fíleOpenpickei-.

Z kolei narzędzie FileSavePicker pozwala na wybranie pliku do zapisu. Poniżej


pokazaliśmy, w jaki sposób, wraz z metodą FileIO.W riteTextAsync(), można go
użyć do zapisania tekstu w wybranym pliku:
Także Fi'leSavePicker zwraca obiekt typu
IS torageFile. Zawiera on wszystkie informacje
F ile S a v e P ic k e r p i c k e r = new F ile S a v e P ic k e r { niezbędne do odczytu lub zapisu zawartości J
D e f a u l t F il e E x t e n s io n = " . t x t " , w
w ^uL omon? * metody
3 ° +p? ekazl ćTbezp! ośredinio
WriteTextAsync().
S u g g e s te d S ta rtL o c a tio n = P ic k e r L o c a t io n ld .D o c u m e n t s L ib r a r y

};
p ic k e r.F ile T y p e C h o ic e s .A d d ("P lik tekstow y", new L i s t < s t r i n g > ( )
p ic k e r.F ile T y p e C h o ic e s .A d d ("P lik d z ie n n ik a ",
new L i s t < s t r i n g > ( ) { ".lo g ", ".d a t
I S t o r a g e F i l e s a v e F ile = a w a it p i c k e r . P ic k S a v e F ile A s y n c ( ) ;
if ( s a v e F i le == n u l i ) re tu rn ;
a w a it F i l e I O . W r i t e T e x t A s y n c ( s a v e F i le , t e x t T o W r i t e ) ;

SkyDrive -
Przejdź w gorę (§ )

jesteś tutaj ► 571


Podnieś swojej aplikacji poprzeczkę

Napisz nieco mniej prosty edytor tekstów Z r ó b to! ^

Spróbuj przero b ić prosty ed y to r tekstów z rozdziału 9. w aplikację dla S klepu W indow s. D o odczytu
i zapisu plików wykorzystasz w niej klasy FilelO , FileOpenPicker o raz FileSavePicker . Jed n ak
w pierw szej kolejności stworzysz stro n ę głów ną aplikacji. A poniew aż b ędzie to aplikacja dla Sklepu
W indow s dysponująca m ożliw ością odczytu i zapisu plików , p o w inna także dysponow ać paskiem
aplikacji z przyciskami O twórz i Z a p isz; d odasz go, korzystając z m ożliwości ID E .

K ontrolka AppBar przypom ina Scrol lViewer lub Border, gdyż m ożna w niej umieszczać inne kontrolki.
O prócz tego kontrolka ta wie, ja k pokazywać się i chować. D ziała zatem ja k wszystkie inne paski aplikacji.
A by ją dodać, wystarczy umieścić w kodzie strony sekcję <BottomAppBar> lub <TopAppBar>.

r® U tw órz now ą aplikację dla S klepu W indow s, używ ając p ro je k tu B la n k A p p , a n astęp n ie zastąp plik
M ainPage.xaml stro n ą u tw o rzo n ą n a podstaw ie szablonu B asic Page. O to zaw artość tej nowej strony:

<Grid Grid.Row="l" Margin="120,0,60,60">


<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="filename" Margin="10" Style="{StaticResource TitleTextStyle}">
że w polu

<TextBox x:Name="text" AcceptsReturn="True"


ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.H orizontalScrollBarVisibility="Visible wyświetlać poziome
TextChanged="text_TextChanged" /> i pionowe paski
przewijania. Te dwie
</Border> właściwości je włączają.
</Grid>

K liknij praw ym przyciskiem myszy text_TextChanged i z w yśw ietlonego m en u w ybierz opcję


*r i-'.;-¡^i i' i ' i.ii:.;!,.!!.!. W efekcie ID E w ygeneruje p ro c e d u rę obsługi zd arzeń TextChanged dla p o la tekstow ego.

Skorzystaj z o kn a D ocum ent Outline, by wybrać elem en t Page (lub zaznacz dow olną kontrolkę i kilkakrotnie naciśnij
klawisz E sc). Przejdź do o kna Properties, rozwiń sekcję C om m on i odszukaj w niej właściwość BottomAppBar:

J Common

Bottom AppBar New □

K liknij przycisk I. aby dodać p a se k aplikacji. K iedy to zrobisz, ID E d o d a do strony następ u jący frag m en t kodu:

<common:LayoutAwarePage.BottomAppBar>
<AppBar/>
</common:LayoutAwarePage.BottomAppBar>

572 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

3 Usuń znacznik <AppBar/> w edytorze XAML. Uzupełnij kod, dodając do niego element
<StackPanel> zawierający dwa przyciski: Otwórz i Zapisz:

<common:LayoutAwarePage.BottomAppBar>
<AppBar x:Name="bottomAppBar" Padding="10,0,10,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="openButton" Click="openButton_Click"
Style="{StaticResource OpenFileAppBarButtonStyle}"/>
<Button x:Name="saveButton" IsEnabled="false" ^ .
Pod stylami początkowo będzie
Click="saveButton_Click" wyświetlana niebieska falista
Style="{StaticResource SaveAppBarButtonStyle}"/> linia - zniknie, kiedy “ suniesz
</StackPanel> komentarze, w których style ą
umieszczone.
</AppBar>
</common:LayoutAwarePage.BottomAppBar>

O rany — wygląda na to, że nie ma dwóch statycznych zasobów: OpenFileAppBarButtonStyle oraz


SaveAppBarButtonStyle! Nie ma problemu. Szablon aplikacji B la n k A p p zawiera plik o nazwie
S tandardStyles.xa m l — zobaczysz go, kiedy wyświetlisz zawartość folderu C o m m o n w oknie Solution M anager.
Przeważająca większość zawartości tego pliku jest umieszczona w komentarzach, jednak nic nie stoi na
przeszkodzie, by wybrane style p rzen ieść p oza ko m en ta rze.

Wybierz z menu głównego opcję E D IT /F ind and Replace/Q uick F ind i odszukaj łańcuch znaków
OpenFileAppBarButtonStyle:

Upewnij się, że przeszukujesz całe rozwiązanie.

Naciskaj przycisk aż w końcu znajdziesz poniższy element <Styl e> w pliku StandardStyles.xaml:
<Style x:Key="OpenFileAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
<Setter Property="AutomationProperties.AutomationId" Value="OpenFileAppBarButton"/>

Dodaj sekwencje --> oraz < !--, by komentarz nie obejmował tego elementu (oznaczają one bowiem, odpowiednio:
początek i koniec komentarzy XML) i zmień nazwę przycisku na polską:
<Style x:Key="OpenFileAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
<Setter Property="AutonationProperties.AutomationId" Value="OpenFileAppBarButton"/>

To samo zrób ze stylem SaveAppBarButtonStyle. Wyszukaj go i zmień kod tak, by nie był umieszczony
w komentarzu.

Na koniec wybierz znacznik <AppBar> w oknie kodu XAML. Spowoduje to wyświetlenie paska aplikacji
w oknie do projektowania interfejsu użytkownika.

r j Wyświetl pasek aplikacji w IDE, zaznaczając


jego kod XAML; następnie kliknij dwukrotnie
każdy z przycisków, aby dodać do nich

O tw ó rz plik
®
Zapiw
.
procedury obsługi zdarzeń Click.

jesteś tutaj ► 573


Twój edytor w ygląda całkiem dobrze

[5^ Oto kod ukryty całego programu. Korzysta on z właściwości TextBox.Text , by modyfikować
Przepisanie programu,
tekst umieszczony w polu. Modyfikujemy w tym celu właściwość obiektu, rezygnując który napisałeś już
z korzystania z techniki wiązania danych. Robimy to celowo, by kod tej aplikacji był możliwie wcześniej, używając
jak najbardziej podobny do edytora, który napisałeś w rozdziale 9. Dzięki temu będziesz miał przy tym nowej
punkt odniesienia umożliwiający porównanie obu programów, na wypadek gdybyś chciał technologii, jest
doskonałym s po­
szczegółowo porównywać różnice pomiędzy programami WinForms oraz aplikacjami dla
sobem, by Twój m ó z g
Sklepu Windows. Będziesz także potrzebował poniższych instrukcji using , które powinieneś przyswoił sobie
umieścić na samym początku pliku: n o w y materiał.

using Windows.System;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Popups;

A oto i reszta kodu. W całości powinien się znaleźć w klasie MainPage:


Będziesz potrzebował tych trzech pól. _Poh logiczne są _
bool textChanged = fa lse ; używane do wyświetlania za nznaku „ . ^Obiekt
bool loading = fa lse ; IStorageFile śledzi edytowany i zapisywany pUk dzięki
Kiedy czemu nie trzeba wyświetlać okna do wyboru pliku.
w metodzie IStorageFile saveFile = n u ll;
jest używany
operator -p riva te async void openButton_Click(object sender, RoutedEventArgs e) {
await, wjej i f (textChanged) {
deklaracji MessageDialog overwriteDialog = new MessageDialog(
musi sie
"P lik zostaf zmieniony. Na pewno chesz wczytać nowy p lik ? " ) ;
pojawić
overwriteDialog.Commands.Add(new UICommand("Tak"));
modyfikator
overwriteDialog.Commands.Add(new UICommand("Nie"));
async.
overwriteDialog.DefaultCommandlndex = 1;
UlCommand re s u lt = await overwriteDialog.ShowAsync() as UlCommand;
i f (re s u lt != n u ll && resu lt.La b el == "Nie")
re tu rn ;
} J ^ n jakieś zmiany nie zostały zapisane, to przycisk Otwórz
plik wyświetla okno dialogowe. Jeśli użytkownik potwierdzi chęć
OpenFile() wczytania pliku, to wyw°ływana jest metoda OpenFile(), która
} wyświetla okno do wyboru pliku i wczytuje wybrany plik.

p riva te void saveButton_Click(object sender, RoutedEventArgs e)


S aveF ile();
Przycisk
Zapisz }
ogranicza
swoje p riva te void text_TextChanged(object sender, TextChangedEventArgs e)
działanie do
wywołania i f (loading) {
metody loading = fa ls e ; ' Kiedy tekst zostanie zmieniony, do nazwy pliku
SaveFile(). retu rn; należy dodać znak „*“ — jednak można to zrobić
} tylko raz. Zmiany tekstu są śledzone przy użyciu
pola textChanged.
i f (¡textChanged) {
filenam e.Text += " * " ;
saveButton.IsEnabled = true - Pole loading pozwala uniknąć dodawania
bezpośrednio po wczytaniu nowego pliku (gdyż wiąże
textChanged = tru e ; się to ze zmianą tekstu w polu czyU_ powoduje
wywołanie zdarzenia). Sprawdź, czy jesteś w stanie
zrozumieć jak ten kod działa.
}
574 Rozdział 11.
Async, aw ait i serializacja kontraktu danych

private async void OpenFile() {


FileOpenPicker picker = new FileOpenPicker {
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationld.DocumentsLibrary
};
picker.FileTypeFilter.A dd(".txt");
picker.FileTypeFilter.Add(".xml");
picker.FileTypeFilter.Add(".xaml");
IStorageFile f i l e = await picker.PickSingleFileAsync();
i f ( f i l e != null) {
string fileContents = await FilelO.ReadTextAsync(file);
loading = true; <-
text.T ext = fileC ontents; \
textChanged = fa lse;
filename.Text = file.Name; Metody OpenFile() oraz SaveFile() są naprawdę podobne d°
saveFile = f ile ; kodu przedstawionego na poprzedniej stronie. Wyświetlają
} odpowiednie okno dialogowe, a mastępnie używają metod
klasy FilelO, by wczytać lub zapisać plik.
}
private async void SaveFile() {
i f (saveFile == null) {
FileSavePicker picker = new FileSavePicker
DefaultFileExtension = '.tx t" ,
SuggestedStartLocation = PickerLocationld.DocumentsLibrary
};
picker.FileTypeChoices.Add("Plik tekstowy", new List<string>() { ".txt" });
picker.FileTypeChoices.Add("Plik XML ", new List<string>() { ".xml", ".xaml" });
saveFile = await picker.PickSaveFileAsync();
i f (saveFile == null) return
A b y wyświetlić pasek
}
aplikacjiwaktualnie
await FileIO.WriteTextAsync(saveFile, text.T ext); działającej aplikacji, wystarczy
await new MessageDialog("Zapisano plik " + saveFile.Name).ShowAsync(); jednocześnie nacisnąć klawisz
textChanged = false; W in d o w s oraz Z.
filename.Text = saveFile.Name;
}
Aplikacja gotowa. Uruchom ją! TERAZ JUŻ WIEM, JAK
MOGĘ UŻYWAĆ PASKA APLIKACJI,
OKIEN DIALOGOWYCH I PROGRAMOWANIA
ASYNCHRONICZNEGO, BY NAPISAĆ APLIKACJĘ DO
Prosty edytor tekstów ZARZĄDZANIA WYMÓWKAMI. JEDNAK WCIĄŻ BRAKUJE
MI KLASY BINARYFORMATTER. W JAKI SPOSÓB MAM
SERIALIZOWAĆ OBIEKTY WYMÓWEK?

ptan(jncMm

O
Pasek aplikacji możesz wyświetlić,
dotykając ekranu lub klikając aplikację
myszą, a następnie wykonując gest
przeciągnięcia od dołu strony w górę.
Możesz ta.kże użyć kombinacji klawiszy
Windows+Z.

® ®

jesteś tutaj ► 575


Coś więcej niż dane w plikach

CZYŻ NIE BYŁOBY CUDOWNIE, GDYBY ISTNIAŁ


JAKIŚ SPOSÓB ZAPISYWANIA OBIEKTÓW,
MAJĄCY WSZYSTKIE ZALETY SERIALIZACJI
BINARNEJ, A JEDNOCZEŚNIE ZAPEWNIAJĄCY
LUDZIOM MOŻLIWOŚĆ ŁATWEGO CZYTANIA
I MODYFIKACJI PLIKÓW?

Taki sposób istnieje! Jest to serializacja kontraktu danych.

Zapisywanie plików tekstowych jest świetne, gdyż wystarczy taki plik


otworzyć w Notatniku i można zobaczyć jego zawartość. Jednak pod
pewnym względem pliki tekstowe nie są najlepsze — trzeba napisać
naprawdę sporo kodu, by przeanalizować ich zawartość.

Serializacja binarna realizowana przez klasę BinaryFormatter jest


wspaniała, gdyż jest niesłychanie wygodna. Jednak także i ona ma swoje
wady! Pliki binarne są wrażliwe! Wystarczy wprowadzić w klasie jedną
niewielką zmianę i nagle okazuje się, że serializowanych obiektów nie
da się już wczytać! Poza tym widziałeś już bałagan, który pojawia się
w Notatniku po otworzeniu takiego pliku. Życzymy powodzenia, gdyby
ktokolwiek chciał własnoręcznie odczytywać lub edytować taki plik binarny.

Serializacja kontraktu danych jest optymalnym połączeniem obu tych


światów. Jest to prawdziwa serializacja, czyli pozwala zapisywać za jednym
zamachem całe grafy obiektów. Jednak generuje pliki XML, które można
naprawdę łatwo czytać, a nawet samodzielnie modyfikować (zwłaszcza jeśli
jesteśmy przyzwyczajeni do pracy z kodem XAML!).
W przypadku korzystania z serializacji
binarnej, w plikach zapisywane są
„czyste“ dane: bajty z pamięci zostają
pobrane, połączone i zapisane w pliku,
towarzyszą im jedynie niezbędne
informacje wymagane przez mechanizm
serializacji do określenia, które bajty
reprezentują składowe poszczególnych
klas w grafie obiektów. Jednak
niewielka zmiana w jednej klasie
powoduje, że zapisane bajty już nagle
nie pasują do składowych, a próba
deserializacji kończy się błędem.

576 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

Kontrakt danych jest abstrakcyjną


definicją danych obiektu

Kontrakt danych jest formalnym porozumieniem dodawanym do klasy. Tworzy się go


przy użyciu atrybutów [DataContract] oraz [DataMember], pozwalających dokładnie określić,
które dane będą odczytywane i zapisywane podczas serializacji.

Jeśli chcesz serializować instancje klasy, definiujesz jej kontrakt danych, umieszczając przed definicją klasy
atrybut [DataContract], a następnie poprzedzając składowe klasy, które chcesz serizalizować, atrybutami
[DataMember]. Poniżej przedstawiliśmy prostą klasę Guy z dodanym kontraktem danych:

using System.Runtime.Serialization;
Atrybuty [DataContract] oraz [DataMember] należą
do przestTzeni nazw System.Runtime.Serialization.
[DataContract]
class Guy { Atrybut [DataContract] tworzy
kontrakt danych tej klasy.
[DataMember]
public string Name get; private set; }

[DataMember] Każda składowa klasy, która ma


public int Age get; private set; } ■£- być zapisywana i odczytywana
podczas serializacji, jest dodawana
do kontraktu danych poprzez
[DataMember] zastosowanie atrybutu [DataMember].
public decimal Cash { get; private set; } W poniższym kodzie XML, przedstawiającym
element <Guy>, xmlns jest nazywane
public Guy(string name, int age, decimal cash) atrybutem, a nie właściwością. W plikach
Name = name; Age = age; Cash = cash; XAML można znaleźć znaczniki z atrybutami,
takimi jak Fill, Text oraz x:Name. W IDE są one
} określane jako właściwości (ang: properties),
gdyż określają cechy obiektów.

Serializacja kontraktu danych używa plików XML


Na szczęście wiesz już całkiem sporo o plikach XML, gdyż XAML jest językiem bazującym na XML-u.
We wszystkich plikach XML do definiowania danych używane są znaczniki otwierające, zamykające oraz
atrybuty. Każda składowa ma swoją nazwę, jednak sam kontrakt także jej potrzebuje — a precyzyjnie rzecz
ujmując, potrzebuje unikatowej przestrzeni nazw — gdyż mechanizm serializacji musi mieć możliwość
odróżnienia plików z danymi dla danego kontraktu od wszystkich innych plików XML. Poniżej zamieściliśmy
kod XML utworzony w efekcie serializacji obiektu klasy Guy. Jak zwykle, dodaliśmy do kodu odstępy i znaki
nowego wiersza, by ułatwić jego analizę:

<Guy xmlns="http://schemas.datacontract.org/2004/07/XamlGuySerializer"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Age>37</Age>
<Cash>164.38</Cash> Każda składowa z danymi
reprezentowana przez odrębny znacznik.
<Name>Joe</Name> Takie rozwiązanie jest nieporównanie
</Guy> bardziej czytelne niż serializacja binarna!

jesteś tutaj ► 577


Kolejne m etody asynchroniczne

Do odnajdywania i otwierania plików używaj metod asynchronicznych


Serializacja kontraktu danych działa bardzo podobnie do serializacji binarnej. Musisz otworzyć plik, utworzyć strumień
do odczytu lub zapisu, a następnie wywołać metodę do odczytu lub zapisu obiektów. Jednak pomiędzy oboma sposobami
serializacji istnieją także pewne różnice: w aplikacjach dla Sklepu Windows do otwierania plików używane są metody
asynchroniczne. Bazują one na interfejsach IStorageF ile oraz IStorageFolder. Możesz skorzystać z IDE, by przyjrzeć
się im i poznać ich składowe.

Przejdź do dowolnego miejsca kodu w jakiejkolwiek metodzie i wpisz Windows.Storage.IStorageFolder, następnie


kliknij IStorageFolder prawym przyciskiem myszy i wybierz opcję G o To D efinition (możesz także nacisnąć klawisz F12):

Class'!.c IStorageFolder [fro m m etad a ta ] @ íta X

-O Wind ows.Storage.IStorageFolder - tjJ GetltemsAsyncQ


□ [Assembly Windows.winmd, v255.255.255.255|
t
□ using System; Jeśli użyjesz opcji Go To Definition, by
using Windows.Foundation; uzyskać informacje na temat klasy lub
using Windows.Foundation.Metadata; interfejsu, których nie ma w bieżącym
To je s t deklaracja
interfejsu IStorageFolder projekcie, to IDE wyświetli zakładkę
□ namespace Windows .Storage
z prawej strony okna, tak jak pokazano
{
. . .p u b l i c
r
i n t e r f a c e IS t o r a g e F o ld e r ï IS t o r a g e lt e m
na tym zrzucie.

IAsyncOperation<StorageFile> CreateFileAsync(string desiredName);


IAsyncOperation<StorageFile> CreateFileAsync(string desiredName, CreationCollisionOption options);
IAsyncOperation<StorageFolder> CreateFolderAsync(string desiredName);
IAsyncOperation<StorageFolder> CreateFolderAsync(string desiredName, CreationCollisionOption options);
IAsyncOperation<StorageFile> GetFileAsync(string name);
IAsyncOperation<System.Collections.Generic.IReadOnlyList<StorageFile>> GetFilesAsync();
IAsyncOperation<StorageFolder> GetFolderAsync(string name);
IAsyncOperation<System.Collections.Generic.IReadonlyList<StorageFolder>> GetFoldersAsync();
IAsyncOperation<IStorageItem> GetItemAsync(string name);
IAsyncOperation<System.Collections.Generic.IReadonlyList<IStorageItem>> GetItemsAsync();

Każdy obiekt IStorageFolder reprezentuje folder dostępny w systemie plików i dysponuje metodami służącymi
do wykonywania operacji na plikach, takimi jak:
★ CreateFileAsync() — ta asynchroniczna metoda tworzy plik w folderze.
★ CreateFolderAsync() — ta asynchroniczna metoda tworzy jeden folder wewnątrz innego.
★ GetFileAsync() — metoda pobiera plik z folderu i zwraca obiekt IStorageFile.
★ GetFolderAsync() — metoda pobiera folder i zwraca obiekt IStorageFolder.
★ GetItemAsync() — metoda pobiera plik lub folder i zwraca obiekt IStorageltem.
★ Metody G etFilesA sync(), GetFoldersAsync() oraz GetItemsAsync() zwracają kolekcje elementów
— są to kolekcje bardzo prostego typu IReadOnlyList, który pozwala pobierać elementy na podstawie
indeksów, lecz nie udostępnia metod pozwalających na ich dodawanie, sortowanie ani porównywanie.

Aplikacje dla Sklepu Windows chronią nasz system plików


Zerknij ponownie do pierwszego przykładu przedstawionego w rozdziale 9. Ostrzegaliśmy w nim,
że najprawdopodobniej zapisywanie plików w folderze C :\ nie jest najlepszym pomysłem, mamy
zatem nadzieję, że wybrałeś bezpieczniejsze miejsce do zapisu swoich plików. Tradycyjne programy
„okienkowe" naprawdę bardzo łatwo mogą uszkodzić ważne pliki systemowe. To właśnie z tego
powodu każda aplikacja dla Sklepu Windows dysponuje swoim własnym folderem przeznaczonym
dla jej plików, w którym może je bezpiecznie zapisywać i odczytywać.
578 Rozdział 11.
Async, aw ait i serializacja kontraktu danych

Przestrzeń nazw Windows.Storage udostępnia jeszcze dwa inne interfejsy, pomagające


w zarządzaniu elementami systemu plików. Pierwszym z nich jest IStorageFile,
a obiekty, które go implementują, pozwalają (bo jakżeby inaczej!) przenosić, kopiować
i otwierać pliki. Jeśli uważnie przyjrzymy się deklaracji interfejsu IStorageFolder, IStorageItem
zauważymy, że rozszerza on interfejs IStorageltem. Interfejs IStorageF ile także Attributes
DateCreated
rozszerza IStorageltem, co jest uzasadnione, jeśli zastanowimy się nad operacjami, które
Name
można wykonywać zarówno na folderach, jak i na plikach — takimi jak: usuwanie, zmiana Path
nazwy, pobieranie nazwy, pobieranie daty utworzenia, ścieżki dostępu oraz atrybutów.
DeleteAsync()
GetBasicProperties-
Każda aplikacja dla Sklepu Windows dysponuje lokalnym folderem, w którym może
Async()
bezpiecznie zapisywać i odczytywać pliki, i który można pobrać w formie obiektu IsOfType()
IStorageFolder, używając właściwości ApplicationData.Current.LocalFolder. RenameAsync()

Następnie wystarczy użyć obiektu IStorageFile, by otworzyć plik do zapisu lub odczytu,
wywołując w tym celu metodę OpenAsync() (która zwraca obiekt IRandomAccessStream).

Kiedy już dysponujemy kontraktem danych i strumieniem, jedyną rzeczą niezbędną do zapisu
i odczytu obiektów w plikach XML będzie obiekt klasy DataContractSeri al i zer: IStorageFolder IStorageFile
ContentType
CreateFileAsync() FileType
using Windows.Storage;
Potrzebujesz tych CreateFolderAsync()
using Windows.Storage.Streams; instrukcji using. G etFileAsync()
CopyAndReplaceAsync()
CopyAsync()
using System.Runtime.Serialization; GetFolderAsync()
MoveAndReplaceAsync()
GetItemAsync()
MoveAsync()
GetFilesAsync()
OpenAsync()
Guy joe = new Guy("Joe" 37, 164.38M); GetFoldersAsync()
OpenTransactedWrite-
GetItem sAsync()
* Async()
Oto obiekt Guy z kontraktem danych, który przedstawiliśmy na poprzedniej stronie.
D ataC ontractSerializer s e r ia liz e r =
new DataContractSerializer(typeof(Guy));
IStorageFolder localFolder Obiekt przeprowadzający serializację musi
znać typ serializowanych obiektów. Oto jak
ApplicationData.Current.LocalFolder; każemy mu serializować obiekty Guy wraz
z całym grafem obiektów zależnych.

IS torageFile guyFile = await localFolder.CreateFileAsync("Joe.xml"


CreationCollisionOption.ReplaceExisting)
using (IRandomAccessStream stream =
await guyF ile. OpenAsync(FileAccessMode.ReadWrite))
using (Stream outputStream = stream.AsStreamForWrite()) {
r
Do metody CreateFileAsync() można
przekazać nazwę pliku oraz parametr
określający, by w przypadku gdy plik
s e r ia liz e r . W riteO bject (outputStream, joe); o takiej nazwie już istnieje, zastąpić
} go, otworzyć, zgłosić niepowodzenie lub
wygenerować nową, unikatową nazwę.
Guy copyOfJoe;
using (IRandomAccessStream stream =
await guyF ile. OpenAsync(FileAccessMode.ReadWrite))
using (Stream inputStream = stream.AsStreamForRead()) { i
\ Teraz, dysponując
copyOfJoe = s e r ia liz e r . ReadObject(inputStream) as Guy; strumieniem wejściowym
i wyjściowym, możesz
} wykonać serializację
obiektów.

jesteś tutaj ► 579


Jak aplikacje chronią Twoje pliki

Klasa KnownFolders ułatwia dostęp do najczęściej używanych folderów


Przestrzeń nazw Wi ndows. Storage zaw iera klasę KnownFol ders , której właściwości zapew niają nam możliwość KnownFolders
DocumentsLibrary
dostępu do biblioteki dokum entów , muzyki oraz pozostałych standardow ych folderów k onta użytkownika
HomeGroup
systemu Windows. Właściwość KnownFol d ers. DocumentLi brary zawiera obiekt typu StorageFol der MediaServerDevices
(im plem entujący interfejs I StorageFol der), którego m ożna użyć, by uzyskać dostęp do biblioteki dokum entów MusicLibrary
PicturesLibrary
bieżącego użytkownika. Inne właściwości pozwalają korzystać z folderów muzyki, zdjęć i w ideo oraz z dysków
RemovableDevices
przenośnych oraz urządzeń pełniących funkcję serwerów multimedialnych, jak rów nież z grupy domowej.
VideoLibrary

Jest je d n a k pew ien haczyk. A plikacje dla S klepu W indow s m ogą b ez ograniczeń zapisywać i odczytywać
pliki z folderu lokalnego; jeśli je d n a k aplikacja ta k a zechce skorzystać z innego fo ld eru , b ędzie jej trzeba
nad ać odpow iednie u praw n ien ia, dodając niezbędne możliwości do manifestu pakietu . Jeśli jaw nie
pozw olim y aplikacji odczytywać i zapisywać pliki w folderze lokalnym , to każdy, kto ją zainstaluje
ze Sklepu W indow s, będzie w stan ie zauważyć, że dysponuje o n a tym i możliw ościam i.

A by dodać do aplikacji możliw ość korzystania z biblioteki dokum entów , dw u k ro tn ie kliknij p lik Package.manifest
w oknie Solution Explorer, kliknij k a rtę Capabilities i zaznacz p o le w yboru D o cu m en t Library.

Stosow anieStorageFile - Patrkage.appxmanifest* ,X“ na czerwonym tle oznacza, że


P a c k a g e .a p p x m a n ife s t* -ft X w konfiguracji są jeszcze jakieś elementy,
które musisz określić. Wskaż ikonę
Zaznacz Th e properties o f the deploym ent package fo r yo u r app are contained in the app m anifest file. You can use
wskaźnikiem myszy, aby wyświetlić
th e Manifest Designer to set or m odify one or more o f th e properties.
pole wyboru komunikat, co jeszcze musisz zrobić.
Documents
Library, by Application Ul Capabilities Q Packaging

zapewnić
U ae this page to specify system feature] ^ the docum ents libiaty capability is declared, then one or m ore file type associations must be added in the Declarations tab.
aplikacji prawo
do odczytu
Capabilities: D e s c r ip tio n :
i zapisu plików
T h is capability is subject to Store policy. See "M ore Inforn
w folderze ocum ents Library
ch an g e or delete files in th e Docum ents Library fo r th e lo
biblioteki Q Enterprise Authentication Docum ents Library that are defined using the File Typ e A ;
dokumentów. 0 Internet (Client) Docum ent Libraries on HomeGroup PCs.

1 I Internet (C lient S i Server) M ore inform ation ^

Kliknij k artę D eclarations, z rozw ijanej listy w ybierz opcję File Type A ssociation i kliknij przycisk A d d . Spow oduje to
w yświetlenie form ularza, k tó reg o dw a p o la b ę d ą o znaczone znakam i „X ” n a czerw onym tle. W p o lu N a m e wpisz
x m l_ file type , a w p o lu File — .xml.

Nam e:

Edit f l a g s -----------y g r —

I | O pen is safe

I | A lw a y s un safe

Supported file ty p es — Możesz dodać także więcej


A t least o n e file ty p e m u s t be sup p o rted. Enter at least on e file typ e; fo r e xa m p le “ jp g "- takich skojarzeń, jeśli chcesz
odczytywać i zapisywać pliki
Supported file tvpe Remove] innych typów.
C o n ten t typ e:

File typ e: jcm l

“C .

Z ap isz m anifest p ak ietu i zam knij go. T e ra z T w oja aplikacja m oże odczytywać i zapisywać p lik .xm l w k atalo g u biblioteki
dokum entów użytkow nika.

580 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

W kodzie XML jest serializowany cały graf obiektów


Kiedy mechanizm serializacji kontraktu danych zapisuje jakiś obiekt, analizuje cały graf obiektów. W wyjściowym pliku
XML zapisywana jest każda instancja obiektu, w którego klasie został podany kontrakt danych. Postać wynikowego kodu
XML można modyfikować poprzez wybór przestrzeni nazw oraz określanie nazw składowych przy użyciu atrybutów
DataContract oraz DataMember.
[DataContract(Namespace = " h ttp://w w w .h eadfirstlab s.co m /C hapterll")]
c la ss Guy {
public G uy(string name, in t age, decimal cash){
Name = name;
Age = age;
Cash = cash;
TrumpCard = Card.RandomCard();
O to k o d ^ M L s e ria liz o w a n e j k la s y Guy:
}
<Guy
[DataMember] xmlns="h ttp://w w w .h eadfirstlab s.co m /C hapterll"
public s trin g Name { get; p riv a te s e t; } x m ln s:i= "http://www.w3.org/2001/XMLSchema-instance"
<Age>37</Age>
[DataMember]
public in t Age { get; p riv a te s e t; } <MyCard> Obiekt Guy zawiera
<Suit>Hearts</Suit> referencję do obiektu
[DataMember] <Value>Three</Value> Card w swoim kontrakcie
public decimal Cash { get; p riv a te set^ danych, dlatego został on
</MyCard> umieszczony w pliku XML
[DataMember(Name = "MyCard")]
<Cash>176.22</Cash> jako znacznik <Card>.
public Card TrumpCard [ get; s e t; } <Name>Joe</Name>
</Guy>
public override strin g ToStringO {
return S trin g . Format ("Mam na imię [ 0 ], [1] l a t , i^TJ” źTotycn "
+ "w k ie sz e n i, a moją atutową kartą je s t { 3 } 1
Name, Age, Cash, TrumpCard);
}
} [DataContract(Namespace = " http://w w w .h eadfirstlab s.com /C h apterll" )]
c la ss Card { ^
Nazwy składowych kontraktu nie muszą [DataMember] Oba kontrakty są
public S u its S u it { get; s e t; } umieszcz°ne w tej samej
odpowiadać nazwom właściwości. Ta przestrzeni nazw, która
klasa Guy definiuje właściwość o nazwie [DataMember] została p^ 0™ j ako wartoś.a
TrumpCard, jednak zastosowaliśmy parametr public Values Value { get; s e t; } w™ ciwo^ xr i nsznacznka
<Guy> w serializowanym
Name atrybutu DataMember, by zmienić kodzie XML.
public C ard (Suits s u it , Values value) {
nazwę na MyCard. I to właśnie ona pojawi się t h is .S u it = s u it ;
w kodzie XML serializowanego obiektu. th is .V a lu e = value;
}
Czy zauważyłeś, że serializowany plik XML
nie zaw iera typu Card? Dzieje się tak, p riv a te s t a t ic Random r = new Random();
public s t a t ic Card RandomCard() {
ponieważ możesz dodać te same atrybuty return new C a rd ((S u its )r.N e x t(4 ), (V a lu e s )r.N e x t(l, 1 4 ));
kontraktu danych do dowolnej klasy }
dysponującej odpowiednimi składowymi —
podobnie jak w przypadku właściwości Suit public s trin g Name {
get { return Value.ToStringO + " of " + S u it.T o S trin g O ; }
oraz Value klasy Card, które mechanizm
}
serializacji potrafił ustawić, używając takich
wartości jak Hearts lub Three, które public override s trin g ToStringO { return Name; }
porównał i dopasował do odpowiednich
typów wyliczeniowych.

jesteś tutaj ► 581


Ci faceci sobie poradzili

Prześlij kilka obiektów Guy do lokalnego folderu aplikacji Zrób to!


Oto projekt, który pomoże Ci poeksperymentować z serializacją kontraktu danych. Utwórz nową aplikację dla Sklepu
Windows i zastąp jej stronę główną plikiem wygenerowanym na podstawie szablonu Basic Page. Następnie otwórz plik
P ackage.m anifest, zezwól na dostęp do biblioteki dokumentów i dodaj rozszerzenie .xm l. Dodaj obie klasy z kontraktem
danych przedstawione na poprzedniej stronie (do każdej z nich będziesz musiał dodać instrukcję using System.Runtime.
S eria liza tio n ). W końcu, dodaj do projektu znane już typy wyliczeniowe Suits oraz Values (używane przez klasę Card).
Oto strona, którą niebawem stworzysz:

Serializacja o b ie k tó w Guy
Mam na imię Joe, mam 37 la l i 176,22 złotych w kieszeni, Mam na imię Bob, m am 45 lat i 4,6 8 złotych w kieszeni, a Mam na im ię Ed, m am 43 lat i 37,51 złotych w kieszeni, a
a m oją atutow ą kartą je s t Six o f C lubs m oją atutową kartą je s t Four o f Sp ades m oją atutową kartą je st Five o f Diamonds

Nazwa ostatniego zapisanego pWu

Dodaj do strony statyczny zasób GuyManager (i przy okazji określ nazwę aplikacji). Możesz już teraz dodać
Klasę GuyManager dodasz na następnej stronie. do projektu pustą klasę
GuyManager, aby pozbyć
<Page.Resources> się błędu wyświetlanego
przez IDE w tym
<local:GuyManager x:Name="guyManager"/> znaczniku — wypełnisz
ją na następnej stronie.
<x:String x:Key="AppName">Serializacja obiektów Guy</x:String> Nie zapomnij ponownie
</Page.Resources> zbudować rozwiązania po
dodaniu nowej klasy, by
usunąć błąd z okna do
n»Amkł^iiii/snin «tron

582 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

J2) Oto kod XAML strony:

< G rid G r id .R o w = "l" D a ta C o n te x t= " {S ta tic R e s o u rc e guyManager}" M arg in = "1 2 0 ,0 ">


< G r id . C o lu m n D e f in it io n s >
< C o lu m n D e f in it io n />
Strona składa Kontekstem danych
< C o lu m n D e f in it io n /> się z trzech
< C o lu m n D e f in it io n />
kontrolki Grid jest zasób
kolumn i dwóch statyczny GuyManager.
< /G r id . C o lu m n D e f in i t i o n s > wierszy.

< G r id . R o w D e f in it io n s >
< R o w D e f in it io n / >
< R o w D e f in it io n / >
< /G r id . R o w D e f in it io n s >

<StackPanel>
< T e xtB lo ck T e x t = " { B in d in g J o e }" S t y le = " { S t a t i c R e s o u r c e I te m T e x t S t y le } " Każda kolumna
M a rg in = "0 ,0 ,0 ,2 0 "/> w górnym wierszu
zawiera StackPanel,
< B utto n x:Name="WriteJoe" C o n te n t= "Z a p isz Joego" C l i c k = " W r i t e J o e _ C l i c k " / > w którym są
</S tackP a ne l> umieszczone
kontrolki TextBlock
<StackPanel G r id .C o lu m n = "l"> oraz Button.
< T e xtB lo ck T e x t = " { B in d in g Bob}" S t y le = " { S t a t i c R e s o u r c e I te m T e x t S t y le } "
M a rg in = "0 ,0 ,0 ,2 0 "/>
< B utto n x:Name="WriteBob" C o n te n t= "Z a p isz Boba" C l ic k = " W r i t e B o b _ C li c k " / >
</S tackP a ne l>
Ta kontrolka TextBlock jest powiązana
z właściwością Ed obiektu GuyManager.
<StackPanel G rid .C o lu m n = "2"> ^
< T e xtB lo ck T e x t = " { B in d in g Ed}" S t y le = " { S t a t i c R e s o u r c e I te m T e x t S t y le } "
M a rg in = "0 ,0 ,0 ,2 0 "/>
< B utto n x:Name="WriteEd" C o nten t= "Z ap is z Eda" C l i c k = " W r i t e E d _ C l i c k " / >
</S tackP a ne l>
Pierwsza komórka drugiego
wiersza zajmuje dwie kolumny.
<StackPanel G r id .R o w = "l" Grid.ColumnSpan="2" M a r g in = " 0 ,0 , 2 0 , 0 " > ^ ----- Umieściliśmy w niej kilka kontrolek
<TextBlock>Nazwa o s t a t n ie g o zapisanego p lik u < / T e x t B lo c k > powiązanych z właściwościami. Jak
<TextBox T e x t = " { B in d in g Path, Mode=TwoWay}" M a r g in = " 0 , 0 , 0 , 2 0 " / > sądzisz, _dJaczego ścieżkę ^ ępu
<TextB lock>Data u tw o rz e n ia < /T e x tB lo c k > rnfSw^hhSrn,, w konchę TextBox?
< T e xtB lo ck T e x t = " { B in d in g L a te s t G u y F ile . D a t e C r e a t e d .Lo ca lD a te T im e }" M a r g in = " 0 ,0 , 0 , 2 0 "
S t y le = " { S t a t i c R e s o u r c e S u b h e a d e rT e x tS ty le }" /> X.
<TextBlock>Typ z a w a rt o ś c i< / T e x t B lo c k > Kontrolkę można powiązać z właściwością
< T e xtB lo ck T e x t = " { B in d in g L a t e s t G u y F ile . C o n t e n tT y p e } " obiektu. Właściwość LatestGuyFile
S t y le = " { S t a t i c R e s o u r c e S u b h e a d e r T e x tS ty le }" /> jest obiektem typu IStorageFile,
</S tackP a ne l> a te kontrolki TextBlock zostały
powiązane z jego właściwościami.
<StackPanel G r id .R o w = "l" G rid .Colu m n="2">
< B utto n x:Name="ReadNewGuy" Content= "W czyta j o b i e k t Guy" Click="ReadNewGuy_Click"
M a rg in = "0 ,1 0 ,0 ,0 "/>
< T e xtB lo ck S t y le = " { S t a t i c R e s o u r c e I t e m T e x t S t y le } " M a r g in = " 0 ,0 , 0 , 2 0 " >
<Run>Nowy f a c e t : </Run>
<Run T e x t = " { B in d in g NewGuy}"/>
< /T e x tB lo c k >
</S tackP a ne l>
</ Grid> Jeszcze nie skończyliśmy — przewróć kartkę! ------

jesteś tutaj ► 583


Pomyśl o separacji zagadnień

Będziesz potrzebował tych instrukcji using System.ComponentModel;


ze względu na klasę GuyManager.
using Windows.Storage;
Dodaj klasę GuyManager. using Windows.Storage.Streams;
using System.IO;
class GuyManager : INotifyPropertyChanged
{ using System .R untim e.Serialization;
p riv a te IStorageFile latestG uyF ile ;
p u b lic IS torageFile LatestGuyFile { get { return latestG uyF ile ; } }

p riv a te Guy joe = new Guy("Joe", 37, 176.22M); Wartość pola wewnętrznego tej właściwości
p u b lic Guy Joe je s t określana przez metodę ReadGuyAsync(),
a kontrolki TextBlock zostały powiązar\e
{ z właściwościami DateCretóeii oraz
get { return jo e ; } ContentType.
}

p riv a te Guy bob = new Guy("Bob", 45, 4.68M);


To są trzy, przeznaczone tylko do
p u b lic Guy Bob
odczytu, właściwości typu Guy oraz
{ używane przez nie pola wewnętrzne.
get { return bob; }
}

p riv a te Guy ed = new Guy("Ed", 43, 37.51M);


p u b lic Guy Ed
{
get { return ed; }
}
Czwarta kontrolka TextBlock jest powiązana
z tą właściwością typu Guy, której wartość
p u b lic Guy NewGuy { get; p riv a te se t; } ¡c ' określa metoda ReadGuyAsync().

p u b lic s trin g Path { get; set; }

Możesz używać statycznej metody S torageF ile.


p u b lic async void ReadGuyAsync()
GetFileFromPathAsync(), by tworzyć obiekty
{ IStorageF ile na podstawie przekazanej ścieżki.
i f (String.IsNullOrW hiteSpace(Path))
re tu rn ;
latestG uyF ile = await StorageFile.GetFileFromPathAsync(Path)

using (IRandomAccessStream stream =


await latestGuyFile.OpenAsync(FileAccessMode.Read))
using (Stream inputStream = stream.AsStreamForRead())
{
D ataC ontractSerializer s e ria liz e r = new D ataC ontractS erializer(typeof(G uy));
NewGuy = serializer.R eadO bject(inputStream ) as Guy;
}
OnPropertyChanged( NewGuy ) ; Metoda ReadGuyAsync() używa ścieżki podanej
OnPropertyChanged("LatestGuyFile"); w kontrolce TextBox, by określić wartość pola
latestGuyFile (typu IStorageFile). Używa ona obiektu
DataContractSerializer, żeby wczytać obiekt z pliku XML,
a następnie wywołuje zdarzenia PropertyChanged dla
właściwości używających atrybutów IStorageFile.

584 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

To wywołanie tworzy w bibliotece dokumentów folder


o nazwie Faceci, którego aplikacja będzie używać
p u b l i c async v o id WriteGuyAsync(Guy guyToWrite)
' do przechowywania plików XML. Jeśli folder już
{ istnieje, zostanie otworzony.
IS to r a g e F o ld e r gu ys F o ld e r =
a w a it K n o w n F o ld e rs .D o c u m e n ts L ib ra ry .C re a te F o ld e rA s y n c ("F a c e c i",
C re a tio n C o llis io n O p tio n .O p e n lfE x is ts );

Ten kod
r la te s tG u y F ile =
a w a it g u y s F o ld er.C rea te F ile A sync(gu yT oW rite .N am e + " . x m l " ,
C re a tio n C o llis io n O p tio n .R e p la c e E x is tin g );
tworzy plik
XML, otwiera
strumień u sing (IRandomAccessStream stream =
i zapisuje
a w a it la te stG u yF ile .O p e n A s yn c (F ile A c ce ss M o d e .R e a d W rite ))
w nim graf
obiektu Guy. using (Stream ou tputStrea m = s tre a m .A s S tre a m F o rW rite ())
{
D a t a C o n t r a c t S e r i a l i z e r s e r i a l i z e r = new D a t a C o n t r a c t S e r i a l i z e r ( t y p e o f ( G u y ) ) ;
s e r i a l i z e r . W r i t e O b j e c t ( o u t p u t S t r e a m , g u y T o W rite );

Metoda WriteGuyAsync() zapisuje obiekt Gtuy


Path = l a t e s t G u y F i l e . P a t h ; w pliku XML umieszczonym w katahgu Faced,
w bibliotece dokumentów. We wtaściwości
latestGuyFile (typu IStorageFile) z a p i je ostatni°
O nPro pertyC h an ged ("Pa th "); używany plik, a następnie wywołuje zda-rzenie
O n P ro pertyC h an ged ("La testG u y F ile ") o zmianie właściwości używających tego pola.

p u b l i c event PropertyChangedEventHandler PropertyChanged;

p r i v a t e v o id O n P ro pertyC h an ged (string propertyName)


{
PropertyChangedEventHandler propertyChangedEvent PropertyChanged;
if (propertyChangedEvent != n u l l )
{
p r o p e r ty C h a n g e d E v e n t( th is , new PropertyChangedEventArgs(propertyName))

To ten sam kod, którego użyłeś wcześniej, by


zaimplementować
mplementować zdarzenia INotifuPropertuCh
INotifyPropertyChanged
i wywoływać zdarzenie PropertyChanged.

Oto procedury obsługi zdarzeń zaimplementowane w pliku MainPage.xaml.cs:

p r i v a t e v o id W r it e J o e _ C l i c k ( o b j e c t sender, RoutedEventArgs e) {
guyManager.WriteGuyAsync(guyManager.Joe);
}
p r i v a t e v o id W r i t e B o b _ C li c k ( o b je c t sender, RoutedEventArgs e) {
guyManager.WriteGuyAsync(guyManager.Bob);
}
p r i v a t e v o id W r i t e E d _ C l i c k ( o b j e c t sender, RoutedEventArgs e) {
guyManager.WriteGuyAsync(guyManager.Ed);
}
p r i v a t e v o id ReadNewGuy_Click(object sender, RoutedEventArgs e) {
guyManager.ReadGuyAsync();
}
jesteś tutaj ► 585
Czym je t zadanie w rzeczywistości?

Wypróbujmy działanie aplikacji


Użyj napisanej przed chwilą aplikacji, aby poeksperymentować z działaniem mechanizmu serializacji kontraktu danych:
★ Zapisz każdy obiekt Guy w folderze biblioteki dokumentów. Kliknij przycisk Wczytaj obiekt G uy, aby wczytać ostatnio
zapisanego faceta. Operacja skorzysta ze ścieżki zapisanej w polu tekstowym, więc możesz spróbować ją zmienić,
by wczytać innego faceta. Spróbuj wczytać plik, którego nie ma. Co się stanie?
★ Otwórz napisaną wcześniej aplikację prostego edytora tekstów. Do ekranów pozwalających na wybieranie pliku do
odczytu i zapisu dodałeś opcję pozwalającą na wybieranie plików XML. Możesz z niej teraz skorzystać, by otworzyć pliki
z serializowanymi obiektami Guy. Otwórz jeden z nich, zmień go, a następnie spróbuj wczytać w aplikacji Serializacja
obiektów G uy. Co się stanie, kiedy kod XML w pliku nie będzie prawidłowy? Co się stanie, gdy wartości koloru lub numeru
karty nie będą odpowiadały prawidłowym wartościom odpowiednich typów wyliczeniowych?
★ Twój Prosty edytor tekstów nie ma przycisku N o w y, który umożliwiałby utworzenie nowego pliku o początkowo
nieokreślonej nazwie. Czy potrafiłbyś go dodać? (Możesz także przywracać aplikację do stanu początkowego). Spróbuj
skopiować plik z serializowanym obiektem i zapisać go w nowym pliku umieszczonym w katalogu Faceci. Co się stanie,
kiedy spróbujesz wczytać taki plik w aplikacji Serializacja obiektów G u y?
★ Spróbuj dodawać i usuwać nazwy składowych, określane w atrybutach DataMember ([DataMember (Name="..." ) ] ) .
Jaki to będzie miało wpływ na postać kodu XML? Co się stanie, kiedy zmodyfikujesz kontrakt danych i spróbujesz wczytać
pliki zapisane wcześniej? Czy potrafisz poprawić kod XML tak, by ponownie można było wczytać plik?
★ Spróbuj zmienić przestrzeń nazw kontraktu typu Card. Co się stanie z kodem XML?

do obiektów do klasy strony Aby obiekty te mogły


być używane i wyświetlane w oknie projektanta, O : Wróćmy nieco i wyjaśnijmy, dlaczego
U : Tworząc aplikację Prosty edytor muszą zostać utworzone. Jeśli w klasie będącej przestrzenie nazw są niezbędne. Pliki C#, XML,
tekstów, nie określałem jej możliwości. typem zasobów statycznych zostały wprowadzone system plików Windows oraz strony WWW
Dlaczego zatem może zapisywać plik zmiany, to okno projektanta ich nie uwzględni, — wszystkie używają różnych (lecz często
w folderze biblioteki dokumentów? póki klasa nie zostanie ponownie skompilowana. wzajemnie powiązanych) systemów nazewniczych
do nadawania unikatowych nazw wszystkim
O: Kiedy aplikacja korzysta z narzędzia File Picker,
I to ma sens — IDE buduje projekt tylko wtedy,
gdy tego zażądamy, a dopóki tego nie zrobimy, klasom, dokumentom XML, plikom i stronom
użytkownik będzie miał dostęp do plików i folderów WWW, Dlaczego to jest ważne? Cóż, załóżmy, że
nie dysponuje skompilowanym kodem, którego
bez konieczności używania uprawnień w oknie w rozdziale 9. utworzyłeś klasę KnownFol ders,
potrzebuje do utworzenia instancji obiektów
manifestu pakietu, gdyż narzędzie to zostało która miała pomagać Damianowi w zarządzaniu
używanych jako zasoby statyczne.
stworzone z myślą o zachowaniu bezpieczeństwa folderami zawierającymi pliki wymówek. Ups
systemu plików, nie daje dostępu do folderów Możesz użyć IDE, by przekonać się, jak to działa.
A teraz okazuje się, że NET Framework także
instalacyjnych, folderów lokalnych, tymczasowych Otwórz ostatnią aplikację i zmodyfikuj metodę
definiuje klasę o tej samej nazwie. Nie przejmuj się.
oraz wielu innych ważnych folderów na dysku, G uy.ToString O tak, by do zwracanej wartości
Klasa KnownFol ders NET należy do przestrzeni
które aplikacja mogłaby przypadkowo uszkodzić. dodawała jakieś słowa. Następnie wróć do
nazw Windows.Storage, dzięki czemu bez
Uprawnienia trzeba stosować wyłącznie głównej strony otworzonej w oknie projektanta.
przeszkód może istnieć obok naszej klasy o tej
w przypadku pisania kodu, który chce bezpośrednio Wciąż będzie przedstawiać stare wartości
samej nazwie. A wszystko dzięki rozwiązywaniu
korzystać z wybranych miejsc na dysku. obiektów. A teraz wybierz z menu opcję BUILD/
niejednoznaczności.
Rebuild Solution. Okno projektanta zostanie
P: Czasami, gdy wprowadzę jakieś zaktualizowane bezpośrednio po zakończeniu Kontrakt danych także służy eliminowaniu
niejednoznaczności. W tej książce przedstawiliśmy
zmiany w kodzie XAML lub kodzie budowania projektu. Spróbuj wprowadzić jeszcze
jedną zmianę, lecz jeszcze nie buduj projektu. już kilka różnych wersji klasy Guy. A co gdybyś
aplikacji, IDE wyświetla komunikat
informujący o konieczności ponownego Zamiast tego dodaj jeszcze jedną kontrolkę chciał mieć dwie różne wersje kontraktu, by
jej zbudowania. O co w tym chodzi? TextBlo ck powiązaną z obiektem Guy. IDE serializować różne wersje klasy Guy? Wystarczy
będzie używać starej wersji obiektu aż do umieścić je w różnych przestrzeniach nazw,
O: Okno do projektowania stron XAML jest momentu ponownego zbudowania aplikacji. a wszelkie niejednoznaczności znikną. A to, że
będą one w innych przestrzeniach nazw niż klasy,
naprawdę inteligentne. Jest w stanie na bieżąco
wyświetlać aktualną postać strony w czasie P : Wciąż mam problemy ma sens — klasy i kontrakty danych nie mogą się
wprowadzania modyfikacji w kodzie XAML. z przestrzeniami nazw. Czym różni się bowiem pomieszać.
Już wiesz, że kiedy kod XAML używa zasobów przestrzeń nazw w programie
statycznych, powoduje to dodawanie referencji od tej w pliku XML?

586 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

Używaj klasy Task, by wywoływać jedną metodę asynchroniczną w innej


Kiedy oznaczysz jakąś metodę modyfikatorem async, to także inna metoda asynchroniczna może chcieć wywoływać ją,
używając operatora await. Jednak aby to zrobić, będziesz musiał wprowadzić w metodzie asynchronicznej jedną zmianę.
Spróbuj dodać do klasy GuyManager.cs następującą metodę:

private async void MethodThatReadsGuys()


{

W efekcie pojawi się błąd, oznaczony czerwoną, falistą linią; jednocześnie w oknie Error L ist zostanie wyświetlony
przydatny komunikat:

IDE precyzyjnie informuje,


co trzeba zrobić, żeby
rozwiązać problem.
(x ) 1 'SerializacjaO biektowG uy.G uyM anager.ReadG uyO ' does n o t re turn a Task and
c a n n o t be aw aited. Consider c h a n g in g it to re turn Task.

Aby jedna metoda asynchroniczna mogła wywołać drugą, wywoływana metoda musi zwracać obiekt klasy Task
(bądź klasy pochodnej dziedziczącej po Task<T>, jeśli metoda musi zwracać jakąś wartość). Ponieważ metoda ReadGuyQ
zwracała void, zatem aby rozwiązać problem, wystarczy zastąpić w jej deklaracji void typem Task.

public async Task ReadGuyAsync() Zgodnie z zalecaną konwencją nazewniczą nazwy metod
{ ^ asynchronicznych, które mają być wywoływane przy użyciu
operatora await, powinny się kończyć słowem Async. Dlatego też
// Kod z poprzedniej strony zmieniliśmy nazwę ReadGuy() na ReadGuyAsync().
}
Teraz metodę można już wywoływać przy użyciu operatora await i będzie ona działać jak wszystkie inne metody
asynchroniczne i przekazywać sterowanie w momencie rozpoczęcia operacji asynchronicznej. Gdyby metoda miała zwracać
jakąś wartość, powinna ona być typu Task<T>. Na przykład, gdyby metoda ReadGuyAsync() miała zwracać wczytany
z pliku obiekt Guy, to jej wartość zwracaną należałoby zadeklarować, używając typu Task<Guy>.

W RZECZYWISTOŚCI ZADANIE JEST CZYMŚ, CO TRZEBA WYKONAĆ. CZY ZATEM


OBIEKT TASK LUB TASK<T> JEST SPOSOBEM, DZIĘKI KTÓREMU METODA MOŻE ZWRÓCIĆ
PEWIEN RODZAJ OBIEKTU POZWALAJĄCY NA WYKONANIE OPERACJI?

Tak! Klasa Task reprezentuje operację asynchroniczną.


Modyfikator async, operator await oraz klasa Task znacznie ułatwiają tworzenie kodu asynchronicznego,
a wszystko dzięki temu, że znaczna część pracy związanej z przekazywaniem sterowania została
hermetyzowana w klasie Task. Skorzystaj z opcji Go To Definition, by wyświetlić jej właściwości i metody.
Definiuje ona takie metody jak: Run(), Continue() i Wait() oraz właściwości: IsCompleted oraz IsFaulted.
To powinno stanowić pewną podpowiedź odnośnie do tego, jak ta klasa działa za kulisami... i wszystkiego, co
robi automatycznie, by ułatwić nam pisanie asynchronicznych metod.

Więcej informacji na temat programowania asynchronicznego możesz znaleźć


na stronie: http://msdn.microsoft.com/pl-pl/library/vstudio/hh191443.aspx. jesteś tutaj ► 587
Zatroskani obywatele

Napisz dla Damiana nową aplikację do zarządzania wymówkami


Wiesz, jak tworzyć strony XAML, jak odczytywać i zapisywać pliki oraz jak serializować obiekty.
Nadszedł czas, by połączyć te wszystkie elementy w całość i przepisać aplikację do zarządzania
wymówkami Damiana w formie aplikacji dla Sklepu Windows.

Oto jej strona główna:

Menedżer w ym ów ek
Wymówka
Muszę zabrać mojego kota do zwierzęcego psychologa.

Ostatnio użyte
Ustaw na aktualny dzień i godzinę

Data pliku
2013-12-05 23:0ft37 +0KM

Nowa wymówka Folder Losowa Otwórz Zapisz Zapisz jako...


wymówka

Uruchom aplikację w symulatorze Visual Studio


Przedstawiony powyżej z rz u t stro n y wykonaliśmy w sym ulatorze wbudowanym w Visual Studio. Sym ulator ten jest aplikacją
„okienkową" instalowaną wraz z Visual Studio, k tó ra pozwala uruchamiać pisane aplikacje w trybie pełnoekranowym , na
symulowanym urządzeniu. Jest on naprawdę niezwykle wygodny, zw łaszcza gdy chcem y sprawdzić, jak aplikacja reaguje na
zdarzenia związane z dotykiem lub zdarzenia sprzętow e; a t o m o ż e się nam przydać podczas testow ania aplikacji.
(To symulator, a nie em u la to r).

Aby uruchomić symulator, kliknij strz a łk ę widoczną z prawej stro n y przycisku * Local Machlne * j wybierz opcję * Slm,,lati’[ *.
Gdy t o zrobisz, aplikacja zostanie uruchom iona w sym ulatorze, pokazującym jak działa w trybie pełnoekranowym i reaguje na
zdarzenia d o ty k u oraz inne zdarzenia sprzętow e.

Więcej informacji o sym ulatorze m o żesz znaleźć na stronie http://m sdn.m icrosofi.com /pl-pl/library/windows/apps/
hh441475.aspx.

588 Rozdziału.
Async, aw ait i serializacja kontraktu danych

Odrębna strona, wymówka i ExcuseManager


ExcuseM anager
Obiekty Excuse stosowane w poprzedniej wersji aplikacji wiedziały, jak mają się zapisywać i wczytywać.
Trzeba przyznać, że to dobry sposób projektowania obiektów. Projektując aplikacje, można jednak
zastosować także inne rozwiązania. Aplikacja do serializacji obiektów Guy przechowywała informacje NewExcuseAsync()
o facetach w jednej klasie, natomiast metody do ich odczytu i zapisu zostały zdefiniowane w klasie SetToCurrentTime()

GuyManager. Ten sam wzorzec zastosujesz, pisząc nową aplikację dla Damiana. ChooseNewFoIderAsync()
O penExcuseAsync()
OpenRandomExcuseAsync()
To kolejny przykład zasady projektowej nazywanej separacją zagadnień, o której wspominaliśmy
SaveCurrentExcuseAsync()
w rozdziałach 5. i 6. Klasa Guy musi jedynie udostępnić kontrakt danych, natomiast podjęcie decyzji, UpdateFiIeDateAsync()
co z tym kontraktem zrobić, należy już do innej klasy, takiej jak GuyManager. A żadna z tych klas nie SaveCurrentExcuseAsAsync()
ma nawet wiersza kodu związanego z aktualizacją interfejsu użytkownika, gdyż to, czym się zajmują, W riteExcuseAsync()
R eadExcuseAsync()
nie ma nic wspólnego z wyświetlaniem wymówek — to leży w gestii obiektu M ainPage .

Główna strona nie zawiera żadnego


kodu służącego do wyboru folderów lub Kontrolki umieszczone
odczytywania bądź zapisywania plików. na stronie są powiązane
Cały ten kod jest hermetyzowany z obiektem ExcuseManager,
w klasie ExcuseManager. który obsługuje odczyt
i zapis obiektów Excuse.

Procedura obsługi i
zdarzeń Click wywołuje
metodę ChooseNewFolder()

e /fte ^ 0
K m M M umieszczone na tej stronie wyświetlają dane,
^r-zystiając z wiązania danych. W polach tekstowych
zostały zast°s° wane powiązania dwukierunkowe
^rntująp^ się bezpośrednio do bieżącego obiektu Excuse,
u ^ ^ p m 'm ^ przez obiekt ExcuseManager.

W IĄ Z A M I-
D W U K IE R U N K O W I \
Właściwość
Results

ekt

WYSIL _____
SZARE KOMÓRKI
Klasy E xcuse oraz E xcuseM anager nie mają żadnego kodu związanego z aktualizacją interfejsu użytkownika. Powinieneś
także wiedzieć, że serializacji kontraktu danych oraz technik programowania asynchronicznego można także używać, pisząc
tradycyjne programy WinForms. Czy potrafiłbyś je wykorzystać do zmodyfikowania wcześniejszej wersji aplikacji do zarządzania
wymówkami (napisanej jako program WinForms) w taki sposób, żeby zapisywała i odczytywała te sam e pliki, których używa
menedżer wymówek Damiana dla Sklepu Windows?

jesteś tutaj ► 589


Gesty symboliczne

Utwórz stronę główną aplikacji Menedżera wymówek ^ ^ Z ró b U ^


Utwórz nowy projekt aplikacji dla Sklepu Windows i zastąp plik M a in P a g e.x a m l stroną
utworzoną na podstawie szablonu B a sic P age. Będziesz potrzebował zasobu ExcuseManager.
Utwórz pustą klasę ExcuseManager, tak by kod prawidłowo się kompilował, a następnie dodaj do
sekcji <Page.Resources> następujący zasób statyczny:

<Page.Resources>
<local:ExcuseManager x:Name="excuseManager"/>
<x:String x:Key="AppName">Menedżer wymówek</x:String>
</Page.Resources>
Poniżej przedstawiliśmy kod XAML strony — ma ona prosty układ, bazujący na kontrolce
StackPanel. Ustaw w niej kontekst danych — ma nim być zasób statyczny ExcuseManager.

Wymówka
| Muszę zabrać mojego kota d o zwierzęcego psychologa.

Wyniki
1 Szef naprawdę uważa, że trzeba dbać o zdrowie psychiczne zwierzaków.
-------------------------------------------------------------------------------------- 1
Ostatnio użyte Przyjrzyj się zawartości okna Toolbox. Nie znajdziesz
Ustaw na a ktualny dzień i godzinę
w nim żadnej kontrolki do w ybierania dat! S k o rzy stamy
zatem ze zwyczajnego pola TextB ox i przy c isk u, który
będzie w nim w yśw ietlał bieżący c zas. Wykorzy s ta my
Data pliku także wbudowane metody .NET s łużące do konwersji
2013-12-05 2300:37 +01:00 tekstu na dane typu D ateTime.

<StackPanel Grid.Row="l" Margin="120,0,0,0"


DataContext="{StaticResource ResourceKey=excuseManager}">
<TextBlock Style="{StaticResource SubheaderTextStyle}" Text="Wymówka" Margin="0,0,0,10"/>
<TextBox Text="{Binding CurrentExcuse.Description, Mode=TwoWay}" Margin="0,0,20,20"/>
<TextBlock Style="{StaticResource SubheaderTextStyle}" Text="Wyniki" Margin="0,0,0,10"/>
<TextBox Text="{Binding CurrentExcuse.Results, Mode=TwoWay}" Margin="0,0,20,20"/>
<TextBlock Style="{StaticResource SubheaderTextStyle}" Text="Ostatnio użyte" Margin="0,0,0,10"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,20">
<TextBox Text="{Binding CurrentExcuse.LastUsed, Mode=TwoWay}" Jeśli data wpisana p rzez użytkownika
MinWidth="300" Margin="0,0,20,0"/> nie będzie prawidłowa, to komunikat
<Button Content="Ustaw na aktualny dzień i godzinę" o tym zo stan ie zap isany we
Click="SetToCurrentTimeClick" Margin="0,0,20,0"/> ^ a ś a w o ś a D n te W ^ n g , a om
<TextBlock Foreground="Red" Text="{Binding CurrentExcuse.DateWarning}" wyświetlona w tej kontrolce Te x tB °x.
Style="{StaticResource SubtitleTextStyle}"/> fi;_______________
</StackPanel>

<TextBlock Style="{StaticResource SubheaderTextStyle}" Text="Data pliku" Margin="0,0,0,10"/>


<TextBlock Text="{Binding FileDate}" Style="{StaticResource ItemTextStyle}"/>
</StackPanel> A Kontrolki TextBox korzystają z dw ukierunkowego p owiąza n ia
z właściwościami obiektu CurrentExcus e ud° s tępn iane g° p rzez
ExcuseManager. Kontrolka TextBox p rezen tu ją m d a tę lu tw ^en ra . pl<ku
je st powiązana z w taściw ością FileDa te obiekt u £xcuseM anager.

590 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

Dodaj pasek aplikacji do strony głównej


Dodaj dolny pasek aplikacji do strony głównej. Dodatkowo będziesz także musiał przenieść poza komentarze
definicje stylów OpenFileAppBarButtonStyle, SaveAppBarButtonStyle oraz FolderppBarButtonStyle,
których użyjesz do określenia postaci przycisków Otwórz, Z a p isz oraz Folder.
W pliku StandardStyles.xaml
jest błąd, który wkradł się wraz
z Visual Studio 2012; polega on na
®
Nowa wymówka
©Folder
©
Losowa
®
Otwórz
®
Zapisz Zapisz jako~.
pominięciu litery ,A" w nazwie stylu
FolderAppBarButtonStyle.
wymówka
Będziesz musiał dodać do strony
<common:LayoutAwarePage.BottomAppBar> BottomAppBar, podobnie jak zrobiłeś,
<AppBar x:Name="appBar"> tworząc prosty edytor tesktów.
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{StaticResource AppBarButtonStyle}" Content=M&#x26F1;M
Automat1onPropert1es.Name="Nowa wymówka" Click="NewExcuseButtonClick"
<Button Style="{StaticResource FolderppBarButtonStyle}"
Uży j tych Click="FolderButtonClick"/>
właściwości, <Button x:Name="randomButton" Style="{StaticResource AppBarButtonStyle}"
by zmienić — ^ Automat1onPropert1es.Name="Losowa wymówka" Content="&#x2047;"
nazwę IsEnabled="False" Click="RandomExcuseButtonClick"/>
przycisków Przyciski
orazich <Button Style="{StaticResource OpenFileAppBarButtonStyle}" Zapisz oraz
ikony. AutomationProperties.Name="Otwórz" Click="OpenButtonClick" /> Losowa
<Button x:Name="saveButton" Style="{StaticResource SaveAppBarButtonStyle}1 wymówka są
IsEnabled="False" Click="SaveButtonClick" ^---------------------------------- początkowo
wyłączone.
AutomationProperties.Name="Zapisz" />
<Button Style="{StaticResource SaveAppBarButtonStyle}"
AutomationProperties.Name="Zapisz ja k o ..." Click="SaveAsButtonClick" />
</StackPanel>
</AppBar>
</common:LayoutAwarePage.BottomAppBar>

A w jaki sposób XAML zmienia ikony przycisków? Przyjrzyj się dokładniej stylom, które przeniosłeś poza komentarz
w pliku StandardStyles.xam l — zobaczysz w nim wartości szesnastkowe &#xE188; w stylu przycisku Folder oraz &#xE1A5;
w stylu przycisku Zapisz. Zawartość przycisku jest zwyczajnym tekstem prezentowanym czcionką Segoe UI Symbol,
natomiast jego ikona — znakiem Unicode wyświetlonym tą samą czcionką.
Aby zapisać wartość szesnastkową
w'pUku XAML (bądź w jakimkolwiek innym
pliku XML), mależy ją poprzedzić sekwencją
^ znaków &#x i zakończyć średnikiem (;).

Content="&#x26F1;"

Abyś zrozumiał, jak to działa,


w dalszej części książki
Nowa wymówka
dokładniej zajmiemy się stylami.

AutomationProperties.Name="Losowa wymówka"
W taki sposób należy określać
nazwę przycisku.

W następnej kolejności zajmiemy się klasa ExcuieManayer -► jesteś tutaj ► 591


Żadnych wym ówek Nie zapomnij zaimplementować
w klasie ExcuseManager-
interfejsu INotifyPropertyChanged.
Napisz klasę ExcuseManager CurrentExcuse
FileDate

Poniżej przedstawiliśmy większą część kodu klasy ExcuseManager — dokończenie jej, jak i napisanie N ewExcuseAsync()

klasy Excuse, wykonasz w ramach ćwiczenia. Definiuje ona dwie właściwości publiczne, które będą SetToCurrentTime()
ChooseNewFolderAsync()
używane do celów wiązania danych: CurrentExcuse oraz FileDate. Pierwsza z nich będzie zawierać O penExcuseAsync()
aktualnie wczytany obiekt Excuse, natomiast druga jest łańcuchem znaków prezentującym datę OpenRandomExcuseAsync()
utworzenia pliku bądź napisem „Nie wczytano wymówki” (jest on wyświetlany, gdy żadna wymówka SaveCurrentExcuseAsync()

nie została jeszcze ani wczytana, ani zapisana). UpdateF i leDateAsync()


SaveCurrentExcuseAsAsync()
Metoda ChooseNewFolderAsync() wyświetla stronę do wyboru folderu i zwraca wartość true W riteExcuseAsync()
R eadExcuseAsync()
wyłącznie w przypadku, gdy użytkownik wybrał folder. Ponieważ jest to metoda asynchroniczna
zwracająca wartość logiczną, zatem typ jej wyniku został zadeklarowany jako Task<bool>.
pub lic Excuse CurrentExcuse { g et; s e t; }
Będziesz musiał dodać do pliku następujące
pub lic s trin g FileD ate { g e t; p riv a te s e t ; } instrukcje using:
using System.ComponentModel;
p riv a te Random random = new Random(); using System.IO;
using System .R u n tim e.Se ria lizatio n ;
p riv a te IStorageFolder excuseFolder = n u ll; using Windows.Storage;
using Windows.Storage.Streams;
p riv a te IS to ra g e F ile e x c u s e F ile ; using W indow s.Storage.FileProperties;
using W indows.Storage.Pickers;
Właściwość excuseFile typu IStorf ge pliku
pub lic ExcuseManagerO przechowuje obiekt ostatni wczytanego pl<ku using Windows.UI.Popups;
I wymówki. Jeśli nowa wymówka: nie zost.a\a
NewExcuseAsyncQ; Klasa Task należy do przestrzem nazw
zdpisana lub nie wczytana żadnej wymówki, System.Threading.Tasks, jednak tę
} właściwość ta przyjmuje wartość null.
instrukcję using IDE już dodat°.
async p u b lic void NewExcuseAsyncQ
I
CurrentExcuse = new E x c u s e d ; Kiedy użytkownik klika przycisk Metodę asynchroniczną, taką
ex c u se F ile = n u ll; Nowa wymówka, obiekt jak NewExcuseAsync(), można
OnPropertyChanged("CurrentExcuse"); ExcuseManager usuwa obiekt wywoływać wewnątrz metody, która
aw ait U pdateFileD ateAsyncQ ; bieżącej wymówki,
a następnie aktualizuje właściwość nie jest asynchroniczna. Wystarczy
} pominąć słowo kluczowe await,
FileDate, wywołując metodę
pub lic void SetToCumentTimeQ
UpdateFileDateAsync(). a wywoływanie metody nie będzie
I przerywane.
Cum entExcuse.LastUsed = DateTim eO ffset.N ow .ToStringQ ;
OnPropertyChangedQ'CurrentExcuse");
} Ta metoda zapisuje bieżącą datę i godzinę we właściwości _
LastUsed, a następnie wywołuje zdarzenie PropertyChanged.
pub lic async Task<bool ChooseNewFolderAsyncQ
{
Fo ld erP ick er fo ld e rP ic k e r new Fo ld erP ick erQ
{ Ta asynchroniczna metoda zwraca wartość
SuggestedStartLocation = PickerLo catio nld.D o cum entsLibrary ^-logiczną, zatem typ jej wyniku został
}; zadeklarowany jako Task<bool>.
fo ld e r P ic k e r .F ile T y p e F ilt e r .A d d (" .x m l" );
ISto rageFo lder fo ld e r = aw ait fo ld e rP ic k e r.P ic k S in g le F o ld e rA sy n c Q ;
i f (fo ld e r != n u li)
{ Jeśli użytkownik wybrał jakiś folder, to metoda zwraca
excuseFolder = fo ld er- true. Metoda asynchroniczna zwracająca
r- w a rto ś ć
return true- ' Task<bool> powinna zwracać jedynie wartość logiczną.
} '
MessageDialog warningDialog = new MessageDialog("Nie wybrano fo lderu wymówek");
aw ait warningDialog.ShowAsync();
return f a ls e ; FolderPicker jest kolejnym narzędziem pozwalającym na wybieranie folderów.
} Działa dokładnie tak samo jak inne narzędzia tego typu, które już poznałeś. Przejrzyj
wszystkie te narzędzia, należące do przestrzeni nazw Windows.Storage.Pickers:
592 Rozdział 11. http://msdn.microsoft.com/library/windows/apps/BR207928.
Async, aw ait i serializacja kontraktu danych
pu b lic async void OpenExcuseAsync()
{
FileOpenPicker p icker = new FileOpenPicker
Metoda OpenExcuseAsync()
{
SuggestedStartLocation = PickerLocationld.DocumentsLibrary,
jest bardzo podobna do metody
ReadGuyAsync() z aplikacji do
CommitButtonText = "Otwórz p l i k wymówki"
synchronizacji obiektów Guy.
p ic k e r . F ile T y p e F ilt e r . A d d ( " . x m l" ) ;
excuseFile = await picker.P ic kS in g le F ile A s yn c ();
i f (excuseFile != n u ll) O rany! G dzieś w tym kodzie je st błąd!
await ReadExcuseAsyncQ; C zy potrafisz go zn aleźć? Popraw isz go
w następnym rozdziale.
pu b lic async void OpenRandomExcuseAsync()
{
IReadOnlyList<IStorageFile> f i l e s = await excuseFolder.GetFilesAsync();
excuseFile = file s[ra nd om .N ext(0, f i l e s . C o u n t ( ) ) ] ;
await ReadExcuseAsync();
Metoda UpdateFileDateAsync() zapisuje we w ^ a w ^ a fdeDate
datę modyfikacji pliku bieżącej wymdwti. Jeśn żadna wymówka
publ ic async Task UpdateFileDateAsyncQ \ . nie\ ostała jeszcze wczytana, właściwość jest pustym fańcuchem
{ znaków. Jest to metoda asynchroniczna, wywotywana przez mną
i f (excuseFile !- n u l l ) metodę asynchroniczną, dlatego też zwmca M ek t Task.
BasicProperties basicProperties = await excuseFile.GetBasicPropertiesAsync();
FileDate = ba sicPro pertie s.D ateM o difie d.T oStrin g ();
}
else
FileDate = " ( n ie wczytano p l i k u ) " ;
OnPropertyChanged("FileDate"); Metoda IStorageFile.GetBasicPropertiesAsync() zwraca
obiekt BasicProperties udostępniający przeznaczone tylko
do odczytu właściwości DateModified oraz Size, zawierające,
pu b lic async void SaveCurrentExcuseAsyncQ odpowiednio: datę ostatniej modyfikacji oraz wielkość pliku.
{
i f (CurrentExcuse == n u ll)
{
await new MessageDialog("Nie wczytano wymówki").ShowAsync();
r e turn ;
}
i f (String.IsNullOrEmpty(CurrentExcuse.Description))
{
await new MessageDialog("Bieżąca wymówka nie ma opisu").ShowAsync();
r e turn ;
}
i f (excuseFile == n u ll)
excuseFile = await excuseFolder.CreateFileAsync(CurrentExcuse.Description + " .x m l",
C re atio nC ollisio nO ptio n.R epla ceExis tin g);

await WriteExcuseAsync();
Metoda SaveCurrentExcuseAsync() najpierw sprawdza, czy
bieżąca wymówka jest równa null bądź czy jej opis jest
pu b lic async Task ReadExcuseAsync() pustym łańcuchem znaków, a jeśli któryś z tych warunków
jest spełniony, to wyświetla komunikat. Jeśli wymówka jest
/ / Napiszesz tę metodę prawidłowa, wywoływana jest metoda WriteExcuseAsync(),
która ją zapisuje. Jeśli jeszcze nie ma żadnej wymówki, jest
ona tworzona poprzez wywołanie metody CreateFileAsync().
pu b lic async Task WriteExcuseAsync()

I I Napiszesz tę metodę

pu b lic async void SaveCurrentExcuseAsAsyncQ

I I Napiszesz tę metodę

W następnej kolejności zajmiemy się klasą ExcuieManayer. ► jesteś tutaj ► 593


Zaktualizuj pasek aplikacji

Dodaj kod obsługujący stronę


To jest cały kod ukryty strony, którego będziesz potrzebował. Procedury obsługi przycisków
jedynie wywołują odpowiednie metody klasy ExcuseM anager . To zaleta, którą daje separacja
zadań — rozdzielenie obsługi wymówek oraz wyświetlania interfejsu użytkownika. Kod obsługi
interfejsu użytkownika może być bardzo prosty, gdyż większość pracy wykonują inne klasy.

Nowa wymówka
®
Folder
© ®
Losowa Otwórz
®
Zapisz Zapisz jako—
wymówka

\ \ ____________________________
Przyciski Losowa wymówka oraz Zapisz działają w yłącznie wtedy, gdy użytkownik
w ybrał folder, dlatego też procedura obsługi przycisku Folder używa wartości
zwracanej przez w yw ołanie metody ChooseNewFolderAsync(). Jeśli w artością tą
jest tru e , przyciski Losowa wymówka oraz Zapisz zostają uaktywnione.

Nowa wymówka
(!)
FoMer
®
Losowa
®
Otwórz
®
Zapisz
®
Zapisz jako—
wymówka

p r iv a t e v o id O p e n B u tto n C lic k (o b je c t se n d e r, RoutedEventArgs e) {


excuseM anager.O penExcuseAsync();
}
p r iv a t e v o id S a v e B u tto n C lic k (o b je c t se n d e r, RoutedEventArgs e) {
excuseM anager.S aveC urrentE xcuseA sync();
}
p r iv a t e v o id N e w E x c u s e B u tto n C lic k (o b je c t sen de r, RoutedEventArgs e) {
excuseM anager.NewExcuseAsync();
}
p r iv a t e v o id S a v e A s B u tto n C lic k (o b je c t sen de r, RoutedEventArgs e) {
excuseM anager.SaveC urrentExcuseAsAsync();
}
p r iv a t e v o id S e tT o C u rre n tT im e C lic k (o b je c t sen de r, RoutedEventArgs e) {
excu seM a nag er.S etT oC u rren tT im e ();
}
p r iv a t e v o id R andom E xcuse B utton C lick(o bject se n d e r, RoutedEventArgs e) {
excuseManager.OpenRandomExcuseAsync();
}
p r iv a t e async v o id F o ld e r B u tto n C lic k (o b je c t se n d e r, RoutedEventArgs e) {

bool fold erC hose n = a w a it excuseM anager.ChooseNewFolderAsync();


i f (fo ld e rC h o s e n ) {
s a v e B u tto n .Is E n a b le d = t r u e ;
ran do m B utto n.IsE na ble d = t r u e ;
}
}

594 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

Dokończ klasy Excuse oraz ExcuseManager używane w nowej wersji programu


Damiana do zarządzania wymówkami, napisanego w formie aplikacji dla Sklepu Windows. INotifyPropertyChanged

PropertyChanged event
Ćwiczenie

Napisz klasę Excuse.


Klasa ta będzie potrzebowała kontraktu danych używającego przestrzeni nazw http://www.
headfirstlabs.com/MenedzerWymowek, obejmującego trzy składowe danych. Pierwszymi
dwiema składowymi mają być automatyczne właściwości D escription oraz R esults. Trzecią
jest pole typu DateTime o nazwie lastUsed, będące polem wewnętrznym dla właściwości
Excuse
LastUsed (jej wartość jest ustawiana w metodzie ExcuseManager.SetToCurrentTime()). Description
Results
Klasa Excuse korzysta ze specjalnej wartości DateTime.MinValue, której używa jako domyślnej
LastUsed
wartości pola lastUsed. Jest to najwcześniejsza data, jaką można zapisywać w zmiennych typu DateWarning
DateTime, a klasa Excuse używa jej w wymówkach, których data nie została jeszcze określona.
Akcesor get właściwości LastUsed zwraca wartość wywołania lastUsed.ToString(), jeśli data
została ustawiona, lub pusty łańcuch znaków (String.Empty), jeśli data jest równa MinValue.
Akcesor s e t właściwości LastUsed konwertuje łańcuch znaków na datę, używając w tym
celu następującego kodu:

DateTime d;
bool datelsValid = DateTime.TryParse(value, out d);
lastUsed = d; INotifyPropertyChanged

PropertyChanged event
Metoda TryParse() zwraca wartość true, jeśli data jest prawidłowa, bądź wartość f a ls e -
w przeciwnym razie. Jeśli użytkownik wpisał nieprawidłową datę, metoda ustawia właściwość
DateWarning (przeznaczoną tylko do odczytu), zapisując w niej: "Nieprawidłowa data: 11
i wpisaną wartość daty. Komunikat ten zostanie wyświetlony na czerwono w kontrolce
TextBlock, informując użytkownika, że wpisana data nie jest prawidłowa. Nie zapomnij
także wywołać zdarzenia PropertyChanged, by poinformować stronę o zmianie wartości
właściwości DateWarning.
ExcuseManager
^2 ) Zaimplementuj metodę ExcuseManager.WriteExcuseAsync(). CurrentExcuse
FileDate
Ta metoda otwiera strumień i serializuje bieżącą wymówkę, zapisując ją w pliku
reprezentowanym przez obiekt IStorageF ile, przechowywanym w polu excuseFile. NewExcuseAsync(]
SetToCurrentTime()
Następnie wyświetla komunikat informujący o prawidłowym zapisaniu wymówki i wywołuje
ChooseNewFdderAsync(]
metodę UpdateFileDateAsync(), by zaktualizować właściwość FileDate. OpenExcuseAsync()
OpenRandomExcuseAsync()
Zaimplementuj metodę ExcuseManager.ReadExcuseAsync(). SaveCurrentExcuseAsync(]
Ta metoda otwiera strumień i tworzy nowy obiekt Excuse, wczytując serializowaną wymówkę UpdateFieDateAsync(]

z pliku określonego przez pole excuseF ile. Metoda wywołuje zdarzenie PropertyChanged, SaveCurrentExcuseAsAsync(]
WriteExcuseAsync()
by poinformować stronę o zmianie wartości właściwości CurrentExcuse, a następnie ReadExcuseAsync(]
wywołuje metodę UpdateFileDateAsync(). Będziesz także musiał zaimplementować
interfejs INotifyPropertyChanged i dodać metodę OnPropertyChanged.
[ 4) Zaimplementuj metodę ExcuseManager.SaveCurrentExcuseAsync().
Ta metoda wyświetla stronę FileSavePicker, pozwalając użytkownikowi wybrać plik XML,
w którym zostanie zapisana wymówka. Jeśli użytkownik wybierze plik, wywoływana jest
metoda WriteExcuseAsync(), która zapisuje wymówkę.

jesteś tutaj ► 595


Rozwiązanie ćwiczenia

Poniżej zostały przedstawione metody, które musisz dodać do klasy E xcuseM anager. Koniecznie
Rozwiązanie upewnij się, że klasa implementuje interfejs IN o tify P ro p e rty C h a n g e d .
ćwiczenia
p u b lic async Task ReadExcuseAsync()
{
using (IRandomAccessStream stream =
await excuseFile.OpenAsync(FileAccessMode.Read))
using (Stream inputStream = stream.AsStreamForReadQ)
{
D a ta C o n tra c tS e ria liz e r s e r i a l i z e r = new D a ta C o n tra c tS e r ia liz e r( ty p e o f(E x c u s e ) );
CumentExcuse = seria lize r.R e a d O b je ct(in p u tS tre a m ) as Excuse;
}
await new MessageDialog("Wymówka wczytana z p l i k u " + excuseFile.Name).ShowAsync();
OnPropertyChanged("CurrentExcuse");
^ await UpdateFileDateAsync(); Metody do odczytu i zapisu obiektów Excuse są
} bardzo podobne do analogicznych metod stosowanych
w aplikacji Serializacja obiektów Guy.
p u b lic async Task WriteExcuseAsync()
{
using (IRandomAccessStream stream =
await excuseFile.OpenAsync(FileAccessMode.ReadWrite))
using (Stream outputStream = stream.AsStreamForWriteQ)
{
D a ta C o n tra c tS e ria liz e r s e r i a l i z e r = new D a ta C o n tra c tS e r ia liz e r( ty p e o f(E x c u s e ) );
s e ria liz e r. W r ite O b je c t( o u t p u tS t r e a m , CumentExcuse);
}
await new MessageDialog("Wymówka zapisana w p l i k u " + excuseFile.Name).ShowAsync();
await UpdateFileDateAsync();
}
p u b lic async void SaveCumentExcuseAsAsync()
{
FileSavePicker p ic k e r = new FileSavePicker
{
SuggestedStartLocation = Picke rLocatio nld .D ocum entsLibra ry,
SuggestedFileName = CumentE xcuse.Descriptio n,
CommitButtonText = "Zapisz p l i k wymówki"
};
p ic k e r.F ile T y p e C h o ic e s .A d d ("P lik XML", new L i s t < s t r i n g > ( ) { ".x m l" } ) ;
IS to ra g e F ile newExcuseFile = await picke r.P ick S a ve F ile A s yn c ();
i f (newExcuseFile != n u l l )
{ Metoda SaveCurrentExcuseAsAsync() wyświetla stronę do wyboru
excuseFile =newExcuseFile; pliku, a następnie zapisuje wymówkę do pliku wskazanego przez
await WriteExcuseAsync(); użytkownika. Aktualizuje także pole excuseFile, by zachować
} informację o pliku wymówki (dzięki temu po kliknięciu przycisku
} Zapisz wymówka zostanie zapisana do tego samego pliku).
p u b lic event PropertyChangedEventHandler PropertyChanged;
To standardowy kod do
p r i v a t e void OnPropertyChanged(string propertyName) ^ ------------- wywotywania zdarzenia
{ PropertyChanged.
PropertyChangedEventHandler propertyChangedEvent = PropertyChanged;
i f (propertyChangedEvent != n u l l )
{
propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
}
}

596 Rozdział 11.


Async, aw ait i serializacja kontraktu danych

Oto nowa klasa E xcuse. Zawiera ona kontrakt danych obejmujący właściwości D e s c r i p tio n i
R e s u lt s oraz pole w ewnętrzne la s tU p d a te d używane przez właściwość L a stU p d ate d .

using System.ComponentModel;
using S y s te m .R u n tim e .S e ria liz a tio n ;

[DataContract(Namespace = " http://www.headfirstlabs.com/M enedzerW ym owek" )]


c la s s Excuse : IN otifyPropertyChanged
{
p u b lic s tr in g DateWarning { g e t; s e t ; }

[DataMember]
p u b lic s tr in g D e sc rip tio n { g e t; s e t ; }
Dodanie atrybutu [DataMember) _do pola
[DataMember]
wewnętrznego lastUsed p°w°duJe> ze
p u b lic s tr in g R e s u lts { g e t; s e t ; } będzie ono zapisywane i odtwarzrme
podczas serializacj i deserializacJ<-
[DataMember]
p riv a te DateTime lastU sed = DateTim e.M inValue;
p u b lic s tr in g LastUsed
{
get
{
i f (lastU se d != DateTime.M inValue)
re tu rn la s tU s e d .T o S trin g Q ;
e ls e Jeśli użytkownik wpisał prawidłową datę,
re tu rn S trin g .E m p ty ; metoda DateTime.TryParse() skonwertuje ją
} na obiekt DateTime i zwróci wartość true.
set W przeciwnym razie wartość pola nie zmieni
{ się i będzie wynosić DateTime.MinValue.
DateTime d = DateTim e.M inValue;
bool d a te ls V a lid = D a te T im e .T ry P a rse (v a lu e , out d ) ;
lastU sed = d;

if (!S trin g .Is N u llO rE m p ty (v a lu e ) ¡d a te ls V a lid ]


{
DateWarning = "Nieprawidłowa d ata: " + v a lu e ;
}
e ls e
DateWarning = S trin g .E m p ty ; Jeśli pole lastUsed przyjmie wartość

}
}
OnPropertyChanged("DateWarning")

p u b lic event PropertyChangedEventHandler PropertyChanged;


V DateTime.MinValue, to w polu DateWarnmg
zostanie zapisane ostrzeżenie, które
następnie zostanie wyświetlone na stronie.

p riv a te vo id O nPropertyChanged(string propertyName)


{
PropertyChangedEventHandler propertyChangedEvent = PropertyChanged;
i f (propertyChangedEvent != n u ll)
{
p ro p e rtyC h an g ed Eve n t(th is, new PropertyChangedEventArgs(propertyNam e));

}
}
±
To jest ten sam kod służący do wywoływania zdarzenia PropertyChanged, którego
używałeś we wcześniejszej części rozdziału. Gdybyś jednak skopiował go i wkleił
do klasy Excuse lub ExcuseManager, lecz zapomniał dodać do deklaracji klasy
: INotifyPropertyChanged, to powiązania kontrolek z danymi nie zostałyby
prawidłowo skonfigurowane. Oznaczałoby to, że obiekty wywoływałyby zdarzenia
PropertyChanged, lecz kontrolki na stronie nie próbowałyby ich odbierać, a zatem
powiązanie danych nie działałoby. Taki błąd mógłby być bardzo frustrujący! jesteś tutaj ► 597
598 R o zd ział 11.
12. Obsługa wyjątków
^ Gaszenie pożarów
nie jest już popularne

Program iści nie m a ją być strażak am i. Pracowałeś jak wół, przebrnąłeś przez
dokumentacje techniczne i kilka zajmujących książek Rusz głową!, wspiąłeś się na szczyt
swoich możliwości: jesteś mistrzem programowania. W dalszym ciągu musisz jednak
odrywać się od pracy, ponieważ program wyłącza się lub nie zachowuje się tak,
jak powinien. Nic nie wybija Cię z rytmu tak, jak obowiązek naprawienia dziwnego
błędu... Z obsługą wyjątków możesz jednak napisać kod, który poradzi sobie
z pojawiającymi się problemami. Jest nawet lepiej, możesz bowiem zareagować
na ich pojawienie się i sprawić, że wszystko będzie dalej działało .

to je st n o w y ro z d z ia ł ► 599
M oje program y — moje problemy

Damian potrzebuje swoich wymówek,


aby być mobilnym
Damian został ostatnio przeniesiony do działu międzynarodowego.
Lata teraz po całym świecie. W dalszym ciągu musi jednak
rejestrować swoje wymówki. Zainstalował więc program na swoim
laptopie i może go teraz wziąć ze sobą wszędzie.

Damian uruchomił
genefator wymówek

c
na swoim laptopie.

Damian Samopas
zawsze próbuje znalezć
wymówką, aby wyrwać
sią z pracy.

Ale program nie działa!


Damian kliknął przycisk „Losowa wymówka” i otrzymał dość nieprzyjemnie
wyglądający błąd. Coś o tym, że nie można znaleźć wymówek. Co się stało?

s* *
Nieobsłużony
wyjątek... musi
być jakiś problem,
którego nie
przewidzieliśmy.

600 Rozdział 12.


Obsługa wyjątków

Zaostrz
¿.U U 3U ołówek

V Oto kolejny przykład niesprawnego kodu. Znajduje się w nim pięć różnych wyjątków zgłaszanych
przez program. Komunikaty o błędach wyświetlone są po prawej stronie. Twoim zadaniem jest
połączenie wyjątku z wierszem kodu, który go generuje. Aby uzyskać podpowiedz, przeczytaj
komunikat i spróbuj go zrozumieć.
public s ta tic void BeeProcessor() { W y w o f a n ie d o u b le . P a r s e f 3 2 " l i s k u t k u j e
n a rs o w a n ie m fa ń c u c h a z n ak ó w
object myBee = new HoneyBee(36.5, "Zippo"); f z w r ó c e n ie m w a r t o ś c i t y p u d o u b te ,
flo a t howMuchMoney = (float)myBee; w ty m p r z y p a d k u 3 2 . D ru g i Paramf tr
k onstruktora k la sy H on eyB ee o k reśla
w a rto ść w fa ściw o ści M yN am e.
HoneyBee anotherBee = new HoneyBee(12.5, "Buzzy")
double beeName = double.Parse(anotherBee.MyName);

double totalHoney = 36.5 + 12.5;


string beesWeCanFeed = "";
for (int i = 1; i < (int)totalHoney; i++) {
beesWeCanFeed += i.T oString(); A n u n h a n d le d e xception o f ty p e 'S ystem .O ve rflo w E xcep tion ' o ccurred in mscorliDTdll I

} A d d itio n a l in fo rm a tio n : W a rto ść je s t za duża a lb o za m a ła dla w a rto ści ty p u Single.

flo a t f = float.Parse(beesWeCanFeed);
-; • — - - A- ,...... (2 )
int drones = 4; BeeProcessingSystem.exe

int queens = 0; A dditional inform ation: Odwołanie do obiektu nie zostało ustawione na wystąpienie
obiektu J
int dronesPerQueen = drones / queens;

anotherBee = null;
A n unhandled exception o f ty p e 'System .InvalidCastException' occurred in
i f (dronesPerQueen < 10) { BeeProcessingSystem.exe

anotherBee.DoMyJob(); A d d itio n a l in fo rm a tio n : O kreślone rzutow anie je s t niepraw idłow e.


}

An unhandled exception of type 'System.DivideByZeroException' occurred in ®


BeeProcessingSystem.exe

Additional information: Nastqpifa prôba podzielenia przezzero.

©
An unhandled exception o f type'S ystem .F orm atE xception' occurred in m sco rlib .d ll

A d d itio n a l in fo rm a tio n : N iepra w idło w y fo rm a t ciągu w ejściow ego.

jesteś tutaj ► 601


Łamiąc zasady

Zaostrz ołówek
Twoim zadaniem było połączenie wiersza kodu
powodującego błąd z wygenerowanym w nim wyjątkiem.
Rozwiązanie

C# pozwala Ci rzutować myBee za typ


nie Istnieje jednak żaden sposób, by przekonw>^tować
ob ject myBee = new HoneyBee(36.5, "Zippo") ; na wartość tego typu obiekt H°zeyBe e', 9 dy fOć'
kod zostanie uruchomiony, CLR n<e bęęizie m\et e
f lo a t howMuchMoney = (float)myBee; naimnieiszego pojęcia, jak wykonać takie rzutowaz<e,
i dlatego zgłosi wyjątek I zvalidCast£xceptioz.

An unhandled exception o f type 'System.InvalidCastException' occurred in


BeeProcessingSystem.exe

A dditional inform a tio n : Określone rzutowanie jest nieprawidłowe.

M « ^ 0 ParseO oczekuje
taĄcucha znaków w określonym
f° rmacie. „Buzzy“ nie jest
HoneyBee anotherBee = new HoneyBee(12.5, "Buzzy"); łaAcuchem, który może zostać
w j akiś sp°sób zamieniony
double beeName = double.Parse(anotherBee.MyName); na liczbę. T° dętego zgłaszany
jest wyjątek Forma^ceptim.

An unhandled exception o f typ e 'System .Form atException' occurred in m scorlib.dll

A d d itio n a l in fo rm a tio n : N iepra w idło w y fo rm a t ciągu w ejściow ego, ©

Pętla for utworzy łańcuch zzjków o w ™ *


double totalHoney = 36.5 + 12.5; beesWeCazFeed, kt<óry t>ę<izie zow^rat liczbą
posiodającą ponad sześćdzies<ąt cyfr. Nie ma
str in g beesWeCanFeed = ""; pakiej możliwości, aby typ float przechow°t j
for (in t i = 1; i < (int)totalH oney; i++) { wielką wartość. Próba siłowego umieszczez<Oku j
w zmiennej float spowoduj e zgłoszezie wyjątku
beesWeCanFeed += i.T o S tr in g (); OverflowExceptioz.
}
f lo a t f = float.Parse(beesWeCanFeed) ;
Nigdy nie dostaniesz tych wszystkich
wyjątków naraz. Program zgłosi pierwszy
A n u nh a n dle d exception o f ty p e S ystem .O verflow Exception' occurred in m s c o rlib .d ll z nich i zatrzyma swoje wykonywanie.
Drugi wyjątek zobaczysz dopiero po
A d d itio n a l in fo rm a tio n : W artość je s t za duża albo za m ała dla w artości ty p u Single. rozwiązaniu problemów powodujących
» powstawanie pierwszego.

602 Rozdział 12.


Obsługa wyjątków

in t drones = 4;
in
1 1 1t
W queens
WI I^ = V0;J
Bardzo tatwo uzyskać wyjątek
in t dronesPerQueen = drones / queens; DivideByZeroException. Po prostu
p°dziel dowolną liczbę przez zero.

Art unhandled exception of type 'System.DivideByZeroException' occurred in


BeeProcessingSystem.exe

Additional information: Nastąpiła próba podzielenia przez zero.

i upewnić się, że nie jest r<5wna zero.

anotherBee = n u ll;
i f (dronesPerQueen < 10) { Ustawienie referencji anotherBee na wartość
anotherBee.DoMyJob(); . t yCel L imir ::,yiZik«wsk° zuJ’
} NullReferenceException to sposób, w jaki C#
przekazuje tram informację, że nie ma żadnego
obiektu, którego metodę DoMyJob() można by
wywołać.

2^
BeeProcessingSystem.exe

Additional inform ation: Odwołanie do obielctu nie zostało ustawione na wystąpienie


obiektuJ

Powstanie powyższego błędu dzielenia przez zero nie powinno mieć miejsca.
W ystarczyłoby dokładnie popatrzeć na kod, aby się zorientować, że coś jest nie tak.
To samo dotyczy innych wyjątków. Takim problemom można zapobiec — im więcej
będziesz w iedział na ich temat, tym łatw iej będzie Ci uchronić program
przed awariami pisanych programów.

jesteś tutaj ► 603


Hm, usterka

Kiedy program zgłasza wyjątek,


.N E T tworzy obiekt Exception
Wyj |tek, rzeczownik
Zapoznałeś się ze sposobem, w jaki .NET próbuje Ci uświadomić, że coś
w programie poszło nie tak: z wyjątkiem. W momencie pojawienia się wyjątku — osoba lub rzecz niezwykła,
w C # tworzony jest obiekt, który dany problem reprezentuje. Nazywa się on odstępstwo od obowiązującego
Exception, co chyba nie jest zaskakujące. p raw a przyjętego zwyczaju,
przepisów.
Przypuśćmy, że posiadasz tablicę z czterema elementami. Próbujesz teraz uzyskać
dostęp do elementu szesnastego (o indeksie 15, ponieważ indeksujemy od zera): Chociaż Jan nie lubi masła orzechowego,
to zrobił w y ją te k dla krówki z masłem '
Ten kod orzechowym od Karola.
int[] anArray = {3, 4, 1, 11}; z pewnością
spowoduje
int aValue = anArray[15]; pow stanie
problemów.

Kiedy IDE przerywa działanie programu ze względu na wystąpienie


wyjątku, szczegółowe informacje na jego temat możesz poznać, dodając
$ e x c e p tio n do okna Watch. Zmienna ta jest zawsze wyświetlana także
w oknie Locals, które bardzo przypomina okno Watch, choć wyświetla
wyłącznie zmienne lokalne.
c x c«&
dy tytko program powoduje
¡ Z T o h i Z m ie Myjątku’ tworzony
d a n e o Z ZaM'eraJąCy

Ten w yjątek posiada ^ m i j m ^


określający, co s powodowało
błąd. Zaw iera om t a kż e
listę w szystkich wywołań
wykonanych przez s y s tem
aż do uruchomienia imstrukcji,
która go w ygenerow ać.

Tworząc obiekt, .NET wykonuje dodatkową pracę, ponieważ chce Ci przekazać wszystkie
informacje o sytuacji powodującej dany wyjątek. Możesz napisać kod, który rozwiąże
problem będący jego przyczyną, ale możesz także potrzebować pewnych zmian w obsłudze
określonych sytuacji w programie.

W tym przypadku wyjątek In d e x O u tO fR a n g e E x c e p tio n wskazuje na rodzaj błędu:


próbujesz uzyskać dostęp do indeksu tablicy, który jest poza zakresem. Dostajesz także
informację, w którym miejscu w kodzie problem wystąpił. W ten sposób łatwo namierzyć
jego nieprawidłowy fragment (nawet gdy posiadasz tysiące takich wierszy).

604 Rozdział 12.


Obsługa wyjątków

■ Nie .istnieją.
głupie pytania

P Dlaczego jest tak dużo różnych wyjątków?


: P : Jeśli zatem mój kod zgłasza wyjątek, to niekoniecznie
jest to spowodowane moim błędem?
O : Istnieje cała masa możliwości napisania takiego kodu, by C#
naprawdę nie wiedział, co z nim zrobić. Znacznie trudniej byłoby O : Dokładnie. Czasami Twoje dane nie są takie, jakie być powinny
poprawiać błędy, gdyby program wyświetlał jeden ogólny wyjątek — na przykład wtedy, gdy posiadasz metodę korzystającą z tablicy
z komunikatem („A problem occured at line 37"). O wiele łatwiej dłuższej lub krótszej niż ta, której się na początku spodziewałeś.
odnaleźć i poprawić błąd w kodzie, gdy dokładnie wiesz, jaki jego Nie zapominaj również, że Twój program będzie obsługiwał
rodzaj wystąpił. przedstawiciel rodzaju ludzkiego, a oni zawsze zachowują się
nieprzewidywalnie. Wyjątki są takim elementem .NET, który
P Czym tak naprawdę jest wyjątek?
: pozwala wyłapać niespodziewane sytuacje. Dzięki nim Twoja
aplikacja może w łagodny sposób obsłużyć błędy, nie kończąc
O : To obiekt tworzony przez .NET, gdy istnieje jakiś problem. nagle działania i nie wyświetlając tajemniczego, bezużytecznego
Możesz także sam generować wyjątki (więcej o tym za chwilę). komunikatu o problemie.

P Zaraz, chwila, że jak? To jest obiekt?


: P : Jak już wiedziałem, na co patrzę, to bardzo łatwo

O : Tak, wyjątek jest ob ie kte m . Jego właściwości zawierają


było powiedzieć, że kod na poprzedniej stronie spowoduje
nieoczekiwane zakończenie się programu. Czy wyjątki
informacje na temat błędu. Posiada on na przykład właściwość
Message, która zawiera pomocny łańcuch znaków w stylu „Nie
zawsze są łatwe do namierzenia?
można porównać dwóch elementów tablicy" lub „Wartość jest za
duża albo za mała dla wartości typu Single" wyświetlany przez
O : Nie. Niestety istnieje wiele sytuacji, w których Twój kod
spowoduje powstanie problemów, ale będzie bardzo trudno
okno wyjątków. Powodem, dla którego .NET generuje taki obiekt,
jest potrzeba udostępnienia informacji pozwalających dokładnie określić, gdzie leży ich przyczyna. To dlatego IDE udostępnia Ci
określić problem pojawiający się podczas wykonywania programu bardzo użyteczne narzędzie nazywane debuggerem. Pozwala ono
oraz instrukcję, która taki wyjątek zgłoska. na zatrzymanie programu i wykonywanie go instrukcja po instrukcji
z jednoczesnym śledzeniem wartości każdej zmiennej i każdego
P : Hm, w dalszym ciągu nie mogę się w tym połapać. pola. W ten sposób znacznie łatwiej jest odnaleźć miejsce,
Przepraszam. Jeszcze raz. Dlaczego jest tyle różnych w którym kod zachowuje się inaczej, niż się tego spodziewałeś.
wyjątków? Masz wtedy największe szanse, aby namierzyć i usunąć błąd,

O : Ponieważ istnieje wiele różnych możliwości nieprawidłowego


a nawet lepiej — uniemożliwić jego powtórne wystąpienie.

zachowania się programu. Może być wiele sytuacji, w których


Twoja aplikacja po prostu zakończy się w niespodziewany sposób.
Byłoby Ci bardzo trudno namierzyć potencjalny problem, gdybyś
nie wiedział, dlaczego program nagle zakończył działanie. Poprzez
zgłaszanie różnych wyjątków w różnych okolicznościach .NET
Wyjątki Domagają
udostępnia Ci naprawdę cenne informacje, które pomogą Ci
znaleźć i naprawić błąd.
Ci określić sytuacje,
w których Twój
P : Wyjątki mają mi więc pomagać, a nie sprowadzać
kod zachowuje się
na głowę kolejne problemy?

O : Tak! Wyjątki są po to, aby pomóc Ci w wyłapaniu rzeczy w niespodziewany sposób.


niespodziewanych. Spora część ludzi popada we frustrację, widząc
kod zgłaszający wyjątki. Jeśli jednak będziesz traktował je jako
sposób stosowany przez .NET do pomocy w lokalizacji i naprawianiu
błędów programu, to naprawdę odniesiesz korzyści podczas
usuwania usterek uniemożliwiających poprawną realizację kodu.

jesteś tutaj ► 605


N ikt się tego nie spodziewał...

Kod Damiana zrobił coś nieoczekiwanego


Gdy Damian pisał swój program do zarządzania wymówkami,
Z ró b to !
nie spodziewał się, że użytkownik będzie próbował odczytać losowe
usprawiedliwienie z pustego folderu.

Problem powstał, gdy Damian nakazał swojemu programowi do zarządzania wymówkami


odczytać usprawiedliwienie z pustego folderu na swoim laptopie, klikając przycisk L osow a
w ym ów ka. Przyjrzyjmy się temu dokładnie i sprawdźmy, czy potrafimy określić, co zostało
zrobione źle. Oto okno nieobsłużonego wyjątku, które zostało wyświetlone podczas
uruchomienia programu bez udziału IDE:

W porządku, to dobry początek. Dzięki temu wiemy, że pojawiła się jakaś wartość,
która nie mieści się w zakresie. Kliknięcie przycisku B reak powoduje powrót do
debuggera, a wykonywanie programu zostanie wstrzymane na konkretnym wierszu kodu:

p u b l i c a s y n c v o id O penR andom E xcuseA sync()


{
I R e a d O n ly L is t < I S t o r a g e F i le > f i l e s = a w a it e x c u s e F o l d e r . G e t F i l e s A s y n c ( ) ;
e x c u s e F ile = file s [ r a n d o m .N e x t( 0 , f ile s .C o u n t( ) ) ] ;
a w a it R e a d E x c u s e A s y n c ();
}

Użyj okna W atch do odnalezienia przyczyny problemu. Dodaj do niego wyrażenie f i l e . C o u n t ( ) .


Wygląda na to, że zwraca ono wartość 0 . A teraz spróbuj dodać do okna Watch wyrażenie
ra n d o m .N e x t(0 , f i l e s . C o u n t ( ) ) . Także ono zwraca wartość 0 . Spróbuj zatem dodać do okna
kolejne wyrażenie: f i l e s [ r a n d o m .N e x t ( 0 , f i l e s . C o u n t ( ) ) ] .

Name Value
0 file s.C o u n tQ 0 a
$3 ra n d o m .N ext(0 , file s.C o u n tQ } 0 a
file s [ra n d o m .N e x t(0 ,file s .C o u n t(}}] | 'fiie s[ra n d o m .N e xt(0 , file s.C o u n tQ }]' th re w a n e xcep tio n o f ty p e S ystem -A rg u m en tExcep tio n '

W'okzi'e Watch m^na także wywoływać metody i wyrożeni0


z indeksami. Jeśli sp°w°dujo one zgłoszenie wyjątku,
to także on zostanie wyświetlony w tym oknie.

606 Rozdział 12.


Obsługa wyjątków

Co się zatem stało? Okazuje się, że wywołanie metody GetFileAsync() obiektu IStorageFolder zwraca
kolekcję IReadOnlyList<IStorageFile>. A podobnie jak inne kolekcje, których już używałeś, także ta
zgłasza wyjątek, jeśli spróbujesz pobrać z niej element, który nie istnieje. Spróbuj pobrać zerowy element
pustej kolekcji, a program zgłosi wyjątek System.ArgumentOutOfRangeException, z komunikatem
„Indeks był spoza zakresu. Musi mieć wartość nieujemną i mniejszą niż rozmiar kolekcji”.

Na szczęście nasz problem można rozwiązać w prosty sposób. Wystarczy przed próbą pobrania pliku
sprawdzić, czy kolekcja zawiera jakiekolwiek elementy.

public async void OpenRandomExcuseAsync()

{
IReadOnlyList<IStorageFile> f ile s = await excuseFolder.GetFilesAsync();
i f (file s .C o u n t() == 0) {
await new MessageDialog("Aktualnie fo ld e r wymowek je s t pusty.").ShowAsync();
re tu rn ;
Sprawdzając pliki
} wymówek w folderze pned,
excuseFile = files[random.Next(0, file s.C o u n t())]; tworzeniem obiektu
await ReadExcuseAsync(); Excuse, możemy zapobiec
zgłoszeniu wyjątku.
Możemy także wyświetlić
okienko dialogowe
z pomocnym komunikatem.
O TAK, JUŻ WIEM. WYJĄTKI NIE ZAWSZE SĄ
TAKIE ZŁE. CZASAMI POMAGAJĄ W IDENTYFIKACJI
BŁĘDU, ALE W WIĘKSZOŚCI PRZYPADKÓW
SUGERUJĄ MI, ŻE WYDARZYŁO SIĘ COŚ,
CZEGO SIĘ NIE SPODZIEWAŁEM.

Tak jest. W yjątki są naprawdę pomocnym narzędziem


używanym do lokalizacji miejsc, w których Twój kod
zachowuje się w nieoczekiwany sposób.

Większość programistów frustruje sytuacja, gdy po raz pierwszy widzą


wyjątek. Wyjątki są jednak bardzo pożyteczne i używając ich, możesz
odnieść znaczące korzyści. Gdy je widzisz, możesz na ich podstawie
określić sposób reakcji na problem, którego nie przewidziałeś. Jest to
dla Ciebie dobre: pozwala poznać nowy scenariusz, który Twój program
musi obsłużyć, i daje możliwość odpowiedniego zareagowania.

jesteś tutaj ► 607


Drzewo genealogiczne w yjątków

Wszystkie obiekty wyjątków dziedziczą po E x c e p t io n


.NET posiada bogaty zestaw wyjątków, których może użyć do raportowania błędów.
W związku z tym, że większość z nich ma podobne cechy, zastosowano technikę
dziedziczenia. .NET definiuje klasę bazową o nazwie Exception , po której dziedziczą
wszystkie szczegółowe wyjątki.

Klasa Exception posiada kilka użytecznych składowych. Właściwość Message przechowuje


łatwy do odczytania komunikat, który definiuje rodzaj błędu. StackTrace pozwala określić,
co się działo w pamięci podczas wywoływania wyjątku i co do tego doprowadziło. (Istnieją także
inne właściwości, ale na początku użyjemy tych).

Exception
ToString() tworzy Message
podsumowanie wszystkich StackTrace_________
informacji zawartych
G e tB a s e E x c e p tio n ()
w polach wyjątku i zwraca je
T o S trin g ()
w postaci łańcucha znaków.

IndexOutOfRange
Exception
Message
StackTrace
G e tB a s e E x c e p tio n ()
T o S trin g ()

c
To naprawdę cenne, że .¡yET
udostępnia tyle różnych typ<Sw
wyjątków, ponieważ każdy z nich
zgłaszany jest w innej sytuacji.
Możesz uzyskać wiele imformacji
na temat niespodziewanych
operacji powodujących
wygenerowanie wyjątku,
po prostu patrząc na jego typ.

608 Rozdział 12.


Obsługa wyjątków

Debugger pozwala Ci wyśledzić


wyjątki w kodzie i zapobiec im Pasek narzędzi Debug
pokazuje się tylko wtedy,
gdy debuguj esz aplikację
Zanim dodasz obsługę wyjątków do programu, powinieneś dokładnie wiedzieć, które za p<°średnictwem IDE.
instrukcje powodują ich zgłaszanie. W tym właśnie wbudowany w IDE debugger może Aby go wyświetlić, będziesz
musiał uruchomić program.
okazać się bardzo pomocny. Podczas lektury tej książki już używałeś debuggera, teraz
jednak poświęć parę minut, by poznać go naprawdę dobrze. Kiedy go uruchomisz, IDE
wyświetli specjalny pasek narzędzi z kilkoma bardzo przydatnymi przyciskami.

B
Kliknij ikonę widoczną z prawej strony paska narzędzi D ebug i wybierz opcję
A d d or R em ove B uttons — pozwoli Ci to poznać wszystkie dostępne operacje związane
z debugowaniem.

Przycisków Continue, Break All oraz Stop Debuaina Przycisk „Refresh Windows app" jest
używany w aplikacjach pisanych w języku
a S Sf a a U Ż .^ IO cC C W S zl^ W U iy^ d ^ ^ m yw an ia JavaScript. W aplikacjach pisanych w C#
jest on niewidoczny.

Ö
A dd or R em ove B u tto n s- ► C on tin u e \ F5

0 11 Break All \ C trl+A lt+ Break

0 ■ Stop D eb u ggin g / Shiftr-FS


PrzyClSk Sh0W_Next Statement sprawia, że 0 0 Restart C trl+ Shift+ F5
IPE z0stanie zaznaczony wiersz
du, który będzie wykonany jako następny 0 to Refresh W in d ow s app F4

0 ■* S h o w Next Statem ent Alt-i-Num *

0 Step Into F 11

Używałeś już tych przycisków podczas sekwencyjnego 0 <4 Step Over FIG
wykonywania kolejnych instrukcji programu. Przycisk
Skip Over powoduje przeskoczenie wywołania metody. 0 C? Step Out S h ift+ F 11
Przycisk Step Into pozwala przejść do pierwszej Hex
instrukcji wewnątrz wywoływanej metody, a przycisk
Step Out pozwala dokończyć jej realizację i zatrzymać 0 S h o w T hreads in So urce
się na pierwszej instrukcji za jej wywołaniem.
W in d ow s /S

C ustom ize...
Jeśli włączysz przycisk Hex, przekonasz się, że Reset T oolbar
możesz nim włączać i wyłączać prezentacj ę w tryloie
szesnastkowym. W razie jego włączenia warto^ W tej książce nie poświęcimy
zmiennych całkowitych (takich typów j ak: int, fong zbyt wiele uwagi wątkom, jeśli
lub byte) w oknie Watch lub po wskazaniu ich myszką jednak jesteś nimi zainteresowany,
będą wyświetlane jako wartości szesnastkowe. to pewne informacje na ich
temat możesz znaleźć w dodatku
„Pozostałości", w punkcie 4.

Przedstawiliśmy tu tę samą
$ value 0x3afb83d9 wartość wyświetloną w trybie & value 989561817
szesnastkowym (po lewej) oraz
dziesiętnym (po prawej).

jesteś tutaj ► 609


Nigdy nie wiesz, gdzie jesteś obserwowany

Użyj debuggera wbudowanego w ID E, aby znaleźć


problem w programie do zarządzania wymówkami
U żyjmy debuggera, aby dokładniej przyjrzeć się problem ow i w pro g ram ie
do zarządzania wym ów kam i. Podczas lek tu ry kilku ostatn ich rozdziałów zapew ne
często z niego korzystałeś, je d n a k po m im o to om ów im y go tu szczegółowo, by m ieć
pew ność, że nie pom inęliśm y żadnego szczegółu n a jego tem at. D e b u g u j to !

DODAJ PUŁAPKĘ DO PROCEDURY OBSŁUGI KLIKNIĘCIA PRZYCISKU? *


Jesteś w dobrym p u n k cie do ro zpoczęcia działań — w yjątek pojaw ia się p o naciśnięciu
przycisku Losow a w ym ów ka, gdy w ybrany je st pusty folder. O tw ó rz k o d dla przycisku,
kliknij gdziekolw iek w pierwszym w ierszu m eto d y i w ybierz Toggle B reakpoint z m en u D E B U G
(lub naciśnij F 9), a n astęp n ie u ru ch o m aplikację. W ybierz jakiś pusty fo ld er i kliknij przycisk
Losow a w ym ów ka, by p ro g ram zatrzym ał się n a p u łap ce :

PRZEJDŹ KROK PO KROKU PRZEZ METODĘ OPENRANPOMEXCUSEASYNC0.


Użyj p olecen ia Step In to (za p o m o cą p ask a n arzędzi lub klaw isza F 1 1 ), aby wejść do m etody.
N astęp n ie skorzystaj z p o lece n ia Step Over, aby w iersz p o w ierszu prześledzić jej realizację.
Poniew aż w ybrałeś pusty fo ld er, pow inieneś zobaczyć, ja k p ro g ram w ykonuje m eto d ę
M essageDialog() , a n a stęp n ie w ychodzi z m etody.

T eraz wskaż folder zawierający jakieś pliki wymówek, ponow nie kliknij przycisk Losow a wymówka
i w ejdź do m etody. Tym razem b lo k instrukcji i f zostanie pom inięty, a realizacja p ro g ra m u przejdzie
do następ n eg o w iersza kodu.

610 Rozdział 12.


Obsługa wyjątków

U Ż Y J O K N A W A T C H D O O D T W O R Z E N IA PR O B LEM U .
Widziałeś już, jak wielkie możliwości daje okno Watch. Teraz wykorzystamy je do odtworzenia wyjątku. Przerwij
Planujesz wykonywanie programu, usuń ustawioną wcześniej pułapkę, a następnie dodaj nową, umieszczając ją w drugim
przerwać
wykonywanie wierszu metody OpenRandomExcuseAsync(). Uruchom program, wybierz pusty folder, a następnie kliknij
kodu w drugim przycisk L osow a w ym ów ka. Kiedy debugger przerwie działanie programu, zaznacz wyrażenie file s.C o u n t(),
wierszu, gdyż kliknij je prawym przyciskiem myszy i wybierz opcję ^ Add Watch, by dodać je do okna Watch:
to właśnie
w nim jest
umieszczone W atch 1 ▼n x
odwołanie do Name Value
obiektu plików. 0 files.CountO 0

D O D A J K O L E J N E W Y R A Ż E N IE D O O K N A W A T C H I Z A C Z N I J P O S Z U K IW A N IE P R O B L E M U
Debugowanie przypomina nieco analizę miejsca zbrodni, którym w tym przypadku jest Twój program.
Niekoniecznie będziesz wiedział, czego szukasz, aż to coś znajdziesz; dlatego też będziesz musiał skorzystać
z wszelkich dostępnych narzędzi programistycznego kryminologa, by odnajdywać wskazówki i wyśledzić
winowajcę. Ponieważ przyczyną problemów nie było wywołanie file s.C o u n t(), zajmij się następnym
podejrzanym: zaznacz wywołanie random .N ext(files.C ount()) i dodaj je do okna Watch:

Okno W atch posiada jeszcze inne użyteczne narzędzie — pozwala ono na zmianę wartości wyświetlanych
zmiennych i pól. Możesz nawet dzięki niemu wykonać metody i utworzyć nowe obiekty. Gdy to zrobisz,
wyświetli ono ikonę ponownego obliczenia wartości (Q ), którą możesz kliknąć, aby po raz drugi wykonać daną
instrukcję. Jest to uzasadnione tym, że niektóre metody wykonane powtórnie mogą generować różne wyniki
(na przykład te z obiektu Random).

O D T W Ó R Z P R O B L E M , K T Ó R Y S P O W O D O W A Ł P O W S T A N IE O R Y G IN A L N E G O W Y J Ą T K U
To w tym miejscu debugowanie staje się naprawdę interesujące. Dodaj do debuggera jeden wiersz kodu —
Nawet jeśli już instrukcję, która spowodowała zgłoszenie wyjątku: fileNames[random.Next(0, f ile s .C o u n t ( ) ) ] . Zaraz po jej
rozwiązałeś wpisaniu wartość wyrażenia zostanie obliczona przez okno Watch... i powstanie wyjątek.
problem,
dodając kod
sprawdzający,
czy folder
zawiera jakieś
pliki, to i tak
będziesz mógł
użyć okna
Watch do Kliknij ikonkę ze znakiem „ + ”, by rozwinąć dane wyjątku, a przekonasz się, że właściwość Message ma wartość
wygenerowania „Value does not fall within the expected range”. Teraz już dokładnie wiesz, co powoduje problem i dlaczego
wyjątku.
on występuje. Wywołanie metody G etFilesAsync() zwraca kolekcję typu IReadOnlyList<IStorageFile>,
której długość dla folderu pustego wynosi 0. Jeśli spróbujesz użyć jej indeksatora (file s [0 ]), odwołanie do
niego zgłosi wyjątek ArgumentException.

Gdy otrzymasz w yjątek, możesz odtworzyć dany problem w debuggerze


i użyć obiektu Exception, by wprowadzić w kodzie odpowiednie poprawki.
jesteś tutaj ► 611
Zrób sobie przerwę

P : K ie d y urucham iam aplikację O : W zasadzie Twój program zatrzymuje


Potem, w związku z tym, że S le e p ()
nie zwraca żadnej wartości, zostanie
w ID E , to dane w y ją tk u mogę zobaczyć się wtedy, gdy pojawi się nieobsłużony
wyświetlony komunikat: „Expression has
w oknie W atch. A co się stanie, kiedy wyjątek. Nie oznacza to wcale, że wszystkie
been evaluated and has no value", który Cię
spróbuję uruchomić aplikację poza ID E? Twoje wyjątki muszą takie być! Już
0 tym poinformuje. Operacja została jednak
niebawem powiemy sobie znacznie
wykonana. Ale to nie wszystko. IntelliSense
O : Na to pytanie można odpowiedzieć więcej na temat przechwytywania
wyświetla odpowiednie okno, które pomaga
w bardzo prosty sposób. Wystarczy, i obsługiwania wyjątków w naszym kodzie.
Ci wpisywać kod. To także użyteczne,
że umieścisz w komentarzach zmiany Nie ma powodu, aby użytkownicy Twoich
ponieważ dzięki temu wiemy, które metody
wprowadzone w celu poprawienia kodu programów musieli je widzieć.
mogą być wywoływane na rzecz obiektu
metody OpenRandomExcuseAsync()
i uruchomisz aplikację, wybierając w tym P : Skąd mam w ie d zie ć, gdzie
podczas jego działania.

celu opcję DEBUG/Start Without Debugging. w sta w ić pułapkę?


P : Z a c z e k a j, c z y nie je st w takim
Spowoduje to uruchomienie aplikacji w taki
sposób, jakby został kliknięty jej kafelek na
O : To bardzo dobre pytanie, ale nie ma
ra z ie m o żliw e uruchom ienie w oknie
W atch czegoś, co zm ieni sposób
na nie właściwej odpowiedzi. Kiedy Twój
ekranie startowym. (Można także wyświetlić w yko n yw a n ia mojego program u?
kod zgłasza wyjątek, to zawsze dobrze
ekran startowy i faktycznie tak zrobić).
jest rozpocząć od instrukcji, która takie
Następnie wybierz pusty folder, kliknij przycisk O : Tak, jest! Nie na stałe, ale wykonanie
zachowanie spowodowała. Zwykle problem
Losowa wymówka i... bum! Aplikacja zniknie! pewnych instrukcji zdecydowanie może mieć
w programie pojawia się nieco wcześniej,
wpływ na działanie aplikacji. A nawet więcej,
Zazwyczaj właśnie to się dzieje, gdy w aplikacji a wyjątek jest tego efektem. Na przykład
bo zwykłe umieszczenie wskaźnika myszy
zostanie zgłoszony nieobsłużony wyjątek. wyrażenie dzielące przez zero może korzystać
nad polem może zmienić jego zachowanie,
(W dalszej części rozdziału dowiesz się z wartości wyliczonych dziesięć instrukcji
powoduje ono bowiem wywołanie jego
więcej o tym, jak można je obsługiwać). przed nim, które po prostu nie zostały do tej
akcesora get, jeśli natomiast posiadasz
Większość użytkowników nie chce oglądać pory użyte. Nie ma zatem dobrej odpowiedzi
właściwość, która wywołuje metodę,
okien wyjątków, pełnych nazw metod na pytanie, gdzie ustawiać pułapki, ponieważ
to czynność ta spowoduje jej uruchomienie.
i szczegółów technicznych. Nie martw się każdą sytuację należy rozpatrywać
Jeżeli metoda ta ustawia jakieś pole, może
jednak — zgłoszony wyjątek nie przepadł. indywidualnie. Jeśli jednak dobrze rozumiesz
ono zostać zapisane i być dostępne podczas
Wystarczy otworzyć Panel sterowania działanie kodu, powinieneś być w stanie
następnego uruchomienia programu. To
(w tym celu wyświetl ekran startowy, wpisz namierzyć kontrowersyjną instrukcję.
z kolei może doprowadzić do powstania
panel i kliknij Panel sterowania), poszukać
„zdarzeń"; i kliknąć łącze Wyświetl dziennik P : C z y w oknie W atch mogę
niespodziewanych rezultatów w debuggerze.
Programiści mają określenie na wyniki, które
uruchom ić dowolną metodę?
zdarzeń. Następnie należy rozwinąć opcję wydają się być losowe i nieprzewidywalne:
Dziennik systemu Windows i kliknąć Aplikacja. mówią na to heisenbug (co jest swego
O : Tak. Każda instrukcja, która jest
Jedno ze zdarzeń łH&My zapisanych w tym rodzaju żartem mającym sens dla fizyków
prawidłowa w programie, powinna także
dzienniku będzie zawierać wyjątek zgłoszony 1kotów w pudełkach).
zadziałać w oknie Watch; dotyczy to nawet
przez Twoją aplikację, w tym postać stosu tych instrukcji, których umieszczanie w nim
wyw oła ń, w tym wiersz, który spowodował nie ma najmniejszego sensu. Oto przykład. Kiedy uruchamiasz
zgłoszenie wyjątku, wiersz, który go wywołał,
i tak dalej (właśnie ta sekwencja jest nazywana
Utwórz program, uruchom go, a następnie program wewnątrz
dodaj coś takiego do okna Watch: System .
stosem wywołań). Podczas debugowania T h re a d in g .T h re a d .S le e p (2 0 0 0 ). (Jak ¡DE, nieobsłużone
aplikacji stos wywołań jest zapisany we sobie przypominasz, metoda ta spowoduje, wyjątki powodują
właściwości S tack T a ce obiektu Excep tio n . że program będzie czekał przez dwie
sekundy). Nie ma powodu, by przeprowadzać jego zatrzymanie,
P : W ięc o to chodzi? Jeśli w yją te k takie manewry w rzeczywistości, ale ich
efekt może być interesujący. Pojawi się
tak jakby podczas
powstaje poza ID E, to mój program
zatrzym uje się i nic z tym nie mogę klepsydra i będzie ona widoczna przez dwie wykonywania natrafił
zrobić? sekundy podczas wykonywania instrukcji.
on na pułapkę.
612 Rozdział 12.
Obsługa wyjątków

O j, oj! — w kodzie dalej są błędy...


D am ian używał szczęśliwie p ro g ra m u do zarząd zan ia w ym ów kam i, aż
w ybrał folder p ełen plików X M L , k tó re nie zostały zapisane przy użyciu
p ro g ram u do zarząd zan ia wym ów kam i. Z obaczm y, co się stanie, kiedy
spróbuje otw orzyć je d e n z n ich ...

M ożesz odtw orzyć p ro b lem D am ian a . O dszukaj je d e n z plików X M L zaw ierających


serializow any o b iek t E xcuse. O tw órz go w N o ta tn ik u i dodaj dow olny tek st (byleby nie
był to p o praw ny k o d X M L ) n a sam ym p o czątk u — p rze d pierwszym znakiem <.

O tw órz p ro g ram do zarząd zan ia w ym ów kam i, a n a stęp n ie wymówkę. Z o sta n ie zgłoszony


wyjątek! Przyjrzyj się uw ażnie kom unikatow i w yjątku, a n a stęp n ie kliknij przycisk Break,
aby rozpocząć poszukiw anie jego przyczyny.

W yświetl okno Locals i dodaj do niego zm ienną $ e x c e p tio n (m ożesz ją także dodać do
okna W atch). D o kładnie przyjrzyj się składowym o biektu i spróbuj określić, co jest nie tak.

C Z Y J U Ż W I E S Z , D L A C Z E G O P R O G R A M Z G Ł O S IŁ W Y J Ą T E K ?

C Z Y Z G Ł A S Z A N I E W Y J Ą T K U , JE Ś L I P R O G R A M N A P O T K A
N I E P R A W I D Ł O W Y P L IK W Y M Ó W K I , M A S E N S ?

C Z Y P O T R A F IS Z W Y M Y Ś L IĆ , C O Z T Y M M O Ż N A Z R O B IĆ ?

jesteś tutaj ► 613


Użytkownicy są nieprzewidywalni

Z A C Z E K A J C H W IL Ę ! O C Z Y W IS T E
JEST, Ż E P R O G R A M N IE BĘDZIE D Z IA Ł A Ł —
P R Z E K A Z A Ł A M M U Z Ł Y P LIK. U Ż Y T K O W N IC Y
C A Ł Y C ZAS COŚ PSUJĄ. N IE SPO DZIEW ASZ SIĘ
C H Y B A , Ż E T O SIĘ Z M IE N I, P R A W D A ?

W łaściw ie co ś m o żn a z tym zrobić.


Tak, to prawda, że użytkownicy cały czas coś psują. To fakt. Nie oznacza
to jednak, że nie można na to nic poradzić. Istnieje nawet specjalna
nazwa dla programów, które radzą sobie z zepsutymi danymi
wejściowymi i niespodziewanymi sytuacjami w sposób elegancki.
Są to programy odporne. C# daje Ci naprawdę potężne narzędzie, które
pomaga takie aplikacje pisać. N ie m ożesz dokładnie kontrolować tego,
co robią użytkownicy, ale m ożesz zadbać o to, aby program nie kończył
niespodziewanie swojego działania, gdy tylko zrobią coś dziwnego.

Odporny, przymiotnik

niew rażliwy na wpływy fizyczne


lub moralne.

Po katastrofie mostu zespół inżynierów


zaczął poszukiwać rozwiązań, k tó e
by fyby bardziej odpo rn e i mogły zastąpić
d m e n ty starej konstrukcji.
Mechanizmy serializacji zgłoszą wyjątek,
jeżeli z deserializowanym plikiem
jest coś nie tak.
Bardzo łatwo otrzymać wyjqtek S e r i a l i z a t i o n E x c e p t i o n
Także klasa BinaryF°rmatter zgłos< wygenerowany przez program do zarządzania wymówkami
wyjątek SerializationException, — wystarczy przekazać mu dowolny plik, który nie jest zserializowanym
jeśli nie przekażesz jej pliku ___ obiektem E x c u s e . Kiedy próbujesz przeprowadzić deserializację z pliku,
zawierającego odpowiednio
serializowanego Mek-ttu.. P°d D a t a C o n t r a c t S e r i a l i z e r spodziewa się, że piik ten będzie zawierał
tym względem jest ona jeszcze zserializowany obiekt zgodny z kontraktem klasy którą stara się wczytać.
bardziej wybredna od klasy Jeżeli zawiera on coś innego, cokolwiek, wtedy metoda R e a d O b je c t()
DataContractSeriaTizeir!
zgłasza wyjątek S e r i a l i z a t i o n E x c e p t i o n .

614 Rozdział 12.


Obsługa wyjątków

Obsłuż wyjątki za pomocą try i catch


W przypadku C # można powiedzieć: „Wypróbuj ten fragment kodu, a jeżeli
wystąpią w nim jakieś wyjątki, wyłap je i obsłuż w innym fragmencie”. Kawałkiem
kodu, który testujesz, jest blok try , natomiast fragmentem, w którym obsługujesz
wyjątki, jest blok catch. W tym drugim możesz wykonać wiele rzeczy, na przykład Kod, który może powodować
wypisać przyjazny dla użytkownika komunikat o błędzie. Jest to lepsze rozwiązanie zgłoszenie wyjątku, umieść
w bloku try. Jeżeli nie pojawi
niż natychmiastowe zatrzymanie się programu. się żaden błąd, to zostanie
on wykonany tak jak zwykle
i instrukcje w bloku catch
public async Task ReadExcuseAsync() { będą ignorowane. Jeśli jednak
instrukcje w bloku try zgłoszą
try wyjątek, to reszta tego bloku
nie zostanie wykonana.
{
using (IRandomAccessStream stream =
await excuseFile.OpenAsync(FileAccessMode.Read))
using (Stream inputStream = stream.AsStreamForRead()) {
DataContractSerializer serializer
To jest blok = new DataContractSerializer(typeof(Excuse));
try. Obsługę
wyjątków CurrentExcuse = serializer.ReadObject(inputStream) as Excuse;
rozpoczynasz
słowem } Rozpoznasz ten kod, gdyż w bloku try
kluczowym umieściliśmy cały kod metody.
t r y . W tym
przypadku await new MessageDialog("Wymówka odczytana z pliku "
umieścimy
w nim + excuseFile.Name).ShowAsync();
istniejący kod.
OnPropertyChanged("CurrentExcuse");
await UpdateFileDateAsync();
} Słowo kluczowe catch oznacza,
catch (SerializationException że blok występujący zaraz po nim
zawiera procedurę obsługi wyjątku.
{
new MessageDia1og("Nie udało się odczytać pliku 11
+ excuseFile.Name).ShowAsync();
} zo stajt zgłoszony wyjątek, program
natychmiast skacze do instrukcji catch
} i wykonuje umieszczony w niej blok kodu.
Oto najprostszy sposób obsługi wyjątków: wstrzymać
działanie programu, wyświetlić komunikat o wyjątku
i kontynuować działanie. Czy zwróciłeś uwagę, że przed
WYSIL
wywołaniem konstruktora MessageDialog nie umieściliśmy SZARE KOMÓRKI
słowa kluczowego await? Wynika to z faktu, że nie można
czekać wewnątrz bloku instrukcji catch. Na szczęście Jeżeli zgłaszanie wyjątku powoduje automatyczne
wciąż można skorzystać z metody ShowAsync(), choć jej przeskoczenie do bloku catch , to co się dzieje z danymi
użycie spowoduje zablokowanie programu aż do momentu i obiektami, z którymi pracujesz podczas takiego zdarzenia?
zamknięcia okna dialogowego przez użytkownika.
jesteś tutaj ► 615
Ryzykowny biznes

Co się stanie, jeżeli wywoływana metoda będzie niebezpieczna?


U żytko w n icy są nieprzew idyw alni. K a rm ią pro gra m dziw nym i danym i i k lik a ją różne rzeczy w sposób,
ja kieg o byś się nigdy nie spodziewał. A le wszystko działa dobrze, poniew aż możesz obsłużyć nieoczekiwane
dane wejściowe, korzystając z dobrej obsługi wyjątków .

@ Powiedzmy, że użytkownik do m e to d y
vV.o^nik wptow
korzysta z Twojego
kodu i wprowadza dane
wejściowe, których program
się nie spodziewa.

jakieś dane wejściowe klasa, k tó rą napisałeś


użytkow nik

@ Ta metoda wykonuje coś p u b lic v o id


niebezpiecznego. Coś, co P ro c e s s (In p u t i ) {
może nie działać w czasie if ( i. I s B a d ( ) ) {
wykonywania. E x p lo d e ();
klasa, k tó rą napisałeś }
}
Po jęcie „cza s wykonywania" oznacza
to s amo co „gdy Tw< 5j program j e s t
Z k flT T y " - którzy traktują wyjątki
jako „btędy czn su wykonywania".

3 Musisz wiedzieć,
że wykonywana przez
Ciebie metoda jest
niebezpieczna.
J e ś li je s t e ś w sta n ie wym yślić, ja.k
zrobić to samo w mniej ryzykowny klasa, k tó rą napisałeś
sposób i uniknąć m ożliwości z g to sz ^ id u żytko w nik
w yjątku, to będzie to najlepsze możliw e
rozwiązanie! Jednak czasam i po p t t t o me
można uniknąć ryzyka i wtaśnie w takich HEJ, TEN PROGRAM
przypadkach będziemy chcieli z robić to... JEST NAPRAWDĘ STABILNY!

Piszesz kod, który może


V
®
obsłużyć wyjątek, jeśli ten
wystąpi. Musisz być na
to przygotowany tak jak
w tym przypadku.
Twoja klasa, teraz już
u żytko w nik z obsługą w yjątków
616 Rozdział 12.
Obsługa wyjątków

P : K ie d y powinienem u żyw a ć t r y
rozwiązanie. Jeśli jednak program
wyświetli komunikat o błędzie informujący
instrukcję albo wejdziesz do funkcji lub
wyjdziesz z niej. Pozwala to na dokładne
i c a tc h ?
0 problemach z odczytaniem pliku, monitorowanie zmiennych po wykonaniu
O : Za każdym razem, gdy masz do to użytkownik będzie miał szansę domyślić
się, co może być nie tak, i użyć tych
każdego fragmentu kodu, co może
okazać się niezwykle pomocne podczas
czynienia z kodem niebezpiecznym lub
takim, który może zgłaszać wyjątki. Cały informacji w celu naprawienia problemu. wyszukiwania problemu.
problem sprowadza się do określenia, który
kod jest ryzykowny, a który pewny. P : C z y debugger u ż y w a n y je st tylko
Okno Watch pozwala Ci także wpisać
dowolne wyrażenie i obliczyć jego wartość.
do badania w y ją tk ó w ?
Widziałeś już kod, który w przypadku Jeśli wyrażenie to zmienia wartości innych
podania nieprawidłowych danych O : Nie. Debugger jest w zasadzie
pól i zmiennych w programie, także może
zostać wykonane. Dzięki temu możliwa
wejściowych może stać się niebezpieczny. bardzo praktycznym narzędziem, którego
Użytkownicy podają nieprawidłowe pliki, staje się modyfikacja wartości podczas
możesz używać do badania każdego
słowa zamiast liczb, nazwy zamiast dat działania aplikacji. To z kolei może być
napisanego kodu. Czasami pomocne
i klikają wszędzie, gdzie tylko jesteś sobie kolejnym narzędziem pomocnym podczas
okazuje się krokowe wykonanie programu
w stanie wyobrazić. Dobry program powtarzania wyjątków i innych błędów.
1sprawdzenie wartości określonych pól

T
przyjmie takie dane i będzie w dalszym i zmiennych — na przykład wtedy, gdy
ciągu pracował w spokojny, przewidywalny badana metoda jest bardzo złożona
sposób. Najprawdopodobniej użytkownik i chcesz mieć pewność, że działa W szy stkie zmiany wprowadzone
nie otrzyma spodziewanych rezultatów, ale w oknie Watch wpły waj ą na dane
prawidłowo. przechowywane w pam ięci i pozostają
będzie przynajmniej wiedział, że program tam do czasu zak°ńc.zenia pr° gramu-
Zapewne domyśliłeś się, biorąc pod uwagę
znalazł problem i zasugerował rozwiązanie. Uruchom aplikację ponownie, a w ar>oścl
nazwę „debugger", że jest to narzędzie te zostaną u su n ięte.
P : W ja k i sposób program może
używane generalnie do wyszukiwania
i usuwania błędów (ang. bug). Czasami
zasugerow ać ro zw ią za n ie problem u,
je że li w cześn iej naw et o nim nie w ie?
błędy takie przyjmują postać zgłaszanych Blok catch
wyjątków. W większości przypadków
O : To do tego służy blok ca tch . Jest on będziesz jednak używał debuggera do
wykonywany jest
wykonywany tylko wtedy, gdy kod w bloku wyszukiwania usterek innego rodzaju,
na przykład wtedy, gdy kod zwraca
tylko wtedy, gdy
t r y spowoduje powstanie wyjątku.
Masz dzięki niemu szansę powiadomić wartości, których się nie spodziewałeś. kod w bloku try
użytkownika, że coś poszło niezgodnie
z planem, i przekazać mu, że jest P : N ie jestem pew ien, c z y dobrze zgłosi wyjątek.
to sytuacja możliwa do naprawienia.
zro zum iałem to w sz y stk o , co robiłeś
w oknie W a tch . M ożesz p o w tó rzyć,
Masz dzięki
Gdyby program do zarządzania
wymówkami kończył działanie w sposób
po co ono jest? niemu możliwość
natychmiastowy w przypadku otrzymania O : Kiedy debugujesz program, zwykle wyświetlenia
nieprawidłowych danych, nie byłoby zwracasz uwagę na zmianę wartości
to szczególnie użyteczne. Gdyby próbował określonych zmiennych i pól. To właśnie użytkownikowi
w jakiś sposób je odczytać i wyświetlić
śmieci na formularzu, także nie byłoby
do tego służy okno Watch. Jeśli wstawisz
w nie kilka zmiennych, ich wartość będzie
stosownej
to pomocne. Co więcej, niektórzy aktualizowana przez okno za każdym informacji
powiedzieliby nawet, że jest to gorsze razem, gdy wykonasz pojedynczą
pozwalającej
naprawić problem.
jesteś tutaj ► 617
Płyń z prądem

Użyj debuggera do prześledzenia przepływu w blokach try/catch


W ażną częścią obsługi w yjątków je st to, że jeżeli instru k cja w bloku t r y
zgłosi je d e n z nich, to p ozostały k o d tego bloku je st pomijany. _ . .
D alsze w ykonyw anie p ro g ra m u rozpoczyna się o d pierw szego w iersza D e b u g u j to !
w bloku c a tc h . N ie w ierz je d n a k we w szystko n a sło w o ... jr

D o m eto d y R ead E x c u seA sy n c() aplikacji zarządzającej w ym ów kam i dodaj,


p rzedstaw io n e kilka stro n w cześniej, instrukcje t r y / c a t c h . N a stęp n ie um ieść
p u łap k ę w w ierszu zaw ierającym otw ierający naw ias klam row y { bloku tr y .:

Klasa S e r ia liz a t io n E x c e p t io n je st zd efin io w an a


w przestrzen i n a z w S y s t e m . R u n t im e . S e r ia liz a t io n . Na szczęście już
w cześniej um ieściłeś instrukcję using S y s t e m . R u n t im e . S e r ia liz a t io n
na sam ym początku pliku E x c u seM a n a g e r.c s.

Zacznij debugow ać aplikację i otw órz plik, któ ry nie jest prawidłowym plikiem wymówki
(ale m a ro zszerzenie .xm l). K iedy realizacja p ro g ra m u zatrzym a się n a p u łap ce , pięć razy
kliknij przycisk Step O ver (lub naciśnij klawisz F 1 0 ), aby d o trzeć do instrukcji wywołującej
m eto d ę R e a d O b je c t( ), k tó ra p o d ejm ie p ró b ę deserializacji o b ie k tu E xcuse. W tym
m om encie o k n o deb u g g era pow inno w yglądać następująco:

public async Task ReadExcuseAsync()


{
try

Um ieść pułapkę -> 0


using (IRandomAccessStream stream =
w w ierszu
await excuseFile.OpenAsync(FileAccessMode.Read))
zawierającym
using (Stream inputStream = stream.AsStreamForReadQ)
otw ierający
nawias klamrowy {
DataContractSerializer serializer
bloku try. = new DataContractSerializer(typeof(Excuse));
----- ^ CurrentExcuse = serializer.ReadObject(inputStream) as Excuse;
}
Wykonuj kolejno await new MessageDialog(”Wym6wka odczytana z pliku "
instru kcje, aż + excuseFile.Name).ShowAsync();
wyróżniony na OnPropertyChanged("CurrentExcuse”);
żółto w iersz await UpdateFileDateAsync();
„następnej
}
in stru kcji" catch (SerializationException)
wskaże, że
{
następną new MessaceDialogi"Nie udało sie odczytać pliku " + excuseFile.Name).ShowAsynci);
V \ A W V V V > A W V V N A A r t ^ M A / V W M V W V V V V > r t V \ « V W V A / W V V W W > r t A r t A A r t A A A r t A A r t r t A A ft ft r t r t A ^ V V W V V V W V V V V V V T V V V V <
instru kcją będzie
wczytanie obiektu
E x c u se ze
strum ienia.

618 Rozdział 12.


Obsługa wyjątków

^ W ykonu j pro gra m dalej in stru kcja po in stru kcji. Z araz po w yko na niu przez debugger in stru kcji
ReadObject() zgłaszany jest w yjątek, a pro gra m p o m ija pozostały ko d i przechodzi bezpośrednio
do p ie rw sze j in s tr u k c ji w b lo k u c a tc h .

Debugger podśw ietli


in stru kcję catch
żółtym kolorem
następnej
in stru kcji, ale
pozostała cz ę ść
bloku będzie
wyświetlana na
szarym tle. Oznacza
to, ż e zostanie
on wykonany
jako ca łość.

[4 W znów w ykonyw anie ko d u za pom ocą przycisku Continue (lub F 5 ). Program rozpocznie
działanie od fragm e ntu ko d u w yróżnionego żółtym kolore m , oznaczającego b lo k następnej
in s tru k c ji — w tym przypadku od b lo k u catch. Spowoduje ona je dyn ie w yśw ietlenie okna
dialogowego, a następnie uda, że nic się nie stało. Jednak aw aria zostanie obsłużona.

W skazówka dotycząca Strzeż się w yjątków w konstruktorze!


Twojej kariery: większ o ść
rozmów kwalifikacyjn ych
na tem at programowania Uu)<M^> Zauważyłeś zapewne, że konstruktor nie ma żadnej wartości
zaw iera pytanie o s p °soby^ wynikowej, nawet gdyby miała to być wartość void.
radzenia so bie z wyjątkami
To dlatego, że nic on nie zwraca. Jedynym jego celem jest inlcjallzacja
w konstruktorze.
obiektu — i właśnie to sprawia, że obsługa wyjątków wewnątrz
konstruktora przysparza wielu problemów. Kiedy zgłaszany je st w nim
wyjątek, instrukcja tworząca obiekt nie p o w o d u je p o w sta n ia je g o
instancji.

jesteś tutaj ► 619


Posprzątaj po sobie

Jeśli posiadasz kod, który ZAW SZE musi zostać wykonany, zastosuj finally
K ie d y T w ó j pro gra m zgłasza w yjątek, może się zdarzyć k ilk a rzeczy. Jeśli nie zosta ł on obsłużony, program zakończy
przetw arzanie i nagle się zam knie. Jeżeli w yją te k je s t obsługiwany, w ykonyw anie in s tru k c ji przeniesione zostanie do
b lo k u c a tc h . Co dzieje się w ted y z resztą kod u w b lo k u t r y ? Co w przypadku, gdy zamyka on strum ie ń lub zwalnia
cenne zasoby? K o d ten p o w in ie n zostać u ru ch o m io n y nawet w tedy, gdy wystąpi w yjątek. W przeciw nym razie możem y
doprow adzić do nieokreślonego stanu w program ie. W takich przypadkach należy użyć b lo k u f i n a l l y , k tó ry jest
umieszczany za blo ka m i t r y i c a tc h . B lo k f i n a l l y je s t w yko n yw a n y zawsze, bez w zględu na to, czy w yją te k zostanie
zgłoszony. O to w ja k i sposób m ożna w ykorzystać b lo k f i n a l l y , by m etoda R eadExcuseA sync() zawsze generowała
zdarzenie P rop ertyC h ang ed .

publ ic async Task ReadExcuseAsync() {


try
{
u s in g (IRandomAccessStream stream =
a w a it excuseF ile.O penA sync(F ileA ccessM ode.R ead))
u s in g (Stream in p u tS tre a m = stream .A sS tream ForR ead()) {
D a ta C o n tra c tS e ria liz e r s e r i a li z e r
Wywołanie metody = new D a ta C o n tr a c tS e r ia liz e r(ty p e o f(E x c u s e ));
N ew Excu seA sync()
powoduje C urrentE xcuse = s e ria liz e r.R e a d O b je c t(in p u tS tre a m ) as Excuse;
przywrócenie
}
obiektu wymówki
do stanu
początkowego, a w a it new MessageDialog("Wymówka odczytana z p lik u "
je d nak strona nie
odczyta w łaściw ości + excuseF ile.N am e ).S h ow A sync();
C urrentExcu se, a w a it U p d a te F ile D a te A s y n c ();
j e śli nie zostanie
zgtoszone zdarzenie }
PropertyChanged. ca tch (S e r ia liz a tio n E x c e p tio n )
Blok finally
zapewni, że {
zdarzenie to new M e ssa ge D ia log ("N ie ud ało s ię o d czyta ć p lik u "
z ostanie zgłoszone
niezależnie od + e xcu se F ile .N a m e ).S h o w A syn c();
tego, czy w ystą pił NewExcuseAsync();
w yjątek czy nie. } Dodanie wywołania N ew ExcuseO do bloku
catch spraw ia, że w przypadku zg f°szenia
f in a lly wyjątku formularz zostanie wyczy szczony ■
{
O n P ro p e rtyC h a n g e d ("C u rre n tE xcu se ");

Zawsze w y ła p u j i o b s łu g u j w yspecjalizow ane w y ją tk i ta k ie ja k S e r ia liz a t io n E x c e p t io n . Zazwyczaj po słowie


kluczowym c a tc h podawana jest nazwa wyspecjalizowanego w yjątku, k tó ry dany b lo k ma obsługiwać. C # pozwala także
na zastosowanie bloku c a tc h o postaci c a tc h (E x c e p tio n ), a nawet na całkow ite pom inięcie nazwy wyłapywanego wyjątku.
W takim przypadku zostaną przechwycone i obsłużone wszystkie w yjątki, niezależnie od ich typu. Jednak stosowanie
takiej ogólnej procedury obsługi wyjątków je s t uważane za bardzo złą praktykę program istyczną . T w ój kod pow inien zawsze
wyłapywać i obsługiwać w yjątki ja k najbardziej szczegółowe.

620 Rozdział 12.


Obsługa wyjątków

^ .
T e r a z d e b u g u j to !

Z a k tu a liz u j m etodę ReadExcuseAsync(), umieszczając w niej k o d przedstaw iony na poprzedniej stronie.


N astępnie dodaj pułapkę w w ierszu zawierającym otw ierający nawias kla m ro w y b lo k u try.

U ru ch o m aplikację zwyczajnie i up ew nij się, że przycisk otw ierający w ym ów kę działa pra w id ło w o, jeśli
spróbujem y wczytać działający p lik w ym ów ki. D ebugger p o w in ie n przerw ać działanie a p lika cji w wierszu
zawierającym pułapkę. D ebugger p o w in ie n zatrzym ać się na ustaw ionej przez C iebie pułapce.

p u b lic a sync T ask ReadExcuseA syncQ


{
try
0 u s in g (IRandom AccessStream stream =
Kiedy wyróżniony a w a it excuseFile.OpenAsync(FileA ccessM o d e .Read))
u s in g (Stream inp utS tre am = s tre a m .A sS tre a m F o rR e a d ())
fragment kodu
następnej t
D a t a C o n t r a c t S e r ia liz e r s e r i a l i z e r
instru kcji = new D a t a C o n t r a c t S e r ia liz e r ( t y p e o f (E x c u s e ));
i pułapka C u rre n tE xc u se = s e r ia liz e r .R e a d 0 b je c t (in p u t 5 t r e a m ) a s Excuse.;
znajdują s ię }
w tej sam ej
linii, wtedy ID E a w a it new M essageD ialo g (” Wymówka o d czytana z p lik u "
+ e xc u s e F ile .N a m e ).S h o w A sy n c ();
na marginesie
a w a it LPp d ate FileD ate A syncQ j
pokazuje
}
żółtą strzałkę ca tc h ( S e r ia liz a t io n E x c e p t io n )
przesłaniającą t
dużą czerwoną
kropkę. N e w E xcu se (); ^

-iia iiv Z wr<5ć szczególną uwagę na to, co dzieje


s ię z tymi oknami dialogowymi. Czasami nie
OnPropertyChanged( "C u rre n tE x c u s e ") ; zostaną one wy ś w ietlone przed zakończeniem
i wywołania metody. Witamy w św ie c ie metod
(^synchronicznych!

Przejdź kro ko w o przez pozostałą część m etody i upew nij się, że wszystko działa tak, ja k się tego
spodziewasz. P ow inien zostać dokończony b lo k t r y , b lo k catch po w in ie n zostać o m in ię ty (ponieważ nie
było żadnych w yjątków ), a następnie po w in n y zostać wykonane in stru kcje b lo k u f in a l ly .

A teraz spróbuj otw orzyć n ie p ra w id ło w y p lik w ym ów ki. W ykonyw anie p o w in no się rozpocząć od b lo k u try ,
a po n a po tka niu w yją tku debugger p o w in ie n przeskoczyć do b lo k u catch. Po w yko na niu wszystkich jego
in s tru k c ji po w in n o się rozpocząć w ykonyw anie ko d u z b lo k u fin a lly .

jesteś tutaj ► 621


W yjątki prow adzą do braku stabilności

P : Powtórzmy sobie. Za każdym razem,


właśnie do niego i zrobione zostanie wszystko,
co przewidziałeś dla określonego wyjątku. P : Co się dzieje, gdy blok catch nie
gdy mój program napotka wyjątek, określa szczegółowo rodzaju wyjątku?
W związku z tym, że napisałeś dla niego blok
zakończy swoje działanie, chyba że napiszę
kod, który go wyłapie. Czy to jest dobre?
catch, jest on traktowany jako obsłużony.
Gdy środowisko wykonawcze nie może odnaleźć
O : Blok catch tego typu będzie wyłapywał
każdy rodzaj wyjątku, jaki może zostać
O : Jedną z najlepszych cech wyjątków jest właściwego dla wyjątku bloku catch, wtedy
program jest zatrzymywany i zgłaszany jest błąd.
zgłoszony w bloku t ry .
to, że w sposób oczywisty sygnalizują, kiedy
w programie pojawiają się problemy Wyobraź Taki wyjątek nazywamy nieobsłużonym
P : Skoro blok catch bez jawnie określonego
sobie, jak łatwo jest w złożonej aplikacji utracić
kontrolę nad wszystkimi używanymi obiektami. P : Czy jednak nie jest łatwiej zastosować
wyjątku jest w stanie wyłapać wszystko,
to po co w ogóle określać jego rodzaj?
Wyjątki zwracają uwagę na problemy i pomagają bardziej ogólny blok catch? Czy nie jest
Ci odnaleźć ich źródło. Dzięki temu zawsze
bezpieczniej napisać kod, który będzie
przechwytywał i obsługiwał wszystkie
O : Dobre pytanie. Ponieważ określone wyjątki
będziesz wiedział, że Twój program rzeczywiście mogą wymagać podjęcia różnych działań, aby
wyjątki? program mógł kontynuować pracę. Wyjątek
wykonuje to, czego się po nim spodziewasz.
Za każdym razem, gdy występuje w nim O : Zawsze powinieneś zrobić wszystko,
generowany w momencie dzielenia przez zero
mógłby przykładowo mieć blok catch, który
wyjątek, ma miejsce zdarzenie, którego nie co tylko możliwe, by uniknąć stosowania
ustawiałby pewne wartości, aby zachować ich
oczekiwałeś. Być może referencja obiektu bloku catch obsługującego wyjątek
stan do późniejszego przetwarzania. Wyjątek
nie wskazuje dokładnie tam, gdzie miała, Exception i zamiast niego przechwytywać
spowodowany używaniem pustych referencji
lub użytkownik wprowadził wartość, której bardziej wyspecjalizowane wyjątki. Znasz
mógłby natomiast zostać obsłużony poprzez
w założeniach nie uwzględniłeś, czy też plik, to stare powiedzenie, że uncja prewencji
utworzenie nowej instancji obiektu.
z którym cały czas pracowałeś, nagle stał się jest lepsza od funta lekarstwa? Jest ono
niedostępny. Jeśli coś podobnego wydarzy się
podczas pracy programu, a Ty nie będziesz
zadziwiająco prawdziwe w odniesieniu do
obsługi wyjątków. Uzależnianie działania
P : Czy każda procedura obsługi wyjątków
składa się z sekwencji try/catch/finally?
o tym wiedział, to najprawdopodobniej wynik aplikacji od ogólnego bloku catch jest prostą
jego działania będzie nieprawidłowy, a jego
zachowanie będzie się od tego momentu różnić
drogą do powstawania złych programów. Na
przykład znacznie lepszym rozwiązaniem jest
O : Nie. Możesz ten schemat trochę
zmodyfikować, na przykład tworząc kilka
od zakładanego podczas pisania kodu. sprawdzanie istnienia pliku przy użyciu metody bloków catch , jeśli chcesz w różny sposób
Wyobraź sobie teraz, że nie masz pojęcia F i l e . E x i s t s ( ) niż przechwytywanie wyjątku obsługiwać różne typy wyjątków. Mógłbyś
o występującym błędzie, a użytkownicy FileN otFoundException. Choć zazwyczaj w ogóle nie mieć instrukcji catch. Całkowicie
dzwonią do Ciebie z informacjami nie da się całkowicie zrezygnować z obsługi prawidłowe jest posiadanie wyłącznie bloków
0 nieprawidłowych danych i narzekają, jakichś wyjątków, to jednak przekonasz się, że t r y i f i n a l l y . Nie obsłużą one co prawda
że program jest niestabilny To właśnie zadziwiająco wielu z nich można uniknąć. żadnych wyjątków, ale uzyskamy pewność,
dlatego dobrze jest, gdy wyjątki przerywają Czasami zrezygnowanie z obsługi wyjątków że kod w bloku f i n a l l y będzie wykonywany
wykonywanie operacji w programie. Zmuszają może być bardzo przydatne. Rzeczywiste zawsze, nawet wtedy, gdy przetwarzanie
Cię do rozwiązania problemu wtedy, gdy jest go programy mają bardzo złożoną logikę i często zostanie przerwane w połowie bloku try .
łatwo znaleźć i naprawić. niezwykle trudno jest przywrócić ich poprawne Powiemy sobie więcej na ten temat za chwilę.
działanie, kiedy coś pójdzie nie tak; zwłaszcza
P : W porządku, a co to jest obsłużony jeśli problem występuje „głęboko" w programie. Nieobsłużone
1nieobsłużony wyjątek? Pomijając obsługę niektórych wyjątków oraz
wyjątki sprawiłyby,
O : Za każdym razem, gdy Twój program
ogólne bloki catch, pozwalamy tym wyjątkom
docierać do „bardziej ogólnych" warstw że Twój program
zgłosi wyjątek, środowisko wykonawcze będzie aplikacji, gdzie także można je przechwycić
przeszukiwało kod i próbowało odnaleźć i obsłużyć. Rozwiązanie takie prowadzi do
zachowywałby się
pasujący blok catch. Jeśli jakiś napisałeś, powstawania solidniejszych programów. nieprzewidywalnie.
to wykonywanie instrukcji zostanie przeniesione
To dlatego zatrzymuje
?
O system ach zaprojektowanych w taki s p osób, by
się on za każdym
natychm iast informowały o problemach (z amias t
powoli przechodzić w stan niestabilny ), mówi s ię >
razem, gdy na taki
że ulegają szybkim awariom. wyjątek natrafi.
622 Rozdział 12.
Obsługa wyjątków

p u b lic c la s s Kangaroo {
Zagadkowy basen
______________ f s ;
T w o im zadaniem jest po bra nie fragm entów i n t c ro c ;
kod u z basenu i wstaw ienie ich w puste i n t dingo 0;
miejsca. M ożesz użyć tego samego
fragm e ntu więcej niż raz i nie p u b lic i n t W om bat(int w a lla b y ) {
musisz wykorzystać ich wszystkich.
Celem jest napisanie program u, try {
k tó ry w yśw ietli ko m u n ika t if ( > 0) {
zaprezentowany poniżej. .O p en W rite ("w o bbieg ong ")
c ro c = 0;
} e ls e i f (___ < 0) {
W ynik: Witaj, bracie! c ro c = 3;
} e ls e {
.OpenRead("wobbiegong")
u s in g System .IO ; cro c 1;
p u b lic s t a t i c v o id M a in () {
}
Kangaroo jo e y = new K a n g a ro o ();
}
i n t k o a la = joey.W om bat( ca tch (IO E x c e p tio n ) {
jo e y.W o m b a t(jo e y.W o m b a t(1 )));
c ro c = -3 ;
try {
}
C o n s o le .W rite L in e ((1 5 / koa la )
ca tch {
+ " j a j na k ilo g ra m " ) ;
cro c 4;
}
c a tch (____________________ }
) {
fin a lly {
C o n s o le .W r ite L in e (" W ita j, b r a c i e ! " ) ;
if ( > 2) {
}
cro c d in g o ;
}
}
Przypominam y: każdy
fra g m e n t kodu z basenu może
zostać użyty w ięcej niż raz!

Zagadkowe baseny robią się coraz trudniejsze, a na zw y stają się coraz bardziej za w iłe , by dawać Ci m niej wskazów ek.
N apraw dę będziesz się m usiał trochę napracować nad tym problem em ! Pamiętaj, że zagadki są opcjonalne, w ięc nie
przejm uj się, jeśli musisz kontynuow ać le ktu rę i w rócić do tej nieco później... Jeśli jednak napraw dę chcesz u trw a lić ten
m a te ria ł w sw oim m ózgu, to ta ła m ig łó w k a na pew no Ci w tym pomoże!
jesteś tutaj ► 623
Usunięcie jednego obiektu może być w ybaw ieniem dla innego

joey.W om batO wywoływane


Zagadkowy basen. Rozwiązanie j e s t trzy razy i za trzecim
zwra.ca 0. Powoduje
to wyge nerowanie wyjątku
Div ideB y Z e roException
p u b lic s t a t i c v o id M a in () { w metodzie W riteLine(). ]
Kangaroo jo e y = new K a n g a ro o ();
i n t k o a la = jo e y.W o m b a t(jo e y.W o m b a t(jo e y.W o m b a t(1 )));
try {
C o n s o le .W rite L in e ((1 5 / Koala) + " j a j na k ilo g ra m "
}
ca tch ( DivideByZeroException) {
C o n s o le .W rite L in e (" W ita j, b r a c ie ! " ) ;

} } ^ Jen blok catchL


w yłapuje tylko wyj ątk i
dzielenia przez ze ro.
Kluczem do rozwiązania je s t
p u b lic c la s s Kangaroo {
tu taj metoda OpenReadÓ
w klasie FileStream FileStream f s ;
oraz zgłaszany wyjątek i n t c ro c ;
IO Exception.
i n t d in g o = 0;

publ ic i n t W om bat(int w a lla b y ) {


dingo++;
try {
if ( wallaby > 0) {
Kod otw iera p lik o nazwie „wobbiegong fs = File .O p e n W rite ("w o b b ie g o n g ");
i pozostaw ia go w tym sta n ie
c ro c = 0;
od pierw s z ego wywołania. Później
ponownie go otw iera. Nie zo sta ł } e ls e if ( wallaby < 0) {
on jednak nigdy zam knięty, więc c ro c = 3;
IO E woduje to wygenerowanie wyjątku
}
e ls e {
fs = File .O penR ead("w obbiegong");
c ro c = 1;
}
}
ca tch (IO E x c e p tio n ) {
c ro c = -3 ;
J uż w iesz, ż e zaw sz e powinieneś
} zamykać pliki po zakończeniu
Pam iętaj, że pow inieneś unikać ca tch { pracy z nim i. J e ś l i togo nie
sto so w ania bloków catch c ro c = 4; z robisz, plik zostanie zablokowany.
przechw ytujących w szystk ie Gdy sp ró b u jesz go otworzyć
} je s z c z e raz, zostanie zgłoszony
w yjątki. pow inieneś unikać także
innych r-ozmązań, których używamy, f in a lly { w yjątek IOExcept ion .
by prezentowane zagadki były bardziej if ( dingo > 2) {
in teresu ją ce, takich ja k bezsensowne
nazwy zmiennych. c ro c - = ding o
}
}
return croc ;

624 Rozdział 12.


Obsługa wyjątków

Użyj obiektu Exception


w celu uzyskania informacji o problemie
Cały czas pow tarzaliśm y, że .N E T tw orzy ob ie kt E x c e p tio n za każdym razem,
gdy jest zgłaszany w yjątek. K ie d y piszesz b lo k c a tc h , masz do tego o b ie ktu dostęp.
D zia ła to następująco:
Jeśli instrukcja w bloku
O b ie k t nuci sobie p o d nosem, w ykonując swoje zadania. DoSom ethingRisky() zgłasza wyjątek,
który nie jest w niej obsługiwany,
N agle spotyka go je d n a k coś nieoczekiwanego i zgłasza wyjątek.
to zostanie on przechwycony przez
procedurę obsługi w yjątków umieszczoną
w kodzie, który tę metodę w yw ołał. Jeśli
kod wyw ołujący nie został wyposażony
o w obsługę wyjątków, to zgłoszony wyjątek
jest przekazywany do kolejnych metod,
umieszczonych wyżej na stosie. Jeśli
wyjątek dotrze na sam wierzchołek stosu
i nie zostanie obsłużony, to zostanie uznany
O za w yjątek nieobsłużony i spowoduje
%
przerwanie działania programu.

N a szczęście b lo k t r y / c a t c h go w yłapuje. W ewnątrz


b lo k u c a tc h nadaliśm y naszemu w yją tko w i nazwę ex.

try {
J e ś li w bloku catch określisz konkretny
D o S o m e th in g R is k y () typ wyjątku i nadasz zmiennej nazwę,
to kod będzie mógł uzyskać dos t ęp do
} obiektu tego w yjątku.
c a tc h ( E x c e p t io n ex)
s tr in g m essage = e x .M e s s a g e ;
M e ssa g e B o x.S h o w (m e ssa g e , " W y s t ą p ił b ł ą d . " ) ;

O b ie k t w yją tku pozostaje przy życiu aż do zakończenia b lo k u c a tc h .


W te dy referencja ex znika, a wskazywany przez nią o b ie k t zostaje
zakw alifikow any do pro ce d u ry oczyszczania pam ięci.

m e s s a ge = ex.Message;
s t r in g

€ E 3x c *

jesteś tutaj ► 625


Zabaw a z catch

Użyj więcej niż jednego bloku catch


do wyłapania różnych typów wyjątków
W iesz już, że możesz w yłapać określony typ w yjątku... Co je d n a k zrobić w przypadku fragm entu
Aby pobrać mnóstwo
kodu, w któ rym może wystąpić k ilk a rodzajów problem ów ? M ógłbyś napisać dla niego kod wartościowych
obsługujący różne ich typy. Jest to sytuacja, k tó ra aż się pro si o użycie większej liczby b lo kó w catch. danych, możesz także
O to p rzykła d ko d u z fa b ryki przetw arzającej nektar. M ożesz zobaczyć, w ja k i sposób wyłapywanych wyw ołać metodę
jest k ilk a typów w yjątków . W nie któ rych przypadkach używane są właściwości o b ie ktu Exception. T o S tr in g ( ) wyjątku.
D ość powszechną techniką jest użycie właściwości Message, k tó ra zwykle zaw iera opis zgłoszonego
w yjątku. Możesz także użyć in s tru k c ji th ro w , by po no w nie zgłosić w yjątek, by m ógł on zostać
obsłużony przez kod um ieszczony w wyższych partiach stosu.

public void ProcessNectar(NectarVat vat, Bee worker, HiveLog log) {


try {
NectarUnit[] units = worker.EmptyVat(vat);
for (int count = 0; count < worker.UnitsExpected; count++) {
Stream hiveLogFile = log.OpenLogFile();
worker.AddLogEntry(hiveLogFile); Gdy posiadasz kilka bloków catch, s p ^ d z w e
s ą o n e w określonym porządku. W ty m .
} J e ±e U nie chces z używać obiektu Exception, kodzie najpierw s p rawdzany je s t wyj i Excen ti<
to nie m u sisz go deklarować. VatEm ptyException, a na stę pme HiyeLo gE xc e p t
} • O statni blok ca^ h wy łapuj e w yjątk' t y P “
IO Exception. J e s t to klasa bazowa dla k<lku
catch (VatEmptyException) { różnych typów wyjątków związanych z obsługą
plików, w tym takich j a k HteNrtFcnjnd&ciipfion
vat.Emptied = true; oraz EndO fŚtream Exce pt 'on-
} Czasam i może s ię zdarzyć, że będziesz
catch (HiveLogException ex) { ch cia ł przekazać w yjątek do metody, która
wy wołała tę aktualnie wykonywaną —
throw; by ponownie zg ło sić w yjątek, użyj throw ;.

} Ten blok catch p rzy p isu je w artość wy ją tk u _do z miennej e x,


która może posłu żyć do zebrania m h m a c ji z obiektu E xce p ti°n.
catch (IOException ex) {
worker.AlertQueen("Nieokreślony błąd:
C ałkow iciepopraw ne
je s t u ż y c ie d w ó c h "Komunikat: " + ex.Message + "\r\n"
bloków catch
korzystających ze
"Stos wywołań: 111 + ex.StackTrace + "\r\n"
z m ie n n e jo t e js a m e j
"Dane: 11 + ex.Data + "\r\n");
nazwie („ex“).
}
finally { Ta in strukcja używa trzech w ła ściw ości obiektu
E xception: M e ssa ge, która standardowo j e s t
vat.Seal(); tekstem wyświetlanym w oknie ID E wyjątku
(„A ttempted to divide by zero"), StackTrace,
worker.FinishedJob(); która umożliwia Ci pobranie informacji o sto sie
wywołań, oraz Data, która czasam i przechowuje
} wartościow e dane skojarzone z wyjątkiem .

}
626 Rozdział 12.
Obsługa wyjątków

Jedna klasa zgłasza _wyjątek, Oczy w iście jedna metoda w pewnej klasie może
zg fasz ać wy ją tk i, kttSr-e będą przechwytywane
przez inną metodę tej sam ej klasy.
inna klasa go przechwytuje
T w orząc klasę, nie zawsze wiesz, w ja k i sposób będzie ona wykorzystywana. Czasami in n i będą używ ali T w o ich obiektów
w sposób, k tó ry sprowadzi na nich pro blem y, a czasem nawet ściągniesz je na siebie sam! W takich przypadkach pojaw iają
się w yjątki.
G łów nym celem przechw ytyw ania i zgłaszania jest określenie, co m ogłoby pójść niezgodnie z naszymi oczekiwaniam i,
i przygotow anie na podstaw ie tej w iedzy jakiegoś p la n u awaryjnego. Zazwyczaj nie w id u je się m etod, k tó re zgłaszają
w yją tki, a następnie same je przechw ytują. W przeważającej większości przypadków w yjątek jest zgłaszany w jednej
m etodzie, a wychwytywany i obsługiw any w zupełnie innej — zazwyczaj należącej do zupełnie innego obiektu.

Krnstr-M or- obiektu B eeP rofile spodziew a s ię


Zamiast tego... nazwy pliku z danymi profilu, który będzie
Bez dobrej obsługi w yjątków jeden błąd może ot w ierał za pomocą File.O pen(). J e ś li wystąpi
spowodować zatrzym anie całej ap lika cji. O to przykład przy ty m ja k iś problem, program zostanie
natychm iast zakończony.
hipotetycznego p ro gra m u zarządzającego p ro fila m i
pszczół, z których korzysta kró lo w a ula. stream = F ile .O p e n R e a d (p r o file );
P „QPrnf i1 e (" Pr°f -d a t 11)

Ob iekt B eeP rofile próbował


odczytać plik, ale go nie
odnalazł. M etoda File.Open()
% x ___
HN® zgtosi'ła w yjątek. O biekt ula nie ^ B ee ? *
przechw ycił go, więc pozostał
on nieobstużony.

Z w róć uwag ę że ob iekt BeeProfile


przechw ytuje wy ją te k, z a p isu je go
za pomocą metody W riteLogEntry()
i ^ riw n i^ go zgfasza, tak aby mógł
X , zo sta ć przekaz any do obiektu u la■
try {
.m ożem y zrobić to. stream = F ile .O p e n R e a d (p r o file );
O b ie k t B e e P r o f ile może przechw ycić w yjątek
} ca tch (F ile N o tF o u n d E xce p tio n ex) {
i dodać w pis do logu. Później może ponow nie
W rite L o g E n try ("N ie można o tw o rzyć 1
zgłosić w yjątek do klasy ula, k tó ra go przechwyci
p r o file + " + ex.M essage);
i z gracją obsłuży. th ro w ex;
}
n ^ P r n f i1 e ( " p r o f . dat 11)^

Teraz, kiedy ul prót>uje utworzyli


nowy obiekt BeeP rofile, przekazując
nieprawidłową nazwę pliku, może
% ^ polegać na kla sie B e e p rofUe . ^
^ Beeff Z a p isz e ona komunituzt o Mąpzw
try { i zasygnalizuje go za. poimocą
p r o f = new B e e P r o f ile ( " p r o f . d a t " ) ; w yjątku. In sta n cja Hive m ote ten
w yjątek przechwycić i podjąćt akcję
} ca tch (F ile N o tF o u n d E x c e p tio n ) {
naprawczą — w ty m przypadku
H iv e .R e c r e a te B e e P r o file ( " p r o f.d a t" ) ponownie tworząc proHl piszczoty.
}
jesteś tutaj ► 627
Własny wyjątek

Exception
Pszczoły potrzebują wyjątku OutOfHoney
Message
StackTrace
T w oje klasy m ogą zgłaszać własne w yją tki. N a przykład w tedy, gdy w m etodzie otrzym asz param e tr
G e tB a s e E x c e p tio n ()
ustaw iony na n u l l , a spodziewałeś się wartości. Dość powszechną tech niką w ta kich przypadkach jest T o S trin g ()

użycie tego samego w yjątku, k tó ry jest zgłaszany przez .N E T :


----------- ^ Twoje metody mogą zgłaszać ten w yjąto^ je ś l i _z osto n ą
th ro w new A rg u m e n tN u llE x c e p tio n (); do nich przekazane nieprawidłowe lub nieoczekiwane
w artości parametrom.
Czasami chcesz, aby T w ó j pro gra m zgłosił w yjątek, poniew aż podczas jego działania może zajść
pewna okoliczność. Przykładow o pszczoły, k tó re um ieściliśm y w u lu , spożywają różne ilości m io du Twój wyjątek
w zależności od ich wagi. G dy m io d u braknie, uzasadnione będzie zgłoszenie określonego w yjątku. Message
Możesz utw orzyć własny, k tó ry będzie zajm ow ał się kon kretnym problem em . N ależy w tym celu StackTrace

utw orzyć własną klasę dziedziczącą po E x c e p tio n i zgłaszać ten w yją te k za każdym razem, gdy G e tB a s e E x c e p tio n ()
T o S trin g ()
w ystąpi dany rodzaj błędu.

c la s s O utOfHoneyException : S yste m .E xce p tio n {


p u b lic O u tO fH o n e y E x c e p tio n (s trin g message) : base(message) { }

} M u s is z utworzyć klasę d h wyj ątku


c la s s H oneyD eliverySystem { i zadbać o to, aby dziedziczyła
po S ystem .E xcep tio n . Zw róć uwagę na_
przeciążenie konstruktora. W ten s posó b
p u b lic v o id FeedHoneyToEggs() { możemy przekazać komunikat o błędzie.

if (honeyLevel == 0) {
th ro w new 0ut0fH oneyExcept1on("W u lu b r a k ło m io d u ." ) ;
}
^ jest instancja
e ls e {
fo re a c h (Egg egg in Eggs) {
V J e ś li w ulu będzie miód,
... \ towyjątek_nigdy nie zostanie
} zgłoszony i kod ten będzie
wykonywany.
p a r tia l c la s s Form1 : Form {

p r iv a t e v o id co n sum e H on ey_C lick(o bje ct se n d e r, EventArgs e) {


H oneyD eliverySystem d e liv e r y = new H o n e yD e live ryS yste m ();
try { M ożesz przechw ycić utworzony
d e live ry.F e e d H o n e yT o E g g s(); wyjątek na podstaw ie jego nazwy
tak s amo ja k w pr-zypadku innych
} wy jątków.. Potom m ożesz zrobić
z jeg o obiektem, co chce sz .
ca tch (O utO fHoneyException ex) {
MessageBox.Show(ex.Message, "O s trz e ż e n ie : P rzyw ra can ie sta n u u l a . " ) ;
H iv e .R e s e t();
W przypadku gdy w ulu braknie miodtu,
} żadna pszczoła nie będzie mogła wykonywać
swoich zadań i sym ulator nie biędzto rnógł
} kontynuować działania. Jedynym _s p os ° bem
} na jego wznowienie po skończemtu s i ę
zapasów j e s t ponowne uruchom ienie
programu. Możemy tego dokona ć w taw iąjąc
odpowiedni kod w bloku catch.

628 Rozdział 12.


Obsługa wyjątków

Magnesiki z w yjątkam i
p u b lic s t a t i c v o id M a in () { Poukładaj magnesy tak, aby aplikacja
C o n so le .W rite ("w h e n i t 1 wypisała wynik na konsolę.
E x T e s tD riv e .Z e ro ("y e s ");
C o n s o le .W rite (" it ");
E x T e s tD riv e .Z e ro ("n o "); Wynik:
C o n s o le .W r ite L in e ( " ." ) ; when it thaws it throws.

jesteś tutaj ► 629


M ały przegląd

p u b lic s t a t i c v o id M a in () {
Magnesiki z w yjątkam i. Rozwiązanie
C o n s o le .W rite ("w h e n i t "); Poukładaj magnesy tak, aby aplikacja
E x T e s tD riv e .Z e ro ("y e s "); wypisała wynik na konsolę.
C o n s o le .W rite (" it ");
E x T e s tD riv e .Z e ro ("n o "); Wynik:
C o n s o le . W r it e L in e ( " ." ) ; — > when it thaws it throws.
}
c la s s M yException : E xce p tio n { }

p u b lic c la s s E xT e stD rive {


p u b lic s t a t i c v o id Z e r o ( s t r in g t e s t ) {
Ten w ie rsz defin iuje
nowy w yjątek o nazwie
1 try { |
MyException, który je s t
przechwytywany w bloku
catch kodu. I Console . W r i t e ( " t " ) ;
M etoda Z e ro () w y p i s j
albo „ thaw s“, albo „throws“
D o R is k y (te s t); w zależności od tego,
czy w param etrze te st
C o n s o le .W n te ( " o " ) ; przekazano „y e s“, czy coś
innego.
j } ca tc h (M yExceptio n ) {

I C o n s o le .W r ite ( " a " ) ;

} f in a lly {
Blok finally dba o to, aby za tiażdym
razem podczas wywołania metody
C o n s o le .W rite ("w" ) ; wypisyw ane było „w“ . „ s “ w ypisyw ane
j e s t poza procedurą obsługi w yjątku,
w ięc także będzie s ię poj awiało
zaw sze.
C o n s o le .W r ite ( " s " ) ;

E X

Ten w ie rsz wykonywany


j e s t tylko w tedy, gdy
D oR isky() nie zg ło si
wyjątku.

M etoda D oR isky() zgłasza


w yjątek tylko wtedy, gdy
pr-zekazano do niej łańcuch
znaków „y e s“ .

630 Rozdział 12.


Obsługa wyjątków

CELNE SPOSTRZEŻENIA

■ Każda in stru kcja może spowodować zgłoszenie ■ Każde t r y może m ieć w ięcej niż je d n o c a tc h :
w yjątku, je ś li podczas je j w ykonyw ania coś się try { ... }
wydarzy. catch (N u llR efe ren ce Excep tion ex) {
/ / Te i n s t r u k c j e będą wykonywane,
■ A b y obsłużyć w yjątek, używaj b lo k u t r y / c a t c h .
/ / gdy z o s t a n i e z gło sz on y w yjątek
N ieobsłużone w y ją tk i będą pow odow ały
I I N u llR e fe re n c e E x c e p t io n .
natychm iastowe zatrzym anie program u
}
i w yśw ietlenie okna błędu.
catch (O verflow Exception ex) { ... }
■ K ażdy w yjątek w b lo k u ko d u za in stru kcją t r y będzie catch (FileN o tFound Exception) { ... }
po w o dow ał przeniesienie w ykonyw ania do pierwszego catch (ArgumentException ex) { ... }
wiersza w b lo k u kod u po c a tc h . ■ T w ó j ko d może zgłaszać w yją tki, używając th ro w :
■ O b ie k t E x c e p tio n udostępnia C i in fo rm a cje na th ro w new Exception("W iadom ość w y ją t k u " ) ;
te m a t przechwyconego w yjątku. Jeżeli w in stru kcji ■ M ożesz ponownie zgłaszać w yją tki, używając
c a tc h umieścisz zm ienną E x c e p tio n , to będzie ona in s tru k c ji th ro w ; choć m ożna to ro b ić ty lk o w ew nątrz
zaw ierała dane dotyczące w yją tku z b lo k u t r y : b lo k u c a tc h . T a kie zgłaszanie w yjątków zachowuje
try { postać stosu wywołań.
/ / I n s t r u k c j e , k tó r e mogą
■ M ożesz tw orzyć własne w yją tki, dziedzicząc po klasie
/ / z g ła s z a ć w y ją t k i .
bazowej E x c e p tio n :
} catch (IO Exception e x ) {
c la s s Custom Exception : E x c e p tio n ;
/ / J e ż e l i z o s t a ł zgło szony wyjątek,
/ / to ex zawiera info rm acje o nim. ■ W większości przypadków będziesz m usiał zgłaszać

} ty lk o w y ją tk i w budow ane w .NET, na przykład


A rg u m e n tE x c e p tio n . Pow odem użycia innych jest
■ Istn ie je w iele różnych rodzajów w yjątków , które
chęć u d z ie le n ia d o d a tk o w y c h in fo r m a c ji T w o im
możesz wyłapywać. K ażdy jest reprezentow any przez
u ż y tk o w n ik o m . W yśw ietlenie okna z kom u nikatem
pew ien o b ie k t dziedziczący p o klasie E x c e p tio n .
„W y s tą p ił nieoczekiw any błąd .” nie jest ta k pom ocne
Staraj się un ika ć w yłapyw ania E x c e p tio n —
ja k k o m u n ik a t „F o ld e r w ym ów ek jest pusty. Jeżeli
przechw ytuj specyficzne rodzaje w yjątków .
chcesz odczytać w ym ó w ki, w yb ierz in n y fo ld e r.” .

Pamiętaj , je ż e li deklaru jesz referencję


Łatwy sposób na uniknięcie licznych problemów: w instrukcj i using, to metoda D isp o se()
j e s t autom atycznie wywoływana na
u s in g umożliwia Ci stosowanie try i finally za darmo końcu bloku.

Już wiesz, że użycie u s in g jest dobrym sposobem na praw idłow e


zam ykanie p likó w . N ie wiesz jednak, że jest to s k ró c o n a fo rm a Y o u rC la s s c = new Y o u r C la s s ( ) ;
ko n stru kcji t r y i f i n a l l y !
try { Kiedy używ asz instrukcji
// kod us ing, nieświadom ie
u s in g (Y o u rC la s s c korzy s tas z z dobrodziejstw
}f in a lly { bloku finally, który daje
= new Y o u r C la s s ( ) ) { Jestj ynoznaczne z
c . D is p o s e ( ) ; Dam pew, n° ś ć , że metoda
// kod “ “ D isp o se () zaw sze zostanie
} wywołana.
}
----------------------------------------
jesteś tutaj ► 631
Odrobina profilaktyki

ID is posable j e s t naprawdę
Unikanie wyjątków: zaimplementuj IDisposable, efektywnym sposobem
na uniknięcie pospolitych
aby przeprowadzić własne procedury sprzątania wyjątków i problemów.
Upewnij s ię , ż e używ asz
in strukcji using za każdym
S trum ienie są wspaniałe, poniew aż zaw ierają ju ż ko d napisany do zam ykania ich razem, gdy p ra cu jesz
w m om encie usuwania ob ie ktu . Co jednak, gdy posiadasz swój o b ie k t i m usi on z klasami implementującymi
ten in terfejs.
w m om encie usuwania wykonać pewną czynność? Czy nie byłoby świetnie, gdybyś
m ógł napisać własny ko d uru cha m ian y po użyciu o b ie ktu w in s tru k c ji using?
W in stru kcji using możesz
używ ać tylko tych klas,
C # pozw ala C i to zrob ić za pom ocą in te rfe jsu IDisposable. Z a im p le m e n tu j go które im plementują interfe js
i napisz ko d robiący p o rząd ki w m etodzie Dispose() , ja k pokazaliśm y na poniższym ID isp osa b le. W przeciwnym razie
program nie skom piluje się .
przykładzie.
J e ś li ch cesz u m ieścić sw ó j obiekt w instrukcji
c la s s N e c ta r : I D is p o s a b le { using, m usi on implementować ID isposable.
p r iv a te d o u b le a m o u n t;
p r iv a te B e e H iv e h i v e ;
p r iv a te S tre a m h iv e L o g ;
p u b lic N e c ta r ( d o u b le a m o u n t, B e e H ive h i v e , S tre a m h iv e L o g ) {
t h is . a m o u n t = a m o u n t;
t h is .h iv e = h iv e ;
t niej umieścisz, zostanie wykonane
t h is .h iv e L o g = h iv e L o g ; po zakończeniu instrukcji using...
Sądź gdjfwywotasz ją. ręcznie.
} ¡ r
Ta metoda
p u b lic v o id D is p o s e ( ) { D isp o se()
if (a m o u n t > 0 ) { zo sta ła napisana
w taki sposób,
h iv e . A d d ( a m o u n t ) ; by można ją
h iv e . W r it e L o g ( h iv e L o g , am ount + 11 mg n e k t a r u z o s t a ło d o d a n ych "); było wywoływać
w iele razy, a nie
am ount = 0 ; tylko raz.
}
} Ten konkretny kod dodaje dostępny
} nektar-^ do ula i zap is u je jeg o ilość. J. e s_t Jedną z wytycznych odnośnie do im plem entacji interfejsu ID is p o s a b le
to ważne i m usi zo sta ć wykona
umie ś c i?i ś 'mmus! zo sta ć w,y kon ane' dlateg ° jest to, by istniała możliwość wielokrotnego wywoływania metody
u m ieściliśmy go w metodzie D isp sp oose
se().
() r\ u • j u f i u . •
D is p o s e () bez żadnych efektów ubocznych. Czy jesteś w stanie
powiedzieć, dlaczego to zalecenie je st bardzo ważne?
M ożem y teraz użyć w ie lu in s tru k c ji using. W pierwszej kolejności
skorzystamy z wbudowanego o b ie ktu im plem entującego IDisposable Z agnieżdżone instru kcje using, takie ja k te, będą
— Stream. Będziem y także pracować z naszym rozbudow anym obiektem sto so wane w sytu acjach , gdy w tym samym bloku
kodu będzie sz m usiał zadeklarować dwa obiekty
Nectar, k tó ry rów nież im p le m e n tu je ten interfejs: klas implementujących in terfejs ID isposable

using (Stream Log = File .O pe n W rite ("lo g.txt"))


&
using (Nectar nect = new Nectar(16.3, hive, Log)) {
Bee.FlyTo(flower);
Obiekt N ectar używa strium irna. ^ Irtóry
Bee.Harvest(nect); j e s t zamykany a u to m a ty cz n i po znk-on^ m u
zewnętrznej in strukcji' us ing.
Bee.FlyTo(hive);
Potem obiekt B e e używa obiektu nect,
który sam z a p isze dane po zakończeniu
wewnętrznej in stru kcji using.

632 Rozdział 12.


Obsługa wyjątków

■ Nie .istnieją.
głupie pytania

P : C z y w in stru k c ji u s in g mogę P : C z y m ożna w y w o ła ć D isp o se ()


try {
D o S o m e th in g R isk y();
u żyw a ć obiektów , k tó re nie na z e w n ą trz in stru k c ji u s in g ?
S o m e t h in g E ls e R is k y ();
im plem entują in te rfe jsu ID is p o s a b le ?
O : Tak. W zasadzie nie potrzebujesz }
O : Nie, w instrukcji u sin g można tworzyć do tego tej instrukcji. Możesz samodzielnie f in a lly {
wyłącznie obiekty klas implementujących wywołać metodę D is p o s e () A lw a y s E x e c u t e T h is ();
interfejs ID iso p o sa b le , gdyż są one po zakończeniu pracy z obiektem. Możesz }
przeznaczone do wzajemnej współpracy. wykonywać wszystkie potrzebne procedury Jeżeli D o S o m e th in g R isk y() zgłosi
Dodanie instrukcji u sin g odpowiada sprzątające — na przykład ręcznie wyjątek, to natychmiast zostanie
utworzeniu instancji klasy, z tym że pod koniec wywoływać metodę C lo s e ( ) strumienia. wywołany blok f i n a l l y .
bloku kodu zawsze będzie wywoływana Jeśli jednak użyjesz instrukcji u s in g , Twój
metoda D is p o s e (). To właśnie dlatego klasa kod będzie łatwiejszy do zrozumienia. P : C z y D is p o s e () d zia ła tylko
musi implementować interfejs ID isp o sa b le . Zapobiegniesz dodatkowo problemom, z plikam i i strum ieniam i?
które mogą się pojawić w przypadku
P : C z y w e w n ą trz bloku u s in g mogę niewłaściwego usunięcia obiektów. O : Nie, istnieje bardzo dużo klas
um ieścić dowolne instru kcje? implementujących ID is p o s a b le . Kiedy

O : Naturalnie. Głównym zadaniem using


P : W spom niałeś o bloku z nich korzystasz, powinieneś zawsze
używać instrukcji u s in g . (Kilka z nich
t r y / f i n a l l y . C z y to o zn acza,
jest troska o to, aby każdy utworzony obiekt że m ożna mieć t r y i f i n a l l y zobaczysz w następnych rozdziałach).
został poprawnie usunięty. Co jednak z nim bez c a tc h ? Gdy piszesz klasę, która powinna zostać
zrobisz, zależy wyłącznie od Ciebie. W zasadzie usunięta w określony sposób, także możesz
możesz utworzyć obiekt wewnątrz tej O : Tak! Oczywiście możesz mieć blok t r y zaimplementować ID is p o s a b le .
instrukcji i nigdy go nie wykorzystać. Byłoby to bez bloku c a tc h , ale z f i n a l l y . Wygląda
jednak całkowicie bezsensowne i dlatego nie to następująco:
polecamy takich rozwiązań.

SKORO BLOK TRY/CATCH JEST TAKI WSPANIAŁY,


TO DLACZEGO IDE NIE WSTAWIA GO WOKÓŁ
WSZYSTKIEGO? NIE MUSIELIBYŚMY WTEDY PISAĆ TYLU
OSOBNYCH BLOKÓW, CZYŻ NIE?

Potrzebujesz wiedzieć, jaki typ wyjątku jest zgłaszany,


abyś mogła go obsłużyć.
O bsługa w yją tku to coś więcej niż ty lk o wypisanie
ogólnego k o m u n ik a tu o błędzie. N a p rzykład w pro gra m ie
w yszukującym w ym ó w ki, wiedząc, że dostaliśm y w yjątek
F ile N o t F o u n d E x c e p t io n , m oglibyśm y wypisać ko m u n ika t
sugerujący popraw ną lokalizację właściwych plików .
G dybyśmy o trzym a li w yją te k związany z bazą danych, To dlatego istn ie je tak
m oglibyśm y wysłać e -m a il do je j adm inistratora. wiele klas dziedziczących
W szystko to zależy od określonego typu w yjątku. po Exception i być może
będ ziesz nawet ch ciał
tw orzyć własne.

jesteś tutaj ► 633


Jeden, który uciekł

Najgorszy z możliwych bloków catch: komentarze


B lok catch pozw ala program ow i n a k ontynuow anie działania. W yjątek zostaje
zgłoszony, w ięc go w yłapujesz i zam iast n atychm iast zam knąć p ro g ram i wyświetlić
stosow ny k o m unikat o błędzie, k ontynuujesz jego wykonywanie. C zasam i nie jest
to dobre.

Przyjrzyj się dokładnie klasie Calculator , k tó ra cały czas zachow uje się dziwnie.
Co się m ogło stać?

p u b lic c la s s C a lc u l a t o r {

p u b lic v o id D i v i d e ( f l o a t d i v id e n d , flo a t d iv is o r ) {
Oto problem. W przypadku
try { dzielenia przez zero otrzy mujemy
D ivid eB yZ eroExce ption.
k r
t h is . q u o t ie n t = d iv id e n d / d iv is o r ;
A le przecież mamy blok catch. Dlaczego
w takim razie dalej otrzym ujem y btędy?
} c a tc h {

/ / Komentarz Jacka: Musimy znaleźć jakiś sposób na uniemożliwienie

/ / ludziom wprowadzania zera przy dzieleniu.

} Pr°g ramista myślat, że może


pogrzebać żyw cem sw oje wyjątki,
} u zy wając p u s tego bloku catch.
Przy p rawi to o ból gtowy kogoś,
kto będ zie zajmował s ię tym
problemem później.
Powinieneś obsługiwać wyjątki, a nie chować je pod dywan
T o, że um ożliw iłeś program ow i dalsze działanie, nie oznacza w cale, że obsłużyłeś
w yjątki. W powyższym kodzie k a lk u lato r nie zakończy niespodziew anie swojego
działania... przynajm niej nie w m eto d zie D iv id e ( ) . C o się je d n a k stanie, gdy jakiś inny
k od wywoła tę m eto d ę i sp ró b u je wypisać wyniki? Jeśli dzielnik b ędzie rów ny zero,
n ajpraw dopodobniej zwróci o n a niepraw idłow e (lub n iespodziew ane) wyniki.

Z am iast dodaw ać k om en tarz, m askując w ten sposób w yjątek, pow inieneś go obsłużyć. Pamię taj , je śli Twój kod
Jeśli nie jesteś w stanie obsłużyć problem ów , n ie zostaw iaj p u stych lub wypełnionych nie obstuży wyjątku, to będzi
on wędrowat w górę sto su
kom entarzem bloków c a tc h ! W te n sposób b ard zo u tru d n isz innej osobie o d nalezienie wyw otań. Zezw olenie na
ich źródła. Z nacznie lepiej je st pozostaw ić p ro g ram , który będzie zgłaszał wyjątki, takie p rzekazywanie wyjątku
poniew aż łatwiej p o tem określić, co się dzieje. j e s t catkowicie poprawnym
sposobem jego obstugi.

634 Rozdział 12.


Obsługa wyjątków

Tymczasowe rozwiązania są dobre (tymczasowo)


Czasami znajdujesz pew ien pro blem . Wiesz, że jest to błąd, ale nie masz pojęcia, J e dnak w praktyce
co z n im zrobić. W ta kich przypadkach mógłbyś zapisać go i zanotować, co się dzieje. tymcza sowe rozwiązania
mają paskudny nawyk
N ie jest to rozw iązanie ta k dobre ja k obsłużenie w yjątku, je d n a k i ta k lepsze niż bra k przekształcania s ię
ja k ie jk o lw ie k reakcji. w m w ią z a n ia trwałe.

O to tymczasowe rozw iązanie dla kalkula tora:


Poświęć chw ilę, by przeanalizow ać
p u b lic c la s s C a lc u l a t o r { prze dstaw ion y tu blok c a tc h . Co się
stanie, jeśli klasa S tr e a n W r ite r nie
będzie w stanie zapisać pliku w folderze
C :\ L o g s \ ? Możesz użyć drugiego,
p u b lic v o id D i v i d e ( f l o a t d i v id e n d , f lo a t d iv is o r ) { zagnieżdżonego bloku t r y / c a t c h ,
by zapew nić w iększą niezawodność
try {
program u. Czy potrafisz w ym yślić jakieś
t h is . q u o t ie n t = d iv id e n d / d iv is o r ; lepsze rozw iązanie?

} c a tc h ( E x c e p t io n ex) {

u s in g ( S t r e a m W r it e r sw = new S t r e a m W r it e r ( @ " C : \ L o g s \ e r r o r s . t x t " ) ;

s w . W r it e L in e ( e x . g e t M e s s a g e ( ) ) ;

};

} To w dalszym ciągu wymaga naprawienia,


ale tymczasowo dobrze określa mie jsc e
w y stępowania problemu. Niemniej j e dnak,
}
czy nie byłoby lepiej spraw dzić, dlaczego
do metody D ivid e() j e s t przekazywany
} dzielnik o w artości 0?

R O Z U M IE M !
T O P EW IEN SPOSÓB OBSŁUGI
W Y J Ą T K Ó W , K T Ó R Y M O Ż E PO M Ó C
O N A M IE R Z Y Ć PO D EJR ZAN E MIEJSCE.

Obsługa wyjątków nie zawsze oznacza to samo co ich NAPRAWIENIE.


N ie jest dobrze doprow adzić do całkow itej zapaści program u. Z nacznie gorzej
jest nie m ieć pojęcia, dlaczego nagle się zakończył lu b co p o taje m nie robi
z danym i użytkow ników . T o dlatego pow inieneś szczególnie dbać o to, aby
zawsze obsługiwać błędy, k tó re jesteś w stanie przew idzieć, oraz zapisywać
te, których przew idzieć nie możesz. Jednak choć lo g i mogą być przydatne do
śledzenia pro blem ów , rozw iązyw anie tych pro b le m ó w jest znacznie lepszym
i bardziej trw ałym rozw iązaniem .

jesteś tutaj ► 635


Kilka szybkich sugestii

Kilka prostych wskazówek dotyczących obsługi wyjątków

Tam, gdzie tylko możesz, zgłaszaj wyjątki wbudowane w .N E T .


Własnych używaj tylko wtedy, gdy chcesz przekazać dodatkowe
informacje.

Pomyśl o kodzie w bloku try, który może zostać ominięty.

I najważniejsza ze wszystkich:

Unikaj niepotrzebnych błędów systemu plików... ZAWSZE UŻYWAJ Oraz ze


wszystkim ,
co implementuje
BLOKU USING PODCZAS PRACY ZE STRUMIENIAMI! in terfejs
ID isposable.
ZAWSZE, ZAWSZE, ZA\
■■ * * * * * * * * * * * * * * ...........................

636 Rozdział 12.


Obsługa

Damian w końcu
pojechał na urlop...
Teraz, kie dy D a m ian obsłużył wszystkie
swoje w yją tki, jego praca idzie
znakom icie. M oże w końcu wyjechać
na zasłużony (i zatw ierdzony przez
szefa!) u rlop .

. i wszystko znów jest


w najlepszym porządku!
T w o je zdolności obsługi w yjątków p o z w o liły zrobić
coś więcej, niż ty lk o zapobiec pro blem om . D z ię k i
n im szef D a m ian a nawet nie zo rie n to w a ł się,
że na początku coś szło niezgodnie z planem !
Dobra obsługa wyjątków
jest niewidoczna
dla użytkowników.
Program nigdy
nie kończy się
w niespodziewany
sposób. Jeśli występują
problemy, są one
obsługiwane z wdziękiem,
bez wyświetlania
dziwnych komunikatów
o błędach.
jesteś tutaj ► 637
638 Rozdział 12.
640
KAPITAN WSPANIAŁY ZAPĘDZIŁ K A N C IA R ZA
W ŚLEPĄ U LIC ZKĘ...

641
Powtórne odegranie kradzieży

Zaostrz
Z.UUDLI ołówek
V Poniżej zaprezentowano kod opisujący szczegółowo walkę pomiędzy Kapitanem Wspaniałym
a Kanciarzem (nie mówiąc o jego armii klonów). Twoje zadanie polega na narysowaniu zmian,
które miały miejsce w pamięci podczas tworzenia instancji klasy F in a l B a t t l e .

c la s s F in a lB a t t le { M ożesz założyć, że Clones z o s M o


zainicjalizowane za pomocą
p u b lic C lo n e F a cto ry F a c to ry = new C lo n e F a c to ry ();
inicjalizatora kolekcji.
p u b lic L is t< C lo n e > Clones = new L is t< C lo n e > () { .
p u b lic S w indlerE scapeP lane escapePlane;

p u b lic F in a lB a t t le ( ) { Rozpoczęliśm y Twoje


V i l l a i n s w in d le r = new V i l l a i n ( t h i s ) ; zadanie od narysowania
elementów z obiektu
u s in g (Superhero captainA m azing = new S u p e rh e ro ()) {
Factory.
F a c to ry .P e o p le ln F a c to ry .A d d (c a p ta in A m a z in g );
F a c to ry .P e o p le ln F a c to ry .A d d (s w in d le r);
c a p ta in A m a z in g .T h in k ("Z lik w id u ję każdą r e fe r e n c ję klo n a je d n ą po d r u g i e j. " )
c a p ta in A m a z in g .Id e n tify T h e C lo n e s (C lo n e s );
captainAm azing.R em oveTheC lones(C lones);
s w in d le r.T h in k ( " Z a k ilk a m in u t t y i moja arm ia z o s ta n ie c ie u s u n ię c i prze z od śm ie ca cz.")
s w in d le r .T h in k ( " ( U s u n ię ty , ta k j e s t ! ) " ) ;
escapePlane = new S w in d le rs E s c a p e P la n e (s w in d le r);T2 ^
s w in d le r.T ra p C a p ta in A m a z in g (F a c to ry );
Na rysu j to, co s i ę
M essageBox.Show("Kanciarz u c i e k ł . " ) ; dzieje w tym m iejscu
} podczas tworzema
} Narysuj obrazek pokazujący s t o s zaraz instancji obiek.tu
go u ru chomieniu konstruktora FinalBattle. Sw in d lersE sca p eP lane .
}
[ S e r ia liz a b l e ]
c la s s Superhero : ID is p o s a b le {
p r iv a t e L is t< C lo n e > clonesToRemove = new L is t< C lo n e > ();
p u b lic v o id Id e n tify T h e C lo n e s (L is t< C lo n e > c lo n e s ) { W tym kodzie nie
fo re a ch (Clone c lo n e in clo n e s ) prezentujem y także klasy
clonesToR em ove.A dd(clone); Clone. Nie po trzebu jesz
} je j, by odpowiedzieć na
p u b lic v o id Rem oveTheClones(List<Clone> clo n e s ) { pytania.
fo re a ch (Clone c lo n e in clonesToRemove)
c lo n e s.R e m o ve (clo n e );
} T „ta i znaiduie s ię w ięcej kodu, włączają c w to metodę
... < — ■>. D isp o se d , której tu taj me p o ^ z ^ m y . Nie potrzebu jesz
} go, aby rozwiązać zada.nie -
c la s s V i l l a i n {
p r iv a t e F in a lB a tt le f in a l B a t t l e ;
p u b lic V il la i n ( F in a lB a t t le f i n a lB a t t le ) {
t h i s . f i n a l B a t t l e = f in a l B a t t l e ;
}
p u b lic v o id T ra p C ap ta inA m a zin g(C lone F actory fa c to r y ) {
f a c t o r y . S e lfD e s t r u c t .T ic k += new E v e n tH a n d le r(S e lfD e s tru c t_ T ic k );
f a c t o r y . S e lf D e s t r u c t . I n t e r v a l = Tim eSpan.From Seconds(60);
f a c t o r y . S e lf D e s t r u c t . S t a r t ( ) ;
}

v o id S e lfD e s tr u c t_ T ic k ( o b je c t sender, EventArgs e)


f in a lB a t t le . F a c t o r y = n u l i ;
}

642 Rozdział 13.


Śmierć obiektu

c la s s S w indlerE scapeP lane {


p u b lic V i l l a i n P ilo ts S e a t;
p u b lic S w in d le rE s c a p e P la n e (V illa in escape) {
P ilo ts S e a t = escape;
}
}
Upewnij się ,
że dodateś do obiektów
c la s s C lo n e F a cto ry { ety kiety , aby pokazać
p u b lic Tim er S e lfD e s tr u c t = new T im e r() ; zm ienne referencyjne,
które na nie wskazują .
p u b lic L is t< o b je c t> P e o p le In F a c to ry = new L is t< o b je c t> ( ) ;

}
Pierw szy punkt zrobiliśm y za C iebie. Zadbaj
o poprawne narysowanie linii odzwierciedlających
pow i<ązan ia — narysowaliśm y jedn ą z fabryki
klonów do obiektu Kanciarza, ponieważ posiada
ona do niego referencję (poprzez jego pole
PeopleInFactory).

0 bi&V*

Pozostaw iliśm y tu p u stą


prz e strzeń, gdyż s ą je sz c z e
dodatkowe obiekty do narysowania
na tym eta p ie. ^

K a n cia rza .

T
Twoim zadaniem j e s t
narysowanie sy tu a cji
istn iejącej w pam ięci dla
tych dwóch fragmentów.

Na po dstaw ie analizy d ia g ra m ó w pow iedz, w któ rym miejscu


w kodzie pro gra m u um rze Kapitan W spaniały.

U p ew n ij się, czy to w łaściw e miejsce, i zaznacz je także na diagram ie.

jesteś tutaj ► 643


H m ... ciekawe, co nam pow iedzą te liczby

ą,Zaostrz ołówek
Narysuj sytuację istniejącą w pamięci podczas pracy programu FinalBattle.
^ ^ R o z w ią z a n ie

Refe rencja cdpMrtAmcizmg w skazuje na


ekk. S y p e rhero> a referencja sw indler
nak°, kt, V illain. L ista PeopleInFactory
w klasie Q m ^ d c to ry zaw iera referencje
do nich obu. J

Oto obiekt, "" *ekt


który powinieneś
dodać do tego
diagramu.

Referencja
escapePlane
wska zu je teraz
na nową
instancję obiektu
Dopóki istn ie je
referencja S w indlersEscapePlane.
do sw indler Jeg o pole P ilo tsS e a t
z escapePlane, odwołuje s ię
obiekt ten nie do obiektu Villain.
zostanie u su nięty » ■ /
'S W in d ^

Gdy s ygnalizo wane j e s t zdarzenie S e lfD e stru c t, referencja


Factony u sta w iana j es t na. nuM i zo sta je zakwalifikowana
do us unięcia . Z o std je z atem z tego rysunku wymazana.

Po u su n ięciu referencji Factory to s amo


dzieje się także z obiektem CloneFactory
— powoduje to u su n ięcie obiekto L is't
wskazywanego przez poto peop le InFactory —
Tylko to utrzymywało przy życiu obiekt
Sup erhero. Z o sta n ie on u su n ięty podczas
kolejnego uruchomienia procedury
oczyszczania pam ięci.
° ó'e £ r f ^

Na po dstaw ie analizy d ia g ra m ó w pow iedz, w któ rym m iejscu w kodzie


pro gra m u um rze Kapitan W spaniały.
yoi.d Sę!fpest.r.uct„Xi.ck.(ob.j.e.ct. se.nd.e.r,.EyentArgs e) _Ż . „ l n i l 't n r n F i n a lB a t t l e

fin.aj Batt.le.Factory. = n.uP/ ........................................................................

Instancja Superhero nie ma ju ż


Po ustaw ieniu f in a lB a t t le . F a c t o r y na null ob ie kt został zakw alifikow any ^ ‘ 'w l^ ^ uj,al^ ięp :C
iaakż^^^^^aji:e fabryki
do usunięcia. Razem z nim zostanie usunięta ostatnia referencja kapitana! zakwalifikowana do usotnięc ia.

644 Rozdział 13.


Czy to Twoja ostateczna odpowiedź?
Ogólnie rzecz biorąc, nigdy nie będziesz pisał
fin a liza to ró w dla obiektów posiadających
wyłącznie zasoby zarządzane (ang. m anaged
Twoją ostatnią szansą na Z R O B IEN IE
resources). Wszystko, z czym się do tej pory
czegoś... jest użycie finalizatora spotkałeś w niniejszej książce, było zarządzane —
zarządzane przez CLR (dotyczy to także wszystkich
obiektów umieszczanych na stercie). Jednak
Czasami musisz zadbać o w ykonanie czegoś p rze d usunięciem obiektu.
czasami programiści muszą korzystać z zasobów
M ógłbyś w ted y na p rzykła d z w o ln ić zasoby n ieza rządza ne . -------
samego systemu operacyjnego W indows, które nie
są elementami platform y .NET Framework. Jeśli
Specjalna m etoda ob ie ktu , zwana fin a liz a to re m , pozw ala napisać kod,
kiedyś w internecie znajdziesz kod zawierający
k tó ry zostanie w ykonany podczas jego usuwania. T ra k tu j to ja k swego
atrybut [D lllm p o r t], to może to świadczyć
rodzaju osobisty b lo k f i n a l l y : jest on w ykonyw any na końcu bez
o korzystaniu z zasobów niezarządzanych,
względu na to, co się będzie działo. a niektóre z takich zasobów, jeśli się ich nie zwolni
(na przykład wyw ołując odpowiednią metodę),
O to p rzykła d fin a liz a to ra w klasie C lone:
mogą doprowadzić do niestabilności systemu.
I właśnie tym zajmują się finalizatory.
c la s s C lo n e {
s tr in g L o c a t io n ;
in t C lo n e lD ;
To je s t konstruktor. Wygląda na to, że pola
CloneID i Location s ą uzupełniane podczas
p u b lic C l o n e ( in t C lo n e lD , s tr in g lo c a t io n ) { tworzenia obieM u Ooira.

th is .C lo n e lD = C lo n e lD ;
t h is . L o c a t io n = lo c a tio n ;
}

p u b lic v o id T e llL o c a t io n ( s tr in g lo c a t io n , in t c lo n e lD ) {
C o n s o le . W r it e L in e ( " M ó j num er i d e n t y f i k a c y j n y to {0 } i +
"m o żesz m n ie z n a le ź ć tu ta j: {1 }." , c lo n e lD , lo c a t io n ) ;
}
Z n ak ~ (tylda) infprmuj e, ż e kod z ^ to n ^
wykonany podczas likw idacji obiektu przez
p u b lic v o i d W re a k H a v o c () {...} mechanizm oczyszioza.ma. pam ięci-

To j e s t finalizator.
~ Clo
lo n e ( ) { Przekaz u je on do czarnego
charakteru komunika t
T e llL o c a t io n ( th is . L o c a t io n , t h is .C lo n e lD ) ; z informacją zaw ierającą
C o n s o le . W r it e L i n e ( " { 0 } z o s ta ł u s u n ię ty ." , C lo n e lD ) ; identyfikator i położenie
klona. M e toda ta j e s t
} wywoływana tylko podczas
usuw ania obiektu.
}
M e to d ę fin a liza to ra piszesz ta k ja k
Niektóre z zamieszczonych tu fragmentów kodu zostały
ko n stru kto r. Z am ia st m o d yfika to ra dostępu
przedstawione wyłącznie w celach dydaktycznych,
przed nazwą klasy wstawiasz ~. Inform u jesz
nie po to, by stosować je w rzeczywistych programach.
w ten sposób .NET, że ko d w b lo ku
llu w a l
fin a liz a to ra p o w in ie n być wykonywany W książce wielokrotnie wspominaliśmy o tym, że „kiedyś" obiekty
za każdym razem, gdy o b ie k t zostanie zostaną usunięte z pamięci, nie określiliśmy jednak precyzyjnie, kiedy
ten moment następuje. Pisaliśmy jedynie, że dzieje się to po usunięciu wszystkich
usunięty.
referencji do danego obiektu. Za chwilę przedstawimy kod, który automatycznie
F in a liz a to ry nie m ogą m ieć żadnych uruchamia procedurę oczyszczania pamięci, w y w o łu ją c w tym celu m etodę
pa ram etrów , gdyż .N E T nie m a ob ie kto w i nic G C . C o lle c t ( ) , i w y św ie tla kom un ikat w fin a liz a to rz e obiektu . Takie roz­
do pow iedzenia poza „Z a ra z z tobą skończę!” . wiązania ingerują w wewnętrzne sposoby działania CLR. Pokazujemy je tylko
po to, by zadem onstrować Ci, ja k działa mechanizm oczyszczania pamięci.
N igdy nie u ż y w a j ich w program ach innych n iż te sto w e !
646 Rozdział 13.
Śmierć obiektu

Kiedy D O KŁA D N IE wywoływany jest finalizator?


To Twój obiekt
Finalizator, któ ry napisałeś dla obiektu, wywołany zostaje przechowywany
po usunięciu wszystkich referencji, ale zanim obiekt zostanie Ten obiekt przechow uje w pam ięci.
usunięty przez mechanizm oczyszczania pamięci. Usuwanie referencję do Twojego
obiektu.
elementów bezużytecznych następuje w przypadku utraty e r
wszystkich referencji do nich, ale nie jest wykonywane zaraz
po ich zniknięciu.

Przypuśćmy, że posiadasz ob ie kt i referencję do niego.


O : - :
.N E T uru cha m ia m echanizm oczyszczania pam ięci i sprawdza
% r O b f °
ĄyO op*
każdy obiekt. Jeżeli istnieją do niego referencje, m echanizm S te r ta
go ig n o ru je i przesuwa się dalej. O b ie k t w dalszym ciągu
przechowywany jest w pam ięci. ... ale teraz nie
Twój obiekt ma ju ż do niego
Potem coś się dzieje i ostatni o b ie k t przechowujący referencję w dalszym ciągu
żadnych odwotań.
j e s t na s te rc ie ...
do Twojego o b ie ktu decyduje się wskazywać na inne miejsce.
T eraz T w ó j o b ie k t przechowywany jest w pam ięci bez S
refere ncji. N ie m ożna uzyskać do niego dostępu.
G eneralnie jest to o b ie k t m a rtw y.

Przechodzim y do sedna sprawy. M ech a n izm oczyszczania


p a m ię ci je s t sterow any p rzez .N E T , nie przez T w o je obiekty.
Jeśli nie jest on urucham iany przez, pow iedzm y, k ilk a sekund
czy m in u t, to T w ój ob ie kt w dalszym ciągu istnieje w pam ięci. S te r ta
N ie m ożna go używać, ale jeszcze nie został usunięty.
Jego fin a liz a to r n ie bę dzie (jeszcze) u ru c h a m ia n y .
W końcu z 0S^ 3ZczaUa°pam^ ci
W koń cu .N E T po raz ko le jn y włącza mechanizm. r iw d T ó Ł zostanie usunięty.
F in a liz a to r zostaje w ykonany — być może nawet kilka
m in u t po usunięciu lu b zm od yfikow aniu ostatniej refere ncji
do obiektu. Teraz, gdy o b ie k t został sfinalizow any, jest ju ż
m artw y i m ożna go usunąć z pam ięci. P yk! —

Możesz ZASU GERO W AĆ .N E T , że nadszedł


/ \
odpowiedni czas na uprzątnięcie śmieci %
^ ^ e rr O b /
S te r ta
.N E T pozwala C i zasu gerow ać właściw y m om e nt rozpoczęcia
public void RemoveTheClones(
sprzątania. W w ię kszo ści p rz y p a d k ó w n ie będziesz używ ał
List<Clone> clones) {
te j m etody, po niew a ż m e c h a n iz m oczyszczania p a m ię c i je s t
foreach (Clone clone in clonesToRemove)
d o stosow an y do w s p ó łp ra c y z w ie lo m a ró ż n y m i w a ru n k a m i
clones.Remove(clone);
C L R i je g o w yw o ływ a n ie n ie je s t dobrym pom ysłem . A b y
G C . C o lle c t ( ) ;
je d n a k zobaczyć sposób działania fin a liza to ra , możesz
}
u ru cho m ić ten m echanizm sam odzielnie. G dy chcesz to zrobić, Z catą mocą podkreślamy, ja k bardzo ztym pomy s tem^j e s t
po p ro stu wyw ołujesz GC.Collect() . wywoływanie metody GC.Collec t () w każdym programie
z wyjątkiem testowych! M oże to wywotać problemy
Bądź je d n a k ostrożny. T a m etoda nie zm usza .N E T do w działaniu mechanizmu oczyszczam d pam ięci C L R .
M etoda ta j e s t doskonałym narzędziem do uczenia s ię
natychm iastowego przeprow adzenia sprzątania. Po pro stu korzystania z finalizatorów i mechaniz mu oczy s z cz ania
m ów i: „P rzeprow adź procedurę oczyszczania pam ięci tak pam ięci, dlatego też stworzym y faM te s towy program,
który pozwoli nam s ię nią „pobawić .
szybko, ja k to m ożliw e” .
jesteś tutaj ► 647
Pozbieraj śm ieci
I , j ak ju ż w cześniej w idziałeś,
metoda D isp o se () działa także
Dispose() działa z u s in g , a finalizatory w przypadkach, gdy nie została
z astosowana instrukcja using.
działają z mechanizmem oczyszczania pamięci J e j w ielokrotne wywoływanie
nie powinno powodować
efektów ubocznych, które mogą
G dy o b ie k t im p le m e n tu je in te rfe js ID is p o s a b le , po d koniec w ykonyw ania bloku przysp orzyć Ci problemów.
in s tru k c ji u s in g zostanie w yw ołana jego m etoda D is p o s e (). Jeśli nie używasz u s in g ,
ustaw ienie re fe re n cji na n u ll nie spowoduje w yw ołania D is p o s e () — musisz ją wywołać
bezpośrednio. F in a liz a to r uru cha m ian y jest podczas działania m echanizm u oczyszczania
pam ięci wobec kon kretne go obiektu. U tw ó rz m y k ilk a o b ie któ w i sprawdźmy, ja ka jest
pom iędzy n im i różnica. U ru ch o m V is u a l S tud io fo r W indow s Desktop i u tw ó rz nowy r o b to !
p ro je k t W indow s F orm s A p p lic a tio n

Utwórz klasę C lone , która implementuje interfejs I D is p o s a b le i ma finalizator.


Klasa po w in na posiadać autom atyczną właściwość i n t o nazwie Id.
Posiada rów nież k o n stru kto r, m etodę D is p o s e () oraz fin a liza to r:
To doskonały przykład pokazujący,
u s in g System.W indows.Forms; że aplikacje „okienkowe" mogą być
doskonałym narzędziem do poznawania
c la s s Clone : ID is p o s a b le { C# i .NET. W tym projekcie ponownie
p u b lic in t Id { g e t; p r iv a t e s e t; } stworzysz projekt W indows Forms, by
W związku z tym, że klasa móc skorzystać z faktu, że wyświetlenie
implementuje I D isposable, okienka MessageBox blokuje działanie
p u b lic C lo n e ( in t Id ) {
musi m ieć metodę programu i użyć tego do poznawania
t h i s . I d = Id ; D isp o se().
funkcjonowania mechanizmu
}
oczyszczania pamięci.

W yśw ietlanie komunikatu p u b lic v o id D isp o se () {


przy użyciu Masy MessageBox.Show("ZostaJem u s u n ię t y !" ,
M essa g eB o x w me todz ie
"K lon " + Id + f . .
S zyb kie przypomniento'- J e ś >i używaisz
finalizatora może
wyw ołać problemy } To j e s t fínWi'zator. Uruchamiany je s t V isu al S tu dio w w e rs ji p roffesion ah
w działaniu C L R . w momencie usuw ania obiektu. Premium lub Ultim ate, m ozesz w nim
Rozwiązania ta kie można tw orzyć zarówno aplikacje Windows
s tosować w yłącznie -C lo n e () {
S to re , ja k i Windows D esktop.
w programach testo w uch, MessageBox.Show("Aaaaaa! DopadJeś m n ie !1
gdy ch cesz p o z i a "K lon " + Id + " . m ó w i. .. " )
działanie m e ch a n ik u
}
oczyszczania pam ięci. }
To je s t formularz, który
powinieneś utw orzyć■
Utwórz formularz z trzema przyciskami.
D o d a j jedną instancję C lo n e w ew nątrz p ro ced ury obsługi C lic k dla pierwszego
przycisku przy użyciu in s tru k c ji u s in g . O to pierwsza część jego kodu:
Metoda
tworzy nowy p r iv a t e v o id c lo n e l_ C lic k ( o b je c t sender, EventArgs e)
obiekt Clone u s in g (Clone c lo n e l = new C lo n e ( l) )
i natychm iast
>. // N ie rób n ic !
go u su w a,
likwidując }
referencję.
} J
Zadeklarowaliśm y M etoda D isposeQ obiektu
clone1 w in stru kcji Clone wywoływana j e s t zaraz
using, dlatego po zakończeniu s ię bloku using.
zostanie w y w o ł a N e ma ju z żadnych referencji,
więc ob ,ekt j e s t oznaczany jako
jego metoda gotowy do usunięcia
D isp o se().
648 Rozdział 13.
W. tf n s p 0sób można obserwować dowolnie
wtete °t>iektów — ttędą one o z n a c z a n e ___ Śmierć obiektu
kmejnym tańcuchami 2 #, 3 # itd.
Dodaj jedną z referencji obiektów
Zaimplementuj pozostałe dwa przyciski. Clone do okna Watch, kliknij
Stwórz kolejn ą instancję C lone w procedurze obsługi zdarzenia C lic k ją prawym przyciskiem myszy,
drugiego przycisku i ręcznie ustaw je j w artość na n u ll. wybierz opcję k* Object id ;
do kolumny wartości zostanie
p r iv a t e v o id c lo n e 2 _ C lic k (o b je c t sen de r, EventArgs e) {
dodany łańcuch {#1}. Dzięki temu
Clone clone2 = new C lo n e (2 ); można obserwować obiekt, nawet
clone2 = n u l l ; n t i n Ś y a m btok u u sing' więc metoda jeśli jego referencje przestaną
, . D 's Po se( ) nie toędzie wywoływana.
} v--«.---------------/ Wywotany zosta n ie finatizator. być dostępne. Okno Watch
poinformuje, kiedy obiekt został
D o trzeciego przycisku dodaj w yw ołanie G C . C o lle c t ( ) , aby zasugerować .N E T usunięty (w takim przypadku może
przeprow adzenie pro ce d u ry oczyszczania pam ięci. być konieczne kliknięcie ikony CJ1,
aby go odświeżyć).
p r iv a t e v o id g c _ C lic k ( o b je c t sen de r, EventArgs e) {
G C .C o lle c tQ ; j ° SUgeruj e uruchomienie
Pam iętaj, zwykle wywoływanie tej metody
} procedury oczyszczania
nie j e s t dobrym pomysłem . W ty m przypadku
pam ięci.
j e s t to dobre rozwiązanie, ponieiważ ułatwia
nam zrozum ienie tego mechanizmu.
Uruchom program, aby pobawić się Dispose() i Analizatorami.
K lik n ij pierwszy przycisk i sprawdź ko m u n ika t: D is p o s e () w yw oływ ane jest pierwsze
Pomimo tego, że oblekł
Klon 1. mówi... cl°ne1 z ° s t a ł
ustawiony
na null i została wywołana
Nie zapomnij dodać in stru kcji _ je go metoda D isp o se(),
u s i n g S y s t e m . Windows. Forms, na
w dalszym ciągu znajduje s ię
samym początku pliku klasy Clone. on na sto s ie i oczekuje na
uruchom ienie oczyszczania
pam ięci.

N iepo trzebn e o b ie kty zostaną usunięte z p a m ię ci... k ied yś. W większości CLONE1
przypadków n ie zobaczysz okna ko m u n ika tu pro ce d u ry usuwania elem entów
bezużytecznych. T w ó j o b ie kt, co prawda, został ustaw iony na n u l l ,
ale pro ced ura nie została jeszcze uruchom iona. Ó' ek t d d ?

K lik n ij teraz dru gi p r z y c is k . N ic się nie stało, prawda? T o dlatego, STERTA


że nie użyliśm y in s tru k c ji u s in g i nie została wyw ołana m etoda D is p o s e ( ) .
Teraz clone2
D o p ó k i nie zostanie uru ch o m io n a pro ced ura oczyszczania pam ięci, dopóty jest także na CLONE1 CLONE2
nie zobaczysz okna fina liza tora. stercie, ale % Ci %
nie posiada
K lik n ij trzeci przycisk, aby zasugerować u ru cho m ien ie procedury e k t ciP
żadnych
referencji. STERTA
oczyszczania pam ięci. Powinieneś zobaczyć fin a liz a to ry zarówno o b ie ktu
c lo n e l, ja k i c lo n e 2 oraz odpowiadające im okna kom unikatów .

\
\ I / - pyk! —
- pyk! —
/ I \
/ I \
STERTA
Po wywołaniu G C .C ollect() wykonywane s ą
finalizatory w obu obiektach, a one sam e znikają.

Pobaw się p ro g ra m e m . K lik n ij przycisk K lo n 1 . , po tem K lo n 2 . , a następnie G C . Z ró b to k ilk a razy. Czasami jako
pierwszy jest usuwany klo n n u m er 1, innym razem jest to klo n 2. M oże się też zdarzyć, że pro ced ura oczyszczania
pam ięci urucham iana będzie nawet bez jawnego w yw ołania G C .C o lle c t( ) .
jesteś tutaj ► 649
N iestabilne środowisko

Powiedzmy, że masz dwa obiekty,


Finalizatory nie mogą polegać na stabilności
które posiadają referencje
do siebie nawzajem...
K ie d y piszesz fin a liz a to r, nie możesz zakładać, że zostanie
on w yw ołany w określonym czasie. N a w et je żeli wywołujesz
GC.Collect() — czego pow inieneś unikać, chyba że masz
ku tem u dobry p ow ód — to ty lk o su gerujesz .N E T
uru cho m ien ie m echanizm u oczyszczania pam ięci. N ie m a żadnej
gw arancji, że rozpocznie się on natychm iast. K ie d y zostanie
zainicjow any, nie m a rów nież żadnego określonego porząd ku
usuwania obiektów .
O
Co to oznacza w praktyce? W yobraź sobie, że posiadasz dwa
obiekty, któ re przechow ują referencje do siebie nawzajem.
G dyby o b ie k t n r 1 został usunięty ja ko pierwszy, to referencja Gdyby oba zostały oznaczone jako
o b ie ktu n r 2 wskazywałaby na ob ie kt, któreg o ju ż nie ma. Gdyby gotowe do usunięcia w tym samym
zaś ja k o pierwszy został usunięty o b ie k t n r 2, to niepraw idłow a czasie, to obiekt nr 1 mógłby zostać
byłaby referencja o b ie ktu 1. Oznacza to, że n ie m ożesz p o leg a ć na usunięty jako p ie rw s z y .
referencjach w A n a liz a to ra ch obiektów . Oznacza to także, że nie
jest dobrym rozw iązaniem wykonywanie w fina liza torze operacji,
któ re są zależne od pra w id ło w ych referencji.

Serializacja jest naprawdę doskonałym przykładem czegoś, czego


\ I /
n ie m ożesz r o b ić w fin a liz a to rz e . Jeśli T w ój ob ie kt posiada
nr 2
zestaw re fe re n cji do innych ob ie któ w , serializacja zależy od tego, pyk! —
czy w szystkie one będą dostępne w p a m ię ci... i od wszystkich / \
ob ie któ w , do których istnieją z nich referencje, i od tych,
do których te referencje się odnoszą, i ta k dalej, i ta k dalej.
Gdybyś spróbow ał serializacji podczas w ykonyw ania pro ced ury
oczyszczania pam ięci, mógłbyś u tra c ić ważne części program u, . a le także obiekt nr 2 mógłby
poniew aż o b ie kty m ogłyby zostać usunięte p rze d uru cho m ien ie m zniknąć przed obiektem nr 1. Nie
finalizatora. masz sposobu, aby poznać kolejność.
N a szczęście C # ma doskonałe rozw iązanie: IDisposable.
Wszystko, co może m odyfikow ać kluczowe dane lub w dużej
mierze zależy od innych o b ie któ w w pam ięci, m usi zostać I /
umieszczone w Dispose(), a nie w finalizatorze.
p y k! —

N ie k tó rz y tra k tu ją fin a liz a to ry ja ko swego rod zaju bezpieczne / i \


m etody Dispose(). M a to pewien sens — w idziałeś
to w przyp ad ku o b ie ktu Clone, bo zaim plem entowałeś
IDisposable, ale nie spowodow ało to wcale, że w ykonana
e4? n r ^
została m etoda Dispose() . M usisz być je d n a k ostrożny — jeśli
T w o ja m etoda Dispose() zależy od innych o b ie któ w na stercie,
w ykonanie je j z fin a liz a to ra może przysporzyć problem ów .
Najlepszym sposobem na poradzenie sobie z n im i jest zadbanie . i właśnie dlatego fin a liza to r obiektu
o to, aby podczas pracy z ob ie kte m IDisposable zawsze nie może polegać na żadnym innym
używ ana b y ła in s tr u k c ja u s in g . obiekcie obecnym na stercie.

650 Rozdział 13.


Śmierć obiektu

Spraw, aby obiekt serializował się w Dispose()


Jeśli ju ż zrozum iałeś różnicę pom iędzy Dispose() a fin a liza to re m , to bardzo łatw o możesz
napisać obiekty, któ re autom atycznie się zserializują podczas ich likw id ow a nia.

[ !> SPRAW, ABY KLASA CLONE ZE STRONY 648 BYŁA ZDOLNA DO SERIALIZACJI.
D o da j po pro stu a tryb u t Serializable na górze klasy, abyśmy m o g li ją zapisać do p liku .

[ S e r ia liz a b le ]
public class Clone IDisposable

ZMODYFIKUJ METODĘ D IS P O S E () OBIEKTU CLONE,


TAK ABY W YKO N YW AŁA SERIALIZACJĘ DO PLIKU?
U żyjm y BinaryFormatter , aby zapisać instancję Clone do p lik u w m etodzie Dispose():
Będziesz potrzebował
using System.IO; jeszcze kilku
using System.Runtime.Serialization.Formatters.Binary; in stru kcji using,
d * " aby uzyskać dostęp
/ / Istniejący kod do używanych przez
nas klas 1/ 0 .
W programach
demonstracyjnych public void Dispose() { Obiekt Clone utworzy katalog
powróciliśm y d° s trin g filename = @"C:\Temp\Klon.dat"; C :\Tem p i z se ria iiz u je sam
s to sowania se ria liz acji s trin g dirname = @"C:\Temp\"; sie b ie do pliku K lrn .d tit ^
binarnej i podanych na i f (File.E xists(filenam e) == false) {
s t ałe nazw folderów, Nazwę pliku podaliśm y na s tałe —
gdyż s ą to n a jprostsze Directory.CreateDirectory(dirname); um ieściliśm y j ą w kodzie j ako hternl
rozwiązania — } łańcuchowy. Takie rozwiązanie
można zaakceptować w przypadku
oraz dlatego, ¿ e BinaryFormatter bf = new BinaryFormatterQ; małych programów testow ych, lecz
nie chcemy, by ś using (Stream output = File.OpenWrite(filename)) t je s t ono problem atyczne. C zy m °że s z
postępow a ł podobnie
w rzeczyw istych b f.S e ria liz e (o u tp u t, t h i s ) ; wskazać, jakich problemów może m o
programach! Można przysporzyć oraz ja k ich uniknąć?
}
tak robić tylko MessageBox.Show("Tu 11 + t h i s . I d + ", muszę z s e ria liz o w a ć ../p o b ie k t.");
w programach }
do nauki. } • t
A czy ta metoda D isp o se() j e s t całk° w icie
URUCHOM APLIKACJĘ. pozbawiona efektów ubocznych? Co s ię
s tanie, je ś li zostanie wywołana w ięcej niż
Zaobserwujesz to samo zachowanie, k tó re widziałeś na k ilk u jeden raz? To w szystko s ą zagadnienia,
poprzednich stronach... ale zanim c lo n e l zostanie usunięty, program o j a kich m u sisz pam iętać, implementując
in te rfe js ID isposable.
przeprow adzi jego serializację do p lik u . Spójrz na ten p lik , a zobaczysz
binarną reprezentację ob ie ktu . „ _ « __ «

ą WYSIL _________
SZARE KOMÓRKI
Ten projekt daje dużo do myślenia. Jak sądzisz, jak może wyglądać pozostała część kodu klasy SuperHero?
Na stronie 642 pokazaliśmy tylko jego fragment. Czy jesteś w stanie napisać jego pozostałą część?
A co ważniejsze — czy pow inieneś to robić?
Bez wątpienia istnieje m ożliw ość , by obiekty same wykonywały swoją serializację w chwili, gdy są usuwane.
Ale czy to dobry pomysł? Czy nie jest to sprzeczne z zasadą separacji zadań? Czy nie prowadzi to do powstawania
kodu trudnego do utrzymania? A jakie inne problemy mogą się z tym wiązać?

jesteś tutaj ► 651


Co się stało z kapitanem ?

Pogawędki przy kominku


Dzisiejsza rozmowa: M etoda D is p o s e () i fin a liz a to r spierają się o to,
które z nich jest bardziej w artościow e.

Dispose: Finalizator:
Bądźmy szczerzy, jestem trochę zaskoczona, że się tutaj
znalazłam. Myślałam, że świat programistyczny doszedł już
do pewnego konsensusu. Mam na myśli to, że jestem bardziej
wartościowa niż ty. Tak poważnie — jesteś całkiem słaby.
Nie możesz nawet siebie serializować, modyfikować kluczowych
danych. nie możesz nic. Jesteś niestabilny, czyż nie?
Przepraszam. To jest trochę niepoważne. Jestem słaby.
dobra. Nie chciałem tego poruszać, ale skoro schodzimy do
takiego poziomu. Ja przynajmniej nie potrzebuję interfejsu,
aby zaistnieć. Bez IDisposable jesteś po prostu kolejną
bezużyteczną metodą.
Specjalny interfejs istnieje, poniew aż jestem tak ważna.
Fakty są takie, że jestem w nim jedyną metodą!
Dobra, dobra. Możesz sobie tak mówić. Co się jednak
stanie, gdy użytkownik zapomni użyć instrukcji using
podczas tworzenia instancji obiektu? Wtedy nie można
cię odnaleźć.
OK, masz rację, programiści muszą wiedzieć, że mnie potrzebują,
i albo wywołują mnie bezpośrednio, albo używają instrukcji
using, która to zrobi. Wiedzą jednak zawsze, w którym
momencie jestem wywoływana, i mogą zrobić wszystko, czego
s i ę bezpo średnio z Windows/
potrzebują, w celu uprzątnięcia obiektu. Jestem potężna, W zw iązku z tym, że .N ET nie
niezawodna i łatwa w użyciu. Jestem „trzy w jednym". A ty? wie nic o ich istn ien iu , nie może
Nikt dokładnie nie wie, kiedy zostaniesz wykonany i jaki będzie po nich posprzątać.
stan aplikacji, gdy w końcu zdecydujesz się pokazać.
W porządku, ale jeśli potrzebujesz zrobić coś dosłownie
w ostatnim momencie podczas usuwania obiektu, nie
możesz tego zrobić inaczej, jak tylko przeze mnie. Mogę
zwolnić zasoby sieciowe i uchwyty okien, i strumienie,
i wszystko inne, co może mieć wpływ na pozostałą część
programu, jeśli nie posprzątasz prawidłowo. Mogę się
upewnić, że obiekty, z którymi pracujesz, zostaną elegancko
usunięte, i nie masz prawa tym gardzić.
Więc nie ma niczego, co możesz zrobić ty, a czego nie mogę ja.
Myślisz, że wiele znaczysz, tylko dlatego, że jesteś wywoływany
podczas uruchamiania procedury oczyszczania pamięci, ale ja
przynajmniej mogę polegać na innych obiektach.
To prawda, koleżanko — ja zostanę wykonany zawsze;
a ty potrzebujesz kogoś, kto cię wywoła. Ja nie potrzebuję
nikogo i niczego!

652 Rozdział 13.


■Nie .istnieją. .
głupie pytania

P : Czy finalizator może używać wszystkich pól i metod P : Jak często wykonywana jest automatycznie procedura
swojego obiektu? oczyszczania pamięci?

O : Oczywiście. Nie możesz co prawda przekazać parametrów O : Nie ma dobrej odpowiedzi na to pytanie. Nie jest ona uruchamiana
do metody finalizatora, ale możesz używać wszystkich pól w obiekcie w jakimś przewidywalnym cyklu i nie masz nad tym żadnej kontroli. Możesz
albo bezpośrednio, albo korzystając z t h is . Bądź jednak ostrożny, ponieważ być pewny, że zostanie wykonana w momencie zakończenia Twojego
pola te mogą przechowywać referencje do innych obiektów, a te mogą już programu. Jeśli chcesz mieć pewność, że mechanizm zadziała, musisz wywołać
być usunięte. Możesz też oczywiście wywoływać podczas finalizacji obiektu G C .C o lle c t (), ale nawet wtedy czas jego wykonania nie jest określony
inne metody i właściwości (o ile ich działanie nie zależy od innych obiektów).
P : Jak szybko po wywołaniu GC.Collect() .NET wykona
P : Co się dzieje z wyjątkami zgłaszanymi w Analizatorze? procedurę oczyszczania pamięci?

O : Dobre pytanie. Użycie bloku try / c a tc h w finalizatorze jest O : Wywołując G C .C o lle c t (), nakazujesz platformie NET wykonać
całkowicie poprawne. Spróbuj sam. Utwórz wyjątek dzielenia przez zero sprzątanie pamięci tak szybko, jak to tylko możliwe. Oznacza to mniej
wewnątrz bloku t r y programu Clone, który przed chwilą napisaliśmy. więcej moment, w którym NET zakończy swoje aktualne zadania. Zostanie
Wyłap go i wyświetl komunikat „Właśnie wyłapałem wyjątek." tuż przed to zrobione dość szybko, ale nie możesz tego dokładnie kontrolować.
komunikatem „Aaaaaa! Dopadłeś mnie!", który już mamy. Uruchom teraz
program i kliknij pierwszy przycisk, a następnie przycisk GC Zobaczysz P : Jeżeli koniecznie muszę coś wykonać, to wrzucam
zarówno okno wyjątku, jak i okno informujące o usunięciu obiektu. to do finalizatora, prawda?
(Oczywiście wyświetlanie okien MessageBox w finalizatorach obiektów,
które nie są tylko zabawkami, to naprawdę zły pom ysł.. Te okna O : Istnieje możliwość, że finalizator nie zostanie wykonany Jest też możliwe
komunikatów mogą się nigdy nie wyświetlić). zablokowanie finalizacji podczas przeprowadzania procedury oczyszczania
pamięci. Proces może się również zakończyć, zanim cokolwiek zdąży się
wykonać. Jeśli robisz cokolwiek oprócz zwalniania zasobów niezarządzanych,
to niemal zawsze lepszym rozwiązaniem będzie użycie ID isposable
i instrukcji using.

jesteś tutaj ► 653


CO SIĘ DZIEJE? DLACZEGO
C A ŁA M O C OPUŚCIŁA
KAPITANA WSPANIAŁEGO?
C Z Y TO KONIEC?

654
Śmierć obiektu

Struktura jest podobna do obiektu...


Jednym z typów, o któ ry m jeszcze nie m ów iliśm y, jest stru ktu ra , oznaczana
słowem kluczow ym struct . Jest to skró t od angielskiego słowa s tru c tu re , czyli
S tru k tu ry mogą implementować
struktura . struct jest pra w ie ja k ob ie kt. Możesz ją nawet przekazać do m etody,
in te rfe isy> ni©
k tó ra ja ko p a ram e tr p rzyjm uje typ object : dziedziczyć po innych klasach.
Poza tym nie można po men
dziedziczyć.
public s tru c t AlmostSuperhero : IDisposable I
public in t SuperStrength; S tru k tu ry mogą mieć
public in t SuperSpeed I get; private set; I wła ściw ości i pola...

public void RemoveVillain(Villain v i l l a i n ) mogą definiować metody.


{
Console.WriteLine("OK, 11 + villain.Name +
" poddaj się i powstrzymaj to cale szaleństwo!")
i f (villain.Surrendered)
v illa in .G o T o J a il( );
else Moc obiektów leży
v illa in .K ill( ); w ich możliwości
}
reprezentowania
public void Dispose() I I zachowań ze świata
I
rzeczywistego
. a l e n ie je s t obiektem poprzez
S tru k tu ry n ie są o b ie kta m i. M ogą m ieć m etody i pola, ale nie mogą dziedziczenie
posiadać fin a liza to ró w . N ie mogą także dziedziczyć po innych klasach
i struktura ch ani być ich klasą bazową.
i polimorfizm.
W szystkie stru k tu ry dziedziczą p°
S ystem .V a lu eType, który z kolei
dziedziczy po S ystem .O b je c t. To
w łaśnie dlatego każda stru kto m
dysponuje metodą ToS trin g() —
dziedziczy j ą po ty p ie O bject.
Jednak j e s t to jedyna forma
dziedziczenia, z której mogą
korzystać stru ktu ry.
o A
M ożesz reprezentować
za pomocą stru k tu ry
pojedynczy obiekt, ale nie
spraw dzają s ię one dobrze
p rzy skomplikowanych
hierarchiach dziedziczenia.
Struktury
najlepiej nadają się
do przechowywania
danych, jednak
brak dziedziczenia
S tru k tu ry nie mogą
dziedziczyć po innych
obiektach. To wtaśnie dlatego klasy s ą
i referencji może
s tru c t stosow ane znacznie cz ę ś c ie j
Nie oznacza to jednak, że
stanowić poważne
stru k tu ry są bezużyteczne!
ograniczenie.
Jednak czynnikiem , k tó ry w znacznie większym stop niu od różn ia s tru k tu ry od obiektów ,
jest to, że są one kopiow ane p rzez w artość, a n ie p rzez referencję. Przew róć kartkę,
aby dowiedzieć się, co to oznacza.
jesteś tutaj ► 655
Tworzenie kopii

Wartości są kopiowane, referencje są przypisywane


M am y ju ż pewne pojęcie o różn icy pom iędzy typam i. Po jednej stronie m am y typ y
w a rto ścio w e takie ja k i n t , b o o l, czy też d e c im a l. Po drugiej m am y o b ie k ty , na przykład
L i s t , S tream i E x c e p tio n . N ie zachowują się one w ta k i sam sposób, prawda? Oto krótkie przypomnienie
różnic pomiędzy typami
K ie d y używasz znaku rów ności w celu przypisania jednej zm iennej typu w artościowego wartościowym i a obiektami.
do drugiej, tw orzysz k o p ię w a rto ś c i te j zm ie n n e j. Po w yko na niu całej operacji zmienne
nie są ze sobą powiązane. Z drugiej strony m am y referencje. U życie znaku rów ności w ich
przypadku skutkuje tym , że ob ie re fe re n c je w s k a z u ją na te n sam o b ie kt.
i
O D e klara cje zm iennych i przypisania działają dokładnie
ta k samo z typa m i w artościow ym i i z o b ie kta m i:
Pam iętasz, ja k kiedyś ¡nt ; booi s ą typami wartościowym i,
stw ierdziliśm y, że i n t howMany = 25; / natom iast L i s t i Exception s ą typam
metody i in stru kcje bool Scary = t r u e ; obiektowy m i-
Z A W S Z E istn ie ją W szystkie one są
j edynie w klasach? Cóż, L is t< d o u b le > te m p e ra tu re s = new L is t< d o u b le > () ;
inicjalizowane w ten
okazuje s ię , że to nie E xce p tio n ex = new E x c e p tio n ("N ie da s ię o b lic z y ć . sam p ro sty sposób.
do końca j e s t prawdą
— można je umieszczać
także w struktura.ch -
O R óżnice po ja w ia ją się w m om encie przypisywania w artości.
T ypy w artościow e są bezpośrednio kopiow ane. O to przykład:

Zmiana Ten w ie rsz kop iuje w artość przechowywaną


w artości w z miennej howMany do zmiennej
i n t fifte e n M o re = howMany;
zm iennej FifteenMore i dodaje do niej 15.
fifteenM ore „■— fifte e n M o re += 15;
nie ma wpływu C o n so le.W riteLine ("ho w M an y ma { 0 } , fifte e n M o re ma { 1 } " , howMany, fifte e n M o re )

n na°odwrót. W y n ik pokazuje, że w artości f if te e n M o r e i howMany n ie są ze sobą powiązane.

howMany ma 25, fifte e n M o r e ma 40

O Przypisując obiekty, ko p iu je m y tylko referencje, nie wskazywane wartości:

Ten w iersz te m p e ra tu re s .A d d (5 6 .5 D );
u sta w ia referencję te m p e ra tu re s .A d d (2 7 .4 D );
diffe re n tL is t tak, temperatures
aby wskazywała L is t< d o u b le > d if f e r e n t L n s t te m p e ra tu re s ;
na ten sam obiekt d iff e r e n t L is t. A d d ( 6 2 .9 D ) ; differentList
co referencja
Obie referencje ws kaz u ją
tempe ra tu re s. na ten sam ob iekt.
^ < ¿ 0 ^
Z m ia n a o b ie ktu L i s t sprawi, że obie referencje odczują różn icę ...
poniew aż obie na niego wskazują.

C o n s o le .W rite L in e ("te m p e ra tu re s ma { 0 } , d i f f e r e n t L i s t ma { 1 } " ,


te m p e ra tu re s .C o u n t(), d i f f e r e n t L i s t . C o u n t ( ) ) ;

W y n ik pokazuje, że d i f f e r e n t L i s t i te m p e ra tu re s
w rzeczywistości wskazują na ten sam obiekt.

te m p e ra tu re s ma 3 , d i f f e r e n t L i s t ma 3 obiektu, na który w sk ^ u je za
— 1 ^ d iffe r e n tL is t, j a k i t e m p e r a tu r e s .

656 Rozdział 13.


Śmierć obiektu

Struktury traktowane są jak typy wartościowe,


obiekty jak typy referencyjne
T w orząc stru ktu rę , tworzysz ty p w a rto ścio w y. Oznacza to, że w m om encie użycia znaku rów ności
w celu przypisania jednej zm iennej do drugiej tworzysz w tej nowej zm iennej świeżą kopię
struktury. P om im o tego, że wygląda ona ja k ob ie kt, zachowuje się zupełnie inaczej.
^ ^ ^ Z ró b to !
Stwórz strukturę o nazwie Dog.
O to prosta stru ktu ra , k tó ra przechow uje in fo rm a cje o psie. W ygląda ja k ob ie kt, ale n im nie jest
D o da j ją do nowej a p lika cji konsolowej.
hermevyzaoja
ie j e s t poprawna nem etyzacja
p u b lic s t r u c t Dog { Tak, to nie
p u b lic s t r in g Name; ta cji. Zrób jednak
implementac} . tok j ak fa fa j
p u b lic s t r in g Breed; Z — mamy Pew ien Pow° '

p u b lic D o g (s trin g name, s t r in g breed)


this.N a m e = name;
th is .B r e e d = bre ed ;
}

p u b lic v o id Speak() {
C o n s o le .W rite L in e ("W a b ię s ię { 0 } . Moja rasa to { 1 }." , Name, B reed);
}
}

Utwórz klasę o nazwie C a n in e .


Stwórz dokładną kop ię s tru k tu ry Dog, ale za m ie ń s t r u c t na c la s s oraz Dog na Canine.
(N ie zapom nij o zm ianie nazwy ko n stru kto ra klasy C a nin e). W ten sposób będziesz m ia ł gotową
do zabawy klasę C anine, k tó ra będzie praw ie taka sama ja k analogiczna stru ktu ra Dog.

Dodaj metodę Main(), która kopiuje instancje Dog i C a n in e .


O to kod:

Canine sp o t = new C a n in e ("B u re k ", "m ops"); W s w o ic h p r o g r a m a c h ju ż


Canine bob = s p o t; u ż y w a ł e ś s t r u k tu r . C zy
bob.Name = " S z a r ik " ;
p a m i ę t a s z t y p DateTime
bob.Breed = "b e a g le ";
s to s o w a n y w p o p rz e d n ic h
s p o t.S p e a k ();
r o z d z i a ła c h ? N ie m a l
Dog ja k e = new D o g ( " T o fik " , " p u d e l" ) ; c a ły c z a s p o s ł u g u je s z s ię
Dog b e t ty = ja k e ; s t r u k tu r a m i !
betty.N am e = "B e c ia ";
b e tty .B re e d = " p i t b u l " ;
ja k e .S p e a k ();
C onsole.R eadK ey(); Zaostrz ołówek
§>. Ł.UU9U

Z) Zanim uruchomisz program.


Napisz, co w e dłu g Ciebie zostanie wypisane w oknie kon soli po u ru ch o m ie n iu tego kodu.

jesteś tutaj ► 657


Stos i sterta

^Zaostrz ołówek
Jak myślisz, co zostanie wypisane w oknie konsoli?
Rozwiązanie Wabię się Szarik._ Moja rasa _to _beagle.
Wabię się Tofik. Moja rasa to pudel.

Oto co się stało... Utworzony z o sta ł

O bie referencje, bob i s p o t, wskazują na ten sam obiekt. n°wskazuj e na niego


■ spot
O bie zm ieniają to samo po le i uzyskują dostęp do tej samej m etody referencja sp ot.
Burek
S p e a k (). S tru k tu ry nie działają w ten sposób. G dy utw orzyłeś b e t ty ,
© mops ©
powstała świeża ko p ia danych o b ie ktu ja k e . S tru k tu ry są całkow icie
od siebie niezależne. 5 ia f c /
Canine spot = new Can in e (" Bu r ek " , "mops"); 0
Canine bob = spot; Utworzona została nowa zmienna
referencyjna bob, ale do s te rty nie
bob.Name = " S z a r i k " ; dodano ż adnego nowego obiektu — mops ^
zmienna bob w skazuje na ten sam
bob.Breed = " bea gl e" ; obiekt co sp o t. % 'e k t
s po t. Sp e ak () ; 0

W związku z tym, ż e sp o t i bob


wska zu ją na ten sam obiekt,
sp o t.S p e a k () i b o b.Spea k() wywołują tę ^ beagle
sam ą metodę i generują te sam e wyniki:
„S za rik “ i % 'e k t ^

Dog jake = new D o g ( " T o f i k " , "pudel"); 0 ©/


Dog betty = jake; Kiedy tw orzysz nową / Tofik
stru k tu rę , wygląda to podobnie 1
\ pudel
betty.Name = " Be c i a " ; do tworzenia obiektu —
o trzym ujesz zmienną, do -
betty.Breed = " p i t b u l " ; której m ożesz uzyskać dostęp
ja k e
za pomocą pól i metod.
j a k e . S p e ak ( ); 0

Tu j e s t wielka różnica. © \
Gdy dodałeś zmienną betty, /
/ Tofik Tofik
Kiedy przypisujesz do jednej utw orzyłeś całkiem nową
stru k tu rę . ,
1
\
pudel pudel
struktury inną, tw orzysz s -
tym samym nową KOPIĘ b e tty ja k e
W związku z tym,
znajdujących się w niej ż e u tw orzyłeś nową
kopię danych, ja k e nie
danych. To dlatego, zos t a ł zmieniony podczas © / \
/ n •
że struktura jest TYPEM modyfikacji pól zmiennej
betty.
Becia
i
Tofik
pudel
W ARTOŚCIOW YM . pitbul v

b e tty ja k e
658 Rozdział 13.
Śmierć obiektu

Stos i sterta: więcej na temat pamięci


B ardzo ła tw o zrozum ieć, ja kie są różnice pom iędzy s tru k tu rą i ob ie kte m — przypisując
tę pierwszą za pom ocą znaku rów ności, tworzysz nową kopię, czego nie możesz zrobić
z obiektem . Co się je d n a k dzieje za kulisam i?
Za kulisami
.N E T C L R umieszcza T w oje dane w dwóch miejscach w pam ięci. Już wiesz, że na stercie Pam iętaj, podczas
wykonywania s ię Twojego
przechowywane są obiekty. Istn ie je także in n a część pam ięci zwana stosem , służąca do programu C LR aktywnie
przechowywania wszystkich zm iennych lokalnych, któ re deklarujesz w m etodach, i param etrów , zarządza pam ięcią. Zajm uje
któ re do tych m etod przekazujesz. Możesz traktow a ć stos ja ko zestaw pewnego rodzaju s i ę sto sem oraz usuwaniem
niepotrzebnych elementów.
kom ó rek, w których możesz umieszczać dane. Podczas w yw ołania m etody C L R w stawia
dodatkow e k o m ó rk i na w ierzchu stosu. Po je j zakończeniu są one usuwane.

p omimo tego, że możesz


p rzyp isa ć stru ktu rę Kod Stos
Tak będzie wyglądat
do zm iennej obiektow ej, s t o s po wykonaniu tych
Oto kod, który możesz To tu ta j przechowywane są
stru ktu ry i obiekty dwóch w ierszy kodu.
znacznie s ię różnią. zobaczyć w programie: struktury i zmienne lokalne.

Canine spot = new Canine("Burek", "mops");

Dog jake = new Dog("Tofik", "pudel");

Canine spot = new Canine("Burek", "mops");

Dog jake = new Dog("Tofik", "pudel");

Dog betty = jake;

Kiedy tw orzysz nową stru k tu rę — lub dowolną inną


zmienną typu wartościowego — do sto su dodawana
je s t nowa „komórka". J e s t ona kopią w artości typu.

Canine spot = new Canine("Burek", "mops");

Dog jake = new Dog("Tofik", "pudel");

Dog betty = jake;

SpeakThreeTimes(jake);

public void SpeakThreeTimes(Dog dog) { Kiedy w yw ołujesz


i nt i- Ć Z _____________________ _________ _ metodę, C LR wstaw ia
je j lokalną zmienną
fo r ( i = 0 ; i < 5 ; 1++) \ a wierzch s to su .
dog.Speak(); Po zakończeniu
} metody w artości s ą
} ze sto su usuwane.

jesteś tutaj ► 659


Nie opakow uj m nie

ZACZEKAJ CHWILĘ. PO CO W OGÓLE


POWINNAM TO WIEDZIEĆ? PRZECIEŻ I TAK
NIE MAM NAD TYM BEZPOŚREDNIEJ KONTROLI,
PRAWDA?

Bez w ątpienia będziesz chciała zrozum ieć, czym stru ktu ra kopiowana
przez wartość różni się od obiektu kopiowanego przez referencję.

Istn ie ją takie przypadki, w których T w o ja m etoda m usi przyjm ow ać w artości typ u wartościowego
lu b referencyjnego — na przykład m etoda, k tó ra p o tra fi pracow ać ze stru ktu rą Dog lub
obiektem Canine. Jeśli kie d yko lw ie k znajdziesz się w takie j sytuacji, możesz użyć słowa
kluczowego o b je c t:

public void WalkDogOrCanine(object getsWalked) { }


Jeśli przekażesz tej m etodzie stru ktu rę , zostanie ona o p a k o w a n a w specjalny o b ie k t zwany
opakow aniem , k tó ry p o zw o li przechowywać ją na stercie. G dy opakow anie zostanie na niej
umieszczone, ze stru k tu rą nie będziesz m óg ł w iele zrobić. A b y z nią pracować, musisz ją
M ożesz także użyć
słowa kluczowego „ is “ „rozp ako w ać” . N a szczęście wszystko to odbywa się automatycznie podczas przypisywania typu
w celu sprawdzenia, wartościowego do o b ie ktu lu b przekazyw ania go do m etody, k tó ra się o b ie ktu spodziewa.
czy obiekt j e s t
stru k tu rą lub innym
Po u tw o rze n iu o b ie ktu i przypisaniu do niego s tru k tu ry Dog
typem wartościowym ,
który z o sta ł opakowany stos i sterta będą w yglądały następująco:
i wrzucony na s t e rtę.
Dog s id = new D o g ("S id ", " h u s k y ");
W alkD ogO rC a nine(sid);
. 5
Po opakowaniu
stru ktu ry
Sid
powstają, dwie
kopie danych: husky
jedna na stosie,
•STdruga, "
opakowana, D o g s id (o p a k o w a n y )
na s te rcie. ------- Z *
M etoda WaikDogOrCanmaO
pobiera referencję do
obiektu, dlatego p ^ d W 2 Jeśli chcesz ten o b ie k t rozpakować, musisz go zrzutow ać na w łaściw y typ. Z ostanie on
je j przekazaniem do tej
metody stru ktu ra Dog rozpakow any autom atycznie. N ie możesz używ ać słow a kluczow ego as w ra z z type m
zosta ła opakowana. w a rto ś c io w y m , a zatem będziesz m usiał zastosować rzutow anie na typ Dog.
Rzutowanie j ej
z powrotem na typ t£ r To s ą stru k tu ry. Dopóki
Dog spow oduje je j Dog happy = (Dog) getsW alked; s ą opakowane, mogą sobie
odpakowanie. leżeć na stercie.

Po wykonaniu
tego w iersza kodu obj
otrzym ujesz trzecią
kop ię danych Sid
w rtowej stru ktu rze
o nazwie happy. husky
Z a jmuje ona sw o ją
własn ą komórkę na
sto s ie . D o g s id (o p a k o w a n y )

660 Rozdział 13.


Śmierć obiektu

W momencie jej wywoływania metoda poszukuje swoich argumentów na stosie


Stos odgryw a znaczącą ro lę w sposobie w ykonyw ania pro gra m ów przez C L R . Z a rzecz zupełnie
Za kulisami
oczywistą uważam y m ożliwość tw orze nia m etod wyw ołujących inne m etody, k tó re z k o le i w yw ołują
jeszcze inne. W rzeczywistości m etoda może nawet wyw oływ ać samą siebie (co nazywamy rekurencją).
W szystkie te m ożliw ości uzyskujem y dzięki w ykorzystaniu stosu.

O b o k przedstaw iliśm y k ilk a m etod p u b lic double FeedDog(Canine dogToFeed, Bowl dogBowl) {
zaczerpniętych z sym ulatora psów. do ub le eaten = E at(dogT oF eed.M ealS ize, dogB ow l);
Są one całkiem proste: FeedDog() r e tu r n eaten + .05D; / / Zawsze cos s ię w ysyp ie.
w yw ołuje E a t ( ) , a ta z k o le i w yw ołuje }
C h e ckB o w l().
p u b lic v o id E a t(d o u b le m e a lS ize , Bowl dogBowl) {
Warto, byś pam iętał o terminologii:
d o g B o w l.C a p a city -= m ea lS ize ;
parametrem nazywamy element
deklaracji metody określający C h eckB o w l(d og B ow l.C apa city);
w artość, ja k ie j ta metoda }
potrzebuje; argumen t to z kolei
faktyczna w artość lub referencja
przekazywana do metody p u b lic v o id CheckBowl(double c a p a c ity ) {
w momencie je j wy woły wania. if (c a p a c ity < 12.5D) {
s t r in g message = "Moja m iska je s t p ra w ie p u s ta !" ;
C o n s o le .W rite L in e (m e s s a g e );
}
}

A o to ja k w ygląda stos, w czasie gdy


m etoda FeedDog() w yw oła m etodę
E a t( ) , k tó ra w yw oła m etodę
C h e c k B o w l(), k tó ra z k o le i w yw oła
C o n s o le .W r ite L in e ( ) :

Metoda FeedDog() l Metoda FeedDog() musi Gdy wywołania metod Kiedy zakończy się
w y m a g a przekazania dwóch przekazać d w a argumenty się kumulują, a program realizacja metody Console.
parametrów — referencji do wywołania metody zagłębia się coraz bardziej WriteLine(), jej argumenty
Canine oraz referencji Eat(), a zatem także w wywołania, które zostaną pobrane iusunięte
Bowl.A zatem w momencie ione zostaną umieszczone wywołują kolejne metody ze stosu. Dzięki temu metoda
jej wywoływania na stosie na stosie. wywołujące metody Eat() będzie mogła być dalej
znajdą się d w a przekazane jeszcze dalsze, stos powoli wykonywana, jak gdyby
do niej argumenty. staje się coraz większy nic się nie stało. To właśnie
iwiększy. dlatego stos jest tak bardzo
użyteczny!

jesteś tutaj ► 661


Referencje na żądanie

Używaj parametrów wyjściowych,


" Z r ó b to !
by zwracać z metody więcej niż jedną wartość
Skoro ju ż m ów im y o param etrach i argum entach, to trzeba wspom nieć, że istnieje jeszcze k ilk a innych
sposobów pozwalających na przekazyw anie w artości do i z program ów ; wszystkie one wymagają dodawania
do dekla racji m etod m o d y fik a to ró w . Jednym z najczęściej spotykanych rozwiązań jest dodanie m o d y fik a to ra
o u t w celu w skazania pa ra m e tru wyjściowego. O to ja k to działa. U tw ó rz nowy p ro je k t typu
W indows F o rm s A p p lica tio n i dodaj do p lik u fo rm u la rza poniższą pustą deklarację metody.
Z w ró ć uwagę na zastosowanie m o d yfika to ró w o u t poprzedzających oba param etry:
Dzięki
p u b lic i n t R e tu rn T h re e V a lu e s(o u t do ub le h a lf , o u t i n t tw ic e )
w ykorzystaniu
{
r e tu r n 1;
param etrów
}
wyjściowych m etoda
K ie d y spróbujesz skom pilow ać ta k i pro gra m , po ja w ią się dwa błędy T h e o u t p a ra m e te r
‘h a l f m u s t be assigned a v a lu e b e fo re c o n tro l leaves th e c u r re n t m e th o d 1 (drugi m oże zwracać więcej
identyczny błąd będzie dotyczył pa ra m e tru ‘tw ice’). Zaw sze gdy używasz pa ram etrów
niż jedną wartość.
wyjściowych, przed zakończeniem m etody musisz ustawić ich w artości — podo bn ie ja k
musisz użyć in s tru k c ji r e t u r n , je śli deklaracja m etody wskazuje, że zwraca ona jakąś
wartość. Poniżej przedstaw iliśm y ko m p le tn y ko d metody.
Szybkie przypomnienie:
Random random = new Random();
p u b lic i n t R e tu rn T h re e V a lu e s(o u t do ub le h a lf , o u t i n t tw ic e ) { Kiedy w programach typu
i n t v a lu e = random .N ext(lO O O ); jj\ ^ W indows Forms jest w yw oływ ana
h a lf = ((d o u b le )v a lu e ) / 2 ; I metoda Console.WriteLine(),
tw ic e = v a lu e * 2 ; ^ d zakończeniem te j metody n a h ży u s *awić jej wyniki są wyświetlane w oknie
r e tu r n v a lu e ; wartoś c i obu jej param etrów w yjściow ych> Output IDE (VIEW/Output).
} w aprzZiw
w num razie
przeciwnym takie jejejj kodu nie uda s ię s k°m p 'lować.

T eraz, kie dy ju ż podałeś w artości dwóch p a ram e tró w wyjściowych, uda się skom pilow ać program .
D o d a j przycisk z następującą pro ced urą obsługi:

p r iv a t e v o id b u t to n l_ C lic k ( o b je c t se n d e r, EventArgs e
i n t a;
Czy zw róciłeś uwagę na to, że nie m usia łeś inicjalizować
double b;
b i c ? Nie trzeba inicjalizować zmiennych przed ich
i n t c; zastosowaniem jako parametrów wyjściow ych.
a = R e tu rn T h re e V a lu e s(b , c ):
C o n s o le .W rite L in e ("v a lu e { 0 } , h a lf = { ! } , do ub le = { 2 } " , b , c)
}
W tym projekcie
O rany! P ojaw iły się nowe błędy: A rg u m e n t 1 m u s t be passed w ith th e o u t ke yw o rd 2. Z a każdym używamy aplikacji
razem, gdy wyw ołujesz m etodę posiadającą p a ra m e try wyjściowe, musisz poprzedzić argum enty typu W indows
słowem kluczow ym o u t. O to ja k należy to zrobić: Forms, gdyż dzięki
a = R e tu rn T h re e V a lu e s(o u t b , o u t c ) ; temu ła tw o możesz
klikać przyciski
T eraz ju ż nie będziesz m ia ł p ro b le m ó w ze zbudowaniem program u. K ie d y go uruchom isz, m etoda i obserwować
R e tu rn T h re e V a lu e s ( ) ustawi i zw róci trzy wartości: zm ienna a stanie się w artością w ynikow ą rezultaty
m etody, b zostanie zw rócona ja ko p a ram e tr h a lf , a c ja ko pa ra m e tr tw ic e . wyświetlane
w oknie Output.

1 Przed opuszczeniem bieżącej metody należy przypisać wartość do parametru wyjściowego ‘half’ — przyp. tłum.
2 Pierwszy parametr musi być przekazany z zastosowaniem słowa kluczowego out — przyp. tłum.

662 Rozdział 13.


Śmierć obiektu

Przekazuj referencje, używając modyfikatora ref


W ie lo k ro tn ie w tej książce widziałeś, że za każdym razem, gdy przekazujesz do m etody wartość
i n t , d o u b le , s t r u c t bądź ja kie g o ko lw ie k innego typ u wartościowego, przekazywana jest je j kopia.
Rozw iązanie to m a swoją nazwę — jest to p rze ka zyw a n ie prze z w a rto ś ć ; oznacza to, że wartość
argum entu jest w całości kopiowana.
Jednak istnieje także in ny sposób przekazyw ania argum entów do m etod. Jest on określany jako
prze ka zyw a n ie prze z re fe re n cję . Możesz użyć słowa kluczowego r e f , by p o zw o lić m etodzie operować
bezpośrednio na przekazanych do niej argum entach. Podobnie ja k w przypadku param etrów
wyjściowych, także i tu musisz użyć słowa kluczowego r e f w dekla racji m etody oraz w je j wyw ołaniu.
N ie m a znaczenia, czy jest przekazywana dana typ u wartościowego, czy referencja — m etoda będzie
m ieć bezpośredni dostęp do każdej zm iennej przekazanej do niej w param etrze r e f . O kazuje s ię ,
że param etry
Zobacz, ja k to działa — dodaj do swojego pro g ra m u następującą m etodę: w yjściow e s ą
bardzo podobne
p u b lic v o id M o d ify A n In tA n d B u tto n (re f i n t v a lu e , r e f B u tto n b u tto n ) { do argumentów re f
z tą różnicą, że me
in t i = v a lu e ;
Kiedy ta metoda określa w artości trzeba określa ć
i *= 5; param etrów value i button, ich w artości przed
v a lu e = i - 3; to w rzeczy w isto ści określa ona ich przekazaniem
b u tto n = b u tto n l w artości zmiennych q i b w metodzie w wywołaniu metody ,
. button2 _ C lic k (), która j ą wywołała. trzeba to natom iast
} zrobić przed je j
T eraz dodaj przycisk i pro ced urę obsługi zdarzenia, k tó ra w yw oła powyższą m etodę: zakończeniem.

p r iv a t e v o id b u tto n 2 _ C lic k ( o b je c t sen de r, EventArgs e) To wywołanie w yśw ie tli komunikat „q =


i n t q = 100; b T ext = button1", gdyż ivytwo^mte m ^ y
B utto n b = b u tto n 3 ; M odifyAnIntAndButton w r z a c z y m ^ c i
zmodyfikowało wartoś c i zmiennych q i b.
M o d ify A n In tA n d B u tto n (re f q , r e f b ) ;
C o n s o le .W rite L in e ("q = { 0 } , b .T e x t = { 1 } ", q, b .T e x t)
}

K ie d y m etoda b u t t o n 2 _ C lic k ( ) w yw ołuje m etodę M o d ify A n In tA n d B u tto n ( ), zm ienne q i b są przekazywane do niej


przez referencję. M o d ify A n In tA n d B u tto n () operuje na nich ja k na zwyczajnych zm iennych. Jednak poniew aż zostały
one przekazane w ten sposób, m etoda w rzeczywistości aktu alizuje q i b, a nie ich kopie. D lateg o też po je j zakończeniu
w artości tych zm iennych będą zm odyfikow ane.
U ru ch o m program i przetestuj go przy użyciu debuggera; dodaj zm ienne q i b do okna W atch, by zobaczyć,
ja k się zm ieniają ich wartości.

Metoda TryParse() wbudowanych typów używa parametrów wyjściowych


Istnieje pewien doskonały przykład zastosowania param etrów wyjściowych dostępny bezpośredniow niektórych typach
wartościowych. Bardzo często będziesz chciał p rzekonwertować łaticudi znaków ° po staci „35.67 na ypu, i
double. Istnieje m etoda, k tó ra właśnie t o robi: double.Parse(„35.67") z wróci wartość double 35 67. Jednak
double.Parse(„xyz") zgłosi w yjątek Form atException. Czasami d o l^ r n e takiego tm M ą z a ra b?dziesz
a|e m oże się także zdarzyć, że będziesz chciał sprawdzić, czy istnieje m o ż liwość konwersji łańcucha znaków na liczbę.
I właśnie w tych sytuacjach przydaje się m etoda TryParse() — w y w o z ie double.T fy Parse(„ x y z , o u t d) zw r óci wartość
false i zmiennej d przypisze wartość 0 , natom iast double.TryParse(„35.67" , out d) zw róci tru e , a zmiennel d przypisze

C r m m ię t a s z , jak w rozdziale 9. używaliśmy instrukcji switch do konwersji łańcucha „ SH es" na warto ś ć Su it.Spades?
Có ż ... istnieją statyczne m e to d y Enum.Parse() oraz Enum.TryParse(), k t ó re działają analogicznie, lecz operują na
wartościach ty p ó w wyliczeniowych.

+ jesteś tutaj ► 663


A rgum enty opcjonalne

Używaj parametrów opcjonalnych, by określać wartości domyślne -^r-


W w ie lu przypadkach T w o je m etody będą w ie lo k ro tn ie wywoływane z ta k im i samymi argum entam i, je d n a k muszą
m ieć param etry, poniew aż przekazywane w artości mogą się zm ienić. Bardzo w ygodna byłaby m ożliwość określenia
w artości dom yślnej, ta k by przekazanie argum entu w w yw oła niu m etody było konieczne w yłącznie w przypadku,
gdy jego w artość jest inna.

W łaśnie taką m ożliw ość zapew niają pa ram e try opcjonalne. Tworzysz je, umieszczając za nazwą pa ra m e tru w deklaracji
m etody znak rów ności i w artość domyślną. Liczba param e tró w opcjonalnych nie jest ograniczona, je d n a k wszystkie one
muszą się znaleźć za pa ram e tra m i wymaganymi.

Poniżej przedstaw iliśm y p rzykład m etody używającej p a ram e tró w opcjonalnych do sprawdzenia, czy ktoś m a podwyższoną
bądź ob niżoną tem peraturę.

void CheckTemperature(double temperature, double tooHigh = 3 7 .5 , double tooLow = 3 5 .8 )


{ > T
i f (temperature < tooHigh && temperature > tooLow) Param etry opcjonalne mają wartości
Console.WriteLine("Czuję się św ietnie!"); c h m y ^ , które s ą określane
el se w deklaracji metody.

Console.WriteLine("Mój Boże - - lep iej p o ślijc ie po doktora!");


}
Powyższa m etoda m a dwa pa ram e try opcjonalne: tooHigh o w artości domyślnej 37.5 oraz tooLow o w artości
dom yślnej 35.8. Jeśli w je j w yw ołaniu zostanie przekazany jeden argum ent, to oba p a ram e try — tooHigh oraz tooLow
— przyjm ą w artości domyślne. Jeśli w w yw oła niu zostaną podane dwa argum enty, to d ru gi z nich zostanie przypisany
p a ram e tro w i tooHigh , natom iast tooLow p rzyjm ie w artość domyślną. W p rzypadku gdy zostaną podane trzy argum enty,
to ich w artości zostaną zapisane we wszystkich trzech param etrach.

Istn ie je także dodatkow a m ożliwość. Jeśli chcesz wykorzystać ty lk o n ie któ re (lecz nie wszystkie) z w artości domyślnych,
to możesz skorzystać z n a zw a n ych a rg u m e n tó w , by okre ślić w artości w ybranych param etrów . W tym celu wystarczy podać
nazwę takiego pa ram e tru , a po niej um ieścić d w ukro pek i przekazywaną wartość. Jeśli w w yw oła niu chcesz podać więcej
niż jeden nazwany argum ent, to nie zapom nij od dzielić ich od siebie przecinkam i.

D o d a j do swojego fo rm u la rza m etodę CheckTemperature(), a następnie przycisk w raz z poniższą m etodą obsługi.
Przetestuj pro gra m w debuggerze i upew nij się, że do kła dnie rozum iesz, ja k on działa.

private void button3_Click(object sender, EventArgs e) U ŻyW3|'


{ p a r a m e tr ó w
/ / T e wartości wystarczą dla przeciętnych osobników. o p c j o n a ln y c h
CheckTemperature(38.5); i
o ra z nazw anych
/ / Temperatura ciaTa psa powinna wysosić pomiędzy 38,05 a 39,16 stopni a r g u m e n tó w ,
C e ls ju s z a .
CheckTemperature(38.5, 39.16, 38.5); ie śli Ch CeSZ
u ic c j ł ,
b y T w o je m e t o d y
I I Temperatura Boba zawsze je s t nieco niższa, a zatem ustawimy tooLow m ia ły w a r t o ś c i
na 35.27. '
CheckTemperature(35.66, tooLow: 35.27); d ° m y ś |n e -

664 Rozdział 13.


Śmierć obiektu

Jeśli musisz używać wartości pustych, stosuj typy, które je akceptują


W rozdziale Tl.,
W wielu projektach przedstawionych we wcześniejszej części książki używałeś n u ll , by zaznaczyć, że jakaś w programie
do zarządzania
zmienna nie ma wartości. To typowe: n u ll można używać, by zaznaczyć, że zmienna, pole lub właściwość są wymówkami,
puste, można także sprawdzać, czy jakaś zmienna lub składowa jest równa n u ll , co będzie oznaczać, że nie u żyłeś wartośc i
D ateTim eM inV alue ,
ma ona wartości. Jednak strukturom (oraz liczbom całkowitym, wartościom logicznym, wartościom typów
by zaznaczyć,
wyliczeniowych oraz innych typów wartościowych) nie można przypisywać n u ll . Umieszczenie w kodzie że data nie
następujących instrukcji: została określona.
bool myBool = n u ll; U życie typu
Nullable<DateTime >
DateTime myDate = n u ll;
poprawiłoby
spowoduje zgłoszenie błędu podczas próby kompilacji programu. p rzejrzysto ść
z arówno kodu, ja k
Załóżmy, że nasz program musi operować na wartościach reprezentujących daty i czas. W takim przypadku i serializow anych
zazwyczaj zastosowalibyśmy zmienną typu DateTime. Co jednak możemy zrobić, jeśli czasami nasza zmienna plików X M L .
może nie mieć wartości? To właśnie w takich sytuacjach przydają się typy akceptujące wartości puste. Wystarczy
dodać znak zapytania ( ? ) tuż za typem zmiennej, a stanie się on typem akceptującym wartości puste
(ang. nullable type) — dzięki temu będziesz mógł przypisać zmiennej wartość n u ll .
int? myNullableInt = null; Nullable<DateTime>
DateTime? myNullableDate = nuli; Value: DateTime
Każdy typ akceptujący wartości puste posiada właściwość V alue , która pozwala pobrać lub ustawić wartość. HasValue: bool
DateTime? będzie mieć właściwość Value typu DateTime , natomiast in t ? — typu i n t . Oba te typy będą także
posiadać właściwość HasValue przyjmującą wartość t ru e , jeśli Value jest różna od n u ll .
GetValueOrDefault():
Zawsze istnieje możliwość przekonwertowania zmiennej typu wartościowego na typ akceptujący wartości puste:
DateTime
DateTime myDate = DateTime.Now;
DateTime? myNullableDate = myDate;
Jednak przypisanie danej typu akceptującego wartości puste z powrotem do zmiennej typu wartościowego
wymaga zastosowania odpowiedniego rzutowania:
myDate = (DateTime)myNullableDate;
Nullable<T> je s t ;
stru k tu rą pozwalającą
na przechowywanie
Jednak uzyskujesz także tę wygodną właściwość Value — również ona zwraca wartość: danej typu
wartościowego LU B
myDate = myNullableDate.Value;
w artości null. Oto
Jeśli właściwość HasValue ma wartość f a ls e , to próba użycia właściwości Value spowoduje zgłoszenie wyjątku niektóre w ła ściw o ści
i metody stru ktu ry
In valid O p e ra tio n E x ce p tio n . To samo stanie się podczas próby rzutowania (gdyż jest ono tożsame z użyciem Nullable<DateTi me>.
właściwości Val ue).

Pytajnik T ? je st tym samym co NuIIabIe < T >


Kiedy dodasz znak zapytania do dowolnego typ u wartościowego (np int ? lub de rim aW , ko m pilator zamieni wyrażenie na stru ktu rę
Nullable<T> (np. Nullable<int> lub Nullable<decimal>). M ożesz t o spraw ce: sam: ¿odą do sw° jeg° programu ^ < * 1 3 t ,
Nullable<DateTime>, a następnie dodaj ją do okna W atch i umieść w t y m wierszu pułapkę. Przekonasz się, z e w ° knie W f c! hy ć
w yśw ietliło System.DateTime?. Jest t o przykład ta k zwanej nazwy zastępczej- i t o nie pierwszy jaki miałeś okazję zoba z y__
l/m ieść wskaźnil< m yszy nad type m in t w dowolnej deklaracji zmiennej - jest on zastępowany stru ktu rą o nazw.e S ystem .ln «2:

struct SystemJnt32
Składowymi tej s tru k tu ry są int.Parse() oraz in U T r y P ^ O . Represents a 32-bit signed integet

Poświęć chwilę, by w analogiczny sposób sprawdzić wszystkie ty p y przedstawione na początku rozdziału 4 . Przekonasz się,
że wszystkie one, z w yjątkiem typ u string, są nazwami zast ępczy mi stru ktu r- Ty p string jest nazwą zastępczą klasy
System.String (czyli jest t o ty p referencyjny, a nie wartościowy ) .

665
Zakosztuj solidności
f Z
rób to!
Typy akceptujące wartości puste poprawiają odporność programów
U żytko w n icy w yczyniają przeróżne zwariow ane rzeczy. Sądzisz, że wiesz, ja k będą korzystać Kiedy będziesz dodawał
z pisanego przez C iebie pro gra m u, lecz potem ktoś k lik a przyciski w nieo dpo w iedn ie j kolejności, metodę RobustG uy.
ToStringO, zwróć uwagę
w pisuje 256 spacji w p o lu tekstow ym lu b używa M enedżera zadań, by zam knąć pro gra m w trakcie na to, co w yśw ietli
zapisywania danych do p lik u , i nagle okazuje się, że aplikacja zgłasza dziesiątki różnych błędów. okienko In te lliS e n se
Czy pam iętasz, ja k w rozdziale 12. opisywaliśm y program y, któ re p o tra fią radzić sobie z błędnie podczas wpisywania
8 irthday.Value. Ponieważ
zapisanymi, nieoczekiw anym i lu b dziw nym i danym i wejściowym i? N azw aliśm y je program am i wtaściw o ść Value j e s t
odpornymi . C ó ż... je śli chodzi o przyjm ow anie in fo rm a c ji podawanych przez użytkow ników , ty p u DateTim e, u jrzysz
w n im standardowe
to typy akceptujące w artości puste mogą znacznie po pra w ić odporność program ów . Przekonaj się
składowe tego typu.
o tym sam — u tw ó rz nowy projekt aplikacji konsolowej i dodaj do niej przedstaw ioną poniżej
klasę RobustGuy. łl
Użyj metody ToLongDateString(),
cla ss RobustGuy { by w y św ietlić datę w po sta ci
public DateTime? Birthday { get; p riv a te s e t; ) zrozum iałej dla człowieka.
public in t? Height { get; p riv a te s e t; }

public RobustGuy(string birth d ay, s trin g height) {


Z a sto su j typ DateTime t empDate ;
D ateTim i (DateTime.T ryP arse (b irthd ay out tempDate))
i metodę Birthday = tempDate;
Tm Pa°rse(), , el Se Birthday = n u ll;
by spróbować J
przek(mwertować. nt tempInt;
informaCJ ę (in t.T ry P a rse (h e ig h t, out tempInt))
podaną prZez Height = tempInt;
użytkownika ei se
na w a rtość. Height = n u ll;
}

public override s trin g ToString () {


H ś li użytkownik s trin g d e scrip tio n ;
w p isa ł ja k ie ś ^ i f (Birthd ay != n u ll)
n i eprawidłowe d escription ="UrodziTem s ię dnia 11+ Birthday.Value.ToLongDateStringO
dane, to zmienne else
te nie będą miały
t
„iały
a Zatem
'’chi'w łaśc iw ość
(Height != n u ll)
.
d escription = "Nie znam daty swoich urodzin";

d escription += " , mam " + Height + " centymetrów w zrostu."


X
S p róbuj poeksperym entować z innymi
metodami klasy DateTim e, których
else nazwy zaczynają s ię na „To“ ,
HasValue d escription += " , nie wiem, il e mam w zro stu ."; i przekonaj s ię , ja k i będzie ich wpływ
zwróci fa lse . return d e scrip tio n ; na wyniki generowane przez program.
}
}
A o to m etoda M a in () naszego program u. P obiera ona dane od użytkow nika, korzystając z m etody C o n s o le .R e a d L in e ():

s t a t ic void M ain (strin g [] args) {


Console.W rite("Podaj datę urodzenia: " ) ;
s trin g birthday = Console.ReadLine();
Console.W rite("Podaj wzrost w centymetrach: " ) ; Obserwuj, co się stanie, gdy będziesz
s trin g height = Console.ReadLine(); w pisyw ał różne wartości jako datę
RobustGuy guy = new RobustGuy(birthday, h eig h t);
C o n so le.W riteLin e (g u y.T o Strin g ()); urodzenia. Metoda DateTime.
Console.ReadKey(); T ry P a rs e () potrafi zrozumieć wiele
} form atów , jeśli jednak wpiszesz
M etoda C o n s o lj.R ja d L in j() pozwala użytkownikowi
podawać dane w oknie w iersza poleceń. Kiedy naciśnie datę, której nie będzie ona w stanie
on klaw isz Enter, metoda zw róci w pisane informacje przetworzyć, właściwość B irth d a y klasy
w posta ci łańcucha znaków.
RobustGuy będzie mieć pustą wartość.

666 Rozdział 13.


Śmierć obiektu
Zagadkowy basen
Twoim zadaniem jest pobranie fragmentów
kodu z basenu i wstawienie ich p u b lic Table
w puste miejsca w kodzie. Możesz p u b lic s t r in g s t a ir s ;
użyć tego samego fragmentu więcej p u b lic Hinge f lo o r ;
niż raz i nie musisz wykorzystać ich
p u b lic v o id S et(H inge b) {
wszystkich. Celem jest napisanie
f lo o r = b;
kodu, który po u tw o rze n iu nowej
instancji klasy F a u ce t wypisze }
w oknie konsoli podany komunikat. p u b lic v o id Lam p(object o i l ) {
if ( o i l _________ in t )
_____________ .b u lb = ( i n t ) o i l ;
p u b lic c la s s Faucet {
e ls e i f ( o i l ____________ s t r in g )
p u b lic F auce t() {
s t a ir s = ( s t r i n g ) o i l ;
Table wine = new T a b le () ;
e ls e i f ( o i l ____________ Hinge) |
Hinge book = new H in g e ();
v in e = o i l
w in e .S e t(b o o k );
C o n s o le .W rite L in e (v in e .T a b le ()
b o o k .S e t(w in e );
+ " " + _____________ .b u lb + " " + s ta ir s ) ;
w ine.L am p(lO );
book.garden.Lam p("z powrotem z a " );
b o o k.b u lb *= 2;
w in e .L a m p ("m in u t");
w ine.Lam p(book);
p u b lic Hinge
I p u b lic i n t b u lb ;
I p u b lic Table garden;
p u b lic v o id S e t(T a b le a) {
garden = a;
Okno konsoli po utw orzeniu
}
obiektu F a u c e t :
p u b lic s t r in g T a b le () {

z powrotem za 20 minut re tu r n ____________ . s t a i r s ;


}
To jest cel-
Trzeba uzyskać Punkty dodatkowe: Zakreśl miejsca,
taki wfaśnie rezultat.
w których zachodzi opakowywanie.
Przypominamy:
każdy fragment
kodu z basenu
public
może zostać użyty
private if
więcej niż raz.
class or garden
new is flo o r
Brush struct
abstract on W in d o w
Lamp ++ string
interface as Door
bulb in t
oop Hinge
Table flo a t
stairs y single
double

*- Odpowiedź na stronie 676. jesteś tutaj ► 667


S truktury są bezpieczne

P : D o brze, w róćm y na ch w ilę. P : W ie m , w ja k i sposób mogę pobrać P : Skąd mam w ie d zie ć, kied y
Dlaczego m iałbym się interesow ać św ie ż ą kopię s tr u k tu ry podczas u ż y w a ć s tru k tu ry , a k ie d y k la sy?
stosem? p rzy p isyw a n ia jednej zm iennej do
dru g iej. Dlaczego je st to dla mnie O : W większości przypadków programiści
O : Ponieważ zrozumienie różnicy pomiędzy istotne? używają klas. Struktury mają spore
stosem a stertą pozwala Ci lepiej używać ograniczenia, które naprawdę utrudniają
typów wartościowych i referencyjnych. O : Jedną z operacji, w których może się posługiwanie się nimi w dużych projektach.
Łatwo zapomnieć, że struktury i obiekty to naprawdę przydać, jest hermetyzacja Nie umożliwiają dziedziczenia i nie oferują
działają inaczej — znaku równości można implementacji. Popatrz na pole w klasie, abstrakcji i jedynie ograniczony polimorfizm,
użyć w stosunku do obu tych typów, która przechowuje swoje położenie: a wiesz, jak ważne są te rzeczy dla łatwego
wyglądają także podobnie. Posiadanie p r iv a t e P o in t lo c a t io n ; tworzenia programów.
pewnej wiedzy na temat wewnętrznego Struktury są naprawdę przydatne wtedy,
p u b lic P o in t L o c a tio n {
traktowania ich przez .NET i CLR ułatwia gdy masz mały, ograniczony typ danych,
get { re tu rn lo c a t io n ; }
zrozumienie, dlaczego referencje i typy z którym musisz często pracować. Prostokąty
wartościowe działają inaczej. }
i punkty są tutaj doskonałym przykładem —
Gdyby P o in t była klasą, hermetyzacja nie
P : A opakow yw anie? Dlaczego jest byłaby właściwa. Nie miałoby znaczenia to,
nie mają wielkich możliwości, ale będziesz
ich używał bez przerwy. Struktury okazują
dla mnie w ażne? że lo c a tio n jest private, ponieważ utworzyłeś
się być względnie małe i mają ograniczony
właściwość tylko do odczytu, która zwraca
O : Ponieważ powinieneś rozumieć, referencję. W takim przypadku każdy obiekt
zasięg. Jeśli posiadasz niewielki zbiór różnych
danych, który chcesz przechowywać w polu
kiedy różne dane są umieszczane na mógłby uzyskać dostęp do tej składowej.
klasy lub przekazywać do metody w postaci
stosie, i musisz wiedzieć, kiedy są one
Na szczęście P o in t jest strukturą. Oznacza parametru, jest on dobrym kandydatem
kopiowane w jedną bądź w drugą stronę.
to, że publiczna właściwość Lo c a tio n na strukturę. Jeśli jednak sposób używania
Opakowywanie wymaga dodatkowej
zwraca świeżą kopię punktu. Obiekt, który struktury sprawia, że w większości
ilości pamięci i czasu. Jeżeli robisz to tylko
tej kopii używa, może z nią zrobić cokolwiek przypadków będzie ona opakowywana, to
kilka (lub kilkaset) razy w programie, nie
— żadne operacje nie spowodują zmian najprawdopodobniej lepszym rozwiązaniem
odczujesz różnicy. Przypuśćmy jednak,
w prywatnym polu lo c a t io n . będzie stworzenie klasy
że masz aplikację, w której takie operacje
mają miejsce wielokrotnie, miliony razy na
sekundę. To nie jest wcale takie nierealne.
Na końcu książki napiszesz grę, która może
f
Z a jrz y j ponownie do proje k tu
Struktura m oże
być naprawdę
wykonywać wiele obliczeń na sekundę. Gdy odbijających s ię e ty k ie t
zauważysz, że Twój program pobiera coraz przedstawionego w rozdziale 4 .
więcej pamięci i działa coraz wolniej, istnieje
Z a kulisam i używaliśmy w nim wartościowa, jeżeli
punktów i położenia, a to oznacza,
możliwość zaradzenia tego typu problemom. że nasz kod korzystał z wartości chcesz dobrze ukryć
Możesz ulepszyć aplikację poprzez unikanie przechowywanych w strukturach
opakowywania w tych fragmentach, które (naw et je ś li nie deklarowaliśmy implementację
ich jaw n ie).
są często powtarzane. klasy, ponieważ
Zaostrz ołówek właściwość ty lk o
do o d c z y tu , k tó ra
Ta metoda ma za zadanie usunąć obiekt C lo n e , ale nie działa.
Dlaczego?
zwraca s tru k tu rę ,
p r iv a t e v o id S e tC lo n e T o N u ll(C lo n e c lo n e ) { zawsze tw o rz y
clo n e = n u l l ; jej świeżą kopię.
}
S zy bki kwiz, geniuszu!
° d p owiedź na stron ie 670.

668 Rozdział 13.


Śmierć obiektu

„Kapitan" W spaniały... nie tak bardzo


Podczas całej tej dyskusji na tem at opakowywania pow inieneś się domyślić, To jedna z z a let
co się stało z osłabionym , przem ęczonym K ap itan em W spaniałym . stru k tu r (oraz innych
W rzeczywistości był on po pro stu opakowaną strukturą : typów w artościowych)
— m ożesz bez
problemu wykonywać
/ ich kopie.
/
i
\
s tru c t kontra

Struktury nie mogą dziedziczyć po klasach. Nie możesz utworzyć świeżej


N ic dziwnego, że superm oce kapitana wydawały kopii obiektu.
się słabe! N ie odziedziczył on żadnych fun kcji. K ie d y przypisujesz jeden o b ie k t do drugiego,
kopiujesz referencję do tej sa m ej zm iennej.

^ Struktury są kopiowane przez wartość. W przypadku obiektu możesz


T o je dn a z ich najbardziej przydatnych cech, użyć słowa kluczowego as .
k tó ra jest najbardziej użyteczna w kontekście O b ie kty u m o żliw ia ją zastosowanie
herm etyzacji. p o lim o rfiz m u poprzez zdolność przejęcia
Ważna uwaga: Można używać słowa kluczowego J s " , by _ fu n k c ji ob ie ktu , po któ rym dziedziczą.
spraw dzać, czy stru ktu ra implement u je interfej s ; j est to
jeden z aspektów polimorfizmu, który stru ktu ry obsługują.

669
Rozszerz to

Metody rozszerzające zwiększają m f Zj ’p m Z T 'S a

funkcjonalność IS T N IE JĄ C Y C H klas ^ Ua sy ' po mrych nie można '

Czasami musisz rozszerzyć klasę, po k tó re j nie możesz dziedziczyć, na przykład klasę s e a le d (spora
część klas .N E T jest s e a le d , co u n ie m o żliw ia dziedziczenie po nich). C # udostępnia C i do tego potężne
narzędzie: metody rozszerzające . G dy dodajesz do p ro je k tu klasę z ta k im i m etodam i, dodajesz te metody
do klas , któ re ju ż istnieją. M usisz ty lk o utw orzyć statyczną klasę i dodać statyczną m etodę, k tó ra przyjm uje
instancję klasy ja ko pierw szy p a ram e tr przy użyciu słowa kluczowego t h is .
Powiedzm y, że posiadasz klasę O rdinaryH um an oznaczoną m o d yfika to re m s e a le d (co, ja k pamiętasz,
u n ie m o żliw ia dziedziczenie p ° n ie j): Klasa OrdinaryHuman j e s t sealed, _

s ę ą lę d _ c la s s OrdinaryHuman { k w ^ g d y C S j" d T '


p r iv a t e i n t age; niej dodać metodę?
i n t w e ig h t;
U żyw asz metody
p u b lic O rd in a ryH u m a n (in t w e ig h t) { ^ óra
+. . • •1 . . . określa p ierw szy param etr
t h is . w e ig h t = w e ig h t;
}

p u b lic v o id GoToWork() { // kod d la GoToWork } ^


p u b lic v o id P a y B ills ( ) { // kod d la P a y B ills } ^ zw iązku z tym, że chcemu
}
pierw szy param etr w pisujem u
Klasa SuperSoldierSerum dodaje metodę rozszerzającą do OrdinaryHuman: t h is O rd in a ry H u m a n

s ta tic c la s s S uperS oldierS erum { ------- - ^


p u b lic s t a t i c s t r in g B re a k W a lls (t h is OrdinaryHuman h , do ub le w a llD e n s ity ) {
re tu r n ("P rze d zie ra m s ię prze z ś c ia n ę o g ę s to ś c i " + w a llD e n s ity + " . " ) ;

} } M etody rozs ze rzające s ą zaw sze statyczn e


} i m uszą być um ieszczone w kla sach s t a tycznych. Kiedy formularz tworzy instancję
klasy OrdinaryHuman, może
Z araz po dodaniu klasy S u p e rS o ld ie rS e ru m do p ro je k tu klasa O rdinaryH um an wywo!y wać metodę
B reakWalls ( ) — o ile ma dostęp
otrzym uje m etodę B re a k W a lls () . Teraz fo rm u la rz może je j użyć: do klasy S u p e rSoldie r S e rum.
s ta tic v o id M a in ( s t r in g [ ] a rg s ) {
OrdinaryHuman ste v e = new0rd in aryH u m an(1 85 ); Nie wahaj s ię — wy p róbuj nową metodę!
C o n s o le .W rite L in e (s te v e .B re a k W a lls (8 9 .2 )); UJ i d j S ^ M ainO .
} Korzystając z debuggera, wejdź do metody
ę Zaostrz Ołówek BreakWallsO i sprawdź, co s/ę w n/ej dzieje.

Rozwiązanie Ta metoda ma za zadanie usunąć obiekt C lone , ale nie działa. Dlaczego?
p r iv a t e v o id S e tC lo n e T o N u ll(C lo n e c lo n e ) {
P a r a m e tr clone znajduje ^
c lo n e = n u l l ;
}
M etoda ta ustawia swój własny parametr na null, ale jest on ty lk o referencją Clone.

.To. tak,, jakbyśmy, przyklejali, do. obiektu, e tykie tę i .zaraz. potem , ją .usuwali:.............................................

670 Rozdział 13.


Śmierć obiektu

■ Nie .istnieją.
głupie pytania

P: : Powiedz mi jeszcze raz, dlaczego nie mogę nowych metod O : Jeśli możesz rozszerzyć klasę, to zapewne właśnie tak
dodać bezpośrednio do kodu klasy i muszę używać metod postąpisz — celem metod rozszerzających nie jest zastępowanie
rozszerzających. dziedziczenia, Bardzo się jednak przydają w przypadkach, gdy nie

O : Możesz to zrobić, a nawet powinieneś, jeśli masz na myśli


możesz z niego skorzystać, Używając metod rozszerzających, możesz
zmienić zachowanie całej grupy obiektów, a nawet rozbudować
tylko dodanie metody, Metody rozszerzające powinny być używane funkcjonalność niektórych z najbardziej podstawowych klas
dość oszczędnie i tylko w przypadkach, gdy z jakiegoś powodu NET Framework,
nie możesz zmienić zawartości klasy (na przykład jest ona częścią
Rozszerzanie klasy poprzez dziedziczenie daje jej nowe funkcje,
,NET Framework lub pochodzi od innego dostawcy), Ukazują one
ale w celu skorzystania z tych dobrodziejstw wymagane jest użycie
pełnię swoich możliwości, rozszerzając coś, do czego normalnie
nowej klasy potomnej,
nie masz dostępu, na przykład obiekty dostarczane za darmo
w ramach platformy NET lub innych bibliotek,
P: : Czy moje metody rozszerzające wpływają na wszystkie

P: : Po co w ogóle miałbym używać metod rozszerzających?


instancje klasy, czy tylko na określone jej wystąpienia?

Czy nie można po prostu rozszerzyć klasy przez dziedziczenie?


O : Będą miały wpływ na wszystkie instancje rozszerzanej klasy
Zaraz po utworzeniu metody rozszerzającej pojawi się ona w IDE
wśród innych, zwykłych metod danej klasy.

AHA, ZAŁAPAŁEM!
Je s z c z e jedna rzecz dotycząca
METOD ROZSZERZAJĄCYCH metod rozszerzających, o jakiej
UŻYWASZ WTEDY, GDY CHCESZ ZWIĘKSZYĆ warto pam iętać: tworząc taką
metodę, nie u zysk u jesz dosfyptu
FUNKCJONALNOŚĆ JEDNEJ Z WBUDOWANYCH do wewnętrznych składowych klasy
KLAS .NET FRAMEWORK, PRAWDA? — wciąż j e s t ona traktowana jako
kod spoza niej!

Zgadza się! Istnieją pewne klasy, po których nie możesz dziedziczyć.

O tw ó rz dow olny p ro je k t i wpisz coś takiego:


c la s s x : s t r in g { }

Spróbuj teraz skom pilow ać swój ko d — I D E w yśw ietli błąd. Spowodow any jest on
tym , że n ie któ re klasy .N E T są s e a le d , co oznacza, że nie m ożna po nich dziedziczyć.
(M ożesz ta k oznaczyć także swoje klasy! Po pro stu dodaj słowo kluczowe s e a le d zaraz
po m od yfikatorze dostępu p u b lic . Ż adn a in n a klasa nie będzie m ogła dziedziczyć
po takie j klasie). M e to d y rozszerzające pozw alają C i rozbudow ać klasę nawet m im o braku
m ożliw ości dziedziczenia.

A le to nie wszystko, co możesz zrob ić z m etod am i rozszerzającymi. O prócz rozszerzania


klas możesz także rozszerzać in te rfe js y . M usisz tylko w m iejscu nazwy klasy, zaraz
po słowie kluczow ym t h i s pierwszego pa ra m e tru m etody, użyć nazwy interfejsu.
Połączenie interfejsów
i metod rozszerzających G dy to zrobisz, m etoda rozszerzająca zostanie dodana do ka żd e j klasy, k tó r a go
je s t niezwykle przydatne, im p le m e n tu je . W następnym rozdziale poznasz L IN Q — w a rto , żebyś poznając
gdyż pozwala dodać nowe
zachowania do w szystkich tę technologię, pa m ię ta ł, że została ona w całości utworzona przy użyciu metod
klas implementujących rozszerzających, a ko n kre tn ie rozszerzających in te rfe js IE nu m erab le< T >.
dany in terfejs.

jesteś tutaj ► 671


Lepszy, szybszy, silniejszy

Rozszerzanie podstawowego typu: string


Nieczęsto będziesz znajdował się w takiej sytuacji, że będziesz m usiał zm ienić zachowanie jednego
z najważniejszych typów takich ja k s t r in g . Korzystając z m etod rozszerzających, możesz to jednak
zrobić! U tw ó rz nowy p ro je k t i dodaj do niego p lik o nazwie H u m anExtension s.cs. Rodzaj p ro je ktu nie
ma znaczenia — do poznawania działania m etod rozszerzających i ta k będziesz używał m ożliwości ID E .

[n WSTAW METODY ROZSZERZAJĄCE DO ODDZIELNEJ PRZESTRZENI NAZW.


D o b rym zwyczajem jest przechowywanie wszystkich rozszerzeń w innej przestrzeni nazw niż ta,
w któ re j znajduje się reszta T w ojego kodu. W ten sposób nie będziesz m ia ł p ro b le m u podczas używania
ich w innych program ach. D o p ro je k tu dodaj klasę statyczną, w k tó re j zdefiniujesz swoją m etodę rozszerzającą.
U życie oddzielnej przestrzen i nazw j e s t
namespace My Exte n s io n s { naprawdę dobrym rozwiązaniem organizacyjnym.
p u b lic s t a t i c c la s s HumanExtensions
Klasa, w której zdefiniowano metodę
rozszerzającą, m usi być klasą statyczną.

UTW ÓRZ STATYCZNĄ METODĘ ROZSZERZAJĄCĄ I ZDEFINIUJ JEJ PIERWSZY


PARAMETR, U Ż Y W A jĄ c THIS. WPISZ TAKŻE TYP KTÓ RY ROZSZERZASZ.
D w ie główne czynności, o których musisz pam iętać podczas deklarow ania m etod rozszerzających,
to zadeklarow anie ich ja ko m etod statycznych i umieszczenie w pierwszym param etrze nazwy rozszerzanej klasy.
»this strin g " oznacza,
M etoda rozszerzają p a p u b lic s t a t i c bool I s D is t r e s s C a ll( t h is s t r in g s) {
także m usi być statyczn a. ~ ze ro zszerzasz klasę strin g .

[3) WSTAW DO METODY KOD SPRAWDZAJĄCY ŁAŃCUCH ZNAKÓW.


p u b lic s t a t i c c la s s HumanExtensions {
Chces z, by ta klas a p u b lic s t a t i c bool I s D is t r e s s C a ll( t h is s t r in g s) {
była dostępna dla kodu i f (s .C o n ta in s ("P o m o c y !"))
należącego do innych r e tu r n t r u e ;
p rzestrzen i nazw, e ls e To s p rawdza łańcuch znaków i szuka w nim
upewnij s ię zatem , że r e tu r n f a ls e ; określomego fragm entu... Nie j e s t to możliwość
j e s t ona zadeklarowana } do s tępna w domyślnej klasie strin g .
jako publiczna! }

W ZASTOSUJ SWOJĄ NOWĄ METODĘ ROZSZERZAJĄCĄ ISDISTRESSCALL().


D o da j do ko d u u s in g M y E x te n s io n s ;, a na fo rm u la rzu um ieść przycisk, abyś m óg ł w ypróbow ać swoje m etody
rozszerzające w jego procedurze obsługi. Teraz, kie dy zobaczysz s t r in g , będziesz m óg ł bez p ro b le m u używać
m etod rozszerzających. Możesz przekonać się o tym sam poprzez w pisanie nazwy zm iennej typ u s t r i n g i k ro p ki:
s t r in g m essagel;
messagel = "A rm ia klonów s ie je sp u s to s z e n ie w fa b ry c e . Pom ocy!";
m essagel.
rozszerzająca
Z a ra z po wpisaniu
kropki ID E Podpowiedz In te lliS e n se informuje,
w yśw ietli okno że j e s t to metoda rozszerzająca.
z podpowiedziami,
które zaw iera
Ten dziecinny przykład pokazuje składnię używaną
w szy stk ie metody
s tn'ng... włączając przy metodach rozszerzających. Aby naprawdę
w to metodę przekonać się, jak bardzo są one użyteczne,
rozszerzającą. poczekaj do następnego rozdziału. Jest on w całości
poświęcony LINQ, którego implementacja składa się
wyłącznie z takich metod.
672 Rozdział 13.
Śmierć obiektu

Magnesy rozszerzające
Poprzestawiaj magnesy tak, aby otrzymać następujący wynik:

a buck begets more bucks

namespace Upside { u s in g Upside;


namespace Sideways {
p u b lic s t a t ic c la s s M argin {

----------------------------
Pub l i c s t a t ic v o id S endIt c la s s Program {

J ______________________________
p u b lic s t a t ic s t r in g T oP rice

i f (n == 1)
re tu r n "a buck

jesteś tutaj ► 673


Kapitan żyje!

Magnesy rozszerzające. Rozwiązanie


Twoim zadaniem było poprzestaw ianie magnesów
tak , ab y o trzym ać n astęp u jący wynik:

a buck begets more bucks

Przestrzeń nazw Upside Klasa M argin rozszerza klasę string,


ma rozszerzenia, dodają c metodę o nazwie S e n d ltO
a przestrzeń nazw wy p isu ją c ą łańcuch znaków w oknie
Sid ew a y s ma l^msoli. Rozszerza także int,
punkt w ejścia . dodają c metodę ToPrice(). Ta z kolei

1 zwraca „a. buck", je ż e li w artość int


j e s r równa 1, lub „more bucks"
w przeciwnym wypadku.
namespace Upside {
]
M etoda punktu wej śc ia
p u b lic s t a t ic c la s s M argin { używa rozszerzeH, które
zosta ły dodane do Nasy
M argin.

]
| p u b lic s t a t ic v o id S e n d lt
( t h is s t r in g s) {
I C o n s o le .W rite (s )

u s in g Upside;
1
1V4I11WJ
namespace Sideways {
pu b lic s t a t i c s t r in g To P ric e ( t h is i n t n) { | _____
if (n == 1) c la s s Program {

e ls e
re tu r n "a buck
I s ta tic void Main (s trin g [] args) {

E r
re tu r n 11 more bu cks":
] C in t i
I
s t r in g s = i.T o P r ic e ( ) ;

p u b lic s t a t ic s t r in g Green | ( t h is bool b) { |

if (b == tru e ) | bool b = t r u e ; E L
r e tu r n "b e ";

e ls e 1 ^ b .G re e n () .S e n d It( );

r e tu r n " g e ts " ; I b = f a ls e ;

_x > i— r
1-------1 '
\ i b .G re e n ().S e n d It( ); |

Llt \
To tu taj klasa M argin rozszena. . < ■ * 1
i.T o P r ic e ( ) .S e n d It( ) ;
G reen(). Je ż e li bool j e s t równe tru e,
to Green powinno zw rócić ,t>e“ .
W przeciwnym razie zwraca „ge t s “ . Console.R eadK ey();

gP
674 Rozdział 13.
ODBUDOWALIŚMY KLASĘ SUPERHERO,
ALE W JAKI SPOSÓB PRZYW RÓ CIM Y
KAPITANA?

WSZECHŚWIAT •
H*0E a®B wuwofl mmmmmm
Śmierć nie była końcem!
Grzegorz Kamień
d z ie n n ik a r z w s z e c h ś w ia t a
OBIEKTOWO

do Obiektowa.

iprzechowane w formacie N M m y r n “ ” « * " * " * odwz or ow an e

k a p i t a l n e j nofatki. ^ deserializacji

i w y m a m r o t a ł : „ R o z d z i a ć 9 ." B l S 3 ™ ^ ^ ^ r a m i o M m i

t a j e m n i c z e j o d p o w i e d z i , ale P ^ z m H że p r e e d ^ e u d tarza w sP r a w ie j e g o

s p ę d z i ł o n d u ż o c z a s u , c z y t a j ą c k s i ą ż k i o r a z st a a n y m P 0 *0 # * * a Kanciarzem

Kapitan Wspaniały powrócił!

Ciąg dalszy str. 5.

jesteś tutaj ► 675


Rozwiązanie układanki

Zagadkowy basen. Rozwiązanie

p u b lic struct Table {

p u b lic s t r in g s t a ir s ;

p u b lic Hinge f l o o r ;
Metoda LampO ustaw ia rożne Maf o iC '
p u b lic v o id S e t(H in g e b) {
string i in t. Jeśli wywołasz J ^ ^ P0'6
Bu/b ustawione zostanie na konkretny
f l o o r = b;
obiekt wskazywany przez Hing .
}

Okno konsoli po utw orzeniu p u b lic v o id Lam p(object o i l ) {


obiektu F a u c e t : if ( o i l is in t ) Gdy do Lamp
z powrotem za 20 minut przekażesz
flo o r .b u lb = ( i n t ) o i l ; s tring, pole
p u b lic c la s s Faucet { S t a ir s ustawione
e ls e i f ( o i l j s_ s t r in g ) ^ z o sta nie na ten
p u b lic F a u ce t() { właśnie łańcuch
s t a ir s = ( s t r in g ) o il; znaków.
Table wine = new T a b le ()
e ls e i f ( o i l is Hinge) {
Hinge book = new H inge ()
Hinge v in e = o i l as Hinge;
w in e .S e t(b o o k );
C o n s o le .W rite L in e (v in e .T a b le ()
b o o k .S e t(w in e );
+ " " + flo o r .b u lb + " " + s t a ir s
ć ^ w in e .L a m p (lO );
} Pam iętaj, słowo kluczowe
bo ok.ga rde n.La m p ("z powrotem z a " ) ; as działa tylko z klasami,
nie ze strukturam i.
b o o k .b u lb *= 2;

i/in e .L a m p ("m in u t");


Zarówno Hinge, jak
w in e .L a m p (b o o k);
p u b lic class Hinge { i Table posiadają
metodę S e t( ). S e t()
} p u b lic i n t b u lb ; klasy Hinge ustaw ia
} J t d'atego Table mu s i być stru ktu ra. pole Garden typu
} Gdyby była klasą, wine wskazywałoby p u b lic T a b le garden; Table, a S e t()
na ten sam otiiekt co book.Garden. stru k tu ry Table
Spowodowałoby to nadpisanie p u b lic v o id S e t(T a b le a) { u sta w ia sw o je pole
łańcucha znaków ,z powrotem za“ . Floor typu Hinge.
garden = a;

Punkty dodatkowe: Zakreśl }


miejsca, gdzie zachodzi opakowywanie.
p u b lic s t r in g T a b le () {
W związku z tym, że metoda Lam p()
pobiera param etr typu object, re tu r n garden. s t a i r s ;
podczas przekazywania w a rto m
typu string i int autom atycznie }
wykonywane j e s t opakowywanie.

676 Rozdział 13.


14. Przeszukiwanie danych i tworzenie aplikacji.przy użyciu LINQ
W
Przejmij kontrolę nad danymi

To św ia t przepełniony d an ym i... Lepiej, ż e b y ś w iedział, ja k w nim żyć.


Czasy, gdy mogłeś programować kilka dni, a nawet klika tygodni, bez konieczności pracy z ogrom em
danych , minęły juz bezpowrotnie. Nadeszła epoka, w której w szystko opiera się na nich. W tym
miejscu do akcji wkracza LINQ . To nie tylko sposób na pobieranie danych w prosty, intuicyjny sposób.
Pozwala on takze grupow ać i łączyć dane pochodzące z różnych ź ró d e ł . A kiedy juz podzielisz dane
na fragmenty, którymi moZna łatwo zarządzać, Twoje aplikacje dla Sklepu Windows będą korzystać
z kon tro le k do n a w ig o w a n ia , pozwalających poruszać się po danych, przeglądać je, a nawet
powiększać i wyświetlać szczegółowe informacje na ich temat.

to je st n o w y ro z d z ia ł ► 677
D ia b e ł tk w i w s z c z e g ó ł a c h

Janekjest superfanem Kapitana Wspaniałego...


Poznaj Janka, jed n eg o z najbardziej energicznych kolekcjonerów kom iksów , pow ieści
graficznych o raz w szelkich innych akcesoriów zw iązanych z K ap itan e m W spaniałym .
Z n a w szelkie szczegóły dotyczące K ap itan a , dialogi z film ów o K ap itan ie i u d ało m u
się zebrać kolekcję kom iksów , k tó rą m o żn a określić jedynie ja k o ... w spaniałą.

Z O B A C Z C IE TEN KUBEK
Z LIMITOWANEJ S E R II
Z NADRUKIEM KAPITANA WSPANIAŁEGO,
Z DRUGIEJ COROCZNEJ KONFERENCJI
W SPAN IAŁO ŚC I, PODPISANY
P R Z E Z PROJEKTANTA I GRAFIKA!

Tak, to naprawdę jest


orygina/ne wyposażenie
z tetewizyjnego programu
o Kapitanie Wspaniałym,
nadawanego od września do
listopada 1973 roku. W jaki
sposób Jankowi udało się go
zdobyć?

678 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

...ale jego kolekcja zajmuje każde wolne miejsce


M oże Ja n e k i je st p asjo n atem , ale n a pew no nie je st zbyt dobrze zorganizow any.
S tara się zachow ać p o rzą d ek w najcenniejszych kom iksach należących do swojej
kolekcji, ale p o trzeb u je przy tym nieco pom ocy. Czy byłbyś w stan ie n apisać dla
Jan k a aplikację do zarząd zan ia kom iksam i?

Obw iedziona
ramkę, okfadka
kom iksu „
„Śm ierć o biektu ,
podpisana
przez a u to ra .

jesteś tutaj ► 679


L IN Q ś p ie s z y n a r a t u n e k

Dzięki LINQ możesz pobrać dane z różnych źródeł


L IN Q śpieszy z pom ocą! L IN Q (ang. L anguage In te g ra te d Q u ery — zapytania
zintegrow ane z językiem ) je st b ardzo elastyczną m ożliw ością C # , k tó ra pozw ala
p isać zap y tan ia p o b ierające d a n e z kolekcji. Je d n a k L IN Q pozw ala op ero w ać nie
tylko n a kolekcjach — w rzeczywistości m o żn a pisać zapytania po b ierające dane
z dow olnych obiektów im plem entujących interfejs IE num erable< T > .

Skorzystajm y zatem z L IN Q , by p o m ó c Jankow i zarządzać jego kolekcją komiksów.

W symulatorze pszczoły
znajd°waty się w kolekcji.

C 987 L
CurrentState = MakingHoney \
Bee1-

L
gg^^^rontStat^^lyin^oFlowe^^^J^
y B e e t|i
Bees
[_B = 1982 1
BeeH. CurrentState = GatheringNectar

var beeGroups =
from bee in world.Bees
group bee by bee.CurrentState
ID = 987 I CurrentState = M akingHoney
into beeGroup ID = 1 2 I CurrentState = FlyinqToFlower'
orderby beeGroup.Key
select beeGroup; Baza danych
F .

W LINQ dobre jest to,


że to samo zapytanie
dziafa z bazą danych oraz
z dokumentem XML pszczof,
L IN Q w spółpracuje z praw ie każdym źródłem danych dostępnym w .NET.
klientów lub czegokolwiek
Twój kod p o trzeb u je w iersza u s in g S y s te m .L in q ; n a górze pliku, innego.
i to wszystko. Jest naw et lepiej, bo ID E autom atycznie w staw ia referen cję
do L IN Q w nagłów ku każdego tw orzonego pliku z k odem źródłowym klasy.

680 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Kolekcje .NET są przystosowane do działania z LINQ


W szystkie typy kolekcji .N E T im p lem en tu ją interfejs IE num erable< T > , k tóry dokładnie
poznałeś w rozdziale 8. Pośw ięć je d n a k m in u tk ę n a k ró tk ie przypom nienie. W o knie ID E
w pisz S y s te m .C o lle c tio n s .G e n e r ic .IE n u m e r a b le < in t> , kliknij to praw ym przyciskiem
myszy i w ybierz G o To D efinition (lub naciśnij klawisz F12). Z obaczysz, że interfejs czy zwróciłeś uwagę, że
IE n u m e ra b le definiuje m e to d ę G e tE n u m e r a to r( ): lEnumerable<T> rmszfirza
IEnumerable? Użyj opcji
Go To Definition, aby zbadać
n am espace S y s te m .C o lle c tio n s .G e n e r ic { także 1 ten Interfejs.
in te rfa c e IE n u m e ra b le < T > : I E n u m e r a b le {
// Summary:
// Returns an enumerator that iterates throughthe collection.
//
// Returns:
// A System.Collections.Generic.IEnumerator<T>that can be
// used to iterate through
// the collection.
IE n u m e ra to r< T > G e tE n u m e r a to r ( ) ;
} To jedyna metoda w interfejsie. Implementuje
} ją każda kolekcja. Możesz utworzyć swój własny
rodzaj kolekcji, która także będzie implementowała
IEnumerable<T>... Jeśli tak zrobisz, będziesz mdgi
używać LINQ także w pracy z nią.

M eto d a ta w ym aga, by Twój o b iek t definiow ał jakiś sposób


p o ru szan ia się po swojej zaw artości. T o swego ro d zaju w aru n ek
w stępny L IN Q . Jeśli m ożesz p o ru szać się p o liście danych
elem en t p o elem encie, to m ożesz zaim plem entow ać interfejs
IE num erable< T > , a L IN Q będzie m ógł wykonywać zapytania
n a Tw ojej kolekcji.

L IN Q używa m e to d ro z s z e rz a ją c y c h do w ykonyw ania zapytań, sortow ania Za kulisami


i m odyfikacji danych. Spraw dź sam . U tw ó rz tablicę i n t o nazw ie l i n q t e s t ,
wstaw do niej jakieś liczby, a n astęp n ie wpisz taki o to w iersz k o d u
(nie przejm uj się, za chwilę się dowiesz, co on robi): Teraz możesz
zobaczyć, dlaczego
v a r r e s u l t = from i in l i n q t e s t w here i < 3 s e l e c t i; metody rozszerzające
z rozdziatu 13. byty
O znacz te ra z jak o k o m en tarz w iersz u s in g S y s te m .L in q ; w nagłów ku takie ważne. Pozwalają
pliku. Jeśli sp róbujesz p rzeb u d o w ać rozw iązanie, to okaże się, że powyższy one .NET (i Tobie)
dodać wszystkie rodzaje
w iersz już się nie kom piluje. M etody, któ re w ywołujesz podczas pracy ciekawych funkcji do
z L IN Q , są p o p ro stu m eto d am i rozszerzającym i używanymi do zw iększenia istniejących typów.
możliwości tablicy.

jesteś tutaj ► 681


N ie k tó r e z a p y t a n i a s ą p ro s te

LINQ ułatwia wykonywanie zapytań


O to prosty przykład składni L IN Q . Z ap y tan ie zw raca w szystkie liczby z tablicy i n t m niejsze
niż 37 i sortuje je w p o rząd k u rosnącym . W ykonyw ane je st to przy użyciu czterech fra z , któ re
określają, n a jakiej kolekcji w ykonać zapytanie, jakie k ry teria zastosow ać przy wyborze jej
elem entów , w jaki sposób poso rto w ać wyniki i ja k pow inny o n e zostać zw rócone.

int[] values = new int[] {0, 12, 44, 36, 92, 54, 13, 8};

To przypisuje w zapytaniu kolejne wartości z tablicy


var result = from v in values do litery „v“. W zwiqzku z tym zmienna ta będzie
miała wartość 0, potem 12, 44, 36 i tak dalej. v nosi
naziuę zmiennej zakresowej.
Zapytanie LINQ where v < 37
Ten fragment nakazuje wybrać z tablicy
składa się z czterech wszystkie wartości, które są mniejsze niż 37 -
fraz: from, where,
orderby i select. orderby v a następnie posortować je
w odpowiedniej kolejności
(od naj mniejszej do największej).
select v;
Jeśli wcześniej używałeś SQL możj
Ci się wydać dziwne umieszczanie
foreach(int i in result) select na końcu, ale właśnie w taki
sposób działa LINQ.

Console.WriteLine(i);
Teraz możesz wykonać iteracje
po tablicy wynikowej i wypisać
Console.ReadKey(); każdy element zwrócony
przez LINQ. W ynik:
0, 8, 12, 13, 36

var

kompilator zastąpi var typem właściwym dla danych, z którym i pracu|esz.


W powyższym przykładzie po k ° mpi|ac|i takiego wiersza-
var re s u lt = from v in values
„var" zostanie zamienione na:
IEnumerable<int> . . • v*

z o s * zam m S j S i
j m X E
d y '^ s z e r z a l* num erable^!, M * . « podczas pracy
bardzo często będziesz spotykał się także z t y m interł e|sem.

Z a jrz y j do rozdziału 8 ., by przypom nieć sobie inform acje dotyczące interfejsu IEnumerable<T>.
Oprócz tego dodatkowe dane na jego tem at zn a jd zie sz w punkcie 7 . dodatku „Po zostało ści” .

682 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

LINQjest prosty, ale Twoje zapytania wcale takie być nie muszą
Ja n e k sp rzed ał sw oją rozw ijającą się firm ę zajm u jącą się sp rz e d a ż ą aplikacji dla S k lep u W indow s
w ielkiem u inw estorow i i chce część sw oich zysków przeznaczyć n a zakup n ajcenniejszych w ydań
kom iksów o K ap itan ie W spaniałym , jak ie się ukazały. W jak i spo só b L IN Q p o m o że m u p rzeszu k ać
kolekcję i znaleźć te najd ro ższe?

Ja n e k p o b ra ł listę w ydań kom iksów z K ap itan e m W spaniałym ze strony jego fanklubu.


W staw ił je w szystkie do listy obiektów Comic, k tó re m ają dw a p o la, Name i Is s u e .

c l a s s Comic {
p u b lic s t r i n g Name { g e t; s e t ; } Nie ma żadnego
p u b lic i n t I s s u e { g e t; s e t ; }
szczególnego
powodu, by
} stosować tu
metodę statyczną,
Ja n e k użył inicjalizatorów obiektów i inicjalizatora kolekcji do u tw o rzen ia sw ojego katalogu: może z wyjątkiem
tego, że dzięki
p riv a te s t a t i c IEnumerable<Comic> B u ild C a ta lo g () temu będzie ją
{ łatwiej wywoływać
r e t u r n new List<Com ic> { z metody punktu
wejścia aplikacji
Pominęliśmy parę new Comic Name "Johnny A m erica v s. th e P in k o ", Is s u e = 6 }, konsolowych.
nawiasów za new Comic Name "Rock and R oll (e d y c ja lim ito w a n a )" , Is s u e = 19 },
inicjalizatorami
kolekcji i obiektów new Comic Name "Woman's Work", Is s u e = 36 },
<Comic>, gdyż ich new Comic Name "H ip p ie Madness ( ź le w ydrukowany)", Is s u e = 5 7 },
nie potrzebujemy. new Comic Name "Revenge o f th e New Wave F reak (u sz k o d z o n y )", Is su e = 68 },
new Comic Name "B lack Monday", Is s u e = 74 },

};
new Comic
new Comic
Name
Name
" T rib a l T a tto o M adness", Is s u e = 83 },
"The Death o f an O b je c t" , Is s u e = 97 } 1
Numer 74. przygód Kapitana
Zajrzyj do punktu 7. dodatku „Pozostałości", by poznać składnię, Wspaniałego nosi tytuł
„Black Monday".
która w takich sytuacjach może być napraw dę przydatna i wygodna.
To doskonała okazja do przeprow adzenia kilku eksperymentów!

N a szczęście istnieje dobrze p ro sp eru jący rynek kom iksów z K ap itan e m W spaniałym , a dane n a jego tem a t
um ieszczone zostały n a liście G rzegorza. Ja n e k wie, że n u m e r 57. Hippie Madness został źle w ydrukow any
i praw ie cały n ak ład został zniszczony przez wydawcę. Z n alazł o statn io n a liście G rzeg o rza rzad k ą kopię
sprzedaw an ą p o 13 525 zł. P o kilku godzinach poszukiw ań utw orzył słownik, który kojarzy nu m ery kom iksów
z w artościam i.

p r i v a t e s t a t i c D ic tio n a r y < in t, d ecim al> G e tP ric e s ()


{
r e t u r n new D ic tio n a r y < in t, decim al> {
6 , 3600M },
19, 500M }, N u m e r 5 7 . je s t wart 13 525 zł.
WYSIL
Czy pamiętasz . 36, 650M }, /
SZARE KOMÓRKI
tę składnię
inicjalizatorakolekcj 57, 13525M }, ^ -----
Przyjrzyj się dokładnie zapytaniu LINQ
dla słowników? 6 8 , 250M },
Przedstawiliśmy ją ze strony 682. Jak myślisz, co Janek
74, 75M }, musi wstawić do swojego zapytania,
w rozdziale 8.
83 , 25.75M }, aby otrzymać najcenniejsze wydanie?
97, 35.25M },
};
jesteś tutaj ► 683
To nie SQL

Anatomia zapytania
D ość łatw o b ędzie Jankow i p o b ra ć d an e w jednym zapytaniu L IN Q . F ra z a w h ere określa
elem enty kolekcji, k tó re pow inny znajdow ać się w wyniku. N ie m usi o n a być w cale prostym
porów naniem . M oże zaw ierać dow olne praw idłow e w yrażenie C # — n a przykład używać
słow nika v a lu e s , aby zw racał tylko te kom iksy, k tó re są w arte więcej niż 500 zł. F raz a o rd e r b y
działa w te n sam sposób — n akazuje L IN Q poso rto w ać kom iksy n a podstaw ie ich w artości.

Zapytanie LINQ wyciąga


obiekty Comic z listy comics,
IEnumerable<Comic> comics = BuildCatalog(); używając danych ze stownika
values, aby zdecydować, który
komiks wybrać.
Dictionary<int, decimal> values = GetPrices();

Pirerwsza fraza w zapytaniu to from. Ta tutaj nakazuje


UNQ sprawdzić kotekcję comics. Nazwa „comic“ zostanie
użyta w zapytaniu do określenia sposobu traktowania
var mostExpensive = każdego fragmentu danych w kolekcji.

Frazy where i orderby mogą


Korzystając from comic in comics zawierać DOWOLNE wyrażenie
z frazy from, C#. Możemy użyć stownika
możesz użyć where values[comic.Issue] > 500 values w celu wybrania tylko
dowolnej nazwy. tych komiksów, których wartość
W yborny przekracza 500 zł. Możemy
„comic“. orderby values[comic.Issue] descending także posortować wyniki, tak
aby najcenniejszy był pierwszy.
select comic;
Nazwa comic została zdefiniowam
we frazie from i może być teraz
używana we frazach where i orderby.
Gdy dodasz „{1:c}“ do
WriteLine, metoda będzie
foreach (Comic comic in mostExpensive) zamieniała wartość przekazaną
w drugim parametrze na
lokalny format waluty.
Console.WriteLine("{0} jest warty {1:c}",

comic.Name, values[comic.Issue]);

JEnumerable<T> rnosttxp ^unikowym \


Każde zapytanie LINQ będziemy

\d
przedstaw iać w tym rozdziale
zapytanie zwróci obiekty Ćormc. “ m,c- J /,
d w u kro tn ie : za pierwszym
razem w aplikacji konsolow ej,
by pomóc Ci zrozum ieć, jak
ono działa , następnie
Wynik:
w w iększej aplikacji dla Sklepu
H ip p ie M adness ( ź l e w ydrukow ane) j e s t w a r ty 13 5 2 5 ,0 0 z l
W indow s, byś m ógł przekonać
Jo h n y A m e ric a v s . t h e P in k o j e s t w a r ty 3 6 0 0 ,0 0 z l się, jak zapytania LINQ działają
W oman's Work j e s t w a r ty 6 5 0 ,0 0 z l w kontekście — a to dlatego, że
"W ludzki mózg lepiej zapam iętuje
rzeczy posiadające kontekst.

684 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Nie martw się, j eśli nigdy


nie używałeś j ęzyka SQL
— nie musisz go znać,
aby pracować z LINQ.
Jeżeli jesteś ciekawy,
to sięgnij po książkę
„SQL. Rusz głową!“.
N IE KUPUJĘ TEGO. Z N A M J U Ż SQL — C Z Y
PISZĄC Z A P Y T A N IA W L IN Q , N IE U Ż Y W A M
PRZYPADKIEM JEGO SKŁAD N I?

LINQ m o że w y g lą d a ć po dobnie d o SQ L, a le ta k n ie działa.


Jeśli długo p racow ałaś z SQ L, to kuszące m oże być lekcew ażenie
składow ych L IN Q i trakto w an ie ich jak czegoś intuicyjnego
i oczywistego. N ie będziesz jed y n a — w ielu p ro gram istów p o p ełn ia
te n błąd. T o praw da, że L IN Q używa słów kluczowych s e l e c t , from ,
w here, d e s c e n d in g i j o i n , k tó re zostały zapożyczone z SQ L, jest
je d n a k o d niego odm ienny. Jeśli będziesz pracow ać z L IN Q ta k jak
z SQ L, m ożesz skończyć z k odem , k tóry n ie k o n ie c z n ie b ę d z ie ro b ił to,
czego się p o n im spodziew asz.

Z n aczącą różnicą je st to , że S Q L działa n a tabelach, k tó re są o d m ien n e


o d kolekcji. B ard zo w ażna różnica p om iędzy nim i p o leg a n a tym,
że w przeciw ieństw ie do kolekcji, zaw artość ta b e l S Q L nie je st zapisana
w żadnej określonej kolejności. K iedy w ykonujesz select n a tabeli
w języku S Q L , to m ożesz być pew na, że jej zaw artość nie zostanie
zm odyfikow ana. S Q L p o siad a cały zestaw w budow anych zabezpieczeń
danych, którym m ożesz zaufać.

S koro chcesz technicznych szczegółów, to p ro szę b ardzo: zapytania SQ L


są o p eracjam i n a zbiorach. O znacza to , że nie przeg ląd ają w ierszy tabeli
w żadnym przew idyw alnym p o rząd k u . K olekcja, z drugiej strony, m oże
przechow yw ać wszystko — w artości, struktury, obiekty itp. — a obiekty
w yliczalne, nazyw ane tak że sekw encjam i, m ają ściśle o k reślo n ą
Istnieje znacznie więcej różnic kolejność. (W iersze w tabeli nie m ają ok reślo n eg o p o rząd k u , chyba
pomędzy SQL i LINQ, W że zo stan ą ta k ustaw ione p rzez zapytanie SQ L; elem en ty w ew nątrz
musisz ich jednak rozumjeći
byśrn óg tju ż teraz rozpocząć listy są u p o rząd k o w an e). L IN Q pozw ala Ci p rzep ro w ad zać dow olne
korzystanie z LINQ! Podejdź op eracje dozw olone dla obiektów kolekcji — m oże naw et wywoływać
i ° J J e9° i 0 < W V " umystem n a rzecz tych obiektów m etody. W ykonuje on n a kolekcji iteracje,
dz ataSt Pttak
dziatat i Z'eMaj Sią' SQL.
samo jak Źe b^ ie a to oznacza, że op eracje tak że w ykonyw ane są w znanym porząd k u .
M oże się to nie wydawać tak ie isto tn e, ale jeśli jesteś przyzw yczajona
do S Q L i będziesz się spodziew ała rezu ltató w w jego stylu, to zapytania
L IN Q m ogą Cię zaskoczyć.

jesteś tutaj ► 685


Z a t e m d o t e g o s ł u ż y p r z y c is k W s t e c z

Janek chętnie skorzystałby z pomocy


Pom óżm y Jankow i, pisząc aplikację dla S klepu W indow s, k tó ra nie tylko ułatwi
m u zarządzanie kolekcją kom iksów , lecz także p o k aże, ja k użyteczne jest L IN Q
jeśli chodzi o operow an ie n a danych.

Komiksy Janka
W yb ie rz zapytanie d o w ykonania
LINQ ułatwia zapytania


Proste zapytanie

PokażmyJankowi jak elastycznajest technologia LINQ

Drogie komiksy
Komiksy powyżej 500 zł.
Komiksy o wartości przekraczającej 500zł. Janek może użyć tych danych do wybrania najbardziej pożądanych komiksów.

Aplikacje dla Sklepu Windows


korzystają z nawigacji bazującej na stronach
O tw órz okno Toolbox i odszukaj odpo w ied n ik k o n tro lk i T a b C o n tro l. N ie u d ało Ci się
go znaleźć? T o nie przypadek. K arty są jednym z elem en tó w aplikacji „okienkow ych”,
kiedy ich je d n a k nie używamy, to m ogą n iep o trzeb n ie zaśm iecać ek ran . Sposób
p o ru szan ia się p o a p lik a c ja c h przeznaczonych d la S klepu W indow s o p ie ra się na
w ykorzystaniu stro n , co pozw ala u n ik n ąć n iep o trzeb n eg o zaśm iecania e k ra n u i tworzyć
bardziej intuicyjne interfejsy użytkow nika. Kiedy aplikacja przechodzi do
kolejnej s^rny, pojawia się na
nieJ przycisk Ws
Wstecz, a Janek
może go użyć, by wrócić na
Komiksy Janka poprzednią stronę.
Wybierz zapytanie do wykonar*a

Kiedy Janek kliknie jed<sn z elementow hshy


zapytań wyświetlonej na_sporne! gkwnej,
aplikacja przejdzie do widok.u ^ziigofow
wybranego zapytania-

Więcej informacji na tem at sposobów projektow ania nawigacji w aplikacjach dla Sklepu Windows
można znaleźć na stronie: http://msdn.microsoft.com/pl-pl/library/windows/apps/hh761500.aspx.

686 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Użyj ID E, by zbadać sposób nawigacji po witrynie


O to kolejna możliwość wykorzystania ID E jako narzędzia edukacyjnego. O tw órz w nim dowolny projekt aplikacji dla Sklepu
Windows, a następnie otw órz plik App.xaml.cs. T o główny plik aplikacji, który jest dostępny w każdej aplikacji tego typu.
Z aw iera on klasę pochodną klasy o nazwie A p p lic a tio n , zdefiniowanej w przestrzeni nazw Windows.UI.Xaml i umieszczonej
w pliku App.xaml. O biekt Application aplikacji inicjalizuje ją i zarządza jej cyklem życia: uruchom ieniem , wstrzymywaniem oraz
wznawianiem wykonywania. O biekt ten wykonuje jeszcze jedną, bardzo przydatną operację: tworzy obiekt Frame (należący do
przestrzeni nazw W in d o w s.U I.X am l.C o n tro ls), którego aplikacja używa do obsługi poruszania się po stronach XAM L.

W klasie App odszukaj m e to d ę O n L a u n c h e d (). Jest o n a w ykonyw ana za każdym razem podczas u ru ch am ian ia aplikacji
i odpow iada za utw o rzen ie i przygotow anie ram ki:

/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
¡if < / su mm ar y> Użyj opcji Go To Definition,
¡ ¡ I <param name="args">Details about the launch request and process.</param> by przejść do klas Window
protected override void OnLaunched(LaunchActivatedEventArgs args) lub Frame reprezentujących,
i ' odpowiednio: główne okno
Frame root Frame = Window. Current .Content as Frame \
bieżącej aplikacji oraz ramkę
/ / D o not repeat app initialization when the Window already has content, nawigacyjną.
// just ensure that the window is active
if (rootFrame == null)
i
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame(); To właśnie w tym miejscu
aplikacja tworzy nową
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated) ramkę nawigacyjną, która
{ będzie zawierać wszystkie
//TODO: Load state from previously suspended application
i strony tej aplikacji.

// Place the frame in the current Window K\QĆy usuwasz z proj ektu plik MainPage.xaml i zastępujesz
Window.Current.Content = rootFramej go nowym plikem Basic Page o tej samej nazwie, dodajesz do
} pr°jektu nową klasę Mainpage, ktOra zastępuję poprzednią.

f
Dzięki temu metoda Navigate() może utworzyć instancję tej
if (rootFrame.Content == null) nowej klasy zamiast domyślnej, wygenerowanej wraz z projektem.
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
if (!rootFrame.Navigate(typeof(MainPage), args.Arguments)) To właśnie w ten sposób aplikacja wyświetla
t stronę główną. Metoda F ram e .N a v ig ate ()
throw new Exception ("Failed to create initial page'');
tworzy nową instancję strony i wyświetla jej
}
zawartość. Słowo kluczowe ty p e o f zwraca typ
t
// Ensure the current window is active klasy, dzięki czemu metoda wie, jakiego typu
Window.Current.Activate(); obiekt ma utworzyć.
1
Twoje aplikacje także m ogą korzystać z m etody N a v ig a te ( ) , by poruszać się pom iędzy stronam i. K ażda stro n a X A M L
dysponuje w łaściw ością o nazw ie Frame. Gdybyś m iał dodać do aplikacji kolejną stronę, o nazwie A n o th erP ag e, to mógłbyś
ją wyświetlić, używając poniższego fragm entu kodu. Z w róć uw agę n a a rg u m e n t q u e ry przekazyw any w wywołaniu m etody
N a v ig a te ( ) . T o p a ra m e tr przekazyw any do tw orzonej strony.
Zajrzyj do punktu 5. dodatku
if (th is .F r a m e != n u ll)
Pozostałości, gdzie możesz znaleźć
th is.F ra m e .N a v ig a te (ty p e o f(A n o th e rP a g e ), q u e ry ); dodatkowe informacje dotyczące
słowa kluczowego ty p e o f.
Jeśli dodasz stronę o nazwie AnotherPage, IDE doda do
projektu klasę AnotherPage, a ten kod pozwo^ przejść na
stronę AnotherPage, przekazując tśo niej „ąuery ‘ jako argument. jesteś tutaj ► 687
N o w a a p lik a c ja , z n a n y w z o r z e c

Zacznij pisać aplikację dla Janka


N apiszesz aplikację, k tó ra korzysta z m ech an izm u nawigacji p o stro n ach w celu
w ykonyw ania różnych zapytań L IN Q . Z aczniesz o d dw óch zapytań, k tó re zostały Z ró b to
przedstaw ione już wcześniej.

U tw ó rz now y p ro je k t a p lik a c ji ty p u Windows Store.

Skorzystaj z szablonu B lank Template, u su ń p lik MainPage.xaml, n a stęp n ie dodaj now ą stronę
u tw orzoną n a podstaw ie szablonu Basic Page i nadaj jej nazw ę MainPage.xaml. N a stęp n ie d o d a j
k o le jn ą s tro n ę Basic Page i n a d a j je j nazw ę QueryDetail.xaml. P rze d przejściem do p u n k tu 2.
nie zapom nij w ybrać z m en u głów nego opcji BUILD/Rebuild Solution .

D o d a j d o p ro je k tu k la s ę Comic .

K lasę Comic poznałeś ju ż kilka stro n w cześniej, a zatem nie w ahaj się i dodaj ją do pro jek tu .

c l a s s Comic {
p u b lic s t r i n g Name { g e t; s e t ; }
p u b lic i n t Is s u e { g e t; s e t ; }
}

D o d a j k la s ę C om icQ uery .

T a klasa będzie Ci p otrzebna do reprezentacji zapytań L IN Q , a po zakończeniu pisania aplikacji


będziesz dysponował jedną instancją tej klasy dla każdego używanego zapytania. Spójrz n a zrzut
ekranu zamieszczony dwie strony wcześniej. K ażde z zapytań m a swoją ikonę, a zatem potrzebujesz
jakiegoś sposobu, by je reprezentow ać w aplikacji. Użyjesz do tego obiektu BitmapIm age. Klasa
Bitm apIm age została zdefiniow ana w przestrzeni nazw W indow s.U I.X am l.M edia.Im aging,
a zatem na samym początku pliku musisz dodać odpow iednią instrukcję u sin g .

u sin g W indow s.U I.X am l.M edia.Im aging;

c l a s s ComicQuery {
p u b lic s t r i n g T i t l e { g e t; p riv a te s e t; } Jeśli chcesz, instrukcję using
p u b lic s t r i n g S u b t i t l e { g e t; p r i v a t e s e t ; }
możesz umieścić wewnątrz
deklaracji przestrzeni nazw
p u b lic s t r i n g D e s c rip tio n { g e t; p r i v a t e s e t ; } w pliku .cs.
p u b lic BitmapImage Image { g e t; p r i v a t e s e t ; }

p u b lic C o m ic Q u e ry (strin g t i t l e , s trin g s u b title ,


s t r i n g d e s c r i p t i o n , BitmapImage image) {
T itle = t i t l e ;
S u b title = s u b title ;
D e s c rip tio n = d e s c r ip ti o n ;
Image = image;
}
}

688 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

^4 Dodaj klasę menedżera zapytań, aby można było z czymś powiązać kontrolki. ComicQueryManager
AvailableQueries
A plikacja dla Ja n k a będzie działać w edług teg o sam ego schem atu, w edług k tó reg o zostały CurrentQueryResults

napisane dwie p o p rz e d n ie aplikacje dla S klepu W indow s. K lasa Com icQ ueryM anager Title

w ykona wszystkie czynności zw iązane z realizacją zapytań i u d o stę p n i właściwości zaw ierające UpdateAva i lab l eQueries()
zw rócone wyniki. K ażda stro n a X A M L będzie dysponow ać statycznym zasobem zaw ierającym UpdateQueryResultsO

instancję klasy C om icQ ueryM anager, będzie także wywoływać jej m eto d y w celu w ykonania static BuildCatalog()
static GetPrices()
zapytań i zad b a o pow iązanie wyników z k o ntrolkam i.
private LinqM akesQueriesEasy()

Kontrolka ListView z zapytaniami‘ private ExpensiveComics()


u sin g S y ste m .C o lle c tio n s .O b je c tM o d e l;
u sin g W indow s.U I.X am l.M edia.Im aging; umieszczona na str°nie głównej, _ private CreateImageFromAssets()
została powiązana z
AvailableQueries.
c l a s s ComicQueryManager {
I
p u b lic O bserv ab leC o llectio n < C o m icQ u ery > A v a ila b le Q u e rie s { g e t; p r i v a t e s e t ; }

p u b lic O b s e rv a b le C o lle c tio n < o b je c t> C u rre n tQ u e ry R e su lts { g e t ; p r i v a t e s e t ; }

f
p u b lic s t r i n S T 1 tle { s e t; } Właściwości CurrentQueryResults eras Title są używane do
wyników zapytania na stronie QueryDetails.
p u b lic ComicQueryM anager() {
U p d a te A v a ila b le Q u e rie s ();
C u rre n tQ u e ry R e su lts = new O b s e rv a b le C o lle c tio n < o b je c t> ();
}

p r i v a t e v o id U p d a te A v a ila b le Q u e rie s () {
A v a ila b le Q u e rie s = new O bservableC o llectio n < C o m icQ u ery > {
new ComicQuery("LINQ u ła tw ia z a p y ta n ia " , " P r o s te z a p y ta n ie " ,
"Pokażmy Jankowi j a k e la s ty c z n a j e s t te c h n o lo g ia LINQ",
Ten inicjalizator C re ate Im ag e F ro m A ssets("p u rp le 2 5 0 x 2 5 0 .jp g " )),
kolekcji tworzy
obiekty ComicQuery,
a przechowywane new C om icQ uery("D rogie kom iksy", "Komiksy powyżej 500 z ł . " ,
w nich informacje "Komiksy o w a rto ś c i p r z e k r a c z a ją c e j 500 z ł . "
zostaną wyświetl°ne + " Ja n e k , może użyć ty c h danych do w yb ran ia n a jb a r d z ie j "
na stronie głównej. + " pożądanych kom iksów .",
C re a te Im a g e F ro m A sse ts("c a p ta in am azing 2 5 0 x 2 5 0 .jp g " )),
};
}

p r i v a t e s t a t i c BitmapImage C re a te Im a g e F ro m A sse ts(strin g im ageFilenam e) {


r e t u r n new Bitmaplmage(new U r i( " m s - a p p x :///A s s e ts /" + im ag eF ilen am e)):
} X -
Przyjrzyj się dokładniej metodzie
p u b lic v o id U pdateQ ueryR esults(C om icQ uery q u ery ) { CreateImageFromAssets(). Czy jesteś
T i t l e = q u e r y .T i t l e ; w stanie powiedzieć, co się w niej dzieje?
sw itc h ( q u e r y .T i tl e ) {
Strona QueryDetails c a se "LINQ u ła tw ia z a p y ta n ia " : L in q M ak esQ u eriesE asy (); b re a k
korzysta z tej metody,
by wykonywać c a se "D rogie kom iksy": E x p en siv eC o m ics(); b re a k ;
zapytanie LINQ. }
Zanim przewrócisz kartkę, by zobaczyć dalszą część kodu, zastanów się,
}
czy jesteś w stanie powiedzieć, jak wyglądają metody LinqMakesQueriesEasy()
oraz ExpensiveComics(). Aplikacja będzie ich używać w celu wykonywania zapytań LINQ.

jesteś tutaj ► 689


N ie z a le ż n ie o d n a z w y

p u b lic s t a t i c IEnumerable<Comic> B u ild C a ta lo g () {


r e t u r n new List<Com ic> {
new Comic Name "Johnny America v s . th e P in k o ", Is s u e = 6 }
new Comic Name "Rock and Roll (e d y c ja lim ito w a n a )" , Is s u e = 19 },
new Comic Name "Woman's Work", Is s u e = 36 },
new Comic Name "H ip p ie Madness ( ź l e w ydrukow any)", Is s u e = 57 },
new Comic Name "Revenge o f th e New Wave F reak (uszkodzony) " , I s s u e =68 },
new Comic Name "B lack Monday", Is s u e = 74 },
new Comic Name " T rib a l T a tto o M adness", Is s u e = 83 },
new Comic Name "The Death o f an O b je c t" , Is s u e = 97 }
};
} tv, metodg BuildCatalogO
Iraz GetPricesO p rz e d s ta w i
p r i v a t e s t a t i c D ic tio n a r y < in t, decim al> G e tP ric e s () { już kilka stron wcześniej.
r e t u r n new D ic tio n a r y < in t, d ecim al> {
{ 6 , 3600M }, { 19, 500M }, { 3 6 , 650M }, { 57, 13525M },
{ 6 8 , 250M }, { 74, 75M }, { 8 3 , 25.75M }, { 9 7 , 35.25M }, Właściwość CurrentQueryResults
}; służy do wyświetlania wyników
} zapytania przy użyciu techniki wiązania
danych. Nie trzeba jej opróżniać, gdyż
p r i v a t e v o id L inqM akesQ ueriesE asy() { po każdej zmianie wyświetlonej strony
i n t [ ] v a lu e s = new i n t [ ] { 0 , 1 2 , 4 4 , 36, 92, 54, 13, 8 }; tworzony jest nowy obiekt strony,
v a r r e s u l t = from v in v a lu e s a ten z kolei dysponuje własną instancją
w here v < 37 ComicQueryManager zawierającą
o rd e rb y v nową, pustą kolekcję.
s e l e c t v;
Wciąż nie do końca wiemy, jak działa metoda
fo re a c h ( i n t i in r e s u l t ) CwteM^FromAssetsO, ale założę się, że
C u rre n tQ u e ry R e su lts.A d d ( dowiemy się tego na następnej stronie.
new {
T itle i.T o S trin g (),
Image CreateImageFromAssets("purple 250x250.jpg"),
}
);
Każda z tych metod wykonuje jedno z zapytań

p r i v a t e v o id E xpensiveC om ics() {
Nl LINQ przedstawionych we wcześniejszej części
rozdziału. Zamiast wyświetlać ich wyniki
w oknie wiersza poleceń, są one zapisywane we
IEnumerable<Comic> comics = B u ild C a ta lo g ( );
D ic tio n a r y < in t, d ecim al> v a lu e s = G e tP r ic e s ( ) ; właściwości CurrentQueryResults, w postaci
kolekcji typu ObservableCollection<object>.
v a r m ostE xpensive = from comic in comics Warto jednak dokładniej przyjrzeć się instrukcji
w here v a lu e s [c o m ic .Is s u e ] > 500 new { }. W jakiś sposób słowo kluczowe new
o rd e rb y v a lu e s [c o m ic .Is s u e ] d esc e n d in g zostało zastosowane wewnątrz inicjalizatora
s e l e c t com ic; obiektu. Zazwyczaj po new jest podawana nazwa
typu, jednak w tych instrukcjach została ona
fo re a c h (Comic comic in m ostE xpensive) pominięta, dzięki czemu tworzone są instancje
C u rre n tQ u e ry R e su lts.A d d ( typu anonimowego.
new {
T itle = S trin g.F o rm at("{0 } je s t warty { 1 :c }" ,
comic.Name, values[com ic.Issue]),
Image = CreateImageFromAssets(,,captain_amazing_250x250.jpg"),
}
);

*
690 Rozdział 14.
Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Używaj słowa kluczowego new, by tworzyć typy anonimowe


Słowa kluczowego new zacząłeś używać do tw orzenia obiektów już w trzecim rozdziale tej książki. Z a każdym razem
um ieszczałeś za nim nazwę typu (a zatem w yrażenie new G uy() tworzyło instancję typu Guy). N iem niej je d n a k m ożna go
także używać bez podaw ania typu — w takim przypadku tw orzony jest ty p anonim ow y. T o całkowicie praw idłowy typ,
który m a właściwości tylko do odczytu, lecz nie m a nazwy. W łaściwości m ożna dodaw ać do typu anonim ow ego, p odając je
w inicjalizatorze obiektu.

Poniżej przedstaw iliśm y instrukcję z k o d u zapytania E x p en siv eC o m ics (przedstaw ionego n a p o p rzed n iej stro n ie),
tw orzącą instancje typu anonim ow ego, k tó re n astęp n ie zo stan ą d o d an e do kolekcji przechow yw anej we właściwości
C u rre n tQ u e r y R e s u lts :

new {
Title = String.Format("{0} jest warty {1:c}”,
comic.Name, values[comic.Issue]),
Image = CreateImageFromAssets("captain_amazing_250x250.jpg”),
}

P o uru ch o m ien iu p ro g ra m u m ożesz się p rzek o n ać, że tw orzone w ten sposób obiekty w yglądają zupełnie ta k sam o ja k
wszystkie inne. O to w jak i sposób in stancja typu anonim ow ego będzie p rez en to w a n a w o knie Watch:

Powyższa instrukcja działa ta k sam o ja k każdy inny inicjalizator


obiektu. W inicjalizatorze m ożesz wywoływać m etody, takie ja k anonimowy, przymiotnik
C re a te Im a g e F ro m A s s e ts () o raz S t r i n g . F o r m a t ( ) , by o k reślać w artości
właściwości. (Oczywiście w razie p o trzeb y m o żn a im także przypisywać — nieujawniający swego nazwiska lub nieznany
z nazwiska.
k o n k retn e w artości).
Agent Dash Martin używa pseudonimu, by
Jedyną rzeczą, której nie m ożesz zrobić, jest odwoływanie się do nazwy typu pozostać anonimowym i aby nie dopuścić do
anonim owego, a to z tej prostej przyczyny, że takiej nazwy nie ma! I właśnie rozpoznania przez agentów KGB.
w tych sytuacjach nieodzow ne okazuje się słowo kluczowe v a r, gdyż pozwala
n a przechow anie referencji do typu anonim owego. O to przykład:

v a r myAnonymousObject = new {
Name = "R o b e rt", Więcej informacji na temat typów
Cash = 186.3M, anonimowych możesz znaleźć
Age = 37, w punkcie 9 . dodatku Pozostałości.
};
C onsole.W riteLine(m yA nonym ousO bject.N am e);

Powyższy kod tw orzy instancję typu anonim ow ego, zapisuje referen cję do tego now ego o b iek tu w zm iennej
m yA nonym ousObject i używ a do w yśw ietlenia w artości właściwości Name.

Przewróć kartkę, by dokończyć aplikację dla Janka.


jesteś tutaj ► 691
P r z e jm ij k o n tr o lę n a d d a n y m i

3 D odaj p lik i obrazków do fo ld eru Assets p ro jek tu .

O dszukaj pliki obrazków purple_250x250.jpg o raz captain_amazing_250x250.jpg używ ane w p ro jek cie (są one
d ostępne w przykładach dołączonych do książki, k tó re m ożesz p o b rać z serw era F T P w ydaw nictw a H elion
— ftp:llftp.helion.pllprzykladylcshru3.zip i zapisać je w po danym folderze). N astęp n ie p rzejd ź do o kna Solution
Explorer, kliknij praw ym przyciskiem fo ld er ä , w ybierz z m en u opcję A dd/E xisting Item i dodaj pliki.
A te ra z przyjrzyj się dokładniej m eto dzie C re a te Im a g e F r o m A s s e ts ():
p r i v a t e s t a t i c BitmapImage C re a te Im a g e F ro m A sse ts(strin g im ageFilenam e) {
r e t u r n new Bitmaplmage(new U r i( " m s - a p p x :///A s s e ts /" + im ag e F ilen a m e ));
}

K ażdy plik należący do p ro je k tu m a u n ik ato w ą nazw ę w p rzestrzen i nazw m s-appx. Plik purple_250x250.
jpg um ieszczony w folderze Assets będzie m iał nazw ę ms-appx:///Assets/purple_250x250.jpg. M ożesz użyć tego
identyfikatora, by wczytać zaw artość plik u do o b iek tu B itm apIm age, a już niebaw em p rzek o n asz się,
w jaki sposób m o żn a pow iązać go z k o n tro lk ą <Image> w kodzie X A M L strony.

[6 D okończ kod XAM L o ra z kod ukry ty stro n y głównej.

O tw órz plik MainPage.xaml. Poniżej przedstaw iliśm y zasoby tej strony:

< P age.R esources> Kontrolka ListView automatycznie


< local:C om icQ ueryM anager x:N ame="com icQueryM anager"/> doda pionowy pasek przewijania,
< x :S tr in g x:Key="AppName">Komiksy J a n k a < /x :S trin g > jeśli zawartość listy wyjdzie poniżej
< /P a g e.R eso u rc e s> jej dolnej krawędzi. Spróbuj dodać
Height="*Mdo drugiego elementu
RowDefinition w definicji kontrolki
D o określenia układu prezentow anych treści możesz wykorzystać kontrolkę G rid:
Grid. Paski przewijania znikną!
<G rid Grid.Row="1" M argin="120,0"
Stanie się tak dlatego, że wiersz
rozszerzy się i dostosuje do obszaru
D a ta C o n te x t= "{ S ta tic R e so u rc e ResourceKey=com icQueryM anager}">
zajmowanego przez wszystkie
< G rid .R o w D e fin itio n s> elementy listy
Przypisanie
właściwości < R ow D efinition H eight= "A uto"/>
SelectionMode < R o w D efin itio n />
wartości None < /G rid .R o w D e fin itio n s> Właściwość
wyłącza IsItemClickedEnabled
możliwość <T extB lock S ty le = " { S ta tic R e s o u rc e S u b h e a d e rT e x tS ty le } "
powoduje, że
zaznaczania Text="W ybierz z a p y ta n ie do w ykonania" M a rg in = " 1 0 ,0 ,0 ,2 0 " /> kliknięcie elementu
elementów <ListV iew Grid.Row="1" M a rg in = "0 ,-1 0 ,0 ,0 " Item sSource="{B inding A v aila b le Q u e ries} " kontrolki ListView
listy. spowoduje
Item T em p late= "{ S taticR eso u rce Standard130Item T em plate}"
zgłoszenie zdarzenia
SelectionM ode="None" IsItem C lickE nabled= "T rue" Item C lick= "L istV iew _Item C lick"/: ItemClicked.
< /G rid>
D o k o d u ukrytego dodaj poniższą p ro c e d u rę obsługi zd arzeń. P ro c e d u ra obsługi zd arzeń S e le c tio n C h a n g e d
kontrolki L istV ie w m oże odwoływać się do zaznaczonych elem en tó w za pośred n ictw em właściwości
e .A d d e d Ite m s. K o n tro lk a L istV ie w je st p ow iązana z kolekcją O b s e r v a b l e C o l le c t io n obiektów ComicQuery,
a zatem w yrażenie e .A d d e d Ite m s [0 ] zawsze b ędzie zaw ierać kliknięty p rzez użytkow nika o b iek t ComicQuery.
O b iek t ten m usisz p rzek azać jak o p a ra m e tr do nowej strony, używ ając do tego celu w yw ołania F ra m e .N a v ig a te ( )

p r i v a t e v o id L istV ie w _ Ite m C lic k (o b je c t s e n d e r , Item C lickE v en tA rg s e) {


ComicQuery q u ery = e .C lic k e d Ite m as ComicQuery;
i f (q u ery != n u ll ) W wywołaniu metody Frame.NavigateO _
ć jakiś
t h is . Frame.Na v iga te ( type° f (QueryDe t a i l ) , _quęryl; mÓ£tt jP k ^ p a Z m ^ te Wyśw7/tla ej Strony.
}

692 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

D okończ kod XAM L o ra z kod ukry ty stro n y szczegółów.

O tw órz plik QueryDetail.xaml. O to zasoby tej strony:

< P age.R esou rces>


<local:C om icQ ueryM anager x:N ame="com icQueryM anager"/>
< x :S tr in g x:Key="AppName">Query D e ta il< /x :S tr in g >
< /P a g e .R e so u rc e s>

D o określenia u k ła d u p rezentow anych treści użyj k o n tro lk i G rid :

<G rid G rid.R ow ="l" M argin="120,0" D a ta C o n te x t= "{ S ta tic R e so u rc e ResourceKey=com icQueryM anager}'
< G rid .R o w D e fin itio n s>
< R ow D efinition H eig h t= "A u to "/> < R o w D efin itio n /> Nic nie stoi na przeszkodzie,
< /G rid .R o w D e fin itio n s> *y .w Jednym w'f rszU umi%szczać
<T extB lock S ty le = " { S ta tic R e s o u rc e S u b h e a d e rT e x tS ty le } "
T ext="Q uery r e s u l t s " M a rg in = " 1 0 ,0 ,0 ,2 0 " />
< L istV iew Grid.Row="1" M a rg in = " 0 ,-1 0 ,0 ,0 " Item sS o u rce= "{ B in d in g C u rre n tQ u e ry R e su lts} "
Ite m T e m p la te = "{ S ta tic R e so u rc e S tan d ard 1 3 0 Ite m T em p late} " S electionM ode="N one"/>
< /G rid>

K iedy stro n a głów na wywołuje m e to d ę F r a m e .N a v ig a te ( ) , by p rzejść n a stro n ę szczegółów, p rzek azu je do niej jako
p a ra m e tr o b iek t ComicQuery. D o stęp do tego p a ra m e tru m o żn a uzyskać, przesłan iając w kodzie ukrytym m eto d ę
O n N av ig ated T o () i p o b ierając o b iek t przy użyciu w yrażenia e .P a r a m e te r :

p r o te c te d o v e r r id e v o id O nN avigatedT o(N avigationE ventA rgs e) {


ComicQuery comicQuery = e .P a ra m e te r as ComicQuery;
i f (com icQ uery != n u ll ) {
co m icQ ueryM anager.U pdateQ ueryR esults(com icQ uery);
p a g e T itle .T e x t = co m icQ u e ry M an ag er.T itle;
Wtaściwości obiektu ComicQuery
} oraz typów anonimowych tworzonych
b a se .O n N a v ig ate d T o (e ); przez zapytanie LINQ odpowiadają
poWiązaniom ° kreś/°nym przez szablon
OataTemp/ate, dzięki czemu są
wyświet/ane w kontrolce ListView.

3 U ruchom aplikację! A n a stę p n ie użyj ID E , by j ą zb ad ać i zrozum ieć, j a k działa.


I
A zatem w jaki sposób aplikacja w yświetla obrazki w k o n tro lk ach L istV ie w ? T o żad n a tajem nica. K ontrolki
L istV ie w używają szablonu Ite m T e m p la te o nazw ie S ta n d a rd 1 3 0 Ite m T e m p la te . Użyj ID E , by odszukać ten
szablon w tw orzonej aplikacji. Z najdziesz go w pliku StandardStyles.xaml. Je st to szablon danych taki ja k ten,
których używałeś ju ż w ro zdziale 1 0 .:

<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="110" Height="110">


<Image Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
</Border>
<StackPanel Grid.Column="l^v£rticalAlignment="Top" Margin="10,0,0,0"
<TextBlock Tex ing Title}" Style="{StaticResource TitleTextStyle}" TextWrapping="NoWrap"/>
<TextBlock Tę binding Subtitle}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
<TextBlock :="{Binding Description}" Style="{StaticResource BodyTextStyle}" MaxHeight=“60"/>
</StackPanel>
\
Drogie komiksy
komiksy powyżej 500 zł.
Komiksy o wartości przekraczającej 500 zł. Janek może użyć tych danych d o wybrania najbardziej pożądanych komiksów.

jesteś tutaj ► 693


To d la t e g o J a n e k lu b i LIN Q Wszystkie kolekcje zapewniają możliwość
pobierania swej zawartości element po elemencie
— implementują one interfejs IEnumerable<T> .

LINQ ma wiele zastosowań Niemniej jednak, z technicznego punktu widzenia, nie


wszystko, co implementuje ten interfejs, jest kolekcją
— kolekcje muszą bowiem implementować interfejs
M ożesz zrobić znacznie w ięcej, niż tylko p o b ra ć kilka elem entów IC o lle ctio n < T> ,a to z kolei w y m a g a dodania takich
z kolekcji. N a przykład p rz e d ich zw róceniem m ożesz je zm odyfikować. metod jak A dd() , C le a r ( ) ,C o n ta in s () ,CopyTo()
Po w ygenerow aniu zbio ru sekw encji wynikowych L IN Q u d o stę p n ia Ci oraz Remove()... Oczywiście IC ollection< T>
rozszerza IEnumerable<T> .LINQ operuje na
bogaty zestaw m eto d , z którym i m ożesz pracow ać. O gólnie rzecz biorąc,
sekwencjach wartości lub obiektów, nie na kolekcjach,
daje Ci on narzędzia do zarząd zan ia danymi. a sekwencja w y m a g a jedynie implementacji interfejsu
IEnumerable<T> .
Zm odyfikuj k a żd y e le m e n t zw ró co n y z zap ytan ia.
Poniższy kod dodaje łańcuch znaków na k ońcu każdego łańcucha w tablicy.
N ie zm ienia on jej sam ej — tw o rzy n o w ą sek w en cję zm odyfikow anych łańcuchów znaków .

s t r in g [ ]
sandwiches = { "szynka i s e r " , "salami z majonezem",
"indyk i se r s z w a jc a rs k i", " k o t le t z kurczaka" };
var sandwichesOnRye =
from sandwich in sandwiches
fodci^ j ańcUch znaków „na chlebie
se le ct sandwich + 11 na chlebie zbożowym"; zb^oiwym do każdego elementu
wynikowego zapytania.
foreach (var sandwich in sandwichesOnRye)
Console.W riteLine(sandwich); Ta zmiana wprowadzana
jest do elementów
wynikowych zapytania,
Zwróć uwagą na to, ż e na końcu nie do elementów
zwróconych elementów dodano Wynik: w oryginalnej kolekcji
„na chlebie zbożowym szy n k a i s e r n a c h l e b i e zbożowym lub bazie danych.
s a la m i z m ajo n ezem n a c h l e b i e zbożowym
in d y k i s e r s z w a j c a r s k i n a c h l e b i e zbożowym
k o tle t z k u r c z a k a n a c h l e b i e zbożowym

Przep ro w ad ź o bliczen ia n a kolekcjach.


Pam iętaj, pow iedzieliśm y, że L IN Q u d o stę p n ia m eto d y rozszerzające kolekcje (obiekty
dostępow e baz danych o raz wszystko, co im p lem en tu je interfejs IE num erable<T >).
N iek tó re z nich są p rzy d atn e jak o takie i nie m uszą być um ieszczone w zapytaniach:

Random random = new R an d o m ();


L i s t < i n t > lis tO fN u m b e r s = new L i s t < i n t > ( ) ;
i n t l e n g t h = ra n d o m .N e x t( 5 0 , 1 5 0 ) ;
f o r ( i n t i = 0 ; i < l e n g t h ; i+ +)
li s t 0 f N u m b e r s .A d d ( r a n d o m .N e x t( 1 0 0 ) ) ;

C o n s o le .W r ite L in e ( " N a l i ś c i e z n a j d u j e s i ę {0} li ii cc zz bd "" ,, Żadna z tych


/ ___________
„ metod me
lis tO f N u m b e r s . C o u n t()) ;
.NET. Wszystkie są
C o n s o le .W r it e L i n e ( " N a jm n ie j s z a z n ic h t o { 0 } " , zdefiniowane przez U NQ.
l i s t O f N u m b e r s .M in ())
C o n s o le .W r it e L i n e ( " N a jw ię k s z a z n i c h t o { 0 } " ,
lis t0 f N u m b e r s .M a x ( ) )
\
Wszystko to są metody rozszerzające zde-
finiowane dla interfejsu I Enumerable<T> ,
C o n s o le .W r ite L in e ( " S u m a w yn o si { 0 } " ,
w statycznej klasie Enumerable naleząceJ
li s t O f N u m b e r s .Sum()) do przestrzeni naz\u System-Linq- Ale
C o n s o l e .W r i t e L i n e ( " Ś r e d n i a w yn o si { 0 :F 2 } " , nie wierz nam na słowo! klikn^^_dowolną
l i s t O f N u m b e r s .A v e ra g e ()) ; z nich, wybierz opcję G To Definition
i sprawdź, czy faktycznie tak jest.
694 Rozdział 14.
Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ
Sekwencja jest uporządkownyrn Tbioram w*LNQ
lub obiektów i to właśnie sekwencje zwraca LINQ
jako IEnumerable<T>.
Zapytania
P rzech o w aj ca ło ść lub c z ę ść zw ró co n ych w ynikó w w nowe* sekw en cji.
Uuiaęa! LINQ nie są
C zasam i chciałbyś przechow ać wyniki zw rócone p rzez L IN Q .
uruchamiane,
M ożesz to zrobić, używ ając m eto d y T o L i s t ( ) .
dopóki nie zażądasz dostępu
v a r u n d e r5 0 s o rte d =
do wyników!
Tym razem posortujemy
fro m n u m b er in lis tO f N u m b e r s Nazywamy to późnym przetwarzaniem
liczby w porządku
w h e re n u m b er < 50 malejącym, od najwięksteJ — zapytanie LINQ nie wykonuje iteracji,
do najmniejs^. dopóki nie zostanie wykonana jakaś
o r d e r b y n u m b er d e s c e n d i n g
instrukcja używająca jego wyników.
s e l e c t n u m b e r;
To diatego ważne jest wywołanie
T o L i s t ( ) : nakazuje ono LINQ
L is t< in t> n e w L is t = u n d e r 5 0 s o r t e d . T o L i s t ( ) ;
natychmiast rozpocząć przetwarzanie
zapytania.

M ożesz naw et p o b ra ć p o d zb ió r zbio ru w ynikowego,


korzystając z m eto d y T a k e ( ) : T°List() zamienia var w instrukcji LINQ na
obiekt List<T>, możesz więc bez problemów
var firs tF iv e = u n d e r 5 0 s o r t e d . Take( 5 ) ; przechowywać zwrócone wyniki. Istnieją
także metody ToArray() oraz ToDictionary(),
które wykonują to, co sugerują ich nazwy.
L i s t < i n t > s h o r t L i s t = f i r s t F i v e . T o L is t() ;
f o r e a c h ( i n t n in s h o r t L i s t ) Take() pobiera określoną habę^poc^tkoiuyrt
C o n s o le .W r ite L in e (n ); elementów wyniku zapytania LINQ
wstawić je w kolejne var i p r w to iw w to ^
na listę.

Zajrzyj* n a oficjalną stronę Microsoftu 101 LINQ S am p le s.


Istnieje znacznie w ięcej rzeczy, k tó re L IN Q m oże zrobić. N a szczęście M icrosoft u d o stęp n ia
w spaniałą d o k u m en tację, k tó ra pozw oli Ci się rozwijać.

http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b

i Nie. istnieją.
głupie pytania

P: : Za dużo jest tych nowych słów operacji jednocześnie. Przyjrzyjmy się dokładniej łączone, aby Twój kod mógł ich użyć.
kluczowych — from, where, orderby, poniższemu zapytaniu. To dlatego LINQ wygląda nieco dziwnie
select... Wygląda to na całkiem inny język. v a r under10 = — C# musi umieścić wiele możliwości
Dlaczego tak się on różni od pozostałej
from number in num berArray w niewielkim fragmencie kodu.
części C#?
where number < 10
O : Ponieważ inny jest jego cel. Większa s e le c t number; LINQ pozwala Ci
część składni C# została zaprojektowana do
wykonywania jednocześnie małej liczby operacji
Wygląda prosto — nie ma tu wielu rzeczy,
prawda? W rzeczywistości jest to dość
pisać zapytania,
lub obliczeń. Możesz rozpocząć pętlę, ustawić
zmienną, obliczyć matematyczne wyrażenie lub
złożony fragment kodu. Pomyśl, co musi które wykonują
się stać w programie, abyś mógł wybrać
wywołać metodę... Wszystko to są pojedyncze z numberArray wszystkie liczby, które są skomplikowane
operacje. mniejsze niż 10. Po pierwsze, musisz przejrzeć
LINQ wygląda nieco inaczej, ponieważ jego całą tablicę. Potem każda liczba porównywana operacje, używając
pojedyncze zapytanie zwykle wykonuje wiele jest z 10. Na końcu wszystkie te wartości są
niewielkiej ilości kodu.
jesteś tutaj ► 695
Rozszerz aplikację Janka To jest przyktad separacji zagadnień. Możesz
modyfikować zawartość obiektu ComicQueryManager
bez wprowadzania zmian w kodzie XAML oraz
w kodzie_krytym, gdyż kod zapytań LINQ
Dodaj nowe zapytania do aplikacji Janka jest w nim dobrze hermetyzowany.

Ja n e k je st ciekaw, w jaki sposób L IN Q m oże m u p o m ó c w zarządzaniu danym i o kom iksach.


D odaj do aplikacji trzy zapytania p rzed staw io n e n a p o p rzed n iej stro n ie, by pokazać
m u, co p o trafi L IN Q . W szystko, co m usisz w tym celu zrobić, to zm odyfikow ać klasę
Com icQ ueryM anager (o raz d odać nowy o b raz do k atalog u A ssets). Z acznij o d d o d an ia trzech
obiektów C om icQuery do inicjalizatora o k reślającego w arto ść właściwości A v a ila b le Q u e r ie s :

p r i v a t e v o id U p d a te A v a ila b le Q u e rie s () {
A v a ila b le Q u e rie s = new O bservableC o llectio n < C o m icQ u ery > { 4- ^
new ComicQuery("LINQ u ła tw ia z a p y ta n ia " , " P r o s te z a p y ta n ie " , Z r ó b to !
"Pokażmy Jankowi ja k e la s ty c z n a j e s t te c h n o lo g ia LINQ",
C re a te Im a g e F ro m A sse ts("p u rp le _ 2 5 0 x 2 5 0 .jp g ")),
t *

new C om icQ uery("D rogie kom iksy", "Komiksy powyżej 500 z ł . " ,
"Komiksy o w a rto ś c i p r z e k r a c z a ją c e j 500 z ł . "
+ " Jan ek może użyć ty c h danych do w y b ran ia n a jb a r d z ie j "
+ " pożądanych kom iksów .",
C re a te Im a g e F ro m A sse ts("c a p ta in _ a m a z in g _ 2 5 0 x 2 5 0 .jp g ")),

new Com1cQuery("LINQ je s t wszechstronne 1", "Modyfikuje wszystkie zwracane dane",


"Ten kod doda łańcuch znaków na końcu każdego tekstu przechowywanego w ta b lic y ." ,
CreateImageFromAssets("bluegray_250x250.jpg")),

Dodaj te trzy new Com1cQuery("LINQ je s t wszechstronne 2 ", "Wykonuje o bliczenia na kolekcjach",


zapytania,by "LINQ udostępnia metody rozszerzające dla k o le k c ji (oraz wszystkich Innych"
pojawiły się + " typów Implementujących In te rfe js IEnumerable<T>).",
na stronie
głównej CreateImageFromAssets("purple_250x250.jpg")),
aplikacji.
new ComicQuery("LINQ je s t wszechstronne 3",
"Zapisuje całe wyniki lub 1ch część w nowej sekwencji",
"Czasami będziesz ch cia ł zachować wyniki zapytania, by 1ch"
+ " użyć w p rz y s z ło ś c i.",
CreateImageFromAssets("bluegray 250x250.jpg")),
};

T eraz musisz zm odyfikować instrukcję s w itc h , żeby wykonywać zapytania, kiedy zostaną zaznaczone w kontrolce L istV iew :
p u b lic v o id U pdateQ ueryR esults(C om icQ uery q u ery ) {
T i t l e = q u e r y .T i t l e ;
sw itc h ( q u e r y .T i tl e ) {
c a se "LINQ u ła tw ia z a p y ta n ia " : L inq M ak esQ u eriesE asy (); b re a k ;
c a se "D rogie kom iksy": E x p en siv eC o m ics(); b re a k ;
case "LINQ je s t wszechstronne 1": L1nqIsV ersat1lel(); break; Dodaj te trzy k/auzu/e case
case "LINQ je s t wszechstronne 2": L1nqIsVersat1le2(); break; do instrukcji switch. Będą
one wykonywane przecz strrnę
case "LINQ je s t wszechstronne 3 ": L1nqIsVersat1le3(); break prezentującą inform aj s.zc.zego^n,
} ; kiedy użytkownik ją wyświet/i.
}

696 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Musisz także dodać trzy poniższe m etody. Porów naj je z zapytaniam i L IN Q przedstaw ionym i n a dwóch poprzednich stronach:

p r i v a t e v o id L in q I s V e r s a tile 1 ( ) {
s t r i n g [ ] sandw iches = { "szynka z se re m ", "salam i z m ajonezem ",
"in d y k z m u s z ta rd ą " , " k o t l e t z k u rczak a" };
v a r sandwichesOnRye =
from sandw ich in sandw iches
s e l e c t sandw ich + " na c h le b ie zbożowym";

fo re a c h (v a r sandw ich in sandwichesOnRye)


C u rren tQ u ery R esu lts.A d d (C reateA n o n y m o u sL istV iew Item (san d w ich , "b lu e g ra y _ 2 5 0 x 2 5 0 .jp g " ));
}

p r i v a t e v o id L in q I s V e r s a tile 2 ( ) {
Będziesz musiał znaleźć ten plik
Random random = new Random(); w przykładach dołączonych do książki
L is t< in t> listO fN u m b ers = new L is t < i n t> ( ) ; i skopiować go do folderu Assets.
i n t le n g th = ran d o m .N ex t(5 0 , 150);
f o r ( i n t i = 0 ; i < le n g th ; i++)
list0 fN u m b e rs.A d d (ra n d o m .N e x t(1 0 0 ));

C u rrentQ ueryR esu lts.A d d (C reateA n o n y m o u sL istV iew Item (


S trin g .F o rm a t(" N a l i ś c i e z n a jd u je s i ę {0} l i c z b " , lis tO fN u m b e r s .C o u n t() ))) ;
C u rre n tQ u e ry R e su lts.A d d (
C re a te A n o n y m o u sL istV ie w Ite m (S trin g .F o rm a t("N a jm n ie jsz a z n ic h to {0}", lis tO fN u m b e rs .M in ())));
C u rre n tQ u e ry R e su lts.A d d (
C reateA n o n y m o u sL istV iew Ite m (S trin g .F o rm a t("N a jw ię k sz a z n ic h to {0}", lis t0 fN u m b e rs .M a x ()) ));
C u rre n tQ u e ry R e su lts.A d d (
C reateA no n y m o u sL istV iew Item (S trin g .F o rm at("S u m a wynosi {0}", lis tO fN u m b e rs .S u m ())));
C u rrentQ ueryR esu lts.A d d (C reateA n o n y m o u sL istV iew Item (
S tr in g .F o rm a t( " Ś re d n ia wynosi {0 :F 2 } ", lis tO fN u m b e r s .A v e ra g e ()) ));
}

p r i v a t e v o id L in q I s V e r s a tile 3 ( ) {
L is t< in t> listO fN u m b ers = new L is t< in t> ( ) Z r ó b to!
f o r ( i n t i = 1 ; i <= 10 0 0 0 ; i++)
listO fN u m b e rs.A d d (i); Aby powyższy kod zadziałał, musisz dodać do
Ćwiczenie niego jeszcze jedną metodę. Każda z nowych
metod LinqIsV ersatile wywołuje metodę
v a r u n d e r5 0 s o rte d =
o nazwie CreateAnonymousListViewItem(). Jej pierwszym
from number in listO fN u m b ers parametrem jest tytuł, który powinien zostać użyty jako
w here number < 50 wartość właściwości T itle nowego obiektu anonimowego.
o rd e rb y number d esc e n d in g Drugi parametr jest opcjonalny. Jest nim nazwa pliku
s e l e c t number; obrazka, który należy wczytać do właściwości Image
obiektu anonimowego, a jej domyślną wartością ma być
v a r f i r s t F i v e = u n d e r5 0 s o rte d .T a k e (6 ) ; purpie_250x250.jpg. Należy jej użyć, jeśli nie zostanie jawnie
podana w wywołaniu. Czy potrafisz napisać kod tej metody?
L is t< in t> s h o r t L i s t = f i r s t F i v e . T o L i s t ( ) ;
Odpowiedź znajdziesz na następnej stronie.
fo re a c h ( i n t n in s h o r t L i s t ) _______________________
C u rren tQ u e ry R esu lts.A d d (C re a te A n o n y m o u sL istV ie w Ite m (n .T o S trin g () z ll
"b lu e o ra v 250x250

jesteś tutaj ► 697


M a ła p o w tó rk a

C E L N E S P O S T R Z E Ż E N IA

■ fro m pozw ala Ci w skazać sekw encję ■ s e l e c t ok reśla, co znajdzie się w zbiorze
IE num erable<T >, n a któ rej w ykonujesz wynikowym ( s e l e c t v a lu e ).
zapytanie. P o niej zawsze w ystępuje nazw a ■ T a k e () pozw ala Ci p o b ra ć początkow e elem enty
zm iennej, p o te m 1 n , a n a stęp n ie nazw a sekw encji z w yniku zapytania L IN Q ( r e s u l t . T a k e ( 1 0 ) ) .
(fro m v a lu e in v a lu e s ). L IN Q u d o stę p n ia Ci tak że inne m etody
■ w h ere g en eraln ie łączy się z frazą from . T o w tym dla każdej sekw encji: M in (), M ax(), Sum()
m iejscu używasz zwykłych w arunków C # w celu i A v e ra g e ().
p rzek azan ia L IN Q , k tó re elem en ty pow inny ■ M ożesz użyć s e l e c t w odniesieniu do wszystkiego
zostać p o b ra n e z kolekcji (w here v a lu e < 1 0 ). — nie jesteś ograniczony do w yboru nazwy, którą
■ o rd e rb y pozw ala Ci posortow ać wyniki. nadałeś w e frazie from. O to przykład: jeżeli
Z araz po nim um ieszczasz kryteria używane Tw oje zapytanie L IN Q p o b iera zestaw cen
do sortow ania i opcjonalne słowo descend'jng , z tablicy i n t i w e frazie from nazwiesz je
które nakazuje odw rócić jego p o rząd ek (o rd e rb y v a lu e , m ożesz zwrócić ich kolekcję w postaci
v a lu e d e sc e n d in g ). łańcuchów znaków w sposób następujący:
To jest tak jak z {0:x}, którego utywateś w rozdziale 9. s e l e c t S t r i n g .F o r m a t ( " { 0 : c } " , v a lu e ) .
podczas tworzenia programu do wyświetlania postaci
szesnastkowej pliku. Istnieje także {0:d} i {°-D} dla krótkiego
i długiego formatu daty oraz {°.P} i {°:Pn} do wypisywania
wartości w procentach (z liczbą n miejsc po przecinku).

p riv a te object CreateAnonymousListViewItem(string t i t l e ,


strin g imageFilename = "purple_250x250.jpg1) {
Rozwiązanie return new {
T it le = t i t l e , To jest parametr
ćwiczenia
Image = CreateImageFromAssets(imageFilename), opcjonalny. Jeśli zostanie
}; pominięty, metoda użuje
} domyślnej nazwy pliku.

i Nie. istnieją.
głupie pytania
P Jak działa fraza
: from? Taka pętla foreach tymczasowo tworzy P: : W jaki sposób LINQ decyduje,
co umieścić w zbiorze wynikowym?
O : Zachowuje się ona jak pierwszy wiersz
zmienną o nazwie i , do której kolejno

w pętli foreach. Tym, co utrudnia zrozumienie


przypisywane są elementy z kolekcji values.
Przyjrzyj się teraz frazie from w zapytaniu LINQ O : To właśnie do tego służy fraza s e le c t.
zapytań LINQ, jest to, że nie wykonują one tylko działającym na tej samej kolekcji: Każde zapytanie LINQ zwraca sekwencję,
jednej operacji tak jak większość instrukcji C#. a każdy jej element ma ten sam typ. W ten
from i in v a lu e s
Zapytanie LINQ wykonuje tę samą operację sposób dokładnie określasz, co ta sekwencja
Fraza ta robi praktycznie to samo. Tworzy powinna zawierać. Podczas wykonywania
wielokrotnie, na każdym elemencie kolekcji.
tymczasową zmienną i , po czym przypisuje zapytania na tablicy lub liście pojedynczego
Fraza from ma dwa zadania: wskazuje LINQ,
do niej kolejne elementy kolekcji valu e s. Pętla typu — na przykład tablicy in t lub
na której kolekcji ma operować zapytanie,
foreach wykonuje na każdym z elementów L is t< s trin g > — oczywiste jest, co zostanie
i przypisuje nazwę dla każdego elementu,
ten sam blok kodu, podczas gdy LINQ stosuje umieszczone we frazie s e le c t . Co jednak
który jest w tym zapytaniu przetwarzany.
te same kryteria przy przekazywaniu każdego wtedy, gdy pobierasz dane z listy obiektów
Sposób, w jaki from tworzy nową nazwę dla z nich do frazy where, która z kolei określa, Comic? Mógłbyś zrobić to samo co Janek i pobrać
każdego elementu kolekcji, jest podobny do czy umieścić go w zbiorze wynikowym, czy nie. całą klasę. Możesz także zmienić ostatni wiersz
tego w pętli foreach. Oto jej pierwszy wiersz: Trzeba jednak przy tym pamiętać, że zapytania zapytania na s e le c t comic.Name, aby zwrócić
fo re a c h ( i n t i in v a lu e s ) LINQ są jedynie metodami rozszerzającymi. kolekcję elementów typu s t r in g , lub wykonać
Wywołują one metody, które wykonują za s e le c t co m ic.Issu e , by otrzymać kolekcję
nie całą pracę. Równie dobrze mógłbyś je wartości typu in t.
wywoływać sam — bez korzystania z LINQ,
698 Rozdział 14.
Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Magnesy LIN Q
Poprzestawiaj magnesy tak, aby po uruchomieniu

c
programu pokazany został tekst zaprezentowany
na dole strony.

pigeon descending
]
Console.WriteLineÇ'Ruszaj przed siebie drogą numer {0}", \

[ wease1s.Sum() |

I (pigeon !- 36 && pigeon < 50) I


— 2 1
W ynik: 1 { 36, 5, 91, 3, 41, 69, 8 } ; [

Ruszaj przed s i e b i e drogą numer 66

jesteś tutaj ► 699


C z y je s t e ś e n t u z ja s t ą L IN Q ?

Magnesy LIN Q . Rozwiązanie


Poprzestawiaj magnesy tak, aby po uruchomieniu
programu pokazany został tekst zaprezentowany
na dole strony. LINQ rozpoczyna działanij
od kolekcji lub tablicy — _
w tym przypadku od tablicy
liczb całkowitych.

in t [ ] badgers = { 36, 5, 91, 3, 41, 69, 8 };

a ^ w pigeon^in badgers" pasuje do układanki,


bOdh* czyt jest
badger in badgers" elne zapytanie
lepsze. LINQ. „from
om

Ta instrukcja LINQ pobiera


z tablicy wszystkie licz-by,
które są mniejsze miż 50
Po wykonaniu tej i różne od 36, doda.je do ^
instrukcji skunks każdej z nich 5, sortuje je
zawiera cztery liczby od największej do najmniejszej
46, 13, 10 i 8 * i wstawia do nowej kolekcji
skunks.

| var bears = T° tutaj p ^ ^ a m y pierwsze trzy


Po wykonaniu tej liczby z kolekcji skunks i wstawiamy je
instrukcji bears 1 skunks | .Take(3);__ ^ nowej k° |ekcj i o nazwie bears.
ł '
zawiera trzy

Ta instrukcja odejmuje 1 od każdej


liczby w kolekcji bears, a wynik
Po wykonaniu tej wstawia do weasels.
instrukcji weasels
zawiera trzy liczby
45, 1 2 i 9 . y'

| Console.WriteLine("Ruszaj przed siebie drogą numer {0 }",

weasels.Sum()
1 Po zsumowaniu liczb

45+12+9 = 66
W ynik:
Ruszaj przed s i e b i e drogą numer 66

700 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

LINQ może połączyć Twoje wyniki wgrupy Testując aplikację sym ulatora ula, m ożesz
przekonać się, ja k to zapytanie LIN Q działa
w praktyce (a jednocześnie dowiedzieć się
Już wiesz, że m ożesz użyć L IN Q w celu p o g ru p o w an ia wyników, czegoś w ięcej o sposobie działania aplikacji
poniew aż uczyniliśm y to w sym ulatorze ula. Przyjrzyjm y się W in Fo rm s). Sym ulator ula zna jd ziesz
dokładniej tem u zapytaniu i zobaczm y, jak o n o działa. w przykładach dołączonych do książki.

R ozpoczyna się o n o tak ja k inne zapytania, k tó re już


w idziałeś — o d p o b ra n ia pojedynczych obiektów bee
var beeGroups = z kolekcji w o rld .B e e s będącej obiektem L ist< B ee> .

from bee in world.Bees N astęp n y w iersz zaw iera słowo kluczow e g ro u p .


N akazuje o n o zw rócić grupy pszczół. O znacza
to, że zam iast zw racać pojedynczą sekw encję,
zapytanie zw róci sek w en cję sekw encji. g ro u p bee
group bee by bee.CurrentState
by b e e .C u r r e n tS ta te każe utw orzyć je d n ą grupę
dla każdej unikatow ej właściwości C u r r e n t S ta t e ,
k tó ra znajduje się w zbiorze w ybranych pszczół.
into beeGroup N a k o ń cu m usim y p rzek azać L IN Q nazw ę dla
takiej grupy. T o tem u służy następ n y w iersz: i n t o
beeGroup w skazuje, że nazw a beeG roup odn o si się
orderby beeGroup.Key do nowej grupy.

select beeGroup; T eraz, gdy już m am y grupy, m ożem y nim i m anipulow ać.
W związku z tym , że zw racam y kolekcję grup, m ożem y
użyć słowa kluczow ego o rd e r b y , aby poso rto w ać je
w edług w artości typu w yliczeniow ego C u r r e n t S t a t e
( I d l e , F ly in g T o F lo w e r itp.). o rd e rb y beeG roup.K ey
nak azuje up o rząd k o w ać sekw encję gru p n a podstaw ie
N astęp n ie m usim y skorzystać ze słow a kluczow ego ich klucza w p o rz ąd k u rosnącym . Poniew aż
s e l e c t , aby w skazać, co m a zostać zw rócone pogrupow aliśm y pszczoły w edług C u r r e n t S ta t e ,
z zapytania. Z w racam y grupy, w ięc w ybierzem y to w łaśnie to p o le zostanie użyte jak o klucz.
nazw ę grupy: s e le c t b e e G ro u p ; .

W związku z tym, że pszczoły pogrup°wane


są według stanu, stan ten będ^e^ nazywrn
kluczem. Kluczem grupy jest kryterium, które
\ CurrentState
Curre = MakingHoney
posłużyło do grupowania elementów.
beeGroup
| CurrentState = FlyingToFlower |
rbeeGroups
\ N ii^ Ł
beeGroup

KoleV-“ S lS Zwróć uwagę na to, że zapytanie


zwraca grupy pszczół, a nie
intState = GatheringNectar pojedyncze ich obiekty.

eElekcja
5
jesteś tutaj ► 701
K lu c z d o s u k c e s u

Połącz wartości Janka wgrupy


Janek k u p ił mnóstwo tanich komiksów, trochę w średniej cenie i kilka naprawdę drogich. Chce teraz, przed decyzją o kupnie
nowych, wiedzieć, jakie ma możliwości. Pobrał cennik z listy Grzegorza i wstawił do słownika D ic tio n a r y < in t, d ecim al> ,
używając metody G e tP r i c e s ( ) . Skorzystajmy z mocy L IN Q i podzielmy kom iksy na trzy grupy: jedną dla tanich, poniżej 100 zł,
jedną dla średnich, które kosztują pomiędzy 100 a 1000 zł, i jedną dla drogich, kosztujących więcej niż 1000 zł. U tworzym y typ
wyliczeniowy P riceR an g e , któ ry będzie używany jako klucz w grupach. Dodam y także metodę E v a lu a t e P r ic e ( ) , która sprawdzi
cenę i zwróci wartość typu P riceR an g e .

(JO K a ż d a g ru p a potrzebuje klu cza — u żyjem y w te* roli typu w yliczeniow ego.
Klucz grupy to coś wspólnego dla wszystkich je j składowych. Może on mieć dowolną postać: łańcucha znaków,
liczby, a nawet refere nq i obiektu. Poszukujemy cen, które Janek uzyskał z listy Grzegorza. Każda grupa zwrócona
przez zapytanie będzie sekwencją numerów komiksów. Jej kluczem będzie typ wyliczeniowy PriceR ange. M etoda
Eval u a te P r i ce () pobiera cenę w postaci param etru i zwraca P ri ceRange:
public enum PriceRange { Cheap, Midrange, Expensive } Spróbuj dodać ten kod do nowej aplikacji
public static PriceRange EvaluatePrice(int price) { konsolowej — przekonajmy się,
if (price < 100M) return PriceRange.Cheap; czy będziesz w stanie go uruchomić!
else if (price <= 1000M) return PriceRange.Midrange; Pod koniec rozdziału dodasz go do
else return PriceRange.Expensive; nowej aplikacji dla Sklepu Windows.
}
M o żem y te ra z p o g ru p o w ać kom iksy n a p o d staw ie kryterium cen o w eg o .
Zapytanie L IN Q zwraca sekw encję sekw encji . Każda grupa w zbiorze wynikowym posiada właściwość Key,
która odpowiada wartości typu P riceR ange zwróconej przez E v a lu a t e P r ic e ( ) . Przypatrz się dokładnie frazie group by
— pobieramy ze słownika pary i używamy nazwy p a i r dla każdej z nich: p a ir.K e y jest numerem komiksu, p a ir .V a lu e
jest ceną z listy Grzegorza. Dodanie group p a ir.K e y nakazuje L IN Q utworzyć kolekcje numerów, a następnie połączyć
je na podstawie kategorii cenowej zwróconej przez E v a lu a t e P r ic e ( ) :
Dictionary<int, decimal> values = GetPrices();
Zapytanie określa, do której grupy
należy dany egzemplarz, przekazująca
var priceGroups =
from pair in values
do EvaluatePrice() j ego cenę. Metoda
group pair.Key by EvaluatePrice(pair.Value)
zwraca typ wyliczeniowy ^ c e R a n ^
in t o priceGroup który jest używany jako klucz grupy.
orderby priceGroup.Key descending
select priceGroup;

foreach (var group in priceGroups) {


string stringKey;
switch (group.Key)
{
case PriceRange.Cheap:
stringKey = "tanie";
break;
case PriceRange.Midrange:
stringKey = "średnie";
break;
default:
stringKey = "drogie";
break;
}

Console.Write("Znalaztem {0} {1} komiksy: numery ", group.Count(), stringKey);


foreach (var issue in group)
Console.Write(issue.ToString() +
Console.WriteLine(); Wynik:
Z n a la z łe m 2 d r o g i e k o m ik sy : n um ery 6 , 57
Każda, z tych grup jest sekwencją. Dodahśmy _ Z n a la z łe m 3 ś r e d n i e k o m ik sy : n um ery 1 9 , 3 6 , 68
zatem wewnętrzną pętlę foreach, kt° rej zada.mem
jest pobranie z grupy numerów wyda.n. Z n a la z łe m 3 t a n i e k o m ik sy : n um ery 7 4 , 8 3 , 97

702 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Zagadkowy basen
Tw oim z a d a n ie m je st p o b ra n ie fragm entów kodu
z basen u i w staw ienie ich w p u ste m iejsca var
w kodzie. M ożesz użyć tego sam ego from
frag m en tu więcej niż raz i nie musisz
l i n e by l i n e .
w ykorzystać ich wszystkich. C e le m jest
n apisanie k o d u , k tóry wypisze taki oto in to wordGroups
komunikat: o rd e rb y _________
s e l e c t __________
H o r s e s e n j o y e a t i n g c a r r o t s , b u t t h e y lo v e e a t i n g a p p le s .

= w ords. ( 2 );
c l a s s L in e {
p u b lic s t r i n g [ ] Words;
p u b lic i n t V alue; fo re a c h (v a r group in twoGroups)
p u b lic L in e ( s tr i n g [ ] Words, i n t V alue) {
{
th is .W o rd s = Words; th i s .V a lu e = V alue;
I in t i = 0 ;
Podpowiedz: LINQ sortuje łańcuchy
I znaków w porządku alfabetycznym. fo re a c h (_________ in n e r in __ ) {
i++;
L in e[] l i n e s = {
new L in e( new s t r i n g [ ] { " e a t in g " , " c a r r o t s , " , if (i == .Key) {
" b u t" , " e n jo y " , "H orses" }, 1 ), v a r poem =
new L in e( new s t r i n g [ ] { " z e b r a s ? " , "h ay ",
word in
"Cows", " b r i d g e ." , " b o lte d " }, 2 ) ,
new L in e( new s t r i n g [ ] { " f o r k " , "d o g s !" , _ word d e sc e n d in g
"E n g in e", "and" }, 3 ) , word + ;
new L in e( new s t r i n g [ ] { " lo v e " , " th e y " ,
fo re a c h (v a r word in
" a p p l e s ." , " e a tin g " }, 2 ) ,
new L in e( new s t r i n g [ ] { " w h is tle d ." , "Bump" }, 1) }; C o n so le .W rite (w o rd );

Przypominamy:
każdy fragm ent kodu
z basenu może zostać
użyty więcej niż raz.

jesteś tutaj ► 703


D w ie k o le k c je w je d n e j z m ie n n e j

Zagadkowy basen. Rozwiązanie

c la s s Line {
p u b lic s t r i n g [ ] Words;
p u b lic in t V alue;
p u b lic L in e ( s tr in g [ ] Words, i n t V alue) {
th is.W o rd s = Words; th i s .V a lu e = V alue;
}
}

L ine[] lin e s = {
new L ine( new s t r i n g [ ] " e a tin g " , " c a r r o t s , " , " b u t" , " e n jo y " , "H orses" } , 1 ),
new L ine( new s t r i n g [ ] " z e b ra s ? " , "h ay ", "Cows", " b r id g e ." , "b o lte d " } , 2 ) ,
new L ine( new s t r i n g [ ] " f o rk " , " d o g s !", "E n g in e", "and" }, 3 ) ,
new L ine( new s t r i n g [ ] " lo v e " , " th e y " , " a p p l e s ." , " e a tin g " }, 2 ) ,
new L ine( new s t r i n g [ ] " w h is tle d ." , "Bump" }, 1 )
};

v a r words =
from line in lines To_pierwsze zapytanie LINQ dzieli
obiekty Line w tablicy lines!) na grupy
group l i n e by l i n e . Value na podj tawie ich pola Value, w Fosnlcym
in to wordGroups porządku klucza Value. y
ord erb y wordGroups. Key
s e l e c t wordGroups;

¿f Dwie pierwsze grupy to linijki


var twoGroups = w ords. Take( 2 ) ; *C~----- z polami Value równymi 1 ■ 2.

fo reach (v a r group in twoGroups)


{ Ta pętla wykonuje zapytanie
LINQ na pierwszym obiekcie
in t i = 0 ;
Linę w pierwszej grupie
fo reach (var in n e r in group) { i na drugim w drugiej.

i f (i == group.Key) {
v a r poem =
from word in inner. Words
orderby word d escending Czy zwróciłeś uwagę na
select word + " " ; to, że dwie frazy: „Horses
enjoy eating carrots, buf
fo reac h (v a r word in poem) i „they love eating apples",
C o n so le .W rite (w o rd ); posortowane są w kolejności
odwrotnej do alfabetycznej?
}

Wynik: Horses enjoy eating carrots, but they love eating apples.

704 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Użyj join do połączenia dwóch kolekcji wjedną sekwencję


Ja n e k otrzym ał całą kolekcję zakupionych kom iksów i chce te ra z poró w n ać ceny jej elem en tó w z cenam i n a liście
G rzegorza. M usi spraw dzić, czy w ynegocjow ał lepsze, czy gorsze. Swoje zakupy um ieszczał w klasie P u rc h a s e
zaw ierającej dwie au tom atyczne właściwości: I s s u e i P r i c e . O trzym ał o b iek t L is t< P u rc h a s e > o nazw ie p u r c h a s e s ,
który zaw iera wszystkie k u pione przez niego komiksy. M usi te ra z połączyć ceny swoich zakupów z tym i z listy
G rzegorza. W jaki sposób m oże tego dokonać?

Z p o m o cą przychodzi L IN Q ! Jego słowo kluczow e j o i n pozw ala p o łącz y ć d a n e z d w ó ch ź ró d e ł w pojedynczym


zapytaniu. D o k o n u je teg o p o p rz e z p o ró w n an ie elem en tó w w kolekcji pierw szej z odpow iadającym i im elem en tam i
w drugiej. (L IN Q je st w ystarczająco sprytny, aby zrobić to spraw nie i w ydajnie — nie p o ró w n u je każdej pary
elem entów , chyba że m usi). W ynikiem jest lista zaw ierająca każd ą zgodną p arę.
Janek u m ieścit sw oje dane —\
R ozpocznij swoje zapytanie o d frazy fro m , je d n a k w następnej w kolekcji obiektów Purchase )
kolejności, zam iast um ieszczać k ry teria używ ane do zaw ężenia ' nazvja t je purchases. .1
zbioru w ynikow ego, dodaj:
jo1n name 1n c o lle c tio n cla ss Purchase {
public in t Issue
F raza j o i n n akazuje L IN Q p rzejść przez w szystkie elem enty { get; set;
dw óch kolekcji, ta k aby połączyć je w p a ry n a podstaw ie jednej public decimal Price
ich składowej. P odczas każdej iteracji p o le name przypisyw ane { get; set;

Q
jest n a podstaw ie wyników pow stałych z p o łączen ia kolekcji. }
P ole to będzie używ ane w e frazie w h ere.

Janek łączy komiksy


z purchases, czyli
z listą tych, które
kupił.
O .L is t^
Kr

N astępnym k rokiem jest


d o d an ie frazy on , k tó ra określa,
w jaki sposób L IN Q m oże
on c o m i c .I s s u e
połączyć kolekcje. Z a ra z p o niej
e q u a ls p u r c h a s e . I s s u e um ieścisz nazw ę składowej
Po select new umieszczono z pierw szej kolekcji, p o tem
Z apytanie L IN Q będzie nawiasy klamrowe, słowo e q u a ls , a n a stęp n ie nazwę
kontynuow ane ta k ja k zwykle które zawierają dane
do zwrócenia w zbiorze kolekcji drugiej, w któ rej będą
przez frazy w here i o r d e r b y . wynikowym. w yszukiw ane elem en ty do pary.
M ożesz także zakończyć je frazą
s e l e c t , ale zwykle będziesz chciał s e l e c t new { c o m ic .N a m e , c o m ic .
p o b rać tylko część danych z jednej I s s u e , p u r c h a s e .P r ic e }
i z drugiej kolekcji. W takim r■ ~results \ i
przypadku używasz s e le c t new
w celu utw orzen ia w łasnego zbioru
wyników, korzystając z ty p u ^o/ekc.)*
a n o n im o w eg o .
Zajrzyj do punktu 9. dodatku „Pozostałości",
aby znaleźć więcej informacji o typach anonimowych. jesteś tutaj ► 705
J a n e k j e s t łą c z n ik ie m

Janek zaoszczędził mnóstwo szmalu


W ygląda n a to, że Ja n e k tw ard o walczył o swoje. Stw orzył listę obiektów
P u rc h a s e , k tó ra zaw iera zbiór zakupów , i p o ró w n ał jej zaw artość
z cenam i znalezionym i n a liście G rzegorza.

[n N A JP IE R W JA N E K U T W O R Z Y Ł K O L E K C JE D O P O Ł Ą C Z E N IA .
Ja n e k m a już je d n ą kolekcję — użył m eto d y B u ild C a ta lo g ( ) napisanej nieco wcześniej.
T eraz m usi tylko n apisać m eto d ę F in d P u r c h a s e s ( ) , k tó ra utw orzy listę obiektów P u rc h a se .

s t a t i c IE num erable<P urchase> F in d P u rc h a se s() {


L ist< P u rc h a se > p u rc h a s e s = new L ist< P u rc h a s e > () {
Za numer 57 Janek
new P u rch aseQ Is su e P ric e 225M },
To statyczna zapłacił 13 215 zł.
new P u rch aseQ Is su e 19, P ric e 375M },
metoda klasy new P u rch aseQ Is s u e 6 , P ric e = 3600M },
Purchase. new P u rch aseQ Is su e 57, P ric e 13215M },
new P u rch aseQ Is s u e 36, P ric e 660M },
};
r e t u r n p u rc h a s e s ;
}

g T E R A Z M O Ż E JE P O Ł Ą C Z Y Ć !
Już w idziałeś wszystkie części tego zap y ta n ia ... O to i one, tym razem w jednym kawałku.

IEnumerable<Comic> com ics = B u ild C a ta lo g ( );


D ic tio n a r y < in t, d ecim al> v a lu e s = G e tP r i c e s ( ) ;
IE num erable<P urchase> p u rc h a s e s = P u rc h a s e .F in d P u rc h a s e s ()
var re s u lts =
from comic in com ics
j o i n p u rc h a se in p u rc h a s e s
on c o m ic .Is s u e e q u a ls p u r c h a s e .I s s u e comic.Issue je s t rowne purchase.Issue.
o rd e rb y c o m ic .Is s u e asc e n d in g
s e l e c t new { comic.Name, c o m ic .Iss u e p u r c h a s e .P r ic e };
decim al g re g s L is tV a lu e = 0; Fraza select new tworzy nowy
decim al to t a lS p e n t = 0; zbiór wynikowy zawierający Name
fo re a c h (v a r r e s u l t in r e s u l t s ) { i Issue składowej comic oraz Price
g re g s L is tV a lu e += v a l u e s [ r e s u l t . I s s u e ] ze składowej purchase.
to t a lS p e n t += r e s u l t . P r i c e ;
C o n so le.W riteL in e("N u m er {0} ({1}) kupiony za { 2 :c } ." ,
r e s u l t . I s s u e , re s u lt.N a m e , r e s u l t . P r i c e ) ;
}
C onsole.W riteL ine("W ydaJem {0:c} na komiksy w a rte { l : c } . " ,
t o t a l S p e n t , g re g s L is tV a lu e );

Janek jest naprwdę


szczęśliwy, że zna Wyniki:
LINQ. Dzięki temu Numer 6 : (J o h n n y A m e ric a v s . t h e P in k o ) k u p io n y z a 3 6 0 0 ,0 0 z ł .
mógł zobaczyć, j ak
twardo negocjował! Numer 19 (R ock an d r o l i ( e d y c j a l i m i to w a n a ) ) k u p io n y z a 3 7 5 ,0 0 z ł .
Numer 36 (W om an's Work) k u p io n y z a 6 6 0 ,0 0 z ł .
Numer 57 ( H ip p ie M adness ( ź l e w y d ru k o w an y )) k u p io n y z a 13 2 1 5 ,0 0 z ł .
Numer 68 (R ev en g e o f t h e New Wave F re a k ( u s z k o d z o n y )) k u p io n y z a 2 2 5 ,0 0 z ł .
Wydałem 18 0 7 5 ,0 0 z a k o m ik sy w a r te 18 5 2 5 ,0 0 z ł .

706 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

CELNE SPOSTRZEŻENIA --------------------

■ F raza g ro u p n ak azu je L IN Q pogrupow ać ■ Użyj frazy j o i n , aby zm usić L IN Q do p ołączenia


wyniki — p o w ykonaniu instrukcji L IN Q tworzy dwu kolekcji w jednym zapytaniu. Podczas
sekw encję grup sekwencji. takiego łączenia L IN Q po ró w n u je każdą
składow ą z pierw szej kolekcji z k ażd ą składow ą
■ K ażda g ru p a zaw iera składow ą w spólną dla z drugiej i jak o w ynik zw raca p a ry zgodne.
wszystkich nazyw aną k lu c z e m . W celu określenia
klucza danej grupy użyj słowa b y . K ażda ■ Podczas w ykonyw ania zapytania j o i n zwykle
sekw encja grupy p o siad a składow ą Key , k tó ra chcesz um ieścić w zbiorze wynikowym część
zaw iera jej klucz. składowych z pierw szej kolekcji i część z drugiej.
F raza s e le c t new pozw ala tw orzyć w łasne zbiory
■ Ł ączenie realizow ane je st p o p rzez frazę on . . . w ynikowe n a podstaw ie kolekcji wynikowej.
e q u a ls . D zięki te m u L IN Q w ie, w jaki sposób
dobierać p ary elem entów . ■ Użyj s e le c t new, by określić now ą po stać
wyników zapytania, k tó re b ę d ą zaw ierać
w yłącznie w ybrane składowe.

Dodaj dwa ostatnie zapytania LINQ do aplikacji Janka.

[n DODAJ OBIEKT COMICQUERY DO METODY UPDATEAVAILABLEQUERIES().


Ćwiczenie
Z aktualizuj inicjalizator o b iek tu A v a ila b l e Q u e r ie s tak, by tw orzył dodatkow o dw a now e obiekty
Com icQuery, k tó re dzięki tem u zo stan ą d o d an e do strony głównej. O to ja k pow inny wyglądać
now e przyciski:
Czy potrafisz Grupuj komiksy według zakresu cen
wymyślić, Pogrupuj komiksy Janka według cen
w jaki sposób Janek kupuje dużo tanich komiksów, trochę średniej wartości i pojedyncze sztuki drogich, jednak przed zakupem chciałby wiedzieć, jakie ma
sprawić, by możliwości.
w tytule strony
były wyświetlane
informacje o tym Połącz zakupy z cenami
ile Janek wydal Przekonajmy się, czy Janek ostro się targuje

pieniędzy i jak To zapytanie tworzy listę obiektów Purchase zawierających zakupy Janka i porównujeje z cenami z Listy Grzegorza.
dużo są warte
kupione przez
niego komiksy?

DODAJ METODY DO W YKO N ANIA ZAPYTAŃ I AKTUALIZACJI


WYŚWIETLANYCH INFORMACJI.
Będziesz także potrzebow ał klasy P u rc h a s e o raz m etody E v a lu a t e P r ic e ( ) przedstaw ionych
kilka stron wcześniej. N ie zapom nij także o d o daniu do p ro jek tu typu wyliczeniowego P riceR an g e.
M eto d ę E v a lu a t e P r ic e ( ) pow inieneś dodać jako statyczną m eto d ę klasy P u rc h a se .

DODAJ NOWE ZAPYTANIA DO METODY UPPATEOUERYRESULTSO.


K iedy już d odasz dwie m etody nowych zapytań do instrukcji s w itc h w m etodzie
U p d a te Q u e r y R e s u lts ( ) , aplikacja zacznie ich używać.

jesteś tutaj ► 707


N ie is t n ie ją g łu p ie z a p y t a n ia

■Nie .istnieją.
głupie pytania

P Nie mogę pojąć, jak działa


: jonn . var r e s u lts = LINQ nie jest jednak tak prosty. Zwykle podczas

O : jo in działa z dwoma dowolnymi


from p la y e r in p la y e r s
where play er.N u m b er > 10
wykonywania jego instrukcji zwracany jest typ,
który niejest nigdzie w programie zdefiniowany
sekwencjami. Powiedzmy, że posiadasz kolekcję Tak, słusznie się domyślasz, że jest to jakaś
j o i n s h i r t in j e r s e y s
piłkarzy o nazwie p la y e rs — jej elementy sekwencja. Jaki jest to jednak jej rodzaj? Tego
on play er.N u m b er
mają właściwości Name, P o s itio n i Number. nie wiesz, ponieważ obiekty, które zawierają
Możemy pobrać wszystkich graczy, których e q u a ls sh irt.N u m b e r
się w wyniku wykonanego na niej zapytania
koszulki mają numer większy niż 10, za pomocą se le ct new { LINQ, całkowicie zależą od tego, co w nim
następującego zapytania: p lay er.N am e, umieścisz. Weźmy dla przykładu takie zapytanie
var r e s u lts = s h ir t.S iz e zaczerpnięte z programu Janka:
from p la y e r in p la y e r s }; v a r mostExpensive =
where player.N um b er > 10 IDE jest wystarczająco sprytne i potrafi from comic in comics
s e l e c t p la y e r ; dokładnie określić, jakie wyniki powstaną na where
Powiedzmy, że chcemy teraz pobrać rozmiar skutek Twojego zapytania. Jeśli tworzysz pętlę, v a lu e s[c o m ic .Issu e ]
koszulki każdego gracza i mamy kolekcję która będzie wykonywała iterację na wszystkich
> 500
je r s e y s , której elementy zawierają wynikach, to zaraz po wpisaniu nazwy
orderby
właściwości Number oraz S iz e . join mogłoby zmiennej IDE wyświetli okno IntelliSense
v a lu e s[c o m ic .Issu e ]
doskonale sprawdzić się w takiej sytuacji: fo re a c h (v a r r in r e s u l t s )
descending
var r e s u lts = r.
s e l e c t comic;
from p la y e r in p la y e r s
where player.N um b er > 10 ^ Equals Co się stanie, jeśli ostatni wiersz zamienisz na
jo1n s h i r t in j e r s e y s * GetHashCode poniższy?
on play er.N um b er ^ GetType_______ | s e l e c t new
equals sh irt.N u m b e r Name
{ Name = comic.Name,
s e le c t s h ir t; IssueNum ber = "#" + com ic.
* Tostring | Is s u e };
P Zaczekaj, takie zapytanie zwraca mi
: W efekcie zostanie zwrócony całkowicie
po prostu zestaw koszulek. A co wtedy, Zwróć uwagę na pola Name i Size, które są prawidłowy typ: typ anonimowy zawierający
gdy zechcę połączyć każdego gracza w środku. Jeśli we frazie s e le c t new umieścisz dwie składowe — Name oraz IssueNumber,
z rozmiarem jego koszulki i nie będę chciał więcej elementów, to także i one znajdą się na obie typu string. Jednak w naszym programie nie
przejmować się jego numerem?
liście. To dlatego, że zapytanie utworzy inny typ mamy definicji takiego typu! Oczywiście wcale
O : Do tego właśnie służą typy anonimowe
anonimowy z innymi składowymi. nie trzeba uruchamiać aplikacji, by się przekonać,
jak ten typ został zdefiniowany. Niemniej jednak
— możesz utworzyć typ anonimowy, który
posiada tylko te dane, które chcesz. Pozwala
P : Czy mógłbyś się cofnąć i powtórzyć, w deklaracji zmiennej mostExpensive i tak
co to jest var? musimy umieścić jakiś typ.
Ci to także wskazać i wybrać te spośród wielu
kolekcji, które będziesz łączyć.
Możesz zatem wybrać nazwę gracza, rozmiar
O : Tak, oczywiście. Słowo kluczowe v ar
C# do takich celów udostępnia nam słowo
kluczowe v a r, które mówi kompilatorowi coś
rozwiązuje ciekawy problem, który pojawia się
koszulki i nic ponadto: takiego: „Dobra, wiemy, że jest to prawidłowy
w pracy z LINQ. Zwykle podczas wywoływania
typ, ale nie możemy dokładnie powiedzieć,
metody lub obliczania wartości wyrażenia
jaki on jest. Dlaczego po prostu sam tego nie
łatwo jest określić typ, z którym się spotykamy,
sprawdzisz? W ten sposób nie będziemy musieli
Jeśli posiadasz na przykład metodę zwracającą
się tym przejmować. Dziękuję bardzo".
s tr in g , wynik jej wykonania możesz przypisać
tylko do pola lub zmiennej typu s trin g .

708 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Oto kod, który musisz dodać do aplikacji Janka, by pojawiały się w niej dwa ostatnie zapytania LINQ.
Rozwiązania
ćwiczenia

p r i v a t e v o id U p d a te A v a ila b le Q u e rie s () {
A v a ila b le Q u e rie s = new O bservableC o llectio n < C o m icQ u ery > {
new ComicQuery("LINQ u ła tw ia z a p y ta n ia " , " P r o s te z a p y ta n ie " ,
"Pokażmy Jan k o w i, j a k e la s ty c z n a j e s t te c h n o lo g ia LINQ",
C re a te Im a g e F ro m A sse ts("p u rp le _ 2 5 0 x 2 5 0 .jp g ")),

new C om icQ uery("D rogie kom iksy", "Komiksy powyżej 500 z ł . " ,
"Komiksy o w a rto ś c i p r z e k r a c z a ją c e j 500 z ł . "
+ " Jan ek może użyć ty c h danych do w y b ran ia n a jb a r d z ie j "
+ " pożądanych kom iksów .",
C re a te Im a g e F ro m A sse ts("c a p ta in _ a m a z in g _ 2 5 0 x 2 5 0 .jp g ")),

new ComicQuery("LINQ j e s t w sz e c h stro n n e 1 ", "M odyfikuje w s z y s tk ie zw racane d an e ",


"Ten kod doda łań c u c h znaków na końcu każdego te k s tu przechowywanego w t a b l i c y . " ,
C re a te Im a g e F ro m A sse ts("b lu e g ra y _ 2 5 0 x 2 5 0 .jp g ")),

new ComicQuery("LINQ j e s t w sz e c h stro n n e 2 " , "Wykonuje o b li c z e n ia na k o le k c ja c h " ,


"LINQ u d o s tę p n ia metody r o z s z e r z a ją c e d la k o le k c ji (o ra z w sz y stk ic h innych"
+ " typów im plem entujących i n t e r f e j s IE n u m erab le< T > ).",
C re a te Im a g e F ro m A sse ts("p u rp le _ 2 5 0 x 2 5 0 .jp g ")),

new ComicQuery("LINQ j e s t w sz e c h stro n n e 3 ",


" Z a p is u je c a łe w yniki lu b ic h c z ę ść w nowej se k w e n c ji" ,
"Czasami b ę d z ie s z c h c ia ł zachować w yniki z a p y ta n ia , by ich "
+ " użyć w p r z y s z ł o ś c i . " ,
C re a te Im a g e F ro m A sse ts("b lu e g ra y _ 2 5 0 x 2 5 0 .jp g ")),

new Com1cQuery("Grupuj komiksy według zakresu cen",


"Pogrupuj komiksy Janka według cen",
"Janek kupuje dużo tanich komiksów, trochę średniej wartości "
+ " 1 pojedyncze sztuki drogich, Jednak przed zakupem chciałby"
+ " wiedzieć, ja k ie ma m ożliw o ści.", ^— Dodanie tych dwóch obiektów
CreateImageFromAssets("captain amazing 250x250.jpg")), — ComicQuery do inicjalizatora
właściwości AvailableQueries
powoduje, że nowe zapytania
new Com1cQuery("Połącz zakupy z cenami", _____''L IN Q pojawią się na stronie
"Sprawdźmy, czy Janek ostro się ta rg u je ", głównej aplikacji.
"To zapytanie tworzy lis t ę obiektów Purchase reprezentujących komiksy"
+ " kupione przez Janka 1 porównuje Je z cenami na U ś c ie Grzegorza.",
CreateImageFromAssets("captain_amazing_250x250.jpg")),
};

jesteś tutaj ► 709


Rozwiązanie ćwiczenia

p u b lic v o id U pdateQ ueryR esults(C om icQ uery q u ery ) {


Rozwiązania T itle = q u e ry .T itle ;
ćwiczenia
sw itc h ( q u e r y .T i tl e ) {
c a se "LINQ u la tw ia z a p y ta n ia " : L in q M ak esQ u eriesE asy(); b re a k ;
c a se "D rogie kom iksy": E x p en siv eC o m ics(); b re a k ;
c a se "LINQ j e s t w sz e c h stro n n e 1" L in q I s V e r s a ti le 1 ( ) ; b re a k ;
Oto nowe c a se "LINQ j e s t w sz e c h stro n n e 2" L in q I s V e r s a ti le 2 ( ) ; b re a k ;
klauzule case c a se "LINQ j e s t w sz e c h stro n n e 3" L in q I s V e r s a ti le 3 ( ) ; b re a k ;
dodane do case "Grupuj komiksy według zakresu cen":
instrukcji _
switch, które CombineJimmysValuesIntoGroups();
wywołują metody break;
wykonujące case "Połącz zakupy z cenami":
zapytania.
JoinPurchasesWithPrices();
break;
Nie zafp)mnij o typie
w/Rczmhwym PriceRange.
/
enum P riceR ange { Cheap, M idrange, E xpensive }

c l a s s P u rch ase {
p u b lic i n t Is s u e { g e t ; s e t ; }
p u b lic decim al P ric e { g e t ; s e t ; }

p u b lic s t a t i c IE num erable<P urchase> F in d P u rc h a se s()


{
L ist< P u rc h a se > p u rc h a s e s = new L ist< P u rc h a s e > () {
new P u rch aseQ Is su e P ric e = 225M },
Oto klasa Purchtzsis.
EvaluatePriceO jest
teraz jej metodą
i new P u rch aseQ
new P u rc h ase()
Is su e
Is s u e
19, P ric e = 375M },
6 , P ric e = 3600M },
statyczną. new P u rch aseQ Is su e 57, P ric e = 13215M },
new P u rch aseQ Is s u e 36, P ric e = 660M },
};
r e t u r n p u rc h a s e s ;

p u b lic s t a t i c P riceR ange E v a lu a te P ric e (d e c im a l p r ic e )


{
i f ( p r ic e < 100M) r e t u r n P riceR an g e.C h eap ;
e l s e i f ( p r ic e < 1000M) r e tu r n P riceR an g e .M id ran g e;
e l s e r e t u r n P ric e R a n g e .E x p e n siv e ;

710 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

p r i v a t e v o id C om bineJim m ysV aluesIntoG roups() {


D ic tio n a r y < in t, d ecim al> v a lu e s = G e tP r i c e s ( ) ;
v a r p ric e G ro u p s =
from p a i r in v a lu e s
group p a ir.K e y by P u r c h a s e .E v a lu a te P r ic e ( p a ir .V a lu e )
in t o p riceG ro u p
o rd e rb y p riceG ro u p .K ey d esce n d in g
s e l e c t p rice G ro u p ;
fo re a c h (v a r group in p ric e G ro u p s) {
s t r i n g m essage = S trin g .F o rm a t("Z n a la z T e m {0} {1} komiksow: numery ",
g ro u p .C o u n t( ), g ro u p .K ey );
fo re a c h (v a r is s u e in group)
m essage += is s u e .T o S tr i n g ( ) + " ";
C u rre n tQ u e ry R e su lts.A d d (
C reateA nonym ousL istV iew Item (m essage, "c a p ta in _ a m a z in g _ 2 5 0 x 2 5 0 .jp g " ));
}
}

p r i v a t e v o id J o in P u rc h a s e s W ith P ric e s () {
IEnumerable<Comic> com ics = B u ild C a ta lo g ( );
D ic tio n a r y < in t, d ecim al> v a lu e s = G e tP r i c e s ( ) ;
IE num erable<P urchase> p u rc h a s e s = P u rc h a s e .F in d P u rc h a s e s ();
var re s u lts =
from comic in com ics
j o i n p u rc h a s e in p u rc h a se s
on c o m ic .Is s u e e q u a ls p u r c h a s e .I s s u e
o rd e rb y c o m ic .Is s u e a sc e n d in g
s e l e c t new {
Comic = com ic,
P ric e = p u r c h a s e .P r ic e ,
T i t l e = comic.Name,
S u b t i t l e = "Numer " + c o m ic .Is s u e ,
D e s c rip tio n = S trin g .F o rm a t("K u p io n y za { 0 :c } " , p u r c h a s e .P r ic e ) ,
Image = C re a te Im a g e F ro m A sse ts("c a p ta in _ a m a z in g _ 2 5 0 x 2 5 0 .jp g "),
};

decim al g re g s L is tV a lu e = 0;
decim al to t a lS p e n t = 0;
fo re a c h (v a r r e s u l t in r e s u l t s ) { ComicQueryManager, a zatem
ten wiersz kodu zmieni go na
g re g s L is tV a lu e += v a lu e s [ r e s u lt .C o m ic .I s s u e ] ; komunikat informujący Janka,
to t a lS p e n t += r e s u l t . P r i c e ; ile pieniędzy wydał oraz ile
C u rre n tQ u e r y R e s u lts .A d d ( re s u lt); są warte kupione przez niego
komiksy.
}

T i t l e = String.Form at("W ydaT em {0:c} na komiksy w a rte { l: c } " ,


t o t a l S p e n t , g re g s L is tV a lu e );

jesteś tutaj ► 711


Ś c iś n ij s w o je d a n e

Użyj semantycznego powiększenia, aby przejść do danych


T o św ietnie, że zapew niliśm y Jankow i możliw ość oglądnięcia jego kolekcji, te ra z je d n a k
pozw olim y m u n a w yśw ietlanie tak że szczegółowych inform acji. Istnieje pew n a k o ntrolka, Skorzystaj z przycisku S
w symulatorze,
k tó ra pozw oli Ci w zbogacić naw igację p o aplikacji o nowy w ym iar. Z o o m se m a n ty c z n y by przełączyć go do trybu
(ang. sem antic zo o m ) to p rzew ijana k o n tro lk a pozw alająca użytkow nikom p rzełączać się powiększenia. Wciśnij
pom iędzy dw om a różnym i sposobam i p rezen tacji sekw encji danych: w idok „pom niejszony” przycisk myszki iużyj jej
kółka, aby przejść
przedstaw ia zaw artość sekw encji w po staci ogólnej, n a to m ia st w idok „pow iększony” do trybu powiększenia.
p rezen tu je szczegółowe inform acje o w ybranym elem encie sekwencji.

Możesz wykonać gest


© Wszystkie komiksy w kolekcji uszczypnięcia, by powiększyć
gest by * » s z c z ^ e U, « t lub pomniejszyć informacje
prezentowane w kontrolce zoomu
swantycznego; dokładnie w taki
sam sposób w jaki powiększasz
i pomniejszasz zdjęcia na
tk KiS fe*
telefonie lub tablecie. Możesz
to także robić przy użyciu kółka
myszki lub klikając wybrane
elementy.

K o ntrolka zoom u
semantycznego © Wszystkie komKX^ kolekcji
e szczegółowe lub listę
pozwala wyświetlać WytonaJ g e s t uszczypnięcia, by wys«n

te same dane na s a n i - ” '


dwa różne sposoby:
w idok pomniejszony
przedstawia
wiele elementów,
- SWiEBSk
natom iast w idok
powiększony zawiera
szczegóły.

W ięcej in fo rm acji o w yko rzysta n iu sem antycznego pow iększenia w aplikacjach możesz znaleźć
na stronie: http://m sdn.m icrosoft.com /pl-pl/library/w indow s/apps/hh465319.aspx.

712 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Poniżej przedstaw iliśm y podstaw ow y szablon k o d u X A M L k o n tro lk i pow iększenia sem antycznego.
W w idoku pom niejszonym używ ana je st k o n tro lk a L istV ie w lub G ridV iew , je d n a z tych dwóch
k o n tro lek używ ana je st także do o k reślen ia po staci w idoku pow iększenia.

<SemanticZoom IsZoomedInViewActive="False" >


<SemanticZoom.ZoomedOutView>
<ListView>
<!-- Ta sekcja kodu zawiera kontrolkę ListView lub GridView
przedstawiającą pomniejszone, ogólne dane -->
</ListView>
Kontrolka GridView jest bardzo podobna do
</SemanticZoom.ZoomedOutView> kontrolki ListView. Podstawowa różnica
pomiędzy nimi polega na tym, że w kontrolce
<SemanticZoom.ZoomedInView> ListView elementy są przewijane w pionie,
a w kontrolce GridView — w poziomie.
<GridView>
<!-- Ta kontrolka ListView lub GridView pokazuje
szczegółowe informacje w widoku powiększenia -->
<GridView.ItemTemplate>
O b a widoki, zarówno
pomniej sze n ia ,ja k <DataTemplate>
ipowiększenia, zawierają
kontrol k i L ie t V iew <!-- Kontrolka ListView lub GridView umieszczona
lub GridView, używające
szablonu danych, który w tej sekcji pokazuje szczegóły w widoku powiększenia -->
zawiera kontrolki,
zapewniajjce p raw idłowe </DataTemplate>
działanie widoku.
</GridView.ItemTemplate>
W tym przyktadzie w widoku pom niejsza
</GridView> zastosowaliśmy hortrolkę ListView, iatomiast
... w y d o po większenia — kontrolkę
</SemanticZoom.ZoomedInView> GndView; Ty jednak możesz zmienić kontrolkę
używaną w którymkolwiek z tych wiić
dokkoónwt.rolkę
</SemanticZoom>

K ontrolki L iStView oraz GridView implementują interfejs ISemanticZoomInformation


K ontrolka Sem anticZoom m o że zawierać wyłącznie kontrolki implementujące interfejs ISem anticZoom lnform ation,
k tó ry udostępnia m eto d y pozwalające jej inicjować i wyko ny wać zmianę wid ° ku. Na szczę ś a e nie musisz sa m e ™
implem entować teg o interfejsu. W przykładzie przedstawiony m na tej s tr o nie r a t o O T w ^ ^ kontrolkę ListView
dla widoku pomniejszenia oraz kontrolkę GridView dla widoku powiększ.onycn szcz.egółów.

jesteś tutaj > 713


To w s z y s t k o s e m a n t y k a

Dodaj zoom semantyczny do aplikacji Janka ^ Zróbtoi


Ja n e k byłby zachwycony, m ogąc og ląd n ąć wszystkie kom iksy w swojej kolekcji
i pow iększać w ybrany z nich, by wyświetlać szczegółow e inform acje n a jego tem at.

J D O D A J N O W Y E L E M E N T D O S T R O N Y G Ł Ó W N E J.
Ja n e k p o trzeb u je czegoś, co m ógłby kliknąć; dlatego pierw szą rzeczą, k tó rą zrobisz, b ędzie d o d an ie now ego
e lem en tu , zw racającego w szystkie kom iksy w kolekcji. N a p o czątk u dodaj do klasy Com icQ ueryM anager m etodę,
k tó ra pozw oli wyświetlić w szystkie komiksy:
p r i v a t e v o id A llC o m ics() {
fo re a c h (Comic comic in B u ild C a ta lo g ( )) {
v a r r e s u l t = new {
Image = C re ate Im ag eF ro m A ssets("cap tain _ am azin g _ zo o m _ 2 5 0 x 2 5 0 .jp g "),
T i t l e = comic.Name,
S u b t i t l e = "Numer " + c o m ic .Is s u e ,
D e s c rip tio n = "K ap itan W spaniaJy k o n tra " + c o m ic .M a in V illa in ,
Comic = com ic,
};
C u rre n tQ u e r y R e s u lts .A d d ( re s u lt);
}
}

N astęp n ie do instrukcji s w itc h um ieszczonej w m eto d zie U p d a te Q u e ry R e s u lts () dodaj kolejną klauzulę c a se :

c a se "W szystk ie komiksy w k o le k c j i" : A llC o m ic s(); b re a k ;

A by zakończyć te n e ta p p rac, dodaj nowy o b iek t Com icQuery do in icjalizatora kolekcji um ieszczonego w m etodzie
U p d a te A v a il a b le Q u e r ie s ( ) . O prócz tego będziesz m usiał d o d ać do fo ld eru Assets/ p lik captain_amazing_
zoom_250x250.jpg.
new C om icQ uery("W szystkie komiksy w k o l e k c j i " , Znaj <iź ten ph'k
"Zobacz w s z y s tk ie komiksy w k o l e k c j i " , w przyktad?ch
dołączonych do książki.
"To z a p y ta n ie zw raca w s z y s tk ie kom iksy",
C reate Im ag eF ro m A sse ts("ca p ta in _ am az in g _ z o o m _ 2 5 0 x 2 5 0 .jp g ")),

[a D O D A J W IĘ C E J W Ł A Ś C IW O Ś C I D O K L A SY C O M IC .
Stosow anie zoom u sem antycznego m a sens wyłącznie w przypadkach, gdy dysponujem y inform acjam i szczegółowymi,
które będziem y mogli wyświetlić w w idoku pow iększenia. T akże w tym w idoku p rezentow ane b ęd ą obiekty Comic
p obierane z kolekcji C o m ic Q u ery M a n a g e r.C u rre n tQ u e ry R esu lts, musim y zatem jedynie dodać odpow iednie
inform acje od klasy Comic i upew nić się, że zostaną one odpow iednio pow iązane w w idoku szczegółów.
u sin g W indow s.U I.X am l.M edia.Im aging;

c l a s s Comic {
p u b lic s t r i n g Name { g e t; s e t ; }
p u b lic i n t Is s u e { g e t; s e t ; }
p u b lic i n t Y ear { g e t ; s e t ; }
p u b lic s t r i n g C o v e rP rice { g e t; s e t ; }
p u b lic s t r i n g S y n o p sis { g e t; s e t ; }
p u b lic s t r i n g M a in V illa in { g e t; s e t ; }
p u b lic BitmapImage Cover { g e t; s e t ; }
}
714 Rozdział 14.
Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

^ D O D A J S Z C Z E G Ó Ł O W E D A N E O K O M IK S A C H .
Z m odyfikuj m eto d ę B u il d C a t a lo g ( ) , do d ając do niej szczegółowe inform acje o wszystkich kom iksach. B ędziesz
także m usiał d odać do fo ld eru Assets o brazki p rzedstaw iające okładki poszczególnych kom iksów . M ożesz je znaleźć
w przykładach dołączonych do książki.

p u b lic s t a t ic IEnumerable<Comic> B u ild C a ta lo g ()


{
re tu rn new List<Comic> {
new Comic { Name = "Johnny America v s . the P in k o ", Issu e = 6 , Year = 1949, C o verP rice = "10 g ro s z y ",
M a in V illa in = "P in k o ", Cover = CreateIm ageFrom Assets("Captain Amazing Issu e 6 c o v e r.p n g "),
Synopsis = "Kapitan W spaniały musi ratować Amerykę przed komunikstami, gdyż Pinko i jego"
+ " komunikstyczne pachołki uknuły plan obrabowania Fo rt Knox i ukradzenia całego z ło t a ." } ,

new Comic { Name = "Rock and R o ll (e d ycja lim ito w a n a )", Issu e = 19, Year = 1957, C o verP rice = "10 groszy"
M a in V illa in = "Doctor V o rtra n ", Cover = CreateIm ageFrom Assets("Captain Amazing Issu e 19 c o v e r.p n g "),
Synopsis = "Doktor Vortran s ie j e sp u sto szen ie wśród m łodzieży przy u życiu swego radiowego u rz ą d z e n ia ,1
+ " któ re k o rzy sta z najnowszego tanecznego sza le ń stw a , by wprowadzać fanów r o c k 'n 'r o lla "
+ " w niekontrolowany t r a n s ." } ,

new Comic { Name = "Woman's Work", Issu e = 36, Year = 1968, C o verP rice = "12 g ro sz y ",
M a in V illa in = "H y ste ria n n a ", Cover = CreateIm ageFrom Assets("Captain Amazing Issu e 36 c o v e r.p n g "),
Synopsis = "Kapitan s t a je tw arzą w tw arz ze swym pierwszym wrogiem p łc i ż e ń s k ie j, H y ste ria n n ą ,"
+ " k tó re j niesam ow ite, te le p a ty czn e i te le k in e ty c z n e zdoln o ści pozw alają powołać arm ię"
+ " k o b ie t, j a k i e j nawet Kapitan będzie m iał problemy s p ro s ta ć ." } ,

new Comic { Name = "H ippie Madness (ż le wydrukowany)", Issu e = 57, Year = 1973, C o verP rice = "20 g ro s z y ",
M a in V illa in = "Mayor", Cover = CreateIm ageFrom Assets("Captain Amazing Issu e 57 c o v e r.p n g "),
Synopsis = "A pokalipsa zombie zagraża is t n ie n iu Obiektowa, gdyż Mayor u s ta w ił w ybory,"
+ " wprowadzając agenta zombie do firm y d o s ta rc z a ją c e j papierosy całemu m ia stu ." } ,

new Comic { Name = "Revenge o f the New Wave Freak (uszko d zo n y)", Issu e = 68, Year = 1984,
C o verP rice = "75 g ro s z y ", M a in V illa in = "S w in d le r",
Cover = CreateIm ageFrom Assets("Captain Amazing Issu e 68 c o v e r.p n g "),
Synopsis = "Zanieczyszczony tu sz do powiek zm ienia D r. A lv in a Mudda w nowe nemezis K ap itana, "
+ " wprowadzając postać K an ciarza do komiksów o K a p itan ie Wspaniałym." } ,

new Comic { Name = "B la ck Monday", Issu e = 74, Year = 1986, C o verP rice = "75 g ro s z y ",
M a in V illa in = "Mayor", Cover = CreateIm ageFrom Assets("Captain Amazing Issu e 74 c o v e r.p n g "),
Synopsis = "Mayor w raca, by doprowadzić Obiektowo do finansowego kryzysu przez w yko rzystan ie"
+ " swych mocy do tw o rzenia zombie przeciw ko G ie łd z ie Obiektowa." } ,

new Comic { Name = " T rib a l Tattoo Madness", Issu e = 83, Year = 1996, C o verP rice = "Dwa z ło t e " ,
M a in V illa in = "Mokey Man", Cover = C reateIm ageFrom Assets("Captain Amazing Issu e 83 c o v e r.p n g "),
Synopsis = "Monkey Man - p rz e ra ż a ją c y czło w iek małpa - ucieka ze swego w ię z ie n ia na wyspie i wraz"
+ " z grupą wytatuowanych cyrkowych pomocników s ie j e sp u sto szen ie przy u życiu śm iertelnego "
+ " prom ienia brudu." } ,

new Comic { Name = "The Death o f an O b je c t", Issu e = 97, Year = 2013, C o verP rice = "C zte ry z ło t e " ,
M a in V illa in = "S w in d le r", Cover = CreateIm ageFrom Assets("Captain Amazing Issu e 97 co v e r.p n g "),
Synopsis = "Armia klonów K an ciarza a ta k u je Obiektowo w d e sp e ra ck ie j próbie zła p a n ia i z a b ic ia "
+ " Kapitana Wspaniałego. Czy naukowcom z Obiektowa uda s ię p rzyw ró cić go do ż y c ia ? " } ,
};
} Przewróć kartkę, by dokończyćaplikację ----------------►
jesteś tutaj > 715
Zajm ujem y się szczegółami

DODAJ NOW Ą STRONĘ BASIC PAGE,


N A KTÓREJ UMIEŚCISZ KONTROLKĘ ZOOMU SEMANTYCZNEGO.
Jan ek jest bardzo zadowolony z pozostałych elem entów aplikacji, dlatego też, zam iast modyfikować jej istniejące strony,
dodam y nową. D o d a j do p ro je k tu stronę QueryDetailZoom.xaml u tw o rzon ą p rz y użyciu szablonu Basic Page.

K iedy już to zrobisz, p rz e jd ź do s tro n y M ainPage.xam l i w pliku k o d u u krytego z m o d y fik u j p ro c e d u rę o b s łu g i


z d a rze ń I te m C lic k tak , by p o kliknięciu now ego zapytania aplikacja w yświetlała stro n ę Q ueryD etailZ oom :

p r i v a t e v o id L istV ie w _ Ite m C lic k (o b je c t s e n d e r , Item C lick E v en tA rg s e)


{
ComicQuery q u ery = e .C lic k e d Ite m a s ComicQuery;
if (q u ery != n u ll)
{
if ( q u e r y .T i tl e == "W szystkie komiksy w k o le k c ji" )
th is.F ra m e .N a v ig a te (ty p e o f(Q u e ry D e ta ilZ o o m ), q u e ry );
e ls e To jest nowa procedur obsługi zdattn
th is .F r a m e .N a v ig a te ( ty p e o f ( Q u e r y D e ta il) , q u e ry ); ItemClick dla strony MainPage-xam'. .
Procedura ta sprawdza tytuł zapytan|a,
} by określić, czy należy p ^ j ^ 'na str°'nę
} QueryDetail, czy też QueryDetailZoom.

[ j DO NOWEJ STRONY DODAJ STATYCZNY ZASÓB CO M IC Q U E R Y M A N A G E R .


N ow a stro n a Q u ery D etailZ o o m działa dokład n ie ta k sam o ja k istniejąca ju ż stro n a Q u e r y D e ta il.
B ędziesz m usiał d odać C om icQ ueryM anager do sekcji < P a g e .R e s o u rc e s > w pliku QueryDetailZoom.xaml.
N ie m usisz m odyfikow ać zasobu AppName, gdyż stro n a określi jego w artość, używ ając n astęp u jąceg o kodu:

< P age.R esources>


<local:ComicQueryManager x:Name="comicQueryManager,7>
< ! - - TODO: Usuń te n w ie rs z j e ś l i k lu c z AppName z o s t a ł zad ek laro w an y w App.xaml -->
< x :S tr in g x:Key="AppName">My A p p lic a tio n < /x :S tr in g >
< /P a g e.R eso u rc e s>

O d o d a j p l ik k o d u u k r y t e g o d l a n o w e j s t r o n y .
W pliku QueryDetailZoom.xaml.cs będziesz m usiał um ieścić d okładnie tę sam ą m eto d ę O n N a v ig a te d T o ():

p r o te c te d o v e r r id e v o id O nN avigatedT o(N avigationE ventA rgs e) {


ComicQuery comicQuery = e .P a ra m e te r a s ComicQuery;
if (com icQ uery != n u ll ) {
com icQ ueryM anager.U pdateQ ueryR esults(com icQ uery);
p a g e T itle .T e x t = co m icQ u e ry M an ag er.T itle;
}
b a se .O n N a v ig ate d T o (e );
}

716 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

U T W Ó R Z K O D X A M L N O W E J S T R O N Y W ID O K U S Z C Z E G Ó Ł Ó W .
O to kolejna rzecz, któ rą musisz zrobić: utworzyć kod X A M L strony QueryDetailZoom.xaml, zawierający kontrolkę
zoom u sem antycznego wyświetlającego szczegółowe inform acje o komiksach. To największa strona, jaką do tej pory
stworzyłeś, dlatego aby ułatwić Ci zrozum ienie, co się w niej dzieje, zamieściliśmy jej kod aż n a dwóch stronach książki.

<G rid Grid.Row="1" M argin="120,0"


D a ta C o n te x t= "{ S ta tic R e so u rc e ResourceKey=com icQueryM anager}">
< G rid .R o w D efin itio n s> ifoieszaanty ^ n M k ę SemanticZoom w wierszu

^ owDef^ ^ on Hei ght ="Aut o "/> Z t^lkl ^i^tl^ W


wWra°nr ^ ^ iridVjieU
Wm^
i d^C^°°:ZW^^¿ej
< R o w D efin itio n /> na przewijanie zawartości.
< /G rid .R o w D e fin itio n s>

<T extB lock S ty le = " { S ta tic R e s o u rc e S u b h e a d e rT ex tS ty le} " M a rg in = " 0 ,0 ,0 ,2 0 "


Text="Wykonaj g e s t u s z c z y p n ię c ia , by w y ś w ie tlić dane szczegółow e lu b l i s t ę " />

<SemanticZoom IsZ oom edInV iew A ctive="False" Grid.Row="1">

W widoku <SemanticZoom.ZoomedOutView>
pomniejszenia <L istV iew Item sS o u rce= "{ B in d in g C u rre n tQ u e ry R e su lts} " M a rg in = " 0 ,0 ,2 0 ,0 "
używamy kontrolki
ListView, dokładnie Ite m T e m p la te = "{ S ta tic R e so u rc e S tan d ard 5 0 0 x l3 0 Item T em p late}"
takiej samej jak na
SelectionM ode="N one" />
stronie QueryDetail.
</SemanticZoom.ZoomedOutView>

<SemanticZoom.ZoomedInView>
<GridView Item sS o u rce= "{ B in d ing C u rre n tQ u e ry R e su lts} "
M a rg in = " 0 ,0 ,2 0 ,0 " SelectionM ode="N one" x :N am e= "detailG ridV iew ">
<G ridV iew .Item T em plate>
<D ataTem plate>
<G rid H eight="780" W idth="600" M argin="10">
W widoku powiększenia Szablon danych kontrolki
została zastosowana < G rid .C o lu m n D e fin itio n s> GridView działa dokładnie
kontrolka GridView. tak samo jak w kontrolce
< C o lu m n D efin itio n W idth="A uto"/> ListView. Jeśli musisz
Jej szablonem
danych jest siatka < C o lu m n D efin itio n /> przypomnieć sobie,
zawierająca kontrolkę jak działają te szablony,
< /G rid .C o lu m n D e fin itio n s > zajrzyj do rozdziału 10.
Image, w której
będzie prezentowana
okładka komiksu oraz
StackPanel zawierająca <Image S o u rce= "{B in d in g C om ic.Cover}" M a rg in = " 0 ,0 ,2 0 ,0 "
kontrolki TextBlock S tre tc h = " U n ifo rm T o F ill" W idth="326" H eight="500"
prezentujące wartośd
właściwości. V e rtic a lA lig n m e n t= "T o p "/>

Przewróć kartkę, aby zobaczyć pozostałą część kodu XAML kontrolki SemanłicZoom.-- - - - - - - - - - - - ►
jesteś tutaj ► 717
J a n e k je s t z a c h w y c o n y

< S tackP anel G rid.C olum n="1">

<T extB lock T ex t= "T y tu ł"


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " />
< T extB lock T ext= "{B in d in g Comic.Name}"
Otopozostałaczęść S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />
szablonu elementów
kontrolki GridView
używanejwwidoku < T extB lock Text="Numer" M a rg in = " 0 ,1 0 ,0 ,0 "
powiększenia.Odpowiada S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " />
ona za wyświetlenie
szczegółowychinformacji < T extB lock T ext= "{B in d in g C o m ic.Issu e} "
wgrupiekontrolek S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />
TextBlock powiązanych
z właściwościami
obiektuComic. < T extB lock Text="Rok" M a rg in = " 0 ,1 0 ,0 ,0 "
S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " />
< T extB lock T ext= "{B in d in g Com ic.Year}"
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock Text="Cena w ydania" M a rg in = " 0 ,1 0 ,0 ,0 "


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " />
< T extB lock T ext= "{B in d in g C o m ic.C o v erP rice}"
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock Text="Główny p rz e c iw n ik " M a rg in = " 0 ,1 0 ,0 ,0 "


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " />
< T extB lock T ext= "{B in d in g C o m ic.M ain V illain } "
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock T e x t= " S tre s z c z e n ie " M a rg in = " 0 ,1 0 ,0 ,0 "


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " />
< T extB lock T ext= "{B in d in g C om ic.S ynopsis}"
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />
< /S ta c k P a n e l>
< /G rid >
< /D ataT em plate> Edytuj zapytania w programie LINQPad
</G ridV iew .Item Tem pl a te > Mamy dla Ciebie doskonałe narzędzie do poznawania i stosowania
</GridV iew > zapytań LINQ. Jest t o program o nazwie LINOPad, k tó ry m ° ż na
</SemanticZoom.ZoomedInView> pobrać bezpłatnie od Joego Albahari (jest to jeden z doskonałycb
</SemanticZoom> recenzentów tej książki, dzięki któreirnu udało się z niej usunąć
wiele błędów). M ożesz go pobrać ze stro ny:
< /G rid>
http://ww w.linqpad.net/

718 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

Zrobiłeś na Janku wielkie wrażenie


D zięki nowej aplikacji Ja n e k m a doskonały p o rz ą d e k w swojej kolekcji. Św ietna robota!

® Wszystkie komiksy w kolekcji


Wykonaj gest uszczypnięcia, by wyświetlić dane szczegółowe lub listę

T O JEST N AJLEPSZA R Z E C Z , JA K A
M I SIĘ P R Z Y D A R Z Y Ł A , O D C Z A S U G DY
N A U L IC Z N E J W Y P R Z E D A Ż Y Z N A L A Z Ł E M
E G Z E M P LA R Z K O M IK S U N U M E R 2 3 Z EDYCJI
L IM IT O W A N E J, I T O Z A JEDYN E PIĘĆ Z Ł O T Y C H !

jesteś tutaj ► 719


P o d z ia ł ro b i r ó ż n ic ę

Szablon Split App ułatwia tworzenie aplikacji


służących do przeglądania danych
Istnieje łatwiejszy sposób tw orzenia aplikacji składających się z dw óch stro n , um ożliw iających
przechodzenie pom iędzy p rezen tac ją o g ó ln ą a stro n ą szczegółów p o k azu jącą p o grupow ane
4- ^
elem enty. Kiedy utworzysz nowy p ro jek t aplikacji, używając szablonu Split App, ID E autom atycznie Z r ó b to !
utw orzy p ro je k t pozw alający użytkow nikow i naw igow ać pom iędzy stro n ą p re z en tu jąc ą elem enty
ogólnie oraz stro n ą szczegółów p o d zielo n ą n a dwie części. M ożem y p o zn ać szablon Split App, w 'J L a

tw orząc n a jego podstaw ie now ą aplikację dla Janka.

[0 U tw ó rz no w y pro je kt aplikacji w e d łu g szablonu Split A pp i uruchom ją.


P ro jek t aplikacji Split A p p zaw iera klasę g en eru jącą dane przykładow e, co oznacza, że zaraz p o jego
utw orzeniu m o żn a ta k ą aplikację skom pilow ać i uruchom ić.

U tw órz zatem nowy p ro je k t aplikacji w edług szablonu Split A pp (XAM L) i n a d a j je j nazwę


KomiksyJankaSplitApp, ta k by w ygenerow ana p rzestrzeń nazw od p ow iad ała frag m en to m kodu
przedstaw ionym n a kilku kolejnych stronach.

Z m ień nazwę aplikacji n a Komiksy Janka, w prowadzając odpow iednią zm ianę w zasobie AppName.
W projektach utworzonych w edług szablonu Split App zasób AppName je s t zde finiow a ny w p lik u App.xamJ \

< x :S tr in g x:Key="AppName">Komiksy J a n k a < /x :S trin g >

720 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

A te ra z uru ch o m aplikację. A plikacje typu Split A pp


składają się z dw óch stron. Pierw szą z nich jest stro n a Alji-
elem entów ; przedstaw ia o n a ich grupy, których szczegóły
m ożna n astęp n ie przeg ląd ać n a drugiej stronie: Kod X A M L tej strony jest umieszczony
w pliku Item sP age.xam l. Został on
przygotowany w taki sposób, by elementy
były wyświetlane przy użyciu szablonu
Kom iksy Janka Standard250x250ItemTemplate,
zdefiniowanego w pliku S ta n d a rd S tyles.xa m l
umieszczonego w folderze Com m on.

Solution Explorer ^ □X
o (2 ’o - ć w & m <> Ap
Search Solution Explorer (Ctrl-*-;} P '
^ Solution KomiksyJankaSplrtApp' (1project)
a |c5] KomiksyJankaSplrtApp
> /■ Properties
> ■■References
> y Assets
> y Common
a y DataModel

D Appjraml
> .Ç) ItemsPage.xaml
ÂÜ KomiksyJankaSplitApp_TemporaryKey.pfx
Kliknij je d e n z elem entów , by przejść n a drugą, dw uczęściow ą stro n ę szczegółów: fel Package.appxmanifest
SplitPage.xaml

p Title: 3

n m U n iK ta n M u r u u p m i i n n i r l t ner
«emvurrt A» ocło <r\ tu * * Cm ponutir. « w

armOKnpKm p o u m iu * » h

Kliknięcie elementu na stronie elementów powoduje


przejście na dwuczęściową stronę szczegółów, której postać
jest zdefiniowana w pliku SplitPage.xam l. Prezentuje ona Kod XAML dwuczęściowej
zawartość wybranej grupy w lewej kolumnie, używając do
strony szczegółów wyświetla
samą sekcję szczegółów
tego znanego już szablonu Standard130ItemTemplate.
j edynie w sytuacji, gdy
Z kolei z prawej strony są wyświetlane szczegóły aplikacja jest prezentowana
wybranego elementu. Postać tej części strony jest określona
w układzie pionowym.
przy użyciu zwyczajnego kodu X A M L wykorzystującego Możesz to sprawdzić
wiązanie danych, a nie żadnego szablonu. samemu: uruchom aplikację
w sym ulatorze i użuj
przycisków 53 o raz K ,
Ton duży blok t e k s t u / f j f ^ ° t z ' k o d e m XAML by obrócić symulowane
TextBlock, którą w 6 . kraka z a s ą p kom iksach, urządzenie.

jesteś tutaj ► 721


T e s a m e d a n e , n o w a a p lik a c ja

^2 Dodaj klasy z danym i do fo ld e ru DataModel.


W o knie Solution Explorer kliknij fo ld er DataModel praw ym przyciskiem myszy
i w ybierz opcję Add/Class , aby d o d ać do niego now ą klasę.

Solution Explorer ▼ n x

tŁ T0 - »? Cl fl ;ä ) : 1

Search Solution Explorer (Ctrt+;) P '

EqI Solution 'KomiksyJankaSplitApp' (1 project)


a jęg Kom iksyJan kaSp litA p p
t> f t Properties
^ References
> 5 j Assets

> C* SampleC Add □ Mew Item,,, Ctrl+Shift+A

.D Appjtam l Scop e to T h is +a Existing Item... Shift+Att+A


.D Item sPagejc ^ New Solution
Explorer View % Mew Folder
*11 KomiksyJan
V Class... Shift+Alt+C
| r ] Package. ap( Exdu de From Pr° j ect
,D SplitPagejra ^ C ut Ctri+X

[J Co py Ctrl+C

t il Paste Ctrf+V

X Delete Del

£::: Renam e F2

C* O pen Folder in File Explorer

f* Properties

U tw ó rz kla sę ComicQueryM anager. K iedy tworzysz klasę w folderze, ID E autom atycznie w ygeneruje ją i um ieści
w przestrzeni nazw zaw ierającej nazw ę folderu:

namespace K om iksyJankaSplitA pp.D ataM odel


{
c l a s s ComicQueryManager
{
}
}

O f2t o - i? » □ ® <> P P
Skopiuj zaw artość klasy Com icQ ueryM anager z poprzed n iej,
Search Solution Explorer (C trl+ ;) p -
działającej aplikacji Ja n k a i w klej do nowej klasy, utw orzonej
Egl Solu tion 'K o m iksyJan kaSp lrtA pp ' (1 project)
w folderze DataModel. U pew nij się, że klasa ta należy do
a [c«] K o m ik s y J a n k a S p litA p p
p rzestrzen i nazw K o m ik sy Ja n k a S p litA p p .D a ta M o d e l, nie > Properties

zapom nij także o użyciu instrukcji u sin g . > References


> 0 A ssets
> 0 Com m on
N astęp n ie pow tó rz te sam e czynności, by u tw o rz y ć kla s y
a ^ DataM odel
Comic, Com icQ uery i P urch ase o ra z ty p w ylicze n io w y > C* C o m ic .c s

P ric e R a n g e . W szystkie pow inny się znaleźć w folderze > C* C o m icQ u e ry.cs
> S 3 C o m icQ u eryM a n a g er.e s
DataModel, a to oznacza, że tak że o n e pow inny należeć do tej > C* PriceR a n g e.cs
sam ej przestrzen i nazw K o m ik sy Ja n k a S p litA p p .D a ta M o d e l. > C* P u rc h a s e r s

O to w jaki sposób pow inno w yglądać o k n o Solution Explorer po > c * Sam p leD ataSo u rce.es
> D A pp .xam l -
do d an iu wszystkich w ym ienionych plików: ►

722 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

W folderze DataModel znajdow ał się ju ż p lik SampleDataSource.cs, zaw ierający


ko d do g eneracji wszystkich przykładow ych danych prezentow anych w aplikacji.

O tw órz go — o k azuje się, że działa on w sposób b ardzo p o d o b n y do klas


zarządzających danym i w aplikacji dla Jan k a. P lik ten zaw iera kilka klas, w tym
tak że S am pleD ataG roup (rep rezen tu jącą grupy danych najwyższego poziom u,
k tó ra odp o w iad a klasie C om icQuery) o ra z S a m p le D a ta Ite m (k tó ra rep rezen tu je
poszczególne elem en ty i odp o w iad a klasie Comic). S am e przykładow e d an e są
tw orzone w k o n stru k to rze klasy S a m p le D a ta S o u rc e, um ieszczonej n a samym
dole pliku.

K od ukryty strony e le m e n tu tw orzy now ą in stancję klasy S am p leD ata S o u rc e

r
i używa jej do w ypełnienia słow nika o nazw ie D efa u ltV ie w M o d el.

Więcej na temat
tego, czym jest
ViewModel oraz
jak go tworzyć,
można znaleźć
w rozdziale 16.

O
O

To prawda. Aplikacje typu Split App zostały


zaprojektowane w taki sposób, by ułatwić nam
dodawanie własnych danych.

A by d odać w łasne dane do aplikacji utw orzonej n a


podstaw ie szablonu Split A pp, w ystarczy w prow adzić d robne
zm iany w kodzie ukrytym stro n y elem e n tu o ra z strony
szczegółów. W łaśnie tym zajm iem y się w dalszej kolejności.
Z m odyfikujem y tak że p o stać dw uczęściowej strony szczegółów,
ta k by używ ała tego sam ego k o d u X A M L do w yświetlania
okładki kom iksu o raz szczegółowych inform acji n a jego tem at.

jesteś tutaj ► 723


L is ta r z e c z y d o z r o b ie n ia w a p lik a c ji

Z m odyfikuj kod u kryty um ieszczony w pliku ItemsPage.xaml.cs.


O tw órz plik ItemsPanel.xaml.cs i skorzystaj z opcji EDIT/Find and Replace, by odszukać w nim łańcuch znaków
„ T O D O :”. Przyjrzyj się k o m en tarzo m — szablon info rm u je nas, że to w łaśnie w nich m am y w prow adzić zmiany,
by usu n ąć dan e przykładow e. D odaj k o m en tarze do dwóch kolejnych w ierszy kodu, określających w arto ść klucza
Ite m s w słow niku D e fau ltV iew M o d el, a n a stęp n ie z a s tą p je sw oim w ła sn y m k o d e m , któ ry odczytuje właściwość
Umieść te
wiersze A v a ila b l e Q u e r ie s z now ego o b iek tu Com icQueryM anager.

/ / TODO: C re a te an a p p r o p r ia te d a ta model f o r y o u r problem domain to r e p la c e th e sam ple d a ta


/ / v a r sam pleD ataG roups = S a m p le D a ta S o u rc e .G e tG ro u p s((S trin g )n a v ig a tio n P a ra m e te r)
//th is .D e fa u ltV ie w M o d e l[" Ite m s " ] = sam pleD ataG roups;
th is.D e fa u ltV ie w M o d e l[" Ite m s" ] = new D ataM o d el.C o m icQ u ery M an ag er().A v ailab leQ u eries;
Dodaj ten
wiersz O prócz tego będziesz m usiał um ieścić w k o m e n ta rz u k o d p ro ced u ry obsługi zdarzeń Ite m V ie w _ Ite m C lic k (),
kodu.
który p ró b u je rzutow ać kliknięty e le m e n t do typu S am pleD ataG roup (jest o n przekazyw any do p ro ced u ry obsługi
zdarzeń jako właściwość e .C lic k e d I te m ) . W łaściw ość A v a ila b le Q u e r ie s zw raca kolekcję obiektów ComicQuery,
a zatem poniżej przedstaw iliśm y nowy k o d p ro ced u ry obsługi zd arzeń I t e m C li c k e d ( ) :

v o id Ite m V ie w _ Ite m C lic k (o b je c t s e n d e r , Item C lick E v en tA rg s e) {


/ / N a v ig a te to th e a p p r o p r ia te d e s t i n a t i o n p ag e , c o n fig u rin g th e new page
/ / by p a s s in g re q u ir e d in fo rm a tio n a s a n a v ig a tio n p a ra m e te r
/ / v a r g ro u p Id = ((S a m p le D a ta G ro u p )e .C lic k e d Ite m ).U n iq u e Id ;
// th i s .F r a m e .N a v i g a te ( ty p e o f ( S p li tP a g e ) , g ro u p Id );
D ataM odel.Com icQuery q u ery = e .C lic k e d Ite m a s D ataM odel.Com icQuery;
i f (q u ery != n u ll )
th is .F r a m e .N a v i g a te ( ty p e o f ( S p li tP a g e ) , q u e ry ) ; Utwomyteś klasę ComicQuery oraz inne
, w f°lderze IZatoMadel, przez co zostaty
one Umieszczone w przestrzeni nazw
DataModel.
Z m odyfikuj kod u kryty um ieszczony w pliku SplitPage.xaml.cs.
S tro n a szczegółów także zaw iera k o m en tarz zaczynający się o d „ T O D O ”, został o n um ieszczony b ezpośrednio
p rzed instrukcjam i określającym i w artość słow nika D efaultV iew M odel pow iązane z kluczam i Group o ra z Item s.
M usisz zastąpić je k odem , któ ry przygotow uje d an e grup i elem en tó w n a tę stro n ę, używ ając przy tym m odelu
danych komiksów:

/ / TODO: C re a te an a p p r o p r ia te d a ta model f o r y o u r problem domain to r e p la c e th e sam ple d a ta


/ / v a r group = S a m p le D a ta S o u rc e .G e tG ro u p ((S trin g )n a v ig a tio n P a ra m e te r);
/ / th is.D efau ltV iew M o d e l["G ro u p "] = gro u p ;
/ / th is.D e fa u ltV ie w M o d e l[" Ite m s" ] = g ro u p .Ite m s ;
DataModel.ComicQueryM anager comicQueryM anager = new D ataM odel.Com icQ ueryM anager();
DataM odel.Com icQuery q u ery = n a v ig a tio n P a ra m e te r a s D ataM odel.Com icQuery;
com icQ ueryM an ag er.U p d ateQ u ery R esu lts(q u ery );
th is.D efau ltV ie w M o d e l["G ro u p "] = q u e ry ;
th is.D e fa u ltV ie w M o d e l[" Ite m s" ] = co m icQ u e ry M an ag er.C u rren tQ u ery R esu lts;

Jest jeszcze jed n a rzecz, k tó rą musisz zrobić. S tro n a szczegółów przesłania m eto d ę S a v e S t a t e ( ) , k tó ra pozw ala jej
zapam iętać, który elem en t został kliknięty. W ygenerow any kod rzutuje wybrany elem en t do typu S am pleD ataItem ;
dlatego, żeby uniknąć wyjątków związanych z rzutow aniem , pow inieneś cały kod tej m etody um ieścić w kom entarzu.
p r o te c te d o v e r r id e v o id S a v e S ta te ( D ic tio n a r y < S tr in g , O b ject> p a g e S ta te ) {
/ / C ały kod t e j metody um ieść w kom entarzu.
}

724 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

© a To • ? CS S 'B * [p ]
Search Solution Explorer (Ctrl-*-;} fi ~

Solution 'KomiksyJankaSplrtApp1(1 project}


Dodaj p liki z obrazkam i do fo ld e ru Assets.
J |oj] KomiksyJankaSplrtApp

B ędziesz p o trzeb o w ał wszystkich plików z ob razk am i z aplikacji Janka. > fi Properties

K liknij fo ld er Assets praw ym przyciskiem myszy i w ybierz opcję


0 bluegray_250x250.jpg
Add/Existing item. , aby wyświetlić o k n o dialogow e A d d Existing Item . 0 c aptain Am azing Issue 19 cover.png

P rzejdź do fo ld eru zaw ierającego k o d aplikacji dla Ja n k a napisanej we S c aptain Am azing Issue 36 cover.png
0 captain Am azing Issue 57 cover.png
w cześniejszej części rozdziału, wciśnij klawisz Ctrl i k olejno klikaj obrazki, EH Captain Am azing Issue 6 cover.png
0 C aptain Am azing Issue 68 cover.png
aby je zaznaczyć (wszystkie o p ró cz plików Logo.png , SmallLogo.png , 0 Captain Am azing Issue 74 cover.png
SplashScreen.png o raz StoreLogo.png ). Kliknij przycisk A d d , by dodać 0 C aptain Am azing Issue 83 cover.png
0 Captain Am azing Issue 97 cover.png
wszystkie zaznaczone pliki do fo ld eru Assets pro jek tu . 0 captain amazing 250x250.jpg
0 captain_amazing_zoom_250x250.jpg
E 3 DarkGray.png
0 LightGray.png
E 3 Logo.png

T eraz T w o ja a p lik a c ja ju ż d zia ła ! S tro n a 0 MediumGray.png


ED purple_250x250.jpg

e le m e n tó w w y ś w ie tla z a p y ta n ia d o s tę p n e SmallLogo.png
SplashScreen.png

w o b ie k c ie C o m icQ u e ryM a n a g e r... 0 StoreLogo.png


> i i C om m on
Solution Explorer Class View

...a d w u c z ę ś c io w a s tro n a
s z c z e g ó łó w p re z e n tu je
w y n ik i w y b ra n e g o z a p y ta n ia
i p o zw a la w y ś w ie tlić
s z c z e g ó ło w e in fo rm a c je na
te m a t w y b ra n e g o e le m e n tu .

© Połącz zakupy z...

W ttim mieiscu nie są prezentowane żadne informacje,


W¿mmeSSCOna tu kontrolka TextBlock jest pow.ązana
g wtaściwością Content, a używane zapytania ,zwracj O L ^
ZtW-e1CrmSająą tej wtaściwości. Zanim p r z ^ r o ^ z kartkę
za sta n ó w w ja k i sp o só b m O g% ś coś tu ta j w y św ietlić .
jesteś tutaj ► 725
To b y ło b ły s k a w ic z n e

^ó ) Z m odyfikuj kod pliku SplitPage.xaml, by strona pre zen to w ała szczegółowe inform acje o komiksie.
K od X A M L um ieszczony w pliku SplitPage.xaml korzysta z szablonów , aby wyświetlać elem en ty Zastosujemy
te właściwości,
w lewej części strony. Je d n a k do w yśw ietlania szczegółów w ybranego e le m e n tu z praw ej strony 0aby
*. ^fJcZeaófc
szczegółowe
używany je st zwyczajny k o d X A M L w ykorzystujący tech n ik ę w iązania danych. Z aw iera on informacje
ko ntrolkę T e x tB lo c k , p ow iązaną z w łaściwością C o n te n t: o wybranym
komiksie były
< T extB lock Grid.Row="2" G rid.C olum nSpan="2" M a rg in = " 0 ,2 0 ,0 ,0 " wyświetlane
w tym samym
T ex t= "{B in d in g C o n ten t}" S ty le = " { S ta tic R e s o u rc e B o d y T ex tS ty le} "/> miejscu strony.
Przykładow e dane um ieszczone w plik u SampleDataSource.cs u d o stę p n iają właściwość C o n te n t,
k tó ra zaw iera duże bloki tek stu . Jed n a k my byśmy chcieli, żeby n asza aplikacja p rezen to w ała inform acje dotyczące
kom iksów z kolekcji Jan k a. N a szczęście dysponujem y już blokiem k o d u X A M L , k tóry schludnie p re ze n tu je te
inform acje, jeśli zostanie pow iązany z o b iek tem Comic. O d s z u k a j k o n tro lk ę w y ś w ie tla ją c ą w a rto ś ć w ła ściw o ści
C o n te n t i zastą p j ą ko d e m X A M L p re z e n tu ją c y m szczegółowe in fo rm a c je o k o m ik s ie . N ie zapom nij d odać do
zew nętrznej kontro lk i G rid właściwości G rid.R ow , G rid .C o lu m n S p an o raz M argin.

<G rid H eight="780" W idth="600" Gr1d.Row="2" Gr1d.ColumnSpan="2" Marg1n="0,20,0,0">


< G rid .C o lu m n D e fin itio n s>
< C olu m n D efin itio n W idth="A uto"/>
_ .....x...¡„Haniu aolikacii
< C o lu m n D efin itio n />
< /G rid .C o lu m n D e fin itio n s >

<Image Source= "{B in d in g Com ic.Cover}" M a rg in = " 0 ,0 ,2 0 ,0 "


ę +tre
S vo+tc. "hh== "" M
Unn' iifo
■ Frm
n y nT
i Ton F
F -ill"
i n " I.H h = 11
W idth="326"11 UHoeight="500
i n h ł - z 11 COf
V e rtic alA lig n m e n t= "T o p "/>
W o r t i r a i f i l i n n m a n t z ^ T n n 11 /">

< S tackP anel G rid.C olum n="1">

< T extB lock T ex t= "T y tu ł"


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " />
< T extB lock T ext= "{B in d in g Comic.Name}"
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock Text="Numer"


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " M a rg in = " 0 ,1 0 ,0 ,0 " />
< T extB lock T ext= "{B in d in g C o m ic.Issu e} "
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock Text="Rok"


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " M a rg in = " 0 ,1 0 ,0 ,0 " />
< T extB lock T ext= "{B in d in g Com ic.Year}"
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock Text="Cena w ydania"


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " M a rg in = " 0 ,1 0 ,0 ,0 " />
< T extB lock T ext= "{B in d in g C o m ic.C o v erP rice}"
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock Text="Główny p rz e c iw n ik "


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " M a rg in = " 0 ,1 0 ,0 ,0 " />
< T extB lock T ext= "{B in d in g C o m ic.M ain V illain } "
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />

< T extB lock T e x t= " S tre s z c z e n ie "


S ty le = " { S ta tic R e s o u rc e C a p tio n T e x tS ty le } " M a rg in = " 0 ,1 0 ,0 ,0 " />
< T extB lock T ext= "{B in d in g C om ic.S ynopsis}"
S ty le = " { S ta tic R e s o u rc e Ite m T e x tS ty le } " />
< /S ta c k P a n e l>
< /G rid>

726 Rozdział 14.


Przeszukiwanie danych i tworzenie aplikacji przy użyciu LINQ

T eraz T w o ja a p lik a c ja p o zw a la d o c ie ra ć do s z c z e g ó ło w y c h in fo rm a c ji,


u ż y w a ją c p rzy ty m za p yta ń z w ra c a ją c y c h k o m ik s y oraz w y ś w ie tla ją c
sz c z e g ó ło w e dan e o w y b ra n y m k o m ik s ie na d w u c z ę ś c io w e j s tro n ie .

o
Hippie Madness (źle
® Połącz zakupy z... wydrukowany)

Johnny America vs. the Pinko

Hippie Madness (źle wydrukowany)

Revenge of the New Wave Freak (uszkodzony)

N iek tó re zapytania nie zw racają o biektu Comic, a zatem wszelkie p o la pow iązane z jego
w łaściwościami b ęd ą puste. W rozdziale 16. dowiesz się o konw erterach w artości, które
pozw alają n a ukryw anie takich p ó l lub wyświetlanie w nich w artości domyślnych.
Zapytanie
Drogie komiksy
zwraca sekwencję
anonimowych © Drogie komiksy Hippie M adness (ile
wydrukowany) jest warty
obiektów IB WS.OOzł
dysponujących Te kontrolki zostały
jedynie powiązane z właściwościami,
właściwościami które nie są dostępne
Title oraz Image. w kontekście danych i dlatego
na stronie są wyświetlane
jako puste miejsca.
W dalszej części książki
dowiesz się o narzędziach,
których będziesz mógł użyć
do ukrycia takich etykiet lub
wyświetlenia w nich wartości
domyślnych.

Możesz także dodać do swojego projektu now e strony, bazujące na szablonach Items Page oraz Split Page, używając do tego celu
tej samej opcji Add New Item , z której skorzystałeś, by dodać stronę Basic Page. Dostępny jest jeszcze jeden cenny szablon —
Grid App — pozwalający na tw orzenie aplikacji o trójpoziomowej nawigacji. Więcej informacji na tem at szablonów Grid App
oraz Split App można znaleźć na stronie: http://msdn.microsoft.com/pl-pl/library/windows/apps/hh768232.aspx.

jesteś tutaj ► 727


728 R o zd ział 14.
15. Zdarzenia i deleęały

Co robi Twój kod,


kiedy nie patrzysz

Twoje obiekty z a c z y n a ją m yśleć o sobie. Nie mozesz zawsze kontrolować tego,


co one robią. Czasami różne rzeczy... zdarzają się. Kiedy to następuje, chciałbyś, aby Twoje obiekty
były wystarczająco sprytne i odpowiednio re a g o w a ły . To miejsce, w którym do akcji wkraczają
zdarzenia. Jeden obiekt udostępnia zdarzenie, inny je obsługuje i wszystko pracuje razem,
aby całość działała sprawnie. Jest to wspaniałe, o ile nie chcesz, by Twój obiekt mógł kontrolować,
kto będzie mógł nasłuchiwać jego zdarzeń. Wtedy bardzo pomocne okazują się fun kcje z w ro tn e .

to je st n o w y ro z d z ia ł ► 729
N a d a w c o , p o z n a j o d b io r c ę

Czy kiedykolwiek marzyłeś o tym,


aby Twoje obiekty potrafiły samodzielnie myśleć?
Przypuśćm y, że piszesz sym ulator gry w baseball. Z am ierzasz stworzyć go, sprzedać pew nej
bogatej drużynie (na pew no m ają w ypchane kieszenie, p raw d a?) i zarobić n a tym m ilion
dolarów . Tworzysz obiekty B a ll , P itc h e r , Umpire, Fan o raz w iele, w iele innych. Piszesz To standardowy sposóti
naw et kod, aby o b iek t P itc h e r m ógł łap ać piłkę. nazywania metod
— powiemy sobie
o tym nieco później.
T eraz m usisz tylko to wszystko połączyć. D o d ajesz do klasy B a ll m eto d ę O n B a llIn P la y()
i o d tego m o m en tu chcesz, by Twój o b iek t P itc h e r odp o w iad ał swoją m e to d ą obsługi
zdarzenia. Po nap isan iu m e to d m usisz jeszcze wszystko właściwie skoordynow ać:

Kiedy p'Fka
z o s ta je uderzona,
wywofywana Pit,a została uderzona pod kątem
70 stopni do poziomu i po/eci
je s t m etoda
OnBalUnPlayO- na (^/eg/ość 82 metrów.
¿ 1 Chcemy, aby obiekt
B a l l . O n B a l l I n P l a y ( 7 0 , 82) P itc h e r z ta p a t tą pitką-

Zawodnik może zająć się


pitką uderzoną pod tym
kątem i postaną na daną

» o odlegtość.
O
y * BaW i ?

P it c h e r .C a t c h B a ll( )

Ale skąd obiekt WIE, że ma odpowiedzieć?


T u je st problem . Chciałbyś, aby Twój o b iek t B all m artw ił się tylko
o u d erzen ie, a o b iek t P itc h e r zajm ow ał się tylko piłkam i, k tó re lecą Chcesz, aby
w jego kierunku. Inaczej m ów iąc, chcesz, aby o b iek t B a ll mówił
obiektow i P itc h e r : „L ecę do cieb ie”. obiekt martwił
się tylko
O biekt Ball nie wie, któru
zawodnik go wytopie — może buć
o siebie, a nie
M o l f t PJ ^ her albo Oatcher.
Mozę to być naw et instancja o inne obiekty.
klasy ThirdBaseman, która
S c 7 o m l) . s iq w y w o ta ć m e to d ą r ^
Przydzielasz więc
^ BaW każdemu z nich
P° ^ “ k t f go ztapat. To n,e
odpowiednie
zadania.

730 Rozdział 15.


Zdarzenia i delegaty

Kiedy wystąpi ZDARZENIE... obiekty nasłuchują_______________


W szystko, czego p o trzeb u jesz w p rzy p ad k u u d erz e n ia piłki, jest zw iązane
z użyciem z d a rz e n ia . Z d arz e n ie to coś, co się wydarza w Tw oim program ie.
In n e obiekty m ogą n a nie odpow iadać — ta k ja k nasz o b iek t P it c h e r . — sytuacja, która wystąpiła bądź
wystąpi, zwłasza coś ważnego.
Jest naw et lepiej, bo nasłuchiw ać zd arzen ia m oże więcej niż je d e n obiekt. Zaćmienie słońca było
Przy zdarzeniu u d erze n ia piłki nasłuchiw ać m oże P i t c h e r , ale rów nież C a tc h e r, niezapomnianym zdarzeniem.
T hirdB asem an, Um pire, a naw et Fan. K ażdy z obiektów m oże różnie na
nie odpow iadać.

P otrzebujem y zatem o b iek tu B a l l , k tóry m ógłby g e n e ro w a ć z d a rz e n ia .


B ędziem y także korzystali z innych obiektów , k tó re o b s łu ż ą d a n y ic h ty p ...
O znacza to, że b ęd ą o n e nasłuchiw ały i zo stan ą p ow iadom ione, jeżeli o kreślone
zdarzenie wystąpi.

Kiedy obiekt Bali z nich 9° nasłuchują.


zostaje uderzony

Zdarzenia w IDE
symbolizują błyskawice.
Ikonę podobną do tej Obiekt Fan nasłuchuje
zobaczysz przy nich pitcher oraz inni zdarzenia polegającego
w IntslliSsnss i w oknach S ę d z ia sp ra w d z a każde
oracze próbują zagranie, aby zadecydować, na tym, że piłka leci
właściwości. wyłapać pitkę- w trybuny.
czy było prawidłowe.
Obserwuje całe zdarzenie.

Chcesz Z R O B IĆ C O Ś ze zdarzeniem? Potrzebujesz procedury jego obsługi


M ożesz wstaw ić do sw ojego p ro g ra m u pew ien kod, k tóry b ędzie u rucham iany, gdy o b iek t „usłyszy”
o zdarzeniu. K od tak i nazyw am y p ro c e d u r ą o b słu g i z d a rz e n ia . O trzym uje o n pew ne dotyczące go
inform acje i je st wykonywany za każdym razem , gdy zostanie o n o wywołane. Korzystaliśmy
z tego przez cały
czas. Za każdym
Pam iętaj, wszystko to dzieje się bez Twojego pośrednictwa podczas działania pro g ram u . N ajpierw razem po kliknięciu
piszesz kod generujący zd arzenia, a n astęp n ie kod, który je obsługuje i pozw ala ożywić aplikację. przycisku wywoływane
P o tem , kiedy dane zd arzen ie zachodzi, u ru ch am ian e są T w oje p ro ced u ry o b s ł u g i . bez żadnej
jest zdarzenie.
Twój kod może na
ingerencji z Twojej strony. N ajlepsze w tym w szystkim jest to, że w te n sposób każdy o b iek t m a swoje nie odpowiednio
w łasne z ad an ia. Z ajm u je się sam ym sobą, a nie innym i obiektam i. zareagować.

jesteś tutaj ► 731


J e ś li w le s ie p r z e w r ó c i s ię d r z e w o ..

Jeden obiekt wywołuje zdarzenie, inne nasłuchują...


Przyjrzyjmy się dokładnie tem u , w jaki sposób w C # działają zdarzenia,
p ro ced u ry ich obsługi i subskrypcje:

® N a po czątku in n e obiekty subskrybują zd arze n ie .


Z an im o b iek t B a ll b ędzie m ógł wywołać zdarzenie B a ll ln P l a y , in n e m uszą
je subskrybow ać. T o swego ro d zaju w yrażenie chęci otrzym yw ania inform acji
o każdym takim zdarzeniu.

Każdy obiekt dodaje


swoją własną
procedurę obsługi
zdarzenia w celu
jego iwstuchiwania
— tak samo ■*-
ja k dodawałeś ----
button1_Click() ■*
do programu,
aby nasłuchiwać Z d arzen ie B allln Play
zdarzenia kliknięcia
przycisku.

(2 C o ś inicjuje zd arze n ie .
Piłka zostaje u d erzo n a. N ad szed ł czas, aby o b iek t B a ll wywołał zdarzenie.

Czasami mówimy,
że zdarzenie zostało
wygenerowane,
zainicjowane,
zasygnalizowane —
wszystko to są określenia
jednej rzeczy. Ludzie
po prostu używają
dla niej różnych nazw.

Piłka w yw o łu je zd arze n ie .
T w orzone je st now e zd arzen ie (jak to się dokład n ie dzieje, pow iem y za m inutę).
M a ono kilka arg u m en tó w takich ja k p ręd k o ść piłki i tra je k to ria lotu. Z o stają
o n e z nim pow iązane w instancji klasy E v en tA rg s. Z d a rze n ie rozsyłane je st do
wszystkich, którzy zgłosili się do nasłuchiw ania.

i tra je k to rii.

732 Rozdział 15.


Zdarzenia i delegaty

Potem inne obiekty obsługują zdarzenie


Po w yw ołaniu zdarzen ia wszystkie obiekty zgłaszające jego subskrypcję
dostają o nim pow iadom ienie i m ogą coś zrobić:

Sub skryb enci d o sta ją pow iadom ienie.


W zw iązku z tym , że P i t c h e r , Umpire o raz Fan zgłosiły chęć
otrzym yw ania pow iadom ień przy zd arzen iu B a ll I n P la y o biektu
B a l l , wszystkie są o tym fakcie inform ow ane — ich p ro c e d u ry obsługi
zdarzenia zostają w ywołane je d n a p o drugiej.

O b ie k* ^
Za.raz po zajściu zdarzenia
tworzony jest obiekt BallEventArgs,
Procedura obsługi zdarzenia a w nim zapi'sywane są informacje
jest metodą znajdującą się o szybkości i trajektorii piłki. Dzięki
w obiekcie subskrybenta, temu dane mogą zostać przekazane
która jest wywoływana do obiektów subskrybentów.
po jego wygenerowaniu.
Zdarzenia obsługiwane są
według zasady pierwszy
K a żd y obiekt obsług uje zd arze n ie . przyszedł, pierwszy
obsłużony — obiekt,
T eraz obiekty P i t c h e r , Umpire o ra z Fan m ogą obsłużyć zd arzen ie B a ll I n P la y który zaczął nasłuchiwać
n a swój sposób. N ie ro b ią teg o je d n a k w tym sam ym czasie — ich p ro ced u ry jako pierwszy, otrzyma
obsługi zdarzen ia wywoływane są je d n a p o drugiej, otrzym ując referen cję powiadomienie w pierwszej
kolejności.
o b iek tu B a llE v e n tA rg s w p ostaci p ara m e tru .
x m usi pracow ać każdy
Oto coś, z czym _, pow inien
• obiekt o b słu g u jąc y z <
i n - obiektu,
on tak ż e otrzym yw ać re fe re n cją
ktćry z g to sit z d arzen ie.
IllnPlay
Ot>iekt Fan sprawdza
BallEventArgs, aby
przekonać się, czy jest
Obiekt Pitcher sprawdza wystarczająco blisko,
BallEventArgs i j e ś ' pitka
żeby złapać piłkę.
je s t w pobliżu, fapie ją-

-%o P itc ^
O
For,
U ri? '* 0
Obiekt Umpire wszystko obserwuje.
Może oczywiście subskrybować także inne
zdarzenia, na przykład BallFielded lub
BallThrown, aby r-eagować na ich wystąpienie.
jesteś tutaj ► 733
P r z y s z e d łe m t u t a j p o a r g u m e n t

Łącząc punkty
T eraz, kiedy m asz już ogólne pojęcie o działaniu
zdarzeń, przyjrzyjmy się dokładniej połączeniom EventArgs
pom iędzy poszczególnym i elem entam i. N a szczęście
zm ienia się tylko kilka rzeczy. "X
O znacza to, S /
że m ożesz obiekt
3 S T j~ r zrzu to w a ć w górą, gdy
p o trz e b u je sz p rzekazać
- r r **** qo do zdarzenia, które
publicznych składow ych. a k u r a t tego określonego
typu nie o b słu g u je.
© Potrzebujem y obiektu p rzech o w u ją ceg o arg u m en ty z d a rze n ia
Pam iętaj, nasze zdarzenie B a l l l n P l a y p o siad a kilka przekazyw anych A
dalej argum entów . P o trzeb u jem y dla nich p ro steg o obiektu. .N E T p o siada
stan d ard o w ą klasę o nazw ie E v en tA rg s, ale n ie m a o n a ż a d n y c h składow ych. BallEventArgs
Trajectory
Jej głównym zad an iem je st um ożliw ianie przekazyw ania Tw oich obiektów do Distance
p ro c e d u r obsługi zdarzeń, k tó re b ę d ą ich używać. O to dek laracja Twojej klasy:

Pitka będzie używał


class BallEventArgs : EventArgs tych wtaściwości
do przekazywania
procedurom obsługi
zdarzenia parametró
j ej ruchu.

M usim y te ra z zd efin io w ać z d a rz e n ie w klasie, która b ę d zie je w yw o ływ ać.


K lasa piłki będzie p o siad ała w iersz ze słow em kluczow ym e v e n t — to w łaśnie za jego
pośrednictw em in n e obiekty b ę d ą inform ow ane o zajściu ok reślo n eg o zd arzen ia i będą
m ogły je subskrybow ać. T e n w iersz m oże zostać um ieszczony w dow olnym miejscu
klasy — zwykle znajduje się o b o k deklaracji właściwości. G dy taki w iersz pojaw i się
w klasie B a ll, in n e o biekty m ogą zgłosić chęć subskrypcji zd arzen ia piłki. Spotkałeś
się już z zastosow aniem tego słowa kluczow ego przy okazji wywoływania zdarzeń
P ro p e rty C h a n g e d . O to ja k w ygląda dek laracja zd arzen ia B a ll ln P l a y :

public event EventHandler BalllnPlay;

Zdarzenia zwykle są
publiczne. To zdefiniowane
jest w klasie Ball, ale
chcemy, aby klasy Pitcher,
\
Po stowie kluczowym event umieszczane iest
EventHandler. Nie j est t0 st0wo kluczowe
Umpire i inne mogły z niego
korzystać. Jeśli chcesz, aby — jest ono zdefiniowane
miały do niego dostęp tylko obois- w? . NET. Potrzebne jest do wskazania
pt>iektom subskrybującym zdarzenie, w jaki sposólo
inne instancje tej samej powinny wyglądać ich procedury jego o b j^ s ^
klasy, możesz użyć słowa
kluczowego private.

Kiedy używasz Event^Hand|er, przekazujesz innym metodom


że ich procedury obsługi zdarzenia muszą mieć dwa
parametry: sender typu object oraz e typu EventArgs.
sender jest referencją do obiektu, który wywołał zdarzenie
natomiast e — referencją do obiektu EventArgs.
734 Rozdział 15.
Zdarzenia i delegaty

K lasy n asłu ch u jące potrzebują p ro ced u r obsługi zd arze n ia.


K ażdy obiek t, k tóry zam ierza nasłuchiw ać zd arzen ia B a l l l n P l a y o b iek tu B a ll, m usi posiadać
p ro ced u rę jego obsługi. Już wiesz, w jak i sposób tak a p ro c e d u ra działa. Z a każdym razem , gdy
dodaw ałeś m eto d ę do obsługi kliknięcia przycisku lub zd arzen ie V alueC hanged przy k o ntrolce
NumericUpDown, ID E dodaw ało p ro c e d u r ę o b słu g i z d a rz e n ia do klasy. B a l l l n P l a y klasy B all
niczym się nie różni, w ięc p ro c e d u ra p o w inna w yglądać dość znajom o:

void ball BallInPlay(object sender, EventArgs e)

Nie me żadnej procedur p;0<iedury obsfugi zdarzenia BaiiJnPiay


a j ako £ventHand/er, co oznacza
s s ^ Ś ^ s s z r s s z ze przyjrnuje dwa parametry — obiekt o nazwie
« m * ; oraz obiekt e typu EventArgs —o
wartości wynikowej. a ' n'e ma

t
K/asa posiadająca tę okreś/oną pr?cedurę , pu
obsługi zdarzenia ma referencj ę obiektu dypu
Ba// o nazwie ba//, więc nazwa, tej p™ ^
będzie się zaczynać od J5a//— . Da/sza c£ęś
będzie miała postać nazwy zdarzenia, w ty™
przypadku Ba//InP/ay.

© K a żd y po jedyn czy o biekt subskrybuje zd arze n ie .


G dy m am y już zdefiniow ane zd arzenie, m ożem y skojarzyć z nim poszczególne p ro ced u ry obsługi
zdarzeń z obiektów P i t c h e r , Um pire, T hirdB asem an o raz Fan. K ażdy z nich b ędzie m iał swoją
w łasną m eto d ę b a l l _ B a l l I n P l a y , k tó ra m oże różnie n a zd arzen ie reagow ać. Jeżeli istnieje
referen cja lub p o le b ęd ące o b iek tem typu B a ll o nazw ie b a l l , to o p e ra to r += skojarzy p ro ced u rę
jego obsługi:

ball.BalllnPlay += new EventHandler(ball BalllnPlay);

To nakazuje C# dodać Ta część okreś/a


procedurę obstugi zdarzenia metodę, która będzie
Operator += kojarzy subskrybentem zdarzenia.
do Ba//InP/ay dowo/nego procedurę obsługi ze
obiektu wskazywanego przez
referencję ba//.
zdarzeniem.
^ .
Sygnatura metody obsługującej
.
zdarzenie (jej parametry i wartość
wynikowa) musi być zgodna
ze zdefiniowaną przez EventHand/er.
W przeciwnym razie program nie
będzie chciał się kompi/ować.

► Przewróć stronę, jest łeęo trochę w ięcej...


jesteś tutaj ► 735
N ie w y w o łu j m n ie ; to ja w y w o ła m c ie b ie

© O b iekt Ball g e n e ru je zd a rze n ie , a b y po w iad o m ić subskrybentów , ż e jest w grze.


T eraz, gdy wszystkie p ro ced u ry są u staw ione, B a ll m oże w ygenerow ać zd arzen ie w odpow iedzi n a coś,
co m iało m iejsce w sym ulatorze. W ygenerow anie zd arzen ia B a ll I n P la y je st p ro ste — w ystarczy je wywołać.
BallImPlay jest kopiowane do zmiennej balllnPlay,
EventHandler b a l l l n P l a y = B a l l l n P l a y ; która p° sprawdzeniu, czy nie jest równa null,
if (b a llln P la y != n u l i ) e jest nowym zostaje użyta do zgłoszenia zdarzenia.
obiektem BallEventArgs.
b a llIn P la y (th is, e); ■■■tworząc nowu
Pitka zostaje uderzona obidct BallEventArqs Jeżeli generujesz
i obiekt Ball wkracza wt^ lw ym ¡ danymi...
zdarzenie bez
do akcji...
& procedur obsługi,
to otrzymasz
...i przekazując go wyjątek.
^ do wywoływanego
i/ zdarzenia. Jeżeli żaden obiekt nie dodał swojej
procedury obsługi zdarzenia, to jego
wartość będzie równa n u l l . Zawsze
sprawdzaj swoje zdarzenie przed
Zd arzenie B alllnPlay wywołaniem, aby upewnić się, że
nie przyjmuje tej wartości. Jeśli tego
O^elrt typu pitcher wrzenie nie zrobisz, to m ożesz otrzymać
skojarzył swoją
N u llR e fe r e n c e E x c e p tio n .
C pZ S uZm lsaŻ Piey t)e ó To także z tego powodu przed
obiektu Ball. sprawdzeniem, czy zdarzenie
jest równe n u l l , powinieneś
Metoda klasy
Pitcher będzie je skopiow ać do zm ien n ej —
więc wywoływana w pewnych niezwykle rzadkich
z właściwymi sytuacjach zdarzenie przyjmuje
danymi wartość n u l l pomiędzy
i może zrobić
Pite! ze zdarzeniem, sprawdzeniem jego wartości
co tylko chce. a jego wywołaniem.

Kiedy dodajesz metodę do wywołania zdarzenia, użyj standardowej nazwy


Pośw ięć chwilę i przejdź do k o d u dow olnego form ularza. W pisz słowo kluczow e o v e r r i d e w dow olnym miejscu,
w którym m ożesz zadeklarow ać m eto d ę. Z a ra z p o naciśnięciu spacji IntelliS en se wyświetli okno:

Czy zwróciłeś uwagę na to, że wszystkie


te metody przyjmują parametr typu
EventArgs? WSzystkie one przekazują go
do zdarzenia, które wyw°tują.

Istnieje o grom na liczba zd arzeń , k tó re m ogą zostać w ywołane p rzez o b iek t Page X A M L , a każde z nich
posiad a sw oją w łasną m eto d ę, k tó ra je generuje. M eto d a strony O nD oubleT aped() g en eru je zdarzenie
D oubleT aped i jest to jedyny cel jej istnienia. Z d a rz en ie klasy B a ll będzie się trzym ało tej sam ej konw encji.
U pew nim y się, że w k la s ie z n a jd u je się m e to d a O n B a llI n P la y ( ) , k tó ra p o b ie ra o b iek t B a llE v e n tA rg s
jako p a ram etr. S ym ulator gry w baseball b ędzie wywoływał tę m e to d ę za każdym razem , gdy b ędzie chciał
zasygnalizow ać zdarzen ie B a ll I n P la y . K iedy wykryje u d e rz e n ie kija w piłkę, utw orzy now ą instancję
B a llE v e n tA rg s z jej tra je k to rią i odległością, k tó re zo stan ą p rzek azan e do O n B a llI n P la y ( ) .

736 Rozdział 15.


Zdarzenia i delegaty

P Dlaczego muszę użyć słowa


:
O nD ra gE n te r() pobiera referencję
D ragEventArgs zamiast EventArgs.
ona procedur dodanych wcześniej. Stanie
się po prostu jedną z wielu możliwych
EventHandler podczas deklaracji
Dziedziczy ona po E ventA rgs podobnie w łańcuchu. Wszystkie będą nasłuchiwały
zdarzenia? Myślałem, że procedura
jak B a llE v e n tA rg s . Zdarzenie tego samego zdarzenia.
jego obsługi jest tym, czego obiekty
strony DragDrop nie używa obiektu
używają do jego subskrypcji.
E ven tH an dle r. Korzysta z czegoś P : Dlaczego piłka używa th is
O : To prawda — kiedy chcesz innego — obiektu D ragE ventH andler. podczas sygnalizowania zdarzenia
B a llIn P la y () ?
subskrybować zdarzenia, piszesz metody Jeśli chcesz je obsłużyć, musisz przyjąć typ
do ich obsługi. Czy zwróciłeś jednak
uwagę na sposób użycia E ven tH an dle r
o b je c t oraz referencję DragEventArgs.
Parametry zdarzenia definiowane są
O : Ponieważ jest to pierwszy parametr
standardowej procedury jego obsługi. Czy
w deklaracji zdarzenia (krok numer 2) i na przez coś, co nazywamy delegatami —
zwróciłeś uwagę na to, że każda metoda
wiersz, który kojarzy z nią obsługującą je E ve n tH a n d le r oraz D ragE ventH andler
C lic k ( ) obsługująca zdarzenie kliknięcia
procedurę (krok numer 4)? E ven tH an dle r są właśnie nimi. Powiemy sobie więcej na
ma parametr o b je c t sender? Jest on
definiuje sygnaturę zdarzenia. Nakazuje ich temat za chwilę.
referencją obiektu, który w yw ołu je
obiektom, które je subskrybują, definiować
ich funkcje obsługi w ściśle określony P : Czy zatem moje funkcje obsługi
zdarzenie. Gdy obsługujesz kliknięcie
przycisku, sender wskazuje właśnie na
sposób. W szczególności sprawia, że każda zdarzeń mogą zwracać coś innego
niego, a gdy obsługujesz B a llln P la y ,
metoda do obsługi zdarzenia musi posiadać niż void ?
sender będzie wskazywał na obiekt B a ll
dwa parametry (o b je c t oraz referencję
E ventA rgs) i zwracać wartość typu vo id . O : Tak, mogą, ale zazwyczaj nie jest w grze. Piłka podczas sygnalizowania
zdarzenia ustawia ten parametr na t h is .
to dobry pomysł. Jeżeli ze swojej metody
P : Co się stanie, jeśli spróbuję użyć obsługi zdarzenia nie zwrócisz wartości
v o id , nie będziesz mógł tworzyć
metody, która nie jest zgodna z tą
zdefiniowaną przez EventHandler? łańcuchów metod obsługi zdarzenia. POJEDYNCZE
Oznacza to, że nie będziesz mógł skojarzyć
O : Program nie skompiluje się. Kompilator z nim więcej niż jednej procedury obsługi. zdarzenie
zadba o to, abyś przez przypadek nie
skojarzył ze zdarzeniem procedury obsługi,
Ponieważ łączenie ich w łańcuchy jest cenną
możliwością, powinieneś zawsze zwracać
zawsze
która nie będzie z nim zgodna. To między
innymi dlatego standardowa procedura
v o id ze swoich metod obsługi zdarzeń. sygnalizowane
obsługi zdarzenia E ve n tH a n d le r jest P : Łączenie w łańcuchy? jest przez
tak użyteczna — widząc ją, nie będziesz A cóż to jest?
miał żadnych wątpliwości co do wyglądu
JEDEN obiekt.
metody. O : To w ten sposób wiele obiektów może

P Zaczekaj, „standardowa”
:
subskrybować jedno zdarzenie — łączą
one swoje procedury obsługi zdarzeń jedną
Na
procedura obsługi zdarzenia? po drugiej. Powiemy sobie o tym więcej
za chwilę.
POJEDYNCZE
To istnieją jakieś inne rodzaje takich
funkcji? zdarzenie może
P : To dlatego używałem += podczas
odpowiedzieć
O : Tak! Twoje zdarzenia wcale nie muszą pisania swojej metody obsługi
przekazywać zmiennych typu o b je c t
oraz E ve n tA rg s. W zasadzie mogą
zdarzenia? To tak, jakbym dodawał
nową metodę do już istniejących.
W IELE
przekazywać wszystko... lub nic! Popatrz
O : Dokładnie! Za każdym razem, gdy
obiektów.
na okno InteHiSense na dole poprzedniej
strony. Zwróć uwagę na to, że metoda dodajesz procedurę obsługi zdarzenia,
używasz +=. W ten sposób nie zastąpi

jesteś tutaj ► 737


To n a m o s z c z ę d z i w p is y w a n ia

IDE automatycznie tworzy za Ciebie procedury obsługi zdarzeń


Większość programistów korzysta z tej samej konwencji nazywania swoich procedur
obsługi zdarzeń. Jeżeli istnieje obiekt B all, który ma zdarzenie B alllnPlay, a referencja
przechowująca obiekt to także b a ll, procedura obsługi zdarzenia otrzyma standardową
nazwę b a ll_ B a llIn P la y (). Nie jest to jedyna i sztywna zasada, ale jeśli będziesz pisał swój
kod w ten właśnie sposób, będzie on bardziej czytelny dla innych programistów.

Na szczęście IDE znacznie ułatwia prawidłowe nadawanie nazw procedurom obsługi


zdarzeń. Posiada ono pewną opcję, która automatycznie dodaje je za Ciebie podczas
pracy z klasą posiadającą zdarzenie. Nie powinno to być dla Ciebie zaskoczeniem —
w końcu jest to dokładnie to samo, co IDE robi za Ciebie w momencie dwukrotnego
kliknięcia przycisku podczas projektowania formularza. (To może Ci się wydać znajome,
gdyż robiłeś to już w poprzednich rozdziałach).
Z ró b to !

j ) Utwórz n o w ą pustą aplikację dla Sklepu W indows i dodaj klasy Ball oraz BallEventArgs.
Oto klasa Ball :
class Ball {
public event EventHandler BalllnPlay;
public void OnBallInPlay(BallEventArgs e) {
EventHandler balllnPlay = BalllnPlay;
i f (balllnPlay != null)
b allIn P lay(th is, e);
}
}

A oto klasa BallEventArgs:


class BallEventArgs : EventArgs {
public int Trajectory { get; private set; }
public int Distance { get; private set; }
public BallEventArgs(int trajectory, int distance) {
this.T rajectory = trajectory;
this.D istance = distance;
}
}

[ 2) Rozpocznij od d odania konstruktora obiektu Pitcher.


Dodaj do projektu nową klasę Pitcher. Wstaw do niej konstruktor, który przyjmuje parametr w postaci
referencji klasy Ball o nazwie b a ll. W konstruktorze powinien się znajdować jeden wiersz, który doda
procedurę obsługi zdarzenia do b a ll.B a llIn P la y . Rozpocznij wpisywanie kodu, ale nie wstawiaj jeszcze +=.
public Pitcher(Ball b all) {
ball.B allInPlay
}

738 Rozdział 15.


Zdarzenia i delegaty

W pisz +=, a IDE d o ko ń czy z a C ieb ie instrukcję.


Z a ra z p o w pisaniu w instrukcji + = ID E wyświetli b ard zo p o m o cn e niew ielkie pole:

Po naciśnięciu klaw isza Tab ID E dokończy instrukcję. B ędzie o n a w yglądała następująco:

ball^BalllnPlay += ball_BallInPlayj

IDE d o d a ta k ż e pro ced u rę obsługi zd a rze n ia .


Jeszcze nie skończyłeś — w dalszym ciągu p o trzeb u jesz m eto d y obsługującej zdarzenie.
N a szczęście ID E i w tym przy p ad k u zajm ie się tym za C iebie.

N aciśnij klawisz Tab jeszcze raz, aby ID E d odało do klasy Pitcher poniższą m e to d ę obsługi zdarzenia.
ID E zawsze będzie generow ało nazwy w edług konw encji (n a zw a O b ie k tu _Nazw aM etody ()):

void ball_BallInPlay(object sender, EventArgs e) {


throw new NotImplementedException();
} IDE za w sze w stawia t(jtaj NotImplementedException()
jako pewnego rodzaju wyp e tniacz. Jeżeli uruchomisz
kod, zo st anie zg łoszony wyjątek, który poinformuje
Cię, że wciąż m u sisz zaimplementować coś, co zostato
autom atycznie wygenerowane.
D okończ m eto d ę obsługi z d a rz e n ia kla sy Pitcher .
T eraz, kiedy m asz już szkielet p ro ced u ry obsługi zd arzen ia d odany do klasy, uzupełnij resztę kodu.
O b iek t klasy Pitcher pow inien wyłapywać w szystkie niskie piłki. Jeśli takich nie m a, pow inien pilnow ać
pierw szej bazy. W zw iązku z tym, że BallEventArgs

void ball_BallInPlay(object sender, EventArgs e) {^ ' j<j zrZUtoWzćCW0dótim ^ m o K ą ¡ ^ o w m ^


i f (e is BallEventArgs) { kluczoweg ° as i' uży wać je j właściwości.
BallEventArgs ballEventArgs = e as BallEventArgs;
i f ((ballEventArgs.Distance < 29) && (ballEventArgs.Trajectory < 60))
CatchBall();
e lse Te metody dodasz za chwilę.
CoverFirstBase();
}
}

jesteś tutaj > 739


Połącz to ze sobą

Czas wykorzystać nabytą wiedzę w praktyce. Twoim zadaniem jest dokończenie klas B a ll
i P i t c h e r , dodanie klasy Fan oraz zadbanie o to, aby wszystko razem działało jako bardzo prosty
symulator gry w baseball.
Ćwiczenie

DOKOŃCZ KLASĘ PITCHER.


Poniżej zaprezen to w an o dotychczasow y k o d tej klasy. D odaj m eto d y C a t c h B a l l( )
o raz C o v e r F i r s t B a s e ( ) . O bie pow inny wypisywać k o m u n ik at określający, czy zaw odnik złapał piłkę,
czy pobiegł do pierw szej bazy, i dodaw ać go do kolekcji typu O b s e r v a b le C o lle c t io n < s t r in g > o nazwie
P it c h e r S a y s .

c la s s P it c h e r {
p u b lic P it c h e r ( B a ll b a ll) {
b a l l . B a l l l n P l a y += new E v e n tH a n d le r(b a ll B a l lI n P la y ) ;
}

v o id b a ll_ B a llI n P la y ( o b je c t se n d e r, EventA rgs e) {


i f (e i s B a llE v e n tA rg s ) {
B a llE v e n tA rg s b a llE v e n tA rg s e as B a llE v e n tA rg s ;
i f ((b a llE v e n t A r g s .D is t a n c e < 29) && (b a llE v e n t A r g s .T r a je c t o r y < 6 0 ))
C a t c h B a ll()
e ls e Będ ziesz m usiał
C o v e r F ir s t B a s e (); zaimplementować te dwie
metody, aby w ypisać wynik
w oknie konsoli.

pjtcV '«*

3 NAPISZ KLASĘ FAN.


S tw órz in n ą klasę o nazw ie Fan . O n a także m usi w k o n stru k to rze subskrybow ać zdarzenie
B a l l I n P l a y . M e to d a jego obsługi p o w inna spraw dzać, czy odległość jest w iększa niż
120 m etrów i tra je k to ria w iększa niż 30 (hom e run). W takim p rzy p ad k u Fan pow inien
w ziąć rękaw icę i złapać piłkę. Jeżeli w arunki nie są spełnione, m a za zadanie krzyczeć
i śpiewać. W szystko, co fan wykrzykuje i śpiew a, pow inno być dodaw ane do kolekcji typu
O b s e r v a b le C o lle c t io n < s t r in g > o nazw ie F a n S a y s .

7 Popatrz na wyniki
przeds taw i°ne na kolejnej
stronie, aby zapoznać s ię
z tym, c ° powinno zostać
wypisane.

740 Rozdział 15.


Zdarzenia i delegaty

STWÓRZ BARDZO PROSTY SYMULATOR.


Jeśli jeszcze tego nie zrobiłeś, to u tw órz p u stą aplikację dla S klepu W indow s (B lank A p p ),
zastąp plik M ainPage.xaml stro n ą w ygenerow aną n a podstaw ie szablonu B asic Page
i dodaj do p ro je k tu n astęp u jącą klasę BaseballSim ulator .

using System.Collections.ObjectModel;

class BaseballSimulator {
private Ball ball = new B all();
private Pitcher pitcher;
private Fan fan;
public ObservableCollection<string> FanSays { get { return fan.FanSays; } }
public ObservableCollection<string>
PitcherSays { get { return pitcher.PitcherSays; } }
public int Trajectory { get; set; }
public int Distance { get; set; }
public BaseballSimulator() {
pitcher = new P itcher(ball);
fan = new Fan(ball);
}
public void PlayBall() {
BallEventArgs ballEventArgs = new BallEventArgs(Trajectory, Distance);
ball.OnBalllnPlay(ballEventArgs);
}
}
[4 UTW ÓRZ STRONĘ GŁÓWNĄ
Czy potrafisz podać kod X A M L, strony patrząc n a jej
wygląd przedstaw iony z prawej strony? Dwie kontrolki
Sym ulator gry w baseball
TextBox są pow iązane z właściwościami Trajectory oraz Miotacz mówi;
Distance obiektu BaseballSimulator zdefiniowanego Rn/t nr 1: Pokryłem pterywzj ba/ę.

jako zasób statyczny; z kolei wypowiedzi zaw odnika i fana Rzut nr Ł Złapałem piłkę.

są prezentow ane w kontrolkach ListView powiązanych


Rn/t nr .fc Pokryłem pierw uą ba/ę.
z kolekcjami ObservableCollection .
I PttfcjWgfCTl Fan mówi:
P rzekonaj się, czy jesteś w stan ie zm usić sym ulator Rn/t nr 1; Jeee! Do boju!

do w ygenerow ania powyższych tekstów wygłoszonych Nie zapomnij Rzut nr Ł Jcccl Do b

przez zaw odnika i fa n a podczas w pro w ad zen ia do gry o dodaniu procedury


obsługi kliknięć Iłom e ru ni Idę p o p
trzech kolejnych piłek. Poniżej zapisz w artości, których dla tego przycis k u .
użyłeś, by uzyskać te wyniki.

Piłka 1.: Piłka 2.: Piłka 3.:

T rajek to ria: .................................... T raje k to ria: .................................... T rajek to ria:

O dległość: ....................................... O dległość: ........................................ O dległość: ..

jesteś tutaj ► 741


Rozwiązanie ćwiczenia

Czas wykorzystać nabytą wiedzę w praktyce. Twoim zadaniem jest dokończenie klas Ball i Pitcher, dodanie
klasy Fan oraz zadbanie o to, aby wszystko razem działało jako bardzo prosty symulator gry w baseball.
R.OZwi^Z9flis cla ss Ball
cwiczenia
public event EventHandler BallInPlay;
public void OnBallInPlay(BallEventArgs e) {
EventHandler ballInPlay = BallInPlay;
i f (ballInPlay != null)
b allIn P lay(th is, e);
} Metoda 0n8a//InP/ay0 ty/ko wywołuje
zdarzenie 8 a//JnP/ay — wcześniej musi
} s prawdzić, czy nie je s t ono równe nu//.
J eże/i je s t , zostanie zgłoszony wyjątek.
cla ss BallEventArgs : EventArgs
Automatyczne {
w łaściw ości tylko
do odczytu bardzo
public int Trajectory { get; private set; }
dobrze spraw u ją public int Distance { get; private set; }
s ię jako argumenty public BallEventArgs(int trajectory, int distance) {
w zdarzeniach,
ponieważ funkcje this.T rajectory = trajectory;
ich obsługi tylko this.D istance = distance;
odczytują przekazane
do nich dane. }
}

using System.Collections.ObjectModel;
cla ss Fan {
public ObservableCollection<string> FanSays new ObservableCollection<string>();
private int pitchNumber = G;
Konstruktor obiektu Fan
dołącza s ię do t a ñ e r a
public Fan(Ball ball) j procedur obsługi
zdarzenia Ba//InP/ay .
ball.B allInP lay += i EventHandler(ball_BallInPlay);
j
void ball_BallInPlay(object sender, EventArgs e) j
Metoda obsługi
pitchNumber++;
zdarzenia kibica i f (e is BallEventArgs) j
sprawdza, czy piłka BallEventArgs ballEventArgs = e as BallEventArgs;
leci wysoko i daleko.
i f (ballEventArgs.Distance > 12G && ballEventArgsr.aTjectory > SG)
FanSays.Add("Rzut nr " + pitchNumber
+ Home run! Idę po piTkę!");
e lse
FanSays.Add("Rzut nr " + pitchNumber + ": Jeee! Do boju!");
j

}
Jedynym kodem ukrytym potrzebnym na tej stronie jest procedura obsługi zdarzeń Button_Click():
private void Button_Click(object sender, RoutedEventArgs e) {
baseballSim ulator.PlayBall();
}

742 Rozdział 15.


Ten za só b sta tyc zn y ma być um ieszczony Z d a rz e n ia i d e le g a ty
w elem encie Page.Resource.

-------------------------------------------------------------------s r -------------------------------------------
Oto kod XAML strony. Będziesz musiał dodać do niego także znacznik: <local:BaseballSimulator x:Name="baseballSimulator"/>.
<Grid Grid.Row="1" Margin="120,0" DataContext="{StaticResource ResourceKey=baseballSimulator}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,40,0">
<TextBlock Text="Trajektoria" Style="{StaticResource GroupHeaderTextStyle}" Margin="0,0,0,20"/>
<TextBox Text="{Binding Trajectory, Mode=TwoWay}" Margin="0,0,0,20"/>
<TextBlock Text="Odległość" Style="{StaticResource GroupHeaderTextStyle}" Margin="0,0,0,20"/>
<TextBox Text="{Binding Distance, Mode=TwoWay}" Margin="0,0,0,20"/>
<Button Content="PiTka w grze!" Click="Button_Click"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Text="Miotacz mówi:" Style="{StaticResource GroupHeaderTextStyle}" Margin="0,0,0,20"/>
<ListView ItemsSource="{Binding PitcherSays}" Height="150"/>
<TextBlock Text="Fan mówi:" Style="{StaticResource GroupHeaderTextStyle}" Margin="0,0,0,20"/>
<ListView ItemsSource="{Binding FanSays}" Height="150"/>
</StackPanel>
</Grid>

A poniżej przedstawiliśmy kod klasy Pitcher (powyżej niego musisz dodać instrukcję using System.Collections.ObjectModel;):
class Pitcher {
public ObservableCollection<string> PitcherSays = new ObservableCollection<string>();
private int pitchNumber = 0;

public Pitcher(Ball ball) { Procedurę obstugi zdarzeń


ball.B allInP lay += ball_BallInPlay; BalllnPlay p r z e d s ta w im y
} ju ż w cześniej — interesuje
s ię tylko niskimi p ifta m -
void ball_BallInPlay(object sender, EventArgs e) {
pitchNumber++;
i f (e is BallEventArgs) {
BallEventArgs ballEventArgs = e as BallEventArgs;
i f ((ballEventArgs.Distance < 29) && (ballEventArgs.Trajectory < 60))
CatchBall();
e lse
CoverFirstBase();
}

private void CatchBall() {


PitcherSays.Add("Rzut nr " + pitchNumber + ZTapaTem pitkę.");
}
private void CoverFirstBase() { Oto wartości,
PitcherSays.Add("Rzut nr " + pitchNumber + Pokryłem pierwszą b azę.1 których u żyliśm y do
} uzyskania wyniku
Iwoje mogą być
Piłka 1.: Piłka 2.: Piłka 3.: nieco inne. <

Trajektoria: . .7.5.. Trajektoria: ..if.?.. Trajektoria: ...4?..


i

Odległość: ... 31 Odległość: .. 24 Odległość: . 132

jesteś tutaj ► 743


P rz e d s ta w ia n ie s tro n y z d a rz e ń

Ogólny typ EventHandler pozwala definiować własne typy zdarzeń


Przyjrzyj się deklaracji zdarzenia umieszczonej w klasie B a ll:
public event EventHandler BallInPlay;
A teraz obejrzyj deklarację zdarzenia Click dostępnego w formularzach, przyciskach oraz większości innych
kontrolek, jakich miałeś okazję używać:
public event EventHandler Click;
Czy coś zauważyłeś? Oba zdarzenia mają inne nazwy, lecz zostały zadeklarowane w identyczny sposób. I choć
takie rozwiązanie działa bez zarzutów, to ktoś analizujący Twój kod niekoniecznie domyśli się, że w momencie
zasygnalizowania zdarzenia do BallEventHandler zostaną przekazane argumenty typu BallEventArgs.
Na szczęście .NET udostępnia narzędzie, które w bardzo prosty sposób to komunikuje: ogólny typ
EventHandler. Zmień swoją procedurę obsługi zdarzenia BallInPlay w następujący sposób:
Ogólny argument
public event EventHandler<BallEventArgs> BallInPlay; przekazywany
do EventHandler
Będziesz także musiał zmodyfikować metodę OnBallInPlay(), zastępując zastosowany w niej mu si być klasą
dziedziczącą po
typ EventHandler typem EventHandler<BallEventArgs>. Następnie przebuduj swój projekt. Eve n tA rg s.
W oknie Error L ist powinien się pojawić komunikat o błędzie:

Error List D
T - O l Error | ! 0 Warnings Search Error List

Description File ^ Line


(X) 1 Cannot implicitly convert type 'System.EventHandler' t o 'System.EventHandler< BaseballSimulatorApp.BallEventArgs>' Fan.cs
i i

Teraz, gdy już zmieniłeś deklarację zdarzenia, powinieneś także zmodyfikować odwołanie do zdarzenia
umieszczone w klasie B a ll:
ball.B allInP lay += new EventHandler<BallEventArgs> (ball_BallInPlay);
i f (ballInPlay != null)
b allIn P lay(th is, e);

C # stosuje niejawną konwersję, gdy pominiesz słowo kluczowe new oraz typ zdarzenia
Kilka stron wcześniej skorzystałeś z możliwości IDE, by utworzyć następującą procedurę obsługi zdarzeń:

b a ll.B a llIn P la y += ball_B allInP lay;

W razie zastosowania takiej składni C # dokonuje niejawnej konwersji i samodzielnie określa typ zdarzenia.
Teraz spróbuj zastąpić fragmenty kodu z klas Pitcher oraz Fan poniższym wierszem:

b a ll.B a llIn P la y += new EventHandler<BallEventArgs>(ball_BallInPlay);

Twój program wciąż będzie działał doskonale, ponieważ IDE automatycznie wygenerowało kod
wykorzystujący niejawną konwersję. Dzięki temu nie musiałeś modyfikować typu po zmianie typu zdarzenia.

744 Rozdział 15.


Zdarzenia i delegaty

Formularze używają wielu różnych zdarzeń


W kolejnych dwóch projektach zmienimy używaną technologię i zajmiemy się ponownie
aplikacjami Windows Forms, a to dlatego, że stanowią one naprawdę doskonałe narzędzie
do nauki. Okazuje się, że za każdym razem, gdy tworzyłeś przycisk, klikałeś go dwukrotnie Z r ó b to !
w oknie projektanta formularzy i pisałeś kod metody takiej jak button1_C lick(),
pracowałeś ze zdarzeniami. (Także aplikacje dla Sklepu Windows używają zdarzeń).

r« Utwórz nowy projekt Windows Forms Application. Wybierz formularz i przejdź do


okna Properties. Czy pamiętasz te ikony widoczne na górze okna? Kliknij przycisk Events
(przedstawiający błyskawicę), aby wyświetlić w oknie Properties kartę ze zdarzeniami.

M ożesz zobaczyć
w szystkie zdarzenia
kontro/ki, k/ikając ją ,
a następnie przycisk Przew iń zaw artość w ddf
Events w oknie w poszukiwaniu stówa U ick
w łaściw ości. i kliknij je dwukrotnie.
Z a ra z po tym ID E doda do
formularza nową Pr°^fdur^ H j
obsfuoi zdarzenia, która będzie
wywoływana po każdym kliknięciu
w jego obszarze. Do pliku
forml .Designer zostanie także
ożesz utworzyć zdarzenie, które
idzie sygnalizowane podczas
jżdego kliknięcia formularza.

^ Kliknij dwukrotnie wiersz Click w zakładce zdarzeń. IDE automatycznie doda do formularza
procedurę obsługi zdarzenia o nazwie Form1_Click. Dodaj taki oto wiersz kodu:

private void Form1_Click(object sender, EventArgs e) {


MessageBox.Show("Właśn1e k lik n ą łe ś fo rm u la rz .");
}

Visual Studio zrobiło coś więcej. Oprócz wstawienia deklaracji metody skojarzyło ją
także ze zdarzeniem Click formularza. Otwórz Form1.Designer.cs i użyj opcji
Q u ick F ind (E D IT /F in d a n d R eplace/Q uick F in d ) w celu odnalezienia w projekcie tekstu
„Form1_Click”. Znajdziesz taki oto wiersz kodu:

th is.C lick += new System.EventHandler(this.Form1_Click);


Uruchom teraz program i sprawdź, czy Twój kod działa!

► Jeszcze nie skończyłeś' — przewróć kartkę!

jesteś tutaj ► 745


W p ro w a d z e n ie d o z a k ła d k i e v e n ts

Jedno zdarzenie, wiele procedur obsługi


P. ' K ie d y dodałem nową
Istnieje coś naprawdę użytecznego, co można zrobić ze zdarzeniami: możesz procedurę obsługi zd arze n ia
utworzyć łańcuch metod ich obsługi. Jedno zdarzenie lub delegat może wtedy do obiektu P itc h e r , ID E zgłosiło
w y ją te k . Dlaczego?
wywoływać wiele metod, jedną za drugą. Dodajmy kilka przycisków do aplikacji
i przekonajmy się, jak to działa. O: IDE dodało do procedury
kod zgłaszający wyjątek
,4 Dodaj takie oto dwie metody do formularza: N o tIm p lem en ted Excep tio n ,
p r iv a t e v o id S a y S o m e th in g (o b je ct s e n d e r, Even tA rg s e) { aby Ci przypomnieć, że wciąż
M essa g e B o x.S h o w ("C o ś"); musisz zaimplementować jej
} właściwy kod. To naprawdę bardzo
przydatny wyjątek, gdyż także
p r iv a t e v o id S a y S o m e th in g E ls e (o b je c t s e n d e r, Even tA rg s e) {
M essageBox.Show ("Cos in n e g o " ); Ty możesz go używać, podobnie
} jak zrobiło IDE. Na przykład,
zazwyczaj stosujemy ten wyjątek,
[ 5) Dodaj do niego dwa przyciski. Kliknij dwukrotnie każdy z nich, kiedy tworzymy szkielet klasy
aby wstawić właściwe procedury obsługi zdarzeń. Oto ich kod: lecz na danym etapie nie chcemy
jeszcze pisać jej kodu. Dzięki temu
p r iv a t e v o id b u t t o n 1 _ C lic k (o b je c t s e n d e r, E ventA rg s e) {
t h i s . C l i c k += new E v e n tH a n d le r(S a y S o m e th in g ); jeśli Twój program zgłosi wyjątek,
} będziesz wiedział, że oznacza on
p r iv a t e v o id b u t t o n 2 _ C lic k (o b je c t s e n d e r, E ven tA rg s e) { konieczność dokończenia kodu,
t h i s . C l i c k += new E v e n tH a n d le r(S a y S o m e th in g E ls e );
a nie błąd programu.
}

Zanim przejdziemy dalej, poświęć chwilę i zastanów się, co te dwa przyciski robią. Każdy z nich
dodaje nową procedurę obsługi zdarzenia C lick formularza. W pierwszych trzech krokach wykorzystałeś
IDE, by w zwyczajny sposób dodać procedurę obsługi zdarzenia, która będzie wyświetlać komunikat
za każdym razem, gdy zdarzenie C l i c k zostanie wygenerowane. By to było możliwe, IDE dodawało do
pliku Form1.Designer.cs wiersz kodu, w którym operator += kojarzył zdarzenie z procedurą jego obsługi.

Teraz dodałeś dwa przyciski, które wykorzystują dokładnie tę samą składnię, by wstawić kolejne dwie metody
do łańcucha procedur obsługi zdarzenia C l i c k . A zatem, zanim przejdziesz do dalszej lektury, spróbuj
zgadnąć, co się stanie, gdy uruchomisz program, klikniesz pierwszy przycisk, następnie drugi, a w końcu
klikniesz formularz. Czy potrafisz odpowiedzieć na to pytanie przed uruchomieniem aplikacji?

W tym projekcie używamy aplikacji g - 1 ...................................................................................................................................................


M I Funkcje obsługi zdarzenia zawsze muszą być podpięte.
Windows Forms, by wykorzystać
sposób, w jaki aplikacje tego typu
używają zdarzeń. Przedstawione .Uuiaęal Jeśli przeciągniesz przycisk na formularz ¡dodasz metodę o nazwie b u tto n l_ C l i c k ( ) ,
rozwiązanie dotyczy wszystkich : która będzie miała prawidłowe parametry ale nie b ęd zie za rejestro w a n a do
zdarzeń, jednak na przykładzie \ nasłuchiw ania zd a rze ń g en ero w a n ych p rz e z przycisk, nigdy nie zostanie ona wykonana.
zdarzenia C lick przycisku : Kliknij dwukrotnie przycisk w projektancie formularzy — IDE zobaczy że domyślna nazwa funkcji
jest szczególnie łatwo pokazać, : obsługi zdarzenia jest zajęta i doda do obsługi zdarzeń przycisku metodę b u t t o n 1 _ C li c k _ 1 ( ) .
o co w nim chodzi.

746 Rozdział 15.


Zdarzenia i delegaty

A teraz uruchom program i wykonaj następujące czynności:

★ Kliknij formularz — pojawi się komunikat M essageBox o treści „Właśnie kliknąłeś formularz”.

Zdarzyło s ię to, czego


oczekiwałeś — procedura
obsługi zdarzenia Click
formularza wyświetliła
komunikat.

★ Kliknij przycisk pierwszy, a następnie ponownie kliknij formularz. Zobaczysz dwa komunikaty:
„Właśnie kliknąłeś formularz” i „Coś”.
Jednak każde
kliknięcie
przycisku
spowoduje,
Właśnie kliknąłeś formularz
że po kolejnym
kliknięciu
formularza pojawi
się dodatkowy
komunikat.

★ Kliknij dwukrotnie przycisk drugi i jeszcze raz kliknij formularz. Tym razem pojawią się
cztery komunikaty: „Właśnie kliknąłeś formularz”, „Coś”, „Coś innego” oraz „Coś innego”.

Właśnie kliknąłeś formularz

Co się State? Kiedy klikasz


Za każdym razem, gdy klikasz przycisk, podpinasz do łańcucha kolejną metodę — albo S o m e t h in g () , te przyciski, do
albo S o m e t h in g E ls e () . Będą one powiadamiane o wystąpieniu zdarzenia C l i c k formularza. zdarzenia Q ick
formularza dodawane
Możesz w dalszym ciągu klikać przyciski — do łańcuchów procedur obsługi zdarzeń będą dodawane s ą kolej ne procedury
te same metody. Dla zdarzenia nie ma znaczenia ich liczba, więc można nawet kilka razy dodać obsługi.
tę samą. Będzie ona wywoływana zawsze, gdy zasygnalizowane zostanie zdarzenie — raz za razem,
w kolejności dodania. 7
To oznacza, że po kliknięciu
przycisku nie zobaczysz
żadnego komunikatu. Aby coś
C lick() zobaczyć, będziesz musiał
kliknąć formularz, gdyż to jego
działanie modyfikują przycis ki,
zmieniając sposób obsługi
* | SaySo m ething() | zdarzenia Click.

Ta sama metoda
'e4t- m oże zo sta ć podpięta
do zdarzenia więcej
niż raz.
[^ S a y S o m e th in g E M r

% jesteś tutaj ► 747


Dzień z życia aplikacji
Aplikacja dla Sklepu Windows może
zakończyć swoje dziatanie, wy wotując

Aplikacje dla Sklepu Windows używają jednak dobrze zaprojektowana apttkacja


nie musi tego robić, gdyż korzysta
zdarzeń do zarządzania cyklem życia procesu i
z zarządzan a cyklem życia procesu ■
'J '
Zauważyłeś zapewne jedną ważną różnicę pomiędzy aplikacjami dla Sklepu Windows oraz tradycyjnymi aplikacjami
okienkowymi: w przypadku tych pierwszych nie ma żadnego oczywistego sposobu na ich zamknięcie. Zastanów się jednak
nad tym przez chwilę... niby dlaczego w ogóle miałbyś chcieć je zamykać? Możesz potrzebować przełączyć się do innej
aplikacji, ale co jeśli komputer ma na tyle dużo pamięci operacyjnej i na tyle wydajny procesor, by móc zmarnować
trochę cykli na podtrzymywanie działającej aplikacji? Kiedy przełączasz się do innej aplikacji, ta wcześniejsza zostanie
zawieszona. W tym stanie aplikacja pozostaje w pamięci, wraz ze wszystkimi obiektami i zasobami niezbędnymi do jej
działania. Kiedy system Windows będzie potrzebował więcej pamięci, zakończy działanie aplikacji, usunie ją z pamięci
i zwolni wszystkie używane przez nią zasoby. Ale czy z punktu widzenia użytkownika fakt zawieszenia lub zakończenia
aplikacji ma jakiekolwiek znaczenie? W większości przypadków użytkownicy nie zwracają na to uwagi — o ile tylko po
ponownym uruchomieniu aplikacji powróci ona do oczekiwanego stanu. Reagowanie przez aplikację na zawieszenie lub
zakończenie przez system operacyjny nazywamy zarządzaniem cyklem życia procesu.

Użyj ID E, by zbadać zdarzenia związane


Za każdym
z zarządzaniem cyklem życia procesu
razem, gdy
Otwórz dowolną aplikację typu Windows Store i dwukrotnie kliknij plik A pp.xam l.cs
widoczny w oknie Solution Explorer. Odszukaj konstruktor klasy App:
W indows
zawiesza
public App()
{ aplikację dla
t h i s .InitializeComponent();
this.Suspending += OnSuspending; Sklepu Windows,
} wywoływane
Powinieneś już rozpoznać, co się w nim dzieje. App, będąca klasą pochodną klasy jest zdarzenie
A pplication zdefiniowanej w przestrzeni nazw Windows.UI.Xaml, dysponuje zdarzeniem Suspending, dzięki
o nazwie Suspending, które w konstruktorze jest kojarzone z procedurą obsługi zdarzeń
OnSuspending. Kliknij prawym przyciskiem myszy.Suspending i wybierz opcję G o To
czemu aplikacja
D efinition. Spowoduje to wyświetlenie karty IJMmuUilfiHiilBEaiHai przedstawiającej m oże zapisać
wszystkie składowe klasy A pplication i przejście do definicji zdarzenia Suspending. swój stan.

// ^ Occurs when the application transitions to Suspended state from some other

To zdarzenie jest wywoływane za każdym razem, gdy przełączasz się ze swojej aplikacji na inną. Oznacza to, że za każdym
razem, gdy aplikacja zostaje zawieszona, wywoływana jest metoda OnSuspending() zdefiniowana w pliku A pp.xam l.cs.
I podobnie, za każdym razem gdy aplikacja zostanie uruchomiona, wywoływana jest metoda OnLaunched().

Kiedy aplikacja jest zawieszona, system Windows w każdej chwili może ją zakończyć. A zatem powinna ona działać w taki
sposób, jak gdyby każde zawieszenie miało się zakończyć jej zakończeniem, czyli za każdym razem powinna zapisywać
swój stan. Metoda OnLaunched() może sprawdzać przekazane argumenty i na ich podstawie określać, czy działanie
aplikacji zostało wznowione po wcześniejszym zawieszeniu.
748 Rozdział 15.
Zdarzenia i delegaty

Dodaj zarządzanie cyklem życia procesu do aplikacji Janka


Zmodyfikujmy aplikację do zarządzania kolekcją komiksów Janka w taki sposób, by zapamiętywała
i odtwarzała aktualnie wyświetloną stronę. Zmodyfikujemy w tym celu jej procedurę obsługi
zdarzeń Suspending: podczas każdego zawieszenia aplikacji będziemy zapisywać nazwę aktualnie v ^
wybranego zapytania w pliku umieszczonym w lokalnym folderze aplikacji. ^ Z r ó b 10.

Dodaj klasę do zarządzania zapisywaniem i wczytywaniem stanu aplikacji.


Dodaj do aplikacji klasę o nazwie SuspensionManager. Powinna ona dysponować
statyczną właściwością przechowującą informację o aktualnie wybranym zapytaniu oraz dwie
metody statyczne, służące do odczytywania i zapisywania nazwy zapytania w pliku o nazwie
_sessionState.txt umieszczonym w folderze local.

using Windows.Storage;

class SuspensionManager {
public s ta tic string CurrentQuery { get; set; }

private const string filename = "_sessionState.txt";

s ta tic async public Task SaveAsync() {


i f (String.IsNullOrEmpty(CurrentQuery))
CurrentQuery = String.Empty;
IStorageFile storageFile =
await ApplicationData.Current.LocalFolder.CreateFileAsync(
filename, CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(storageFile, CurrentQuery);
}

s ta tic async public Task RestoreAsync() {


IStorageFile storageFile =
await ApplicationData.Current.LocalFolder.GetFileAsync(filename);
CurrentQuery = await FileIO.ReadTextAsync(storageFile);
}
}

Zmodyfikuj stronę, by aktualizowała informacje przechowywane w SuspensionManager


podczas wczytywania każdego zapytania.
Kontrolka ListView umieszczona w pliku MainPage.xaml powoduje, że w odpowiedzi na każde kliknięcie elementu listy
aplikacja zmienia prezentowaną stronę. A zatem dodaj do procedury obsługi zdarzeń ItemCl i ck wiersz kodu, który
będzie zapisywał nazwę wybranego zapytania w statycznej właściwości CurrentQuery klasy SuspensionManager:
private void ListView_ItemClick(object sender, ItemClickEventArgs e) {
ComicQuery query = e.ClickedItem as ComicQuery;
i f (query != null) {
SuspenslonManager.CurrentQuery = query.Tltle; ^ Każde kliknięcie zapytania
i f (query.Title == "Wszystkie komiksy w kolekcji")
this.Frame.Navigate(typeof(QueryDetailZoom), query);
e lse
this.Frame.Navigate(typeof(QueryDetail), query);
}
jesteś tutaj ► 749
D z ia ła n ie z o s ta ło p r z e r w a n e

Przesłoń metodę OnNavigatedFrom(), by usunąć zapisane zapytanie.


Kiedy użytkownik kliknie przycisk ze strzałką wstecz, by wyjść ze strony, to jednym z efektów wykonania tej
czynności będzie wywołanie zdarzenia NavigatedFrom. Zaktualizuj zatem kod ukryty stron QueryDetail
oraz QueryDetailZoom, przesłaniając w nich metodę OnNavigatedFrom(), która jest wywoływana zaraz po
wyjściu ze strony. Przejdź do pliku Q ueryD etail.xam l.cs i wewnątrz klasy wpisz słowo kluczowe override,
następie skorzystaj z okienka IntelliSense, by utworzyć szkielet metody:

Po wybraniu z okienka IntelliSense metody OnNavigatedFrom() IDE dodało do kodu szkielet metody, w którym
jest wywoływana metoda OnNavigatedFrom() klasy bazowej. Dodaj do niej wiersz, który usunie zapamiętaną
nazwę zapytania. Nie zapomnij zrobić tego samego w pliku Q ueryD etailZoom .xam l.cs.

protected override void OnNavigatedFrom(NavigationEventArgs e) {


SuspenslonManager.CurrentQuery = n u l l ; ^ ^ ^ Kie dy strony QueryD etai>oraz QueryD etailZ° ° rn
base.OnNavigatedFrom(e); ^ zTg powrotem
r a j ą , ^ ' na
N stronę
a ^ r O m a wmramach
gtówną, i c F ich
ij
obsługi zostanie u s u n ię ć nazwa za pytania
} zapam iętana we w ła ś c iw o ^ CurremtQuery
klasy SuspensionM anage r-

Zm odyfikuj procedurę obsługi zdarzeń Suspending tak, by zapamiętywała stan.


Otwórz plik A pp.xam l.cs i odszukaj w nim procedurę obsługi OnSuspending(), powiązaną ze zdarzeniami
Suspending. Wewnątrz niej jest umieszczony komentarz zaczynający się od słów TODO:
priv a t e void O n S u s p e n ding(object sender, Suspend i n g E v e n t A r g s e)
Niektóre z szablonów
{
udostępnianych p rzez IDE
var d e f erral = e . S u s p e n d i n g O p e r a t i o n . G e t D e f e r r a l Q ; za w ierają takie wiersze
//TODO: Save application state and stop any background activity „JODO", informując nas,
d e f e r r a l . C o m p l e t e ( ); gdzie bez p iecznie możemy
dodawać sw ój kod.
}

Zastąp wiersz z komentarzem TODO wywołaniem metody SaveAsync(). Nie zapomnij umieścić na początku
deklaracji metody słowa kluczowego async, gdyż w przeciwnym razie nie będziesz mógł użyć wewnątrz niej słowa
await, by wykonać wywołanie asynchroniczne:

async private void OnSuspending(object sender, SuspendingEventArgs e)


{
var deferral = e.SuspendingOperation.GetDeferral();
await Suspens1onManager.SaveAsync();
deferral.Complete();
}

750 Rozdział 15.


Zdarzenia i delegaty

Zmodyfikuj metodę OnLaunched, by odtwarzała stan aplikacji.


Wszystkie zmiany wprowadzone do tej pory miały za zadanie zapewnić aktualność stanu aplikacji, przechowywanego
we właściwości CurrentQuery klasy SuspensionManager. Skoro ta część zadania jest gotowa, pozostało nam jedynie
zaktualizować umieszczony w plikuA pp.xam l.cs kod procedury obsługi zdarzeń Launched i odtworzyć zapisany stan.

async protected override void OnLaunched(LaunchActivatedEventArgs args) {


SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;
B ęd ziesz tego
potrzebować Frame rootFrame = Window.Current.Content as Frame;
by móc używiJÓ
operatora a w ait / / Nie powtarzamy in ic j a liz a c j i, kiedy okno ma już jakąś zawartość,
/ / zapewniamy jedynie, że je s t aktywne
i f (rootFrame == null)
{
/ / Tworzymy obiekt Frame działający jako kontekst nawigacji
/ / i przechodzimy na pierwszą stronę
rootFrame = new Frame();
i f (args.PreviousExecutionState == ApplicationExecutionState.Terminated) {
a w a lt Suspens1onM anager.R estoreAsync();
} Tutaj je s t umieszczony ko/ejny komentarz
TODO, w skazujący m iejsce gdzie możesz
/ / Umieszczamy obiekt Frame w bieżącym oknie (Window) wczytać stan wcześniej zaw ieszonej
Window.Current.Content = rootFrame; ap/ikacji. Z a stą p go wywołaniem metody
RestoreAsyncO k/asy SuspensionM anager

i f (rootFrame.Content == null) {
/ / J e śli nie uda s ię odtworzyć sekwencji nawigacji, wracamy na pierwszą
/ / stronę, przy czym konfigurujemy ją , przekazując do niej odpowiednie
/ / informacje w formie parametru
i f (!rootFrame.Navigate(typeof(MainPage), args.Arguments)) {
throw new Exception("Nie udało s ię utworzyć strony początkowej");
}
1 f (!S tr1 n g .Is N u ll0 rE m p ty (S u s p e n s 1 o n M a n a g e r.C u rre n tQ u e ry )) {
v a r currentQ uerySequence =
Przyjrzyj s ię dokładni’e
from qu ery 1n new Com 1cQ ueryM anager().Ava1lableQ uer1es
temu zapytaniu LIN Q .
Dodaj ten kod, by where q u e r y . T lt le == S uspenslonM anager.C urrentQ uery ^ C zy rozumiesz, j ak ono
porównać wcześniej s e le c t q u e ry ; działa?
zap isany stan
ap/ikacji z /istą 1 f (cu rre n tQ u e ryS e q u e n ce .C o u n t() == 1) {
zapytań. Jeś/i ComlcQuery qu ery = c u rre n tQ u e ry S e q u e n c e .F 1 rs t();
odpowiada on 1 f (q u e ry != n u l l ) {
jakiem u ś znanemu 1 f ( q u e r y . T lt le == "W s z y s tk ie kom iksy w k o l e k c ji " )
zapytaniu, to
ap/ikacja może ro o tF ra m e .N a v1 g a te (typ e o f(Q u e ryD e ta 1 lZ o o m ), q u e ry );
w yświet/ić stronę e ls e
szczegółów ro o tF ra m e .N a v 1 g a te (ty p e o f(Q u e ry D e ta 1 l), q u e ry );
prezentującą wyniki }
tego zapytania. }

}
/ / Zapewniamy, że bieżące okno będzie aktywne
Window.Current.Activate();

Działanie aplikacji możesz przetestować, używając rozwijanej listy Suspend.


Jest ona dostępna na pasku narzędzi Debug Location, w yśw ietlanym wyłącznie
w czasie, gdy aplikacja jest uruchomiona w debuggerze (jeśli go nie widzisz,
w ybierz z menu opcję ViEW/Toolbars). W ybierz opcję Suspend and shutdown,
aby zakończyć aplikację i wygenerować zdarzenie Suspendlng.
jesteś tutaj ► 751
B rn ie m y , b r n ie m y , w t r u d z i e i z n o ju

Kontrolki XAM L korzystają ze zdarzeń trasowanych


Zajrzyj na je d n ą z wcześniejszych stro n , n a której zostało p rzed staw io n e okien k o IntelliSense w yśw ietlane p o w pisaniu
w ID E słow a override . D w ie nazwy typów zdarzeń przekazyw anych jak o arg u m en ty są nieco inne o d pozostałych. D rugi
a rg u m en t zdarzen ia DoubleTapped je st typu DoubleTappedRoutedEventArg, n a to m ia st arg u m en t zd arzen ia GotFocus
jest typu RoutedEventArgs. T e różnice w ynikają z faktu, że DoubleTapped o raz GotFocus są zdarzeniami trasowanymi
(ang. routed events). P rzypom inają o n e zwyczajne zd arzenia, lecz ró żn ią się o d nich p o d jednym w zględem : kiedy o b iekt
k o ntrolki odpow iada n a zd arzen ie traso w an e, w pierw szej kolejności, ja k stan d ard o w o , w ykonuje p ro ce d u rę obsługi
zdarzenia. Je d n a k później ro b i coś w ięcej: jeśli zd arzen ie nie zostało obsłużone, przekazuje zdarzenie trasowane
w górę, do kontrolki pełniącej rolę pojemnika. K iedy to n astąp i, tak że k o n tro lk a p o jem n ik a wywołuje zdarzen ie, a jeśli
nie zostanie ono obsłużone, p rzek azu je je do sw ojego p ojem nika. Z d a rz e n ie w ciąż propaguje ku górze, aż d o trze do
elementu głównego (ang. root) — czyli najwyższego e lem en tu . Poniżej przedstaw iliśm y typow ą sygnaturę p ro ced u ry
obsługi zdarzen ia trasow anego:

private void EventHandler(object sender, RoutedEventArgs e)

O b iek t typu RoutedEventArgs p o siad a w łaściwość o nazw ie Handled, której p ro c e d u ra obsługi zd arzen ia m oże użyć, by
zaznaczyć, że zdarzenie zostało obsłużone. Przypisanie jej w artości true pow oduje zatrzymanie propagacji zdarzenia .

W przypadku obu rodzajów zd arzeń , zarów no standardow ych, ja k i trasow anych, p a ra m e tr sender b ędzie zaw ierał
referen cję do obiektu, k tóry wywołał p ro c e d u rę obsługi zdarzenia. A zatem , jeśli zd arzen ie p ro p ag u je z kontro lk i do jej
k o n ten era, takiego ja k Grid , to kiedy k o n tro lk a Grid wywoła swoją p ro c e d u rę obsługi zdarzenia, p a ra m e tr sender będzie
w skazywał n a tę k o n tro lk ę Grid. A co m o żn a zrobić, jeśli będziem y chcieli określić, k tó ra k o n tro lk a wywołała początkow e
zdarzenie? Ż a d e n prob lem . O b iek t RoutedEventArgs p o siad a w łaściwość o nazw ie OriginalSource , zaw ierającą
referen cję do kontrolki, k tó ra wywołała zd arzen ie ja k o pierw sza. Jeśli zarów no właściwość OriginalSource , ja k i p a ra m e tr
sender w skazują ten sam o biekt, to k o n tro lk a, k tó ra w ywołała p ro c e d u rę obsługi, je st tą sam ą, k tó ra zapoczątkow ała
zdarzenie i od której zaczęła się jego propagacja.

Struktura kontrolek,
IsHitTestVisible określa,
k tó re zawierają
czy element jest „widoczny" dla wskaźnika lub myszy
inne k o n tro lk i,
Zazwyczaj dow olny ele m e n t um ieszczony n a stro n ie m oże zostać „trafiony” w skaźnikiem
lub myszką — o ile tylko sp ełn ia pew ne kryteria: m usi być w idoczny (co m o żn a zm ieniać zawierające jeszcze
przy w ykorzystaniu właściwości V i s i b il i t y ), m usi m ieć w łaściwość Background lub F ill inne k o n tro lk i, jest
o w artości różnej o d null (choć m oże być przezroczysty — Transparent ) i w k o ń cu jego
szerokość (width ) i wysokość ( height ) m uszą być w iększe o d zera. Jeśli wszystkie te nazywana drzewem
w arunki są spełnione, to właściwość IsH itT estV isib le zwróci w arto ść tru e , a to z kolei
obiektów , a zdarzenia
sprawi, że k o n tro lk a będzie reagow ać n a zd arzen ia zw iązane ze w skaźnikiem lub myszą.
trasowane propagują
W łaściw ość ta je st szczególnie użyteczna w sytuacjach, gdy chcem y, by nasze zd arzen ia były
„niew idoczne” dla myszy. Jeśli przypiszem y właściwości IsH itT estV isib le w artość f a ls e ,
od elementów
to wszystkie zdarzenia zw iązane z naciśnięciam i lub kliknięciam i b ę d ą przekazywane dalej, poto m nych do ich
nie wywołując żadnej reakcji kontrolki. Jeśli poniżej kontro lk i będzie się znajdow ać inna,
to zdarzenie trafi do niej.
rodziców, aż dotrą
do elementu głównego
Listę zdarzeń trasowanych można znaleźć na tej stronie:
h ttp://m sd n .m icrosoft.co m /pl-pl/libra ry/w in do w s/a pps/hh 7 5 8 28 6 .asp x. na samej górze.
752 Rozdział 15.
Zdarzenia i delegaty

Utwórz aplikację do badania zdarzeń trasowanych


Nie zapomnij zastąpić
Na kilku kolejnych stronach przedstawiliśmy aplikację dla Sklepu Windows, której możesz używać do pliku M a inPage.xaml
eksperymentowania ze zdarzeniami trasowanymi. Składa się ona z kontrolki StackPanel, zawierającej nową stroną, utworzoną
na podstaw ie szablonu
kontrolkę Border, wewnątrz której umieszczona jest kontrolka Grid, a w niej dwie dalsze kontrolki E llipse Basic Page.
oraz Rectangle. Czy zwróciłeś uwagę, że kontrolka Rectangle jest widoczna nad kontrolką E llipse? Jeśli
dwie kontrolki umieścimy w tej samej komórce, to zostaną one wyświetlone jedna nad drugą. Pomimo to obie
mają tego samego rodzica: kontrolkę Gri d, której rodzicem
jest z kolei kontrolka Border, a jej rodzicem — kontrolka
StackPanel. Zdarzenia trasowane wywoływane przez
kontrolki Rectangle lub E llip se propagują w górę struktury,
aż do elementu głównego drzewa obiektów.

To jest kontrolka ToggleSwitch, której można


używać do włączania i wyłączania wartości. Tekst
nagłówka jest określany przy użyciu właściwości
Header, natomiast wartość kontrolki można
pobrać, korzystając z właściwości IsOn.

<Grid Grid.Row="1" Margin="120,0">


<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel x:Name="panel" PointerPressed="StackPanel_PointerPressed">
<Border BorderThickness="10" BorderBrush="Blue" Width="155" x:Name="border"
Margin="20" PointerPressed="Border_PointerPressed">
<Grid x:Name="grid" PointerPressed="Grid_PointerPressed">
Zdarzenia <Ellipse Fill="Red" Width="100" Height="100"
trasowane
propagują PointerPressed="Ellipse_PointerPressed"/>
w górę <Rectangle Fill="DarkSlateGray" Width="50" Height="50"
drzewa PointerPressed="Rectangle_PointerPressed" x:Name="grayRectangle"/>
obiektów. </Grid>
</Border>
<ListBox BorderThickness="1" Width="300" Height="250" x:Name="output" Margin="0,0,20,0"/>
</StackPanel>
<StackPanel Grid.Column="1">
<ToggleSwitch Header="Obsłużone w kontrolce Border" x:Name="borderSetsHandled"
OffContent="Wyłączone" OnContent="Włączone"/>
<ToggleSwitch Header="Obsłużone w kontrolce Grid" x:Name="gridSetsHandled"
OffContent="Wyłączone" OnContent="Włączone" />
<ToggleSwitch Header="Obsłużone w kontrolce Ellipse" x:Name="ellipseSetsHandled"
OffContent="Wyłączone" OnContent="Włączone"/>
<ToggleSwitch Header="Obsłużone w kontrolce Rectangle" OffContent="Wyłączone" Domyślną wartością
właściwości IsOn jest
x:Name="rectangleSetsHandled" OnContent="Włączone"/> False. W tej kontrolce
<Button Content="Zaktualizuj właściwość IsHitTestVisible" wartość domyślną
Click="UpdateHitTestButton" Margin="0,20,20,0"/> ustawiliśmy na True,
<ToggleSwitch IsOn="True" Header="Nowa wartość właściwości IsHitTestVisible gdyż je s t to domyślna
x:Name="newHitTestVisibleValue" OffContent="Wyłączona" OnContent="Włączona" wartość właściwości
IsHitTestVisible
</StackPanel> wszystkich kontrolek.

Przewróć kartkę, by dokończyć aplikację. - - - - - - - - - - - ► jesteś tutaj k 753


W c h o d z e n ie n a d rz e w o o b ie k tó w

DO WYŚWIETLANIA WYNIKÓW NA LIŚCIE BĘDZIESZ POTRZEBOWAŁ KOLEKCJI


OBSERV ABLE CO LLE C T iO N .
Utwórz pole o nazwie o u tp u tIte m s i w konstruktorze strony określ wartość właściwości L is t B o x .It e m s S o u r c e .
Nie zapomnij także dodać na samym początku strony instrukcji u s in g S y s t e m .C o lle c t io n s .O b je c t M o d e l; ,
gdyż inaczej nie będziesz mógł korzystać z klasy O b s e r v a b le C o lle c t io n < T > .
p u b lic se a le d p a r t i a l c la s s MainPage : RoutedEvents.Com m on.LayoutAw arePage {
0bservableCollect1on<str1ng> outputItems = new 0bservableCollect1on<str1ng>();

p u b lic M ainPage() {
t h is .In it ia liz e C o m p o n e n t ( ) ;

output.ItemsSource = outputItems;
}

Oto kod ukryty strony. Procedura obsługi zdarzeń P o in te r P re s s e d każdej z kontrolek czyści listę wyników, o ile tylko dana
kontrolka jest oryginalnym źródłem zdarzenia, a następnie dodaje do wyników swój własny komunikat. Jeśli przełącznik
odpowiadający danej kontrolce jest włączony, to podczas obsługi zdarzenia jego właściwość e.H a n d le d jest ustawiana na t r u e .
p r iv a t e v o id E llip s e _ P o in t e r P r e s s e d ( o b je c t s e n d e r, P o in te rR o u te d E ve n tA rg s e)

i f (se n d e r == e .O r ig in a lS o u r c e ) o u t p u t It e m s .C le a r ( ) ;
o u tp u tIte m s .A d d (" N a c iś n ię to k o n tro lk ę E l l i p s e " ) ; procedur ° bsługi nif k« n / ch zdarzeń ,
trasowanych będą przekazywane instancje
if (e llip s e S e t s H a n d le d . IsO n) e . Handled = t r u e ; k/as pochodnych RoutadEw ntA rgs, takich jak
PointerRoutedEventArgs /ub PointerPressed.

p r iv a t e v o id R e c t a n g le _ P o in t e rP r e s s e d (o b je c t s e n d e r, P o in te rR o u te d E ve n tA rg s e)

i f (se n d e r == e .O r ig in a lS o u r c e ) o u t p u t It e m s .C le a r ( ) ;
o u tp u tIte m s .A d d (" N a c iś n ię to k o n tro lk ę R e c t a n g le " ) ;
i f (re c ta n g le S e ts H a n d le d .Is O n ) e.H a n d le d = t r u e ;

p r iv a t e v o id G rid _ P o in t e r P r e s s e d (o b je c t s e n d e r, P o in te rR o u te d E ve n tA rg s e)

i f (se n d e r == e .O r ig in a lS o u r c e ) o u t p u t It e m s .C le a r ( ) ;
o u tp u tIte m s .A d d (" N a c iś n ię to k o n tro lk ę G r id " ) ;
i f (g rid S e ts H a n d le d .Is O n ) e.H a n d le d = t r u e ;

p r iv a t e v o id B o rd e r_ P o in te rP re s s e d (o b je c t s e n d e r, P o in te rR o u te d E ve n tA rg s e)

i f (se n d e r == e .O r ig in a lS o u r c e ) o u t p u t It e m s .C le a r ( ) ;
o u tp u tIte m s .A d d (" N a c iś n ię to k o n tro lk ę B o r d e r " );
i f (b o rd e rS e ts H a n d le d .IsO n ) e.H an d le d = t r u e ;

p r iv a t e v o id S ta c k P a n e l_ P o in t e rP r e s s e d (o b je c t s e n d e r, P o in te rR o u te d E ve n tA rg s e)

i f (se n d e r == e .O r ig in a lS o u r c e ) o u t p u t It e m s .C le a r ( ) ;
o u tp u tIte m s .A d d (" N a c iś n ię to k o n tro lk ę S t a c k P a n e l" ) ;

p r iv a t e v o id U p d a te H itT e s tB u tto n (o b je c t s e n d e r, R outedEventA rgs e) procedura obs ługi zdarzeń Click przycisku
używ a w łaściw ości IsO n kontro/ki
g r a y R e c t a n g le .Is H it T e s t V is ib le = n e w H itT e s t V is ib le V a lu e .Is O n ; przełacznika aby włączać i wyłączać
p .^
Właściwość T .u
Is H.i*T__rt/.-e;bfe
itT e s tV is ib le Imntro/ki
kontrolki
Rectangle.
754 Rozdział 15.
Zdarzenia i delegaty

OTO GRAF OBIEKTÓW STRONY GŁÓWNEJ APLIKACJI


Elementem głównym drzewa obiektów aplikacji jest instancja klasy MainPage.
Domyślnie plik M ainPage.xaml reprezentuje instancję typu Page, jednak w przypadku
stron tworzonych na podstawie szablonu Basic Page klasa MainPage dziedziczy po
klasie LayoutAwarePage, która z kolei jest klasą pochodną klasy Page.

To jest obiekt siatki, dodany do strony głównej


jako jeden z elementów szablonu Basic Page.

To jest obiekt StackPanel zawierający


kontrolki: Border, Grid, E llip se oraz
Rectangle.

Ten obiekt siatki może otrzymywać trasowane


zdarzenia PointerPressed, nie może jednak
ich wywoływać. Domyślną wartością jego
właściwości IsH itT estV isib le jest fa lse ,
gdyż jego właściwości Backgroud oraz F ill nie
mają określonych wartości. Jeśli zaktualizujesz
kod XAML i podasz w nim wartość właściwości
Background, to domyślną wartością właściwości
Obie*-
IsH itT estV isib le będzie true — i to nawet
jeśli tło będzie mieć wartość Transparent.
Dzięki temu kontrolka będzie odpowiadać

O
na zdarzenia związane z naciskaniem.

F
Obie'*'
O O b ie ^
.
Przewróć kartkę, by użyć swojej nowej aplikacji do wypróbowania zdarzeń trasowanych. jesteś tutaj ► 755
B ą b e lk i p r o p a g u j ą w p r o s t d o g ło w y

URUCHOM APLIKACJĘ I KLIKNIJ


LUB DOTKNIJ SZARY PROSTOKĄT.
Pow inieneś ujrzeć wyniki przed staw io n e n a zrzucie p o praw ej stronie.

A by dokładnie zobaczyć, co się dzieje, u m ieść p u łap k ę w pierwszym


w ierszu m etody R ectangle_PointerPressed() — p ro ced u ry obsługi
zdarzeń PointerPressed kontro lk i Rectangle :
O
private void Rectangle_PointerPressed(object sender, PointerRoutedEventArgs e)
{ ________________________
if (sender == e.OriginalSource) outputltems.Clear();
outputltems.Add("Naciśnięto kontrolkę Rectangle");
if (rectangleSetsHandled.IsOn) e.Handled = true;
}

P onow nie kliknij szary p ro sto k ą t — tym razem p o w inna zadziałać pułap k a. Użyj
polecenia Step Over (F10), by wykonać kolejne wiersze kodu jeden po drugim .
N a sam ym p o czątku zostanie w ykonany b lo k i f , k tóry wyczyści zaw artość kolekcji
outputltems (typu O bservableC ollection ), pow iązanej z k o n tro lk ą ListBox .
D zieje się ta k dlatego, że referen cje sender o raz e.O riginalSource w skazują tę sam ą k o n tro lk ę Rectangle ; a w aru n ek
sender == e.O riginalSource jest spełniony wyłącznie w p ro c e d u rze obsługi zd arzeń kontro lk i, k tó ra jak o pierw sza
w ywołała zdarzenie (w tym przy p ad k u będzie to k o n tro lk a, k tó rą kliknąłeś lub dotknąłeś).

K iedy dotrzesz do końca m etody, kontynuuj krokowe wykonywanie programu . Z d arze n ie będzie pro p ag o w ać w górę
drzew a obiektów , u ru ch am iając najpierw p ro ce d u rę obsługi zd arzen ia k o n tro lk i Rectangle , n a stęp n ie k o n tro lk i Grid ,
StackPanel i docierając w k ońcu do p ro ced u ry obsługi zd arzen ia strony LayoutAwarePage, k tó ra jest zdefiniow ana
po za kodem aplikacji, dlatego je st w ykonyw ana zawsze. Poniew aż ża d n a z tych k o n tro le k nie je st początkow ym źródłem
zdarzenia, zatem w żadnej z kolejnych p ro c e d u r obsługi zd arzeń p a ra m e tr sender nie będzie rów ny właściwości
e.O riginalSource , w ięc żad n a z nich nie wyczyści listy z w ynikami.

W YŁĄ C Z WŁAŚCIWOŚĆ ISHITTESTVISIBLE, KLIKNIJ PRZYCISK


„ZAKTUALIZUJ" I PONOWNIE KLIKNIJ LUB DOTKNIJ PROSTOKĄT. Zaktuakau) w ło ic iw o K IsHrtTcstVbibl« kontrolki Rcctangl

Nowa warto« właiowoió kHtT«tv<*t*

! Pow inieneś zobaczyć takie wyniki.

Chwileczkę! K liknąłeś p ro sto k ą t, a została w ykonana p ro c e d u ra obsługi


zd arzeń PointerPressed k o n tro lk i E llip se . C o się stało?

K iedy nacisnąłeś przycisk, jego p ro c e d u ra obsługi zd arzeń Click zm ieniła


w arto ść właściwości IsH itT estV isib le k o n tro lk i Rectangle , przypisując
jej f a ls e . W te n sposób stała się o n a „niew idoczna” dla takich zdarzeń
ja k naciśnięcia, kliknięcia o ra z w szelkie in n e zd arzen ia zw iązane ze
w skazyw aniem . A zatem kiedy d o tk n ąłeś p ro sto k ąta , in fo rm acja o tym
zo stała p rzek a za n a do pierw szej k o n tro lk i n a stro n ie, um ieszczonej poniżej
p ro sto k ą ta , któ rej w łaściwość IsH itT estV isib le m a w arto ść true i której
w łaściwość Background o k reśla jakiś ko lo r lub m a w arto ść Transparent .
W naszym p rzy p ad k u o k azuje się, że w arunki te spełnia k o n tro lk a
E llip s e , zatem to o n a wywołuje zd arzen ie PointerPressed .

756 Rozdział 15.


Zdarzenia i delegaty

PRZEŁĄCZ PRZEŁĄCZNIK „OBSŁUŻONE


W KONTROLCE GRID" I KLIKNIJ
LUB DOTKNIJ SZARY PROSTOKĄT.
Uzyskane wyniki powinny przypominać
te przedstawione na zrzucie. ---------------------------*
Dlaczego w tym przypadku do listy zostały dodane
tylko dwa komunikaty? Ponownie prześledź
działanie programu instrukcja po instrukcji, aby
zobaczyć, co się w nim dzieje. W tym przypadku
właściwość gridSetsHandled.IsOn miała wartość
true, ponieważ przełączyłeś przełącznik na pozycję
W łączone. A zatem ostatni wiersz procedury
obsługi zdarzeń kontrolki Grid przypisuje
właściwości e.Handled wartość true. Kiedy to
się stanie, propagacja zdarzenia jest natychmiast przerywana. Zaraz gdy procedura obsługi zdarzeń PointerPressed
kontrolki Grid zostanie zakończona, aplikacja zauważy, że zdarzenie zostało obsłużone i nie wywoła jego procedur
obsługi zdefiniowanych dla kontrolek Border oraz StackPanel; przeskoczy bezpośrednio do procedury obsługi klasy
LayoutAwarePage, która została zdefiniowana poza naszym kodem.

UŻYJ APLIKACJI, BY POEKSPERYMENTOWAĆ Zdarzenie trasowane


ZE ZDARZENIAMI TRASOWANYMI.
najpierw powoduje
Oto kilka rzeczy, które możesz wypróbować:

★ Kliknij szary prostokąt oraz czerwoną elipsę, by sprawdzić,


wykonanie procedury
jak propagują zdarzenia. obsługi określonej
★ Włącz każdy z przełączników (zaczynając do tego na samej górze),
by sprawić, że procedury obsługi zdarzeń będą przypisywały
w kontrolce,
właściwości e.Handled wartość true. która wywołała
★ Ustawiaj pułapki i prześledź działanie wszystkich procedur obsługi dane zdarzenie.
zdarzeń.

★ Spróbuj ustawić pułapkę w procedurze obsługi zdarzeń


Następnie zdarzenie
kontrolki E llip se , a następnie włączaj i wyłączaj właściwość to propaguje w górę
IsH itT estV isib le kontrolki Rectangle, używając w tym
celu przycisku i przełącznika u dołu strony. Prześledź działanie hierarchii kontrolek aż
procedury obsługi zdarzeń kontrolki Rectangle w przypadku,
gdy jej właściwość IsH itT estV isib le ma wartość fa lse . do samej góry, chyba
★ Zatrzymaj program, a następnie określ wartość właściwości że któraś z procedur
Background kontrolki Grid tak, by zaczęły do niej trafiać zdarzenia
związane z klikaniem i dotykaniem. obsługi ustawi
e.Handled na true.

jesteś tutaj ► 757


N a d a w c y i o d b io r c y

Połączenie nadawców zdarzenia z jego odbiorcami


Jedną z najciekawszych rzeczy związanych ze zdarzeniami jest to, że nadawca musi
wiedzieć, jaki rodzaj zdarzenia wysłać — włączając w to argumenty do jego przekazania
— natomiast odbiorca zdarzenia musi wiedzieć o typie zwracanym oraz argumentach
procedury jego obsługi.

Jednak — i tu jest rzecz najbardziej interesująca — nie możesz połączyć nadawcy


i odbiorcy. Chcesz, aby nadawca wysyłał zdarzenie i nie interesował się tym, kto jego
w iadom ości odbierze. Odbiorca zaś ma się martwić tylko o zdarzenie, nie o obiekt, który
je zasygnalizował. Zarówno jeden, jak i drugi skupia się na zdarzeniu; nie interesują się
sobą nawzajem.

B all musi wiedzieć


o BalllnPlay, ponieważ
m usi zdarzenie
1=5 3=
^ odpowiednią
procedurą ich obstuoi
s y g n a li z o w a ć .

^ BaW % P itc ^
Bali NIE chce interesować
s ię obiektem Pitcher.
Nie przejmuje s ię tym, j
obiektem pracuje, tan,

„Moi ludzie będą w kontakcie z twoimi ludźmi".


Wiesz już, co robi ten kod:
Ball currentBall;
Tworzy on zmienną referencyjną, która może odwoływać się do dowolnego
obiektu Ball — nie jest przywiązana do jednego z nich. Może wskazywać
na dowolną instancję B a ll, ale także przyjąć wartość null i nie wskazywać
na nic.

Zdarzenie potrzebuje podobnej referencji — zamiast wskazywać na obiekt,


musi jednak wskazywać na metodę. Każde ze zdarzeń przechowuje Delegat, rzeczownik
listę wszystkich metod, które je subskrybują. Widziałeś już, że mogą one — osoba wysłana przez kogoś lub
znajdować się w innych klasach. Mogą nawet być metodami prywatnymi. posiadająca upoważnienie, by kogoś
W jaki zatem sposób zdarzenie przechowuje listę wszystkich procedur reprezentować. Prezydent wysłał na
obsługi, które musi wywołać? Używa w tym celu tak zwanego delegatu. szczyt swojego delegata.

758 Rozdział 15.


Zdarzenia i delegaty

Delegat Z A S T Ę P U JE właściwą metodę Gdy tw orzysz


delegat, m u sisz
Jednym z najbardziej użytecznych aspektów zdarzeń jest to, że w momencie ich sygnalizowania jedynie określić
sygnaturę metod,
nie mają one pojęcia o właścicielach wywoływanych metod. Każdy obiekt, który subskrybuje na które będzie on
zdarzenie, ma metodę jego obsługi. W jaki sposób zdarzenie tym zarządza? wskazywał.

Używa typu, który nazywamy delegatem. Pozwala Ci on na utworzenie zmiennej referencyjnej,


zamiast jednak wskazywać na instancję klasy, wskazuje na metodę wewnątrz niej. W ten
sposób delegaty stanowią podstawę obsługi zdarzeń. Ten delegat będzie
I
mógł być używany
z każdą metodą, która
W zasadzie korzystałeś już z delegatów w tym rozdziale! Kiedy tworzyłeś zdarzenie BallInPlay, przyjm uje object oraz
używałeś EventHandler. Prawdę mówiąc, EventHandler jest właśnie delegatem. Jeśli przejdziesz EventArgs i nie ma
do IDE i klikniesz prawym przyciskiem myszy, a potem wybierzesz Go To D efinition, to zobaczysz wartości zwracanej.
coś takiego (sprawdź sam):

public delegate void EventHandler(object sender, EventArgs e);


To określa wartość wynikową sygnatury Ten delegat nazwano
~
delegatu — oznacza, że EventHandler możeże
• t _
EventHandler.
w skazyw ać tylko na te m etody, które nic
nie zwracają.
Z r ó b to !

Delegat dodaje do Twojego projektu nowy typ * +

Kiedy do projektu dodajesz delegat, to właściwie dodajesz nowy typ. Używając go do utworzenia pola lub zmiennej,
tworzysz instancję tego typu. Utwórz zatem nowy projekt typu Console Application, dodaj do niego nowy plik klasy
i nazwij go C onvertsIntToString.cs . Zamiast wstawiać do niego klasę, wpisz pojedynczy wiersz:
delegate string ConvertsIntToString(int i); — C°nverts I nt ToS tn n g j e s t typem delegatu, który dodałeś
do swojego p rojektu. Teraz m ożesz używ ać go podczas
deklarowania zm iennych, dokładnie tak samo, ja k m ożesz
Następnie dodaj do klasy Program metodę HiThere(): w tym celu używ ać klas oraz interfejsów.
private s ta tic string HiThere(int i) S y gnatura tej metody p a su je do
sy g natury typ u ConvertsIntToString.
{
return "Witamy, towarzyszu nr " + (i * 100);
}
W końcu uzupełnij kod metody Main(): someM ethod j e s t zm ienną typ u ConvertsIntToString.
J e s t ona bardzo podobna do zmiennych
s ta tic void Main(string[] args) refe rencyjnych, je d nak za m ia st przyklejać e tykietkę
obiektowi na s t ercie, przyczepia ją do metody.
{
ConvertslntToString someMethod = new ConvertslntToString(HiThere);
string message = someMethod(5);
Conso! e 'Wr'd;;Lin' ( messag6);
Console.ReadKey O;
}

Zmienna someMethod wskazuje na metodę HiThere(). Kiedy Twój program wykonuje wywołanie someMethod(5),
w rzeczywistości wywołuje metodę HiThere(), przekazując do niej wartość 5. W efekcie zwrócony zostanie łańcuch znaków
o postaci „Witamy, towarzyszu nr 500” — czyli taki sam, jaki uzyskalibyśmy, wywołując metodę HiThere() bezpośrednio.
Poświęć chwilkę, by krok po kroku przetestować ten program w debuggerze, tak byś dokładnie wiedział, co się w nim dzieje.

jesteś tutaj ► 759


To kolejny program,
Poznawanie delegatów w którym używamy
aplikacji Windows
Forms. Tym razem
Delegat w akcji powodem jest chęć
skorzystania z wywołań
Nie ma żadnej tajemnicy w działaniu delegatów — w zasadzie
nie potrzebują one wiele kodu, aby działać. Użyjmy jednego z nich
do pomocy przy wyodrębnianiu supertajnego składnika szefa kuchni.
* b to !
MessageBox.Show(),
które bardzo ułatwiają
pokazanie, o co chodzi.

U tw órz n o w y projekt W in d o w s Form s Application i d o d a j deleg at.


Delegaty są zazwyczaj umieszczane poza klasami. Dodaj zatem do projektu nowy plik klasowy i nadaj mu
Usuń z nowego nazwę GetSecretIngredient.cs. Będzie on zawierał dokładnie jeden wiersz kodu:
pliku deklarację
klasy i zastąp - —^ d eleg a te strln g GetSecretIngred1ent(1nt amount);
ją tym wierszem
kodu. (Upewnij się, że usunąłeś z pliku całą deklarację klasy). Delegat ten może zostać użyty do utworzenia
zmiennej wskazującej na dowolną metodę, która przyjmuje jeden parametr typu in t i zwraca string.

D odaj kla sę d la pierw szeg o sze fa kuchni, S u za n n e.


Plik Suzanne.cs będzie przechowywał klasę zawierającą informację o supertajnym składniku pierwszego
szefa kuchni. Posiada on prywatną metodę o nazwie SuzannesSecretIngredient() z sygnaturą, która
jest zgodna z G etSecretIngredient. Zawiera także właściwość tylko do odczytu (sprawdź jej typ),
która zwraca G etSecretIngredient. Inne obiekty mogą jej używać do pobrania referencji metody
SuzannesSecretIngredient() — właściwość może zwracać delegat odwołujący się do tej metody,
nawet jeśli jest ona metodą prywatną.
cla ss Suzanne {
public GetSecretIngredient MySecretlngredlentMethod {

kltS y^S u za n n e’'''1* return new GetSecretIngred1ent(SuzannesSecretIngred1ent);


jako parametr }
przyjm uje am ourt }
typu int i D iraca private string SuzannesSecretIngred1ent(1nt amount) {
' return amount.ToStr1ng() + 11 dekagramów goździków";

S Do d aj t e a z Ma s ę d a drug ie g o sz e fa kuch ni
, Am y.
Metody tej klasy działają podobnie: co ufatw ia pokazanie tego,
cla ss Amy { V
public GetSecretIngredient MySecretlngredlentMethod {
Także w tym
get {
przypadku metoda
zwracająca return new GetSecretIngred1ent(AmysSecretIngred1ent);
sekretny składnik }
pobiera wartość }
typu int i zwraca private string AmysSecretIngred1ent(1nt amount) {
łańcuch znaków,
jednak różni się 1f (amount < 10)
on od łańcucha return amount.ToStr1ng() + " puszek sardynek - - potrzebujesz więcej!";
Suzanne. e lse
return amount.ToStr1ng() + " puszek sardynek";
}
}

760 Rozdział 15.


Zdarzenia i delegaty

U tw órz taki form ularz. --------------------------------------------------------1

Oto jego kod:


GetSecretIngredient ingredientMethod = null;
Suzanne suzanne = new Suzanne();
Amy amy = new Amy();

private void useIngred1ent_CHck(object sender, EventArgs e) {


i f (ingredientMethod != null)
Console.WriteLine("Dodam " + ingredientMethod((int)amount.Value));
else
Console.WriteLine("Nie mam tajnego składnika!") Upewnij s ię , że kontro/ce
NumericUpDown, którą umieścited
} w formularzu, nadałeś nazwę „a m c^ n f.

private void getSuzanne_CHck(object sender, EventArgs e) {


ingredientMethod = new GetSecretlngredient(suzanne.MySecretlngredientMethod):
}

private void getAmy_CHck(object sender, EventArgs e) {


ingredientMethod = new GetSecretlngredient(amy.MySecretlngredientMethod);
}

U żyj d e b u g g era w celu sp ra w d ze n ia d zia ła n ia deleg ató w .


Masz wspaniałe narzędzie — debugger IDE. Pozwoli Ci on na dokładne zbadanie działania delegatów.
Wykonaj następujące czynności (pamiętaj, że wyniki będą wyświetlane w oknie O utput IDE):

★ Uruchom program. Kliknij na początku przycisk Pobierz skła d n ik — powinien on wypisać w oknie
konsoli komunikat „Nie mam tajnego składnika!”.
★ Kliknij przycisk Użyj delegatu S u za n n e. Do pola ingredientMethod (które jest
delegatem G etSecretIngredient) zostanie wpisana wartość zwrócona przez właściwość
MySecretIngredientMethod. Zwróci ona nową instancję typu G etSecretIngredient, która
będzie wskazywała na metodę SuzannesSecretIngredient().
★ Kliknij po raz kolejny przycisk Pobierz skła d n ik. Teraz pole ingredientMethod wskazuje na
SuzannesSecretIngredient(). Metoda zostanie wywołana. Przekazana zostanie do niej wartość
z kontrolki NumericUpDown (upewnij się, że nadałeś jej nazwę amount), a całość zostanie wypisana
w oknie konsoli.
★ Kliknij przycisk Użyj delegatu A m y . Korzysta on z właściwości Amy.MySecretIngredientMethod,
aby ustawić pole ingredientMethod formularza tak, by wskazywało ono na metodę
AmysSecretIngredient().
★ Kliknij przycisk Pobierz skła d n ik ponownie. Teraz wywoływana jest metoda klasy Amy.
★ Użyj teraz debuggera, aby dokładnie sprawdzić, co się dzieje. Wstaw pułapkę w pierwszym wierszu
wszystkich trzech metod formularza. Uruchom ponownie program (co ustawi ingredientMethod
na domyślną wartość n u ll) i powtórz pięć powyższych kroków. Użyj Step Into (F11), aby przejść
przez każdy wiersz kodu. Sprawdź, co się stanie, gdy klikniesz Pobierz skła d n ik. Wykonanie
programu przeniesie się bezpośrednio do klasy Suzanne lub Amy w zależności od metody, która
znajduje się w polu ingredientMethod.

jesteś tutaj ► 761


N ie k tó r e z d a r z e n ia s ą z b y t p u b lic z n e

Zagadkowy basen

public Form1() I
InitializeComponent();
th is . += new EventHandler(Minivan);
th is . += new EventHandler(______________ ) ;
I
void Towtruck(object sender, EventArgs e) I
Console.Write("zbliża s i ę , " ) ;
Twoim za d a n ie m jest pobranie fragmentów I
kodu z basenu i wstawienie ich w puste miejsca.
void Motorcycle(object sender, EventArgs e) I
Możesz użyć tego samego fragmentu więcej
niż raz i nie musisz wykorzystać ich wszystkich. button1.____________ += new EventHandler(_______________) ;
Celem jest dokończenie kodu formularza, który I
po kliknięciu przycisku buttonl wyświetli
w oknie konsoli poniższy komunikat.
void Bicycle(object sender, EventArgs e) I
Console.WriteLine("aby Cię z ła p a ć !");
I
W ynik:
void ______________ (object sender, EventArgs e) I
Palczasty z b liż a s ię , aby C1ę złapać!
button1.____________ += new EventHandler(Dumptruck);
button1.____________ += new EventHandler(_______________) ;
I
void ______________ (object sender, EventArgs e) I
Console.Write("Palczasty ")
I

Przypominamy: każdy
fragment kodu z basenu
może zostać użyty
więcej niż raz!

Rozwiązanie zamieściliśmy na stronie 765. - - - - - - - - - - - ►


762 Rozdział 1S.
Zdarzenia i delegaty

Każdy obiekt może subskrybować publiczne zdarzenie...


Przypuśćmy, że dodaliśmy do naszego symulatora nową klasę Bat, a ta wnosi do kolekcji
zdarzenie HitTheBall. Wygląda to w sposób następujący: symulator wykrywa uderzenie piłki
przez gracza i wywołuje metodę OnHitTheBall klasy Bat, która sygnalizuje to zdarzenie.

Możemy teraz dodać do klasy Ball metodę bat_HitTheBall, która będzie nasłuchiwała
zdarzenia HitTheBall obiektu Bat. Gdy piłka zostanie uderzona, jej własna procedura
obsługi zdarzenia uruchomi OnBallInPlay(), aby wywołać własne zdarzenie BallInPlay.
W tym momencie zaczyna się cała reakcja łańcuchowa. Zawodnicy z pola pilnują baz, kibice Teraz procedura
dopingują, sędziowie krzyczą... mamy piłkę w grze. obsługi' zdarzenia może
pobrać informację
o si/e zamachu, a na
je j podstaw ie okreś/ić
Obiekt Ba// subskrybuje dy s ta ns i trajektorię
Symulator wykrywa zdarzenie HitTheBa//. oraz wywołać zdarzenie
zderzenie kija z pitką, B a//InP/ay.
wywotuje zatem metodą
OnHitTheBaHO obiektu B at.

— ^ „
bat.O nH itTheBall() h

Zdarzenie HitTheBall ^ Bal\

O j, oj i Te pitki
mia/y być użyte jako
rezerwowe, gdyby
pierw sza poleciała
gdzieś poza boisko.
...ale to nie zawsze jest dobre!
W danej chwili w grze może być tylko i wyłącznie jedna piłka, jednak obiekt Bat
i
używa zdarzeń do sygnalizowania faktu uderzenia piłki, a to oznacza, że zdarzenie to
może subskrybować dowolny obiekt B a ll. W ten sposób udało się nam wprowadzić
do programu niewielki, ale nieprzyjemny błąd. Co się stanie, jeżeli programista przez rźofconą przez miotacza, w szystkie
przypadek doda trzy kolejne obiekty Ball ? Pałkarz weźmie zamach, uderzy, a w pole cztery poleciały w pole gry!
polecą cztery różne piłki!

8 at€S&
Zdarzenie HitTheBall

^ Bal\
O b ie k t ^
jesteś tutaj ► 763
F u n k c je z w r o t n e s p i e s z ą z p o m o c ą

Użyj funkcji zwrotnej, by wiedzieć, kto nasłuchuje


Nasz system zdarzeń działa tylko wtedy, gdy mamy jeden obiekt Ball i jeden Bat. Jeżeli masz kilka obiektów Ball
i wszystkie one subskrybują publiczne zdarzenie HitTheBall, to po jego zasygnalizowaniu każdy z nich poleci na boisko.
Nie ma to większego sensu... W końcu istnieje tylko jeden obiekt B all, który został uderzony. Musimy zadbać o to,
aby w danym momencie tylko jedna piłka mogła być powiązana z kijem. Inne nie mogą w tym czasie podpinać się pod
to zdarzenie.
To właśnie do tego służy funkcja zwrotna. Jest to technika, której możesz używać wraz z delegatami. Zamiast udostępniać
zdarzenie, które każdy może subskrybować, obiekt korzysta z metody (zazwyczaj konstruktora), by pobrać delegat i zapisać
go w polu prywatnym. Wykorzystamy tę technikę, by zagwarantować, że obiekt Bat poinformuje o uderzeniu dokładnie
jeden obiekt B a ll:

Pole deleg atu w o biekcie B a t b ę d zie polem pryw atnym .


Najłatwiejszy sposób zapobiegania nieprawidłowemu podłączaniu się obiektów Ball do łańcucha wywołań
polega na uczynieniu pola delegatu kija prywatnym. Dzięki temu będziemy mieli kontrolę nad tym, którego
obiektu Ball metoda jest wywoływana.

Konstruktor Bat p o biera deleg at, który w sk a zu je n a m eto d ę piłki.


Kiedy piłka jest w grze, tworzona jest nowa instancja kija. Następnie do obiektu Bat przesyłana jest
referencja do metody OnBallInPlay(). Nazywamy to funkcją zwrotną, ponieważ Bat używa jej do
wywoływania metod obiektu, który tę metodę przekazał.

Ot>iekt Ball przekazuje


delegatowi referencję swojej
wtasn ej metody OnBallInPlay()
do konstruktora B at. Kij
za p amię tu je delegat w swoim
prywatny m polu hitBallCallback.

Kiedy kij u d erzy w piłkę, w yw ołuje m eto d ę zw rotną.


W związku z tym, że delegat w klasie Bat jest prywatny, możemy być na 100% pewni, że żadna inna piłka
nie została uderzona. To rozwiązuje problem!

| hitBallCallback

Teraz obiekt B at może


Bał Inne pitki nie mogą
wywoływać swój de!ego.t
podtączyć się do
hitBallCallback, który
łańcucha delegatu, z kolei wywołuje metodę
ponieważ j e s t OnBallInPlay() obiektu Ba>l.
to pole prywatne
w obiekcie Bat.

764 Rozdział 15. Bal\


Zdarzenia i delegaty

P rzyp ad ek złotego skorupiaka


Heniek „Płaskostopie” Hopkins jest obiektem TreasureHunter. Znalazł on w unikatowym
i niezwykłym sklepie z biżuterią, stylizowanym na podwodny salon, jeden z najcenniejszych
przedmiotów: pokrytego nefrytem, półprzezroczystego złotego kraba. Istnieje wiele instancji
TreasureHunter i wszystkie posiadają referencję do tego samego kraba przekazaną w konstruktorze,
ale Heniek pragnie jako pierw szy zdobyć nagrodę.

W zrabowanym zbiorze diagramów klas odkrył, że obiekt GoldenCrab sygnalizuje zdarzenie


RunForCover za każdym razem, gdy ktoś się do niego zbliży. Jest nawet lepiej,
Z a g a d k a
bo zdarzenie posiada parametr NewLocationArgs, który zawiera szczegółowe
n a p ię ć informacje na temat położenia kraba. Żaden z pozostałych obiektów łowców skarbów
o nim nie wie, więc Heniek jest już pewny, że pieniądze są jego.
m in u t .
Heniek dodał do swojego konstruktora kod, który rejestruje jego metodę treasure_
RunForCover() jako procedurę obsługi zdarzenia RunForCover obiektu kraba. Wysłał zatem
swoich podwładnych, aby kraba szukali, wiedząc, że ten będzie uciekał, ukrywał się i sygnalizował
zdarzenie RunForCover. Heniek, mając w posiadaniu metodę treasure_RunForCover(), będzie
otrzymywał wszelkie potrzebne informacje.

Wszystko szło zgodnie z planem, do czasu gdy Heniek przeniósł się do nowej lokalizacji
i spróbował kraba pochwycić. Zdziwił się bardzo, widząc tam walczące o niego pozostałe trzy obiekty
TreasureHunter.
W j a k i sposób in n i poszukiw acze skarbów p o ko n a li H eń ka w walce o kraba?

- ► Odpowiedź znajdziesz na stronie 769.

Konstruktor tworzy
tańcach dwóch
public Form1() { Zagadkowy basen
InitializeComponent();
procedur obsfugi
zdarzenia wczytania^ t h is . Load += new EventHandler(Minivan);
Rozwiązanie
formularza. t h is . Load += new EventHandler(M otorcycle );
Zdarzenie to je s t
sygnalizowane }
bezpośrednio void Towtruck(object sender, EventArgs e) {
po zakończeniu
w czytyw ania.
Console.Write("zbliża s ię , ");

void Motorcycle(object sender, EventArgs e) {


buttonl. Click += new EventHandler(Bicycle);
Dwie m etody obsfugi
void Bicycle(object sender, EventArgs e) { zdarzenia Load dodają trzu
funkcje obsfugi zdarzenia
Po kliknięciu ^
Console.WriteLine("aby Cię złapać!"); U ick przycisku.
przycisku }
wywoływane są void Minivan (object sender, EventArgs e) {
trzy procedury
obstugi zdarzenia, buttonl. Click += new EventHandler(Dumptruck)
które zo sta ły buttonl. Click += new EventHandler(Towtruck );
w cześniej
za rejestrowane.
}
void Dum ptruck (object sender, EventArgs e) { Pam iętaj, że w aplikacjach
WinForms wywołanie
Console.Write("Palczasty "); Console.W riteLine()
} wy św ietla łańcuch znaków
w oknie O u tp u t IDE.

jesteś tutaj ► 765


Z o s ta w w ia d o m o ś ć , o d d z w o n ię

Funkcje zwrotne są jedynie sposobem używania delegatów


Funkcja zwrotna to inny sposób użycia delegatu. Nie jest to nowe słowo
kluczowe ani operator. To wzorzec działania i jedna z technik korzystania
z delegatów w klasach, dzięki której jeden obiekt może powiedzieć drugiemu:
„Jeśli nie masz nic przeciwko temu, powiadom mnie, kiedy to się stanie”.
^ «
© Do projektu sym ulatora g ry w b aseb a ll d o d a j je sz c z e jed en d elegat.
W związku z tym, że Bat posiada prywatne pole delegatu wskazujące na metodę OnBallInPlay() obiektu B a ll,
potrzebujemy delegatu o sygnaturze:
delegate void BatCallback(BallEventArgs e)
Funkcja z wrotna o ^ e k tu B a t będzie wskazywała
Delegaty nie m u szą być um ieszczane w odrębnych na m etodę OnBallInPlay() obiektu Ball, je j
plikach. Spróbuj um ieścić tego delegata w pliku delegat loędzie więc m usiał pokrywać s ię
zawierającym definicję klasy Bat. Upewnij się, że z sy g natu rą m etody. Znaczy to, że powinna
zostanie on um ieszczony wewnątrz tej sam ej przestrzeni p rzy jm° wać param etr BallEventArgs i mieć
nazw, do której należy ta klasa. wartość wynikową w postaci void.

© D odaj d o projektu kla sę Bat.


Klasa Bat jest prosta. Posiada metodę H itTheBall(), z której symulator korzysta za każdym razem,
gdy ma być uderzona piłka. Używa ona delegatu hitBal lCallback do wywołania metody OnBallInPlay()
(lub jakiejkolwiek innej przekazanej do konstruktora).
class Bat j
private BatCallback hitBallCallback;
Upewnij się
za każdym public Bat(BatCallback callbackDelegate) j
razem, this.hitB allC allback = new BatCallback(callbackDelegate);
czy żaden
z delegatów nie I
j e s t równy null. public void HitTheBall(BallEventArgs e) j
Gdyby tak było, i f (hitBallCallback != null)
otrzym ałbyś
w yjątek pu stej hitBallC allback(e);
referencji. }
} Zastosowaliśmy operator =, a nie +=, gdyż w tym przypadku chcemy, by obiekt Bat poinformował
o uderzeniu tylko jedną piłkę; dlatego też wartość delegatu jest określana tylko raz. Nic jednak nie
stoi na przeszkodzie, by zastosować operator += i wywoływać wiele metod. Celem funkcji zwrotnych
jest zapewnienie obiektowi kontroli nad tym, kto zostanie poinformowany. W przypadku zdarzeń,
poprzez fakt dodania procedury obsługi, to inne obiekty żądały, by je informowano. Natomiast w razie
stosowania funkcji zwrotnych inne obiekty przekazują delegaty i grzecznie proszą, by je wywołano.

© B ędziem y m usieli sko jarzyć kij z piłką.


W jaki sposób konstruktor Bat otrzymuje referencję do określonej metody OnBallInPlay() ?
To proste — dostaje ją poprzez metodę GetNewBat() obiektu B a ll, którą musisz dodać do klasy B a ll:
public Bat GetNewBat()
j
return new Bat(new BatCallback(OnBallInPlay));
M etoda_GetNew B at() klasy Ball tworzy

^ Ś m
I
/ w konstruktorze
B
noawy obie kt B a t i używa delegatu
BatCallback w celu przekazania referenc
do sw ojej w łasnej m etody OnBallInPlay()
CenekU+ W n[ektóry ch przypadkach korzystniej nowej in sta n c j B at. To j e s t właśnie
ta metoda, której kij będzie używ ał
^ Z l
lUb se e? *1 p0przez m et0^ pub liczną C w momencie uderzenia piłki.

766 Rozdział 1S.


Zdarzenia i delegaty

(4 Teraz m o żem y tro chę p o p raw ić h erm etyzację klasy B a li .


Zwykle metody sygnalizujące zdarzenia o postaci O n... nie są deklarowane jako public. Zastosujmy ten sam
wzorzec dla naszej piłki, tworząc chronioną metodę OnBallInPlay():
protected void OnBallInPlay(BallEventArgs e) {
EventHandler<BallEventArgs> ballInPlay = BallInPlay;
i f (BallInPlay != null) To je s t naprawdę stand°rdowy w ^ e c ^ P O C y
B allInP lay(this, e); będziesz s ię wielokrotnie spofylwt ^ d « « pr°c y
z k/asami .N ET. Kiedy g o t u j ą one
} zdarzenie, prawie zaw sze m° żeszij znatezć metodę
chronioną zaczynają c ą s ię od „On .

© Pozostało n a m tylko p o p raw ić k la sę BaseballSim ulator .


Klasa BaseballSim ulator nie może już wywoływać metody OnBallInPlay() obiektu Ball — osiągnęliśmy zatem
nasz cel (choć teraz IDE wyświetla komunikat o błędzie). Teraz klasa ta musi poprosić klasę Ball o nowy kij,
którego użyjemy do uderzenia piłki. Postępując w ten sposób, mamy pewność, że metoda OnBallInPlay() obiektu
Ball została odpowiednio połączona z funkcją zwrotną kija.
Gdy obiekt Baseba//Simu/ator chce uderzyć
public void PlayBall() { p iłkę, m usi pobrać od niej nowy obiekt B a t.
{ Piłka zadba o to, aby funkcja zwrotna została
w ła ściw ie podpięta do k ija . Teraz, kiedy
Bat bat = ball.GetNewBat();
s y mu/ator wywołuje metodę HitTheBa//0,
BallEventArgs ballEventArgs = uruchamiana je s t też metoda OnBa//InP/ayO
new BallEventArgs(Trajectory, Distance); p iłk i. Ta z ko/ei wywołuje zdarzenie
Ba//InP/ay.
bat.HitTheBall(ballEventArgs);
}
Uruchom program — powinien działać dokładnie tak jak wcześniej. Jest teraz chroniony przed
problemami, które mogły wynikać z subskrybowania zdarzeń kija przez większą liczbę obiektów piłek.

Nie w ierz nam jednak na słowo — otwórz to w debuggerze!

CELNE SPOSTRZEŻENIA
■ Wszystkie kontrolki okna Toolbox używają zdarzeń,
gdy coś się stanie w programie.
Kiedy do projektu dodajesz delegat, to tworzysz nowy ■ Kiedy jeden obiekt przekazuje referencję metody
typ, który przechowuje referencje do metod. do innego, tak aby mógł uzyskać za jej pomocą
Zdarzenia używają delegatów do powiadamiania informacje zwrotne (tylko on sam), nazywamy
obiektów o swojej aktywności. to funkcją zwrotną.

Obiekty subskrybują zdarzenia innych obiektów, ■ Zdarzenia pozwalają metodom anonimowo podłączać
jeśli chcą w jakiś sposób na nie zareagować. się pod nie, natomiast funkcje zwrotne pozwalają Ci
uzyskać lepszą kontrolę nad delegatami, które przyjmują.
EventHandler jest rodzajem delegatu szczególnie
popularnego w pracy ze zdarzeniami. ■ Zarówno funkcje zwrotne, jak i zdarzenia używają
delegatów do przechowywania i wywoływania metod
Możesz dołączyć do jednego zdarzenia kilka procedur w innych obiektach.
jego obsługi. To dlatego do ich przypisania używasz +=.
■ Debugger jest naprawdę dobrym narzędziem,
Przed użyciem zdarzenia lub delegatu zawsze sprawdzaj, które często okazuje się pomocne. Możesz go użyć
czy nie są one równe n u ll. Unikniesz w ten sposób dla lepszego zrozumienia działania zdarzeń, delegatów
wyjątku NullReferenceException. oraz funkcji zwrotnych. Korzystaj z niego!

jesteś tutaj ► 767


T e w z o rc e p ro je k to w e s ą p o m o c n e

Możesz używać funkcji zwrotnych Spróbuj dodać te


wiersze kodu do swojej
w oknach dialogowych MessageDialog aplikacji. Możesz użyć
polecenia Generate
Tworząc obiekty UICommand używane w oknach dialogowych M e ssa g e D ia lo g , przekazujemy M eth od Stub IDE, by
do nich funkcję zwrotną w postaci delegatu U IC om m andInvokedH andler . Dodatkowo można wygenerować szkielet
funkcji zwrotnej.
także przekazać opcjonalny obiekt identyfikatora; zarówno etykieta, jak i identyfikator będą
dostępne za pośrednictwem parametrów delegatu IUICommand .
To może być dowolny obiekt,
' niekoniecznie fofcuch znaków.
MessageDialog dialog = new MessageDialog("Oto okno dialogowe");
,
dialog.Commands.Add(new UICommand("Opcja” MyUiCommandCallback, "Mój identyfikator")); P
await dialog.ShowAsync();

P : C zym się ró żn ią fu n kcje zw ro tn e od zd arzeń?


Istnieje wiele problemów, na które natrafisz w trakcie pracy, a które
zostały już rozwiązane. Te pojawiające się dość często mają swoje
O : Zdarzenia i delegaty są częścią C#. To sposób, w jaki jeden własne wzorce projektowe, z których możesz skorzystać.
obiekt ogłasza innym, że coś się wydarzyło. Kiedy udostępnia on
zdarzenie, może je subskrybować dowolna liczba innych obiektów, P : Funkcje zw ro tn e są w ięc p ryw a tn ym i zd arzen iam i?
a obiekt udostępniający nie będzie o tym nic wiedział ani nie
O : Nie do końca tak jest. Wydaje się, że w uproszczeniu
będzie go to interesować. Gdy zasygnalizuje on zajście zdarzenia,
można to tak rozumieć, ale są to całkiem odmienne rzeczy
wywołana zostanie funkcja jego obsługi w każdym obiekcie
Pamiętasz, co dokładnie oznacza modyfikator dostępu p r iv a t e ?
zgłoszonym do nasłuchiwania.
Kiedy oznaczysz składową klasy tym słowem, dostęp do niej
Funkcje zwrotne w ogóle nie są częścią platformy .NET — to będą mogły uzyskać tylko instancje tej samej klasy. Jeśli oznaczysz
jedynie nazwa dla pewnego sposobu korzystania z delegatów zdarzenia jako p r iv a t e , to tylko instancje tej samej klasy będą
(bądź zdarzeń — nic nie stoi na przeszkodzie, by wykorzystać mogły go subskrybować. Różni się to od funkcji zwrotnych,
prywatne zdarzenie i użyć go do utworzenia funkcji zwrotnej). ponieważ w dalszym ciągu może być jeden lub więcej obiektów,
Funkcja zwrotna jest tylko związkiem pomiędzy dwoma klasami, które anonimowo się pod zdarzenie podpinają.
w którym jeden obiekt prosi o przekazanie mu powiadomienia.
Porównaj to rozwiązanie ze zdarzeniami, w przypadku których P : W ygląda to ja k zd arze n ie . Nie m a tylko słowa
obiekt żąda, by go powiadomiono. kluczowego e v e n t, zgad za się?

P : F u n k cja zw ro tn a nie je st w takim ra z ie typem C#? O : Zdarzenie wygląda podobnie do funkcji zwrotnej dlatego,
że oba mechanizmy używają de legatów . To, że są one
O : Nie. Jest ona wzorcem — to po prostu nowatorski sposób
wykorzystywane w obu przypadkach, ma głębszy sens, gdyż jest to
użycia istniejących typów, słów kluczowych i innych narzędzi
sposób, w jaki C# pozwala jednemu obiektowi przekazać do innego
udostępnianych przez C#. Cofnij się nieco i jeszcze raz spójrz na
referencję do jednej ze swych metod.
kod klas B a ll i B a t. Czy widzisz jakiekolwiek słowa kluczowe,
których wcześniej nie używaliśmy? Nie! Użyliśmy w nim jednak Wielką różnicą pomiędzy funkcjami zwrotnymi a zdarzeniami jest to,
delegatu, a on jest typem .NET. że zdarzenia powiadamiają wszystkich o tym, że coś się stało. Funkcja
,, , .................................... ,. , . , , zwrotna nigdy nie jest udostępniana. Jest ustawiona jako prywatna
Tak się składa że istnieje wiele wzorców, których możesz używać. test i sprawuje ścisłą kontrolę nad tym, jaka metoda zostanie wywołana.
nawet cały obszar programowania zwany wzorcam i projektow ym i.

Sprawdź książkę W zorce projektow e. Rusz g ło w ą ! na w itryn ie w ydaw nictw a Helion. To doskonałe źródło różnych
wzorców, które możesz wykorzystać w swoich programach. Pierwszym z nich jest wzorzec obserwatora, który będzie
wyglądał znajomo. Jeden obiekt udostępnia informacje, a drugi nasłuchuje. Hm... http://helion.pl/ksiazki/w zorrg.htm .

Delegaty często są stosowane w raz z metodami anonimowymi i wyrażeniami lambda.


Więcej informacji na ich temat możesz znaleźć w punkcie 9. dodatku „Pozostałości” .
768 Rozdział 15.
Zdarzenia i delegaty

Przypadek złotego skorupiaka


Z a g a d k a
W j a k i sposób in n i poszukiw acze skarbów p o ko n a li H eń ka
w walce o kraba? n a p ię ć m in u t

Klucz do rozwiązania zagadki leżał w sposobie tropienia zwierza przez r o z w ią z a n a .


poszukiwacza skarbów. Najpierw jednak musimy zobaczyć, co dokładnie
Heniek znalazł w skradzionych diagramach.

W zrabowanym zbiorze diagramów klas odkrył, że obiekt G oldenC rab sygnalizuje zdarzenie
R unF orC over za każdym razem, gdy k to ś się do niego zbliży. Jest nawet lepiej, bo zdarzenie posiada
param etr N ew L o ca tio n A rg s, który zawiera szczegółowe inform acje na tem at położenia kraba. Ż aden
z pozostałych obiektów łow ców skarbów o tym nie wie, więc H en iek jest ju ż pewny, że pieniądze są jego.

c la ss GoldenCrab {
p u b lic delegate void Escape(NewLocationArgs e ) ;
p u b lic event Escape RunForCover;
p u b lic void SomeoneNearby() {
Escape runForCover = RunForCover;
i f (runForCover != n u ll)
ru n F o rC o v e r(th is, new NewLocationArgs("Pod s k a łą " ) ;

}
} £ razern' 9dy ktoś zbliża
c la s s NewLocationArgs { się do ztotego kraba, metoda
p u b lic N ewLocationArgs(H idingPlace newLocation) { SomeoneNearbyO w ygotuje
th is.n e w L o c a tio n = new Location; zdarzenie RunforCover, które
} w yszukuje m iejsce do ukrycia.
p riv a te H idin g Place newLocation;
p u b lic H idin g Place NewLocation { get { re tu rn new Location; } }
}

W jaki sposób Heniek skorzystał z uzyskanych informacji?

H eniek dodał do swojego konstruktora kod, który rejestruje jego m etodę tr e a s u r e _ R u n F o r C o v e r () ja k o


procedurę obsługi zdarzenia R unF orC over obiektu kraba. Wysłał zatem swoich podw ładnych, aby kraba
szukali, wiedząc, że ten będzie uciekał, ukryw ał się i sygnalizował zdarzenie R unF orC over. H eniek, m ając
w posiadaniu m etodę tr e a s u r e _ R u n F o r C o v e r (), będzie otrzymywał wszelkie potrzebne informacje.

p u b lic c la s s TreasureH unter {


p u b lic TreasureHunter(GoldenCrab tre a su re ) {
treasu re .R u n Fo rC o ver += new G o ld e n C rab .Escap e(treasu re_R unFo rC o ver);

void tre a su re RunForCover(NewLocationArgs e) { ,, . . . ,,„,^tnrczaiaco sprutny, umieszczając


M oveHerefe.NewLocation);
} ^ w konstruktorze f^ kcł \ ^ ^ razem, gdy krab zasygnalizuje
p riv a te void MoveHere(HidingPlace h id in g P la ce ) { metodę MoveHereC; za i ^ a y - » ^ jnni
" / / Kod zm ie n ia ją cy położenie " ) { zdarzenie ^ F o ^ o v e r ^ Zapommar j ^
M poszukiwacze s k a tó w f- e d z - c z ą po j ; fcrtcucłlfll

To wyjaśnia, dlaczego plan Heńka się nie powiódł. Kiedy do konstruktora TreasureHunter dodał on
procedurę obsługi zdarzenia, nieumyślnie zro b ił to sam o dla w szystkich czterech poszukiw aczy skarbów!
W ten sposób każda ich funkcja obsługi zdarzenia została podłączona do łańcucha wywołań zdarzenia
RunForCover. Kiedy złoty skorupiak uciekał do kryjówki, każdy był o tym informowany. Wszystko byłoby
dobrze, gdyby Heniek był pierwszym, który otrzyma powiadomienie. Nie mógł on jednak wiedzieć, czy
inni poszukiwacze skarbów już je otrzymali. Zależało to od tego, czy zgłosili chęć subskrypcji przed nim.
Wtedy zostaliby poinformowani w pierwszej kolejności.
jesteś tutaj ► 769
J a k fu n k c jo n a ln e

Użyj delegatów, by skorzystać z panelu Ustawienia


Wyświetl ekran startowy Windows 8, a następnie kliknij kafelek Internet Explorera, po uruchomieniu aplikacji wyświetl panel
funkcji i kliknij pozycję Ustawienia. Internet Explorer zażądał dodania do panelu Ustawienia takich opcji jak Opcje. Jednak
kiedy ją klikniemy, to wygląd wyświetlonego okienka będzie się znacznie różnił od tych, które możemy zobaczyć w aplikacjach
Mapy, Poczta, czy też w innych aplikacjach pobieranych ze Sklepu Windows. Dzieje się tak dlatego, że jego wygląd zależy od
danej aplikacji — a nawet od konkretnej strony — która może przekazać systemowi Windows informacje o opcjach, które
mają się znaleźć w panelu Ustawienia, i zarejestrować funkcje zwrotne wywoływane, gdy użytkownik wybierze umieszczone w nim
opcje. W aplikacjach dla Sklepu Windows pisanych w C# do tego celu są używane delegaty. Skorzystajmy z możliwości IDE,
by dowiedzieć się, jak to działa i jak dodać polecenie O aplikacji do panelu Ustawienia aplikacji Janka.

Otwórz plik MainPage.xaml.cs i dodaj do niego przedstawione poniżej instrukcje using oraz jeden wiersz kodu do konstruktora:
using Mindows.UI.ApplicationSettings;
using Mindows.UI.Popups; S e ttln g sP a g e jest klasą statyczną pozwalającą
aplikacji dodawać i usuwać polecenia z panelu
/ / / <summary> Ustawienia. Należy ona do przestrzeni nazw
I I I Prosta strona udostępniająca możliwości wspólne dla wszystkich a p lik a c ji. M in d o w s .U I.A p p lic a tio n S e ttin g s . Trzeba
/ / / </summary> zauważyć, że umieszczenie kodu dodającego
p ublic sealed p a rtia l c la ss MainPage : KomiksyJanka.Common.LayoutAwarePage opcję „O aplikacji" do panelu Ustawienia
{ w konstruktorze strony głównej aplikacji
s t a t ic bool aboutCommandAdded = fa ls e ; powoduje, że każde ponowne wejście na stronę
public MainPage() { głów ną spowoduje dodanie do panelu kolejnej,
th is .In itia liz e C o m p o n e n t(); takiej samej opcji. Aby tem u zapobiec używamy
i f (¡aboutCommandAdded) { pola aboutCommandAdded. W arto umieścić
SettingsPane.GetForCurrentView().CommandsRequested += w komentarzu instrukcję przypisującą temu polu
MainPage_CommandsRequested; wartość true i odwiedzić kilka stron aplikacji,
aboutCommandAdded = tru e; by przekonać się, że faktycznie w panelu
} Ustawienia pojawi się kilka opcji „O aplikacji".
}
}

Po wpisaniu += IDE zaoferuje automatyczne wygenerowanie szkieletu procedury obsługi zdarzeń. Poniżej pokazaliśmy,
co należy umieścić w tej procedurze obsługi. Używa ona delegatu o nazwie UICommandInvokedHandler, dodajmy zatem
metodę o nazwie AboutInvokeHandler(). Oto kod metody wywoływanej przez polecenie O aplikacji.

void Ma1nPage_CommandsRequested(SettingsPane sender,


SettingsPaneCommandsRequestedEventArgs args) {
UICommandlnvokedHandler invokedHandler =
new UICommandInvokedHandler(AboutInvokedHandler);
SettingsCommand aboutCommand = new SettingsCommand("About", "O aplikacji Janka",
invokedHandler);
args.Request.ApplicationCommands.Add(aboutCommand);
}

async void AboutInvokedHandler(IUICommand command) {


await new MessageDialog("Aplikacja ułatwiająca Jankowi zarządzanie komiksami",
"Komiksy Janka").ShowAsync();
}

770 Rozdział 15.


Zdarzenia i delegaty

Teraz uruchom aplikację. Wyświetl panel funkcji, kliknij lub dotknij pozycję U stawienia , a następnie wybierz opcję
O aplikacji. Aplikacja wywoła metodę AboutInvokedHandler(), która wyświetli okienko MessageDialog.

U ży j klaw isza W indows (■«), by w yśw ietlać


Kiedy przejdziesz na głSwną panele funkcji o ra z pasek aplikacji.
stronę aplikacji, wyśw ie t
panel Ustawienia, a następn'<e
kliknij opcję O aplikacji, ★ Naciśnij ■■ + C, by wyświetlić panel funkcji.
spowoduje to wyświetlenie
★ Naciśnij SS + I, by wyświetlić panel Ustawienia.
okienka MessageDia>og-
★ Naciśnij ¡ S + Z, by wyświetlić pasek aplikacji.

A teraz skorzystaj z IDE, aby zobaczyć, jak to działa. Zatrzymaj program i użyj polecenia Go To D efinition,
by przejść do definicji klasy SettingsCommand pobranej z metadanych. Powinna wyglądać następująco:

I ¡public S e t t i n g s C o m m a n d ( o b j e c t s e t t i n g s C o m m a n d l d , s t n i n g tabel. UICorrma nd In vo ke dH an dl en handler);


Wyszukiwanie

Udostępnianie

Teraz ponownie użyj tego samego polecenia, by wyświetlić definicję typu UICommandlnvokedHandler:

|...p ublic d e l e g a t e void U I C o m m a n d I n v o k e d H a n d l e r ( I U I C o m m a n d c o m mand);

Sprawdź różne obiekty i metody, by przekonać się, jak działa to rozwiązanie:


★ Metoda SettingsPane.GetForCurrentView() zwraca obiekt wywołujący zdarzenie
CommandsRequested. Ponownie przejdź do kodu aplikacji i wyświetl definicję zdarzenia Urządzenia

CommandsRequested.
★ Procedura obsługi zdarzenia ma argument typu SettingsPaneCommandsRequestedEventsArgs.
Wyświetl jego definicję, by zobaczyć obiekt Request, używany w trzecim wierszu procedury obsługi.
★ Klasa Request definiuje jedną właściwość: kolekcję o nazwie ApplicationCommands, zawierającą
obiekty klasy SettingsCommand.
★ Ponownie wróć do kodu procedury obsługi zdarzeń, gdyż teraz już rozumiesz, jak ona działa. Kiedy Panel U staw ienia
użytkownik kliknie panel Ustawienia, wywołuje on swoje zdarzenie CommandsRequested, by zapytać się możesz w yśw ietlić
aplikację o polecenia, które należy w nim wyświetlić, oraz ich funkcje zwrotne. Podłączyłeś do tego naciskając
jednocześnie klaw isz
zdarzenia procedurę obsługi, która zwraca obiekt SettingsCommand definiujący polecenie O aplikacji Windows + C
oraz delegat wskazujący na metodę wyświetlającą okienko MessageDialog. Po kliknięciu O aplikacji
panel użyje delegatu, by wywołać metodę AboutInvokedHandler() .
★ Wciąż nie do końca to rozumiesz? Nie przejmuj się, skorzystaj z przycisków nawigacyjnych 0 * 0
umieszczonych na pasku narzędzi, by zmieniać prezentowane definicje. Spróbuj umieścić pułapki
w konstruktorze oraz obu przedstawionych wcześniej metodach. Czasami, zanim całość rozwiązania
„zaskoczy” Ci w głowie, będziesz musiał kilka razy wyświetlić definicje wszystkich używanych klas i metod.
Aplikacje mogą współdziałać także z panelami Wyszukiwanie i Udostępnianie! Zajrzyj do punktu 1.
dodatku „Pozostałości” , aby dowiedzieć się, gdzie możesz znaleźć informacje na ten temat.
jesteś tutaj ► 771
772 R o zd ział 1S.
16. Projektowanie aplikacji wedłuę wzorca

Świetne aplikacje
od zewnątrz i od środka

Twoje aplikacje m u szą być nie tylko w sp an iałe w izualnie. Kiedy mówimy o projekcie,
co Ci przychodzi do głowy? Przykład jakiejś wspaniałej architektury budowlanej? Doskonale rozpla­
nowana strona WWW? Produkt zarówno estetyczny, jak i dobrze zaprojektowany? Dokładnie te
same zasady odnoszą się do aplikacji. W tym rozdziale poznasz wzorzec Model-View-ViewModel
(MVVM, model-widok-widok modelu) i dowiesz się, jak używać go do tworzenia dobrze zapro­
jektowanych aplikacji o luźnych powiązaniach. Przy okazji poznasz animacje, szablony kontrolek
używane do projektowania wyglądu aplikacji, nauczysz się stosować konwertery, by ułatwiać
korzystanie z techniki wiązania danych, a w końcu zobaczysz, jak połączyć te wszystkie elementy,
by stworzyć solidne podstawy do tworzenia dowolnych aplikacji w języku C#.

to je st n o w y ro z d z ia ł ► 773
D a m i a n i K u b a z a c z y n a j ą s i ę k łó c i ć

Liga „Koszykówka. Rusz głową" potrzebuje swojej aplikacji


Damian i Kuba są kapitanami dwóch czołowych zespołów w lidze „Koszykówka. Rusz głową” —
amatorskiej lidze koszykówki w Obiektowie. W skład obu zespołów wchodzą doskonali gracze,
a ci gracze zasługują na doskonałą aplikację, która pozwalałaby śledzić i notować, kto występuje na
parkiecie, a kto siedzi na ławce.

W każdym zespo>e j acy ś gracze


w ystęp u ją na p arkieęie, a j acyś
sied zą na ławce; każdy z nich
ma im ię i num er.

Liga "Koszykówka. Rusz głową"

Wspaniali Bombiarze
Na parkiecie Na parkiecie
U» IV4i Orni m 11
Hnvytrttl Ifuttm ii
takub«4 Cyilyn»rt6
lidanrtt Mtortn 0
Cmn* Ult IV41

Na ławce Na ławce

(<te*IVii firn*o ■

r
Na tej stronie znajdują s ię cztery
odrębne kontrolki L istV iew , zatem
każda z nich potrzebuje odrębnej
kolekcji ObservableCollection, którą
można by powiązać z je j właściwością
Item sS o u rce.

774 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Jednak czy wszyscy uzgodnią, jak napisać tę aplikację?


O rany — Damian i Kuba mają zupełnie inne opinie na temat tego, jak należy napisać aplikację
dla ligi, a ich dyskusja zaczyna być naprawdę gorąca. Wygląda na to, że Damian obstaje przy
tym, by zapewnić łatwość zarządzania danymi wyświetlanymi na stronie, natomiast Kuba
przykłada wielkie znaczenie do uproszczenia wiązania danych. Ta sytuacja może doprowadzić
do poważnej konfrontacji, która nie będzie miała wiele wspólnego z rywalizacją na boisku,
choć wcale nie ułatwi napisania aplikacji!

WEDŁUG MNIE OCZYWISTYM


JEST, ŻE MUSIMY ZAPEWNIĆ ŁATWOŚĆ
DODAW ANIA DANYCH, PRAWDA?

Kuba: Chwileczkę, kowboju. To wygląda na dosyć krótkowzroczną strategię.

Damian: Jestem przekonany, że nie rozumiesz, co do ciebie mówię... powtórzę to


zatem bardzo wolno i wyraźnie. Zaczniemy od prostej klasy Player, mającej właściwości
pozwalające na przechowywanie imienia, numeru oraz informacji, czy dany gracz
występuje na parkiecie.

Kuba: Tak, rozumiem, co mówisz. Ale to ty mnie nie słuchasz. Ty myślisz o modelu danych.

Damian: No pewnie. To właśnie od niego wszystko się zaczyna.

Kuba: Owszem, w ten sposób jest łatwiej tworzyć dane.

Damian: W końcu zaczynasz ła p a ć .

Kuba: Nie skończyłem. A co z całą resztą aplikacji? Będą w niej kontrolki ListView
i TextBox, w których te dane trzeba będzie jakoś wyświetlać. Jeśli nie użyjemy kolekcji,
z którymi te kontrolki będziemy mogli powiązać, to nic z tego nie wyjdzie.

Damian: H m .

Kuba: Ano właśnie. Czyli będziemy chyba musieli podjąć kilka, h m ., strategicznych
decyzji związanych z modelem obiektów.

Damian: Masz na myśli to, że będziemy musieli dojść do kompromisu i stworzyć marny
model obiektów, którego nie da się łatwo używać, i to tylko dlatego, żebyś miał coś,
do czego będziesz mógł powiązać kontrolki?

Kuba: No, chyba że wpadniesz na jakiś lepszy pomysł.

WYSIL
SZARE KOMÓRKI
Jak można by stworzyć klasy, które zapewnią łatwość wiązania danych z kontrolkami,
a jednocześnie pozwolą utworzyć model obiektów gwarantujący łatwość zarządzania danymi?

jesteś tutaj ► 775


To nie jest m łotek

Czy projektujesz pod kątem wiązania danych, czy łatwości pracy z danymi?
Już wiesz, jak ważne jest, by tworzyć model obiektów zapewniający łatwość pracy z danymi. Co jednak zrobić w przypadku,
gdy obiekty mają służyć do dwóch różnych rzeczy? To jeden z najczęstszych problemów, z jakimi będziesz się stykał
jako projektant aplikacji. Twoje obiekty muszą posiadać publiczne właściwości oraz kolekcje O bservableCollections
umożliwiające powiązanie danych z kontrolkami XAML. Jednak czasami składowe te utrudniają operowanie na danych,
gdyż zmuszają nas do tworzenia nieintuicyjnego modelu obiektów, z którego korzystanie nie jest wygodne.

______ Player Roster


Name: string TeamName: string Trudno jest zoptymalizować klasy
Number: int Piayers: IEnumerabie<Piayer> pod kątem operowania na danych
Starter: bool
przy użyciu zapytań LINQ...

var benchPlayers =
from player in _roster.Players
where player.Starter == false
select player;

eśli swoim danym


adasz taką posta ć,
toże to ograniczyć
\ożliwości
tworzenia takich
tron, na jakich Ci
a le ż y -

Łatwo j e s t stw orzyć m e ^ a ^ e narządzę J e śli zaprojektujesz


swoje dane w ten
do wykonania konkretnego z adama. s posób, łatwiej
będzie stw orzyć
strony aplikacji, lecz
trudniej napisać
kod do pobierania
i' za rządzania tymi
danymi.

i
... je ś li je d n o c z e ś n ie m usisz
Player Roster League
z a p e w n ić m o ż liw o ś ć w ią z a n ia Name: string TeamName: string JimmysTeam: Roster
ty c h d a n ych z k o n tro lk a m i Number: int Starters: BriansTeam: Roster
ObservableCollection
X A M L na s tro n a c h a p lik a c ji. Bench:
ObservableCollection Przydałyby się
tu ta j ja k ie ś
POWIĄZANIE prywatne metody
generujące dane
I t e m s S o u r c e = " {B i n d i n g } testo w e.
/ek t

776 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Wzorzec MVVM pozwala projektować,


uwzględniając zarówno wiązanie, jak_i_ dane
Niemal wszystkie aplikacje korzystające z odpowiednio dużych lub złożonych modeli obiektów stawiają
przed twórcami problem związany z koniecznością rezygnacji z dążenia do utworzenia optymalnego
diagramu klas bądź z optymalnych obiektów używanych do wiązania danych. Na szczęście istnieje
WVVM j e s t wzorcem
pewien wzorzec projektowy, którego można użyć do rozwiązania tego dylematu. Jest on nazywany 4 wykorzystującym
wzorcem model-widok-model widoku (ang. M odel-View-ViewM odel, w skrócie MVVM). Jego narzędzia, którymi ju ż
dysponujesz, takie jak
działanie opiera się na podzieleniu aplikacji na trzy warstwy: m odel przechowujący dane oraz stan funkcje zw rotne oraz
aplikacji, w idok zawierający strony oraz kontrolki, z których korzysta użytkownik, oraz m odel w id o ku , wzorzec obserwatora,
przedstaw ione
odpowiedzialnego za konwertowanie danych z modelu do postaci obiektów, z którymi można powiązać
w poprzednim
kontrolki i które będą nasłuchiwać zdarzeń wywoływanych przez widok, o jakich musi wiedzieć model. rozdziale.

Do widoku tra fia ją w szystk ie obiekty, z którym i


użytkow nik prowadzi bezpośrednią interakcję.
Można zaliczyć do nich strony, przyciski, teksty, siatki,
Model widoku
kontrolki StackPanel, ListView oraz wszelkie inne można porównać
kontrolki XAML, które można wyświetlać na stronach,
pisząc odpowiedni kod XAML. Kontrolki te są powiązane do systemu
z obiektami należącymi do modelu widoku, a procedury
obsługi zdarzeń tych kontrolek wywołują metody kanalizacyjnego
obiektów należących do modelu widoku.
łączącego obiekty
Model widoku dysponuje właściwościami, które można
powiązać z kontrolkami należącymi do widoku.
należące do widoku
Właściwości kontrolek widoku pobierają używane dane z obiektami modelu
z obiektów modelu, konwertują je do postaci przydatnej
dla kontrolek widoku i powiadamiają te kontrolki i wykorzystującego
o wszelkich zmianach zachodzących w tych danych.
przy ty m narzędzia,
którym i już umiesz
się posługiwać.
W szystkie obiekty przechowujące
stan aplikacji są zaliczane do modelu.
To właśnie w modelu aplikacja przechowuje wszystkie
swoje dane. Jego właściwości i metody są wywoływane
przez obiekty należące do modelu widoku. Jeśli istnieją
jakieś obiekty, których stan zmienia się w trakcie działania
aplikacji, bądź jeśli jakieś dane muszą być wczytywane lub
zapisywane w plikach, to trafiają one właśnie do modelu.

jesteś tutaj ► 777


Zastosowanie wzorca

Użyj wzorca MVVM, by rozpocząć


tworzenie aplikacji dla ligi koszykówki
Utwórz nowy projekt aplikacji dla Sklepu Windows i koniecznie nadaj mu nazwę R ozgryw kiL igiK oszykow ki
(w kodzie będziemy używali przestrzeni nazw RozgrywkiLigiKoszykowki, a zatem używając tej samej
nazwy, sprawisz, że kod Twojej aplikacji będzie odpowiadał temu prezentowanemu w książce).
Z r ó b to!

© W PROJEKCIE UTW ÓRZ FOLDERY DLA WIDOKU, MODELU ORAZ MODELU WIDOKU
Kliknij prawym przyciskiem myszy nazwę projektu widoczną w oknie Solution Explorer i z wyświetlonego menu
wybierz opcję A d d /N e w Folder:

Solution Explorer ^ □ X Nie zastępuj je szcze pliku MainPage.xaml


fit "O - « # i i I f [p]
stroną utw orzoną na podstawie szablonu
SearchSolution Explorer(Ctri+;) f i -
¡71 Solution 'RozgrywkiLigiiKoszykowki' (1project) Basic Page. Zro bisz to w kroku 4.
> f t pFC b±j| Build
> ■■ Ref Rebuild
> tf As; Dep|0y Kiedy dodajesz do projektu nowy
> fi Co
RurtCode Analysis folder, używając opcji dostępnych
> D Ap
> P| Ma ScopetoThis w oknie Solution Explorer, IDE tworzy
Ifel Pa< [pi New Solution ExplorerView
przestrzeń nazw odpowiadającą
m R" Add □ New Item... Ctrl+Shrft+A
Add Reference.., Existing Item... Shrft+Alt+A
nazwie nowego folderu. Oznacza
Add ServiceReference.., ii New Folder to, że klasy dodawane do tego
Store V Class... Shift+Ałt+C folderu przy użyciu opcji A dd/C lass
@3 Manage NuGet Packages..,
O SetasStartl/p Project
będą należały do tej przestrzeni
Debug ► nazw. A zatem, jeśli dodasz jakąś
H Add Solutionto Source Control.., klasę do folderu Model, to IDE
¿4 Cut Ctrl+X umieści ją w przestrzeni nazw
pj Paste Ctrl+V
X Remove Del
RozgrywkiLigiKoszykowki.Model.
H -! Rename F2
Unload Project
C* Open FolderinFileExplorer
Solution Explorer
Open inBlend...
f* Properties « & tg • (i 9

Search Solution Explorer (Ctrl-»-;)

5 1 Solution 'RozgrywkiLigiiKoszykowki' (1 project)


a [c«j R o zgryw kiL ig iK oszyko w ki
Dodaj do projektu folder M odel, a następnie, w taki sam > f* Properties
> References
sposób, dodaj kolejne dwa foldery: View oraz ViewModel.
> i | Assets
Struktura folderów projektu powinna wyglądać jak na > fiXcmjrion
rysunku obok. ^ Q Model
i i View
Te foldery bądą I ViewModel^
przechowywały klasy,
kontrolki i strony M ainPage.xaml
Twojej aplikacji. l a Pa ckage.appxmanifest
¿13 RozgrywkiLigiiKoszykowki_TemporaryKey.pfx

Solution Explorer Class View

778 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

ROZPOCZNIJ TW ORZENIE MODELU OD DODANIA KLASY PLAYER.


Kliknij folder M o d el prawym przyciskiem myszy i dodaj do niego nową klasę Player.
Kiedy dodajesz klasę do folderu, IDE modyfikuje przestrzeń nazw, dodając do niej
nazwę folderu. Poniżej przedstawiliśmy kod klasy Player:
Kiedy dodajesz plik klasy
namespace RozgrywkiLigiKoszykowki.Model { ^----- do folderu, IDE dodaje jego
class Player { nazwę do przestrzeni nazw.
public string Name { get; private set; }
public int Number { get; private set; }
public bool Starter { get; private set; }

public Player(string name, int number, bool starter) {


Name = name;
Number = number;
Starter = starter;

Różne klasy zajm ujące


Te klasy są niewielkie, gdyż zajmują się jedynie
} s ię różnymi sprawami? —^ przechowywaniem informacji o graczach
} To brzmi znajomo... biorących udział w rozgrywkach ligi.

5) DOKOŃCZ MODEL, TW O RZĄC KLASĘ R OSTER.


Następnie dodaj do folderu M o d el klasę Roster. Oto jej kod:
Roster
namespace RozgrywkiLigiKoszykowki.Model { TeamName: string
Players: IEnumerable<string>
class Roster {
public string TeamName { get; private set; }

private readonly List<Player> _players = new List<Player>();


public IEnumerable<Player> Players {
get { return new List<Player>(_players); }
}

Znak podkreślenia public Roster(string teamName, IEnumerable<Player> players) {


(_ ) informuje, że
pole j e s t prywatne. TeamName = teamName;
players.AddRange(players);
}

}
Teraz folder M odel powinien mieć następującą zawartość: J Model
> c * Player, cs
Jak widać, nazwa pola _players rozpoczyna się > c* Ro5ter.cs
od znaku podkreślenia. Dodawanie tego znaku do
nazw pól prywatnych jest bardzo często stosowaną
konwencją nazewniczą. Będziemy jej używali w tym Na następnej stronie doda» widok.
rozdziale, więc pewnie się do niej przyzwyczaisz.
jesteś tutaj ► 779
Przejmij kontrolę nad kontrolkami

DODAJ DO FOLDERU VIEW STRONĘ GŁÓW NĄ APLIKACJI.


Kliknij prawym przyciskiem myszy folder View i dodaj do niego nową stronę typu B asic Page,
nadaj jej nazwę LeaguePage.xaml. Zostaniesz zapytany, czy należy dodać brakujące strony,
a po ich dodaniu powinieneś ponownie zbudować całe rozwiązanie, podobnie jak w przypadku
zastępowania strony M ainPage.xaml nową stroną typu B asic Page. Wyświetl kod XAML nowej
strony i zmień jej tytuł na: Liga „Koszykówka. Rusz głową”, podając go (jak zwykle) w zasobie
statycznym AppName. Nie będziemy używali w tej aplikacji pliku M ainPage.xam l, dlatego
w następnym kroku go usuniesz.

USUŃ STRONĘ GŁÓW NĄ I ZASTĄP JĄ SWOJĄ NOWĄ STRONĄ L EAGUEPAGE.XAML.


Usuń z projektu plik M ainPage.xaml. Spróbuj ponownie zbudować rozwiązanie — pojawi się następujący błąd:

Dwukrotnie kliknij błąd, by przejść do problematycznego miejsca w kodzie, gdzie po usunięciu pliku
M ainPage.xaml pojawił się błąd:

if ( !rootFrame.Navigate(typeof (^łainPage) * args.Arguments))

throw new Exception("Failed to create initial page");

Chwileczkę, już wiesz, co ten kod robi! Modyfikowałeś go podczas pisania aplikacji dla Kuby. Szuka on
klasy MainPage, żeby do niej przejść podczas uruchamiania aplikacji. Jednak przed chwilą usunąłeś plik
XAML definiujący tę klasę. Nie ma problemu! Wystarczy podać klasę, którą chcesz uruchomić:

if ( !rootFrame.Navigate(typeof (LeaguePage)., args.Arguments))

throw new Exception("Failed to create initial page");

Hm... to dziwne. Przecież dodałeś do projektu klasę LeaguePage, jednak najwyraźniej IDE jej nie
rozpoznaje. Wszystko przez to, że dodałeś ją do folderu, dlatego IDE umieściło ją w przestrzeni nazw
View. A zatem rozwiązanie problemu sprowadza się do określenia przestrzeni nazw w odwołaniu do klasy:

if (!rootFrame.Navigate(typeof(View.LeaguePage ), args.Arguments))
throw new Exception("Failed to create initial page");

Spróbuj teraz ponownie zbudować aplikację. Kompiluje się bez problemów!


Możesz ją teraz uruchomić, aby zobaczyć jej nową stronę główną.

780 Rozdział 16.


i -
Tworzenie aplikacji według wzorca MVVM

Kontrolki użytkownika pozwalają tworzyć swoje własne kontrolki


Przyjrzyj się tw orzonem u program ow i do zarządzania ligą koszykówki. D la każdego z zespołów będzie używany identyczny
zestaw kontrolek: TextBlock, druga kontrolka TextBlock , ListView , jeszcze jedna kontrolka TextBlock i następna
kontrolka ListView , a wszystkie um ieszczone w ewnątrz kontrolek StackPanel i Border. Czy napraw dę musimy
umieszczać n a stronie dwa identyczne zestawy kontrolek? A co by się stało, gdybyśmy chcieli dodać do aplikacji trzecią
i czwartą drużynę? Oznaczałoby to kolejne pow tórzenia. T o właśnie w takich sytuacjach na arenę w kraczają kontrolki
użytkownika (ang. user Controls). K ontrolka użytkownika jest klasą, której m ożna używać do tw orzenia własnych kontrolek.
D o przygotowania takiej kontrolki używany jest kod X A M L oraz kod ukryty — czyli te sam e elem enty, które są stosowane
podczas tw orzenia norm alnych stron aplikacji. N ie ociągaj się zatem i oddaj do p rojektu swoją własną kontrolkę.

L1 | Dodaj nową kontrolkę użytkownika do folderu View.


UserControl jest
Kliknij praw ym przyciskiem myszy folder View i dodaj do niego nowy elem ent.
W oknie dialogowym wybierz opcję £ — i zapisz go w pliku RosterControlxam l. klasą bazową
¿I P rzyjrzyj się kodowi ukrytem u swojej kontrolki.
pozwalającą na
O tw órz plik RosterControl.xaml.cs. T w oja k o n tro lk a dziedziczy p o klasie bazowej
UserControl. T o w łaśnie w tym plik u definiow any je st cały k o d określający hermetyzację
zachow anie kontrolki.
name sp ac e Rozg ry wk iL ig iK os zy ko wk i.V i e w
kontrolek,
public sealed partial class RosterControl : UserControl
które są ze sobą
powiązane, oraz
napisanie logiki
[3 ) Przeanalizuj kod XAML kontrolki. definiującej ich
ID E dodało now ą k o n tro lk ę użytkow nika zaw ierającą jedynie p u stą k o ntrolkę
Grid. W ew nątrz niej um ieścisz swój k o d X A M L. zachowanie.
Zanim przewrócisz kartkę, przekonajmy się, czy na podstawie analizy
zrzutu ekranu tworzonej aplikacji będziesz w stanie powiedzieć, „NAUCZ CZŁOWIEKA ŁOWIĆ RYBY..."
jaki kod XAML należy umieścić w kontrolce R osterC ontrol .
Zbliżamy się już powoli do końca
★ B ędzie on zaw ierał ele m e n t <StackPanel> grupujący k o n tro lk i w yświetlane książki, dlatego chcemy stawiać przed
w ew nątrz niebieskiego o b ram o w an ia <Border>. Czy potrafisz w skazać Tobą wyzwania podobne do tych,
właściwość, dzięki której k o n tro lk a Border m a zao k rąg lo n e w ierzchołki? które będziesz napotykał w realnym
świecie. Dobry programista podejmuje
★ B ędzie on zaw ierał dwie kontro lk i ListView służące do w yśw ietlania
wiele świadomych przypuszczeń,
danych o graczach, dlatego p o trzeb u je tak że sekcji <UserControl. dlatego podajemy Ci tylko minimum
Resources> zaw ierającej szablon danych — DataTemplate. N azw iem y go niezbędnych informacji na temat
PlayerItemTemplate. działania kontrolki U serC ontrol.
Brakuje w niej nawet definicji wiązania
★ K onieczne będzie tak że pow iązanie elem en tó w kontro lk i ListView danych, więc żadne dane nie będą
z w łaściwościam i o nazw ach S ta rters i Bench, ja k rów nież górnej kontrolki prezentowane w oknie projektanta!
TextBlock z w łaściwością TeamName. Jak dużo kodu XAML będziesz w stanie
samemu napisać, zanim przewrócisz
★ K o n tro lk a Border je st um ieszczona w ew nątrz k o n tro lk i <Grid>, zaw ierającej
kartkę, by zobaczyć pełną wersję
je d e n w iersz o wysokości zdefiniow anej jak o Height="Auto", dzięki czem u kontrolki R o ste rC o n tro l?
jej d olna kraw ędź znajdzie się b ezp o śred n io poniżej dolnej listy, a nie
zostanie ro zszerzona, by zająć całą w ysokość strony. jesteś tutaj ► 781
M o d e l, w id o k , w id o k m o d e lu

Dokończ kod XAML kontrolki RosterControl.


Poniżej przedstawiliśmy kod kontrolki RosterControl, którą dodałeś do folderu View. Czy zauważyłeś, że
dodaliśmy do niej właściwości, które posłużą do wiązania danych, lecz nie podaliśmy żadnego kontekstu danych?
To powinno być zrozumiałe. Dwie kontrolki umieszczone na stronie prezentują różne dane, dlatego dla każdej
z nich aplikacja użyje innego kontekstu.

<UserControl
x:Class="RozgrywkiLigiKoszykowki.View.RosterControl"
xmlns="http://schem as.m icrosoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RozgrywkiLigiKoszykowki.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" W iesz, że kontrolki zm ieniają sw oje wymiary na podstaw ie właściwości Me'?11*
d:De s i gnWidth ="400"> oraz Width. M ożesz zm ieniać te wartości, b y okre ś|ić, jaka ma t>yć w ielkość
J kontrolki w yśw ietlanej w oknie D esigner IDE, podczas je j projektowania.

<UserControl.Resources>
<DataTemplate x:Key="PlayerItemTemplate">
<TextBlock Style="{StaticResource ItemTextStyle}"> Oto szablon elementów wyświetlanych
<Run Text="{Binding Name}"/> w kontrolkach L istV iew . Każdy w iersz
<Run Text=" nr "/> składa s ię z jednej kontrolki TextBlock
<Run Text="{Binding Number}"/> oraz trzech elementów Run wyświetlających
imię zawodnika i jego numer.
</TextBlock>
</DataTemplate>
</UserControl.Resources>
<Grid> A by kontrolka Border miała zaokrąglone
<Grid.RowDefinitions> wierzchołki, należy zastosow ać
właściwość CornerRadius.
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> V
<Border BorderThickness="2" BorderBrush "Blue" CornerRadius="6" Margin="0,0,40,0">
<StackPanel Margin="20">
<TextBlock Text="{Binding TeamName}"
Style="{StaticResource HeaderTextStyle}"/>
<TextBlock Text="Starting Players" Margin="0,20,0,0"
Style="{StaticResource GroupHeaderTextStyle}" />
<ListView ItemsSource="{Binding Starters}" Margin="0,20,0,0M
_ -----------------^ ItemTemplate="{StaticResource PlayerItemTemplate}
Obie kontrolki L istV iew <TextBlock Text="Bench Players" Margin="0,20,0,0"
korzystają z tego samego Style="{StaticResource GroupHeaderTextStyle}" />
szablonu zd efiniowanego <ListView ItemsSource="{Binding Bench}" Margin="0,20,0,0"
jako zasób statyczny.
ItemTemplate="{StaticResource PlayerItemTemplate}
</StackPanel>
</Border>
</Grid>
</UserControl>

782 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Napisz model widoku aplikacji do zarządzania ligą koszykówki. W tym celu przeanalizuj
obiekty modelu i powiązania używane w widoku i na ich podstawie określ, jakiego
systemu „kanalizacyjnego" potrzebuje aplikacja, żeby je ze sobą połączyć.
Ćwiczenie

[ n ZAKTUALIZUJ STRONĘ L EAGUEPAGE.XAML, DODAJĄC DO NIEJ KONTROLKI ROSTER


W pierwszej kolejności dodaj do strony dwie następujące właściwości xmlns,
aby strona rozpoznawała nowe przestrzenie nazw:
xmlns:view="using:RozgrywkiLigiKoszykowki.View"
xmlns:viewmodel="using:RozgrywkiLigiKoszykowki.ViewModel"
Następnie dodaj do strony instancję klasy LeagueViewModel jako zasób statyczny:
<Page.Resources>
<viewmodel:LeagueViewModel x:Name="LeagueViewModel"/>
< x:S trin g x:Key="AppName">Liga "Koszykówka. Rusz gtową"</x:String>
</Page.Resources>
Teraz możesz umieścić na stronie kontrolkę StackPanel zawierającą dwie kontrolki RosterControl:
<StackPanel O rientation= "H orizontal" Margin="120,0,0,0" Grid.Row="1"
DataContext="{StaticResource ResourceKey=LeagueViewModel}" >
<view:RosterControl DataContext="{Binding JimmysTeam}" Margin="0,0,20,0"/>
<view:RosterControl DataContext="{Binding BriansTeam}" Margin="0,0,20,0"/>
</StackPanel>
Upewnij s ię , że w szystkie k/asy i strony
O UTW ÓRZ KLASY MODELU WIDOKU. <- zo stały umieszczone w odpowiednich fo/derach;
w przeciwnym razie przestrzenie nazw nie
W folderze ViewM odel utwórz trzy przedstawione poniżej klasy. będą pasowały do kodu w rozwiązaniu.

PlayerViewModel RosterViewModel LeagueViewModel


Name: string TeamName: string JimmysTeam: RosterViewModel
Number: int Starters: ObservableCollection BriansTeam: RosterViewModel
<PlayerViewModel>
Bench: ObservableCollection private GetBomberPlayers(): IEnumerable<Player>
<PlayerViewModel>
private GetAmazinPlayersQ: Enumerable<Player>
constructor:
RosterViewModel(Model.Roster)
private UpdateRosters()

O ZADBAJ O TO, BY KLASY MODELU WIDOKU DZIAŁAŁY PRAWIDŁOWO


★ Klasa PlayerViewModel jest prostą klasą danych, dysponującą dwiema właściwościami przeznaczonymi
tylko do odczytu.
★ Klasa LeagueViewModel definiuje dwie metody prywatne, służące do generowania danych
Podpowiedz przykładowych wyświetlanych na stronie. W konstruktorze tej klasy tworzone są obiekty Model.Roster
dotyczącą dla każdej z dwóch drużyn uczestniczących w rozgrywkach ligi.
zapytania
LIN Q * Klasa RosterViewModel definiuje konstruktor pobierający obiekt Model.Roster. Określa on właściwość
znajdziesz TeamName, a następnie wywołuje swoją prywatną metodę UpdateRosters(), która używa zapytania
ki/ka stron
wcześniej. LINQ, by pobrać imiona graczy, którzy uczestniczą w grze oraz siedzą na ławce, i aktualizuje właściwości
S tarters oraz Bench. Na początku każdej z tych klas dodaj instrukcję using Model;, żebyś mógł
używać w nich klas należących do przestrzeni nazw Model .

Jeśli w oknie projektanta XAML IDE w yśw ietli komunikat, że w przestrzeni nazw ViewModel nie
istnieje klasa LeagueViewModel, a Ty jesteś na 100% pewny, że dodałeś ją do właściwego folderu,
to spróbuj kliknąć projekt prawym przyciskiem myszy i wybrać z menu opcję Unload Project,
następnie kliknij go ponownie prawym przyciskiem myszy i wybierz opcję Reload Project.
jesteś tutaj ► 783
Rozwiązanie ćwiczenia

A
g e s tia :
Model widoku aplikacji do zarządzania ligą koszykówki składa się z trzech klas: LeagueV iew M odel,
P la yerV iew M o de l oraz R o s te rV ie w M o d e l. Wszystkie trzy znajdują się w folderze ViewModei
Ćwiczenie
Rozwiązanie namespace RozgrywkiLigiKoszykowki.ViewModel { Jeśli pominiesz wiersz u s in g M o d e l; , to
using Model;
using System .Collections.ObjectM odel;
wszędzie w kodzie, zamiast R o ste r , będziesz
musiał używać nazwy M o d e l.R o s te r .
Klasa c la ss LeagueViewModel {
T public RosterViewModel BriansTeam { get; p riv a te s e t; }
Ud0 stępnia 0bie kty public RosterViewModel JimmysTeam { get; p riv a te se t;
RosterViewModel,
których kontrolki public LeagueViewModel () {
RosterControl mogą Roster briansRoster = new Roster("Bombiarze G etBom berPlayers());
używ ać jako swojego BriansTeam = new RosterViewM odel(briansRoster);
kontekstu danych.
Tworzony j e s t także Roster jimmysRoster = new R o ster("Tw ard ziele", G etAm azinPlayers());
obiekt modelu, JimmysTeam = new RosterViewModel(jimmysRoster);
Roster, którego }
może używ ać obiekt
RosterViewModel. p riv a te IEnumerable<Player> GetBomberPlayers() {
List<Player> bomberPlayers = new List< Player> () {
Ta prywatna new Player( Damian", 31, tru e ),
metoda generuje new Player( Ludwik", 23, tru e ),
przykładowe new Player( Krystian 6, tru e ), Dane przykładowe są zazwyczaj generowane
dane o drużynie new Player( Maciek",, 0, tru e ), przez klasy modelu widoku, gdyż stan
„Bombiarze', new Player( Janek" 42, tru e ), aplikacji tworzonych w oparciu o wzorzec
tworząc w tym new Player( Hubert ,3 2 , fa ls e ) , MVVM jest zarządzany przy użyciu klas
celu listę new Player( Ferdek ,8 , f a ls e ) , modelu, które z kolei są hermetyzowane
obiektów Player. }; przez obiekty modelu widoku.
return bomberPlayers;

p riv a te IEnumerable<Player> GetAmazinPlayers() {


Do przechowywania List<Player> amazinPlayers = new List< Player> () {
danych używane new Player( Kuba",42, tru e ),
s ą klasy należące new Player( H eniek",11, tru e ),
do widoku, to new Player( Ro bert",4, tru e ),
właśnie z tego new Player( Lucek", 18, tru e ),
powodu ta metoda new Player( Kim", 16, tru e ),
zwraca obiekty new Player( Barte k", 23, fa ls e ),
Player, a nie new Player( Edek",21, f a ls e ) ,
PlayerViewModel. };
return am azinPlayers;
}
Oto definicja klasy PlayerViewModel. To
} zwyczajny obiekt do przechowywania danych,
ud o stęp niający właściwości, z którymi szablon
namespace RozgrywkiLigiKoszykowki.ViewModel { < damych może powiązać sw oje kontrolki.
c la ss PlayerViewModel {
public s trin g Name { g e t; p riv a te s e t ; }
public in t Number { g e t; p riv a te s e t ; }

public PlayerViewM odel(string name, in t number) {


Name = name;
Number = number;
}

784 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

namespace RozgrywkiLigiKoszykowki.ViewModel { W typowych aplikacjach tworzonych na podstawie


using Model; wzorca MVVM jedynie klasy modelu widoku
using System .Collections.ObjectM odel; im plementują interfejs IN o tify P ro p e rty C h a n g e d ,
using System.ComponentModel; gdyż tylko z nimi są powiązane kontrolki XAML.

c la ss RosterViewModel : INotifyPropertyChanged {
public ObservableCollection<PlayerViewModel> S ta rte rs { get; p riv a te se t; }
public ObservableCollection<PlayerViewModel> Bench { get; p riv a te s e t; )

p riv a te Roster _ ro s te r; To właśnie tutaj aplikacja przechowtuje swój s tan —


w obiektach Roster przechowywanych w modelu widok<j.
Pozostałe klasy jedynie przeksztateają ^dane modelu
i zapisują je we właściwościach, z którymi można
p riv a te s trin g _teamName;
powiązać kontrolki widoktu.
public s trin g TeamName {
get { return _teamName; )
set Za każdym razem, gdy zmienia się wartość
{ właściwości TeamName, klasa RosterViewModel
_teamName = value; wywołuje zdarzenie PropertyChanged, dzięki
OnPropertyChanged("TeamName" czemu każdy powiązany z nim obiekt zostanie
poinformowany o zmianie.
)
)
W typowych aplikacjach MVVM jedynie klasy
public RosterViewModel(Roster ro ste r) { umieszczone w katalogu ViewModel implementują
_ ro s te r = ro s te r; interfejs IN o tifyP ro p e rtyC h a n g e d .
Wynika to z faktu, że jedynie w tym katalogu znajdują
S ta rte rs = new ObservableCollection<PlayerViewModel>();
się obiekty, z którymi są powiązane kontrolki XAML.
Bench = new ObservableCollection<PlayerViewModel>();
Jednak w tym projekcie nie musieliśmy implementować
TeamName = roster.TeamName; interfejsu IN otifyP ropertyC hanged, gdyż powiązane
właściwości są aktualizowane w konstruktorze. Gdybyś
UpdateRosters(); chciał zmodyfikować projekt w taki sposób,
} by Damian i Kuba mogli zmieniać nazwy swoich drużyn,
to musiałbyś wywołać zdarzenie PropertyChanged
p riv a te void UpdateRosters() { w akcesorze set właściwości TeamName.
var s ta rtin g P la y e rs =
from player in _ro s te r.P la y e rs
To zapytanie LINQ odnajduje w ^ t k i c h graczy,
where p la y e r.S ta rte r
s e le ct p laye r; którzy wychodzą na p a rk ie t,! dodaje , '£ hnl£ crion
właśiiwo>ści S ta rte rs typu O ^ e ^ e C ^ e c f i m .
S t a r t e r s .C le a r ();
foreach (P layer player in sta rtin g P la y e rs)
Starters.Add(new PlayerViewModel(player.Name, player.Num ber));

var benchPlayers =
from player in _ro s te r.P la y e rs
where p la y e r.S ta rte r == fa lse To je s t podobne zapytanie
s e le ct p laye r; LINQ, które odnajduje graczy
B e n ch .C le a r(); s iedzących na ławce.
foreach (P layer player in benchPlayers)
Bench.Add(new PlayerViewModel(player.Name, player.Num ber));

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName) {


PropertyChangedEventHandler propertyChanged = PropertyChanged;
i f (propertyChanged != n u ll)
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

jesteś tutaj ► 785


U p s, to n a p r a w d ę d z ia ła !

C Z Y K O N T R O L K I U Ż Y T K O W N IK A N IE SĄ
W RZEC ZYW ISTO ŚC I JEDYN IE SPOSOBEM
R O Z D Z IE L E N IA K O D U X A M L N A K IL K A P L IK Ó W ?

Kontrolki użytkow nika są w pełni funkcjonalnymi


kontrolkam i, które m ożesz tw o rzyć samodzielnie.
I podobnie jak wszystkie inne kontrolki, także kontrolka
użytkownika jest zwyczajnym obiektem — w tym przypadku
dziedziczy on po klasie bazowej UserControl, co udostępnia
dobrze znane właściwości, takie jak Height i V is ib ility , oraz
zdarzenia trasowane, takie jak Tapped czy PointerEntered.
Możesz także dodawać do nich własne właściwości oraz korzystać
z innych kontrolek XAML, by tworzyć bardziej skomplikowane
i wizualnie zachwycające interfejsy użytkownika. Jednak przede
wszystkim, kontrolki użytkownika pozwalają hermetyzować
wszystkie inne elementy w formie jednej kontrolki XAML,
której będziesz mógł wygodnie wielokrotnie używać.

C H W IL A ... TE C A Ł E R O Z W A Ż A N IA ^ -----
O H E R M E T Y Z A C JI I U M IE S Z C Z A N IU O B IE K T Ó W
W R Ó Ż N Y C H W A R S T W A C H B R Z M IĄ BAR D ZO Z N A J O M O . C Z Y NIE
O M A J Ą O N E CZEGOŚ W SPÓ LN EG O Z S EP A R A C JĄ Z A G A D N IEŃ ?

M asz całkow itą rację! Model, w idok o ra z model widoku


to różne zagadnienia, na jakie je st podzielona aplikacja.
Jednym z aspektów projektowania dużych, solidnych aplikacji,
który stanowi największe wyzwanie, jest określenie, co powinny
robić poszczególne obiekty. Aplikacje można projektować na niemal
nieograniczoną liczbę sposobów. To wspaniała możliwość, gdyż
oznacza, że C # udostępnia nam bardzo elastyczne narzędzia.
Lecz jednocześnie stanowi ona wyzwanie, gdyż decyzje podejmowane
dzisiaj mogą w przyszłości znacznie utrudnić zarządzanie
zmianami. Wzorzec MVVM ułatwia rozdzielanie zagadnienia
zarządzania danymi w aplikacji od zagadnienia tworzenia i obsługi
interfejsu użytkownika. Ta separacja może nam znacząco ułatwić
projektowanie aplikacji, gdyż pomaga określić, gdzie powinny się
tnieje nazwa d/a sytuacji", w której znnana znaleźć konkretne dane, a gdzie elementy interfejsu użytkownika,
prowadzana w jednej k/asie wym aga m° dyfikacj i a dodatkowo udostępnia narzędzie, które pozwala je połączyć.
póch innych k/as, co z ko/ei’ z musza do
nodyfikowania je sz c ze innyc/i k/as. Pro g raw sci
S eparacja zagadnień je s t doskonałym sposobem na
;reś/ają j ą jako „reakcję tań c^ to w ą” i" j es t ona
rozwiązywanie prob/emów tego typu, a wzorzec MVVM
irdzo fru stru jąca , zw łaszcza je ś/i s ię sp ieszym y.
je s t użytecznym narzędziem, które może nam pomóc
^ w rozdzie/aniu ważnych obszarów występujących niema/
we w szystkich ap/ikacjach.
786 Rozdział 16.
Tworzenie aplikacji według wzorca MVVM

P : W cią ż w id z ę na sw ojej stronie


odpowiednio aktualizowała kolekcje

P : A c z y je st coś, co mogłoby mnie tró jk ą t ze znakiem w y k rz y k n ik a .


S ta r te r s oraz Bench i wywoływała
zdarzenia C ollectionC hanged, co z kolei
p o w strzym ać przed um ieszczaniem Co on o znacza?
powodowałoby aktualizację kontrolek
kontrolek w modelu w idoku lub kolekcji
O b s e rv a b le C o lle c tio n w modelu? O : Okno do projektowania kodu XAML IDE
ListV iew .
Zdarzenia są świetnym sposobem na
to naprawdę złożone narzędzie. Działa tak
O : Nie, absolutnie nic — może tylko to, dobrze, że czasami zapominamy, jak wiele zapewnienie komunikacji modelu z resztą
aplikacji, gdyż nie musi on wiedzieć, czy
że w takim przypadku nie używałbyś już pracy musi włożyć w wyświetlanie strony
wzorca MVVM. Zadaniem takich klas jak i aktualizowanie jej na bieżąco podczas jakiekolwiek inne klasy nasłuchują jego
kontrolki i strony jest wyświetlanie danych. modyfikowania kodu XAML. Teraz, kiedy zdarzeń, czy nie. Może zajmować się swoim
Jeśli umieścisz je w widoku, łatwiej będzie nasza aplikacja dla ligi koszykówki jest już zadaniem — czyli zarządzaniem stanem
Ci zarządzać kodem swojej aplikacji, gdy gotowa, w oknie Designer prezentowane aplikacji — i pozwolić innym klasom dbać
będzie się ona rozwijać. są przykładowe dane obu drużyn. Ale 0 pobieranie danych i aktualizację interfejsu
chwileczkę — czy te dane nie są tworzone użytkownika, ponieważ jest oddzielony od
P : W ciąż nie rozumiem, czym jest stan. przez prywatne metody klasy należącej klas należących do warstwy modelu widoku
1widoku.
do modelu widoku? Oznacza to, że okno
O : Kiedy mowa o stanie, mamy zazwyczaj Designer musi je wywoływać za każdym
na myśli przechowywane w pamięci razem, gdy musi zaktualizować wygląd
obiekty, które określają, jak będzie działać strony. A zatem, aby metody te można
aplikacja: tekst w edytorze tekstów, było prawidłowo wykonać, muszą być
położenie przeciwników i gracza oraz skompilowane. Jeśli zmodyfikujemy kontrolki
aktualny wynik w grze, wartości komórek na stronie, to jej najnowszy kod C# nie będzie
w arkuszu kalkulacyjnym. To zagadnienie, jeszcze skompilowany, a zatem okno Designer
które naprawdę trudno poukładać sobie poinformuje nas, że prezentowana strona
w głowie, gdyż czasami trudno jest może być nieaktualna. Wystarczy ponownie
stwierdzić: „ten obiekt jest elementem stanu, zbudować projekt, a znaki wykrzyknika
a ten nie". Jednym z celów następnego znikną.
projektu przedstawionego w tym rozdziale
jest ułatwienie Ci zdobycia praktycznej
i realistycznej wiedzy, czym jest stan.
P : Stworzona przed chwilą aplikacja
u żyw ała jedynie danych przykładowych,
tworzonych podczas jej uruchamiania.
P : Dlaczego m uszę u żyw ać instrukcji A co gdybym chciał dodać do niej Jeśli dziś zaufasz
using M odel; w klasach modelu w idoku? możliwość modyfikowania danych
w zorcow i
O : Kiedy utworzyłeś klasy w folderze Model,
w modelu — ja k w tedy by działała?

IDE automatycznie umieśdo je w przestrzeni


nazwRozgryw kiLigiKoszykow ki.M odel.
O : Załóżmy, że chciałbyś zmodyfikować
MVVM ,
ją w taki sposób, żeby Kuba i Damian
Kropka w tej nazwie oznacza, że Model mogli wymieniać się graczami. Już wiesz,
t o w przyszłości
znajduje się poniżej przestrzeni że kontrolki L is tV ie w używane w Twoje życie
R ozgryw kiLigiK oszykow ki. Wszelkie widoku są powiązane z obiektami typu
klasy należące do przestrzeni nazw O b s e rv a b le C o lle c tio n , a zatem obiekty będzie łatwiejsze,
R ozgryw kiLigiK oszykow ki mogą modelu widoku komunikują się z widokiem
odwoływać się do klas w przestrzeni przy użyciu zdarzeń PropertyChanged gdyż będzie Ci
Model, umieszczając przed ich nazwami oraz C ollectionC hanged. W dokładnie ten
Model. lub używając instrukcji using. sam sposób model może się komunikować łatw iej zarządzać
W klasach znajdujących się poza przestrzenią z modelem widoku. Mógłbyś zatem dodać
nazw R ozgryw kiLigiK oszykow ki do klasy R oster zdarzenie RosterUpdated.
kodem aplikacji.
konieczne będzie dodanie instrukcji using Obiekty RosterViewModel nasłuchiwałyby
R o zg ryw kiL ig iK o szyko w ki.M o d e l. tych zdarzeń, a ich procedura obsługi

Wzorzec model-widok-widok modelu jest w rzeczywistości zaadaptowaną wersją innego wzorca


projektowego: model-widok-kontroler (w skrócie: MVC). W oparciu o ten wzorzec została stworzona
aplikacja symulatora ula, którą można znaleźć w przykładach dołączonych do książki, w folderze GDI+.

jesteś tutaj ► 787


M o d e l k o n tr a m o d e l w id o k u

Pogawędki przy kominku


W ramach dzisiejszej pogawędki model oraz model
widoku przeprowadzą żarliwą dyskusję na jeden
z obecnie najważniejszych problemów:
który z nich je st bardziej potrzebny.

Model: Model widoku:


Nawet nie rozumiem, dlaczego mamy prowadzić tę
dyskusję. Czym byś był beze mnie? To ja dysponuję
danymi; to ja implementuję ważną logikę określającą,
jak działa aplikacja. Beze mnie nie miałbyś nic do roboty.
I znowu zaczynasz... uważasz, że jesteś pępkiem
wszechświata.
Cóż, jeśli chodzi o ciebie, to wygląda na to, że mogę nim być.
Ha! A co by się stało, gdybym zdecydował się wycofać
z aktywnej działalności?
Nie śmiałbyś tego zrobić!
A założymy się!? Beze mnie byłbyś bezużyteczny. Widok
nie miałby pojęcia, jak z tobą rozmawiać. Kontrolki byłyby
puste, a użytkownik zagubiony jak dziecko we mgle.
Teraz już rozumiesz, dlaczego komunikuję się z tobą tylko
przy użyciu zdarzeń. Jesteś tak koszmarnie denerwujący.
Wiesz co? Porozmawiajmy o tym trochę. Dlaczego
nie pozwalasz mi nawet spojrzeć na swoją wewnętrzną
konstrukcję? Pokazujesz mi jedynie metody i właściwości,
a wiadomości przesyłasz jedynie w formie argumentów
zdarzeń.
No oczywiście, że tak robię! Kto wie, jakich szkód
mógłbyś narobić, gdyby moje dane nie były odpowiednio
hermetyzowane.
Wygląda na to, że k to ś ma problemy z zaufaniem.
Oczywiście! Jeśli chodzi o zarządzanie danymi, to nie ufam
nikomu za wyjątkiem moich własnych metod prywatnych;
w przeciwnym razie cały stan aplikacji mógłby pójść w diabły.
Ale nie ja jeden postępuję w ten sposób! Dlaczego nigdy
nie pozwalasz mi porozmawiać bezpośrednio z widokiem?
Wygląda na to, że jest równym gościem.
Ty ledwie co jesteś w stanie mówić tym samym językiem
co on! Nigdy nie widziałem, żebyś wywoływał zdarzenia
PropertyChanged — w rzeczywistości nie przypuszczam,
by którykolwiek z twoich obiektów implementował interfejs
INotifyPropertyChanged.
Jak śmiesz! A dlaczego miałbym wywoływać zdarzenia
PropertyChanged? Żaden szanujący się model nigdy nie
zgłasza takich zdarzeń! Sama sugestia, że mogłoby mnie
interesować cokolwiek innego niż dane, jest dla mnie
obraźliwa. Za jaką warstwę aplikacji ty mnie uważasz?

788 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Sędziowie potrzebują stopera


Kuba i Damian musieli odwołać ostatni mecz swoich drużyn, gdyż sędzia
zapomniał o zabraniu stopera. Czy potrafisz zastosować wzorzec MVVM,
by napisać dla nich aplikację działającą jak stoper?

jesteś tutaj ► 789


C o ta k n a p ra w d ę o z n a c z a s ta n ?

Wzorzec MVVM oznacza myślenie o stanie aplikacji


Aplikacje tworzone w oparciu o wzorzec MVVM używają warstw modelu oraz widoku do oddzielenia stanu
aplikacji od jej interfejsu użytkownika. A zatem kiedy zaczynasz tworzyć taką aplikację, pierwszą rzeczą,
którą powinieneś zrobić, jest zastanowienie się, co dokładnie oznacza zarządzanie jej stanem. Kiedy już
wymyślisz, jak należy kontrolować stan, możesz zacząć tworzyć klasy wchodzące w skład modelu, które
będą zawierać właściwości i pola służące do przechowywania stanu — czyli tego, co aplikacja musi śledzić
i pamiętać, by mogła działać. Większość aplikacji musi także dysponować możliwością modyfikowania
stanu, dlatego model udostępnia metody publiczne służące do tego celu. Pozostała część aplikacji
musi mieć dostęp do aktualnego stanu, na co pozwalają publiczne właściwości modelu.

A zatem co oznacza zarządzanie stanem aplikacji stopera?

Stoper w ie, c z y m ie rzy cza s,


c z y je st zatrzym any.
Jeden rzut okna na stoper wystarczy, by
M odel śledzi
stwierdzić, czy jego wskazówki się poruszają, i przechowuje
czy nie. Dlatego model aplikacji stopera
Z m ierzony czas zaw sze je st dostępny.
musi pozwalać na określenie, czy stoper stan aplikacji:
działa, czy jest zatrzymany. Niezależnie od tego, czy są to wskazówki
tradycyjnego stopera, czy cyfry na to , o czym
wyświetlaczu stopera elektronicznego
— zawsze można odczytać aktualnie aplikacja
zmierzony czas.
w danej chwili wie.
Istnieje m ożliwość ustawienia Udostępnia akcje
i zobaczenia czasu okrążenia.
Większość stoperów dysponuje
pozwalające na
funkcją pomiaru czasu okrążenia, modyfikowanie
który można zatrzymać w dowolnym
momencie bez zatrzymywania stanu aplikacji
całego pomiaru. Tradycyjne stopery
używają w tym celu dodatkowego i właściwości,
zestawu wskazówek, natomiast te
elektroniczne dysponują zazwyczaj
dzięki k tó ry m
wyodrębnioną częścią wyświetlacza, pozostałe
na której prezentowany jest czas
okrążeń. fragm enty
aplikacji potrafią
Stoper można za trzy m a ć, kontynuować pomiar i w yzerow ać.
Aplikacja będzie musiała zapewniać możliwość uruchamiania ten stan
stopera, zatrzymywania go oraz wyzerowania; oznacza to, że model
odczytać.
będzie musiał udostępnić reszcie aplikacji jakiś sposób wykonywania
tych czynności.

790 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Zacznij tworzenie modelu aplikacji stopera


Skoro już wiemy, co oznacza zdefiniowanie stanu aplikacji stopera, dysponujemy wszystkimi
informacjami, których potrzebujemy, by zacząć tworzyć jej warstwę modelu. Utwórz nowy projekt
aplikacji typu Windows Store. Nadaj mu nazwę Stoper. Następnie utwórz foldery M odel, View oraz
ViewModel. Do folderu M odel dodaj klasę StopwatchModel:

cla ss StopwatchModel { Z ró b to ! - Ą r

{
priiv a te DateTime? sta rte d ;
Te dwa pola
prywatne pri vate TimeSpan? _previousElapsedTime; Upewnij się, że utw orzyłeś tę klasę w folderze Model.
przechowują Pominęliśmy dodatkowe w iersze określające przestrzeń
stan aplikacji public bool Running { nazw, gdyż w iesz, jak mają wyglądać.
stopera. Oba get { return _started .H asV alue; } Moglibyśmy użyć dodatkowego pola logicznego, by śledzić,
akceptują } czy sto p er działa, czy j e s t zatrzym any, jednak przyjmowałoby
wartość null.
_ ono wartość true wyłącznie w przypadku, gdy pole _ sta rte d
publi ' c Ti meSpan? Elapsed { miałoby ja ką ś wartość. Czy nie lepiej będzie zatem zwracać
get { wartość _started.H asV alue?
i f (_started.H asValue) {
i f (_previousElapsedTime.HasValue)
return CalculateTim eElapsedSinceStarted() + _previousElapsedTime;
else
return C alculateTim eElapsedSinceStarted();
} Ta właściwość, przeznaczona tylko do
else odczytu, wylicza zm ierzony czas, używając
return _previousElapsedTime; do tego dwóch pól prywatnych . Przyjrzyj s ię
je j dokładnie. Czy rozum iesz, ja k ona dz'iała?
}
t
p riv a te TimeSpan CalculateTim eElapsedSinceStarted() { Oto podpowiedz: Kiedy dodajesz lub
return DateTime.Now - _s ta rte d .V a lu e ; odejm ujesz dane typu DateTime lub
} TimeSpan, za w sze u zy sk u je sz wynik
typu TimeSpan.
public void S t a r t () {
_sta rte d = DateTime.Now;
i f (!_previousElapsedTime.HasValue) Inne fragmenty aplikacji m u szą mieć
_previousElapsedTime = new TimeSpan(O); możliwość uruchamiania i zatrzym ywania
s t opera, dlatego model udostępnia
}
m etody s łużące do tego celu.
public void Stop() {
i f (_started.H asValue)
Z
Wyzerowanie _previousElapsedTime += DateTime.Now sta rte d .V a lu e ;
stanu _sta rte d = n u ll;
oznacza }
przypisanie
polom public void Reset() {
wartości null _previousElapsedTime = n u ll; S truktury TimeSpan oraz DateTime.
_sta rte d = n u ll;
} Istnieją dwie sfruk-but-y, k t óre są niezwykle przydatne podczas
operowania na czasie. Struktu rę DateTime, służącą do przechOWy Wania
public StopwatchModel() { daty i godziny, poznałes już wcześniej. Struktura TimeSpan reprezen tuje
R e s e t(); natom iast okres czasu. O k res ten m ierzony jest w ta k zwanych ta k tach
}
(an3; tlck) , p rzy czym jeden ta k t odpowiada jednej dziesięciomilionowej
?
Ta metoda inicjalizuje każdą
części sekundy/ czy |i na jedną milisekundę przypada 10 tysięcy
ta k tó w . Struktura TimeSpan udostępnia zatem m e to d y pozwalające
nową instancję StopwatchModel, konw ertow ać ta k ty na sekundy, milisekundy, dni itd.
tak by sto p er był wyzerowany
i zatrzym any.

jesteś tutaj ► 791


W a r s tw y k o m u n ik u ją s ię z e s o b ą z a p o m o c ą z d a r z e ń

Zdarzenia ostrzegają resztę aplikacji o zmianie stanu


Nasz stoper musi śledzić czas okrążenia, dlatego też jednym z elementów przechowywanego stanu musi
być czas. Oprócz tego model musi udostępniać metodę do odczytu czasu okrążenia. Co jednak zrobić,
gdybyśmy chcieli, by w trakcie pomiaru czasu okrążenia aplikacja wykonała kilka innych czynności?
Na przykład model widoku mógłby chcieć wyświetlić jakieś oznaczenie lub szybką animację. Bardzo
często model informuje pozostałe fragmenty aplikacji o ważnych zmianach stanu, używając
do tego celu zdarzeń. A zatem dodajmy do modelu zdarzenie, które będzie wywoływane po każdej
zmianie zmierzonego czasu okrążenia. Zacznij od dodania do folderu M odel klasy L a p E v e n tA rg s :

c la s s LapEventArgs : EventArgs { To Je s t klasa LapEventArgs. Upewnij


s 'ę, ż e dodałeś ją do folderu Model,
p u b lic TimeSpan? LapTime { g e t; p r iv a t e s e t; } aby została u m ieszczona w odpowiedniej
p fz e s tzz e ni nazw.
p u b lic LapEventArgs(TimeSpan? lapT im e) {
LapTime = lapT im e;

} Kiedy czas okrążenia j e s t akt<jalizowany,


aplikacja m usi wiedzieć, ile cza su uply nęło M odel m oże
} od rozpoczęcia pomiaru. Do te go celu j e s t
używana właściwość typ u Time S p an. w yw oływ ać
Zmodyfikuj swoją klasę S to p w a tch M o d e l , dodając do niej metodę L a p ( ) , zdarzenia, by
która ustawia wartość właściwości LapTime i wywołuje zdarzenie LapTim eU pdated . inform ować resztę
p u b lic v o id R e se t() { aplikacji o ważnych
nu-|-|; Nie zapomnij o wyzerowa-rnu
_p re vio usE la pse dT im e
; w łaściwości LapTime p°dczas zmianach stanu,
_ s ta rte d = n u ll; przywracania początkoweg°
stanu całego sto p e ra. bez używania
LapTime = n u l l ;

}
referencji do
jakichkolwiek
p u b lic TimeSpan? LapTime { g e t; p r iv a t e s e t; } klas spoza
zupełności w ystarczy użyć właściwości modelu. Taki
Metoda p u b lic v o id Lap() { autom atycznej. Prywatne pole wewnętrzne
Lap()
LapTime = Elapsed; nie j e s t potrzebne, gdyż w tym przypadku model jest łatw iej
aktualizuje
właściwość OnLapTimeUpdated(LapTime)
nie trzeba hermetyzować żadnyclt otiltczeń. stw o rzyć, gdyż
i wywołuje
zdarzenie. } jest niezależny
p u b lic eve nt E ventH andler<LapEventArgs> LapTimeUpdated; od pozostałych
p r iv a t e v o id OnLapTimeUpdated(TimeSpan? lapT im e) {
warstw aplikacji
E ventH andler<LapEventArgs> lapTim eUpdated = LapTimeUpdated M VVM.
if (lapTim eU pdated != n u ll) {
la p T im e U p d a te d (th is , new L a p E v e n tA rg s (la p T im e ));

} t
Bardzo miłym efektem ubocznym
} To j e s t zw yczajny kod do oddzielonych od siebie warstw jest
wywoływania zdarzeń. możliwość zbudowania projektu
bezpośrednio po napisaniu klas modelu.

Jeśli IDE stwierdzi, że nie może znaleźć klasy StopwatchViewModel w przestrzeni nazw ViewModel,
792 Rozdział 16. choć Ty jesteś absolutnie pewny, że ją tam umieściłeś, to spróbuj skorzystać z opcji Unload Project iReload Project.
Tworzenie aplikacji według wzorca MVVM

Utwórz widok prostej aplikacji stopera


Poniżej przedstawiliśmy kod XAML prostej kontrolki stopera. Do folderu View dodaj kontrolkę
użytkownika o nazwie B a sicStopw atch.xam l, a następnie umieść w niej poniższy kod. Kontrolka
użytkownika będzie korzystać z kontrolek TextBlock, by wyświetlać zmierzony czas i czas
okrążenia oraz udostępniać przyciski do rozpoczynania pomiaru, zatrzymywania go, zerowania
stopera oraz pomiaru czasu okrążenia.
<UserControl Ta kontro/ka znajduje s ię
x:Class="Stopwatch.View.BasicStopwatch" w fo/derze ^V iew , wewnątrz głównej
xmlns="http://schem as.m icrosoft.com/winfx/2006/xaml/presentation" przestrzeni nazw projektu.
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Stopwatch.View"
xmlns:d="http://schem as.m icrosoft.com /expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006
mc:Ignorable="d"
d:DesignHeight="300"
Będ ziesz potrzebował tej w łaściw ości xm/ns, by dodać przestrzeń
d:DesignWidth="400" k? ----- nazw. C ały projekt nazwa/iśmy Stoper, zatem przestrzeń nazw
xm ln s:view m o de l= Mu s in g :S to p e r.V ie w M o d e lM> mode/u widoku będzie miała nazwę Stoper.ViewM ode/.
<UserControl.Resources>
<viewmodel:StopwatchViewModel x:Name="viewModel"/> Ta kontrolka użytkownika przechowuje
</UserControl.Resources> instancję klasy modelu w idoku w form ie zasobu
statycznego i używa jej jako swojego kontekstu
<Grid DataContext="{StaticResource ResourceKey=viewModel}"> danych. Jak widać, do określenia tego kontekstu
<StackPanel>
nie musi używać swojego kontenera. Sama jest
<TextBlock>
w stanie zadbać o swój kontekst danych.
<Run>Zmierzony czas: </Run>
<Run Text="{ inding Hours}"/>
<Run>:</Run>
Ta kontro/ka TeytB/ock je s t
<Run Text="{ inding Minutes}"/> powiązana z właściwościam i
<Run>:</Run> w obiekcie mode/u widoku,
<Run Text="{ inding Seconds}"/> które zw racają zmierzony czas.
</TextBlock> Aby te wartości
były prawidłowo
<TextBlock> aktua/izowane,
<Run>Czas okrążenia: </Run> obiekty mode/u
<Run Text="{ inding LapHours}"/> widoku m uszą
<Run>:</Run> Ta kontrolka Text8/ock je s t wywoływać zdarzenia
<Run Text="{ inding LapMinutes}"/> powiązana z właściw ościam * PropertyChanged.
w obiekcie mode/u widoku<
<Run>:</Run> które zw ra cają cz a s o lcn^ n m .
<Run Text="{ inding LapSeconds}"/>
</TextBlock>
<StackPanel Orientation="Horizontal
<Button Click= "StartButton_Click">Start</Button>
<Button Click= "StopButton_Click">Stop</Button>
<Button Click= "ResetButton_Click">Zeruj</Button> Aby można było skompilować ten kod,
<Button Click= "LapButton_Click">Okrążenie</Button> będziesz musiał dodać do kontrolki
</StackPanel> procedurę obsługi zdarzeń C lic k ,
</StackPanel> Oto podpowiedz: Sko rzystaj z k/asy
a do przestrzeni nazw ViewModel
</Grid> DispatcherTim er, by cyk/icznie sprawdzać
mode/ i aktua/izować jego w łaściw ości. dodać klasę StopwatchViewModel.
</UserControl>
i
Kod modelu w idoku zo stał przedstaw iony na następnej stronie. Jak dużo tego kodu jesteś w stanie napisać
w yłączn ie na podstawie an alizy widoku o ra z modelu, bez zaglądania na następną stronę? Dodaj do strony
głównej aplikacji kontrolkę BasicStopwatch (no roz/e) i przekonaj się, ja k dużo uda Ci się samemu napisać.

Zachowaj naprawdę dużą ostrożność inie zakładaj, że IDE się myli. Czasami błąd w kodzie X A M L jednej strony
(taki jak nieprawidłowa właściwość xmlns) może spowodować problemy w e wszystkich oknach Designer. jesteś tutaj ► 793
N a p is z m o d e l w id o k u
%
Dodaj model widoku aplikacji stopera
Poniżej przedstawiony został kod klasy stanowiącej model widoku aplikacji stopera.
Upewnij się, że zostanie ona umieszczona w przestrzeni nazw ViewModel.
cla ss StopwatchViewModel : INotifyPropertyChanged {
p riv a te StopwatchModel _stopwatchModel = new StopwatchModel();
W łaściwość
p riv a te DispatcherTimer _tim er = new D ispatcherTim er(); Running odwołuje Te instrukcje using
się do modelu, będą konieczne do
public bool Running { get { return _stopwatchModel.Running; ) ) by sprawdzić czy skompilowania klasy.
stoper je s t aktualnie
public StopwatchViewModel() {
_ tim e r.In te rv a l = TimeSpan.FromMilliseconds(50);
_ tim e r.T ic k += Tim erTick;
_ t im e r .S t a r t ();
S ta rt();
uruchomiony.

using Model;
c
using System.ComponentModel;
_stopwatchModel.LapTimeUpdated += LapTimeUpdatedEventHandler;
using Windows.UI.Xaml;
}

public void S t a r t () {
_stopw atchM odel.Start();
}
Działanie metod Start(), Stop()
°raz Lap() sprowadza się
public void Stop() {
do wywołania analogicznych
_stopwatchModel.Stop();
metod modelu.
}

public void Lap() {


_stopwatchModel Lap ();
}
Metoda Reset() w pierwszej kolejności
public void Reset() {
wywołuje metodę Reset() modelu,
bool running = Running;
a następnie, je śli stoper wcześniej był
_stopwatchM odel.Reset(); \
uruchomiony, wywołuje metodę Start().
i f (running)
_stopw atchM odel.Start();
)

in t _lastH o u rs;
in t _lastM in utes;
decimal _lastSeconds;
void Tim erTick(object sender, object e) { Każde zdarzenie zgłoszone przez
i f (_lastH ours != Hours) { DispatcherTimer powoduje sprawdzenie,
_lastH ours = Hours; czy nie uległy zmianie właściwości
OnPropertyChanged("Hours"); określające liczbę zmierzonych sekund,
) minut i godzin. Je śli któraś z nich
i f (_lastM inutes != Minutes) { zmieniła wartość, to wywoływane je st
_lastM inutes = Minutes; odpowiednie zdarzenie PropertyChanged,
OnPropertyChanged("Minutes") pozwalając na aktualizację widoku.
)
i f (_lastSeconds != Seconds) {
_lastSeconds = Seconds; Składnia ? : pozwala zapisać w jednym wierszu
OnPropertyChanged("Seconds") wyrażenie warunkowe działające tak samo jak
)
) instrukcja i f . Więcej informacji na jego tem at możesz
znaleźć w punkcie 2. dodatku „Pozostałości".
public in t Hours {
get { 7 ^
return _stopwatchModel.Elapsed, HasValue ? _stopwatchModel.Elapsed.Value.Hours : 0;
)
)

794 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

public in t Minutes {
get { return _stopwatchModel.Elapsed.HasValue ? _stopwatchModel.Elapsed.Value.Minutes : 0; }
} N---------------------------------- ^
Właściwość Elapsed.Value zwraca daną typu TimeSpan,
publlgce tde{clmal Seconds { a jej właściwość Minutes zwraca liczbę typu int. P
if (_stopwatchModel.Elapsed.HasValue) {
return (decimal)_stopwatchModel.Elapsed.Value.Seconds
+ (_stopwatchM odel.Elapsed.Value.M illiseconds * .001M);
}
else Właściwość Seconds zwraca liczbę sekund oraz setnych
return 0 . 0M; sekundy w postaci liczby typu decimal. Ustaw tu
} pułapkę i skorzystaj z debuggera, aby przekonać się,
jak ta właściwość działa.
public in t LapHours {
get { return _stopwatchModel.LapTime.HasValue ? _stopwatchModel.LapTime.Value.Hours : 0; }

public in t LapMinutes {
get { return _stopwatchModel.LapTime.HasValue ? _stopwatchModel.LapTime.Value.Minutes : 0; }
}

public decimal LapSeconds {


get {
i f (_stopwatchModel.LapTime.HasValue) { Te właściwości działają tak samo jak
return (decimal)_stopwatchModel.LapTime.Value.Seconds właściwości określające zmierzony czas,
+ (_stopwatchModel.LapTime.Value.M illiseconds * .001M); jedyna różnica polega na tym, że działają
} w oparciu o właściwość LapTime, a nie
else Elapsed.
return 0.0M;
}
}

in t _lastLapH ours;
in t _lastLapM inutes;
decimal _lastLapSeconds;
p riv a te void LapTimeUpdatedEventHandler(object sender, LapEventArgs e) {
i f (_lastLapHours != LapHours) {

}
_lastLapHours = LapHours;
OnPropertyChanged("LapHours"); V To je s t procedura obsługi zdarzeń LapTimeUpdated
i f (_lastLapM inutes != LapMinutes) { wywoływanych przez model. Działa bardzo podobnie
_lastLapM inutes = LapMinutes; do procedury obsługi zdarzeń wywoływanych przez
OnPropertyChanged("LapMinutes"); obiekt DispatcherTimer, czyli sprawdza właściwości
} przechowujące zmierzony czas i wywołuje zdarzenie
i f (_lastLapSeconds != LapSeconds) { PropertyChanged, je śli wartość którejś z nich uległa
_lastLapSeconds = LapSeconds;
OnPropertyChanged("LapSeconds");
}
}

public event PropertyChangedEventHandler PropertyChanged;


protected void OnPropertyChanged(string propertyName) { Oto dobrze znany kod do
PropertyChangedEventHandler propertyChanged = PropertyChanged; wywoływania zdarzenia
i f (propertyChanged != n u ll) PropertyChanged.
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

jesteś tutaj ► 795


T ik , t a k , ti k

Dokończ aplikację stopera


Jest jeszcze kilka dro b n o stek , o k tó re m usisz zadbać, by dokończyć aplikację. T w oja k o n tro lk a użytkow nika
BasicStopwatch nie p o siad a p ro c e d u r obsługi zd arzeń, będziesz w ięc m usiał je dodać. Później p ozostanie
jedynie um ieścić k o ntro lk ę n a stro n ie głównej aplikacji.

W pierw szej kolejności ponownie otwórz plik B asicStopw atch.xam l.cs i dodaj do niego następ u jące
p ro ced u ry obsługi zdarzeń:

private void StartButton_Click(object sender, RoutedEventArgs e) {


viewModel.Start();
} P rzyciski zdefiniowane
private void StopButton_Click(object sender, RoutedEventArgs e) { w w idoku jedynie
viewModel.Stop(); wywołują metody
^ le k tu mode/u widoku
} To zupełnie typowy
private void ResetButton_Click(object sender, RoutedEventArgs e) { wzorzec postępowania
viewModel.Reset(); stosowany podczas
tworzenia widoków.
}
private void LapButton_Click(object sender, RoutedEventArgs e) {
viewModel.Lap();
}

Całe zachowanie
t2 N astęp n ie usuń plik M a in P a g e.x a m l i za stą p go nową stroną typu B a sic P a g e, dokład n ie ta k sam o
zostało zdefiniowane
jak to robiłeś w p o p rzed n ich p ro jek tach (nie zapom nij tak że o ponow nym zbudow aniu aplikacji). w kontro/ce
użytkownika, d/atego
(3 O tw órz nowy plik M ainPage.xaml i do jego znacznika głów nego dodaj poniższą p rzestrzeń nazw: w kodzie ukrytym
strony głównej nie
xmlns:view="using:Stoper.View" znajdziemy nawet
jednego w iersza
związanego
Z m odyfikuj zasób AppName zdefiniow any w plik u M ainPage.xaml i podaj w nim nazwę aplikacji
z obsługą kontro/ki.
<Page.Resources>
<x:String x:Key="AppName">Stoper</x:String>
Solution Explorer ^ □ X
</Page.Resources> « û t0 - if # > [p]
Search Solution Explorer (Ctrl-«-;) p-
(5 Dodaj kontrolkę BasicStopwatch do kodu XAML strony MainPage.xaml: |rj| Solution 'Stoper' (1 project}
[c*l Stoper
<view:BasicStopwatch Grid.Row="1" Margin="120,0"/> >y Properties
> References
> £ Assets
T eraz aplikacja pow inna już działać. Kliknij przyciski: Start, Stop, Zeruj |> ■ Common
a Model
o raz O krążenie, by p rzek o n ać się, czy dobrze działają. |> c# LapEventArgs.es
CK StopwatchModel.es
a it View
a ,Q BasicStopwatch-xaml

Stoper a
> "l I BasicStopwatchjcaml.es
ViewModel
> c# StopwatchViewModel.es
> ,.Q App.xaml
Zmierzonyczas:0:0:14,335 a P| MainPagejcaml
Czasokrążenia:0:0:9.063 P ^ MainPage.xaml.es
He I Package.appxmanifest
J13 Stopwatch_TemporaryKey.pfx

Solution Explorer ClassView


Czy czegoś Ci brakuje? Oto ja k wygląda
nasze rozwiązanie w oknie S ° iu tion E x p/orer.
796 Rozdział 16.
Tworzenie aplikacji według wzorca MVVM

NO DOBRZE, MUSIMY SIĘ NA CHWILKĘ ZATRZYMAĆ


I POROZMAWIAĆ O TYM, W JAKI SPOSÓB ZDECYDOWALIŚCIE, CO
GDZIE UMIEŚCIĆ. DLACZEGO W APLIKACJI DO ZARZĄDZANIA ROZGRYWKAMI
LIGI KOSZYKÓWKI STRONĘ GŁÓWNĄ UMIEŚCILIŚCIE W FOLDERZE WIDOKU,
A W TEJ APLIKACJI NIE? DLACZEGO UŻYLIŚCIE ZEGARA DO POMIARU
CZASU, A ZDARZENIA DO OKREŚLANIA CZASU OKRĄŻENIA? I DLACZEGO
UMIEŚCILIŚCIE ZEGAR W WIDOKU MODELU, A NIE W MODELU?
TE WSZYSTKIE DECYZJE WYGLĄDAJĄ NA BARDZO PRZYPADKOWE.

Stosowanie wzorca MVVM oznacza podejmowanie decyzji.


MVVM jest wzorcem, a to oznacza, że określa on pewne konwencje, a nie ścisłe reguły,
które mógłby sprawdzać kompilator. Jest on wzorcem elastycznym , co z kolei oznacza, że
można go implementować na wiele różnych sposobów. W przykładach zamieszczonych
w tym rozdziale przedstawimy niektóre najczęściej spotykane rozwiązania stosowane
w architekturze MVVM. A kiedy będziemy stosować różne rozwiązania, wyjaśnimy,
dlaczego się na nie zdecydowaliśmy. Naszym celem jest pokazanie Ci, jak bardzo
elastyczny (ewentualnie w jakim stopniu nie je st elastyczny) wzorzec MVVM,
żebyś później, podczas pisania aplikacji, mógł podejmować właściwe decyzje.

OTO KILKA REGUŁ, KTÓRYMI KIERUJEMY SIĘ PODCZAS PISANIA APLIKACJI


KORZYSTAJĄCYCH ZE WZORCA MVVM:
★ Klasy modelu, modelu widoku oraz widoku są umieszczane w odrębnych przestrzeniach nazw.
★ Kontrolki i strony należące do warstwy widoku przechowują referencje do obiektów modelu
widoku, dzięki czemu mogą wywoływać ich metody i używać ich właściwości do określania
jedno- i dwukierunkowych powiązań.
★ Obiekty należące do warstwy modelu widoku nie przechowują żadnych referencji do obiektów
należących do warstwy widoku.
★ Jeśli model widoku dysponuje jakąś informacją, którą chce przekazać do widoku, używa
zdarzeń PropertyChanged oraz CollectionChanged, dzięki czemu powiązanie zostanie
automatycznie uaktualnione.
★ Obiekty należące do warstwy modelu widoku dysponują referencjami do obiektów modelu, dzięki
czemu mogą wywoływać ich metody, jak również odczytywać i ustawiać wartości ich właściwości.
★ Jeśli model ma jakieś informacje, które chce przekazać do modelu widoku, może to zrobić,
wywołując zdarzenia.
★ Obiekty modelu nie dysponują referencjami do obiektów należących do warstwy modelu widoku.
★ Model musi być prawidłowo hermetyzowany, tak by zależał wyłącznie od innych obiektów
modelu. Gdyby cały pozostały kod aplikacji został usunięty, to i tak powinno się dać
skompilować klasy modelu.
★ Obiekty DispatcherTimer oraz cały kod asynchroniczny zazwyczaj są umieszczane w klasach
modelu widoku, a nie modelu. Kod związany z pomiarem upływającego czasu określa, ja k
zmienia się stan aplikacji, lecz w większości przypadków sa m n ie stanow i elementu tego stanu.

jesteś tutaj ► 797


U ż y te c z n e n a r z ę d z i a d la k la s m o d e lu w id o k u

Konwertery automatycznie konwertują wartości na potrzeby powiązań


Każdy posiadacz zegarka elektronicznego wie, że zazwyczaj jeśli liczba minut jest jednocyfrowa, to
zostaje ona poprzedzona cyfrą 0. Także nasz stoper powinien prezentować minuty w formie liczby
dwucyfrowej. Podobnie zresztą sekundy powinny być wyświetlane jako liczba dwucyfrowa i zaokrąglane
z dokładnością do setnych sekundy. M oglibyśmy zmodyfikować model widoku w taki sposób, by
udostępniał on odpowiednio sformatowane łańcuchy znaków, jednak oznaczałoby to, że za każdym
razem, gdybyśmy chcieli zastosować jakiś nowy sposób formatowania, musielibyśmy wprowadzać
dodatkowe właściwości. To właśnie w takich sytuacjach bardzo przydają się konwertery wartości.
Są to obiekty, których powiązania XAML używają do zmodyfikowania danych, zanim zostaną one
wyświetlone w kontrolkach. Konwerter danych można stworzyć, pisząc klasę implementującą interfejs T .|s
IValueConverter (zdefiniowany w przestrzeni nazw Windows.UI.Xaml.Data). A zatem dodaj
konwerter danych do aplikacji stopera. K onw ertery s ą bardzo przydatnym
n arzędziem do tw orzenia
Do folderu ViewModel dodaj klasę TimeNumberFormatConverter. w a rstw y modelu widoku.

Na samej górze klas dodaj instrukcję using Windows.UI.Xaml.Data;, a następnie zmień definicję klasy tak, by
implementowała interfejs IValueConverter. Skorzystaj z możliwości IDE, aby automatycznie zaimplementować
ten interfejs. Spowoduje to dodanie do klasy szkieletu dwóch metod: Convert() oraz ConvertBack().

.2 W klasie konwertera danych zaimplementuj metodę C o n vert() .


Metoda Convert() ma kilka parametrów — my użyjemy dwóch z nich. Parametr value reprezentuje
nieprzetworzoną wartość przekazywaną do powiązania danych, natomiast parametr parameter pozwala określić
parametr używany w kodzie XAML.

using Windows.UI.Xaml.Data;
c l a s s TimeNumberFormatConverter : IVal ueConverter {
p u b l i c object Convert(object value, Type targetType,
object parameter, s t r i n g language) {
Ten konwerter wie,
i f ( v a l u e i s decimal)
jak konwertować r eturn ( ( d e c i m a l ) v a l u e ) . T o S t r i n g ( " 0 0 . 0 0 " ) ;
wartości typów else i f (value i s int) {
decimal oraz int.
W przypadku i f (parameter == n u l l )
zastosowania r eturn ( ( i n t ) v a l u e ) . T o S t r i n g ( " d 1 " ) ;
wartości typu
else
int można podać
opcjonalny r eturn ( ( i n t ) v a l u e ) . T o S t r i n g ( p a r a m e t e r . T o S t r i n g ( ) ) ;
parametr. } M etoda ConvertBack() j e s t używana w przy p adku s tosowanią powiązań
r eturn value; dwukierunkowych. W tym projekcie m e będz'\emy ich używ ać, więc
pozostaw im y szk ie le t m etody w niezm ien ionej p o sta ci.
}

p u b l i c object ConvertBack(object value, Type targetType,


object parameter, s t r i n g language)
throw new NotImplementedException();

Czy pozostawianie w kodzie w yjątku N otIm plem entedE xception jest dobrym pomysłem? W przypadku
tego projektu ten kod nigdy nie powinien zostać wykonany. Gdyby jednak był wykonywany, to czy lepiej
będzie, by przechodził niezauważony i użytkow nik nigdy się o nim nie dowiadyw ał? A może jednak lepiej
zgłaszać ten wyjątek, aby ła tw ie j było wyśledzić ew entualny problem? Które z tych rozwiązań pozwoli
stworzyć solidniejszą aplikację? Nikt nie tw ie rdzi, że istnieje tylko jedna właściwa odpowiedź na to pytanie.
798 Rozdział 16.
Tworzenie aplikacji według wzorca MVVM

Dodaj konwerter do kontrolki stopera jako zasób statyczny.


P ow inien się on znaleźć bezpośrednio poniżej o b ie ktu V iew M odel:

< U se rC o n tro l.R e so u rce s>


<viewm odel:StopwatchViewM odel x:Nam e="view M odel"/>
<view m odel:Tim eN um berForm atConverter x:Nam e="tim eNum berForm atConverter,7 >
< / U s e rC o n tro l.Resourc e s > /k Po dodaniu tego wiersza kodu może s ię okazać,
że będziesz musiał ponownie zbudować projekt.
W sporadycznych przypadkach może się nawet
okuzać konieczne skorzystanie z opcji Unload
Project i Reload Project.
Zaktualizuj kod XAML tak, by korzystał z konwertera.
W każdym ze znaczników <Run> zm od yfikuj w yrażenia { B in d in g } definiujące pow iązania danych, dodając
do nich określenie kon w e rte ra danych: C o n v e rte r= .

< T e xtB lock>


Je ś li parametr nie został określony, nie możesz
<Run>Zm ie rzony c z a s : </Run> zapomnieć o dodatkowym nawiasie zamykającym.
<Run T e x t= " {B in d in g Hours, vj,
C o n v e rte r= {S ta tic R e s o u rc e tim e N u m b e rF o rm a tC o n ve rte r}}"/>
<Run>:</Run>
<Run T e x t= " {B in d in g M in u te s ,
C o n v e rte r= {S ta tic R e s o u rc e tim eN um berF orm atC onverter}, C o n v e rte rP a ra m e te r= d 2 }"/>
<Run>:</Run> /f"
<Run T e x t= " {B in d in g Seconds, UżW ,zap i.sul
przekazać do konwertera parametr.
C o n v e rte r= {S ta tic R e s o u rc e tim e N u m b e rF o rm a tC o n ve rte r}}"/>
< /T e x tB lo c k >
< T e xtB lock>
<Run>Cz as o k rą ż e n ia : </Run>
<Run T e x t= " {B in d in g LapHours,
C o n v e rte r= {S ta tic R e s o u rc e tim e N u m b e rF o rm a tC o n ve rte r}}"/>
<Run>:</Run>
<Run T e x t= " {B in d in g LapM inutes,
C o n v e rte r= {S ta tic R e s o u rc e tim eN um berF orm atC onverter}, C o n v e rte rP a ra m e te r= d 2 }"/>
<Run>:</Run>
<Run T e x t= " {B in d in g LapSeconds,
C o n v e rte r= {S ta tic R e s o u rc e tim e N u m b e rF o rm a tC o n ve rte r}}"/>
< /T e x tB l ock>

Stoper
Z a n im w artości tra fią do k o n tro le k TextB ox,
Zm ierzony czas: 0 : 0 0 :1 1 ,55
zostaną n a jp ie rw przekazane do konw ertera, dzięki Czas okrążenia: 0 : 00 : 08.11

czemu przed w yśw ietleniem zostaną od po w ie dn io S tart Stop Z e ru j O krążenie


sform atowane.

jesteś tutaj ► 799


Konwertowanie różnych typów danych

Konwertery mogą operować na wielu różnych typach danych


K o n tro lk i T e x tB lo c k oraz T e xtB o x operują na tekstach, dlatego w iązanie ich właściwości T e x t z łańcucham i znaków lub
liczbam i m a sens. Istn ie je je d n a k w iele innych właściwości, z k tó ry m i takich w artości nie m ożna powiązać. Jeśli nasz m odel
w id o k u posiada jakieś właściwości logiczne, to będzie je m ożna powiązać z d o w o lnym i w łaściw ościam i przyjm ującym i
w artości t r u e lu b f a ls e . M ożn a nawet wiązać właściwości typów w yliczeniow ych, na p rzykła d właściwość I s V i s i b l e używa
typu w yliczeniow ego V i s i b i l i t y , co oznacza, że także dla niej m ożna napisać odpo w ie dn i konw erter. D o da j zatem do
swojego stopera pow iązanie właściwości logicznej z właściwością V i s i b i l i t y oraz o d po w ie dn i konw erter.

O to d w a konw ertery, które Ci się przydadzą.


Czasami może się zdarzyć, że będziesz chciał pow iązać w łaściw ość logiczną, taką jak Is E n a b le d , z kon tro lką w taki
sposób, by kon tro lka była a ktyw n a , gdy w łaściw ość przyjm ie w artość f a ls e . W tym celu można dodać n o w y kon w e rte r
o nazw ie B o o le a n N o tC o n v e rte r, k tó ry używ a operatora !, by zm ienić w artość w łaściw ości logicznej na przeciwną.

Is E n a b le d = M{ B in d in g R u n n in g , C o n v e rte r= {S ta tic R e s o u rc e n o t C o n v e r t e r } } "

Bardzo często będziesz chciał w yśw ietla ć i ukrywać kontrolki, w zależności od właściw ości logicznych dostępnych
w kontekście danych. W łaściwość V i s i b i l i t y kon tro lki możesz jednak powiązać w yłącznie z właściw ością docelow ą typu
V i s i b i l i t y (co oznacza, że będzie ona zwracać w artości takie jak V is i b il it y . C o l la p s e d ) . Dodamy do projektu stopera
konw erter o nazwie B o o le a n V is ib ility C o n v e r te r , który pozw oli powiązać w łaściwość V i s i b i l i t y kontrolki z logiczną
właściw ością docelową, dzięki czemu kontrolkę będzie można w yśw ietla ć i ukrywać na podstaw ie w artości logicznej.

V is ib ilit y = " { B in d in g R u n n in g , C o n v e r te r= {S ta tic R e s o u r c e v i s i b i l i t y C o n v e r t e r } } "

ZMODYFIKUJ PROCEDURĘ OBSŁUGI ZDARZEŃ ZEGARA W KLASIE MODELU WIDOKU


Z m o d y fik u j pro ced urę obsługi zdarzeń T ic k o b ie ktu D is p a tc h e rT im e r tak, by w yw oływ ała zdarzenia
P ro p e rtyC h a n g e d , kie dy wartość właściwości Runni ng uległa zm ianie:

i n t _ la s tH o u rs ;
i n t _ la s tM in u te s ; Do procedury
decim al _ la s tS e c o n d s ; obsługi zdarzeń
bool _ la s tR u n n in g ; zegara doda/iśmy
sprawdzanie
v o id T im e rT ic k (o b je c t se n d e r, o b je c t e) { właściwości
i f (_ la s tR u n n in g != Running) { Running.
_ la s tR u n n in g = Running; Czy /epszym
O nP ropertyC hangedC 'R unning"); rozwiązaniem
byłoby
} ^ wywoływanie przez
if (_ la s tH o u rs != Hours) { mode/ zdarzenia?
_ la s tH o u rs = Hours;
O nP ro pertyC h an ged ("H o urs");
Theme Light *■
}
if (_ la s tM in u te s != M in u te s) { Jeś/i masz Default
prob/emy
_ la s tM in u te s = M in u te s ; Dark
}z rozpoznaniem
O n P ro p e rtyC h a n g e d ("M in u te s"); kontro/ki Light
} użytkownika
High Contrast (default)
if (_la stS e co n d s != Seconds) { w oknie Designer,
to możesz High Contrast White
_la stS e co n d s = Seconds;
spróbować High Contrast Black
O nPropertyC hanged("S econds"); wybrać w oknie
} Deive inny motyw High Contrast #1
} ko/orystyczny. High Contrast#?

800 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

DODAJ KONWERTER, KTÓRY ZMIENIA WARTOŚCI LOGICZNE NA PRZECIWNE.


O to kon w e rte r w artości, k tó ry zm ienia w artości true na fa lse i na od w ró t. M ożesz go używać,
określając w artości logicznych właściwości ko n tro le k , takich ja k IsEnabled.
using Windows.UI.Xaml.Data;

c la ss BooleanNotConverter : IValueConverter {
p ublic object Convert(object value, Type targetType, object parameter, s trin g language) {
i f ((va lu e is bool) && ((b o o l)value ) == fa lse )
return tru e ;
else
return f a ls e ;
}
p ublic object ConvertBack(object valu e , Type targetType, object parameter, s trin g language) {
throw new NotImplementedException();
}
}

DODAJ KONW ERTER PRZEKSZTAŁCAJĄCY WARTOŚCI LOGICZNE


NA WARTOŚCI TYPU W YLICZENIOW EGO VISIBILITY.
V is ib ilit y
Przekonałeś się ju ż, w ja k i sposób m ożna w yśw ietlać i ukryw ać ko n tro lk ę , przypisując je j właściwości
w artości V isib le Collapsed. W artości te pochodzą z typ u w yliczeniow ego o nazwie V is ib ilit y , należącego
oraz
do przestrzeni nazw Windows.UI.Xaml. Poniżej przedstaw iliśm y kon w e rte r, k tó ry przekształca w artości logiczne na
w artości typu w yliczeniow ego V is ib ilit y :

using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

c la ss B o o lean V isib ilityC o n ve rter : IValueConverter {


p ublic object Convert(object value, Type targetType, object parameter, s trin g language) {
i f ((va lu e is bool) && ((b o o l)value ) == true)
return V is ib ili t y .V is ib l e ;
el se
return V is ib ility .C o lla p s e d ;
}
p ublic object ConvertBack(object valu e , Type targetType, object parameter, s trin g language) {
throw new NotImplementedException();
}
}

ZMODYFIKUJ PROSTĄ KONTROLKĘ STOPERA, BY KORZYSTAŁA Z KONWERTERÓW.


Z m o d y fik u j p lik BasicStopwatch.xaml, dodając do niego instancje tych dwóch kon w e rte rów , zdefiniw ow ane jako
zasoby statyczne:
< view m odel:BooleanVisibilityConverter x:K e y= "v isib ility C o n v e rte r"/>
<viewmodel:BooleanNotConverter x:Key="notConverter"/>
T eraz możesz ju ż powiązać właściwości IsEnabled oraz V is ib ilit y k o n tro lk i z właściwością Running m odelu
w idoku :
<StackPanel Orientation="Horizontal"> D'k' t k s* *
<Button IsEnabled="{Binding Running, Converter={StaticResource notConverter}}" ^ przycis !*r
będzie aktywny wyłącznie
C lick= "StartButton_Click"> Start</Button>
w przypadku, gdy stoper
<Button IsEnabled="{Binding Running}" Click="StopButton_Click">Stop</Button>
zostanie zatrzymany.
<Button Click="ResetButton_Click">Zeruj</Button>
<Button IsEnabled="{Binding Running}" Click="LapButton_Click">Okrążenie</Button>
</StackPanel>
<TextBlock Text="Stoper działa"
Visibility="{Binding Running, Converter={StaticResource visibilityConverter}}"/>
Ta właściwość sprawia, że kcmtmlka bądz ie
widoczna tylko wtedy, gdy stoper będzie działat. jesteś tutaj ► 801
Czas na stylowy wygląd

Styl modyfikuje wygląd danego typu kontrolki


K ie dy tworzysz warstwę w idoku swojej aplikacji, to zazwyczaj piszesz kod X A M L . Używane przy tym
k o n tro lk i X A M L są zwyczajnymi obiektam i, dlatego też bez wątpienia można stworzyć caiy w idok, pisząc
wyłącznie kod C # ; jednak X A M L jest naprawdę zoptymalizowany pod kątem ułatw iania tego zadania.
Przyjrzyjmy się dokładniej, jak to działa, używając znanego już przykładu: dolnego paska aplikacji.

Z acznij od zm od yfikow ania przycisków umieszczonych w p lik u BasicStopwatch.xaml tak, by


w yglądały ja k przyciski paska aplikacji. W tym celu pow inieneś zrob ić do kła dnie to samo,
co robiłeś w poprzednich aplikacjach, czyli dodać właściwość S t y le = " { S ta tic R e s o u r c e
A p p B a rB u tto n S ty le } " i zapisać w zawartości przycisku wartość szesnastkową reprezentującą
w ybrany znak czcionki Segoe U I Symbol:

< B u tto n S ty le = "{S ta tic R e s o u rc e A p p B a rB u tto n S ty le }"


Is E n a b le d = "{B in d in g Running, C o n v e rte r= {S ta tic R e s o u rc e n o tC o n v e rte r}}"
A u to m a tio n P ro p e rtie s .N a m e = "S ta rt"
C lic k = "S ta rtB u tto n _ C lic k "> & # x E 1 0 2 ;< /B u tto n >
< B u tto n S ty le = "{S ta tic R e s o u rc e A p p B a rB u tto n S ty le }"
A u to m a tio n P ro p e rtie s.N a m e = "S to p "
Is E n a b le d = "{B in d in g R u nn ing }" C lic k = "S to p B u tto n _ C lic k "> & # x E 1 0 3 ;< /B u tto n >
< B u tto n S ty le = "{S ta tic R e s o u rc e A p p B a rB u tto n S ty le }"
A u to m a tio n P ro p e rtie s .N a m e = "Z e ru j"
C lic k = "R e s e tB u tto n _ C lic k "> & # x E 1 0 E ;< /B u tto n >
< B u tto n S ty le = "{S ta tic R e s o u rc e A p p B a rB u tto n S ty le }"
Autom at1onPropert1es.N am e=M0krążen1e"
Is E n a b le d = "{B in d in g R u nn ing }" C lic k = "L a p B u tto n _ C lic k "> & # x E 1 6 D ;< /B u tto n >

Teraz przyciski są okrągłe i pre zen tu ją zarówno iko nę , ja k i nazwę;


dokładnie tak samo ja k przyciski na pasku ap lika cji:

Stoper
Zmierzony czas: 0 :0 0 : 1242
Czas okrążenia: 0 :0 0 ; 00.00

© ®Stop
©Zeruj
®
O krążenie

Stoper działa

W rozdziale 11. dowiedziałeś się, że istnieje zasób statyczny o nazwie A p p B a rB u tto n S ty le ; został on zdefiniowany
w p lik u StandardStyles.xaml, któ ry jest dodawany do pro je ktu podczas tworzenia w nim nowej strony typu Basic
Page. A le o co w tym wszystkim chodzi? Jak to zawsze bywa w aplikacjach C # , nie ma w tym nic magicznego
i wszystko ma swoje wyjaśnienie: przyciski umieszczane na pasku aplikacji używają stylów oraz szablonów
k o n tro le k , które pozwalają zdefiniować ich wygląd tylko jeden raz, a stosować w ielokrotnie.

802 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

O tw ó rz w I D E p lik StandardStyles.xaml. Jego znacznik otw ierający in fo rm u je , że p lik zawiera


R e s o u rc e D ic tio n a ry — o b ie k t udostępniający a p lika cji grupę zasobów statycznych.

< R e s o u rc e D ic tio n a ry
x m ln s= "h ttp ://s c h e m a s .m ic ro s o ft.c o m /w in fx /2 0 0 6 /x a m l/p re s e n ta tio n "
x m ln s :x = "h ttp ://s c h e m a s .m ic ro s o ft.c o m /w in fx /2 0 0 6 /x a m l">

W yszukaj łańcuch A p p B a rB u tto n S ty le , by znaleźć zasób statyczny, którego użyłeś w przyciskach. Przekonasz
się, że został on zdefiniow any p rzy użyciu znacznika < S ty le > . S tyl zawiera grupę znaczników, któ re u s ta w ia ją
w a rto ś c i w ła ściw o ści d o w o ln e j k o n tro lk i, w k tó re j z o sta n ie o n zastosow any. W łaściwość T a rg e tT y p e
Te znaczniki stylu określa typ k o n tro lk i, w któ re j może on być stosowany — w tym przypadku jest to B u tto n B a se , czyli
okreś/ają ko/or,
wyrównanie klasa bazowa wszystkich k o n tro le k B u tto n . Styl zaw iera znaczniki < S e tte r> , określające w artości k o n tro le k,
oraz czcionkę, w których został on zastosowany.
która będzie
używana we
< S ty le x:K e y = "A p p B a rB u tto n S ty le " T arge tT yp e= "B utton B ase">
wszystkich
przyciskach, r .< S e tte r P ro p e rty= "F o re g ro u n d "
w których
został V a lu e = "{S ta tic R e s o u rc e A ppB arItem ForegroundThem eBrush}"/>
zastosowany < S e tte r P ro p e rty = " V e rtic a lA lig n m e n t" V a lu e = "S tre tc h "/> Ten zasób statyczny okreś/a ja sny
ten sty/.
< S e tte r P ro p e rty = "F o n tF a m ily " Value="Segoe UI S ym bol"/> bądź ciemny ko/or, za/eżnie od iw ty w u
ko/orystycznego zastosowanego do
Inne kontro/ki mogą < S e tte r P ro p e rty = "F o n tW e ig h t" V a lu e = "N o rm a l"/> wyświet/enia kontro/ki .
używać tej właściwości,
by dowiedzieć się, że < S e tte r P ro p e rty = "F o n tS iz e " V a lu e = "2 0 "/>
dana kontro/ka je s t — < S e tte r P ro p e rty = "A u to m a tio n P ro p e rtie s .Ite m T y p e " Value="App Bar B u tto n "/>
przyciskiem paska
ap/ikacji.

K o le jn y znacznik < S e tte r> określa wartość właściwości T em plate; będzie nią: < C o n tro lT e m p la te > . Z nacznik
ten definiuje szablon k o n tro lk i. K ie d y przycisk jest rysowany na stronie, system W indows szuka szablonu
k o n tro lk i, żeby dowiedzieć się, ja k ją należy przedstawić, i wyświetla wszystkie k o n tro lk i umieszczone w szablonie.

< S e tte r P ro p e rty= "T e m p la te ">


< S e tte r.V a lu e >
< C o n tro lT e m p la te T arge tT yp e= "B utton B ase">

C H W IL E C Z K Ę ,
T O M I W Y G L Ą D A Z N A J O M O . C Z Y S Z A B LO N Ó W
C O N T R O L T E M P L A T E N IE U Ż Y W A L IŚ M Y C Z A S E M W R O Z D Z IA L E 1.?

Owszem! Skorzystałeś z możliwości IDE,


by stworzyć szablon dla kosmitów.
Jeśli zajrzysz ponow nie do tego kodu, przekonasz się, ja k to działa. I D E dodało szablon
k o n tro lk i ja ko zasób statyczny o nazwie EnemyTemplate, a T y mogłeś nadać kontrolce
odpow iedni wygląd, przypisując je j właściwości T e m p la te wartość wskazującą na
szablon. I D E utw o rzyło szablon posiadający właściwość x :K e y , a nie x:Name; a zatem
w swoim kodzie odszukałeś szablon na podstaw ie nazwy w ko le kcji R esources.

jesteś tutaj ► 803


Modne szablony

Szablon rysuje przycisk, używając w tym celu k o n tro lk i StackPanel, zawierającej k o n tro lk ę Grid oraz TextBlock .
K o n tro lk a Grid nie zawiera żadnych wierszy ani ko lu m n — w ykorzystuje zasadę, że k o n tro lk i w kom órce są
rysowane je dn a na drugiej (o czym miałeś okazję się przekonać w p o prze dnim rozdziale p rzy oka zji prezentacji
zdarzeń trasowanych). U żyw a dwóch znaków czcionki Segoe U I Symbol, by narysować okrą gły przycisk: znak
&#xE0A8; przedstaw ia w yp ełn io ny okrąg, a znak &#xE0A7; — pusty okrąg. (Sprawdź to samemu w program ie
Tablica znaków). Ponad znakam i w yśw ietlana jest k o n tro lk a ContentPresenter. W m om encie tw orzenia
o b ie ktu Button k o n tro lk a ta jest zastępowana dow olną treścią umieszczoną pom iędzy znacznikiem otw ierającym
i zamykającym przycisku, lu b podaną w jego właściwości Content — w dosłownym znaczeniu tego słowa,
przedstaw ia ona zawartość.

<ControlTemplate TargetType="ButtonBase">

<Grid x:Name="RootGrid" Width="100" Background="Transparent">

<StackPanel VerticalAlignment="Top" Margin="0,12,0,11">

<Grid Width="40" Height="40" M argin="0,0,0,5" HorizontalAlignment="Center">

<TextBlock x:Name="BackgroundGlyph" Text="&#xE0A8;"


FontFamily="Segoe UI Symbol" FontSize="53.333" M argin="-4,-19,0,0"
Foreground="{StaticResource AppBarItemBackgroundThemeBrush}"/>

<TextBlock x:Name="OutlineGlyph" Text="&#xE0A7;" FontSize="53.333"


FontFamily="Segoe UI Symbol" M argin="-4,-19,0,0"/>

<ContentPresenter x:Name="Content" HorizontalAlignment="Center"


M argin="-1,-1,0,0" VerticalAlignment="Center"/>

/Grid> Na poprzedniej s t ronie urnteścifeś w przycisku znak E103 „pauza", więc to właśnie
on z os! ani e u, mief zcz° ny w k°nfrolce TextBlock. Znaczniki <Setter> przedstawione
w kroku 2. określają, że w k° ntr° lce będzie używana czcionka Segoe U I Symbol.
<TextBlock
x:Name="TextLabel" Text= "{ TemplateBinding AutomationProperties.Name)
Foreground="{StaticResource AppBarItemForegroundThemeBrush)1
M argin="0,0,2,0" FontSize="12" TextAlignment="Center"
Width="88" MaxHeight="32" TextTrimming="WordEllipsis"
Style= "{StaticR eso urce B asicTe xtS tyle)"/>

</StackPanel>

Kod T e m p la te B in d in g pozwala powiązać właściwości używane w ew nątrz szablonu


z właściwościami kontrolki, w której szablon jest używany. A zatem, jeśli powiążesz
Wyszukaj w pliku
StandardStyles.xaml właściwości T ext, W idth, Foreground itd., to będziesz mógł ustawić je w znaczniku
łańcuch znaków < B utto n> i odczytać w ew nątrz szablonu. Obiekt A u to m a tio n P ro p e rtie s udostępnia
AutomationPropertie s, kilka kolejnych nazw, których możesz podobnie używać.
by przeanalizować
dodatkowe przyktedy-
Klasa A u to m a tio n P ro p e rtie s stanowi wygodny sposób przekazywania dodatkowych
wartości do szablonu kontrolki, jednak w rzeczywistości została opracowana jako
mechanizm ułatw iania dostępu. Więcej informacji na jej tem at możesz znaleźć na stronie
http://msdn.microsoft.com/iibrary/windows/apps/hh868160.aspx.

804 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

O statnie dwie k o n tro lk i w yśw ietlają obram ow anie w o k ó ł całej k o n tro lk i. Jedna z nich nosi nazwę
F o c u s V is u a lW h ite i jest prezentow ana ja k o lin ia przerywana. A druga nosi nazwę F o c u s V is u a lB la c k i także
jest prezentow ana ja ko lin ia przerywana, je d n a k m a m niejsze kreseczki. Możesz używać tych pro sto kątów
podczas korzystania ze stopera do w yró żnia nia wybranego przycisku p rzy użyciu klawisza Tab .

<R ectangle
x:N am e="FocusV isualW hite" I s H itT e s tV is ib le = " F a ls e "
S tro k e = "{S ta tic R e s o u rc e F ocusV isualW hiteS trokeT hem eB rush}"
S trokeEndLineC ap="Square" S tro ke D a sh A rra y= "1 ,1 "
O p a c ity = "0 " S tro k e D a s h O ffs e t= "1 .5 "/>
<R ectangle
x:N am e="F ocusV isualB lack I s H itT e s tV is ib le = " F a ls e "
S tro k e = "{S ta tic R e s o u rc e FocusV isualB lackS trokeT hem eB rush}"
S trokeEndLineC ap="Square" S tro ke D a sh A rra y= "1 ,1 "
O p a c ity = "0 " S tro k e D a s h O ffs e t= "0 .5 "/>

Style mogą zmieniać wygląd każdej kontrolki odpowiedniego typu.


Przyjrzyj się, w ja k i sposób styl A p p B a rB u tto n S ty le został zdefiniow any w p lik u StandardStyles.xaml:

< S ty le x:K e y= MA p p B a rB u tto n S ty le " T arge tT yp e= "B utton B ase">

Styl został dodany ja ko zasób statyczny i przypisano m u klucz A p p B a rB u tto n S ty le , aby można go było stosować w przyciskach
(oraz wszelkich innych klasach dziedziczących po B utton B ase ) przy użyciu właściwości S ty le . A co by się stało, gdybyśmy
p o m in ę li określenie klucza? W takim przypadku styl zostanie a u to m a tyczn ie zastosow any we w szystkich kla sa ch
p a sujących do k la s y po da ne j we w łaściw ości T a rg e tT yp e . Zastosujm y zatem ten styl, aby przyjrzeć m u się w działaniu.

O tw ó rz p lik BasicStopwatch.xaml i zm ień sekcję < U s e rC o n tro l.R e s o u rc e s > , dodając styl w fo rm ie zasobu
statycznego i przypisując jego właściwości T a rg e tT y p e w artość T e x tB lo c k . Styl p o w in ie n określać wielkość
oraz grubość czcionki:

< S ty le T a rg e tT yp e = "T e xtB lo ck">


< S e tte r P ro p e rty = "F o n tS iz e " V a lu e = "1 6 "/>
< S e tte r P ro p e rty = "F o n tW e ig h t" V a lu e = "B o ld "/>
< /S ty le >
G dy ty lk o dodasz ten styl, wszystkie k o n tro lk i T e x tB lo c k w ko n tro lce B a s ic S to p w a tc h w ykorzystają
określone w n im właściwości F o n tS iz e oraz F ontW eig ht.

Dodanie sty/u powoduje Skorzysta/fśmy z okna Device,


natychm iastową zmianę by zmienić motyw na Light, aby
wyg/ądu wszystkich kontro/ka była /epiej widoczna
kontro/ek TextB/ock — w oknie Designer.
ich właściwość FontSize
przyjm ie wartość 16,
a FontWeight wartość Bo/d.

jesteś tutaj ► 805


Także kontrolki mają stan

Stan wizualny sprawia, że kontrolki odpowiadają na zmiany


K ie d y umieszczasz w skaźnik myszy nad przyciskiem , przestaje być przezroczysty. K ie d y wybierzesz
przycisk, przechodząc do niego klawiszem Tab , po ja w ia się w o k ó ł niego cienka przeryw ana linia.
D zie je się tak, poniew aż z m ie n iłe ś sta n p rz y c is k u . U m ieszczenie w skaźnika myszy na przycisku
pow oduje, że zm ienia on stan na PointerOver, w ybranie przycisku klaw iszam i także pow oduje
zm ianę jego stanu. Istn ie je w iele różnych stanów, w których m ogą się znajdować k o n tro lk i,
lecz większość z k o n tro le k nie m usi reagować na każdy z tych stanów.

K o n tro lk i oraz szablony k o n tro le k używają ta k zwanych grup stanu w izualnego do zm iany wyglądu
oraz zachowania k o n tro le k w zależności od stanu, w ja k im się one znajdują. Przyciski dysponują grupą
stanów o nazwie CommonStates obejm ującą stany: Normal, PointerOver (gdy w skaźnik myszy znajduje
się nad przyciskiem ), Pressed (kie d y u żytko w n ik naciska przycisk) oraz Disabled (kie dy przycisk
jest wyłączony). Szablon k o n tro lk i w stylu AppBarButtonStyle zaw iera sekcję <VisualStateGroup>
określającą, ja k zm ieniają się właściwości przycisku, gdy znajduje się on w jednym z tych stanów.

<VisualStateG roup x:Name="CommonStatesM>


Kontrolka nie musi robić niczego
< V is u a lS t a t e x:Name="Normal"/> < ------------- specjalnego, gdy znajduje się
w stanie normalnym.
< V is u a lS t a t e x:Name="PointerOver">
<Storyboard>
Animacja właściwości w przypadku, gdy wskaźnik znajdzie się nad przyciskiem.
Inna animacja k o le jn e j w łaściw ości dla tego samego stanu.
</Storyboard>
Każdy stan jest obsługiwany przez znacznik < V is u a lS ta te > ,
< / V isu a lSta te > zawierający obiekty S to ryb o a rd . Obiekty S to ry b o a rd
< V is u a lS t a t e x:Name="Pressed"> zarządzają animacjami i mogą używać linii czasu do
określania, kiedy animacja ma się zaczynać i kończyć.
<Storyboard>
Animacja w łaściw ości w przypadku, gdy p rz y c is k j e s t w ciśn ię ty .
Inna animacja k o le jn e j w łaściw ości dla tego samego stanu.
I je s z c z e inna animacja - może ich być w iele, dla różnych właściwości.
</Storyboard>
Każdy obiekt S to ry b o a rd modyfikuje wartości właściwości,
< / V isu a lSta te > używając przy tym animacji. Kiedy kontrolka przyjmuje
< V is u a lS t a t e x:Name="Disabled"> określony stan, obiekt S to ry b o a rd uruchamia animację,
która zmienia wartości właściwości.
<Storyboard>
Animacja w łaściw ości w przypadku, gdy p rz y c is k j e s t nieaktywny.
Inna animacja k o le jn e j w łaściw ości dla tego samego stanu.
It d .
</Storyboard>
< / V isu a lSta te >
</VisualStateGroup>

806 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Używaj DoubleAnimation,
by animować wartości zmiennoprzecinkowe
K ie d y w kodzie X A M L podajesz w artości właściwości takich ja k W id th lu b H e ig h t, to pow oduje to określenie
w artości właściwości typ u d o u b le . Klasa D o u b le A n im a tio n pozwala na sto p n io w e z m ie n ia n ie w ła ś c iw o ś c i ty p u
d o u b le od je d n e j w a rto ś c i do d ru g ie j, w za d a n ym o kre sie czasu, i jest często stosowana do m odyfikow ania
postaci k o n tro lk i, gdy przechodzi ona do jakiegoś stanu w izualnego. Szablon k o n tro lk i w stylu A p p B a rB u tto n S ty le
używa klasy D o u b le A n im a tio n do w ykonania anim acji wybranego przycisku, polegającej na zm ianie przezroczystości
dwóch pro sto kątów wyświetlanych w o k ó ł k o n tro lk i od w artości 0 (przezroczysta) do 1 (nieprzezroczysta).
O kres, w ja k im będzie w ykonywana ta anim acja, wynosi 0, co oznacza, że zm iana zajdzie natychm iast:

< V is u a lS ta te x:Name="Focused">
< S to rybo ard>
<DoubleAnim ation
Animowana je s t S toryb oard .T arg etN a m e= "F ocusV isua lW h ite"
kontrolka o nazwie
S to ry b o a rd .T a rg e tP ro p e rty = "O p a c ity " Kiedy p rzycisk wychodzi ze stanu Focused,
FocusVisualW hite.
To="1" obiekt Storyboard j e s t przywracany do
D u ra tio n = "0 "/> stanu początkowego, a animacje powrócą
<DoubleAnim ation do sw ych w artości początkowych; w naszym
Anim acja zmienia
S to ryb o a rd .T a rg e tN a m e = "F o cu sV isu a lB la ck" przypadku oznacza to, że w łaściw ość
w artość w łaściw ości
O pacity przyjm ie w artość 0.
O pacity od je j S to ry b o a rd .T a rg e tP ro p e rty = "O p a c ity "
w artości domyślnej To="1"
do w artości 1 . D u ra tio n = "0 "/>
< /S to ry b o a rd >
< /V is u a lS ta te >

Poeksperym entuj nieco z anim acjam i, abyś trochę po zna ł ich działanie. W tym celu skopiujesz cały styl do swojej k o n tro lk i
użytko w nika, zmienisz przyciski tak, by korzystały z nowego stylu, a następnie wprowadzisz k ilk a zm ian w anim acji:

S kop iu j cały styl, zaczynając od < S ty le x :K e y = "A p p B a rB u tto n S ty le " T a rg e tT y p e = "B u tto n B a s e "> , a kończąc
na < /S ty le > . W k le j go do s e k c ji < U s e rC o n tro l.R e s o u rc e s > w p lik u BasicStopwatch.xam l, bezpośrednio poniżej
kon w e rte rów , a następnie zm ie ń w łaściw ość x :K e y z A p p B a rB u tto n S ty le na S to p w a tc h B u tto n S ty le .

Z m o d y fik u j wszystkie cztery przyciski tak, by korzystały z nowego stylu. W tym celu w każdym z nich
Przyciski
zm ień właściwość S t y le na S t y le = " { S t a tic R e s o u r c e S to p w a tc h B u tto n S ty le } " . — jeszcze nie
będą wyglądały
Z m o d y fik u j znaczniki D o u b le A n im a tio n w stanie w izualnym Focused tak, by zm iana nie zachodziła inaczej, gdyż
nowy styl,
natychm iast, lecz by trw ała pięć sekund. Czas trw an ia anim acji zawsze jest zapisywany w postaci: który dodałeś
g o d z in y :m in u ty :se k u n d y , a zatem użyj właściwości D u r a t io n = " 0 : 0 : 5 " (w prow adź tę zmianę jako zasób
statyczny, je s t
w obu anim acjach, by działały tak samo zarówno w jasnym, jak i ciem nym m otyw ie kolorystycznym ).
skopiowanym
stylem, który był
U ru ch o m program , następnie u ż y j k la w is z a Tab , aby zm ieniać aktu aln ie w ybrany przycisk. w nich używany
Teraz przeryw ana lin ia po w in na pojaw iać się i zanikać p o w o li, w czasie p ię ciu sekund. ju ż wcześniej.

Ponow nie zm od yfikuj anim acje; tym razem użyj w nich następujących właściwości: D u r a t io n = " 0 : 0 : 0 . 5 "
A u to R e v e rs e = "tru e " R e p e a tB e h a v io r= "F o re v e r".

3 Ponow nie uru cho m aplikację. Teraz przeryw any pro sto kąt na wybranym przycisku pulsuje — p o ja w ia się płynn ie
w czasie p ó ł sekundy, a następnie p łynn ie zanika w czasie kolejnej p o ło w y sekundy.

jesteś tutaj ► 807


Animuj wszystko

Używaj animacji obiektów do animowania wartości obiektów


C hoć n iek tó re właściwości k o n tro lek używ ają w artości zm iennoprzecinkow ych, to je d n a k w artościam i
innych są obiekty. N a przykład, przypisując właściwości F o re g ro u n d w artość B la c k , w rzeczywistości
zapisujesz w niej o b iek t S o lid C o lo r B r u s h . Przykłady takich w artości m ożesz znaleźć w anim acji
stosow anej w stan ie w izualnym P ressed zdefiniow anym w Tw oim stylu S to p w a tc h B u tto n S ty le —
została w nim zastosow ana anim acja O b je c tA n im a tio n U s in g K e y F ra m e s używ ana do zm iany koloru
tła okręgu ze znakiem w m om encie nacisk an ia przycisku:

< V is u a lS ta te x:N am e ="P oin terO ver">


< S toryboard>
<O bjectAnim ationU singKeyFram es
S toryboard.T argetN am e="B ackgroundG lyph" S to ry b o a rd .T a rg e tP ro p e rty = "F o re g ro u n d ">
<D iscreteO bjectK eyFram e KeyTime="0"
V a lu e = "{S ta tic R e s o u rc e A ppB arItem P ointerO verB ackgroundT hem eB rush}"/>
< /O bjectA nim ationU singK eyFram es>
<O bjectAnim ationU singKeyFram es S toryb oard .T arg etN a m e= "C o nten t"
S to ry b o a rd .T a rg e tP ro p e rty = "F o re g ro u n d ">
<D iscreteO bjectK eyFram e KeyTime="0"
V a lu e = "{S ta tic R e s o u rc e A ppB arItem P ointerO verF oregroundThem eB rush}"/>
< /O bjectA nim ationU singK eyFram es>
< /S to ry b o a rd >
< /V is u a lS ta te >

D ziałanie anim acji ram k i kluczowej (ang. key fra m e anim ations) p o leg a n a tw orzeniu ra m e k klu c z o w y c h . R a m k a kluczow a
jest dyskretnym zdarzeniem , zachodzącym w anim acji w określonym czasie. M ożesz się p rzek o n ać, ja k one działają,
d o d a ją c jeszcze je d n ą , trz e c ią a n im a c ję do w iz u a ln e g o s ta n u P re sse d . U m ieść ją b ezp o śred n io p rzez zamykającym
znacznikiem < /S to ry b o a rd > :

<O bjectAnim ationU singKeyFram es


S toryb oard .T arg etN a m e= "C o nten t" S t o r y b o a r d .T a r g e tP r o p e r ty = " V is ib ility " >
<D iscreteO bjectK eyFram e K eyT im e ="0:0:0" V a lu e = " V is ib le " />
<D iscreteO bjectK eyFram e K e yT im e = "0 :0 :0 .2 " V a lu e = "C o lla p s e d "/>
<D iscreteO bjectK eyFram e K e yT im e = "0 :0 :0 .4 " V a lu e = " V is ib le " />
<D iscreteO bjectK eyFram e K e yT im e = "0 :0 :0 .6 " V a lu e = "C o lla p s e d "/>
<D iscreteO bjectK eyFram e K e yT im e = "0 :0 :0 .8 " V a lu e = " V is ib le " />
< /O bjectA nim ationU singK eyFram es>

Ponow nie uru ch o m aplikację. K iedy tera z naciśniesz przycisk, znak zapisany w jego właściwości C o n te n t zacznie błyskać.
Czy zauw ażyłeś, że anim acja zostanie zatrzym ana w połow ie, jeśli p rzestan iesz naciskać przycisk? D zieje się tak, poniew aż
stan przycisku zm ienił się n a N o rm al , a zatem anim acja pow róciła do p u n k tu początkow ego.

Na stronie http://msdn.microsoft.com/library/windows/apps/hh465374.aspx został


przedstawiony przykład wykorzystania stanów wizualnych w szablonie kontrolki pól wyboru.

808 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Stwórz stoper wskazówkowy, używając tego samego modelu widoku


W zorzec M V V M zapewnia separację w id o ku od m odelu w id o ku oraz m odelu w id o ku ^¡¿fs pp^^howujcaych^danae6!»
od m odelu. Separacja ta jest niezwykle przydatna, gdy chcemy w prow adzić jakieś zm iany aplikacji' urządzającej kolekcją
w jednej z warstw. D z ię k i niej m ożna m ieć pewność, że wprowadzone zmiany nie będą ^'ncistÓprien&tosowałeś j 14”
m iaiy w pływ u na inne warstwy aplikacji i nie w yw ołają w nich żadnych problem ów . Czy ^ ponownie, bez jakichkolwiek
zatem udało nam się dobrze rozdzielić warstwy w id o ku oraz m odelu w id o ku w aplikacji mody fikacji , w kolejnej wersji
aplikacji prezentującej dane
stopera? M ożna się o tym przekonać w jeden sposób: utw órzm y całkowicie nową warstwę na dwuczęściowej stronie?
w id o ku aplikacji, nie wprowadzając jednocześnie żadnych zm ian w klasach m odelu Idea je s t dokładnie ta sama.
w idoku. Jedyną zmianą, jaką w prow adzim y w kodzie C # , będzie d o d a n ie w m od elu
w id o k u nowego ko n w e rte ra , k tó ry będzie przekształcał m in uty i sekundy na stopnie.
Z ró b to !
O d o d a j k o n w e r t e r z m ie n ia j ą c y c z a s n a k ą t y .
D o fo ld e ru ViewModel d o d a j kla sę A n g le C o n v e rte r. Będzie potrzebna do wyśw ietlania
wskazówek stopera.

using Windows.UI.Xaml.Data;
cla ss AngleConverter : IValueConverter {
public object Convert(object valu e , Type targetType, object parameter, s trin g language) {
double parsedValue;
i f ((va lu e != n u ll)
&& d o u b le .T ry P a rse (v a lu e .T o S trin g (), out parsedValue)
&& (parameter != n u ll))
Wartości godzin należą do zakresu od
switch (param eter.To Strin g ()) { 0 do 11, a zatem, aby skonw&rtować
case "Hours": je na kąt, wystarczy je pomnożyć
return parsedValue * 30;
przez 30.
case "Minutes":
case "Seconds": Minuty i sekundy przyjmują wartości
return parsedValue * 6; < z zakresu od 0 do 59, a zatem w ich
} przypadku konwersja polega na pomnożeniu
return 0; ich przez 6.

public object ConvertBack(object valu e, Type targetType, object parameter, s trin g language) {
throw new NotImplementedException();

DODAJ NOW Ą KONTROLKĘ UŻYTKOW NIKA.


U tw ó rz no w ą k o n tro lk ę u ż y tk o w n ik a o na zw ie A n a lo g S to p w a tc h , d o d a j j ą do fo ld e ru View, a następnie dodaj
przestrzeń nazw ViewModel do znacznika < U s e rC o n tro l> . Z m ie ń także dom yślną szerokość i wysokość k o n tro lk i:
d:Des1gnHe1ght="300"
d:Des1gnW 1dth="400"
x m ln s :vie w m o d e l= "u sin g :S to p w a tch .V ie w M o d e l">
Następnie dodaj do zasobów statycznych ko n tro lki obiekt m odelu w idoku, dwa konwertery oraz styl.
<UserControl.Resources>
<viewmodel:StopwatchViewModel x:Name="viewModel"/>
<viewmodel:BooleanNotConverter x:Key="notConverter"/>
<viewmodel:AngleConverter x:Key="angleConverter"/>
< /U s e rC o n tro l.R e s o u rc e s >

jesteś tutaj ► 809


Przekształć swoje kontrolki

DODAJ TARCZĘ I WSKAZÓWKI STOPERA.


D o znacznika < G rid> dodaj tarczę stopera oraz cztery prostokąty, które staną się jego wskazówkami.

<Grid x:Name="baseGrid" DataContext="{StaticResource ResourceKey=viewModel}">


<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
Okreś/enie To je s t tarcza stopera.
</Grid.ColumnDefinitions>
szerokości Ma czarną krawędź i tło
< Ellip se Width="300" Height="300" Stroke="Black" StrokeThickness="2">
ko/umny w postaci szarego gradientu.
zapobiega < E llip s e .F ill>
rozszerzaniu <LinearGradientBrush EndPoint="0.5,1" S tartPo in t= "0.5,0"> ™
się kontro/ki na <LinearGradientBrush.RelativeTransform >
całą dostępną <CompositeTransform CenterY="0.5" CenterX="0.5" Rotation= "45"/>
szerokość </LinearGradientBrush.RelativeTransform >
kontenera. <GradientStop Color="#FFB03F3F"/>
<GradientStop Color="#FFE4CECE" Offset= "1"/>
</LinearGradientBrush>
< / E llip s e .F ill>
</Ellipse>
<Rectangle RenderTransformOrigin="0.5,0.5" Width="2" Height="150" F ill= "B la c k "
<Rectangle.RenderTransform>
<TransformGroup> To je s t wskazówka
<TranslateTransform Y="-60"/> odmierzająca
<RotateTransform Angle="{Binding Seconds, sekundy. Tworzy
Converter={StaticResource ResourceKey=angleConverter}, ją długa, cienka
ConverterParameter=Seconds}"/> kontro/ka Rectang/e,
</TransformGroup> prezentowana przy
</Rectangle.RenderTransform> wykorzystaniu
przekształceń
</Rectangle>
przesunięcia
<Rectangle RenderTransformOrigin="0.5,0.5" Width="4" Height="100" Fill= "B lack">
i obrotu.
<Rectangle.RenderTransform>
<TransformGroup> Każda k°nt ro/ka zawiera je d ną
To je s t <TranslateTransform Y="-50"/> sekcję RenderTransform.
wskazówka <RotateTransform Angle="{Binding Minutes,
pokazująca Converter={StaticResource ResourceKey=angleConverter},
m inuty. ConverterParameter=Minutes}"/>
</TransformGroup>
</Rectangle.RenderTransform>
:/Rectangle>
<Rectangle RenderTransformOrigin="0.5,0.5" Width="1" Height="150" Fill= "Y e llo w
<Rectangle.RenderTransform>
<TransformGroup>
<TranslateTransform Y="-60"/>
<RotateTransform Angle="{Binding LapSeconds,
Converter={StaticResource ResourceKey=angleConverter},
S toper ma ConverterParameter=Seconds}"/>
także dwie
</TransformGroup>
dodatkowe,
</Rectangle.RenderTransform>
żółte
:/Rectangle>
wskazówki
odmierzające <Rectangle RenderTransformOrigin="0.5,0.5" Width="2" Height="100" Fill= "Y e llo w
czas <Rectangle.RenderTransform>
okrążenia. <TransformGroup> Znacznik TransformGroup
<TranslateTransform Y="-50"/> pozwa/a zastosować w jednej
<RotateTransform Angle="{Binding LapMinutes, kontro/ce wie/e przekształceń.
Converter={StaticResource ResourceKey=angleConverter},
ConverterParameter=Minutes}"/>
</TransformGroup>
</Rectangle.RenderTransform> Ten znacznik rysuje na środku stopera dodatkowe koło,
:/Rectangle> przykrywające miejsce, w którym nachodzą na siebie
< Ellip se Width="10" Height="10" F ill= "B la c k"/> wszystkie wskazówki. Ponieważ zo sta ł on podany
na samym końcu, zatem zostanie wyświet/ony „nad"
:/Grid> wszystkim i pozostałymi kontro/kami.

810 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Każda wskazówka zegara jest przekształcana dwa razy.


Początkowo jest ona wyświetlana pośrodku tarczy stopera,
dlatego pierwsze przekształcenie przesuwa ją ku górze,
a drugie — obraca. .

< T ra n s la te T ra n s fo rm Y = "-6 0 "/>

< R o ta te T ra n sfo rm A n g le = "{B in d in g Seconds,


C o n v e rte r= {S ta tic R e s o u rc e R esourceK ey=angleC onverter}
C o nve rte rP aram e te r= S econ ds}"/>

Drugie przekształcenie obraca wskazówkę o odpowiedni


kąt. Właściwość Angle obrotu jest powiązana
z sekundami i minutami obiektu modelu widoku
i korzysta z konwertera, by przekształcić ich wartości
na odpowiednie kąty.

Każda kontrolka może określać


jeden element RenderTransform,
który zmienia sposób jej Twój stoper zacznie odmierzać
prezentacji. Tą zmianą może być czas, zaraz gdy dodasz
obrót, przesunięcie o pewien wskazówkę odmierzającą
odcinek, przekrzywienie, sekundy, gdyż tworzy on
instancję klasy modelu widoku
przeskalowanie w górę
jako zasób statyczny, co
lub w dół i tak dalej.______ pozwala wyświetlać ją w oknie
Designer. Okno to może
Używałeś już przekształceń zatrzymać aktualizowanie
w aplikacji Ratuj iudzi — użyłeś stopera, można go jednak
uruchomić ponownie poprzez
ich do zmiany postaci elips,
w yjście i ponowne wyświetlenie
by w yglądały jak tw arz obcego. okna Designer.

jesteś tutaj ► 811


Dodawanie zasobów

DODAJ PRZYCISKI DO STOPERA.


D o da jm y przyciski do nowego, wskazówkowego stopera. Będą one używały tego samego stylu, k tó ry wcześniej
skopiowałeś do p lik u BasicStopwatch.xaml, je dn ak ponowne kopiow anie go i w klejanie nie m a najmniejszego sensu.
N a szczęście poznałeś ju ż sposób, w ja k i m ożna sobie z tym problem em poradzić: wystarczy skorzystać ze słownika
zasobów, takiego ja k ten, k tó ry zobaczyłeś w p lik u StandardStyles.xaml. A zatem d o d a j do p ro je k tu now y p lik
ty p u Resource Dictionary o nazw ie StopwatchStyles.xaml i um ieść go w fo ld e rze View (opcja Resource Dictionary
jest dostępna w tym samym oknie dialogowym New Item , które udostępnia opcję User Control). Następnie w ytnij
cały elem ent definiujący styl StopwatchButtonStyle z p lik u BasicStopwatch.xaml i w k le j go do nowego p lik u
StopwatchStyles.xaml.

<ResourceDictionary
xmlns="http://schem as.m icrosoft.com /winfx/2006/xam l/presentation"
xm lns:x="http://schem as.m icrosoft.com /winfx/2006/xam l"
xmlns:local="using:Stopwatch.View">

<Style x:Key="StopwatchButtonStyle" TargetType="ButtonBase">


<Setter Property="Foreground"
Value="{StaticResource AppBarItemForegroundThemeBrush}"/>

</Style>
</ResoucreDictionary>

N astępnie zm od yfikuj p lik A pp.xam l i dodaj sło w n ik do zasobów ap lika cji. K ie d y tworzysz nową aplikację dla
Sklepu W indow s, I D E tw orzy p lik App.xam l zawierający jeden znacznik <Application.Resources> i to właśnie
dzięki niem u aplikacja w ie o istn ie n iu p lik u StandardStyles.com . Z m o d y fik u j ten znacznik, dodając do niego
in fo rm a cje o nowym sło w niku zasobów:

<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>

<!--
S ty le s that define common aspects of the platform look and feel
Required by Visual Studio p roject and item templates
-- >
<ResourceDictionary Source="Common/StandardStyles.xaml"/>
<ResourceDictionary Source="View/StopwatchStyles.xaml"/>
</ResourceDictionary.M ergedDictionaries> Kiedy dodasz ten wiersz kodu do pliku A pp.xamh
style z nowego pliku Stopwatch S ty le s xaml
</ResourceDictionary> zostaną dołączone d° zasobów apHkacj i .
</Application.Resources>
Podoba nam
się wygląd T eraz możesz ju ż dodać przyciski. W ystarczy, że skopiujesz całą k o n tro lk ę StackPanel w raz z zawartością
aplikacji i umieścisz ją w nowej kon tro lce .
z przyciskami l^idaj wyrównanie w pionie,
widocznymi <StackPanel O rientation= "H orizontal" VerticalAlignment="Bottom"> ^—' by um ieścić przyciski na dole .
na tle tarczy <Button Style= "{StaticR esource StopwatchButtonStyle)"
stopera. IsEnabled="{Binding Running, Converter={StaticResource notConverter}}"
Możesz je AutomationProperties.Name="Start" Click="StartButton_Click">&#xE102;</Button>
także dodać <Button Style= "{StaticR esource StopwatchButtonStyle)" IsEnabled="{Binding Running)"
w drugim AutomationProperties.Name="Stop" Click="StopButton_Click">&#xE103;</Button>
wierszu <Button Style= "{StaticR esource StopwatchButtonStyle)" Click= "ResetButton_Click"
siatki, by były AutomationProperties.Name="Zeruj">&#xE10E;</Button>
wyświetlone <Button Style= "{StaticR esource StopwatchButtonStyle)" IsEnabled="{Binding Running)"
poniżej tarczy. AutomationProperties.Name="Okrążenie" Click="LapButton_Click">&#xE16D;</Button>
</StackPanel>

812 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

^ ZMODYFIKUJ KOD U K R YTY I ZMIEŃ STRONĘ GŁÓW NĄ APLIKACJI.


Dodałeś przyciski, lecz wciąż pozostaje C i napisanie p ro ce d u r obsługi zdarzeń. K o d , któreg o w tym celu
użyjesz, jest ta k i sam ja k w poprzedniej w e rsji a p lika cji stopera:

p riv a te void S ta rtB u tto n _C lick (o b je ct sender, RoutedEventArgs e) {


vie w M o d el.S tart();
}
p riv a te void StopButton_C lick(object sender, RoutedEventArgs e) {
viewM odel.Stop();
}
p riv a te void ResetButton_C lick(object sender, RoutedEventArgs e) {
viewM odel.Reset();
}
p riv a te void LapButton_Click(object sender, RoutedEventArgs e) {
viewM odel.Lap();
}

Teraz pozostaje ju ż tylko zm odyfikow ać p lik MainPage.xaml i dodać do niego k o n tro lk ę


AnalogStopwatch:
<StackPanel O rien tatio n = "V ertical" Grid.Row="1" Margin="120,0">
<view:BasicStopwatch M argin="0,0,0,40" />
<view:AnalogStopwatch/>
</StackPanel>

U ru ch o m aplikację. T eraz na stronie po ja w ią się dwie k o n tro lk i stopera.

Stoper
Zm ierzon y czas: 0 : 2 1 : 12.95
Czas okrążenia: 0 : 2 0 : 51.06

Każdy stoper mierzy


© ®
Stop
©
Zeruj
®
Okrążenie
własny czas, gdyż każdy
S t o p e r d z i a ła
posiada własną instancję
klasy modelu widoku,
zdefiniowaną j ako zas ób
statyczny.

Spróbuj zm odyfikow ać klasę


m odelu w id o k u w ta k i sposób,
by pole _ sto p w a tch M o d e l było
statyczne. Jakie zm iany w y w o ła
to w d zia ła n iu aplikacji stopera?
Czy po tra fisz w yjaśnić, dlaczego
ta k się dzieje?

jesteś tutaj ► 813


W końcu to tylko kod..

Kontrolki interfejsu użytkownika można także tworzyć w kodzie C#


Już wiesz, że ko d X A M L tw orzy instancje klas należących do przestrzeni nazw W in d o w s.U I, a w rozdziale 10.
użyłeś nawet okna Watch, aby do kła dniej przyjrzeć się tym o b ie kto m . A co gdybyś chciał tw orzyć k o n tro lk i
bezpośrednio w kodzie aplikacji? Cóż, k o n tro lk i są zw ykłym i ob ie kta m i, zatem nic nie stoi na przeszkodzie, by
je tw orzyć i pracować z n im i ta k ja k ze w szystkim i in n ym i o b ie kta m i. Z m o d y fik u j k o d u k ry ty s tro n y i d o d a j
p o d z ia łk ę na ta rc z y stop era.
p rzesfrzeń nazw W indows.UI je s t potrzebna
u s in g W indows.UI; ze wzg/ędu na w ytorzystanie k/asy Co/ors, przestrzeń
u s in g W indow s.U I.Xam l.Shapes; W m ^w s.U LZarn/.S hapes ze wzg/ędu na k/asę
Rectang/e ^ a z przekształcenia, a przestrzeń Windows.
u s in g W indow s.U I.X am l.M edia;
U J.AarnJM e^a ze wzg/ędu na k/asę So/idCo/or8rush.

p u b lic sea le d p a r t i a l c la s s AnalogStopwatch : U se rC on tro l {


p u b lic A na lo gS top w atch() {
t h is . I n it ia liz e C o m p o n e n t ( ) ; Do tomstraktora d° daj
A ddM ark1ngs(); <----------------wywołanie metody Te instrukcje używają operatora
w yśw ietlającej podziałkę
} na tarczy stopera. reszty z dzielenia (%— modulo),
by podziałki godzin były grubsze
p r iv a t e v o id AddMark1ngs() { niż podziałki minut. Wyrażenie
f o r ( i n t i = 0; i < 360; i += 3) { i %30 zwraca 0, wyłącznie jeśli
R e cta ng le re c ta n g le = new R e c ta n g le (); i jest podzielne przez 30.
Ten kod tworzy
re c ta n g le .W id th = ( i % 30 == 0) ? 3 : 1;
instancje k/asy
Rectang/e, re c ta n g le .H e ig h t = 15;
które można r e c t a n g l e . F i ll = new S o lid C o lo rB ru s h (C o lo rs .B la c k );
także tworzyć, re c ta n g le .R e n d e rT ra n s fo rm O rig in = new P o in t( 0 .5 , 0 .5 );
stosując
znacznik
<Rectang/e>. Transform G roup tra n s fo rm s = new T ra n sfo rm G ro u p ();
tra n s fo rm s .C h ild re n .A d d (n e w T ra n s la te T ra n s fo rm () { Y = -140 } ) ;
tra n s fo rm s .C h ild re n .A d d (n e w R o ta te T ra n s fo rm () { Angle = i } ) ;
re c ta n g le .R e n d e rT ra n s fo rm = tra n s fo rm s ;
b a s e G rid .C h ild re n .A d d (re c ta n g le ); Z a jrzyj ponownie do kodu XAM L
tworzącego wskazówki pokazujące /iczbę
\
} m in ut i godzin. Ten kod tworzy dokładnie
te same przekształcenia, choć z ami’ as t
// p ro ce d u ry o b s łu g i p rz y c is k ó w p o z o s ta ją n ie z m ie n io n e
powiązania z właściw ością Angte,
oo orostu okreś/a użuwane warto ś c i.

Kontrolki takie jak G rid , StackPanel oraz Canvas


dysponują kolekcją C h ild re n , przechowującą
referencje do innych kontrolek, które są w nich
umieszczone. Kontrolki można dodawać do siatki przy W rozdziale 11. użyłeś obiektu B in d in g ,
użyciu metody A dd (), a usuwać z niej przy użyciu by zdefiniować powiązanie bezpośrednio
metody C le a r (). W dokładnie taki sam sposób w kodzie C#. Czy potrafisz wymyślić,
można dodawać obiekty Transform Group. jak usunąć kod XAML tworzący kontrolki
R e c ta n g le reprezentujące wskazówki —
godzinową i minutową — i zastąpić go kodem
C#, który utworzy identyczne kontrolki?

814 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Która z drużyn w ygra


i zdobędzie zaszczytne
trofeum Obiektowa? Tego
nikt nie w ie. Jednego
możemy być pewni:
Damian, Kuba i Edek
harfa ni a walczuh ze

jesteś tutaj ► 815


Żywe i animowane

C# pozwala także na tworzenie „prawdziwych" animacji


W świecie C # i X A M L term in anim acja m oże odnosić się do
dow olnej właściwości, której w artość zm ienia się w zadanym czasie.
Z ró b to ! ^
Jed n ak w rzeczywistości oznacza o n a rysunki, k tó re się p o ruszają
lub zm ieniają. A zatem napiszm y pro sty p ro g ram używający
„praw dziw ej” anim acji. Każda z tych rysunkowych pszczół
je s t jedną ramką anim acji, która
w nieznaczny sposób różni s ię od
ramki poprzedniej i następnej. J e ś li
będziemy je szybko w yśw ietlali
w kolejności, a później w odwrotnym
porządku (czyli ramki 1., 2 ., 3. i 4.,
a następnie 3., 2 . i 1.), to uzyskamy
animację pszczoły m achającej
skrzydełkam i.

Utwórz projekt i dodaj do niego rysunki.


Zacznijm y p race n ad p ro jek tem . U tw ó rz now y p ro je k t a p lik a c ji
ty p u Windows Store i n a d a j m u nazwę Anim owanaPszczola.
O dszukaj cztery rysunki pszczół (są to pliki .png) w przykładach
Pobierz p rzykłady do książki
dołączonych do książki, k tó re m o żn a p o b rać z serw era FT P z serwera FTP wydawnictwa
w ydawnictw a H elion. N astęp n ie dodaj każdy z nich do fo ld eru
A ssets. B ędziesz także m usiał utw orzyć foldery View , M odel Helion: ftp ://ftp .h e lio n .p l/
oraz V iew M odel.
Na następnej s t r o n i, Twoj e
przyklady/cshru3.zip.
pszczoły będą ju ż radośnie
machać skrzydełkam i-

N AJPR AW D O PO D O BN IEJ W ŁA Ś N IE
T O PR Z Y C H O D ZI M I DO G ŁO W Y,
KIEDY M YŚLĘ O ANIMACJI...

Myśląc o animacjach, musisz mieć


o tw arty umysł.
Przyjrzyj się uw ażnie tem u , co się dzieje, gdy
o tw ierasz stro n ę starto w ą system u W indow s, stro n ę
z inform acjam i o aplikacji, kiedy przesuw asz w skaźnik
myszy n a d przycisk lub w ykonujesz p rzeró żn e in n e
czynności w system ie W indow s. A n im acje są w szędzie,
a kiedy zaczniesz ich szukać, będziesz m ógł je zobaczyć
b ard zo często.
816 Rozdział 16.
Tworzenie aplikacji według wzorca MVVM

Użyj kontrolki użytkownika, by wyświetlać rysunki tworzące animację


Spróbujm y zadbać o dobrą hermetyzację całego kod u obsługującego animację poklatkow ą. D o d a j do p ro je k tu k o n tro lk ę
u ż y tk o w n ik a o nazw ie AnimatedImage i zapisz ją w fo ld e rze View. Jej kod X A M L jest bardzo prosty, a cała inteligencja
zostanie zaim plem entowana w fo rm ie kodu C # . O to cały kod X A M L umieszczony wewnątrz znacznika < U s e rC o n tro l> :
< G rid>
<Image x:Name="1mage" S t r e t c h = " F 1 llM/>
< /G rid >
Cała anim acja jest obsługiw ana przez ko d u k ry ty k o n tro lk i. Z w ró ć uwagę na przeciążony k o n stru kto r, wyw ołujący
m etodę S t a r t A n im a t io n ( ) , k tó ry tw o rz y o b ie k t S to ry b o a rd o ra z o b ie k ty ra m e k k lu czo w ych a n im a c ji, m odyfikujące
w artość właściwości S ource k o n tro lk i Image.

u s in g W in d o w s.U I.X a m l.M e d ia .A n im a tio n ;


u s in g W in d o w s.U I.X a m l.M e d ia .Im a g in g ; Klasa Bitm apIm age należy do
przestrzeni nazw M edia.rmaging<'
pu b iic sea ie d pa r t i a i c ia s s Anim atedim age : U sercontro1 { o * * ? naieżąadO opr a eSn n e n lasy
p u b lic Anim atedIm age() { nazw Media.Anim ation.
t h is .In itia 1 iz e C o m p o n e n t( ) ;
}

p u b lic An1matedImage(IE n u m e ra b le < s trin g > imageNames, TimeSpan in t e r v a l)


: t h is ( )
Jeśli chcemy tw orzyć instancję kontrolki w kodzie XAML, to musi
{
ona definiować konstruktor bezargumentowy. Wciąż można do niej
S tartA nim ation (im ag eN am es, i n t e r v a l ) ;
dodać przeciążone konstruktory, jednak przydadzą się one jedynie
} podczas tw orzenia kontrolki w kodzie C#.

p u b lic v o id S ta r tA n im a tio n (IE n u m e ra b le < s trin g > imageNames, TimeSpan in t e r v a l) {


S to ry b o a rd s to ry b o a rd = new S to ry b o a rd ();
O bjectAnim ationU singKeyFram es a n im a tio n = new O bje ctA nim a tion U sin gK e yF ra m e s();
S to ry b o a rd .S e tT a rg e t(a n im a tio n , im age);
S to ry b o a rd .S e tT a rg e tP ro p e rty (a n im a tio n , "S o u rc e ");
Statyczne metody
S e tT a rg e t() oraz
TimeSpan c u r r e n t ln te r v a l = T im e S p a n .F ro m M illise co n d s(O ); S e tT a rg e tP ro p e rty ()
fo re a ch ( s t r in g imageName in imageNames) { klasy S to ry b o a rd określają
ObjectKeyFrame keyFrame = new D iscre te O b je c tK e y F ra m e (); docelowy animowany
keyFram e.Value = CreatelmageFromAssets(im ageName); obiekt ("im a g e ") oraz
keyFrame.KeyTime = c u r r e n t ln t e r v a l; właściwość, która będzie
an im ation.K eyF ram es.A dd(keyF ram e); animowana ("S o urce").
c u r r e n t ln te r v a l = c u r r e n t ln t e r v a l. A d d ( in t e r v a l) ;
}

s to ry b o a rd .R e p e a tB e h a v io r = R e p e a tB e h a vio r.F o re ve r;
sto ry b o a rd .A u to R e v e rs e = t r u e ;
s to ry b o a rd .C h ild re n .A d d (a n im a tio n ); Kiedy obiekt S to ry b o a rd został już skonfigurowany,
s to ry b o a rd .B e g in ( ); a animacje dodane do jego kolekcji C h ild re n , możesz
} rozpocząć animację, w yw ołując jego metodę B e g in ().

p r iv a t e s t a t i c BitmapImage C reateIm ageFrom Assets( s t r in g im ageFilenam e) {


r e tu r n new BitmapImage(new U r i( " m s - a p p x : / // A s s e t s / " + im a ge F ilen am e));
}
} jesteś tutaj ► 817
Pszczoły będą latać

Niech Twoje pszczoły latają po stronie


Weź swoją nową k o n tro lk ę A nim ated lm ag e na lo t próbny.
T l Z r ó b to! -Ą r

[m ZASTĄP STRONĘ MAINPAGE.XAML STRONĄ TYPU BASIC PAGE


UMIESZCZONĄ W FOLDERZE VIEW.
U tw ó rz no w ą stro n ę na p o d sta w ie sza b lo n u B a sic Page, nadaj je j nazwę FlyingBees.xaml. Usuń
z p ro je k tu stronę MainPage.xaml. N astępnie zm od yfikuj p lik App.xam l.cs tak, by po uru ch o m ie n iu
a p lika cji była wyśw ietlana nowa strona:

if (!ro o tF ra m e .N a v ig a te (ty p e o f(V ie w .F ly in g B e e s ), a rg s.A rg u m e n ts))

PSZCZOŁY BĘDĄ LATAĆ NA KONTROLCE CANVAS.


W pierwszej kolejności zm ień wartość zasobu AppName na L a ta ją c e p s z c z o ły . N astępnie dodaj
do znacznika <com mon:LayoutAwarePage> strony właściwość xm ln s, by m ia ła ona dostęp do
przestrzeni nazw View:
Je śli bądą ja k ie ś problemy z kompilacją, to
xm ln s:vie w = "u sin g :A n im o w a n a P szczo la .V ie w " <-------- - P ^ y f j l n T L Unl° ad. . Project oraz Load
r roject. Je Z li Twój projekt ma inną nazwę,
to podaj ją zamiast AnimowanaPszczola.

N astępnie d o d a j do p lik u FlyingBees.xam l k o n tro lk ę Canvas. K o n tro lk a Canvas jest kontenerem ,


zatem może zawierać in ne k o n tro lk i, takie ja k G r id lu b S ta c k P a n e l. R óżnica pom iędzy n im i polega
na tym , że k o n tro lk a Canvas pozwala określać w spółrzędne innych, w yśw ietlonych w niej k o n tro le k,
używając do tego celu właściwości C a n v a s .L e ft oraz C anvas.Top. Zastosowałeś ju ż tę ko n tro lkę
w rozdziale 1., by stworzyć pole gry Ratuj ludzi. O to ko d X A M L , k tó ry pow inieneś dodać do p lik u
FlyingBees.xaml:

<Canvas G rid.R ow = "1" Background="SkyBlue" W idth="600"


H o riz o n ta lA lig n m e n t= " L e ft" M a rg in = "1 2 0 ,0 ,1 2 0 ,1 2 0 ">
<view :Anim atedIm age C a n v a s .L e ft= "5 5 " Canvas.Top="40"
x :N a m e = "firs tB e e " W idth= "5 0" H e ig h t= "5 0 "/>
<view :Anim atedIm age C a n v a s .L e ft= "8 0 " Canvas.Top="260"
x:Name="secondBee" W idth= "200" H e ig h t= "2 0 0 "/>
<view :Anim atedIm age C a n v a s.L e ft= "2 3 0 " Canvas.Top="100"
x:N a m e = "th ird B e e " W idth= "300" H e ig h t= "1 2 5 "/>
</Canvas>_____________________________________________________ ___________________________________
Latające pszczoły

U l 1a
Kontrolka Animatedlmage jest niewidoczna aż do momentu,
gdy zostanie w yw ołana jej metoda S ta r tA n im a tio n ( ) ,
dlatego też kontrolki w yśw ietlone w ew nątrz kontrolki
Canvas będą pokazane w form ie zarysu. Można je wybrać,
korzystając z okna Document Outline. Spróbuj poprzeciągać
je w różne miejsca kontrolki Canvas, sprawdzając przy tym
__ 1
wartości właściwości C a n v a s .L e ft oraz Canvas.Top.

818 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

0 DODAJ KOD U K R YTY STRONY.


Będziesz potrze bo w ał poniższej in s tru k c ji u s in g , określającej przestrzeń nazw, w któ re j zostały zdefiniow ane
klasy S to ry b o a rd oraz D o u b le A n im a tio n :

u s in g W in d o w s.U I.X a m l.M e d ia .A n im a tio n ;

Teraz możesz zm odyfikow ać ko n stru kto r umieszczony w p lik u FlyingBees.xaml.cs, aby u rucham iał animację
pszczoły. D o datkow o utw ó rz animację typu D o u b le A n im a tio n , k tó ra będzie m odyfikow ać w artości właściwości
C a n v a s .L e ft. Porównaj kod odpow iedzialny za utw orzenie obiektów S to ry b o a rd i o b ie ktu anim acji, z kodem
X A M L używającym znacznika < D o u b le A n im a tio n > przedstawionym we wcześniejszej części rozdziału.

p u b lic F ly in g B e e s () {
t h is . I n itia liz e C o m p o n e n t ( ) ;

Metoda StartAnimationO pobiera


L is t< s t r in g > imageNames = new L is t < s t r in g > ( ) ; sekwencję nazw zasobów oraz
imageNames.Add("Bee a n im a tio n 1 .p n g " ); °b iekt typu TimeSpan określający
imageNames.Add("Bee a n im a tio n 2 .p n g " ); szybkość zmiany ramek.
imageNames.Add("Bee a n im a tio n 3 .p n g " );
imageNames.Add("Bee a n im a tio n 4 .p n g " );

firs tB e e .S ta rtA n im a tio n (im a g e N a m e s , T im e S p a n .F ro m M illis e c o n d s (5 0 ));


secondB ee.S tartA nim ation(im ageN am es, T im e S p a n .F ro m M illis e c o n d s (1 0 ));
th ird B e e .S ta rtA n im a tio n (im a g e N a m e s , T im e S p a n .F ro m M illis e c o n d s (1 0 0 ));
Zam iast używać
znaczników
<Storyboard> oraz S to ryb o a rd s to ry b o a rd = new S to ry b o a rd ();
<DoubleAnimation>, D oubleA nim ation a n im a tio n = new D o u b le A n im a tio n (); Obiekt Storyboard jest usuwany
jak zrobiłeś to

{
S to ry b o a rd .S e tT a rg e t(a n im a tio n , f ir s t B e e ) ; z pamięci po zakończeniu odtwarzania
we wcześniejszej
części rozdziału, S to ry b o a rd .S e tT a rg e tP ro p e rty (a n im a tio n , " ( C a n v a s .L e ft) 1 animacji. Możesz się
teraz samemu an im a tio n .F ro m = 50; o tym przekonać, korzystając
utworzysz obiekty a n im a tio n .T o = 450; z opcji M ake O bje ct ID, by zacząć
Storyboard
i DoubleAnimation a n im a tio n .D u ra tio n = Tim eSpan.FromSeconds(3); obserwować obiekt, i klikając O , by
i ustaw isz wartości a n im a tio n .R e p e a tB e h a v io r = R e p e a tB e h a vio r.F o re ve r; odświeżyć go po zakończeniu animacji,
ich właściwości. a n im a tio n .A u to R e ve rse = t r u e ; ale najpierw zamień w yw ołanie
s to ry b o a rd .C h ild re n .A d d (a n im a tio n ); RepeatBehavior na RepeatBehavior(2),
s to ry b o a rd .B e g in () ; w przeciwnym przypadku animacja
nigdy się nie zakończy!

U ru ch o m swoją aplikację. Zobaczysz w niej trzy pszczoły machające


skrzydełkam i. W każdej z nich podałeś in n e odstępy czasu pom iędzy
ko le jn ym i ram ka m i, zatem pszczoły będą m achały skrzydełkam i z różną
szybkością. W p rzypadku górnej pszczoły właściwość C a n v a s .L e ft
jest anim owana w zakresie od 40 do 450 i z po w ro te m , co sprawia,
że będzie się ona poruszała po stronie. Przyjrzyj się dokładniej
używanym właściwościom o b ie ktu D o u b le A n im a tio n i porów naj je
z właściwościam i X A M L , których używałeś wcześniej w tym rozdziale.

W tym projekcie coś jest nie w porządku. Czy potrafisz wskazać, gdzie tk w i błąd?
jesteś tutaj ► 819
Pamiętaj — MVVM jest wzorcem

Coś jest nie w porządku: foldery View oraz ViewModei są


puste, a my tw orzym y dane przykładowe w klasie widoku.
To nie jest zgodne ze wzorcem MVVM!

Gdybyśmy chcieli dodać więcej pszczół, m usielibyśm y dodać więcej


k o n tro le k do ko d u w id o k u i osobno zainicjow ać każdą z nich. A co by
było, gdybyśmy chcieli stworzyć pszczoły różnej w ielkości lu b różnych
rodzajów? B yło by to znacznie łatwiejsze, gdyby m od el a p lika cji został
zoptym alizow any p o d kątem danych. Jak sprawić, by ten p ro je k t był
zgodny ze wzorcem M V V M ?

? ? ?
* * *

T O PROSTE. W Y S T A R C Z Y DO D AĆ KO LEKCJĘ T Y P U
O B S E R V A B LE C O LLE C T IO N , W KTÓ R EJ BĘDZIEM Y
O P R Z E C H O W Y W A L I K O N T R O L K I, I P O W IĄ Z A Ć JĄ
Z W ŁA Ś C IW O Ś C IĄ C H ILD R EN K O N T R O L K I CANVAS.
C Z E M U ROBICIE Z TEG O T A K I W IE L K I PROBLEM?

To nie zda egzaminu. Technika wiązania danych nie jest w stanie


operować na właściwości C h ild r e n . . . i to z ważnego powodu.

T echn ika w iązania danych została stworzona z myślą o operow aniu na


w ła ściw o ścia ch do łą czonych , czyli takich, któ re są dostępne w kodzie X A M L .
T o prawda, że o b ie kty Canvas dysponują publiczną właściwością Children , jeśli
je d n a k spróbujesz użyć je j w pow iązaniu danych zdefiniow anym w kodzie X A M L
— (Children="{Binding . . . } " ) — to ko d u nie uda się skompilować.

N ie m n ie j je d n a k ju ż wiesz, w ja k i sposób m ożna powiązać kolekcję ob ie któ w


z k o n tro lk ą X A M L , gdyż robiłeś to w przypadku k o n tro le k ListView oraz
GridView — wystarczy użyć właściwości ItemsSource. M ożem y skorzystać z tej
m ożliw ości, by powiązać k o n tro lk ę Canvas z k o n tro lk a m i, k tó re chcemy w niej
wyśw ietlić.

820 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Użyj ItemsPanelTemplate, by powiązać pszczoły z kontrolką Canvas


K iedy korzystałeś z właściwości Ite m s S o u rc e , by pow iązać elem en ty z k o n tro lkam i L is tV ie w , G rid V ie w lub
L is tB o x , nie m iało znaczenia, z k tó rą k o n tro lk ą tw orzyłeś pow iązanie, gdyż w łaściwość Ite m sS o u rce zawsze działa
ta k sam o. Gdybyś m iał n apisać trzy klasy dysponujące dokład n ie tym sam ym zachow aniem , to mógłbyś um ieścić
je w klasie bazow ej i dziedziczyć p o niej w każdej z trzech klas, praw d a? D o k ład n ie w te n sam sposób postąpili
pracow nicy firm y M icrosoft, tw orząc k o n tro lk i do w ybierania elem entów . Klasy L is tV ie w , G rid V ie w o raz L is tB o x
dziedziczą po w spólnej klasie bazow ej o nazw ie S e le c t o r , a ta z kolei dziedziczy p o k la s ie Ite m s C o n tr o l,
k tó r a słu ż y do w y ś w ie tla n ia k o le k c ji ele m e n tó w .

U żyjem y jej właściwości Ite m s P a n e l , by o k re ś lić sza b lo n p a n e lu zarządzającego


rozm ieszczen ie m ele m e n tó w . Z acznij o d d o d an ia p rzestrzen i nazw ViewModel do pliku
FlyingBees.xaml:
Gdyby Twój projekt miał
xm lns:view m odel= "using:A nim ow anaP szczola.V iew M odel" ^ — inrlą. nazUJę t o powm ieneś
je j tu użyć zamiast nazwy
AnimowanaPszczola.

N astęp n ie d o fo ld e ru ViewModel d o d a j pustą kla sę o na zw ie BeeViewM odel,


po czym dodaj jej in stancję do plik u FlyingBees.xaml, definiując ją jak o zasób statyczny:

<viewmodel:BeeViewModel x:K e y= "vie w M o d e l"/>

Z m odyfikuj plik FlyingBees.xaml.cs — u s u ń z nieg o d o d a tk o w y kod , k tó r y do da łeś do


k o n s tru k to ra F ly in g B e e s ( ) . U pew nij się, że przez p rzy p ad ek nie usunąłeś tak że wywołania
m etody I n it ia liz e C o m p o n e n t s ( ) !

O to k o d X A M L k o n tro lk i I te m s C o n tr o l . O tw órz p lik FlyingBees.xaml i u s u ń z nieg o d o d a n y


w cześniej z n a c z n ik < C a n v a s > , a z a m ia s t niego u m ie ść w p lik u p o n iższy k o d :

Użyj statycznego zasobu


< Ite m s C o n tr o l D a ta C o n t e x t = " { S ta t ic R e s o u r c e v ie w M o d e l} " yie^/Model jako kontekstu
danych i powiąż właściwość
Ite m s S o u r c e = " { B in d in g P a t h = S p r it e s } " ^ Item sSource z właściwością
o nazwie S p rite s.
G r id .R o w = " 1 " M a r g in = " 1 2 0 s0 , 1 2 0 s 1 2 0 ">

Użyj właściwości Ite m sP a n e l, by skonfigurować


< I t e m s C o n t r o l. It e m s P a n e l>
szablon Item sP anelTem plate. Zawiera on
Panel można pojedynczą kontrolkę Panel, a zarówno
skonfigurować < Ite m s P a n e lT e m p la te > <
G rid , jak i Canvas dziedziczą po klasie Panel.
w dow olny sposób. Dowolne elementy powiązane z właściwością
My zastosowaliśmy <Canvas B a c k g ro u n d = "S k y B lu e " />
Item sS ource zostaną dodane do kolekcji
kontrolkę Canvas C h ild re n panelu.
z tłem błękitnego kolo r u . < /Ite m s P a n e lT e m p la te >

< / I t e m s C o n t r o l. I t e m s P a n e l>

< / I t e m s C o n t r o l> W momencie tw orzenia kontrolki Item sTem plate tw o rzy ona Panel,
którego będzie używała do prezentowania swoich elementów,
a do określenia ich postaci używa szablonu Item sPanelTem plate.

jesteś tutaj ► 821


Wytwórnia pszczół

W zorzec m etody wytwórczej.


U tw ó rz now ą kla sę o na zw ie B ee H e lpe r i zap isz ją M V V M jest ty lk o jednym z wielu wzorców projektowych. Jedny m
w fo ld e rz e View. U p e w n ij się, że zadeklarujesz ją jako z najczęściej używanych i najbardziej użytecznych spośród -tych
klasę statyczną, gdyż będzie ona zawierać tylko wzorcó w jest wzorzec m etody w ytwórczej (ang. factory method),
którego założeniem jest tworzenie m etody „ wy twarzającej"
m etody statyczne, których zadaniem będzie ułatw ienie
obiekty. Taka metoda jest zazwyczaj definiowana jako s taty czna,
w arstw ie m od elu w id o k u w zarządzania pszczołami.
a jej nazwa zazwyczaj kończy się słowem „ Factory , dzięki czemu
od razu wiadomo, jakie jest jej przeznaczenie-
u s in g W in d o w s .U I.X a m l.C o n tro ls ;
u s in g W in d o w s.U I.X a m l.M e d ia .A n im a tio n ;

s t a t i c c la s s BeeHelper {
p u b lic s t a t i c Animatedlmage B eeF actory(
do ub le w id th , double h e ig h t, TimeSpan f la p I n t e r v a l ) {
Z 1
Ta metoda L is t< s t r in g > imageNames = new L is t < s t r in g > ( ) ;
wytwórcza tworzy imageNames.Add("Bee a n im a tio n l.p n g " )
kontrolki B e e .
imageNames.Add("Bee a n im a tio n 2 .p n g ")
U m ieszczenie je j
w w arstw ie widoku imageNames.Add("Bee a n im a tio n 3 .p n g ")
j e s t sensownym imageNames.Add("Bee a n im a tio n 4 .p n g ")
rozwiązaniem,
gdyż cały ten kod
AnimatedImage bee = new AnimatedImage(imageNames, f la p I n t e r v a l ) ;
j e s t związany
z interfejsem be e.W idth = w id th ;
użytkownika. Kiedy jakiś często używany fragm ent kodu umieścisz w odrębnej (często statycznej)
b e e .H e ig h t = h e ig h t;
metodzie, to taka metoda jest zazwyczaj nazywana metodą pomocniczą.
r e tu r n bee;
Sensownym rozwiązaniem, które ułatw ia analizę kodu, jest gromadzenie takich
}
metod w klasie statycznej, której nazwa kończy się słowem „Helper".

p u b lic s t a t i c v o id S etB eeLocation(A nim atedIm age bee, do ub le x , double y ) {


C a n v a s .S e tL e ft(b e e , x ) ;
C anvas.SetTop(bee, y ) ;
}

p u b lic s t a t i c v o id MakeBeeMove(AnimatedImage bee,


double from X, do ub le to X , double y ) {
C anvas.SetTop(bee, y ) ;
S to ry b o a rd s to ry b o a rd = new S to ry b o a rd ();
D oubleA nim ation a n im a tio n = new D o u b le A n im a tio n ();
S to ry b o a rd .S e tT a rg e t(a n im a tio n , b e e );
S to ry b o a rd .S e tT a rg e tP ro p e rty (a n im a tio n , " ( C a n v a s .L e ft) " ) ;
a n im a tio n .F ro m = fromX;
a n im a tio n .T o = toX ;
a n im a tio n .D u ra tio n = Tim eSpan.From Seconds(3);
a n im a tio n .R e p e a tB e h a v io r = R e p e a tB e h a vio r.F o re ve r;
a n im a tio n .A u to R e ve rse = t r u e ;
s to ry b o a rd .C h ild re n .A d d (a n im a tio n );
s to ry b o a rd .B e g in () ;
}
To je s t ten sam kod, który w cześniej
u m ieściłeś w konstruktorze strony.
Teraz p rzen ieśliśm y go do statyczn ej
metody pom ocniczej.

822 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Te informacje Wszystkie kontrolki XAML dziedziczą po klasie bazowej UIElement, zdefiniowanej w przestrzeni nazw W indows.UI.Xaml.
przydadzą Ci W tym przypadku celowo podaliśmy przestrzeń nazw (W indows.UI.Xam l.U IElem ent) w kodzie klasy, a nie w instrukcji
s ię w ostatnim
laboratorium. using, aby ograniczyć ilość kodu związanego z interfejsem użytkownika, dodawanego do klasy modelu widoku.

Zastosowaliśmy klasę UIElement, gdyż jest to najbardziej abstrakcyjna z klas, po której dziedziczą wszystkie sprajty.
W niektórych projektach bardziej odpowiednim rozwiązaniem mogłoby być zastosowanie klasy FrameworkElement,
gdyż to w łaśnie ona definiuje w iele właściwości takich jak: H e ig h t, W idth, O p a city, H o r iz o n ta lA lig n itd.

Poniżej przedstaw iliśm y ko d pustej klasy BeeViewM odel ,


k tó rą dodałeś do fo ld e ru ViewModel. D z ię k i przeniesieniu kodu
związanego z interfejsem użytko w nika do w arstw y w id o ku ,
ko d w arstw y m od elu w id o k u może być pro sty i m ożem y go
Kiedy kontro/ka AnimatedJmage je s t dodawana
ograniczyć w yłącznie do lo g ik i zarządzania pszczołami. do ko/ekcji _ s p rite s (typu Observab/eCo//ection),
zostaje powiązana z właściwością Item sSource
kontro/ki ItemsContro/ i umieszczona na
u sin g View;
u sin g S y s te m .C o lle c tio n s .O b je c tM o d e l; ?ane/u utworzonym na podstawie szab/onu
temsPane/Temp/ate.
u sin g S y s te m .C o lle c tio n s .S p e c ia liz e d ;

c l a s s BeeViewModel {
p r i v a t e re a d o n ly O b servableC ollection< W indow s.U I.X am l.U IElem ent>
_ s p r it e s = new O b serv ab leC o llectio n < W in d o w s.U I.X am l.U IE lem en t> ();
p u b lic IN o tify C o lle c tio n C h a n g e d S p r ite s { g e t { r e tu r n _ s p r i t e s ; } }
Hermetyzacja właściwości
p u b lic BeeViewModel() { S p rite s je s t przeprowadzana
AnimatedIm age f i r s t B e e = w dwóch etapach. Pierwszym
B e e H e lp e r.B e e F a c to ry (5 0 , 50, z nich je s t dodanie do definicji
T im e S p a n .F ro m M illise c o n d s(5 0 )); po/a wewnętrznego modyfikatora
Sprajt (ang. readon/y, dzięki czemu jego
sprite) to term i n s p r ite s .A d d ( fir s tB e e );
początkowej wartości nie
określający wsze l k i e będzie można później zmienić.
AnimatedIm age secondBee = Dodatkowo właściwość je s t
dw uw ym iarow e
obrazki lub
B e e H e lp e r.B e e F a c to ry (2 0 0 , 200, T im e S p a n .F ro m M illise c o n d s(1 0 )); typu JNotifyCo//ectionChanged,
_ s p rite s .A d d (s e c o n d B e e ); dzięki czemu inne k/asy mogą
animacje, stosowa ne j ą jedynie obserwować, /ecz nie
w dużych grach mogą je j zmieniać.
kom puterowy ch
AnimatedIm age th ird B e e =
lub większych
B e e H e lp e r.B e e F a c to ry (3 0 0 , 125, T im e S p a n .F ro m M illise co n d s(1 0 0 ));
_ s p r ite s .A d d ( th ir d B e e ) ;
animacjach.
Zmieniasz właściwości kontrolek
B eeH elper.M akeB eeM ove(firstB ee, 50, 450, 40) i dodajesz do nich animacje już
B e e H e lp e r.S e tB e e L o c a tio n (se c o n d B e e, 8 0 , 260) po dodaniu kontrolek do kolekcji
B e e H e lp e r.S e tB e e L o c a tio n (th ird B e e , 230, 100)
O b s e r v a b le C o lle c t io n . Zatem
dlaczego ten kod działa?

Słowo kluczowe readonly


U ru cho m aplikację. Powinna wyglądać dokładnie tak
samo ja k wcześniej, lecz teraz jej działanie zostało Jednym z ważnych powodów stosowania hermetyzacji ¡est chęć
uniemożliwienia nadpisania danych ¡ednej k lasy przez inną klasę.
rozdzielone i umieszczone w różnych warstwach
Co jednak może zapobiec nadpisaniu danych przez: kod należący do
— k o d związany z interfejsem użytkow nika został
te j samej klasy? W takich przypadkach może nam p ° móc sł° w°
umieszczony w warstwie w id o ku , natom iast kod
kluczowe readonly. Każde pole oznaczone ty m mody fikatorem może
związany z tw orzeniem i przesuwaniem pszczół
być modyfikowane wyłącznie w deklaracp bądź w kon stru ktorze-
— w warstwie m odelu widoku.

jesteś tutaj ► 823


Gwiazdki i paski

Długie ćwiczenie
To już ostatnie ćwiczenie w tej książce. Twoim zadaniem będzie napisanie programu, który animuje pszczoły
i gwiazdy. Trzeba będzie napisać sporo kodu, jednak jesteś do tego dobrze przygotowany... a kiedy już się
uporasz z tym zadaniem, będziesz dysponował wszystkimi narzędziami niezbędnymi do napisania gry
wideo. (Czy potrafisz odgadnąć, co będzie tematem trzeciego Laboratorium?).

TAK WYGLĄDA APLIKACJA, KTÓRĄ MASZ NAPISAĆ.


Pszczoły będą machać skrzydełkami i latać po błękitnym niebie (kon tro lce Canvas), a ponad n im i będą m igotały
gwiazdy. A p lik a c ja będzie się składała z warstwy w id o ku zawierającej pszczoły, gwiazdy oraz prezentującą je stronę,
z warstwy m odelu przechowującego inform acje o położeniu pszczół i gwiazd oraz wywołującego zdarzenia, gdy
pszczoły zm ienią położenie, a gwiazdy stan; ja k rów nież z warstwy m odelu w id o ku łączącej dwie poprzednie.

Pszczoły latają po
niebie, udając się
w losowo wybrane
m iejsca. Kiedy
w m i ary .nie ba c Gwiazdy przygasają
(kontrolki Canvas) i rozbłyskują.
ulegną zmianie,
pszczoły polecą A
w nowe miejsca.

“■ Je śli obszar kontrolki Canvas ulegnie zmianie, gwiazdy momentalnie


^ zmieniają położenie, a pszczoły powoli zaczynają lecieć w nowe
miejsca. Możesz to przetestować, uruchamiając aplikacją w symulatorze
i używając przycisku □ , by zmienić jego rozdzielczość.

UTW ÓRZ NOW Y PROJEKT APLIKACJI DLA SKLEPU WINDOWS.


U tw ó rz p ro je k t nowej ap lika cji i nadaj je j nazwę GwiazdzisteNiebo . N astępnie d o d a j do n ie j fo ld e ry View,
ViewModel o ra z M odel, a do fo ld e ru ViewModel d o d a j p u s tą kla sę B eeS tarV iew M odel.

W FOLDERZE VIEW UTW ÓRZ NOWĄ STRONĘ TYPU BASIC PAGE.


D o fo ld e ru View dodaj nową stronę typ u Basic Page, nadaj je j nazwę BeesOnAStarryNight.xaml. D o znacznika
głównego strony dodaj przestrzeń nazw (po w in na ona odpow iadać nazwie p ro je k tu : G w ia z d z is te N ie b o ):

xm ln s:vie w m o d e l= "u sin g :G w ia zd ziste N ie b o .V ie w M o d e l"


D o da j do strony o b ie k t m od elu w id o k u (d e fin iu ją c go ja ko zasób statyczny) i zm ień je j nazwę:
Zdarzenie
<Page.Resources> SizeChanged jest
<view m odel:BeeStarViewM odel x:N am e="view M odel"/> w yw oływ ane,
< x :S t r in g x:Key="AppName">PszczoTy na g w ia źd zistym n ie b ie < / x :S t r in g > gdy kontrolka
</Page.R esources> zmieni wielkość,
informacje
K o d X A M L strony jest d o k ła d n ie t a k i sam ja k ko d w p lik u FlyingBees.xaml w poprzednim o nowej wielkości
pro je kcie, z tą różnicą, że tło k o n tro lk i Canvas m a m ieć w artość B lu e , a dodatkow o określona są przekazywane
została pro ced ura obsługi zdarzeń SizeChanged: w argumencie
EventArgs.
<Canvas B ackground="B lue" SizeChanged="SizeChangedHandler" /> ^ -------------

Visual Studio d ostarcza fantastycznego narzęd zia do przeprow adzania eksperym entów z przeróżnym i
kształtam i! Uruchom program Blend for V isual Studio 2012 i sko rzystaj z pióra, ołów ka i przybornika,
824 b y tw o rz y ć k szta łty X A M L, które następnie będziesz mógł kopiować i u ży w ać w swoich projektach C # .
Tworzenie aplikacji według wzorca MVVM

K tóu przeds t awi°n eg ° w Irnliu 4. nie uda. się skom pilować aż do momentu
dodania w kroku 9. i w ^ ś a ^ ś d play A re a S iz e . Na razie m ożesz użyć ID E do
wyge nerowania szk ieletu te j w ła ściw ości. .
^ DODAJ KOD UKRYTY STRONY ORAZ APLIKACJI.
D o p lik u BeesOnAStarryNight.xaml w folderze View dodaj procedurę obsługi zdarzeń
S izeC hanged:
p r iv a t e v o id S ize C h a n g e d H a n d le r(o b je ct sen de r, SizeChangedEventArgs e) {
v ie w M o d e l.P la yA re a S ize = new S iz e (e .N e w S iz e .W id th , e .N e w S iz e .H e ig h t);

N astępnie zm od yfikuj p lik App.xam l.css, zm ieniając w nim wyw ołanie ro o tF ra m e .N a v ig a te ()


w ta k i sposób, by podczas uru cha m ian ia a p lika cji w yśw ietlana była nowa strona:

if ( !r o o tF ra m e .N a v ig a te ( ty p e o f(V ie w .B ee sO n A S tarryN ight) , a rg s.A rg u m e n ts))

DO FOLDERU VIEW DODAJ KONTROLKĘ A N IM A T E P IM A G E .


P rzejdź po n o w n ie do fo ld e ru View i dodaj do niego k o n tro lk ę A n im a te d Im a g e . T o je st d o kła d n ie ta sama
k o n tro lk a , k tó re j ju ż używałeś wcześniej w tym rozdziale. N ie zap om n ij d o d a ć p lik ó w z o b ra z k a m i do
fo ld e ru A ssets .

K DO FOLDERU VIEW DODAJ KONTROLKĘ UŻYTKO W NIKA O NAZW IE ST A R CO N T R O L .


T a k o n tro lk a będzie rysować gwiazdę. Będzie ona używała dwóch o b ie któ w S to ry b o a rd — pierwszy z nich
będzie odpow iadał za płynne pojaw ianie się gwiazdy, a d ru gi za je j zanikanie. D o ko d u ukryteg o strony d o d a j
m e to d y F a d e ln () o ra z F a d e O u t(), służące do u ru cho m ien ia anim acji.

Kontrolka Polygon używa zbioru punktów, by na


ich podstawie narysować wielokąt. Nasza kontrolka
U se rC o n tro l będzie jej używać do w yśw ietlenia gwiazdy.
< U serC ontrol
/ / Standardowy kod XAML generowany prze z IDE j e s t w porządku
/ / by używać t e j k o n t r o lk i u ży tk o w n ik a , n ie p o trz e b a dodatkowych p rz e s trz e n i nazw
>

< U serC ontro l.R eso urces>


< S to ryb o a rd x :N a m e = "fa d e In S to ryb o a rd ">
< D oubleA nim ation From="0" To="1" S to ryb o a rd .T a rg e tN a m e = "sta rP o lyg o n "
S to ry b o a rd .T a rg e tP ro p e rty = "O p a c ity " D u ra tio n = " 0 :0 :1 .5 " />
< /S to ry b o a rd >
< S to ryb o a rd x:N am e="fadeO utS toryboard">
< D oubleA nim ation From="1" To="0" S to ryb o a rd .T a rg e tN a m e = "sta rP o lyg o n "
S to ry b o a rd .T a rg e tP ro p e rty = "O p a c ity " D u r a tio n = " 0 :0 :1 .5 " />
- / i. A to ryb o a _rd ^ Qo kodu ukrytego będziesz m u sia ł dodać dwie metody publiczne
</U s e rC o n tro l. Resources> ^ n O o ra ł fldoO utO , które będą urucham iały 0, ^ ^ ^
To właśnie dzięki nim gwiazdy będą pojaw iać s ię i znikać.
<G rid>
<Polygon P o in ts = "0 ,7 5 75 ,0 100,100 0,2 5 150,25" F ill= "S n o w "
S tro k e = "B la c k " x:Name= "s ta rP o ly g o n " / > Ten wielokąt ry su je gwiazdę. M ożesz ją
< /G rid > R,____________ z a stą p ić innym kształtem , by spraw dzić,
< /U s e rC o n tro l> ja k one działają.

Oprócz elips, prostokątów i w ielo kątów istnieją także inne kształty; możesz się o nich
dowiedzieć na stronie: http://msdn.microsoft.com/library/windows/apps/hh465055.aspx.
jesteś tutaj ► 825
Ach moje gwiazdy

Długie ćwiczenie
Rozwiązanie DODAJ DO W ARSTW Y WIDOKU KLASĘ BeeS TARHelper.
#6using Windows.UI.Xaml;
Poniżej przedstaw iliśm y ko d użytecznej klasy pom ocniczej. Z aw iera
m etody, k tó re ju ż znasz, oraz k ilk a nowych. U m ieść ją w folderze View.
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Shapes;

s t a t ic c la ss BeeStarHelper {
public s t a t ic AnimatedImage BeeFactory (double width, double height, TimeSpan fla p In te rv a l) {
List< string> imageNames = new L is t< s trin g > ();
imageNames.Add("Bee animation 1.png");
imageNames.Add("Bee animation 2.png");
imageNames.Add("Bee animation 3.png");
imageNames.Add("Bee animation 4.png");

AnimatedImage bee = new AnimatedImage(imageNames, f la p In t e r v a l);


bee.Width = width;
bee.Height = height; Metody SetLeft() oraz GetLeft() kontrolki Canvas, odpowiednio: ustawiają ipobierają
return bee; współrzędną X położenia kontrolki. Z kolei metody SetTop() oraz GetTop() ustawiają ipobierają
} współrzędną Y. Wszystkie te metody działają nawet po dodaniu kontrolki do kontrolki Canvas.

public s t a t ic void SetCanvasLocation(UIElement co n tro l, double x , double y) {


C a n v a s.S e tL e ft(co n tro l, x ) ;
Canvas.SetTop(control, y ) ;
}

public s t a t ic void MoveElementOnCanvas(UIElement uiElement, double toX, double toY){


double fromX = C anvas.G etLeft(uiE]em ent);
double fromY = Canvas.GetTop(uiElement); Doda/iśmy metodę
pomocniczą o nazwie
Storyboard storyboard = new Storybo ard(); CreateDoub/eAnimation(),
DoubleAnimation animationX = CreateDoub]eAnimation(uiE]ement, która tworzy animację typu
Doub/eAnimation trwającą
fromX, toX, "(C an vas.Le ft)
trzy sekundy. Ta metoda
DoubleAnimation animationY = CreateDoub]eAnimation(uiE]ement, używa je j, by przesunąć
fromY, toY, "(Canvas.Top)" kontro/kę UIE/ement z je j
storyboard.Chi]dren.Add(anim ationX); aktua/nego położenia
storyboard.Chi]dren.Add(anim ationY); w nowe miejsce, wykonując
sto ryb o ard .B eg in(); w tym ce/u animację
} właściwości Canvas.Left
oraz Canvas.Top.
public s t a t ic DoubleAnimation CreateDoubleAnimation(UIElement uiElement,
double from, double to, s trin g propertyToAnimate) {
DoubleAnimation animation = new DoubleAnimation();
Storyboard.SetTarget(anim ation, uiElem ent);
Storyboard.SetTargetProperty(anim ation, propertyToAnimate);
animation.Erom = from;
animation.To = to; „Z index" określa kolejność, w jakiej
animation.Duration = TimeSpan.EromSeconds(3); kontrolki są rozmieszczane na panelu.
return animation; Kontrolki posiadające większą wartość
} Z index, są umieszczane powyżej tych,
w których wartość ta jest mniejsza.
public s t a t ic void SendToBack(StarControl newStar) {
Canvas.SetZIndex(newStar, -1000);

826 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

DO WARSTWY MODELU APLIKACJI DODAJ KLASY BEE, STAR


ORAZ BEEMOVE EVENTARGS.
M o d e l a p lika cji m usi przechowywać in fo rm a cje o w ielkości i p o ło żeniu pszczół oraz o po ło żeniu
gwiazd. O prócz tego będzie w yw oływ ał zdarzenia, by p o in fo rm o w a ć warstwę m od elu w id o ku
o wszelkich zm ianach, k tó re zaszły w pszczołach lu b gwiazdach.

u s in g W indow s.Foundation;
c la s s Bee {
p u b lic P o in t L o c a tio n { g e t; s e t; }
p u b lic S ize S ize { g e t; s e t; }
p u b lic Rect P o s itio n { g e t { r e tu r n new R e c t(L o c a tio n , S iz e ); } }
p u b lic do ub le W idth { g e t { re tu r n P o s itio n .W id th ; }}
u s in g W indow s.Foundation;
p u b lic do ub le H e igh t { g e t { r e tu r n P o s itio n .H e ig h t; } }
c la s s S ta r {
p u b lic B e e (P o in t lo c a t io n , S ize s iz e ) {
p u b lic P o in t L o c a tio n {
L o c a tio n = lo c a t io n ; g e t; s e t;
S ize = s iz e ; }
}
} p u b lic S ta r ( P o in t lo c a t io n ) {
L o c a tio n = lo c a t io n ;
u s in g W indow s.F oundation; }
c la s s BeeMovedEventArgs : EventArgs { } Kiedy ju ż uda Ci się uruchomić tę
p u b lic Bee BeeThatMoved { g e t; p r iv a t e s e t; } aplikację, spróbu j dodać do klasy
S ta r logiczną w łaściw ość Rotating
p u b lic double X { g e t; p r iv a t e s e t; }
i użyj je j do powolnego obracania
p u b lic double Y { g e t; p r iv a t e s e t; } niektórych gwiazd.

p u b lic BeeMovedEventArgs(Bee beeThatMoved, do ub le x , double y) {


BeeThatMoved = beeThatMoved;
X = x;
Y = y;
}

M odel będzie wywoływał zdarzenia używ ające tej klasy EventA rgs, S tru k tu ra R ect udostępnia
by informować model widoku o zachodzących zmianach. kilka przeciążonych
konstruktorów i metod,
u s in g W indow s.F oundation; pozwalających na pobieranie
c la s s StarChangedEventArgs : EventArgs { jego szero kości, w ysokości,
w ielkości i położenia
p u b lic S ta r StarThatChanged { g e t; p r iv a t e s e t; }
(w formie danych typu
p u b lic bool Removed { g e t; p r iv a t e s e t; } Point lub pojedynczych
współrzędnych X i Y ).
p u b lic S tarC h an ge dE ven tA rgs(S tar starT hatC hanged, bool removed) {
StarThatChanged = starT hatC hanged;
Removed = removed;
} S truktury Point, Size oraz Rect.
_____________________________________ W przestrzeni Windows.Foundation dostępnych jest kilka przydatnych struktur.
Struktura Point używa właściwości X i Y typu double do przechowywania
Właściwość Points ----- zest awu współrzędnych. Także struktura Size ma dwfe whśdwośd typu doub|e
kontrolki Polygon je s t — W idth oraz Height — oraz trzecią, specjalną właściwość Emp iy Struktura
kolekcją stru k tu r Point.
Rect przechowuje dwa zestawy współrzędnych, ° kreś|ające położenie teweg°
górnego oraz prawego dolnego wierzchołka p to s to l^ta . Udostępnia także wie|e
użytecznych metod, na przykład do określania jego szerokości, wysokości, części
wspólnej z innymi prostokątami i tak dalej. 827
Bzzz, bzzz, bzzz

Długie ćwiczenie (ciąg dalszy)


L , . 1
DO WARSTWY MODELU DODAJ KLASĘ BEESTARMODEL.
Podaliśm y po la pryw atne klasy oraz ko d k ilk u m etod. T w o im zadaniem
jest dokończenie klasy BeeStarModel.
using Windows.Foundation; M ożesz użyć modyfikatora readonly, by utworzyć
pole sta łe , którego typem j e s t stru k tu ra .
class BeeStarModel {
public s ta t ic readonly Size StarSize = new Size(150, 100);

p rivate readonly Dictionary<Bee, Point> bees = new Dictionary<Bee, Point> ();


p rivate readonly D ictionary<Star, Point>' stars = new Dictionary<Star, Point> ();
p rivate Random random = new Random();

public BeeStarModel () { S iz e .E m p ty je s t wartością


playAreaSize = Size.Empty; typu S iz e , przeznaczoną do
} " reprezentacji p u ste j w ielkości.

public void Update () {


MoveOneBee(); Model widoku będzie używ ał zegara do
AddOrRemoveAStar(); cy klicznego wywoływania metody Update().
}
p rivate s t a t ic bool RectsOverlap(Rect r1 , Rect r2) {
Ta metoda sprawdza dwie stru ktu ry
r1 .In t e r s e c t (r 2 );
i f (r1.Width > 0 || r1.Height > 0) Rect i zwraca w artość true, je ś li zachodzą
return true; one na sie b ie ; używa przy tym metody
return fa ls e ; R e c t.In te rse c t().
i P layA reaSize je s t
w łaściw ością.
public Size PlayAreaSize {
/ / Dodaj pole wewnętrzne, je g o a k c e s o r s e t p o w in ie n w yw oływ a ć m e to d y C r e a t e B e e s ( ) o ra z C re a te S ta r s ()
i --------------------
p rivate void CreateBees() {
I I J e ś l i o b s z a r g r y j e s t p u s t y , k oń czym y w y w o ła n ie . J e ś l i s q na nim p s z c z o ł y , p rzesu w a m y k a żd ą z n i c h .
I I U p rz e c iw n y m r a z i e tw orzym y od 5 do 15 p s z c z ó ł o r ó ż n e j w i e l k o ś c i (o d 40 do 1 50 p i k s e l i ) .
I I D o d a j j e do k o l e k c j i _ b e e s i w y w o ła j z d a r z e n i e B ee M o ved .
J e ś li metoda sprawdzi
i 1000 losowych m iejsc
p rivate void CreateStars() { i nie uda je j się
I I J e ś l i o bsza r g ry j e s t p u sty , k o ń czym y w y w o ła n ie . J e ś l i s q j u ż n a nim j a k i e ś g w ia z d y , znaleźć takiego, które
I I t o p r z y p i s z k a ż d e j z n ic h nowe p o ło ż e n ie ( o k r e ś lo n e j a k o p u n k t) i w y w o ła j z d a r z e n ie S ta rC h a n g e d , by nie zachodziło
I I w p r z e c iw n y m r a z i e w y w o ła j od 5 do 1 0 r a z y m e to d ę C r e a t e A S t a r ( ) . na inne kontrolki, to
i można wnioskować,
p rivate void CreateAStar() { że brakuje ju ż na
I I Z n a jd ź nowy p u n k t , k t ó r y n i e p o k ry w a s i ę z p o ło ż e n ie m ż a d n e j i s t n i e j ą c e j k o n t r o l k i , n a s t ę p n ie niej wolnego m iejsca.
I I d o d a j do k o l e k c j i s t a r s nową k o n t r o l k ę S t a r i w y w o ła j z d a r z e n i e S ta r C h a n g e d . W takim przypadku
i można zw rócić
p rivate Point FindNonOverlappingPoint(Size size ) { dowolną lokalizację.
II O k r e ś l le w y g ó r n y w i e r z c h o ł e k p r o s t o k ą t a , k t ó r y n i e p o k ry w a s i ę z ża d n ą p s z c z o ł ą a n i g w ia z d ą .
II B ę d z i e s z m u s ia ł w yp róbo w ać r ó ż n e lo s o w e p r o s t o k ą t y , a n a s t ę p n i e s k o r z y s t a ć z z a p y t a n ia LIN Q , b y o dszu kać
II p o k r y w a ją c e s i ę z n im p s z c z o ł y i g w ia z d y ( p r z y d a C i s i ę do t e g o c e l u m eto d a R e c t s O v e r l a p ( ) ) .
i
p rivate void MoveOneBee(Bee bee = n u ll) {
I I J e ś l i n i e ma ż a d n y c h p s z c z ó ł , kończym y w y w o ła n ie . J e ś l i p a r a m e tr b e e ma w a r t o ś ć n u l i , w y b ie ra m y lo so w ą
I I p s z c z o ł ę , w p rz e c iw n y m r a z i e używamy a rg u m e n tu . N a s tę p n ie z n a jd ź nowy p u n k t , k t ó r y n i e p o k ry w a s i ę z żad n ym i
I I k o n t r o l k a m i, z a k t u a l i z u j p o ł o ż e n i e p s z c z o ł y , z a k t u a l i z u j k o l e k c j ę b e e s i w y w o ła j z d a r z e n i e O nBeeM oved.
i
p rivate void AddOrRemoveAStar() {
I I R z u ć m o n etą ( _ r a n d o m .N e x t (2 ) == 0 ) i , z a l e ż n i e od w y n ik u , a lb o s t w ó r z g w ia z d ę , w y w o łu ją c C r e a t e A S t a r ( ) ,
I I a lb o usuń g w ia z d ę , n a s t ę p n ie w yw o ła j z d a r z e n ie O nStarC hanged. Z aw sze tw ó rz g w ia z d y , j e ś l i j e s t ic h <= 5 , a u su w a j,
I I j e ś l i j e s t ic h >= 2 0 . W y ra że n ie o p o s t a c i s t a r s . K e y s . T o L i s t ( ) [ r a n d o m .N e x t( s t a r s . C o u n t ) ] z w r ó c i lo so w ą g w ia z d ę .
i
II M u s is z t a k ż e d o d a ć z d a r z e n ia B eeM oved i S ta r C h a n g e d o r a z m e to d y do i c h w y w o ły w a n ia .
II B ęd ą on e k o r z y s t a ć z k l a s B e e M o v e d E v e n tA rg s o r a z S ta r C h a n g e d E v e n t A r g s .

Możesz sprawdzić swoją aplikację w symulatorze, by przekonać się,


828 Rozdział 16. czy prawidłowo działa na ekranach o różnej wielkości i orientacji.
Tworzenie aplikacji według wzorca MVVM

DO WARSTWY MODELU WIDOKU DODAJ KLASĘ BE E ST A R VlE W M O D EL


U zupełnij kod m etod z komentarzami.
Chcieliśmy mieć pewność, że D isp a tc h e rT im e r
Podczas pracy nad n im i będziesz musiał
oraz UIElem ent są jedynym i klasami
uważnie sprawdzać, ja k działa m odel oraz czego należącymi do przestrzeni nazw Windows.
oczekuje w idok. Bardzo się także przydadzą U I.X am l, używanymi w klasie modelu widoku.
napisane wcześniej m etody pomocnicze. Słowo kluczowe u s in g pozwala użyć operatora
=, by zadeklarować pojedynczą składową
using View;
z wybranej przestrzeni nazw.
using Model;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Windows.Foundation;
using DispatcherTimer = Windows.UI.Xaml.DispatcherTimer;
using UIElement = Windows.UI.Xaml.UIElement;

class BeeStarViewModel {
private readonly ObservableCollection<UIElement>
_sprites = new ObservableCollection<UIElement>();
public INotifyCollectionChanged Sprites { get { return _sprites; } }

private readonly Dictionary<Star, StarControl> _stars = new Dictionary<Star, StarControl>();


private readonly List<StarControl> _fadedStars = new List<StarControl>();

private BeeStarModel _model = new BeeStarModel();

private readonly Dictionary<Bee, AnimatedImage> _bees = new Dictionary<Bee, AnimatedImage>();

private DispatcherTimer _timer = new DispatcherTimer();

public Size PlayAreaSize { /* a k c e s o r y g e t i se t, k t ó r e z w r a c a ją i u s t a w ia ją jm o d e l .P la y A r e a S i z e */ }

public BeeStarViewModel () {
I I O k r e ś l p r o c e d u r y o b s łu g i z d a r z e ń BeeM oved o r a z S ta rC h a n g e d k l a s y B e e S t a r M o d e l, a n a s t ę p n ie
I I uruchom z e g a r , b y z g ł a s z a ł z d a r z e n ie c o d w ie s e k u n d y .
}
void timer_Tick(object sender, object e) {
I I U ramach o b s łu g i k a żd e g o z d a r z e n ia T i c k z e g a r a z n a jd ź w s z y s t k i e r e f e r e n c j e S t a r C o n t r o l w k o l e k c j i
II _ f a d e d S t a r s i usuh k a żd ą z n ic h z k o l e k c j i _ s p r i t e s , n a s t ę p n ie w yw o ła j m etod ę U p d a te () k l a s y
I l B e e V ie w M o d e l, a b y j ą p o in fo rm o w a ć o k o n i e c z n o ś c i a k t u a l i z a c j i .
}
void BeeMovedHandler(object sender, BeeMovedEventArgs e) {
I I S ło w n ik _ b e e s o d w zo ro w u je o b i e k t y B ee przech o w y w a n e p r z e z m odel na k o n t r o l k i A n im a te d lm a g e używ ane w w id o k u .
I I U p rz y p a d k u p r z e s u w a n ia p s z c z o ł y o b ie k t B e e S ta rM o d e l w y w o łu je z d a r z e n ie BeeM oved, b y p o in fo rm o w a ć w s z y s t k ic h
I I z a in t e r e s o w a n y c h , k t ó r a p s z c z o ła z m ie n ił a p o ł o ż e n ie i g d z ie s i ę t e r a z z n a j d u je . J e ś l i s ł o w n i k _ b e e s
I I j e s z c z e n i e z a w ie r a ż a d n y ch k o n t r o l e k A n im a te d lm a g e r e p r e z e n t u ją c y c h p s z c z o ł y , t o n a l e ż y u t w o r z y ć ta k ą
I I k o n t r o l k ę , o k r e ś l i ć j e j p o ł o ż e n ie w o b s z a r z e k o n t r o l k i C a nva s o r a z z a k t u a liz o w a ć k o l e k c j e _ b e e s i _ s p r i t e s .
I I J e ś l i s ło w n i k _ b e e s z a w ie r a j u ż j a k i e ś k o n t r o l k i , t o n a l e ż y j e d y n i e o d s z u k a ć o d p o w ia d a ją c e im k o n t r o l k i
I I A n im a te d lm a g e i p r z e s u n ą ć j e w nowe m i e j s c e , u ż y w a ją c p r z y tym a n i m a c ji.
}
void StarChangedHandler(object sender, StarChangedEventArgs e) {
I I S ło w n ik _ s t a r s d z i a ł a p o d o b n ie j a k _ b e e s , z t ą r ó ż n i c ą , ż e o d w zo ro w u je o b i e k t y S t a r na o d p o w ia d a ją c e im
I I k o n t r o l k i S t a r C o n t r o l . P a ra m e try E v e n tA r g s z a w ie r a ją r e f e r e n c j e do o b ie k tó w S t a r ( k t ó r e d y s p o n u ją w ła ś c iw o ś c ią
I l L o c a tio n ) o ra z w ła ś c iw o ś ć lo g ic z n ą in fo rm u ją c ą , c z y dana gw iazda z o s t a ł a u s u n ię t a . J e ś l i gw iazda z o s t a ł a u s u n ię t a ,
I I to chcem y j ą p ł y n n ie u k r y ć - w tym c e l u n a l e ż y u su n ą ć j ą z k o l e k c j i _ s t a r s , p r z e n i e ś ć do k o l e k c j i
I I _ f a d e d S t a r s , a n a s t ę p n ie w yw ołać m eto d ę F a d e O u t() ( k o n t r o l k a z o s t a n i e u s u n ię t a z k o l e k c j i _ s p r i t e s
I I p o d c z a s n a s tę p n e g o w yw o ła n ia m e to d y U p d a te () - t o w ła ś n ie z te g o powodu c z ę s t o t l i w o ś ć
I I z g ł a s z a n i a z d a r z e ń T i c k z e g a r a j e s t w ię k s z a od c z a s u tr w a n ia a n im a c ji w y g a sz a n ia g w ia z d y .
//
I I J e ś l i g w ia z d a n i e z o s t a j e u s u n ię t a , to sp raw d za m y, c z y z n a jd u je s i ę ona w k o l e k c j i _ s t a r s - j e ś l i t a k , to
I I p o b ie ra m y r e f e r e n c j ę S t a r C o n t r o l ; j e ś l i j e j n i e ma, t o t r z e b a u tw o r z y ć nową k o n t r o lk ę S t a r C o n t r o l , p ł y n n i e j ą
I I w y ś w i e t l i ć , d o d a ć do k o l e k c j i _ s p r i t e s , a n a s t ę p n ie z m ie n ić w a r t o ś ć z - i n d e x , b y k o n t r o l k i p s z c z ó ł
I I b y ł y w y ś w ie t la n e n a d n i ą . Na samym k o h cu n a l e ż y o k r e ś l i ć p o ł o ż e n ie g w ia z d y w o b s z a r z e k o n t r o l k i C a n v a s.
}
Kiedy określasz nowe położenie w obszarze kontrolki Canvas, powoduje to
aktualizację kontrolki — nawet je ś li była ju ż wyświetlona. To właśnie w ten sposób
gwiazdy poruszają s ię w oknie aplikacji, kiedy zmienia się obszar pola gry.
jesteś tutaj ► 829
Rozwiązanie ćwiczenia

Długie ćwiczenie
Rozwiązanie
h Poniżej p rze dstaw iliśm y uzupełnione m etody klasy BeeS tarM odel.

using Windows.Foundation;

c la ss BeeStarModel {
public s t a t ic readonly S ize S ta rS iz e = new S ize(1 5 0 , 100);

p riv a te readonly Dictionary<Bee, Point> _bees = new Dictionary<Bee, P o in t> ();


p riv a te readonly D iction ary< Star, Point> _ s ta rs = new D iction ary< Star, P o in t> ();
p riv a te Random _random = new Random();

public BeeStarModel () {
_playAreaSize = Size.Em pty; O to m etody umieszczone w kodzie
} ukrytym k o n tro lki S ta r C o n tr o l:
p u b lic v o id F a d e In () {
public void Update () { Te w ła ściw ości i metody
przedstaw iliśm y ju ż w cześnie j. fa d e In S to ry b o a rd .B e g in ();
MoveOneBee();
AddOrRemoveAStar(); }
}
p u b lic v o id FadeOut() {
p riv a te s t a t ic bool RectsOverlap(Rect r1 , Rect r2) { fa d e O u tS to ry b o a rd .B e g in ();
r 1 .In t e r s e c t (r 2 );
i f (r1.W idth > 0 || r1.H eight > 0) }
return tru e;
return fa ls e ;
}

p riv a te S ize _playAreaSize; Z a każdym razem, gdy zmienia s ię w łaściw ość


public S ize PlayAreaSize { P layA reaSize, model aktualizuje pole wewnętrzne
get { return _playA reaSize; } _ p la y A re a S iz e i wywołuje metody C rea teB ees()
set oraz C re a te S ta rs(). Takie rozwiązanie spraw ia,
że model widoku może poinformować model
{
_playAreaSize = value; o konieczności aktualizacji danych zaw sze, gdy
zm ienia s ię rozm iar strony — a to zdarza się,
C reateBees();
gdy uruchom isz aplikację na tablecie,
C re a te S ta rs ();
a następnie b ęd ziesz zm ieniał jego orientację.

p ri vate void CreateBees () {


i f (PlayAreaSize == Size.Empty) re tu rn ;
J e ś li w modelu
nie ma je sz c z e if (_bees.Count() > 0) {
x List<Bee> allB ee s = _b e e s.K e y s.T o L ist() J e ś li ju ż s ą ja k ie ś pszczoły,
żadnych pszczół,
to ten fragment foreach (Bee bee in allB ees) przesuw am y każdą z nich. Metoda
kodu tworzy MoveOneBee(bee); M °v nOnnBnn() znajdzie nowe,
nowe obiekty ninp ° kryw ającn s ię z niczym
} else {
lokalizacje dla w szystkich pszczół,
B ee i określa ich in t beeCount = _random.Next(5, 16); a następnie wywoła zdarzenie
położenia. Za fo r (in t i = 0; i < beeCount; i++) { B eeM m eed .
każdym razem, in t s = _random.Next(40, 151);
gdy pszczoła S ize beeSize = new S iz e ( s , s ) ;
j e s t dodawana Point newLocation = FindNonOverlappingPoint(beeSize);
lub zmienia s ię Bee newBee = new Bee(newLocation, beeSize);
je j położenie, _bees[newBee] = new Point(newLocation.X, newLocation.Y);
konieczne je s t OnBeeMoved(newBee, newLocation.X, newLocation.Y);
zgłoszenie
zdarzenia }
BeeM oved. }

830 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

p riv a te void CreateStars () {


i f (PlayAreaSize == Size.Empty) re tu rn ; Je ś li gwiazdy ju ż istnieją,
to jedynie znajdujemy dla
if (_stars.C o u n t > 0) { każdej z nich nowe położenie
foreach (S ta r s ta r in _s ta rs .K e y s ) { w obszarze PlayArea
sta r.L o ca tio n = FindNonOverlappingPoint(StarSize); i wywołujemy zdarzenie
OnStarChanged(star, f a ls e ) ; StarChanged. Zdarzenie to
} obsługuje model widoku,
} else { który odpowiednio przesuwa
in t starCount = _random.Next(5, 11); wszystkie kontrolki.
fo r (in t i = 0; i < starCount; i++)
C rea teA Sta r();

p riv a te void CreateAStar () {


Point newLocation = FindNonOverlappingPoint(StarSize); Ten fragment tworzy losowy
S ta r newStar = new Star(new Location); prostokąt i sprawdza, czy nie
_stars[n ew S tar] = new Point(newLocation.X, newLocation.Y); pokrywa s ię on z innymi kontrolkami
OnStarChanged(newStar, f a ls e ) ; na sti^ome. S tosujemy odstęp
i o s zerokości 150 pikseli od prawej
krawędzi obszaru gry oraz
p riv a te Point FindNonOverlappingPoint (S ize s iz e ) { o wysokośd 150 p ikseli od jego dolnej
Rect newRect; krawędzi, aby pszczoły i gwiazdy nie
bool noOverlap = fa ls e ; byty umieszczane poza nim.
in t count = 0;
w hile (!noOverlap) {
newRect = new Rect(_random .N ext((int)PlayA reaSize.W idth - 150),
_random .N ext((int)PlayA reaSize.H eight - 150),
size.W id th , s iz e .H e ig h t);

var overlappingBees = Te zapytania LINQ wywołują mefrdę


from bee in _bees.Keys RectsOverlap(), aby odszukać
where R ectsO verlap(bee.Position, newRect) wszystkie pszczoły i gwiazdy, które
se le ct bee; pokrywają się z nowym prostokątetn.
Je śli którakolwiek ze zwróconych
var overlappingStars = kolekcji wynikowych zawiera jakieś
from s ta r in _s ta rs .K e y s elementy (jej metoda CountO zwfaca
where RectsOverlap( wartość większą od zera), oznacza
new R e c t(sta r.L o c a tio n .X , s ta r.L o c a tio n .Y , to, że nowy prostokąt pokrywa się
S tarS ize.W id th , S ta rS iz e .H e ig h t), z jakąś pszczołą lub gwiazdą.
newRect)
se le ct s ta r ;

i f ((overlappingBees.Count() + overlappingStars.Count() == 0) || (count++ > 1000))


noOverlap = tru e ;
i
return new Point(newRect.X, newRect.Y); Je ś li ta pętla zostanie wykonana więcej
i niż tysiąc razy, będzie to oznaczać,
że trudno je s t znaleźć ja kieś puste
p riv a te void MoveOneBee (Bee bee = n u ll) { m iejsce w obszarze gry i konieczne je s t
i f (_bees.Keys.Count() == 0) re tu rn ; przefwamie nieskończonej pętli.
i f (bee == n u ll) {
List<Bee> bees = _ b e e s .K e y s .T o L is t();
bee = bees[_random.Next(bees.Count)];
}
bee.Location = FindNonOverlappingPoint(bee.Size)
_bees[bee] = bee.Location;
OnBeeMoved(bee, bee.Lo catio n.X , b e e.Lo ca tio n .Y );
}

jesteś tutaj ► 831


Rozwiązanie ćwiczenia

Długie ćwiczenie
Rozwiązanie Rzuć monetą, wybierając losowo wartość 0
J f e O to kilka osta tn ich składow ych klasy B e e S ta rM o d e l. gdy ™h tą c z rn T m m ^ rn ż T f usuwał
1Lgdy j e s t ich więcej niż 20. ' j'
p riv a te void AddOrRemoveAStar () {
i f (((_random .Next(2) == 0) || (_stars.C o u n t <= 5 )) && (_stars.C o u n t < 20 ))
C rea teA Sta r();
else {
S ta r starToRemove = _sta rs.K e y s .T o L is t()[_ ra n d o m .N e x t(_ sta rs.C o u n t)];
_stars.Remove(starToRemove);
OnStarChanged(starToRemove, tru e );
Podczas każdego wywołania metody Update()
chcemy usunąć lub dodać gwiazdę. Gwiazdy
}
} tworzy ju ż metoda CreateAStar(). Je śli chcemy
usunąć gwiazdę, to wystarczy usunąć ją
public event EventHandler<BeeMovedEventArgs> BeeMoved ; z kolekcji _ s ta r s i wywołać zdarzenie
StarChanged.
p riv a te void OnBeeMoved (Bee beeThatMoved, double x , double y)
{
EventHandler<BeeMovedEventArgs> beeMoved = BeeMoved;
i f (beeMoved != n u ll)
{
beeMoved(this, new BeeMovedEventArgs(beeThatMoved, x , y ) ) ;
}
}
To typowe procedury
public event EventHandler<StarChangedEventArgs> StarChanged; obsługi zdarzeń
oraz metody do ich
p riv a te void OnStarChanged(S ta r starThatChanged, bool removed) wywoływania.
{
EventHandler<StarChangedEventArgs> starChanged = StarChanged;
i f (starChanged != n u ll)
{
starChanged(this, new StarChangedEventArgs(starThatChanged, removed));
}
}

Poniżej p rze dstaw iliśm y uzupełnione m etody klasy B eeS tarV iew M odel.

using View;
using Model;
using System .Collections.ObjectM odel;
using S yste m .C o lle ctio n s.S p e cialize d ;
using Windows.Foundation;
using DispatcherTimer = Windows.UI.Xaml.DispatcherTimer;
using UIElement = Windows.UI.Xaml.UIElement;
Ten kod przedstawiliśmy
c la ss BeeStarViewModel { ju ż wcześniej.
p riv a te readonly ObservableCollection<UIElement>
_sprites = new ObservableCollection<UIElement>();
public INotifyCollectionChanged Sprites { get { return _ s p r it e s ; } }

p riv a te readonly D ictio nary< Star, StarControl>


_stars = new D ictio nary< Star, S tarC o n tro l> ();
p riv a te readonly List<StarControl> _fadedStars = new List< S tarC o n tro l> ();

p riv a te BeeStarModel _model = new BeeStarModel();

p riv a te readonly Dictionary<Bee, AnimatedImage> _bees


= new Dictionary<Bee, AnimatedImage>();

p riv a te DispatcherTimer timer = new D ispatcherTim er();

832 Rozdział 16.


Tworzenie aplikacji według wzorca MVVM

Właściwość PlayArea obiektu modelu widoku jest przekazywana


Jeś/i udało Ci się dobrze przeprowadzić dalej, do właściwości modelu — jednak akcesor set właściwości
separację zagadnień, to zazwyczaj ______ PlayAreaSize obiektu modelu w yw ołuje metody zgłaszające
e/ementy Twoich projektów w natura/ny zdarzenia BeeMoved oraz StarChanged. Oto co się dzieje
sposób będą /uźno powiązane. w momencie zmiany rozdzielczości ekranu: 1) kontrolka Canvas
w yw ołuje zdarzenie SIzeChanged, które 2) zmienia wartość
public S ize PlayAreaSize { właściwości P layA reaS ize obiektu modelu widoku, co 3) powoduje
get { return _m odel.PlayAreaSize; } aktualizację właściwości modelu, a to z kolei 4) powoduje
set { _m odel.PlayAreaSize = valu e; } wyw ołanie metod do aktualizacji pszczół i gwiazd, co z kolei 5)
} powoduje w yw ołanie zdarzeń BeeMoved oraz StarChanged, to
natomiast 6) powoduje wyw ołanie procedur obsługi zdarzeń
public BeeStarViewModel() {
widoku, to z kolei powoduje 7) aktualizację kolekcji S p rite s , a to
_model.BeeMoved += BeeMovedHandler;
_model.StarChanged += StarChangedHandler; w końcu 8) aktualizuje kontrolki umieszczone wewnątrz kontrolki
_tim e r.In te rv a l = TimeSpan.FromSeconds(2); Canvas. To jest właśnie przykład luźnych połączeń, czyli sytuacji,
_tim e r.T ic k += tim e r_T ick ; w której nie istnieje jeden centralny obiekt koordynujący działanie
_ t im e r .S t a r t (); aplikacji. To bardzo stabilny sposób tworzenia oprogramowania,
} gdyż żaden z obiektów nie musi dysponować jawną znajomością
sposobów działania pozostałych obiektów. Poszczególne obiekty
void timer_Tick(object sender, object e) { muszą natomiast dobrze realizować jedno niewielkie zadanie:
foreach (StarControl StarControl in _fadedStars)
obsługiwać zdarzenie, wyw oływ ać zdarzenie, wywoływać metodę,
_sp rite s.R em o ve (sta rC o n tro l);
ustawiać wartość właściwości itd.
_model.Update();
}

void BeeMovedHandler(object sender, BeeMovedEventArgs e) {


i f (!_bees.ContainsKey(e.BeeThatMoved)) {
AnimatedImage beeControl = BeeStarFlelper.BeeEactory(
e.BeeThatMoved.Width, e.BeeThatMoved.Height, Tim eSpan.Erom M illiseconds(20));
BeeStarFlelper.SetCanvasLocation(beeControl, e. X, e. Y) ;
_bees[e.BeeThatMoved] = beeControl;
_sp rite s.A d d (b ee C o ntro l);
} else {
AnimatedImage beeControl = _bees[e.BeeThatMoved];
BeeStarFlelper.MoveElementOnCanvas(beeControl, e. X, e. Y) ;
}
}

void StarChangedHandler(object sender, StarChangedEventArgs e) {


i f (e.Removed) {
StarControl starControl = _stars[e.StarTh atC han ged ];
_stars.Rem ove(e.StarThatChanged);
_fa d ed Stars.A d d (starC o n tro l); ^---- Ko/ekcja _fadedS tars zawiera g w ia ^ y ,
starC o n trol.Ead eO ut(); które aktua/nie znikają i zostaną
} else { usunięte podczas ko/ejnego wywołania
StarControl newStar; metody Update().
i f (_stars.ContainsKey(e.StarThatChanged))
newStar = _stars[e.StarTh atC han ged ];
else {
newStar = new S ta rC o n tro l();
_stars[e.StarThatChanged] = newStar;
new Star.Ead eIn();
BeeStarFlelper.SendToBack(newStar);
_sp rite s.A d d (n ew S tar);

BeeStarFlelper.SetCanvasLocation(
newStar, e.StarThatChanged.Location.X, e.StarThatChanged.Location.Y);

Jeśli gwiazda została dodana, to konieczne je s t wywołanie je j


metody FadelnO. Jeśli gwiazda ju ż istnieje, to je s t jedynie
przesuwana, gdyż zm ieniła się wielkość obszaru gry. W obu
przypadkach chcemy przenieść gwiazdę w nowe położenie
w obszarze kontrolki Canvas. jesteś tutaj ► 833
Jesteś profesjonalnym programistą C#

Gratulujemy! (Choćjeszcze nie skończyłeś...)


U d a ło C i się skończyć ostatnie ćwiczenie? Czy rozum iesz wszystko, co się działo w napisanej aplikacji? Jeśli tak,
to g ra tu lu je m y — nauczyłeś się naprawdę bardzo dużo o C # i to najp raw d op odo bn iej w krótszym czasie, niż
przypuszczałeś! T eraz świat p rogram ow ania stoi przed T obą otworem .

N ie m n ie j je d n a k wciąż jest jeszcze k ilk a spraw, k tó ry m i pow inieneś się zająć, zanim przejdziesz do ostatniego
la b o ra to riu m , je ś li chcesz, by in fo rm acje , k tó re ud ało C i się w tłoczyć do swojego m ózgu, pozostały w nim .

Jeszcze raz przeanalizuj aplikację Ratuj ludzi.


Ich unikaj
Jeśli zrobiłeś wszystko, o co prosiliśm y, to dwa razy napisałeś aplikację Ratuj ludzi —
pierw szy raz na samym początku tej książki, a następnie jeszcze raz przed rozpoczęciem
le k tu ry rozd zia łu 10. Jednak nawet gdy pisałeś tę aplikację po raz drugi, to były takie
fragm enty, k tó re m ogły wydawać się dosyć magiczne. A le w pro gra m ow a niu nie
$ m a niczego m agicznego. A zatem przeanalizuj napisany ko d jeszcze raz. Będziesz
zdum iony, ja k w iele teraz rozum iesz! N ic le pie j nie utrw a la wyuczonej le kcji, ja k
pozytywne po tw ie rdzen ie swojej wiedzy.

Porozmawiaj o tym ze swoimi przyjaciółmi.


W programowaniu L u dzie są zw ierzętam i społecznym i i je śli rozmawiasz
o tym , czego się nauczyłeś, z gronem swoich znajom ych,
nie ma niczego dodatkow o utrwalasz swą wiedzę w pam ięci. A dziś
te „ro zm o w y” oznaczają także korzystanie z sieci
magicznego. społecznościowych! Poza tym podczas le k tu ry tej książki
ju ż coś osiągnąłeś. A zatem nie ociągaj się —
Każdy program p o ch w a l się sw ym i o sią g n ię cia m i!

działa,
Odpocznij. A może nawet lepiej
gdyż zo sta ł — utnij sobie drzemkę.
T w ój mózg przysw oił bardzo dużo in fo rm a c ji, a czasami
odpowiednio najlepszym , co możesz zrobić, aby tę całą wiedzę
„zatrzym ać” , jest przespać się. Istn ie je bardzo dużo
napisany, a każdy badań nad pracą m ózgu, k tó re w ykazują, że przyswajanie

fragm ent kodu in fo rm a c ji znacząco popra w ia się po d o b rze p rze spa nej
nocy. D lateg o zapew nij swojem u m ózgow i dobrze
można zrozum ieć. zasłużony odpoczynek!

K
.■.choć kod można
tatw'iej zrozumieć,
je śli programista
wykorzystał odpowiednie
wzorce projektowe
i zasady programowania
obiektowego.

834 Rozdział 16.


Imię i Nazwisko: Data:

Invaders
To laboratorium zawiera specyfikację opisującą program, który
musisz napisać, wykorzystując wiedzę zdobytą podczas lektury
tej książki.
Ten projekt jest większy niż te, które widziałeś do tej pory.
Przeczytaj więc uważnie wszystko, zanim przystąpisz do pisania,
i przeznacz na to trochę czasu. Jeśli wykonałeś wszystkie
ćwiczenia zamieszczone w książce, to dysponujesz wszystkimi
narzędziami niezbędnymi do napisania tego laboratorium.
Uzupełniliśmy część detali projektu za Ciebie i upewniliśmy się,
że masz wszystkie potrzebne elementy... i nic więcej.
Do Ciebie należy ukończenie pracy. Możesz pobrać naszą
wersję gotowej gry ze Sklepu Windows. Jest ona dostępna
w formie projektu otwartego, zatem jest dostępny także jej
kod ź ró d ło w y . niemniej jednak nauczysz się znacznie więcej,
jeśli spróbujesz napisać ją samodzielnie!
Więcej informacji na ten tem at można znaleźć w witrynie
poświęconej książce: http://www. headfirstlabs.com/hfcsharp.

L A

Laboratorium C# 835
Invaders

Dziadek wszystkich gier


D z ię k i tem u la b o ra to riu m oddasz h o łd jednej z najbardziej popularnych,
czczonych i najczęściej pow ielanych iko n w h is to rii gier kom puterow ych.
Gr-acz, niszcząc ko/ejne statki,
N ie po trze bu je ona żadnego w prow adzenia. Czas u tw o rz y ć g rę In v a d e rs . powiększa swój wynik, który je s t
w y ś w ie tta y w prawym górnym rogu.
Najeźdźcy atakują w f a ła ^ s kładaj ącyęh
s ię z 11 kolumn po 6 d a tk ó w w ^każdej .
Invaders to aplikacja Pierwsza fala porusza _s ię powol' 1 strzela
mniejszą liczbą p o cztó w . Kolej na Gracz rozpoczyna zabawę,
przeznaczona dla ?klepu mając do dyspozycji trzy
Windows, utworzona porusza s ię szy bci'ej , p rz e to cała
i oddaje większą liczbę s trzatow. Gdy cała s ta tk i. Pierwszy uczestniczy
na podstawie szablonu w grze, a pozostałe dwa są
B asic Page. fala najeźdźców zostanie z likwidowana,
wtedy atakuje nas tępna. w rezerwie. Niewykorzystane
s ta tk i pokazywane są
w prawym górnym rogu.

S ta tk i obcych
są animowane
i mają duże
pikse/e
przypominające
grafikę z /a t 80.
ubiegłego wieku.
Obszar gry ma
proporcje 4:3,
podobnie ja k
stare automaty
do gier;
dodatkowo, aby
gra wyg/ądała
autentycznie,
w je j obszarze
widać
symu/owane /inie
znane ze starych
monitorów.

Gracz porusza s ta tkiem


w /ewo oraz w p ra wo Wrogowie odpowiadają °9niem- Wie/obarwne gwiazdy
i s trze/a do przeciwm tów. Jeśli tra fią gracza, tw e . on jedno cały czas migotają,
Jeś/i pocisk tra fi życie. Gdy gracz utraci wszystkie a/e nie mają wpływu
w najeźdźcę, je s t on życia lub najeźdźcy znajdą się na rozgrywkę.
niszczony, a /iczba punktów na samym dole ekranu, gra się
gracza zwiększa się. kończy, a na środku monitora

836
Invaders

Twoja misja: obronić planetę Pamiętaj, że grafiki do gry są dostępne


przed kolejnymi falami najeźdźców w przykładach dołączonych do książki,
które możesz pobrać z serwera FTP
Najeźdźcy atakują fala m i, a każda z nich jest zwartą form acją
składającą się z 66 w rogów . W m iarę ich likw id o w a n ia rośnie wydawnictwa Helion: ftp://ftp.helion.pl/
liczba punktów . W rogowie znajdujący się w dwóch dolnych rzędach przyklady/cshru3.zip.
m ają postać gwiazd i wartość 10 pu nktów . S atelity są w arte
20 pu nktów , latające spodki 30, za zniszczenie robaka otrzym uje się I stn ieje pięć typów najeźdźców, ale
40 pu nktów , a nieznośne statki obcych, któ rzy najeżdżali Z ie m ię już zachowują s ię one w ten sam sposób.
Rozpoczynają na górze ekranu i poruszają
od pierwszego rozd zia łu tej książki, są w arte 50. Gracz rozpoczyna s ię w leiwo aż do osiągnięcia krawędzi.
zabawę, posiadając trzy życia. G dy wszystkie trzy straci lu b najeźdźcy Przesuwają s ię wtedy w dół i zaczynają się
poruszać w prawo. Po dotarciu do prawej
znajdą się na dole ekranu, gra się kończy. krawędzi znów schodzą niżej i lecą w lewą
s t ronę . J eże li riotrą na sam dół ekranu, gra
s ię kończy.

S3 120 30 40

Pierwsza fala
najeźdźców może
jednocześnie
strzelać dwoma
pociskami — będą
oni wstrzymywać
ogień, gdy na
ekranie będą dwa Gra powinna
pociski lub więcej. przechowywać
Następna fala informację o wszystkich
może wystrzelić naciśnięciach
j ednocześnie trzy Mawiszy. Naciśnięcie
Gracz może strzelać, pociski, kolejna strzałki w prawo
dotykając ekranu lub cztery i tak daty. i spacji spowoduje
naciskając klawisz spacji. przemieszczenie
Na ekranie w danej cnwili s ię statku w prawo
mogą znajdować się jednak Je ś li pocisk i wystrzelenie pocisku
tylko trzy pociski gracza. uderzy wroga, (chyba że na ekranie
Gdy pocisk w coś uderzy to oba obiekty znajdują s ię ju ż trzy).
lub zniknie, to może zostać znikają.
wystrzelony kolejny. W przeciwnym
razie pocisk znika
dopiero wtedy,
S P A C fi gdy wychodzi
poza ekran.

P r z e c ią g n ię c ie p a lc e m w lewo lub n a c iś n ię c ie Przeciągnięcie palcem w prawo lub


klawisza strzałki w lewo przesuwa statek naciśnięcie klawisza strzałki w prawo
w stronę lewej krawędzi ekranu. przesuwa s tate k w prawą stronę.

837
Invaders

Architektura gry Invaders


Invaders jest aplikacją M V V M . M o d e l m usi przechowywać in fo rm acje
o fa li najeźdźców (włączając w to ich położenie, typ, w artość punktow ą), Gtówny obszar gry je s t
kontrolką ItemsControl,
statku gracza, pociskach wystrzelonych przez gracza i najeźdźców oraz
której szablon
gwiazdach w tle. W id o k używa szablonu Basic Page i k o n tro le k służących ItemsTemplate zawiera
do prezentow ania anim owanych obrazków i gwiazd, ja k rów nież statycznej kontrolkę Canvas
i której właściwość
klasy pom ocniczej, wspomagającej m od el w idoku . Item sSource je s t
powiązana z kolekcją
O gólny zarys ob ie któ w , k tó re będziesz m usiał utworzyć, ObservableCollection
zawierającą kontrolki.
przedstaw ia się następująco:

Ta aplikacja składa s ię ze strony


B asic Page, zawierającej kontrolkę
ItemsControl reprezentującą obszar Obi'ekt modelu widoku oczekuje na
gry, gdzie wszystko s ię dzieje, zdarzenia generowane przez model
GridView służącej do wyświetlania i używa ich do aktualizacji kolekcji
pozostałych statków gracza kontrolek, z którymi je s t powiązany
TextBlock prezentującej wy nik, widok. Oprócz tego zgłasza on zdarzenia
Popup wyświetlającej informacje PropertyChanged, by poinformować
o aplikacji oraz kilka dodatkowych widok, kiedy zmieni s ię liczba żyć
kontrolek TextBlock oraz przycisków, gracza, kiedy gra zostanie wstrzymana
używanych gdy gra je s t wstrzymana lub zakończona.
lub zakończona.

N ie spotkałeś się jeszcze z k o n tro lk ą <Popup>.


W pro gra m ow a niu bardzo ważna jest um iejętność określania, ja k używać narzędzi, z któ rym i
jeszcze się nie spotkaliśm y. D o da liśm y do tej a p lika cji nową ko n tro lk ę , Popup, któ re j użyjesz
do w yśw ietlenia dodatkow ych in fo rm a c ji o ap lika cji, gdy u żytko w n ik tego zażąda.

838
Invaders

Wszystkie s ta tk i najeźdźców
wyświet/one na ekranie są
przechowywane w obiekcie List.
Kiedy statek zostaje zniszczony,
je s t usuwany z /is ty.

°6,eki ?\cff
Obiekt reprezentujący statek
przechowuje informacje o jego
położeniu i sam się przesuwa
w /ewo i prawo, dbając
jednocześnie o to, by nie
przekroczyć krawędzi po/a gry.

Gra przechowuje
/istę obiektów Shot
reprezentujących
strzały, i to zarówno
tych oddanych
przez gracza do
najeźdźców, jak
i strza ły oddawane
do gracza. Za
każdym razem,
gdy s trz a ł zostaje
dodany, przesunięty
/ub usunięty, obiekt
InvadersMode/
O biekt S tars dysponuje /is tą (typu L is t)
zgłasza zdarzenie
s tru k tu r Point, przy czym każdej gwieździe
ShotMoved.
migającej w t/e odpowiada jedna struktura .
Obiekt InvadersMode/ zgłasza zdarzenia
StarChanged, aby dodawać i usuwać
gwiazdy, dzięki czemu będą one spraw iały
'/s f < P o ^ wrażenie migotania.

839
Invaders

Napisz klasy stanowiące model aplikacji


Z a n im zabierzesz się za pisanie klasy InvadersModel, będziesz m usiał utw orzyć kilka
Wykrywanie
kolizji oznacza
klas, któ re będą używane przez m od el do nadzorow ania przebiegu gry. M o d e l będzie określanie, kiedy
dysponował ob ie kte m gracza oraz ko le kcja m i statków najeźdźców, strzałów i gwiazd. dwa poruszające
s ię sprajty zderzyły
Oznacza to, że będziesz po trze bo w ał klas reprezentujących najeźdźców oraz strzały s ię ze sobą.
(będzie ona używała stru k tu ry Point do określenia po ło żenia każdej gwiazdy).

K lasy Player oraz Invader będą dzied ziczyć po a b s tra k c y jn e j k la s ie S h ip ,


dysponującej właściwościam i (określanym i w ko n stru kto rze ) przechow ującym i położenie
i w ielkość statku. Klasa ta dysponuje także w ygodną właściwością, k tó ra na podstawie
położenia i w ielkości statku zwraca stru ktu rę Rect, k tó re j m ożna używać do w ykryw ania
k o liz ji. Będziesz m usiał zaim plem entow ać te dw ie klasy pochodne.

O to abstrakcyjna klasa Ship, k tó rą pow inieneś um ieścić w folderze M odel:


Akcesor se t
właściwości Location
je s t zadeklarowany
jako chroniony,
zatem mogą z niego
korzystać jedynie
klasy pochodne.

Player Invader
static PlayerSize: Size static InvaderSize: Size
InvaderType: InvaderType
Score: int

Move(Direction) Move(Direction)
ctor: InvaderType, Point,
Size

G racz przesuw a s w ó j s ta te k w le w o i w praw o. S ta tk i n a je ź d ź c ó w p rz e s u w a ją s ię


M o d e l będzie w yw oływ ał m etodę Move() o b ie ktu Player , w le w o , w p ra w o oraz w dół.
by po in fo rm ow a ć go o tym , że należy przesunąć statek. Klasy Invader oraz Player posiadają m etody Move(),
K ie ru n e k będzie określany poprzez przekazanie w artości typu które określają kie run ek ruchu statku, używając do tego
w yliczeniow ego Direction . G racz nie może przesunąć swojego celu in stru kcji switch . Klasa Invader definiuje także
statku poza obszar gry. A b y zatrzym ać statek gracza, kie dy dotrze dodatkow y kon stru ktor, którego param etry pozwalają
do kraw ędzi obszaru gry, m ożna w ykorzystać statyczną właściwość na określanie właściwości InvaderType oraz Score.
PlayAreaSize o b ie ktu InvadersModel. W obiekcie Player O kreślają one, k tó ra grafika zostanie w yśw ietlona na
będziesz także potrze bo w ał statycznej właściwości ty lk o do stronie oraz ile p u nktów zostanie dodanych do w yniku,
odczytu o nazwie Size (określającej w ym iary statku ja k o 2 5 x 1 5 gdy gracz zniszczy dany statek.
p ikse li) oraz stałej typu double określającej jego szybkość (będzie
się przesuw ał o 10 p ikse li na każde w yw ołanie m etody Move()).
840
Invaders

Możesz
class S h o t {
przyspieszyć
publ ic const double Sh o t P i x e l s P e r S e c o n d = 95;
lub zwolnić
strzał,
publ ic Point Location { get; pr ivate set; } zmieniając
publ ic static Size S h ot Si ze = new Size(2, 10); tę wielkość
przesunięcia,
pr iv at e Dire ct io n _d ir ection; wyrażoną T a k la s a Shot b ę d z ie C i p o t r z e b n a .
publ ic Di rection Di re ct io n { get; pr iv at e set; } w pikselach. M o d e l używa jej do śledzenia strzałów oddanych
przez gracza do statków najeźdźców, ja k
pr iv at e D a te Ti me _l astMoved;
rów nież strzałów oddanych przez najeźdźców
publ ic S h ot (P oi nt location, Dire ct io n direction ) { do gracza. Przyjrzyj się dokładniej je j m etodzie
Location = location; Move(): używa ona prywatnego po la typu
_ d ir ec ti on = direction; DateTime, przechowującego inform ację o tym
_ l a s t M o v e d = DateTime.Now; kiedy nastąpiło ostatnie przesunięcie strzału.
} Podczas każdego w yw ołania m etody położenie
strzału (określane przez właściwość Location)
publ ic void Move () {
Time Sp an ti m e S i n c e L a s t M o v e d = Da te T i m e . N o w - _l astMoved; jest przesuwane w górę lub w d ó ł z szybkością
doub le d i st an ce = ti me Si nc eL a s t M o v e d . M i l l i s e c o n d s 95 pikseli na sekundę.
* Sh ot Pi xe l s P e r S e c o n d / 1000; Oprócz tego będziesz także potrzebował
if (Direction == Direction.Up) dist an ce *= -1;
przedstawionych obok trzech klas EventArgs,
Location = new Po int(Location.X, Location.Y + distance);
lastMoved = DateTime.Now;
używanych przez m odel do inform ow ania
m odelu w idoku o pojaw ianiu się lub znikaniu
gwiazd, przesunięciach, pojaw ianiu się lub
znikaniu strzałów oraz przesunięciach lub
class S t a r C h a n g e d E v e n t A r g s : EventArgs { zniszczeniach statków. K iedy gracz lub jakiś
public Point Point { get; pr iv at e set; }
statek najeźdźcy oddadzą strzał, m odel
public bool D i sa pp ea re d { get; pr iv at e set; }
utworzy obiekt Shot, a następnie wygeneruje
public S t a r C h a n g e d E v e n t A r g s (Point point,
zdarzenie ShotMoved. O bie kt m odelu
bool disappeared) {
Point = point; w idoku obsłuży to zdarzenie i zaktualizuje
Di sa p p e a r e d = disappeared; swoją kolekcję Sprites , która z kolei
} poin fo rm uje o zmianach obiekt widoku.
}
Ten typ
class S h o t M o v e d E v e n t A r g s : EventArgs {
publ ic Shot Shot { get; pr iv at e set; }
wyliczeniowy
określa
publ ic bool D i sa pp ea re d { get; pr ivate set; }
rodzaj sta tk u
pilotowanego
publ ic S h o t M o v e d E v e n t A r g s (Shot shot, bool disappeared) { przez
Shot = shot; najeźdźcę.
D i sa pp ea re d = disappeared;
} enum InvaderType
Bug,
Saucer,
S a t e llit e ,
class S h i p C h a n g e d E v e n t A r g s : Ev entArgs { Spaceship,
public Ship Sh ip U p d a t e d { get; pr iv at e set; } S ta r,
public bool K i l l e d { get; private set; } }

public S h i p C h a n g e d E v e n t A r g s (Ship shipUpdated, bool killed) { Statki oraz strzały


S h ip Up da te d = shipUpdated; używają tego typu
Killed = killed; wyliczeniowego
} do określania
kierunku, w jakim
s ię przesuwają.

841
Invaders

Tworzenie klasy InvadersModel


Klasa In va d e rsM o d e l zarządza całą grą. Poniżej pokazaliśm y, ja k w ygląda je j początkow y
fragm e nt — wciąż je d n a k znaczną część je j ko d u będziesz m usiał napisać sam.

u s in g W indow s.F oundation;

c la s s InvadersM odel j
p u b lic re a d o n ly s t a t i c S ize P la yA rea S ize = new S iz e (4 0 0 , S00);
p u b lic co n st i n t MaximumPlayerShots = S;
p u b lic co n st i n t I n it ia lS t a r C o u n t = 50;

p r iv a t e re a d o n ly Random _random = new Random();

p u b lic i n t Score { g e t; p r iv a t e s e t ; }
p u b lic i n t Wave { g e t; p r iv a t e s e t; }
Kiedy gracz ginie, obiekt modelu widoku
p u b lic i n t L ive s { g e t; p r iv a t e s e t ; } sprawia, że jego statek będzie migotał przez
2,5 &ekundu. Model używa prywatnego pola
p u b lic bool GameOver { g e t; p r iv a t e s e t ; } typu Date Time?, by zapamiętać, kiedy to
nastąpiło, i uniemożliwić przesuwanie statków
i strzałów w czasie, gdy gracz umiera.
p r iv a t e DateTime? _ p la y e rD ie d = n u l l ;
p u b lic bool P la ye rD yin g { g e t { r e tu r n p la y e rD ie d .H a s V a lu e ; } }

p r iv a t e P la y e r _ p la y e r ;

p r iv a t e re a d o n ly L is t< In v a d e r> _ in v a d e rs = new L is t< In v a d e r > ( ) ;


p r iv a t e re a d o n ly L is t< S h o t> _ p la y e rS h o ts = new L is t< S h o t> () ;
p r iv a t e re a d o n ly L is t< S h o t> _ in v a d e rS h o ts = new L is t< S h o t> ( );
p r iv a t e re a d o n ly L is t< P o in t> _ s ta r s = new L is t< P o in t > ( ) ;

p r iv a t e D ir e c tio n _ in v a d e r D ir e c tio n = D ir e c t io n . L e f t ;
p r iv a t e bool _justMovedDown = f a ls e ;

p r iv a t e DateTime _ la s tU p d a te d = D ateTim e.M inV alue;

p u b lic In va d e rsM o d e l() {


EndGame();
}

p u b lic v o id EndGame() {
GameOver = t r u e ;
}

// B ę d z ie s z m u s ia ł d o k o ń c z y ć r e s z t ę k o d u k l a s y In v a d e r s M o d e l.

a42
Invaders

Metody klasy InvadersModels


Klasa InvadersModel d e fin iu je sześć m e to d publicznych, używanych przez klasę m od elu w idoku.
M e to d aEndGame() została przedstaw iona na poprzedniej stronie — poniżej opisaliśm y pozostałe:

O m e to d a startgame O r o z p o c z y n a g r ę .
T a m etoda przypisuje właściwościGameOver wartość fa lse . Następnie usuwa wszystkie statki najeźdźców z kolekcji
_invaders _playerShots oraz _invaderShots (jednak zanim to zrobi, dla każdej z nich
oraz strzały z kolekcji
generuje zdarzenia ShipChanged oraz ShotMoved). Następnie m etoda czyści istniejące gwiazdy (generując dla każdej
z nich zdarzenie StarChanged) i tw orzy nowe. W końcu m etoda tw orzy nowy ob ie kt Player (generując zdarzenie
ShipChanged), przypisuje właściwości Lives wartość 2, właściwości Wave wartość 0 i dodaje pierwszą falę najeźdźców.

S METODA FIRESHOTO POWODUJE WYSTRZELENIE W STRONĘ NAJEŹDŹCÓW.


M e to d a sprawdza liczbę strzałów gracza w idocznych na ekranie, upew niając się, że nie jest ich zbyt dużo,
a następnie dodaje nowy o b ie k t Shot do ko le k c ji _playerShots i generuje zdarzenie ShotMoved.

S METODA MOVEPLAYER () PRZESUWA STATEK GRACZA.


Jeśli gracz ju ż zginął, to m etoda niczego nie ro b i. W przeciw nym razie w yw ołuje m etodę Move() o b ie ktu Player ,
a następnie generuje zdarzenie ShipChanged, by p o in fo rm o w a ć o b ie k t m odelu w id o k u o przesunięciu statku.

METODA TWINKLE() MIGOTA GWIAZDAMI.


T a m etoda rzuca m onetą i dodaje bądź usuwa gwiazdę, generując przy tym zdarzenie StarChanged.
G w iazd jest zawsze m niej niż 150% ich początkow ej liczby oraz więcej od 15% ich początkow ej wartości.

S m e to d a UPDATE() p o d t r z y m u je g r ę .
O b ie k t m od elu w id o k u używa zegara, by wyw oływ ać m etodę Update() w iele razy na sekundę, o ile tylko gra
jeszcze nie została zakończona — to właśnie dzięki tem u gra może się toczyć. W pierwszej kolejności m etoda
ta sprawdza, czy gra nie została wstrzym ana. Jeśli nie została, to m etoda w ykonuje następujące czynności
(bo gw iazdam i m ruga zawsze, niezależnie od tego czy gra się toczy, czy jest wstrzym ana):

★ Jeśli nie m a ju ż statków najeźdźców, to generuje nową falę.

★ Jeśli gracz nie zginął, to przesuwa wszystkie statki najeźdźców (więcej in fo rm a c ji na ten tem at podam y na
następnej stronie).

★ N astępnie aktu alizuje wszystkie strzały (jeśli gracz nie zginął). G ra m usi zaktualizow ać w p ę tli obie kolekcje
strzałów, w yw ołując m etodę Move() każdego z nich. Jeśli k tó ry k o lw ie k strzał przekroczył granice obszaru gry,
zostanie usunięty, a m etoda zgłasza zdarzenie ShotMoved.
★ Najeźdźcy odpow iadają ogniem (więcej na ten te m a t napiszem y na następnej stronie).

★ W końcu m etoda sprawdza ko lizje : w pierwszej kolejności szukając strzałów, k tó re tra fiły w statki najeźdźców
(i usuwa oba z odpow iednich k o le k c ji), a następnie szukając strzałów, k tó re tra fiły w statek gracza. T o
w tym m iejscu bardzo się przyda właściwość Rect klasy bazowej Ship — możesz skorzystać z przedstaw ionej
w rozdziale 16. m etody sprawdzającej, czy p ro sto kąty zachodzą na siebie, aby wykryw ać ko liz je (więcej na ten
tem at napiszemy na następnej stronie).

O to podpow iedź: Jeśli spróbujesz usunąć ob ie kt z kolekcji podczas przeglądania jej zaw artości w pętli
fo re a c h , kolekcja zgłosi w yjątek. Jednak możesz skorzystać z m etody rozszerzenia LINQ T o L i s t ( ) ,
aby n a jp ie rw zrobić kopię kolekcji, a dopiero później w ykonać pętlę.

843
Invaders

Wypełnianie klasy (nvadersModeI


Na p ierw szy r z u t oka inform acje
P roblem z diagram am i klas polega na tym , że zazwyczaj p o m ija ją niepubliczne m etody zam ieszczone na n a stęp n ą )
i właściwości. N a w et je ś li powstawiałeś ju ż wszystkie m etody z poprzedniej strony, stro n ie mogą s i ę wydawać -
zfożone, jed n ak każde w p y ta m e
w dalszym ciągu pozostało C i w iele do zrobienia. O to k ilk a rzeczy, o których LINQ skfada s i ę jed y n ie z ki i ku
pow inieneś pomyśleć: w ierszy kodu. Oto podpow iedz,
n ie komplikuj ich zbytnio!

Gra jest toczona w obszarze o wymiarach 4 0 0 x 3 0 0


Pierwszy wiersz kod u klasy InvaderModel tw orzy pole pu bliczne typ u Size o nazwie
PlayAreaSize. Jest to statyczne pole ty lk o do odczytu, co oznacza, że jego wartość nie " \
Powiemy sob ie o tych
może się zm ienić podczas życia o b ie k tu InvaderModel. Pole to określa granice obszaru obiektach na kilku kolejnych
gry dla wszystkich o b ie któ w m odelu: strzały mogą używać go do określania, czy nie stroncich laboratorium-
przekroczyły górnej bądź dolnej kraw ędzi obszaru gry, a statki najeźdźców — czy nie
d o ta rły do jednej z jego bocznych kraw ędzi. O b ie kty umieszczone w obiekcie w id o ku
będą się zazwyczaj poruszały p o ko n tro lce Canvas o w ym iarach przekraczających
4 0 0x3 00 , dlatego jednym z zadań o b ie ktu m od elu w id o k u będzie przeskalowanie
wszystkich współrzędnych w górę tak, by o b ie kty znalazły się w odpow iednich miejscach.

Stwórz metodę NextWave()


Przyda się prosta m etoda do tw orzenia następnej fa li najeźdźców. Pow inna ona
inkrem entow ać właściwość Wave, czyścić pryw atną kolekcję _invaders , następnie
stworzyć wszystkie o b ie kty Invader i przekazać do każdego z nich współrzędne
określające jego aktualne położenie, k tó re zostaną zapisane w p o lu Location . Spróbuj
rozm ieścić statki najeźdźców w ta k i sposób, by były one oddalone od siebie w po zio m ie
o 1,4 szerokości statku oraz o 1,4 wysokości statku w pionie.
Oto pnzykład prywatnej
metody, która naprawdę
Kilka sugestii dotyczących metod prywatnych pom° że w lepszej organizacji
klasy modelu widoku.
O to k ilk a pom ysłów na pryw atne m etody, któ re możesz utworzyć.
Sprawdź, czy pom ogą one w lepszym zap rojektow a niu klasy Game.
S tatki najeźdźców poruszają
s ię sa modzielnie od jednej
★ M etoda sprawdzająca, czy gracz został tra fio n y ( CheckForPlayerCollisions() ).
krawędzi pola gry do drugiej.
★ M e to d a sprawdzająca, czy najeźdźca został tra fio n y Wedy dotrą do krawędzi pola gry,
przesuwają s ię w dół. Metoda
( CheckForInvaderCollisions() ). stużąca do przesuwania całej
★ M e to d a do poruszania całą grupą najeźdźców ( MoveInvaders()). ^ -~ fali najeźdźców wywołuje metodę
Move() każdego statku. Może ona
★ M e to d a pozwalająca najeźdźcom dać ognia ( ReturnFire() ). s korzysta ć z pola _lastUpdated,
by przyspieszać ruch najeźdźców,
gdy ich liczba w formacji będzie

WYSIL się zmniej szać, redukując


okres czasu pomiędzy kolejnymi
przesunięciami.
SZARE KOMÓRKI
Istnieje możliwość pokazania w diagramie klas chronionych I prywatnych
właściwości i metod, ale najczęściej się tego nie robi. Jak myślisz, dlaczego?

844
Invaders

LINQ znacznie ułatwia wykrywanie kolizji


Posiadasz kolekcje najeźdźców i pocisków. M usisz teraz je przeszukać i znaleźć
określone ich elem enty. Z a każdym razem, gdy słyszysz słowa „k o le k c je ” i „szuka nie”
w tym samym zdaniu, pow inieneś pom yśleć o L IN Q . O to co musisz zrobić:

J S P R A W D Z IĆ , C Z Y F O R M A C J A N A J E Ź D Ź C Ó W O S IĄ G N Ę Ł A K R A W Ę D Ź P O L A W A L K I .
Najeźdźcy muszą zmieniać kierunek za każdym razem, gdy jeden z nich znajduje się w odległości podwojonego
jednokrotnego przesunięcia od krawędzi pola walki. Gdy poruszają się w prawo i są ju ż bliscy osiągnięcia prawej krawędzi,
gra musi nakazać im przesunąć się w dół i rozpocząć poruszanie się w lewo. Kiedy eskadra porusza się w tę stronę,
gra musi sprawdzać, czy nie została osiągnięta lewa krawędź. A by wykonywać taką procedurę, dodaj prywatną metodę
M o v e In v a d e rs (), która będzie wywoływana przez U p d a te (). Pierwszym zadaniem jest obliczenie czasu, ja k i upłynął od
ostatniego przesunięcia, co można zrobić, używając pola _ la s tU p d a te d . Jeśli nie upłynęło dostatecznie dużo czasu, nic nie
należy robić. Jeśli najeźdźcy poruszają się w prawo, to metoda M o ve In vad ers() powinna użyć L IN Q w celu przeszukania
kolekcji _ in v a d e rs i odnalezienia tego z nich, którego współrzędna X znajduje się w obszarze prawej krawędzi. Gdy taki
najeźdźca zostanie znaleziony, formacja powinna zostać przesunięta w dół, a pole in v a d e rD ire c tio n powinno zostać
ustawione na D ir e c t io n . L e f t . W przeciwnym razie grupa powinna kontynuować poruszanie się w prawo. Z drugiej
strony, jeśli najeźdźcy poruszają się w lewo, powinna zostać wykonana operacja przeciwna. Kolejne zapytanie L IN Q
powinno wtedy sprawdzić, czy jakiś najeźdźca znajduje się blisko lewej krawędzi. Jeśli tak, form aqa także musi przesunąć
się w dół i zmienić kierunek ruchu. Można przy tym używać prywatnego pola _justMovedDown, by przechowywać
inform ację o tym, kiedy formacja została przesunięta w dół i zmieniła kierunek.

O K R E Ś L IĆ , K T Ó R Z Y N A J E Ź D Ź C Y M O G Ą S T R Z E L A Ć . Gdy któryś z najeźdźców osiągnie


D odaj prywatną metodę o nazwie R e tu rn F ir e (), która będzie
krawędź obszaru gry, wtedy cała
formacja zmienia kierunek.
wywoływana przez U p d a te (). Powinna ona wykonać re tu rn , gdy
lista pocisków najeźdźców zawiera wave + 1 elementów, a także Tylko najeźdźcy ^
na dole form acji >- + T +■
wtedy, gdy random .N ext(10) < 10 - wave (w ten sposób s trz e la ją do g r a c z f l i ^ - y ^ A . •*.
najeźdźcy strzelają losowo, a nie cały czas). Jeżeli oba warunki
zostaną spełnione, użyty zostanie L IN Q w celu pogrupowania . ( * ) * 1* *
wrogów na podstawie L o c a tio n .X i posortowania ich malejąco.
Po uzyskaniu grup możesz losowo wybrać jedną z nich i użyć metody
Jeśli któryś z najeźdźców . 4
L a s t ( ) do odnalezienia najeźdźcy na dole kolumny. Wszystko
osiągnie dolną krawędź 1
w porządku, masz teraz strzelca — możesz dodać strzał do listy obszaru gry, to gra s ię Y
_ in v a d e rS h o ts , umieszczając go zaraz pod środkiem jego obiektu kończy. ^ ^
(użyj pola Area najeźdźcy do ustawienia położenia ładunku).

S P R A W D Z IĆ Z D E R Z E N IA G R A C Z A I N A J E Ź D Ź C Ó W .
Będziesz chciał utworzyć metodę do badania kolizji. Istnieją trzy rodzaje zderzeń, jakie możesz badać, a bardzo Ci się
w tym przyda metoda do odnajdywania zachodzących na siebie prostokątów, przedstawiona w rozdziale 16.
★ Użyj L IN Q w celu odnalezienia nieżywych najeźdźców, wykonując iterację po wszystkich pociskach na liście gracza
i wybierając tego wroga, którego właściwość Area zawiera położenie strzału. Usuń najeźdźcę i strzał.
★ Dodaj zapytanie, aby sprawdzić, czy jakiś najeźdźca nie osiągnął dolnej krawędzi ekranu — jeśli tak, zakończ grę.
★ Nie potrzebujesz L IN Q do wyszukiwania strzałów, które trafiają w gracza. W ykonaj pętlę, która porówna
położenie strzałów wroga i właściwość Area statku. (Pamiętaj, nie możesz m odyfikow ać k o le k c ji wewnątrz
p ę tli fo re a ch . Jeżeli to zrobisz, otrzymasz wyjątek In v a lid O p e ra tio n E x c e p tio n z kom unikatem , że kolekcja
została zmodyfikowana. Być może będziesz musiał utworzyć tymczasową listę obiektów do usunięcia, bądź użyć
metody rozszerzenia T o L is t( ) , by najpierw skopiować kolekqę).

845
Invaders

Zbuduj stronę aplikacji na potrzeby widoku


G łów na strona a p lika cji Invaders bazuje na szablonie Basic Page i jest przechowywana w folderze View.
Dysponuje ona ob ie kte m m od elu w id o ku , zdefiniow anym ja ko zasób statyczny; o b ie k t ten służy jako
DataContext dla wszystkich k o n tro le k wyświetlanych na stronie.

W s z y s tk ie a k c je s ą o b s łu g iw a n e
przy u życiu w ią z a n ia .
Statki najeźdźców, statek gracza, strzały i gwiazdy,
a nawet lin ie symulujące stare m o n ito ry... — wszystko to
są ko n tro lk i dodawane do kolekcji ObservableControls
obiektu m odelu w idoku. O prócz tego będziesz także
potrzebował k o n tro lk i T e x tB lo c k z napisem „G ra
skończona” , której właściwość V is ib ilit y zostanie
powiązana z właściwością GameOver, oraz drugiej
ko n tro lk i tego samego typu z tekstem „G ra wstrzymana” ,
powiązanej z właściwością Paused.

W y n ik oraz d o d a tk o w e
s ta tk i g ra c z a s ą o so b n ym i
Invaders k o n tro lk a m i.
W praw ym górnym rogu gry znajduje się
k o n tro lk a StackPanel, w któ re j została
<*> <*> j a j <a > »*> j a > «a ; 'a j
um ieszczona k o n tro lk a TextBlock
/Xv /Xv !*v /Tv /X\ /xv /Xv Jxv /xv /Xv /Xv
'fi. % 'fi % % % ■»_ powiązana z właściwością Score,
oraz k o n tro lk a GridView powiązana
^ ^^ v^v z właściwością Lives . K o n tro lk a
'i' + . + GridView w yśw ietla statki gracza, gdyż
je j sza b lo n d a n ych (D a ta T e m p la te )
je s t k o n tr o lk ą Image; dlatego też, aby
k o n tro lk a ta pozw alała na dodawanie

V i usuwanie obrazków, właściwość Lives


o b ie k tu m odelu w id o k u m usi być
kolekcją o b ie któ w — new o b je ct() .

W y n ik oraz d o d a tk o w e s ta tk i
g ra c z a s ą o so b n ym i k o n tro lk a m i.
G łów ny obszar gry to kon tro lka Border
z zaokrąglonymi rogami, zawierająca kontrolkę
ItemsControl, której właściwość ItemsSource
została powiązana z właściwością Sprites i której
właściwość ItemsPanel jest kon tro lką Canvas
o białym tle. N a następnej stronie przedstawimy
kod, któ ry zadba o aktualizację ich marginesów
tak, by obszar wewnątrz zawsze m ia ł p ro p o rc je 4:3
— właściwość Margin k o n tro lk i Border zapewni,
że właściwość Height będzie równa 4/3 wartości
właściwości Width, i to nawet w przypadku zmiany
orientacji ekranu lub jego wielkości.

846
Invaders

Zachowanie proporcji obszaru gry


K o d u kryty strony głów nej ma do w ykonania dwa zadania. M u si obsługiwać zdarzenia związane ze
zm ianą w ielkości strony, aby o b sza r g ry zach ow yw ał p ro p o rc je 4:3, oraz m usi obsługiwać zdarzenia
związane z używaniem kla w ia tu ry oraz gestów. Jeśli gracz używa tabletu, to o b ró t urządzenia
spowoduje zm ianę w ielko ści obszaru gry. D lateg o też w kodzie X A M L głównego elem entu strony
będziesz m usiał obsługiwać k ilk a zdarzeń:
Tuch zdarzeń M anipulation oraz _Tapped
<common:LayoutAwarePage będziesz potrzebował do obs ługi d(mych
x:Name="pageRoot" w ejściow ych; zajm iemy s ię nimi na
... następnej s t r o n i .
x m ln s :v ie w m o d e l= "u s in g : (zmieft używaną p r z e s trz e ń nazw ). ViewModel"

S1zeChanged="pageRoot SizeChanged"

M a n ip u la tio n M o d e = "T ra n sla te X " M a n ip u la tio n D e lta = "p a g e R o o t_ M a n ip u la tio n D e lta "
M an ip ula tion C om p lete d= "pa geR oo t M a n ip u la tio n C o m p le te d " Tapped="pageRoot Tapped"

K o le jn e zdarzenie będziesz obsługiw ał w ko n tro lce B o rd e r otaczającej obszar gry:

< B order x:N am e="playA rea" B o rd e rB ru sh = "B lu e " B o rd e rT h ickn e ss= "2 " C ornerR adius="10"
B ackground="B lack" M a rg in = "5 " G rid.R ow = "1" Loaded="playArea_Loaded">
< Ite m sC o n tro l

O to ko d ukryty, k tó ry dba o zachowanie p ro p o rc ji 4:3 obszaru gry, dodając lu b usuwając m arginesy odpow iednio:
lewy i praw y oraz górny i dolny.
p r iv a t e v o id playA re a_Lo ad ed(o b je c t sen de r, RoutedEventArgs e) {
U p d a te P la y A re a S iz e (p la y A re a .R e n d e rS iz e );
}

p r iv a t e v o id pageRoot_S1zeC hanged(object se n d e r, SizeChangedEventArgs e) {


UpdatePlayA reaSize(new S iz e (e .N e w S iz e .W id th , e.N e w S ize .H e ig h t - 1 6 0 ));
}

p r iv a t e v o id U pdateP layA reaS 1ze(S ize new PlayAreaSize) {


do ub le ta rg e tW id th ;
do ub le ta r g e tH e ig h t;
i f (new P la yA rea S ize .W id th > n e w P la yA re a S ize .H e ig h t) {
ta rg e tW id th = n e w P la yA re a S ize .H e ig h t * 4 / 3;
ta rg e tH e ig h t = n e w P la yA re a S ize .H e ig h t;
do ub le le ftR ig h tM a r g in = (new P la yA rea S ize .W id th - ta rg e tW id th ) / 2;
p la y A re a .M a rg in = new T h ic k n e s s (le ftR ig h tM a r g in , 0 , le ftR ig h tM a r g in , 0 );
} e ls e {
ta rg e tH e ig h t = new P layA reaS ize.W idth * 3 / 4;
ta rg e tW id th = n e w P layA rea S ize .W id th ;
do ub le topB ottom M argin = (n e w P la yA re a S ize .H e ig h t - ta r g e tH e ig h t) / 2;
p la y A re a .M a rg in = new T h ic k n e s s (0 , top B o tto m M a rg in , 0 , to p B o tto m M a rg in );
}
pla y A re a . W idth = t a r ge tW id th ; M etoda Up d ateP layA rea Size() oblicza nową szerokość
p la y A re a .H e ig h t = ta r g e tH e ig h t; i w ysokoś ć , zm ienia k o n tro li, a m ^ ę p m e a k t jd u u je
vie w M o d e l.P la yA re a S ize = p la y A re a .R e n d e rS iz e ; właściwość P la yA rea S ize obiektu mode|u w »doku-
}
847
Invaders

Odpowiadaj na zdarzenia kiwnięcia i zdarzenia klawiatury


T w oja gra będzie m usiała odpowiadać na zdarzenia związane z naciskaniem klawiszy oraz przesuwaniem palcem po ekranie
dotykowym , gdyż w ten sposób u żytko w nik może sterować swoim statkiem . A ponieważ tw orzym y aplikację M V V M ,
występuje w niej ważna separacja odpowiedzialności. Zadaniem strony jest śledzenie naciskanych klawiszy, gestów kiw nięcia
i dotknięcia ekranu oraz przekazywanie in fo rm a cji o tym , co się stało, ob ie kto w i m odelu w idoku. Z ko le i zadaniem obiektu
w id o ku jest interpretacja tych zdarzeń ja ko akcji podejm owanych w grze i wywoływanie odpow iednich m etod modelu.

Procedury obsługi zdarzeń klawiatury są dodawane do kodu ukrytego


Przesłoń m etody O nN a vig ated T o () oraz O nN avigatedFrom () (ta k ja k zrobiłeś w rozdziale 14.), by dodać lu b usunąć
procedury obsługi zdarzeń KeyUp oraz KeyDown. W celu in te rpre ta cji naciśnięć klawiszy pow inny one wywoływać m etody
obiektu m odelu w idoku.

p ro te c te d o v e r rid e v o id O nN a vig ated T o (N avigatio nE ve ntA rgs e) {


W indow.Current.CoreW indow.KeyDown += KeyDownHandler;
W indow.Current.CoreW indow.KeyUp += KeyUpHandler;
ba se.O nN a vig atedT o (e );
}
Window.Current.CoreWindow zawiera
p ro te c te d o v e r rid e v o id O nN avigatedF rom (N avigationE ventA rgs e) { referencję do obiektu CoreWindow,
W indow.Current.CoreW indow.KeyDown -= KeyDownHandler; dyspomują cego zdarzeniami związanymi
z pn stym i działaniami interfejsu
W indow.Current.CoreW indow.KeyUp -= KeyUpHandler; ^ -------- użytkownika, takimi jak naciśnięcia
base.O nN avigatedF rom (e); klawiszy. Dzięki temu zdarzenia związane
} z naciskaniem klawiszy zawsze będą
t rafiać do ja k ie jś procedury obsługi.
p r iv a t e v o id KeyD ow nH andler(object se n d e r, KeyEventArgs e) {
vie w M o d e l.K e y D o w n (e .V irtu a lK e y );
}
p r iv a t e v o id K eyU pH and le r(o bject sen de r, KeyEventArgs e) {
v ie w M o d e l.K e y U p (e .V irtu a lK e y );
}

Dodaj procedury obsługi zdarzeń związanych z gestami kiwnięcia i dotknięcia


Będziesz m usiał obsługiwać gesty kiw nięcia w lewo oraz w prawo służące do sterowania statkiem oraz gest dotknięcia, który
pozwoli graczowi strzelać. Procedury obsługi tych zdarzeń zostały określone w kodzie X A M L przedstawionym na poprzedniej
stronie, zatem wystarczy, że teraz przedstawimy jedynie procedury obsługi tych zdarzeń.

p r iv a t e v o id p a g e R o o t_ M a n ip u la tio n D e lta (o b je c t se n d e r, M a n ip u la tio n D e lta R o u te d E v e n tA rg s e) {


i f ( e .D e lta .T r a n s la tio n .X < -1 )
v ie w M o d e l.L e ftG e s tu re S ta rte d (); Zdarzenie ManipulationDelta j es t bezustannie zgtaszane po<iczas
e ls e i f ( e .D e lta .T r a n s la tio n .X > 1) ^ wykonywawa gestu gdy u ż y tk o ^ k
Przef “w r L j eecn j ki
viewModel .R ig h tG e s tu r e S ta r te d ( ) ; przy czyJ m w aścj'w°_ć
M l 11 V ) M I J —
^- el a ^
rum zosmto,

dystans palec zostat przesunięty od miejs c a, w Ltó
ni tton^m znstnto
} zgłoszone kolejne zdarzenie .

p r iv a t e v o id p a g e R o o t_M a n ip u la tio n C o m p le te d (o b je ct se n d e r, M an ip ula tion C om p lete dR o uted E ven tA rg s e) {


v ie w M o d e l.L e ftG e s tu re C o m p le te d ();
v ie w M o d e l.R ig h tG e s tu re C o m p le te d (); ^ ----------- Z darzenie ManipulationCompleted
je s t zgłaszane w momencie, gdy
}
użytkownik oderwie palec od
ekranu. Obiekt modelu widoku
p r iv a t e v o id pageR oot_Tapped(object se n d e r, TappedRoutedEventArgs e) { zdecyduje, w jaki sposób obsłużyć
vie w M o d e l.T a p p e d (); te zdarzenia.
}
Invaders

Statek jest wyświetlany przez kontrolkę Animatedlmage


D o w yśw ietlania statków, zarówno statku gracza, ja k i najeźdźców, możesz użyć k o n tro le k Anim atedlm age,
które poznałeś w rozdziale 16. Statek gracza używa tylko jednego obrazu, k tó ry nie jest animowany;
dlatego w jego przypadku wystarczy przekazać do k o n tro lk i listę zawierającą jeden obraz (zyskujesz dzięki
tem u możliwość późniejszego dodania anim acji statku gracza).
T ra fia n e statki najeźdźców po w in n y stopniow o stawać się coraz jaśniejsze, a w końcu zniknąć całkowicie.
O prócz tego każdy, k to w latach 80. grał w gry zręcznościowe, w ie, że tra fio n y statek gracza m a migać
przez 2,5 sekundy, a następnie gra po w in na być kontynuow ana. D lateg o do ko d u ukryteg o k o n tro lk i
A nim ated lm ag e będziesz m usiał dodać następujące m etody:

p u b lic v o id In v a d e rS h o t() {
in v a d e rS h o tS to ry b o a rd .B e g in ();
}

p u b lic v o id S ta r tF la s h in g ( ) {
fla s h S to ry b o a r d .B e g in ( );
}

p u b lic v o id S to p F la s h in g O {
f la s h S to r y b o a r d .S to p ( ) ;
}

O prócz tego musisz dodać odpowiednie obiekty zarządzające animacjami. O biekt in v a d e rS h o tS to ry b o a rd


będzie typu D o ub leA n im a tion, a jego zadaniem będzie zmiana wartości właściwości O p a c ity z 1 na 0. Z kolei
fla s h S to ry b o a rd to animacja ram ek kluczowych, która przełącza właściwość V i s i b i l i t y , tak by kontrolka
znikała i ponownie pojawiała się.

Dodaj kontrolkę dla dużych gwiazd


N a gwiaździste tło obszaru gry składają się trzy rodzaje gwiazd: koła, prostokąty oraz duże gwiazdy.
N aw et duże gwiazdy są w rzeczywistości całkiem małe — mają w ym iary 10 x1 0 pikseli. Musisz zatem
stworzyć własną ko n tro lkę wyświetlającą ob ie kt Polygon. Gwiazdy mogą m ieć różne kolory, dlatego też
k o n tro lk a będzie m usiała udostępniać publiczną m etodę służącą do zm ieniania k o lo ru o b ie ktu Polygon:

p u b lic v o id Set F il 1 (S o lid C o lo rB ru s h s o lid C o lo rB ru s h ) {


p o ly g o n . F ill = s o lid C o lo rB ru s h ;

Statyczna klasa InvadersHelper wspomaga model widoku


Klasa m od elu w id o k u m ogłaby korzystać z klasy pom ocniczej, definiującej m etody w ytw órcze służące do tw orzenia
k o n tro le k statków najeźdźców, strzałów oraz gwiazd. M e to d a S t a r C o n t r o lF a c to r y ( ) po w in na pobierać liczbę losową
i zwracać pro sto kąt, elipsę lu b dużą gwiazdę. M ógłbyś dodać do tej klasy także m etodę pryw atną zwracającą losowy k o lo r
(re tu rn C o lo r s . L ig h t B lu e ; ) , by m etoda S t a r C o n t r o lF a c to r y ( ) zwracała różne gwiazdy, w różnych kolorach.
Będziesz także potrze bo w ał m etody S c a n L in e F a c to ry (), tworzącej sym ulowane lin ie starych m o n ito ró w . Każda taka lin ia
będzie prostokątem , którego właściwości F i l l przypiszesz w artość new S o lid C o lo r B r u s h ( C o lo r s .W h ite ) , właściwości
H e ig h t w artość 2, a właściwości O p a c ity wartość 1.
W szystkie m etody wytwórcze po w in n y pobierać pa ra m e tr s c a le typ u d o u b le , któreg o znaczenie w yjaśnim y p rzy okazji
opisywania klasy m od elu w idoku.

849
Invaders

Użyj panelu Ustawienia, by wyświetlić okienko O a p lik a c ji

W rozdziale 15. dowiedziałeś się, ja k dodawać w yw ołanie zw rotne, pozwalające w yśw ietlić okienko
z p o zio m u pa ne lu Ustawienia. T w o im zadaniem jest dowiedzieć się, w ja k i sposób można um ieścić na
stronie k o n tro lk ę Popup. Poniżej przedstaw iliśm y ko d ukryty, um o żliw ia jący w yśw ietlenie je j z poziom u
panelu Ustawienia; dodatkow o zaw iera on także pro ced urę obsługi zdarzeń obsługujących użycie
przycisku rozpoczynającego grę, k tó ry także będziesz m usiał dodać: ____________

p u b lic In va d e rsP a g e () { @ O aplikacji


t h is . I n it ia liz e C o m p o n e n t ( ) ;
Ctt. Ruszgłową
S ettingsP ane.G etForC urrentV iew ().C om m andsR equested prezentuje...
+= InvadersPage_CommandsRequested;
Invaders
}
“ Invaders" to hold oddany jednej z
v o id InvadersPage_Comm andsRequested(SettingsPane sen de r,
najpopularniejszych i najczęściej powielanych
SettingsPaneCommandsRequestedEventArgs a rg s) Ikon przemysłu gier wideo, To ostatni projekt
książki C#. R u s z g ł o w ą (3 . w y d a n ie ) napisanej
{ przez Andrew Stellman oraz Jennifer Greene
(Helion, 2013).
UICommandlnvokedHandler in vo ke d H a n d le r =
Kod projektu m o żna pobrać z serw era FTP
new U IC om m andlnvokedH andler(A boutlnvokedH andler); w ydaw nictw a Helion

SettingsCommand aboutCommand = new SettingsCommand( © 2013 A n drew Stellm an o raz Jenn ifer G reene

;
„O a p l i k a c j i " , „O a p l i k a c j i " , in v o k e d H a n d le r);
args.R equest.ApplicationC om m ands.Add(aboutC om m and);
} Oto okienko, które
p r iv a t e v o id AboutInvokedH andler(IU IC om m and command) { przygotowaliśmy — możesz
viewM odel.Paused = t r u e ; w nim użyć kontrolek
StackPanel oraz Grid, by
aboutPopup.IsO pen = t r u e ; wyśw ietlić w nim inne,
} d°wolnie wybrane kontrolki.
p r iv a t e v o id C lo se P o p u p (o b je ct se n d e r, RoutedEventArgs e) { ^ Przycisk o postaci strzałki,
zamykający okienko, je s t
aboutPopup.IsO pen = f a ls e ; skojarzony z procedurą
viewM odel.Paused = f a ls e ; dbsługi zdarzeń ClosePopup.
}
p r iv a t e v o id S ta rtB u tto n C l1 c k (o b je c t sen de r, RoutedEventArgs e) {
aboutPopup.IsO pen = f a ls e ;
vie w M o d e l.S ta rtG a m e ();
}

A to jest fragm e nt kod u X A M L , od któreg o możesz zacząć:

<Popup x:Name="aboutPopup" Grid.RowSpan="2"


V e r tic a lA lig n m e n t= " S tre tc h " H o riz o n ta lA lig n m e n t= "R ig h t"
W idth= "400" IsO pen="F alse">

<StackPanel Background="B lue" V e rtic a lA lig n m e n t= " S tre tc h "


H o riz o n ta lA lig n m e n t= "S tre tc h " W idth= "360" M arg in= "2 0">

I jeszcze jedno: uzyskasz znacznie ładniejszy efekt, jeśli okienko będzie się pojawiało stopniowo. Przekonaj się, czy będziesz
wiedział, ja k skorzystać z kolekcji T r a n s itio n s w oknie P r o p e r t ie s , by dodać do okienka efekt E n tra n ce T h e m e T ra n sitio n .

Transitions (Collection)

850
Invaders

Utwórz model widoku


M o d e l w id o k u tej a p lika cji będą tw orzyć dwie klasy. G łów ną jest klasa In v a d e rs V ie w M o d e l, natom iast
druga klasa — B o o le a n V is ib ilit y C o n v e r t e r — jest taka sama ja k klasa zastosowana w rozdziale 16.
— możesz je j użyć, by powiązać właściwości V is i b le k o n tro le k T e x tB lo c k z napisam i „G ra skończona”
oraz „G ra w strzym ana” , z w łaściw ościam i GameOver oraz Paused o b ie k tu m odelu w idoku . Cała
pozostała część tego la b o ra to riu m zostanie poświęcona tw orze niu klasy m od elu w idoku.

O to sam początek klasy In va d e rsV ie w M o d e l , k tó ry pom oże C i rozpocząć pracę nad nią:

u s in g View; To dokfadnie ten sam wzorzec, który


u s in g Model; zastosowałeś w rozdzia/e 16., by zarządzać
u s in g System.ComponentModel; spr ajtam i w projekcie „Pszczoły na
u s in g S y s te m .C o lle c tio n s .O b je c tM o d e l; gwiaździs tym niebie"; po/ega on na utworzeniu
u s in g S y s te m .C o lle c tio n s .S p e c ia liz e d ; pry watneg°, przeznaczonego jedynie do odczytu
u s in g W indow s.F oundation; po/a Observab/eCo//ection przeznaczonego do
u s in g D is p a tc h e rT im e r = W in d o w s.U I.X a m l.D isp a tch e rT im e r; przechowy wania kontro/ek i zapewnienia
u s in g FrameworkElement = W indows.UI.Xam l.Fram eworkElem ent; je g o separacji od widoku poprzez
udostępnienie jedynie właściwości
c la s s InvadersViewM odel : IN o tify P ro p e rty C h a n g e d t ypu lN otifyCo//ectionChanged.
p r iv a t e re a d o n ly O bse rva bleC o lle ction <F ram e w orkE lem e nt>
_ s p r it e s = new O b se rva b le C o lle ctio n < F ra m e w o rkE le m e n t> ();
p u b lic IN o tify C o lle c tio n C h a n g e d S p r ite s { g e t { r e tu r n _ s p r it e s ; } }

p u b lic bool GameOver { g e t { r e tu r n _model.GameOver; } }

p r iv a t e re a d o n ly O b s e rv a b le C o lle c tio n < o b je c t> _ li v e s =


new O b s e rv a b le C o lle c tio n < o b je c t> ();
p u b lic IN o tify C o lle c tio n C h a n g e d L iv e s { g e t { r e tu r n _ li v e s ; } }

p u b lic bool Paused { g e t; s e t; } Właściwość S cale jest mnożnikiem, który pozwala


p r iv a t e bool _la s tP a u s e d = t r u e ; przeliczyć właściwości X, Y, W idth oraz H e igh t
kontrolek z układu współrzędnych o wymiarach
p u b lic s t a t i c do ub le S cale { g e t; p r iv a t e s e t ; }
400x300 na układ współrzędnych kontrolki Canvas
p u b lic i n t Score { g e t; p r iv a t e s e t; } stanowiącej rzeczywisty obszar gry.

p u b lic S ize P la yA rea S ize { Pole P la yA rea S ize udostępnia jedynie akcesor set i jest
set { aktualizowane przez obiekt w idoku za każdym razem, gdy
S cale = v a lu e .W id th / 405; wielkość obszaru gry ulegnie zmianie. Kiedy wartość właściwości
m o d e l.U p d a te A llS h ip s A n d S ta rs (): P la yA rea S ize zostanie określona, akcesor set oblicza nową wartość
R e c re a te S c a n L in e s ();
mnożnika Scale, a następnie inform uje model, że powinien zgłosić
}
zdarzenia, by zaktualizować wszystkie statki i gwiazdy.
}
Przy okazji odtwarzane są także linie symulujące stare monitory.

p r iv a t e re a d o n ly InvadersM odel _model = new In v a d e rs M o d e l();


p r iv a t e re a d o n ly D is p a tc h e rT im e r _ tim e r = new D is p a tc h e rT im e r();
p r iv a t e FrameworkElement p la y e rC o n tro l = n u l l ;
p r iv a t e bool _ p la y e rF la s h T n g = f a ls e ;
p r iv a t e re a d o n ly D ic tio n a ry < In v a d e r, FrameworkElement> _ in v a d e rs =
new D ic tio n a r y < In v a d e r , Fram ew orkElem ent>();
p r iv a t e re a d o n ly D ictiona ry< F ram e w o rkE lem e nt, DateTime> s h o tln v a d e rs =
new D ictiona ry< F ram e w o rkE lem e nt, D ateTim e> ();
p r iv a t e re a d o n ly D ic tio n a ry < S h o t, FrameworkElement> _ s h o ts =
new D ic tio n a ry < S h o t, Fram ew orkElem ent>();
p r iv a t e re a d o n ly D ic tio n a r y < P o in t, FrameworkElement> _ s ta r s =
new D ic tio n a r y < P o in t, Fram ew orkElem ent>();
p r iv a t e re a d o n ly List<Fram ew orkElem ent> _sca n L in e s =
new L ist< F ra m e w o rkE le m e n t> ();

851
Invaders

Obsługa interakcji z użytkownikiem Czy zauważyłeś, że wszystkie metody


przedstawione na tej stronie używają
Zobaczyłeś już, ja k strona główna w obiekcie w idoku wywołuje m etody m odelu m odyfikatora in te r n a l? W ynika to
w idoku, by obsługiwać naciskane klawisze oraz gesty. N a tej stronie przedstawiliśmy z faktu, że stworzyliśmy te metody,
najpierw dodając kod przedstawiony kilka
metody, które są w tych przypadkach wywoływane. U żytkow nik musi mieć
stron wcześniej, a następnie używając
możliwość używania zarówno klawiatury, ja k i ekranu dotykowego. W tym celu
opcji Generate MethodStub IDE, by
zarówno gest dotknięcia, ja k i naciśnięcie klawisza spacji powoduje wywołanie wygenerować odpowiednie deklaracje
m etody F ire S h o t( ) obiektu modelu. Nieco bardziej złożone jest przesuwanie metod. M odyfikator in te r n a l oznacza,
statku gracza w prawo i w lewo: zarówno naciśnięcia odpowiednich klawiszy, ja k że metody będą dostępne publicznie
w ew nątrz złożenia, lecz poza nim będą
i gesty kiwnięcia aktualizują pola DateTime? zawierające dane o ostatnim zdarzeniu
w yglądały na prywatne. Więcej informacji
(naciśnięciu klawisza lub geście) lub wartość n u l l , jeśli nie naciśnięto żadnego
na tem at złożeń można znaleźć w punkcie
klawisza ani nie wykonano odpowiedniego gestu. 3. dodatku „Pozostałości” .

iM^ a "w ^ 'k U. Uu^ w ;a,J pi:!d^ ':UIi! l,ul^ a l^ .,D .,.T ir . , , by przechowywai
p r iv a t e DateTime? le f t A c t io n = n u l l ;
p r iv a t e DateTime? r ig h t A c t io n = n u ll

in te r n a l v o id KeyD ow n(W indow s.System .VirtualKey v ir tu a lK e y )


i f ( v ir t u a lK e y == W ind o w s.S yste m .V irtu a lK e y.S p a ce )
_ m o d e l.F ire S h o t(); Widok wywołuje metodę KeyDown w metodzie
obsługi zdarzeń klawiatury strony i przekazuje
do niej informacje o naciśniętym klaw iszu. J e ś li
if ( v ir t u a lK e y == W in d o w s .S y s te m .V irtu a lK e y .L e ft)
użytkownik nacisnął klaw isz sp a c ji, model widoku
_ le f t A c t io n = DateTime.Now; informuje model, że powinien utworzyć nowy
strz a ł. J e ś li użytkownik nacisnął klaw isz strzałki
if ( v ir t u a lK e y == W in d o w s .S y s te m .V irtu a lK e y .R ig h t) w lewo lub w prawo, to aktualizowane s ą pola
r ig h t A c t io n = DateTime.Now; _ le ftA ctio n lub _rig h tA ctio n .
}

in te r n a l v o id K eyU p(W indow s.S ystem .V irtualK ey v ir tu a lK e y ) {


i f ( v ir t u a lK e y == W in d o w s .S y s te m .V irtu a lK e y .L e ft) Taśli użytkownik nacisnął klawisz strzałki
le f t A c t io n = n u l l ;
je s t przypisywana wartość nuli.
if ( v ir t u a lK e y == W in d o w s .S y s te m .V irtu a lK e y .R ig h t)
r ig h t A c t io n = n u l l ;
}
Dzięki temu procedura obsługi zdarzeń
in te r n a l v o id L e ftG e s tu re S ta rte d () { zegara modelu widoku może spraw dzić
le f t A c t io n = DateTime.Now; pola _ le ftA ctio n oraz _rig h tA ctio n
} " i na ich podstaw ie określić, czy
powinna przesunąć sta tek gracza.
in te r n a l v o id L e ftG e stu re C o m p le te d () {
le f t A c t io n = n u l l ;
}
Widok wywołuje te metody
w procedurach obsługi
in te r n a l v o id R ig h tG e s tu re S ta rte d () { zdarzeń gestów kiwnięcia
r ig h t A c t io n = DateTime.Now; i dotknięcia ekranu. Kiedy
} użytkownik wykona g e s t
kiwnięcia w lewo lub
Zdarzenie Tapped stron y zostanie
in te r n a l v o id R ightG esture C om p le te d() { w prawo, aktualizowane
s ą pola _le ftA c tio n lub zgłoszone, kiedy użytkownik kliknie
_ r ig h t A c t io n = n u ll; przy cisk rozpoczynający rozgrywkę,
_rig h tA ctio n ; je ś li natom iast
} a zatem zacznie s ię ona od
użytkownik wykonał g e s t
oddania strza łu przez gracza.
dotknięcia, oddawany je s t
strza ł. Czy p o tra fisz w ym yślić, jak
in te r n a l v o id Tapped() { zmodyfikować metodę Tapped(),
_ m o d e l.F ir e S h o t( ); by uniknąć tego efektu ?
}

852
Invaders

Napisz metody klasy (nvadersViewModeI


U ła tw im y C i rozpoczęcie pracy nad tą klasą, przedstaw iając ko d ko n stru kto ra
oraz dwóch przydatnych m etod.

Konstruktor klasy ln va d ersV iew M o d ei


określa procedury obsługi modelu i kończy grę.
p u b lic Inva dersV iew M o de l() {
Je śli właściwość S ca le przyjm ie
Scale = 1; <r
wartość 1, to mod<sl widoku będzie .
aktualizował widok. w s kaii 1:} > j n - Ł ącL
_model.ShipChanged += M odelShipChangedEventH andler; obszarowi gry wymiary 400x300; je dna.k.
_model.ShotMoved += M odelShotM ovedEventHandler; obiekt widoku szybko zaktualizuje
wartość w łaściw ośc ph y A rea Size
_m odel.StarC hanged += M odelStarC hangedEventH andler; modelu widoku, co z k°lei spowoduj e
_ t im e r . I n t e r v a l = T im e S p a n .F ro m M illise co n d s(1 0 0 ); zmianę wartości właściwości S ca le .
t im e r . T ic k += T im e rT ickE ve n tH a n d le r;

EndGame();
Zgłaszanie zdarzenia zegara co 100 milisekund
} sprawi, że widok będzie aktualizowany 10 razy na
? sekundę. Liczba ta nie odpowiada liczbie ramek
Kończymy grę, przez co
rozpocznie się ona od wyświetlanych w ciągu sekundy, gdyż sprajty są
wyświetlenia napisu przesuwane po ekranie przy wykorzystaniu animacji.
„Gra skończona“ .

Metoda S t a r t G a m e () usuwa wszystkie statki najeźdźców oraz strzały


z kolekcji sprajtów, informuje model, że ma rozpocząć grę, i uruchamia zegar.
p u b lic v o id S tartG a m e(){
Paused = f a ls e ;
fo re a ch ( v a r in v a d e r in _ in v a d e rs .V a lu e s ) _ s p rite s .R e m o v e (in v a d e r);
fo re a ch ( v a r sho t in s h o ts .V a lu e s ) s p rite s .R e m o v e (s h o t);
_ m o d e l.S ta rtG a m e ();
OnPropertyChanged("GameOver" Kiedy obiekt modelu rozpoczyna grę, aktualizuje
_ t im e r . S t a r t ( ) ; wartość swojej w łaściw ośc GameOver, prz.ez
} co obiekt modelu widoku generuje ztdarz^e
PropertyChanged, by zaktualizować widok.

Metoda R e cre a te S ca n L in e sO dodaje symulowane linie


przypominające ekrany starych monitorów.
p r iv a t e v o id R e cre a te S ca n L in e s(){
fo re a ch (FrameworkElement scanLine in scan Lines)
if (_ s p rite s .C o n ta in s ( s c a n L in e ))
_ s p rite s .R e m o v e (s c a n L in e ); Będziesz m usiał napisać tę metodę wytwórczą.
_ s c a n L in e s .C le a r();
fo r ( i n t y = 0; y < 300; y += 2) { J
FrameworkElement scanLine = In v a d e rs H e lp e r.S c a n L in e F a c to ry (y , 400, S c a le );
_ s c a n L in e s .A d d (s c a n L in e );
_ s p rite s .A d d (s c a n L in e ); Metoda uży wa te9° ar9umentu, . żeby, skalować
prostokąty do odpowiedniej wielkości
} i umieszczać je we właściwych miejscach.
}

853
Invaders

Widokjest aktualizowany, gdy zegar wygeneruje zdarzenie


K ie d y o b ie k t m odelu, In v a d e rs M o d e l, generuje zdarzenie ShipC hanged, o b ie k t m od elu w id o ku
musi określić, ja k i statek został zm odyfikow any, i od po w ie dn io zm ienić swoje kolekcje, by w id o k m ógł
właściw ie przedstaw ić stan m odelu. O to w ja k i sposób m a działać m etoda obsługująca zdarzenia ShipChanged:

v o id T im e rT ic k E v e n tH a n d le r(o b je c t sen de r, o b je c t e) {
if (_la stP a u se d != Paused)

{
Użyj p o la _la stP a u se d , by z g ła sz a ć zd a rz e n ie PropertyChanged za każdym razem,
gdy zm ieni s i ę w artość w ła ściw ości Paused.

}
if (!Paused)

{
J e ś l i o b ie w ła ściw o ści, J L e ftA c tio n oraz _ r ig h t A c t io n , mają w artość,
oznacza to , t e użytkownik n a cisn ą ł Jed n o cześn ie dwa k la w isze lub n a cisn ą ł
k la w isz i wykonał g e s t k iw n ię c ia . W takim przypadku do o k re śle n ia kierunku
przesuw ania s ta tk u gracza w ybierz p ó ź n ie js z e z d a rz e n ie . J e ś l i ty lk o Jedna
z ty ch w ła ściw ości ma w a rto ść, w ybierz J ą i p rzekaż w wywołaniu metody
_m odel.M oveP layer().

}
Zażądaj a k t u a liz a c ji modelu widoku; n a stęp n ie sprawdź w łaściw ość S c o re . J e ś l i j e j
w a rto ść n ie odpowiada w ła ściw ości _m o d el.S co re , zmień J ą i wygeneruj zd a rzen ie
PropertyChanged.
Z a k tu a liz u j w arto ść w ła ściw o ści L iv e s , by odpowiadała w ła ściw o ści _m o d e l.L iv e s ;
w tym c e lu usuń o b ie k t lub dodaj go, używając new o b je c t ( ) .

fo re a ch (FrameworkElement c o n tro l in _ s h o tIn v a d e r s .K e y s .T o L is t( ))

{
Każdy k lu c z słow nika _sh o t!n v a d e rs J e s t ko n tro lk ą Antmatedlmage, a Jego
w artość o k re śla cz a s , w którym dany s t a te k najeźdźców z o s t a ł z n isz cz o n y .
Pełne zakończenie anim acji zn iszczon ego s ta tk u n a je ź d ź cy zajm uje p ó ł sekundy,
a zatem każdy s ta te k , k tó ry u le g ł z n is z c z e n iu w c ze śn ie j n iż p ó ł sekundy temu,
powinien z o s ta ć u su n ię ty z e słowników _ s p r i t e s oraz _sh o t!n v a d e rs.

J e ś l i g ra z o s ta ła zakończona, n a le ży z g ł o s i ć zd a rzen ie PropertyChanged i zatrzym ać zeg a r.

854
Invaders

Statek gracza może się przemieszczać


i może zostać zestrzelony
K ie d y o b ie k t In va d e rM o d e l generuje zdarzenie ShipChanged, to o b ie k t m odelu w id o ku
m usi określić rodzaj statku, k tó ry został zm ieniony, i od po w ie dn io zaktualizow ać jego
mm
kolekcje, ta k by w id o k był w stanie pra w id ło w o odzw ierciedlić zm iany w prow adzone
w m odelu. O to ja k p o w in na działać pro ced ura obsługi zdarzeń ShipChanged:
Będziesz musiał rzutować
e .Sh ipUpdated w dół do
v o id M od elS hipC hangedE ventH andler(object se n d e r, ShipChangedEventArgs e) { odp°wiedniego typu, bądź to
Invader, bądź też Player.
if ( ! e . K ill e d ) {
if (e.S h ipU pda ted is In v a d e r) {
In v a d e r in v a d e r = e.S hipU pdated as In v a d e r;
J e ś l i k o le k c ja _ln v a d e rs n ie zaw iera tego s ta tk u najeźdźców , to u ż y j metody
In v a d e rC o n tro lF a c to ry (), by utw orzyć nową k o n tro lk ę i dodaj J ą do k o le k c ji
oraz do sp ra jtó w . N przeciwnym r a z ie p rzesuń k o n tro lk ę n a je ź d ź cy w odpowiednie
m ie js c e i zmień j e j wymiary - n ie zapomnij p rz y tym p rzekaza ć do n i e j w a rto ści
S c a le ! P o n iż e j p rzed sta w iliśm y przydatną metodę pomocniczą, k tó rą być może
z e ch ce sz dodać do k la s y In va d erH elp er:
In v a d e rs H e lp e r.R e s iz e E le m e n t(in v a d e rC o n tro l, in v a d e r.S iz e .W id th * S ca le ,
in v a d e r.S iz e .H e ig h t * S c a le );
} e ls e i f (e.S h ipU pda ted is P la y e r) {
J e ś l i p o le _p la y e rF la s h in g ma w arto ść tru e , to s ta te k gracza a k tu a ln ie m igocze,
bo w c ze śn ie j z o s t a ł z n is z c z o n y ; p r z e r w ij m igotanie. N astępnie sprawdź, czy
_p la y e rC o n tro l ma w artość n u l i . J e ś l i ma, to u ż y j metody P la y e rC o n tro lF a cto ry ()
do utw orzenia sta tk u gracza i dodaj go do sp ra jtó w ; w przeciwnym r a z ie przesuń
s t a te k g ra cza i zmień Jego wymiary.
} e ls e {
if (e.S h ipU pda ted is In v a d e r) {
J e ś l i b ie żą c y s ta te k J e s t sta tk iem najeźdźców i J e s t różny od n u l i , to wywołaj
Jego metodę In v a d erS h o t() (bę d z ie sz p rz y tym m usiał odszukać k o n tro lk ę sta tk u
w słowniku _in v a d e rs , a n a stęp n ie rzutować J ą do typu Antmatedlmage). N astępnie
dodaj n a jeźd źcę do słow nika _sh o t!n v a d e rs i usuń z e słow nika _in v a d e rs . Słow nik
_sh o t!n v a d e rs zaw iera czas z e s t r z e le n ia każdego sta tk u n a je źd ź cy . O biekt modelu
widoku n ie usuwa k o n tro lk i Animatedlmage re p re z e n t u ją c e j s t a te k n a je ź d ź cy aż do
momentu, gdy zakończy on m igotać.
} e ls e i f (e.S h ipU pda ted is P la y e r) {
R z u tu j _p la y e rC o n tro l do typu Antmatedlmage, ro z p o c z n ij m igotanie i p r z y p is z
w arto ść tru e p o lu _p la y e rF la s h tn g . Animacja m igotania może być odtwarzana aż
do momentu z g ło s z e n ia p rz e z model ko lejn eg o zd a rzen ia ShipChanged, b ęd zie ono
bowiem oznaczać, ż e gra z o s ta ła wznowiona.

}
}

855
Invaders

Statek gracza może się przemieszczać


i może zostać zestrzelony
N a tej stronie przedstaw iliśm y p ro ced ury obsługi zdarzeń ShotMoved oraz S tarC hanged,
zdefiniow ane w klasie In v a d e rs V ie w M o d e l:

v o id M odelS hotM ovedE ventH andler(object sen de r, ShotMovedEventArgs e) {


if (!e .D is a p p e a re d )

{
J e ś l i s t r z a ł n ie J e s t kluczem w słow niku _ s h o t s , to używając Jego metody w ytw órczej,
utwórz nową k o n tro lk ę s t r z a ł u , a n a stęp n ie dodaj J ą do słowników _s h o ts oraz _ s p r i t e s .
J e ś l i s t r z a ł J e s t dostępny w słowniku _ s h o t s , oznacza to , t e Ju ż J e s t um ieszczony na
e k ra n ie ; w tym przypadku odszukaj Jego k o n tro lk ę i s k o r z y s ta j z metody pom ocn iczej,
by Ją przesu n ą ć na ek ra n ie , używając p rz y tym w ła ściw ości Lo ca tio n .
} e ls e {
S t r z a ł z n ik n ą ł, a zatem sprawdź słow n ik _ s h o ts , by zobaczyć, c z y J e s t w nim o b iek t
s t r z a ł u . J e ś l i J e s t , to usuft k o n tro lk ę s t r z a łu ze słow nika _ s p r i t e s , a o b ie k t s t r z a łu
ze słow nika s h o ts .

}
}

v o id M o d e lS ta rC ha ng ed E ventH a nd ler(o bje ct se n d e r, StarChangedEventArgs e) {


if (e .D isa p p e a re d && _ s ta rs .C o n ta in s K e y ( e .P o in t))

{
Odszukaj gwiazdę w słow niku _ s t a r s i usuft j e j k o n tro lk ę z e słow nika _ s p r i t e s .
} e ls e {
if (! s ta rs .C o n ta in s K e y (e .P o in t))
{
U żyj metody w ytw órczej, by utw orzyć nową k o n tro lk ę , n a stęp n ie dodaj J ą do słownika
_ s t a r s (używając Jako k lu cza w ła ściw o ści e .P o in t przekazanego argumentu z d a rz e n ia ).
} e ls e {
Gwiazdy zazw yczaj n ie zm ien ia ją p o ło że n ia , dlatego ta kla u zu la e ls e r a c z e j n ie
z o s ta n ie wykonana; możesz z n i e j Jednak s k o rz y s ta ć , by dodać do g ry s t r z e l a ją c e
gwiazdy. Odszukaj k o n tro lk ę gwiazdy w słowniku _ s t a r s i u ż y j metody pom ocn iczej,
by przesu n ą ć J ą w nowe m ie js c e .

LUDZIE W ZM OCNILI OBRONĘ!


NIECH ZACZNIE SIĘ EPICKA
BITWA O DOMINACJĘ!

856
Invaders

Można zrobić znacznie więcej...


M yślisz, że gra w ygląda dość dobrze? M ożesz przejść na wyższy poziom i w prow adzić k ilk a udoskonaleń:

Dodaj dźwięk
Znacznik XAML MediaElement pozwala dodawać do aplikacji dźwięki. Czy potrafisz dowiedzieć
się, jak go wykorzystać, by dodać dźwięki maszerujących najeźdźców, strzałów oddawanych przez
gracza oraz niszczonych statków kosmicznych? Wszystkiego dowiesz się na tej stronie:
http://m sdn.m icrosoft.com/pl-pl/library/windows/apps/xaml/hh465160.aspx

Dodaj statek matkę


Raz na jakiś czas po niebie na górze pola walki może poruszać się statek matka wart 250
punktów. Jeśli gracz go zestrzeli, dostanie specjalny bonus. Każda osłona może s ię składać z wielu
niewielkich bloków, które, podobnie jak statki
oticych, znika)ą, kiedy zostaną trafione; przy
Dodaj osłony "V czy m trafienie osłony nie daje żadnych punktów.
Dodaj unoszące się osłony, za którymi gracz może się skryć. Możesz utworzyć proste osłony,
przez które on i przeciwnicy nie mogą strzelać. Potem, jeśli naprawdę chcesz nadać grze blasku,
dodaj takie, przez które gracz i najeźdźcy mogą się przebić, oddając określoną liczbę strzałów.

Dodaj bombowce nurkujące


Stwórz specjalny typ wroga, który podlatuje na linię gracza. Bombowiec nurkujący powinien
wyłamać się z formacji, przemieścić w stronę gracza, przelecieć dołem ekranu i powrócić na
swoją pozycję.

Dodaj więcej broni


Rozpocznij wyścig zbrojeń! Inteligentne bomby, lasery, rakiety samonaprowadzające...
Istnieje cały arsenał broni, która może zostać użyta przez gracza i najeźdźców do ataku.
Sprawdź, czy potrafisz dodać do gry trzy nowe typy.

Dodaj polecenie Ustawienia do panelu Ustawienia


Polecenie Ustawienia możesz dodać do panelu Ustawienia tak samo, jak wcześniej dodałeś
do niego polecenie O aplikacji wyświetlające okienko informacyjne. W ustawieniach możesz
zmieniać takie parametry jak wyświetlanie linii symulujących wygląd ekranów starych monitorów,
liczba statków gracza, wyłączenie muzyki itd.

To jest Twoja szansa na wybicie się! Czy utworzyłeś nową, lepszą wersję gry?
Opublikuj swoją wersję gry na CodePlex lub innej witrynie do publikacji projektów
informatycznych i skorzystaj z możliwości pokazania swoich możliwości na
forum Head First C#: www.headfirstlabs.com/books/hfcsharp/.

857
Invaders

858 Laborotorium C#
17. Projekt dodatkowy!

Napisz aplikację Windows Phone

Klasy, obiekty, XAML, hermetyzacja, dziedziczenie, polimorfizm, LINQ, MVVM... dysponujesz juz
wszystkimi narzędziami niezbędnymi do pisania wspaniałych aplikacji dla Sklepu Windows oraz
tradycyjnych aplikacji okienkowych. Czy jednak wiesz, ze tych samych narzędzi możesz użyć do
pisania aplikacji dla W indows Phone ? Tak, to prawda! W tym dodatkowym projekcie poznasz
proces tworzenia gry dla systemu Windows Phone. Jeśli nie posiadasz odpowiedniego urządzenia,
to i tak nie masz się czym przejmować — będziesz mógł w nią grać na em ulatorze W indow s
Phone. A zatem zaczynajmy!

to jest nowy rozdział ► 859


Projekt dodatkowy

Atak pszczół!
W tym rozdziale napiszesz grę o nazw ie A tak pszczół przeznaczoną
dla system u W indow s P h o n e 8 . W grze pojaw i się ul rozw ścieczonych
pszczół, a jedynym sposobem ich u sp o k o jen ia będzie użycie bardzo
sm akow itego kw iatka. Im więcej pszczół u d a Ci się złapać przy jego
użyciu, tym wyższy uzyskasz wynik.

Łap pszczoły lecące w dół ekranu.


U l p o ru sza się w praw o i w lewo w zdłuż górnej kraw ędzi
Ml> 9:55
ek ran u , w ypuszczając przy tym pszczoły, k tó re lecą w dół,
w k ieru n k u T w ojego kw iatka. G dy T y będziesz łapał
kolejne pszczoły, u l b ędzie w ypuszczał now e z coraz w iększą
częstotliw ością i będzie się przesuw ał dalej w k ieru n k u
praw ej i lewej kraw ędzi ekranu.

Kontroluj kwiatek, używając gestów.


K ontrolow anie kw iatka o p ie ra się n a przeciąg an iu go
w praw o i w lewo. K w iatek b ędzie się przesuw ał za
Tw oim palcem , gdy będziesz go przeciągał p o ekranie,
pozw alając Ci łap ać pszczoły lecące do dolnej kraw ędzi
ekranu. M ożesz przeg ap ić tylko p ięć pszczół, później
g ra się skończy; co w ięcej, w raz z k ażd ą k o lejn ą złapaną
pszczołą gra będzie coraz trudniejsza.

860 Rozdział 17.


Napisz grę Windows Phone

Zanim zaczniesz...
A by w ykonać ten p ro jek t, będziesz m usiał zainstalować Visual Studio 2012 fo r Windows Phone .
W ersję E xpress tej w ersji V isual S tu d io m o żn a p o b ra ć ze strony:

http://www.microsoft.com/visualstudio/eng/products/visual-studio-express-for-windows-phone

G rę m ożna uruchom ić n a dw a sposoby. Łatw iejszym z nich je st zastosow anie p ro g ram u


Windows Phone E m ulator , dostarczan eg o w raz z V isual Studio.

Uruchamianie gry w emulatorze. Emulator pozwala uruchamiać


aplikacje Windows Phone na
D om yślnie V isual Studio fo r W indow s P h o n e u ru ch am ia aplikacje komputerze.
w em u lato rze, który em uluje k o m p letn e środow isko W indow s P h o n e 8
(włącznie z połączen iem z in te rn e te m , fałszywą siecią G S M itd.).

t Emulator WVGA 512MB - Przycisk Run w IDE


uruchamia emulator.

^ 1 W indows Phone Em ulator wym aga Hyper-V


Hyper-V jest technologią wirtualizacji stosowaną w Windows 8, jed n a k nie
ftkhimfi ł-nl wszystkie procesory ani nie wszystkie wersje systemu Windows 8 pozwalają na jej
vJU“) V) ' wykorzystanie. Firma M icrosoft przygotowała poniższy poradnik, który pomaga
skonfigurować i uruchamiać Hyper-V:
http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj863509.aspx
Hyper-V będzie działać na wirtualnych komputerach tworzonych przy użyciu VMWare,
VirtualBox, Parallels oraz innych produktów wirtualizacyjnych, o ile tylko procesor naszego
komputera będzie na to pozwalał. Będziesz musiał zajrzeć do dokumentacji używanego
oprogramowania wirtualizacyjnego bądź na poświęconą m u witrynę W W W (być może, aby
skorzystać z Hyper-V będziesz także musiał włączyć takie opcje ja k „nested virtual m achines”
lub „virtualize VT-x or A M D -V ”).

Uruchamianie gry w systemie Windows Phone 8


Jeśli podłączysz u rząd zen ie z system em W indow s P h o n e 8
do k o m p u te ra przy użyciu kabla U SB , to będziesz m ógł
n a nim uruchom ić i debugow ać sw oją aplikację.

A by m óc skorzystać z tej możliw ości, telefo n m usi być


zarejestrow any na aktywnym koncie W indow s P h o n e D ev
C e n te r (co m oże w ym agać w niesienia stosow nej opłaty):
https://dev.windowsphone.com/.
P o utw orzeniu k o n ta m ożesz zarejestrow ać n a nim
telefon, którego chcesz używać do testow ania aplikacji.
Inform acje o tym , ja k to zrobić, znajdziesz n a tej stronie:

http://m sdn.microsoft.com /pl-pl/library/windowsphone/develop/ff769508.aspx

jesteś tutaj ► 861


Projekt dodatkowy

UTW ÓRZ NO W Y PROJEKT TYPU WINDOWS PHONE APP


U ru ch o m V isual S tu d io 2012 fo r W indow s P h o n e i u tw ó rz nowy projekt typu
Windows Phone App, o nazwie A ta k pszczol. (Nic nie stoi n a przeszkodzie, byś
w ybrał in n ą nazw ę, choć w takim p rzy p ad k u Tw oje p rzestrzen ie nazw b ę d ą się
nieco różnić od tych p rezentow anych n a rysukach).

O PRZEJRZYJ PLIKI, KTÓRE IDE WYGENEROWAŁO, TW ORZĄC PROJEKT.


ID E V isual S tudio 2012 for W indow s P h o n e pow inno w yglądać b ard zo znajom o, gdyż je st niem al takie sam o
jak środow isko do tw orzenia aplikacji dla system u W indow s 8 o raz tradycyjnych aplikacji okienkow ych.
W m om encie tw o rzen ia now ego p ro je k tu ID E autom atycznie dod aje do niego pliki, tak ie jak:

★ plik X A M L i C # strony głównej


(MainPage.xaml o raz MainPage.xaml.cs);

★ główny p lik aplikacji (App.xaml oraz


App.xaml.cs);

★ fo ld e r Assets;

★ plik C # , który b ędzie zaw ierał zasoby


statyczne zlokalizow anych tekstów
(LocalizedStrings.cs);

★ m anifest aplikacji, zasoby o raz kilka


dodatkow ych plików i folderów (w tym
p rojek cie nie będziesz p o trzeb o w ał niczego
w ięcej).

862 Rozdział 17.


Napisz grę Windows Phone

U K R Y J P A N E L T Y T U Ł U N A S T R O N IE G Ł Ó W N E J .
S tro n a głów na, MainPage.xaml, pow in n a ju ż być o tw o rzo n a w ID E (jeśli nie jest, to ją otw órz).
T o jest głów na stro n a Twojej aplikacji. Przyjrzyj się jej kodow i X A M L , a n astęp n ie odszukaj
w nim k o n tro lk ę StackPanel o nazw ie T itle P a n e l .

< !--T itle P a n e l contains the name o f the a p p lic a tio n and page t i t l e —>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
<TextBlock Text="page name" M argin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
IDE utworzyło plik.
MainPage.xaml
* awifrający tą kontrolką
otackPanel o nazwie
Zm odyfikuj kod X A M L: dodaj do elementu StackPanel właściwość V is ib ilit y = M
CollapsedM,
ta k by zniknęła k o n tro lk a TextBox z tytułem aplikacji.
/— Dodaj tą właściwość
< !--T itle P a n e l contains the name o f the a p p lic a tio n and page t i t l e —>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28" Visibility=MCollapsedM>
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
<TextBlock Text="page name" M argin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>

0
MY APPLICATION

page name

£>

jesteś tutaj ► 863


Projekt dodatkowy

UTW ÓRZ FOLDER MODEL I DODAJ DO NIEGO KLASĘ BE E A T T A C K M O D EL.


T w orzona aplikacja bazuje n a w zorcu M V V M , dlatego też b ędzie p o siad ać warstwy: m odelu, w idoku oraz
m o d elu w idoku. Z aczniem y o d n ap isan ia w arstwy m odelu. W tym p rzy p ad k u będzie ją tw orzyć je d n a klasa:
BeeAttackModel, k tó rą um ieścim y w folderze o ra z w p rzestrzen i nazw Model. A zatem u tw órz folder
Model, a następ n ie dodaj do niego klasę BeeAttackModel.

Solution Explorer Solution Explorer - □ X

« (2 b • <» « □ ®i P p ] < d) b - * ea ff w « M [p]


Search Solution Explorer (Ctrl+;) P • Search Solution Explorer (Ctrl+;) P-
^ Solution 'AtakPszczol' (1 project} Solution 'AtakPszczol' (1 project)
W oknie Solution * [ci] AtakPszczol
J [c#] AtakPszczol Explorer utwórz folder > ft Properties
> f* Properties Model, a następnie l> References
> References kliknij go prawym > l l Assets
> ■! Assets
przyciskiem myszy j Model
1^1 Model
i‘ dodaj do niego klasę > S3 BeeAttackModel.cs
> 0 Resources /{\> ^ Resources
> Appjcaml
BeeAttackModel.
W ten sposób / l> Appjcaml
> c# LocalizedStrings.es |> C“ LocalizedStrings.cs
powstanie
> £¡1 MainPagejcaml \> r]| MainPage.xaml
przestrzeń nazw v"-
AtakPszczol.Model. Solution Explorer Class View
Solution Explorer Class View

Poniżej przedstaw iliśm y k o d klasy BeeAttackModel. D efiniuje o n a właściwości do p o b iera n ia liczby niezłapanych
pszczół, wyniku o raz czasu pom iędzy w ypuszczaniem kolejnych pszczół, jak rów nież m eto d ę do rozpoczynania gry.
O prócz tego u d o stę p n ia o n a m etody do info rm o w an ia m o d elu o tym , że użytkow nik p rzesu n ął kw iatek, że pszczoła
wylądow ała oraz m eto d ę zw racającą p o ło żen ie ula, używ aną pod czas w ypuszczania kolejnej pszczoły.

using Windows.Foundation;
Model widoku będzie używat tych
cla ss BeeAttackModel { właściwości, by aktualizować wynik
public in t MissesLeft { get; p riv a te s e t; } oraz liczbę niezłapanych pszczół.
public in t Score { get; p riv a te s e t; }
public TimeSpan TimeBetweenBees {
get {
double m illisecon ds = 500;
m illiseco n ds = Math.Max(milliseconds - Score * 2 .5 , 100);
return Tim eSpan.From M illiseconds(m illi seconds);
}
} Wraz z łapaniem przez gracza kolejnych
pszczół gra przyspiesza. Ta właściwość
p riv a te double _flowerW idth;
p riv a te double _beeWidth;
wylicza czas pomiędzy pojawianiem się
p riv a te double _flo w e rL e ft; kolejnych pszczół, zależny od wyniku
p riv a te double _playAreaWidth; uzyskanego przez gracza.
p riv a te double _hiveW idth;
p riv a te double _lastH ive Lo ca tio n ;
p riv a te bool _gameOver;
p riv a te readonly Random _random = new Random()

public void StartGame (double flowerWidth, double beeWidth double playAreaWidth. double hiveWidth) {
_flowerWidth = flowerWidth;
_beeWidth = beeWidth;
_playAreaWidth = playAreaWidth; \ t ?
_hiveWidth = hiveWidth; Metoda StartGame() przywraca grę do stanu
_lastH iveLo catio n = playAreaWidth / 2; początkowego. Model widoku przekaże do niej
M issesLeft = 5; szer°k°ść kwiatka, pszczoły oraz obszaru gry
Score = 0; i ula, które będą następnie używane podczas
_gameOver = f a ls e ; prowadzenia rozgrywki.
OnPlayerScored();
}

864 Rozdział 17.


Napisz grę Windows Phone

Ta metoda jest wywoływana


public void MoveFlower(double flo w erLeft) { za każdym razem, gdy gracz
_flo w e rL e ft = flo w e rL e ft; przesunie kwiatek. Jej działanie
sprowadza się do aktualizacji
} położenia kwiatka.
public void BeeLanded (double beeLeft) {
i f ((b eeLeft + _beeWidth) < _flo w e rL e ft || beeLeft > ( flo w erLeft + flowerWidth))
i f (M issesLeft > 0) {
M isse sLeft--;
OnMissed();
} else {
Niezależnie od tego, gdzie wyląduje pszczoła, model
_gameOver = tru e; sprawdza, czy jej lewy wierzchołek nie znajduje się
OnGameOver(); wewnątrz obszaru kwiatu. Jeśli się nie znajdzie,
j oznacza to, że gracz chybił; w przeciwnym razie
j należy powiększyć wynik gracza.
else i f (!_gameOver) j
Score++;
OnPlayerScored();
Za każdym razem, gdy z ula
j wylatuje nowa pszczoła, model
używa tej metody, by wyznaczyć
kolejne położenie ula w poziomie,
public double NextHiveLocation () j w którym trzeba będzie wyświetlić
double d e lta = 10 + Math.Max(1, Score * , 5 ); następną pszczołę. Metoda
upewnia się, że ul nie przesunął
się zbyt daleko — im więcej
i f (_lastH iveLo catio n <= _hiveWidth * 2)
punktów zdobywa gracz, tym dalej
_lastH iveLo catio n += d e lta ; będzie przesuwał się ul pomiędzy
else i f (_lastH iveLo catio n >= _playAreaWidth hiveWidth * 2) miejscami, w których pojawią się
_lastH iveLo catio n -= d e lta ; kolejne pszczoły.
else
_lastH iveLo catio n += d e lta * (_random.Next(2) == 0 ? 1 - i) ;

return lastH iveLo catio n;

public EventHandler Missed ;


p riv a te void OnMissed () {
EventHandler missed = Missed;
i f (missed != n u ll)
m isse d (th is, new Even tA rg s());
}

public EventHandler GameOver ;


p riv a te void OnGameOver () {
EventHandler gameOver = GameOver; Model widoku nasłuchuje tych zdarzeń,
i f (gameOver != n u ll) ^ dzięki którym może aktualizować widok
gameOver(this, new Even tA rg s()); za każdym razem, gdy użytkownik złapie
pszczołę i zdobędzie punkty, nie uda mu
} się złapać pszczoły lub gdy gra się skończy.
public EventHandler PlayerScored;
p riv a te void OnPlayerScored () {
EventHandler playerScored = PlayerScored;
i f (playerScored != n u ll)
p laye rS co red (th is, new Even tA rg s());
}
}

jesteś tutaj ► 865


Projekt dodatkowy

U T W Ó R Z F O L D E R V I E W I K O N T R O L K Ę B EEC o n t r o l .
W arstw a w idoku pisanej aplikacji sk ład a się z dw óch k o n tro lek użytkow nika. Pierw sza z nich nosi
nazw ę BeeControl i je st anim ow aną, rysunkow ą pszczołą, k tó ra b ędzie się p o ru szać w dół ekranu.
Z acznij o d u tw o rzen ia fo ld eru View . N astę p n ie kliknij go w o knie Solution Explorer praw ym przyciskiem
myszy i w ybierz opcję Add/N ew Item. N astęp n ie d o d a j n o w ą k o n tro lk ę Windows Phone UserControl i zapisz ją

w p lik u BeeControl.xaml.

Add New Item - BeeAttack


Installed Sort by: Default * ||= Search InstalledTemplates (Ctrl+E) P I-
M
i Visual C# Type: Visual C# jType|
Windows Phone PortraitPage Visua C#
Code 1 Add a new Windows Phone User Con
So lu tion Exp lo rer ~ □ <
General
Windows Phone ■ Windows Phone Landscape Page Visual C# b ■# Q S iS) <> P
XNA Game Studio4.0 Search Solu tion Exp lo rer (Ctrl-»-;) fi -
Windows Phone User Control Visua c#
1
Solu tion 'A ta kPszczo l' (1 p roject)
Windows Phone Panorama Page Visua c#
i a [ç#] A ta k P s z c z o l
> f* Properties
■ Windows Phone Pivot Page Visual C#
> References

SRGS Grammar Visua c# > l l A ssets


tt > ^ M odel
Visua c#
I* Voice Command Definition > ^ Resources
A Ú View
Class Visual C#
KT a ^ BeeControlotam l

IBeeControlJtaml > " Î ) B e eC o n tro l.xa m l.cs


> A p p .xa m l
> C« Lo ca lized String s.es

>. 0 M a inPag e .xam l


j So lu tion Exp lo rer C la ss V iew

T eraz m usisz zm od y fik o w ać k o d X A M L z a p is a n y w p lik u BeeControl.xaml . O dszukaj


w nim k o ntrolk ę G rid o nazw ie LayoutRoot i dodaj do niej przezroczyste tło. Poniżej
pokazaliśm y, ja k pow inien w yglądać k o d X A M L p o w prow adzeniu tej zmiany:

<Grid x:Name="LayoutRoot" Background="TransparentM>


<Image x:Name="image" S tre tc h = "F ill" />
</Grid>

M usisz także zadbać o grafiki używ ane do


anim acji pszczoły o raz o obrazki kw iatka o raz ula.
S o lu tio n Exp lo rer ~n X
M ożesz je znaleźć w przykładach dołączonych 0 (2 '0 ■ í O 1 is) * [ p ]

do książki, dostępnych n a serw erze FT P Search So lu tio n Exp lo rer (Ctrl-»-;) p-


w ydaw nictw a H elio n : a

> 0
A ssets
Tile s
D
A lig n m e n tG rid .p n g
ftp:lljip.helion.pllprzykladylcshru3.zip E 3 A p p lica tio n lco n .p n g
a Bee a n im a tio n l.p n g
N astęp n ie kliknij praw ym przyciskiem myszy Bee a n im a tio n 2.png

fo ld er Assets i d o d a j d o n ie g o p lik i, w y b ie ra ją c Bee a n im a tio n 3.png


Bee a n im a tio n 4 .p n g
o p cję Add/Existing Item. Po d o d an iu plików folder F lo w er.p n g
Assets pow inien w yglądać jak n a rysunku obok. H iv e (o u ts id e ).png
M od el
So lu tio n Exp lo rer C la ss V ie w

866 Rozdział 17.


Napisz grę Windows Phone

T eraz jesteś już gotów , by d o d a ć k o d C # k o n tr o lk i BeeControl . Przedstaw iliśm y go poniżej,


a pow inieneś go um ieścić w plik u BeeControl.xaml.cs:

using M icrosoft.Phone.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;

public sealed p a rtia l cla ss BeeControl : UserControl {


Kiedy obiekt Storyboard zakończy animację, wywołuje
public readonly Storyboard FallingStoryboard;
zdarzenie Completed. Ten przeciążony konstruktor
public BeeControl () { klasy BeeControl pobiera jako parametr delegat
th is .In itia liz e C o m p o n e n t(); EventHandler. Obiekt modelu widoku będzie używał
StartFlapping(Tim eSpan.From M illiseconds(30)); tego delegatu, by określać, kiedy pszczoła wylądowała.
}

public BeeControl(double X, double fromY, double toY, EventHandler completed) t h is ( ) {


FallingStoryboard = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
Ta animacja przesuwa
Storyboard.SetTarget(anim ation, t h is ) ; pszczołę ku dołowi
C a n v a s .S e tL e ft(th is, X ); kontrolki Canvas
Storyboard.SetTargetProperty(anim ation, new P ro p ertyPath ("(C an vas.To p )")); stanowiącej obszar
animation.From = fromY; gry, zaczynając od
animation.To = toY; wyświetlenia jej przy
animation.Duration = TimeSpan.FromSeconds(l); ulu, a kończąc, gdy
dotrze do kwiatka.
i f (completed != n u ll) FallingStoryboard.Completed += completed;

FallingStoryboard.C hildren.A dd(anim ation); Delegat EventHandler przekazany jako argument


Fa lling Sto ryb o ard .B e g in (); l zostaje użyty do obsługi zdarzenia Completed
} obiektu Storyboard.
public void StartFlapping (TimeSpan in te rv a l) {
List< string> imageNames = new L ist< strin g > () {
"Bee animation l.p n g ", "Bee animation 2.png" "Bee animation 3.png"
"Bee animation 4.png" } ;

Storyboard storyboard = new Storyboard();


ObjectAnimationUsingKeyFrames animation = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTarget(anim ation, image);
Storyboard.SetTargetProperty(anim ation, new Pro pertyPath ("Sou rce"));

TimeSpan cu rre n tln te rv a l = TimeSpan.FromMilliseconds(O);


foreach (s trin g imageName in imageNames) {
ObjectKeyFrame keyFrame = new DiscreteObjectKeyFram e();
keyFrame.Value = CreatelmageFromAssets(imageName);
;
keyFrame.KeyTime = c u rre n tln te v a l; Ta animacja ramek kluczowych,
animation.KeyFrames.Add(keyFrame); wyświetla wszystkie obrazki
cu rre n tln teval = c u rre n tln te v a l.A d d (in te rv a l); pszczoły, zapewniając efekt
} machania skrzydełkam.
storyboard.RepeatBehavior = RepeatBehavior.Forever;
storyboard.AutoReverse = true;
storyboard.Children.Add(anim ation);
sto ryb o ard .B eg in();

p riv a te s t a t ic BitmapImage CreateImageFromAssets(s trin g imageFilename) {


return new BitmapImage(new U ri("/A ss e ts /" + imageFilename, U riK in d .R e lative O rA b so lu te));

jesteś tutaj ► 867


Projekt dodatkowy

U T W Ó R Z K LA SĘ M O D E L U W ID O K U Solu tion Explorer

W arstw a m odelu w idoku naszej aplikacji b ędzie się składać z jed n ej klasy O (2) b - d « gl o p
Search Solution Explorer (Ctrl+;) f-
— BeeAttackViewModel — k tó ra b ędzie korzystać z m eto d , właściwości
Solution 'A takPszczo l' (1 project)
o raz zdarzeń m odelu, by aktualizow ać w idok. Utwórz folder ViewModel,
a [c*] A ta k P s z c z o l
a następnie dodaj do niego klasę BeeAttackViewModel. Poniżej > P Properties

przedstaw iliśm y jej kod: > References


> Assets
Widok używa właściwości > M odel

using View; ItemsPanel, by powiązać > 0 R esources


kolekcję obiektów BeeContro1 > 0 View
using System .Collections.ObjectM odel;
z właściwością Children a View M odel
using S yste m .C o lle ctio n s.S p e cialize d ; kontrolki Canvas, dzięki czernu > S 3 BeeA ttackView M odel.es
using System.ComponentModel; model widoku może dodawać > ,Cl A pp .xam l
using System.Windows; pszczoły, aktualizując swoje t> C» LocalizedStrings.es
using System.Windows.Controls; pole _beeControls. Solu tion Explorer C la ss V iew
using System.Windows.Threading;

cla ss BeeAttackViewModel : INotifyPropertyChanged { \


public INotifyCollectionChanged BeeControls { get { return _beeControls; } }
p riv a te readonly ObservableCollection<BeeControl> _beeControls
= new ObservableCollection<BeeControl>();

Te właściwości są powiązane z właściwościami


public
public
public
Thickness FlowerMargin { get; p riv a te s e t; )
Thickness HiveMargin { get; p riv a te s e t; ) }
in t MissesLeft { get { return _m odel.M issesLeft; ) )
Margin kwiatka i ula, dzięki temu obiekt
modelu widoku może je przesuwać w prawo
i w lewo, aktualizując wartość lewego marginesu
public in t Score { get { return _model.Score; ) )
i wywołując zdarzenie PropertyChanged.
public V is i b i l i t y GameOver { get; p riv a te s e t; )

p riv a te S ize _beeSize;


p riv a te readonly Model.BeeAttackModel _model = new Model.BeeAttackModel();
p riv a te readonly DispatcherTimer _tim er = new D ispatcherTim er();
p riv a te double _ la s tX ;
p riv a te S ize _playAreaSize { get; s e t; )
p riv a te S ize _h ive S ize { get; s e t; )
p riv a te S ize _flo w erS ize { get; s e t; )

public BeeAttackViewModel() {
Obiekt modelu widoku przechowujereferencję
_model.Missed += MissedEventHandler;
do instancji modelu w polu pryw‘atnym-
_model.GameOver += GameOverEventHandler; To właśnie w tym miejscu określane są
_model.PlayerScored += PlayerScoredEventHandler; procedury obsługi zdo-rzeń modelu.
_ tim e r.T ic k += HiveTim erTick;
GameOver = V is ib ili t y .V is ib l e ;
OnPropertyChanged("GameOver");
}
Oloiekt modelu widoku tworzy
public void StartGame (S ize flo w erS ize, S ize h iv e S ize , S ize playAreaSize) { każdą kontrolkę BeeControl
_flo w erS ize = flo w erS ize; i określa jej szerokość
_h iv e S ize = h ive Size ; i wysokość, musi w tym celu
_playAreaSize = playA reaSize;
dysponować odpowiednimi
informacjami.
_beeSize = new Size(playA reaSize.W idth / 10 playAreaSize.W idth / 10);
_model.StartGam e(flowerSize.W idth, _beeSize.WWidth, playAreaSize.W idth,
h ive Size .W id th );
OnPropertyChanged("MissesLeft");
_ tim e r.In te rv a l = _model.TimeBetweenBees; Widok może rozpocząć grę, wywołując metodę
_ t im e r .S t a r t (); StartGame klasy modelu widoku i przekazując do niej
wielkość kwiatka, ula oraz obszaru gry. Obiekt modelu
GameOver = V is ib ility .C o lla p s e d ;
widoku ustawia wartości swoich pól prywatnych,
OnPropertyChanged("GameOver"); uruchamia zegar i aktualizuje właściwość GameOver.
}

868 Rozdział 17.


Napisz grę Windows Phone
Kiedy gracz przeciągnie palcem po ekranie,
wywoływane jest zdarzenie ManipulationDelta,
a procedura jego obsługi wywołuje tę metodę
w celu aktualizacji położenia kwiatka.
i
mm
public void ManipulationDelta (double newX) j
newX = _la s tX + newX * 1 .5 ;
i f (newX >= 0 && newX < (_playAreaSize.W idth flo w erSize.W id th)) j
_model.MoveFlower(newX);
FlowerMargin = new Thickness(newX, 0, 0, 0 );
OnPropertyChanged("FlowerMargin");
_la s tX = newX;
j

p riv a te void GameOverEventHandler(object sender, EventArgs e) {


_ tim e r.S to p ();
GameOver = V is ib i l i t y .V i s i b l e ; Kiedy gra zostaje zakończona, obiekt modelu widoku zatrzymuje zegar
OnPrope rtyChanged("GameOv e r" ) ; i aktualizuje wartość właściwości GameOver, która jest powiązana
} z właściwością Visibility panelu StackPanel, zawierającego kontrolkę
TextBlock z tekstem informującym o zakończeniu gry oraz łączem.
p riv a te void MissedEventHandler(object sender, EventArgs e) {
OnPropertyChanged("MissesLeft");
}

void HiveTimerTick(object sender, EventArgs e) {


i f (_ playA reaSize .Wi'dth <= 0) re tu rn ; Za każdym razem, gdy zegar wywołuje zdarzenie Tick, obiekt
modelu widoku prosi model o wyliczenie kolejnego położenia ula
double x = _m odel.N extH iveLocation(); i wypuszcza nową pszczołę (tworząc kontrolkę BeeControl
i dodając ją do obserwowalnej kolekcji _beeControls, co powoduje
HiveMargin = new Thickn ess(x, 0, 0, 0 ); dodanie jej do właściwości Children kontrolki Canvas).
OnPropertyChanged("HiveMargin");

BeeControl bee = new BeeControl(x + _hiveSize.W idth / 2, 0,


_playAreaSize.H eight + _flow erSize.H eig h t / 3, BeeLanded);
bee.Width = _beeSize.W idth;
bee.Height = _beeSize.H eight;
_beeControls.Add(bee);
} Delegat wskazujący metodę BeeLanded jest przekazywany
do kontrolki BeeControl , która dodaje go do zdarzenia
p riv a te void BeeLanded(object sender, EventArgs e) { Completed animacji — dlatego też, kiedy zdarzenie to
BeeControl landedBee = n u ll; zostanie wywołane, atrybut sender będzie referencją do
foreach (BeeControl s p rite in _beeControls) {
obiektu Storyboard. Właściwość FallingStoryboard
i f (s p rite .F a llin g S to ry b o a rd == sender) -----
landedBee = s p rite ;
kontrolki BeeControl zwraca referencję do obiektu
}
Storyboard, dzięki czemu obiekt modelu widoku może
_model.BeeLanded(Canvas.GetLeft(landedBee)); jej użyć do odszukania w swojej kolekcji _beeControls
i f (landedBee != n u ll) _beeControls.Remove(landedBee); odpowiedniej kontrolki BeeControl.
}

public event PropertyChangedEventHandler PropertyChanged;


p riv a te void OnPropertyChanged (s trin g propertyName) {
PropertyChangedEventHandler propertyChanged = PropertyChanged;
i f (propertyChanged != n u ll)
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
j
Za każdym razem, gdy gracz zdobędzie
p riv a te void PlayerScoredEventHandler(object sender, EventArgs e) j
jakieś punkty, aktualizowana jest właściwość
OnPropertyChanged("Score"); TimeBetweenBees modelu, przez co kolejne
_tim e r.In te rv a l = _model.TimeBetweenBees; ^----- pszczoły będą pojawiały się w krótszych
j odstępach czasu.

jesteś tutaj ► 869


Projekt dodatkowy

U T W Ó R Z K O N T R O L K Ę B EEA T TACKG AME C ONTROL Z A R Z Ą D Z A J Ą C Ą C A Ł Ą G R Ą .


W arstw a w idoku zaw iera jeszcze jed en elem ent. K ontrolka BeeAttackGameControl zaw iera kontrolki Image kw iatka
oraz ula, kontrolkę Canvas, n a której będą prezentow ane pszczoły zlatujące w dół, o raz kontrolki wyświetlane po
zakończeniu gry (w tym także przycisk do jej rozpoczęcia). D odaj do p ro jek tu nową kontrolkę W indow s Ph on e User Control

nadaj je j nazwę BeeAttackGameControl.xaml i zapisz w folderze View.


Poniżej przedstaw iliśm y jej kod X A M L:

<Grid x:Name="LayoutRoot" Background="SkyBlue">


<Grid.RowDefinitions>
<RowDefinition Height="*"/>
Kiedy tworzysz nową kontrolkę użytkownika Windows Phone, IDE dodaje
<RowDefinition Height="10*"/
ją do pustej kontrolki Grid o nazwie LayoutRoot. Zmteń jej wdtócnwśc
BackGround na SkyBlue i zmodyfikuj tak, by siatka składata się
<RowDefinition Height="2*"/>
z trzech wierszy: w górnym będzie prezentowany obrazek u|a<_
</Grid.RowDefinitions>
w środkowym znajdzie się obszar gry, a w do|nym obrazek kwiatka.
<Image x:Name="hive"
Source="/Assets/Hive (outside).png" Właściwość Margin obrazka ula jest
HorizontalAlignment="Left" powiązana z właściwością HiveMargin
Margin="{Binding HiveMargin}"/> obiektu modelu widoku.

<ItemsControl Grid.Row="1" x:Name="playArea">


<ItemsControl ItemsSource="{Binding BeeControls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/> WłaściwośC PternsSource kontrolki ItemsControl je s t powiązana
</ItemsPanelTemplate> z obserwowalną t e r c ją obiektu modelu widoku, przechowującą
</ItemsControl.ItemsPanel> kontrolki BeeControl. Dzięki temu każda pszczoła zostanie
</ItemsControl>
wyświetlona w kontrolce Canvas umieszczonej w szablonie
ItemsPanelTemplate.
</ItemsControl>

<TextBlock Grid.Row="1" Foreground="Black" VerticalAlignment="Top">


<Run>Możesz przegapić je szcze </Run>
<Run Text="{Binding M issesLeft}"/>
<Run> pszcz6T</Run>
Te kontrolki TextBlock prezentują
</TextBlock> wynik gracza oraz liczbę pszczół,
które gracz może przegapic,
<TextBlock Grid.Row="1" Foreground="Black" VerticalAlignment="Top" zanim gra się skończy.
HorizontalAlignment="Right" Text="{Binding Score}"
Style= "{StaticR eso urce PanoramaItemHeaderTextStyle}"/

<Image x:Name="flower"
Source="/Assets/Flower.png" Właściwość GameOver obiektu modelu widoku
Grid.Row="2" zwraca wartości typu w yliczeniow y Visibility,
HorizontalAlignment="Left" można ją zatem bezpośrednio powiązaC w k°dzie
Margin="{Binding FlowerMargin}"/> XAML z właściwością Visibility.
Aby móc skorzystać
z tego wiersza,
<StackPanel Grid.Row="1" VerticalAlignm ent="Center"
potrzebujesz
określenia HorizontalAlignment="Center" V is ib ility = "{B in d in g GameOver}">
przestrzeni nazw <StackPanel O rientation= "H orizontal" HorizontalAlignment="Center">
XML xmlns:view <TextBlock Foreground="Yellow"
przedstawionego Style= "{StaticR esource JumpListAlphabetSmallStyle}">Atak</TextBlock>
na następnej <view:BeeControl Width="75" Height="75"/>
stronie. Pozwala <TextBlock Foreground="Black"
on wyświetlić na Style= "{StaticR esource JumpListAlphabetSmallStyle}">pszcz6T</TextBlock>
stronie pszczołę </StackPanel>
machającą <Button Click="Button_Click">Rozpocznij nową grę</Button> Procedura obsługi kliknięć tego przycisku
skrzydełkami. /StackPanel> ^ ______ została przedstawiona na następnej stronie.
</Grid>

870 Rozdział 17.


Napisz grę Windows Phone

N astęp n ie do znacznika <UserControl> na p o czątk u pliku X A M L dodaj właściwość


xmlns:view oraz p ro c e d u rę obsługi zd arzeń M anipulationDelta . W p row adzanie zm ian
zacznij o d um ieszczenia k u rso ra b ezp o śred n io p rz ed zam ykającym naw iasem > w linii 1 0 .
i naciśnięcia klaw isza Enter w celu d o d an ia now ego w iersza. N a stęp n ie z a c z n ij w pisyw ać
xmlns:view="" — gdy tylko w piszesz zn ak cudzysłowu, IntelliSense wyświetli okienko
sugerujące sposób d okończenia przestrzen i nazw:

xmlns:view=""
http://sch e m as.m icro so ft.co m /clie n t/2 0 0 7
http://schem as.m icrosoft.com /expression/blend/2008
http://schem as.m icrosoft.com /w infx/2006/xam l Z wyświetlonego
h ttp ://schem as.openxm lform ats.org/m arkup-com patibility/2006 okienka wybierz
AtakPszczol (AtakPszczol) przestrzeń nazw
View. Jeśli swojemu
AtakPszczol.Model (AtakPszczol)
projektowi nadałeś inną
AtakPszczol.Resources (AtakPszczol) nazwę, to zobaczysz
AtakPszczol.View (AtakPszczol) ją w okienku zamiast
AtakPszczol.ViewModel (AtakPszczol)
" nazwy AtakPszczol.

Ponow nie naciśnij klawisz Enter i zacznij wpisywać ManipulationDelta="" , by d odać p ro ced u rę
obsługi zdarzeń. ID E wyświetli o k ienko IntelliSense, pozw alające d odać do k o d u u krytego strony
now ą p ro c e d u rę obsługi zdarzeń:

xmlns:view="clr-nam espace:AtakPszczol.View”

K iedy zakończysz w prow adzanie tych zm ian, w otw ierającym znaczniku <UserControl>
p ojaw ią się dwa dodatkow e w iersze: jeśli IDE z jakiegoś powodu nie
xmlns:view="clr-namespace:AtakPszczol.View" \wyświetli °kienkafatelh^iine, ^
to mozesz te wiersze w całości
ManipulationDelta="UserControl_ManipulationDelta" ' wpisać ręcznie.

W k ońcu otw órz p lik BeeAttackGameControl.xaml.cs i d o d a j d o n ieg o n a s tę p u ją c y k o d C # :

using ViewModel;

public p a rtia l c la ss BeeAttackGameControl : UserControl {


p riv a te readonly ViewModel.BeeAttackViewModel _viewModel = new ViewModel.BeeAttackViewModel();
\ '
publi c BeeAt t a c kGameContr o l( ) { Kontrolka gry dysponuje
I ni t i a l i zeComponent( ) , instancją obiektu modelu Kliknięcie przycisku r°zpoczynaj ącego grę
DataContext - _ viewModel; ^ — widoku, której używa jako powoduje wywołanie metody totairttJameO
} swojego kontekstu danych. Z — obiektu modelu widoku i pirzekazanie d°
niej wielkości, które są mu potrzetme.
p riv a te void B u tton _C lick(o bject sender, RoutedEventArgs e) { "'•f
_viewM odel.StartGame(flower.RenderSize, hive.R enderSize, playA rea.RenderSize);
}

p riv a te void UserControl_M anipulationDelta(object sender,


System.Windows.Input.ManipulationDeltaEventArgs e) {
_view M odel.M anipulationD elta(e.D eltaM anipulation.Translation.X);

} \ Działanie procedury obsługi zdarzeń Marnpiiula^onDelt'a


ogranicza się do bezp°średniego wywtfama metody jesteś tutaj ► 871
obiektu modelu widoku. ' '
Projekt dodatkowy

O Z A K T U A L IZ U J S T R O N Ę G Ł Ó W N Ą — D O D A J D O N IE J K O N T R O L K Ę G R Y .
T eraz, kiedy u d ało się n am herm etyzow ać całą grę w ew nątrz kontro lk i BeeAttackGameControl,
w ystarczy dodać ją do strony głównej aplikacji. O tw órz p lik MainPage.xaml i do otwierającego
znacznika <phone:PhoneApplicationPage> dodaj przestrzeń nazw xmlns:view , ta k sam o jak
zrobiłeś w pliku BeeAttackGameControl.xaml.
Także w tym przypadku, jeśli swojemu
xmlns:view="clr-namespace:AtakPszczol.View" ^ ------- pr°jektowi nadałeś inną nazwę, te ta
przestrzeń nazw będzie jej odpowiadać.

N astęp n ie odszukaj k o n tro lk ę G rid o nazw ie ContentPanel i dodaj do niej swoją kon tro lk ę
BeeAttackGameControl. U m ieść k u rso r p om iędzy znacznikiem otw ierającym i zamykającym,
a następ n ie zacznij wpisywać <view: — ID E wyświetli okien k o IntelliSense, z k tó reg o pow inieneś
wybrać kon tro lk ę BeeAttackGameControl :

< ! --ContentPanel - place addi t i o n a l c ontent h e r e - - >


<Grid x : N a m e = ’,C o n t e n t P a n e l ’' G r i d . R o w = ”l ” M a r g i n = " 1 2 , 0 , 1 2 j G " >
<view:y>
< /Grid> ^
BeeAttackGameControl
[U] BeeControl

O to ja k pow inien w yglądać k o d X A M L p o w prow adzeniu zm ian:

<!--ContentPanel - place add itio na l content here-->


<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<view:BeeAttackGameControl/>
</Grid>

Gratulujemy — świetna robota!


G dy tylko w prow adzisz zm iany w pliku MainPage.xaml, w oknie
Designer p ow in n a się pojaw ić stro n a głów na aplikacji. T eraz m ożesz
ju ż u ru ch o m ić grę w e m u lato rze lub n a u rz ą d z e n iu ... m ożesz także
w ypróbow ać swą kreatyw ność, rozszerzając m ożliwości gry!

★ S próbuj d odać bonusow e pszczoły, za k tó re gracz d ostanie


w ięcej punktów , bądź też dzikie pszczoły, których gracz musi
unikać.

★ Spraw , by n iek tó re pszczoły latały n a boki, do d ając do nich


odp o w ied n ią anim ację (właściwości C anvas.Left ).

★ D o plików dodaliśm y tak że o b razek w n ętrza ula.


Czy po trafisz zastosow ać go w jakiś interesujący sposób?

★ Pochwal się swoimi osiągnięciami, publik u jąc swój kod


n a C odePlex, G itH u b lub innej w itrynie pozw alającej na
u d o stę p n ian ie kodu.

872 Rozdział 17.


Dodatek A Pozostałości

10 najważniejszych rzeczy, które


chcieliśmy umiescic w tej książce

Z a b a w a dopiero się z a c zy n a ! Pokazaliśmy Ci mnóstwo wspaniałych narzędzi


do tworzenia naprawdę potężnych programów w C#. Nie było jednak możliwe, abyśmy
w tej książce zmieścili każde narzędzie, technologię i technikę — nie ma ona po prostu
tylu stron. Musieliśmy podjąć naprawdę przemyślaną decyzję, co umieścić, a co pominąć.
Oto kilka tematów, których nie mogliśmy przedstawić. Pomimo tego, że nie zajęliśmy się nimi,
w dalszym ciągu myślimy, że są one ważne i przydatne. Należałoby więc chociaż o nich
wspomnieć. Tak też zrobiliśmy

to je st n o w y ro z d z ia ł ► 873
M ic ro s o ft Ci p o m o ż e

1. Na temat aplikacji dla Sklepu Windows


można dowiedzieć się znacznie więcej
Chciałbyś się dow iedzieć czegoś w ięcej n a te m a t aplikacji dla S klepu W indow s? F irm a
M icrosoft przygotow ała dla C iebie fantastyczne m ateriały dydaktyczne1. Pierwszym
krokiem , aby z nich skorzystać, je st p o b ra n ie p a k ie tu W indow s 8 C am p T raining
Kit, w skład któ reg o w chodzą p rezen tacje, przykłady, łącza do innych użytecznych
m ateriałów , a przed e wszystkim zestaw praktycznych laboratoriów , k tó re nauczą
Cię n iem al wszystkiego, zaczynając o d p o b iera n ia zdjęć z w budow anego a p a ra tu
fotograficznego, a kończąc n a tw o rzen iu aktywnych kafelków o raz przekazyw aniu
pow iadom ień do aplikacji. P ak iet te n m o żn a p o b ra ć ze strony:

http://www. microsoft.com/en-us/download/details.aspx?id=29854

Po jego zainstalow aniu n a Tw oim k o m p u terze pojaw i się zbiór stro n W W W , plików
m ultim edialnych i p rezen tacji, jak rów nież d o k u m en tacja i kody źródłow e przykładów.
Stanow i on doskonały kolejny k ro k n a d rodze do dalszego p oznaw ania języka C # .

W elcom e to W indow s 8 Cam p in a box


Windows Camps are free trainings tojumpstart your Windows Store app development. You can find a camp near you via our
registration site: http://www.devcamps.ms/windows.

"Windows Camp in a box" is an off-line version of the resources we use for our camps. This kit includes the hands-on-labs, presentations,
samples (with source), and links to additional resources.

Once you have completed and polished your app, attend an application excellence lab to get a developer token to submit your app to
the Windows store.

Happy Windows 8 coding!

Let us know how we are doing atwin8tkfb@microsoft.com

1 Materiafy te są dostępne w języku angielskim - przyp. tłum.

874 D odatek A
Pozostałości

jesteś tutaj ► 875


Bardzo byśmy chcieli przedstawić zamieszczone tu
P o d s ta w y , o k tó ry c h c h c e s z w ie d z ie ć informacje równie wyczerpująco, jak opisywaliśmyJ nne
zagadnienia. Niestety, zabrakło nam na to stron! Chcemy
Ci jednak zapewnić dobry^ punlct startowy ofoz wskazać
źródła dalszych informacj i.
2 . Podstawy
Z an im zaczniem y, przedstaw im y klasę Guy, któ rej będziem y używali jak o przykładu w tym d o d atku . Przyjrzyj się uw ażnie
tem u w jaki sposób została o n a skom entow ana. Czy zw róciłeś uw agę, że zarów no klasa, jak i jej właściwości zostały opisane
przy użyciu k o m entarzy rozpoczynających się trz em a znakam i uko śn ik a ( / / / )? Są to komentarze X M L , a ID E po m ag a
nam je dodaw ać. W ystarczy w pisać „///” b ezp o śred n io p rz e d definicją klasy, m etody, właściwości lub p o la (o raz w kilku
innych m iejscach kodu ), a środow isko autom atycznie d o d a szkielet takiego k o m en tarza. Później, kiedy będziesz chciał
skorzystać z m etody, właściwości lub klasy (itd.), ID E wyświetli okien k o IntelliSense z in form acjam i po b ran y m i z tych
kom entarzy.
Komentarze XML dla klasy
I I I <summary> zawierają blok <summary>.
Zwfóć uwagę, że zaczyna się
I I I Facet - o ja k iś imieniu (name), wieku (age) i z portfelem pełnym kasy on od znacznika <summary>
I I I <|summary> i kończy znacznikiem
class Guy </summary>.

{
I*
* Zwróć uwagę, że Name i Age są właściwościami, którym towarzyszą pola wewnętrzne
* przeznaczone ty lk o do odczytu. Zastosowanie modyfikatora readonly oznacza,
* że wartość tych pól może być określona wyłącznie w momencie in ic ja liz a c ji
* obiektu (w ich d e k la ra c ji lub w konstruktorze).
*I

/ / / <summary>
I I I Wewnętrzne pole ty lk o do odczytu dla właściwości Name Tworzenie pól tylko do odczytu
poprawia hermetyczność klasy, gdyż
/ / / </summary> po utworzeniu obiektu ich wartości
p riv a te readonly s trin g name; j uż nigdy me będzie można zmienić.

/ / / <summary>
I I I Imię faceta
/ / / </summary>
I
Słowo kluczowe readonly poznałeś
w rozdziale 16., jednak chcieliśmy
p u b lic s trin g Name { get { return name; } } o nim napisać także tutaj, na wypadek
gdybyś zajrzał do tego dodatku, zanim
/ / / <summary> przeczytasz rozdział I6-
I I I Wewnętrzne pole ty lk o do odczytu dla właściwości Age
/ / / </summary>
p riv a te readonly in t age;

/ / / <summary>
I I I Wiek faceta
/ / / </summary>
p u b lic in t Age { get { return age; } }

/*
* Właściwość Cash nie je s t ty lk o do odczytu, gdyż je j wartość może się zmieniać
* podczas is tn ie n ia obiektu Guy.
*/

/ / / <summary>
I I I Ilo ś ć gotówki w posiadaniu faceta
/ / / </summary>
p u b lic in t Cash { get; p riva te se t; }

876 Dodatek A
Pozostałości

I I I <summary> Kiedy IDE dodaje szkie/et


III K o n s t r u k t o r u sta w ia im ię , w iek i posiadaną gotówkę. ^ in s t r u k t o r a tob innej metody,
I I I <|summary> dOdaj e -t akże zna1czniki <Param>
I I I <param name="name">Imię faceta </param > ^ a azegO z lc parametrów.
I I I <param name="age">Wiek faceta </param>
I I I <param name="cash">Początkowa i l o ś ć gotówki</param>
p u b l i c G u y ( s t r in g name, i n t age, i n t cash) {
t h is .n a m e = name;
t h i s . a g e = age;
Cash = cash;
}

p u b l i c o v e r r i d e s t r i n g T o S t r in g ( ) {
r e t u r n S t r i n g . F o r m a t ( " { 0 } ma {1 } l a t a i {2} z f g o t ó w k i . " , Name, Age, Cash);

1
W t ym miejscu przesłaniamy
W <summary> metodę ToStringC). Rozwi a nie to
III B ie r z e fa c e to w i p ie n ią d z e z p o r t f e l a . opisa/iśmy w rozdzia/e 8.
I I I <Isummary>
I I I <param name="amount">Odbierana kwota pie nię d zy</pa ram >
I I I < re tu rn s > W ie lk o ś ó oddanej kwoty lu b 0, j e ś l i f a c e t n i e ma w y s ta rc z a ją c o dużo g o t ó w k i < / r e t u r n s >
p u b l i c i n t G iv e C a s h ( in t amount) {
i f (amount <= Cash && amount > 0)
{
Cash -= amount;
r e t u r n amount;
}
e ls e
{
r e t u r n 0;
}
}

I I I <summary>
I I I Dodaje gotówkę do p o r t f e l a f a c e t a .
I I I <Isummary>
I l l <param name="amount">Otrzymana kwota</param>
I I I <returns>Wysokośó otrzym anej kwoty lu b 0, j e ś l i fa c e t n ic nie otrz y m a f< /re tu rn s >
p u b l i c i n t R e ce iv e C a s h (in t amount) {
i f (amount > 0)
{
Cash += amount;
r e t u r n amount;
}
C o n s o le . W r it e L i n e ( " { 0 } mówi: {1 } z f n i e j e s t kwotą, j a k ą bym p r z y j ą f . " , Name, amount);
r e t u r n 0;
}

jesteś tutaj ► 877


W ię c e j in f o r m a c ji p o d s ta w o w y c h

Więcej podstaw...
U cząc się now ego języka prog ram o w an ia, łatw o m o żn a poczuć się przytłoczonym . C # nie je st p o d tym w zględem
żadnym w yjątkiem . T o w łaśnie z teg o p o w o d u skoncentrow aliśm y się n a tych jego asp ek tach , k tó re, ja k w ynika z naszych
dośw iadczeń, są najistotniejsze i najczęściej stosow ane p rzez początkujących i śred n io zaaw ansow anych program istów .
Jed n ak C # o raz .N E T p o siad ają tak ie konstrukcje składniow e, k tó re znacznie łatwiej m o żn a opisać w łasnym i słowami,
kiedy już zdobędzie się pew ne dośw iadczenie. N a kilku kolejnych stro n ach przedstaw iliśm y przykładow ą aplikację
konsolow ą, k tó ra je dem onstruje.

s ta tic void M a in (s trin g [] args)


{
/ / Będziemy używali tych in s ta n c ji Guy i Random w dalszej części przykładu.
Guy bob = new Guy("Bob", 43, 100); Najlepszym sposobem, by zrozumieć te zagadnienia,
Guy joe = new Guy("Joe", 41, 100); je s t skorzystanie z debuggera, by przyjrzeć się
Random random = new Random(); działaniu programtu. Warto podczas hMtuiny^k^ąź^
poeksperymentować z niektórymi rozwiązaniami-

/*
* Oto dwa przydatne słowa kluczowe używane w pętlach. Słowo "contlnue"
Wiele osób * nakazuje rozpoczęcie następnej ite r a c ji p ę t li, natomiast "break" powoduje
uważa, że * natychmiastowe przerwanie je j wykonywania.
*
instrukcje
skoków * In stru kcje break, continue, throw oraz return są nazywane Instrukcjam i skoków,
są złym * gdyż ich wykonanie powoduje przeskoczenie do innego miejsca programu. (Poznałeś
rozwiązaniem. * in s tru k c ję break wraz z in s tru k c ją switch/case w rozdziale 8 ., a throw
Zazwyczaj * w rozdziale 10.). Is tn ie je jeszcze jedna in s tru k c ja skoku - goto. Umożliwia
istnieją inne * ona przeskoczenie do miejsca opatrzonego e ty k ie tą . (Na pewno poznasz te e ty k ie ty ,
sposoby * bo mają podobną składnię do tych stosowanych w in s tru k c ji case).
umożliwiające *
uzyskanie * Poniższą p ętlę bez żadnego problemu mógłbyś napisać bez używania in s tru k c ji
tych samych * continue i break. Stanowi ona doskonały przykład pokazujący, ja k C# pozwala zrobić
efektów. * to samo na w iele różnych sposobów. To właśnie dlatego nie musiałeś używać
Warto jednak, * in s tru k c ji break, continue ani innych słów kluczowych i operatorów do pisania
byś wiedział, * programów przedstawionych w te j książce.
jak one *
działają, jeśli * In stru kcja break je s t także stosowana wraz z case, o czym mogłeś się przekonać
kiedyś się na * w rozdziale 8.
nie natkniesz. */

w hile (true) {
in t amountToGive = random.Next(20);

Instrukcja // Słowo kluczowe continue powoduje rozpoczęcie następnej it e r a c ji p ę t li.


break // Użyjemy go, by przekazywać Joemu jedynie kwoty
powoduje // większe od 10 złotych.
przerwanie _ i f (amountToGive < 10) Instrukcja continue powoduje, że program pomija
realizacjipętli continue; dalszą część pętli i zaczyna nową iterację.
i przejście
programu do
wywołania // Słowo kluczowe break przerywa wykonywanie p ę t li,
Console. if (joe.ReceiveCash(bob.GiveCash(amountToGive)) == 0)
WriteLineC). break;

Console.WriteLine("Bob dał Joemu {0} z ło tych , Joe ma {1} zło ty c h , a Bob ma {2} z ło ty c h .",
amountToGive, joe.Cash, bob.Cash);
}
Console.WriteLine("Bobowi zostało {0} z ło ty c h .", bob.Cash);

878 Dodatek A
Pozostałości

I I Operator warunkowy ? : je s t odpowiednikiem in s tru k c ji if/th e n /e ls e zapisanych


/ / w jednym wyrażeniu.
/ / [te s t logiczny] ? [in s tru k c ja wykonywana, gdy tru e ] : [in s tru k c ja wykonywana, gdy fa lse]
Console.WriteLine("Bob {0} więcej gotówki n iż Jo e.",
bob.Cash > joe.Cash ? "ma" : "nie ma");

I I Operator łączący n u li ?? sprawdza, czy podana wartość to n u li. Zwraca on wartość,


/ / j e ś l i je s t ona różna od n u li, lub drugą z podanych wartości w przeciwnym razie.
/ / [sprawdzana wartość] ?? [wartość zwracana, j e ś l i n u li]
bob - n u li; ^ Ponieważ bob wynosi null,
Console.WriteLine("Wynik zwrócony przez ?? to '{ 0 } '" , bob ?? jo e ); operator- ?? zwróci j oe.

I I A oto przykład p ę tli używającej In s tru k c ji goto 1 e ty k ie t. Rzadko można


/ / je zobaczyć, jednak w przypadku stosowania zagnieżdżonych p ę tli mogą się okazać
/ / przydatne. (In s tru k c ja break powoduje jedynie zakończenie p ę tli najbardziej
/ / wewnętrznej).
fo r ( in t i = 0; i < 10; i++)
{
fo r ( in t j = 0; j < 3; j++)
{ Instrukcja goto powoduje
i f (i > 3) L________przeskoczenie programu bezpośrednio
fif, inolc/iT/moi otllkie.tiJ
goto afterLoopT do wskazanej etykiety.
Console.W riteLine("i = {0 }, j = {1 }", i , j ) ;
} Etykieta. to łańcuch znaków składający
się z liter, cyfr i znaków podkreślenia
zakończony dwukropkiem.

I I Kiedy używasz operatora = w przypisaniu, zwraca on wartość, k tó re j możesz


/ / następnie użyć w kolejnym przypisaniu lub w in s tru k c ji i f .
a; _____ Ta instrukcja najpierw zapisuje iloczyn
in t b = (a = 3 * 5 ); ^ " 3 * 5 w a, a następnie tę samą wartość
Console.W riteLine("a = {0 }; b = { ! } ; " , a, b ); zapisuje w zmiennej b.

I I J e ś li umieścisz operator ++ przed zmienną, to najpierw zostanie ona


/ / powiększona, a dopiero potem program wykona resztę in s tr u k c ji.
a - ++b * 10;------------------------------------------------------ Użycie ++b oznacza, że najpierw wartość
Console.W riteLine("a = {0 }; b = { 1 } ;" , a, b ); b zostanie zwiększona, a dopiero później
wynik b*W z°stanie zapisany w zmiennej a.
I I Umieszczenie ++ za zmienną powoduje wykonanie in s tru k c ji w pierwszej ko le jno ści,
/ / a potem inkrementację zmiennej.
a = b++ * 10; Użycie b++ oznacza, że najpierw w zmiennej
Console.W riteLine("a = {0 }; b = { 1 } ;" , a, b ); ^ a zapisywany jest iloczyn b * 10, a dopiero
potem wartość zmiennej b jest inkrementowana.
I*
Używając operatorów && i || w testach logicznych, wykorzystujemy "przetwarzanie
* skrócone", co oznacza, że przetwarzanie warunkukończy s ię , gdy ty lk o okaże s ię ,
że jego wynik je s t ju ż znany. W przypadku (A || B), je ś l i A wynosi tru e , to całe
wyrażenie A || B także będzie mieć wartość true niezależnie od B.
W przypadku przetwarzania (A && B), j e ś l i A wynosi fa ls e , to całe
wyrażenie także będzie mieć wartość fa lse niezależnie od B. W obu sytuacjach
* B nigdy nie zostanie przetworzone, gdyż jego wartość je s t niepotrzebna do
podania wynikowej wartości całego wyrażenia.
*I
in t x - 0 , p Użyjemy tych wartości Kiedy używasz / * oraz */ ^
in t y = 1 0 ;? w kodzie przedstawionym do wstawienia koment'arzy w pliku,
in t z = 2 0 ; ^ na następnej stronie. poszczególne wiersze komentarza nie
muszą rozpoczynać się od znaku *. jesteś tutaj ► 879
Korzystanie ze skróconego przetwarzania, jakie zapewniają /ogiczne
Jeszcze w ię c e i n f o r m a c j i p o d s t a w o w y c h operatory „ ' ub" oraz „i", jest Lko/ejnym sp° s° bem t ^ rzenm
efektywnych instrukcji warunkowych. To tak, jakbyś powiedział:
„Wykonaj (y / x == 4), ty/ko jeś/i (y < z) ma wartość true.
/ / y / x spowoduje z g ł o s z e n i e w y j ą t k u D i v i d e By Z er o Ex ce pt i o n , gdyż x wynos i 0.
// J e d n a k z e w z g l ę d u na t o , że (y < z) wynosi true, operator || wie, iż całe
// wyrażenie przyjmie wartość t r u e , bez konieczności wyliczania jego drugiej części.
// Dl atego drugi c z ł o n wa r u n k u : (y / x == 4 ) , n i g d y n i e z o s t a n i e wykonany,
if ( ( y < z) || (y / x == 4 ) )
Console.WriteLine("Wiersz pojawił się, gdyż o p e r a t o r || używa s k r ó c o n e g o p r z e t w a r z a n i a . " ) ;

// Poni e wa ż (y > z) ma w a r t o ś ć f a l s e , o p e r a t o r && w i e , że c a ł e wyrażenie


// przyjmie wartość f a l s e , bez konieczności wyliczania d r u g i e g o c z ł o n u .
// D l a t e g o n i g d y n i e z o s t a n i e on wykonany, a wyjątek nie zo s tan ie zgłoszony.
if ( ( y > z) && (y / x == 4 ) )
Console.WriteLine("Ten wiersz nigdy nie zostanie wyświetlony, gdyż && t e ż używa s k r ó c o n e g o p r z e t w a r z a n i a . " ) ;

/*
Wi e l u z n a s , myśl ąc o programowaniu, myśl i o zerach i jedynkach, a do
sprawdzania tych z e r i jed ynek s ł u ż ą o p e r a t o ry logiczne.
/

// Użyj C o n v e r t . T o S t r i n g ( ) oraz Convert.ToInt32(), by p r z e k o n w e r t o w a ć l i c z b ę na ł a ń c u c h


// z e r i j e d y n e k l u b by ł a ń c u c h z e r i j e d y n e k p r z e k o n w e r t o w a ć na p o s t a ć b i n a r n ą .
// Dr ugi argument o k r e ś l a , ż e k o n we r t u j e my na l i c z b ę z a p i s a n ą w s y s t e m i e dwójkowym.
s t r i n g binaryValue = Convert.T oString(217, 2);
int intValue = Convert.ToInt32(binaryValue, 2);
Console.WriteLine("Dwójkowa l i c z b a {0} ma d z i e s i ę t n ą w a r t o ś ć {!}.", binaryValue, intValue);

/ / Operatory &, |, ~ oraz - o d p o w i a d a j ą o p e r a t o r o m l o g i c z n y m AND, OR, X0R o r a z


// d o p e ł n i e n i u b i t owe mu.
nt v ail = Convert.ToInt32("100000001", 2);
Logiczne operatory &, | oraz A operują domyś/ni’e na
nt val2 = Convert.ToInt32("001010100", 2); wszystkich typach całkowitych, wyh'czeniowych oraz na
nt or = v ail | val2; wartościach logicznych. Jedyna różnią pomiędzy & i &&
(oraz I i II) operujących na wartościach /ogicznych po/ega
nt and = v a i l & val2;
na tym, że nie wykorzystują one przetwarzania skróconego.
nt xor =v ail ^ val2;
_ je st /ogiczną negacją operującą na typach całkowitych
int not =- v a i l ; ^ --------------

---------- i wy/iczeniowych i odpowiada operatorowi ! d/a wartości
/ogicznych.
// Wyś wi et l amy w a r t o ś c i - używamy S tring.P adLeft() , by w y ś w i e t l i ć p o c z ą t k o w e z e r a .
Console.WriteLine("vall: {0}' Convert.ToString(vall, 2)); Metoda Convert.ToString()
Console.WriteLine("val2: {0}' Convert.ToString(val2, 2).PadLeft(9, '0 ' ) ) ; zwraca obiekt string, a my
wywołujemy na rzecz
Console.W riteLine("or: {0}", Convert.ToString(or, 2).PadLeft(9, '0 '));
tego obiektu PadLe^^
Console.WriteLine("and: {0}", Convert.T oString(and, 2).PadLeft(9, '0')) by w wynikach wyświethć
Console.WriteLine("xor: {0}", Convert.ToString(xor, 2).PadLeft(9, '0')) początkowe zera.
Console.WriteLine("not: {0}", Convert.ToString(not, 2).PadLeft(9, '0'))
// Zauważ, ż e o p e r a t o r - z w r ó c i ł : 11 1111111 1111 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 .
// To 32-bitowe dopełnienie w a r t o ś c i z m i e n n e j v a i l : 0 0000000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 .
// i n t , k t ó r e s ą 32-bitowymi liczbami całkowitymi.
O p e r a t o r y l o g i c z n e o p e r u j ą na l i c z b a c h

ŻF Wszystko to nabierze większego sensu, gdy uruchomisz program


i spojrzysz na wyniki. Pamiętaj, nie musisz niczego wpisywać ręcznie
— możesz pobrać kody z serwera wydawwchM He/ion:
ftp://ftp.he/ion.p//przyk/ady/cshru3.zip.
880 Dodatek A
Pozostałości

// Operatory « i » p r z e s u w a j ą b i t y w lewo i w p r a wo . Dowolne o p e r a t o r y l o g i c z n e


/ / możes z p o ł ą c z y ć z =, a wi ę c » = l u b &= d z i a ł a j ą p o d o b n i e do += l u b *=.
int bits = Convert.ToInt32("ll", 2);
for (int i = 0; i < 5; i++)
{
bits « = 2;
Console.W ritetine(Convert.ToString(bits, 2).PadLeft(12, '0 '));
}
for (int i = 0; i < 5; i++)
{
To nie ma nic wspólnego
bits » = 2; z logiką. To normalne,
Console.W ritetine(Convert.ToString(bits, 2).PadLeft(12, '0 ')); w miarę często stosowane
rozwiązanie.

// Możesz u t w o r z y ć nową i n s t a n c j ę k l a s y i wywoł ać na j e j r z e c z me t o d ę bez


// w c z e ś n i e j s z e g o p r z y p i s y w a n i a j e j do z m i e n n e j .
C on so l e .W r i te ti n e (n e w Guy("Harry", 47, 376).ToString());

// W k s i ą ż c e do ł ą c z e n i a ł a ń c u c h ó w znaków uż y wa l i ś my o p e r a t o r a + i w s z y s t k o
// d z i a ł a ł o d o s k o n a l e . J e dnak w i e l e osób u ni ka s t o s o w a n i a t e g o o p e r a t o r a w p ę t l a c h ,
// k t ó r e b ę d ą w i e l o k r o t n i e wykonywane, gd yż ka ż d e j e g o u ż y c i e p o wodu j e u t w o r z e n i e na
// s t e r c i e nowego o b i e k t u , k t ó r y pot em b ę d z i e m u s i a ł z o s t a ć u s u n i ę t y . W ł a ś n i e z t e g o
// w z g l ę d u .NET u d o s t ę p n i a k l a s ę StringBuilder, k t ó r a d o s k o n a l e n a d a j e s i ę do
// e f e k t y w n e g o t w o r z e n i a i ł ą c z e n i a ł a ń c u c h ó w . J e j metoda Append() d o d a j e ł a ń c u c h na
// k ońc u, AppendFormat() d o d a j e ł a ń c u c h s f o r ma t o w a n y ( u ż y w a j ą c p r z y t ym {0} o r a z
// {1}, t a k j a k r o b i ą t o me t o d y S t r i n g . F o r m a t ( ) o r a z C o n s o l e . W r i t e L i n e ( ) ) , a me t oda
// AppendLine() d o d a j e l i n i j k ę z e z n a k i e m nowego w i e r s z a na k o ń c u . Aby u z y s k a ć
// o s t a t e c z n y ł a ń c u c h , w y s t a r c z y wywoł ać me t o d ę T o S t r i n g ( ) .
S t r i n g B u i l d e r S t r i n g B u i l d e r = new S t r i n g B u i l d e r ( " C z e ś ć ");
stringBuilder.Append("kolego, ");
stringBuilder.AppendFormat("wita {0}-letni facet imi eni em {1}. " , j o e . A g e , joe.Name);
s t r i n g B u i l d e r . A p p e n d t i n e ( " J a k ą z w s p a n i a ł ą pogodę d z i ś mamy."); Jedna uwaga: W tym konkretnym przypadku
Console.W ritetine(stringBuilder.ToStringO); wydajn°ść °biektu StrimgBuilder będzie mniejsza
. niż wydajn°ść operatora +, gdyż ten drugi
Obiektu StringBuilder- u j M s'.ę wstępnie dolicza długość łańcucha i dokładnie
Console.ReadKey(); zazwyczaj w sy^a^ac^ gdy n\e określa, ile pamięci należy dla niego przydzielić
wiadomo, ile operacji łączenia
łańcuchów trzeba będzie wykonać.

/*
* To w y s t a r c z a j ą c e i n f o r m a c j e , by z a c z ą ć p r ogr amowa ć w C#, j e d n a k w żadnym wypadku
* nie są one kompletne ani wyczerpujące. Na s z c z ę ś c i e M i c r o s o f t u d o s t ę p n i a
* d o k u m e n t a c j ę z a w i e r a j ą c ą wykaz w s z y s t k i c h o p e r a t o r ó w , s ł ó w k l uc z owyc h o r a z i nn y c h
* c e c h t e g o j ę z y k a . P r z e j r z y j j ą - j e ś l i d o p i e r o z a c z y n a s z poznawać C#, n i e p r z e j m u j
* s i ę t ym, ż e może s i ę ona wydawać t r u d n a do z r o z u m i e n i a . W i t r y n a MSDN j e s t
* w s p a n i a ł y m ź r ó d ł e m i n f o r m a c j i , choć n i e z o s t a ł a p o m y ś l a n a j a k o c e n t r u m do n a u k i .
*
* d o k u m e n t a c j a j ę z y k a C#: h t t p : / / m s d n . m i c r o s o f t . c o m / p l - p l / l i b r a r y / 6 1 8 a y h y 6 . a s p x
* o p e r a t o r y C#: h t t p : / / m s d n . m i c r o s o f t . c o m / p l - p l / l i b r a r y / 6 a 7 1 f 4 5 d . a s p x
* s ł o wa k l u c z o we C#: h t t p : / / m s d n . m i c r o s o f t . c o m / p l - p l / l i b r a r y / x 5 3 a 0 6 b b . a s p x
*/

jesteś tutaj > 881


M u s im y c o ś z ło ż y ć

3 . Przestrzenie nazw i złożenia


Podjęliśm y decyzję, by w tej książce skoncentrow ać uw agę n a praktycznych zagadnieniach, któ re musisz poznać, by pisać
i urucham iać aplikacje. W każdym z rozdziałów tworzyłeś projekty w V isual Studio i urucham iałeś je w debuggerze.
Pokazaliśm y Ci, gdzie są um ieszczane kody skom pilow ane do postaci p ro g ram u wykonywalnego o raz ja k publikow ać swoje
program y, tak by inne osoby mogły je zainstalow ać n a swoich kom puterach. T o wystarczy, byś był w stanie w ykonać każde
ćwiczenie zam ieszczone w tej książce. W arto jed n a k cofnąć się i przyjrzeć dokładniej tem u, co w rzeczywistości budujesz.
K iedy kom pilujesz swój p ro g ram n apisany w C # , tworzysz złożenie (nazyw ane także zestaw em ; ang. assembly). Jest to plik
zaw ierający skom pilow any kod. Istn ieją dw a ro d zaje złożeń. W ykonyw alne (czasam i nazyw ane tak że złożeniam i procesów ,
ang. process assembly) m ają ro zszerzenie .EXE . W szystkie p rogram y, jak ie pisałeś podczas lektury tej książki, były właśnie
plikam i w ykonywalnymi. S ą to złożenia, k tó re m ożesz w ykonać (no w iesz... pliki .exe m ożesz dw ukrotnie kliknąć i zostaną
one w ykonane). Istn ieją także złożenia biblioteczne (ang. library assembly), k tó re m ają ro zszerzenie .D LL . Z aw ierają
one klasy, których m ożesz używać w swoich p ro g ram ach , a ja k się niebaw em przek o n asz, w sposobie korzystania z nich
znaczącą rolę odgryw ają p rzestrzen ie nazw.
A by zdobyć podstaw ow e um iejętności zw iązane ze złożeniam i, m ożesz najpierw stworzyć bibliotekę klas, a n astęp n ie użyć
jej w innym program ie. Zacznij o d u tw o rzen ia w Visual Studio 2012 for Desktop now ego p ro je k tu typu Class Library; nadaj
m u nazw ę Headfirst.Csharp.Leftover3. B ezp o śred n io p o u tw orzeniu biblioteki zaw iera o n a p lik o nazw ie Class.cs. Usuń go,
a n astęp n ie dodaj do projektu nowy p lik klasy o nazw ie Guy.cs. O tw órz ten plik.

namespace H e a d f i r s t . C s h a r p . L e f t o v e r 3
{ Biblioteki można także tworzyć w Visual Studio for Windows 8.
c la s s Guy Poprosiliśmy Cię jednak, żebyś utworzył ten projekt w Visual Studio
{ for Windows Desktop, gdyż dodaje ona odwołania do wszystkich
} złożeń .NET Framewrok, dzięki czemu okno „Reference Manager"
} przedstawione na następnej stronie będzie puste.

Z w róć uw agę, jak V isual Studio dostosow ało nazw ę p rzestrzen i nazw do nazwy biblioteki. T o stan d ard o w e rozw iązanie.
T eraz m ożesz uzupełnić zawartość klasy Guy — wstaw do niej k o d p rzedstaw iony w drugim p unkcie teg o d o d atk u ; już
niedługo będziem y go p o trzebow ać. N a stęp n ie dodaj do biblioteki dwie kolejne klasy: H i T h e r e W r i t e r o ra z L i n e W r i t e r .
Poniżej przedstaw iliśm y k o d pierw szej z nich.

namespace H e a d f i r s t . C s h a r p . L e f t o v e r 3
{
p u b l i c s t a t i c c la s s H iT h e r e W rite r
{
p u b l i c s t a t i c v o id H i T h e r e ( s t r i n g name)
{
MessageBox.Show("Siemka! Mam na im ię " + name);
}
}
}

A o to kod klasy L i n e W r i t e r (także o n a należy do p rzestrzen i nazw H e a d f i r s t . C s h a r p . L e f t o v e r 3 ):

i n t e r n a l s t a t i c c la s s L i n e W r it e r {
p u b l i c s t a t i c v o i d W r i t e A L i n e ( s t r i n g message)
{
C o n so le .W rite L in e (m e ssa g e ); Naszej bibliotece nadaliśmy nazwę Headf1rst.Csharp.Leftover3,
}
gdyż jest to standardowy sposób nazywania złożeń.
Więcej informacji na ten temat możesz znaleźć na stronie
}
http://msdn.microsoft.com/pl-pl/library/ms229048.aspx.
882 D odatek A
Pozostałości

A te ra z spróbuj skom pilow ać p ro g ram . Pojaw i się następ u jący błąd:

Error List

- © 1 Error ! 0 W arnings | © 0 Messages Search Error List P-


Description File a L.. ^ C. ^
[x ) 1 The nam e MessageBox' does n o t exist in th e current context HiThereW riter.es 13 13
I I
N o dobra, nie m a sprawy — wiemy, ja k sobie z tym poradzić. N a p o czątk u plik u dodaj następ u jący w iersz kodu:
u sin g System .W indow s.Form s;

Chwila! N asza biblio tek a w ciąż nie chce się skom pilow ać! D ziw na spraw a. Czy podczas w pisyw ania powyższej instrukcji
u s in g zauw ażyłeś, że o k ienko IntelliSense p rzestało w yświetlać podpow iedzi, gdy d o tarłeś do „using System .W in” ? D zieje
się tak dlatego, że w projekcie nie ma odwołania do złożenia System.Windows.Forms.
R ozw iążm y zatem te n p ro b lem , do d ając n iezb ęd n e odw ołanie. P rzejdź do o k n a Solution Explorer i rozw iń opcję References.
K liknij ją praw ym przyciskiem myszy i w ybierz opcję A d d Reference...; n a ek ran ie pojaw i się poniższe o k n o dialogowe.

W oknie dialogowym R eferen ces M an ag er, w k o lum nie z lewej strony


rozw iń opcje A ssem bilies o raz F ram ew ork, a n astęp n ie odszukaj na
liście. T eraz w folderze References pow in n a pojaw ić się pozycja S y stem .
W indow s.Form s, a nasza b iblioteka skom piluje się bez problem ów !

Okno dialogowe Reference Manager określa, jakie złożenia należy wyświetlać,


analizując klucz Rejestru systemowego, a nie GAC. Więcej informacji na ten
temat znajdziesz na stronie http://support.microsoft.com/kb/306149.

jesteś tutaj ► 883


A w ię c d l a t e g o t o z ro b iliś m y !

To co ja właściwie zrobiłem?
Przyjrzyj się dokładnie deklaracjom klas L in e W r ite r o ra z H iT h e re W rite r:
p u b llc c l a s s H iT h ereW riter
In te rn al s t a t i c c l a s s L in e W rite r

W deklaracjach tych zastosowaliśmy m odyfikatory dostępu : klasa H iT h e re W rite r zo stała p o p rze d zo n a m odyfikatorem
p u b lic , n ato m iast L in e W r ite r m o dyfikatorem in t e r n a l . Już za chwilę napiszem y aplikację konsolow ą, k tó ra korzysta
z naszej nowej biblioteki. P ro g ram m oże odwoływać się do klas publicznych biblioteki jedynie bezpośrednio (choć klasy
m ogą być także używ ane p o śred n io , n a przykład gdy je d n a m e to d a wywołuje in n ą lub zw raca o biekt klasy w ew nętrznej
im plem entującej jakiś publiczny interfejs).

W róćm y zatem do naszej klasy Guy i przyjrzyjm y się jej deklaracji:


c l a s s Guy

Poniew aż nie p o d a n o w niej żadnego m odyfikatora d o stęp u , dom yślnie został użyty m ody fik ato r i n t e r n a l .
B ędziem y chcieli używać klasy Guy w innych p ro g ram ach , zatem zamieńmy ją n a klasę publiczną:
p u b llc c l a s s Guy

A te ra z spróbuj uru ch o m ić p ro g ram w debuggerze. N a ek ran ie pojaw i się następ u jący kom unikat:

K iedy się n ad tym zastanow isz, dojdziesz do w niosku, że to całkiem logiczne — w k ońcu b ib lio tek a klas nie m a żadnego
p u n k tu w ejścia. T o jedynie g ru p a klas, których m ogą używać in n e program y. A zatem dodajm y p ro g ram wykonywalny,
który będzie używ ał naszej biblioteki — dzięki tem u d ebugger będzie m ógł coś u ruchom ić. V isual S tu d io u d o stęp n ia
napraw dę w ygodne narzęd zie, z k tó reg o m ożem y skorzystać przy takich okazjach: pozw ala dodaw ać w iele pro jek tó w do
jednego rozw iązania. K likn ij prawym przyciskiem myszy nazwę rozwiązania w oknie Solution Explorer i wybierz opcję
Add/New Project; n a ek ran ie pojaw i się o k n o dialogow e Add Project. D odaj now ą aplikację konsolow ą o nazw ie MyProgram.

Kiedy już dodasz nowy program , jego p ro jek t pow inien pojaw ić się w oknie Solution Explorer poniżej biblioteki klas. Prawym
przyciskiem myszy kliknij opcję References n ależącą do p ro jek tu MyProgram i wybierz A d d Reference, następnie rozw iń opcję
■* Solution i kliknij Projects. Pow inieneś n a niej zobaczyć utw orzoną wcześniej bibliotekę klas IfTMUffOBTOlfimiSlWWBBl
— zaznacz ją i kliknij przycisk OK . B iblioteka pow inna się teraz pojaw ić w oknie Solution Explorer.

Przejdź n a sam p o czątek pliku Program.cs p ro je k tu MyProgram i zacznij wpisywać instrukcję u sin g :

u sin g H e a d f ir s t.C s h a r p .L e f to v e r 3 ; ^\. Czy zauważyteś, ze podczas


wpisywania okienko Int^hSanüe
podpowiada „Csharp oraz
„Leftover3"?

884 Dodatek A
T eraz m ożem y napisać now y pro g ram . Zacznij o d w pisania Guy. O bserw uj podpow iedzi Pozostałości
i inform acje w yśw ietlane n a ekranie:
W tekście książki niejednokrotnie
piszemy o kompilacji kodu. Kiedy
to robisz, kod jest kompilowany do
Common Intermediate Language (IL)
W o k ienku IntelliSense pojaw i się p e łn a nazw a przestrzen i nazw, do jakiej należy klasa — języka niskiego poziomu używanego
Guy, dzięki czem u m ożesz się p rzek o n ać, że używasz klasy zdefiniow anej w zupełnie przez .NET. Jest to asembler, którego
innym złożeniu. D o k o ń cz p ro g ram : kod nadaje się do analizy i pisania przez
człowieka i do którego kompilowane są
s t a t i c v o id M a i n ( s t r i n g [ ] args) wszystkie programy pisane na platformie
{ .NET (w tym także te w C # i Visual
Guy guy = new G uy("Ja ne k ", 43, 125); Basicu). Kod IL jest z kolei kompilowany
H iT h e re W rite r.H iT h e re (g u y .N a m e );
do kodu maszynowego w momencie
} uruchamiania programu przez CLR.
A te ra z go uru chom . O rany! Z n o w u pojaw ił się te n sam k o m u n ik at co wcześniej, Służy do tego kompilator JIT (ang. just­
inform ujący o tym , że nie m o żn a u ruchom ić biblioteki klas! N ie m a p ro b lem u . Kliknij in-time). Jego nazwa wzięła się stąd,
projekt MyProgram praw ym przyciskiem myszy i w ybierz o p cję O Set 35 startup Project, że kod IL jest kompilowany w ramach
wykonywania programu, a nie wcześniej
W skład rozw iązania m oże w chodzić w iele różnych projektów , a w ten sposób
— na etapie jego przygotowywania do
inform ujesz ID E , który z nich należy w ykonać podczas u ru ch am ian ia debuggera. wykonania.
Jeszcze raz spróbuj u ru ch o m ić p ro g ram — te ra z w k ońcu się udało!
Oznacza to, że pliki EXE i DLL
Zbuduj program „W itaj, świecie" z poziomu wiersza poleceń zawierają kod IL, a nie kod maszynowy.
Ma to duże znaczenie, gdyż dzięki
N au k a p ro gram ow an ia w iąże się z n ap isan iem tradycyjnego ju ż p ro g ra m u Witaj, świecie: temu wiele języków może kompilować
p ro steg o p ro g ram u , k tóry wyświetla pojedynczy w iersz tek stu (w łaśnie: „W itaj, św iecie”). kod źródłowy do IL, zapewniając
Jest to zazwyczaj pierw szy p ro g ram pisany w poznaw anym języku, gdyż um iejętność tym samym możliwość wykonywania
programów przez CLR. Należą do
jego napisan ia pokazuje, że dysponujem y w ystarczającym i u m iejętnościam i, by zabrać
nich między innymi: Visual Basic
się za pisan ie bardziej złożonych program ów . P ro g ram Developer C om m and Prom pt jest .NET, F # , J # zarządzane C ++/C L I,
instalow any w raz z V isual S tudio 2012, a kiedy go uruchom im y, k o m p ilato r C # , csc.exe, JScript .NET, Windows PowerShell,
będzie dostępny. U ru ch o m zatem Developer Command Prompt i skorzystaj z N o tatn ik a, IronPython, IronRuby. Jest to bardzo
by napisać p ro g ram WitajSwiecie.cs, n a stęp n ie skom piluj go do po staci p ro g ram u użyteczne rozwiązanie: kod VB.NET
jest kompilowany do IL, zatem istnieje
w ykonyw alnego, używ ając csc.exe, i wykonaj:
możliwość napisania złożenia w C #
D eveloper C om m and Prom pt fo r VS2012
i wykorzystania go w programie pisanym
w Visual Basic.NET (i na odwrót).
c :\Users\Public\Documents>type HelloWorld.es
using System,; Jeśli używasz Macintosha lub systemu
elass HelloWorld {
public static void Main(string[] args) { Linux, to wypróbuj Mono. Jest to
Console.WriteLine("Witaj, t'wiecie! "); ogólnie dostępna implementacja IL
ł
pozwalająca na wykonywanie programów
c :\Users\Public\I>ocuments>csc HelloWorld.es
Kompilator Microsoft (R) Visual C# w wersji 4.0.30319.33440
EXE napisanych w systemie Windows
dla programu Microsoft (R) .MET Framework 4.5 (wystarczy wydać polecenie mono
Copyright (C) Microsoft Corporation. Wszelkie prawa zastrzeżone.
MyProgram.exe, choć zazwyczaj działa
ono tylko z niektórymi złożeniami .NET).
c :\Users\Public\Documents>HelloWorld.exe
Witajj świecie1 Nie będziemy już więcej pisać o Mono,
gdyż nasza książka koncentruje się na
c :\Users\Public\Documents>-
technologiach Microsoftu. Musimy
jednak przyznać, że oglądanie symulatora
ula lub gry w karty na Macu lub Linuksie
jest naprawdę fascynujące!

To tylko jedno spośród wielu zagadnień związanych z tworzeniem i stosowaniem złożeń. Jest ich znacznie więcej
(w tym także i te związane z określaniem ich wersji oraz ich podpisywaniem w celu zapewnienia bezpieczeństwa).
Więcej informacji o złożeniach możesz znaleźć na stronie http://msdn.microsoft.com/pl-pl/library/k3677y81.aspx.

jesteś tutaj ► 885


Ż ą d a n ie o d p o w ie d z i

4 . Użyj BackgroundWorker, by poprawić działanie interfejsu użytkownika


W niniejszej książce pokazaliśm y dwa sposoby pozw alające program om n a jednoczesne wykonywanie więcej niż jednej
operacji. W rozdziale 2. dow iedziałeś się, ja k użyć m etody A p p lica tio n .D o E ve n ts() , by odpow iadać n a poczynania
użytkow nika podczas wykonywania pętli. Jed n a k nie je st to dobre rozwiązanie (i to z kilku pow odów ), dlatego też
w rozdziale 4. zastosowaliśm y zegar, który sygnalizuje zdarzenie w określonych odstępach czasu. W dalszej części książki
dow iedziałeś się, jak korzystać z instrukcji async i await oraz klasy Task. A lternatyw ą dla program ow ania asynchronicznego
są w ątki, jed n ak korzystanie z nich m oże być tru d n e i prow adzić do krytycznych błędów , jeśli nie zachow am y odpow iedniej
ostrożności. N a szczęście .N E T u d o stęp n ia napraw dę przydatny k o m p o n en t upraszczający tw orzenie zadań, któ re będzie on
m ógł wykonywać w tle. N osi on nazw ę B ackgroundW orker i w tym punkcie pokażem y Ci przykład jego zastosow ania.

O to prosty projekt, który pom oże Ci zrozum ieć, ja k działa kom ponent BackgroundWorker. Zacznij o d utw orzenia formularza.
U m ieść na nim pole w yboru (nadaj m u nazwę useBackgroundWorkerCheckbox), dwa przyciski ( goButton oraz cancel Button )
oraz kom ponent ProgressBar (o nazwie progressBarl ). N astępnie przeciągnij n a form ularz kom ponent BackgroundWorker.
Pojawi się on u dołu okna projek tan ta formularzy. Zachowaj jego oryginalną nazwę — backgroundWorkerl — a właściwościom
WorkerReportsProgress oraz WorkerSupportsCancelation przypisz wartości tru e .

Oto komponent
BackgroundWorker. Zwróć
uwagę, że ma on bardzo
niewiele właściwości,
które możesz ustawiać.

Z aznacz k o m p o n en t BackgroundWorker i przejdź n a stro n ę Events w o knie Properties (w tym celu kliknij przycisk z ikoną
błyskawicy). P ojaw ią się trzy d o stęp n e zdarzenia: DoWork, ProgressChanged o ra z RunWorkerCompleted. D w ukrotnie
kliknij każde z nich, by d odać o d pow iednie p ro ced u ry obsługi.

K od fo rm ularza został przedstaw iony n a następnych stronach.

886 Dodatek A
Pozostałości

I I I <summary>
I I I Zużywamy cykle procesora zwalniając d zia ła n ie programu poprzez wykonywanie operacji
I I I przez 100 ms.
I I I <|summary>
p riv a te void WasteCPUCycles() { Metoda WasteCPUCycles()
DateTime startTim e = DateTime.Now wykonuje całą masę złożonych
double value = Math.E; operacji matematycznych, zajmując
w hile (DateTime.Now < startTime.AddMilliseconds(lOO)) procesorowi aż 1 0 0 milisekund;
value /= Math.PI; później jej działanie się kończy.
value *= M ath.S qrt(2);
}
}

I I I <summary>
I I I K lik n ię c ie przycisku Jazda! rozpoczyna zużywanie c y k li procesora przez 10 sekund.
I I I <Isummary>
p riv a te void goButton_Click(object sender, EventArgs e) {
goButton.Enabled = fa lse ;
i f (¡useBackgroundWorkerCheckbox.Checked) {
/ / J e ś li nie używamy wątku roboczego, zaczynamy marnować cykle procesora.
fo r ( in t i = 1; i <= 100; i + + ) Kiedy użytkownik kliknie przycisk Jazdal, m^oda
WasteCPUCycles(); obsługująca zdarzenie sprawdzi, czy zostało zaznaczone
progressBarl.Value = i pole wyboru „Użyj BackgroundWorker“. Jeśli nie,
Jeśli formularz to formularz będzie zajmował proces°r przez W sekun<d.
używa komponentu }
goButton.Enabled tru e ; Jeśli jednak zaznaczono pole, to wywołana zostanie
BackgroundWorker, metoda RunWorkerAsync() komponentu Backgr°undWorker,
uaktywniamy } else {
cancelButton.Enabled = tru e ; nakazując mu wykonywanie tych samych operacji w tle.
przycisk Anufaj *_

/ / J e ś li używamy wątku roboczego uruchamiamy go, wywołując metodę


/ / RunWorkerAsync().
backgroundWorkerl.RunWorkerAsync(new Guy("Bob" 37, 146));

} Nakazuj ąc komponentowi BackgroundWorker rozpoczęcie wykonywania operacji,


mozesz przekazać do niego argument. W tym przypadku przekazujemy obiekt Guy
(je9° definicję znajdziesz w punkcie 2 . tego dodatku).
I I I <summary>
I I I Obiekt BackgroundWorker uruchamia swoją procedurę obsługi zdarzenia DoWork
I I I i wykonuje ją w t le .
I I I <Isummary>
p riv a te void backgroundWorkerl_DoWork(object sender, DoWorkEventArgs e) {
/ / Właściwość e.Argument zwraca argument przekazany w wywołaniu RunWorkerAsync().
Console.WriteLine("BackgroundWorker argument: " + (e.Argument ?? " n u li" ) '
A oto doskonały przykład zastosowania operatora ??, o którym
I I Zaczynamy marnować cykle procesora, wspommaliśmy w punkcie 2. Jeśli e.Argument wynosi nuli,
fo r ( in t i 1; i < 100;i++) { to zwencamiy"null"; w przeciwnym razie
WasteCPUCycles(); zwracamy e.Argument.
I I Używamy metody BackgroundWorker.ReportProgress() do wyświetlenia danych
/ / dotyczących postępów wykonywanych o p e ra c ji.
backgroundWorkerl.ReportProgress(l);
/ / J e ś li właściwość BackgroundWorker.CancellationPending wynosi tru e , to
/ / kończymy.
i f (backgroundWorkerl.CancellatlonPendlng) {
Console.WriteLine("Anulowane"); Kiedy zostanie wywołana metoda RunWorkerAsyncO
break; komponentu BackgroundWorker, zacznie on wykonywać
} Właściwość CancellationPending w tle metodę obsługi zdarzenia DoWork. Zwróć uwagę, że
określa, czy została wywołana cyklicznie wywołuje ona metodę WasteCPUCycles(), zajmując
metoda CancelAsync() komponentu w ten sposób procesor. Dodatkowo wywoływana jest metoda
BackgroundWorker. ReportProgress(), która sygnalizuje, jaki procent zadania
został już wykonany (liczba od 0 do 1 0 0 ).

jesteś tutaj > 887


B e z p ie c z n e ty p o w a n ie Komponent BackgroundWorker sygnalizuje swoje zdarzenia ProgressChanged
oraz RunWorkerCompleted, gdy właściwościom WorkerReportsProgress oraz
WorkerSupportsCancellation zostaną przypisane wartości true.
I I I <summary>
I I I Komponent BackgroundWorker s y g n a l i z u j e z d a r z e n i e P r ogr es s Chan ged, ki edy wąt ek roboczy
I I I zgTosi p o s t ę p w wykonywanych o p e r a c j a c h .
I I I <|summary>
p r i v a t e voi d ba c k g r o u n d Wo r k e r l _ P r o g r e s s Ch a n g e d ( o b j e c t s e n d e r , Pr ogr es sChangedEvent Ar gs e) {
p r o g r e s s B a r l . V a l u e = e . P r o g r e sOsOPl eC lr cC eC InI t a g e ;
Gdy procedura obstugi zdar^ema DoWor-k wywołuje metodę ProgressChanged(),
Mmponemt Ba<:kgroundWorker sygmlizuj e zdarzenie ProgressChanged, przy czym
właściwość e.ProgressPercentage zostaje ustawiona na wartość przekazaną
III <summary> w wywołaniu metody ProgressChanged(). r
III Komponent BackgroundWorker s y g n a l i z u j e z d a r z e n i e RunWorkerCompleted, ki edy z a d a n i e zo s t aTo
III wykonane ( l u b anul owane) .
III <Isummary>
p r i v a t e voi d backgr oundWor ker l _RunWor ker Compl eted( obj ect s e n d e r , RunWorkerCompletedEventArgs e)
{
goButton.Enabled = t r u e ; Kiedy operacje zostają zakończone, metoda obsługując zdarzmy
cance lBut ton. Enabl ed = f a l s e ; r RunWorkerCompleted ponownie uaktywnia przycisk Jazda! i wytącza
} przycisk Anuluj.

I I I <summary>
I I I Kiedy użyt kowni k k l i k n i e p r z y c i s k An u l u j , z o s t a n i e wywoTana met oda Backgr oundWor ker . Cance l Async( ) ,
I I I co spowoduje z g ł o s z e n i e komuni kat u o anul owani u wąt ku.
I / / < Isummary> , , Jeśli ut y kownik Miknie przycisk Anuluj,
p r i v a t e voi d c a n c e l B u t t o n _ C l i c k ( o b j e c t s e n d e r , EventArgs e) { spowoduje to wywołanie metody CancelAsync()
backgroundWorkerl.CancelAsync(); ^ komponentu BackgroundWorker, która
} przekazuj e komunikat o przerwaniu pracy.

Kiedy już zakończysz pisanie formularza, uruchom program. Bez trudu zauważysz, że wykorzystanie komponentu BackgroundWorker
znacznie poprawia reakcje programu na poczynania użytkownika.
-# Upewnij się, że pole wyboru Użyj BackgroundWorker nie zostało zaznaczone, a następnie kliknij przycisk Jazda! Zobaczysz,
jak pasek postępu powoli się wypełnia. Spróbuj przeciągnąć okno formularza — nie uda Ci się. Formularz będzie zablokowany.
Jeśli będziesz mieć szczęście, to po jakimś czasie przeskoczy w inne miejsce, odpowiadając na Twoje próby przeciągania.
-# Kiedy operacje zostaną zakończone, zaznacz pole wyboru Użyj BackgroundWorker i ponownie kliknij przycisk Jazda! Teraz
program będzie reagował znacznie lepiej. Bez żadnych opóźnień będziesz mógł go przeciągać, a nawet zamknąć. Kiedy prace
zostaną zakończone, procedura obsługi zdarzenia RunWorkerCompleted przywróci przyciski do stanu początkowego.
-# Podczas działania programu (z użyciem komponentu BackgroundWorker) kliknij przycisk Anuluj. Spowoduje to zmianę wartości
właściwości C ancellatio n P en d in g , co z kolei nakaże programowi przerwać pętlę.
Zastanawiasz się zapewne, dlaczego należy używać metody R e p o rtP ro g re ss(), a nie bezpośrednio ustawiać wartość właściwości Value
komponentu ProgressB ar. Spróbuj tak zrobić. Dodaj poniższy wiersz do metody obsługi zdarzenia DoWork:
progressB arl.V alue = 10;
Następnie ponownie uruchom program. Gdy tylko dotrze on do nowego wiersza kodu, zostanie zgłoszony wyjątek
In validO perationE xcep tio n z informacją: „Cross-thread operation not valid: Control ‘progressBarl’ accessed from a thread other
than the thread it was created on.”2. Jest on zgłaszany, ponieważ komponent BackgroundWorker uruchamia osobny wątek i w nim
wykonuje metodę DoWork(). A zatem w programie istnieją dwa wątki: ten, w którym działa formularz ze swoim graficznym interfejsem
użytkownika, oraz drugi wątek działający w tle. Według zasad działania .NET jedynie wątek GUI może aktualizować kontrolki
formularza; w każdym innym przypadku zostanie zgłoszony wyjątek.

To jedynie jeden z wielu problemów, jakie mogą przydarzyć się początkującemu programiście próbującemu używać wątków — i to właśnie z tego
powodu całkowicie pominęliśmy w tej książce związane z nimi zagadnienia. Jeśli jednak chcesz się z nimi zapoznać, gorąco polecamy doskonały
e-book Joego Albahari w całości poświęcony wykorzystaniu w ątków w C# i .NET. Znajdziesz go na stronie http://www.albahari.com/threading.

2 Niedozwolona operacja pomiędzy wątkami: komponent progressBarl został użyty z innego wątku niż ten,
w którym go utworzono — przyp. tłum.

888 Dodatek A
Pozostałości

5 . Klasa Type oraz metoda GetType()


Jednym z najpotężniejszych aspektów języka C # je st b ard zo bogaty system typów. T ru d n o go je d n a k w p ełn i docenić,
zanim zdobędziem y w iększe dośw iadczenie w pisan iu p ro g ram ó w — co w ięcej, początkow o m oże być zagadkow y
i przysparzać problem ów . P om im o to chcem y dać Ci choć p rze d sm a k tego, ja k działają typy w C # i .NET. Poniżej
zaprezen to w an a została aplikacja konsolow a przedstaw iająca n iek tó re z narzędzi pozw alających n am n a p ra c ę z typam i.
Ty/ko prze/otnie o tym wspomina/iśmy,
c l a s s Program {
a/e przypominamy, że istnieje
c l a s s Ne s t e d Cl a s s {
moż/iwość zagnieżdżania jednych k/as
p u b l i c c l a s s Doubl eNes t edCl as s {
w e w nątrz mnych. N asz program zawiera
/ / Zawar t oś ć k l a s y z a g n i e ż d ż o n e j . . .
zag nieżdżoną k/asę NestedC/ass, wewnątrz
której umieszczona jest ko/ejna k/asa
Doub/eNestedC/ass.
Mo ż e s z użyć słowa k/uczowego łypeof, by zamienić
s t a t i c v oi d M a i n ( s t r i n g [ ] a r gs ) {
typ (taki jak Guy, int czy DateTime) w obiekt
Z *" Type guyType = typeof(Guy);
Type. Dzięki temu będziesz m ó gł pobrać petną
A oto punkt Co n s o l e . Wr i t e L i n e ( " { 0 } r o z s z e r z a {!}'
wejścia n a z w ę typu oraz jego typ baz°w y (gdyby nie
guyTy pe. FullName, dziedziczył po żadnym innym typie, to jego typem
programu. guyType. Bas eType. Ful l Name) ; b a j o w y m będzie System .Object).
/ / wy ni ki : TypeExamples.Guy r o z s z e r z a S y s t e m. Ob j e c t

Type n e s t e d Cl a s s T y p e = t y p e o f ( N e s t e d C l a s s . D o u b l e N e s t e d C l a s s ) ;
Co n s o l e . Wr i t e L i n e ( n e s t e d C l a s s T y p e . F u l l N a m e ) ;
Gdy pobierzesz obiekt Type d/a
/ / wy ni ki : TypeExampl es . Pr ogr am+Nes t e dCl ass +Doubl eNes t edCl as s
typu generycznego, jego nazwą
będzie nazwa typu z dodanym
List<Guy> g u y L i s t = new Li s t <Guy>( ) ;
odwrotnym apostrofem oraz /iczbą
C o n s o l e . W r i t e L i n e ( g u y L i s t . G e t T y p e ( ) .Name); jego generycznych parametrów.
/ / wy ni ki : L i s t ' 1

D i c t i o n a r y < s t r i n g , Guy> g u y D i c t i o n a r y = new D i c t i o n a r y < s t r i n g , Guy>();


Co n s o l e . Wr i t e L i n e ( g u y D i c t i o n a r y . Ge t Ty p e ( ) . N a m e ) ;
To jest k/asa ^ H wyniki: D i c t i o n a r y ' 2
Właściwość Fu//Name,
System.Type. której uży/iśmy na poraątiuu
Meto d a ^ p Type t = t y p e o f ( P r o g r a m) ; f/oat to nazwa
programu, je s t składową k/asy zastępcza typu
GetType() zwraca C o n s o l e . Wr i t e L i n e ( t . F u l l Na me ) ;
System.Type. System.Sing/e,
obiekt Type. / / wy ni ki : TypeExampl es. Program
a int — typu
Type i nt Type = t y p e o f ( i n t ) ; System.Int32.
Type i nt 32Type = t y p e o f ( I n t 3 2 ) ; Oba te typy
Co n s o i e . Wr i t e L i n e ( " { 0 } - {1}", i n t Ty p e . F u l l Na me , i n t 3 2 Ty p e . F u l l Na me ) ; są strukturami
/ / Syst em.I nt 32 - Syst em. Int 32 ______________
(poznałeś je
w rozdzia/e H .J
Co n s o l e . Wr i t e L i n e ( " { 0 } {1}", f l o a t . M i n V a l u e , float .MaxVal ue);
/ / wy ni ki : - 3.402823E+38 3.402823E+38 Typy numeryczne oraz DateTime
udostępniają właściwości
Co n s o l e . Wr i t e L i n e ( " { 0 } {1}", i n t . Mi n V a l u e , i n t . Ma x Va l u e ) ; MinVa/ue oraz MaxVa/ue
/ / wy ni ki : - 2147483648 2147483647
które zwracają, odpowiednio:
najmniejszą i największą
prawidłową wartość danego typu.
Co n s o l e . Wr i t e L i n e ( " { 0 } {1}", Dat eTime. Mi nVal ue, DateTime.MaxValue)
/ / wy ni ki : 1/ 1/ 0001 12: 0 0 : 0 0 AM 12/ 31/ 9999 11: 5 9 : 5 9 PM

C o n s o l e . Wr i t e L i n e ( 1 2 3 4 5 . Ge t T y p e ( ) . F u l l N a me ) ;
¡ I wy ni ki : S y s t e m . I n t 3 2 IS
Także /iterały mają swoj e typy.1 Możesz j e
Cons o l e . Re a dKe y( ) ; pobrać, używając metody GetType().

To jedynie początek zagadnień dotyczących typów! Znacznie więcej na ich temat


możesz znaleźć na stronie http://msdn.microsoft.com/pl-pl/library/ms173104.aspx.

jesteś tutaj ► 889


W s z y s tk o , c o r ó w n e

6 . Równość, lEquatable oraz Equals()


W całej książce, gdy chciałeś porównać ze sobą wartości dwóch zmiennych, używałeś operatora ==. Lecz doskonale wiesz, że pośród
wszystkich równych rzeczy niektóre są „równiejsze” od pozostałych. Operator == działa doskonale w przypadku porównywania
typów wartościowych (takich jak in t , double , DateTime czy też s t r u c t ). Jeśli jednak użyjesz go do porównania dwóch zmiennych
referencyjnych, to okaże się, że sprawdzasz jedynie, czy wskazują one ten sam obiekt (bądź też czy obie mają wartość n u li ). Takie
możliwości są czasami potrzebne, jednak C # oraz .NET udostępniają szeroki wachlarz narzędzi służących do sprawdzania równości
obiektów.
Zacznijmy od tego, że każdy obiekt udostępnia metodę E q u a ls () , która domyślnie zwraca tru e wyłącznie w przypadku, gdy w jej
wywołaniu przekażesz referencję do tego samego obiektu. Oprócz tego istnieje także statyczna metoda O b je c t.R e fe re n c e E q u a ls () ,
która wymaga przekazania dwóch parametrów i zwraca tru e , jeśli obie przekazane referencje wskazują ten sam obiekt (bądź obie są
równe n u li ). Oto przykład, który możesz sprawdzić samemu, umieszczając fragment kodu w aplikacji konsolowej:

Guy jo e l = new Guy("Joe", 37, 100); Także tu korzystamy


Guy joe2 = jo e l; z Masy^Guy przedstawionej
C o nsole.W riteLine(O bject.R eferenceEq uals(jo el, jo e 2 )); / / true w drugim punkcie tego
C o n so le .W rite L in e (jo e l.E q u a ls (jo e 2 )); / / true dodatku.
C on so le.W riteLin e(O bject.ReferenceEq uals(nu ll, n u ll) ); / / true

joe2 = new Guy("Joe", 37, 100);


C o nsole.W riteLine(O bject.R eferenceEq uals(jo el, jo e 2 )); / / fa ls e
C o n so le .W rite L in e (jo e l.E q u a ls (jo e 2 )); / / fa ls e

Jednak to dopiero początek. Platforma .NET udostępnia wbudowany interfejs o nazwie IEquatable<T> , który umożliwia dodanie
do obiektów kodu służącego do porównywania ich i określania, czy są równe innym obiektom. Obiekt implementujący go wie,
jak ma porównywać swoje wartości z wartościami obiektów typu T. Interfejs ten posiada tylko jedną metodę — E q u a ls() — a jego
implementacja polega na napisaniu kodu porównującego zawartość danego obiektu z innym obiektem. W witrynie MSDN dostępna jest
strona zawierająca więcej informacji na ten temat (http:llmsdn.microsoft.comlpl-plllibrarylms131190.aspx). Oto jej fragment:

Jeśli tego Jeśli implementujesz metodę Equals(), powinieneś także przesłonić pochodzące z klasy bazowej implementacje metod
nie zrobisz,
Object.Equals(Object) oraz GetHashCode(), tak by ich działanie było spójne z metodą IEquatable<T>.Equals(). Jeśli przesłonisz
kompilator
wyświetli metodę Object.Equals(Object), to twoja implementacja będzie także wywoływana w ramach wykonywania na rzecz obiektów twojej
ostrzeżenie. klasy statycznej metody Equals(System.Object, System.Object). W ten sposób mamy gwarancję, że wszystkie wywołania metody
Equals() będą zwracać spójne wyniki, co demonstruje przedstawiony przykład.

Poniżej przedstawiliśmy klasę o nazwie EquatableGuy , która rozszerza Guy i implementuje interfejs IEquatable<Guy> .

I I I <summary>
I I I Facet, który w ie, ja k porównywać s ię z innymi facetami
I I I <|summary>
cla ss EquatableGuy : Guy, IEquatable<Guy> {
Metoda Equals() porównuje faktyczne
public EquatableGuy(string name, in t age, in t cash) wartości składowych przekazanego obiektu
: base(name, age, cash) { } z polami Name, Age oraz Cash obiektu
bieżącego, sprawdzając, czy są takie same;
I I I <summary> jeśli są, zwraca true.
I I I Porównuje ten obiekt z innym obiektem EquatableGuy.
I I I <Isummary>
I I I <param name="other">Obiekt EquatableGuy, z którym należy s ię porównaó</param>
I I I <returns>True, j e ś l i obiekty mają te same w a rto ści, fa ls e w przeciwnym przypadku</returns>
public bool Equals(Guy other) {
i f (R eferen ceEq uals(n ull, other)) return fa ls e ;
i f (R efe ren ceEq u als(th is, other)) return tru e ;
return Equals(other.Name, Name) &! . other.Age == Age other.Cash == Cash;
}

890 Dodatek A
Pozostałości

III <summary>
III Przesłania metodę Equals() i wywołuje w n ie j Equals(Guy).
III <Isummary>
III <param name="obj">Obiekt, z którym porównujemy</param>
III <returns>True, je ś l i wartość przekazanego obiektu je s t ró wartości tego obiektu</returns>
publ ic override bool Equals(object obj) {
i f (!(o b j is Guy)) return fa ls e ; Przesłaniamy także metodę
return Equals((Guy)obj); Equals() odziedziczoną po
obiekcie Object oraz metodę
} Ponieważ nasza metoda Equals() porównuje już GetHashCode() (ze względu na
obiekty Guy, po prostu ją wywołujemy. kontrakt, o którym wspominał
III <summary> artykuł z witryny MSDN).
III Elementem kontraktu na przesłonięcie metody Equals() je s t konieczność
III przesłonięcia także metody GetHashCode(). Powinna ona porównać wartości
III i zwrócić tru e , j e ś l i są one sobie równe.
To standardowy sposób działania
III <Isummary> metody GetHashCode(). Zwróć uwagę
III <returns></returns> na zastosowanie bitowego operatora
publ ic override in t GetHashCode() { XOR O , liczby pierwszej oraz
const in t prime = 397; operatora warunkowego (?:).
in t re s u lt = Age;
re s u lt = (re s u lt * prime) ^ (Name != n u ll ? Name.GetHashCode() 0) ;
re s u lt = (re s u lt * prime) ^ Cash;
return re s u lt;

A ta k wyglądałoby zastosow anie m eto d y Equals() do p o ró w n an ia dw óch obiektów EquatableGuy :

jo e l = new EquatableGuy("Joe", 37, 100);


joe2 = new EquatableGuy("Joe", 37, 100);
Console.W riteLine(Object.ReferenceEquals(joel, jo e 2 )); / / false
C onsole.W riteLine(joel.E quals(joe2)); / / true
Metoda Guy.Equals() zwróci
true wyłącznie w przypadku,
joel.GiveCash(50); gdy faktyczne wartości
C onsole.W riteLine(joel.E quals(joe2)); / / fa lse porównywanych obiektów
joe2.GiveCash(50); będą takie same.
C onsole.W riteLine(joel.E quals(joe2)); / / true

T eraz, kiedy m etody Equals() i GetHashCode() zostały zaim p lem en to w an e tak, by spraw dzać w artości p ó l i właściwości
obiektów , p opraw nie zacznie działać także m eto d a L is t.C o n ta in s () . O to lista List<Guy> zaw ierająca kilka obiektów
Guy, w tym także nowy o b iek t EquatableGuy z takim i sam ym i w artościam i co o b iek t zapisany w zm iennej j o e l :

List<Guy> guys = new List<Guy>() {


new Guy("Bob", 42, 125), Metoda Ust.ContainsO przejrzy
new EquatableGuy(joel.Name, joe l.A g e, joe l.C ash ), zawartość listy i dla każdego
z umieszczonych na niej obiektów
new Guy("Ed", 39, 95)
wywoła metodę Equals(), która
}; poi-ówm go z obiektem przekazanym
w wywołaniu.
C onsole.W riteLine(guys.C ontains(joel)); / / true

C onsole.W riteLine(joel == jo e 2 ); / / false

Choć zmierme joel oraz joe2 wskazują


obiekty z takimi samymi wartościami, Czy możemy coś na to poradzić?
to j ednak operatory == i != porównują Przewróć kartkę, a się przekonasz!
ich referencje, a nie zawartość.

jesteś tutaj ► 891


N ie k tó r e k la s y s ą ró w n ie js z e o d p o z o s ta ły c h

Jeśli spróbujesz porów n ać dwie referen cje do obiektów E q u a tab leG u y przy użyciu o p erato ró w == o raz !=, to ich działanie
ograniczy się do spraw dzenia, czy o bie w skazują n a ten sam o b iek t lub czy obie m ają w artość n u l i . A co zrobić, gdybyś
chciał, by porów nyw ały o n e faktyczne w artości składowych o b u obiektów ? O k azuje się, że m ożesz przeciążyć operator
— przedefiniow ać go tak , by wykonywał w skazane czynności, jeśli zostanie użyty do p o ró w n an ia referen cji określonego
typu. M ożesz przeanalizow ać tak ie rozw iązanie n a przykładzie klasy E q u a tab le G u y W ith O v erlo a d , k tó ra rozszerza
E q u atab leG u y i dodatkow o p rzeciąża o p e ra to ry == i !=:

/ / / <summary>
/ / / F a c e t, k tó ry w ie, j a k s i ę porównywać z innymi face ta m i
/ / / </summary>
c l a s s E quatableG uyW ithO verload : E quatableG uy
{
p u b lic E q u a ta b leG u y W ith O v e rlo a d (strin g name, i n t a g e , i n t cash )
: base(nam e, a g e , cash ) { }

p u b lic s t a t i c bool o p e r a to r == (E quatableG uyW ithO verload l e f t ,


E quatableG uyW ithO verload r i g h t )

{ i f ( O b je c t.R e f e r e n c e E q u a ls ( le f t, n u l i ) ) re tu rn f a ls e ; fy n ^ a tr ^ ™ ™
e ls e re tu rn le f t.E q u a ls ( r i g h t) ; ReferenceEqua/sO, zostałby zg^ szebu
} wyj ątek StackOverf/owException Czy
p°trafisz powiedzieć d/aczego?
Ponieważ juz p u b lic s t a t i c bool o p e r a to r != (E quatableG uyW ithO verload l e f t ,
zdefiniowa/iśmy E quatableG uyW ithO verload r i g h t )
działanie {
operatora = = __ r e t u r n ¡ ( l e f t == r i g h t ) ; Gdybyśmy nie przesłoni/i metod £qua/sO
tutaj wystarczy } oraz GetHashCodeO, IDE wyświet/ił°by
zwrócić wartość ostrzeżenie: ,/Equatab/eGuyWithOver/oad
przeciwną. defines operator == or operator 1= but does
p u b lic o v e r r id e bool E q u a ls ( o b je c t o b j) not override Object.GetHashCodeO’ 3.
r e t u r n b a s e .E q u a ls ( o b j) ;
}

p u b lic o v e r r id e i n t GetHashCodeQ
r e t u r n b ase.G e tH ash C o d e ();
{
1
Ponieważ k/asa
Equatab/eGuyWithOver/oad działa
} tak samo jak k/asy Equatab/eGuy
oraz Guy, wystarczy, że wywołamy
metodę k/asy bazowej.
A o to frag m en t k o d u używ ającego obiektów E q u a ta b le G u y W ith O v e rlo a d :
Chwi/eczkę! Co tu się stało?
j o e l = new E q u atab leG u y W ith O v erlo ad (jo el.N am e, jo e l.A g e , jo e l .C a s h ) ; Zostały tu zastosowane
jo e 2 = new E q u atab leG u y W ith O v erlo ad (jo el.N am e, jo e l.A g e , jo e l .C a s h ) ; operatory == i != k/asy
C o n s o le .W r ite L in e (jo e l == jo e 2 ) ; / / fa ls e Guy. Aby użyć właściwych,
nowych operatorów,
C o n s o le .W r ite L in e (jo e l j o e 2 ): / / tr u e zastosuj rzutowanie na
Equatab/eGuyWithOver/oad.
C o n so le .W rite L in e ((E q u a ta b le G u y W ith O v e rlo a d )jo e l ==
(E q u a ta b le G u y W ith 0 v e rlo a d )jo e 2 ); / / tr u e
C o n so le .W rite L in e ((E q u a ta b le G u y W ith O v e rlo a d )jo e l !=
(E q u a ta b le G u y W ith 0 v e rlo a d )jo e 2 ); // fa ls e
jo e 2 .R e c e iv e C a s h (2 5 );
C o n so le .W rite L in e ((E q u a ta b le G u y W ith O v e rlo a d )jo e l ==
(E q u a ta b le G u y W ith 0 v e rlo a d )jo e 2 ); // fa ls e
C o n so le .W rite L in e ((E q u a ta b le G u y W ith O v e rlo a d )jo e l !=
(E q u a ta b le G u y W ith 0 v e rlo a d )jo e 2 ); / / tr u e

3 Klasa EquatableGuyWithOverload definiuje operator = = lub !=, lecz nie przesłania metody Object.GetHashCode() — przyp. tłum.

892 Dodatek A
Pozostałości

7 . Stosowanie yield return do tworzenia obiektów


umożliwiających iterację
W rozdziale 8 . przedstaw iliśm y interfejs ¡E nu m era ble i pokazaliśm y, w jaki sposób jest o n używ any w p ętlach f o r e a c h .
C # o raz .N E T u d o stę p n iają p rzy d atn e n arzęd zia p o m ag ające w tw o rzen iu własnych kolekcji; pierwszym z nich jest
w łaśnie ¡ E n u m e ra b le . Z ałóżm y, że chciałbyś utw orzyć e n u m e ra to r zw racający w odpow iedniej kolejności w artości typu
w yliczeniow ego S p o r t :

enum Sport
{
F o o t b a l l , B a s e b a ll,
B a s k e t b a l l, Hockey,
Boxing, Rugby, Fencing,
}

Mógłbyś sam odzielnie zaim plem entow ać interfejs ¡ E n u m e ra b le , tw orząc w pew nej klasie właściwość C u r r e n t
o raz m eto d ę M o v e N e x t( ) :

c la s s S p o r t C o l l e c t i o n : !Enumerable<Sport> { Interfejs I Einumei-able rnrnera tylko j edną


p u b l i c IEnum erator<Sport> GetEnumeratorQ { / mfk ■ +GetEnum frator° l jednak musimy
} r e t u r n new ManualSPo r t E n u m e r a t o r ( ) ; ^ ^

S y s t e m . C o lle c ti o n s . lE n u m e r a t o r S y s te m .C o lle c tio n s .lE n u m e r a b le .G e tE n u m e ra to r ( ) {


r e t u r n G e tE nu m erato r( );
} Enumerator implementuje interfej s
c la s s ManualSportE numerator : !Enumerator<Sport> { ^ ------------ IEnumerable<Sport>. Pętia f° reach
in t curre nt = -1 ; używa j egowtaśc,wośc, Current
p u b l i c S po rt C u rre n t { get { r e t u r n ( S p o r t ) c u r r e n t ; } } oraz m e o y ove '
p u b l i c v o id Dispose () { r e t u r n ; } / / Nie ma co usuwać,
o b j e c t S y s t e m . C o lle c t i o n s . lE n u m e r a t o r . C u r r e n t { get { r e t u r n C u r r e n t ; } }
p u b l i c bool MoveNext() {
i n t maxEnumValue = E n u m . G e tV a lu e s ( ty p e o f(S p o rt) ).L e n g th - 1;
i f ( ( i n t ) c u r r e n t >= maxEnumValue)
re tu rn fa ls e ; »v
current++; Metoda MoveNextC) inkrementuje
re tu rn tru e ; bieżącą wartość i korzystając
j z mej, zwraca następny sport
p u b l i c v o id Reset() { c u r r e n t = 0; } z typu wyliczeniowego.
}
}

Poniżej pokazaliśm y p ę tlę f o r e a c h w yśw ietlającą kolejne elem en ty kolekcji M a n u a lS p o r tE n u m e ra to r . Z w raca o n a sporty
w określonej kolejności (F o o tb all, B aseball, B asketball, H ockey, Boxing, R ugby, Fencing):
C o n s o le . W rit e L in e ( " Z a w a rto ś ć k o l e k c j i S p o r t C o l l e c t i o n : " ) ;
S p o r t C o l l e c t i o n S p o r t C o l l e c t i o n = new S p o r t C o l l e c t i o n ( ) ;
fore a c h (S p o rt s p o r t in S p o r t C o l l e c t i o n )
C o n s o le .W rite L in e (s p o rt.T o S trin g ());
S tw orzenie e n u m e ra to ra w ym aga sp o ro p racy — m usi o n przechow yw ać swój stan i pam iętać, jakie w artości zostały
już zw rócone. N a szczęście C # u d o stę p n ia n ap raw d ę użyteczne narzędzie, k tó re znacznie to ułatw ia. Je st nim instrukcja
y i e l d r e t u r n , a dow iesz się o niej w ięcej, gdy przew rócisz kartkę.

To tylko przypomnienie informacji podanych w rozdziale 15.: wszystkie kolekcje pozwalają na


wyliczanie elementów, jednak nie wszystkie dane, które na to pozwalają, są kolekcjami — te muszą
bowiem implementować interfejs ICollection<T>. Nie pokazywaliśmy Ci, jak należy tworzyć kolekcje
od podstaw, jednak zrozumienie zasad działania enumeratorów na pewno będzie stanowiło dobry
punkt wyjściowy. jesteś tu ta j ► 893
Wylicz to!

In stru k cja y ie ld re tu rn je st pew nego ro d zaju kom pletnym narzędziem do tw orzenia e n u m erato ró w . P o k azan a poniżej
klasa S p o rtC o lle c tio n zapew nia d okładnie te sam e m ożliwości co klasa p rzed staw io n a n a p o p rzed n iej stro n ie, je d n a k jej
e n u m e ra to r składa się jedynie z trzech w ierszy kodu.

class SportC ollection : IEnumerable<Sport> {


System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}
p u b lic IEnumerator<Sport> GetEnumerator() {
in t maxEnumValue = Enum.GetValues(typeof(Sport)).Length
fo r ( in t i = 0; i < = maxEnumValue; i++) { - • • + „^i,,nio
y ie ld return (S p o rt)i; Jak już zaznaczymy wcze&n<ej, to j edyn
} punkt wyjściowy do Wrzenia klasy
i ' V SportCollection. Na pewno ęfaafoyś _
, ‘ zaimplementować w niej także interfejs
' ICollection<Sport>.

C hoć te n kod w ydaje się nieco dziwny, to jeśli prześledzisz jego działanie w d ebuggerze, n a pew no zo rien tu jesz się,
co się w nim dzieje. G dy k o m p ilato r znajdzie m e to d ę zaw ierającą instrukcję y i e l d re tu rn , k tó ra zw raca w artość typu
IEnumerator lub IEnumerator<T> , to automatycznie doda do klasy metodę MoveNext() oraz właściwość C urrent .
W m om encie w yw ołania tej m etody pierw sza w ykonana instru k cja y ie ld re tu rn spow oduje zw rócenie pierw szej w artości
do pętli foreach . G dy działanie pętli będzie kontynuow ane (poprzez w ywołanie m eto d y MoveNext() ), m eto d a wznowi
działanie bezpośrednio po w ykonanej w cześniej instrukcji y ie ld re tu rn . M eto d a MoveNext() zwróci w arto ść fa ls e
p o zakończeniu działania m eto d y e n u m e rato ra . Z ro zu m ien ie teg o rozw iązania n a podstaw ie o pisu słow nego m oże być
dosyć tru d n e. Z nacznie łatwiej b ędzie je pojąć, u ru ch am iając p ro g ram w debuggerze i śledząc jego działanie k ro k p o kroku
(przy użyciu opcji Step Into lub klaw isza F l l ) . A by nieco ułatw ić Ci zadanie, przedstaw iliśm y poniżej nap raw d ę prosty
e n u m e ra to r o nazw ie NameEnumerator, który zw raca kolejno cztery im iona.

s ta tic IEnumerable<string> NameEnumerator() {


y ie ld return "B artek"; / / Metoda kończy d zia ła n ie po te j in s t r u k c ji. . .
y ie ld return "Henryk"; / / .. . a po ponownym uruchomieniu zaczyna od tego miejsca.
y ie ld return "Jurek";
y ie ld return "Franek";
}

A to p ę tla foreach op e ru ją c a n a tym en u m era to rz e . Skorzystaj z opcji Step Into (F11), by dokład n ie prześledzić,
co się w niej dzieje:

IEnumerable<string> names = NameEnumerator(); / / Ustaw pułapkę tu ta j


foreach (s trin g name in names)
Console.WriteLine(name);
W kolekcjach m ożna zazwyczaj sp o tk ać jeszcze je d n o rozw iązanie: indeksatory . G dy używasz naw iasów kw adratow ych
([ ] ), by p o b rać o b iek t z listy, tablicy bądź słow nika (np. m y L is t[3 ] lub m y D ictio n a ry["S ta sze k"] ), korzystasz w łaśnie
z indeksatora. W rzeczywistości jest o n zwyczajną m eto d ą. Z w yglądu p rzypom ina właściwości, z tą różnicą, że p o siad a
tylko je d e n nazw any p aram etr.
ID E u d o stę p n ia przydatny szablon kodu, k tóry m oże nam b ard zo ułatw ić życie. W pisz indexer i d w ukrotnie naciśnij
klawisz Tab, a ID E autom atycznie d o d a szkielet indeksatora:
O to in d ek sato r dla klasy S p o rtC o lle c tio n :

p u b lic Sport t h is [ in t index] {


get { return (Sport)index; }
}
P rzekazanie do niego w artości 3 spow oduje zw rócenie łań cu ch a znaków "Hockey".

894 Dodatek A
Pozostałości

A o to klasa IEnumerable<Guy> zaw ierająca dane kilku facetów o ra z in d e k sa to r pozw alający n a ustaw ianie
lub p o b ieran ie ich wieku:

class GuyCollection : IEnumerable<Guy> {


p riva te s ta tic readonly D ictio n a ry< s trin g , in t> namesAndAges =
new D ictio n a ry< strin g , in t> ()
{
{"Ju re k", 41}, {"B a rte k", 43}, {"Edek", 39}, {"Lech", 44}, {"Franek", 45}
};
Enumerafor używa tego prywatnego słownika
p ub lic IEnumerator<Guy> GetEnumerator() { do ś/edzenia tworzonych obiektów Guy, jednak
Random random = new Random(); one f ame nie są tworzone aż do momentu
in t pileOfCash = 125 * namesAndAges.Count; użycia enumeratora.

in t count = 0;
foreach (s trin g name in namesAndAges.Keys) {
in t cashForGuy =
(++count < namesAndAges.Count) ? random.Next(125) : pileOfCash;
pileOfCash -= cashForGuy; tS
y ie ld return new Guy(name, namesAndAges[name], cashForGuy); \
^ Tutaj tworzymy obiekty Guy z /osową i/ością posiadanej gotówta..
} Robimy to, by pokazać, że enumerator może tworzyć °biekty na żądan'e
podczas wykonywania pęt/i foreach.
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}

/ / / <summary>
/ / / Indeksator pobiera lub ustawia wiek faceta
/ / / </summary>
/ / / <param name="name">Imię faceta</param>
/ / / <returns>Wiek faceta</returns> Zazwyczaj gdy do indeksatora
p ub lic in t th is [s tr in g name] { zostanie przekazany niewłaściwy
get { indeks, zgłosi on wyjątek
i f (namesAndAges.ContainsKey(name)) I ndexOutOfRangeException.
return namesAndAges[name];
throw new KeyNotFoundException("Nie znaleziono faceta o imieniu " + name);
}
set {
i f (namesAndAges.ContainsKey(name))
namesAndAges[name] = value; Ten indeksator posiada akcesor set,
else który a/bo aktua/izuje wiek faceta,
namesAndAges.Add(name, value); a/bo dodaje nowy obiekt Guy do
słownika.
}
}
}

Poniżej pokazaliśm y kod, który używa in d ek sato ra , by zaktualizow ać w iek jed n eg o faceta, a n a stęp n ie dodaje
dwóch kolejnych, by n a k ońcu wyświetlić wszystkich w pętli.

Console.WriteLine("Dodanie dwóch facetów i modyfikacja kolejnego");


g uyC ollectio n ["B artek"] = g uyC ollectio n ["Ju re k"] + 3;
guyC ollection["Janek"] = 57;
guyC ollection["H enryk"] = 31;
foreach (Guy guy in guyCollection)
C onsole.W riteLine(guy.ToS tring());

jesteś tutaj ► 895


R e fa k to ry z a c ja to w s p a n ia ły n a w y k p rz y p ro g r a m o w a n iu

Fragm enty kodu przedstawione na tej stronie pochodzą


z projektu sym ulatora prezentującego możliwości GDI+,
8 . Refaktoryzacja dostępnego w przykładach dołączonych do tej książki.

R efaktoryzacja oznacza zm ianę stru k tu ry k o d u p ro g ram u bez zm iany jego zachow ania. Z aw sze w tedy, gdy piszesz
skom plikow aną m eto d ę , pow inieneś pośw ięcić chwilę, by pow rócić do n apisanego k o d u i pom yśleć, w jak i sposób mógłbyś
go zm ienić, aby był łatwiejszy do zrozum ienia. N a szczęście ID E p o sia d a kilka w budow anych n arzęd zi do refaktoryzacji.
D aje o n a w iele m ożliwości — o to kilka najczęściej używanych.

Wyciągnij metody
W dodatkow ych p ro jek tach sym u lato ra u la, dostępnych w przykładach dołączonych do książki, um ieściliśm y
początkow o kod prezen tu jący w irtualny św iat przy użyciu poniższych p ę tli f o r e a c h :

forea ch (Bee bee in w o rld .B e e s) {


be eControl = G etB e e C o n tro l(b e e );
Te cztery i f ( b e e . I n s id e H iv e )
i f (fie ld F o rm .C o n tro ls .C o n ta in s (b e e C o n tro l))
przemieszczają <T fie ld F o r m . C o n t r o ls . R e m o v e ( b e e C o n t r o l) ;
BeeControl — —^ uet b e e C o n t r o l.S iz e = new S iz e ( 4 0 , 4 0 );
z formularza ; h i \ve F o rm .C o n tro ls .A d d (b e e C o n tro l)
Field (. bee
b e e C o n tro l.B rin g T o F ro n t();
na formularz } A te cztery wiersze
Hive. } e ls e i f ( h i v e F o r m . C o n t r o ls . C o n t a i n s ( b e e C o n t r o l ) ) przemieszczają
h iv e F o rm .C o ntrols .R e m ove (bee C o ntrol) BeeControl
b e e C o n t r o l.S iz e = new S iz e ( 2 0 , 2 0 ) ; z formularza Hive
f i e l d F o r m . C o n t r o ls . A d d ( b e e C o n t r o l ) ; na formularz Field.
b e e C o n tro l.B rin g T o F ro n t();
}
b e e C o n t r o l .L o c a ti o n = b e e . L o c a t io n ;
}
Jed en z naszych k orek to ró w technicznych, Jo e A lb ah ari, zauważył, że k o d je st tro c h ę tru d n y do czytania. Z asugerow ał
wyciągnięcie dwóch bloków składających się z czterech wierszy kodu do metod . W ybraliśm y pierw szy z nich,
kliknęliśm y go praw ym przyciskiem myszy i użyliśm y Refactor/Extract Method... — pok azało się n astęp u jące okno:

Wpisaliśmy nazwę dla nowej


metody. Zdecydowaliśmy, IDE sprawdziło kod
że będzie się ona nazywata i wywnioskowało,
MoveBeeFromFieldT°Hive(), że używana jest
w nim zmienna typu
ponieważ dość dobrze i
BeeControl o nazwie
opisuje to zadanie beeControl. Taki
wskazanego bloku kodu.
parametr zostanie
więc dodany do
tworzonej metody.

Z robiliśm y p o tem tę sam ą rzecz dla drugiego bloku składającego się z czterech wierszy. U tw orzyliśm y n a ich podstaw ie
m eto d ę M o v e B e e F r o m H iv e T o F ie ld ( ) . O to efe k t m odyfikacji p ętli f o r e a c h — jest znacznie łatw iejsza do czytania:

forea ch (Bee bee in w o rld .B e e s) {


be eControl = G etB e e C o n tro l(b e e );
i f ( b e e . I n s id e H i v e ) {
i f (fie ld F o rm .C o n tro ls .C o n ta in s (b e e C o n tro l))
MoveBeeFromF1eldToH1ve(beeControl);
} e ls e i f ( h i v e F o r m . C o n t r o ls . C o n t a i n s ( b e e C o n t r o l ) )
MoveBeeFromH1veToF1eld(beeControl);
b e e C o n t r o l .L o c a ti o n = b e e . L o c a t io n ;

896 Dodatek A
Pozostałości

Zmień nazwę zmiennej


W rozdziale 3. w yjaśnialiśmy, w jaki sposób, w ybierając intuicyjne nazwy dla klas, m eto d , właściwości, p ó l i zm iennych,
m ożem y ułatw ić późniejsze zrozum ienie kodu. Przy nazyw aniu różnych jego fragm entów ID E p o trafi n ap raw d ę bardzo
pom óc. Kliknij praw ym przyciskiem myszy dow olną klasę, zm ienną, pole, właściwość, p rzestrzeń nazw, stałą — w zasadzie
w szystko, co m oże m ieć nazw ę — i w ybierz Refactor/Rename ... M ożesz także nacisnąć klawisz F2, co je st b ard zo wygodne
— jeśli już raz zm ienisz nazw ę, to będziesz to robić n ieustannie.

Z robiliśm y to w przy p ad k u b e e C o n t r o l w kodzie sym ulatora. W yśw ietliło się n astęp u jące okno:

To okno
umożliwia wybór IDE wykonuje naprawdę
nowej nazwy ciężką pracę podczas
dla elementu. zamiany nazw. Gdy
Gdybyśmy nazwali zmieniasz nazwę klasy,
go, powiedzmy, musi zostać zmodyfikowana
„Sambo", IDE każda instrukcja, która
przeszukałoby tworzy jej instancję lub
cały kod jej używa. Możesz kliknąć
i zamieniło każde dowolne wystąpienie nazwy
j ego wystąpienie w kodzie. IDE wprowadzi
na „Sambo". zmianę w każdym miejscu
w programie.

Połącz instrukcję warunkową


O to prosty sposób n a użycie opcji Extract M ethod. O tw órz dow olny p ro g ram , dodaj przycisk
i w staw taki kod do p ro ced u ry obsługi zdarzenia:

p r i v a t e v o id b u t t o n l _ C l i c k ( o b j e c t sender, EventArgs e) {
i n t va lu e = 5;
s t r i n g t e x t = " W i t a j c i e wszyscy";
if (v a lu e = = 3 6 || te x t.C o n ta in s ("w s z y s c y ")) Dodatkowo IDE
zauważy, że powinno
MessageBox.Show("Bum!"); utworzyć metodę
} statyczną, gdyż
wewnątrz niej nie są
używane żadne pola
W ybierz całą zaw artość instrukcji if: v a l u e = = 3 6 | | t e x t . C o n t a i n s ( " w s z y s c y " ) . Kliknij ją obiektów.
praw ym przyciskiem myszy i użyj Refactor/Extract M ethod... W yświetli się n astęp u jące okno:
Wyrażenie używa dwóch
zmiennych o nazwach
value i text. IDE dodało
Każde wyrażenie zatem do metody dwa
warunkowe przyjmuje parametry i nadało im
wartość logiczną. IDE takie same nazwy.
utworzy zatem metodę,
która zwraca bool, T
i zamieni to wyrażenie Dzięki takiemu rozwiązaniu
na jej wywołanie uzyskujesz nie tylko łatwiejszy
w czytaniu kod. Posiadasz
także nową metodę, która
może zostać ponownie użyta
w dowolnym miejscu!

jesteś tutaj ► 897


W ie m y , ż e g d z i e ś t u u k r y t o m e t a f o r ę s u p e r b o h a t e r a

9 . Anonimowe typy i metody oraz wyrażenia lambda


C # pozw ala tw orzyć typy i m eto d y bez stosow ania jaw nych deklaracji określających ich nazwy. Typ lub m eto d a
zadeklarow ane bez jej p o d aw an ia są o k reślan e ja k o anonimowe . Są to b ard zo p o tę ż n e narzęd zia — n a przykład b ez nich
nie byłoby m ożliw e stw orzenie technologii L IN Q . O panow anie zagadnień zw iązanych z anonim ow ym i typam i, m eto d am i
o raz w yrażeniam i lam b d a je st znacznie łatw iejsze, kiedy ju ż dysponujem y solid n ą b azą w iedzy o języku C # ; to w łaśnie
z tego pow odu nie opisaliśm y ich szczegółowo w treści książki. P ostanow iliśm y je d n a k zam ieścić tu k ró tk ie w prow adzenie,
k tó re p om oże Ci rozpocząć dalszą naukę.

class Program {
delegate void M yIntAndString(int i , s trin g s);
delegate in t CombineTwoInts(int x, in t y );

s ta tic void M a in (s trin g [] args) {


/*
* W rozdziale 14. pisaliśm y, że słowo kluczowe var pozwala IDE o k re ś lić typ
* obiektu podczas ko m p ila cji.
*
* Używając var i new, można także tworzyć obiekty o anonimowych typach.
*
* Więcej inform acji na temat tych typów możesz znaleźć na s tro n ie
* h ttp ://m sdn .m icro soft.co m /p l-p l/lib rary/bb 3 97 69 6.asp x.
*/

/ / Tworzymy typ anonimowy przypominający klasę Guy.


var anonymousGuy = new { Name = "B artek", Age = 43, Cash = 137 };

I I Kiedy będziesz wpisywał poniższą in s tru k c ję , In te lliS e n s e w yśw ietli


I I składowe - Name, Age oraz Cash.
Console.W riteLine("{0} ma {1} la ta i {2} z ł g otó w ki.",
anonymousGuy.Name, anonymousGuy.Age, anonymousGuy.Cash);
I I Wyniki: Bartek ma 43 la ta i 137 z ł gotówki.

I I Instancja typu anonimowego ma także całkiem sensowną metodę T oS trin g().


Console.WriteLine(anonymousGuy.ToString());
I I Wyniki: { Name = Bartek, Age = 43, Cash = 137 }

/*
* W rozdziale 15. dowiedziałeś s ię , ja k używać delegatów, by utworzyć
* referencje do metod. We wszystkich przedstawionych przykładach
* ich zastosowania przypisywaliśmy do nich is tn ie ją c e metody.
*
* Metody anonimowe to metody deklarowane w in s tru k c ji - używane są przy
* tym nawiasy klamrowe { } podobnie ja k w przypadku anonimowych typów.
*
* Więcej inform acji na temat anonimowych metod znajdziesz na s tron ie
* h ttp ://m sd n .m icro so ft.co m /p l-p l/lib ra ry /0 y w 3 tz 5 k .a s p x .
*/

898 Dodatek A
Pozostałości

/ / Oto metoda anonimowa, k t ó r a w y ś w ie t la na k o n s o li l i c z b ę i ła ńcuch znaków.


/ / J e j d e k l a r a c j a pa suje do d e le g a ta M y ln tA n d S trin g (z de finiow a ne go
/ / w c z e ś n i e j ) , zatem możemy j ą p r z y p is a ć do zmiennej ty p u M y ln tA n d S t rin g .
M y ln tA n d S tr in g printTh em = d e l e g a t e ( i n t i , s t r i n g s)
{ C o n s o le . W r it e L i n e ( " { 0 } - { 1 } " , i , s ) ; } ;
prin tT h e m (1 2 3 , " c z t e r y p ię ć s z e ś ć " ) ;
/ / W y n ik i: 123 - c z t e r y p i ę ć sześć

/ / A t u mamy k o l e j n ą anonimową metodę o t a k i e j samej syg n a tu rz e ( i n t ,


/ / s t r i n g ) . Ta metoda sprawdza, czy w łańcuchu w ys tęp uje l i c z b a .
M y ln tA n d S tr in g c o n t a in s = d e l e g a t e ( i n t i , s t r i n g s)
{ C o n s o le .W rite L in e (s .C o n ta in s (i.T o S trin g ())); };
c o n t a in s ( 1 2 3 , " c z t e r y p ię ć s z e ś ć " ) ;
/ / W y n ik i: False

c o n t a in s ( 1 2 3 , " c z t e r y 123 p ię ć s z e ś ć " ) ;


/ / W y n ik i: True

/ / Można dynam icznie wywołać metodę, używając D e le g ate.D yn am icIn voke ()


/ / i p r z e k a z u ją c pa ram etry wywołania w f o rm ie t a b l i c y ob ie któ w .
Delegate d = c o n t a in s ;
d.DynamicInvoke(new o b j e c t [ ] { 123, " c z t e r y 123 p ię ć sześć" } ) ;
/ / W y n ik i: True

/*
* Wyrażenia lambda są sp ecja ln ym rodzajem anonimowych metod, k t ó r e
* k o r z y s t a j ą z o p e r a to r a =>. Nosi on nazwę o p e ra to ra lambda, je d n a k
* w przypadku ty c h wyrażeń, c z y t a ją c go, zazwyczaj używamy
* o k r e ś l e n i a "p rz e c h o d z i d o ". Oto p r o s t e w yrażenie lambda:
*
* (a, b) => { r e t u r n a + b; }
*
* Można j e p r z e c z y ta ć j a k o "a i b prz e cho dzi do a p lu s b" - j e s t t o
* metoda anonimowa dodająca dwie w a r t o ś c i . Można wyobrażać s ob ie wyrażenia
* lambda ja k o metody anonimowe p o b i e r a j ą c e pa ram e try i zwracają ce w a r t o ś c i .
*
* Więcej i n f o r m a c j i na temat wyrażeń lambda z n a jd z ie s z na s t r o n i e
* h ttp ://m s d n .m ic ro s o ft.c o m /p l-p l/lib ra ry /b b 3 9 7 6 8 7 .a s p x .
*/

/ / Oto w yrażenie lambda s łu żą c e do dodawania dwóch l i c z b . Jego sy gn atura


I I odpowiada de le g a to w i CombineTwoInts, a zatem możesz j e p r z y p is a ć do
I I zmiennej ty p u CombineTwoInts. Zwróć uwagę, że d e le g a t ten zwraca
I I w a rto ś ć ty p u i n t , zatem ta k ż e w yrażenie lambda musi t a k ą zwracać.
CombineTwoInts adder = (a, b) => { r e t u r n a + b; } ;
C o n s o le . W r it e L in e ( a d d e r ( 3 , 5 ) ) ;
/ / W y n ik i: 8

I I A o t o k o l e jn e w yrażenie lambda - t o z k o l e i mnoży dwie l i c z b y :


CombineTwoInts m u l t i p l i e r = ( i n t a, i n t b) => { r e t u r n a * b; } ;
C o n s o le .W rite L in e (m u ltip lie r(3 , 5 ) ) ;
/ / W y n ik i: 15

I I Łącząc w yrażenia lambda z LINQ, można wykonywać naprawdę złożone


I I o p e r a c je . Pon iż e j p r z e d s t a w ili ś m y p r o s t y p r z y k ł a d .
v a r gre aterThan3 = new L i s t < i n t > { 1, 2, 3, 4, 5, 6 }. W h ere (x => x > 3 ) ;
forea ch ( i n t i in gre aterT han3) C o n s o le . W r it e ( " { 0 } " , i ) ;
/ / W y n ik i: 4 5 6

Console.ReadKey();
jesteś tutaj ► 899
J e s t t a k d u ż o L IN Q

10. Zastosowanie LINQ to XML


X M L — Extensible Markup Language — to fo rm a t plików i strum ieni, k tóry re p re z en tu je złożone dane w p o staci plików
tekstow ych. .N E T u d o stęp n ia n ap raw d ę p o tężn e narzęd zia do tw orzenia, wczytywania i zapisyw ania teg o typu plików.
K iedy ju ż posiądziesz w iedzę o X M L , to będziesz m ógł użyć L IN Q do w ykonania w nim zapytań. N a górze plik u dodaj
u s i n g S y s t e m . X m l. L i n q ; i w pisz poniższą m e to d ę — utw orzy o n a d o k u m en t X M L przechow ujący inform acje
o p ro g ram ie lojalnościow ym Starbuzz.

p r i v a t e s t a t i c XDocument G etS ta rbu zzD ata () {


XDocument doc = new XDocument(
new X D e c l a r a t i o n ( " 1 . 0 " , " u t f - 8 " , " y e s " ; , Movssz używać klasy XDocument,
new XComment("Dane programu lo ja l n o ś c io w e g o S t a r b u z z " ) , by tworzyti p ^-, ^ L , w tym także
new X E le m e n t( " s ta r b u z z D a ta " , pl>ki, _które będz'tesz mógł odczytywać
i zapisywać przy użyciu obiektów
new X A t t r i b u t e ( " s t o r e N a m e " , "Park S lo p e " ) ,
DataContractSerializer.
new X A t t r i b u t e ( " l o c a t i o n " , " B r o o k ly n , NY"),
new XElementCperson",
new X E le m e n t ( " p e r s o n a lI n f o " ,
Obiekt XDocument
new XElement("name", "Janet V e n u t ia n " ) , reprezentuje dokument XML.
new X E le m e n t ( " z ip " , 11215)), Należy on do przestrzeni
new X E l e m e n t ( " f a v o u r i t e D r i n k " , "Choco M ac ch ia to ") nazw System.Xml.Linq.
new XElement("moneySpent", 150),
new X E l e m e n t ( " v i s i t s " , 5 0 ) ) ,
new XElement(Mperson",
new X E le m e n t ( " p e r s o n a lI n f o " ,
new XElement("name", " L i z N e ls o n " ) ,
new X E le m e n t ( " z ip " , 11238)),
new X E l e m e n t ( " f a v o u r i t e D r i n k " , "Double Cappuccino
new XElement("moneySpent", 150),
Użyj obiektów XElemtsnt by tworzyó
new X E l e m e n t ( " v i s i t s " , 3 5 ) ) ,
elementy w drzewie XML.
new XElementCperson",
new X E le m e n t ( " p e r s o n a lI n f o " ,
new XElement("name", "M a tt F r a n k s " ) ,
new X E le m e n t ( " z ip " , 11217)),
new X E l e m e n t ( " f a v o u r i t e D r i n k " , "Z e s ty Lemon Chai"
new XElement("moneySpent", 75 ),
new X E l e m e n t ( " v i s i t s " , 1 5 ) ) ,
new XElementCperson",
new X E le m e n t ( " p e r s o n a lI n f o " ,
new XElement("name", "Joe N g "),
new X E le m e n t ( " z ip " , 11217))
new X E le m e n t ( " f a v o u r i t e D r i n k " , "Banana S p l i t in a Cup")
new XElement("moneySpent", 60) ,
new X E l e m e n t ( " v i s i t s " , 1 0 ) ) ,
new XElementCperson",
new X E le m e n t ( " p e r s o n a lI n f o " ,
new XElement("name", "Sarah K a l t e r "
new X E le m e n t ( " z ip " , 11215))
new X E le m e n t ( " f a v o u r i t e D r i n k " , "B o rin g C o ffe e ")
new XElement("moneySpent", 110),
new X E l e m e n t ( " v i s i t s " , 1 5 ) ) ) ) ; Microsoft udostępnia w internecie wiele przydatnych informacji dotyczących
r e t u r n doc; LINQ oraz stosowania LINQ i XML. Więcej danych na ten temat, w tym także
dotyczących przestrzeni nazw System.Xml.Linq, znajdziesz na stronie
}
h ttp://m sdn.m icrosoft.com /pl-pl/library/bb387098.aspx.

900 Dodatek A
Pozostałości

Zapisz i wczytaj pliki XML


Z aw artość o b iek tu XDocument m ożesz wyświetlić w o knie konsoli lub zapisać
w pliku, m ożesz także wczytać do tego o b iek tu zaw artość pliku X M L: M?tody LoadO i Save() obiektu
XDocument doc = G e tS ta rb u z z D a ta (); XDocument wczy tują i zapisują p/iki
< - - ..... XML. Jego metoda ToStringO zamienia
C o n s o le .W rite L in e (d o c .T o S trin g ()); wszystko, co je s t w środku, na jeden
d o c . S a v e ( " s ta r b u z z D a t a . x m l" ) ; wie/ki dokument.
XDocument anotherDoc = X D o cum e nt.L oa d("starb uz zD a ta.xm l" )

Wykonaj zapytanie na danych


O to p ro ste zapytanie L IN Q w ykonane n a danych S tarbuzz z w ykorzystaniem X D ocum ent:
Metoda Descendant^)
v a r data = from item in d o c .D es ce nd ants("p erson " zwraca referencję do obiektu<
s e l e c t new { d r i n k = i t e m . E l e m e n t ( " f a v o u r i t e D r i n k " ) . V a l u e , który można zastosować
moneySpent = it e m .E le m e n t(" m o n e y S p e n t").V a lu e , bezpośrednio w zapytaniu
zipCode = it e m . E l e m e n t ( " p e r s o n a l I n f o " ) . E l e m e n t ( " z i p " ) . V a l u e }; LINQ.
fore a c h (v a r p in data)
C o n s o le .W rite L in e (p .T o S trin g ());
Ju^ w,esz' że LINQ p°zwa/a Ci wywoływać
metody _, uzywać fcb jako części zapytania.
Działa to doskona/e ^ m utoS ąlyim pO
M ożesz także w ykonać bardziej skom plikow ane zapytania:

v a r zipcodeGroups = from item in do c.D esce nd ants("p erson " E/ementO zwraca obiekt
group it e m . E l e m e n t ( " f a v o u r i t e D r i n k " ) . V a l u e XE/ement. Możesz
użyć jego właściwości
by it e m . E l e m e n t ( " p e r s o n a l I n f o " ) . E l e m e n t ( " z i p " ) . V a l u e
w ce/u sprawdzenia
i n t o zipcodeGroup
okreś/onych pó/
s e l e c t zip codeGroup;
w dokumencie XML.
fore a c h (v a r group in zipcodeGroups)
C o n s o le . W r it e L i n e ( " { 0 } u lu b io n y c h napojów w { 1 } " ,
g r o u p . D i s t i n c t ( ) . C o u n t ( ) , g r o u p .K e y ) ;

Wczytywanie danych z kanału R S S


Z L IN Q to X M L m ożesz zrobić kilka nap raw d ę interesujących rzeczy.
O to prosty przykład zapytania, k tó re pobiera artykuły z naszego blogu :

XDocument o u rB lo g = XDocument.Load("h t t p : / /w w w . s t e ll m a n - g r e e n e . c o m / f e e d " ) ;


C o n s o le .W rite L in e (o u rB lo g .E le m e n t("rs s ").E le m e n t("c h a n n e l").E le m e n t("title ").V a lu e );
v a r po sts = from p o s t in o u r B lo g . D e s c e n d a n ts ( " ite m " )
s e l e c t new Metoda XDocument.^adO p°siada
ki/ka przecią^ny^ wersji'. Ta tu taj
T it le = p o s t.E le m e n t("title ").V a lu e , wyciąga dane XMl na p°dstaw|e URL
Date = p o s t .E le m e n t( " p u b D a te " ) . V a lu e
};
fore a c h (v a r po st in p o sts)
C o n s o le .W rite L in e (p o s t.T o S trin g ()); ^
Wstaw do formu/arza przycisk U ży liśm y adresu URL naszego blogu „ Building B etter Software
i upewnij się, że wpisałeś na górze ' ' ( fcftp-//www.5 tellmcin~greene.com /).
„using System.Xm/.Linq;". Wprowadź to
zapytanie do metody Main() i sprawdź,
co zostanie wypisane w oknie konso/i.

jesteś tutaj ► 901


XAM L ry m u je s ię z „ p s a lm "

11. Windows Presentation Foundation


W tej książce pisałeś aplikaqe, posługując się trzem a różnymi technologiami. Pierwszą były aplikaqe dla Sklepu Windows pisane
w języku C # i XAM L — najnowszej platform ie M icrosoftu do tworzenia aplikacji o graficznym interfejsie użytkownika; kolejnymi
dwiema były „okienkowe” aplikaqe Windows Form s oraz aplikaqe konsolowe. Poznałeś także czwartą technologię: aplikaqe
Windows Phone pisane w C # i XAML.

Istnieje jeszcze jedna technologia służąca do tworzenia aplikaqi „okienkowych”, której można używać w Visual Studio 2012 for
Windows Desktop. Nosi ona nazwę Windows Presentation Foundation (w skrócie: W PF). Podobnie jak aplikaqe dla Sklepu
Windows, także aplikaqe W PF bazują na języku XAM L i korzystają z wielu identycznych konstrukqi i składni: kontrolek Grid,
StackPanel , T e xtB lo ck , zasobów statycznych, wiązania danych itd.

Bardzo byśmy chcieli opisać aplikaqe W PF w tej książce, jednak nie mamy na to miejsca.

Nie masz Windows 8 ? Nie przejmuj się!


Wciąż możesz napisać większość prezentowanych projektów w WPF.
K orzystając z W PF, m ożesz stworzyć alternatyw ne w ersje aplikacji dla S klepu W indow s przedstaw ionych w tej książce,
w rozdziałach 1., 2. o ra z o d 10. do 16. K iedy zainstalujesz now ą w ersję system u, będziesz m ógł p rzero b ić je n a aplikacje
dla S klepu W indows. Je d n a k naw et jeśli po siad asz system W indow s 8, to m ożesz spróbow ać p rzero b ić p rezen to w an e tu
przykładow e aplikacje, n a pro g ram y WPF.

W aplikacji WPF używ asz języka


X A M L do projektowania form uhi^y
w yświetlanych w oknach, a nie na
całym ekranie.

Przyjrzyj się dokładnie oknu ■■■a resz ta ID E wygląda


Toolbox — znajdziesz w nim i działa niemal dokładnie Wię kszo ść kontrolek X A M L j e s t
tak samo. taka sama i ma identyczne
wiele znajomych kontrolelt...
właś ciwości... choć oczyw iście
istn ieją pew ne różnice.

902 Dodatek A
Pozostałości

Czy wiesz, że C# i .N E T Framework potrafią...

★ Dać Ci większą władzę nad danymi, umożliwiając budowanie


zaawansowanych zapytań LINQ?
★ Uzyskać dostęp do stron internetowych i innych zasobów sieciowych za pomocą
wbudowanych klas?
★ Dodać zaawansowane mechanizmy szyfrowania i zabezpieczania programów?
★ Tworzyć złożone, wielowątkowe aplikacje?
★ Instalować i udostępniać klasy tak, aby inni użytkownicy mieli do nich dostęp?
★ Używać wyrażeń regularnych do zaawansowanego wyszukiwania tekstu?
★ I wiele więcej! Będziesz zaskoczony tym, jak ogromne możliwości daje język

NIE M.AŁEM
O TYM POJĘCIA! SKĄD
MOGĘ DOWIEDZIEĆ SIĘ
WIĘCEJ?

Joseph Albahari
bardzo nam pomógł
podczas prac
nad pierwszym
wydaniem tej
książki —
przeprowadził jej
bardzo dokładną
korektę techniczną.
Dziękujemy
za pomoc, Joe!

(U7*5.( )
Istnieje wspaniała ksiqżka, która to wszystko opisuje!
IN A NUTSHELL Jej tytuł to C # 5 .0 in a Nutshell. A u to ram i pozycji są Jo sep h
A lb a h a ri i B en A lbahari. Jest to kom pleksow y p rzew odnik
The Definitive Nefetvnce p o m ożliw ościach C # . Poznasz zaaw ansow ane techniki
teg o języka i zobaczysz wszystkie kluczow e klasy i narzędzia
.N E T F ram ew ork. D ow iesz się, co nap raw d ę dzieje się
Ja są ib A lixih a ri w ew nątrz C # .
O RE ILLY* i - fit’ll AM xiimri
Spraw dź na http://www.oreiHy.com/.

jesteś tutaj ► 903


>F
Skorowidz *
A B lend fo r V isual Studio, 824 dziedziczenie, 16, 275, 285, 286, 287,
abstrakcja, 366, Patrz też: klasa blok 294, 297, 303, 308, 327, 331, 366,
abstrakcyjna, m eto d a abstrakcyjna catch, 614, 617, 618, 620, 622, 625, 608, 655, 671
get, 261, 264, 267, 612 626, 631, 633, 634 interfejsów , Patrz: interfejs
set, 261, 263, 267, 283 finally, 620, 622, 631, 633 dziedziczenie
anim acja, 27, 140, 816 try, 614, 617, 618, 620, 622, 631, w ielo k ro tn e, 364
obiektów , 808 633 dźwięk, 857
w artości using, 632, 633, Patrz też: słowo
obiektów , 808 kluczow e using E
zm iennoprzecinkow ych, 807 błąd, 76, 81, 539, 617 ek ran
aplikacja, 27, 45, 134 in co n sisten t accessibility, 230 dotykowy, 848, 852, 860
arch itek tu ra, 230 kom pilacji, 109, 230 startow y, 89
cykl życia, 552 tytułowy, 89
zarządzanie, 748 C ele m e n t wyliczeniowy, 387
dla S klepu W indow s, 519 C L R , 99, 210, 211, 646, 659, 885 elipsa, 825
konsolow a, 304, 305, 306, 410, cod e-b eh in d, Patrz: ko d ukryty e n u m e ra to r, 413, 681, 893, 894
684, 878 C om m on L ang uag e R u n tim e, Patrz: etykieta, 131
naw igacja trójpoziom ow a, 727 CLR
o luźnych pow iązaniach, 27 F
publikow anie, 90 D factory m eth o d , Patrz: w zorzec m etody
pu sta, 54 dane wytwórczej
Split A pp, Patrz: szablon Split A pp k o n tek st, Patrz: k o n tek st danych finalizator, 646, 647, 648, 649, 650,
stru k tu ra, 52 k o n tra k t, 567, 577 652, 653
tw orzenie, 96, 115, 129, 135, 720 serializacja, Patrz: serializacja fo rm at
uru ch am ian ie n a innym k o n tra k tu danych A S C II, 492
kom p u terze, 91 łączenie, 705 big en d ian , 492
W indow s F orm s, Patrz: W indows m odyfikacja, 681 binarny, 480, 481, Patrz też: system
F orm s A pplication sortow anie, Patrz: lista sortow anie dwójkowy
W indow s P h o n e, Patrz: W indow s szablon, Patrz: szablon danych little en d ian , 492
Phone w iązanie, Patrz: w iązanie danych U n ico d e, 480, 481, 492
w ykorzystująca strony, 686 d a ta binding, Patrz: w iązanie danych U T F - 8, 492
zaw ieszenie, 748, 749 debugger, 92, 111, 112, 397, 405, 411, form ularz, 172, 177, 209, 210, 265, 266,
assem bly, Patrz: złożenie 526, 528, 605, 609, 610, 617, 618, 524, 745
atrybut, 477 751, 761, 767 k o lo r tła, 140
D a ta C o n tra c t, 577 ikony, 609 przycisk
D a taM em b er, 577 krokow e w ykonanie p ro g ram u , 617 m aksym alizacji, 148
D llim port, 646 delegat, 737, 758, 759, 760, 764, 766, m inim alizacji, 148
768, 770, 869, 898 z kon tro lk ą, 219
B deserializacja, 472, 473, 476, 614 zakładka, 281
biblioteka d ia g ra m klas, 149, 164, 166, 320, funkcja zw rotna, 764, 766, 767, 768
.NET, 76 341, 370
fo r W indow s D esk to p , 99 d iam en t śm ierci piekielny, 364 G
fo r W indow s S tore, 99 drzew o obiektów , 752, 753 G A C , 883
klas, 351, 882, 883 dyrektywa, Patrz: słowo kluczow e garbage collection, Patrz: odśm iecanie

jesteś tutaj ► 905


Skorowidz

G D I+ , 896 IN otify P ro p ertyC h an g ed , 556, 557, F ilelO , 570, 572


geocaching, 256 785 F ileO p en P ick er, 572
G lobal A ssem bly C ache, Patrz: G A C instancja, 338, 354 FileSavePicker, 572
G o T o D efinition, 461 IS em an ticZ o o m In fo rm atio n , 713 F ileS tream , 492
G PS, 256 IS to rag eF ile, 578, 579 herm etyczna, 258, 354, 493, 548
gra Invaders, Patrz: Invaders IS to rag eF o ld er, 578, 579, 580 hierarch ia, 287, 292, 309, 345
graf, 475, 476, 524, 527, 552, 576, 581, IS to rag eItem , 579 instancja, 152, 552, 671
755 IV alu eC o n v erter, 798 pole, Patrz: pole
G raphical U ser In terface, Patrz: G U I nazw a, 333 tw orzenie, 154
G U I, 52, 152, 228 publiczny, 333 Item sC o n tro l, 821
referen cja, 338, 339, 354 K now nF olders, 580
H rozszerzający, 671 List, 394
heisenbug, 612 rzutow anie, 347 M ath, 117
herm etyzacja, 15, 235, 247, 249, 250, użytkow nika, 45, 787, 790, 814, 886 M en u M ak er, 546, 547, 553, 556
252, 255, 256, 257, 259, 260, 261, graficzny, Patrz: G U I M essageB ox, 133, 136, 570
263, 267, 354, 366, 374, 587, 668, 786 w budow any w .NET, 333 m odelu, 797, 897
autom atyczna, 263 właściwość, 340 m od elu w idoku, 797
hex dum p, Patrz: w idok szesnastkow y Invaders, 29, 836 nadpisyw anie, 823
hierarch ia klas, Patrz: klasa h ierarch ia iteracja, 119, 121, 212, 398, 682, 685, n ad rzęd n a, Patrz: klasa bazow a
Hyper-V, 861 695, 705, 708, 845, 878, 893 nazw a, 162
O bject, 288, 411
I J O bservableC ollection, 543, 820
indeksator, 894 j ęzyk p o ch o d n a, Patrz: klasa p o to m n a
instrukcja, 11, 123, 267, 631, Patrz też: X A M L , Patrz: X A M L p o to m n a, 286, 290, 293, 299, 351
słowo kluczowe X M L , Patrz: X M L przesłan ian ie, 290, 298
blok, 104 public, 107, Patrz też: słowo
b reak, 469, 470 K kluczow e public
if/else, 114, 118, 189, 210, 279, 468 kalk u lato r, 183 rozszerzanie, 293
new, Patrz: słowo kluczow e new k a n a ł R SS, 901 S crollableC ontrol, 527
retu rn , 103, 146, 147, 469 k arta , 100 , 281 S elector, 821
skoku, 878 klasa, 11, 17, 102, 103, 107, 123, 135, S erializationE xception, 618
switch, 469, 470 272, 549, 668 S ettingsPage, 770
szablon, Patrz: szablon instrukcji abstrakcyjna, 356, 358 składow e, 351
throw , 626, 631 atrybut, Patrz: atry b u t zasięg, 352
using, Patrz: słowo kluczow e using bazowa, 286, 289, 299, 310, 345, 655 statyczna, 456, 670, 770
w arunkow a, 880, 897 B in ary F o rm atter, 476, 567, 614 S tream , 443
yield re tu rn , 894 B in ary R ead er, 484 S tream R ead er, 449, 457
IntelliSense, Patrz: okn o IntelliS ense B inaryW riter, 483 S tream W riter, 445, 446
interfejs, 17, 329, 332, 333, 334, 336, B inding, 543 stru k tu ra, 164
348, 354, 356 C o n tain erC o n tro l, 527 strum ieni .NET, 19
dziedziczenie, 341 C ontrol, 524 S ystem .W indow s.Form , 527
IC ollection, 893 diagram , Patrz: diagram klas T ask, 587
IC o m p arab le, 405 D irectory, 456 T extB lock, 528, 533
IC o m p arer, 406, 407, 408, 409 D o u b leA n im atio n , 807 tw orzenie, 176
ID isposable, 461, 462, 632, 633, 648 E ventA rgs, 734 typ, 687
IE n u m erab le, 413, 414, 431, 435, E xception, 608, 631 T ype, 889
438, 681, 682, 694, 893 Excuse, 493, 589 U IE le m e n t, 823
IE q u ata b le, 890 File, 456, 492, 570 U serC o n tro l, 781, 786
im plem entacja, 334, 348, 401 F ileInfo, 456, 492 w budow ana, Patrz: zestaw

906 Dodatek
Skorowidz

w idoku, 797, 820 C om boB ox, 321, 371, 375, 423, 543 L IN Q , 25, 680, 681, 685, 694, 695, 698,
W indow s.Storage, 570 C o n te n tC o n tro l, 65 701, 845, 898
zagnieżdżanie, 402, 632, 889 dodaw anie, 62, 63 L IN Q to X M L , 900, 901
klaw iatura, 848, 852 G rid , 52, 65, 528, 532, 535, 536, L IN Q P ad , 718
klucz, 702, 707 538, 544, 545, 555, 753, 814 Linux, 885
R ejestru system ow ego, 883 G ridV iew , 713, 821 lista, 393, 394, 395, 395, 396, 397, 398,
klucz-w artość, 413 in terfejsu użytkow nika, 814 401, 422, 439
kod k o n tek st danych, 542 A rrayL ist, 401
IL , 885 k o n te n era , 59 niegeneryczna, Patrz: lista
maszynowy, 885 kopiow anie, 281 A rrayL ist
ukryty, 543 ListBox, 216, 543, 821 sortow anie, 404, 408, 681
znacznikowy, 543 ListView, 692, 713, 777, 821 stała, 401
źródłowy, 885 niew izualna, Patrz: ko m p o n en t wyliczeniowa, 387
kolejka, 435, 436 N u m ericU p D o w n , 148 literał, 183, 189, 211, 240, 241, 242,
kolekcja, 18, 543, 556, 680, 684, 694 N u m ericU p D o w n , 281 243, 889
C hildren, 545, 814, 817, 820, 821 O pen F ileD ialo g , 454
generyczna, 398, 401, 435 PictureB ox, 138, 228, 230, 232, Ł
inicjalizator, 402 289, 498 ładow anie b ezp o śred n ie, 91
ObservableCollections, 776, 787, 823 Polygon, 825 łańcuch
kolizja, 840, 845 P o p u p , 838, 850 strum ieni, 450
kolor, 140 P rogressB ar, 52, 66, 692, 886, Patrz znaków , 110, 147, 205, 243, 386,
k om entarz, 102 , 11 1 też: p asek p o stęp u 411, 457, 483, 549
kom pilator, 98, 269, 682, 885 ScrollV iew er, 532, 533, 535, 542, dzielenie n a fragm enty, 471
k o m p o n en t, Patrz: ko n tro lk a 543, 544, 572 k o n k aten acja, 188, 411
k onkatenacja, Patrz: łańcuch znaków S tackP anel, 52, 65, 539, 555, 753, konw ersja n a tablicę bajtów , 457
k onkatenacja 777, 814
konsola, 262 S tatusS trip, 216 M
k o n stru k to r, 265, 266, 310, 386, 619 szablon, Patrz: szablon k o n tro lek M acin to sh , 885
bezargum entow y, 267, 553, 817 T extB lock, 63, 65, 67, 117, 542, m an ag e d resources, Patrz: zasoby
klasy 543, 544, 545 zarządzane
bazow ej, 311 TextB ox, 148, 281, 459, 532, 542, m anifest
p o to m n ej, 311 543, 547, 551 aplikacji, 53
przeciążony, 471, 817 T im er, 215, 216, 231 p ak ietu , 580, 586
S tream W riter, 445 ToggleSw itch, 753 m aszyna w irtualna, 99, 210
k o n tek st danych, 542, 547, 550, 551, tw orzenie, 781 m eto d a, 11, 73, 102, 103, 211, 267, 351
552, 553 użytkow nika, 781, 786, 817 abstrakcyjna, 356, 359
k o n tra k t danych, Patrz: d an e k o n tra k t z zaw artością, 544 anonim ow a, 768, 898, 899
serializacja, Patrz: serializacja zagnieżdżanie, 545 A ppen d A llT ex t, 456
k o n trak tu danych zoom sem antyczny, 712, 714 A p p en d L in es, 570
k o ntrolka, 52, 66, 96, 99, 115, 132, 219, ko n w erter, 27, 773, 798, 800 A ppendL inesA sync, 570
454, 530 B o o lean N o tC o n v erter, 800 A p p licatio n .D o E v en ts, 140, 377,
A n im ated lm ag e, 825, 849 B ooleanV isibilityC onverter, 800 382, 886,
A ppB ar, 572 w artości, 798 arg u m en t, 188, Patrz też: m eto d a
B ackgroundW ork er, 886 kow ariancja, 414 p a ra m e tr
B o rd er, 544, 572, 753 nazw any, 664
B u tto n , 532 L przekazyw any p rzez referen cję,
C anvas, 63, 65, 66, 85, 545, 814, library assem bly, Patrz: złożenie 656, 657, 660, 661, 662, 663
818, 826 biblioteczne przekazyw any p rzez w artość, 656,
C heckBox, 125, 281 licznik czasu, 53, 80, 82 657, 660, 661, 662, 663

jesteś tutaj ► 907


Skorowidz

m etoda wyjściowy, 662 M icrosoft D ev elo p er N etw ork, Patrz:


A rray.R esize, 392 pom ocnicza, 822 M SD N
asynchroniczna, 22, 565, 578, 587, pryw atna, 251, 252, 844, 845 m odel, 777, 786, 788, 792
592 przeciążona, 389 w idoku, 777, 786, 787, 788, 809,
Close, 445, 457, 492 tw orzenie, 415 846, 851
C olor.F rom A rgb, 140 p rzesłan ian ie, 290, 298, 306, 309, m odyfikator, 662
C om pare, 406 310, 331, 411, 892 async, 568
C om pareT o, 405 publiczna, 252, 259 dostęp u , 351, 352, 884
C o n sole.E rror.W riteL in e, 490 nazw a, 269 in tern al, 852, 884
C onsole.R eadK ey, 409, 410 R ead , 443 o u t, 662
C onsole.W riteL ine, 262, 409, 411, R eadA llB ytes, 481, 492 public, 884
412, 431, 662 R eadA llT ext, 459, 481, 492 ref, 663
C reateD irectory, 456 R eadB lock, 489 sealed, 670
definicja, 461 R eadExcuseA sync, 595, 618 M ono, 885
deklaracja, 147 R ead T ex t, 570 M S D N , 90
D elete , 456 R eadT extA sync, 570 M V C , 787
D eserialize, 476 R em oveA t, 398 M V V M , 27, 773, 777, 787, 790, 797,
D ispose, 461, 462, 633, 648, 650, rozszerzająca, 670, 671, 672, 681 822, 838
651, 652 S aveC urrentE xcuseA sync, 595
E num .P arse, 472 SaveFile, 575 N
E quals, 890 Seek, 443 n otacja
Exists, 456 Serialize, 476 Pascal, 260, 269
F ile.C reate, 483, 485 S etL eft, 826 w ielbłądzia, 260, 269
F ile.O p en W rite, 483, 485 S etT arg et, 817
G C .C ollect, 646, 653 S etT arg e tP ro p erty , 817 O
G e tE n u m e ra to r, 413, 681, 893 ShowAsync, 615 o biekt, 15, 26, 143, 150, 158, 195, 197,
G etF iles, 456 Show D ialog, 453, 454, 455 198, 210, 549, 639, 655, 659
G etH ash C o d e, 890 Sleep, 140, 142, 289, 377, 612 B u tto n , 526
G etL astA ccessT im e, 456 S ort, 405, 407, 408 definicja, 461
G etL astW riteT im e, 456 Split, 471 E xception, 604, 608, 612, 625, 656
G etL eft, 826 staty czn a, 157, 159, 249, 303, F ileS tream , 444, 445, 450, 456
G etT ype, 889 670 F ram e, 687
In itializeC o m p o n en t, 528 S tream .R ead , 490, 492 graf, Patrz: graf
IsN ullO rE m pty, 320, 351 S trin g .F orm at, 411 inicjalizator, 175, 177, 264, 691
L ist.Sort, 405, 407 sygnatura, 267, 735 O pen F ileD ialo g , 455, 457
M ain, 135, 139, 304 szkielet, 73, 74 Page, 528
M essageB ox.Show , 137, 189, 431 T oA rray, 401 porów nyw anie, 404, 408
M essageB ox.Show , 137 T oL ist, 843 SaveFileD ialog, 455
M oveN ext, 413, 894 T oS tring, 188, 411, 412 stan, 474
N avigate, 687 T ryParse, 663 S tream , Patrz: strum ień
nazw a, 81, 162, 269, 587, 897 ukryw anie, 306, 307, 309 S tream R ead er, 489, 492
O bject.R eferenceE q u als, 890, 892 w artość zw racana, 103, 146 S tream W riter, 483, 492
obsługująca zdarzenie, 735 w irtualna, 303 tw orzenie, 150, 151, 160, 175, 242
O nL au n ch ed , 748, 751 W rite, 443, 445, 457 usuw anie, 461, 646, 647, 648
O penF ile, 575 W riteA llB ytes, 492 o dśm iecanie, 198, 201, 210, 211, 646,
p a ram etr, 103, 189, 207, 220, Patrz W riteA llT ext, 459, 492 647, 650, 653
też: m eto d a argum en t W riteE xcuseA sync, 595 okno
logiczny, 241 W riteL ine, 445, 457 D esigner, 54, 123, 536, 537, 552, 872
opcjonalny, 664 w ytwórcza, 822, 856 D evice, 537

908 Dodatek
Skorowidz

dialogow e, 453, 454 narzędzi tekstow e, 172, 173


tw orzenie, 457 D ebug, 609 tw orzenie, 159, 267
D o cu m en t O utlin e, 66, 67, 84, 572 D eb u g L ocation, 751 w ew nętrzne, 261, 372
E rro r List, 76, 82, 101, 104, 264 p o stęp u , 66 w yboru, 125, 131, 246, 247
IntelliS ense, 74, 76, 86, 101, 255, przew ijania, 692 polim orfizm , 366, 367, 393, 655, 669
265, 389, 553 p ętla, 113 pow iązanie dw ukierunkow e, 543, 547
MessageBox, 148, 160, 171, 187, 648 for, 121 p ro c e d u ra obsługi zdarzeń, 84, 132,
M essageD ialog, 768 foreach, 397, 398, 411, 435, 843, 262, 589, 731, 735, 746, 750, 848
O aplikacji, 850 893, 894 łańcuch, 737, 746
O u tp u t, 262 nieskończona, 121, 140, 141 nazw a, 735
P ro p erties, 64, 218, 572 w hile, 113, 119, 123, 141, 279 P o in te rE n te re d , 85, 86
R eferen ce M anager, 883 piksel, 537 P o in terP ressed , 85
S olution E xplorer, 54, 56, 100, 324, p la tfo rm a .N ET, 1 1 , 99 stan d ard o w a, 737
351, 778, 882, 884 plik, 415, 441, 442, 443, 444, 445, 446, tw orzenie, 738
S olution M anager, 303 449, 450, 456, 457, 459, 570, 579 process assem bly, Patrz: złożenie
T oolbox, 125, 767 .cs, 53, 544 wykonywalne
W atch, 1 1 2 , 527, 528, 555, 611, .D esigner.cs, 324 p ro g ram , 98
612, 617, 648 .D L L , 882, 884 B lend for V isual Studio, Patrz:
$exception, 604 .E X E , 53, 882, 884 B lend fo r V isual Studio
this, 527 .png, 53 debugow anie, Patrz: debugger
w iersza poleceń, 306, 885 .resx, 324 L IN Q P ad , Patrz: L IN Q P ad
OOP, Patrz: program o w an ie obiektow e A pp.xam l.cs, 46 o dporny, 614, 666
opakow yw anie, 667, 668 binarny, 480, 481, 482, 484, 485, zatrzym yw anie, 77, 81
o p e rato r, Patrz też: znak 486, 487, 576 p rogram ow anie
-- , 110 Form 1.cs, 303 obiektow e, 366
!, 110 M ainPage.xam l, 46, 54, 55, 56, 75, specyfikacja, 424
! = , 118, 892 96, 107, 687 p ro sto k ąt, 825
& & , 118, 141 M ainPage.X am l.cs, 46, 75, 96, 107, przeciążanie, 389
* = , 110 529, 550 p rzestrzeń nazw, 11, 102, 107, 123,
| | , 118 P ackage.m anifest, 580 135, 324, 415, 552, 586, 672, 797, 882
+ , 110, 187, 188, 411, 457 P rogram .cs, 134 System , 117, 123, 476
+ + , 110 P rogram .cs, 134, 303, 304 S ystem .C om ponentM odel, 557
+ = , 110 SplitPage.xam l, 721 S Y S T E M .IO , 566
= , 114, 123, 188 StandardStyles.xam l, 573 System .L inq, 123
-=,194 system , Patrz: system plików S y stem .R untim e.S erialization, 618
= = , 114, 118, 123, 890, 892 ścieżka dostęp u , 570 using, 552
aw ait, 568, 587, 615 tekstow y, 431 W in dow s.F oundation, 827
lam bda, 899 wykonywalny, 98 W indow s.Storage, 579, 580
logiczny, 85, 118, 880 X M L , 576, 577, 581 W indow s.U I.A pplicationSettings,
new , 157 zam ykanie, 444 770
przeciążanie, 892 p odklasa, Patrz: klasa p o to m n a W indow s.U I.X am l, 823, 829
przypisania, Patrz: o p e ra to r = = p o le, 158, 211, 351, 354 W indow s.U I.X am l.C ontrols, 687
w arunkow y, 118 nazw a, 897 W indow s.U I.X am l.D ata, 798
pryw atne, 249, 251, 255, 260, 779, p rzetw arzan ie skrócone, 879, 880
P 851 przycisk, 117, 125, 131, 172, 177, 777
pam ięci oczyszczanie, Patrz: inicjalizacja, 265 m aksym alizacji fo rm ularza, 148
odśm iecanie nazw a, 269 m inim alizacji form ularza, 148
p asek p rzesłan ian ie, 266, 273 przycisk U n d o , 123
aplikacji, 591, 802 publiczne, 264, 542 p u łap k a, 610, 611, 612, 756

jesteś tutaj ► 909


Skorowidz

punkt sideloading, Patrz: ładow anie typeof, 687


przerw ania, 111, 527 bezp o śred n ie using, 76, 102, 135, 415, 462, 631,
w ejścia, 134, 135, 136, 139, 303 Sklep W indow s, 90, 96, 129, 517, 519, 633, 648, 829
528, 530, 536, 546, 552, 567, 568, var, 211, 682, 708
Q 570, 578, 579, 580, 684, 686, 748, virtual, 298, 304, 308, 309
Q ueue, Patrz: kolejka 770, 874 splash screen, Patrz: ek ran tytułowy
słownik, 421, 422, 423 sprajt, 823
R długość, 413 sp rite, Patrz: sprajt
refaktoryzacja, 896 elem en t, 413 S Q L, 685
referencja, 196, 197, 198, 204, p o b ran ie listy kluczy, 413 S tack, Patrz: stos
207, 209, 211, 347, 354, 371, 647, values, 685 stała, 240, 242
655, 656, 669, Patrz też: zm ienna w yszukiwanie n a podstaw ie klucza, E nvironm ent.N ew L ine, 431
referencyjna 413 stan, 787, 790, 792
do klasy p o chodnej, 302 słowo kluczow e, 190, 211, 221 ste rta , 160, 196, 659, 668
do kontrolki, 219 abstract, 359 stos, 435, 437, 659, 661, 668
do sam ego siebie, 209 as, 343, 346, 347, 669 wywołań, 612
null, 392, 665 await, Patrz: o p e ra to r aw ait stro n a
o b iek tu w yw ołującego zd arzen ie, base, 310 Basic Page, 55, 818, 846
737 b reak, 878 szablon, 55, 846
w ielokrotna, 199 by, 707 głów na, Patrz: Basic Page
rekurencja, 661 case, 469, 470 nagłów ek, 65
relacja, 294 continue, 878 naw igacja, 686, 687
R em o te D ebugger, 91 event, 734 u kład, 71, 534
rodzic, 288, Patrz też: klasa bazow a E v en tH an d ler, 734, 737, 744 stru k tu ra, 655, 656, 657, 658, 668, 669,
ro u te d events, Patrz: zdarzenie group, 707 890
trasow ane interface, 333 N ullable, 665
rzutow anie, 388, 476, 892 in tern al, 351 P oint, 827
w dół, 346, 347, 348, 367, 371 is, 340, 343, 347, 668 R ect, 827
w górę, 345, 347, 348, 367, 414, 734 join, 705, 707, 708 Size, 827
new, 150, 157, 307, 311, 338, 402, stru m ień , 442, 443, 450, 476, 632, 656
S 691 plików, 450, 456, 487
sekw encja, 694 null, 210, 392, 665 sieciowy, 443, 450
form atująca, 117 object, 660 S tream R ead er, 449
sem antic zoom , Patrz: k o n tro lk a zoom on ... equals, 707 S tream W riter, 446, 457
sem antyczny o verride, 298, 304, 307, 308, 309, zam ykanie, 462
separacja zagadnień, 272, 316, 589, 786 736, 752 system
serializacja, 472, 473, 474, 475, 476, p artial, 107, 123 dwójkowy, 19, 480, Patrz też:
485, 545, 567, 576, 581, 650, 651 p rivate, 258, 351, 353, 354, 768 fo rm at binarny
klasy, 476, 577 p ro te cte d , 351, 352, 353, 354 Linux, 885
k o n trak tu danych, 576, 578, 581, public, 228, 230, 258, 351, 354, plików, 462, 578
582 Patrz też: klasa public siatki, Patrz: siatk a system
w yjątek, 614 readonly, 823 szesnastkow y, 19, 480, 487, 488,
siatka, 55, 58, 534, 777 ref, 663 Patrz też: w idok szesnastkow y
kolum na, 59, 60, 115, 534 sealed, 351 szablon
534, 537 select new , 707 Basic Page, 55, 846
system , 536, 537 static, 157, 159 danych, 554
w iersz, 58, 115 struct, 655 G rid A p p , 727
wysokość, 60, 535, 537 this, 209, 211, 266, 269, 352, 527, instrukcji, 113
wymiary, 60 653, 670, 737 Item s Page, 727

910 Dodatek
Skorowidz

k o n trolek, 27, 67, 88, 773 U C o n ten t, 544, 545


Split A p p, 720, 723 u se r con trol, Patrz: ko n tro lk a C ontrols, 524
Split Page, 727 użytkow nika docelow a, 542, 543
dołączona, 820
T V dom yślna, 545
tablica, 205, 207, 391, 395, 396, 401, V isual D esig n er, 45 e .H an d led , 757
681 V isual S tu d io E xpress, 38, 49 F ileN am e, 455
args, 490 V isual S tu d io fo r W indow s D esktop, F ilter, 454, 455, 459
bajtów , 482 Patrz: W indow s D esk to p F low D irection, 459
długość, 206, 392 V isual S tu d io fo r W indow s P hone, In itialD irecto ry , 459
m eto d w irtualnych, 303 Patrz: W indow s P hone Item sS o u rce, 821
zm iennych referencyjnych, 206, V isual S tu d io ID E , 44, 46, 47, 48, 73, nazw a, 897
318, 338 90, 96, 97, 98, 100, 104, 175, 527, przesłan ian ie, 354
znaków , 480 687, 897 publiczna, 269
typ, 211, 682, 889 Visual Studio Integrated D evelopm ent sygnatura, 267
anonim ow y, 691, 708, 898, 899 Environm ent, Patrz: Visual Studio ID E T ext, 545
bool, 182, 184, 656 V isual S tu d io R e m o te D ebugger, T itle, 459
byte, 182, 211, 388 Patrz: R em o te D eb u g g er x:Key, 555
char, 183, 184, 481 v table, Patrz: tablica m e to d w irtualnych x:N am e, 79, 547, 552, 555
D ateT im e, 546, 548, 657, 890 xmlns:, 552
decim al, 183, 184, 186, 243, 244, W local, 552
656 w arstw a źródłow a, 542, 543
double, 182, 183, 187, 663, 890 m odelu, 790 W PF, 38, 53, 55, 902
enum , Patrz: typ wyliczeniowy w idoku, 868 w yjątek, 23, 599, 604, 605, 607, 608,
E v en tH an d ler, Patrz: słowo w idoku, 790, 797 616, 620, 622, 626, 634, 635, 653, 843
kluczow e E v en tH a n d ler w ątek, 888 F o rm atex cep tio n , 663
float, 183, 184, 187 w iązanie danych, 542, 543, 546, 551, In d ex O u tO fR an g eE x cep tio n , 604
generyczny, 398 552, 554, 557, 574, 721, 776, 820 In v alid O p eratio n E x cep tio n , 888
int, 182, 186, 388, 411, 656, 890 dw ukierunkow e, 543, 547 nieobsłużony, 612, 622, 625
long, 182, 211, 388 w idok, 777, 786, 809 N o tIm p lem en ted E x cep tio n , 746,
N u lla b le < D a te T im e > , 665 szesnastkow y, 487 798
object, 183 w ielokąt, 825 N u llR eferen ceE x cep tio n , 767
referencyjny, 657, 660, 668 w iersz p o lece ń , 306, 885 S erializationE xception, 614, 620
rzutow anie, 186, 187, 188 W indow s 8 C am p T rain in g K it, 874 w yrażenie lam bda, 768, 898, 899
sbyte, 182 W indow s A p p C ertification K it, 90 wywołanie A p p licatio n .D o E v en ts, 140
short, 182 W indow s D esk to p , 129, 141, 882 w zorzec, 153, 154
string, 182, 184, 188, 481 W indow s F o rm s A p plication, 99, 130, m eto d y wytwórczej, 822
rozszerzanie, 672 182, 262, 303, 304, 524, 545 M odel-V iew -ViewM odel, Patrz:
u in t, 182 W indow s P h o n e, 29, 859, 861 MVVM
ulong, 182 W indow s P re se n ta tio n F o u n d atio n , m odel-w idok-kontroler, Patrz: M V C
u sh o rt, 182 Patrz: W P F o b serw ato ra, 768
void, 146, 737 W indow s R u n tim e, 99 projektow y, 768
w artościow y, 211, 401, 411, 656, W inF orm , Patrz: W indow s Form s
657, 658, 660, 668 A p plication X
ze znakiem ?, 665 właściwość, 64, 96, 261, 267, 333, 340, X A M L , 21, 45, 49, 53, 517, 528, 538
wyliczeniowy, 387, 388, 390, 401, 351, 354 ko d znacznikowy, Patrz: kod
411, 431, 702 autom atyczna, 263, 372 znacznikowy
wynikowy, 146, 146, 265, 266 tylko do odczytu, 263, 264 siatka, Patrz: siatka
B ack g ro u n d im ag e, 498 X M L , 49, 900

jesteś tutaj ► 911


Skorowidz

Z p ro p ag acja, 752, 757 typ, 108, 117, 682


zablokow anie, 568 P ro p erty C h an g ed , 556, 797 w artość, 109, 112
zapytanie, 681, 682, 683, 684, 705, 900 pryw atne, 768 znacznik, 49
edycja, 718 publiczne, 734, 763 kolejności bajtów , 492
from , 685, 698 SizeC hanged, 824, 833 k o n te n e ra , 49
orderby, 685, 698 subskrypcja, 731, 733, 737, 763 otw ierający, 49
select, 698, 701 sygnatura, 737 V isualS tate, 806
w here, 685, 698 Tick, 220 zamykający, 49
zasoby trasow ane, 752, 753, 755, 757 znak, Patrz też: o p e ra to r
alokacja, 461 typ, 744 ‘, 183
niezarządzane, 646 zarządzające cyklem życia procesu, *, 534, 537
sta ty c z n e , 552, 553, 555, 573, 748 */, 111
716, 803 zegara, 800, 843, 852, 854 /, 535
zarządzane, 646 zgłoszenie, 556 /*, 111
zw alnianie, 461 zestaw , Patrz: złożenie //, 102
zdarzenie, 218, 745, 768, 792 złożenie, 351, 882, 883 :, 294, 334, 337, 348
bez p ro c e d u r obsługi, 736 biblioteczne, 882 ?, 665
Click, 746, 793 wykonywalne, 882 ? :, 794
C ollectionC hanged, 797 zm ienna, 108, 140, 172, 174, 184, 186, @, 446, 457
C om p leted, 867 195, 211 [ ], 104, 894
dotknięcia ek ran u , 848 definicja, 461 \\, 457
funkcja zw rotna, Patrz: funkcja logiczna, 110, 113 \n, 117, 183, 431, 457
zw rotna lokalna, 659 \r\n, 431, 457
generow anie, 731, 732 nazw a, 108, 113, 117, 269, 897 \t, 183, 457
kiw nięcia, 848 p rivate, Patrz: słowo kluczow e _, 779
klaw iatury, 848 p rivate { }, 113, 123
nadaw ca, 758 p ro te cte d , Patrz: słowo kluczow e + = , 735, 737, 738, 739, 766, 767
nasłuchiw anie, 731, 732 p ro te c te d apostrof, Patrz: znak ‘
N avigatedFrom , 750 p u b lic, 251, 252, Patrz też: klasa dw ukropek, Patrz: znak :
obsługa, Patrz: p ro c e d u ra obsługi p u b lic , słow o kluczow e p u b lic U nico d e, 480, 481, 492
zdarzeń referencyjna, 196, 205, 307, 338,
odbiorca, 758 401, 474, Patrz też: referen cja
p a ram etr, Patrz: delegat grupa, 206

912 Dodatek

You might also like